feat(FN-3967): document executorRuntimeEnv plugin hook

Adds `docs/PLUGIN_AUTHORING.md` documenting the `executorRuntimeEnv` hook with cross-references to the architecture docs, accompanied by tests validating the documentation contract.

Fusion-Task-Id: FN-3967
This commit is contained in:
Fusion
2026-05-11 01:10:43 -07:00
committed by gsxdsm
parent c02aadec19
commit f6af38318b
3 changed files with 98 additions and 1 deletions

View File

@@ -343,6 +343,7 @@ const plugin: FusionPlugin = {
| `onTaskCompleted` | `(task: Task, ctx: PluginContext) => Promise<void> \| void` | Task reached "done" |
| `onError` | `(error: Error, ctx: PluginContext) => Promise<void> \| void` | Error occurred in plugin execution |
| `onSchemaInit` | `(db: Database) => Promise<void> \| void` | After enabled plugins are loaded at startup (engine/daemon/dashboard/serve) |
| `executorRuntimeEnv` | `(taskCtx: ExecutorRuntimeTaskContext, ctx: PluginContext) => Promise<ExecutorRuntimeEnvContribution> \| ExecutorRuntimeEnvContribution` | Before executor-spawned task commands run, to contribute task-scoped env and PATH prepends |
### Hook Behavior
@@ -373,6 +374,85 @@ hooks: {
},
```
### `executorRuntimeEnv`: task-scoped executor subprocess environment
Use `executorRuntimeEnv` when your plugin needs to provide runtime environment values for **executor-spawned user commands** tied to a specific task.
This hook runs when Fusion prepares subprocess environments for executor command surfaces such as configured commands, verification commands, and step-session subprocesses.
It does **not** apply to internal git plumbing subprocesses used by worktree/branch management.
```typescript
import type {
ExecutorRuntimeEnvContribution,
ExecutorRuntimeTaskContext,
PluginContext,
} from "@fusion/plugin-sdk";
function executorRuntimeEnv(
taskCtx: ExecutorRuntimeTaskContext,
ctx: PluginContext,
): ExecutorRuntimeEnvContribution {
ctx.logger.info(`Preparing env for task ${taskCtx.taskId}`);
return {
pathPrepend: ["/absolute/path/to/tools"],
env: {
MY_PLUGIN_TASK_ID: taskCtx.taskId,
},
};
}
```
`ExecutorRuntimeTaskContext` fields:
- `taskId`: Fusion task ID
- `worktreePath`: absolute path to the task worktree
- `rootDir`: project root directory
- `branch?`: task branch name when available
`ExecutorRuntimeEnvContribution` fields:
- `pathPrepend?`: array of **absolute** path strings prepended to `PATH` for executor-spawned commands
- `env?`: key/value string map merged into the subprocess environment
- `description?`: optional human-readable note for debugging/telemetry
Validation and merge behavior (from engine runtime collection):
- `pathPrepend` must be an array of absolute path strings; non-absolute entries are rejected.
- `env` values must be strings.
- `env` must not include `PATH`; use `pathPrepend` instead.
- When multiple plugins set the same `env` key, later plugins override earlier values and the engine logs a warning.
- `pathPrepend` entries from later plugins are placed earlier in the final prepend list.
### Example: prepend a generated tool directory for executor commands
```typescript
import { definePlugin } from "@fusion/plugin-sdk";
import path from "node:path";
export default definePlugin({
manifest: {
id: "fusion-plugin-tooling-example",
name: "Tooling Example",
version: "1.0.0",
},
state: "installed",
hooks: {},
executorRuntimeEnv: (taskCtx) => {
const toolDir = path.resolve(taskCtx.rootDir, ".fusion/tools/my-plugin/bin");
return {
pathPrepend: [toolDir],
env: {
MY_PLUGIN_TOOL_HOME: toolDir,
},
};
},
});
```
With this hook enabled, executor-spawned commands (for example verification commands or `bash` tool subprocesses in the task session) can resolve binaries from `toolDir` without mutating global process environment.
### Example: Notification on Task Completion
```typescript

View File

@@ -357,7 +357,7 @@ Hybrid evaluator pipeline (FN-3389/FN-3391):
- `TaskStore.getPluginStore()` now propagates the configured `globalSettingsDir`/central directory so all CLI and dashboard install paths resolve the same central DB
- `PluginLoader` (`plugin-loader.ts`) loads/unloads plugin modules using the effective per-project plugin state
- Plugin contributions now include both embedded `uiSlots` and top-level `dashboardViews`
- Executor runtime contributions can be provided via `executorRuntimeEnv(taskCtx, ctx)`; the engine aggregates plugin-provided `pathPrepend` + `env` overlays per task and applies them only to executor-spawned user commands (configured commands, verification commands, step-session subprocesses), never to git plumbing subprocesses.
- Executor runtime contributions can be provided via `executorRuntimeEnv(taskCtx, ctx)`; see the canonical plugin-authoring contract in [`docs/PLUGIN_AUTHORING.md` §4 "`executorRuntimeEnv`: task-scoped executor subprocess environment"](./PLUGIN_AUTHORING.md#executorruntimeenv-task-scoped-executor-subprocess-environment). The engine applies these task-scoped overlays only to executor-spawned user commands, never to git plumbing subprocesses.
- Discovery endpoints:
- `GET /api/plugins/ui-slots`
- `GET /api/plugins/dashboard-views`

View File

@@ -85,3 +85,20 @@ test("PLUGIN_AUTHORING TOC includes top-level dashboard views and anchors align
assert.ok(topLevelEntry, "TOC should include section 8");
assert.equal(topLevelEntry.title, "Registering Top-Level Dashboard Views");
});
test("PLUGIN_AUTHORING documents executorRuntimeEnv hook signature in hook reference", () => {
assert.match(
doc,
/\| `executorRuntimeEnv` \| `\(taskCtx: ExecutorRuntimeTaskContext, ctx: PluginContext\) => Promise<ExecutorRuntimeEnvContribution> \\| ExecutorRuntimeEnvContribution` \|/,
);
});
test("PLUGIN_AUTHORING documents executorRuntimeEnv runtime env contract and PATH injection example", () => {
assert.match(doc, /### `executorRuntimeEnv`: task-scoped executor subprocess environment/);
assert.match(doc, /does \*\*not\*\* apply to internal git plumbing subprocesses/);
assert.match(doc, /pathPrepend` must be an array of absolute path strings/);
assert.match(doc, /must not include `PATH`; use `pathPrepend` instead/);
assert.match(doc, /later plugins override earlier values and the engine logs a warning/);
assert.match(doc, /later plugins are placed earlier in the final prepend list/);
assert.match(doc, /pathPrepend: \[toolDir\]/);
});