@marcoscale98/pi-extensible-workflows 5.19.1-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 +77 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +365 -127
- package/dist/src/background-widget.d.ts +0 -1
- package/dist/src/background-widget.js +4 -9
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +33 -37
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +5 -5
- package/dist/src/host-delivery.js +4 -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 +6 -6
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +1 -1
- package/dist/src/host-view.js +17 -15
- package/dist/src/host.js +62 -87
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1523 -1630
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +7 -6
- package/dist/src/paths.js +24 -15
- 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 +28 -70
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +6 -3
- package/dist/src/settings.js +36 -47
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +0 -1
- package/dist/src/trajectory.js +2 -3
- package/dist/src/types.d.ts +100 -44
- package/dist/src/types.js +4 -7
- package/dist/src/utils.d.ts +3 -2
- package/dist/src/utils.js +20 -10
- package/dist/src/validation.d.ts +8 -2
- package/dist/src/validation.js +122 -86
- package/dist/starter/index.js +8 -9
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.js +8 -7
- package/dist/subagents/index.js +654 -814
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -3
- package/dist/subagents/src/contracts.js +2 -2
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +1 -2
- package/dist/subagents/src/manager.js +49 -70
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +0 -1
- package/dist/subagents/src/view.js +2 -16
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +8 -8
- package/dist/trajectory/index.js +487 -891
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +8 -8
- package/dist/trajectory/src/index.js +126 -86
- package/dist/trajectory/src/server.js +264 -25
- 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 +5 -5
- package/skills/pi-extensible-workflows/SKILL.md +10 -8
- package/src/agent-execution.ts +339 -127
- package/src/background-widget.ts +4 -10
- package/src/decoders.ts +31 -37
- package/src/execution.ts +5 -5
- package/src/host-delivery.ts +4 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +20 -26
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +18 -11
- package/src/host.ts +62 -113
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -12
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +27 -63
- package/src/runtime/agent-runner.ts +2 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +35 -47
- package/src/store.ts +18 -2
- package/src/trajectory.ts +2 -4
- package/src/types.ts +71 -30
- package/src/utils.ts +18 -9
- package/src/validation.ts +102 -79
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -7
- 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 +8 -14
- package/subagents/src/contracts.ts +3 -4
- package/subagents/src/index.ts +2 -3
- package/subagents/src/manager.ts +45 -125
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +4 -14
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +8 -8
- package/trajectory/src/index.ts +106 -82
- package/trajectory/src/server.ts +44 -8
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -326
- 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 -327
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",
|
|
@@ -56,13 +58,14 @@ var ERROR_CODES = [
|
|
|
56
58
|
"INTERNAL_ERROR"
|
|
57
59
|
];
|
|
58
60
|
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
59
|
-
var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
|
|
60
|
-
var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
|
|
61
61
|
var CONTEXT_FILE_SCOPES = ["global", "project", "cwd"];
|
|
62
62
|
function isContextFileScope(value) {
|
|
63
63
|
return CONTEXT_FILE_SCOPES.some((scope) => scope === value);
|
|
64
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"];
|
|
65
67
|
var WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
68
|
+
var LAUNCH_SNAPSHOT_IDENTITY_VERSION = 6;
|
|
66
69
|
var WORKFLOW_ERROR_BRAND = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-error");
|
|
67
70
|
var WorkflowError = class extends Error {
|
|
68
71
|
constructor(code, message) {
|
|
@@ -140,90 +143,13 @@ function errorText(error) {
|
|
|
140
143
|
function coerceWorkflowError(code, error) {
|
|
141
144
|
return error instanceof WorkflowError && error.code === code ? error : new WorkflowError(code, errorText(error));
|
|
142
145
|
}
|
|
143
|
-
function errorCode(error) {
|
|
144
|
-
if (error instanceof WorkflowError) return isWorkflowErrorCode(error.code) ? error.code : void 0;
|
|
145
|
-
if (!object(error)) return void 0;
|
|
146
|
-
return isWorkflowErrorCode(error.code) ? error.code : void 0;
|
|
147
|
-
}
|
|
148
|
-
function fail(code, message) {
|
|
149
|
-
throw new WorkflowError(code, message);
|
|
150
|
-
}
|
|
151
|
-
function byPriorityThenName(left, right) {
|
|
152
|
-
return left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
153
|
-
}
|
|
154
146
|
function isThinkingLevel(value) {
|
|
155
147
|
return THINKING_LEVELS.some((level) => level === value);
|
|
156
148
|
}
|
|
157
|
-
var MODEL_ALIAS_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
158
149
|
var WORKFLOW_EXTENSION_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
159
150
|
function validWorkflowExtensionNamespace(value) {
|
|
160
151
|
return WORKFLOW_EXTENSION_NAMESPACE.test(value) && value !== "__proto__" && value !== "constructor" && value !== "prototype";
|
|
161
152
|
}
|
|
162
|
-
function parseModelReference(value) {
|
|
163
|
-
const match = /^([^/:\s]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
|
|
164
|
-
if (!match?.[1] || !match[2]) fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
|
|
165
|
-
const thinking = match[3];
|
|
166
|
-
if (thinking !== void 0 && !isThinkingLevel(thinking)) fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
|
|
167
|
-
return { provider: match[1], model: match[2], ...thinking !== void 0 ? { thinking } : {} };
|
|
168
|
-
}
|
|
169
|
-
var MODEL_ALIAS_ERROR_NAME = /* @__PURE__ */ Symbol("modelAliasErrorName");
|
|
170
|
-
function aliasError(message, settingsPath, name) {
|
|
171
|
-
const error = new WorkflowError("CONFIG_ERROR", `${message} (settings: ${settingsPath})`);
|
|
172
|
-
if (name) Object.defineProperty(error, MODEL_ALIAS_ERROR_NAME, { value: name, configurable: true });
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
function modelAliasName(value, aliases) {
|
|
176
|
-
const name = /^([^/:\s]+)(?::[^:\s]+)?$/.exec(value)?.[1];
|
|
177
|
-
return name && Object.prototype.hasOwnProperty.call(aliases, name) ? name : void 0;
|
|
178
|
-
}
|
|
179
|
-
function validateModelAliases(value, settingsPath = "workflow settings") {
|
|
180
|
-
if (!object(value)) aliasError("modelAliases must be an object", settingsPath);
|
|
181
|
-
const aliases = {};
|
|
182
|
-
for (const [name, target] of Object.entries(value)) {
|
|
183
|
-
if (!MODEL_ALIAS_NAME.test(name)) aliasError(`Invalid model alias name: ${name}`, settingsPath, name);
|
|
184
|
-
if (typeof target !== "string" || !target.trim()) aliasError(`Invalid model alias target for ${name}`, settingsPath, name);
|
|
185
|
-
aliases[name] = target;
|
|
186
|
-
}
|
|
187
|
-
for (const name of Object.keys(aliases)) {
|
|
188
|
-
try {
|
|
189
|
-
resolveModelReference(name, aliases);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
aliasError(`Invalid model alias target for ${name}: ${errorText(error)}`, settingsPath, name);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return Object.freeze(aliases);
|
|
195
|
-
}
|
|
196
|
-
function unknownModel(value, target, settingsPath) {
|
|
197
|
-
const resolved = target ? ` resolved to ${target}` : "";
|
|
198
|
-
const path = settingsPath ? ` (settings: ${settingsPath})` : "";
|
|
199
|
-
fail("UNKNOWN_MODEL", `Unknown model${target ? " alias" : ""} ${value}${resolved}${path}`);
|
|
200
|
-
}
|
|
201
|
-
function resolveModelReference(value, aliases = {}, knownModels, settingsPath) {
|
|
202
|
-
const resolveReference = (reference, chain) => {
|
|
203
|
-
if (reference.includes("/")) return parseModelReference(reference);
|
|
204
|
-
const match = /^([^:\s]+)(?::([^:\s]+))?$/.exec(reference);
|
|
205
|
-
const thinking = match?.[2];
|
|
206
|
-
if (!match?.[1] || thinking !== void 0 && !isThinkingLevel(thinking)) unknownModel(reference, void 0, settingsPath);
|
|
207
|
-
const alias = modelAliasName(reference, aliases);
|
|
208
|
-
if (alias) {
|
|
209
|
-
if (chain.includes(alias)) fail("UNKNOWN_MODEL", `Circular model alias: ${[...chain, alias].join(" -> ")}${settingsPath ? ` (settings: ${settingsPath})` : ""}`);
|
|
210
|
-
const { [alias]: target } = aliases;
|
|
211
|
-
if (typeof target !== "string") unknownModel(reference, void 0, settingsPath);
|
|
212
|
-
const parsed = resolveReference(target, [...chain, alias]);
|
|
213
|
-
return thinking !== void 0 ? { ...parsed, thinking } : parsed;
|
|
214
|
-
}
|
|
215
|
-
const candidates = [...knownModels ?? []].filter((model) => model.slice(model.indexOf("/") + 1) === match[1]);
|
|
216
|
-
if (candidates.length === 1) {
|
|
217
|
-
const [candidate] = candidates;
|
|
218
|
-
if (candidate !== void 0) {
|
|
219
|
-
const parsed = parseModelReference(candidate);
|
|
220
|
-
return thinking !== void 0 ? { ...parsed, thinking } : parsed;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
unknownModel(reference, void 0, settingsPath);
|
|
224
|
-
};
|
|
225
|
-
return resolveReference(value, []);
|
|
226
|
-
}
|
|
227
153
|
var RESOURCE_PATTERN_OPTIONS = { dot: true, nonegate: true, nocomment: true };
|
|
228
154
|
function resourcePatternBody(pattern) {
|
|
229
155
|
return pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
@@ -231,12 +157,6 @@ function resourcePatternBody(pattern) {
|
|
|
231
157
|
function resourcePatternPath(value) {
|
|
232
158
|
return value.replaceAll("\\", "/");
|
|
233
159
|
}
|
|
234
|
-
function validateResourcePattern(pattern) {
|
|
235
|
-
const body = resourcePatternBody(pattern);
|
|
236
|
-
if (!body) throw new Error(`Empty minimatch pattern ${JSON.stringify(pattern)}`);
|
|
237
|
-
const matcher = new Minimatch(resourcePatternPath(body), RESOURCE_PATTERN_OPTIONS);
|
|
238
|
-
if (matcher.makeRe() === false) throw new Error(`Invalid minimatch pattern ${JSON.stringify(pattern)}`);
|
|
239
|
-
}
|
|
240
160
|
function resourcePatternMatches(resource, pattern) {
|
|
241
161
|
const body = resourcePatternBody(pattern);
|
|
242
162
|
if (body === "*") return true;
|
|
@@ -261,13 +181,137 @@ function resourcePatternHasMagic(pattern) {
|
|
|
261
181
|
function loadLaunchSnapshot(input) {
|
|
262
182
|
return deepFreeze(structuredClone(input));
|
|
263
183
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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;
|
|
269
263
|
}
|
|
270
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
|
+
|
|
271
315
|
// src/decoders.ts
|
|
272
316
|
var INVALID_PERSISTED_VALUE = /* @__PURE__ */ Symbol("invalid persisted value");
|
|
273
317
|
function integer(value) {
|
|
@@ -356,32 +400,19 @@ function decodeAgentResourceSelectors(value) {
|
|
|
356
400
|
function decodeContextFileScopes(value) {
|
|
357
401
|
return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : void 0);
|
|
358
402
|
}
|
|
359
|
-
function
|
|
360
|
-
if (!object(value)) return void 0;
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
|
|
367
|
-
const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
|
|
368
|
-
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);
|
|
369
410
|
const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
...prompt === void 0 ? {} : { prompt },
|
|
376
|
-
...description === void 0 ? {} : { description },
|
|
377
|
-
...foldedModel === void 0 ? {} : { model: foldedModel },
|
|
378
|
-
...tools === void 0 ? {} : { tools },
|
|
379
|
-
...skills === void 0 ? {} : { skills },
|
|
380
|
-
...extensions === void 0 ? {} : { extensions },
|
|
381
|
-
...overrideSystemPrompt === void 0 ? {} : { overrideSystemPrompt },
|
|
382
|
-
...contextFiles === void 0 ? {} : { contextFiles },
|
|
383
|
-
...extensionSettings === void 0 ? {} : { extensionSettings }
|
|
384
|
-
};
|
|
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 } };
|
|
385
416
|
}
|
|
386
417
|
function decodeWorkflowMetadata(value) {
|
|
387
418
|
if (!object(value) || typeof value.name !== "string") return void 0;
|
|
@@ -490,17 +521,22 @@ function decodeScheduledAgentOptions(value) {
|
|
|
490
521
|
const parentBreadcrumb = optionalString(value.parentBreadcrumb);
|
|
491
522
|
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
492
523
|
const model = optionalString(value.model);
|
|
493
|
-
const role = typeof value.role === "string" ? value.role : void 0;
|
|
494
524
|
const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
|
|
495
525
|
const schema = value.schema === void 0 ? void 0 : decodeJsonObject(value.schema);
|
|
496
526
|
const retries = optionalNumber(value.retries);
|
|
497
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;
|
|
498
534
|
const agentOptions = value.agentOptions === void 0 ? void 0 : decodeJsonObject(value.agentOptions);
|
|
499
535
|
const agentIdentity = value.agentIdentity === void 0 ? void 0 : decodeIdentity(value.agentIdentity);
|
|
500
536
|
const sessionPath = optionalString(value.sessionPath);
|
|
501
537
|
const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
502
538
|
const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
|
|
503
|
-
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;
|
|
504
540
|
return {
|
|
505
541
|
label: value.label,
|
|
506
542
|
...requestedLabel === void 0 ? {} : { requestedLabel },
|
|
@@ -511,7 +547,6 @@ function decodeScheduledAgentOptions(value) {
|
|
|
511
547
|
...extensions === void 0 ? {} : { extensions },
|
|
512
548
|
...worktreeOwner === void 0 ? {} : { worktreeOwner },
|
|
513
549
|
...model === void 0 ? {} : { model },
|
|
514
|
-
...role === void 0 ? {} : { role },
|
|
515
550
|
...contextFiles === void 0 ? {} : { contextFiles },
|
|
516
551
|
...schema === void 0 ? {} : { schema },
|
|
517
552
|
...retries === void 0 ? {} : { retries },
|
|
@@ -519,6 +554,9 @@ function decodeScheduledAgentOptions(value) {
|
|
|
519
554
|
...sessionPath === void 0 ? {} : { sessionPath },
|
|
520
555
|
...continuity === void 0 ? {} : { continuity },
|
|
521
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 } } : {},
|
|
522
560
|
...agentIdentity === void 0 ? {} : { agentIdentity },
|
|
523
561
|
...extensionSettings === void 0 ? {} : { extensionSettings }
|
|
524
562
|
};
|
|
@@ -559,11 +597,10 @@ function decodeAgentResourceInspection(value) {
|
|
|
559
597
|
const sources = sourceRecord === void 0 ? void 0 : {
|
|
560
598
|
global: decodeAgentResourceSelectors(sourceRecord.global),
|
|
561
599
|
project: decodeAgentResourceSelectors(sourceRecord.project),
|
|
562
|
-
...sourceRecord.role === void 0 ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) },
|
|
563
600
|
...sourceRecord.call === void 0 ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }
|
|
564
601
|
};
|
|
565
|
-
if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== void 0 && (!sources || !sources.global || !sources.project || sources.
|
|
566
|
-
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 } } } };
|
|
567
604
|
}
|
|
568
605
|
function decodeAgentSetupSummary(value) {
|
|
569
606
|
if (!object(value) || typeof value.cwd !== "string") return void 0;
|
|
@@ -601,7 +638,6 @@ function decodeAgent(value) {
|
|
|
601
638
|
const worktreeOwner = optionalString(value.worktreeOwner);
|
|
602
639
|
const handle = optionalString(value.handle);
|
|
603
640
|
const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
|
|
604
|
-
const role = optionalString(value.role);
|
|
605
641
|
const requestedModel = optionalString(value.requestedModel);
|
|
606
642
|
const startedAt = optionalNumber(value.startedAt);
|
|
607
643
|
const durationMs = optionalNumber(value.durationMs);
|
|
@@ -610,7 +646,7 @@ function decodeAgent(value) {
|
|
|
610
646
|
const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
|
|
611
647
|
const activity = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
|
|
612
648
|
const lastEventAt = optionalNumber(value.lastEventAt);
|
|
613
|
-
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;
|
|
614
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;
|
|
615
651
|
return {
|
|
616
652
|
...systemPrompt === void 0 ? {} : { systemPrompt },
|
|
@@ -627,7 +663,6 @@ function decodeAgent(value) {
|
|
|
627
663
|
...worktreeOwner === void 0 ? {} : { worktreeOwner },
|
|
628
664
|
...handle === void 0 ? {} : { handle, turn: value.turn },
|
|
629
665
|
...continuity === void 0 ? {} : { continuity },
|
|
630
|
-
...role === void 0 ? {} : { role },
|
|
631
666
|
...requestedModel === void 0 ? {} : { requestedModel },
|
|
632
667
|
model,
|
|
633
668
|
tools,
|
|
@@ -775,13 +810,11 @@ function decodeLaunchSnapshot(value) {
|
|
|
775
810
|
const phases = value.phases === void 0 ? void 0 : decodeStringArray(value.phases);
|
|
776
811
|
const models = decodeStringArray(value.models);
|
|
777
812
|
const tools = decodeStringArray(value.tools);
|
|
778
|
-
const
|
|
779
|
-
const roles = value.roles === void 0 ? void 0 : decodeRecord(value.roles, decodeAgentDefinition);
|
|
780
|
-
const projectRoles = value.projectRoles === void 0 ? void 0 : decodeStringArray(value.projectRoles);
|
|
813
|
+
const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
|
|
781
814
|
const schemas = decodeArray(value.schemas, decodeJsonObject);
|
|
782
|
-
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;
|
|
783
816
|
return {
|
|
784
|
-
|
|
817
|
+
identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
|
|
785
818
|
...launchMode === void 0 ? {} : { launchMode },
|
|
786
819
|
script: value.script,
|
|
787
820
|
args,
|
|
@@ -794,9 +827,7 @@ function decodeLaunchSnapshot(value) {
|
|
|
794
827
|
...phases === void 0 ? {} : { phases },
|
|
795
828
|
models,
|
|
796
829
|
tools,
|
|
797
|
-
|
|
798
|
-
...roles === void 0 ? {} : { roles },
|
|
799
|
-
...projectRoles === void 0 ? {} : { projectRoles },
|
|
830
|
+
agentConfigurations,
|
|
800
831
|
schemas
|
|
801
832
|
};
|
|
802
833
|
}
|
|
@@ -876,143 +907,45 @@ function decodeSystemPromptArtifact(value) {
|
|
|
876
907
|
return { version: value.version, ...value.format === void 0 ? {} : { format: value.format }, ...value.storage === void 0 ? {} : { storage: value.storage }, ...entries === void 0 ? {} : { entries } };
|
|
877
908
|
}
|
|
878
909
|
|
|
879
|
-
// src/
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
891
|
-
async function renameWithRetry(from, to) {
|
|
892
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
893
|
-
try {
|
|
894
|
-
await rename(from, to);
|
|
895
|
-
return;
|
|
896
|
-
} catch (error) {
|
|
897
|
-
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
898
|
-
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
899
|
-
await new Promise((resolve5) => setTimeout(resolve5, delayMs));
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
function renameSyncWithRetry(from, to) {
|
|
904
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
905
|
-
try {
|
|
906
|
-
renameSync(from, to);
|
|
907
|
-
return;
|
|
908
|
-
} catch (error) {
|
|
909
|
-
const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
|
|
910
|
-
if (!retryableRenameError(error) || delayMs === void 0) throw error;
|
|
911
|
-
Atomics.wait(syncSleepBuffer, 0, 0, delayMs);
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
var gitIdentity = {
|
|
916
|
-
GIT_AUTHOR_NAME: "pi-extensible-workflows",
|
|
917
|
-
GIT_AUTHOR_EMAIL: "pi-extensible-workflows@localhost",
|
|
918
|
-
GIT_COMMITTER_NAME: "pi-extensible-workflows",
|
|
919
|
-
GIT_COMMITTER_EMAIL: "pi-extensible-workflows@localhost",
|
|
920
|
-
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
921
|
-
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
922
|
-
};
|
|
923
|
-
function atomicWriteFile(path, content, sync = false) {
|
|
924
|
-
const temporary = `${path}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
925
|
-
if (sync) {
|
|
926
|
-
try {
|
|
927
|
-
writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
|
|
928
|
-
renameSyncWithRetry(temporary, path);
|
|
929
|
-
} catch (error) {
|
|
930
|
-
try {
|
|
931
|
-
rmSync(temporary, { force: true });
|
|
932
|
-
} catch {
|
|
933
|
-
}
|
|
934
|
-
throw error;
|
|
935
|
-
}
|
|
936
|
-
return;
|
|
910
|
+
// src/session-lease.ts
|
|
911
|
+
var RUN_CREATE_TEMP = /^\.([a-zA-Z0-9._-]+)\.(\d+)\.[0-9a-f-]+\.tmp$/;
|
|
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;
|
|
937
921
|
}
|
|
938
|
-
return
|
|
939
|
-
try {
|
|
940
|
-
await rm(temporary, { force: true });
|
|
941
|
-
} catch {
|
|
942
|
-
}
|
|
943
|
-
throw error;
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
async function atomicJson(path, value) {
|
|
947
|
-
const serialized = JSON.stringify(value);
|
|
948
|
-
if (typeof serialized !== "string") throw new WorkflowError("INTERNAL_ERROR", `Cannot serialize JSON for ${path}`);
|
|
949
|
-
await atomicWriteFile(path, `${serialized}
|
|
950
|
-
`);
|
|
951
|
-
}
|
|
952
|
-
async function atomicPrettyJson(path, value) {
|
|
953
|
-
await atomicWriteFile(path, `${JSON.stringify(value, null, 2)}
|
|
954
|
-
`);
|
|
955
|
-
}
|
|
956
|
-
async function json(path) {
|
|
957
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
958
|
-
}
|
|
959
|
-
async function git(cwd, args, extraEnv = {}) {
|
|
960
|
-
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 });
|
|
961
|
-
return stdout;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// src/paths.ts
|
|
965
|
-
import { createHash } from "node:crypto";
|
|
966
|
-
import { realpathSync } from "node:fs";
|
|
967
|
-
import { homedir } from "node:os";
|
|
968
|
-
import { basename, join, resolve } from "node:path";
|
|
969
|
-
function safePart(value) {
|
|
970
|
-
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
922
|
+
return clockTicks;
|
|
971
923
|
}
|
|
972
|
-
function
|
|
973
|
-
const absolute = resolve(path);
|
|
924
|
+
async function linuxProcessStart(pid, procRoot) {
|
|
974
925
|
try {
|
|
975
|
-
|
|
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;
|
|
976
932
|
} catch {
|
|
977
|
-
return
|
|
933
|
+
return void 0;
|
|
978
934
|
}
|
|
979
935
|
}
|
|
980
|
-
function
|
|
981
|
-
return canonicalPath(left) === canonicalPath(right);
|
|
982
|
-
}
|
|
983
|
-
function workflowProjectSettingsPath(cwd) {
|
|
984
|
-
return join(cwd, ".pi", "pi-extensible-workflows", "settings.json");
|
|
985
|
-
}
|
|
986
|
-
function projectStorageKey(cwd) {
|
|
987
|
-
const exact = resolve(cwd);
|
|
988
|
-
const slug = safePart(basename(exact)) || "root";
|
|
989
|
-
return `${slug}-${createHash("sha256").update(exact).digest("hex").slice(0, 12)}`;
|
|
990
|
-
}
|
|
991
|
-
function projectSessionsDirectory(cwd, home = homedir()) {
|
|
992
|
-
return join(home, ".pi", "workflows", "projects", projectStorageKey(cwd), "sessions");
|
|
993
|
-
}
|
|
994
|
-
function runsDirectory(cwd, sessionId, home = homedir()) {
|
|
995
|
-
return join(projectSessionsDirectory(cwd, home), safePart(sessionId), "runs");
|
|
996
|
-
}
|
|
997
|
-
function structuralPath(...names) {
|
|
998
|
-
if (names.length === 0 || names.some((name) => name.trim() === "")) throw new WorkflowError("INVALID_METADATA", "Structural paths require non-empty explicit names");
|
|
999
|
-
return names.map((name) => encodeURIComponent(name)).join("/");
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// src/session-lease.ts
|
|
1003
|
-
var RUN_CREATE_TEMP = /^\.([a-zA-Z0-9._-]+)\.(\d+)\.[0-9a-f-]+\.tmp$/;
|
|
1004
|
-
async function processAlive(pid, startedAt) {
|
|
936
|
+
async function processAlive(pid, startedAt, procRoot = "/proc") {
|
|
1005
937
|
try {
|
|
1006
938
|
process.kill(pid, 0);
|
|
1007
939
|
} catch (error) {
|
|
1008
940
|
return !isNodeError(error, "ESRCH");
|
|
1009
941
|
}
|
|
1010
|
-
if (startedAt
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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");
|
|
1016
949
|
}
|
|
1017
950
|
return true;
|
|
1018
951
|
}
|
|
@@ -1035,18 +968,12 @@ async function listRunIds(cwd, sessionId, home = homedir2(), cleanTemps = true)
|
|
|
1035
968
|
}
|
|
1036
969
|
}
|
|
1037
970
|
|
|
1038
|
-
// src/trajectory.ts
|
|
1039
|
-
import { open as open2, readFile as readFile4, readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
1040
|
-
import { homedir as homedir5 } from "node:os";
|
|
1041
|
-
import { basename as basename3, dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
|
|
1042
|
-
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager, getAgentDir as getAgentDir3, SettingsManager, DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
|
|
1043
|
-
|
|
1044
971
|
// src/store.ts
|
|
1045
972
|
import { execFile as execFile2 } from "node:child_process";
|
|
1046
973
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
1047
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";
|
|
1048
975
|
import { homedir as homedir3 } from "node:os";
|
|
1049
|
-
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";
|
|
1050
977
|
import { promisify as promisify2 } from "node:util";
|
|
1051
978
|
|
|
1052
979
|
// src/budget.ts
|
|
@@ -1071,7 +998,7 @@ function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, no
|
|
|
1071
998
|
const failedAt = run.failedAt ?? run.error?.failedAt;
|
|
1072
999
|
const replayablePaths = [.../* @__PURE__ */ new Set([...run.retry?.completedPaths ?? [], ...Object.keys(journal.completed)])];
|
|
1073
1000
|
const incompletePaths = [.../* @__PURE__ */ new Set([...run.retry?.incompletePaths ?? [], ...failedAt ? [failedAt] : []])];
|
|
1074
|
-
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) };
|
|
1075
1002
|
}
|
|
1076
1003
|
function systemPromptStoragePath(directory) {
|
|
1077
1004
|
return join3(directory, SYSTEM_PROMPT_STORAGE);
|
|
@@ -1219,6 +1146,20 @@ var RunStore = class _RunStore {
|
|
|
1219
1146
|
});
|
|
1220
1147
|
return write;
|
|
1221
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
|
+
}
|
|
1222
1163
|
async saveSnapshot(snapshot) {
|
|
1223
1164
|
const write = this.launchSnapshotLane.run(() => atomicJson(join3(this.directory, "snapshot.json"), snapshot));
|
|
1224
1165
|
await write;
|
|
@@ -1493,9 +1434,9 @@ var RunStore = class _RunStore {
|
|
|
1493
1434
|
}
|
|
1494
1435
|
structuralWorktree(owner, record) {
|
|
1495
1436
|
const expected = this.expectedWorktree(owner);
|
|
1496
|
-
const relativePath =
|
|
1497
|
-
const relativeCwd =
|
|
1498
|
-
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}`);
|
|
1499
1440
|
return record;
|
|
1500
1441
|
}
|
|
1501
1442
|
async #loadWorktreeRecords(missingOk = true) {
|
|
@@ -1733,8 +1674,8 @@ var RunStore = class _RunStore {
|
|
|
1733
1674
|
try {
|
|
1734
1675
|
const root = (await git(this.cwd, ["rev-parse", "--show-toplevel"])).trim();
|
|
1735
1676
|
const [canonicalRoot, canonicalCwd] = await Promise.all([realpath(root), realpath(this.cwd)]);
|
|
1736
|
-
const launchRelative =
|
|
1737
|
-
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");
|
|
1738
1679
|
await this.cleanupMarker(markerPath);
|
|
1739
1680
|
const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=normal"])).trim();
|
|
1740
1681
|
if (dirty) {
|
|
@@ -1920,520 +1861,12 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
1920
1861
|
// src/validation.ts
|
|
1921
1862
|
import * as acorn from "acorn";
|
|
1922
1863
|
|
|
1923
|
-
// src/roles.ts
|
|
1924
|
-
import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
1925
|
-
|
|
1926
|
-
// src/registry.ts
|
|
1927
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1928
|
-
import { basename as basename2, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
1929
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1930
|
-
import { Value } from "typebox/value";
|
|
1931
|
-
|
|
1932
1864
|
// src/settings.ts
|
|
1933
|
-
import { mkdirSync, readFileSync } from "node:fs";
|
|
1934
|
-
import { homedir as homedir4 } from "node:os";
|
|
1935
|
-
import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
|
|
1936
|
-
import { fileURLToPath } from "node:url";
|
|
1937
1865
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
1938
|
-
var ROLE_DIRECTORY = "pi-extensible-workflows";
|
|
1939
1866
|
var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
|
|
1940
|
-
function workflowSettingsPath(agentDir = getAgentDir()) {
|
|
1941
|
-
return join4(agentDir, ROLE_DIRECTORY, "settings.json");
|
|
1942
|
-
}
|
|
1943
|
-
function normalizedResourcePath(value, settingsPath) {
|
|
1944
|
-
if (value === "*" || value.startsWith("builtin:")) return value;
|
|
1945
|
-
let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir4(), value.slice(2)) : value;
|
|
1946
|
-
if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
|
|
1947
|
-
const resolved = resolve3(dirname2(settingsPath), expanded);
|
|
1948
|
-
if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
|
|
1949
|
-
if (resourcePatternHasMagic(expanded)) {
|
|
1950
|
-
const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
|
|
1951
|
-
const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
|
|
1952
|
-
const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
|
|
1953
|
-
const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
|
|
1954
|
-
const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
|
|
1955
|
-
return `${canonicalPath(prefix)}${suffix}`;
|
|
1956
|
-
}
|
|
1957
|
-
return canonicalPath(resolved);
|
|
1958
|
-
}
|
|
1959
|
-
function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
|
|
1960
|
-
if (value === void 0) return void 0;
|
|
1961
|
-
if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
|
|
1962
|
-
const normalized = [];
|
|
1963
|
-
for (const [index, entry] of value.entries()) {
|
|
1964
|
-
if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
|
|
1965
|
-
let selector = entry.trim();
|
|
1966
|
-
if (kind === "extensions" && normalizeExtensions) {
|
|
1967
|
-
const negated = selector.startsWith("!");
|
|
1968
|
-
const body = negated ? selector.slice(1) : selector;
|
|
1969
|
-
if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
|
|
1970
|
-
try {
|
|
1971
|
-
selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
|
|
1972
|
-
} catch (error) {
|
|
1973
|
-
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
try {
|
|
1977
|
-
validateResourcePattern(selector);
|
|
1978
|
-
} catch (error) {
|
|
1979
|
-
fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
|
|
1980
|
-
}
|
|
1981
|
-
normalized.push(selector);
|
|
1982
|
-
}
|
|
1983
|
-
return Object.freeze(normalized);
|
|
1984
|
-
}
|
|
1985
|
-
function validateWorktreePostCreateCommand(value, settingsPath) {
|
|
1986
|
-
if (value === void 0) return void 0;
|
|
1987
|
-
if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
|
|
1988
|
-
return Object.freeze(value.map((entry, index) => {
|
|
1989
|
-
if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
|
|
1990
|
-
return entry;
|
|
1991
|
-
}));
|
|
1992
|
-
}
|
|
1993
|
-
function selectorsFromSettings(settings) {
|
|
1994
|
-
return {
|
|
1995
|
-
...settings.skills === void 0 ? {} : { skills: settings.skills },
|
|
1996
|
-
...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
|
|
1997
|
-
...settings.tools === void 0 ? {} : { tools: settings.tools }
|
|
1998
|
-
};
|
|
1999
|
-
}
|
|
2000
|
-
function selectorSet(value) {
|
|
2001
|
-
return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
|
|
2002
|
-
}
|
|
2003
|
-
function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
|
|
2004
|
-
if (value === void 0) return void 0;
|
|
2005
|
-
const base = `${settingsPath}.extensionSettings`;
|
|
2006
|
-
if (!object(value)) fail(errorCode2, `${base} must be an object`);
|
|
2007
|
-
const normalized = {};
|
|
2008
|
-
for (const [namespace, raw] of Object.entries(value)) {
|
|
2009
|
-
if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
|
|
2010
|
-
if (namespace === "herdr") {
|
|
2011
|
-
if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
|
|
2012
|
-
if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
|
|
2013
|
-
if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
|
|
2014
|
-
normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
|
|
2015
|
-
continue;
|
|
2016
|
-
}
|
|
2017
|
-
if (namespace === "trajectory") {
|
|
2018
|
-
if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
|
|
2019
|
-
if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
|
|
2020
|
-
if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
|
|
2021
|
-
normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
|
|
2022
|
-
continue;
|
|
2023
|
-
}
|
|
2024
|
-
if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
|
|
2025
|
-
normalized[namespace] = structuredClone(raw);
|
|
2026
|
-
}
|
|
2027
|
-
return deepFreeze(normalized);
|
|
2028
|
-
}
|
|
2029
|
-
function positiveRetentionInteger(value) {
|
|
2030
|
-
return positiveInteger(value) && Number.isSafeInteger(value);
|
|
2031
|
-
}
|
|
2032
|
-
function validateRetention(value, settingsPath) {
|
|
2033
|
-
if (value === void 0) return void 0;
|
|
2034
|
-
const base = `${settingsPath}.retention`;
|
|
2035
|
-
if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
|
|
2036
|
-
const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
|
|
2037
|
-
if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
|
|
2038
|
-
if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
|
|
2039
|
-
if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
|
|
2040
|
-
return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
|
|
2041
|
-
}
|
|
2042
|
-
function parseSettings(path, partial) {
|
|
2043
|
-
let parsed;
|
|
2044
|
-
try {
|
|
2045
|
-
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
2046
|
-
} catch (error) {
|
|
2047
|
-
if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
|
|
2048
|
-
fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
|
|
2049
|
-
}
|
|
2050
|
-
if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
|
|
2051
|
-
const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget", "codemodeTools"]]);
|
|
2052
|
-
const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
|
|
2053
|
-
if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
|
|
2054
|
-
if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
|
|
2055
|
-
const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
|
|
2056
|
-
if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
|
|
2057
|
-
const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
|
|
2058
|
-
if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
|
|
2059
|
-
const codemodeTools = parsed.codemodeTools;
|
|
2060
|
-
if (codemodeTools !== void 0 && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none") fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
|
|
2061
|
-
const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
|
|
2062
|
-
const skills = validateSelectorList(parsed.skills, path, "skills");
|
|
2063
|
-
const tools = validateSelectorList(parsed.tools, path, "tools");
|
|
2064
|
-
const extensions = validateSelectorList(parsed.extensions, path, "extensions");
|
|
2065
|
-
const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
|
|
2066
|
-
const retention = validateRetention(parsed.retention, path);
|
|
2067
|
-
const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
|
|
2068
|
-
return Object.freeze({
|
|
2069
|
-
...concurrency === void 0 ? {} : { concurrency },
|
|
2070
|
-
...backgroundWidget === void 0 ? {} : { backgroundWidget },
|
|
2071
|
-
...codemodeTools === void 0 ? {} : { codemodeTools },
|
|
2072
|
-
...modelAliases === void 0 ? {} : { modelAliases },
|
|
2073
|
-
...skills === void 0 ? {} : { skills },
|
|
2074
|
-
...extensions === void 0 ? {} : { extensions },
|
|
2075
|
-
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2076
|
-
...tools === void 0 ? {} : { tools },
|
|
2077
|
-
...retention === void 0 ? {} : { retention },
|
|
2078
|
-
...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
|
|
2079
|
-
});
|
|
2080
|
-
}
|
|
2081
|
-
function loadSettings(path = workflowSettingsPath()) {
|
|
2082
|
-
return parseSettings(path, false);
|
|
2083
|
-
}
|
|
2084
|
-
function loadSettingsOverrides(path) {
|
|
2085
|
-
return parseSettings(path, true);
|
|
2086
|
-
}
|
|
2087
|
-
function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
|
|
2088
|
-
const projectSettingsPath = workflowProjectSettingsPath(cwd);
|
|
2089
|
-
const global = loadSettings(globalSettingsPath);
|
|
2090
|
-
const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
|
|
2091
|
-
const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
|
|
2092
|
-
const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
|
|
2093
|
-
const globalSelectors = selectorsFromSettings(global);
|
|
2094
|
-
const projectSelectors = selectorsFromSettings(project);
|
|
2095
|
-
const effectiveSelectors = selectorSet({
|
|
2096
|
-
skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
|
|
2097
|
-
extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
|
|
2098
|
-
...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
|
|
2099
|
-
});
|
|
2100
|
-
const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
|
|
2101
|
-
const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
|
|
2102
|
-
const sources = {
|
|
2103
|
-
concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
|
|
2104
|
-
modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
|
|
2105
|
-
skills: sourceFor("skills"),
|
|
2106
|
-
extensions: sourceFor("extensions"),
|
|
2107
|
-
tools: sourceFor("tools"),
|
|
2108
|
-
...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
|
|
2109
|
-
...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
|
|
2110
|
-
...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
|
|
2111
|
-
};
|
|
2112
|
-
const effective = Object.freeze({
|
|
2113
|
-
concurrency: project.concurrency ?? global.concurrency,
|
|
2114
|
-
backgroundWidget: global.backgroundWidget ?? true,
|
|
2115
|
-
...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
|
|
2116
|
-
...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
|
|
2117
|
-
...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
|
|
2118
|
-
...extensionSettings === void 0 ? {} : { extensionSettings },
|
|
2119
|
-
...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
|
|
2120
|
-
...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
|
|
2121
|
-
...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
|
|
2122
|
-
});
|
|
2123
|
-
return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
// src/package-identity.ts
|
|
2127
|
-
var CORE_PACKAGE_NAME = "@marcoscale98/pi-extensible-workflows";
|
|
2128
|
-
var CORE_PACKAGE_COMPATIBILITY_NAME = "pi-extensible-workflows";
|
|
2129
|
-
var CORE_PACKAGE_NAMES = [CORE_PACKAGE_NAME, CORE_PACKAGE_COMPATIBILITY_NAME];
|
|
2130
|
-
function isCorePackageName(value) {
|
|
2131
|
-
return typeof value === "string" && CORE_PACKAGE_NAMES.includes(value);
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
// src/registry.ts
|
|
2135
|
-
var RESERVED_GLOBALS = /* @__PURE__ */ new Set(["agent", "shell", "tools", "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"]);
|
|
2136
|
-
var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
2137
|
-
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2138
|
-
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
2139
|
-
function normalizeRoleDirectory(value) {
|
|
2140
|
-
try {
|
|
2141
|
-
if (value instanceof URL) {
|
|
2142
|
-
if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
2143
|
-
return fileURLToPath2(value);
|
|
2144
|
-
}
|
|
2145
|
-
if (typeof value === "string") {
|
|
2146
|
-
if (value.startsWith("file:")) return fileURLToPath2(value);
|
|
2147
|
-
if (isAbsolute(value)) return value;
|
|
2148
|
-
}
|
|
2149
|
-
} catch (error) {
|
|
2150
|
-
fail("INVALID_METADATA", `Invalid workflow role directory: ${errorText(error)}`);
|
|
2151
|
-
}
|
|
2152
|
-
fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
|
|
2153
|
-
}
|
|
2154
|
-
function isBuiltinRoleDirectory(path) {
|
|
2155
|
-
const canonical = canonicalPath(path);
|
|
2156
|
-
const starterDirectory = dirname3(canonical);
|
|
2157
|
-
if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
|
|
2158
|
-
const distributionDirectory = dirname3(starterDirectory);
|
|
2159
|
-
const packageRoot = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
|
|
2160
|
-
try {
|
|
2161
|
-
const metadata = JSON.parse(readFileSync2(join5(packageRoot, "package.json"), "utf8"));
|
|
2162
|
-
return object(metadata) && isCorePackageName(metadata.name);
|
|
2163
|
-
} catch {
|
|
2164
|
-
return false;
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
function catalogSchema(schema, at) {
|
|
2168
|
-
validateSchema(schema, at);
|
|
2169
|
-
return structuredClone(schema);
|
|
2170
|
-
}
|
|
2171
|
-
var WorkflowRegistry = class {
|
|
2172
|
-
#extensions = /* @__PURE__ */ new Set();
|
|
2173
|
-
#functionSources = /* @__PURE__ */ new Map();
|
|
2174
|
-
#globals = /* @__PURE__ */ new Map();
|
|
2175
|
-
#hooks = /* @__PURE__ */ new Map();
|
|
2176
|
-
#agentAttemptActions = /* @__PURE__ */ new Map();
|
|
2177
|
-
#roleDirectories = /* @__PURE__ */ new Map();
|
|
2178
|
-
#modelAliases = /* @__PURE__ */ new Map();
|
|
2179
|
-
#settingsValidators = /* @__PURE__ */ new Set();
|
|
2180
|
-
#frozen = false;
|
|
2181
|
-
#subagentStatusObserver;
|
|
2182
|
-
get frozen() {
|
|
2183
|
-
return this.#frozen;
|
|
2184
|
-
}
|
|
2185
|
-
freeze() {
|
|
2186
|
-
this.#frozen = true;
|
|
2187
|
-
}
|
|
2188
|
-
setSubagentStatusObserver(observer) {
|
|
2189
|
-
this.#subagentStatusObserver = observer;
|
|
2190
|
-
}
|
|
2191
|
-
observeSubagentStatus(status, request) {
|
|
2192
|
-
this.#subagentStatusObserver?.(status, request);
|
|
2193
|
-
}
|
|
2194
|
-
register(extension2) {
|
|
2195
|
-
if (this.#frozen) fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
|
|
2196
|
-
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");
|
|
2197
|
-
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");
|
|
2198
|
-
const functions = extension2.functions ?? {};
|
|
2199
|
-
const modelAliases = extension2.modelAliases ?? {};
|
|
2200
|
-
const agentSetupHooks = extension2.agentSetupHooks ?? {};
|
|
2201
|
-
const agentAttemptActions = extension2.agentAttemptActions ?? {};
|
|
2202
|
-
const source = extension2.source;
|
|
2203
|
-
const dependencyValues = extension2.dependencies;
|
|
2204
|
-
const dependencies = dependencyValues ?? [];
|
|
2205
|
-
const validateSettings = extension2.validateSettings;
|
|
2206
|
-
if (validateSettings !== void 0 && typeof validateSettings !== "function") fail("INVALID_METADATA", "Workflow extension validateSettings must be a function");
|
|
2207
|
-
if (source !== void 0 && (typeof source !== "string" || !source.trim())) fail("INVALID_METADATA", "Workflow extension source must be a non-empty module URL");
|
|
2208
|
-
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");
|
|
2209
|
-
const roleDirectoryValues = extension2.roleDirectories === void 0 ? [] : extension2.roleDirectories;
|
|
2210
|
-
if (!Array.isArray(roleDirectoryValues)) fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
|
|
2211
|
-
const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
|
|
2212
|
-
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");
|
|
2213
|
-
const names = Object.keys(functions);
|
|
2214
|
-
if (new Set(names).size !== names.length) fail("GLOBAL_COLLISION", "Global name collision inside extension");
|
|
2215
|
-
for (const name of names) {
|
|
2216
|
-
if (!IDENTIFIER.test(name) || name.startsWith("__pi_extensible_workflows_")) fail("INVALID_METADATA", `Invalid global name: ${name}`);
|
|
2217
|
-
if (RESERVED_GLOBALS.has(name)) fail("GLOBAL_COLLISION", `Global name is reserved: ${name}`);
|
|
2218
|
-
if (this.#globals.has(name)) fail("GLOBAL_COLLISION", `Global name is already registered: ${name}`);
|
|
2219
|
-
}
|
|
2220
|
-
for (const [name, fn] of Object.entries(functions)) {
|
|
2221
|
-
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}`);
|
|
2222
|
-
validateSchema(fn.input, `${name} input`);
|
|
2223
|
-
validateSchema(fn.output, `${name} output`);
|
|
2224
|
-
if (fn.input.type !== "object") fail("INVALID_SCHEMA", `${name} input must describe one object`);
|
|
2225
|
-
}
|
|
2226
|
-
for (const [name, alias] of Object.entries(modelAliases)) {
|
|
2227
|
-
if (!MODEL_ALIAS_NAME.test(name)) fail("INVALID_METADATA", `Invalid model alias name: ${name}`);
|
|
2228
|
-
if (!object(alias) || Object.keys(alias).some((key) => key !== "resolve") || typeof alias.resolve !== "function") fail("INVALID_METADATA", `Invalid model alias resolver: ${name}`);
|
|
2229
|
-
if (this.#modelAliases.has(name)) fail("DUPLICATE_NAME", `Model alias already registered: ${name}`);
|
|
2230
|
-
}
|
|
2231
|
-
for (const [name, hook] of Object.entries(agentSetupHooks)) {
|
|
2232
|
-
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}`);
|
|
2233
|
-
if (this.#hooks.has(name)) fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
|
|
2234
|
-
}
|
|
2235
|
-
for (const [name, action] of Object.entries(agentAttemptActions)) {
|
|
2236
|
-
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}`);
|
|
2237
|
-
if (action.visibleStandalone !== void 0 !== (action.runStandalone !== void 0)) fail("INVALID_METADATA", `Standalone agent attempt actions require visibleStandalone and runStandalone: ${name}`);
|
|
2238
|
-
if (this.#agentAttemptActions.has(name)) fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
|
|
2239
|
-
}
|
|
2240
|
-
const stored = deepFreeze({ ...extension2, functions, modelAliases, agentSetupHooks, agentAttemptActions, ...roleDirectories.length ? { roleDirectories } : {} });
|
|
2241
|
-
if (source !== void 0) for (const name of names) this.#functionSources.set(name, Object.freeze({ module: source, export: "default", dependencies: Object.freeze([...dependencies]) }));
|
|
2242
|
-
this.#extensions.add(stored);
|
|
2243
|
-
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 } : {} }));
|
|
2244
|
-
for (const name of names) this.#globals.set(name, name);
|
|
2245
|
-
for (const [name, alias] of Object.entries(modelAliases)) this.#modelAliases.set(name, { name, version: extension2.version, headline: extension2.headline, resolve: alias.resolve });
|
|
2246
|
-
for (const [name, hook] of Object.entries(agentSetupHooks)) this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
|
|
2247
|
-
for (const [name, action] of Object.entries(agentAttemptActions)) this.#agentAttemptActions.set(name, action);
|
|
2248
|
-
if (validateSettings !== void 0) this.#settingsValidators.add({ headline: extension2.headline, validate: validateSettings });
|
|
2249
|
-
}
|
|
2250
|
-
function(name) {
|
|
2251
|
-
if (!IDENTIFIER.test(name)) fail("MISSING_WORKFLOW", `Registered functions require an unqualified name: ${name}`);
|
|
2252
|
-
const fn = [...this.#extensions].find((extension2) => extension2.functions?.[name])?.functions?.[name];
|
|
2253
|
-
if (!fn) fail("MISSING_WORKFLOW", `Registered function is unavailable: ${name}; the separate registered-workflow format was removed`);
|
|
2254
|
-
return fn;
|
|
2255
|
-
}
|
|
2256
|
-
functions() {
|
|
2257
|
-
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension2) => Object.entries(extension2.functions ?? {}))));
|
|
2258
|
-
}
|
|
2259
|
-
functionSources() {
|
|
2260
|
-
return Object.freeze(Object.fromEntries(this.#functionSources));
|
|
2261
|
-
}
|
|
2262
|
-
catalog(context) {
|
|
2263
|
-
const functions = [];
|
|
2264
|
-
for (const extension2 of this.#extensions) {
|
|
2265
|
-
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`) });
|
|
2266
|
-
}
|
|
2267
|
-
let aliases;
|
|
2268
|
-
let settings;
|
|
2269
|
-
let source = "global settings";
|
|
2270
|
-
try {
|
|
2271
|
-
const resolved = context ? resolveWorkflowSettings(context.cwd, context.projectTrusted, context.globalSettingsPath) : void 0;
|
|
2272
|
-
aliases = resolved?.effective.modelAliases ?? loadSettings().modelAliases;
|
|
2273
|
-
if (resolved) {
|
|
2274
|
-
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";
|
|
2275
|
-
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 };
|
|
2276
|
-
}
|
|
2277
|
-
} catch {
|
|
2278
|
-
aliases = void 0;
|
|
2279
|
-
}
|
|
2280
|
-
const staticEntries = Object.keys(aliases ?? {}).map((name) => ({ name, kind: "static", provenance: source }));
|
|
2281
|
-
const dynamicEntries = [...this.#modelAliases.values()].map(({ name, version, headline }) => ({ name, kind: "dynamic", provenance: `extension: ${headline}`, version, headline }));
|
|
2282
|
-
const modelAliasEntries = [...staticEntries, ...dynamicEntries].sort((left, right) => left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind) || left.provenance.localeCompare(right.provenance));
|
|
2283
|
-
const sort = (left, right) => left.name.localeCompare(right.name);
|
|
2284
|
-
const catalog = { functions: functions.sort(sort), ...modelAliasEntries.length ? { modelAliasEntries } : {} };
|
|
2285
|
-
if (aliases && Object.keys(aliases).length) Object.defineProperty(catalog, "modelAliases", { value: Object.freeze(structuredClone(aliases)), enumerable: false });
|
|
2286
|
-
if (settings) {
|
|
2287
|
-
const publicSettings = { ...settings, modelAliases: Object.keys(aliases ?? {}).length ? {} : settings.modelAliases };
|
|
2288
|
-
Object.defineProperty(catalog, "settings", { value: deepFreeze(publicSettings), enumerable: true });
|
|
2289
|
-
}
|
|
2290
|
-
return deepFreeze(catalog);
|
|
2291
|
-
}
|
|
2292
|
-
catalogIndex(context) {
|
|
2293
|
-
const catalog = this.catalog(context);
|
|
2294
|
-
let modelAliasEntries;
|
|
2295
|
-
if (catalog.modelAliasEntries !== void 0) {
|
|
2296
|
-
const winningEntries = /* @__PURE__ */ new Map();
|
|
2297
|
-
for (const entry of catalog.modelAliasEntries) {
|
|
2298
|
-
if (!winningEntries.has(entry.name) || entry.kind === "static") winningEntries.set(entry.name, entry);
|
|
2299
|
-
}
|
|
2300
|
-
modelAliasEntries = [...winningEntries.values()];
|
|
2301
|
-
}
|
|
2302
|
-
const index = {
|
|
2303
|
-
functions: catalog.functions.map(({ name, description, input }) => ({ name, description, input: structuredClone(input) })),
|
|
2304
|
-
...modelAliasEntries ? { modelAliasEntries: structuredClone(modelAliasEntries) } : {}
|
|
2305
|
-
};
|
|
2306
|
-
if (catalog.modelAliases) Object.defineProperty(index, "modelAliases", { value: Object.freeze(structuredClone(catalog.modelAliases)), enumerable: false });
|
|
2307
|
-
if (catalog.settings) Object.defineProperty(index, "settings", { value: deepFreeze(structuredClone(catalog.settings)), enumerable: true });
|
|
2308
|
-
return deepFreeze(index);
|
|
2309
|
-
}
|
|
2310
|
-
catalogDetail(name, context) {
|
|
2311
|
-
const catalog = this.catalog(context);
|
|
2312
|
-
const entry = catalog.functions.find((candidate) => candidate.name === name);
|
|
2313
|
-
if (entry) return entry;
|
|
2314
|
-
const alias = catalog.modelAliasEntries?.find((candidate) => candidate.name === name && candidate.kind === "static") ?? catalog.modelAliasEntries?.find((candidate) => candidate.name === name);
|
|
2315
|
-
if (alias) return alias;
|
|
2316
|
-
return deepFreeze({ error: { code: "NOT_FOUND", name, message: `No registered workflow function is available: ${name}` } });
|
|
2317
|
-
}
|
|
2318
|
-
globals() {
|
|
2319
|
-
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension2) => Object.keys(extension2.functions ?? {}).map((name) => [name, { name }]))));
|
|
2320
|
-
}
|
|
2321
|
-
validateExtensionSettings(settings, context) {
|
|
2322
|
-
const effectiveSettings = settings ?? Object.freeze({});
|
|
2323
|
-
if (!jsonValue(effectiveSettings) || !object(effectiveSettings)) fail("INVALID_SETTINGS", "Workflow extension settings must be a JSON object");
|
|
2324
|
-
const unsafeNamespace = Object.keys(effectiveSettings).find((namespace) => !validWorkflowExtensionNamespace(namespace));
|
|
2325
|
-
if (unsafeNamespace !== void 0) fail("INVALID_SETTINGS", `Workflow extension settings contains an invalid namespace: ${unsafeNamespace}`);
|
|
2326
|
-
const frozenSettings = deepFreeze(structuredClone(effectiveSettings));
|
|
2327
|
-
for (const { headline, validate } of this.#settingsValidators) {
|
|
2328
|
-
try {
|
|
2329
|
-
validate(frozenSettings, Object.freeze({ ...context }));
|
|
2330
|
-
} catch (error) {
|
|
2331
|
-
const location = context.source === "role" && context.role ? `role ${context.role}` : context.source;
|
|
2332
|
-
fail("INVALID_SETTINGS", `Invalid extension settings (${headline}; ${location}): ${errorText(error)}`);
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
async invokeFunction(name, input, context, path, journal) {
|
|
2337
|
-
const fn = this.function(name);
|
|
2338
|
-
if (!object(input) || !jsonValue(input) || !Value.Check(fn.input, input)) fail("RESULT_INVALID", `Invalid input for ${name}`);
|
|
2339
|
-
const replayed = journal.get(path);
|
|
2340
|
-
if (replayed !== void 0) {
|
|
2341
|
-
if (!jsonValue(replayed) || !Value.Check(fn.output, replayed)) fail("RESULT_INVALID", `Invalid replay for ${name}`);
|
|
2342
|
-
return structuredClone(replayed);
|
|
2343
|
-
}
|
|
2344
|
-
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 }));
|
|
2345
|
-
if (!jsonValue(result) || !Value.Check(fn.output, result)) fail("RESULT_INVALID", `Invalid output from ${name}`);
|
|
2346
|
-
const stored = structuredClone(result);
|
|
2347
|
-
journal.put(path, stored);
|
|
2348
|
-
return structuredClone(stored);
|
|
2349
|
-
}
|
|
2350
|
-
agentSetupHooks() {
|
|
2351
|
-
return [...this.#hooks.values()].sort(byPriorityThenName);
|
|
2352
|
-
}
|
|
2353
|
-
agentAttemptActions() {
|
|
2354
|
-
return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries()));
|
|
2355
|
-
}
|
|
2356
|
-
roleDirectories() {
|
|
2357
|
-
return [...this.#roleDirectories.keys()];
|
|
2358
|
-
}
|
|
2359
|
-
roleDirectoryRegistrations() {
|
|
2360
|
-
return [...this.#roleDirectories.values()];
|
|
2361
|
-
}
|
|
2362
|
-
modelAliases() {
|
|
2363
|
-
return [...this.#modelAliases.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
2364
|
-
}
|
|
2365
|
-
async resolveModelAliases(context, shadowed = /* @__PURE__ */ new Set()) {
|
|
2366
|
-
const resolved = {};
|
|
2367
|
-
const isAborted = () => context.signal.aborted;
|
|
2368
|
-
for (const alias of this.modelAliases()) {
|
|
2369
|
-
if (shadowed.has(alias.name)) continue;
|
|
2370
|
-
if (isAborted()) fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2371
|
-
let target;
|
|
2372
|
-
try {
|
|
2373
|
-
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 }));
|
|
2374
|
-
} catch (error) {
|
|
2375
|
-
if (isAborted() || error instanceof Error && error.name === "AbortError" || errorCode(error) === "CANCELLED") fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2376
|
-
fail("CONFIG_ERROR", `Model alias resolver failed for ${alias.name} (${alias.headline}): ${errorText(error)}`);
|
|
2377
|
-
}
|
|
2378
|
-
if (isAborted()) fail("CANCELLED", `Model alias resolver cancelled: ${alias.name} (${alias.headline})`);
|
|
2379
|
-
if (typeof target !== "string" || !target.trim()) fail("CONFIG_ERROR", `Model alias resolver returned an invalid target for ${alias.name} (${alias.headline})`);
|
|
2380
|
-
resolved[alias.name] = target.trim();
|
|
2381
|
-
}
|
|
2382
|
-
return Object.freeze(resolved);
|
|
2383
|
-
}
|
|
2384
|
-
};
|
|
2385
|
-
var WORKFLOW_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-registry");
|
|
2386
|
-
var globalRegistry = globalThis;
|
|
2387
|
-
function createWorkflowRegistryApi(registry) {
|
|
2388
|
-
return {
|
|
2389
|
-
get frozen() {
|
|
2390
|
-
return registry.frozen;
|
|
2391
|
-
},
|
|
2392
|
-
freeze: () => {
|
|
2393
|
-
registry.freeze();
|
|
2394
|
-
},
|
|
2395
|
-
setSubagentStatusObserver: (observer) => {
|
|
2396
|
-
registry.setSubagentStatusObserver(observer);
|
|
2397
|
-
},
|
|
2398
|
-
observeSubagentStatus: (status, request) => {
|
|
2399
|
-
registry.observeSubagentStatus(status, request);
|
|
2400
|
-
},
|
|
2401
|
-
register: (extension2) => {
|
|
2402
|
-
registry.register(extension2);
|
|
2403
|
-
},
|
|
2404
|
-
function: (name) => registry.function(name),
|
|
2405
|
-
functions: () => registry.functions(),
|
|
2406
|
-
functionSources: () => registry.functionSources(),
|
|
2407
|
-
catalog: (context) => registry.catalog(context),
|
|
2408
|
-
catalogIndex: (context) => registry.catalogIndex(context),
|
|
2409
|
-
catalogDetail: (name, context) => registry.catalogDetail(name, context),
|
|
2410
|
-
globals: () => registry.globals(),
|
|
2411
|
-
invokeFunction: (...args) => registry.invokeFunction(...args),
|
|
2412
|
-
validateExtensionSettings: (...args) => {
|
|
2413
|
-
registry.validateExtensionSettings(...args);
|
|
2414
|
-
},
|
|
2415
|
-
modelAliases: () => registry.modelAliases(),
|
|
2416
|
-
resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
|
|
2417
|
-
roleDirectories: () => registry.roleDirectories(),
|
|
2418
|
-
roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
|
|
2419
|
-
agentSetupHooks: () => registry.agentSetupHooks(),
|
|
2420
|
-
agentAttemptActions: () => registry.agentAttemptActions()
|
|
2421
|
-
};
|
|
2422
|
-
}
|
|
2423
|
-
function workflowRegistryHost() {
|
|
2424
|
-
return globalRegistry[WORKFLOW_REGISTRY_KEY] ??= { api: createWorkflowRegistryApi(new WorkflowRegistry()), activeHosts: 0 };
|
|
2425
|
-
}
|
|
2426
|
-
function resetWorkflowRegistry() {
|
|
2427
|
-
workflowRegistryHost().api = createWorkflowRegistryApi(new WorkflowRegistry());
|
|
2428
|
-
}
|
|
2429
|
-
function beginWorkflowExtensionLoading() {
|
|
2430
|
-
const host = workflowRegistryHost();
|
|
2431
|
-
if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
|
|
2432
|
-
}
|
|
2433
|
-
beginWorkflowExtensionLoading();
|
|
2434
1867
|
|
|
2435
1868
|
// subagents/src/contracts.ts
|
|
2436
|
-
import { Value
|
|
1869
|
+
import { Value } from "typebox/value";
|
|
2437
1870
|
var SUBAGENT_MAX_RETRIES = 255;
|
|
2438
1871
|
var SUBAGENT_SYSTEM_PROMPT_LIMIT = 64 * 1024;
|
|
2439
1872
|
var SUBAGENTS_MODE = Type.Union([
|
|
@@ -2446,15 +1879,15 @@ var SUBAGENTS_RUN_PARAMETERS = Type.Object({
|
|
|
2446
1879
|
label: Type.Optional(Type.String({ description: "Optional display label for the subagent" })),
|
|
2447
1880
|
model: Type.Optional(Type.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
|
|
2448
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" })),
|
|
2449
1883
|
skills: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
|
|
2450
1884
|
extensions: Type.Optional(Type.Array(Type.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
|
|
2451
1885
|
contextFiles: Type.Optional(Type.Array(Type.String(), { description: "Optional context-file scopes: global, project, cwd" })),
|
|
2452
|
-
role: Type.Optional(Type.String({ description: "Workflow role name" })),
|
|
2453
1886
|
worktree: Type.Optional(Type.String({ description: "Optional named worktree; requires a clean working tree" })),
|
|
2454
1887
|
outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional JSON schema for the result" })),
|
|
2455
1888
|
retries: Type.Optional(Type.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
|
|
2456
1889
|
timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type.Null()]))
|
|
2457
|
-
}, { additionalProperties:
|
|
1890
|
+
}, { additionalProperties: true });
|
|
2458
1891
|
var SUBAGENTS_INSPECT_PARAMETERS = Type.Object({
|
|
2459
1892
|
id: Type.Optional(Type.String({ description: "Subagent ID; omit to list ordered run summaries" })),
|
|
2460
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" }))
|
|
@@ -2644,20 +2077,20 @@ function canonicalExtensionSelector(selector, base) {
|
|
|
2644
2077
|
const negated = selector.startsWith("!");
|
|
2645
2078
|
const body = negated ? selector.slice(1) : selector;
|
|
2646
2079
|
if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
|
|
2647
|
-
const resolved =
|
|
2080
|
+
const resolved = resolve3(base, body);
|
|
2648
2081
|
if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
|
|
2649
2082
|
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
2650
2083
|
}
|
|
2651
2084
|
function skillNameFromPath(path) {
|
|
2652
|
-
const file =
|
|
2653
|
-
return file.toLowerCase() === "skill.md" ?
|
|
2085
|
+
const file = basename2(path);
|
|
2086
|
+
return file.toLowerCase() === "skill.md" ? basename2(dirname2(path)) : file;
|
|
2654
2087
|
}
|
|
2655
2088
|
var discoveredResourcesByCwd = /* @__PURE__ */ new Map();
|
|
2656
2089
|
async function discoveredResources(cwd) {
|
|
2657
2090
|
const cached = discoveredResourcesByCwd.get(cwd);
|
|
2658
2091
|
if (cached) return cached;
|
|
2659
2092
|
const pending = (async () => {
|
|
2660
|
-
const agentDir =
|
|
2093
|
+
const agentDir = getAgentDir2();
|
|
2661
2094
|
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
|
|
2662
2095
|
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
|
2663
2096
|
const resolved = await packageManager.resolve();
|
|
@@ -2679,8 +2112,8 @@ async function resolveResourceInspection(inspection, cwd, selectorCwd = cwd) {
|
|
|
2679
2112
|
return inspection;
|
|
2680
2113
|
}
|
|
2681
2114
|
const sources = inspection.selectorSources ?? { global: inspection.selectors, project: {} };
|
|
2682
|
-
const skills = inspection.skills.length ? inspection.skills : selectResourcesByLayers([sources.global.skills, sources.project.skills, sources.
|
|
2683
|
-
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);
|
|
2684
2117
|
return skills === inspection.skills && extensions === inspection.extensions ? inspection : { ...inspection, skills, extensions };
|
|
2685
2118
|
}
|
|
2686
2119
|
async function withResolvedResources(run, cwd) {
|
|
@@ -2762,7 +2195,7 @@ async function loadTrajectoryRun(store, includeTranscripts = true) {
|
|
|
2762
2195
|
const run = applyTrajectoryAgentOutputs(prepared, operations);
|
|
2763
2196
|
return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...summary?.createdAt === void 0 ? {} : { createdAt: summary.createdAt }, transcripts: includeTranscripts ? await runTranscripts(run) : {} };
|
|
2764
2197
|
}
|
|
2765
|
-
async function loadTrajectoryRuns(cwd, sessionId, home =
|
|
2198
|
+
async function loadTrajectoryRuns(cwd, sessionId, home = homedir4(), overlay) {
|
|
2766
2199
|
const loaded = [];
|
|
2767
2200
|
for (const runId of await listRunIds(cwd, sessionId, home, false)) {
|
|
2768
2201
|
try {
|
|
@@ -2777,15 +2210,15 @@ async function loadTrajectoryRuns(cwd, sessionId, home = homedir5(), overlay) {
|
|
|
2777
2210
|
// trajectory/src/export.ts
|
|
2778
2211
|
import { execFile as execFile3 } from "node:child_process";
|
|
2779
2212
|
import { mkdtemp, readFile as readFile5, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
2780
|
-
import { homedir as
|
|
2781
|
-
import { join as
|
|
2213
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
2214
|
+
import { join as join5 } from "node:path";
|
|
2782
2215
|
var DEFAULT_SHARE_VIEWER_URL = "https://vekexasia.github.io/pi-extensible-workflows/run.html";
|
|
2783
2216
|
function inlineAsset(html, tag, replacement) {
|
|
2784
2217
|
if (!html.includes(tag)) throw new Error(`Trajectory UI is missing the expected ${tag} reference`);
|
|
2785
2218
|
return html.replace(tag, () => replacement);
|
|
2786
2219
|
}
|
|
2787
2220
|
async function exportTrajectoryRunHtml(options) {
|
|
2788
|
-
const home = options.home ??
|
|
2221
|
+
const home = options.home ?? homedir5();
|
|
2789
2222
|
const runs = await loadTrajectoryRuns(options.cwd, options.sessionId, home);
|
|
2790
2223
|
const run = runs.find((candidate) => candidate.run.id === options.runId);
|
|
2791
2224
|
if (!run) throw new Error(`Workflow run ${options.runId} was not found for session ${options.sessionId}`);
|
|
@@ -2813,22 +2246,22 @@ async function exportTrajectoryRunHtml(options) {
|
|
|
2813
2246
|
return output;
|
|
2814
2247
|
}
|
|
2815
2248
|
function runGh(ghPath, args) {
|
|
2816
|
-
return new Promise((
|
|
2249
|
+
return new Promise((resolve4, reject) => {
|
|
2817
2250
|
execFile3(ghPath, [...args], { encoding: "utf8", maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
2818
2251
|
if (error) {
|
|
2819
2252
|
const notFound = error.code === "ENOENT";
|
|
2820
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));
|
|
2821
2254
|
return;
|
|
2822
2255
|
}
|
|
2823
|
-
|
|
2256
|
+
resolve4(stdout);
|
|
2824
2257
|
});
|
|
2825
2258
|
});
|
|
2826
2259
|
}
|
|
2827
2260
|
async function shareTrajectoryRun(options) {
|
|
2828
2261
|
const html = await exportTrajectoryRunHtml(options);
|
|
2829
|
-
const directory = await mkdtemp(
|
|
2262
|
+
const directory = await mkdtemp(join5(tmpdir(), "piewf-share-"));
|
|
2830
2263
|
try {
|
|
2831
|
-
const file =
|
|
2264
|
+
const file = join5(directory, "trajectory.html");
|
|
2832
2265
|
await writeFile3(file, html);
|
|
2833
2266
|
const stdout = await runGh(options.ghPath ?? "gh", ["gist", "create", "--public=false", file]);
|
|
2834
2267
|
const gistUrl = stdout.trim().split("\n").pop() ?? "";
|
|
@@ -2841,21 +2274,158 @@ async function shareTrajectoryRun(options) {
|
|
|
2841
2274
|
}
|
|
2842
2275
|
}
|
|
2843
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
|
+
|
|
2844
2414
|
// trajectory/src/index.ts
|
|
2845
2415
|
var DEFAULT_TRAJECTORY_PORT = 7432;
|
|
2846
2416
|
var TRAJECTORY_IDLE_EXIT_MS = 5 * 60 * 1e3;
|
|
2847
2417
|
var TRAJECTORY_LOCK_NAME = "trajectory.lock";
|
|
2848
2418
|
function trajectoryLockPath(agentDir) {
|
|
2849
|
-
return
|
|
2419
|
+
return join7(agentDir, "pi-extensible-workflows", TRAJECTORY_LOCK_NAME);
|
|
2850
2420
|
}
|
|
2851
|
-
function trajectoryServerPath(moduleDirectory =
|
|
2852
|
-
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")];
|
|
2853
2423
|
const path = candidates.find((candidate) => existsSync(candidate));
|
|
2854
2424
|
if (!path) throw new Error("Trajectory server implementation is unavailable");
|
|
2855
2425
|
return path;
|
|
2856
2426
|
}
|
|
2857
2427
|
async function trajectoryFingerprint(serverPath) {
|
|
2858
|
-
const [serverBytes, htmlBytes] = await Promise.all([
|
|
2428
|
+
const [serverBytes, htmlBytes] = await Promise.all([readFile7(serverPath), readFile7(join7(dirname3(serverPath), "assets/index.html"))]);
|
|
2859
2429
|
return `${createHash3("sha256").update(serverBytes).digest("hex")}:${createHash3("sha256").update(htmlBytes).digest("hex")}`;
|
|
2860
2430
|
}
|
|
2861
2431
|
function publisherId(cwd, sessionId) {
|
|
@@ -2866,7 +2436,7 @@ function trajectoryPort(value) {
|
|
|
2866
2436
|
return positiveInteger(value) && value <= 65535 ? value : DEFAULT_TRAJECTORY_PORT;
|
|
2867
2437
|
}
|
|
2868
2438
|
function delay(ms) {
|
|
2869
|
-
return new Promise((
|
|
2439
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2870
2440
|
}
|
|
2871
2441
|
async function serverHealth(port) {
|
|
2872
2442
|
try {
|
|
@@ -2889,107 +2459,133 @@ function signalProcess(pid, signal) {
|
|
|
2889
2459
|
if (!isNodeError(error, "ESRCH")) throw error;
|
|
2890
2460
|
}
|
|
2891
2461
|
}
|
|
2892
|
-
async function stopStaleServer(
|
|
2893
|
-
|
|
2894
|
-
if (!
|
|
2895
|
-
signalProcess(
|
|
2462
|
+
async function stopStaleServer(pid, port) {
|
|
2463
|
+
const server = identityOf(pid);
|
|
2464
|
+
if (!processLive(server)) return;
|
|
2465
|
+
signalProcess(pid, "SIGTERM");
|
|
2896
2466
|
const deadline = Date.now() + 3e3;
|
|
2897
2467
|
while (Date.now() < deadline) {
|
|
2898
|
-
if (!await serverHealthy(
|
|
2468
|
+
if (!await serverHealthy(port)) break;
|
|
2899
2469
|
const remaining = deadline - Date.now();
|
|
2900
2470
|
if (remaining <= 0) break;
|
|
2901
2471
|
await delay(Math.min(50, remaining));
|
|
2902
2472
|
}
|
|
2903
|
-
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`);
|
|
2904
2476
|
}
|
|
2905
2477
|
async function readLock(path) {
|
|
2478
|
+
let text;
|
|
2906
2479
|
try {
|
|
2907
|
-
|
|
2908
|
-
if (!object(parsed) || !positiveInteger(parsed.pid) || !positiveInteger(parsed.port) || parsed.port > 65535) return void 0;
|
|
2909
|
-
const fingerprint = typeof parsed.fingerprint === "string" ? parsed.fingerprint : void 0;
|
|
2910
|
-
const startedAt = positiveInteger(parsed.startedAt) ? parsed.startedAt : void 0;
|
|
2911
|
-
return { pid: parsed.pid, port: parsed.port, ...fingerprint === void 0 ? {} : { fingerprint }, ...startedAt === void 0 ? {} : { startedAt } };
|
|
2480
|
+
text = await readFile7(path, "utf8");
|
|
2912
2481
|
} catch (error) {
|
|
2913
2482
|
if (isNodeError(error, "ENOENT")) return void 0;
|
|
2483
|
+
throw error;
|
|
2484
|
+
}
|
|
2485
|
+
let parsed;
|
|
2486
|
+
try {
|
|
2487
|
+
parsed = JSON.parse(text);
|
|
2488
|
+
} catch {
|
|
2914
2489
|
return void 0;
|
|
2915
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
|
+
}
|
|
2916
2506
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2507
|
+
function isTrajectoryServer(health) {
|
|
2508
|
+
return health?.pid !== void 0 && health.fingerprint !== void 0;
|
|
2509
|
+
}
|
|
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) {
|
|
2920
2515
|
const health = await serverHealth(port);
|
|
2921
|
-
if (health
|
|
2922
|
-
foreign = health !== void 0;
|
|
2516
|
+
if (health !== void 0 && accept(health)) return health;
|
|
2923
2517
|
await delay(50);
|
|
2924
2518
|
}
|
|
2925
|
-
|
|
2519
|
+
return void 0;
|
|
2926
2520
|
}
|
|
2927
|
-
async function
|
|
2928
|
-
const
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
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;
|
|
2934
2552
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
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`);
|
|
2943
2565
|
}
|
|
2566
|
+
await rm6(lockPath, { force: true });
|
|
2944
2567
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
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);
|
|
2947
2578
|
}
|
|
2948
2579
|
async function ensureTrajectoryServer(agentDir, configuredPort) {
|
|
2949
2580
|
const lockPath = trajectoryLockPath(agentDir);
|
|
2950
2581
|
const serverPath = trajectoryServerPath();
|
|
2951
2582
|
const fingerprint = await trajectoryFingerprint(serverPath);
|
|
2952
|
-
await
|
|
2953
|
-
const
|
|
2954
|
-
if (existing) {
|
|
2955
|
-
const reused = await resolveExistingServer(lockPath, existing, fingerprint);
|
|
2956
|
-
if (reused) return reused;
|
|
2957
|
-
}
|
|
2958
|
-
let lockHandle;
|
|
2583
|
+
await mkdir4(dirname3(lockPath), { recursive: true, mode: 448 });
|
|
2584
|
+
const startupToken = await acquireStartupMutex(lockPath);
|
|
2959
2585
|
try {
|
|
2960
|
-
|
|
2961
|
-
await lockHandle.writeFile(`${JSON.stringify({ pid: process.pid, port: configuredPort, fingerprint, startedAt: Date.now() })}
|
|
2962
|
-
`, "utf8");
|
|
2963
|
-
} catch (error) {
|
|
2964
|
-
if (isNodeError(error, "EEXIST")) {
|
|
2965
|
-
const raced = await readLock(lockPath);
|
|
2966
|
-
if (raced) {
|
|
2967
|
-
const reused = await resolveExistingServer(lockPath, raced, fingerprint);
|
|
2968
|
-
if (reused) return reused;
|
|
2969
|
-
}
|
|
2970
|
-
await rm5(lockPath, { force: true });
|
|
2971
|
-
lockHandle = await open3(lockPath, "wx", 384);
|
|
2972
|
-
await lockHandle.writeFile(`${JSON.stringify({ pid: process.pid, port: configuredPort, fingerprint, startedAt: Date.now() })}
|
|
2973
|
-
`, "utf8");
|
|
2974
|
-
} else {
|
|
2975
|
-
throw error;
|
|
2976
|
-
}
|
|
2586
|
+
return await startOrReuseServer(lockPath, serverPath, fingerprint, configuredPort, startupToken);
|
|
2977
2587
|
} finally {
|
|
2978
|
-
await
|
|
2979
|
-
}
|
|
2980
|
-
const occupant = await serverHealth(configuredPort);
|
|
2981
|
-
if (occupant?.pid !== void 0 && occupant.fingerprint !== fingerprint) await stopStaleServer({ pid: occupant.pid, port: configuredPort, ...occupant.startedAt === void 0 ? {} : { startedAt: occupant.startedAt } });
|
|
2982
|
-
try {
|
|
2983
|
-
const child = spawn(process.versions.bun ? "node" : process.execPath, [serverPath, "--port", String(configuredPort), "--lock", lockPath, "--fingerprint", fingerprint], { detached: true, stdio: "ignore" });
|
|
2984
|
-
const startupError = new Promise((_resolve, reject) => {
|
|
2985
|
-
child.once("error", reject);
|
|
2986
|
-
});
|
|
2987
|
-
child.unref();
|
|
2988
|
-
await Promise.race([waitForServer(configuredPort, fingerprint), startupError]);
|
|
2989
|
-
return { port: configuredPort };
|
|
2990
|
-
} catch (error) {
|
|
2991
|
-
await rm5(lockPath, { force: true });
|
|
2992
|
-
throw error;
|
|
2588
|
+
await releaseStartupMutex(lockPath, startupToken);
|
|
2993
2589
|
}
|
|
2994
2590
|
}
|
|
2995
2591
|
function trajectoryWebSocket() {
|
|
@@ -3460,13 +3056,13 @@ function createTrajectoryController(agentDir) {
|
|
|
3460
3056
|
} catch {
|
|
3461
3057
|
}
|
|
3462
3058
|
});
|
|
3463
|
-
await new Promise((
|
|
3059
|
+
await new Promise((resolve4, reject) => {
|
|
3464
3060
|
let settled = false;
|
|
3465
3061
|
const finish = (error) => {
|
|
3466
3062
|
if (settled) return;
|
|
3467
3063
|
settled = true;
|
|
3468
3064
|
if (error) reject(error);
|
|
3469
|
-
else
|
|
3065
|
+
else resolve4();
|
|
3470
3066
|
};
|
|
3471
3067
|
next.addEventListener("open", () => {
|
|
3472
3068
|
finish();
|
|
@@ -3486,11 +3082,11 @@ function createTrajectoryController(agentDir) {
|
|
|
3486
3082
|
next.send(JSON.stringify({ type: "publisher:attach", publisherId: publisherId(input.cwd, input.sessionId) }));
|
|
3487
3083
|
};
|
|
3488
3084
|
let reconnectCancel;
|
|
3489
|
-
const waitReconnect = (ms) => new Promise((
|
|
3085
|
+
const waitReconnect = (ms) => new Promise((resolve4) => {
|
|
3490
3086
|
const timer = setTimeout(() => {
|
|
3491
3087
|
if (reconnectTimer === timer) reconnectTimer = void 0;
|
|
3492
3088
|
reconnectCancel = void 0;
|
|
3493
|
-
|
|
3089
|
+
resolve4();
|
|
3494
3090
|
}, ms);
|
|
3495
3091
|
timer.unref();
|
|
3496
3092
|
reconnectTimer = timer;
|
|
@@ -3498,7 +3094,7 @@ function createTrajectoryController(agentDir) {
|
|
|
3498
3094
|
clearTimeout(timer);
|
|
3499
3095
|
if (reconnectTimer === timer) reconnectTimer = void 0;
|
|
3500
3096
|
reconnectCancel = void 0;
|
|
3501
|
-
|
|
3097
|
+
resolve4();
|
|
3502
3098
|
};
|
|
3503
3099
|
});
|
|
3504
3100
|
const scheduleReconnect = () => {
|
|
@@ -3565,7 +3161,7 @@ function createTrajectoryController(agentDir) {
|
|
|
3565
3161
|
};
|
|
3566
3162
|
}
|
|
3567
3163
|
function registerTrajectoryExtension(pi, options = {}) {
|
|
3568
|
-
const controller = options.controller ?? createTrajectoryController(options.agentDir ??
|
|
3164
|
+
const controller = options.controller ?? createTrajectoryController(options.agentDir ?? getAgentDir3());
|
|
3569
3165
|
const openUrl = options.openUrl ?? openTrajectoryUrl;
|
|
3570
3166
|
let autoAttached = false;
|
|
3571
3167
|
const notify = (context, message, level) => {
|
|
@@ -3581,7 +3177,7 @@ function registerTrajectoryExtension(pi, options = {}) {
|
|
|
3581
3177
|
return void 0;
|
|
3582
3178
|
}
|
|
3583
3179
|
};
|
|
3584
|
-
const
|
|
3180
|
+
const open3 = async (provider, context) => {
|
|
3585
3181
|
const server = await attach(provider, context);
|
|
3586
3182
|
if (server === void 0) return;
|
|
3587
3183
|
const url = trajectoryUrl(server.port);
|
|
@@ -3589,7 +3185,7 @@ function registerTrajectoryExtension(pi, options = {}) {
|
|
|
3589
3185
|
notify(context, `Trajectory opened at ${url}`, "info");
|
|
3590
3186
|
};
|
|
3591
3187
|
const host = {
|
|
3592
|
-
open:
|
|
3188
|
+
open: open3,
|
|
3593
3189
|
autoAttach(provider, context) {
|
|
3594
3190
|
if (autoAttached || !context.hasUI) return;
|
|
3595
3191
|
autoAttached = true;
|