@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.17.0-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 +67 -0
- package/dist/src/agent-execution.js +36 -51
- package/dist/src/background-widget.d.ts +56 -0
- package/dist/src/background-widget.js +22 -15
- package/dist/src/decoders.js +2 -1
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-view.d.ts +16 -1
- package/dist/src/host-view.js +109 -42
- package/dist/src/host.js +57 -24
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +867 -545
- package/dist/src/index.js.map +3 -3
- package/dist/src/registry.js +2 -1
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +325 -0
- package/dist/src/settings.d.ts +13 -0
- package/dist/src/settings.js +253 -0
- package/dist/src/trajectory.d.ts +29 -4
- package/dist/src/trajectory.js +58 -3
- package/dist/src/types.d.ts +0 -1
- package/dist/src/utils.d.ts +2 -1
- package/dist/src/utils.js +10 -0
- package/dist/src/validation.d.ts +5 -17
- package/dist/src/validation.js +9 -458
- package/dist/subagents/index.js +1132 -694
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -0
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +2 -1
- package/dist/subagents/src/manager.js +29 -10
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +23 -2
- package/dist/subagents/src/view.js +96 -30
- package/dist/trajectory/assets/index.html +347 -231
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +377 -257
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +347 -231
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +143 -42
- package/dist/trajectory/src/server.js +134 -9
- package/dist/trajectory/src/server.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-execution.ts +36 -42
- package/src/background-widget.ts +25 -18
- package/src/decoders.ts +2 -1
- package/src/host-navigator.ts +58 -14
- package/src/host-view.ts +93 -37
- package/src/host.ts +51 -21
- package/src/index.ts +3 -1
- package/src/registry.ts +2 -1
- package/src/roles.ts +326 -0
- package/src/settings.ts +206 -0
- package/src/trajectory.ts +76 -7
- package/src/types.ts +1 -1
- package/src/utils.ts +8 -1
- package/src/validation.ts +12 -356
- package/subagents/README.md +1 -1
- package/subagents/src/contracts.ts +4 -0
- package/subagents/src/index.ts +3 -2
- package/subagents/src/manager.ts +28 -10
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +97 -28
- package/trajectory/src/assets/index.html +347 -231
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +118 -38
- package/trajectory/src/server.ts +112 -12
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
if (value === "*")
|
|
16
|
+
return value;
|
|
17
|
+
let expanded = value === "~" ? homedir() : value.startsWith("~/") || value.startsWith("~\\") ? join(homedir(), value.slice(2)) : value;
|
|
18
|
+
if (expanded.startsWith("file://"))
|
|
19
|
+
expanded = fileURLToPath(expanded);
|
|
20
|
+
const resolved = resolve(dirname(settingsPath), expanded);
|
|
21
|
+
if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\"))
|
|
22
|
+
return expanded;
|
|
23
|
+
if (resourcePatternHasMagic(expanded)) {
|
|
24
|
+
const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
|
|
25
|
+
const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
|
|
26
|
+
const rootBoundary = separatorIndex === 0 || (separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved));
|
|
27
|
+
const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
|
|
28
|
+
const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
|
|
29
|
+
return `${canonicalPath(prefix)}${suffix}`;
|
|
30
|
+
}
|
|
31
|
+
return canonicalPath(resolved);
|
|
32
|
+
}
|
|
33
|
+
export function validateSelectorList(value, path, kind, errorCode = "INVALID_SETTINGS", normalizeExtensions = true) {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (!Array.isArray(value))
|
|
37
|
+
fail(errorCode, `${path}.${kind} must be an array`);
|
|
38
|
+
const normalized = [];
|
|
39
|
+
for (const [index, entry] of value.entries()) {
|
|
40
|
+
if (typeof entry !== "string" || !entry.trim())
|
|
41
|
+
fail(errorCode, `${path}.${kind}[${String(index)}] must be a non-empty string`);
|
|
42
|
+
let selector = entry.trim();
|
|
43
|
+
if (kind === "extensions" && normalizeExtensions) {
|
|
44
|
+
const negated = selector.startsWith("!");
|
|
45
|
+
const body = negated ? selector.slice(1) : selector;
|
|
46
|
+
if (!body)
|
|
47
|
+
fail(errorCode, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
|
|
48
|
+
try {
|
|
49
|
+
selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
fail(errorCode, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
validateResourcePattern(selector);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
fail(errorCode, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
|
|
60
|
+
}
|
|
61
|
+
normalized.push(selector);
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze(normalized);
|
|
64
|
+
}
|
|
65
|
+
function validateWorktreePostCreateCommand(value, settingsPath) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
69
|
+
fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
|
|
70
|
+
return Object.freeze(value.map((entry, index) => {
|
|
71
|
+
if (typeof entry !== "string" || !entry.trim())
|
|
72
|
+
fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
|
|
73
|
+
return entry;
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
function selectorsFromSettings(settings) {
|
|
77
|
+
return {
|
|
78
|
+
...(settings.skills === undefined ? {} : { skills: settings.skills }),
|
|
79
|
+
...(settings.extensions === undefined ? {} : { extensions: settings.extensions }),
|
|
80
|
+
...(settings.tools === undefined ? {} : { tools: settings.tools }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function selectorSet(value) { return { skills: [...(value?.skills ?? [])], extensions: [...(value?.extensions ?? [])], ...(value?.tools === undefined ? {} : { tools: [...value.tools] }) }; }
|
|
84
|
+
export function validateContextFileScopes(value, rolePath) {
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (!Array.isArray(value) || !value.every(isContextFileScope))
|
|
88
|
+
fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
|
|
89
|
+
return [...value];
|
|
90
|
+
}
|
|
91
|
+
export function validateWorkflowExtensionSettings(value, settingsPath, errorCode = "INVALID_SETTINGS") {
|
|
92
|
+
if (value === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
const base = `${settingsPath}.extensionSettings`;
|
|
95
|
+
if (!object(value))
|
|
96
|
+
fail(errorCode, `${base} must be an object`);
|
|
97
|
+
const normalized = {};
|
|
98
|
+
for (const [namespace, raw] of Object.entries(value)) {
|
|
99
|
+
if (!validWorkflowExtensionNamespace(namespace))
|
|
100
|
+
fail(errorCode, `${base} contains an invalid namespace: ${namespace}`);
|
|
101
|
+
if (namespace === "herdr") {
|
|
102
|
+
if (!object(raw))
|
|
103
|
+
fail(errorCode, `${base}.herdr must be an object`);
|
|
104
|
+
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode"))
|
|
105
|
+
fail(errorCode, `${base}.herdr contains an unsupported setting`);
|
|
106
|
+
if (raw.enableFullyInspectableMode !== undefined && typeof raw.enableFullyInspectableMode !== "boolean")
|
|
107
|
+
fail(errorCode, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
|
|
108
|
+
normalized.herdr = Object.freeze({ ...(raw.enableFullyInspectableMode === undefined ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode }) });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (namespace === "trajectory") {
|
|
112
|
+
if (!object(raw))
|
|
113
|
+
fail(errorCode, `${base}.trajectory must be an object`);
|
|
114
|
+
if (Object.keys(raw).some((key) => key !== "port"))
|
|
115
|
+
fail(errorCode, `${base}.trajectory contains an unsupported setting`);
|
|
116
|
+
if (raw.port !== undefined && (!positiveInteger(raw.port) || raw.port > 65535))
|
|
117
|
+
fail(errorCode, `${base}.trajectory.port must be an integer from 1 to 65535`);
|
|
118
|
+
normalized.trajectory = Object.freeze({ ...(raw.port === undefined ? {} : { port: raw.port }) });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!jsonValue(raw))
|
|
122
|
+
fail(errorCode, `${base}.${namespace} must be JSON-compatible`);
|
|
123
|
+
normalized[namespace] = structuredClone(raw);
|
|
124
|
+
}
|
|
125
|
+
return deepFreeze(normalized);
|
|
126
|
+
}
|
|
127
|
+
function positiveRetentionInteger(value) { return positiveInteger(value) && Number.isSafeInteger(value); }
|
|
128
|
+
function validateRetention(value, settingsPath) {
|
|
129
|
+
if (value === undefined)
|
|
130
|
+
return undefined;
|
|
131
|
+
const base = `${settingsPath}.retention`;
|
|
132
|
+
if (!object(value))
|
|
133
|
+
fail("INVALID_SETTINGS", `${base} must be an object`);
|
|
134
|
+
const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
|
|
135
|
+
if (unknown)
|
|
136
|
+
fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
|
|
137
|
+
if (value.olderThanDays !== undefined && !positiveRetentionInteger(value.olderThanDays))
|
|
138
|
+
fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
|
|
139
|
+
if (value.maxTerminalRuns !== undefined && !positiveRetentionInteger(value.maxTerminalRuns))
|
|
140
|
+
fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
|
|
141
|
+
return Object.freeze({ ...(value.olderThanDays === undefined ? {} : { olderThanDays: value.olderThanDays }), ...(value.maxTerminalRuns === undefined ? {} : { maxTerminalRuns: value.maxTerminalRuns }) });
|
|
142
|
+
}
|
|
143
|
+
function parseSettings(path, partial) {
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (isNodeError(error, "ENOENT"))
|
|
150
|
+
return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
|
|
151
|
+
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
152
|
+
}
|
|
153
|
+
if (!object(parsed))
|
|
154
|
+
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"])]);
|
|
156
|
+
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
157
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources"))
|
|
158
|
+
fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
159
|
+
if (unknown)
|
|
160
|
+
fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
161
|
+
const concurrency = parsed.concurrency === undefined ? (partial ? undefined : DEFAULT_SETTINGS.concurrency) : parsed.concurrency;
|
|
162
|
+
if (concurrency !== undefined && (!positiveInteger(concurrency) || concurrency > 16))
|
|
163
|
+
fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
164
|
+
const backgroundWidget = parsed.backgroundWidget === undefined ? (partial ? undefined : DEFAULT_SETTINGS.backgroundWidget) : parsed.backgroundWidget;
|
|
165
|
+
if (backgroundWidget !== undefined && typeof backgroundWidget !== "boolean")
|
|
166
|
+
fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
167
|
+
const modelAliases = parsed.modelAliases === undefined ? undefined : validateModelAliases(parsed.modelAliases, path);
|
|
168
|
+
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
169
|
+
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
170
|
+
const extensions = validateSelectorList(parsed.extensions, path, "extensions");
|
|
171
|
+
const extensionSettings = parsed.extensionSettings === undefined ? undefined : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
|
|
172
|
+
const retention = validateRetention(parsed.retention, path);
|
|
173
|
+
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(modelAliases === undefined ? {} : { modelAliases }),
|
|
176
|
+
...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
|
|
177
|
+
...(extensionSettings === undefined ? {} : { extensionSettings }), ...(tools === undefined ? {} : { tools }), ...(retention === undefined ? {} : { retention }),
|
|
178
|
+
...(worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand }),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export function loadSettings(path = workflowSettingsPath()) { return parseSettings(path, false); }
|
|
182
|
+
export function loadSettingsOverrides(path) { return parseSettings(path, true); }
|
|
183
|
+
export function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
|
|
184
|
+
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
|
+
};
|
|
206
|
+
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 }),
|
|
216
|
+
});
|
|
217
|
+
return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
|
|
218
|
+
}
|
|
219
|
+
export function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
|
|
220
|
+
for (const name of names) {
|
|
221
|
+
try {
|
|
222
|
+
const target = modelCapability(name, aliases, knownModels, settingsPath);
|
|
223
|
+
if (!availableModels.has(target))
|
|
224
|
+
unknownModel(name, target, settingsPath);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw annotateModelAliasError(error, name);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
|
|
232
|
+
const resolved = resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath);
|
|
233
|
+
const global = selectorSet(selectorsFromSettings(resolved.global));
|
|
234
|
+
const project = selectorSet(selectorsFromSettings(resolved.project));
|
|
235
|
+
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) } };
|
|
237
|
+
}
|
|
238
|
+
export function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
|
|
239
|
+
const normalized = validateModelAliases(aliases, path);
|
|
240
|
+
let parsed = {};
|
|
241
|
+
try {
|
|
242
|
+
loadSettings(path);
|
|
243
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (!isNodeError(error, "ENOENT"))
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
if (!object(parsed))
|
|
250
|
+
fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
251
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
252
|
+
atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}\n`, true);
|
|
253
|
+
}
|
package/dist/src/trajectory.d.ts
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
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 } 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 TrajectoryRunRecord = Omit<PersistedRun, "agents"> & {
|
|
28
|
+
readonly agents: readonly TrajectoryAgent[];
|
|
29
|
+
};
|
|
6
30
|
export type TrajectoryRun = {
|
|
7
|
-
run:
|
|
31
|
+
run: TrajectoryRunRecord;
|
|
8
32
|
snapshot: Readonly<LaunchSnapshot>;
|
|
9
33
|
awaiting: readonly AwaitingCheckpoint[];
|
|
10
34
|
createdAt?: string;
|
|
@@ -58,6 +82,7 @@ export type TrajectorySubagent = {
|
|
|
58
82
|
readonly code: string;
|
|
59
83
|
readonly message: string;
|
|
60
84
|
} | TrajectorySubagentArtifact;
|
|
85
|
+
output: TrajectoryAgentOutput;
|
|
61
86
|
transcript: readonly unknown[];
|
|
62
87
|
};
|
|
63
88
|
export type TrajectorySubagentMetadata = Omit<TrajectorySubagent, "transcript"> & {
|
|
@@ -104,7 +129,6 @@ export type TrajectoryPublisherInput = {
|
|
|
104
129
|
cwd: string;
|
|
105
130
|
sessionId: string;
|
|
106
131
|
port?: number;
|
|
107
|
-
themes: boolean;
|
|
108
132
|
loadRuns: TrajectoryRunLoader;
|
|
109
133
|
loadSubagents: TrajectorySubagentLoader;
|
|
110
134
|
loadMetadata: TrajectoryPublisherMetadataLoader;
|
|
@@ -120,6 +144,7 @@ export declare function withPiToolDescriptionsForTools(tools: readonly string[],
|
|
|
120
144
|
}[];
|
|
121
145
|
export declare function withResolvedAttemptResources(attempt: AgentAttemptSummary, cwd: string): Promise<AgentAttemptSummary>;
|
|
122
146
|
export declare function withResolvedResources(run: PersistedRun, cwd: string): Promise<PersistedRun>;
|
|
147
|
+
export declare function applyTrajectoryAgentOutputs(run: PersistedRun, operations: readonly CompletedOperation[]): TrajectoryRunRecord;
|
|
123
148
|
export declare function loadTrajectoryRuns(cwd: string, sessionId: string, home?: string, overlay?: (run: PersistedRun) => PersistedRun): Promise<readonly TrajectoryRun[]>;
|
|
124
149
|
export declare function createTrajectoryRunLoader(cwd: string, sessionId: string, home?: string, overlay?: (run: PersistedRun) => PersistedRun): TrajectoryRunLoader;
|
|
125
150
|
export declare function createTrajectoryRunMetadataLoader(cwd: string, sessionId: string, home?: string, overlay?: (run: PersistedRun) => PersistedRun): TrajectoryRunMetadataLoader;
|
package/dist/src/trajectory.js
CHANGED
|
@@ -11,6 +11,23 @@ import { isNodeError, jsonValue, object, resourcePatternHasMagic, selectResource
|
|
|
11
11
|
import { canonicalPath, sameFilesystemPath } from "./paths.js";
|
|
12
12
|
export const TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
|
|
13
13
|
const TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
|
|
14
|
+
function subagentOutput(options) {
|
|
15
|
+
if (options.resultTruncated)
|
|
16
|
+
return { status: "truncated", kind: "result", path: options.resultPath, bytes: options.resultBytes ?? 0 };
|
|
17
|
+
if (options.result !== undefined)
|
|
18
|
+
return { status: "available", value: options.result, bytes: serializedJsonBytes(options.result) };
|
|
19
|
+
if (options.failureTruncated)
|
|
20
|
+
return { status: "truncated", kind: "failure", path: options.failurePath, bytes: options.failureBytes ?? 0 };
|
|
21
|
+
if (options.failure !== undefined)
|
|
22
|
+
return { status: "failed", code: options.failure.code, message: options.failure.message };
|
|
23
|
+
if (options.state === "failed" || options.state === "stopped") {
|
|
24
|
+
const error = options.statusError ?? options.attempt?.error;
|
|
25
|
+
return { status: options.state === "stopped" ? "cancelled" : "failed", code: error?.code ?? (options.state === "stopped" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Subagent ${options.state}` };
|
|
26
|
+
}
|
|
27
|
+
if (options.state === "completed")
|
|
28
|
+
return { status: "unavailable" };
|
|
29
|
+
return { status: "pending" };
|
|
30
|
+
}
|
|
14
31
|
export { isTrajectoryAction, isTrajectoryTarget, trajectoryActionError } from "./trajectory-contracts.js";
|
|
15
32
|
function sessionFile(reference) {
|
|
16
33
|
const locator = reference?.locator;
|
|
@@ -254,11 +271,48 @@ function withoutAgentActivities(run) {
|
|
|
254
271
|
});
|
|
255
272
|
return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
|
|
256
273
|
}
|
|
274
|
+
function serializedJsonBytes(value) {
|
|
275
|
+
const serialized = JSON.stringify(value);
|
|
276
|
+
return Buffer.byteLength(serialized, "utf8");
|
|
277
|
+
}
|
|
278
|
+
function failedAgentOutput(agent) {
|
|
279
|
+
const error = [...(agent.attemptDetails ?? [])].reverse().find((attempt) => attempt.error)?.error;
|
|
280
|
+
const status = agent.state === "cancelled" ? "cancelled" : "failed";
|
|
281
|
+
return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
|
|
282
|
+
}
|
|
283
|
+
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
|
+
}
|
|
288
|
+
if (agent.state === "failed" || agent.state === "cancelled")
|
|
289
|
+
return failedAgentOutput(agent);
|
|
290
|
+
if (agent.state === "completed")
|
|
291
|
+
return { status: "unavailable" };
|
|
292
|
+
return { status: "pending" };
|
|
293
|
+
}
|
|
294
|
+
export function applyTrajectoryAgentOutputs(run, operations) {
|
|
295
|
+
const byPath = new Map(operations.map((operation) => [operation.path, operation]));
|
|
296
|
+
const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === undefined ? undefined : byPath.get(agent.resultPath)) }));
|
|
297
|
+
return { ...run, agents };
|
|
298
|
+
}
|
|
299
|
+
function overlayTrajectoryRun(run, overlay) {
|
|
300
|
+
const overlaid = overlay(run);
|
|
301
|
+
const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
|
|
302
|
+
const agents = overlaid.agents.map((agent) => {
|
|
303
|
+
const previous = outputs.get(agent.id);
|
|
304
|
+
const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, undefined);
|
|
305
|
+
return { ...agent, output };
|
|
306
|
+
});
|
|
307
|
+
return { ...overlaid, agents };
|
|
308
|
+
}
|
|
257
309
|
async function loadTrajectoryRun(store, includeTranscripts = true) {
|
|
258
310
|
const value = await store.load();
|
|
259
311
|
const summary = await store.loadSummary().catch(() => undefined);
|
|
260
312
|
const prompts = await store.systemPrompts().catch(() => []);
|
|
261
|
-
const
|
|
313
|
+
const prepared = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
|
|
314
|
+
const operations = await store.replayableOperations().catch(() => []);
|
|
315
|
+
const run = applyTrajectoryAgentOutputs(prepared, operations);
|
|
262
316
|
return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...(summary?.createdAt === undefined ? {} : { createdAt: summary.createdAt }), transcripts: includeTranscripts ? await runTranscripts(run) : {} };
|
|
263
317
|
}
|
|
264
318
|
export async function loadTrajectoryRuns(cwd, sessionId, home = homedir(), overlay) {
|
|
@@ -266,7 +320,7 @@ export async function loadTrajectoryRuns(cwd, sessionId, home = homedir(), overl
|
|
|
266
320
|
for (const runId of await listRunIds(cwd, sessionId, home, false)) {
|
|
267
321
|
try {
|
|
268
322
|
const value = await loadTrajectoryRun(new RunStore(cwd, sessionId, runId, home));
|
|
269
|
-
loaded.push(overlay ? { ...value, run:
|
|
323
|
+
loaded.push(overlay ? { ...value, run: overlayTrajectoryRun(value.run, overlay) } : value);
|
|
270
324
|
}
|
|
271
325
|
catch { /* Ignore corrupt or concurrently removed runs. */ }
|
|
272
326
|
}
|
|
@@ -305,7 +359,7 @@ function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature,
|
|
|
305
359
|
entry = { value, stateMtimeMs: (await fileMtime(join(store.directory, "state.json"))) ?? 0, journalMtimeMs: (await fileMtime(join(store.directory, "journal.json"))) ?? 0, transcripts };
|
|
306
360
|
cache.set(runId, entry);
|
|
307
361
|
}
|
|
308
|
-
loaded.push(overlay ? { ...entry.value, run:
|
|
362
|
+
loaded.push(overlay ? { ...entry.value, run: overlayTrajectoryRun(entry.value.run, overlay) } : entry.value);
|
|
309
363
|
}
|
|
310
364
|
catch {
|
|
311
365
|
cache.delete(runId); /* Ignore corrupt or concurrently removed runs. */
|
|
@@ -513,6 +567,7 @@ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscri
|
|
|
513
567
|
...(attempt === undefined ? {} : { attempt }),
|
|
514
568
|
...(result === undefined ? {} : { result }),
|
|
515
569
|
...(failure === undefined ? {} : { failure }),
|
|
570
|
+
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
571
|
};
|
|
517
572
|
}
|
|
518
573
|
catch {
|
package/dist/src/types.d.ts
CHANGED
package/dist/src/utils.d.ts
CHANGED
|
@@ -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>;
|
|
@@ -55,3 +55,4 @@ export declare function createLaunchSnapshot(input: Omit<import("./types.js").La
|
|
|
55
55
|
identityVersion?: number;
|
|
56
56
|
}): Readonly<import("./types.js").LaunchSnapshot>;
|
|
57
57
|
export declare function loadLaunchSnapshot(input: import("./types.js").LaunchSnapshot): Readonly<import("./types.js").LaunchSnapshot>;
|
|
58
|
+
export declare function validateSchema(schema: unknown, at?: string): asserts schema is JsonSchema;
|
package/dist/src/utils.js
CHANGED
|
@@ -223,3 +223,13 @@ export function resourcePatternHasMagic(pattern) { return /[*?\x5b\x5d{}()]/.tes
|
|
|
223
223
|
export function unmatchedResourcePatterns(patterns, resources) { return patterns.filter((pattern) => !resources.some((resource) => resourcePatternMatches(resource, pattern))); }
|
|
224
224
|
export function createLaunchSnapshot(input) { return deepFreeze(structuredClone({ ...input, identityVersion: input.identityVersion ?? LAUNCH_SNAPSHOT_IDENTITY_VERSION })); }
|
|
225
225
|
export function loadLaunchSnapshot(input) { return deepFreeze(structuredClone(input)); }
|
|
226
|
+
export function validateSchema(schema, at = "schema") {
|
|
227
|
+
if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema))
|
|
228
|
+
fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
|
|
229
|
+
if (typeof schema.type !== "string" && !Array.isArray(schema.type) && schema.$ref === undefined && schema.anyOf === undefined && schema.oneOf === undefined && schema.allOf === undefined && schema.const === undefined && schema.enum === undefined)
|
|
230
|
+
fail("INVALID_SCHEMA", `${at} has no JSON Schema shape`);
|
|
231
|
+
if (schema.required !== undefined && (!Array.isArray(schema.required) || schema.required.some((key) => typeof key !== "string")))
|
|
232
|
+
fail("INVALID_SCHEMA", `${at}.required must be an array of strings`);
|
|
233
|
+
if (schema.properties !== undefined && !object(schema.properties))
|
|
234
|
+
fail("INVALID_SCHEMA", `${at}.properties must be an object`);
|
|
235
|
+
}
|
package/dist/src/validation.d.ts
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CheckpointInput, JsonValue, PreflightCapabilities, PreflightResult, ShellOptions, StaticWorkflowCall, ValidatedWorkflowLaunch, WorkflowMetadata, WorkflowValidationContext, WorkflowValidationParameters } from "./types.js";
|
|
2
2
|
import type { WorkflowRegistryApi } from "./registry.js";
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
3
|
+
export { validateSchema } from "./utils.js";
|
|
4
|
+
export { loadAgentDefinitions, loadProjectAgentDefinitions, parseRoleMarkdown, workflowRoleDirectories } from "./roles.js";
|
|
5
|
+
export type { WorkflowRoleDirectoryInput } from "./roles.js";
|
|
6
|
+
export { DEFAULT_SETTINGS, loadSettings, loadSettingsOverrides, resolveAgentResourcePolicy, resolveWorkflowSettings, saveModelAliases, validateContextFileScopes, validateModelAliasAvailability, validateSelectorList, validateWorkflowExtensionSettings, workflowProjectSettingsPath, workflowSettingsPath } from "./settings.js";
|
|
5
7
|
export declare function validateCheckpoint(value: unknown): CheckpointInput;
|
|
6
|
-
export declare function workflowSettingsPath(agentDir?: string): string;
|
|
7
|
-
export declare function validateWorkflowExtensionSettings(value: unknown, settingsPath: string, errorCode?: "INVALID_SETTINGS" | "INVALID_METADATA", applyDefaults?: boolean): WorkflowExtensionSettings | undefined;
|
|
8
|
-
export declare function loadSettings(path?: string): Readonly<WorkflowSettings>;
|
|
9
|
-
export declare function loadSettingsOverrides(path: string): Readonly<WorkflowSettingsOverrides>;
|
|
10
|
-
export declare function resolveWorkflowSettings(cwd: string, projectTrusted: boolean, globalSettingsPath?: string): WorkflowSettingsResolution;
|
|
11
|
-
export declare function validateModelAliasAvailability(aliases: Readonly<Record<string, string>>, names: readonly string[], availableModels: ReadonlySet<string>, knownModels: ReadonlySet<string>, settingsPath?: string): void;
|
|
12
|
-
export declare function resolveAgentResourcePolicy(cwd: string, projectTrusted: boolean, globalSettingsPath?: string): AgentResourcePolicy;
|
|
13
|
-
export declare function saveModelAliases(path?: string, aliases?: Readonly<Record<string, string>>): void;
|
|
14
|
-
export declare function parseRoleMarkdown(content: string, strict?: boolean, rolePath?: string): AgentDefinition;
|
|
15
|
-
export declare function workflowRoleDirectories(agentDir?: string): readonly string[];
|
|
16
|
-
export type WorkflowRoleDirectoryInput = string | WorkflowRoleDirectoryRegistration;
|
|
17
|
-
export declare function loadProjectAgentDefinitions(cwd: string): Readonly<Record<string, AgentDefinition>>;
|
|
18
|
-
export declare function loadAgentDefinitions(cwd: string, agentDir?: string, projectTrusted?: boolean, extensionRoleDirectories?: readonly WorkflowRoleDirectoryInput[]): Readonly<Record<string, AgentDefinition>>;
|
|
19
8
|
export declare function instrumentWorkflow(script: string): string;
|
|
20
9
|
export declare function workflowPrompt(template: string, values: Readonly<Record<string, JsonValue>>): string;
|
|
21
|
-
export declare function validateSchema(schema: unknown, at?: string): asserts schema is JsonSchema;
|
|
22
10
|
export declare function validateAgentOptions(value: unknown): Readonly<Record<string, JsonValue>>;
|
|
23
11
|
export declare function validateShellOptions(value: unknown): ShellOptions;
|
|
24
12
|
export declare function validateShellCommand(value: unknown): string;
|