@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.
- package/CHANGELOG.md +145 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +442 -162
- package/dist/src/background-widget.d.ts +5 -1
- package/dist/src/background-widget.js +5 -10
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +59 -37
- package/dist/src/execution.d.ts +6 -2
- package/dist/src/execution.js +48 -7
- package/dist/src/host-delivery.js +5 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +11 -8
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +6 -1
- package/dist/src/host-view.js +46 -20
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -118
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1985 -1690
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +8 -5
- package/dist/src/paths.js +24 -13
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +29 -71
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +19 -3
- package/dist/src/settings.js +63 -50
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +12 -3
- package/dist/src/trajectory.js +32 -11
- package/dist/src/types.d.ts +129 -45
- package/dist/src/types.js +5 -8
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +46 -6
- package/dist/src/validation.d.ts +14 -3
- package/dist/src/validation.js +143 -87
- package/dist/starter/index.js +8 -11
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +8 -9
- package/dist/subagents/index.js +904 -956
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +48 -31
- package/dist/subagents/src/contracts.js +20 -9
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +14 -8
- package/dist/subagents/src/manager.js +52 -73
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +1 -1
- package/dist/subagents/src/view.js +15 -68
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +35 -13
- package/dist/trajectory/index.js +576 -932
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +35 -13
- package/dist/trajectory/src/index.js +134 -104
- package/dist/trajectory/src/server.js +268 -27
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +10 -10
- package/skills/pi-extensible-workflows/SKILL.md +12 -8
- package/src/agent-execution.ts +412 -164
- package/src/background-widget.ts +5 -11
- package/src/decoders.ts +51 -37
- package/src/execution.ts +46 -9
- package/src/host-delivery.ts +5 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +23 -28
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +41 -16
- package/src/host.ts +229 -142
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -9
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +28 -64
- package/src/runtime/agent-runner.ts +2 -0
- package/src/script-tools.ts +76 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +56 -50
- package/src/store.ts +18 -2
- package/src/trajectory.ts +32 -15
- package/src/types.ts +82 -34
- package/src/utils.ts +42 -5
- package/src/validation.ts +121 -80
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -9
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +10 -16
- package/subagents/src/contracts.ts +21 -11
- package/subagents/src/index.ts +17 -8
- package/subagents/src/manager.ts +48 -128
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +16 -63
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +35 -13
- package/trajectory/src/index.ts +112 -91
- package/trajectory/src/server.ts +46 -10
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -325
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -326
package/src/settings.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
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, type ToolAnnotations } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { AgentResourcePolicy, AgentResourceSelectorSources, AgentResourceSelectors, AgentResourceSelectorSet, ContextFileScope, JsonValue, WorkflowExtensionSettings, WorkflowRetentionSettings, WorkflowSettings, WorkflowSettingsOverrides, WorkflowSettingsResolution, WorkflowSettingsSources } from "./types.js";
|
|
3
6
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
7
|
+
import { resolve } from "node:path";
|
|
5
8
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import type { AgentResourcePolicy, AgentResourceSelectors, AgentResourceSelectorSet, ContextFileScope, JsonValue, WorkflowExtensionSettings, WorkflowRetentionSettings, WorkflowSettings, WorkflowSettingsOverrides, WorkflowSettingsResolution, WorkflowSettingsSources } from "./types.js";
|
|
8
|
-
import { isContextFileScope } from "./types.js";
|
|
9
|
-
import { annotateModelAliasError, deepFreeze, errorText, fail, isNodeError, jsonValue, modelCapability, object, positiveInteger, resourcePatternHasMagic, unknownModel, validateModelAliases, validateResourcePattern, validWorkflowExtensionNamespace } from "./utils.js";
|
|
10
9
|
import { canonicalPath, workflowProjectSettingsPath } from "./paths.js";
|
|
11
10
|
export { workflowProjectSettingsPath } from "./paths.js";
|
|
11
|
+
import { isContextFileScope } from "./types.js";
|
|
12
|
+
import { annotateModelAliasError, deepFreeze, errorText, fail, isNodeError, modelCapability, object, positiveInteger, unknownModel, validateModelAliases } from "./utils.js";
|
|
13
|
+
import { jsonValue, resourcePatternHasMagic, validateResourcePattern, validWorkflowExtensionNamespace } from "./utils.js";
|
|
12
14
|
|
|
13
|
-
const
|
|
15
|
+
const WORKFLOW_DIRECTORY = "pi-extensible-workflows";
|
|
14
16
|
export const DEFAULT_SETTINGS: Readonly<WorkflowSettings> = Object.freeze({ concurrency: 8, backgroundWidget: true });
|
|
15
|
-
export function workflowSettingsPath(agentDir = getAgentDir()): string { return join(agentDir,
|
|
17
|
+
export function workflowSettingsPath(agentDir = getAgentDir()): string { return join(agentDir, WORKFLOW_DIRECTORY, "settings.json"); }
|
|
16
18
|
function normalizedResourcePath(value: string, settingsPath: string): string {
|
|
17
|
-
|
|
19
|
+
// Built-in extensions are named `builtin:<name>`, not by a path.
|
|
20
|
+
if (value === "*" || value.startsWith("builtin:")) return value;
|
|
18
21
|
let expanded = value === "~" ? homedir() : value.startsWith("~/") || value.startsWith("~\\") ? join(homedir(), value.slice(2)) : value;
|
|
19
22
|
if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
|
|
20
23
|
const resolved = resolve(dirname(settingsPath), expanded);
|
|
@@ -63,18 +66,19 @@ function selectorsFromSettings(settings: Readonly<WorkflowSettings | WorkflowSet
|
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
function selectorSet(value: AgentResourceSelectors | undefined): AgentResourceSelectorSet { return { skills: [...(value?.skills ?? [])], extensions: [...(value?.extensions ?? [])], ...(value?.tools === undefined ? {} : { tools: [...value.tools] }) }; }
|
|
66
|
-
export function validateContextFileScopes(value: unknown,
|
|
69
|
+
export function validateContextFileScopes(value: unknown, sourcePath: string): readonly ContextFileScope[] | undefined {
|
|
67
70
|
if (value === undefined) return undefined;
|
|
68
|
-
if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${
|
|
71
|
+
if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${sourcePath}.contextFiles must be an array containing only global, project, or cwd`);
|
|
69
72
|
return [...value];
|
|
70
73
|
}
|
|
71
74
|
export function validateWorkflowExtensionSettings(value: unknown, settingsPath: string, errorCode: "INVALID_SETTINGS" | "INVALID_METADATA" = "INVALID_SETTINGS"): WorkflowExtensionSettings | undefined {
|
|
72
75
|
if (value === undefined) return undefined;
|
|
76
|
+
if (!object(value) || !jsonValue(value)) fail(errorCode, `${settingsPath}.extensionSettings must be a JSON object`);
|
|
77
|
+
for (const namespace of Object.keys(value)) if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode, `${settingsPath}.extensionSettings contains an invalid namespace: ${namespace}`);
|
|
78
|
+
const generic = value;
|
|
73
79
|
const base = `${settingsPath}.extensionSettings`;
|
|
74
|
-
if (!object(value)) fail(errorCode, `${base} must be an object`);
|
|
75
80
|
const normalized: Record<string, JsonValue> = {};
|
|
76
|
-
for (const [namespace, raw] of Object.entries(
|
|
77
|
-
if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode, `${base} contains an invalid namespace: ${namespace}`);
|
|
81
|
+
for (const [namespace, raw] of Object.entries(generic)) {
|
|
78
82
|
if (namespace === "herdr") {
|
|
79
83
|
if (!object(raw)) fail(errorCode, `${base}.herdr must be an object`);
|
|
80
84
|
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode, `${base}.herdr contains an unsupported setting`);
|
|
@@ -89,7 +93,6 @@ export function validateWorkflowExtensionSettings(value: unknown, settingsPath:
|
|
|
89
93
|
normalized.trajectory = Object.freeze({ ...(raw.port === undefined ? {} : { port: raw.port }) });
|
|
90
94
|
continue;
|
|
91
95
|
}
|
|
92
|
-
if (!jsonValue(raw)) fail(errorCode, `${base}.${namespace} must be JSON-compatible`);
|
|
93
96
|
normalized[namespace] = structuredClone(raw);
|
|
94
97
|
}
|
|
95
98
|
return deepFreeze(normalized as WorkflowExtensionSettings);
|
|
@@ -115,7 +118,7 @@ function parseSettings(path: string, partial: boolean): Readonly<WorkflowSetting
|
|
|
115
118
|
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
116
119
|
}
|
|
117
120
|
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
118
|
-
const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget"]) ]);
|
|
121
|
+
const allowed = new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...(partial ? [] : ["backgroundWidget", "codemodeTools"]) ]);
|
|
119
122
|
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
120
123
|
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
121
124
|
if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
@@ -123,6 +126,8 @@ function parseSettings(path: string, partial: boolean): Readonly<WorkflowSetting
|
|
|
123
126
|
if (concurrency !== undefined && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
124
127
|
const backgroundWidget = parsed.backgroundWidget === undefined ? (partial ? undefined : DEFAULT_SETTINGS.backgroundWidget) : parsed.backgroundWidget;
|
|
125
128
|
if (backgroundWidget !== undefined && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
129
|
+
const codemodeTools = parsed.codemodeTools;
|
|
130
|
+
if (codemodeTools !== undefined && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none") fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
|
|
126
131
|
const modelAliases = parsed.modelAliases === undefined ? undefined : validateModelAliases(parsed.modelAliases, path);
|
|
127
132
|
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
128
133
|
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
@@ -131,49 +136,45 @@ function parseSettings(path: string, partial: boolean): Readonly<WorkflowSetting
|
|
|
131
136
|
const retention = validateRetention(parsed.retention, path);
|
|
132
137
|
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
133
138
|
return Object.freeze({
|
|
134
|
-
...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(modelAliases === undefined ? {} : { modelAliases }),
|
|
139
|
+
...(concurrency === undefined ? {} : { concurrency }), ...(backgroundWidget === undefined ? {} : { backgroundWidget }), ...(codemodeTools === undefined ? {} : { codemodeTools }), ...(modelAliases === undefined ? {} : { modelAliases }),
|
|
135
140
|
...(skills === undefined ? {} : { skills }), ...(extensions === undefined ? {} : { extensions }),
|
|
136
141
|
...(extensionSettings === undefined ? {} : { extensionSettings }), ...(tools === undefined ? {} : { tools }), ...(retention === undefined ? {} : { retention }),
|
|
137
142
|
...(worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand }),
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
145
|
export function loadSettings(path = workflowSettingsPath()): Readonly<WorkflowSettings> { return parseSettings(path, false); }
|
|
146
|
+
const READ_ONLY_WORKFLOW_TOOLS: ReadonlySet<string> = new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
|
|
147
|
+
/**
|
|
148
|
+
* Exposure and annotations of a workflow or subagent tool. Under the global `codemodeTools` setting,
|
|
149
|
+
* `model-only` keeps the model's direct access and stops codemode scripts from calling the tool.
|
|
150
|
+
* Tools are registered once at load, so the setting is global and applies after a reload.
|
|
151
|
+
* Read-only tools carry `readOnlyHint` so permission extensions need not confirm them.
|
|
152
|
+
*/
|
|
153
|
+
export function workflowToolExposure(name: string, codemodeTools: WorkflowSettings["codemodeTools"]): { exposure?: "model-only"; annotations?: ToolAnnotations } {
|
|
154
|
+
const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
|
|
155
|
+
return { ...(codemodeTools === "none" || (codemodeTools === "read-only" && !readOnly) ? { exposure: "model-only" as const } : {}), ...(readOnly ? { annotations: { readOnlyHint: true } } : {}) };
|
|
156
|
+
}
|
|
157
|
+
/** The `codemodeTools` setting at load time. Invalid settings keep the default; launches report them. */
|
|
158
|
+
export function loadCodemodeToolsSetting(agentDir?: string): WorkflowSettings["codemodeTools"] {
|
|
159
|
+
try { return loadSettings(workflowSettingsPath(agentDir)).codemodeTools; } catch { return undefined; }
|
|
160
|
+
}
|
|
141
161
|
export function loadSettingsOverrides(path: string): Readonly<WorkflowSettingsOverrides> { return parseSettings(path, true); }
|
|
142
|
-
export function resolveWorkflowSettings(cwd: string, projectTrusted: boolean, globalSettingsPath = workflowSettingsPath()): WorkflowSettingsResolution {
|
|
162
|
+
export function resolveWorkflowSettings(cwd: string, projectTrusted: boolean, globalSettingsPath = workflowSettingsPath()): WorkflowSettingsResolution & { selectorSources: AgentResourceSelectorSources } {
|
|
143
163
|
const projectSettingsPath = workflowProjectSettingsPath(cwd);
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
skills: [...(
|
|
152
|
-
extensions: [...(
|
|
153
|
-
...(
|
|
164
|
+
const consumerGlobal = loadSettings(globalSettingsPath);
|
|
165
|
+
const consumerProject: Readonly<WorkflowSettingsOverrides> = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
|
|
166
|
+
const global = consumerGlobal;
|
|
167
|
+
const project = consumerProject;
|
|
168
|
+
const selectorSources = { global: selectorsFromSettings(global), project: selectorsFromSettings(project) };
|
|
169
|
+
const effective: Readonly<WorkflowSettings> = Object.freeze({
|
|
170
|
+
...global, ...project,
|
|
171
|
+
skills: [...(global.skills ?? []), ...(project.skills ?? [])],
|
|
172
|
+
extensions: [...(global.extensions ?? []), ...(project.extensions ?? [])],
|
|
173
|
+
...(global.tools === undefined && project.tools === undefined ? {} : { tools: [...(global.tools ?? []), ...(project.tools ?? [])] }),
|
|
154
174
|
});
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
|
|
159
|
-
modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
|
|
160
|
-
skills: sourceFor("skills"), extensions: sourceFor("extensions"), tools: sourceFor("tools"),
|
|
161
|
-
...(extensionSettings === undefined ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath }),
|
|
162
|
-
...(project.retention === undefined && global.retention === undefined ? {} : { retention: project.retention === undefined ? globalSettingsPath : projectSettingsPath }),
|
|
163
|
-
...(projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== undefined ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}),
|
|
164
|
-
};
|
|
165
|
-
const effective = Object.freeze({
|
|
166
|
-
concurrency: project.concurrency ?? global.concurrency,
|
|
167
|
-
backgroundWidget: global.backgroundWidget ?? true,
|
|
168
|
-
...(projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === undefined ? {} : { modelAliases: global.modelAliases }),
|
|
169
|
-
...(effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== undefined || project.skills !== undefined ? { skills: effectiveSelectors.skills } : {}),
|
|
170
|
-
...(hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {}),
|
|
171
|
-
...(extensionSettings === undefined ? {} : { extensionSettings }),
|
|
172
|
-
...(effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== undefined || project.tools !== undefined ? { tools: effectiveSelectors.tools } : {}),
|
|
173
|
-
...((project.retention ?? global.retention) === undefined ? {} : { retention: project.retention ?? global.retention }),
|
|
174
|
-
...(projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === undefined ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }),
|
|
175
|
-
});
|
|
176
|
-
return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
|
|
175
|
+
const sources: WorkflowSettingsSources = { concurrency: project.concurrency === undefined ? globalSettingsPath : projectSettingsPath, modelAliases: project.modelAliases === undefined ? globalSettingsPath : projectSettingsPath };
|
|
176
|
+
for (const key of ["skills", "extensions", "tools", "extensionSettings", "retention", "worktreePostCreateCommand"] as const) if (effective[key] !== undefined) sources[key] = project[key] === undefined ? globalSettingsPath : projectSettingsPath;
|
|
177
|
+
return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources, selectorSources };
|
|
177
178
|
}
|
|
178
179
|
export function validateModelAliasAvailability(aliases: Readonly<Record<string, string>>, names: readonly string[], availableModels: ReadonlySet<string>, knownModels: ReadonlySet<string>, settingsPath?: string): void {
|
|
179
180
|
for (const name of names) {
|
|
@@ -188,7 +189,7 @@ export function resolveAgentResourcePolicy(cwd: string, projectTrusted: boolean,
|
|
|
188
189
|
const global = selectorSet(selectorsFromSettings(resolved.global));
|
|
189
190
|
const project = selectorSet(selectorsFromSettings(resolved.project));
|
|
190
191
|
const effective = selectorSet(selectorsFromSettings(resolved.effective));
|
|
191
|
-
return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources:
|
|
192
|
+
return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: resolved.selectorSources };
|
|
192
193
|
}
|
|
193
194
|
export function saveModelAliases(path = workflowSettingsPath(), aliases: Readonly<Record<string, string>> = {}): void {
|
|
194
195
|
const normalized = validateModelAliases(aliases, path);
|
|
@@ -204,3 +205,8 @@ export function saveModelAliases(path = workflowSettingsPath(), aliases: Readonl
|
|
|
204
205
|
atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}\n`, true);
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
|
|
209
|
+
export function canonicalExtensionSelector(selector: string, cwd: string): string {
|
|
210
|
+
const negated = selector.startsWith("!");
|
|
211
|
+
return `${negated ? "!" : ""}${normalizedResourcePath(negated ? selector.slice(1) : selector, join(cwd, "settings.json"))}`;
|
|
212
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { access, mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { HARD_TERMINAL_RUN_STATES, WorkflowError, type JsonValue, type LaunchSnapshot, type WorkflowErrorCode, type WorkflowRunEvent } from "./types.js";
|
|
7
|
+
import { HARD_TERMINAL_RUN_STATES, WorkflowError, type PreparedAgentConfiguration, type JsonValue, type LaunchSnapshot, type WorkflowErrorCode, type WorkflowLaunchMode, type WorkflowRunEvent } from "./types.js";
|
|
8
8
|
import { coerceWorkflowError, errorText, isNodeError, loadLaunchSnapshot, object, positiveInteger, SerialLane } from "./utils.js";
|
|
9
9
|
import { budgetUsage } from "./budget.js";
|
|
10
10
|
import {
|
|
@@ -32,7 +32,7 @@ function summaryFromRun(run: PersistedRun, directory: string, journal: Journal,
|
|
|
32
32
|
const failedAt = run.failedAt ?? run.error?.failedAt;
|
|
33
33
|
const replayablePaths = [...new Set([...(run.retry?.completedPaths ?? []), ...Object.keys(journal.completed)])];
|
|
34
34
|
const incompletePaths = [...new Set([...(run.retry?.incompletePaths ?? []), ...(failedAt ? [failedAt] : [])])];
|
|
35
|
-
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,
|
|
35
|
+
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) };
|
|
36
36
|
}
|
|
37
37
|
function systemPromptStoragePath(directory: string): string { return join(directory, SYSTEM_PROMPT_STORAGE); }
|
|
38
38
|
function systemPromptRecordsPath(directory: string): string { return join(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS); }
|
|
@@ -163,6 +163,22 @@ export class RunStore {
|
|
|
163
163
|
return write;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
async saveAgentConfiguration(key: string, configuration: PreparedAgentConfiguration): Promise<void> {
|
|
167
|
+
await this.launchSnapshotLane.run(async () => {
|
|
168
|
+
const snapshot = decodeLaunchSnapshot(await json(join(this.directory, "snapshot.json")));
|
|
169
|
+
if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
|
|
170
|
+
await atomicJson(join(this.directory, "snapshot.json"), { ...snapshot, agentConfigurations: { ...snapshot.agentConfigurations, [key]: configuration } });
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async setLaunchMode(launchMode: WorkflowLaunchMode): Promise<void> {
|
|
175
|
+
await this.launchSnapshotLane.run(async () => {
|
|
176
|
+
const snapshot = decodeLaunchSnapshot(await json(join(this.directory, "snapshot.json")));
|
|
177
|
+
if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
|
|
178
|
+
await atomicJson(join(this.directory, "snapshot.json"), { ...snapshot, launchMode });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
166
182
|
async saveSnapshot(snapshot: Readonly<LaunchSnapshot>): Promise<void> {
|
|
167
183
|
const write = this.launchSnapshotLane.run(() => atomicJson(join(this.directory, "snapshot.json"), snapshot));
|
|
168
184
|
await write;
|
package/src/trajectory.ts
CHANGED
|
@@ -5,12 +5,13 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
5
5
|
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { listRunIds, RunStore, type AwaitingCheckpoint, type CompletedOperation, type EffectiveSystemPrompt, type PersistedRun } from "./persistence.js";
|
|
7
7
|
import { navigatorAttentionSortByState } from "./host-view.js";
|
|
8
|
-
import type { AgentAttemptSummary, AgentRecord, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference } from "./types.js";
|
|
8
|
+
import type { AgentAttemptSummary, AgentRecord, JsonValue, LaunchSnapshot, WorkflowAgentSessionReference, WorkflowErrorCode, WorkflowScriptCall } from "./types.js";
|
|
9
9
|
import { normalizeSubagentRunRequest, type SubagentProgress, type SubagentRunRequest, type SubagentStatus } from "../subagents/src/contracts.js";
|
|
10
10
|
import { statusValue, subagentErrorValue } from "../subagents/src/decode.js";
|
|
11
11
|
import { isNodeError, jsonValue, object, resourcePatternHasMagic, selectResourcesByLayers } from "./utils.js";
|
|
12
12
|
import type { TrajectoryAction, TrajectoryTarget } from "./trajectory-contracts.js";
|
|
13
13
|
import { canonicalPath, sameFilesystemPath } from "./paths.js";
|
|
14
|
+
import { TOOL_TIMING_ENTRY_TYPE } from "./tool-timing.js";
|
|
14
15
|
|
|
15
16
|
export const TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
|
|
16
17
|
const TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
|
|
@@ -22,7 +23,8 @@ export type TrajectoryAgentOutput =
|
|
|
22
23
|
| { readonly status: "truncated"; readonly kind: "result" | "failure"; readonly bytes: number; readonly path?: string }
|
|
23
24
|
| { readonly status: "unavailable" };
|
|
24
25
|
export type TrajectoryAgent = AgentRecord & { readonly output: TrajectoryAgentOutput };
|
|
25
|
-
export type
|
|
26
|
+
export type TrajectoryScriptCall = WorkflowScriptCall & { readonly output: TrajectoryAgentOutput | { readonly status: "completed" } | { readonly status: "failed" | "cancelled"; readonly code: WorkflowErrorCode } };
|
|
27
|
+
export type TrajectoryRunRecord = Omit<PersistedRun, "agents" | "scriptCalls"> & { readonly agents: readonly TrajectoryAgent[]; readonly scriptCalls?: readonly TrajectoryScriptCall[] };
|
|
26
28
|
export type TrajectoryRun = {
|
|
27
29
|
run: TrajectoryRunRecord;
|
|
28
30
|
snapshot: Readonly<LaunchSnapshot>;
|
|
@@ -64,7 +66,6 @@ export type TrajectorySubagent = {
|
|
|
64
66
|
sessionId: string;
|
|
65
67
|
cwd: string;
|
|
66
68
|
label?: string;
|
|
67
|
-
role?: string;
|
|
68
69
|
mode: "background" | "foreground";
|
|
69
70
|
state: SubagentStatus["state"];
|
|
70
71
|
startedAt?: number;
|
|
@@ -124,7 +125,7 @@ function transcriptToolCallId(value: unknown): string | undefined {
|
|
|
124
125
|
for (const part of message.content) if (object(part) && typeof part.id === "string") return part.id;
|
|
125
126
|
return undefined;
|
|
126
127
|
}
|
|
127
|
-
function isTimingTranscriptEntry(value: unknown): boolean { return object(value) && value.type === "custom" && value.customType ===
|
|
128
|
+
export function isTimingTranscriptEntry(value: unknown): boolean { return object(value) && value.type === "custom" && value.customType === TOOL_TIMING_ENTRY_TYPE; }
|
|
128
129
|
function timingToolCallId(value: unknown): string | undefined {
|
|
129
130
|
if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data)) return undefined;
|
|
130
131
|
return typeof value.data.toolCallId === "string" ? value.data.toolCallId : undefined;
|
|
@@ -243,7 +244,8 @@ export function withPiToolDescriptionsForTools(tools: readonly string[], cwd: st
|
|
|
243
244
|
function canonicalExtensionSelector(selector: string, base: string): string {
|
|
244
245
|
const negated = selector.startsWith("!");
|
|
245
246
|
const body = negated ? selector.slice(1) : selector;
|
|
246
|
-
|
|
247
|
+
// Built-in extensions are named `builtin:<name>`, not by a path.
|
|
248
|
+
if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
|
|
247
249
|
const resolved = resolve(base, body);
|
|
248
250
|
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
249
251
|
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
@@ -279,8 +281,8 @@ async function resolveResourceInspection(inspection: ResourceInspection | undefi
|
|
|
279
281
|
let discovered: DiscoveredResources;
|
|
280
282
|
try { discovered = await discoveredResources(cwd); } catch { return inspection; }
|
|
281
283
|
const sources = inspection.selectorSources ?? { global: inspection.selectors, project: {} };
|
|
282
|
-
const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.
|
|
283
|
-
const extensions = inspection.extensions.length ? inspection.extensions : selectResourcesByLayers([sources.global.extensions, sources.project.extensions, sources.
|
|
284
|
+
const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.call?.skills], discovered.skills);
|
|
285
|
+
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);
|
|
284
286
|
return skills === inspection.skills && extensions === inspection.extensions ? inspection : { ...inspection, skills, extensions };
|
|
285
287
|
}
|
|
286
288
|
export async function withResolvedAttemptResources(attempt: AgentAttemptSummary, cwd: string): Promise<AgentAttemptSummary> {
|
|
@@ -319,11 +321,17 @@ function failedAgentOutput(agent: PersistedRun["agents"][number]): TrajectoryAge
|
|
|
319
321
|
const status = agent.state === "cancelled" ? "cancelled" : "failed";
|
|
320
322
|
return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
|
|
321
323
|
}
|
|
324
|
+
function operationOutput(operation: CompletedOperation): TrajectoryAgentOutput {
|
|
325
|
+
const bytes = serializedJsonBytes(operation.value);
|
|
326
|
+
return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
|
|
327
|
+
}
|
|
328
|
+
function outputForCall(call: WorkflowScriptCall, operation: CompletedOperation | undefined): TrajectoryScriptCall["output"] {
|
|
329
|
+
if (operation !== undefined) return call.kind === "tool" ? { status: "completed" } : operationOutput(operation);
|
|
330
|
+
if (call.error) return { status: call.error.code === "CANCELLED" ? "cancelled" : "failed", code: call.error.code, ...(call.kind === "shell" ? { message: call.error.message ?? "Shell failed" } : {}) };
|
|
331
|
+
return call.finishedAt === undefined ? { status: "pending" } : { status: "unavailable" };
|
|
332
|
+
}
|
|
322
333
|
function outputForAgent(agent: PersistedRun["agents"][number], operation: CompletedOperation | undefined): TrajectoryAgentOutput {
|
|
323
|
-
if (operation !== undefined)
|
|
324
|
-
const bytes = serializedJsonBytes(operation.value);
|
|
325
|
-
return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
|
|
326
|
-
}
|
|
334
|
+
if (operation !== undefined) return operationOutput(operation);
|
|
327
335
|
if (agent.state === "failed" || agent.state === "cancelled") return failedAgentOutput(agent);
|
|
328
336
|
if (agent.state === "completed") return { status: "unavailable" };
|
|
329
337
|
return { status: "pending" };
|
|
@@ -331,17 +339,27 @@ function outputForAgent(agent: PersistedRun["agents"][number], operation: Comple
|
|
|
331
339
|
export function applyTrajectoryAgentOutputs(run: PersistedRun, operations: readonly CompletedOperation[]): TrajectoryRunRecord {
|
|
332
340
|
const byPath = new Map(operations.map((operation) => [operation.path, operation]));
|
|
333
341
|
const agents: readonly TrajectoryAgent[] = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === undefined ? undefined : byPath.get(agent.resultPath)) }));
|
|
334
|
-
|
|
342
|
+
const scriptCalls = run.scriptCalls?.map((call): TrajectoryScriptCall => {
|
|
343
|
+
const recorded = { ...call, output: outputForCall(call, byPath.get(call.path)) };
|
|
344
|
+
if (call.kind === "tool") {
|
|
345
|
+
delete recorded.input;
|
|
346
|
+
delete recorded.inputBytes;
|
|
347
|
+
if (call.error) recorded.error = { code: call.error.code };
|
|
348
|
+
}
|
|
349
|
+
return recorded;
|
|
350
|
+
});
|
|
351
|
+
const rest: Omit<PersistedRun, "scriptCalls"> = run;
|
|
352
|
+
return { ...rest, agents, ...(scriptCalls === undefined ? {} : { scriptCalls }) };
|
|
335
353
|
}
|
|
336
354
|
function overlayTrajectoryRun(run: TrajectoryRunRecord, overlay: (run: PersistedRun) => PersistedRun): TrajectoryRunRecord {
|
|
337
|
-
const overlaid = overlay(run);
|
|
355
|
+
const overlaid: Omit<PersistedRun, "scriptCalls"> = overlay(run);
|
|
338
356
|
const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
|
|
339
357
|
const agents: readonly TrajectoryAgent[] = overlaid.agents.map((agent) => {
|
|
340
358
|
const previous = outputs.get(agent.id);
|
|
341
359
|
const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, undefined);
|
|
342
360
|
return { ...agent, output };
|
|
343
361
|
});
|
|
344
|
-
return { ...overlaid, agents };
|
|
362
|
+
return { ...overlaid, agents, ...(run.scriptCalls === undefined ? {} : { scriptCalls: run.scriptCalls }) };
|
|
345
363
|
}
|
|
346
364
|
async function loadTrajectoryRun(store: RunStore, includeTranscripts = true): Promise<TrajectoryRun> {
|
|
347
365
|
const value = await store.load();
|
|
@@ -546,7 +564,6 @@ async function loadTrajectorySubagent(directory: string, cwd: string, sessionId:
|
|
|
546
564
|
return {
|
|
547
565
|
id, sessionId, cwd, mode: request.mode ?? "background", state: status.state, request, tools, transcript,
|
|
548
566
|
...(request.label === undefined ? {} : { label: request.label }),
|
|
549
|
-
...(request.role === undefined ? {} : { role: request.role }),
|
|
550
567
|
...(status.startedAt === undefined ? {} : { startedAt: status.startedAt }),
|
|
551
568
|
...(status.finishedAt === undefined ? {} : { finishedAt: status.finishedAt }),
|
|
552
569
|
...(status.attempts === undefined ? {} : { attempts: status.attempts }),
|