@marcoscale98/pi-extensible-workflows 5.17.0-fork.1 → 6.2.1-fork.1

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