@marcoscale98/pi-extensible-workflows 5.19.1-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.
- package/CHANGELOG.md +77 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +365 -127
- package/dist/src/background-widget.d.ts +0 -1
- package/dist/src/background-widget.js +4 -9
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +33 -37
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +5 -5
- package/dist/src/host-delivery.js +4 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +6 -6
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +1 -1
- package/dist/src/host-view.js +17 -15
- package/dist/src/host.js +62 -87
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1523 -1630
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +7 -6
- package/dist/src/paths.js +24 -15
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +28 -70
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +6 -3
- package/dist/src/settings.js +36 -47
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +0 -1
- package/dist/src/trajectory.js +2 -3
- package/dist/src/types.d.ts +100 -44
- package/dist/src/types.js +4 -7
- package/dist/src/utils.d.ts +3 -2
- package/dist/src/utils.js +20 -10
- package/dist/src/validation.d.ts +8 -2
- package/dist/src/validation.js +122 -86
- package/dist/starter/index.js +8 -9
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.js +8 -7
- package/dist/subagents/index.js +654 -814
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -3
- package/dist/subagents/src/contracts.js +2 -2
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +1 -2
- package/dist/subagents/src/manager.js +49 -70
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +0 -1
- package/dist/subagents/src/view.js +2 -16
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +8 -8
- package/dist/trajectory/index.js +487 -891
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +8 -8
- package/dist/trajectory/src/index.js +126 -86
- package/dist/trajectory/src/server.js +264 -25
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +5 -5
- package/skills/pi-extensible-workflows/SKILL.md +10 -8
- package/src/agent-execution.ts +339 -127
- package/src/background-widget.ts +4 -10
- package/src/decoders.ts +31 -37
- package/src/execution.ts +5 -5
- package/src/host-delivery.ts +4 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +20 -26
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +18 -11
- package/src/host.ts +62 -113
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -12
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +27 -63
- package/src/runtime/agent-runner.ts +2 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +35 -47
- package/src/store.ts +18 -2
- package/src/trajectory.ts +2 -4
- package/src/types.ts +71 -30
- package/src/utils.ts +18 -9
- package/src/validation.ts +102 -79
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -7
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +8 -14
- package/subagents/src/contracts.ts +3 -4
- package/subagents/src/index.ts +2 -3
- package/subagents/src/manager.ts +45 -125
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +4 -14
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +8 -8
- package/trajectory/src/index.ts +106 -82
- package/trajectory/src/server.ts +44 -8
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -326
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -327
package/src/host.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { Type, type Api, type Model, type Static, type TSchema } from "@earendil-works/pi-ai";
|
|
7
7
|
import { copyToClipboard, getAgentDir, ModelSelectorComponent, type ExtensionAPI, type ExtensionContext, type ExtensionToolContext, type ModelRuntime, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { FairAgentScheduler, getAgentAttempts, WorkflowAgentExecutor, localAgentTransport, type AgentActivity, type AgentAttempt, type
|
|
8
|
+
import { FairAgentScheduler, getAgentAttempts, WorkflowAgentExecutor, localAgentTransport, type AgentActivity, type AgentAttempt, type AgentExecutionRoot, type AgentProgress, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
|
|
9
9
|
import { RunLifecycle, WorkflowEventPublisher, hostSessionContext, nextNamedOccurrence, withWorkflowFunctions, withoutActiveShells, workflowRunContext, type WorkflowEventSink, type WorkflowRunRecord, type WorkflowToolUpdate } from "./host-runtime.js";
|
|
10
10
|
import { createWorkflowRecovery, persistedFailure, type ModelRegistryCapability } from "./host-recovery.js";
|
|
11
11
|
import { registerWorkflowNavigator, uiHostCapabilities } from "./host-navigator.js";
|
|
@@ -13,9 +13,8 @@ import { acquireSessionLease, isPersistedRun, listPersistedSessionIds, listRunId
|
|
|
13
13
|
import { retainTerminalRuns } from "./retention.js";
|
|
14
14
|
import type { PersistedRun, WorktreeReference } from "./persistence.js";
|
|
15
15
|
import { validateBudget, WorkflowBudgetRuntime } from "./budget.js";
|
|
16
|
-
import { SerialLane, asWorkflowError, createLaunchSnapshot, errorCode, errorText, fail, isNodeError, jsonValue,
|
|
17
|
-
import { loadCodemodeToolsSetting,
|
|
18
|
-
import { loadAgentDefinitions, loadProjectAgentDefinitions } from "./roles.js";
|
|
16
|
+
import { SerialLane, asWorkflowError, createLaunchSnapshot, errorCode, errorText, fail, isNodeError, jsonValue, modelAliasErrorName, object, parseModelReference, positiveInteger, reachableTools, resolveModelReference, VIRTUAL_MODEL_PROVIDER, sanitizeDisplayText, validateModelAliases } from "./utils.js";
|
|
17
|
+
import { loadCodemodeToolsSetting, preflight, resolveAgentResourcePolicy, staticAgentPreparationOptions, resolveWorkflowSettings, validateCheckpoint, validateModelAliasAvailability, validateWorkflowLaunchWithRegistry, workflowProjectSettingsPath, workflowSettingsPath, workflowToolExposure } from "./validation.js";
|
|
19
18
|
import { beginWorkflowExtensionLoading, loadingRegistry, resetWorkflowRegistryIfIdle, retainWorkflowRegistry, type WorkflowRegistryApi } from "./registry.js";
|
|
20
19
|
import { agentHandleTurnPath, agentIdentityPath, agentWorktree, encoded, executeShellCommand, persistActiveAgentAttempt, persistAgentAttempts, readShellResult, runWorkflow, shellIdentityPath, toolIdentityPath } from "./execution.js";
|
|
21
20
|
import { prepareScriptToolLoadout, scriptTool, scriptToolContext, scriptToolValue, validateScriptToolReferences } from "./script-tools.js";
|
|
@@ -24,41 +23,10 @@ import { showChangelogNotice } from "./changelog.js";
|
|
|
24
23
|
import { createTrajectoryRunLoader, createTrajectoryRunMetadataLoader, createTrajectorySubagentLoader, createTrajectorySubagentMetadataLoader, createTrajectoryTranscriptLoader, type TrajectoryActionRequest, type TrajectoryActionResult, type TrajectorySubagent } from "./trajectory.js";
|
|
25
24
|
import { getTrajectoryHost, type TrajectoryPublisherProvider } from "./trajectory-host-handle.js";
|
|
26
25
|
import { getSubagentManager } from "./subagent-manager-handle.js";
|
|
27
|
-
import { HARD_TERMINAL_RUN_STATES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, THINKING_LEVELS, WORKFLOW_BLOCKED_EVENT, WorkflowError, isContextFileScope, isExternallyEndedRunState, isHardTerminalRunState,
|
|
26
|
+
import { HARD_TERMINAL_RUN_STATES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, THINKING_LEVELS, WORKFLOW_BLOCKED_EVENT, WorkflowError, type WorkflowFunction, isContextFileScope, isExternallyEndedRunState, isHardTerminalRunState, type AgentAccounting, type AgentIdentity, type AgentRecord, type AgentResourcePolicy, type AgentTransport, type JsonValue, type LaunchSnapshot, type LiveSessionHandoff, type HardTerminalRunState, type ModelSpec, type PreparedAgentSession, type RunState, type ShellIdentity, type ShellOptions, type ShellResult, type ToolIdentity, type WorkflowAgentSession, type WorkflowErrorCode, type WorkflowMetadata, type WorkflowModelAliasResolverContext, type WorkflowSettings, type WorkflowScriptCall, type WorkflowSettingsResolution, type WorkflowWorktreeReference } from "./types.js";
|
|
28
27
|
import type { SubagentManagerContext, SubagentRunRequest, SubagentStatus } from "../subagents/src/contracts.js";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
catalogResultValue,
|
|
32
|
-
formatWorkflowCatalog,
|
|
33
|
-
styledTextBlock,
|
|
34
|
-
textBlock,
|
|
35
|
-
workflowCatalogBlock,
|
|
36
|
-
workflowControlCall,
|
|
37
|
-
workflowControlResult,
|
|
38
|
-
workflowProgressBlock,
|
|
39
|
-
type WorkflowProgressRenderState,
|
|
40
|
-
} from "./host-view.js";
|
|
41
|
-
import {
|
|
42
|
-
DELIVERY_LIMIT_BYTES,
|
|
43
|
-
ForegroundDeliveryController,
|
|
44
|
-
markWorkflowFailureDiagnostics,
|
|
45
|
-
WORKFLOW_LOG_ENTRY,
|
|
46
|
-
completionDescriptor,
|
|
47
|
-
completionDeliveryFromStore,
|
|
48
|
-
createWorkflowFailureDiagnostics,
|
|
49
|
-
failureDiagnosticsFrom,
|
|
50
|
-
formatWorkflowFailure,
|
|
51
|
-
formatWorkflowFailureDelivery,
|
|
52
|
-
formatWorkflowFailureDeliveryFallback,
|
|
53
|
-
formatWorkflowFailureDiagnostics,
|
|
54
|
-
isWorkflowFailureDiagnostics,
|
|
55
|
-
serializeWorkflowFailureDiagnostics,
|
|
56
|
-
utf8Prefix,
|
|
57
|
-
type CompletionDeliveryContext,
|
|
58
|
-
type ForegroundDelivery,
|
|
59
|
-
type ForegroundDetachResult,
|
|
60
|
-
type WorkflowLogEntry,
|
|
61
|
-
} from "./host-delivery.js";
|
|
28
|
+
import { SETTLED_AGENT_STATES, catalogResultValue, formatWorkflowCatalog, styledTextBlock, textBlock, workflowCatalogBlock, workflowControlCall, workflowControlResult, workflowProgressBlock, type WorkflowProgressRenderState } from "./host-view.js";
|
|
29
|
+
import { DELIVERY_LIMIT_BYTES, ForegroundDeliveryController, markWorkflowFailureDiagnostics, WORKFLOW_LOG_ENTRY, completionDescriptor, completionDeliveryFromStore, createWorkflowFailureDiagnostics, failureDiagnosticsFrom, formatWorkflowFailure, formatWorkflowFailureDelivery, formatWorkflowFailureDeliveryFallback, formatWorkflowFailureDiagnostics, isWorkflowFailureDiagnostics, serializeWorkflowFailureDiagnostics, utf8Prefix, type CompletionDeliveryContext, type ForegroundDelivery, type ForegroundDetachResult, type WorkflowLogEntry } from "./host-delivery.js";
|
|
62
30
|
|
|
63
31
|
export type WorkflowExtensionAPI = Pick<ExtensionAPI, "appendEntry" | "getActiveTools" | "getThinkingLevel" | "on" | "registerCommand" | "registerTool" | "sendMessage"> & Partial<Pick<ExtensionAPI, "getAllTools">> & Pick<BackgroundWidgetAPI, "events" | "registerEntryRenderer" | "registerShortcut">;
|
|
64
32
|
|
|
@@ -141,26 +109,6 @@ function completionControlContent(result: unknown, controlRunId?: string): strin
|
|
|
141
109
|
delete record.run;
|
|
142
110
|
return JSON.stringify({ ...record, value });
|
|
143
111
|
}
|
|
144
|
-
type RoleCapture = { readonly snapshot: Readonly<LaunchSnapshot>; capture(role: string, model: ModelSpec): Promise<void> };
|
|
145
|
-
/** Persists a role definition (and its model) into the launch snapshot the first time an agent uses it, so later replays and retries see the definition the run actually ran with. */
|
|
146
|
-
function roleCapture(store: RunStore, initial: Readonly<LaunchSnapshot>, definitions: Readonly<Record<string, AgentDefinition>>, projectDefinitions: Readonly<Record<string, AgentDefinition>>): RoleCapture {
|
|
147
|
-
let persisted = initial;
|
|
148
|
-
return {
|
|
149
|
-
get snapshot() { return persisted; },
|
|
150
|
-
async capture(role, model) {
|
|
151
|
-
const definition = definitions[role];
|
|
152
|
-
if (!definition) return;
|
|
153
|
-
const modelName = `${model.provider}/${model.model}`;
|
|
154
|
-
const hasProjectRole = projectDefinitions[role] !== undefined;
|
|
155
|
-
if (persisted.roles?.[role] !== undefined && (!hasProjectRole || persisted.projectRoles?.includes(role)) && persisted.models.includes(modelName)) return;
|
|
156
|
-
const roles = { ...(persisted.roles ?? {}), [role]: definition };
|
|
157
|
-
const projectRoles = hasProjectRole ? [...new Set([...(persisted.projectRoles ?? []), role])] : persisted.projectRoles ?? [];
|
|
158
|
-
const models = [...new Set([...persisted.models, modelName])];
|
|
159
|
-
persisted = createLaunchSnapshot({ ...persisted, models, roles, projectRoles });
|
|
160
|
-
await store.saveSnapshot(persisted);
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
112
|
export function formatWorkflowPreview(args: { script?: unknown; scriptPath?: unknown; name?: unknown; description?: unknown }): string {
|
|
165
113
|
const name = typeof args.name === "string" && args.name.trim() ? args.name.trim() : "workflow";
|
|
166
114
|
if (typeof args.script !== "string" || !args.script.trim()) return `workflow ${name}`;
|
|
@@ -336,6 +284,20 @@ async function resolveLaunchAliases(registry: WorkflowRegistryApi, staticAliases
|
|
|
336
284
|
}
|
|
337
285
|
}
|
|
338
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Prepares each completely static agent configuration in inspection mode, so preparation plugins resolve their own aliases
|
|
289
|
+
* and options without persisting a configuration. Any preparation failure rejects launch before effects; cancellation stops
|
|
290
|
+
* the remaining inspections.
|
|
291
|
+
*/
|
|
292
|
+
async function inspectStaticAgentConfigurations(executor: WorkflowAgentExecutor, script: string, workflowName: string, signal: AbortSignal, functions: Readonly<Record<string, WorkflowFunction>>): Promise<void> {
|
|
293
|
+
// NOTE: inspection uses the launch cwd; agents inside withWorktree(...) prepare against their checkout at runtime.
|
|
294
|
+
for (const agentOptions of staticAgentPreparationOptions(script, functions)) {
|
|
295
|
+
if (signal.aborted) throw new WorkflowError("CANCELLED", "Workflow launch cancelled");
|
|
296
|
+
await executor.prepare({ label: "agent", workflowName, agentOptions }, undefined, signal, "inspection");
|
|
297
|
+
}
|
|
298
|
+
if (signal.aborted) throw new WorkflowError("CANCELLED", "Workflow launch cancelled");
|
|
299
|
+
}
|
|
300
|
+
|
|
339
301
|
export default function workflowExtension(pi: WorkflowExtensionAPI, home?: string, clipboard = copyToClipboard, transport: AgentTransport = localAgentTransport, agentDir?: string, additionalSkillPaths: readonly string[] = []) {
|
|
340
302
|
beginWorkflowExtensionLoading();
|
|
341
303
|
const registry = loadingRegistry();
|
|
@@ -351,7 +313,7 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
351
313
|
});
|
|
352
314
|
let backgroundWidgetEnabled = true;
|
|
353
315
|
try {
|
|
354
|
-
const globalSettings =
|
|
316
|
+
const globalSettings = resolveWorkflowSettings(process.cwd(), false, workflowSettingsPath(extensionAgentDir)).effective;
|
|
355
317
|
backgroundWidgetEnabled = globalSettings.backgroundWidget ?? true;
|
|
356
318
|
// Registered at load, before Pi resolves --model and restores the session model. Project aliases need project trust, which is unknown here.
|
|
357
319
|
registerAliasVirtualModels(pi, Object.keys(globalSettings.modelAliases ?? {}), workflowSettingsPath(extensionAgentDir));
|
|
@@ -380,6 +342,9 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
380
342
|
return skillPath ? { skillPaths: [skillPath] } : undefined;
|
|
381
343
|
});
|
|
382
344
|
const runs = new Map<string, WorkflowRunRecord>();
|
|
345
|
+
// Extension load failures a headless launcher reported for this host. They hold for the whole process, so every executor,
|
|
346
|
+
// including those that paused resume, cold resume and retry create, keeps failing ownerless options closed.
|
|
347
|
+
let extensionLoad: AgentExecutionRoot["extensionLoad"];
|
|
383
348
|
const deliveryController = new ForegroundDeliveryController({ runs, ...(typeof pi.sendMessage === "function" ? { deliver: (content: string) => { deliver(pi, content); } } : {}) });
|
|
384
349
|
let releaseWorkflowRegistry: (() => void) | undefined;
|
|
385
350
|
const providerRecoveryLane = new SerialLane();
|
|
@@ -758,7 +723,7 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
758
723
|
const persisted = await persistRunState(run.store, run.metadata, (current) => ({ ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? { ...agent, lastEventAt } : agent) }));
|
|
759
724
|
run.update?.(workflowToolUpdate(withLiveActivities(persisted)));
|
|
760
725
|
};
|
|
761
|
-
const result = await run.executor.execute(prompt, { label: options.label, workflowName: run.metadata.name, tuiIndex, tuiLabel: options.requestedLabel ?? options.label, agentNodeId: id, ...(options.extensionSettings === undefined ? {} : { inheritedExtensionSettings: options.extensionSettings }), onProgress, onAttempt, budget, ...(run.providerErrorRecovery ? { providerErrorRecovery: run.providerErrorRecovery } : {}), ...(parentId ? { parent: parentId, cwd: options.cwd, ...(options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}) } : options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}), ...(options.model ? { model: options.model } : {}), ...(options.
|
|
726
|
+
const result = await run.executor.execute(prompt, { label: options.label, workflowName: run.metadata.name, tuiIndex, tuiLabel: options.requestedLabel ?? options.label, agentNodeId: id, ...(options.extensionSettings === undefined ? {} : { inheritedExtensionSettings: options.extensionSettings }), onProgress, onAttempt, budget, ...(run.providerErrorRecovery ? { providerErrorRecovery: run.providerErrorRecovery } : {}), ...(parentId ? { parent: parentId, cwd: options.cwd, ...(options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}) } : options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}), ...(options.model ? { model: options.model } : {}), ...(options.contextFiles ? { contextFiles: options.contextFiles } : {}), tools: options.tools, ...(options.skills ? { skills: options.skills } : {}), ...(options.extensions ? { extensions: options.extensions } : {}), effectiveTools: options.tools, ...(options.schema ? { schema: options.schema } : {}), ...(options.retries === undefined ? {} : { retries: options.retries }), ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.sessionPath ? { sessionPath: options.sessionPath } : {}), ...(options.agentOptions ? { agentOptions: options.agentOptions } : {}), ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}), ...(options.configuration ? { configuration: options.configuration } : {}), ...(options.capabilities ? { capabilities: options.capabilities } : {}), ...(options.projectTrusted === undefined ? {} : { projectTrusted: options.projectTrusted }), ...(options.inheritedExcludeTools ? { inheritedExcludeTools: options.inheritedExcludeTools } : {}), onConfiguration: (configuration) => scheduler.setConfiguration(id, configuration) }, signal, scheduler.toolsFor(id), setSteer, () => { scheduler.cancelChildren(id); scheduler.retry(id); });
|
|
762
727
|
cancelDeferredProgress();
|
|
763
728
|
const before = (await run.store.load()).run;
|
|
764
729
|
await persistAgentAttempts(run.store, id, result.attempts);
|
|
@@ -794,17 +759,16 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
794
759
|
const existing = new Map(current.agents.map((agent) => [agent.id, agent]));
|
|
795
760
|
const agents = ownership.map((node) => {
|
|
796
761
|
const previous = existing.get(node.id);
|
|
797
|
-
const requested = { label: node.options.label, workflowName: run.metadata.name, ...(node.options.skills ? { skills: node.options.skills } : {}), ...(node.options.extensions ? { extensions: node.options.extensions } : {}), ...(node.options.model ? { model: node.options.model } : {}), ...(node.options.
|
|
762
|
+
const requested = { label: node.options.label, workflowName: run.metadata.name, ...(node.options.skills ? { skills: node.options.skills } : {}), ...(node.options.extensions ? { extensions: node.options.extensions } : {}), ...(node.options.model ? { model: node.options.model } : {}), ...(node.options.contextFiles ? { contextFiles: node.options.contextFiles } : {}), ...(node.options.configuration ? { configuration: node.options.configuration } : {}) };
|
|
798
763
|
let effective: { model: ModelSpec; requestedModel?: string; tools: readonly string[] };
|
|
799
764
|
try { effective = { ...run.executor.resolve({ ...requested, effectiveTools: node.options.tools }), tools: node.options.tools }; }
|
|
800
765
|
catch { effective = previous ? { model: previous.model, ...(previous.requestedModel ? { requestedModel: previous.requestedModel } : {}), tools: previous.tools } : { model: node.options.model ? modelSpec(node.options.model, run.model) : run.model, ...(node.options.model ? { requestedModel: node.options.model } : {}), tools: node.options.tools }; }
|
|
801
766
|
const resultPath = !node.parentId && node.options.agentIdentity ? agentIdentityPath(node.options.agentIdentity) : undefined;
|
|
802
|
-
const nodeRole = roleNameOf(node.options.role);
|
|
803
767
|
const now = Date.now();
|
|
804
768
|
const lastEventAt = node.state === "running" ? previous?.state === "running" && previous.lastEventAt !== undefined ? previous.lastEventAt : now : previous?.lastEventAt;
|
|
805
769
|
const startedAt = previous?.startedAt ?? (node.state === "running" ? now : undefined);
|
|
806
770
|
const durationMs = previous?.durationMs ?? (SETTLED_AGENT_STATES.has(node.state) && startedAt !== undefined ? Math.max(0, now - startedAt) : undefined);
|
|
807
|
-
return { ...(previous?.systemPrompt === undefined ? {} : { systemPrompt: previous.systemPrompt }), ...(node.prompt !== undefined ? { prompt: node.prompt } : previous?.prompt !== undefined ? { prompt: previous.prompt } : {}), id: node.id, name: node.label, ...(node.options.requestedLabel ? { label: node.options.requestedLabel } : {}), path: node.id, state: node.state, ...(node.parentId ? { parentId: node.parentId } : {}), structuralPath: [...(node.options.agentIdentity?.structuralPath ?? [])], ...(resultPath ? { resultPath } : {}), ...(node.options.parentBreadcrumb ? { parentBreadcrumb: node.options.parentBreadcrumb } : {}), ...(node.options.worktreeOwner ? { worktreeOwner: node.options.worktreeOwner } : {}), ...(node.options.agentIdentity?.handle === undefined ? {} : { handle: node.options.agentIdentity.handle, ...(node.options.agentIdentity.turn === undefined ? {} : { turn: node.options.agentIdentity.turn }), ...(node.options.continuity ? { continuity: node.options.continuity } : {}) }), ...(
|
|
771
|
+
return { ...(previous?.systemPrompt === undefined ? {} : { systemPrompt: previous.systemPrompt }), ...(node.prompt !== undefined ? { prompt: node.prompt } : previous?.prompt !== undefined ? { prompt: previous.prompt } : {}), id: node.id, name: node.label, ...(node.options.requestedLabel ? { label: node.options.requestedLabel } : {}), path: node.id, state: node.state, ...(node.parentId ? { parentId: node.parentId } : {}), structuralPath: [...(node.options.agentIdentity?.structuralPath ?? [])], ...(resultPath ? { resultPath } : {}), ...(node.options.parentBreadcrumb ? { parentBreadcrumb: node.options.parentBreadcrumb } : {}), ...(node.options.worktreeOwner ? { worktreeOwner: node.options.worktreeOwner } : {}), ...(node.options.agentIdentity?.handle === undefined ? {} : { handle: node.options.agentIdentity.handle, ...(node.options.agentIdentity.turn === undefined ? {} : { turn: node.options.agentIdentity.turn }), ...(node.options.continuity ? { continuity: node.options.continuity } : {}) }), ...(effective.requestedModel ? { requestedModel: effective.requestedModel } : {}), model: effective.model, tools: effective.tools, attempts: previous?.attempts ?? 0, ...(startedAt === undefined ? {} : { startedAt }), ...(durationMs === undefined ? {} : { durationMs }), ...(previous?.attemptDetails ? { attemptDetails: previous.attemptDetails } : {}), ...(previous?.accounting ? { accounting: previous.accounting } : {}), ...(previous?.toolCalls ? { toolCalls: previous.toolCalls } : {}), ...(previous?.activity ? { activity: previous.activity } : {}), ...(lastEventAt === undefined ? {} : { lastEventAt }) };
|
|
808
772
|
});
|
|
809
773
|
return { ...current, agents };
|
|
810
774
|
});
|
|
@@ -1028,20 +992,7 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1028
992
|
});
|
|
1029
993
|
catalogRegistered = true;
|
|
1030
994
|
};
|
|
1031
|
-
const createAgentExecutor = (root: Omit<AgentExecutionRoot, "agentDir" | "agentSetupHooks">) => new WorkflowAgentExecutor({ ...root, agentDir: extensionAgentDir, ...(additionalSkillPaths.length ? { additionalSkillPaths } : {}), agentSetupHooks: registry.agentSetupHooks(), validateExtensionSettings: registry.validateExtensionSettings, onResourceWarning: (message) => { deliverWarning(pi, message); }, onAgentSettings: (agentId, settings) => { scheduler.setExtensionSettings(agentId, settings); } }, transport);
|
|
1032
|
-
// Resumed runs keep the role definitions frozen in their snapshot and fall back to the current definitions for roles a
|
|
1033
|
-
// registered function reaches for the first time after the restart; those are captured into the snapshot like at launch.
|
|
1034
|
-
const resumeRoles = (store: RunStore, snapshot: Readonly<LaunchSnapshot>, cwd: string, trustedProject: boolean) => {
|
|
1035
|
-
let current: Readonly<Record<string, AgentDefinition>> = {};
|
|
1036
|
-
let project: Readonly<Record<string, AgentDefinition>> = {};
|
|
1037
|
-
try {
|
|
1038
|
-
current = loadAgentDefinitions(cwd, extensionAgentDir, trustedProject, registry.roleDirectoryRegistrations());
|
|
1039
|
-
project = trustedProject ? loadProjectAgentDefinitions(cwd) : {};
|
|
1040
|
-
} catch { /* Unreadable current roles leave the resume with the snapshot roles, as before. */ }
|
|
1041
|
-
const definitions = { ...current, ...(snapshot.roles ?? {}) };
|
|
1042
|
-
const captured = roleCapture(store, snapshot, definitions, project);
|
|
1043
|
-
return { definitions, capture: (role: string, model: ModelSpec) => captured.capture(role, model) };
|
|
1044
|
-
};
|
|
995
|
+
const createAgentExecutor = (root: Omit<AgentExecutionRoot, "agentDir" | "agentSetupHooks">) => new WorkflowAgentExecutor({ ...root, ...(extensionLoad ? { extensionLoad } : {}), agentDir: extensionAgentDir, ...(additionalSkillPaths.length ? { additionalSkillPaths } : {}), agentPreparationHooks: registry.agentPreparationHooks(), agentSetupHooks: registry.agentSetupHooks(), validateExtensionSettings: registry.validateExtensionSettings, onResourceWarning: root.onResourceWarning ?? ((message) => { deliverWarning(pi, message); }), onAgentSettings: (agentId, settings) => { scheduler.setExtensionSettings(agentId, settings); }, onAgentCapabilities: (agentId, capabilities, projectTrusted) => { scheduler.setAttemptCapabilities(agentId, capabilities, projectTrusted); } }, transport);
|
|
1045
996
|
const activeSnapshotTools = (tools: readonly string[], active: ReadonlySet<string> | "session") => active === "session"
|
|
1046
997
|
? new Set(tools.filter((tool) => reachableTools(pi).includes(tool) && tool !== "workflow_catalog"))
|
|
1047
998
|
: new Set(tools.filter((tool) => active.has(tool) || tool === "workflow_catalog"));
|
|
@@ -1053,6 +1004,7 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1053
1004
|
modelRegistry?: ModelRegistryCapability | undefined;
|
|
1054
1005
|
signal: AbortSignal;
|
|
1055
1006
|
resolvedAliases?: Readonly<Record<string, string>>;
|
|
1007
|
+
resolvedDynamicAliasNames?: readonly string[];
|
|
1056
1008
|
blockedAliases?: ReadonlySet<string>;
|
|
1057
1009
|
blockedAliasTargets?: Readonly<Record<string, string>>;
|
|
1058
1010
|
withPreflight: boolean;
|
|
@@ -1065,26 +1017,27 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1065
1017
|
const extensionSettings = input.snapshot.settings.extensionSettings;
|
|
1066
1018
|
const extensionSettingsPath = input.snapshot.settingsSources?.extensionSettings ?? input.snapshot.settingsPath ?? settingsPath;
|
|
1067
1019
|
registry.validateExtensionSettings(extensionSettings, { source: "effective", cwd: input.cwd, projectTrusted: input.trustedProject, settingsPath: extensionSettingsPath });
|
|
1068
|
-
for (const [role, definition] of Object.entries(input.snapshot.roles ?? {})) registry.validateExtensionSettings(mergeWorkflowExtensionSettings(extensionSettings, definition.extensionSettings), { source: "role", cwd: input.cwd, projectTrusted: input.trustedProject, settingsPath: extensionSettingsPath, role });
|
|
1069
1020
|
const currentPolicy = resolveAgentResourcePolicy(input.cwd, input.trustedProject, settingsPath);
|
|
1070
1021
|
const staticAliases = resolution.effective.modelAliases ?? {};
|
|
1071
1022
|
const previousAliases = input.snapshot.modelAliases ?? input.snapshot.settings.modelAliases ?? {};
|
|
1072
1023
|
const inventory = modelInventory(input.rootModel, input.modelRegistry);
|
|
1073
1024
|
const knownModels = input.modelRegistry ? inventory.knownModels : new Set([...input.snapshot.models, ...inventory.knownModels]);
|
|
1074
1025
|
const availableModels = input.modelRegistry ? inventory.availableModels : new Set([...input.snapshot.models, ...inventory.availableModels]);
|
|
1075
|
-
const
|
|
1026
|
+
const resolved = input.resolvedAliases ? { aliases: input.resolvedAliases, dynamicNames: input.resolvedDynamicAliasNames ?? [] } : await resolveLaunchAliases(registry, staticAliases, { cwd: input.cwd, projectTrusted: input.trustedProject, rootModel: input.rootModel, knownModels, availableModels, signal: input.signal }, availableModels, knownModels, settingsPath);
|
|
1027
|
+
const currentAliases = resolved.aliases;
|
|
1028
|
+
const dynamicAliasNames = resolved.dynamicNames;
|
|
1076
1029
|
const blockedAliases = input.blockedAliases ?? new Set(Object.keys(previousAliases).filter((name) => !Object.prototype.hasOwnProperty.call(currentAliases, name)));
|
|
1077
1030
|
const blockedAliasTargets = input.blockedAliasTargets ?? Object.fromEntries(Object.entries(previousAliases).filter(([name]) => !Object.prototype.hasOwnProperty.call(currentAliases, name)));
|
|
1078
1031
|
const script = input.withPreflight ? input.snapshot.script : undefined;
|
|
1079
1032
|
if (script !== undefined) {
|
|
1080
1033
|
const resumeAliases = { ...previousAliases, ...currentAliases };
|
|
1081
|
-
preflight(script, { models: availableModels, tools: active,
|
|
1034
|
+
preflight(script, { models: availableModels, tools: active, modelAliases: resumeAliases, knownModels, settingsPath, skipModelAvailability: true }, input.snapshot.schemas, input.snapshot.metadata, true);
|
|
1082
1035
|
}
|
|
1083
1036
|
const refreshed = resumedSnapshotSettings(input.snapshot, resolution, currentAliases);
|
|
1084
1037
|
const snapshot = createLaunchSnapshot({ ...input.snapshot, settingsPath, ...refreshed, modelAliases: currentAliases });
|
|
1085
|
-
return { active, settingsPath, resolution, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, blockedAliases, blockedAliasTargets, snapshot, script };
|
|
1038
|
+
return { active, settingsPath, resolution, currentPolicy, previousAliases, knownModels, availableModels, currentAliases, dynamicAliasNames, blockedAliases, blockedAliasTargets, snapshot, script };
|
|
1086
1039
|
};
|
|
1087
|
-
const workflowAgentHandler = (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string
|
|
1040
|
+
const workflowAgentHandler = (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string) => {
|
|
1088
1041
|
// Replay lookup and spawn are serialised per run so concurrent agent(...) calls reach the scheduler in call order.
|
|
1089
1042
|
const admission = new SerialLane();
|
|
1090
1043
|
return async (prompt: string, options: Readonly<Record<string, JsonValue>>, agentSignal: AbortSignal, identity: AgentIdentity) => {
|
|
@@ -1096,20 +1049,21 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1096
1049
|
if (replayed) return { replayed };
|
|
1097
1050
|
const worktree = agentWorktree(identity);
|
|
1098
1051
|
const agentCwd = worktree.worktreeOwner ? (await persistWorktree(store, metadata, worktree.worktreeOwner)).cwd : cwd;
|
|
1099
|
-
const role = typeof options.role === "string" ? options.role : undefined;
|
|
1100
1052
|
const model = typeof options.model === "string" ? options.model : undefined;
|
|
1101
1053
|
const requestedLabel = typeof options.label === "string" ? options.label : undefined;
|
|
1102
1054
|
const skills = Array.isArray(options.skills) ? options.skills as string[] : undefined;
|
|
1103
1055
|
const extensions = Array.isArray(options.extensions) ? options.extensions as string[] : undefined;
|
|
1104
1056
|
const contextFiles = Array.isArray(options.contextFiles) && options.contextFiles.every(isContextFileScope) ? options.contextFiles : undefined;
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1057
|
+
// A paused resume replaces the run executor with refreshed aliases; new identities must prepare against it.
|
|
1058
|
+
const current = runs.get(runId)?.executor ?? executor;
|
|
1059
|
+
const configuration = await current.prepare({ label: requestedLabel ?? "agent", workflowName: metadata.name, agentOptions: options, agentIdentity: identity, ...(worktree.worktreeOwner ? { worktreeOwner: worktree.worktreeOwner } : {}) }, agentCwd, agentSignal);
|
|
1060
|
+
const resolved = configuration;
|
|
1061
|
+
const label = requestedLabel ?? configuration.label ?? resolved.model.model;
|
|
1108
1062
|
const tools = resolved.tools;
|
|
1109
1063
|
const schema = object(options.outputSchema) ? options.outputSchema : undefined;
|
|
1110
1064
|
const sessionPath = identity.handle !== undefined && identity.turn !== undefined ? await handleTurnInput(store, identity.handle, identity.turn) : undefined;
|
|
1111
1065
|
const continuity = identity.handle === undefined ? undefined : sessionPath ? "continued" as const : "fresh" as const;
|
|
1112
|
-
return { spawned: scheduler.spawn(runId, prompt, { label, ...(requestedLabel ? { requestedLabel } : {}), ...(identity.parentBreadcrumb ? { parentBreadcrumb: identity.parentBreadcrumb } : {}), cwd: agentCwd, tools, ...(skills ? { skills } : {}), ...(extensions ? { extensions } : {}), ...worktree, ...(model ? { model } : {}), ...(
|
|
1066
|
+
return { spawned: scheduler.spawn(runId, prompt, { label, ...(requestedLabel ? { requestedLabel } : {}), ...(identity.parentBreadcrumb ? { parentBreadcrumb: identity.parentBreadcrumb } : {}), cwd: agentCwd, tools, ...(skills ? { skills } : {}), ...(extensions ? { extensions } : {}), ...worktree, ...(model ? { model } : {}), ...(contextFiles ? { contextFiles } : {}), ...(schema ? { schema } : {}), ...(typeof options.retries === "number" ? { retries: options.retries } : {}), ...(positiveInteger(options.timeoutMs) || options.timeoutMs === null ? { timeoutMs: options.timeoutMs } : {}), ...(sessionPath ? { sessionPath } : {}), ...(continuity ? { continuity } : {}), agentOptions: options, agentIdentity: identity, configuration }) };
|
|
1113
1067
|
});
|
|
1114
1068
|
if ("replayed" in admitted) return admitted.replayed.value;
|
|
1115
1069
|
const { spawned } = admitted;
|
|
@@ -1124,7 +1078,7 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1124
1078
|
};
|
|
1125
1079
|
};
|
|
1126
1080
|
const recovery = createWorkflowRecovery({
|
|
1127
|
-
pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, coordinateRunMutation, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue: resumeLaunchPrologue,
|
|
1081
|
+
pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, coordinateRunMutation, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue: resumeLaunchPrologue, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver: (content) => { deliver(pi, content); }, deliverTerminal: deliveryController.deliverTerminal, workflowToolUpdate, registry, modelSpec,
|
|
1128
1082
|
});
|
|
1129
1083
|
const resumeSelectedWorkflow = async (runId: string, foreground: boolean, context: unknown, budgetPatch?: unknown): Promise<{ workflowName: string; state: "running" | "completed" | "awaiting_approval"; attached: boolean; value?: JsonValue }> => {
|
|
1130
1084
|
const run = runs.get(runId);
|
|
@@ -1143,9 +1097,8 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1143
1097
|
if (!foreground && wasAttached) await deliveryController.moveForegroundToBackground(runId);
|
|
1144
1098
|
if (foreground && !wasAttached) {
|
|
1145
1099
|
run.foreground = true;
|
|
1146
|
-
const loaded = await run.store.load();
|
|
1147
1100
|
await persistRunState(run.store, run.metadata, (current) => ({ ...current, delivery: { ...(current.delivery ?? {}), mode: "foreground", state: "attached" } }));
|
|
1148
|
-
await run.store.
|
|
1101
|
+
await run.store.setLaunchMode("foreground");
|
|
1149
1102
|
} else if (!foreground) run.foreground = false;
|
|
1150
1103
|
else run.foreground = true;
|
|
1151
1104
|
await recovery.refreshPausedRunAliases(run, { ...recoveryContext, projectTrusted: projectTrusted(context) });
|
|
@@ -1251,10 +1204,9 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1251
1204
|
const budgetRuntime = new WorkflowBudgetRuntime(budget, loaded.run.budgetVersion ?? 1, loaded.run.usage, loaded.run.budgetEvents, { active: loaded.run.state === "running" });
|
|
1252
1205
|
const lifecycle = lifecycleFor(store, loaded.run.state, loaded.snapshot.metadata);
|
|
1253
1206
|
const providerPause = async () => { deliver(pi, `Workflow ${loaded.snapshot.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
|
|
1254
|
-
const roleDefinitions = loaded.snapshot.roles ?? {};
|
|
1255
1207
|
const abortController = new AbortController();
|
|
1256
1208
|
const providerErrorRecovery = createProviderErrorRecovery(ctx, new Set(loaded.snapshot.models), () => { abortController.abort(); });
|
|
1257
|
-
runs.set(runId, { executor: createAgentExecutor({ cwd: ctx.cwd, model, tools: activeSnapshotTools(loaded.snapshot.tools, "session"), resourceSelectors: snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir)).effective, extensionSettings: loaded.snapshot.settings.extensionSettings, availableModels: new Set(loaded.snapshot.models), knownModels: new Set(loaded.snapshot.models), ...(loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases ? { modelAliases: loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases } : {}), ...(loaded.snapshot.settingsSources?.modelAliases ? { settingsPath: loaded.snapshot.settingsSources.modelAliases } : loaded.snapshot.settingsPath ? { settingsPath: loaded.snapshot.settingsPath } : {}),
|
|
1209
|
+
runs.set(runId, { executor: createAgentExecutor({ cwd: ctx.cwd, projectTrusted: projectTrusted(ctx), model, tools: activeSnapshotTools(loaded.snapshot.tools, "session"), resourceSelectors: snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir)).effective, extensionSettings: loaded.snapshot.settings.extensionSettings, availableModels: new Set(loaded.snapshot.models), knownModels: new Set(loaded.snapshot.models), ...(loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases ? { modelAliases: loaded.snapshot.modelAliases ?? loaded.snapshot.settings.modelAliases } : {}), ...(loaded.snapshot.settingsSources?.modelAliases ? { settingsPath: loaded.snapshot.settingsSources.modelAliases } : loaded.snapshot.settingsPath ? { settingsPath: loaded.snapshot.settingsPath } : {}), runStore: store, providerPause, agentResourcePolicy: frozenResourcePolicy(snapshotResourcePolicy(loaded.snapshot, store.cwd, projectTrusted(ctx), workflowSettingsPath(extensionAgentDir))) }), store, metadata: loaded.snapshot.metadata, model, lifecycle, budget: budgetRuntime, abortController, projectTrusted: () => projectTrusted(ctx), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}) });
|
|
1258
1210
|
for (const checkpoint of await store.awaitingCheckpoints()) deliver(pi, `Workflow ${loaded.snapshot.metadata.name} checkpoint ${checkpoint.name}: ${checkpoint.prompt}\nContext: ${JSON.stringify(checkpoint.context)}\nRespond with workflow_respond.`);
|
|
1259
1211
|
for (const decision of await store.pendingWorkflowDecisions()) deliver(pi, recovery.budgetDecisionDelivery(loaded.snapshot.metadata, decision));
|
|
1260
1212
|
scheduler.restoreRun(runId, loaded.snapshot.settings.concurrency, loaded.snapshot.identityVersion === LAUNCH_SNAPSHOT_IDENTITY_VERSION ? await store.loadOwnership() : [], () => runs.get(runId)?.budget.checkAgentLaunch(), loaded.snapshot.settings.extensionSettings);
|
|
@@ -1283,13 +1235,6 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1283
1235
|
throw error;
|
|
1284
1236
|
}
|
|
1285
1237
|
});
|
|
1286
|
-
pi.on("before_agent_start", (event, ctx) => {
|
|
1287
|
-
if (!pi.getActiveTools().includes("workflow")) return;
|
|
1288
|
-
const roles = Object.entries(loadAgentDefinitions(ctx.cwd, extensionAgentDir, projectTrusted(ctx), typeof registry.roleDirectoryRegistrations === "function" ? registry.roleDirectoryRegistrations() : undefined)).filter(([, definition]) => definition.description);
|
|
1289
|
-
if (!roles.length) return;
|
|
1290
|
-
const content = `Workflow role descriptions:\n${roles.map(([name, definition]) => `- \`${name}\`: ${String(definition.description)}`).join("\n")}`;
|
|
1291
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${content}` };
|
|
1292
|
-
});
|
|
1293
1238
|
const workflowTool: ToolDefinition<typeof WORKFLOW_TOOL_PARAMETERS, WorkflowToolResult, WorkflowProgressRenderState> = {
|
|
1294
1239
|
name: "workflow",
|
|
1295
1240
|
label: WORKFLOW_TOOL_LABEL,
|
|
@@ -1332,25 +1277,31 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1332
1277
|
if (launch.resolution.global.extensionSettings !== undefined) registry.validateExtensionSettings(launch.resolution.global.extensionSettings, { source: "global", cwd: ctx.cwd, projectTrusted: trustedProject, settingsPath: launch.resolution.globalSettingsPath });
|
|
1333
1278
|
if (trustedProject && launch.resolution.project.extensionSettings !== undefined) registry.validateExtensionSettings(launch.resolution.project.extensionSettings, { source: "project", cwd: ctx.cwd, projectTrusted: true, settingsPath: launch.resolution.projectSettingsPath });
|
|
1334
1279
|
const validated = validateWorkflowLaunchWithRegistry(params, { cwd: ctx.cwd, agentDir: extensionAgentDir, projectTrusted: trustedProject, availableModels, rootTools: new Set(rootTools), modelAliases, knownModels, settingsPath, ...(settings.extensionSettings === undefined ? {} : { extensionSettings: settings.extensionSettings }) }, registry);
|
|
1335
|
-
const { script, checked
|
|
1280
|
+
const { script, checked } = validated;
|
|
1336
1281
|
const toolContext = scriptToolContext(ctx);
|
|
1337
1282
|
validateScriptToolReferences(script, toolContext);
|
|
1283
|
+
// Headless hosts that tolerated extension load failures report them with the extensions they loaded.
|
|
1284
|
+
const strings = (value: unknown) => Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : [];
|
|
1285
|
+
const loadErrors = object(ctx) ? strings(ctx.extensionLoadErrors) : [];
|
|
1286
|
+
if (loadErrors.length) extensionLoad = Object.freeze({ errors: loadErrors, loaded: object(ctx) ? strings(ctx.loadedExtensionPaths) : [] });
|
|
1287
|
+
const executorRoot = { cwd: ctx.cwd, projectTrusted: launch.resourcePolicy.projectTrusted, model: rootModel, tools: new Set(rootTools), resourceSelectors: launch.resourcePolicy.effective, extensionSettings: settings.extensionSettings, availableModels, knownModels, modelAliases, dynamicModelAliasNames: resolvedAliases.dynamicNames, settingsPath, agentResourcePolicy: frozenResourcePolicy(launch.resourcePolicy) };
|
|
1288
|
+
// Runtime emits the same resource warnings, so inspection stays silent.
|
|
1289
|
+
await inspectStaticAgentConfigurations(createAgentExecutor({ ...executorRoot, onResourceWarning: () => undefined }), script, checked.metadata.name, runController.signal, registry.functions());
|
|
1338
1290
|
await ensureSessionLease(ctx.cwd, ctx.sessionManager.getSessionId());
|
|
1291
|
+
// Cancellation can arrive while the lease or a queued parent validation is pending; no run may be created after it.
|
|
1292
|
+
const assertLaunchActive = () => { if (runController.signal.aborted) throw new WorkflowError("CANCELLED", "Workflow launch cancelled"); };
|
|
1293
|
+
assertLaunchActive();
|
|
1339
1294
|
const runId = randomUUID();
|
|
1340
1295
|
const args = params.args ?? null;
|
|
1341
1296
|
encoded(args);
|
|
1342
1297
|
const runContext = workflowRunContext(ctx.cwd, ctx.sessionManager.getSessionId(), runId, checked.metadata, args, runController.signal);
|
|
1343
1298
|
const store = new RunStore(ctx.cwd, ctx.sessionManager.getSessionId(), runId, home);
|
|
1344
1299
|
const parentRunId = params.parentRunId;
|
|
1345
|
-
const
|
|
1346
|
-
const
|
|
1347
|
-
const roleModels = roleNames.flatMap((role) => { const model = agentDefinitions[role]?.model; return model ? [modelCapability(model, modelAliases, knownModels, settingsPath)] : []; });
|
|
1348
|
-
const snapshotModels = [...new Set([rootModelName, ...checked.referenced.models, ...roleModels])];
|
|
1349
|
-
const snapshot = createLaunchSnapshot({ script, args, metadata: checked.metadata, launchMode: params.foreground ? "foreground" : "background", settings, settingsPath, settingsSources: { ...launch.resolution.sources, concurrency: params.concurrency === undefined ? launch.resolution.sources.concurrency : "per-run options" }, ...(Object.keys(modelAliases).length ? { modelAliases } : {}), ...(budget ? { budget } : {}), ...(checked.referenced.phases.length ? { phases: checked.referenced.phases } : {}), models: snapshotModels, tools: rootTools, agentTypes: checked.referenced.agentTypes, roles, projectRoles, schemas: checked.schemas });
|
|
1350
|
-
const capturedRoles = roleCapture(store, snapshot, agentDefinitions, projectAgentDefinitions);
|
|
1300
|
+
const snapshotModels = [...new Set([rootModelName, ...checked.referenced.models])];
|
|
1301
|
+
const snapshot = createLaunchSnapshot({ script, args, metadata: checked.metadata, launchMode: params.foreground ? "foreground" : "background", settings, settingsPath, settingsSources: { ...launch.resolution.sources, concurrency: params.concurrency === undefined ? launch.resolution.sources.concurrency : "per-run options" }, ...(Object.keys(modelAliases).length ? { modelAliases } : {}), ...(budget ? { budget } : {}), ...(checked.referenced.phases.length ? { phases: checked.referenced.phases } : {}), models: snapshotModels, tools: rootTools, agentConfigurations: {}, schemas: checked.schemas });
|
|
1351
1302
|
const budgetRuntime = new WorkflowBudgetRuntime(budget);
|
|
1352
1303
|
const initialBudget = budgetRuntime.snapshot();
|
|
1353
|
-
const createRun = () => store.create({ id: runId, workflowName: checked.metadata.name, cwd: ctx.cwd, sessionId: ctx.sessionManager.getSessionId(), state: "running", ...(parentRunId !== undefined ? { parentRunId } : {}), agents: [], agentSessions: [], delivery: params.foreground ? { mode: "foreground", state: "attached", toolCallId } : { mode: "background", state: "pending" }, ...(budget ? { budget } : {}), budgetVersion: 1, ...initialBudget }, snapshot);
|
|
1304
|
+
const createRun = () => { assertLaunchActive(); return store.create({ id: runId, workflowName: checked.metadata.name, cwd: ctx.cwd, sessionId: ctx.sessionManager.getSessionId(), state: "running", ...(parentRunId !== undefined ? { parentRunId } : {}), agents: [], agentSessions: [], delivery: params.foreground ? { mode: "foreground", state: "attached", toolCallId } : { mode: "background", state: "pending" }, ...(budget ? { budget } : {}), budgetVersion: 1, ...initialBudget }, snapshot); };
|
|
1354
1305
|
// Parent validation and persistence share the mutation lane so a manual deletion cannot remove the parent between the two.
|
|
1355
1306
|
if (parentRunId !== undefined) await coordinateRunMutation(async () => { await store.validateParentRun(parentRunId); await createRun(); });
|
|
1356
1307
|
else await createRun();
|
|
@@ -1371,7 +1322,7 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1371
1322
|
delivery.detached = true;
|
|
1372
1323
|
const activeRun = runs.get(runId);
|
|
1373
1324
|
if (activeRun) { activeRun.foreground = false; delete activeRun.update; }
|
|
1374
|
-
await store.
|
|
1325
|
+
await store.setLaunchMode("background");
|
|
1375
1326
|
for (const checkpoint of await store.awaitingCheckpoints()) deliverBackgroundCheckpoint(checked.metadata.name, runId, checkpoint);
|
|
1376
1327
|
signal?.removeEventListener("abort", onForegroundAbort);
|
|
1377
1328
|
const run = (await store.load()).run;
|
|
@@ -1386,12 +1337,12 @@ export default function workflowExtension(pi: WorkflowExtensionAPI, home?: strin
|
|
|
1386
1337
|
const backgroundLaunch = !params.foreground;
|
|
1387
1338
|
const providerPause = async () => { if (!foregroundAttached) deliver(pi, `Workflow ${checked.metadata.name} paused: provider limit.`); await lifecycle.providerPause(); };
|
|
1388
1339
|
const providerErrorRecovery = createProviderErrorRecovery(ctx, availableModels, () => { runController.abort(); });
|
|
1389
|
-
const executor = createAgentExecutor({
|
|
1340
|
+
const executor = createAgentExecutor({ ...executorRoot, runStore: store, providerPause, runContext });
|
|
1390
1341
|
const runRecord: WorkflowRunRecord = { executor, store, metadata: checked.metadata, model: rootModel, lifecycle, budget: budgetRuntime, abortController: runController, foreground: foregroundAttached, projectTrusted: () => projectTrusted(ctx), checkpointResolvers: new Map(), ...(providerErrorRecovery ? { providerErrorRecovery } : {}), ...(params.foreground && onUpdate ? { update: onUpdate } : {}) };
|
|
1391
1342
|
runs.set(runId, runRecord);
|
|
1392
1343
|
if (params.foreground && onUpdate) onUpdate(workflowToolUpdate((await store.load()).run));
|
|
1393
1344
|
scheduler.addRun(runId, settings.concurrency, () => runs.get(runId)?.budget.checkAgentLaunch(), settings.extensionSettings);
|
|
1394
|
-
const execution = runWorkflow(script, args, withWorkflowFunctions({ tool: (identifier, toolArgs, signal, identity) => toolForRun(store, lifecycle, toolContext, identifier, toolArgs, signal, identity), shell: (command, options, signal, identity) => shellForRun(store, checked.metadata, lifecycle, command, options, signal, identity), agent: workflowAgentHandler(store, checked.metadata, lifecycle, executor, ctx.cwd, runId
|
|
1345
|
+
const execution = runWorkflow(script, args, withWorkflowFunctions({ tool: (identifier, toolArgs, signal, identity) => toolForRun(store, lifecycle, toolContext, identifier, toolArgs, signal, identity), shell: (command, options, signal, identity) => shellForRun(store, checked.metadata, lifecycle, command, options, signal, identity), agent: workflowAgentHandler(store, checked.metadata, lifecycle, executor, ctx.cwd, runId), worktree: async (owner) => resolveWorktree(store, checked.metadata, owner), checkpoint: checkpointBridge(runId, store, checked.metadata, () => runs.get(runId)?.foreground ?? foregroundAttached, ctx.hasUI ? ctx.ui : undefined, headless), phase: phaseBridge(store, checked.metadata, lifecycle), log: logBridge(store, lifecycle, checked.metadata.name) }, store, runContext, registry, settings.extensionSettings), runController.signal);
|
|
1395
1346
|
runRecord.execution = execution;
|
|
1396
1347
|
await eventPublisher.runStarted(store, checked.metadata);
|
|
1397
1348
|
const finish = execution.result.then(async (value) => {
|
|
@@ -1653,9 +1604,7 @@ async function handleTurnInput(store: RunStore, handle: string, turn: number): P
|
|
|
1653
1604
|
return target;
|
|
1654
1605
|
}
|
|
1655
1606
|
|
|
1656
|
-
|
|
1657
|
-
return label ?? role ?? model.model;
|
|
1658
|
-
}
|
|
1607
|
+
|
|
1659
1608
|
|
|
1660
1609
|
function modelSpec(value: string, fallback: ModelSpec): ModelSpec {
|
|
1661
1610
|
try {
|
package/src/index.ts
CHANGED
|
@@ -3,10 +3,9 @@ export * from "./package-identity.js";
|
|
|
3
3
|
export { createToolTimingExtension, TOOL_TIMING_ENTRY_TYPE } from "./tool-timing.js";
|
|
4
4
|
export type { ToolTimingEntry } from "./tool-timing.js";
|
|
5
5
|
export * from "./utils.js";
|
|
6
|
-
export { canonicalPath, sameFilesystemPath } from "./paths.js";
|
|
6
|
+
export { canonicalPath, extensionIdentity, sameFilesystemPath } from "./paths.js";
|
|
7
7
|
export * from "./budget.js";
|
|
8
8
|
export * from "./validation.js";
|
|
9
|
-
export * from "./roles.js";
|
|
10
9
|
export * from "./registry.js";
|
|
11
10
|
export * from "./execution.js";
|
|
12
11
|
export * from "./host.js";
|
package/src/paths.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { readdir } from "node:fs/promises";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
|
-
import { basename, join, resolve } from "node:path";
|
|
6
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
7
|
import { WorkflowError } from "./types.js";
|
|
7
8
|
import { isNodeError } from "./utils.js";
|
|
8
9
|
|
|
9
10
|
export function safePart(value: string): string { return value.replace(/[^a-zA-Z0-9._-]/g, "_"); }
|
|
10
11
|
|
|
12
|
+
export function canonicalPath(path: string): string { const absolute = resolve(path); try { return realpathSync(absolute); } catch { return absolute; } }
|
|
13
|
+
export function extensionIdentity(path: string): string { return path.startsWith("builtin:") ? path : canonicalPath(path.startsWith("file:") ? fileURLToPath(path) : path); }
|
|
14
|
+
export function sameFilesystemPath(left: string, right: string): boolean { return canonicalPath(left) === canonicalPath(right); }
|
|
11
15
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* guessing at the identity of an object that does not exist.
|
|
16
|
+
* The path Pi's resource loader uses for a resource path an extension returns: trimmed, `~`, `~/` and `file://` expanded,
|
|
17
|
+
* relative to the loader cwd. Synthetic `<...>` and `builtin:` paths stay as they are.
|
|
15
18
|
*/
|
|
16
|
-
export function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
export function piResourcePath(path: string, cwd: string): string {
|
|
20
|
+
//NOTE: Pi also rewrites Windows shell paths and `~\` on win32; those forms are not mirrored here and fail closed.
|
|
21
|
+
if (path.startsWith("<") || path.startsWith("builtin:")) return path;
|
|
22
|
+
const trimmed = path.trim();
|
|
23
|
+
const expanded = trimmed === "~" ? homedir() : trimmed.startsWith("~/") ? join(homedir(), trimmed.slice(2)) : /^file:\/\//.test(trimmed) ? fileURLToPath(trimmed) : trimmed;
|
|
24
|
+
return isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
|
|
25
|
+
}
|
|
26
|
+
/** Pi attributes a loaded resource to a contributed path by lexical containment of resolved paths, without following symlinks. */
|
|
27
|
+
export function piResourceContains(root: string, path: string): boolean {
|
|
28
|
+
const relativePath = relative(resolve(root), resolve(path));
|
|
29
|
+
return relativePath === "" || (relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));
|
|
19
30
|
}
|
|
20
|
-
|
|
21
|
-
/** Pi names built-in extensions `builtin:<name>` instead of by a path; other extensions are files. */
|
|
22
|
-
export function extensionIdentity(path: string): string { return path.startsWith("builtin:") ? path : canonicalPath(path); }
|
|
23
|
-
|
|
24
|
-
export function sameFilesystemPath(left: string, right: string): boolean { return canonicalPath(left) === canonicalPath(right); }
|
|
25
31
|
export function workflowProjectSettingsPath(cwd: string): string { return join(cwd, ".pi", "pi-extensible-workflows", "settings.json"); }
|
|
26
32
|
|
|
27
33
|
export function projectStorageKey(cwd: string): string {
|
package/src/persistence.ts
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
} from "./paths.js";
|
|
5
5
|
export { hasLiveSessionLease, SessionLease, acquireSessionLease, listRunIds, processAlive } from "./session-lease.js";
|
|
6
6
|
export { RunStore } from "./store.js";
|
|
7
|
+
export { decodePreparedAgentConfiguration } from "./decoders.js";
|
|
7
8
|
export {
|
|
8
9
|
EffectiveSystemPrompt, PersistedRun, RunSummaryAgent, RunSummaryArtifacts, RunSummary,
|
|
9
10
|
CompletedOperation, AwaitingCheckpoint, PendingWorkflowDecision, PersistedOwnershipNode,
|
package/src/pi-runtime-runner.ts
CHANGED
|
@@ -172,7 +172,7 @@ export class PiRuntimeAgentRunner implements RuntimeAgentRunner {
|
|
|
172
172
|
let structuredResult: RuntimeJsonValue | undefined;
|
|
173
173
|
const resultTool = prepared.resultTool;
|
|
174
174
|
if (!resultTool) throw new WorkflowError("INTERNAL_ERROR", "Workflow agent session is missing its workflow_result tool");
|
|
175
|
-
const preparedForSession = preparedForRuntimeRequest(prepared, request.customTools, request.signal, (value) => { structuredResult = value; }, resultTool);
|
|
175
|
+
const preparedForSession = Object.freeze({ ...preparedForRuntimeRequest(prepared, request.customTools, request.signal, (value) => { structuredResult = value; }, resultTool), ...(request.turnControl ? { turnControl: request.turnControl } : {}) });
|
|
176
176
|
const callbacks = this.#options.callbacks;
|
|
177
177
|
const handoff = this.#options.handoff;
|
|
178
178
|
let adapter: PiRuntimeSessionAdapter | undefined;
|
|
@@ -364,6 +364,8 @@ export class PiRuntimeAgentRunner implements RuntimeAgentRunner {
|
|
|
364
364
|
throwIfProgressFailed();
|
|
365
365
|
beginTurn(false);
|
|
366
366
|
throwIfTurnPolicyFailed();
|
|
367
|
+
// NOTE: A transport that already sent turnControl (Herdr, in its pane launch prompt) reports turnControlDelivered; steering its suspended local session would fail.
|
|
368
|
+
if (request.turnControl && !session.turnControlDelivered) await activeSession.steer(request.turnControl);
|
|
367
369
|
await promptAndRecover(request.task, true);
|
|
368
370
|
completeTurn(hasResult() || !hasToolCall(lastAssistant), false, true);
|
|
369
371
|
if (turnPolicyFailure) throw turnPolicyFailure;
|