@marcoscale98/pi-extensible-workflows 5.17.0-fork.1 → 6.2.1-fork.1

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +442 -162
  5. package/dist/src/background-widget.d.ts +5 -1
  6. package/dist/src/background-widget.js +5 -10
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +59 -37
  9. package/dist/src/execution.d.ts +6 -2
  10. package/dist/src/execution.js +48 -7
  11. package/dist/src/host-delivery.js +5 -8
  12. package/dist/src/host-navigator.js +5 -2
  13. package/dist/src/host-phases.d.ts +1 -0
  14. package/dist/src/host-recovery.d.ts +11 -8
  15. package/dist/src/host-recovery.js +14 -22
  16. package/dist/src/host-runtime.js +56 -5
  17. package/dist/src/host-view.d.ts +6 -1
  18. package/dist/src/host-view.js +46 -20
  19. package/dist/src/host.d.ts +2 -2
  20. package/dist/src/host.js +246 -118
  21. package/dist/src/index.d.ts +1 -2
  22. package/dist/src/index.js +1985 -1690
  23. package/dist/src/index.js.map +3 -3
  24. package/dist/src/paths.d.ts +8 -5
  25. package/dist/src/paths.js +24 -13
  26. package/dist/src/persistence.d.ts +1 -0
  27. package/dist/src/persistence.js +1 -0
  28. package/dist/src/pi-runtime-runner.js +4 -1
  29. package/dist/src/registry.d.ts +9 -7
  30. package/dist/src/registry.js +29 -71
  31. package/dist/src/runtime/agent-runner.d.ts +2 -0
  32. package/dist/src/script-tools.d.ts +27 -0
  33. package/dist/src/script-tools.js +87 -0
  34. package/dist/src/session-lease.d.ts +1 -1
  35. package/dist/src/session-lease.js +45 -11
  36. package/dist/src/settings.d.ts +19 -3
  37. package/dist/src/settings.js +63 -50
  38. package/dist/src/store.d.ts +3 -1
  39. package/dist/src/store.js +17 -1
  40. package/dist/src/trajectory.d.ts +12 -3
  41. package/dist/src/trajectory.js +32 -11
  42. package/dist/src/types.d.ts +129 -45
  43. package/dist/src/types.js +5 -8
  44. package/dist/src/utils.d.ts +21 -1
  45. package/dist/src/utils.js +46 -6
  46. package/dist/src/validation.d.ts +14 -3
  47. package/dist/src/validation.js +143 -87
  48. package/dist/starter/index.js +8 -11
  49. package/dist/starter/index.js.map +1 -1
  50. package/dist/starter/review-loop.d.ts +0 -13
  51. package/dist/starter/review-loop.js +8 -9
  52. package/dist/subagents/index.js +904 -956
  53. package/dist/subagents/index.js.map +3 -3
  54. package/dist/subagents/src/contracts.d.ts +48 -31
  55. package/dist/subagents/src/contracts.js +20 -9
  56. package/dist/subagents/src/index.d.ts +1 -1
  57. package/dist/subagents/src/index.js +14 -8
  58. package/dist/subagents/src/manager.js +52 -73
  59. package/dist/subagents/src/navigator.js +3 -14
  60. package/dist/subagents/src/view.d.ts +1 -1
  61. package/dist/subagents/src/view.js +15 -68
  62. package/dist/subagents/src/worktree.js +1 -1
  63. package/dist/trajectory/assets/index.html +35 -13
  64. package/dist/trajectory/index.js +576 -932
  65. package/dist/trajectory/index.js.map +3 -3
  66. package/dist/trajectory/src/assets/index.html +35 -13
  67. package/dist/trajectory/src/index.js +134 -104
  68. package/dist/trajectory/src/server.js +268 -27
  69. package/dist/trajectory/src/server.js.map +3 -3
  70. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  71. package/dist/trajectory/src/startup-mutex.js +172 -0
  72. package/examples/workflow-extension-template/README.md +4 -7
  73. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  74. package/examples/workflow-extension-template/index.js +0 -3
  75. package/package.json +10 -10
  76. package/skills/pi-extensible-workflows/SKILL.md +12 -8
  77. package/src/agent-execution.ts +412 -164
  78. package/src/background-widget.ts +5 -11
  79. package/src/decoders.ts +51 -37
  80. package/src/execution.ts +46 -9
  81. package/src/host-delivery.ts +5 -8
  82. package/src/host-navigator.ts +4 -2
  83. package/src/host-phases.ts +1 -1
  84. package/src/host-recovery.ts +23 -28
  85. package/src/host-runtime.ts +44 -6
  86. package/src/host-view.ts +41 -16
  87. package/src/host.ts +229 -142
  88. package/src/index.ts +1 -2
  89. package/src/paths.ts +18 -9
  90. package/src/persistence.ts +1 -0
  91. package/src/pi-runtime-runner.ts +3 -1
  92. package/src/registry.ts +28 -64
  93. package/src/runtime/agent-runner.ts +2 -0
  94. package/src/script-tools.ts +76 -0
  95. package/src/session-lease.ts +34 -6
  96. package/src/settings.ts +56 -50
  97. package/src/store.ts +18 -2
  98. package/src/trajectory.ts +32 -15
  99. package/src/types.ts +82 -34
  100. package/src/utils.ts +42 -5
  101. package/src/validation.ts +121 -80
  102. package/starter/index.ts +0 -1
  103. package/starter/review-loop.ts +8 -9
  104. package/starter/roles/developer.md +0 -1
  105. package/starter/roles/oracle.md +0 -1
  106. package/starter/roles/researcher.md +0 -1
  107. package/starter/roles/reviewer.md +0 -1
  108. package/starter/roles/scout.md +0 -1
  109. package/subagents/README.md +10 -16
  110. package/subagents/src/contracts.ts +21 -11
  111. package/subagents/src/index.ts +17 -8
  112. package/subagents/src/manager.ts +48 -128
  113. package/subagents/src/navigator.ts +4 -13
  114. package/subagents/src/view.ts +16 -63
  115. package/subagents/src/worktree.ts +1 -1
  116. package/trajectory/src/assets/index.html +35 -13
  117. package/trajectory/src/index.ts +112 -91
  118. package/trajectory/src/server.ts +46 -10
  119. package/trajectory/src/startup-mutex.ts +127 -0
  120. package/dist/src/roles.d.ts +0 -70
  121. package/dist/src/roles.js +0 -325
  122. package/dist/starter/roles/developer.md +0 -6
  123. package/dist/starter/roles/oracle.md +0 -18
  124. package/dist/starter/roles/researcher.md +0 -20
  125. package/dist/starter/roles/reviewer.md +0 -19
  126. package/dist/starter/roles/scout.md +0 -18
  127. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  128. package/src/roles.ts +0 -326
package/src/types.ts CHANGED
@@ -27,29 +27,30 @@ export const WORKFLOW_BUDGET_EVENT = "workflow:budget-event";
27
27
  export const WORKFLOW_WORKTREE_CREATED_EVENT = "workflow:worktree-created";
28
28
  export const WORKFLOW_BLOCKED_EVENT = "workflow:blocked";
29
29
  export const ERROR_CODES = [
30
- "CONFIG_ERROR", "INVALID_SETTINGS", "INVALID_SYNTAX", "INVALID_METADATA", "DUPLICATE_NAME", "INVALID_SCHEMA", "UNKNOWN_MODEL", "UNKNOWN_TOOL", "UNKNOWN_AGENT_TYPE",
30
+ "CONFIG_ERROR", "INVALID_SETTINGS", "INVALID_SYNTAX", "INVALID_METADATA", "DUPLICATE_NAME", "INVALID_SCHEMA", "UNKNOWN_MODEL", "UNKNOWN_TOOL", "UNKNOWN_AGENT",
31
31
  "RUN_OWNED", "RUN_NOT_FOUND", "REGISTRY_FROZEN", "GLOBAL_COLLISION", "MISSING_WORKFLOW", "RPC_LIMIT_EXCEEDED", "SHELL_FAILED", "AGENT_TIMEOUT", "AGENT_FAILED", "AGENT_RESULT_COLLECTED", "RESULT_INVALID",
32
- "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
32
+ "TOOL_FAILED", "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
33
33
  ] as const;
34
34
 
35
35
  export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
36
36
  export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
37
+ export const CONTEXT_FILE_SCOPES = ["global", "project", "cwd"] as const;
38
+ export type ContextFileScope = (typeof CONTEXT_FILE_SCOPES)[number];
39
+ export function isContextFileScope(value: unknown): value is ContextFileScope { return CONTEXT_FILE_SCOPES.some((scope) => scope === value); }
37
40
  export type WorkflowErrorCode = (typeof ERROR_CODES)[number];
38
41
  export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
39
42
  export type JsonSchema = { [key: string]: JsonValue };
40
43
  type WorkflowSchema = JsonSchema | TSchema;
41
- export function roleNameOf(value: unknown): string | undefined {
42
- return typeof value === "string" ? value : undefined;
43
- }
44
44
  // Extra keys are reserved for extensions and forwarded as JSON to agentOptions; core options remain typed.
45
45
  export interface AgentOptions<Schema extends TSchema = never> {
46
46
  label?: string;
47
47
  model?: string;
48
48
  tools?: string[];
49
+ /** Exact tool names removed after preparation hooks; never widens the parent ceiling. */
50
+ excludeTools?: string[];
49
51
  skills?: string[];
50
52
  extensions?: string[];
51
53
  contextFiles?: ContextFileScope[];
52
- role?: string;
53
54
  outputSchema?: JsonSchema | Schema;
54
55
  retries?: number;
55
56
  timeoutMs?: number | null;
@@ -80,7 +81,7 @@ export type WorkflowRunResumedEvent = WorkflowEventBase;
80
81
  export interface WorkflowRunStateChangedEvent extends WorkflowEventBase { previousState: RunState; state: RunState; reason?: string; errorCode?: WorkflowErrorCode }
81
82
  export interface WorkflowRunCompletedEvent extends WorkflowEventBase { resultPath: string }
82
83
  export interface WorkflowRunFailedEvent extends WorkflowEventBase { error: WorkflowErrorShape }
83
- export interface WorkflowAgentStateChangedEvent extends WorkflowEventBase { agentId: string; displayLabel: string; role?: string; structuralPath: readonly string[]; parentId?: string; parentBreadcrumb?: string; worktreeOwner?: string; previousState?: AgentState; state: AgentState; attempt: number }
84
+ export interface WorkflowAgentStateChangedEvent extends WorkflowEventBase { agentId: string; displayLabel: string; structuralPath: readonly string[]; parentId?: string; parentBreadcrumb?: string; worktreeOwner?: string; previousState?: AgentState; state: AgentState; attempt: number }
84
85
  export interface WorkflowPhaseChangedEvent extends WorkflowEventBase { previousPhase?: string; phase: string }
85
86
  export type WorkflowCheckpointState = "awaiting" | "approved" | "rejected";
86
87
  export interface WorkflowCheckpointStateChangedEvent extends WorkflowEventBase { name: string; state: WorkflowCheckpointState }
@@ -92,22 +93,21 @@ export interface WorkflowMetadata { name: string; description?: string }
92
93
  export interface HerdrExtensionSettings { enableFullyInspectableMode?: boolean }
93
94
  export interface TrajectoryExtensionSettings { port?: number }
94
95
  export interface WorkflowExtensionSettings { readonly [namespace: string]: JsonValue; readonly herdr?: Readonly<HerdrExtensionSettings>; readonly trajectory?: Readonly<TrajectoryExtensionSettings> }
95
- export type WorkflowExtensionSettingsSource = "global" | "project" | "role" | "effective";
96
- export interface WorkflowExtensionSettingsValidatorContext { source: WorkflowExtensionSettingsSource; cwd: string; projectTrusted: boolean; settingsPath?: string; role?: string }
96
+ export type WorkflowExtensionSettingsSource = "global" | "project" | "effective";
97
+ export interface WorkflowExtensionSettingsValidatorContext { source: WorkflowExtensionSettingsSource; cwd: string; projectTrusted: boolean; settingsPath?: string }
97
98
  export type WorkflowExtensionSettingsValidator = (settings: Readonly<WorkflowExtensionSettings>, context: Readonly<WorkflowExtensionSettingsValidatorContext>) => void;
98
99
  export interface WorkflowSessionStartEvent { type: "session_start"; reason: "startup" | "reload" | "new" | "resume" | "fork"; previousSessionFile?: string; settings: Readonly<WorkflowExtensionSettings> }
99
100
  export interface WorkflowRetentionSettings { olderThanDays?: number; maxTerminalRuns?: number }
100
- export interface WorkflowSettings { concurrency: number; backgroundWidget?: boolean; modelAliases?: Readonly<Record<string, string>>; skills?: readonly string[]; extensions?: readonly string[]; extensionSettings?: Readonly<WorkflowExtensionSettings>; tools?: readonly string[]; retention?: Readonly<WorkflowRetentionSettings>; worktreePostCreateCommand?: readonly string[] }
101
- export type WorkflowSettingsOverrides = Partial<Omit<WorkflowSettings, "backgroundWidget">>;
101
+ export type CodemodeToolsSetting = "all" | "read-only" | "none";
102
+ export interface WorkflowSettings { concurrency: number; backgroundWidget?: boolean; codemodeTools?: CodemodeToolsSetting; modelAliases?: Readonly<Record<string, string>>; skills?: readonly string[]; extensions?: readonly string[]; extensionSettings?: Readonly<WorkflowExtensionSettings>; tools?: readonly string[]; retention?: Readonly<WorkflowRetentionSettings>; worktreePostCreateCommand?: readonly string[] }
103
+ export type WorkflowSettingsOverrides = Partial<Omit<WorkflowSettings, "backgroundWidget" | "codemodeTools">>;
102
104
  export interface WorkflowSettingsSources { concurrency: string; modelAliases: string; skills?: string; extensions?: string; tools?: string; extensionSettings?: string; retention?: string; worktreePostCreateCommand?: string }
103
- export interface WorkflowSettingsResolution { globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; global: Readonly<WorkflowSettings>; project: Readonly<WorkflowSettingsOverrides>; effective: Readonly<WorkflowSettings>; sources: Readonly<WorkflowSettingsSources> }
105
+ export interface WorkflowSettingsResolution { selectorSources?: AgentResourceSelectorSources; globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; global: Readonly<WorkflowSettings>; project: Readonly<WorkflowSettingsOverrides>; effective: Readonly<WorkflowSettings>; sources: Readonly<WorkflowSettingsSources> }
104
106
  export interface AgentResourceSelectors { skills?: readonly string[]; extensions?: readonly string[]; tools?: readonly string[] }
105
107
  export interface AgentResourceSelectorSet { skills: readonly string[]; extensions: readonly string[]; tools?: readonly string[] }
106
- export interface AgentResourceSelectorSources { global: AgentResourceSelectors; project: AgentResourceSelectors; role?: AgentResourceSelectors; call?: AgentResourceSelectors }
107
- export const CONTEXT_FILE_SCOPES = ["global", "project", "cwd"] as const;
108
- export type ContextFileScope = (typeof CONTEXT_FILE_SCOPES)[number];
109
- export function isContextFileScope(value: unknown): value is ContextFileScope { return CONTEXT_FILE_SCOPES.some((scope) => scope === value); }
108
+ export interface AgentResourceSelectorSources { global: AgentResourceSelectors; project: AgentResourceSelectors; call?: AgentResourceSelectors }
110
109
  export interface AgentResourcePolicy {
110
+ capabilities?: Readonly<{ tools: readonly string[]; skills: readonly string[]; extensions: readonly string[] }>;
111
111
  globalSettingsPath: string;
112
112
  projectSettingsPath: string;
113
113
  projectTrusted: boolean;
@@ -121,6 +121,8 @@ export interface AgentResourcePolicy {
121
121
  unmatchedExtensions: readonly string[];
122
122
  unmatchedTools?: readonly string[];
123
123
  selectorSources: AgentResourceSelectorSources;
124
+ /** Skills loaded by the parent session; ceiling for skills that extensions contribute through resources_discover. Absent for top-level agents. */
125
+ parentSkills?: readonly string[];
124
126
  }
125
127
  export interface AgentResourceInspection { selectors: AgentResourceSelectorSet; skills: readonly string[]; extensions: readonly string[]; tools: readonly string[]; unmatchedSkills: readonly string[]; unmatchedExtensions: readonly string[]; unmatchedTools: readonly string[]; selectorSources?: AgentResourceSelectorSources }
126
128
 
@@ -157,7 +159,6 @@ export interface AgentRecord {
157
159
  handle?: string;
158
160
  turn?: number;
159
161
  continuity?: AgentContinuity;
160
- role?: string;
161
162
  requestedModel?: string;
162
163
  model: ModelSpec;
163
164
  tools: readonly string[];
@@ -178,6 +179,8 @@ export interface WorkflowRunEvent { type: string; message: string; timestamp?: n
178
179
  export interface WorkflowRetryProvenance { sourceRunId: string; lineageRootRunId: string; completedPaths: readonly string[]; incompletePaths: readonly string[]; namedWorktrees: readonly string[] }
179
180
  export interface WorkflowPhaseRecord { phase: string; afterAgent: number }
180
181
  export interface WorkflowPhaseShellActivity { phaseIndex: number; active: number; startedAt: number }
182
+ /** A script call recorded for inspection. Tool payloads stay in the replay journal, not this record. Oversized shell inputs are JSON prefixes, with `inputBytes` their full size. */
183
+ export interface WorkflowScriptCall { kind: "shell" | "tool"; name: string; path: string; input?: JsonValue; inputBytes?: number; phase?: string; startedAt: number; finishedAt?: number; error?: { code: WorkflowErrorCode; message?: string } }
181
184
  export interface RunRecord {
182
185
  id: string;
183
186
  workflowName: string;
@@ -194,6 +197,8 @@ export interface RunRecord {
194
197
  activeShells?: number;
195
198
  activeShellStartedAt?: number;
196
199
  activeShellsByPhase?: readonly WorkflowPhaseShellActivity[];
200
+ activeTools?: readonly { name: string; startedAt: number }[]; // Script tool calls in flight: a live overlay for rendering, never persisted.
201
+ scriptCalls?: readonly WorkflowScriptCall[];
197
202
  error?: WorkflowErrorShape;
198
203
  failedAt?: string;
199
204
  budget?: WorkflowBudget;
@@ -203,11 +208,10 @@ export interface RunRecord {
203
208
  events?: readonly WorkflowRunEvent[];
204
209
  delivery?: WorkflowRunDelivery;
205
210
  }
206
- export const LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
211
+ export const LAUNCH_SNAPSHOT_IDENTITY_VERSION = 6;
207
212
  export type WorkflowLaunchMode = "foreground" | "background";
208
- export interface AgentDefinition { prompt?: string; description?: string; model?: string; thinking?: NonNullable<ModelSpec["thinking"]>; tools?: readonly string[]; skills?: readonly string[]; extensions?: readonly string[]; overrideSystemPrompt?: boolean; contextFiles?: readonly ContextFileScope[]; extensionSettings?: Readonly<WorkflowExtensionSettings> }
209
213
  export interface LaunchSnapshot {
210
- identityVersion?: number;
214
+ identityVersion: number;
211
215
  launchMode?: WorkflowLaunchMode;
212
216
  script: string;
213
217
  args: JsonValue;
@@ -220,14 +224,18 @@ export interface LaunchSnapshot {
220
224
  phases?: readonly string[];
221
225
  models: readonly string[];
222
226
  tools: readonly string[];
223
- agentTypes: readonly string[];
224
- roles?: Readonly<Record<string, AgentDefinition>>;
225
- projectRoles?: readonly string[];
227
+ agentConfigurations: Readonly<Record<string, PreparedAgentConfiguration>>;
226
228
  schemas: readonly JsonSchema[];
227
229
  }
228
- export interface PreflightCapabilities { models: ReadonlySet<string>; tools: ReadonlySet<string>; agentTypes: ReadonlySet<string>; modelAliases?: Readonly<Record<string, string>>; knownModels?: ReadonlySet<string>; settingsPath?: string; skipModelAvailability?: boolean }
229
- export interface PreflightResult { metadata: WorkflowMetadata; referenced: { phases: readonly string[]; models: readonly string[]; tools: readonly string[]; agentTypes: readonly string[] }; schemas: readonly JsonSchema[]; dynamicAgentRoles: boolean }
230
- export interface WorkflowOrchestrationContext { agent: { <Schema extends TSchema>(prompt: string, options: Readonly<AgentOptions<Schema> & { outputSchema: Schema }>): Promise<Static<Schema>>; (prompt: string, options?: Readonly<AgentOptions>): Promise<JsonValue> }; shell: (command: string, options?: ShellOptions) => Promise<ShellResult>; prompt: (template: string, values: Readonly<Record<string, JsonValue>>) => string; parallel: <Tasks extends ParallelTasks>(operationName: string, tasks: Tasks) => Promise<ParallelResult<Tasks>>; pipeline: <Items extends PipelineItems, Output extends JsonValue>(operationName: string, items: Items, stages: PipelineStages<Items[keyof Items], Output>) => Promise<PipelineResult<Items, Output>>; withWorktree: <Result extends JsonValue>(name: string, callback: WorkflowWorktreeCallback<Result>) => Promise<Result>; checkpoint: (input: CheckpointInput) => Promise<boolean>; phase: (name: string) => void; log: (message: string) => void }
230
+ export interface PreflightCapabilities { models: ReadonlySet<string>; tools: ReadonlySet<string>; modelAliases?: Readonly<Record<string, string>>; knownModels?: ReadonlySet<string>; settingsPath?: string; skipModelAvailability?: boolean }
231
+ export interface PreflightResult { metadata: WorkflowMetadata; referenced: { phases: readonly string[]; models: readonly string[]; tools: readonly string[]; }; schemas: readonly JsonSchema[] }
232
+ export interface AgentHandleSendOptions<Schema extends TSchema = never> { outputSchema?: JsonSchema | Schema; timeoutMs?: number | null }
233
+ /** A persistent agent: each send continues the transcript of the previous completed turn. Sends must be awaited one at a time, in the scope that created the handle. */
234
+ export interface AgentHandle {
235
+ readonly name: string;
236
+ send: { <Schema extends TSchema>(prompt: string, options: Readonly<AgentHandleSendOptions<Schema> & { outputSchema: Schema }>): Promise<Static<Schema>>; (prompt: string, options?: Readonly<AgentHandleSendOptions>): Promise<JsonValue> };
237
+ }
238
+ export interface WorkflowOrchestrationContext { agent: { <Schema extends TSchema>(prompt: string, options: Readonly<AgentOptions<Schema> & { outputSchema: Schema }>): Promise<Static<Schema>>; (prompt: string, options?: Readonly<AgentOptions>): Promise<JsonValue>; create: (options: Readonly<AgentOptions & { name: string }>) => AgentHandle }; shell: (command: string, options?: ShellOptions) => Promise<ShellResult>; prompt: (template: string, values: Readonly<Record<string, JsonValue>>) => string; parallel: <Tasks extends ParallelTasks>(operationName: string, tasks: Tasks) => Promise<ParallelResult<Tasks>>; pipeline: <Items extends PipelineItems, Output extends JsonValue>(operationName: string, items: Items, stages: PipelineStages<Items[keyof Items], Output>) => Promise<PipelineResult<Items, Output>>; withWorktree: <Result extends JsonValue>(name: string, callback: WorkflowWorktreeCallback<Result>) => Promise<Result>; checkpoint: (input: CheckpointInput) => Promise<boolean>; phase: (name: string) => void; log: (message: string) => void }
231
239
  export interface WorkflowRunContext { cwd: string; sessionId: string; runId: string; workflow: Readonly<WorkflowMetadata>; args: JsonValue; signal: AbortSignal }
232
240
  export interface WorkflowFunctionContext extends WorkflowOrchestrationContext { run: Readonly<WorkflowRunContext>; settings: Readonly<WorkflowExtensionSettings>; invoke: (name: string, input: Readonly<Record<string, JsonValue>>, label?: string) => Promise<JsonValue> }
233
241
  export type WorkflowWorktreeCallback<Result extends JsonValue = JsonValue> = (reference: Readonly<WorkflowWorktreeReference>) => Result | Promise<Result>;
@@ -262,6 +270,8 @@ export interface WorkflowAgentSession {
262
270
  getState(): Readonly<WorkflowAgentSessionState>;
263
271
  getSessionStats(): WorkflowAgentSessionStats;
264
272
  getLastAssistant(): WorkflowAgentMessage | undefined;
273
+ /** Set by a transport that already delivered `PreparedAgentSession.turnControl` itself; otherwise the runner steers it before the first prompt. */
274
+ readonly turnControlDelivered?: boolean;
265
275
  subscribe(listener: (event: WorkflowAgentSessionEvent) => void): () => void;
266
276
  subscribeAsync?(listener: (event: WorkflowAgentSessionEvent) => void | Promise<void>): () => void;
267
277
  prompt(text: string): Promise<WorkflowAgentTurnResult>;
@@ -275,7 +285,8 @@ export interface WorkflowAgentSession {
275
285
  type SessionTools = NonNullable<CreateAgentSessionOptions["tools"]>;
276
286
  type SessionCustomTools = NonNullable<CreateAgentSessionOptions["customTools"]>;
277
287
  export interface SessionInput {
278
- cwd: string;
288
+ /** Read-only: setup hooks cannot move an agent, because Pi derives trust, settings and `.pi` resources from cwd. */
289
+ readonly cwd: string;
279
290
  model: ModelSpec;
280
291
  tools: SessionTools;
281
292
  sessionLabel: string;
@@ -304,6 +315,8 @@ export interface PreparedAgentSession {
304
315
  readonly sessionLabel: string;
305
316
  readonly sessionPath?: string;
306
317
  readonly initialPrompt?: string;
318
+ /** Dynamic per-attempt control text (retry state, budget). The runner steers it before the first prompt unless the session reports `turnControlDelivered`. */
319
+ readonly turnControl?: string;
307
320
  readonly agentDir?: string;
308
321
  readonly customTools?: readonly ToolDefinition[];
309
322
  readonly resultTool?: ToolDefinition;
@@ -325,17 +338,50 @@ export interface AgentTransport { readonly id: string; createSession(prepared: R
325
338
  export interface AgentSetup { prompt: string; options: AgentOptions; sessionInput: SessionInput; prepared: Readonly<PreparedAgentSession>; transport: AgentTransport }
326
339
  export interface AgentSetupContext { readonly run: Readonly<WorkflowRunContext>; readonly identity: Readonly<AgentIdentity>; readonly attempt: number; readonly signal: AbortSignal; readonly tuiIndex?: number; readonly tuiLabel?: string; readonly mode?: AgentInspectionMode; readonly settings: Readonly<WorkflowExtensionSettings> }
327
340
 
341
+ export interface AgentPreparation {
342
+ label?: string;
343
+ model?: string;
344
+ tools: string[];
345
+ skills: string[];
346
+ extensions: string[];
347
+ contextFiles?: ContextFileScope[];
348
+ systemPrompt?: string;
349
+ systemPromptAppend: string;
350
+ settings: WorkflowExtensionSettings;
351
+ }
352
+ export interface AgentPreparationContext {
353
+ readonly options: Readonly<AgentOptions>;
354
+ readonly cwd: string;
355
+ /** Launch project root that owns project configuration; differs from `cwd` inside worktree scopes. Optional for older hosts. */
356
+ readonly projectCwd?: string;
357
+ readonly agentDir: string;
358
+ readonly projectTrusted: boolean;
359
+ /** `dynamicModelAliasNames` lists the `modelAliases` entries resolved by extensions rather than read from static settings. */
360
+ readonly defaults: Readonly<{ model: ModelSpec; modelAliases: Readonly<Record<string, string>>; dynamicModelAliasNames?: readonly string[]; selectorSources: AgentResourceSelectorSources; settings: Readonly<WorkflowExtensionSettings> }>;
361
+ readonly capabilities: Readonly<{ tools: readonly string[]; skills: readonly string[]; extensions: readonly string[] }>;
362
+ readonly knownModels: ReadonlySet<string>;
363
+ readonly availableModels: ReadonlySet<string>;
364
+ readonly signal: AbortSignal;
365
+ readonly mode: AgentInspectionMode;
366
+ }
367
+ export interface AgentPreparationHook { priority?: number; optionsSchema?: JsonSchema; prepare: (configuration: AgentPreparation, context: Readonly<AgentPreparationContext>) => void | Promise<void> }
368
+ /** `source` is the registering extension's module provenance, when it declared one. */
369
+ export interface RegisteredAgentPreparationHook { name: string; priority: number; optionsSchema?: JsonSchema; source?: string; prepare: AgentPreparationHook["prepare"] }
370
+ /**
371
+ * `excludeTools` holds the exact tool names the agent and its ancestors excluded, frozen with the configuration so setup and
372
+ * children apply them even when re-evaluated options change. It is absent only in configurations prepared before it existed, which cannot resume.
373
+ */
374
+ export interface PreparedAgentConfiguration extends Omit<AgentPreparation, "model"> { model: ModelSpec; projectTrusted: boolean; excludeTools?: string[] }
328
375
  export interface AgentSetupHook { priority?: number; setup: (agent: AgentSetup, context: Readonly<AgentSetupContext>) => void | Promise<void> }
329
376
  export interface RegisteredAgentSetupHook { name: string; priority: number; setup: AgentSetupHook["setup"] }
330
377
  export interface WorkflowExtensionMetadata { version: string; headline: string }
331
378
  export interface WorkflowFunctionSource { readonly module: string; readonly export: string; readonly dependencies: readonly string[] }
332
- export interface WorkflowRoleDirectoryRegistration { path: string; extension: WorkflowExtensionMetadata; readonly builtin?: true }
333
379
  export interface AgentAttemptActionUi { notify(message: string, level?: "info" | "warning" | "error"): void; confirm(title: string, message: string): Promise<boolean>; select(title: string, options: readonly string[]): Promise<string | undefined>; input(title: string, placeholder?: string): Promise<string | undefined>; setWorkingMessage?(message?: string): void }
334
380
  export interface StandaloneAgentRecord { readonly id: string; readonly name: string; readonly label?: string; readonly state: "running" | "completed" | "failed" | "stopped"; readonly structuralPath?: readonly string[] }
335
381
  export interface StandaloneAgentAttemptActionContext { readonly agent: Readonly<StandaloneAgentRecord>; readonly attempt: Readonly<AgentAttemptSummary>; readonly session?: WorkflowAgentSessionReference; readonly liveSession?: WorkflowAgentSession; readonly prepared?: Readonly<PreparedAgentSession>; readonly handoff?: LiveSessionHandoff; readonly signal: AbortSignal; readonly ui: Readonly<AgentAttemptActionUi> }
336
382
  export interface AgentAttemptActionContext { readonly run: Readonly<RunRecord>; readonly agent: Readonly<AgentRecord>; readonly attempt: Readonly<AgentAttemptSummary>; readonly session?: WorkflowAgentSessionReference; readonly liveSession?: WorkflowAgentSession; readonly prepared?: Readonly<PreparedAgentSession>; readonly handoff?: LiveSessionHandoff; readonly signal: AbortSignal; readonly ui: Readonly<AgentAttemptActionUi> }
337
383
  export interface AgentAttemptAction { readonly label: string; visible(context: Readonly<AgentAttemptActionContext>): boolean; run(context: Readonly<AgentAttemptActionContext>): void | Promise<void>; visibleStandalone?(context: Readonly<StandaloneAgentAttemptActionContext>): boolean; runStandalone?(context: Readonly<StandaloneAgentAttemptActionContext>): void | Promise<void> }
338
- export interface WorkflowExtension extends WorkflowExtensionMetadata { description?: string; source?: string; dependencies?: readonly string[]; validateSettings?: WorkflowExtensionSettingsValidator; functions?: Readonly<Record<string, WorkflowFunction>>; modelAliases?: Readonly<Record<string, WorkflowModelAlias>>; agentSetupHooks?: Readonly<Record<string, AgentSetupHook>>; agentAttemptActions?: Readonly<Record<string, AgentAttemptAction>>; roleDirectories?: readonly (string | URL)[] }
384
+ export interface WorkflowExtension extends WorkflowExtensionMetadata { description?: string; source?: string; dependencies?: readonly string[]; validateSettings?: WorkflowExtensionSettingsValidator; functions?: Readonly<Record<string, WorkflowFunction>>; modelAliases?: Readonly<Record<string, WorkflowModelAlias>>; agentPreparationHooks?: Readonly<Record<string, AgentPreparationHook>>; agentSetupHooks?: Readonly<Record<string, AgentSetupHook>>; agentAttemptActions?: Readonly<Record<string, AgentAttemptAction>> }
339
385
  export interface WorkflowJournal { get(path: string): JsonValue | undefined; put(path: string, value: JsonValue): void }
340
386
  // The brand keeps instanceof working across the bundled extension entries, which each inline their own copy of this class.
341
387
  const WORKFLOW_ERROR_BRAND = Symbol.for("pi-extensible-workflows.workflow-error");
@@ -348,19 +394,21 @@ export class WorkflowError extends Error {
348
394
  }
349
395
  }
350
396
  Object.defineProperty(WorkflowError, WORKFLOW_ERROR_BRAND, { value: true });
351
- export interface WorkflowFailureAgent { id: string; label?: string; role?: string; structuralPath: readonly string[]; attempt: number; transport?: string; session?: WorkflowAgentSessionReference }
352
- export interface WorkflowSiblingAgent { id: string; label?: string; role?: string; structuralPath: readonly string[] }
397
+ export interface WorkflowFailureAgent { id: string; label?: string; structuralPath: readonly string[]; attempt: number; transport?: string; session?: WorkflowAgentSessionReference }
398
+ export interface WorkflowSiblingAgent { id: string; label?: string; structuralPath: readonly string[] }
353
399
  export interface WorkflowFailureDiagnostics { runId: string; workflowName: string; state: RunState; failedAt: string | null; error: WorkflowErrorShape; failedAgent?: WorkflowFailureAgent; completedSiblingAgents?: readonly WorkflowSiblingAgent[]; completedSiblingPaths: readonly (readonly string[])[]; retry?: { sourceRunId: string; action: string; completedPaths: readonly string[]; incompletePaths: readonly string[]; namedWorktrees: readonly string[]; warning: string }; artifacts: { runDirectory: string; statePath: string; journalPath: string } }
354
400
  export interface CheckpointInput { name: string; prompt: string; context: JsonValue }
355
401
  export interface FunctionIdentity { path: string; structuralPath: readonly string[]; occurrence: number; worktreeOwner?: string }
356
402
  export type AgentContinuity = "fresh" | "continued";
357
403
  export interface AgentIdentity { structuralPath: readonly string[]; callSite: string; occurrence: number; parentBreadcrumb?: string; worktreeOwner?: string; handle?: string; turn?: number }
358
404
  export interface ShellIdentity { structuralPath: readonly string[]; callSite: string; occurrence: number; worktreeOwner?: string }
359
- export interface WorkflowBridge { agent?: (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>; shell?: (command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>; checkpoint?: (input: Readonly<Record<string, JsonValue>>, signal: AbortSignal) => boolean | Promise<boolean>; function?: (name: string, input: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: FunctionIdentity) => Promise<JsonValue>; worktree?: (owner: string, signal: AbortSignal) => Promise<Readonly<WorkflowWorktreeReference>>; functions?: Readonly<Record<string, { name: string }>>; phase?: (name: string) => void | Promise<void>; log?: (message: string) => void | Promise<void> }
405
+ /** A script `tools.<id>(args)` call: the n-th call of that tool within one structural scope. */
406
+ export interface ToolIdentity { structuralPath: readonly string[]; occurrence: number }
407
+ export interface WorkflowBridge { agent?: (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>; shell?: (command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>; checkpoint?: (input: Readonly<Record<string, JsonValue>>, signal: AbortSignal) => boolean | Promise<boolean>; function?: (name: string, input: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: FunctionIdentity) => Promise<JsonValue>; worktree?: (owner: string, signal: AbortSignal) => Promise<Readonly<WorkflowWorktreeReference>>; functions?: Readonly<Record<string, { name: string }>>; phase?: (name: string) => void | Promise<void>; log?: (message: string) => void | Promise<void>; tool?: (identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue> }
360
408
  export interface WorkflowExecution { result: Promise<JsonValue>; cancel: () => void }
361
409
  export interface StaticWorkflowScope { kind: "parallel" | "pipeline"; name: string | null; key: string | null }
362
410
  export type StaticWorkflowExecution = "parallel" | "sequential";
363
- export interface StaticWorkflowCall { kind: WorkflowCallKind; start: number; end: number; name: string | null; prompt: string | null; model: string | null; label?: string | null; role: string | null; retries?: number | null; outputSchema?: JsonSchema | null; options?: Readonly<Record<string, JsonValue>> | null; optionKeys?: readonly string[]; execution?: StaticWorkflowExecution; structure?: readonly StaticWorkflowScope[] }
411
+ export interface StaticWorkflowCall { kind: WorkflowCallKind; start: number; end: number; name: string | null; prompt: string | null; model: string | null; label?: string | null; retries?: number | null; outputSchema?: JsonSchema | null; options?: Readonly<Record<string, JsonValue>> | null; optionKeys?: readonly string[]; execution?: StaticWorkflowExecution; structure?: readonly StaticWorkflowScope[] }
364
412
  export interface WorkflowCatalogFunction { name: string; version: string; headline: string; description: string; input: JsonSchema; output: JsonSchema }
365
413
  export interface WorkflowCatalogModelAlias { name: string; kind: "static" | "dynamic"; provenance: string; version?: string; headline?: string }
366
414
  export interface WorkflowCatalogSettings { concurrency: number; backgroundWidget: boolean; modelAliases: Readonly<Record<string, string>>; skills: readonly string[]; extensions: readonly string[]; tools: readonly string[]; extensionSettings?: Readonly<WorkflowExtensionSettings>; worktreePostCreateCommand?: readonly string[]; globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; sources: WorkflowSettingsSources }
@@ -371,4 +419,4 @@ export interface WorkflowCatalogIndex { functions: readonly WorkflowCatalogIndex
371
419
  export interface WorkflowCatalogError { error: { code: "NOT_FOUND"; name: string; message: string } }
372
420
  export interface WorkflowValidationParameters { name: string; description?: string; script?: string; scriptPath?: string; args?: unknown }
373
421
  export interface WorkflowValidationContext { cwd: string; projectTrusted: boolean; availableModels: ReadonlySet<string>; rootTools: ReadonlySet<string>; modelAliases?: Readonly<Record<string, string>>; knownModels?: ReadonlySet<string>; settingsPath?: string; agentDir?: string; extensionSettings?: Readonly<WorkflowExtensionSettings> }
374
- export interface ValidatedWorkflowLaunch { script: string; checked: PreflightResult; agentDefinitions: Readonly<Record<string, AgentDefinition>>; projectAgentDefinitions: Readonly<Record<string, AgentDefinition>>; roleNames: readonly string[] }
422
+ export interface ValidatedWorkflowLaunch { script: string; checked: PreflightResult }
package/src/utils.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { ERROR_CODES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, THINKING_LEVELS, WorkflowError, type JsonSchema, type JsonValue, type ModelSpec, type ThinkingLevel, type WorkflowErrorCode, type WorkflowExtensionSettings } from "./types.js";
1
+ import { createHash } from "node:crypto";
2
+ import { ERROR_CODES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, WorkflowError, type JsonSchema, type JsonValue, type ModelSpec, type WorkflowErrorCode, type WorkflowExtensionSettings } from "./types.js";
2
3
  import { Minimatch } from "minimatch";
4
+ import { THINKING_LEVELS, type ThinkingLevel } from "./types.js";
3
5
  export class SerialLane {
4
6
  #tail: Promise<void> = Promise.resolve();
5
7
  run<T>(task: () => Promise<T>): Promise<T> {
@@ -18,6 +20,15 @@ export function sanitizeDisplayText(value: string): string { return value.replac
18
20
  export { object as isObject };
19
21
  function isStringKey(key: PropertyKey): key is string { return typeof key === "string"; }
20
22
  function stringKeyValue(value: object, key: string): unknown { return object(value) ? value[key] : undefined; }
23
+ function sortedJson(value: JsonValue): JsonValue {
24
+ if (Array.isArray(value)) return value.map(sortedJson);
25
+ if (value === null || typeof value !== "object") return value;
26
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortedJson(value[key] as JsonValue)]));
27
+ }
28
+ /** A short digest of a JSON value that ignores object key order. */
29
+ export function jsonDigest(value: JsonValue): string {
30
+ return createHash("sha256").update(JSON.stringify(sortedJson(value))).digest("hex").slice(0, 16);
31
+ }
21
32
  export function jsonValue(value: unknown, seen = new Set<object>()): value is JsonValue {
22
33
  if (value === null || typeof value === "boolean" || typeof value === "string") return true;
23
34
  if (typeof value === "number") return Number.isFinite(value);
@@ -82,11 +93,19 @@ export const WORKFLOW_EXTENSION_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
82
93
  export function validWorkflowExtensionNamespace(value: string): boolean { return WORKFLOW_EXTENSION_NAMESPACE.test(value) && value !== "__proto__" && value !== "constructor" && value !== "prototype"; }
83
94
  export function parseThinking(value: unknown): ModelSpec["thinking"] | undefined { return isThinkingLevel(value) ? value : undefined; }
84
95
  export function parseModelReference(value: string): ModelSpec {
85
- const match = /^([^/:\s]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
96
+ const match = /^([^/:\s]+)\/([^\s]+)$/.exec(value);
86
97
  if (!match?.[1] || !match[2]) fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
87
- const thinking = match[3];
88
- if (thinking !== undefined && !isThinkingLevel(thinking)) fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
89
- return { provider: match[1], model: match[2], ...(thinking !== undefined ? { thinking } : {}) };
98
+ // The model id is everything after the first slash, so namespaced ids such as
99
+ // `deepseek/deepseek-v4.1-flash:nitro` survive intact. Only a trailing segment
100
+ // that names a thinking level is one.
101
+ const reference = match[2];
102
+ const separator = reference.lastIndexOf(":");
103
+ const suffix = separator === -1 ? undefined : reference.slice(separator + 1);
104
+ if (suffix === "") fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
105
+ const thinking = suffix !== undefined && isThinkingLevel(suffix) ? suffix : undefined;
106
+ const model = thinking === undefined ? reference : reference.slice(0, separator);
107
+ if (model === "") fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
108
+ return { provider: match[1], model, ...(thinking === undefined ? {} : { thinking }) };
90
109
  }
91
110
  export function assertModelThinking(value: string, path = "model"): void {
92
111
  if (value.includes("/") && parseModelReference(value).thinking === undefined) fail("INVALID_METADATA", `${path} must be provider/model:thinking`);
@@ -157,6 +176,24 @@ export function modelCapability(value: string | ModelSpec, aliases?: Readonly<Re
157
176
  const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
158
177
  return `${parsed.provider}/${parsed.model}`;
159
178
  }
179
+ type ToolSource = { getActiveTools(): string[]; getAllTools?(): readonly { name: string; exposure?: string }[] };
180
+ /**
181
+ * Tools a session can reach, and so the ceiling for its agents: the declared tools plus the ones
182
+ * only codemode scripts call or tool_search loads, such as MCP tools with the default exposure.
183
+ * Agent sessions allow tools by name, so leaving these out would make them unreachable there.
184
+ */
185
+ export function reachableTools(pi: ToolSource): string[] {
186
+ const undeclared = pi.getAllTools?.().filter(({ exposure }) => exposure === "codemode" || exposure === "deferred").map(({ name }) => name) ?? [];
187
+ return [...new Set([...pi.getActiveTools(), ...undeclared])];
188
+ }
189
+ /** Provider of the virtual models that expose workflow model aliases in `/model`. */
190
+ export const VIRTUAL_MODEL_PROVIDER = "workflow";
191
+ /** Child sessions do not load the workflow extension, so they receive the alias target instead of its virtual model. */
192
+ export function physicalModel(spec: ModelSpec, aliases: Readonly<Record<string, string>> = {}, knownModels?: ReadonlySet<string>, settingsPath?: string): ModelSpec {
193
+ if (spec.provider !== VIRTUAL_MODEL_PROVIDER || !modelAliasName(spec.model, aliases)) return spec;
194
+ const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
195
+ return spec.thinking === undefined ? target : { ...target, thinking: spec.thinking };
196
+ }
160
197
  export function aliasDrift(previous: Readonly<Record<string, string>>, current: Readonly<Record<string, string>>): string[] {
161
198
  return [...new Set([...Object.keys(previous), ...Object.keys(current)])].sort().flatMap((name) => previous[name] === current[name] ? [] : [`${name}: ${previous[name] ?? "(missing)"} -> ${current[name] ?? "(missing)"}`]);
162
199
  }