@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.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 (94) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/dist/src/agent-execution.js +114 -87
  3. package/dist/src/background-widget.d.ts +61 -0
  4. package/dist/src/background-widget.js +23 -16
  5. package/dist/src/decoders.js +28 -1
  6. package/dist/src/execution.d.ts +4 -2
  7. package/dist/src/execution.js +43 -2
  8. package/dist/src/host-delivery.js +1 -0
  9. package/dist/src/host-navigator.d.ts +18 -0
  10. package/dist/src/host-navigator.js +71 -13
  11. package/dist/src/host-recovery.d.ts +5 -2
  12. package/dist/src/host-recovery.js +3 -3
  13. package/dist/src/host-view.d.ts +21 -1
  14. package/dist/src/host-view.js +138 -47
  15. package/dist/src/host.d.ts +2 -2
  16. package/dist/src/host.js +246 -60
  17. package/dist/src/index.d.ts +3 -1
  18. package/dist/src/index.js +1346 -622
  19. package/dist/src/index.js.map +3 -3
  20. package/dist/src/paths.d.ts +2 -0
  21. package/dist/src/paths.js +2 -0
  22. package/dist/src/registry.js +3 -2
  23. package/dist/src/roles.d.ts +70 -0
  24. package/dist/src/roles.js +326 -0
  25. package/dist/src/script-tools.d.ts +27 -0
  26. package/dist/src/script-tools.js +87 -0
  27. package/dist/src/settings.d.ts +26 -0
  28. package/dist/src/settings.js +277 -0
  29. package/dist/src/trajectory.d.ts +39 -4
  30. package/dist/src/trajectory.js +82 -5
  31. package/dist/src/types.d.ts +30 -3
  32. package/dist/src/types.js +1 -1
  33. package/dist/src/utils.d.ts +21 -1
  34. package/dist/src/utils.js +40 -0
  35. package/dist/src/validation.d.ts +10 -17
  36. package/dist/src/validation.js +29 -458
  37. package/dist/starter/index.js +0 -2
  38. package/dist/starter/index.js.map +1 -1
  39. package/dist/starter/review-loop.d.ts +0 -13
  40. package/dist/starter/review-loop.js +0 -2
  41. package/dist/subagents/index.js +1344 -798
  42. package/dist/subagents/index.js.map +3 -3
  43. package/dist/subagents/src/contracts.d.ts +49 -29
  44. package/dist/subagents/src/contracts.js +18 -7
  45. package/dist/subagents/src/index.d.ts +1 -1
  46. package/dist/subagents/src/index.js +15 -7
  47. package/dist/subagents/src/manager.js +32 -13
  48. package/dist/subagents/src/navigator.js +396 -247
  49. package/dist/subagents/src/view.d.ts +24 -2
  50. package/dist/subagents/src/view.js +107 -80
  51. package/dist/trajectory/assets/index.html +370 -232
  52. package/dist/trajectory/assets/prism.min.js +13 -0
  53. package/dist/trajectory/index.js +445 -277
  54. package/dist/trajectory/index.js.map +3 -3
  55. package/dist/trajectory/src/assets/index.html +370 -232
  56. package/dist/trajectory/src/assets/prism.min.js +13 -0
  57. package/dist/trajectory/src/export.js +4 -2
  58. package/dist/trajectory/src/index.d.ts +2 -0
  59. package/dist/trajectory/src/index.js +151 -60
  60. package/dist/trajectory/src/server.js +138 -11
  61. package/dist/trajectory/src/server.js.map +2 -2
  62. package/package.json +7 -6
  63. package/skills/pi-extensible-workflows/SKILL.md +2 -0
  64. package/src/agent-execution.ts +110 -80
  65. package/src/background-widget.ts +26 -19
  66. package/src/decoders.ts +22 -1
  67. package/src/execution.ts +41 -4
  68. package/src/host-delivery.ts +1 -0
  69. package/src/host-navigator.ts +58 -14
  70. package/src/host-recovery.ts +8 -7
  71. package/src/host-view.ts +116 -42
  72. package/src/host.ts +223 -55
  73. package/src/index.ts +3 -1
  74. package/src/paths.ts +3 -0
  75. package/src/registry.ts +3 -2
  76. package/src/roles.ts +327 -0
  77. package/src/script-tools.ts +76 -0
  78. package/src/settings.ts +224 -0
  79. package/src/trajectory.ts +97 -9
  80. package/src/types.ts +12 -5
  81. package/src/utils.ts +36 -1
  82. package/src/validation.ts +30 -356
  83. package/starter/review-loop.ts +0 -2
  84. package/subagents/README.md +3 -3
  85. package/subagents/src/contracts.ts +22 -7
  86. package/subagents/src/index.ts +18 -7
  87. package/subagents/src/manager.ts +31 -13
  88. package/subagents/src/navigator.ts +336 -180
  89. package/subagents/src/view.ts +108 -76
  90. package/trajectory/src/assets/index.html +370 -232
  91. package/trajectory/src/assets/prism.min.js +13 -0
  92. package/trajectory/src/export.ts +4 -2
  93. package/trajectory/src/index.ts +124 -47
  94. package/trajectory/src/server.ts +114 -14
@@ -0,0 +1,277 @@
1
+ import { atomicWriteFile } from "./persistence.js";
2
+ import { mkdirSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ 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
+ import { canonicalPath, workflowProjectSettingsPath } from "./paths.js";
10
+ export { workflowProjectSettingsPath } from "./paths.js";
11
+ const ROLE_DIRECTORY = "pi-extensible-workflows";
12
+ export const DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
13
+ export function workflowSettingsPath(agentDir = getAgentDir()) { return join(agentDir, ROLE_DIRECTORY, "settings.json"); }
14
+ function normalizedResourcePath(value, settingsPath) {
15
+ // Built-in extensions are named `builtin:<name>`, not by a path.
16
+ if (value === "*" || value.startsWith("builtin:"))
17
+ return value;
18
+ let expanded = value === "~" ? homedir() : value.startsWith("~/") || value.startsWith("~\\") ? join(homedir(), value.slice(2)) : value;
19
+ if (expanded.startsWith("file://"))
20
+ expanded = fileURLToPath(expanded);
21
+ const resolved = resolve(dirname(settingsPath), expanded);
22
+ if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\"))
23
+ return expanded;
24
+ if (resourcePatternHasMagic(expanded)) {
25
+ const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
26
+ const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
27
+ const rootBoundary = separatorIndex === 0 || (separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved));
28
+ const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
29
+ const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
30
+ return `${canonicalPath(prefix)}${suffix}`;
31
+ }
32
+ return canonicalPath(resolved);
33
+ }
34
+ export function validateSelectorList(value, path, kind, errorCode = "INVALID_SETTINGS", normalizeExtensions = true) {
35
+ if (value === undefined)
36
+ return undefined;
37
+ if (!Array.isArray(value))
38
+ fail(errorCode, `${path}.${kind} must be an array`);
39
+ const normalized = [];
40
+ for (const [index, entry] of value.entries()) {
41
+ if (typeof entry !== "string" || !entry.trim())
42
+ fail(errorCode, `${path}.${kind}[${String(index)}] must be a non-empty string`);
43
+ let selector = entry.trim();
44
+ if (kind === "extensions" && normalizeExtensions) {
45
+ const negated = selector.startsWith("!");
46
+ const body = negated ? selector.slice(1) : selector;
47
+ if (!body)
48
+ fail(errorCode, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
49
+ try {
50
+ selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
51
+ }
52
+ catch (error) {
53
+ fail(errorCode, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
54
+ }
55
+ }
56
+ try {
57
+ validateResourcePattern(selector);
58
+ }
59
+ catch (error) {
60
+ fail(errorCode, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
61
+ }
62
+ normalized.push(selector);
63
+ }
64
+ return Object.freeze(normalized);
65
+ }
66
+ function validateWorktreePostCreateCommand(value, settingsPath) {
67
+ if (value === undefined)
68
+ return undefined;
69
+ if (!Array.isArray(value) || value.length === 0)
70
+ fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
71
+ return Object.freeze(value.map((entry, index) => {
72
+ if (typeof entry !== "string" || !entry.trim())
73
+ fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
74
+ return entry;
75
+ }));
76
+ }
77
+ function selectorsFromSettings(settings) {
78
+ return {
79
+ ...(settings.skills === undefined ? {} : { skills: settings.skills }),
80
+ ...(settings.extensions === undefined ? {} : { extensions: settings.extensions }),
81
+ ...(settings.tools === undefined ? {} : { tools: settings.tools }),
82
+ };
83
+ }
84
+ function selectorSet(value) { return { skills: [...(value?.skills ?? [])], extensions: [...(value?.extensions ?? [])], ...(value?.tools === undefined ? {} : { tools: [...value.tools] }) }; }
85
+ export function validateContextFileScopes(value, rolePath) {
86
+ if (value === undefined)
87
+ return undefined;
88
+ if (!Array.isArray(value) || !value.every(isContextFileScope))
89
+ fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
90
+ return [...value];
91
+ }
92
+ export function validateWorkflowExtensionSettings(value, settingsPath, errorCode = "INVALID_SETTINGS") {
93
+ if (value === undefined)
94
+ return undefined;
95
+ const base = `${settingsPath}.extensionSettings`;
96
+ if (!object(value))
97
+ fail(errorCode, `${base} must be an object`);
98
+ const normalized = {};
99
+ for (const [namespace, raw] of Object.entries(value)) {
100
+ if (!validWorkflowExtensionNamespace(namespace))
101
+ fail(errorCode, `${base} contains an invalid namespace: ${namespace}`);
102
+ if (namespace === "herdr") {
103
+ if (!object(raw))
104
+ fail(errorCode, `${base}.herdr must be an object`);
105
+ if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode"))
106
+ fail(errorCode, `${base}.herdr contains an unsupported setting`);
107
+ if (raw.enableFullyInspectableMode !== undefined && typeof raw.enableFullyInspectableMode !== "boolean")
108
+ fail(errorCode, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
109
+ normalized.herdr = Object.freeze({ ...(raw.enableFullyInspectableMode === undefined ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode }) });
110
+ continue;
111
+ }
112
+ if (namespace === "trajectory") {
113
+ if (!object(raw))
114
+ fail(errorCode, `${base}.trajectory must be an object`);
115
+ if (Object.keys(raw).some((key) => key !== "port"))
116
+ fail(errorCode, `${base}.trajectory contains an unsupported setting`);
117
+ if (raw.port !== undefined && (!positiveInteger(raw.port) || raw.port > 65535))
118
+ fail(errorCode, `${base}.trajectory.port must be an integer from 1 to 65535`);
119
+ normalized.trajectory = Object.freeze({ ...(raw.port === undefined ? {} : { port: raw.port }) });
120
+ continue;
121
+ }
122
+ if (!jsonValue(raw))
123
+ fail(errorCode, `${base}.${namespace} must be JSON-compatible`);
124
+ normalized[namespace] = structuredClone(raw);
125
+ }
126
+ return deepFreeze(normalized);
127
+ }
128
+ function positiveRetentionInteger(value) { return positiveInteger(value) && Number.isSafeInteger(value); }
129
+ function validateRetention(value, settingsPath) {
130
+ if (value === undefined)
131
+ return undefined;
132
+ const base = `${settingsPath}.retention`;
133
+ if (!object(value))
134
+ fail("INVALID_SETTINGS", `${base} must be an object`);
135
+ const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
136
+ if (unknown)
137
+ fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
138
+ if (value.olderThanDays !== undefined && !positiveRetentionInteger(value.olderThanDays))
139
+ fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
140
+ if (value.maxTerminalRuns !== undefined && !positiveRetentionInteger(value.maxTerminalRuns))
141
+ fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
142
+ return Object.freeze({ ...(value.olderThanDays === undefined ? {} : { olderThanDays: value.olderThanDays }), ...(value.maxTerminalRuns === undefined ? {} : { maxTerminalRuns: value.maxTerminalRuns }) });
143
+ }
144
+ function parseSettings(path, partial) {
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(readFileSync(path, "utf8"));
148
+ }
149
+ catch (error) {
150
+ if (isNodeError(error, "ENOENT"))
151
+ return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
152
+ fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
153
+ }
154
+ if (!object(parsed))
155
+ fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
156
+ const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget", "codemodeTools"])]);
157
+ const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
158
+ if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources"))
159
+ fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
160
+ if (unknown)
161
+ fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
162
+ const concurrency = parsed.concurrency === undefined ? (partial ? undefined : DEFAULT_SETTINGS.concurrency) : parsed.concurrency;
163
+ if (concurrency !== undefined && (!positiveInteger(concurrency) || concurrency > 16))
164
+ fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
165
+ const backgroundWidget = parsed.backgroundWidget === undefined ? (partial ? undefined : DEFAULT_SETTINGS.backgroundWidget) : parsed.backgroundWidget;
166
+ if (backgroundWidget !== undefined && typeof backgroundWidget !== "boolean")
167
+ fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
168
+ const codemodeTools = parsed.codemodeTools;
169
+ if (codemodeTools !== undefined && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none")
170
+ fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
171
+ const modelAliases = parsed.modelAliases === undefined ? undefined : validateModelAliases(parsed.modelAliases, path);
172
+ const skills = validateSelectorList(parsed.skills, path, "skills");
173
+ const tools = validateSelectorList(parsed.tools, path, "tools");
174
+ const extensions = validateSelectorList(parsed.extensions, path, "extensions");
175
+ const extensionSettings = parsed.extensionSettings === undefined ? undefined : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
176
+ const retention = validateRetention(parsed.retention, path);
177
+ const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
178
+ return Object.freeze({
179
+ ...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(codemodeTools === undefined ? {} : { codemodeTools }), ...(modelAliases === undefined ? {} : { modelAliases }),
180
+ ...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
181
+ ...(extensionSettings === undefined ? {} : { extensionSettings }), ...(tools === undefined ? {} : { tools }), ...(retention === undefined ? {} : { retention }),
182
+ ...(worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand }),
183
+ });
184
+ }
185
+ export function loadSettings(path = workflowSettingsPath()) { return parseSettings(path, false); }
186
+ const READ_ONLY_WORKFLOW_TOOLS = new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
187
+ /**
188
+ * Exposure and annotations of a workflow or subagent tool. Under the global `codemodeTools` setting,
189
+ * `model-only` keeps the model's direct access and stops codemode scripts from calling the tool.
190
+ * Tools are registered once at load, so the setting is global and applies after a reload.
191
+ * Read-only tools carry `readOnlyHint` so permission extensions need not confirm them.
192
+ */
193
+ export function workflowToolExposure(name, codemodeTools) {
194
+ const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
195
+ return { ...(codemodeTools === "none" || (codemodeTools === "read-only" && !readOnly) ? { exposure: "model-only" } : {}), ...(readOnly ? { annotations: { readOnlyHint: true } } : {}) };
196
+ }
197
+ /** The `codemodeTools` setting at load time. Invalid settings keep the default; launches report them. */
198
+ export function loadCodemodeToolsSetting(agentDir) {
199
+ try {
200
+ return loadSettings(workflowSettingsPath(agentDir)).codemodeTools;
201
+ }
202
+ catch {
203
+ return undefined;
204
+ }
205
+ }
206
+ export function loadSettingsOverrides(path) { return parseSettings(path, true); }
207
+ export function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
208
+ const projectSettingsPath = workflowProjectSettingsPath(cwd);
209
+ const global = loadSettings(globalSettingsPath);
210
+ const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
211
+ const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
212
+ const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
213
+ const globalSelectors = selectorsFromSettings(global);
214
+ const projectSelectors = selectorsFromSettings(project);
215
+ const effectiveSelectors = selectorSet({
216
+ skills: [...(globalSelectors.skills ?? []), ...(projectSelectors.skills ?? [])],
217
+ extensions: [...(globalSelectors.extensions ?? []), ...(projectSelectors.extensions ?? [])],
218
+ ...(globalSelectors.tools === undefined && projectSelectors.tools === undefined ? {} : { tools: [...(globalSelectors.tools ?? []), ...(projectSelectors.tools ?? [])] }),
219
+ });
220
+ const hasExtensionSelectors = global.extensions !== undefined || project.extensions !== undefined;
221
+ const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
222
+ const sources = {
223
+ concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
224
+ modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
225
+ skills: sourceFor("skills"), extensions: sourceFor("extensions"), tools: sourceFor("tools"),
226
+ ...(extensionSettings === undefined ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath }),
227
+ ...(project.retention === undefined && global.retention === undefined ? {} : { retention: project.retention === undefined ? globalSettingsPath : projectSettingsPath }),
228
+ ...(projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== undefined ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}),
229
+ };
230
+ const effective = Object.freeze({
231
+ concurrency: project.concurrency ?? global.concurrency,
232
+ backgroundWidget: global.backgroundWidget ?? true,
233
+ ...(projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === undefined ? {} : { modelAliases: global.modelAliases }),
234
+ ...(effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== undefined || project.skills !== undefined ? { skills: effectiveSelectors.skills } : {}),
235
+ ...(hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {}),
236
+ ...(extensionSettings === undefined ? {} : { extensionSettings }),
237
+ ...(effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== undefined || project.tools !== undefined ? { tools: effectiveSelectors.tools } : {}),
238
+ ...((project.retention ?? global.retention) === undefined ? {} : { retention: project.retention ?? global.retention }),
239
+ ...(projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }),
240
+ });
241
+ return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
242
+ }
243
+ export function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
244
+ for (const name of names) {
245
+ try {
246
+ const target = modelCapability(name, aliases, knownModels, settingsPath);
247
+ if (!availableModels.has(target))
248
+ unknownModel(name, target, settingsPath);
249
+ }
250
+ catch (error) {
251
+ throw annotateModelAliasError(error, name);
252
+ }
253
+ }
254
+ }
255
+ export function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
256
+ const resolved = resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath);
257
+ const global = selectorSet(selectorsFromSettings(resolved.global));
258
+ const project = selectorSet(selectorsFromSettings(resolved.project));
259
+ const effective = selectorSet(selectorsFromSettings(resolved.effective));
260
+ return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
261
+ }
262
+ export function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
263
+ const normalized = validateModelAliases(aliases, path);
264
+ let parsed = {};
265
+ try {
266
+ loadSettings(path);
267
+ parsed = JSON.parse(readFileSync(path, "utf8"));
268
+ }
269
+ catch (error) {
270
+ if (!isNodeError(error, "ENOENT"))
271
+ throw error;
272
+ }
273
+ if (!object(parsed))
274
+ fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
275
+ mkdirSync(dirname(path), { recursive: true });
276
+ atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}\n`, true);
277
+ }
@@ -1,10 +1,43 @@
1
- import { type AwaitingCheckpoint, type EffectiveSystemPrompt, type PersistedRun } from "./persistence.js";
2
- import type { AgentAttemptSummary, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference } from "./types.js";
1
+ import { type AwaitingCheckpoint, type CompletedOperation, type EffectiveSystemPrompt, type PersistedRun } from "./persistence.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;
6
+ export type TrajectoryAgentOutput = {
7
+ readonly status: "available";
8
+ readonly value: JsonValue;
9
+ readonly bytes: number;
10
+ } | {
11
+ readonly status: "pending";
12
+ } | {
13
+ readonly status: "failed" | "cancelled";
14
+ readonly code: string;
15
+ readonly message: string;
16
+ } | {
17
+ readonly status: "truncated";
18
+ readonly kind: "result" | "failure";
19
+ readonly bytes: number;
20
+ readonly path?: string;
21
+ } | {
22
+ readonly status: "unavailable";
23
+ };
24
+ export type TrajectoryAgent = AgentRecord & {
25
+ readonly output: TrajectoryAgentOutput;
26
+ };
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"> & {
36
+ readonly agents: readonly TrajectoryAgent[];
37
+ readonly scriptCalls?: readonly TrajectoryScriptCall[];
38
+ };
6
39
  export type TrajectoryRun = {
7
- run: PersistedRun;
40
+ run: TrajectoryRunRecord;
8
41
  snapshot: Readonly<LaunchSnapshot>;
9
42
  awaiting: readonly AwaitingCheckpoint[];
10
43
  createdAt?: string;
@@ -58,6 +91,7 @@ export type TrajectorySubagent = {
58
91
  readonly code: string;
59
92
  readonly message: string;
60
93
  } | TrajectorySubagentArtifact;
94
+ output: TrajectoryAgentOutput;
61
95
  transcript: readonly unknown[];
62
96
  };
63
97
  export type TrajectorySubagentMetadata = Omit<TrajectorySubagent, "transcript"> & {
@@ -104,13 +138,13 @@ export type TrajectoryPublisherInput = {
104
138
  cwd: string;
105
139
  sessionId: string;
106
140
  port?: number;
107
- themes: boolean;
108
141
  loadRuns: TrajectoryRunLoader;
109
142
  loadSubagents: TrajectorySubagentLoader;
110
143
  loadMetadata: TrajectoryPublisherMetadataLoader;
111
144
  loadTranscript?: TrajectoryTranscriptLoader;
112
145
  handleAction: TrajectoryActionHandler;
113
146
  };
147
+ export declare function isTimingTranscriptEntry(value: unknown): boolean;
114
148
  export declare function applySystemPrompts(run: PersistedRun, prompts: readonly EffectiveSystemPrompt[]): PersistedRun;
115
149
  export declare function applyToolDescriptions(run: PersistedRun, catalog: ReadonlyMap<string, string>): PersistedRun;
116
150
  export declare function withPiToolDescriptions(run: PersistedRun): PersistedRun;
@@ -120,6 +154,7 @@ export declare function withPiToolDescriptionsForTools(tools: readonly string[],
120
154
  }[];
121
155
  export declare function withResolvedAttemptResources(attempt: AgentAttemptSummary, cwd: string): Promise<AgentAttemptSummary>;
122
156
  export declare function withResolvedResources(run: PersistedRun, cwd: string): Promise<PersistedRun>;
157
+ export declare function applyTrajectoryAgentOutputs(run: PersistedRun, operations: readonly CompletedOperation[]): TrajectoryRunRecord;
123
158
  export declare function loadTrajectoryRuns(cwd: string, sessionId: string, home?: string, overlay?: (run: PersistedRun) => PersistedRun): Promise<readonly TrajectoryRun[]>;
124
159
  export declare function createTrajectoryRunLoader(cwd: string, sessionId: string, home?: string, overlay?: (run: PersistedRun) => PersistedRun): TrajectoryRunLoader;
125
160
  export declare function createTrajectoryRunMetadataLoader(cwd: string, sessionId: string, home?: string, overlay?: (run: PersistedRun) => PersistedRun): TrajectoryRunMetadataLoader;
@@ -9,8 +9,26 @@ 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;
15
+ function subagentOutput(options) {
16
+ if (options.resultTruncated)
17
+ return { status: "truncated", kind: "result", path: options.resultPath, bytes: options.resultBytes ?? 0 };
18
+ if (options.result !== undefined)
19
+ return { status: "available", value: options.result, bytes: serializedJsonBytes(options.result) };
20
+ if (options.failureTruncated)
21
+ return { status: "truncated", kind: "failure", path: options.failurePath, bytes: options.failureBytes ?? 0 };
22
+ if (options.failure !== undefined)
23
+ return { status: "failed", code: options.failure.code, message: options.failure.message };
24
+ if (options.state === "failed" || options.state === "stopped") {
25
+ const error = options.statusError ?? options.attempt?.error;
26
+ return { status: options.state === "stopped" ? "cancelled" : "failed", code: error?.code ?? (options.state === "stopped" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Subagent ${options.state}` };
27
+ }
28
+ if (options.state === "completed")
29
+ return { status: "unavailable" };
30
+ return { status: "pending" };
31
+ }
14
32
  export { isTrajectoryAction, isTrajectoryTarget, trajectoryActionError } from "./trajectory-contracts.js";
15
33
  function sessionFile(reference) {
16
34
  const locator = reference?.locator;
@@ -33,7 +51,7 @@ function transcriptToolCallId(value) {
33
51
  return part.id;
34
52
  return undefined;
35
53
  }
36
- 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; }
37
55
  function timingToolCallId(value) {
38
56
  if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data))
39
57
  return undefined;
@@ -177,7 +195,8 @@ export function withPiToolDescriptionsForTools(tools, cwd) {
177
195
  function canonicalExtensionSelector(selector, base) {
178
196
  const negated = selector.startsWith("!");
179
197
  const body = negated ? selector.slice(1) : selector;
180
- 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:"))
181
200
  return selector;
182
201
  const resolved = resolve(base, body);
183
202
  if (resourcePatternHasMagic(body))
@@ -254,11 +273,68 @@ function withoutAgentActivities(run) {
254
273
  });
255
274
  return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
256
275
  }
276
+ function serializedJsonBytes(value) {
277
+ const serialized = JSON.stringify(value);
278
+ return Buffer.byteLength(serialized, "utf8");
279
+ }
280
+ function failedAgentOutput(agent) {
281
+ const error = [...(agent.attemptDetails ?? [])].reverse().find((attempt) => attempt.error)?.error;
282
+ const status = agent.state === "cancelled" ? "cancelled" : "failed";
283
+ return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
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
+ }
296
+ function outputForAgent(agent, operation) {
297
+ if (operation !== undefined)
298
+ return operationOutput(operation);
299
+ if (agent.state === "failed" || agent.state === "cancelled")
300
+ return failedAgentOutput(agent);
301
+ if (agent.state === "completed")
302
+ return { status: "unavailable" };
303
+ return { status: "pending" };
304
+ }
305
+ export function applyTrajectoryAgentOutputs(run, operations) {
306
+ const byPath = new Map(operations.map((operation) => [operation.path, operation]));
307
+ const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === undefined ? undefined : byPath.get(agent.resultPath)) }));
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 }) };
320
+ }
321
+ function overlayTrajectoryRun(run, overlay) {
322
+ const overlaid = overlay(run);
323
+ const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
324
+ const agents = overlaid.agents.map((agent) => {
325
+ const previous = outputs.get(agent.id);
326
+ const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, undefined);
327
+ return { ...agent, output };
328
+ });
329
+ return { ...overlaid, agents, ...(run.scriptCalls === undefined ? {} : { scriptCalls: run.scriptCalls }) };
330
+ }
257
331
  async function loadTrajectoryRun(store, includeTranscripts = true) {
258
332
  const value = await store.load();
259
333
  const summary = await store.loadSummary().catch(() => undefined);
260
334
  const prompts = await store.systemPrompts().catch(() => []);
261
- const run = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
335
+ const prepared = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
336
+ const operations = await store.replayableOperations().catch(() => []);
337
+ const run = applyTrajectoryAgentOutputs(prepared, operations);
262
338
  return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...(summary?.createdAt === undefined ? {} : { createdAt: summary.createdAt }), transcripts: includeTranscripts ? await runTranscripts(run) : {} };
263
339
  }
264
340
  export async function loadTrajectoryRuns(cwd, sessionId, home = homedir(), overlay) {
@@ -266,7 +342,7 @@ export async function loadTrajectoryRuns(cwd, sessionId, home = homedir(), overl
266
342
  for (const runId of await listRunIds(cwd, sessionId, home, false)) {
267
343
  try {
268
344
  const value = await loadTrajectoryRun(new RunStore(cwd, sessionId, runId, home));
269
- loaded.push(overlay ? { ...value, run: overlay(value.run) } : value);
345
+ loaded.push(overlay ? { ...value, run: overlayTrajectoryRun(value.run, overlay) } : value);
270
346
  }
271
347
  catch { /* Ignore corrupt or concurrently removed runs. */ }
272
348
  }
@@ -305,7 +381,7 @@ function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature,
305
381
  entry = { value, stateMtimeMs: (await fileMtime(join(store.directory, "state.json"))) ?? 0, journalMtimeMs: (await fileMtime(join(store.directory, "journal.json"))) ?? 0, transcripts };
306
382
  cache.set(runId, entry);
307
383
  }
308
- loaded.push(overlay ? { ...entry.value, run: overlay(entry.value.run) } : entry.value);
384
+ loaded.push(overlay ? { ...entry.value, run: overlayTrajectoryRun(entry.value.run, overlay) } : entry.value);
309
385
  }
310
386
  catch {
311
387
  cache.delete(runId); /* Ignore corrupt or concurrently removed runs. */
@@ -513,6 +589,7 @@ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscri
513
589
  ...(attempt === undefined ? {} : { attempt }),
514
590
  ...(result === undefined ? {} : { result }),
515
591
  ...(failure === undefined ? {} : { failure }),
592
+ output: subagentOutput({ state: status.state, statusError: status.error, attempt, result: resultValue === undefined ? undefined : boundedSubagentJson(resultValue), resultTruncated, resultPath, resultBytes, failure: failureValue === undefined ? undefined : subagentErrorValue(failureValue), failureTruncated, failurePath, failureBytes }),
516
593
  };
517
594
  }
518
595
  catch {
@@ -23,7 +23,7 @@ export declare const WORKFLOW_CHECKPOINT_STATE_CHANGED_EVENT = "workflow:checkpo
23
23
  export declare const WORKFLOW_BUDGET_EVENT = "workflow:budget-event";
24
24
  export declare const WORKFLOW_WORKTREE_CREATED_EVENT = "workflow:worktree-created";
25
25
  export declare const WORKFLOW_BLOCKED_EVENT = "workflow:blocked";
26
- export declare const ERROR_CODES: readonly ["CONFIG_ERROR", "INVALID_SETTINGS", "INVALID_SYNTAX", "INVALID_METADATA", "DUPLICATE_NAME", "INVALID_SCHEMA", "UNKNOWN_MODEL", "UNKNOWN_TOOL", "UNKNOWN_AGENT_TYPE", "RUN_OWNED", "RUN_NOT_FOUND", "REGISTRY_FROZEN", "GLOBAL_COLLISION", "MISSING_WORKFLOW", "RPC_LIMIT_EXCEEDED", "SHELL_FAILED", "AGENT_TIMEOUT", "AGENT_FAILED", "AGENT_RESULT_COLLECTED", "RESULT_INVALID", "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR"];
26
+ export declare const ERROR_CODES: readonly ["CONFIG_ERROR", "INVALID_SETTINGS", "INVALID_SYNTAX", "INVALID_METADATA", "DUPLICATE_NAME", "INVALID_SCHEMA", "UNKNOWN_MODEL", "UNKNOWN_TOOL", "UNKNOWN_AGENT_TYPE", "RUN_OWNED", "RUN_NOT_FOUND", "REGISTRY_FROZEN", "GLOBAL_COLLISION", "MISSING_WORKFLOW", "RPC_LIMIT_EXCEEDED", "SHELL_FAILED", "AGENT_TIMEOUT", "AGENT_FAILED", "AGENT_RESULT_COLLECTED", "RESULT_INVALID", "TOOL_FAILED", "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR"];
27
27
  export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
28
28
  export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
29
29
  export type WorkflowErrorCode = (typeof ERROR_CODES)[number];
@@ -190,7 +190,6 @@ export interface HerdrExtensionSettings {
190
190
  }
191
191
  export interface TrajectoryExtensionSettings {
192
192
  port?: number;
193
- themes?: boolean;
194
193
  }
195
194
  export interface WorkflowExtensionSettings {
196
195
  readonly [namespace: string]: JsonValue;
@@ -216,9 +215,11 @@ export interface WorkflowRetentionSettings {
216
215
  olderThanDays?: number;
217
216
  maxTerminalRuns?: number;
218
217
  }
218
+ export type CodemodeToolsSetting = "all" | "read-only" | "none";
219
219
  export interface WorkflowSettings {
220
220
  concurrency: number;
221
221
  backgroundWidget?: boolean;
222
+ codemodeTools?: CodemodeToolsSetting;
222
223
  modelAliases?: Readonly<Record<string, string>>;
223
224
  skills?: readonly string[];
224
225
  extensions?: readonly string[];
@@ -227,7 +228,7 @@ export interface WorkflowSettings {
227
228
  retention?: Readonly<WorkflowRetentionSettings>;
228
229
  worktreePostCreateCommand?: readonly string[];
229
230
  }
230
- export type WorkflowSettingsOverrides = Partial<Omit<WorkflowSettings, "backgroundWidget">>;
231
+ export type WorkflowSettingsOverrides = Partial<Omit<WorkflowSettings, "backgroundWidget" | "codemodeTools">>;
231
232
  export interface WorkflowSettingsSources {
232
233
  concurrency: string;
233
234
  modelAliases: string;
@@ -404,6 +405,21 @@ export interface WorkflowPhaseShellActivity {
404
405
  active: number;
405
406
  startedAt: number;
406
407
  }
408
+ /** A script call recorded for inspection. Tool payloads stay in the replay journal, not this record. Oversized shell inputs are JSON prefixes, with `inputBytes` their full size. */
409
+ export interface WorkflowScriptCall {
410
+ kind: "shell" | "tool";
411
+ name: string;
412
+ path: string;
413
+ input?: JsonValue;
414
+ inputBytes?: number;
415
+ phase?: string;
416
+ startedAt: number;
417
+ finishedAt?: number;
418
+ error?: {
419
+ code: WorkflowErrorCode;
420
+ message?: string;
421
+ };
422
+ }
407
423
  export interface RunRecord {
408
424
  id: string;
409
425
  workflowName: string;
@@ -420,6 +436,11 @@ export interface RunRecord {
420
436
  activeShells?: number;
421
437
  activeShellStartedAt?: number;
422
438
  activeShellsByPhase?: readonly WorkflowPhaseShellActivity[];
439
+ activeTools?: readonly {
440
+ name: string;
441
+ startedAt: number;
442
+ }[];
443
+ scriptCalls?: readonly WorkflowScriptCall[];
423
444
  error?: WorkflowErrorShape;
424
445
  failedAt?: string;
425
446
  budget?: WorkflowBudget;
@@ -836,6 +857,11 @@ export interface ShellIdentity {
836
857
  occurrence: number;
837
858
  worktreeOwner?: string;
838
859
  }
860
+ /** A script `tools.<id>(args)` call: the n-th call of that tool within one structural scope. */
861
+ export interface ToolIdentity {
862
+ structuralPath: readonly string[];
863
+ occurrence: number;
864
+ }
839
865
  export interface WorkflowBridge {
840
866
  agent?: (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
841
867
  shell?: (command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>;
@@ -847,6 +873,7 @@ export interface WorkflowBridge {
847
873
  }>>;
848
874
  phase?: (name: string) => void | Promise<void>;
849
875
  log?: (message: string) => void | Promise<void>;
876
+ tool?: (identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue>;
850
877
  }
851
878
  export interface WorkflowExecution {
852
879
  result: Promise<JsonValue>;
package/dist/src/types.js CHANGED
@@ -19,7 +19,7 @@ export const WORKFLOW_BLOCKED_EVENT = "workflow:blocked";
19
19
  export const ERROR_CODES = [
20
20
  "CONFIG_ERROR", "INVALID_SETTINGS", "INVALID_SYNTAX", "INVALID_METADATA", "DUPLICATE_NAME", "INVALID_SCHEMA", "UNKNOWN_MODEL", "UNKNOWN_TOOL", "UNKNOWN_AGENT_TYPE",
21
21
  "RUN_OWNED", "RUN_NOT_FOUND", "REGISTRY_FROZEN", "GLOBAL_COLLISION", "MISSING_WORKFLOW", "RPC_LIMIT_EXCEEDED", "SHELL_FAILED", "AGENT_TIMEOUT", "AGENT_FAILED", "AGENT_RESULT_COLLECTED", "RESULT_INVALID",
22
- "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
22
+ "TOOL_FAILED", "CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
23
23
  ];
24
24
  export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
25
25
  export function roleNameOf(value) {
@@ -1,4 +1,4 @@
1
- import { WorkflowError, type JsonValue, type ModelSpec, type ThinkingLevel, type WorkflowErrorCode, type WorkflowExtensionSettings } from "./types.js";
1
+ import { WorkflowError, type JsonSchema, type JsonValue, type ModelSpec, type ThinkingLevel, type WorkflowErrorCode, type WorkflowExtensionSettings } from "./types.js";
2
2
  export declare class SerialLane {
3
3
  #private;
4
4
  run<T>(task: () => Promise<T>): Promise<T>;
@@ -6,6 +6,8 @@ export declare class SerialLane {
6
6
  export declare function object(value: unknown): value is Record<string, unknown>;
7
7
  export declare function sanitizeDisplayText(value: string): string;
8
8
  export { object as isObject };
9
+ /** A short digest of a JSON value that ignores object key order. */
10
+ export declare function jsonDigest(value: JsonValue): string;
9
11
  export declare function jsonValue(value: unknown, seen?: Set<object>): value is JsonValue;
10
12
  export declare function jsonObject(value: unknown): value is Record<string, JsonValue>;
11
13
  export declare function mergeWorkflowExtensionSettings(...layers: readonly (Readonly<WorkflowExtensionSettings> | undefined)[]): Readonly<WorkflowExtensionSettings> | undefined;
@@ -44,6 +46,23 @@ export declare function modelAliasName(value: string, aliases: Readonly<Record<s
44
46
  export declare function validateModelAliases(value: unknown, settingsPath?: string): Readonly<Record<string, string>>;
45
47
  export declare function unknownModel(value: string, target: string | undefined, settingsPath?: string): never;
46
48
  export declare function resolveModelReference(value: string, aliases?: Readonly<Record<string, string>>, knownModels?: ReadonlySet<string>, settingsPath?: string): ModelSpec;
49
+ type ToolSource = {
50
+ getActiveTools(): string[];
51
+ getAllTools?(): readonly {
52
+ name: string;
53
+ exposure?: string;
54
+ }[];
55
+ };
56
+ /**
57
+ * Tools a session can reach, and so the ceiling for its agents: the declared tools plus the ones
58
+ * only codemode scripts call or tool_search loads, such as MCP tools with the default exposure.
59
+ * Agent sessions allow tools by name, so leaving these out would make them unreachable there.
60
+ */
61
+ export declare function reachableTools(pi: ToolSource): string[];
62
+ /** Provider of the virtual models that expose workflow model aliases in `/model`. */
63
+ export declare const VIRTUAL_MODEL_PROVIDER = "workflow";
64
+ /** Child sessions do not load the workflow extension, so they receive the alias target instead of its virtual model. */
65
+ export declare function physicalModel(spec: ModelSpec, aliases?: Readonly<Record<string, string>>, knownModels?: ReadonlySet<string>, settingsPath?: string): ModelSpec;
47
66
  export declare function modelCapability(value: string | ModelSpec, aliases?: Readonly<Record<string, string>>, knownModels?: ReadonlySet<string>, settingsPath?: string): string;
48
67
  export declare function aliasDrift(previous: Readonly<Record<string, string>>, current: Readonly<Record<string, string>>): string[];
49
68
  export declare function validateResourcePattern(pattern: string): void;
@@ -55,3 +74,4 @@ export declare function createLaunchSnapshot(input: Omit<import("./types.js").La
55
74
  identityVersion?: number;
56
75
  }): Readonly<import("./types.js").LaunchSnapshot>;
57
76
  export declare function loadLaunchSnapshot(input: import("./types.js").LaunchSnapshot): Readonly<import("./types.js").LaunchSnapshot>;
77
+ export declare function validateSchema(schema: unknown, at?: string): asserts schema is JsonSchema;