@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/dist/trajectory/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// trajectory/src/index.ts
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createHash as createHash3 } from "node:crypto";
|
|
4
|
-
import { mkdir as
|
|
4
|
+
import { mkdir as mkdir4, readFile as readFile7, rm as rm6 } from "node:fs/promises";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
|
-
import { dirname as
|
|
7
|
-
import { fileURLToPath
|
|
8
|
-
import { getAgentDir as
|
|
6
|
+
import { dirname as dirname3, join as join7 } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { getAgentDir as getAgentDir3 } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
// src/trajectory-host-handle.ts
|
|
11
11
|
var TRAJECTORY_HOST_KEY = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.trajectory-host");
|
|
@@ -17,10 +17,12 @@ function clearTrajectoryHost(host) {
|
|
|
17
17
|
if (globalTrajectoryHosts[TRAJECTORY_HOST_KEY] === host) globalTrajectoryHosts[TRAJECTORY_HOST_KEY] = void 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// src/
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
20
|
+
// src/io.ts
|
|
21
|
+
import { execFile } from "node:child_process";
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
25
|
+
import { promisify } from "node:util";
|
|
24
26
|
|
|
25
27
|
// src/types.ts
|
|
26
28
|
var RUN_STATES = ["queued", "running", "pausing", "paused", "awaiting_input", "completed", "failed", "stopped", "interrupted", "budget_exhausted"];
|
|
@@ -35,7 +37,7 @@ var ERROR_CODES = [
|
|
|
35
37
|
"INVALID_SCHEMA",
|
|
36
38
|
"UNKNOWN_MODEL",
|
|
37
39
|
"UNKNOWN_TOOL",
|
|
38
|
-
"
|
|
40
|
+
"UNKNOWN_AGENT",
|
|
39
41
|
"RUN_OWNED",
|
|
40
42
|
"RUN_NOT_FOUND",
|
|
41
43
|
"REGISTRY_FROZEN",
|
|
@@ -47,6 +49,7 @@ var ERROR_CODES = [
|
|
|
47
49
|
"AGENT_FAILED",
|
|
48
50
|
"AGENT_RESULT_COLLECTED",
|
|
49
51
|
"RESULT_INVALID",
|
|
52
|
+
"TOOL_FAILED",
|
|
50
53
|
"CANCELLED",
|
|
51
54
|
"WORKER_UNRESPONSIVE",
|
|
52
55
|
"WORKTREE_FAILED",
|
|
@@ -55,13 +58,14 @@ var ERROR_CODES = [
|
|
|
55
58
|
"INTERNAL_ERROR"
|
|
56
59
|
];
|
|
57
60
|
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
58
|
-
var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
|
|
59
|
-
var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
|
|
60
61
|
var CONTEXT_FILE_SCOPES = ["global", "project", "cwd"];
|
|
61
62
|
function isContextFileScope(value) {
|
|
62
63
|
return CONTEXT_FILE_SCOPES.some((scope) => scope === value);
|
|
63
64
|
}
|
|
65
|
+
var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
|
|
66
|
+
var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
|
|
64
67
|
var WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
68
|
+
var LAUNCH_SNAPSHOT_IDENTITY_VERSION = 6;
|
|
65
69
|
var WORKFLOW_ERROR_BRAND = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-error");
|
|
66
70
|
var WorkflowError = class extends Error {
|
|
67
71
|
constructor(code, message) {
|
|
@@ -139,90 +143,13 @@ function errorText(error) {
|
|
|
139
143
|
function coerceWorkflowError(code, error) {
|
|
140
144
|
return error instanceof WorkflowError && error.code === code ? error : new WorkflowError(code, errorText(error));
|
|
141
145
|
}
|
|
142
|
-
function errorCode(error) {
|
|
143
|
-
if (error instanceof WorkflowError) return isWorkflowErrorCode(error.code) ? error.code : void 0;
|
|
144
|
-
if (!object(error)) return void 0;
|
|
145
|
-
return isWorkflowErrorCode(error.code) ? error.code : void 0;
|
|
146
|
-
}
|
|
147
|
-
function fail(code, message) {
|
|
148
|
-
throw new WorkflowError(code, message);
|
|
149
|
-
}
|
|
150
|
-
function byPriorityThenName(left, right) {
|
|
151
|
-
return left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
152
|
-
}
|
|
153
146
|
function isThinkingLevel(value) {
|
|
154
147
|
return THINKING_LEVELS.some((level) => level === value);
|
|
155
148
|
}
|
|
156
|
-
var MODEL_ALIAS_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
157
149
|
var WORKFLOW_EXTENSION_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
158
150
|
function validWorkflowExtensionNamespace(value) {
|
|
159
151
|
return WORKFLOW_EXTENSION_NAMESPACE.test(value) && value !== "__proto__" && value !== "constructor" && value !== "prototype";
|
|
160
152
|
}
|
|
161
|
-
function parseModelReference(value) {
|
|
162
|
-
const match = /^([^/:\s]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
|
|
163
|
-
if (!match?.[1] || !match[2]) fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
|
|
164
|
-
const thinking = match[3];
|
|
165
|
-
if (thinking !== void 0 && !isThinkingLevel(thinking)) fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
|
|
166
|
-
return { provider: match[1], model: match[2], ...thinking !== void 0 ? { thinking } : {} };
|
|
167
|
-
}
|
|
168
|
-
var MODEL_ALIAS_ERROR_NAME = /* @__PURE__ */ Symbol("modelAliasErrorName");
|
|
169
|
-
function aliasError(message, settingsPath, name) {
|
|
170
|
-
const error = new WorkflowError("CONFIG_ERROR", `${message} (settings: ${settingsPath})`);
|
|
171
|
-
if (name) Object.defineProperty(error, MODEL_ALIAS_ERROR_NAME, { value: name, configurable: true });
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
function modelAliasName(value, aliases) {
|
|
175
|
-
const name = /^([^/:\s]+)(?::[^:\s]+)?$/.exec(value)?.[1];
|
|
176
|
-
return name && Object.prototype.hasOwnProperty.call(aliases, name) ? name : void 0;
|
|
177
|
-
}
|
|
178
|
-
function validateModelAliases(value, settingsPath = "workflow settings") {
|
|
179
|
-
if (!object(value)) aliasError("modelAliases must be an object", settingsPath);
|
|
180
|
-
const aliases = {};
|
|
181
|
-
for (const [name, target] of Object.entries(value)) {
|
|
182
|
-
if (!MODEL_ALIAS_NAME.test(name)) aliasError(`Invalid model alias name: ${name}`, settingsPath, name);
|
|
183
|
-
if (typeof target !== "string" || !target.trim()) aliasError(`Invalid model alias target for ${name}`, settingsPath, name);
|
|
184
|
-
aliases[name] = target;
|
|
185
|
-
}
|
|
186
|
-
for (const name of Object.keys(aliases)) {
|
|
187
|
-
try {
|
|
188
|
-
resolveModelReference(name, aliases);
|
|
189
|
-
} catch (error) {
|
|
190
|
-
aliasError(`Invalid model alias target for ${name}: ${errorText(error)}`, settingsPath, name);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return Object.freeze(aliases);
|
|
194
|
-
}
|
|
195
|
-
function unknownModel(value, target, settingsPath) {
|
|
196
|
-
const resolved = target ? ` resolved to ${target}` : "";
|
|
197
|
-
const path = settingsPath ? ` (settings: ${settingsPath})` : "";
|
|
198
|
-
fail("UNKNOWN_MODEL", `Unknown model${target ? " alias" : ""} ${value}${resolved}${path}`);
|
|
199
|
-
}
|
|
200
|
-
function resolveModelReference(value, aliases = {}, knownModels, settingsPath) {
|
|
201
|
-
const resolveReference = (reference, chain) => {
|
|
202
|
-
if (reference.includes("/")) return parseModelReference(reference);
|
|
203
|
-
const match = /^([^:\s]+)(?::([^:\s]+))?$/.exec(reference);
|
|
204
|
-
const thinking = match?.[2];
|
|
205
|
-
if (!match?.[1] || thinking !== void 0 && !isThinkingLevel(thinking)) unknownModel(reference, void 0, settingsPath);
|
|
206
|
-
const alias = modelAliasName(reference, aliases);
|
|
207
|
-
if (alias) {
|
|
208
|
-
if (chain.includes(alias)) fail("UNKNOWN_MODEL", `Circular model alias: ${[...chain, alias].join(" -> ")}${settingsPath ? ` (settings: ${settingsPath})` : ""}`);
|
|
209
|
-
const { [alias]: target } = aliases;
|
|
210
|
-
if (typeof target !== "string") unknownModel(reference, void 0, settingsPath);
|
|
211
|
-
const parsed = resolveReference(target, [...chain, alias]);
|
|
212
|
-
return thinking !== void 0 ? { ...parsed, thinking } : parsed;
|
|
213
|
-
}
|
|
214
|
-
const candidates = [...knownModels ?? []].filter((model) => model.slice(model.indexOf("/") + 1) === match[1]);
|
|
215
|
-
if (candidates.length === 1) {
|
|
216
|
-
const [candidate] = candidates;
|
|
217
|
-
if (candidate !== void 0) {
|
|
218
|
-
const parsed = parseModelReference(candidate);
|
|
219
|
-
return thinking !== void 0 ? { ...parsed, thinking } : parsed;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
unknownModel(reference, void 0, settingsPath);
|
|
223
|
-
};
|
|
224
|
-
return resolveReference(value, []);
|
|
225
|
-
}
|
|
226
153
|
var RESOURCE_PATTERN_OPTIONS = { dot: true, nonegate: true, nocomment: true };
|
|
227
154
|
function resourcePatternBody(pattern) {
|
|
228
155
|
return pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
@@ -230,12 +157,6 @@ function resourcePatternBody(pattern) {
|
|
|
230
157
|
function resourcePatternPath(value) {
|
|
231
158
|
return value.replaceAll("\\", "/");
|
|
232
159
|
}
|
|
233
|
-
function validateResourcePattern(pattern) {
|
|
234
|
-
const body = resourcePatternBody(pattern);
|
|
235
|
-
if (!body) throw new Error(`Empty minimatch pattern ${JSON.stringify(pattern)}`);
|
|
236
|
-
const matcher = new Minimatch(resourcePatternPath(body), RESOURCE_PATTERN_OPTIONS);
|
|
237
|
-
if (matcher.makeRe() === false) throw new Error(`Invalid minimatch pattern ${JSON.stringify(pattern)}`);
|
|
238
|
-
}
|
|
239
160
|
function resourcePatternMatches(resource, pattern) {
|
|
240
161
|
const body = resourcePatternBody(pattern);
|
|
241
162
|
if (body === "*") return true;
|
|
@@ -260,13 +181,137 @@ function resourcePatternHasMagic(pattern) {
|
|
|
260
181
|
function loadLaunchSnapshot(input) {
|
|
261
182
|
return deepFreeze(structuredClone(input));
|
|
262
183
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
184
|
+
|
|
185
|
+
// src/io.ts
|
|
186
|
+
var execute = promisify(execFile);
|
|
187
|
+
var RENAME_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 400, 800];
|
|
188
|
+
var syncSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
189
|
+
function retryableRenameError(error) {
|
|
190
|
+
return isNodeError(error, "EPERM") || isNodeError(error, "EBUSY") || isNodeError(error, "EACCES");
|
|
191
|
+
}
|
|
192
|
+
async function renameWithRetry(from, to) {
|
|
193
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
194
|
+
try {
|
|
195
|
+
await rename(from, to);
|
|
196
|
+
return;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
199
|
+
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
200
|
+
await new Promise((resolve4) => setTimeout(resolve4, delayMs));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function renameSyncWithRetry(from, to) {
|
|
205
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
206
|
+
try {
|
|
207
|
+
renameSync(from, to);
|
|
208
|
+
return;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
211
|
+
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
212
|
+
Atomics.wait(syncSleepBuffer, 0, 0, delayMs);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
var gitIdentity = {
|
|
217
|
+
GIT_AUTHOR_NAME: "pi-extensible-workflows",
|
|
218
|
+
GIT_AUTHOR_EMAIL: "pi-extensible-workflows@localhost",
|
|
219
|
+
GIT_COMMITTER_NAME: "pi-extensible-workflows",
|
|
220
|
+
GIT_COMMITTER_EMAIL: "pi-extensible-workflows@localhost",
|
|
221
|
+
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
222
|
+
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
223
|
+
};
|
|
224
|
+
function atomicWriteFile(path, content, sync = false) {
|
|
225
|
+
const temporary = `${path}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
226
|
+
if (sync) {
|
|
227
|
+
try {
|
|
228
|
+
writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
|
|
229
|
+
renameSyncWithRetry(temporary, path);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
try {
|
|
232
|
+
rmSync(temporary, { force: true });
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
return writeFile(temporary, content, { encoding: "utf8", mode: 384 }).then(() => renameWithRetry(temporary, path)).catch(async (error) => {
|
|
240
|
+
try {
|
|
241
|
+
await rm(temporary, { force: true });
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async function atomicJson(path, value) {
|
|
248
|
+
const serialized = JSON.stringify(value);
|
|
249
|
+
if (typeof serialized !== "string") throw new WorkflowError("INTERNAL_ERROR", `Cannot serialize JSON for ${path}`);
|
|
250
|
+
await atomicWriteFile(path, `${serialized}
|
|
251
|
+
`);
|
|
252
|
+
}
|
|
253
|
+
async function atomicPrettyJson(path, value) {
|
|
254
|
+
await atomicWriteFile(path, `${JSON.stringify(value, null, 2)}
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
async function json(path) {
|
|
258
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
259
|
+
}
|
|
260
|
+
async function git(cwd, args, extraEnv = {}) {
|
|
261
|
+
const { stdout } = await execute("git", ["-c", "core.hooksPath=/dev/null", "-c", "commit.gpgSign=false", ...args], { cwd, env: { ...process.env, ...extraEnv }, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
|
|
262
|
+
return stdout;
|
|
268
263
|
}
|
|
269
264
|
|
|
265
|
+
// src/trajectory.ts
|
|
266
|
+
import { open as open2, readFile as readFile4, readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
267
|
+
import { homedir as homedir4 } from "node:os";
|
|
268
|
+
import { basename as basename2, dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
|
|
269
|
+
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir as getAgentDir2, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
270
|
+
|
|
271
|
+
// src/paths.ts
|
|
272
|
+
import { createHash } from "node:crypto";
|
|
273
|
+
import { realpathSync } from "node:fs";
|
|
274
|
+
import { homedir } from "node:os";
|
|
275
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
276
|
+
function safePart(value) {
|
|
277
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
278
|
+
}
|
|
279
|
+
function canonicalPath(path) {
|
|
280
|
+
const absolute = resolve(path);
|
|
281
|
+
try {
|
|
282
|
+
return realpathSync(absolute);
|
|
283
|
+
} catch {
|
|
284
|
+
return absolute;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function sameFilesystemPath(left, right) {
|
|
288
|
+
return canonicalPath(left) === canonicalPath(right);
|
|
289
|
+
}
|
|
290
|
+
function workflowProjectSettingsPath(cwd) {
|
|
291
|
+
return join(cwd, ".pi", "pi-extensible-workflows", "settings.json");
|
|
292
|
+
}
|
|
293
|
+
function projectStorageKey(cwd) {
|
|
294
|
+
const exact = resolve(cwd);
|
|
295
|
+
const slug = safePart(basename(exact)) || "root";
|
|
296
|
+
return `${slug}-${createHash("sha256").update(exact).digest("hex").slice(0, 12)}`;
|
|
297
|
+
}
|
|
298
|
+
function projectSessionsDirectory(cwd, home = homedir()) {
|
|
299
|
+
return join(home, ".pi", "workflows", "projects", projectStorageKey(cwd), "sessions");
|
|
300
|
+
}
|
|
301
|
+
function runsDirectory(cwd, sessionId, home = homedir()) {
|
|
302
|
+
return join(projectSessionsDirectory(cwd, home), safePart(sessionId), "runs");
|
|
303
|
+
}
|
|
304
|
+
function structuralPath(...names) {
|
|
305
|
+
if (names.length === 0 || names.some((name) => name.trim() === "")) throw new WorkflowError("INVALID_METADATA", "Structural paths require non-empty explicit names");
|
|
306
|
+
return names.map((name) => encodeURIComponent(name)).join("/");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/session-lease.ts
|
|
310
|
+
import { execFileSync } from "node:child_process";
|
|
311
|
+
import { link, mkdir, open, readFile as readFile2, readdir, rename as rename2, rm as rm2, stat } from "node:fs/promises";
|
|
312
|
+
import { homedir as homedir2 } from "node:os";
|
|
313
|
+
import { join as join2 } from "node:path";
|
|
314
|
+
|
|
270
315
|
// src/decoders.ts
|
|
271
316
|
var INVALID_PERSISTED_VALUE = /* @__PURE__ */ Symbol("invalid persisted value");
|
|
272
317
|
function integer(value) {
|
|
@@ -355,32 +400,19 @@ function decodeAgentResourceSelectors(value) {
|
|
|
355
400
|
function decodeContextFileScopes(value) {
|
|
356
401
|
return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : void 0);
|
|
357
402
|
}
|
|
358
|
-
function
|
|
359
|
-
if (!object(value)) return void 0;
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
|
|
366
|
-
const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
|
|
367
|
-
const overrideSystemPrompt = optionalBoolean(value.overrideSystemPrompt);
|
|
403
|
+
function decodePreparedAgentConfiguration(value) {
|
|
404
|
+
if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean" || typeof value.systemPromptAppend !== "string") return void 0;
|
|
405
|
+
const model = decodeModelSpec(value.model);
|
|
406
|
+
const tools = decodeStringArray(value.tools);
|
|
407
|
+
const skills = decodeStringArray(value.skills);
|
|
408
|
+
const extensions = decodeStringArray(value.extensions);
|
|
409
|
+
const settings = decodeWorkflowExtensions(value.settings);
|
|
368
410
|
const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return {
|
|
374
|
-
...prompt === void 0 ? {} : { prompt },
|
|
375
|
-
...description === void 0 ? {} : { description },
|
|
376
|
-
...foldedModel === void 0 ? {} : { model: foldedModel },
|
|
377
|
-
...tools === void 0 ? {} : { tools },
|
|
378
|
-
...skills === void 0 ? {} : { skills },
|
|
379
|
-
...extensions === void 0 ? {} : { extensions },
|
|
380
|
-
...overrideSystemPrompt === void 0 ? {} : { overrideSystemPrompt },
|
|
381
|
-
...contextFiles === void 0 ? {} : { contextFiles },
|
|
382
|
-
...extensionSettings === void 0 ? {} : { extensionSettings }
|
|
383
|
-
};
|
|
411
|
+
const label = optionalString(value.label);
|
|
412
|
+
const systemPrompt = optionalString(value.systemPrompt);
|
|
413
|
+
const excludeTools = value.excludeTools === void 0 ? void 0 : decodeStringArray(value.excludeTools);
|
|
414
|
+
if (!model || !tools || !skills || !extensions || !settings || label === INVALID_PERSISTED_VALUE || systemPrompt === INVALID_PERSISTED_VALUE || value.contextFiles !== void 0 && !contextFiles || value.excludeTools !== void 0 && !excludeTools) return void 0;
|
|
415
|
+
return { model, tools, skills, extensions, settings, projectTrusted: value.projectTrusted, systemPromptAppend: value.systemPromptAppend, ...contextFiles === void 0 ? {} : { contextFiles }, ...excludeTools === void 0 ? {} : { excludeTools }, ...label === void 0 ? {} : { label }, ...systemPrompt === void 0 ? {} : { systemPrompt } };
|
|
384
416
|
}
|
|
385
417
|
function decodeWorkflowMetadata(value) {
|
|
386
418
|
if (!object(value) || typeof value.name !== "string") return void 0;
|
|
@@ -489,17 +521,22 @@ function decodeScheduledAgentOptions(value) {
|
|
|
489
521
|
const parentBreadcrumb = optionalString(value.parentBreadcrumb);
|
|
490
522
|
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
491
523
|
const model = optionalString(value.model);
|
|
492
|
-
const role = typeof value.role === "string" ? value.role : void 0;
|
|
493
524
|
const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
|
|
494
525
|
const schema = value.schema === void 0 ? void 0 : decodeJsonObject(value.schema);
|
|
495
526
|
const retries = optionalNumber(value.retries);
|
|
496
527
|
const timeoutMs = value.timeoutMs;
|
|
528
|
+
if (value.projectTrusted !== void 0 && typeof value.projectTrusted !== "boolean") return void 0;
|
|
529
|
+
const rawCapabilities = value.capabilities;
|
|
530
|
+
const capabilities = rawCapabilities === void 0 ? void 0 : object(rawCapabilities) ? { tools: decodeStringArray(rawCapabilities.tools), skills: decodeStringArray(rawCapabilities.skills), extensions: decodeStringArray(rawCapabilities.extensions) } : void 0;
|
|
531
|
+
if (rawCapabilities !== void 0 && (!capabilities?.tools || !capabilities.skills || !capabilities.extensions)) return void 0;
|
|
532
|
+
const configuration = value.configuration === void 0 ? void 0 : decodePreparedAgentConfiguration(value.configuration);
|
|
533
|
+
if (value.configuration !== void 0 && configuration === void 0) return void 0;
|
|
497
534
|
const agentOptions = value.agentOptions === void 0 ? void 0 : decodeJsonObject(value.agentOptions);
|
|
498
535
|
const agentIdentity = value.agentIdentity === void 0 ? void 0 : decodeIdentity(value.agentIdentity);
|
|
499
536
|
const sessionPath = optionalString(value.sessionPath);
|
|
500
537
|
const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
501
538
|
const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
|
|
502
|
-
if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.
|
|
539
|
+
if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.contextFiles !== void 0 && !contextFiles || value.schema !== void 0 && !schema || retries === INVALID_PERSISTED_VALUE || retries !== void 0 && !integer(retries) || timeoutMs !== void 0 && timeoutMs !== null && !finiteNumber(timeoutMs) || value.agentOptions !== void 0 && !agentOptions || value.agentIdentity !== void 0 && !agentIdentity || value.extensionSettings !== void 0 && !extensionSettings) return void 0;
|
|
503
540
|
return {
|
|
504
541
|
label: value.label,
|
|
505
542
|
...requestedLabel === void 0 ? {} : { requestedLabel },
|
|
@@ -510,7 +547,6 @@ function decodeScheduledAgentOptions(value) {
|
|
|
510
547
|
...extensions === void 0 ? {} : { extensions },
|
|
511
548
|
...worktreeOwner === void 0 ? {} : { worktreeOwner },
|
|
512
549
|
...model === void 0 ? {} : { model },
|
|
513
|
-
...role === void 0 ? {} : { role },
|
|
514
550
|
...contextFiles === void 0 ? {} : { contextFiles },
|
|
515
551
|
...schema === void 0 ? {} : { schema },
|
|
516
552
|
...retries === void 0 ? {} : { retries },
|
|
@@ -518,6 +554,9 @@ function decodeScheduledAgentOptions(value) {
|
|
|
518
554
|
...sessionPath === void 0 ? {} : { sessionPath },
|
|
519
555
|
...continuity === void 0 ? {} : { continuity },
|
|
520
556
|
...agentOptions === void 0 ? {} : { agentOptions },
|
|
557
|
+
...configuration === void 0 ? {} : { configuration },
|
|
558
|
+
...value.projectTrusted === void 0 ? {} : { projectTrusted: value.projectTrusted },
|
|
559
|
+
...capabilities?.tools && capabilities.skills && capabilities.extensions ? { capabilities: { tools: capabilities.tools, skills: capabilities.skills, extensions: capabilities.extensions } } : {},
|
|
521
560
|
...agentIdentity === void 0 ? {} : { agentIdentity },
|
|
522
561
|
...extensionSettings === void 0 ? {} : { extensionSettings }
|
|
523
562
|
};
|
|
@@ -558,11 +597,10 @@ function decodeAgentResourceInspection(value) {
|
|
|
558
597
|
const sources = sourceRecord === void 0 ? void 0 : {
|
|
559
598
|
global: decodeAgentResourceSelectors(sourceRecord.global),
|
|
560
599
|
project: decodeAgentResourceSelectors(sourceRecord.project),
|
|
561
|
-
...sourceRecord.role === void 0 ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) },
|
|
562
600
|
...sourceRecord.call === void 0 ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }
|
|
563
601
|
};
|
|
564
|
-
if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== void 0 && (!sources || !sources.global || !sources.project || sources.
|
|
565
|
-
return { selectors: { skills: [...selectors.skills ?? []], extensions: [...selectors.extensions ?? []], tools: [...selectors.tools ?? []] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...sources === void 0 ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...sources.
|
|
602
|
+
if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== void 0 && (!sources || !sources.global || !sources.project || sources.call === void 0 && sourceRecord?.call !== void 0)) return void 0;
|
|
603
|
+
return { selectors: { skills: [...selectors.skills ?? []], extensions: [...selectors.extensions ?? []], tools: [...selectors.tools ?? []] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...sources === void 0 ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...sources.call === void 0 ? {} : { call: sources.call } } } };
|
|
566
604
|
}
|
|
567
605
|
function decodeAgentSetupSummary(value) {
|
|
568
606
|
if (!object(value) || typeof value.cwd !== "string") return void 0;
|
|
@@ -600,7 +638,6 @@ function decodeAgent(value) {
|
|
|
600
638
|
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
601
639
|
const handle = optionalString(value.handle);
|
|
602
640
|
const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
603
|
-
const role = optionalString(value.role);
|
|
604
641
|
const requestedModel = optionalString(value.requestedModel);
|
|
605
642
|
const startedAt = optionalNumber(value.startedAt);
|
|
606
643
|
const durationMs = optionalNumber(value.durationMs);
|
|
@@ -609,7 +646,7 @@ function decodeAgent(value) {
|
|
|
609
646
|
const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
|
|
610
647
|
const activity = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
|
|
611
648
|
const lastEventAt = optionalNumber(value.lastEventAt);
|
|
612
|
-
if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE ||
|
|
649
|
+
if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== void 0 && !positiveInteger(value.turn)) return void 0;
|
|
613
650
|
if (!model || !tools || value.structuralPath !== void 0 && !structuralPath2 || value.attemptDetails !== void 0 && !attemptDetails || value.accounting !== void 0 && !accounting || value.toolCalls !== void 0 && !toolCalls || value.activity !== void 0 && !activity) return void 0;
|
|
614
651
|
return {
|
|
615
652
|
...systemPrompt === void 0 ? {} : { systemPrompt },
|
|
@@ -626,7 +663,6 @@ function decodeAgent(value) {
|
|
|
626
663
|
...worktreeOwner === void 0 ? {} : { worktreeOwner },
|
|
627
664
|
...handle === void 0 ? {} : { handle, turn: value.turn },
|
|
628
665
|
...continuity === void 0 ? {} : { continuity },
|
|
629
|
-
...role === void 0 ? {} : { role },
|
|
630
666
|
...requestedModel === void 0 ? {} : { requestedModel },
|
|
631
667
|
model,
|
|
632
668
|
tools,
|
|
@@ -684,6 +720,22 @@ function decodeRunEvent(value) {
|
|
|
684
720
|
if (timestamp === INVALID_PERSISTED_VALUE) return void 0;
|
|
685
721
|
return { type: value.type, message: value.message, ...timestamp === void 0 ? {} : { timestamp } };
|
|
686
722
|
}
|
|
723
|
+
function decodeScriptCall(value) {
|
|
724
|
+
if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt)) return void 0;
|
|
725
|
+
const shell = value.kind === "shell";
|
|
726
|
+
if (shell && !jsonValue(value.input)) return void 0;
|
|
727
|
+
const inputBytes = shell ? optionalNumber(value.inputBytes) : void 0;
|
|
728
|
+
const phase = optionalString(value.phase);
|
|
729
|
+
const finishedAt = optionalNumber(value.finishedAt);
|
|
730
|
+
let error;
|
|
731
|
+
if (value.error !== void 0) {
|
|
732
|
+
if (shell) error = decodeWorkflowError(value.error);
|
|
733
|
+
else if (object(value.error) && typeof value.error.code === "string") error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
|
|
734
|
+
if (!error) return void 0;
|
|
735
|
+
}
|
|
736
|
+
if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE) return void 0;
|
|
737
|
+
return { kind: value.kind, name: value.name, path: value.path, ...shell && jsonValue(value.input) ? { input: value.input } : {}, ...inputBytes === void 0 ? {} : { inputBytes }, ...phase === void 0 ? {} : { phase }, startedAt: value.startedAt, ...finishedAt === void 0 ? {} : { finishedAt }, ...error === void 0 ? {} : { error } };
|
|
738
|
+
}
|
|
687
739
|
function decodeDelivery(value) {
|
|
688
740
|
if (!object(value)) return void 0;
|
|
689
741
|
const mode = value.mode;
|
|
@@ -713,6 +765,8 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
|
|
|
713
765
|
const budgetEvents = value.budgetEvents === void 0 ? void 0 : decodeArray(value.budgetEvents, decodeBudgetEvent);
|
|
714
766
|
const events = value.events === void 0 ? void 0 : decodeArray(value.events, decodeRunEvent);
|
|
715
767
|
const delivery = value.delivery === void 0 ? void 0 : decodeDelivery(value.delivery);
|
|
768
|
+
const scriptCalls = value.scriptCalls === void 0 ? void 0 : decodeArray(value.scriptCalls, decodeScriptCall);
|
|
769
|
+
if (value.scriptCalls !== void 0 && !scriptCalls) return void 0;
|
|
716
770
|
if (!agentSessions || !agents || parentRunId === INVALID_PERSISTED_VALUE || value.retry !== void 0 && !retry || phase === INVALID_PERSISTED_VALUE || value.phaseHistory !== void 0 && !phaseHistory || phaseHistoryIndex === INVALID_PERSISTED_VALUE || activeShells === INVALID_PERSISTED_VALUE || activeShellStartedAt === INVALID_PERSISTED_VALUE || value.activeShellsByPhase !== void 0 && !activeShellsByPhase || value.error !== void 0 && !error || failedAt === INVALID_PERSISTED_VALUE || value.budget !== void 0 && !budget || budgetVersion === INVALID_PERSISTED_VALUE || value.budgetVersion !== void 0 && !integer(budgetVersion) || value.usage !== void 0 && !usage || value.budgetEvents !== void 0 && !budgetEvents || value.events !== void 0 && !events || value.delivery !== void 0 && !delivery) return void 0;
|
|
717
771
|
if (value.agentSessions === void 0 && !allowLegacyAgentSessions) return void 0;
|
|
718
772
|
return {
|
|
@@ -731,6 +785,7 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
|
|
|
731
785
|
...activeShells === void 0 ? {} : { activeShells },
|
|
732
786
|
...activeShellStartedAt === void 0 ? {} : { activeShellStartedAt },
|
|
733
787
|
...activeShellsByPhase === void 0 ? {} : { activeShellsByPhase },
|
|
788
|
+
...scriptCalls === void 0 ? {} : { scriptCalls },
|
|
734
789
|
...error === void 0 ? {} : { error },
|
|
735
790
|
...failedAt === void 0 ? {} : { failedAt },
|
|
736
791
|
...budget === void 0 ? {} : { budget },
|
|
@@ -755,13 +810,11 @@ function decodeLaunchSnapshot(value) {
|
|
|
755
810
|
const phases = value.phases === void 0 ? void 0 : decodeStringArray(value.phases);
|
|
756
811
|
const models = decodeStringArray(value.models);
|
|
757
812
|
const tools = decodeStringArray(value.tools);
|
|
758
|
-
const
|
|
759
|
-
const roles = value.roles === void 0 ? void 0 : decodeRecord(value.roles, decodeAgentDefinition);
|
|
760
|
-
const projectRoles = value.projectRoles === void 0 ? void 0 : decodeStringArray(value.projectRoles);
|
|
813
|
+
const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
|
|
761
814
|
const schemas = decodeArray(value.schemas, decodeJsonObject);
|
|
762
|
-
if (identityVersion
|
|
815
|
+
if (identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION || launchMode !== void 0 && !isLaunchMode(launchMode) || args === void 0 || !metadata || !settings || value.settingsSources !== void 0 && !settingsSources || value.budget !== void 0 && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== void 0 && !modelAliases || value.phases !== void 0 && !phases || !models || !tools || !agentConfigurations || !schemas) return void 0;
|
|
763
816
|
return {
|
|
764
|
-
|
|
817
|
+
identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
|
|
765
818
|
...launchMode === void 0 ? {} : { launchMode },
|
|
766
819
|
script: value.script,
|
|
767
820
|
args,
|
|
@@ -774,9 +827,7 @@ function decodeLaunchSnapshot(value) {
|
|
|
774
827
|
...phases === void 0 ? {} : { phases },
|
|
775
828
|
models,
|
|
776
829
|
tools,
|
|
777
|
-
|
|
778
|
-
...roles === void 0 ? {} : { roles },
|
|
779
|
-
...projectRoles === void 0 ? {} : { projectRoles },
|
|
830
|
+
agentConfigurations,
|
|
780
831
|
schemas
|
|
781
832
|
};
|
|
782
833
|
}
|
|
@@ -837,162 +888,64 @@ function decodeJournal(value) {
|
|
|
837
888
|
function decodeBooleanCheckpointResult(value) {
|
|
838
889
|
return typeof value === "boolean" ? value : void 0;
|
|
839
890
|
}
|
|
840
|
-
function decodeSummaryProjection(value) {
|
|
841
|
-
if (!object(value)) return void 0;
|
|
842
|
-
const createdAt = optionalString(value.createdAt);
|
|
843
|
-
const updatedAt = optionalString(value.updatedAt);
|
|
844
|
-
const terminalAt = optionalString(value.terminalAt);
|
|
845
|
-
if (createdAt === INVALID_PERSISTED_VALUE || updatedAt === INVALID_PERSISTED_VALUE || terminalAt === INVALID_PERSISTED_VALUE) return void 0;
|
|
846
|
-
return { ...createdAt === void 0 ? {} : { createdAt }, ...updatedAt === void 0 ? {} : { updatedAt }, ...terminalAt === void 0 ? {} : { terminalAt } };
|
|
847
|
-
}
|
|
848
|
-
function decodeEffectiveSystemPrompt(value) {
|
|
849
|
-
if (!object(value) || typeof value.sessionId !== "string" || !positiveInteger(value.attempt) || !positiveInteger(value.turn) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(value.sha256) || typeof value.prompt !== "string") return void 0;
|
|
850
|
-
return { sessionId: value.sessionId, attempt: value.attempt, turn: value.turn, sha256: value.sha256, prompt: value.prompt };
|
|
851
|
-
}
|
|
852
|
-
function decodeSystemPromptArtifact(value) {
|
|
853
|
-
if (!object(value) || !finiteNumber(value.version)) return void 0;
|
|
854
|
-
const entries = value.entries === void 0 ? void 0 : decodeArray(value.entries, decodeEffectiveSystemPrompt);
|
|
855
|
-
if (value.entries !== void 0 && !entries) return void 0;
|
|
856
|
-
return { version: value.version, ...value.format === void 0 ? {} : { format: value.format }, ...value.storage === void 0 ? {} : { storage: value.storage }, ...entries === void 0 ? {} : { entries } };
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// src/io.ts
|
|
860
|
-
import { execFile } from "node:child_process";
|
|
861
|
-
import { randomUUID } from "node:crypto";
|
|
862
|
-
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
863
|
-
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
864
|
-
import { promisify } from "node:util";
|
|
865
|
-
var execute = promisify(execFile);
|
|
866
|
-
var RENAME_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 400, 800];
|
|
867
|
-
var syncSleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
868
|
-
function retryableRenameError(error) {
|
|
869
|
-
return isNodeError(error, "EPERM") || isNodeError(error, "EBUSY") || isNodeError(error, "EACCES");
|
|
870
|
-
}
|
|
871
|
-
async function renameWithRetry(from, to) {
|
|
872
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
873
|
-
try {
|
|
874
|
-
await rename(from, to);
|
|
875
|
-
return;
|
|
876
|
-
} catch (error) {
|
|
877
|
-
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
878
|
-
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
879
|
-
await new Promise((resolve5) => setTimeout(resolve5, delayMs));
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
function renameSyncWithRetry(from, to) {
|
|
884
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
885
|
-
try {
|
|
886
|
-
renameSync(from, to);
|
|
887
|
-
return;
|
|
888
|
-
} catch (error) {
|
|
889
|
-
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
890
|
-
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
891
|
-
Atomics.wait(syncSleepBuffer, 0, 0, delayMs);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
var gitIdentity = {
|
|
896
|
-
GIT_AUTHOR_NAME: "pi-extensible-workflows",
|
|
897
|
-
GIT_AUTHOR_EMAIL: "pi-extensible-workflows@localhost",
|
|
898
|
-
GIT_COMMITTER_NAME: "pi-extensible-workflows",
|
|
899
|
-
GIT_COMMITTER_EMAIL: "pi-extensible-workflows@localhost",
|
|
900
|
-
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
901
|
-
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
902
|
-
};
|
|
903
|
-
function atomicWriteFile(path, content, sync = false) {
|
|
904
|
-
const temporary = `${path}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
905
|
-
if (sync) {
|
|
906
|
-
try {
|
|
907
|
-
writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
|
|
908
|
-
renameSyncWithRetry(temporary, path);
|
|
909
|
-
} catch (error) {
|
|
910
|
-
try {
|
|
911
|
-
rmSync(temporary, { force: true });
|
|
912
|
-
} catch {
|
|
913
|
-
}
|
|
914
|
-
throw error;
|
|
915
|
-
}
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
return writeFile(temporary, content, { encoding: "utf8", mode: 384 }).then(() => renameWithRetry(temporary, path)).catch(async (error) => {
|
|
919
|
-
try {
|
|
920
|
-
await rm(temporary, { force: true });
|
|
921
|
-
} catch {
|
|
922
|
-
}
|
|
923
|
-
throw error;
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
async function atomicJson(path, value) {
|
|
927
|
-
const serialized = JSON.stringify(value);
|
|
928
|
-
if (typeof serialized !== "string") throw new WorkflowError("INTERNAL_ERROR", `Cannot serialize JSON for ${path}`);
|
|
929
|
-
await atomicWriteFile(path, `${serialized}
|
|
930
|
-
`);
|
|
931
|
-
}
|
|
932
|
-
async function atomicPrettyJson(path, value) {
|
|
933
|
-
await atomicWriteFile(path, `${JSON.stringify(value, null, 2)}
|
|
934
|
-
`);
|
|
935
|
-
}
|
|
936
|
-
async function json(path) {
|
|
937
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
938
|
-
}
|
|
939
|
-
async function git(cwd, args, extraEnv = {}) {
|
|
940
|
-
const { stdout } = await execute("git", ["-c", "core.hooksPath=/dev/null", "-c", "commit.gpgSign=false", ...args], { cwd, env: { ...process.env, ...extraEnv }, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
|
|
941
|
-
return stdout;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/paths.ts
|
|
945
|
-
import { createHash } from "node:crypto";
|
|
946
|
-
import { realpathSync } from "node:fs";
|
|
947
|
-
import { homedir } from "node:os";
|
|
948
|
-
import { basename, join, resolve } from "node:path";
|
|
949
|
-
function safePart(value) {
|
|
950
|
-
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
951
|
-
}
|
|
952
|
-
function canonicalPath(path) {
|
|
953
|
-
const absolute = resolve(path);
|
|
954
|
-
try {
|
|
955
|
-
return realpathSync(absolute);
|
|
956
|
-
} catch {
|
|
957
|
-
return absolute;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
function sameFilesystemPath(left, right) {
|
|
961
|
-
return canonicalPath(left) === canonicalPath(right);
|
|
962
|
-
}
|
|
963
|
-
function workflowProjectSettingsPath(cwd) {
|
|
964
|
-
return join(cwd, ".pi", "pi-extensible-workflows", "settings.json");
|
|
965
|
-
}
|
|
966
|
-
function projectStorageKey(cwd) {
|
|
967
|
-
const exact = resolve(cwd);
|
|
968
|
-
const slug = safePart(basename(exact)) || "root";
|
|
969
|
-
return `${slug}-${createHash("sha256").update(exact).digest("hex").slice(0, 12)}`;
|
|
970
|
-
}
|
|
971
|
-
function projectSessionsDirectory(cwd, home = homedir()) {
|
|
972
|
-
return join(home, ".pi", "workflows", "projects", projectStorageKey(cwd), "sessions");
|
|
891
|
+
function decodeSummaryProjection(value) {
|
|
892
|
+
if (!object(value)) return void 0;
|
|
893
|
+
const createdAt = optionalString(value.createdAt);
|
|
894
|
+
const updatedAt = optionalString(value.updatedAt);
|
|
895
|
+
const terminalAt = optionalString(value.terminalAt);
|
|
896
|
+
if (createdAt === INVALID_PERSISTED_VALUE || updatedAt === INVALID_PERSISTED_VALUE || terminalAt === INVALID_PERSISTED_VALUE) return void 0;
|
|
897
|
+
return { ...createdAt === void 0 ? {} : { createdAt }, ...updatedAt === void 0 ? {} : { updatedAt }, ...terminalAt === void 0 ? {} : { terminalAt } };
|
|
973
898
|
}
|
|
974
|
-
function
|
|
975
|
-
|
|
899
|
+
function decodeEffectiveSystemPrompt(value) {
|
|
900
|
+
if (!object(value) || typeof value.sessionId !== "string" || !positiveInteger(value.attempt) || !positiveInteger(value.turn) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(value.sha256) || typeof value.prompt !== "string") return void 0;
|
|
901
|
+
return { sessionId: value.sessionId, attempt: value.attempt, turn: value.turn, sha256: value.sha256, prompt: value.prompt };
|
|
976
902
|
}
|
|
977
|
-
function
|
|
978
|
-
if (
|
|
979
|
-
|
|
903
|
+
function decodeSystemPromptArtifact(value) {
|
|
904
|
+
if (!object(value) || !finiteNumber(value.version)) return void 0;
|
|
905
|
+
const entries = value.entries === void 0 ? void 0 : decodeArray(value.entries, decodeEffectiveSystemPrompt);
|
|
906
|
+
if (value.entries !== void 0 && !entries) return void 0;
|
|
907
|
+
return { version: value.version, ...value.format === void 0 ? {} : { format: value.format }, ...value.storage === void 0 ? {} : { storage: value.storage }, ...entries === void 0 ? {} : { entries } };
|
|
980
908
|
}
|
|
981
909
|
|
|
982
910
|
// src/session-lease.ts
|
|
983
911
|
var RUN_CREATE_TEMP = /^\.([a-zA-Z0-9._-]+)\.(\d+)\.[0-9a-f-]+\.tmp$/;
|
|
984
|
-
|
|
912
|
+
var START_MARGIN_MS = 6e4;
|
|
913
|
+
var clockTicks;
|
|
914
|
+
function clockTickRate() {
|
|
915
|
+
if (clockTicks !== void 0) return clockTicks;
|
|
916
|
+
try {
|
|
917
|
+
const rate = Number(execFileSync("getconf", ["CLK_TCK"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim());
|
|
918
|
+
clockTicks = Number.isInteger(rate) && rate > 0 ? rate : 100;
|
|
919
|
+
} catch {
|
|
920
|
+
clockTicks = 100;
|
|
921
|
+
}
|
|
922
|
+
return clockTicks;
|
|
923
|
+
}
|
|
924
|
+
async function linuxProcessStart(pid, procRoot) {
|
|
925
|
+
try {
|
|
926
|
+
const text = await readFile2(join2(procRoot, String(pid), "stat"), "utf8");
|
|
927
|
+
const close = text.lastIndexOf(")");
|
|
928
|
+
const ticks = close < 0 ? Number.NaN : Number(text.slice(close + 2).split(" ")[19]);
|
|
929
|
+
const btime = /^btime (\d+)$/m.exec(await readFile2(join2(procRoot, "stat"), "utf8"))?.[1];
|
|
930
|
+
if (btime === void 0 || !Number.isInteger(ticks) || ticks < 0) return void 0;
|
|
931
|
+
return Number(btime) * 1e3 + ticks / clockTickRate() * 1e3;
|
|
932
|
+
} catch {
|
|
933
|
+
return void 0;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
async function processAlive(pid, startedAt, procRoot = "/proc") {
|
|
985
937
|
try {
|
|
986
938
|
process.kill(pid, 0);
|
|
987
939
|
} catch (error) {
|
|
988
940
|
return !isNodeError(error, "ESRCH");
|
|
989
941
|
}
|
|
990
|
-
if (startedAt
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
942
|
+
if (startedAt === void 0 || process.platform !== "linux") return true;
|
|
943
|
+
const start = await linuxProcessStart(pid, procRoot);
|
|
944
|
+
if (start !== void 0) return start <= startedAt + START_MARGIN_MS;
|
|
945
|
+
try {
|
|
946
|
+
process.kill(pid, 0);
|
|
947
|
+
} catch (error) {
|
|
948
|
+
return !isNodeError(error, "ESRCH");
|
|
996
949
|
}
|
|
997
950
|
return true;
|
|
998
951
|
}
|
|
@@ -1015,18 +968,12 @@ async function listRunIds(cwd, sessionId, home = homedir2(), cleanTemps = true)
|
|
|
1015
968
|
}
|
|
1016
969
|
}
|
|
1017
970
|
|
|
1018
|
-
// src/trajectory.ts
|
|
1019
|
-
import { open as open2, readFile as readFile4, readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
1020
|
-
import { homedir as homedir5 } from "node:os";
|
|
1021
|
-
import { basename as basename3, dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
|
|
1022
|
-
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir as getAgentDir3, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
1023
|
-
|
|
1024
971
|
// src/store.ts
|
|
1025
972
|
import { execFile as execFile2 } from "node:child_process";
|
|
1026
973
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
1027
974
|
import { access, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, realpath, rename as rename3, rm as rm3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1028
975
|
import { homedir as homedir3 } from "node:os";
|
|
1029
|
-
import { dirname, join as join3, relative, resolve as resolve2, sep } from "node:path";
|
|
976
|
+
import { dirname, join as join3, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
1030
977
|
import { promisify as promisify2 } from "node:util";
|
|
1031
978
|
|
|
1032
979
|
// src/budget.ts
|
|
@@ -1051,7 +998,7 @@ function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, no
|
|
|
1051
998
|
const failedAt = run.failedAt ?? run.error?.failedAt;
|
|
1052
999
|
const replayablePaths = [.../* @__PURE__ */ new Set([...run.retry?.completedPaths ?? [], ...Object.keys(journal.completed)])];
|
|
1053
1000
|
const incompletePaths = [.../* @__PURE__ */ new Set([...run.retry?.incompletePaths ?? [], ...failedAt ? [failedAt] : []])];
|
|
1054
|
-
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,
|
|
1001
|
+
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) };
|
|
1055
1002
|
}
|
|
1056
1003
|
function systemPromptStoragePath(directory) {
|
|
1057
1004
|
return join3(directory, SYSTEM_PROMPT_STORAGE);
|
|
@@ -1199,6 +1146,20 @@ var RunStore = class _RunStore {
|
|
|
1199
1146
|
});
|
|
1200
1147
|
return write;
|
|
1201
1148
|
}
|
|
1149
|
+
async saveAgentConfiguration(key, configuration) {
|
|
1150
|
+
await this.launchSnapshotLane.run(async () => {
|
|
1151
|
+
const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
|
|
1152
|
+
if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
|
|
1153
|
+
await atomicJson(join3(this.directory, "snapshot.json"), { ...snapshot, agentConfigurations: { ...snapshot.agentConfigurations, [key]: configuration } });
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
async setLaunchMode(launchMode) {
|
|
1157
|
+
await this.launchSnapshotLane.run(async () => {
|
|
1158
|
+
const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
|
|
1159
|
+
if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
|
|
1160
|
+
await atomicJson(join3(this.directory, "snapshot.json"), { ...snapshot, launchMode });
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1202
1163
|
async saveSnapshot(snapshot) {
|
|
1203
1164
|
const write = this.launchSnapshotLane.run(() => atomicJson(join3(this.directory, "snapshot.json"), snapshot));
|
|
1204
1165
|
await write;
|
|
@@ -1473,9 +1434,9 @@ var RunStore = class _RunStore {
|
|
|
1473
1434
|
}
|
|
1474
1435
|
structuralWorktree(owner, record) {
|
|
1475
1436
|
const expected = this.expectedWorktree(owner);
|
|
1476
|
-
const relativePath =
|
|
1477
|
-
const relativeCwd =
|
|
1478
|
-
if (record.owner !== owner || resolve2(record.path) !== expected.path || record.branch !== expected.branch || relativePath === ".." || relativePath.startsWith(`..${
|
|
1437
|
+
const relativePath = relative2(this.directory, record.path);
|
|
1438
|
+
const relativeCwd = relative2(record.path, record.cwd);
|
|
1439
|
+
if (record.owner !== owner || resolve2(record.path) !== expected.path || record.branch !== expected.branch || relativePath === ".." || relativePath.startsWith(`..${sep2}`) || relativeCwd === ".." || relativeCwd.startsWith(`..${sep2}`)) throw new Error(`Invalid worktree record for ${owner}`);
|
|
1479
1440
|
return record;
|
|
1480
1441
|
}
|
|
1481
1442
|
async #loadWorktreeRecords(missingOk = true) {
|
|
@@ -1713,8 +1674,8 @@ var RunStore = class _RunStore {
|
|
|
1713
1674
|
try {
|
|
1714
1675
|
const root = (await git(this.cwd, ["rev-parse", "--show-toplevel"])).trim();
|
|
1715
1676
|
const [canonicalRoot, canonicalCwd] = await Promise.all([realpath(root), realpath(this.cwd)]);
|
|
1716
|
-
const launchRelative =
|
|
1717
|
-
if (launchRelative === ".." || launchRelative.startsWith(`..${
|
|
1677
|
+
const launchRelative = relative2(canonicalRoot, canonicalCwd);
|
|
1678
|
+
if (launchRelative === ".." || launchRelative.startsWith(`..${sep2}`)) throw new Error("launch cwd is outside the repository");
|
|
1718
1679
|
await this.cleanupMarker(markerPath);
|
|
1719
1680
|
const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=normal"])).trim();
|
|
1720
1681
|
if (dirty) {
|
|
@@ -1900,517 +1861,12 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
1900
1861
|
// src/validation.ts
|
|
1901
1862
|
import * as acorn from "acorn";
|
|
1902
1863
|
|
|
1903
|
-
// src/roles.ts
|
|
1904
|
-
import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
1905
|
-
|
|
1906
|
-
// src/registry.ts
|
|
1907
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1908
|
-
import { basename as basename2, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
1909
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1910
|
-
import { Value } from "typebox/value";
|
|
1911
|
-
|
|
1912
1864
|
// src/settings.ts
|
|
1913
|
-
import { mkdirSync, readFileSync } from "node:fs";
|
|
1914
|
-
import { homedir as homedir4 } from "node:os";
|
|
1915
|
-
import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
|
|
1916
|
-
import { fileURLToPath } from "node:url";
|
|
1917
1865
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
1918
|
-
var ROLE_DIRECTORY = "pi-extensible-workflows";
|
|
1919
1866
|
var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
|
|
1920
|
-
function workflowSettingsPath(agentDir = getAgentDir()) {
|
|
1921
|
-
return join4(agentDir, ROLE_DIRECTORY, "settings.json");
|
|
1922
|
-
}
|
|
1923
|
-
function normalizedResourcePath(value, settingsPath) {
|
|
1924
|
-
if (value === "*") return value;
|
|
1925
|
-
let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir4(), value.slice(2)) : value;
|
|
1926
|
-
if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
|
|
1927
|
-
const resolved = resolve3(dirname2(settingsPath), expanded);
|
|
1928
|
-
if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
|
|
1929
|
-
if (resourcePatternHasMagic(expanded)) {
|
|
1930
|
-
const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
|
|
1931
|
-
const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
|
|
1932
|
-
const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
|
|
1933
|
-
const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
|
|
1934
|
-
const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
|
|
1935
|
-
return `${canonicalPath(prefix)}${suffix}`;
|
|
1936
|
-
}
|
|
1937
|
-
return canonicalPath(resolved);
|
|
1938
|
-
}
|
|
1939
|
-
function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
|
|
1940
|
-
if (value === void 0) return void 0;
|
|
1941
|
-
if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
|
|
1942
|
-
const normalized = [];
|
|
1943
|
-
for (const [index, entry] of value.entries()) {
|
|
1944
|
-
if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
|
|
1945
|
-
let selector = entry.trim();
|
|
1946
|
-
if (kind === "extensions" && normalizeExtensions) {
|
|
1947
|
-
const negated = selector.startsWith("!");
|
|
1948
|
-
const body = negated ? selector.slice(1) : selector;
|
|
1949
|
-
if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
|
|
1950
|
-
try {
|
|
1951
|
-
selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
|
|
1952
|
-
} catch (error) {
|
|
1953
|
-
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
try {
|
|
1957
|
-
validateResourcePattern(selector);
|
|
1958
|
-
} catch (error) {
|
|
1959
|
-
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
|
|
1960
|
-
}
|
|
1961
|
-
normalized.push(selector);
|
|
1962
|
-
}
|
|
1963
|
-
return Object.freeze(normalized);
|
|
1964
|
-
}
|
|
1965
|
-
function validateWorktreePostCreateCommand(value, settingsPath) {
|
|
1966
|
-
if (value === void 0) return void 0;
|
|
1967
|
-
if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
|
|
1968
|
-
return Object.freeze(value.map((entry, index) => {
|
|
1969
|
-
if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
|
|
1970
|
-
return entry;
|
|
1971
|
-
}));
|
|
1972
|
-
}
|
|
1973
|
-
function selectorsFromSettings(settings) {
|
|
1974
|
-
return {
|
|
1975
|
-
...settings.skills === void 0 ? {} : { skills: settings.skills },
|
|
1976
|
-
...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
|
|
1977
|
-
...settings.tools === void 0 ? {} : { tools: settings.tools }
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
function selectorSet(value) {
|
|
1981
|
-
return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
|
|
1982
|
-
}
|
|
1983
|
-
function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
|
|
1984
|
-
if (value === void 0) return void 0;
|
|
1985
|
-
const base = `${settingsPath}.extensionSettings`;
|
|
1986
|
-
if (!object(value)) fail(errorCode2, `${base} must be an object`);
|
|
1987
|
-
const normalized = {};
|
|
1988
|
-
for (const [namespace, raw] of Object.entries(value)) {
|
|
1989
|
-
if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
|
|
1990
|
-
if (namespace === "herdr") {
|
|
1991
|
-
if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
|
|
1992
|
-
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
|
|
1993
|
-
if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
|
|
1994
|
-
normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
|
|
1995
|
-
continue;
|
|
1996
|
-
}
|
|
1997
|
-
if (namespace === "trajectory") {
|
|
1998
|
-
if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
|
|
1999
|
-
if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
|
|
2000
|
-
if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
|
|
2001
|
-
normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
|
|
2002
|
-
continue;
|
|
2003
|
-
}
|
|
2004
|
-
if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
|
|
2005
|
-
normalized[namespace] = structuredClone(raw);
|
|
2006
|
-
}
|
|
2007
|
-
return deepFreeze(normalized);
|
|
2008
|
-
}
|
|
2009
|
-
function positiveRetentionInteger(value) {
|
|
2010
|
-
return positiveInteger(value) && Number.isSafeInteger(value);
|
|
2011
|
-
}
|
|
2012
|
-
function validateRetention(value, settingsPath) {
|
|
2013
|
-
if (value === void 0) return void 0;
|
|
2014
|
-
const base = `${settingsPath}.retention`;
|
|
2015
|
-
if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
|
|
2016
|
-
const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
|
|
2017
|
-
if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
|
|
2018
|
-
if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
|
|
2019
|
-
if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
|
|
2020
|
-
return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
|
|
2021
|
-
}
|
|
2022
|
-
function parseSettings(path, partial) {
|
|
2023
|
-
let parsed;
|
|
2024
|
-
try {
|
|
2025
|
-
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
2026
|
-
} catch (error) {
|
|
2027
|
-
if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
|
|
2028
|
-
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
2029
|
-
}
|
|
2030
|
-
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2031
|
-
const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
|
|
2032
|
-
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
2033
|
-
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
2034
|
-
if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
2035
|
-
const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
|
|
2036
|
-
if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
2037
|
-
const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
|
|
2038
|
-
if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
2039
|
-
const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
|
|
2040
|
-
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
2041
|
-
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
2042
|
-
const extensions = validateSelectorList(parsed.extensions, path, "extensions");
|
|
2043
|
-
const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
|
|
2044
|
-
const retention = validateRetention(parsed.retention, path);
|
|
2045
|
-
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
2046
|
-
return Object.freeze({
|
|
2047
|
-
...concurrency === void 0 ? {} : { concurrency },
|
|
2048
|
-
...backgroundWidget === void 0 ? {} : { backgroundWidget },
|
|
2049
|
-
...modelAliases === void 0 ? {} : { modelAliases },
|
|
2050
|
-
...skills === void 0 ? {} : { skills },
|
|
2051
|
-
...extensions === void 0 ? {} : { extensions },
|
|
2052
|
-
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2053
|
-
...tools === void 0 ? {} : { tools },
|
|
2054
|
-
...retention === void 0 ? {} : { retention },
|
|
2055
|
-
...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
|
|
2056
|
-
});
|
|
2057
|
-
}
|
|
2058
|
-
function loadSettings(path = workflowSettingsPath()) {
|
|
2059
|
-
return parseSettings(path, false);
|
|
2060
|
-
}
|
|
2061
|
-
function loadSettingsOverrides(path) {
|
|
2062
|
-
return parseSettings(path, true);
|
|
2063
|
-
}
|
|
2064
|
-
function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
|
|
2065
|
-
const projectSettingsPath = workflowProjectSettingsPath(cwd);
|
|
2066
|
-
const global = loadSettings(globalSettingsPath);
|
|
2067
|
-
const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
|
|
2068
|
-
const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
|
|
2069
|
-
const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
|
|
2070
|
-
const globalSelectors = selectorsFromSettings(global);
|
|
2071
|
-
const projectSelectors = selectorsFromSettings(project);
|
|
2072
|
-
const effectiveSelectors = selectorSet({
|
|
2073
|
-
skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
|
|
2074
|
-
extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
|
|
2075
|
-
...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
|
|
2076
|
-
});
|
|
2077
|
-
const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
|
|
2078
|
-
const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
|
|
2079
|
-
const sources = {
|
|
2080
|
-
concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
|
|
2081
|
-
modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
|
|
2082
|
-
skills: sourceFor("skills"),
|
|
2083
|
-
extensions: sourceFor("extensions"),
|
|
2084
|
-
tools: sourceFor("tools"),
|
|
2085
|
-
...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
|
|
2086
|
-
...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
|
|
2087
|
-
...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
|
|
2088
|
-
};
|
|
2089
|
-
const effective = Object.freeze({
|
|
2090
|
-
concurrency: project.concurrency ?? global.concurrency,
|
|
2091
|
-
backgroundWidget: global.backgroundWidget ?? true,
|
|
2092
|
-
...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
|
|
2093
|
-
...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
|
|
2094
|
-
...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
|
|
2095
|
-
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2096
|
-
...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
|
|
2097
|
-
...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
|
|
2098
|
-
...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
|
|
2099
|
-
});
|
|
2100
|
-
return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
// src/package-identity.ts
|
|
2104
|
-
var CORE_PACKAGE_NAME = "@marcoscale98/pi-extensible-workflows";
|
|
2105
|
-
var CORE_PACKAGE_COMPATIBILITY_NAME = "pi-extensible-workflows";
|
|
2106
|
-
var CORE_PACKAGE_NAMES = [CORE_PACKAGE_NAME, CORE_PACKAGE_COMPATIBILITY_NAME];
|
|
2107
|
-
function isCorePackageName(value) {
|
|
2108
|
-
return typeof value === "string" && CORE_PACKAGE_NAMES.includes(value);
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
// src/registry.ts
|
|
2112
|
-
var RESERVED_GLOBALS = /* @__PURE__ */ new Set(["agent", "shell", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
|
|
2113
|
-
var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
2114
|
-
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2115
|
-
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
2116
|
-
function normalizeRoleDirectory(value) {
|
|
2117
|
-
try {
|
|
2118
|
-
if (value instanceof URL) {
|
|
2119
|
-
if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
2120
|
-
return fileURLToPath2(value);
|
|
2121
|
-
}
|
|
2122
|
-
if (typeof value === "string") {
|
|
2123
|
-
if (value.startsWith("file:")) return fileURLToPath2(value);
|
|
2124
|
-
if (isAbsolute(value)) return value;
|
|
2125
|
-
}
|
|
2126
|
-
} catch (error) {
|
|
2127
|
-
fail("INVALID_METADATA", `Invalid workflow role directory: ${errorText(error)}`);
|
|
2128
|
-
}
|
|
2129
|
-
fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
2130
|
-
}
|
|
2131
|
-
function isBuiltinRoleDirectory(path) {
|
|
2132
|
-
const canonical = canonicalPath(path);
|
|
2133
|
-
const starterDirectory = dirname3(canonical);
|
|
2134
|
-
if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
|
|
2135
|
-
const distributionDirectory = dirname3(starterDirectory);
|
|
2136
|
-
const packageRoot = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
|
|
2137
|
-
try {
|
|
2138
|
-
const metadata = JSON.parse(readFileSync2(join5(packageRoot, "package.json"), "utf8"));
|
|
2139
|
-
return object(metadata) && isCorePackageName(metadata.name);
|
|
2140
|
-
} catch {
|
|
2141
|
-
return false;
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
function catalogSchema(schema, at) {
|
|
2145
|
-
validateSchema(schema, at);
|
|
2146
|
-
return structuredClone(schema);
|
|
2147
|
-
}
|
|
2148
|
-
var WorkflowRegistry = class {
|
|
2149
|
-
#extensions = /* @__PURE__ */ new Set();
|
|
2150
|
-
#functionSources = /* @__PURE__ */ new Map();
|
|
2151
|
-
#globals = /* @__PURE__ */ new Map();
|
|
2152
|
-
#hooks = /* @__PURE__ */ new Map();
|
|
2153
|
-
#agentAttemptActions = /* @__PURE__ */ new Map();
|
|
2154
|
-
#roleDirectories = /* @__PURE__ */ new Map();
|
|
2155
|
-
#modelAliases = /* @__PURE__ */ new Map();
|
|
2156
|
-
#settingsValidators = /* @__PURE__ */ new Set();
|
|
2157
|
-
#frozen = false;
|
|
2158
|
-
#subagentStatusObserver;
|
|
2159
|
-
get frozen() {
|
|
2160
|
-
return this.#frozen;
|
|
2161
|
-
}
|
|
2162
|
-
freeze() {
|
|
2163
|
-
this.#frozen = true;
|
|
2164
|
-
}
|
|
2165
|
-
setSubagentStatusObserver(observer) {
|
|
2166
|
-
this.#subagentStatusObserver = observer;
|
|
2167
|
-
}
|
|
2168
|
-
observeSubagentStatus(status, request) {
|
|
2169
|
-
this.#subagentStatusObserver?.(status, request);
|
|
2170
|
-
}
|
|
2171
|
-
register(extension2) {
|
|
2172
|
-
if (this.#frozen) fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
|
|
2173
|
-
if (object(extension2) && Object.prototype.hasOwnProperty.call(extension2, "workflows")) fail("INVALID_METADATA", "Separate registered workflow definitions were removed; register a function with input and output schemas instead");
|
|
2174
|
-
if (!object(extension2) || Object.keys(extension2).some((key) => !["version", "headline", "description", "source", "dependencies", "validateSettings", "functions", "modelAliases", "agentSetupHooks", "agentAttemptActions", "roleDirectories"].includes(key)) || typeof extension2.version !== "string" || !SEMVER.test(extension2.version) || typeof extension2.headline !== "string" || !extension2.headline.trim()) fail("INVALID_METADATA", "Workflow extensions require a semantic version and non-empty headline");
|
|
2175
|
-
const functions = extension2.functions ?? {};
|
|
2176
|
-
const modelAliases = extension2.modelAliases ?? {};
|
|
2177
|
-
const agentSetupHooks = extension2.agentSetupHooks ?? {};
|
|
2178
|
-
const agentAttemptActions = extension2.agentAttemptActions ?? {};
|
|
2179
|
-
const source = extension2.source;
|
|
2180
|
-
const dependencyValues = extension2.dependencies;
|
|
2181
|
-
const dependencies = dependencyValues ?? [];
|
|
2182
|
-
const validateSettings = extension2.validateSettings;
|
|
2183
|
-
if (validateSettings !== void 0 && typeof validateSettings !== "function") fail("INVALID_METADATA", "Workflow extension validateSettings must be a function");
|
|
2184
|
-
if (source !== void 0 && (typeof source !== "string" || !source.trim())) fail("INVALID_METADATA", "Workflow extension source must be a non-empty module URL");
|
|
2185
|
-
if (dependencyValues !== void 0 && (!Array.isArray(dependencyValues) || dependencyValues.some((dependency) => typeof dependency !== "string" || dependency.trim() !== dependency || !PACKAGE_NAME.test(dependency)) || new Set(dependencyValues).size !== dependencyValues.length)) fail("INVALID_METADATA", "Workflow extension dependencies must be unique, non-empty package names");
|
|
2186
|
-
const roleDirectoryValues = extension2.roleDirectories === void 0 ? [] : extension2.roleDirectories;
|
|
2187
|
-
if (!Array.isArray(roleDirectoryValues)) fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
|
|
2188
|
-
const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
|
|
2189
|
-
if (!object(functions) || !object(modelAliases) || !object(agentSetupHooks) || !object(agentAttemptActions) || Object.keys(functions).length === 0 && Object.keys(modelAliases).length === 0 && Object.keys(agentSetupHooks).length === 0 && Object.keys(agentAttemptActions).length === 0 && roleDirectories.length === 0 && validateSettings === void 0) fail("INVALID_METADATA", "Workflow extensions require functions, model aliases, agent setup hooks, agent attempt actions, role directories, or a settings validator");
|
|
2190
|
-
const names = Object.keys(functions);
|
|
2191
|
-
if (new Set(names).size !== names.length) fail("GLOBAL_COLLISION", "Global name collision inside extension");
|
|
2192
|
-
for (const name of names) {
|
|
2193
|
-
if (!IDENTIFIER.test(name) || name.startsWith("__pi_extensible_workflows_")) fail("INVALID_METADATA", `Invalid global name: ${name}`);
|
|
2194
|
-
if (RESERVED_GLOBALS.has(name)) fail("GLOBAL_COLLISION", `Global name is reserved: ${name}`);
|
|
2195
|
-
if (this.#globals.has(name)) fail("GLOBAL_COLLISION", `Global name is already registered: ${name}`);
|
|
2196
|
-
}
|
|
2197
|
-
for (const [name, fn] of Object.entries(functions)) {
|
|
2198
|
-
if (!object(fn) || Object.keys(fn).some((key) => !["description", "input", "output", "run"].includes(key)) || typeof fn.description !== "string" || !fn.description.trim() || typeof fn.run !== "function") fail("INVALID_METADATA", `Invalid workflow function: ${name}`);
|
|
2199
|
-
validateSchema(fn.input, `${name} input`);
|
|
2200
|
-
validateSchema(fn.output, `${name} output`);
|
|
2201
|
-
if (fn.input.type !== "object") fail("INVALID_SCHEMA", `${name} input must describe one object`);
|
|
2202
|
-
}
|
|
2203
|
-
for (const [name, alias] of Object.entries(modelAliases)) {
|
|
2204
|
-
if (!MODEL_ALIAS_NAME.test(name)) fail("INVALID_METADATA", `Invalid model alias name: ${name}`);
|
|
2205
|
-
if (!object(alias) || Object.keys(alias).some((key) => key !== "resolve") || typeof alias.resolve !== "function") fail("INVALID_METADATA", `Invalid model alias resolver: ${name}`);
|
|
2206
|
-
if (this.#modelAliases.has(name)) fail("DUPLICATE_NAME", `Model alias already registered: ${name}`);
|
|
2207
|
-
}
|
|
2208
|
-
for (const [name, hook] of Object.entries(agentSetupHooks)) {
|
|
2209
|
-
if (!IDENTIFIER.test(name) || !object(hook) || Object.keys(hook).some((key) => !["priority", "setup"].includes(key)) || typeof hook.setup !== "function" || hook.priority !== void 0 && (typeof hook.priority !== "number" || !Number.isFinite(hook.priority))) fail("INVALID_METADATA", `Invalid agent setup hook: ${name}`);
|
|
2210
|
-
if (this.#hooks.has(name)) fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
|
|
2211
|
-
}
|
|
2212
|
-
for (const [name, action] of Object.entries(agentAttemptActions)) {
|
|
2213
|
-
if (!IDENTIFIER.test(name) || !object(action) || Object.keys(action).some((key) => !["label", "visible", "run", "visibleStandalone", "runStandalone"].includes(key)) || typeof action.label !== "string" || !action.label.trim() || typeof action.visible !== "function" || typeof action.run !== "function" || action.visibleStandalone !== void 0 && typeof action.visibleStandalone !== "function" || action.runStandalone !== void 0 && typeof action.runStandalone !== "function") fail("INVALID_METADATA", `Invalid agent attempt action: ${name}`);
|
|
2214
|
-
if (action.visibleStandalone !== void 0 !== (action.runStandalone !== void 0)) fail("INVALID_METADATA", `Standalone agent attempt actions require visibleStandalone and runStandalone: ${name}`);
|
|
2215
|
-
if (this.#agentAttemptActions.has(name)) fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
|
|
2216
|
-
}
|
|
2217
|
-
const stored = deepFreeze({ ...extension2, functions, modelAliases, agentSetupHooks, agentAttemptActions, ...roleDirectories.length ? { roleDirectories } : {} });
|
|
2218
|
-
if (source !== void 0) for (const name of names) this.#functionSources.set(name, Object.freeze({ module: source, export: "default", dependencies: Object.freeze([...dependencies]) }));
|
|
2219
|
-
this.#extensions.add(stored);
|
|
2220
|
-
for (const directory of roleDirectories) if (!this.#roleDirectories.has(directory)) this.#roleDirectories.set(directory, deepFreeze({ path: directory, extension: { version: extension2.version, headline: extension2.headline }, ...isBuiltinRoleDirectory(directory) ? { builtin: true } : {} }));
|
|
2221
|
-
for (const name of names) this.#globals.set(name, name);
|
|
2222
|
-
for (const [name, alias] of Object.entries(modelAliases)) this.#modelAliases.set(name, { name, version: extension2.version, headline: extension2.headline, resolve: alias.resolve });
|
|
2223
|
-
for (const [name, hook] of Object.entries(agentSetupHooks)) this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
|
|
2224
|
-
for (const [name, action] of Object.entries(agentAttemptActions)) this.#agentAttemptActions.set(name, action);
|
|
2225
|
-
if (validateSettings !== void 0) this.#settingsValidators.add({ headline: extension2.headline, validate: validateSettings });
|
|
2226
|
-
}
|
|
2227
|
-
function(name) {
|
|
2228
|
-
if (!IDENTIFIER.test(name)) fail("MISSING_WORKFLOW", `Registered functions require an unqualified name: ${name}`);
|
|
2229
|
-
const fn = [...this.#extensions].find((extension2) => extension2.functions?.[name])?.functions?.[name];
|
|
2230
|
-
if (!fn) fail("MISSING_WORKFLOW", `Registered function is unavailable: ${name}; the separate registered-workflow format was removed`);
|
|
2231
|
-
return fn;
|
|
2232
|
-
}
|
|
2233
|
-
functions() {
|
|
2234
|
-
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension2) => Object.entries(extension2.functions ?? {}))));
|
|
2235
|
-
}
|
|
2236
|
-
functionSources() {
|
|
2237
|
-
return Object.freeze(Object.fromEntries(this.#functionSources));
|
|
2238
|
-
}
|
|
2239
|
-
catalog(context) {
|
|
2240
|
-
const functions = [];
|
|
2241
|
-
for (const extension2 of this.#extensions) {
|
|
2242
|
-
for (const [name, fn] of Object.entries(extension2.functions ?? {})) functions.push({ name, version: extension2.version, headline: extension2.headline, description: fn.description, input: catalogSchema(fn.input, `${name} input`), output: catalogSchema(fn.output, `${name} output`) });
|
|
2243
|
-
}
|
|
2244
|
-
let aliases;
|
|
2245
|
-
let settings;
|
|
2246
|
-
let source = "global settings";
|
|
2247
|
-
try {
|
|
2248
|
-
const resolved = context ? resolveWorkflowSettings(context.cwd, context.projectTrusted, context.globalSettingsPath) : void 0;
|
|
2249
|
-
aliases = resolved?.effective.modelAliases ?? loadSettings().modelAliases;
|
|
2250
|
-
if (resolved) {
|
|
2251
|
-
source = resolved.projectTrusted && [resolved.sources.modelAliases, resolved.sources.skills, resolved.sources.extensions, resolved.sources.tools].some((path) => path === resolved.projectSettingsPath) ? "trusted project settings" : "global settings";
|
|
2252
|
-
settings = { concurrency: resolved.effective.concurrency, backgroundWidget: resolved.effective.backgroundWidget ?? true, modelAliases: resolved.effective.modelAliases ?? {}, skills: resolved.effective.skills ?? [], extensions: Array.isArray(resolved.effective.extensions) ? resolved.effective.extensions : [], tools: resolved.effective.tools ?? [], ...resolved.effective.extensionSettings === void 0 ? {} : { extensionSettings: resolved.effective.extensionSettings }, ...resolved.effective.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: resolved.effective.worktreePostCreateCommand }, globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: resolved.projectTrusted, sources: resolved.sources };
|
|
2253
|
-
}
|
|
2254
|
-
} catch {
|
|
2255
|
-
aliases = void 0;
|
|
2256
|
-
}
|
|
2257
|
-
const staticEntries = Object.keys(aliases ?? {}).map((name) => ({ name, kind: "static", provenance: source }));
|
|
2258
|
-
const dynamicEntries = [...this.#modelAliases.values()].map(({ name, version, headline }) => ({ name, kind: "dynamic", provenance: `extension: ${headline}`, version, headline }));
|
|
2259
|
-
const modelAliasEntries = [...staticEntries, ...dynamicEntries].sort((left, right) => left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind) || left.provenance.localeCompare(right.provenance));
|
|
2260
|
-
const sort = (left, right) => left.name.localeCompare(right.name);
|
|
2261
|
-
const catalog = { functions: functions.sort(sort), ...modelAliasEntries.length ? { modelAliasEntries } : {} };
|
|
2262
|
-
if (aliases && Object.keys(aliases).length) Object.defineProperty(catalog, "modelAliases", { value: Object.freeze(structuredClone(aliases)), enumerable: false });
|
|
2263
|
-
if (settings) {
|
|
2264
|
-
const publicSettings = { ...settings, modelAliases: Object.keys(aliases ?? {}).length ? {} : settings.modelAliases };
|
|
2265
|
-
Object.defineProperty(catalog, "settings", { value: deepFreeze(publicSettings), enumerable: true });
|
|
2266
|
-
}
|
|
2267
|
-
return deepFreeze(catalog);
|
|
2268
|
-
}
|
|
2269
|
-
catalogIndex(context) {
|
|
2270
|
-
const catalog = this.catalog(context);
|
|
2271
|
-
let modelAliasEntries;
|
|
2272
|
-
if (catalog.modelAliasEntries !== void 0) {
|
|
2273
|
-
const winningEntries = /* @__PURE__ */ new Map();
|
|
2274
|
-
for (const entry of catalog.modelAliasEntries) {
|
|
2275
|
-
if (!winningEntries.has(entry.name) || entry.kind === "static") winningEntries.set(entry.name, entry);
|
|
2276
|
-
}
|
|
2277
|
-
modelAliasEntries = [...winningEntries.values()];
|
|
2278
|
-
}
|
|
2279
|
-
const index = {
|
|
2280
|
-
functions: catalog.functions.map(({ name, description, input }) => ({ name, description, input: structuredClone(input) })),
|
|
2281
|
-
...modelAliasEntries ? { modelAliasEntries: structuredClone(modelAliasEntries) } : {}
|
|
2282
|
-
};
|
|
2283
|
-
if (catalog.modelAliases) Object.defineProperty(index, "modelAliases", { value: Object.freeze(structuredClone(catalog.modelAliases)), enumerable: false });
|
|
2284
|
-
if (catalog.settings) Object.defineProperty(index, "settings", { value: deepFreeze(structuredClone(catalog.settings)), enumerable: true });
|
|
2285
|
-
return deepFreeze(index);
|
|
2286
|
-
}
|
|
2287
|
-
catalogDetail(name, context) {
|
|
2288
|
-
const catalog = this.catalog(context);
|
|
2289
|
-
const entry = catalog.functions.find((candidate) => candidate.name === name);
|
|
2290
|
-
if (entry) return entry;
|
|
2291
|
-
const alias = catalog.modelAliasEntries?.find((candidate) => candidate.name === name && candidate.kind === "static") ?? catalog.modelAliasEntries?.find((candidate) => candidate.name === name);
|
|
2292
|
-
if (alias) return alias;
|
|
2293
|
-
return deepFreeze({ error: { code: "NOT_FOUND", name, message: `No registered workflow function is available: ${name}` } });
|
|
2294
|
-
}
|
|
2295
|
-
globals() {
|
|
2296
|
-
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension2) => Object.keys(extension2.functions ?? {}).map((name) => [name, { name }]))));
|
|
2297
|
-
}
|
|
2298
|
-
validateExtensionSettings(settings, context) {
|
|
2299
|
-
const effectiveSettings = settings ?? Object.freeze({});
|
|
2300
|
-
if (!jsonValue(effectiveSettings) || !object(effectiveSettings)) fail("INVALID_SETTINGS", "Workflow extension settings must be a JSON object");
|
|
2301
|
-
const unsafeNamespace = Object.keys(effectiveSettings).find((namespace) => !validWorkflowExtensionNamespace(namespace));
|
|
2302
|
-
if (unsafeNamespace !== void 0) fail("INVALID_SETTINGS", `Workflow extension settings contains an invalid namespace: ${unsafeNamespace}`);
|
|
2303
|
-
const frozenSettings = deepFreeze(structuredClone(effectiveSettings));
|
|
2304
|
-
for (const { headline, validate } of this.#settingsValidators) {
|
|
2305
|
-
try {
|
|
2306
|
-
validate(frozenSettings, Object.freeze({ ...context }));
|
|
2307
|
-
} catch (error) {
|
|
2308
|
-
const location = context.source === "role" && context.role ? `role ${context.role}` : context.source;
|
|
2309
|
-
fail("INVALID_SETTINGS", `Invalid extension settings (${headline}; ${location}): ${errorText(error)}`);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
async invokeFunction(name, input, context, path, journal) {
|
|
2314
|
-
const fn = this.function(name);
|
|
2315
|
-
if (!object(input) || !jsonValue(input) || !Value.Check(fn.input, input)) fail("RESULT_INVALID", `Invalid input for ${name}`);
|
|
2316
|
-
const replayed = journal.get(path);
|
|
2317
|
-
if (replayed !== void 0) {
|
|
2318
|
-
if (!jsonValue(replayed) || !Value.Check(fn.output, replayed)) fail("RESULT_INVALID", `Invalid replay for ${name}`);
|
|
2319
|
-
return structuredClone(replayed);
|
|
2320
|
-
}
|
|
2321
|
-
const result = await fn.run(deepFreeze(structuredClone(input)), Object.freeze({ run: context.run, settings: deepFreeze(structuredClone(context.settings)), invoke: context.invoke, agent: context.agent, shell: context.shell, prompt: context.prompt, parallel: context.parallel, pipeline: context.pipeline, withWorktree: context.withWorktree, checkpoint: context.checkpoint, phase: context.phase, log: context.log }));
|
|
2322
|
-
if (!jsonValue(result) || !Value.Check(fn.output, result)) fail("RESULT_INVALID", `Invalid output from ${name}`);
|
|
2323
|
-
const stored = structuredClone(result);
|
|
2324
|
-
journal.put(path, stored);
|
|
2325
|
-
return structuredClone(stored);
|
|
2326
|
-
}
|
|
2327
|
-
agentSetupHooks() {
|
|
2328
|
-
return [...this.#hooks.values()].sort(byPriorityThenName);
|
|
2329
|
-
}
|
|
2330
|
-
agentAttemptActions() {
|
|
2331
|
-
return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries()));
|
|
2332
|
-
}
|
|
2333
|
-
roleDirectories() {
|
|
2334
|
-
return [...this.#roleDirectories.keys()];
|
|
2335
|
-
}
|
|
2336
|
-
roleDirectoryRegistrations() {
|
|
2337
|
-
return [...this.#roleDirectories.values()];
|
|
2338
|
-
}
|
|
2339
|
-
modelAliases() {
|
|
2340
|
-
return [...this.#modelAliases.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
2341
|
-
}
|
|
2342
|
-
async resolveModelAliases(context, shadowed = /* @__PURE__ */ new Set()) {
|
|
2343
|
-
const resolved = {};
|
|
2344
|
-
const isAborted = () => context.signal.aborted;
|
|
2345
|
-
for (const alias of this.modelAliases()) {
|
|
2346
|
-
if (shadowed.has(alias.name)) continue;
|
|
2347
|
-
if (isAborted()) fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2348
|
-
let target;
|
|
2349
|
-
try {
|
|
2350
|
-
target = await alias.resolve(Object.freeze({ cwd: context.cwd, projectTrusted: context.projectTrusted, rootModel: Object.freeze({ ...context.rootModel }), knownModels: new Set(context.knownModels), availableModels: new Set(context.availableModels), signal: context.signal }));
|
|
2351
|
-
} catch (error) {
|
|
2352
|
-
if (isAborted() || error instanceof Error && error.name === "AbortError" || errorCode(error) === "CANCELLED") fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2353
|
-
fail("CONFIG_ERROR", `Model alias resolver failed for ${alias.name} (${alias.headline}): ${errorText(error)}`);
|
|
2354
|
-
}
|
|
2355
|
-
if (isAborted()) fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2356
|
-
if (typeof target !== "string" || !target.trim()) fail("CONFIG_ERROR", `Model alias resolver returned an invalid target for ${alias.name} (${alias.headline})`);
|
|
2357
|
-
resolved[alias.name] = target.trim();
|
|
2358
|
-
}
|
|
2359
|
-
return Object.freeze(resolved);
|
|
2360
|
-
}
|
|
2361
|
-
};
|
|
2362
|
-
var WORKFLOW_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-registry");
|
|
2363
|
-
var globalRegistry = globalThis;
|
|
2364
|
-
function createWorkflowRegistryApi(registry) {
|
|
2365
|
-
return {
|
|
2366
|
-
get frozen() {
|
|
2367
|
-
return registry.frozen;
|
|
2368
|
-
},
|
|
2369
|
-
freeze: () => {
|
|
2370
|
-
registry.freeze();
|
|
2371
|
-
},
|
|
2372
|
-
setSubagentStatusObserver: (observer) => {
|
|
2373
|
-
registry.setSubagentStatusObserver(observer);
|
|
2374
|
-
},
|
|
2375
|
-
observeSubagentStatus: (status, request) => {
|
|
2376
|
-
registry.observeSubagentStatus(status, request);
|
|
2377
|
-
},
|
|
2378
|
-
register: (extension2) => {
|
|
2379
|
-
registry.register(extension2);
|
|
2380
|
-
},
|
|
2381
|
-
function: (name) => registry.function(name),
|
|
2382
|
-
functions: () => registry.functions(),
|
|
2383
|
-
functionSources: () => registry.functionSources(),
|
|
2384
|
-
catalog: (context) => registry.catalog(context),
|
|
2385
|
-
catalogIndex: (context) => registry.catalogIndex(context),
|
|
2386
|
-
catalogDetail: (name, context) => registry.catalogDetail(name, context),
|
|
2387
|
-
globals: () => registry.globals(),
|
|
2388
|
-
invokeFunction: (...args) => registry.invokeFunction(...args),
|
|
2389
|
-
validateExtensionSettings: (...args) => {
|
|
2390
|
-
registry.validateExtensionSettings(...args);
|
|
2391
|
-
},
|
|
2392
|
-
modelAliases: () => registry.modelAliases(),
|
|
2393
|
-
resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
|
|
2394
|
-
roleDirectories: () => registry.roleDirectories(),
|
|
2395
|
-
roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
|
|
2396
|
-
agentSetupHooks: () => registry.agentSetupHooks(),
|
|
2397
|
-
agentAttemptActions: () => registry.agentAttemptActions()
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
function workflowRegistryHost() {
|
|
2401
|
-
return globalRegistry[WORKFLOW_REGISTRY_KEY] ??= { api: createWorkflowRegistryApi(new WorkflowRegistry()), activeHosts: 0 };
|
|
2402
|
-
}
|
|
2403
|
-
function resetWorkflowRegistry() {
|
|
2404
|
-
workflowRegistryHost().api = createWorkflowRegistryApi(new WorkflowRegistry());
|
|
2405
|
-
}
|
|
2406
|
-
function beginWorkflowExtensionLoading() {
|
|
2407
|
-
const host = workflowRegistryHost();
|
|
2408
|
-
if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
|
|
2409
|
-
}
|
|
2410
|
-
beginWorkflowExtensionLoading();
|
|
2411
1867
|
|
|
2412
1868
|
// subagents/src/contracts.ts
|
|
2413
|
-
import { Value
|
|
1869
|
+
import { Value } from "typebox/value";
|
|
2414
1870
|
var SUBAGENT_MAX_RETRIES = 255;
|
|
2415
1871
|
var SUBAGENT_SYSTEM_PROMPT_LIMIT = 64 * 1024;
|
|
2416
1872
|
var SUBAGENTS_MODE = Type.Union([
|
|
@@ -2423,17 +1879,18 @@ var SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
|
2423
1879
|
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
2424
1880
|
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
2425
1881
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
|
|
1882
|
+
excludeTools: Type.Optional(Type.Array(Type.String(), { description: "Optional exact tool names removed after preparation; no patterns, cannot remove workflow_result" })),
|
|
2426
1883
|
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
2427
1884
|
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
2428
1885
|
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
2429
|
-
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
2430
1886
|
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
2431
1887
|
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
2432
1888
|
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
2433
1889
|
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()]))
|
|
2434
|
-
}, { additionalProperties:
|
|
1890
|
+
}, { additionalProperties: true });
|
|
2435
1891
|
var SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
2436
|
-
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" }))
|
|
1892
|
+
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" })),
|
|
1893
|
+
scope: Type.Optional(Type.Union([Type.Literal("session"), Type.Literal("all")], { description: "List scope when id is omitted: session (default) lists runs from the current Pi session; all lists every stored run" }))
|
|
2437
1894
|
}, { additionalProperties: false });
|
|
2438
1895
|
var SUBAGENTS_ID_PARAMETERS = Type.Object({
|
|
2439
1896
|
id: Type.String({ description: "Subagent ID" })
|
|
@@ -2448,6 +1905,24 @@ var SUBAGENTS_STOP_PARAMETERS = Type.Object({
|
|
|
2448
1905
|
var SUBAGENTS_RETRY_PARAMETERS = Type.Object({
|
|
2449
1906
|
id: Type.String({ description: "Failed or stopped subagent ID to retry" })
|
|
2450
1907
|
}, { additionalProperties: false });
|
|
1908
|
+
var SUBAGENT_STATUS_OUTPUT = Type.Object({
|
|
1909
|
+
id: Type.String(),
|
|
1910
|
+
state: Type.String(),
|
|
1911
|
+
sessionId: Type.Optional(Type.String()),
|
|
1912
|
+
startedAt: Type.Optional(Type.Number()),
|
|
1913
|
+
finishedAt: Type.Optional(Type.Number()),
|
|
1914
|
+
attempts: Type.Optional(Type.Number()),
|
|
1915
|
+
attemptDetails: Type.Optional(Type.Array(Type.Unknown())),
|
|
1916
|
+
worktree: Type.Optional(Type.Object({ path: Type.String(), branch: Type.String() })),
|
|
1917
|
+
error: Type.Optional(Type.Object({ code: Type.String(), message: Type.String() })),
|
|
1918
|
+
progress: Type.Optional(Type.Unknown()),
|
|
1919
|
+
value: Type.Optional(Type.Unknown())
|
|
1920
|
+
});
|
|
1921
|
+
var SUBAGENTS_INSPECT_OUTPUT = Type.Union([SUBAGENT_STATUS_OUTPUT, Type.Array(Type.Object({ id: Type.String(), state: Type.String() }))]);
|
|
1922
|
+
var SUBAGENTS_STEER_OUTPUT = Type.Object({ id: Type.String(), accepted: Type.Boolean() });
|
|
1923
|
+
|
|
1924
|
+
// src/tool-timing.ts
|
|
1925
|
+
var TOOL_TIMING_ENTRY_TYPE = "pi-workflows:tool-timing";
|
|
2451
1926
|
|
|
2452
1927
|
// src/trajectory-contracts.ts
|
|
2453
1928
|
var TRAJECTORY_ACTIONS = ["checkpoint-approve", "checkpoint-reject", "pause", "resume", "steer", "stop", "retry", "share"];
|
|
@@ -2480,7 +1955,7 @@ function transcriptToolCallId(value) {
|
|
|
2480
1955
|
return void 0;
|
|
2481
1956
|
}
|
|
2482
1957
|
function isTimingTranscriptEntry(value) {
|
|
2483
|
-
return object(value) && value.type === "custom" && value.customType ===
|
|
1958
|
+
return object(value) && value.type === "custom" && value.customType === TOOL_TIMING_ENTRY_TYPE;
|
|
2484
1959
|
}
|
|
2485
1960
|
function timingToolCallId(value) {
|
|
2486
1961
|
if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data)) return void 0;
|
|
@@ -2601,21 +2076,21 @@ function withPiToolDescriptions(run) {
|
|
|
2601
2076
|
function canonicalExtensionSelector(selector, base) {
|
|
2602
2077
|
const negated = selector.startsWith("!");
|
|
2603
2078
|
const body = negated ? selector.slice(1) : selector;
|
|
2604
|
-
if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
|
|
2605
|
-
const resolved =
|
|
2079
|
+
if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
|
|
2080
|
+
const resolved = resolve3(base, body);
|
|
2606
2081
|
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
2607
2082
|
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
2608
2083
|
}
|
|
2609
2084
|
function skillNameFromPath(path) {
|
|
2610
|
-
const file =
|
|
2611
|
-
return file.toLowerCase() === "skill.md" ?
|
|
2085
|
+
const file = basename2(path);
|
|
2086
|
+
return file.toLowerCase() === "skill.md" ? basename2(dirname2(path)) : file;
|
|
2612
2087
|
}
|
|
2613
2088
|
var discoveredResourcesByCwd = /* @__PURE__ */ new Map();
|
|
2614
2089
|
async function discoveredResources(cwd) {
|
|
2615
2090
|
const cached = discoveredResourcesByCwd.get(cwd);
|
|
2616
2091
|
if (cached) return cached;
|
|
2617
2092
|
const pending = (async () => {
|
|
2618
|
-
const agentDir =
|
|
2093
|
+
const agentDir = getAgentDir2();
|
|
2619
2094
|
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
|
|
2620
2095
|
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
|
2621
2096
|
const resolved = await packageManager.resolve();
|
|
@@ -2637,8 +2112,8 @@ async function resolveResourceInspection(inspection, cwd, selectorCwd = cwd) {
|
|
|
2637
2112
|
return inspection;
|
|
2638
2113
|
}
|
|
2639
2114
|
const sources = inspection.selectorSources ?? { global: inspection.selectors, project: {} };
|
|
2640
|
-
const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.
|
|
2641
|
-
const extensions = inspection.extensions.length ? inspection.extensions : selectResourcesByLayers([sources.global.extensions, sources.project.extensions, sources.
|
|
2115
|
+
const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.call?.skills], discovered.skills);
|
|
2116
|
+
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);
|
|
2642
2117
|
return skills === inspection.skills && extensions === inspection.extensions ? inspection : { ...inspection, skills, extensions };
|
|
2643
2118
|
}
|
|
2644
2119
|
async function withResolvedResources(run, cwd) {
|
|
@@ -2671,11 +2146,17 @@ function failedAgentOutput(agent) {
|
|
|
2671
2146
|
const status = agent.state === "cancelled" ? "cancelled" : "failed";
|
|
2672
2147
|
return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
|
|
2673
2148
|
}
|
|
2149
|
+
function operationOutput(operation) {
|
|
2150
|
+
const bytes = serializedJsonBytes(operation.value);
|
|
2151
|
+
return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
|
|
2152
|
+
}
|
|
2153
|
+
function outputForCall(call, operation) {
|
|
2154
|
+
if (operation !== void 0) return call.kind === "tool" ? { status: "completed" } : operationOutput(operation);
|
|
2155
|
+
if (call.error) return { status: call.error.code === "CANCELLED" ? "cancelled" : "failed", code: call.error.code, ...call.kind === "shell" ? { message: call.error.message ?? "Shell failed" } : {} };
|
|
2156
|
+
return call.finishedAt === void 0 ? { status: "pending" } : { status: "unavailable" };
|
|
2157
|
+
}
|
|
2674
2158
|
function outputForAgent(agent, operation) {
|
|
2675
|
-
if (operation !== void 0)
|
|
2676
|
-
const bytes = serializedJsonBytes(operation.value);
|
|
2677
|
-
return bytes > DEFAULT_MAX_BYTES ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
|
|
2678
|
-
}
|
|
2159
|
+
if (operation !== void 0) return operationOutput(operation);
|
|
2679
2160
|
if (agent.state === "failed" || agent.state === "cancelled") return failedAgentOutput(agent);
|
|
2680
2161
|
if (agent.state === "completed") return { status: "unavailable" };
|
|
2681
2162
|
return { status: "pending" };
|
|
@@ -2683,7 +2164,17 @@ function outputForAgent(agent, operation) {
|
|
|
2683
2164
|
function applyTrajectoryAgentOutputs(run, operations) {
|
|
2684
2165
|
const byPath = new Map(operations.map((operation) => [operation.path, operation]));
|
|
2685
2166
|
const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === void 0 ? void 0 : byPath.get(agent.resultPath)) }));
|
|
2686
|
-
|
|
2167
|
+
const scriptCalls = run.scriptCalls?.map((call) => {
|
|
2168
|
+
const recorded = { ...call, output: outputForCall(call, byPath.get(call.path)) };
|
|
2169
|
+
if (call.kind === "tool") {
|
|
2170
|
+
delete recorded.input;
|
|
2171
|
+
delete recorded.inputBytes;
|
|
2172
|
+
if (call.error) recorded.error = { code: call.error.code };
|
|
2173
|
+
}
|
|
2174
|
+
return recorded;
|
|
2175
|
+
});
|
|
2176
|
+
const rest = run;
|
|
2177
|
+
return { ...rest, agents, ...scriptCalls === void 0 ? {} : { scriptCalls } };
|
|
2687
2178
|
}
|
|
2688
2179
|
function overlayTrajectoryRun(run, overlay) {
|
|
2689
2180
|
const overlaid = overlay(run);
|
|
@@ -2693,7 +2184,7 @@ function overlayTrajectoryRun(run, overlay) {
|
|
|
2693
2184
|
const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, void 0);
|
|
2694
2185
|
return { ...agent, output };
|
|
2695
2186
|
});
|
|
2696
|
-
return { ...overlaid, agents };
|
|
2187
|
+
return { ...overlaid, agents, ...run.scriptCalls === void 0 ? {} : { scriptCalls: run.scriptCalls } };
|
|
2697
2188
|
}
|
|
2698
2189
|
async function loadTrajectoryRun(store, includeTranscripts = true) {
|
|
2699
2190
|
const value = await store.load();
|
|
@@ -2704,7 +2195,7 @@ async function loadTrajectoryRun(store, includeTranscripts = true) {
|
|
|
2704
2195
|
const run = applyTrajectoryAgentOutputs(prepared, operations);
|
|
2705
2196
|
return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...summary?.createdAt === void 0 ? {} : { createdAt: summary.createdAt }, transcripts: includeTranscripts ? await runTranscripts(run) : {} };
|
|
2706
2197
|
}
|
|
2707
|
-
async function loadTrajectoryRuns(cwd, sessionId, home =
|
|
2198
|
+
async function loadTrajectoryRuns(cwd, sessionId, home = homedir4(), overlay) {
|
|
2708
2199
|
const loaded = [];
|
|
2709
2200
|
for (const runId of await listRunIds(cwd, sessionId, home, false)) {
|
|
2710
2201
|
try {
|
|
@@ -2719,15 +2210,15 @@ async function loadTrajectoryRuns(cwd, sessionId, home = homedir5(), overlay) {
|
|
|
2719
2210
|
// trajectory/src/export.ts
|
|
2720
2211
|
import { execFile as execFile3 } from "node:child_process";
|
|
2721
2212
|
import { mkdtemp, readFile as readFile5, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
2722
|
-
import { homedir as
|
|
2723
|
-
import { join as
|
|
2213
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
2214
|
+
import { join as join5 } from "node:path";
|
|
2724
2215
|
var DEFAULT_SHARE_VIEWER_URL = "https://vekexasia.github.io/pi-extensible-workflows/run.html";
|
|
2725
2216
|
function inlineAsset(html, tag, replacement) {
|
|
2726
2217
|
if (!html.includes(tag)) throw new Error(`Trajectory UI is missing the expected ${tag} reference`);
|
|
2727
2218
|
return html.replace(tag, () => replacement);
|
|
2728
2219
|
}
|
|
2729
2220
|
async function exportTrajectoryRunHtml(options) {
|
|
2730
|
-
const home = options.home ??
|
|
2221
|
+
const home = options.home ?? homedir5();
|
|
2731
2222
|
const runs = await loadTrajectoryRuns(options.cwd, options.sessionId, home);
|
|
2732
2223
|
const run = runs.find((candidate) => candidate.run.id === options.runId);
|
|
2733
2224
|
if (!run) throw new Error(`Workflow run ${options.runId} was not found for session ${options.sessionId}`);
|
|
@@ -2755,22 +2246,22 @@ async function exportTrajectoryRunHtml(options) {
|
|
|
2755
2246
|
return output;
|
|
2756
2247
|
}
|
|
2757
2248
|
function runGh(ghPath, args) {
|
|
2758
|
-
return new Promise((
|
|
2249
|
+
return new Promise((resolve4, reject) => {
|
|
2759
2250
|
execFile3(ghPath, [...args], { encoding: "utf8", maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
2760
2251
|
if (error) {
|
|
2761
2252
|
const notFound = error.code === "ENOENT";
|
|
2762
2253
|
reject(new Error(notFound ? "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/ and run `gh auth login`." : stderr.trim() || error.message));
|
|
2763
2254
|
return;
|
|
2764
2255
|
}
|
|
2765
|
-
|
|
2256
|
+
resolve4(stdout);
|
|
2766
2257
|
});
|
|
2767
2258
|
});
|
|
2768
2259
|
}
|
|
2769
2260
|
async function shareTrajectoryRun(options) {
|
|
2770
2261
|
const html = await exportTrajectoryRunHtml(options);
|
|
2771
|
-
const directory = await mkdtemp(
|
|
2262
|
+
const directory = await mkdtemp(join5(tmpdir(), "piewf-share-"));
|
|
2772
2263
|
try {
|
|
2773
|
-
const file =
|
|
2264
|
+
const file = join5(directory, "trajectory.html");
|
|
2774
2265
|
await writeFile3(file, html);
|
|
2775
2266
|
const stdout = await runGh(options.ghPath ?? "gh", ["gist", "create", "--public=false", file]);
|
|
2776
2267
|
const gistUrl = stdout.trim().split("\n").pop() ?? "";
|
|
@@ -2783,21 +2274,158 @@ async function shareTrajectoryRun(options) {
|
|
|
2783
2274
|
}
|
|
2784
2275
|
}
|
|
2785
2276
|
|
|
2277
|
+
// trajectory/src/startup-mutex.ts
|
|
2278
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2279
|
+
import { readFileSync } from "node:fs";
|
|
2280
|
+
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile6, rm as rm5 } from "node:fs/promises";
|
|
2281
|
+
import { join as join6 } from "node:path";
|
|
2282
|
+
var bootId;
|
|
2283
|
+
function processStart(pid) {
|
|
2284
|
+
if (process.platform !== "linux") return void 0;
|
|
2285
|
+
try {
|
|
2286
|
+
bootId ??= readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
2287
|
+
const stat4 = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
|
|
2288
|
+
const startTicks = stat4.slice(stat4.lastIndexOf(")") + 2).split(" ")[19];
|
|
2289
|
+
return startTicks === void 0 || !/^\d+$/.test(startTicks) ? void 0 : `${bootId}:${startTicks}`;
|
|
2290
|
+
} catch {
|
|
2291
|
+
return void 0;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
function processLive(identity) {
|
|
2295
|
+
try {
|
|
2296
|
+
process.kill(identity.pid, 0);
|
|
2297
|
+
} catch (error) {
|
|
2298
|
+
if (isNodeError(error, "ESRCH")) return false;
|
|
2299
|
+
}
|
|
2300
|
+
if (identity.start === void 0) return true;
|
|
2301
|
+
const current = processStart(identity.pid);
|
|
2302
|
+
return current === void 0 || current === identity.start;
|
|
2303
|
+
}
|
|
2304
|
+
function processConfirmed(identity) {
|
|
2305
|
+
return identity.start !== void 0 && processStart(identity.pid) === identity.start;
|
|
2306
|
+
}
|
|
2307
|
+
function holderDirectory(lockPath) {
|
|
2308
|
+
return `${lockPath}.startup`;
|
|
2309
|
+
}
|
|
2310
|
+
function holderPath(lockPath, token) {
|
|
2311
|
+
return join6(holderDirectory(lockPath), `${token}.json`);
|
|
2312
|
+
}
|
|
2313
|
+
function identityOf(pid) {
|
|
2314
|
+
const start = processStart(pid);
|
|
2315
|
+
return start === void 0 ? { pid } : { pid, start };
|
|
2316
|
+
}
|
|
2317
|
+
function processIdentity(value) {
|
|
2318
|
+
if (!object(value) || !positiveInteger(value.pid)) return void 0;
|
|
2319
|
+
return typeof value.start === "string" ? { pid: value.pid, start: value.start } : { pid: value.pid };
|
|
2320
|
+
}
|
|
2321
|
+
function decodeHolder(text) {
|
|
2322
|
+
try {
|
|
2323
|
+
const parsed = JSON.parse(text);
|
|
2324
|
+
const holder = processIdentity(parsed);
|
|
2325
|
+
if (holder === void 0 || !object(parsed) || typeof parsed.token !== "string") return void 0;
|
|
2326
|
+
const server = processIdentity(parsed.server);
|
|
2327
|
+
return { ...holder, token: parsed.token, ...server === void 0 ? {} : { server } };
|
|
2328
|
+
} catch {
|
|
2329
|
+
return void 0;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
function readStartupHolder(lockPath, token) {
|
|
2333
|
+
try {
|
|
2334
|
+
return decodeHolder(readFileSync(holderPath(lockPath, token), "utf8"));
|
|
2335
|
+
} catch (error) {
|
|
2336
|
+
if (isNodeError(error, "ENOENT")) return void 0;
|
|
2337
|
+
throw error;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
async function serverPending(lockPath, server) {
|
|
2341
|
+
if (!processLive(server)) return false;
|
|
2342
|
+
let text;
|
|
2343
|
+
try {
|
|
2344
|
+
text = await readFile6(lockPath, "utf8");
|
|
2345
|
+
} catch (error) {
|
|
2346
|
+
if (isNodeError(error, "ENOENT")) return true;
|
|
2347
|
+
throw error;
|
|
2348
|
+
}
|
|
2349
|
+
try {
|
|
2350
|
+
const lock = JSON.parse(text);
|
|
2351
|
+
return !(object(lock) && lock.pid === server.pid);
|
|
2352
|
+
} catch {
|
|
2353
|
+
return true;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
async function holderLive(lockPath, path, read) {
|
|
2357
|
+
if (processLive(read)) return true;
|
|
2358
|
+
let holder;
|
|
2359
|
+
try {
|
|
2360
|
+
holder = decodeHolder(await readFile6(path, "utf8"));
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
if (isNodeError(error, "ENOENT")) return false;
|
|
2363
|
+
throw error;
|
|
2364
|
+
}
|
|
2365
|
+
if (holder?.server === void 0 || !await serverPending(lockPath, holder.server)) return false;
|
|
2366
|
+
if (!processConfirmed(holder.server)) return true;
|
|
2367
|
+
try {
|
|
2368
|
+
process.kill(holder.server.pid, "SIGKILL");
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
if (!isNodeError(error, "ESRCH")) throw error;
|
|
2371
|
+
}
|
|
2372
|
+
return true;
|
|
2373
|
+
}
|
|
2374
|
+
async function tryAcquireStartupMutex(lockPath) {
|
|
2375
|
+
const directory = holderDirectory(lockPath);
|
|
2376
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
2377
|
+
const token = randomUUID3();
|
|
2378
|
+
const own = `${token}.json`;
|
|
2379
|
+
await atomicWriteFile(join6(directory, own), `${JSON.stringify({ ...identityOf(process.pid), token })}
|
|
2380
|
+
`);
|
|
2381
|
+
let contended = false;
|
|
2382
|
+
try {
|
|
2383
|
+
for (const name of await readdir4(directory)) {
|
|
2384
|
+
if (name === own || !name.endsWith(".json")) continue;
|
|
2385
|
+
const path = join6(directory, name);
|
|
2386
|
+
let holder;
|
|
2387
|
+
try {
|
|
2388
|
+
holder = decodeHolder(await readFile6(path, "utf8"));
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
if (isNodeError(error, "ENOENT")) continue;
|
|
2391
|
+
throw error;
|
|
2392
|
+
}
|
|
2393
|
+
if (holder !== void 0 && await holderLive(lockPath, path, holder)) contended = true;
|
|
2394
|
+
else await rm5(path, { force: true });
|
|
2395
|
+
}
|
|
2396
|
+
} catch (error) {
|
|
2397
|
+
await rm5(join6(directory, own), { force: true });
|
|
2398
|
+
throw error;
|
|
2399
|
+
}
|
|
2400
|
+
if (!contended) return token;
|
|
2401
|
+
await rm5(join6(directory, own), { force: true });
|
|
2402
|
+
return void 0;
|
|
2403
|
+
}
|
|
2404
|
+
function recordStartupServer(lockPath, token, pid) {
|
|
2405
|
+
const holder = readStartupHolder(lockPath, token);
|
|
2406
|
+
if (holder === void 0) throw new Error("Trajectory startup mutex was lost");
|
|
2407
|
+
atomicWriteFile(holderPath(lockPath, token), `${JSON.stringify({ ...holder, server: identityOf(pid) })}
|
|
2408
|
+
`, true);
|
|
2409
|
+
}
|
|
2410
|
+
async function releaseStartupMutex(lockPath, token) {
|
|
2411
|
+
await rm5(holderPath(lockPath, token), { force: true });
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2786
2414
|
// trajectory/src/index.ts
|
|
2787
2415
|
var DEFAULT_TRAJECTORY_PORT = 7432;
|
|
2788
2416
|
var TRAJECTORY_IDLE_EXIT_MS = 5 * 60 * 1e3;
|
|
2789
2417
|
var TRAJECTORY_LOCK_NAME = "trajectory.lock";
|
|
2790
2418
|
function trajectoryLockPath(agentDir) {
|
|
2791
|
-
return
|
|
2419
|
+
return join7(agentDir, "pi-extensible-workflows", TRAJECTORY_LOCK_NAME);
|
|
2792
2420
|
}
|
|
2793
|
-
function trajectoryServerPath(moduleDirectory =
|
|
2794
|
-
const candidates = [
|
|
2421
|
+
function trajectoryServerPath(moduleDirectory = dirname3(fileURLToPath(import.meta.url))) {
|
|
2422
|
+
const candidates = [join7(moduleDirectory, "server.js"), join7(moduleDirectory, "../../dist/trajectory/src/server.js")];
|
|
2795
2423
|
const path = candidates.find((candidate) => existsSync(candidate));
|
|
2796
2424
|
if (!path) throw new Error("Trajectory server implementation is unavailable");
|
|
2797
2425
|
return path;
|
|
2798
2426
|
}
|
|
2799
2427
|
async function trajectoryFingerprint(serverPath) {
|
|
2800
|
-
const [serverBytes, htmlBytes] = await Promise.all([
|
|
2428
|
+
const [serverBytes, htmlBytes] = await Promise.all([readFile7(serverPath), readFile7(join7(dirname3(serverPath), "assets/index.html"))]);
|
|
2801
2429
|
return `${createHash3("sha256").update(serverBytes).digest("hex")}:${createHash3("sha256").update(htmlBytes).digest("hex")}`;
|
|
2802
2430
|
}
|
|
2803
2431
|
function publisherId(cwd, sessionId) {
|
|
@@ -2808,7 +2436,7 @@ function trajectoryPort(value) {
|
|
|
2808
2436
|
return positiveInteger(value) && value <= 65535 ? value : DEFAULT_TRAJECTORY_PORT;
|
|
2809
2437
|
}
|
|
2810
2438
|
function delay(ms) {
|
|
2811
|
-
return new Promise((
|
|
2439
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2812
2440
|
}
|
|
2813
2441
|
async function serverHealth(port) {
|
|
2814
2442
|
try {
|
|
@@ -2831,107 +2459,133 @@ function signalProcess(pid, signal) {
|
|
|
2831
2459
|
if (!isNodeError(error, "ESRCH")) throw error;
|
|
2832
2460
|
}
|
|
2833
2461
|
}
|
|
2834
|
-
async function stopStaleServer(
|
|
2835
|
-
|
|
2836
|
-
if (!
|
|
2837
|
-
signalProcess(
|
|
2462
|
+
async function stopStaleServer(pid, port) {
|
|
2463
|
+
const server = identityOf(pid);
|
|
2464
|
+
if (!processLive(server)) return;
|
|
2465
|
+
signalProcess(pid, "SIGTERM");
|
|
2838
2466
|
const deadline = Date.now() + 3e3;
|
|
2839
2467
|
while (Date.now() < deadline) {
|
|
2840
|
-
if (!await serverHealthy(
|
|
2468
|
+
if (!await serverHealthy(port)) break;
|
|
2841
2469
|
const remaining = deadline - Date.now();
|
|
2842
2470
|
if (remaining <= 0) break;
|
|
2843
2471
|
await delay(Math.min(50, remaining));
|
|
2844
2472
|
}
|
|
2845
|
-
if (
|
|
2473
|
+
if (processConfirmed(server)) signalProcess(pid, "SIGKILL");
|
|
2474
|
+
for (let attempt = 0; attempt < 50 && processLive(server); attempt += 1) await delay(20);
|
|
2475
|
+
if (processLive(server)) throw new Error(`Stale Trajectory server ${String(pid)} on port ${String(port)} did not stop; stop it and retry`);
|
|
2846
2476
|
}
|
|
2847
2477
|
async function readLock(path) {
|
|
2478
|
+
let text;
|
|
2848
2479
|
try {
|
|
2849
|
-
|
|
2850
|
-
if (!object(parsed) || !positiveInteger(parsed.pid) || !positiveInteger(parsed.port) || parsed.port > 65535) return void 0;
|
|
2851
|
-
const fingerprint = typeof parsed.fingerprint === "string" ? parsed.fingerprint : void 0;
|
|
2852
|
-
const startedAt = positiveInteger(parsed.startedAt) ? parsed.startedAt : void 0;
|
|
2853
|
-
return { pid: parsed.pid, port: parsed.port, ...fingerprint === void 0 ? {} : { fingerprint }, ...startedAt === void 0 ? {} : { startedAt } };
|
|
2480
|
+
text = await readFile7(path, "utf8");
|
|
2854
2481
|
} catch (error) {
|
|
2855
2482
|
if (isNodeError(error, "ENOENT")) return void 0;
|
|
2483
|
+
throw error;
|
|
2484
|
+
}
|
|
2485
|
+
let parsed;
|
|
2486
|
+
try {
|
|
2487
|
+
parsed = JSON.parse(text);
|
|
2488
|
+
} catch {
|
|
2856
2489
|
return void 0;
|
|
2857
2490
|
}
|
|
2491
|
+
if (!object(parsed) || !positiveInteger(parsed.pid) || !positiveInteger(parsed.port) || parsed.port > 65535) return void 0;
|
|
2492
|
+
const fingerprint = typeof parsed.fingerprint === "string" ? parsed.fingerprint : void 0;
|
|
2493
|
+
const startedAt = positiveInteger(parsed.startedAt) ? parsed.startedAt : void 0;
|
|
2494
|
+
const start = typeof parsed.start === "string" ? parsed.start : void 0;
|
|
2495
|
+
return { pid: parsed.pid, port: parsed.port, ...fingerprint === void 0 ? {} : { fingerprint }, ...startedAt === void 0 ? {} : { startedAt }, ...start === void 0 ? {} : { start } };
|
|
2496
|
+
}
|
|
2497
|
+
var STARTUP_MUTEX_WAIT_MS = 2 * 60 * (300 + 50) + 3e3 + 1e3 + 1e3;
|
|
2498
|
+
async function acquireStartupMutex(lockPath) {
|
|
2499
|
+
const deadline = Date.now() + STARTUP_MUTEX_WAIT_MS;
|
|
2500
|
+
for (; ; ) {
|
|
2501
|
+
const token = await tryAcquireStartupMutex(lockPath);
|
|
2502
|
+
if (token !== void 0) return token;
|
|
2503
|
+
if (Date.now() >= deadline) throw new Error("Another Pi is still starting Trajectory; retry");
|
|
2504
|
+
await delay(25 + Math.floor(Math.random() * 50));
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function isTrajectoryServer(health) {
|
|
2508
|
+
return health?.pid !== void 0 && health.fingerprint !== void 0;
|
|
2858
2509
|
}
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2510
|
+
function ownsLock(health, lock) {
|
|
2511
|
+
return isTrajectoryServer(health) && health.pid === lock.pid && (lock.startedAt === void 0 || lock.startedAt === health.startedAt);
|
|
2512
|
+
}
|
|
2513
|
+
async function awaitHealth(port, accept, stopped = () => false) {
|
|
2514
|
+
for (let attempt = 0; attempt < 60 && !stopped(); attempt += 1) {
|
|
2862
2515
|
const health = await serverHealth(port);
|
|
2863
|
-
if (health
|
|
2864
|
-
foreign = health !== void 0;
|
|
2516
|
+
if (health !== void 0 && accept(health)) return health;
|
|
2865
2517
|
await delay(50);
|
|
2866
2518
|
}
|
|
2867
|
-
|
|
2519
|
+
return void 0;
|
|
2868
2520
|
}
|
|
2869
|
-
async function
|
|
2870
|
-
const
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2521
|
+
async function startServer(lockPath, serverPath, fingerprint, port, startupToken) {
|
|
2522
|
+
const child = spawn(process.versions.bun ? "node" : process.execPath, [serverPath, "--port", String(port), "--lock", lockPath, "--fingerprint", fingerprint, "--startup", startupToken], { detached: true, stdio: "ignore" });
|
|
2523
|
+
const startup = { ended: false };
|
|
2524
|
+
const exited = new Promise((resolve4) => {
|
|
2525
|
+
child.once("error", (error) => {
|
|
2526
|
+
startup.error = error;
|
|
2527
|
+
startup.ended = true;
|
|
2528
|
+
resolve4();
|
|
2529
|
+
});
|
|
2530
|
+
child.once("exit", () => {
|
|
2531
|
+
startup.ended = true;
|
|
2532
|
+
resolve4();
|
|
2533
|
+
});
|
|
2534
|
+
});
|
|
2535
|
+
try {
|
|
2536
|
+
if (child.pid !== void 0) recordStartupServer(lockPath, startupToken, child.pid);
|
|
2537
|
+
const health = await awaitHealth(port, (candidate) => candidate.pid === child.pid && candidate.fingerprint === fingerprint, () => startup.ended);
|
|
2538
|
+
if (startup.error !== void 0) throw startup.error;
|
|
2539
|
+
const lock = health === void 0 ? void 0 : await readLock(lockPath);
|
|
2540
|
+
if (lock !== void 0 && ownsLock(health, lock) && lock.port === port && lock.fingerprint === fingerprint) {
|
|
2541
|
+
child.unref();
|
|
2542
|
+
return { port };
|
|
2543
|
+
}
|
|
2544
|
+
throw new Error(`Trajectory server did not start on port ${String(port)}`);
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
if (!startup.ended) {
|
|
2547
|
+
child.kill("SIGKILL");
|
|
2548
|
+
await exited;
|
|
2549
|
+
}
|
|
2550
|
+
if ((await readLock(lockPath))?.pid === child.pid) await rm6(lockPath, { force: true });
|
|
2551
|
+
throw error;
|
|
2876
2552
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2553
|
+
}
|
|
2554
|
+
async function startOrReuseServer(lockPath, serverPath, fingerprint, configuredPort, startupToken) {
|
|
2555
|
+
const existing = await readLock(lockPath);
|
|
2556
|
+
if (existing) {
|
|
2557
|
+
let health = await serverHealth(existing.port);
|
|
2558
|
+
if (health === void 0 && processLive(existing)) health = await awaitHealth(existing.port, (candidate) => candidate.pid === existing.pid);
|
|
2559
|
+
const lock = health === void 0 ? existing : await readLock(lockPath) ?? existing;
|
|
2560
|
+
if (ownsLock(health, lock)) {
|
|
2561
|
+
if (lock.fingerprint === fingerprint && health.fingerprint === fingerprint) return { port: lock.port };
|
|
2562
|
+
await stopStaleServer(health.pid, lock.port);
|
|
2563
|
+
} else if (!isTrajectoryServer(health) && lock.pid !== process.pid && processLive(lock)) {
|
|
2564
|
+
throw new Error(health === void 0 ? `Trajectory server ${String(lock.pid)} named by ${lockPath} is alive but not answering on port ${String(lock.port)}; retry once it answers, or remove the lock if that process is not a Trajectory server` : `Trajectory port ${String(lock.port)} is held by another Trajectory server; stop it and retry`);
|
|
2885
2565
|
}
|
|
2566
|
+
await rm6(lockPath, { force: true });
|
|
2886
2567
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2568
|
+
const occupant = await serverHealth(configuredPort);
|
|
2569
|
+
if (occupant !== void 0) {
|
|
2570
|
+
if (!isTrajectoryServer(occupant)) throw new Error(`Trajectory port ${String(configuredPort)} is held by another Trajectory server; stop it and retry`);
|
|
2571
|
+
if (occupant.fingerprint === fingerprint && occupant.startedAt !== void 0) {
|
|
2572
|
+
await atomicJson(lockPath, { pid: occupant.pid, port: configuredPort, fingerprint, startedAt: occupant.startedAt, start: processStart(occupant.pid) });
|
|
2573
|
+
return { port: configuredPort };
|
|
2574
|
+
}
|
|
2575
|
+
await stopStaleServer(occupant.pid, configuredPort);
|
|
2576
|
+
}
|
|
2577
|
+
return startServer(lockPath, serverPath, fingerprint, configuredPort, startupToken);
|
|
2889
2578
|
}
|
|
2890
2579
|
async function ensureTrajectoryServer(agentDir, configuredPort) {
|
|
2891
2580
|
const lockPath = trajectoryLockPath(agentDir);
|
|
2892
2581
|
const serverPath = trajectoryServerPath();
|
|
2893
2582
|
const fingerprint = await trajectoryFingerprint(serverPath);
|
|
2894
|
-
await
|
|
2895
|
-
const
|
|
2896
|
-
if (existing) {
|
|
2897
|
-
const reused = await resolveExistingServer(lockPath, existing, fingerprint);
|
|
2898
|
-
if (reused) return reused;
|
|
2899
|
-
}
|
|
2900
|
-
let lockHandle;
|
|
2583
|
+
await mkdir4(dirname3(lockPath), { recursive: true, mode: 448 });
|
|
2584
|
+
const startupToken = await acquireStartupMutex(lockPath);
|
|
2901
2585
|
try {
|
|
2902
|
-
|
|
2903
|
-
await lockHandle.writeFile(`${JSON.stringify({ pid: process.pid, port: configuredPort, fingerprint, startedAt: Date.now() })}
|
|
2904
|
-
`, "utf8");
|
|
2905
|
-
} catch (error) {
|
|
2906
|
-
if (isNodeError(error, "EEXIST")) {
|
|
2907
|
-
const raced = await readLock(lockPath);
|
|
2908
|
-
if (raced) {
|
|
2909
|
-
const reused = await resolveExistingServer(lockPath, raced, fingerprint);
|
|
2910
|
-
if (reused) return reused;
|
|
2911
|
-
}
|
|
2912
|
-
await rm5(lockPath, { force: true });
|
|
2913
|
-
lockHandle = await open3(lockPath, "wx", 384);
|
|
2914
|
-
await lockHandle.writeFile(`${JSON.stringify({ pid: process.pid, port: configuredPort, fingerprint, startedAt: Date.now() })}
|
|
2915
|
-
`, "utf8");
|
|
2916
|
-
} else {
|
|
2917
|
-
throw error;
|
|
2918
|
-
}
|
|
2586
|
+
return await startOrReuseServer(lockPath, serverPath, fingerprint, configuredPort, startupToken);
|
|
2919
2587
|
} finally {
|
|
2920
|
-
await
|
|
2921
|
-
}
|
|
2922
|
-
const occupant = await serverHealth(configuredPort);
|
|
2923
|
-
if (occupant?.pid !== void 0 && occupant.fingerprint !== fingerprint) await stopStaleServer({ pid: occupant.pid, port: configuredPort, ...occupant.startedAt === void 0 ? {} : { startedAt: occupant.startedAt } });
|
|
2924
|
-
try {
|
|
2925
|
-
const child = spawn(process.versions.bun ? "node" : process.execPath, [serverPath, "--port", String(configuredPort), "--lock", lockPath, "--fingerprint", fingerprint], { detached: true, stdio: "ignore" });
|
|
2926
|
-
const startupError = new Promise((_resolve, reject) => {
|
|
2927
|
-
child.once("error", reject);
|
|
2928
|
-
});
|
|
2929
|
-
child.unref();
|
|
2930
|
-
await Promise.race([waitForServer(configuredPort, fingerprint), startupError]);
|
|
2931
|
-
return { port: configuredPort };
|
|
2932
|
-
} catch (error) {
|
|
2933
|
-
await rm5(lockPath, { force: true });
|
|
2934
|
-
throw error;
|
|
2588
|
+
await releaseStartupMutex(lockPath, startupToken);
|
|
2935
2589
|
}
|
|
2936
2590
|
}
|
|
2937
2591
|
function trajectoryWebSocket() {
|
|
@@ -2962,9 +2616,6 @@ var ACTIVE_POLL_MS = 1e3;
|
|
|
2962
2616
|
var IDLE_POLL_MS = 1e4;
|
|
2963
2617
|
var RECONNECT_INITIAL_DELAY_MS = 100;
|
|
2964
2618
|
var RECONNECT_MAX_DELAY_MS = 5e3;
|
|
2965
|
-
function isTimingEntry(value) {
|
|
2966
|
-
return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing";
|
|
2967
|
-
}
|
|
2968
2619
|
function boundedString(value, maxLength = 1024) {
|
|
2969
2620
|
if (typeof value !== "string") return value;
|
|
2970
2621
|
const bytes = Buffer.from(value);
|
|
@@ -2974,7 +2625,7 @@ var MAX_LIVE_STRING_BYTES = 64 * 1024;
|
|
|
2974
2625
|
var MAX_LIVE_ARRAY_ENTRIES = 256;
|
|
2975
2626
|
var MAX_LIVE_OBJECT_KEYS = 64;
|
|
2976
2627
|
var LIVE_METADATA_ARRAY_KEYS = /* @__PURE__ */ new Set(["agents", "runs", "subagents"]);
|
|
2977
|
-
var LIVE_TRUNCATABLE_ARRAY_KEYS = /* @__PURE__ */ new Set(["events", "phaseHistory"]);
|
|
2628
|
+
var LIVE_TRUNCATABLE_ARRAY_KEYS = /* @__PURE__ */ new Set(["events", "phaseHistory", "scriptCalls"]);
|
|
2978
2629
|
var LIVE_METADATA_OBJECT_KEYS = /* @__PURE__ */ new Set(["transcripts"]);
|
|
2979
2630
|
function liveValueWillBeBounded(value, key = "", depth = 0) {
|
|
2980
2631
|
if (typeof value === "string") return Buffer.byteLength(value) > MAX_LIVE_STRING_BYTES;
|
|
@@ -2992,7 +2643,7 @@ function liveValueWillBeBounded(value, key = "", depth = 0) {
|
|
|
2992
2643
|
}
|
|
2993
2644
|
var MAX_LIVE_TIMING_BYTES = 64 * 1024;
|
|
2994
2645
|
function boundedTiming(value) {
|
|
2995
|
-
const entries = Array.isArray(value) ? value.filter(
|
|
2646
|
+
const entries = Array.isArray(value) ? value.filter(isTimingTranscriptEntry) : [];
|
|
2996
2647
|
const retained = [];
|
|
2997
2648
|
let bytes = 2;
|
|
2998
2649
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
@@ -3241,22 +2892,15 @@ function createTrajectoryController(agentDir) {
|
|
|
3241
2892
|
}
|
|
3242
2893
|
};
|
|
3243
2894
|
const transcriptFromFallback = async (input, request) => {
|
|
3244
|
-
const
|
|
3245
|
-
try {
|
|
3246
|
-
if (result.status === "available" && Buffer.byteLength(JSON.stringify(result.entries)) > TRAJECTORY_MAX_TRANSCRIPT_BYTES) return { status: "oversized", revision: result.revision, entries: [], error: "Transcript is too large" };
|
|
3247
|
-
} catch {
|
|
3248
|
-
return { status: "failed", revision: result.revision, entries: [], error: "Transcript failed" };
|
|
3249
|
-
}
|
|
3250
|
-
return result;
|
|
3251
|
-
};
|
|
3252
|
-
const boundedResult = (entries, revision) => {
|
|
2895
|
+
const oversized = (entries, revision) => {
|
|
3253
2896
|
try {
|
|
3254
|
-
|
|
2897
|
+
return Buffer.byteLength(JSON.stringify(entries)) > TRAJECTORY_MAX_TRANSCRIPT_BYTES ? { status: "oversized", revision, entries: [], error: "Transcript is too large" } : void 0;
|
|
3255
2898
|
} catch {
|
|
3256
2899
|
return { status: "failed", revision, entries: [], error: "Transcript failed" };
|
|
3257
2900
|
}
|
|
3258
|
-
return { status: entries.length ? "available" : "empty", revision, entries };
|
|
3259
2901
|
};
|
|
2902
|
+
const boundedTranscriptResult = (result) => (result.status === "available" ? oversized(result.entries, result.revision) : void 0) ?? result;
|
|
2903
|
+
const boundedResult = (entries, revision) => oversized(entries, revision) ?? { status: entries.length ? "available" : "empty", revision, entries };
|
|
3260
2904
|
if (input.loadTranscript) {
|
|
3261
2905
|
const result = await input.loadTranscript(request);
|
|
3262
2906
|
const revisionChanged = request.revision !== void 0 && result.revision !== request.revision && result.status !== "missing" && result.status !== "failed" && result.status !== "oversized" && result.status !== "disconnected";
|
|
@@ -3412,13 +3056,13 @@ function createTrajectoryController(agentDir) {
|
|
|
3412
3056
|
} catch {
|
|
3413
3057
|
}
|
|
3414
3058
|
});
|
|
3415
|
-
await new Promise((
|
|
3059
|
+
await new Promise((resolve4, reject) => {
|
|
3416
3060
|
let settled = false;
|
|
3417
3061
|
const finish = (error) => {
|
|
3418
3062
|
if (settled) return;
|
|
3419
3063
|
settled = true;
|
|
3420
3064
|
if (error) reject(error);
|
|
3421
|
-
else
|
|
3065
|
+
else resolve4();
|
|
3422
3066
|
};
|
|
3423
3067
|
next.addEventListener("open", () => {
|
|
3424
3068
|
finish();
|
|
@@ -3438,11 +3082,11 @@ function createTrajectoryController(agentDir) {
|
|
|
3438
3082
|
next.send(JSON.stringify({ type: "publisher:attach", publisherId: publisherId(input.cwd, input.sessionId) }));
|
|
3439
3083
|
};
|
|
3440
3084
|
let reconnectCancel;
|
|
3441
|
-
const waitReconnect = (ms) => new Promise((
|
|
3085
|
+
const waitReconnect = (ms) => new Promise((resolve4) => {
|
|
3442
3086
|
const timer = setTimeout(() => {
|
|
3443
3087
|
if (reconnectTimer === timer) reconnectTimer = void 0;
|
|
3444
3088
|
reconnectCancel = void 0;
|
|
3445
|
-
|
|
3089
|
+
resolve4();
|
|
3446
3090
|
}, ms);
|
|
3447
3091
|
timer.unref();
|
|
3448
3092
|
reconnectTimer = timer;
|
|
@@ -3450,7 +3094,7 @@ function createTrajectoryController(agentDir) {
|
|
|
3450
3094
|
clearTimeout(timer);
|
|
3451
3095
|
if (reconnectTimer === timer) reconnectTimer = void 0;
|
|
3452
3096
|
reconnectCancel = void 0;
|
|
3453
|
-
|
|
3097
|
+
resolve4();
|
|
3454
3098
|
};
|
|
3455
3099
|
});
|
|
3456
3100
|
const scheduleReconnect = () => {
|
|
@@ -3517,7 +3161,7 @@ function createTrajectoryController(agentDir) {
|
|
|
3517
3161
|
};
|
|
3518
3162
|
}
|
|
3519
3163
|
function registerTrajectoryExtension(pi, options = {}) {
|
|
3520
|
-
const controller = options.controller ?? createTrajectoryController(options.agentDir ??
|
|
3164
|
+
const controller = options.controller ?? createTrajectoryController(options.agentDir ?? getAgentDir3());
|
|
3521
3165
|
const openUrl = options.openUrl ?? openTrajectoryUrl;
|
|
3522
3166
|
let autoAttached = false;
|
|
3523
3167
|
const notify = (context, message, level) => {
|
|
@@ -3533,7 +3177,7 @@ function registerTrajectoryExtension(pi, options = {}) {
|
|
|
3533
3177
|
return void 0;
|
|
3534
3178
|
}
|
|
3535
3179
|
};
|
|
3536
|
-
const
|
|
3180
|
+
const open3 = async (provider, context) => {
|
|
3537
3181
|
const server = await attach(provider, context);
|
|
3538
3182
|
if (server === void 0) return;
|
|
3539
3183
|
const url = trajectoryUrl(server.port);
|
|
@@ -3541,7 +3185,7 @@ function registerTrajectoryExtension(pi, options = {}) {
|
|
|
3541
3185
|
notify(context, `Trajectory opened at ${url}`, "info");
|
|
3542
3186
|
};
|
|
3543
3187
|
const host = {
|
|
3544
|
-
open:
|
|
3188
|
+
open: open3,
|
|
3545
3189
|
autoAttach(provider, context) {
|
|
3546
3190
|
if (autoAttached || !context.hasUI) return;
|
|
3547
3191
|
autoAttached = true;
|