@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
@@ -1,18 +1,21 @@
1
1
  import { atomicWriteFile } from "./persistence.js";
2
2
  import { mkdirSync, readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
3
5
  import { homedir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
6
+ import { resolve } from "node:path";
5
7
  import { fileURLToPath } from "node:url";
6
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
7
- import { isContextFileScope } from "./types.js";
8
- import { annotateModelAliasError, deepFreeze, errorText, fail, isNodeError, jsonValue, modelCapability, object, positiveInteger, resourcePatternHasMagic, unknownModel, validateModelAliases, validateResourcePattern, validWorkflowExtensionNamespace } from "./utils.js";
9
8
  import { canonicalPath, workflowProjectSettingsPath } from "./paths.js";
10
9
  export { workflowProjectSettingsPath } from "./paths.js";
11
- const ROLE_DIRECTORY = "pi-extensible-workflows";
10
+ import { isContextFileScope } from "./types.js";
11
+ import { annotateModelAliasError, deepFreeze, errorText, fail, isNodeError, modelCapability, object, positiveInteger, unknownModel, validateModelAliases } from "./utils.js";
12
+ import { jsonValue, resourcePatternHasMagic, validateResourcePattern, validWorkflowExtensionNamespace } from "./utils.js";
13
+ const WORKFLOW_DIRECTORY = "pi-extensible-workflows";
12
14
  export const DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
13
- export function workflowSettingsPath(agentDir = getAgentDir()) { return join(agentDir, ROLE_DIRECTORY, "settings.json"); }
15
+ export function workflowSettingsPath(agentDir = getAgentDir()) { return join(agentDir, WORKFLOW_DIRECTORY, "settings.json"); }
14
16
  function normalizedResourcePath(value, settingsPath) {
15
- if (value === "*")
17
+ // Built-in extensions are named `builtin:<name>`, not by a path.
18
+ if (value === "*" || value.startsWith("builtin:"))
16
19
  return value;
17
20
  let expanded = value === "~" ? homedir() : value.startsWith("~/") || value.startsWith("~\\") ? join(homedir(), value.slice(2)) : value;
18
21
  if (expanded.startsWith("file://"))
@@ -81,23 +84,25 @@ function selectorsFromSettings(settings) {
81
84
  };
82
85
  }
83
86
  function selectorSet(value) { return { skills: [...(value?.skills ?? [])], extensions: [...(value?.extensions ?? [])], ...(value?.tools === undefined ? {} : { tools: [...value.tools] }) }; }
84
- export function validateContextFileScopes(value, rolePath) {
87
+ export function validateContextFileScopes(value, sourcePath) {
85
88
  if (value === undefined)
86
89
  return undefined;
87
90
  if (!Array.isArray(value) || !value.every(isContextFileScope))
88
- fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
91
+ fail("INVALID_METADATA", `${sourcePath}.contextFiles must be an array containing only global, project, or cwd`);
89
92
  return [...value];
90
93
  }
91
94
  export function validateWorkflowExtensionSettings(value, settingsPath, errorCode = "INVALID_SETTINGS") {
92
95
  if (value === undefined)
93
96
  return undefined;
97
+ if (!object(value) || !jsonValue(value))
98
+ fail(errorCode, `${settingsPath}.extensionSettings must be a JSON object`);
99
+ for (const namespace of Object.keys(value))
100
+ if (!validWorkflowExtensionNamespace(namespace))
101
+ fail(errorCode, `${settingsPath}.extensionSettings contains an invalid namespace: ${namespace}`);
102
+ const generic = value;
94
103
  const base = `${settingsPath}.extensionSettings`;
95
- if (!object(value))
96
- fail(errorCode, `${base} must be an object`);
97
104
  const normalized = {};
98
- for (const [namespace, raw] of Object.entries(value)) {
99
- if (!validWorkflowExtensionNamespace(namespace))
100
- fail(errorCode, `${base} contains an invalid namespace: ${namespace}`);
105
+ for (const [namespace, raw] of Object.entries(generic)) {
101
106
  if (namespace === "herdr") {
102
107
  if (!object(raw))
103
108
  fail(errorCode, `${base}.herdr must be an object`);
@@ -118,8 +123,6 @@ export function validateWorkflowExtensionSettings(value, settingsPath, errorCode
118
123
  normalized.trajectory = Object.freeze({ ...(raw.port === undefined ? {} : { port: raw.port }) });
119
124
  continue;
120
125
  }
121
- if (!jsonValue(raw))
122
- fail(errorCode, `${base}.${namespace} must be JSON-compatible`);
123
126
  normalized[namespace] = structuredClone(raw);
124
127
  }
125
128
  return deepFreeze(normalized);
@@ -152,7 +155,7 @@ function parseSettings(path, partial) {
152
155
  }
153
156
  if (!object(parsed))
154
157
  fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
155
- const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget"])]);
158
+ const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget", "codemodeTools"])]);
156
159
  const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
157
160
  if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources"))
158
161
  fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
@@ -164,6 +167,9 @@ function parseSettings(path, partial) {
164
167
  const backgroundWidget = parsed.backgroundWidget === undefined ? (partial ? undefined : DEFAULT_SETTINGS.backgroundWidget) : parsed.backgroundWidget;
165
168
  if (backgroundWidget !== undefined && typeof backgroundWidget !== "boolean")
166
169
  fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
170
+ const codemodeTools = parsed.codemodeTools;
171
+ if (codemodeTools !== undefined && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none")
172
+ fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
167
173
  const modelAliases = parsed.modelAliases === undefined ? undefined : validateModelAliases(parsed.modelAliases, path);
168
174
  const skills = validateSelectorList(parsed.skills, path, "skills");
169
175
  const tools = validateSelectorList(parsed.tools, path, "tools");
@@ -172,49 +178,52 @@ function parseSettings(path, partial) {
172
178
  const retention = validateRetention(parsed.retention, path);
173
179
  const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
174
180
  return Object.freeze({
175
- ...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(modelAliases === undefined ? {} : { modelAliases }),
181
+ ...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(codemodeTools === undefined ? {} : { codemodeTools }), ...(modelAliases === undefined ? {} : { modelAliases }),
176
182
  ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
177
183
  ...(extensionSettings === undefined ? {} : { extensionSettings }), ...(tools === undefined ? {} : { tools }), ...(retention === undefined ? {} : { retention }),
178
184
  ...(worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand }),
179
185
  });
180
186
  }
181
187
  export function loadSettings(path = workflowSettingsPath()) { return parseSettings(path, false); }
188
+ const READ_ONLY_WORKFLOW_TOOLS = new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
189
+ /**
190
+ * Exposure and annotations of a workflow or subagent tool. Under the global `codemodeTools` setting,
191
+ * `model-only` keeps the model's direct access and stops codemode scripts from calling the tool.
192
+ * Tools are registered once at load, so the setting is global and applies after a reload.
193
+ * Read-only tools carry `readOnlyHint` so permission extensions need not confirm them.
194
+ */
195
+ export function workflowToolExposure(name, codemodeTools) {
196
+ const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
197
+ return { ...(codemodeTools === "none" || (codemodeTools === "read-only" && !readOnly) ? { exposure: "model-only" } : {}), ...(readOnly ? { annotations: { readOnlyHint: true } } : {}) };
198
+ }
199
+ /** The `codemodeTools` setting at load time. Invalid settings keep the default; launches report them. */
200
+ export function loadCodemodeToolsSetting(agentDir) {
201
+ try {
202
+ return loadSettings(workflowSettingsPath(agentDir)).codemodeTools;
203
+ }
204
+ catch {
205
+ return undefined;
206
+ }
207
+ }
182
208
  export function loadSettingsOverrides(path) { return parseSettings(path, true); }
183
209
  export function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
184
210
  const projectSettingsPath = workflowProjectSettingsPath(cwd);
185
- const global = loadSettings(globalSettingsPath);
186
- const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
187
- const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
188
- const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
189
- const globalSelectors = selectorsFromSettings(global);
190
- const projectSelectors = selectorsFromSettings(project);
191
- const effectiveSelectors = selectorSet({
192
- skills: [...(globalSelectors.skills ?? []), ...(projectSelectors.skills ?? [])],
193
- extensions: [...(globalSelectors.extensions ?? []), ...(projectSelectors.extensions ?? [])],
194
- ...(globalSelectors.tools === undefined && projectSelectors.tools === undefined ? {} : { tools: [...(globalSelectors.tools ?? []), ...(projectSelectors.tools ?? [])] }),
195
- });
196
- const hasExtensionSelectors = global.extensions !== undefined || project.extensions !== undefined;
197
- const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
198
- const sources = {
199
- concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
200
- modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
201
- skills: sourceFor("skills"), extensions: sourceFor("extensions"), tools: sourceFor("tools"),
202
- ...(extensionSettings === undefined ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath }),
203
- ...(project.retention === undefined && global.retention === undefined ? {} : { retention: project.retention === undefined ? globalSettingsPath : projectSettingsPath }),
204
- ...(projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== undefined ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}),
205
- };
211
+ const consumerGlobal = loadSettings(globalSettingsPath);
212
+ const consumerProject = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
213
+ const global = consumerGlobal;
214
+ const project = consumerProject;
215
+ const selectorSources = { global: selectorsFromSettings(global), project: selectorsFromSettings(project) };
206
216
  const effective = Object.freeze({
207
- concurrency: project.concurrency ?? global.concurrency,
208
- backgroundWidget: global.backgroundWidget ?? true,
209
- ...(projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === undefined ? {} : { modelAliases: global.modelAliases }),
210
- ...(effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== undefined || project.skills !== undefined ? { skills: effectiveSelectors.skills } : {}),
211
- ...(hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {}),
212
- ...(extensionSettings === undefined ? {} : { extensionSettings }),
213
- ...(effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== undefined || project.tools !== undefined ? { tools: effectiveSelectors.tools } : {}),
214
- ...((project.retention ?? global.retention) === undefined ? {} : { retention: project.retention ?? global.retention }),
215
- ...(projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }),
217
+ ...global, ...project,
218
+ skills: [...(global.skills ?? []), ...(project.skills ?? [])],
219
+ extensions: [...(global.extensions ?? []), ...(project.extensions ?? [])],
220
+ ...(global.tools === undefined && project.tools === undefined ? {} : { tools: [...(global.tools ?? []), ...(project.tools ?? [])] }),
216
221
  });
217
- return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
222
+ const sources = { concurrency: project.concurrency === undefined ? globalSettingsPath : projectSettingsPath, modelAliases: project.modelAliases === undefined ? globalSettingsPath : projectSettingsPath };
223
+ for (const key of ["skills", "extensions", "tools", "extensionSettings", "retention", "worktreePostCreateCommand"])
224
+ if (effective[key] !== undefined)
225
+ sources[key] = project[key] === undefined ? globalSettingsPath : projectSettingsPath;
226
+ return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources, selectorSources };
218
227
  }
219
228
  export function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
220
229
  for (const name of names) {
@@ -233,7 +242,7 @@ export function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPa
233
242
  const global = selectorSet(selectorsFromSettings(resolved.global));
234
243
  const project = selectorSet(selectorsFromSettings(resolved.project));
235
244
  const effective = selectorSet(selectorsFromSettings(resolved.effective));
236
- return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
245
+ return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: resolved.selectorSources };
237
246
  }
238
247
  export function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
239
248
  const normalized = validateModelAliases(aliases, path);
@@ -251,3 +260,7 @@ export function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
251
260
  mkdirSync(dirname(path), { recursive: true });
252
261
  atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}\n`, true);
253
262
  }
263
+ export function canonicalExtensionSelector(selector, cwd) {
264
+ const negated = selector.startsWith("!");
265
+ return `${negated ? "!" : ""}${normalizedResourcePath(negated ? selector.slice(1) : selector, join(cwd, "settings.json"))}`;
266
+ }
@@ -1,4 +1,4 @@
1
- import { type JsonValue, type LaunchSnapshot, type WorkflowRunEvent } from "./types.js";
1
+ import { type PreparedAgentConfiguration, type JsonValue, type LaunchSnapshot, type WorkflowLaunchMode, type WorkflowRunEvent } from "./types.js";
2
2
  import { type AwaitingCheckpoint, type BorrowedWorktreeBinding, type CompletedOperation, type EffectiveSystemPrompt, type PendingWorkflowDecision, type PersistedOwnershipNode, type PersistedRun, type RunSummary, type WorktreeReference } from "./decoders.js";
3
3
  export declare class RunStore {
4
4
  #private;
@@ -28,6 +28,8 @@ export declare class RunStore {
28
28
  loadSummary(): Promise<RunSummary>;
29
29
  saveState(run: PersistedRun): Promise<void>;
30
30
  updateState(update: (run: PersistedRun) => PersistedRun | Promise<PersistedRun>): Promise<PersistedRun>;
31
+ saveAgentConfiguration(key: string, configuration: PreparedAgentConfiguration): Promise<void>;
32
+ setLaunchMode(launchMode: WorkflowLaunchMode): Promise<void>;
31
33
  saveSnapshot(snapshot: Readonly<LaunchSnapshot>): Promise<void>;
32
34
  appendEvent(event: WorkflowRunEvent): Promise<void>;
33
35
  saveOwnership(nodes: readonly PersistedOwnershipNode[]): Promise<void>;
package/dist/src/store.js CHANGED
@@ -24,7 +24,7 @@ function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, no
24
24
  const failedAt = run.failedAt ?? run.error?.failedAt;
25
25
  const replayablePaths = [...new Set([...(run.retry?.completedPaths ?? []), ...Object.keys(journal.completed)])];
26
26
  const incompletePaths = [...new Set([...(run.retry?.incompletePaths ?? []), ...(failedAt ? [failedAt] : [])])];
27
- return { schemaVersion: 1, runId: run.id, sessionId: run.sessionId, workflowName: run.workflowName, state: run.state, createdAt, updatedAt: now, ...(previous?.terminalAt || HARD_TERMINAL_RUN_STATES.has(run.state) ? { terminalAt: previous?.terminalAt ?? now } : {}), usage: budgetUsage(run.usage), agents: run.agents.map(({ id, name, label, state, role, attempts }) => ({ id, name, ...(label ? { label } : {}), state, ...(role ? { role } : {}), attempts })), ...(run.error ? { error: run.error } : {}), ...(failedAt ? { failedAt } : {}), replayablePaths, incompletePaths, artifacts: summaryArtifacts(directory) };
27
+ return { schemaVersion: 1, runId: run.id, sessionId: run.sessionId, workflowName: run.workflowName, state: run.state, createdAt, updatedAt: now, ...(previous?.terminalAt || HARD_TERMINAL_RUN_STATES.has(run.state) ? { terminalAt: previous?.terminalAt ?? now } : {}), usage: budgetUsage(run.usage), agents: run.agents.map(({ id, name, label, state, attempts }) => ({ id, name, ...(label ? { label } : {}), state, attempts })), ...(run.error ? { error: run.error } : {}), ...(failedAt ? { failedAt } : {}), replayablePaths, incompletePaths, artifacts: summaryArtifacts(directory) };
28
28
  }
29
29
  function systemPromptStoragePath(directory) { return join(directory, SYSTEM_PROMPT_STORAGE); }
30
30
  function systemPromptRecordsPath(directory) { return join(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS); }
@@ -171,6 +171,22 @@ export class RunStore {
171
171
  });
172
172
  return write;
173
173
  }
174
+ async saveAgentConfiguration(key, configuration) {
175
+ await this.launchSnapshotLane.run(async () => {
176
+ const snapshot = decodeLaunchSnapshot(await json(join(this.directory, "snapshot.json")));
177
+ if (!snapshot)
178
+ throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
179
+ await atomicJson(join(this.directory, "snapshot.json"), { ...snapshot, agentConfigurations: { ...snapshot.agentConfigurations, [key]: configuration } });
180
+ });
181
+ }
182
+ async setLaunchMode(launchMode) {
183
+ await this.launchSnapshotLane.run(async () => {
184
+ const snapshot = decodeLaunchSnapshot(await json(join(this.directory, "snapshot.json")));
185
+ if (!snapshot)
186
+ throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
187
+ await atomicJson(join(this.directory, "snapshot.json"), { ...snapshot, launchMode });
188
+ });
189
+ }
174
190
  async saveSnapshot(snapshot) {
175
191
  const write = this.launchSnapshotLane.run(() => atomicJson(join(this.directory, "snapshot.json"), snapshot));
176
192
  await write;
@@ -1,5 +1,5 @@
1
1
  import { type AwaitingCheckpoint, type CompletedOperation, type EffectiveSystemPrompt, type PersistedRun } from "./persistence.js";
2
- import type { AgentAttemptSummary, AgentRecord, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference } from "./types.js";
2
+ import type { AgentAttemptSummary, AgentRecord, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference, WorkflowErrorCode, WorkflowScriptCall } from "./types.js";
3
3
  import { type SubagentProgress, type SubagentRunRequest, type SubagentStatus } from "../subagents/src/contracts.js";
4
4
  import type { TrajectoryAction, TrajectoryTarget } from "./trajectory-contracts.js";
5
5
  export declare const TRAJECTORY_MAX_TRANSCRIPT_BYTES: number;
@@ -24,8 +24,17 @@ export type TrajectoryAgentOutput = {
24
24
  export type TrajectoryAgent = AgentRecord & {
25
25
  readonly output: TrajectoryAgentOutput;
26
26
  };
27
- export type TrajectoryRunRecord = Omit<PersistedRun, "agents"> & {
27
+ export type TrajectoryScriptCall = WorkflowScriptCall & {
28
+ readonly output: TrajectoryAgentOutput | {
29
+ readonly status: "completed";
30
+ } | {
31
+ readonly status: "failed" | "cancelled";
32
+ readonly code: WorkflowErrorCode;
33
+ };
34
+ };
35
+ export type TrajectoryRunRecord = Omit<PersistedRun, "agents" | "scriptCalls"> & {
28
36
  readonly agents: readonly TrajectoryAgent[];
37
+ readonly scriptCalls?: readonly TrajectoryScriptCall[];
29
38
  };
30
39
  export type TrajectoryRun = {
31
40
  run: TrajectoryRunRecord;
@@ -54,7 +63,6 @@ export type TrajectorySubagent = {
54
63
  sessionId: string;
55
64
  cwd: string;
56
65
  label?: string;
57
- role?: string;
58
66
  mode: "background" | "foreground";
59
67
  state: SubagentStatus["state"];
60
68
  startedAt?: number;
@@ -135,6 +143,7 @@ export type TrajectoryPublisherInput = {
135
143
  loadTranscript?: TrajectoryTranscriptLoader;
136
144
  handleAction: TrajectoryActionHandler;
137
145
  };
146
+ export declare function isTimingTranscriptEntry(value: unknown): boolean;
138
147
  export declare function applySystemPrompts(run: PersistedRun, prompts: readonly EffectiveSystemPrompt[]): PersistedRun;
139
148
  export declare function applyToolDescriptions(run: PersistedRun, catalog: ReadonlyMap<string, string>): PersistedRun;
140
149
  export declare function withPiToolDescriptions(run: PersistedRun): PersistedRun;
@@ -9,6 +9,7 @@ import { normalizeSubagentRunRequest } from "../subagents/src/contracts.js";
9
9
  import { statusValue, subagentErrorValue } from "../subagents/src/decode.js";
10
10
  import { isNodeError, jsonValue, object, resourcePatternHasMagic, selectResourcesByLayers } from "./utils.js";
11
11
  import { canonicalPath, sameFilesystemPath } from "./paths.js";
12
+ import { TOOL_TIMING_ENTRY_TYPE } from "./tool-timing.js";
12
13
  export const TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
13
14
  const TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
14
15
  function subagentOutput(options) {
@@ -50,7 +51,7 @@ function transcriptToolCallId(value) {
50
51
  return part.id;
51
52
  return undefined;
52
53
  }
53
- function isTimingTranscriptEntry(value) { return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing"; }
54
+ export function isTimingTranscriptEntry(value) { return object(value) && value.type === "custom" && value.customType === TOOL_TIMING_ENTRY_TYPE; }
54
55
  function timingToolCallId(value) {
55
56
  if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data))
56
57
  return undefined;
@@ -194,7 +195,8 @@ export function withPiToolDescriptionsForTools(tools, cwd) {
194
195
  function canonicalExtensionSelector(selector, base) {
195
196
  const negated = selector.startsWith("!");
196
197
  const body = negated ? selector.slice(1) : selector;
197
- if (body === "*" || body === "**" || body.startsWith("**/"))
198
+ // Built-in extensions are named `builtin:<name>`, not by a path.
199
+ if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:"))
198
200
  return selector;
199
201
  const resolved = resolve(base, body);
200
202
  if (resourcePatternHasMagic(body))
@@ -236,8 +238,8 @@ async function resolveResourceInspection(inspection, cwd, selectorCwd = cwd) {
236
238
  return inspection;
237
239
  }
238
240
  const sources = inspection.selectorSources ?? { global: inspection.selectors, project: {} };
239
- const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.role?.skills, sources.call?.skills], discovered.skills);
240
- const extensions = inspection.extensions.length ? inspection.extensions : selectResourcesByLayers([sources.global.extensions, sources.project.extensions, sources.role?.extensions, sources.call?.extensions].map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, selectorCwd))), discovered.extensions);
241
+ const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.call?.skills], discovered.skills);
242
+ const extensions = inspection.extensions.length ? inspection.extensions : selectResourcesByLayers([sources.global.extensions, sources.project.extensions, sources.call?.extensions].map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, selectorCwd))), discovered.extensions);
241
243
  return skills === inspection.skills && extensions === inspection.extensions ? inspection : { ...inspection, skills, extensions };
242
244
  }
243
245
  export async function withResolvedAttemptResources(attempt, cwd) {
@@ -280,11 +282,20 @@ function failedAgentOutput(agent) {
280
282
  const status = agent.state === "cancelled" ? "cancelled" : "failed";
281
283
  return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
282
284
  }
285
+ function operationOutput(operation) {
286
+ const bytes = serializedJsonBytes(operation.value);
287
+ return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
288
+ }
289
+ function outputForCall(call, operation) {
290
+ if (operation !== undefined)
291
+ return call.kind === "tool" ? { status: "completed" } : operationOutput(operation);
292
+ if (call.error)
293
+ return { status: call.error.code === "CANCELLED" ? "cancelled" : "failed", code: call.error.code, ...(call.kind === "shell" ? { message: call.error.message ?? "Shell failed" } : {}) };
294
+ return call.finishedAt === undefined ? { status: "pending" } : { status: "unavailable" };
295
+ }
283
296
  function outputForAgent(agent, operation) {
284
- if (operation !== undefined) {
285
- const bytes = serializedJsonBytes(operation.value);
286
- return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
287
- }
297
+ if (operation !== undefined)
298
+ return operationOutput(operation);
288
299
  if (agent.state === "failed" || agent.state === "cancelled")
289
300
  return failedAgentOutput(agent);
290
301
  if (agent.state === "completed")
@@ -294,7 +305,18 @@ function outputForAgent(agent, operation) {
294
305
  export function applyTrajectoryAgentOutputs(run, operations) {
295
306
  const byPath = new Map(operations.map((operation) => [operation.path, operation]));
296
307
  const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === undefined ? undefined : byPath.get(agent.resultPath)) }));
297
- return { ...run, agents };
308
+ const scriptCalls = run.scriptCalls?.map((call) => {
309
+ const recorded = { ...call, output: outputForCall(call, byPath.get(call.path)) };
310
+ if (call.kind === "tool") {
311
+ delete recorded.input;
312
+ delete recorded.inputBytes;
313
+ if (call.error)
314
+ recorded.error = { code: call.error.code };
315
+ }
316
+ return recorded;
317
+ });
318
+ const rest = run;
319
+ return { ...rest, agents, ...(scriptCalls === undefined ? {} : { scriptCalls }) };
298
320
  }
299
321
  function overlayTrajectoryRun(run, overlay) {
300
322
  const overlaid = overlay(run);
@@ -304,7 +326,7 @@ function overlayTrajectoryRun(run, overlay) {
304
326
  const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, undefined);
305
327
  return { ...agent, output };
306
328
  });
307
- return { ...overlaid, agents };
329
+ return { ...overlaid, agents, ...(run.scriptCalls === undefined ? {} : { scriptCalls: run.scriptCalls }) };
308
330
  }
309
331
  async function loadTrajectoryRun(store, includeTranscripts = true) {
310
332
  const value = await store.load();
@@ -555,7 +577,6 @@ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscri
555
577
  return {
556
578
  id, sessionId, cwd, mode: request.mode ?? "background", state: status.state, request, tools, transcript,
557
579
  ...(request.label === undefined ? {} : { label: request.label }),
558
- ...(request.role === undefined ? {} : { role: request.role }),
559
580
  ...(status.startedAt === undefined ? {} : { startedAt: status.startedAt }),
560
581
  ...(status.finishedAt === undefined ? {} : { finishedAt: status.finishedAt }),
561
582
  ...(status.attempts === undefined ? {} : { attempts: status.attempts }),