@wichayutdew/pi-workflows 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -39
- package/dist/index.js +5754 -4794
- package/package.json +1 -1
- package/src/command-names.ts +7 -1
- package/src/commands.ts +126 -52
- package/src/config/catalog.ts +162 -0
- package/src/config/ceiling.ts +116 -116
- package/src/config/command-conflicts.ts +4 -4
- package/src/config/diagnostics.ts +28 -0
- package/src/config/load-settings.ts +37 -0
- package/src/config/load-types.ts +53 -0
- package/src/config/load-workflows.ts +135 -0
- package/src/config/load.ts +43 -311
- package/src/config/types.ts +137 -135
- package/src/config/validate.ts +12 -1261
- package/src/config/validation/permissions.ts +291 -0
- package/src/config/validation/prompt.ts +19 -0
- package/src/config/validation/settings.ts +122 -0
- package/src/config/validation/shared.ts +129 -0
- package/src/config/validation/shortcut.ts +118 -0
- package/src/config/validation/step.ts +228 -0
- package/src/config/validation/subagent.ts +288 -0
- package/src/config/validation/workflow.ts +156 -0
- package/src/config/yaml.ts +29 -0
- package/src/digest.ts +38 -6
- package/src/engine/checkpoint.ts +13 -9
- package/src/engine/create-run.ts +42 -0
- package/src/engine/gate-transitions.ts +184 -0
- package/src/engine/reconciliation-history.ts +88 -0
- package/src/engine/resume.ts +19 -7
- package/src/engine/run-advance.ts +122 -0
- package/src/engine/run-lifecycle.ts +124 -0
- package/src/engine/run-reconciliation.ts +116 -0
- package/src/engine/run-validation.ts +128 -0
- package/src/engine/state-types.ts +64 -0
- package/src/engine/state.ts +10 -193
- package/src/engine/transition-helpers.ts +28 -0
- package/src/engine/transition-types.ts +8 -0
- package/src/engine/transitions.ts +17 -471
- package/src/harness/action-context.ts +181 -0
- package/src/harness/catalog.ts +45 -0
- package/src/harness/context-idle.ts +21 -0
- package/src/harness/core-actions.ts +299 -0
- package/src/harness/delegation-control-actions.ts +250 -0
- package/src/harness/delegation-failure.ts +215 -0
- package/src/harness/delegation-plan.ts +219 -0
- package/src/harness/delegation-recovery-validation.ts +159 -0
- package/src/harness/delegation-response-actions.ts +295 -0
- package/src/harness/delegation-retry-policy.ts +118 -0
- package/src/harness/dependencies.ts +165 -0
- package/src/harness/gate-submission-action.ts +154 -0
- package/src/harness/lifecycle-actions.ts +154 -0
- package/src/harness/pause-actions.ts +120 -0
- package/src/harness/plannotator-result-actions.ts +123 -0
- package/src/harness/prompt-gate-actions.ts +270 -0
- package/src/harness/resume-action.ts +261 -0
- package/src/harness/start-actions.ts +183 -0
- package/src/harness/status-actions.ts +191 -0
- package/src/harness/step-execution-actions.ts +283 -0
- package/src/harness/types.ts +76 -0
- package/src/harness.ts +236 -2303
- package/src/index.ts +73 -11
- package/src/integrations/plannotator-requests.ts +127 -0
- package/src/integrations/plannotator-responses.ts +141 -0
- package/src/integrations/plannotator-types.ts +44 -0
- package/src/integrations/plannotator.ts +16 -235
- package/src/integrations/prompt-gate.ts +17 -10
- package/src/integrations/subagents/child-policy-envelope.ts +119 -0
- package/src/integrations/subagents/child-policy-paths.ts +66 -0
- package/src/integrations/subagents/child-policy-sections.ts +174 -0
- package/src/integrations/subagents/child-policy-types.ts +32 -0
- package/src/integrations/subagents/child-policy-validation.ts +163 -0
- package/src/integrations/subagents/child-runtime-completion.ts +56 -0
- package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
- package/src/integrations/subagents/child-runtime-files.ts +156 -0
- package/src/integrations/subagents/child-runtime-policy.ts +83 -0
- package/src/integrations/subagents/child-runtime-types.ts +30 -0
- package/src/integrations/subagents/child-runtime.ts +140 -301
- package/src/integrations/subagents/client-delegation.ts +181 -0
- package/src/integrations/subagents/client-messages.ts +66 -0
- package/src/integrations/subagents/client-types.ts +36 -0
- package/src/integrations/subagents/client.ts +127 -219
- package/src/integrations/subagents/delegated-result.ts +31 -0
- package/src/integrations/subagents/diagnostic-format.ts +40 -0
- package/src/integrations/subagents/diagnostic-text.ts +114 -0
- package/src/integrations/subagents/diagnostic-types.ts +77 -0
- package/src/integrations/subagents/diagnostics.ts +24 -977
- package/src/integrations/subagents/failure-correlation.ts +235 -0
- package/src/integrations/subagents/failure-transcript.ts +192 -0
- package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
- package/src/integrations/subagents/protocol-events.ts +27 -0
- package/src/integrations/subagents/protocol.ts +30 -375
- package/src/integrations/subagents/replay-audit.ts +147 -0
- package/src/integrations/subagents/replay-safety.ts +82 -0
- package/src/integrations/subagents/session-diagnostics.ts +258 -0
- package/src/policy/approved-command-extraction.ts +71 -0
- package/src/policy/approved-commands.ts +15 -432
- package/src/policy/bash-authorization.ts +86 -0
- package/src/policy/bash-read-only.ts +161 -0
- package/src/policy/bash-types.ts +20 -0
- package/src/policy/bash.ts +4 -346
- package/src/policy/completion-batch.ts +21 -16
- package/src/policy/immutable-input.ts +5 -2
- package/src/policy/mcp-authorization.ts +71 -0
- package/src/policy/restricted-command.ts +141 -0
- package/src/policy/restricted-git.ts +30 -0
- package/src/policy/reviewed-artifact.ts +97 -0
- package/src/policy/reviewed-command-safety.ts +150 -0
- package/src/policy/reviewed-command-shape.ts +49 -0
- package/src/policy/reviewed-repository-contract.ts +138 -0
- package/src/policy/reviewed-repository-cwd.ts +102 -0
- package/src/policy/tool-call-authorization.ts +58 -0
- package/src/policy/tool-selection.ts +72 -0
- package/src/policy/tool-types.ts +14 -0
- package/src/policy/tools.ts +11 -150
- package/src/preflight.ts +96 -61
- package/src/prompt/main-workflow-notice.ts +41 -0
- package/src/prompt/retry-task.ts +83 -0
- package/src/prompt/step-contract.ts +101 -0
- package/src/prompt/step-sections.ts +75 -0
- package/src/prompt/step-task.ts +156 -0
- package/src/prompt/template.ts +81 -0
- package/src/prompt.ts +10 -254
- package/src/runtime/completion-tool.ts +3 -0
- package/src/runtime/main-step-completion.ts +78 -0
- package/src/runtime/main-step-lifecycle.ts +37 -0
- package/src/runtime/main-step-policy.ts +84 -0
- package/src/runtime/main-step-runtime-types.ts +64 -0
- package/src/runtime/main-step-runtime.ts +129 -200
- package/src/runtime/main-step-state.ts +85 -0
- package/src/runtime/serial-task-queue.ts +48 -13
- package/src/runtime/step-result.ts +48 -27
- package/src/workflow-list.ts +15 -6
- package/src/workflow-status/format-status.ts +52 -0
- package/src/workflow-status/formatting.ts +178 -0
- package/src/workflow-status/layout.ts +109 -0
- package/src/workflow-status/render-board.ts +101 -0
- package/src/workflow-status/render-path.ts +123 -0
- package/src/workflow-status/render-summary.ts +144 -0
- package/src/workflow-status/types.ts +40 -0
- package/src/workflow-status/view.ts +273 -0
- package/src/workflow-status.ts +20 -820
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { WORKFLOW_COMPLETION_TOOL } from './completion-tool.ts';
|
|
3
|
+
import type {
|
|
4
|
+
MainStepRuntimeDependencies,
|
|
5
|
+
MainStepRuntimeState,
|
|
6
|
+
} from './main-step-runtime-types.ts';
|
|
7
|
+
|
|
8
|
+
type RegisterMainStepPolicyOptions = {
|
|
9
|
+
readonly pi: ExtensionAPI;
|
|
10
|
+
readonly state: MainStepRuntimeState;
|
|
11
|
+
readonly dependencies: MainStepRuntimeDependencies;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registers policy handlers that isolate and freeze active-step tool calls.
|
|
16
|
+
*
|
|
17
|
+
* @param options - Pi API, runtime state, and injected policy operations.
|
|
18
|
+
*/
|
|
19
|
+
export function registerMainStepPolicy({
|
|
20
|
+
pi,
|
|
21
|
+
state,
|
|
22
|
+
dependencies,
|
|
23
|
+
}: RegisterMainStepPolicyOptions): void {
|
|
24
|
+
pi.on('turn_start', () => {
|
|
25
|
+
state.invalidCompletionCalls = new Set();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
pi.on('message_end', (event) => {
|
|
29
|
+
if (!state.active) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const invalidCalls = dependencies.invalidCompletionCallIds(
|
|
34
|
+
event.message,
|
|
35
|
+
WORKFLOW_COMPLETION_TOOL,
|
|
36
|
+
);
|
|
37
|
+
if (invalidCalls.size > 0 || event.message.role === 'assistant') {
|
|
38
|
+
state.invalidCompletionCalls = invalidCalls;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
pi.on('tool_call', (event) => {
|
|
43
|
+
if (state.isSuspended) {
|
|
44
|
+
return {
|
|
45
|
+
block: true,
|
|
46
|
+
reason: 'Main-agent workflow step is suspended',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (!state.active) {
|
|
50
|
+
return event.toolName === WORKFLOW_COMPLETION_TOOL
|
|
51
|
+
? {
|
|
52
|
+
block: true,
|
|
53
|
+
reason: 'No main-agent workflow step is active',
|
|
54
|
+
}
|
|
55
|
+
: undefined;
|
|
56
|
+
}
|
|
57
|
+
if (state.invalidCompletionCalls.has(event.toolCallId)) {
|
|
58
|
+
return {
|
|
59
|
+
block: true,
|
|
60
|
+
reason: `${WORKFLOW_COMPLETION_TOOL} must be the only tool call in its message`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (event.toolName === WORKFLOW_COMPLETION_TOOL) {
|
|
64
|
+
dependencies.freezeToolInput(event.input);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const authorization = dependencies.authorizeToolCall(
|
|
69
|
+
event.toolName,
|
|
70
|
+
Object.fromEntries(Object.entries(event.input)),
|
|
71
|
+
state.active.step,
|
|
72
|
+
pi.getAllTools(),
|
|
73
|
+
state.active.approvedBashCommands,
|
|
74
|
+
);
|
|
75
|
+
if (!authorization.allowed) {
|
|
76
|
+
return {
|
|
77
|
+
block: true,
|
|
78
|
+
reason: authorization.reason ?? 'Tool blocked by main workflow policy',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
dependencies.freezeToolInput(event.input);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { WorkflowStep } from '../config/types.ts';
|
|
3
|
+
import type { invalidCompletionCallIds } from '../policy/completion-batch.ts';
|
|
4
|
+
import type { freezeToolInput } from '../policy/immutable-input.ts';
|
|
5
|
+
import type { authorizeToolCall, resolveActiveTools } from '../policy/tools.ts';
|
|
6
|
+
import type {
|
|
7
|
+
parseWorkflowStepResult,
|
|
8
|
+
StepResultPolicy,
|
|
9
|
+
WorkflowStepResult,
|
|
10
|
+
} from './step-result.ts';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Identity, policy, and settlement callback for one main-agent step.
|
|
14
|
+
*/
|
|
15
|
+
export type MainStepExecution = StepResultPolicy & {
|
|
16
|
+
readonly workflowId: string;
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly stepId: string;
|
|
19
|
+
readonly stepDigest: string;
|
|
20
|
+
readonly step: WorkflowStep;
|
|
21
|
+
readonly approvedBashCommands: ReadonlyArray<string>;
|
|
22
|
+
/** Handles the captured result after Pi fully settles the agent run. */
|
|
23
|
+
readonly onSettled: (
|
|
24
|
+
result: WorkflowStepResult | undefined,
|
|
25
|
+
context: ExtensionContext,
|
|
26
|
+
) => Promise<void> | void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Functional controller consumed by the workflow harness.
|
|
31
|
+
*/
|
|
32
|
+
export type MainStepRuntimeController = {
|
|
33
|
+
/** ID of the currently active workflow step. */
|
|
34
|
+
readonly activeStepId: string | undefined;
|
|
35
|
+
/** Activates one workflow step. */
|
|
36
|
+
readonly activate: (execution: MainStepExecution) => void;
|
|
37
|
+
/** Clears the active step and reports whether one existed. */
|
|
38
|
+
readonly deactivate: () => boolean;
|
|
39
|
+
/** Clears the active step and blocks tools until released. */
|
|
40
|
+
readonly suspend: () => boolean;
|
|
41
|
+
/** Allows a future step to be activated. */
|
|
42
|
+
readonly release: () => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Injectable policy and parsing operations used by the runtime.
|
|
47
|
+
*/
|
|
48
|
+
export type MainStepRuntimeDependencies = {
|
|
49
|
+
readonly invalidCompletionCallIds: typeof invalidCompletionCallIds;
|
|
50
|
+
readonly freezeToolInput: typeof freezeToolInput;
|
|
51
|
+
readonly authorizeToolCall: typeof authorizeToolCall;
|
|
52
|
+
readonly resolveActiveTools: typeof resolveActiveTools;
|
|
53
|
+
readonly parseWorkflowStepResult: typeof parseWorkflowStepResult;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Mutable state isolated within one runtime controller closure.
|
|
58
|
+
*/
|
|
59
|
+
export type MainStepRuntimeState = {
|
|
60
|
+
active: MainStepExecution | undefined;
|
|
61
|
+
pendingResult: WorkflowStepResult | undefined;
|
|
62
|
+
invalidCompletionCalls: ReadonlySet<string>;
|
|
63
|
+
isSuspended: boolean;
|
|
64
|
+
};
|
|
@@ -1,227 +1,156 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionContext,
|
|
4
|
-
} from '@earendil-works/pi-coding-agent';
|
|
5
|
-
import type { WorkflowStep } from '../config/types.ts';
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
6
2
|
import { invalidCompletionCallIds } from '../policy/completion-batch.ts';
|
|
7
3
|
import { freezeToolInput } from '../policy/immutable-input.ts';
|
|
8
4
|
import { authorizeToolCall, resolveActiveTools } from '../policy/tools.ts';
|
|
5
|
+
import { registerMainStepCompletion } from './main-step-completion.ts';
|
|
6
|
+
import { registerMainStepLifecycle } from './main-step-lifecycle.ts';
|
|
7
|
+
import { registerMainStepPolicy } from './main-step-policy.ts';
|
|
8
|
+
import type {
|
|
9
|
+
MainStepExecution,
|
|
10
|
+
MainStepRuntimeController,
|
|
11
|
+
MainStepRuntimeDependencies,
|
|
12
|
+
} from './main-step-runtime-types.ts';
|
|
9
13
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type WorkflowStepResult,
|
|
17
|
-
} from './step-result.ts';
|
|
18
|
-
|
|
19
|
-
export interface MainStepExecution extends StepResultPolicy {
|
|
20
|
-
workflowId: string;
|
|
21
|
-
runId: string;
|
|
22
|
-
stepId: string;
|
|
23
|
-
stepDigest: string;
|
|
24
|
-
step: WorkflowStep;
|
|
25
|
-
approvedBashCommands: string[];
|
|
26
|
-
onSettled(
|
|
27
|
-
result: WorkflowStepResult | undefined,
|
|
28
|
-
context: ExtensionContext,
|
|
29
|
-
): Promise<void> | void;
|
|
30
|
-
}
|
|
14
|
+
activateMainStep,
|
|
15
|
+
createMainStepRuntimeState,
|
|
16
|
+
deactivateMainStep,
|
|
17
|
+
} from './main-step-state.ts';
|
|
18
|
+
import { parseWorkflowStepResult } from './step-result.ts';
|
|
19
|
+
import { WORKFLOW_COMPLETION_TOOL } from './completion-tool.ts';
|
|
31
20
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
export type {
|
|
22
|
+
MainStepExecution,
|
|
23
|
+
MainStepRuntimeController,
|
|
24
|
+
MainStepRuntimeDependencies,
|
|
25
|
+
} from './main-step-runtime-types.ts';
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
const DEFAULT_DEPENDENCIES = {
|
|
28
|
+
invalidCompletionCallIds,
|
|
29
|
+
freezeToolInput,
|
|
30
|
+
authorizeToolCall,
|
|
31
|
+
resolveActiveTools,
|
|
32
|
+
parseWorkflowStepResult,
|
|
33
|
+
} as const satisfies MainStepRuntimeDependencies;
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Inputs used to create an isolated main-step runtime.
|
|
37
|
+
*/
|
|
38
|
+
export type CreateMainStepRuntimeOptions = {
|
|
39
|
+
readonly pi: ExtensionAPI;
|
|
40
|
+
readonly dependencies?: Partial<MainStepRuntimeDependencies>;
|
|
41
|
+
};
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Creates a closure-based main-step runtime with explicit policy dependencies.
|
|
45
|
+
*
|
|
46
|
+
* @param options - Pi API and optional dependency overrides.
|
|
47
|
+
* @returns A controller compatible with the workflow harness.
|
|
48
|
+
*/
|
|
49
|
+
export function createMainStepRuntime({
|
|
50
|
+
pi,
|
|
51
|
+
dependencies: dependencyOverrides = {},
|
|
52
|
+
}: CreateMainStepRuntimeOptions): MainStepRuntimeController {
|
|
53
|
+
const dependencies = {
|
|
54
|
+
...DEFAULT_DEPENDENCIES,
|
|
55
|
+
...dependencyOverrides,
|
|
56
|
+
};
|
|
57
|
+
const state = createMainStepRuntimeState();
|
|
58
|
+
|
|
59
|
+
const activate = (execution: MainStepExecution): void => {
|
|
60
|
+
activateMainStep({ state, execution });
|
|
61
|
+
pi.setActiveTools(
|
|
62
|
+
dependencies.resolveActiveTools(
|
|
63
|
+
pi.getAllTools(),
|
|
61
64
|
execution.step,
|
|
62
65
|
WORKFLOW_COMPLETION_TOOL,
|
|
63
66
|
),
|
|
64
67
|
);
|
|
65
|
-
}
|
|
68
|
+
};
|
|
66
69
|
|
|
67
|
-
deactivate(): boolean
|
|
68
|
-
const wasActive = this.active !== undefined;
|
|
69
|
-
this.active = undefined;
|
|
70
|
-
this.pendingResult = undefined;
|
|
71
|
-
this.invalidCompletionCalls.clear();
|
|
72
|
-
return wasActive;
|
|
73
|
-
}
|
|
70
|
+
const deactivate = (): boolean => deactivateMainStep(state);
|
|
74
71
|
|
|
75
|
-
suspend(): boolean {
|
|
76
|
-
const wasActive =
|
|
72
|
+
const suspend = (): boolean => {
|
|
73
|
+
const wasActive = deactivate();
|
|
77
74
|
if (wasActive) {
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
state.isSuspended = true;
|
|
76
|
+
pi.setActiveTools([]);
|
|
80
77
|
}
|
|
81
78
|
return wasActive;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const release = (): void => {
|
|
82
|
+
state.isSuspended = false;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
registerMainStepLifecycle({ pi, state });
|
|
86
|
+
registerMainStepPolicy({ pi, state, dependencies });
|
|
87
|
+
registerMainStepCompletion({ pi, state, dependencies });
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
get activeStepId() {
|
|
91
|
+
return state.active?.stepId;
|
|
92
|
+
},
|
|
93
|
+
activate,
|
|
94
|
+
deactivate,
|
|
95
|
+
suspend,
|
|
96
|
+
release,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Compatibility facade for harness code that constructs the runtime with
|
|
102
|
+
* `new MainStepRuntime(pi)`.
|
|
103
|
+
*/
|
|
104
|
+
export class MainStepRuntime implements MainStepRuntimeController {
|
|
105
|
+
private readonly controller: MainStepRuntimeController;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates and registers a main-step runtime against the Pi extension API.
|
|
109
|
+
*
|
|
110
|
+
* @param pi - Pi extension API used for lifecycle and tool registration.
|
|
111
|
+
* @param dependencies - Optional policy and parsing overrides.
|
|
112
|
+
*/
|
|
113
|
+
constructor(
|
|
114
|
+
pi: ExtensionAPI,
|
|
115
|
+
dependencies: Partial<MainStepRuntimeDependencies> = {},
|
|
116
|
+
) {
|
|
117
|
+
this.controller = createMainStepRuntime({ pi, dependencies });
|
|
82
118
|
}
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
|
|
120
|
+
/** ID of the active main-agent workflow step, when present. */
|
|
121
|
+
get activeStepId(): string | undefined {
|
|
122
|
+
return this.controller.activeStepId;
|
|
86
123
|
}
|
|
87
124
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.filter((tool) => tool !== WORKFLOW_COMPLETION_TOOL),
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
this.pi.on('session_start', reset);
|
|
99
|
-
this.pi.on('session_tree', reset);
|
|
100
|
-
this.pi.on('session_shutdown', () => {
|
|
101
|
-
this.suspended = false;
|
|
102
|
-
this.deactivate();
|
|
103
|
-
});
|
|
104
|
-
this.pi.on('agent_settled', (_event, context) => {
|
|
105
|
-
const active = this.active;
|
|
106
|
-
if (!active) return;
|
|
107
|
-
const result = this.pendingResult;
|
|
108
|
-
this.active = undefined;
|
|
109
|
-
this.pendingResult = undefined;
|
|
110
|
-
this.invalidCompletionCalls.clear();
|
|
111
|
-
return active.onSettled(result, context);
|
|
112
|
-
});
|
|
125
|
+
/**
|
|
126
|
+
* Activates one main-agent workflow step.
|
|
127
|
+
*
|
|
128
|
+
* @param execution - Step identity, policy, and settlement callback.
|
|
129
|
+
*/
|
|
130
|
+
activate(execution: MainStepExecution): void {
|
|
131
|
+
this.controller.activate(execution);
|
|
113
132
|
}
|
|
114
133
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
event.message,
|
|
123
|
-
WORKFLOW_COMPLETION_TOOL,
|
|
124
|
-
);
|
|
125
|
-
if (
|
|
126
|
-
invalid.size > 0 ||
|
|
127
|
-
(event.message as { role?: unknown }).role === 'assistant'
|
|
128
|
-
) {
|
|
129
|
-
this.invalidCompletionCalls = invalid;
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
this.pi.on('tool_call', (event) => {
|
|
133
|
-
if (this.suspended) {
|
|
134
|
-
return {
|
|
135
|
-
block: true,
|
|
136
|
-
reason: 'Main-agent workflow step is suspended',
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (!this.active) {
|
|
140
|
-
if (event.toolName === WORKFLOW_COMPLETION_TOOL) {
|
|
141
|
-
return {
|
|
142
|
-
block: true,
|
|
143
|
-
reason: 'No main-agent workflow step is active',
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
if (this.invalidCompletionCalls.has(event.toolCallId)) {
|
|
149
|
-
return {
|
|
150
|
-
block: true,
|
|
151
|
-
reason: `${WORKFLOW_COMPLETION_TOOL} must be the only tool call in its message`,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
if (event.toolName === WORKFLOW_COMPLETION_TOOL) {
|
|
155
|
-
freezeToolInput(event.input);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const authorization = authorizeToolCall(
|
|
160
|
-
event.toolName,
|
|
161
|
-
event.input as unknown as Record<string, unknown>,
|
|
162
|
-
this.active.step,
|
|
163
|
-
this.pi.getAllTools(),
|
|
164
|
-
this.active.approvedBashCommands,
|
|
165
|
-
);
|
|
166
|
-
if (!authorization.allowed) {
|
|
167
|
-
return {
|
|
168
|
-
block: true,
|
|
169
|
-
reason:
|
|
170
|
-
authorization.reason ?? 'Tool blocked by main workflow policy',
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
freezeToolInput(event.input);
|
|
174
|
-
});
|
|
134
|
+
/**
|
|
135
|
+
* Clears the active step.
|
|
136
|
+
*
|
|
137
|
+
* @returns Whether a step was active.
|
|
138
|
+
*/
|
|
139
|
+
deactivate(): boolean {
|
|
140
|
+
return this.controller.deactivate();
|
|
175
141
|
}
|
|
176
142
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!this.active) {
|
|
190
|
-
throw new Error('No main-agent workflow step is active');
|
|
191
|
-
}
|
|
192
|
-
if (this.pendingResult) {
|
|
193
|
-
throw new Error('Main-agent workflow step already produced a result');
|
|
194
|
-
}
|
|
195
|
-
const result = parseWorkflowStepResult(
|
|
196
|
-
{
|
|
197
|
-
version: 1,
|
|
198
|
-
policyDigest: this.active.policyDigest,
|
|
199
|
-
outcome: params.outcome,
|
|
200
|
-
summary: params.summary,
|
|
201
|
-
...(params.artifact !== undefined
|
|
202
|
-
? { artifact: params.artifact }
|
|
203
|
-
: {}),
|
|
204
|
-
},
|
|
205
|
-
this.active,
|
|
206
|
-
);
|
|
207
|
-
this.pendingResult = result;
|
|
208
|
-
this.pi.setActiveTools([]);
|
|
209
|
-
return {
|
|
210
|
-
content: [
|
|
211
|
-
{
|
|
212
|
-
type: 'text' as const,
|
|
213
|
-
text: `Captured workflow step outcome "${result.outcome}".`,
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
details: {
|
|
217
|
-
workflowId: this.active.workflowId,
|
|
218
|
-
runId: this.active.runId,
|
|
219
|
-
stepId: this.active.stepId,
|
|
220
|
-
outcome: result.outcome,
|
|
221
|
-
},
|
|
222
|
-
terminate: true,
|
|
223
|
-
};
|
|
224
|
-
},
|
|
225
|
-
});
|
|
143
|
+
/**
|
|
144
|
+
* Clears the active step and blocks tools until released.
|
|
145
|
+
*
|
|
146
|
+
* @returns Whether a step was active.
|
|
147
|
+
*/
|
|
148
|
+
suspend(): boolean {
|
|
149
|
+
return this.controller.suspend();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Releases a suspended runtime for future activation. */
|
|
153
|
+
release(): void {
|
|
154
|
+
this.controller.release();
|
|
226
155
|
}
|
|
227
156
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type {
|
|
3
|
+
MainStepExecution,
|
|
4
|
+
MainStepRuntimeState,
|
|
5
|
+
} from './main-step-runtime-types.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates the isolated mutable state captured by one main-step runtime.
|
|
9
|
+
*
|
|
10
|
+
* @returns A fresh inactive runtime state.
|
|
11
|
+
*/
|
|
12
|
+
export function createMainStepRuntimeState(): MainStepRuntimeState {
|
|
13
|
+
return {
|
|
14
|
+
active: undefined,
|
|
15
|
+
pendingResult: undefined,
|
|
16
|
+
invalidCompletionCalls: new Set(),
|
|
17
|
+
isSuspended: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ActivateMainStepOptions = {
|
|
22
|
+
readonly state: MainStepRuntimeState;
|
|
23
|
+
readonly execution: MainStepExecution;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Activates an execution in a runtime state that currently has no active step.
|
|
28
|
+
*
|
|
29
|
+
* @param options - State and execution to activate.
|
|
30
|
+
* @throws When another main-agent step remains active.
|
|
31
|
+
*/
|
|
32
|
+
export function activateMainStep({
|
|
33
|
+
state,
|
|
34
|
+
execution,
|
|
35
|
+
}: ActivateMainStepOptions): void {
|
|
36
|
+
if (state.active) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`main workflow step "${state.active.stepId}" is still active`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
state.isSuspended = false;
|
|
43
|
+
state.active = execution;
|
|
44
|
+
state.pendingResult = undefined;
|
|
45
|
+
state.invalidCompletionCalls = new Set();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Clears active execution data while preserving the suspension flag.
|
|
50
|
+
*
|
|
51
|
+
* @param state - Runtime state to clear.
|
|
52
|
+
* @returns Whether a step was active.
|
|
53
|
+
*/
|
|
54
|
+
export function deactivateMainStep(state: MainStepRuntimeState): boolean {
|
|
55
|
+
const wasActive = state.active !== undefined;
|
|
56
|
+
state.active = undefined;
|
|
57
|
+
state.pendingResult = undefined;
|
|
58
|
+
state.invalidCompletionCalls = new Set();
|
|
59
|
+
return wasActive;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type SettleMainStepOptions = {
|
|
63
|
+
readonly state: MainStepRuntimeState;
|
|
64
|
+
readonly context: ExtensionContext;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Removes and settles the active execution with its captured result.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Runtime state and extension context for the callback.
|
|
71
|
+
* @returns The execution's settlement callback result.
|
|
72
|
+
*/
|
|
73
|
+
export function settleMainStep({
|
|
74
|
+
state,
|
|
75
|
+
context,
|
|
76
|
+
}: SettleMainStepOptions): Promise<void> | void {
|
|
77
|
+
const activeExecution = state.active;
|
|
78
|
+
if (!activeExecution) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = state.pendingResult;
|
|
83
|
+
deactivateMainStep(state);
|
|
84
|
+
return activeExecution.onSettled(result, context);
|
|
85
|
+
}
|
|
@@ -1,21 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Serialize state-changing commands while allowing the queue to recover after
|
|
4
|
-
* an individual command rejects.
|
|
2
|
+
* Minimal functional surface for serial task execution.
|
|
5
3
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
4
|
+
export type SerialTaskQueueController = {
|
|
5
|
+
/** Enqueues a task and returns its eventual result. */
|
|
6
|
+
readonly run: <TResult>(task: () => Promise<TResult>) => Promise<TResult>;
|
|
7
|
+
};
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const settleTask = async (taskResult: Promise<unknown>): Promise<void> => {
|
|
10
|
+
try {
|
|
11
|
+
await taskResult;
|
|
12
|
+
} catch {
|
|
13
|
+
// The original promise is returned to the caller; only the queue tail recovers.
|
|
11
14
|
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a closure-based queue that serializes asynchronous state changes.
|
|
19
|
+
*
|
|
20
|
+
* A rejected task does not poison the queue; later tasks still execute.
|
|
21
|
+
*
|
|
22
|
+
* @returns A serial task queue controller.
|
|
23
|
+
*/
|
|
24
|
+
export function createSerialTaskQueue(): SerialTaskQueueController {
|
|
25
|
+
let tail = Promise.resolve();
|
|
12
26
|
|
|
13
|
-
run<
|
|
14
|
-
const result =
|
|
15
|
-
|
|
16
|
-
() => undefined,
|
|
17
|
-
() => undefined,
|
|
18
|
-
);
|
|
27
|
+
const run = <TResult>(task: () => Promise<TResult>): Promise<TResult> => {
|
|
28
|
+
const result = tail.then(task, task);
|
|
29
|
+
tail = settleTask(result);
|
|
19
30
|
return result;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return { run };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compatibility facade for callers that construct a serial queue with `new`.
|
|
38
|
+
*/
|
|
39
|
+
export class SerialTaskQueue implements SerialTaskQueueController {
|
|
40
|
+
private readonly controller: SerialTaskQueueController;
|
|
41
|
+
|
|
42
|
+
/** Creates an isolated serial task queue. */
|
|
43
|
+
constructor() {
|
|
44
|
+
this.controller = createSerialTaskQueue();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Enqueues one asynchronous task after all previously submitted tasks.
|
|
49
|
+
*
|
|
50
|
+
* @param task - State-changing operation to serialize.
|
|
51
|
+
* @returns The task result or rejection.
|
|
52
|
+
*/
|
|
53
|
+
run<TResult>(task: () => Promise<TResult>): Promise<TResult> {
|
|
54
|
+
return this.controller.run(task);
|
|
20
55
|
}
|
|
21
56
|
}
|