@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.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 (94) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/dist/src/agent-execution.js +114 -87
  3. package/dist/src/background-widget.d.ts +61 -0
  4. package/dist/src/background-widget.js +23 -16
  5. package/dist/src/decoders.js +28 -1
  6. package/dist/src/execution.d.ts +4 -2
  7. package/dist/src/execution.js +43 -2
  8. package/dist/src/host-delivery.js +1 -0
  9. package/dist/src/host-navigator.d.ts +18 -0
  10. package/dist/src/host-navigator.js +71 -13
  11. package/dist/src/host-recovery.d.ts +5 -2
  12. package/dist/src/host-recovery.js +3 -3
  13. package/dist/src/host-view.d.ts +21 -1
  14. package/dist/src/host-view.js +138 -47
  15. package/dist/src/host.d.ts +2 -2
  16. package/dist/src/host.js +246 -60
  17. package/dist/src/index.d.ts +3 -1
  18. package/dist/src/index.js +1346 -622
  19. package/dist/src/index.js.map +3 -3
  20. package/dist/src/paths.d.ts +2 -0
  21. package/dist/src/paths.js +2 -0
  22. package/dist/src/registry.js +3 -2
  23. package/dist/src/roles.d.ts +70 -0
  24. package/dist/src/roles.js +326 -0
  25. package/dist/src/script-tools.d.ts +27 -0
  26. package/dist/src/script-tools.js +87 -0
  27. package/dist/src/settings.d.ts +26 -0
  28. package/dist/src/settings.js +277 -0
  29. package/dist/src/trajectory.d.ts +39 -4
  30. package/dist/src/trajectory.js +82 -5
  31. package/dist/src/types.d.ts +30 -3
  32. package/dist/src/types.js +1 -1
  33. package/dist/src/utils.d.ts +21 -1
  34. package/dist/src/utils.js +40 -0
  35. package/dist/src/validation.d.ts +10 -17
  36. package/dist/src/validation.js +29 -458
  37. package/dist/starter/index.js +0 -2
  38. package/dist/starter/index.js.map +1 -1
  39. package/dist/starter/review-loop.d.ts +0 -13
  40. package/dist/starter/review-loop.js +0 -2
  41. package/dist/subagents/index.js +1344 -798
  42. package/dist/subagents/index.js.map +3 -3
  43. package/dist/subagents/src/contracts.d.ts +49 -29
  44. package/dist/subagents/src/contracts.js +18 -7
  45. package/dist/subagents/src/index.d.ts +1 -1
  46. package/dist/subagents/src/index.js +15 -7
  47. package/dist/subagents/src/manager.js +32 -13
  48. package/dist/subagents/src/navigator.js +396 -247
  49. package/dist/subagents/src/view.d.ts +24 -2
  50. package/dist/subagents/src/view.js +107 -80
  51. package/dist/trajectory/assets/index.html +370 -232
  52. package/dist/trajectory/assets/prism.min.js +13 -0
  53. package/dist/trajectory/index.js +445 -277
  54. package/dist/trajectory/index.js.map +3 -3
  55. package/dist/trajectory/src/assets/index.html +370 -232
  56. package/dist/trajectory/src/assets/prism.min.js +13 -0
  57. package/dist/trajectory/src/export.js +4 -2
  58. package/dist/trajectory/src/index.d.ts +2 -0
  59. package/dist/trajectory/src/index.js +151 -60
  60. package/dist/trajectory/src/server.js +138 -11
  61. package/dist/trajectory/src/server.js.map +2 -2
  62. package/package.json +7 -6
  63. package/skills/pi-extensible-workflows/SKILL.md +2 -0
  64. package/src/agent-execution.ts +110 -80
  65. package/src/background-widget.ts +26 -19
  66. package/src/decoders.ts +22 -1
  67. package/src/execution.ts +41 -4
  68. package/src/host-delivery.ts +1 -0
  69. package/src/host-navigator.ts +58 -14
  70. package/src/host-recovery.ts +8 -7
  71. package/src/host-view.ts +116 -42
  72. package/src/host.ts +223 -55
  73. package/src/index.ts +3 -1
  74. package/src/paths.ts +3 -0
  75. package/src/registry.ts +3 -2
  76. package/src/roles.ts +327 -0
  77. package/src/script-tools.ts +76 -0
  78. package/src/settings.ts +224 -0
  79. package/src/trajectory.ts +97 -9
  80. package/src/types.ts +12 -5
  81. package/src/utils.ts +36 -1
  82. package/src/validation.ts +30 -356
  83. package/starter/review-loop.ts +0 -2
  84. package/subagents/README.md +3 -3
  85. package/subagents/src/contracts.ts +22 -7
  86. package/subagents/src/index.ts +18 -7
  87. package/subagents/src/manager.ts +31 -13
  88. package/subagents/src/navigator.ts +336 -180
  89. package/subagents/src/view.ts +108 -76
  90. package/trajectory/src/assets/index.html +370 -232
  91. package/trajectory/src/assets/prism.min.js +13 -0
  92. package/trajectory/src/export.ts +4 -2
  93. package/trajectory/src/index.ts +124 -47
  94. package/trajectory/src/server.ts +114 -14
package/dist/src/index.js CHANGED
@@ -42,6 +42,7 @@ var ERROR_CODES = [
42
42
  "AGENT_FAILED",
43
43
  "AGENT_RESULT_COLLECTED",
44
44
  "RESULT_INVALID",
45
+ "TOOL_FAILED",
45
46
  "CANCELLED",
46
47
  "WORKER_UNRESPONSIVE",
47
48
  "WORKTREE_FAILED",
@@ -135,6 +136,7 @@ function createToolTimingExtension(clock = Date.now) {
135
136
  }
136
137
 
137
138
  // src/utils.ts
139
+ import { createHash } from "node:crypto";
138
140
  import { Minimatch } from "minimatch";
139
141
  var SerialLane = class {
140
142
  #tail = Promise.resolve();
@@ -160,6 +162,14 @@ function isStringKey(key) {
160
162
  function stringKeyValue(value, key) {
161
163
  return object(value) ? value[key] : void 0;
162
164
  }
165
+ function sortedJson(value) {
166
+ if (Array.isArray(value)) return value.map(sortedJson);
167
+ if (value === null || typeof value !== "object") return value;
168
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortedJson(value[key])]));
169
+ }
170
+ function jsonDigest(value) {
171
+ return createHash("sha256").update(JSON.stringify(sortedJson(value))).digest("hex").slice(0, 16);
172
+ }
163
173
  function jsonValue(value, seen = /* @__PURE__ */ new Set()) {
164
174
  if (value === null || typeof value === "boolean" || typeof value === "string") return true;
165
175
  if (typeof value === "number") return Number.isFinite(value);
@@ -320,6 +330,16 @@ function resolveModelReference(value, aliases = {}, knownModels, settingsPath) {
320
330
  };
321
331
  return resolveReference(value, []);
322
332
  }
333
+ function reachableTools(pi) {
334
+ const undeclared = pi.getAllTools?.().filter(({ exposure }) => exposure === "codemode" || exposure === "deferred").map(({ name }) => name) ?? [];
335
+ return [.../* @__PURE__ */ new Set([...pi.getActiveTools(), ...undeclared])];
336
+ }
337
+ var VIRTUAL_MODEL_PROVIDER = "workflow";
338
+ function physicalModel(spec, aliases = {}, knownModels, settingsPath) {
339
+ if (spec.provider !== VIRTUAL_MODEL_PROVIDER || !modelAliasName(spec.model, aliases)) return spec;
340
+ const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
341
+ return spec.thinking === void 0 ? target : { ...target, thinking: spec.thinking };
342
+ }
323
343
  function modelCapability(value, aliases, knownModels, settingsPath) {
324
344
  const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
325
345
  return `${parsed.provider}/${parsed.model}`;
@@ -370,9 +390,15 @@ function createLaunchSnapshot(input) {
370
390
  function loadLaunchSnapshot(input) {
371
391
  return deepFreeze(structuredClone(input));
372
392
  }
393
+ function validateSchema(schema, at = "schema") {
394
+ if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
395
+ 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`);
396
+ 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`);
397
+ if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
398
+ }
373
399
 
374
400
  // src/paths.ts
375
- import { createHash } from "node:crypto";
401
+ import { createHash as createHash2 } from "node:crypto";
376
402
  import { realpathSync } from "node:fs";
377
403
  import { readdir } from "node:fs/promises";
378
404
  import { homedir } from "node:os";
@@ -388,6 +414,9 @@ function canonicalPath(path) {
388
414
  return absolute;
389
415
  }
390
416
  }
417
+ function extensionIdentity(path) {
418
+ return path.startsWith("builtin:") ? path : canonicalPath(path);
419
+ }
391
420
  function sameFilesystemPath(left, right) {
392
421
  return canonicalPath(left) === canonicalPath(right);
393
422
  }
@@ -397,7 +426,7 @@ function workflowProjectSettingsPath(cwd) {
397
426
  function projectStorageKey(cwd) {
398
427
  const exact = resolve(cwd);
399
428
  const slug = safePart(basename(exact)) || "root";
400
- return `${slug}-${createHash("sha256").update(exact).digest("hex").slice(0, 12)}`;
429
+ return `${slug}-${createHash2("sha256").update(exact).digest("hex").slice(0, 12)}`;
401
430
  }
402
431
  function projectSessionsDirectory(cwd, home = homedir()) {
403
432
  return join(home, ".pi", "workflows", "projects", projectStorageKey(cwd), "sessions");
@@ -670,6 +699,23 @@ function resumeBudgetAllowed(budget, usage) {
670
699
  return exhaustedBudgetDimensions(budget, usage).length === 0;
671
700
  }
672
701
 
702
+ // src/validation.ts
703
+ import { readFileSync as readFileSync4 } from "node:fs";
704
+ import { resolve as resolve5 } from "node:path";
705
+ import * as acorn from "acorn";
706
+ import { Script } from "node:vm";
707
+
708
+ // src/roles.ts
709
+ import { readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
710
+ import { basename as basename3, dirname as dirname4, extname, join as join6, resolve as resolve4 } from "node:path";
711
+ import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
712
+
713
+ // src/registry.ts
714
+ import { readFileSync as readFileSync2 } from "node:fs";
715
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
716
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
717
+ import { Value } from "typebox/value";
718
+
673
719
  // src/io.ts
674
720
  import { execFile } from "node:child_process";
675
721
  import { randomUUID } from "node:crypto";
@@ -690,7 +736,7 @@ async function renameWithRetry(from, to) {
690
736
  } catch (error) {
691
737
  const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
692
738
  if (!retryableRenameError(error) || delayMs === void 0) throw error;
693
- await new Promise((resolve7) => setTimeout(resolve7, delayMs));
739
+ await new Promise((resolve9) => setTimeout(resolve9, delayMs));
694
740
  }
695
741
  }
696
742
  }
@@ -894,7 +940,7 @@ function decodeWorkflowExtensions(value) {
894
940
  }
895
941
  if (namespace === "trajectory") {
896
942
  if (!object(raw) || Object.keys(raw).some((key) => key !== "port" && key !== "themes") || raw.port !== void 0 && (typeof raw.port !== "number" || !Number.isSafeInteger(raw.port) || raw.port < 1 || raw.port > 65535) || raw.themes !== void 0 && typeof raw.themes !== "boolean") return void 0;
897
- normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? {} : { themes: raw.themes } };
943
+ normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port } };
898
944
  continue;
899
945
  }
900
946
  if (!jsonValue(raw)) return void 0;
@@ -1178,6 +1224,22 @@ function decodeRunEvent(value) {
1178
1224
  if (timestamp === INVALID_PERSISTED_VALUE) return void 0;
1179
1225
  return { type: value.type, message: value.message, ...timestamp === void 0 ? {} : { timestamp } };
1180
1226
  }
1227
+ function decodeScriptCall(value) {
1228
+ if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt)) return void 0;
1229
+ const shell = value.kind === "shell";
1230
+ if (shell && !jsonValue(value.input)) return void 0;
1231
+ const inputBytes = shell ? optionalNumber(value.inputBytes) : void 0;
1232
+ const phase = optionalString(value.phase);
1233
+ const finishedAt = optionalNumber(value.finishedAt);
1234
+ let error;
1235
+ if (value.error !== void 0) {
1236
+ if (shell) error = decodeWorkflowError(value.error);
1237
+ else if (object(value.error) && typeof value.error.code === "string") error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
1238
+ if (!error) return void 0;
1239
+ }
1240
+ if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE) return void 0;
1241
+ 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 } };
1242
+ }
1181
1243
  function decodeDelivery(value) {
1182
1244
  if (!object(value)) return void 0;
1183
1245
  const mode = value.mode;
@@ -1207,6 +1269,8 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
1207
1269
  const budgetEvents = value.budgetEvents === void 0 ? void 0 : decodeArray(value.budgetEvents, decodeBudgetEvent);
1208
1270
  const events = value.events === void 0 ? void 0 : decodeArray(value.events, decodeRunEvent);
1209
1271
  const delivery = value.delivery === void 0 ? void 0 : decodeDelivery(value.delivery);
1272
+ const scriptCalls = value.scriptCalls === void 0 ? void 0 : decodeArray(value.scriptCalls, decodeScriptCall);
1273
+ if (value.scriptCalls !== void 0 && !scriptCalls) return void 0;
1210
1274
  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;
1211
1275
  if (value.agentSessions === void 0 && !allowLegacyAgentSessions) return void 0;
1212
1276
  return {
@@ -1225,6 +1289,7 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
1225
1289
  ...activeShells === void 0 ? {} : { activeShells },
1226
1290
  ...activeShellStartedAt === void 0 ? {} : { activeShellStartedAt },
1227
1291
  ...activeShellsByPhase === void 0 ? {} : { activeShellsByPhase },
1292
+ ...scriptCalls === void 0 ? {} : { scriptCalls },
1228
1293
  ...error === void 0 ? {} : { error },
1229
1294
  ...failedAt === void 0 ? {} : { failedAt },
1230
1295
  ...budget === void 0 ? {} : { budget },
@@ -1504,7 +1569,7 @@ async function listRunIds(cwd, sessionId, home = homedir2(), cleanTemps = true)
1504
1569
 
1505
1570
  // src/store.ts
1506
1571
  import { execFile as execFile2 } from "node:child_process";
1507
- import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
1572
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
1508
1573
  import { access, mkdir as mkdir2, readdir as readdir3, readFile as readFile3, realpath, rename as rename3, rm as rm3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
1509
1574
  import { homedir as homedir3 } from "node:os";
1510
1575
  import { dirname, join as join3, relative, resolve as resolve2, sep } from "node:path";
@@ -1702,7 +1767,7 @@ var RunStore = class _RunStore {
1702
1767
  }
1703
1768
  }
1704
1769
  async appendSystemPromptV2(entry) {
1705
- const sha256 = createHash2("sha256").update(entry.prompt).digest("hex");
1770
+ const sha256 = createHash3("sha256").update(entry.prompt).digest("hex");
1706
1771
  const bodyPath = join3(systemPromptBodiesPath(this.directory), sha256);
1707
1772
  try {
1708
1773
  await access(bodyPath);
@@ -1755,7 +1820,7 @@ var RunStore = class _RunStore {
1755
1820
  const sha256 = record3.sha256;
1756
1821
  if (typeof sessionId !== "string" || !sessionId || !positiveInteger(attempt) || !positiveInteger(turn) || typeof sha256 !== "string" || !/^[0-9a-f]{64}$/.test(sha256)) throw new Error("Persisted system-prompt record is invalid");
1757
1822
  const prompt = await readFile3(join3(systemPromptBodiesPath(this.directory), sha256), "utf8");
1758
- if (createHash2("sha256").update(prompt).digest("hex") !== sha256) throw new Error("Persisted system-prompt body is invalid");
1823
+ if (createHash3("sha256").update(prompt).digest("hex") !== sha256) throw new Error("Persisted system-prompt body is invalid");
1759
1824
  entries.push({ sessionId, attempt, turn, sha256, prompt });
1760
1825
  }
1761
1826
  if (sequence < highest) throw new Error("Persisted system-prompt sequence is invalid");
@@ -1897,7 +1962,7 @@ var RunStore = class _RunStore {
1897
1962
  });
1898
1963
  }
1899
1964
  #worktreeKey(owner) {
1900
- return createHash2("sha256").update(`${this.sessionId}\0${this.runId}\0${owner}`).digest("hex").slice(0, 16);
1965
+ return createHash3("sha256").update(`${this.sessionId}\0${this.runId}\0${owner}`).digest("hex").slice(0, 16);
1901
1966
  }
1902
1967
  expectedWorktree(owner) {
1903
1968
  const key = this.#worktreeKey(owner);
@@ -2356,21 +2421,250 @@ var RunStore = class _RunStore {
2356
2421
  }
2357
2422
  };
2358
2423
 
2359
- // src/validation.ts
2360
- import { mkdirSync, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
2424
+ // src/settings.ts
2425
+ import { mkdirSync, readFileSync } from "node:fs";
2361
2426
  import { homedir as homedir4 } from "node:os";
2362
- import { basename as basename3, dirname as dirname3, extname, join as join5, resolve as resolve3 } from "node:path";
2363
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2364
- import * as acorn from "acorn";
2365
- import { Script } from "node:vm";
2366
- import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
2427
+ import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
2428
+ import { fileURLToPath } from "node:url";
2429
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2430
+ var ROLE_DIRECTORY = "pi-extensible-workflows";
2431
+ var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
2432
+ function workflowSettingsPath(agentDir = getAgentDir()) {
2433
+ return join4(agentDir, ROLE_DIRECTORY, "settings.json");
2434
+ }
2435
+ function normalizedResourcePath(value, settingsPath) {
2436
+ if (value === "*" || value.startsWith("builtin:")) return value;
2437
+ let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir4(), value.slice(2)) : value;
2438
+ if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
2439
+ const resolved = resolve3(dirname2(settingsPath), expanded);
2440
+ if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
2441
+ if (resourcePatternHasMagic(expanded)) {
2442
+ const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
2443
+ const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
2444
+ const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
2445
+ const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
2446
+ const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
2447
+ return `${canonicalPath(prefix)}${suffix}`;
2448
+ }
2449
+ return canonicalPath(resolved);
2450
+ }
2451
+ function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
2452
+ if (value === void 0) return void 0;
2453
+ if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
2454
+ const normalized = [];
2455
+ for (const [index, entry] of value.entries()) {
2456
+ if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
2457
+ let selector = entry.trim();
2458
+ if (kind === "extensions" && normalizeExtensions) {
2459
+ const negated = selector.startsWith("!");
2460
+ const body = negated ? selector.slice(1) : selector;
2461
+ if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
2462
+ try {
2463
+ selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
2464
+ } catch (error) {
2465
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
2466
+ }
2467
+ }
2468
+ try {
2469
+ validateResourcePattern(selector);
2470
+ } catch (error) {
2471
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
2472
+ }
2473
+ normalized.push(selector);
2474
+ }
2475
+ return Object.freeze(normalized);
2476
+ }
2477
+ function validateWorktreePostCreateCommand(value, settingsPath) {
2478
+ if (value === void 0) return void 0;
2479
+ if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
2480
+ return Object.freeze(value.map((entry, index) => {
2481
+ if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2482
+ return entry;
2483
+ }));
2484
+ }
2485
+ function selectorsFromSettings(settings) {
2486
+ return {
2487
+ ...settings.skills === void 0 ? {} : { skills: settings.skills },
2488
+ ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2489
+ ...settings.tools === void 0 ? {} : { tools: settings.tools }
2490
+ };
2491
+ }
2492
+ function selectorSet(value) {
2493
+ return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2494
+ }
2495
+ function validateContextFileScopes(value, rolePath) {
2496
+ if (value === void 0) return void 0;
2497
+ if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2498
+ return [...value];
2499
+ }
2500
+ function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
2501
+ if (value === void 0) return void 0;
2502
+ const base = `${settingsPath}.extensionSettings`;
2503
+ if (!object(value)) fail(errorCode2, `${base} must be an object`);
2504
+ const normalized = {};
2505
+ for (const [namespace, raw] of Object.entries(value)) {
2506
+ if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2507
+ if (namespace === "herdr") {
2508
+ if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2509
+ if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2510
+ if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2511
+ normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2512
+ continue;
2513
+ }
2514
+ if (namespace === "trajectory") {
2515
+ if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2516
+ if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2517
+ if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2518
+ normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
2519
+ continue;
2520
+ }
2521
+ if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2522
+ normalized[namespace] = structuredClone(raw);
2523
+ }
2524
+ return deepFreeze(normalized);
2525
+ }
2526
+ function positiveRetentionInteger(value) {
2527
+ return positiveInteger(value) && Number.isSafeInteger(value);
2528
+ }
2529
+ function validateRetention(value, settingsPath) {
2530
+ if (value === void 0) return void 0;
2531
+ const base = `${settingsPath}.retention`;
2532
+ if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2533
+ const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2534
+ if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2535
+ if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2536
+ if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2537
+ return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2538
+ }
2539
+ function parseSettings(path, partial) {
2540
+ let parsed;
2541
+ try {
2542
+ parsed = JSON.parse(readFileSync(path, "utf8"));
2543
+ } catch (error) {
2544
+ if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2545
+ fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2546
+ }
2547
+ if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2548
+ const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget", "codemodeTools"]]);
2549
+ const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2550
+ if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2551
+ if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2552
+ const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2553
+ if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2554
+ const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2555
+ if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2556
+ const codemodeTools = parsed.codemodeTools;
2557
+ if (codemodeTools !== void 0 && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none") fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
2558
+ const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2559
+ const skills = validateSelectorList(parsed.skills, path, "skills");
2560
+ const tools = validateSelectorList(parsed.tools, path, "tools");
2561
+ const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2562
+ const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
2563
+ const retention = validateRetention(parsed.retention, path);
2564
+ const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2565
+ return Object.freeze({
2566
+ ...concurrency === void 0 ? {} : { concurrency },
2567
+ ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2568
+ ...codemodeTools === void 0 ? {} : { codemodeTools },
2569
+ ...modelAliases === void 0 ? {} : { modelAliases },
2570
+ ...skills === void 0 ? {} : { skills },
2571
+ ...extensions === void 0 ? {} : { extensions },
2572
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2573
+ ...tools === void 0 ? {} : { tools },
2574
+ ...retention === void 0 ? {} : { retention },
2575
+ ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2576
+ });
2577
+ }
2578
+ function loadSettings(path = workflowSettingsPath()) {
2579
+ return parseSettings(path, false);
2580
+ }
2581
+ var READ_ONLY_WORKFLOW_TOOLS = /* @__PURE__ */ new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
2582
+ function workflowToolExposure(name, codemodeTools) {
2583
+ const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
2584
+ return { ...codemodeTools === "none" || codemodeTools === "read-only" && !readOnly ? { exposure: "model-only" } : {}, ...readOnly ? { annotations: { readOnlyHint: true } } : {} };
2585
+ }
2586
+ function loadCodemodeToolsSetting(agentDir) {
2587
+ try {
2588
+ return loadSettings(workflowSettingsPath(agentDir)).codemodeTools;
2589
+ } catch {
2590
+ return void 0;
2591
+ }
2592
+ }
2593
+ function loadSettingsOverrides(path) {
2594
+ return parseSettings(path, true);
2595
+ }
2596
+ function resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2597
+ const projectSettingsPath = workflowProjectSettingsPath(cwd);
2598
+ const global = loadSettings(globalSettingsPath);
2599
+ const project = projectTrusted2 ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2600
+ const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2601
+ const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2602
+ const globalSelectors = selectorsFromSettings(global);
2603
+ const projectSelectors = selectorsFromSettings(project);
2604
+ const effectiveSelectors = selectorSet({
2605
+ skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2606
+ extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2607
+ ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2608
+ });
2609
+ const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2610
+ const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2611
+ const sources = {
2612
+ concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2613
+ modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2614
+ skills: sourceFor("skills"),
2615
+ extensions: sourceFor("extensions"),
2616
+ tools: sourceFor("tools"),
2617
+ ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2618
+ ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2619
+ ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2620
+ };
2621
+ const effective = Object.freeze({
2622
+ concurrency: project.concurrency ?? global.concurrency,
2623
+ backgroundWidget: global.backgroundWidget ?? true,
2624
+ ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2625
+ ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2626
+ ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2627
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2628
+ ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2629
+ ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2630
+ ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2631
+ });
2632
+ return { globalSettingsPath, projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, sources };
2633
+ }
2634
+ function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2635
+ for (const name of names) {
2636
+ try {
2637
+ const target = modelCapability(name, aliases, knownModels, settingsPath);
2638
+ if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2639
+ } catch (error) {
2640
+ throw annotateModelAliasError(error, name);
2641
+ }
2642
+ }
2643
+ }
2644
+ function resolveAgentResourcePolicy(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2645
+ const resolved = resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath);
2646
+ const global = selectorSet(selectorsFromSettings(resolved.global));
2647
+ const project = selectorSet(selectorsFromSettings(resolved.project));
2648
+ const effective = selectorSet(selectorsFromSettings(resolved.effective));
2649
+ return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2650
+ }
2651
+ function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
2652
+ const normalized = validateModelAliases(aliases, path);
2653
+ let parsed = {};
2654
+ try {
2655
+ loadSettings(path);
2656
+ parsed = JSON.parse(readFileSync(path, "utf8"));
2657
+ } catch (error) {
2658
+ if (!isNodeError(error, "ENOENT")) throw error;
2659
+ }
2660
+ if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2661
+ mkdirSync(dirname2(path), { recursive: true });
2662
+ atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}
2663
+ `, true);
2664
+ }
2367
2665
 
2368
2666
  // src/registry.ts
2369
- import { readFileSync } from "node:fs";
2370
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join4 } from "node:path";
2371
- import { fileURLToPath } from "node:url";
2372
- import { Value } from "typebox/value";
2373
- 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"]);
2667
+ var RESERVED_GLOBALS = /* @__PURE__ */ new Set(["agent", "shell", "tools", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
2374
2668
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
2375
2669
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2376
2670
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
@@ -2378,10 +2672,10 @@ function normalizeRoleDirectory(value) {
2378
2672
  try {
2379
2673
  if (value instanceof URL) {
2380
2674
  if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
2381
- return fileURLToPath(value);
2675
+ return fileURLToPath2(value);
2382
2676
  }
2383
2677
  if (typeof value === "string") {
2384
- if (value.startsWith("file:")) return fileURLToPath(value);
2678
+ if (value.startsWith("file:")) return fileURLToPath2(value);
2385
2679
  if (isAbsolute(value)) return value;
2386
2680
  }
2387
2681
  } catch (error) {
@@ -2391,12 +2685,12 @@ function normalizeRoleDirectory(value) {
2391
2685
  }
2392
2686
  function isBuiltinRoleDirectory(path) {
2393
2687
  const canonical = canonicalPath(path);
2394
- const starterDirectory = dirname2(canonical);
2688
+ const starterDirectory = dirname3(canonical);
2395
2689
  if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
2396
- const distributionDirectory = dirname2(starterDirectory);
2397
- const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname2(distributionDirectory) : distributionDirectory;
2690
+ const distributionDirectory = dirname3(starterDirectory);
2691
+ const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
2398
2692
  try {
2399
- const metadata = JSON.parse(readFileSync(join4(packageRoot2, "package.json"), "utf8"));
2693
+ const metadata = JSON.parse(readFileSync2(join5(packageRoot2, "package.json"), "utf8"));
2400
2694
  return object(metadata) && isCorePackageName(metadata.name);
2401
2695
  } catch {
2402
2696
  return false;
@@ -2669,278 +2963,54 @@ function resetWorkflowRegistryIfIdle() {
2669
2963
  }
2670
2964
  function beginWorkflowExtensionLoading() {
2671
2965
  const host = workflowRegistryHost();
2672
- if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
2673
- }
2674
- function retainWorkflowRegistry() {
2675
- const host = workflowRegistryHost();
2676
- host.activeHosts += 1;
2677
- let retained = true;
2678
- return () => {
2679
- if (!retained) return;
2680
- retained = false;
2681
- host.activeHosts -= 1;
2682
- if (host.activeHosts === 0) resetWorkflowRegistry();
2683
- };
2684
- }
2685
- function loadingRegistry() {
2686
- return workflowRegistryHost().api;
2687
- }
2688
- beginWorkflowExtensionLoading();
2689
- function registerWorkflowExtension(extension) {
2690
- loadingRegistry().register(extension);
2691
- }
2692
- function workflowCatalog(context) {
2693
- return loadingRegistry().catalog(context);
2694
- }
2695
- function workflowCatalogIndex(context) {
2696
- return loadingRegistry().catalogIndex(context);
2697
- }
2698
- function workflowCatalogDetail(name, context) {
2699
- return loadingRegistry().catalogDetail(name, context);
2700
- }
2701
- function registeredWorkflowFunctions() {
2702
- return loadingRegistry().functions();
2703
- }
2704
- function registeredWorkflowFunctionSources() {
2705
- const sources = loadingRegistry().functionSources;
2706
- return typeof sources === "function" ? sources() : {};
2707
- }
2708
- function registeredWorkflowRoleDirectories() {
2709
- const directories = loadingRegistry().roleDirectories;
2710
- return typeof directories === "function" ? directories() : [];
2711
- }
2712
- function registeredWorkflowRoleDirectoryRegistrations() {
2713
- const registrations = loadingRegistry().roleDirectoryRegistrations;
2714
- return typeof registrations === "function" ? registrations() : [];
2715
- }
2716
-
2717
- // src/validation.ts
2718
- var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
2719
- function validateCheckpoint(value) {
2720
- if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
2721
- if (Buffer.byteLength(value.prompt) > 1024) fail("INVALID_METADATA", "checkpoint prompt exceeds 1024 UTF-8 bytes");
2722
- if (Buffer.byteLength(JSON.stringify(value.context)) > 4096) fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
2723
- return { name: value.name, prompt: value.prompt, context: value.context };
2724
- }
2725
- function workflowSettingsPath(agentDir = getAgentDir()) {
2726
- return join5(agentDir, ROLE_DIRECTORY, "settings.json");
2727
- }
2728
- function normalizedResourcePath(value, settingsPath) {
2729
- if (value === "*") return value;
2730
- let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join5(homedir4(), value.slice(2)) : value;
2731
- if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
2732
- const resolved = resolve3(dirname3(settingsPath), expanded);
2733
- if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
2734
- if (resourcePatternHasMagic(expanded)) {
2735
- const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
2736
- const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
2737
- const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
2738
- const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
2739
- const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
2740
- return `${canonicalPath(prefix)}${suffix}`;
2741
- }
2742
- return canonicalPath(resolved);
2743
- }
2744
- function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
2745
- if (value === void 0) return void 0;
2746
- if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
2747
- const normalized = [];
2748
- for (const [index, entry] of value.entries()) {
2749
- if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
2750
- let selector = entry.trim();
2751
- if (kind === "extensions" && normalizeExtensions) {
2752
- const negated = selector.startsWith("!");
2753
- const body = negated ? selector.slice(1) : selector;
2754
- if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
2755
- try {
2756
- selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
2757
- } catch (error) {
2758
- fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
2759
- }
2760
- }
2761
- try {
2762
- validateResourcePattern(selector);
2763
- } catch (error) {
2764
- fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
2765
- }
2766
- normalized.push(selector);
2767
- }
2768
- return Object.freeze(normalized);
2769
- }
2770
- function validateWorktreePostCreateCommand(value, settingsPath) {
2771
- if (value === void 0) return void 0;
2772
- if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
2773
- return Object.freeze(value.map((entry, index) => {
2774
- if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2775
- return entry;
2776
- }));
2777
- }
2778
- function selectorsFromSettings(settings) {
2779
- return {
2780
- ...settings.skills === void 0 ? {} : { skills: settings.skills },
2781
- ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2782
- ...settings.tools === void 0 ? {} : { tools: settings.tools }
2783
- };
2784
- }
2785
- function selectorSet(value) {
2786
- return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2787
- }
2788
- function validateContextFileScopes(value, rolePath) {
2789
- if (value === void 0) return void 0;
2790
- if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2791
- return [...value];
2792
- }
2793
- function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS", applyDefaults = true) {
2794
- if (value === void 0) return void 0;
2795
- const base = `${settingsPath}.extensionSettings`;
2796
- if (!object(value)) fail(errorCode2, `${base} must be an object`);
2797
- const normalized = {};
2798
- for (const [namespace, raw] of Object.entries(value)) {
2799
- if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2800
- if (namespace === "herdr") {
2801
- if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2802
- if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2803
- if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2804
- normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2805
- continue;
2806
- }
2807
- if (namespace === "trajectory") {
2808
- if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2809
- if (Object.keys(raw).some((key) => key !== "port" && key !== "themes")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2810
- if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2811
- if (raw.themes !== void 0 && typeof raw.themes !== "boolean") fail(errorCode2, `${base}.trajectory.themes must be a boolean`);
2812
- normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? applyDefaults ? { themes: false } : {} : { themes: raw.themes } });
2813
- continue;
2814
- }
2815
- if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2816
- normalized[namespace] = structuredClone(raw);
2817
- }
2818
- return deepFreeze(normalized);
2819
- }
2820
- function positiveRetentionInteger(value) {
2821
- return positiveInteger(value) && Number.isSafeInteger(value);
2822
- }
2823
- function validateRetention(value, settingsPath) {
2824
- if (value === void 0) return void 0;
2825
- const base = `${settingsPath}.retention`;
2826
- if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2827
- const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2828
- if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2829
- if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2830
- if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2831
- return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2832
- }
2833
- function parseSettings(path, partial) {
2834
- let parsed;
2835
- try {
2836
- parsed = JSON.parse(readFileSync2(path, "utf8"));
2837
- } catch (error) {
2838
- if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2839
- fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2840
- }
2841
- if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2842
- const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
2843
- const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2844
- if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2845
- if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2846
- const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2847
- if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2848
- const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2849
- if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2850
- const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2851
- const skills = validateSelectorList(parsed.skills, path, "skills");
2852
- const tools = validateSelectorList(parsed.tools, path, "tools");
2853
- const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2854
- const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS", !partial);
2855
- const retention = validateRetention(parsed.retention, path);
2856
- const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2857
- return Object.freeze({
2858
- ...concurrency === void 0 ? {} : { concurrency },
2859
- ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2860
- ...modelAliases === void 0 ? {} : { modelAliases },
2861
- ...skills === void 0 ? {} : { skills },
2862
- ...extensions === void 0 ? {} : { extensions },
2863
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2864
- ...tools === void 0 ? {} : { tools },
2865
- ...retention === void 0 ? {} : { retention },
2866
- ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2867
- });
2868
- }
2869
- function loadSettings(path = workflowSettingsPath()) {
2870
- return parseSettings(path, false);
2871
- }
2872
- function loadSettingsOverrides(path) {
2873
- return parseSettings(path, true);
2874
- }
2875
- function resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2876
- const projectSettingsPath = workflowProjectSettingsPath(cwd);
2877
- const global = loadSettings(globalSettingsPath);
2878
- const project = projectTrusted2 ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2879
- const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2880
- const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2881
- const globalSelectors = selectorsFromSettings(global);
2882
- const projectSelectors = selectorsFromSettings(project);
2883
- const effectiveSelectors = selectorSet({
2884
- skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2885
- extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2886
- ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2887
- });
2888
- const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2889
- const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2890
- const sources = {
2891
- concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2892
- modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2893
- skills: sourceFor("skills"),
2894
- extensions: sourceFor("extensions"),
2895
- tools: sourceFor("tools"),
2896
- ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2897
- ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2898
- ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2966
+ if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
2967
+ }
2968
+ function retainWorkflowRegistry() {
2969
+ const host = workflowRegistryHost();
2970
+ host.activeHosts += 1;
2971
+ let retained = true;
2972
+ return () => {
2973
+ if (!retained) return;
2974
+ retained = false;
2975
+ host.activeHosts -= 1;
2976
+ if (host.activeHosts === 0) resetWorkflowRegistry();
2899
2977
  };
2900
- const effective = Object.freeze({
2901
- concurrency: project.concurrency ?? global.concurrency,
2902
- backgroundWidget: global.backgroundWidget ?? true,
2903
- ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2904
- ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2905
- ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2906
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2907
- ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2908
- ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2909
- ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2910
- });
2911
- return { globalSettingsPath, projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, sources };
2912
2978
  }
2913
- function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2914
- for (const name of names) {
2915
- try {
2916
- const target = modelCapability(name, aliases, knownModels, settingsPath);
2917
- if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2918
- } catch (error) {
2919
- throw annotateModelAliasError(error, name);
2920
- }
2921
- }
2979
+ function loadingRegistry() {
2980
+ return workflowRegistryHost().api;
2922
2981
  }
2923
- function resolveAgentResourcePolicy(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2924
- const resolved = resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath);
2925
- const global = selectorSet(selectorsFromSettings(resolved.global));
2926
- const project = selectorSet(selectorsFromSettings(resolved.project));
2927
- const effective = selectorSet(selectorsFromSettings(resolved.effective));
2928
- return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2982
+ beginWorkflowExtensionLoading();
2983
+ function registerWorkflowExtension(extension) {
2984
+ loadingRegistry().register(extension);
2929
2985
  }
2930
- function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
2931
- const normalized = validateModelAliases(aliases, path);
2932
- let parsed = {};
2933
- try {
2934
- loadSettings(path);
2935
- parsed = JSON.parse(readFileSync2(path, "utf8"));
2936
- } catch (error) {
2937
- if (!isNodeError(error, "ENOENT")) throw error;
2938
- }
2939
- if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2940
- mkdirSync(dirname3(path), { recursive: true });
2941
- atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}
2942
- `, true);
2986
+ function workflowCatalog(context) {
2987
+ return loadingRegistry().catalog(context);
2988
+ }
2989
+ function workflowCatalogIndex(context) {
2990
+ return loadingRegistry().catalogIndex(context);
2991
+ }
2992
+ function workflowCatalogDetail(name, context) {
2993
+ return loadingRegistry().catalogDetail(name, context);
2994
+ }
2995
+ function registeredWorkflowFunctions() {
2996
+ return loadingRegistry().functions();
2997
+ }
2998
+ function registeredWorkflowFunctionSources() {
2999
+ const sources = loadingRegistry().functionSources;
3000
+ return typeof sources === "function" ? sources() : {};
3001
+ }
3002
+ function registeredWorkflowRoleDirectories() {
3003
+ const directories = loadingRegistry().roleDirectories;
3004
+ return typeof directories === "function" ? directories() : [];
3005
+ }
3006
+ function registeredWorkflowRoleDirectoryRegistrations() {
3007
+ const registrations = loadingRegistry().roleDirectoryRegistrations;
3008
+ return typeof registrations === "function" ? registrations() : [];
2943
3009
  }
3010
+
3011
+ // src/roles.ts
3012
+ var ROLE_DIRECTORY2 = "pi-extensible-workflows";
3013
+ var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
2944
3014
  function parseRoleMarkdown(content, strict = false, rolePath) {
2945
3015
  if (!strict) {
2946
3016
  if (!content.startsWith("---\n")) return { prompt: content };
@@ -2967,7 +3037,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2967
3037
  }
2968
3038
  }
2969
3039
  const rolePathValue2 = rolePath ?? "<role>";
2970
- const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA", false);
3040
+ const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
2971
3041
  const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
2972
3042
  if (meta.model) {
2973
3043
  const model2 = unquote(meta.model);
@@ -3007,7 +3077,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
3007
3077
  const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
3008
3078
  const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
3009
3079
  const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
3010
- const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA", false);
3080
+ const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
3011
3081
  const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
3012
3082
  const normalizedModel = typeof model === "string" ? model.trim() : void 0;
3013
3083
  if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
@@ -3022,12 +3092,11 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
3022
3092
  if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
3023
3093
  return definition;
3024
3094
  }
3025
- var ROLE_DIRECTORY = "pi-extensible-workflows";
3026
- function workflowRoleDirectories(agentDir = getAgentDir()) {
3027
- return [join5(agentDir, ROLE_DIRECTORY, "roles")];
3095
+ function workflowRoleDirectories(agentDir = getAgentDir2()) {
3096
+ return [join6(agentDir, ROLE_DIRECTORY2, "roles")];
3028
3097
  }
3029
3098
  function projectRoleDirectories(root) {
3030
- return [join5(root, ROLE_DIRECTORY, "roles")];
3099
+ return [join6(root, ROLE_DIRECTORY2, "roles")];
3031
3100
  }
3032
3101
  function roleDirectorySources(dirs) {
3033
3102
  const seen = /* @__PURE__ */ new Set();
@@ -3046,7 +3115,7 @@ function isRoleFile(dir, entry) {
3046
3115
  if (entry.isFile()) return true;
3047
3116
  if (!entry.isSymbolicLink()) return false;
3048
3117
  try {
3049
- return statSync(join5(dir, entry.name)).isFile();
3118
+ return statSync(join6(dir, entry.name)).isFile();
3050
3119
  } catch (error) {
3051
3120
  if (isNodeError(error, "ENOENT")) return false;
3052
3121
  throw error;
@@ -3062,11 +3131,11 @@ function scanRoleFiles(dirs, extension) {
3062
3131
  if (!extension && isNodeError(error, "ENOENT")) continue;
3063
3132
  fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
3064
3133
  }
3065
- for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join5(source.path, entry.name), source });
3134
+ for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join6(source.path, entry.name), source });
3066
3135
  }
3067
3136
  return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
3068
3137
  }
3069
- function readRoleDefinitions(dirs, extension = false) {
3138
+ function roleEntries(dirs, extension) {
3070
3139
  const files = scanRoleFiles(dirs, extension);
3071
3140
  const starterFiles = files.filter(({ source }) => source.builtin === true);
3072
3141
  const regularFiles = files.filter(({ source }) => source.builtin !== true);
@@ -3077,7 +3146,9 @@ function readRoleDefinitions(dirs, extension = false) {
3077
3146
  }
3078
3147
  const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
3079
3148
  try {
3080
- return [name, parseRoleMarkdown(readFileSync2(path, "utf8"), true, path)];
3149
+ const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
3150
+ roleDefinitionPaths.set(definition, path);
3151
+ return [name, { name, path, source, definition }];
3081
3152
  } catch (error) {
3082
3153
  if (extension) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
3083
3154
  throw error;
@@ -3085,11 +3156,127 @@ function readRoleDefinitions(dirs, extension = false) {
3085
3156
  }));
3086
3157
  return extension ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
3087
3158
  }
3159
+ function discoveryInput(input) {
3160
+ return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir2(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
3161
+ }
3162
+ function discoveredRoleEntries(input) {
3163
+ const resolved = discoveryInput(input);
3164
+ return {
3165
+ ...roleEntries(resolved.extensionRoleDirectories, true),
3166
+ ...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
3167
+ ...resolved.projectTrusted ? roleEntries(projectRoleDirectories(join6(resolved.cwd, ".pi")), false) : {}
3168
+ };
3169
+ }
3088
3170
  function loadProjectAgentDefinitions(cwd) {
3089
- return readRoleDefinitions(projectRoleDirectories(join5(cwd, ".pi")));
3171
+ return deepFreeze(Object.fromEntries(Object.entries(roleEntries(projectRoleDirectories(join6(cwd, ".pi")), false)).map(([name, entry]) => [name, entry.definition])));
3172
+ }
3173
+ function discoverRoles(input) {
3174
+ return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
3175
+ }
3176
+ function loadRole(name, input) {
3177
+ const entry = discoveredRoleEntries(input)[name];
3178
+ if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
3179
+ return entry.definition;
3180
+ }
3181
+ function loadAgentDefinitions(cwd, agentDir = getAgentDir2(), projectTrusted2 = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
3182
+ return discoverRoles({ cwd, agentDir, projectTrusted: projectTrusted2, extensionRoleDirectories });
3183
+ }
3184
+ function canonicalExtensionSelector(selector, base = process.cwd()) {
3185
+ const negated = selector.startsWith("!");
3186
+ const body = negated ? selector.slice(1) : selector;
3187
+ if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
3188
+ const resolved = resolve4(base, body);
3189
+ if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
3190
+ return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
3191
+ }
3192
+ function selectorsFromSettings2(settings) {
3193
+ return { ...settings.skills === void 0 ? {} : { skills: settings.skills }, ...settings.extensions === void 0 ? {} : { extensions: settings.extensions }, ...settings.tools === void 0 ? {} : { tools: settings.tools } };
3194
+ }
3195
+ function rootToolNames(value) {
3196
+ return value === void 0 ? void 0 : value instanceof Set ? [...value] : [...value];
3197
+ }
3198
+ function roleDefinitionFor(name, options) {
3199
+ if (name === void 0) return {};
3200
+ if (options.definition !== void 0) {
3201
+ const path = roleDefinitionPaths.get(options.definition);
3202
+ return { definition: options.definition, ...path === void 0 ? {} : { path } };
3203
+ }
3204
+ if (options.definitions?.[name] !== void 0) {
3205
+ const definition = options.definitions[name];
3206
+ const path = roleDefinitionPaths.get(definition);
3207
+ return { definition, ...path === void 0 ? {} : { path } };
3208
+ }
3209
+ const entries = discoveredRoleEntries(options);
3210
+ const entry = entries[name];
3211
+ if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
3212
+ return entry;
3213
+ }
3214
+ function resolveRole(name, options) {
3215
+ const { definition, path } = roleDefinitionFor(name, options);
3216
+ const settings = options.selectorSources === void 0 ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir2())) : void 0;
3217
+ const baseSources = options.selectorSources ?? { global: selectorsFromSettings2(settings?.global ?? {}), project: selectorsFromSettings2(settings?.project ?? {}) };
3218
+ const canonicalSources = (sources) => ({
3219
+ ...sources,
3220
+ global: { ...sources.global, ...sources.global.extensions === void 0 ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
3221
+ project: { ...sources.project, ...sources.project.extensions === void 0 ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
3222
+ ...sources.role === void 0 ? {} : { role: { ...sources.role, ...sources.role.extensions === void 0 ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } },
3223
+ ...sources.call === void 0 ? {} : { call: { ...sources.call, ...sources.call.extensions === void 0 ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } }
3224
+ });
3225
+ const roleLayer = definition?.skills !== void 0 || definition?.extensions !== void 0 || definition?.tools !== void 0 ? { ...definition.skills === void 0 ? {} : { skills: definition.skills }, ...definition.extensions === void 0 ? {} : { extensions: definition.extensions.map((selector) => canonicalExtensionSelector(selector, path ? dirname4(path) : options.cwd)) }, ...definition.tools === void 0 ? {} : { tools: definition.tools } } : baseSources.role;
3226
+ const callLayer = options.skills !== void 0 || options.extensions !== void 0 || options.tools !== void 0 ? { ...options.skills === void 0 ? {} : { skills: options.skills }, ...options.extensions === void 0 ? {} : { extensions: options.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }, ...options.tools === void 0 ? {} : { tools: options.tools } } : baseSources.call;
3227
+ const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...roleLayer === void 0 ? {} : { role: roleLayer }, ...callLayer === void 0 ? {} : { call: callLayer } });
3228
+ const callSelectors = selectorSources.call;
3229
+ const roleSelectors = selectorSources.role;
3230
+ const selectorLayers = {
3231
+ skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
3232
+ extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
3233
+ tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools]
3234
+ };
3235
+ const resources = options.resources ?? {};
3236
+ const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
3237
+ const tools = options.effectiveTools ?? (candidateTools === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.tools, candidateTools));
3238
+ const rootTools = rootToolNames(options.rootTools);
3239
+ if (rootTools !== void 0) {
3240
+ const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
3241
+ if (outsideEffectiveTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
3242
+ const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
3243
+ if (outsideCallTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
3244
+ const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
3245
+ if (outsideRootTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
3246
+ }
3247
+ const selectedSkills = resources.skills === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.skills, resources.skills);
3248
+ const extensionResources = resources.extensions?.map((extension) => extensionIdentity(extension));
3249
+ const selectedExtensions = extensionResources === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
3250
+ const unmatchedSkills = resources.skills === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
3251
+ const unmatchedExtensions = extensionResources === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
3252
+ const unmatchedTools = candidateTools === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
3253
+ const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
3254
+ const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
3255
+ const blockedAlias = requestedModel?.split(":", 1)[0];
3256
+ if (requestedModel !== void 0 && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
3257
+ const target = options.blockedAliasTargets?.[blockedAlias];
3258
+ fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
3259
+ }
3260
+ let model = options.modelOverride;
3261
+ if (model === void 0 && requestedModel !== void 0) {
3262
+ assertModelThinking(requestedModel);
3263
+ model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
3264
+ }
3265
+ if (model === void 0) model = options.rootModel;
3266
+ const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === void 0 ? void 0 : /* @__PURE__ */ new Set([modelCapability(options.rootModel)]));
3267
+ if (model !== void 0 && availableModels !== void 0 && !availableModels.has(modelCapability(model))) fail("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
3268
+ const prompt = definition?.prompt ?? "";
3269
+ const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
3270
+ const contextFiles = options.contextFiles ?? definition?.contextFiles;
3271
+ return { ...name === void 0 ? {} : { name }, ...definition === void 0 ? {} : { definition }, ...model === void 0 ? {} : { model }, ...modelAliasName(requestedModel ?? "", aliases) && requestedModel ? { requestedModel } : {}, ...tools === void 0 ? {} : { tools }, prompt, overrideSystemPrompt, systemPrompt: { mode: overrideSystemPrompt ? "override" : "append", text: prompt }, ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] }, ...definition?.extensionSettings === void 0 ? {} : { extensionSettings: definition.extensionSettings }, selectorSources, selectorLayers, ...selectedSkills === void 0 ? {} : { selectedSkills }, ...selectedExtensions === void 0 ? {} : { selectedExtensions }, ...unmatchedSkills === void 0 ? {} : { unmatchedSkills }, ...unmatchedExtensions === void 0 ? {} : { unmatchedExtensions }, ...unmatchedTools === void 0 ? {} : { unmatchedTools } };
3090
3272
  }
3091
- function loadAgentDefinitions(cwd, agentDir = getAgentDir(), projectTrusted2 = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
3092
- return deepFreeze({ ...readRoleDefinitions(extensionRoleDirectories, true), ...readRoleDefinitions(workflowRoleDirectories(agentDir)), ...projectTrusted2 ? readRoleDefinitions(projectRoleDirectories(join5(cwd, ".pi"))) : {} });
3273
+
3274
+ // src/validation.ts
3275
+ function validateCheckpoint(value) {
3276
+ if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
3277
+ if (Buffer.byteLength(value.prompt) > 1024) fail("INVALID_METADATA", "checkpoint prompt exceeds 1024 UTF-8 bytes");
3278
+ if (Buffer.byteLength(JSON.stringify(value.context)) > 4096) fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
3279
+ return { name: value.name, prompt: value.prompt, context: value.context };
3093
3280
  }
3094
3281
  function validateRolePolicies(definitions, roles, availableModels, aliases = {}, knownModels = availableModels, settingsPath) {
3095
3282
  for (const role of roles) {
@@ -3278,6 +3465,19 @@ function callHasTrailingComma(source, call) {
3278
3465
  }
3279
3466
  return current?.type.label === ")" && previous?.type.label === ",";
3280
3467
  }
3468
+ function scriptToolReferences(script) {
3469
+ const body = workflowBody(script);
3470
+ if (!body.trim()) return [];
3471
+ const names = /* @__PURE__ */ new Set();
3472
+ const opaque = (node, parent) => {
3473
+ if (node.type === "Identifier" && node.name === "tools") {
3474
+ if (parent?.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier") names.add(parent.property.name);
3475
+ else if (!(parent?.type === "Property" && parent.key === node && !parent.computed && !parent.shorthand || parent?.type === "MemberExpression" && parent.property === node && !parent.computed)) return true;
3476
+ }
3477
+ return astChildren(node).some((child) => opaque(child, node));
3478
+ };
3479
+ return opaque(parseWorkflow(body)) ? void 0 : [...names];
3480
+ }
3281
3481
  function instrumentWorkflow(script) {
3282
3482
  const body = workflowBody(script);
3283
3483
  if (!body.trim()) return body;
@@ -3346,12 +3546,6 @@ function workflowPrompt(template, values) {
3346
3546
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
3347
3547
  });
3348
3548
  }
3349
- function validateSchema(schema, at = "schema") {
3350
- if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
3351
- 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`);
3352
- 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`);
3353
- if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
3354
- }
3355
3549
  var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
3356
3550
  function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
3357
3551
  switch (key) {
@@ -3597,7 +3791,7 @@ function validateWorkflowLaunchWithRegistry(params, context, registry) {
3597
3791
  let fileScript;
3598
3792
  if (scriptPath !== void 0) {
3599
3793
  try {
3600
- fileScript = readFileSync2(resolve3(context.cwd, scriptPath), "utf8");
3794
+ fileScript = readFileSync4(resolve5(context.cwd, scriptPath), "utf8");
3601
3795
  } catch (error) {
3602
3796
  fail("INVALID_SYNTAX", `Cannot read workflow script file ${scriptPath}: ${errorText(error)}`);
3603
3797
  }
@@ -3631,7 +3825,7 @@ function validateWorkflowLaunchWithRegistry(params, context, registry) {
3631
3825
  import { fork, spawn } from "node:child_process";
3632
3826
  import { mkdtempSync, realpathSync as realpathSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
3633
3827
  import { tmpdir } from "node:os";
3634
- import { join as join6 } from "node:path";
3828
+ import { join as join7 } from "node:path";
3635
3829
  import { StringDecoder } from "node:string_decoder";
3636
3830
  var RPC_LIMIT_BYTES = 10 * 1024 * 1024;
3637
3831
  var HEARTBEAT_TIMEOUT_MS = 5e3;
@@ -3889,6 +4083,30 @@ const functions = Object.freeze(Object.fromEntries(Object.entries(config.functio
3889
4083
  Object.defineProperty(result, "toJSON", { value() { throw workError("INVALID_METADATA", "Workflow function result is a Promise; await it before serialization"); } });
3890
4084
  return result;
3891
4085
  }])));
4086
+ // tools.<id>(args) runs a Pi session tool through the host. Any identifier resolves, so a resumed run
4087
+ // replays journaled calls even when the tool is gone; the host rejects identifiers it cannot call.
4088
+ // A call is the n-th call with the same arguments in its scope, so replay does not depend on the
4089
+ // order in which concurrent calls start.
4090
+ const NOT_TOOLS = new Set(["then", "toJSON", "toString", "valueOf", "constructor"]);
4091
+ const sortedJson = value => Array.isArray(value) ? value.map(sortedJson) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(key => [key, sortedJson(value[key])])) : value;
4092
+ const toolOccurrences = new Map();
4093
+ const toolCall = id => (...values) => {
4094
+ if (worktreeOwners.getStore()) throw workError("INVALID_METADATA", "tools." + id + " is unavailable inside withWorktree: session tools resolve paths against the session working directory");
4095
+ if (values.length > 1 || values[0] !== undefined && (!values[0] || typeof values[0] !== "object" || Array.isArray(values[0]))) throw workError("INVALID_METADATA", "tools." + id + " takes one arguments object");
4096
+ const args = values[0] === undefined ? {} : values[0];
4097
+ const structuralPath = inheritedAgentPath.getStore() || [];
4098
+ const key = JSON.stringify([structuralPath, id, sortedJson(args)]);
4099
+ const occurrence = (toolOccurrences.get(key) || 0) + 1;
4100
+ toolOccurrences.set(key, occurrence);
4101
+ return guardedResult(rpc("tool", [id, args, { structuralPath: [...structuralPath], occurrence }]), "tool");
4102
+ };
4103
+ const tools = new Proxy(Object.create(null), {
4104
+ get: (_target, key) => typeof key === "string" && !NOT_TOOLS.has(key) ? toolCall(key) : undefined,
4105
+ // Probing would branch on the session that resumed the run; the launch already checks named tools.
4106
+ has: () => { throw workError("INVALID_METADATA", "Workflow scripts cannot probe tools with 'in'; call tools.<name>(...) directly, and the launch checks that the tool exists"); },
4107
+ set: () => false,
4108
+ defineProperty: () => false,
4109
+ });
3892
4110
  const recordEntries = (value, kind) => {
3893
4111
  if (!value || typeof value !== "object" || Array.isArray(value)) throw workError("INVALID_METADATA", kind + " must be a record");
3894
4112
  return Object.entries(value);
@@ -3949,7 +4167,7 @@ const pipeline = async (operationName, items, stages) => {
3949
4167
  return Object.fromEntries(results.map(result => [result.name, result.value]));
3950
4168
  };
3951
4169
  const safeMath = Object.fromEntries(Object.getOwnPropertyNames(Math).filter(name => name !== "random").map(name => [name, Math[name]]));
3952
- const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
4170
+ const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, tools, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
3953
4171
  for (const [name, fn] of Object.entries(functions)) Object.defineProperty(sandbox, name, { value: fn, writable: false, configurable: false });
3954
4172
  for (const name of ["Date","eval","Function","WebAssembly","process","require","module","exports","console","fetch","XMLHttpRequest","WebSocket","performance","crypto","setTimeout","setInterval","setImmediate","queueMicrotask","Intl","SharedArrayBuffer","Atomics"]) sandbox[name] = undefined;
3955
4173
  const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
@@ -3993,6 +4211,13 @@ function readShellIdentity(value) {
3993
4211
  const identity = readAgentIdentity(value);
3994
4212
  return { structuralPath: identity.structuralPath, callSite: identity.callSite, occurrence: identity.occurrence, ...identity.worktreeOwner ? { worktreeOwner: identity.worktreeOwner } : {} };
3995
4213
  }
4214
+ function readToolIdentity(value) {
4215
+ if (!object(value) || !Array.isArray(value.structuralPath) || !value.structuralPath.every((part) => typeof part === "string" && Boolean(part.trim())) || !positiveInteger(value.occurrence)) fail("INTERNAL_ERROR", "Invalid workflow tool identity");
4216
+ return { structuralPath: [...value.structuralPath], occurrence: value.occurrence };
4217
+ }
4218
+ function toolIdentityPath(identifier, args, identity) {
4219
+ return structuralPath("tool", ...identity.structuralPath, identifier, `args:${jsonDigest(args)}`, String(identity.occurrence));
4220
+ }
3996
4221
  function agentHandleTurnPath(handle, turn) {
3997
4222
  return structuralPath("agent", "handle", handle, `turn:${String(turn)}`);
3998
4223
  }
@@ -4037,7 +4262,7 @@ function shellProcessKill(child) {
4037
4262
  forceKill.unref();
4038
4263
  }
4039
4264
  function executeShellCommand(command, options, signal, cwd = process.cwd()) {
4040
- return new Promise((resolve7, reject) => {
4265
+ return new Promise((resolve9, reject) => {
4041
4266
  let child;
4042
4267
  try {
4043
4268
  child = spawn(command, { shell: true, cwd, env: { ...process.env, ...options.env ?? {} }, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"] });
@@ -4107,7 +4332,7 @@ function executeShellCommand(command, options, signal, cwd = process.cwd()) {
4107
4332
  reject(error instanceof WorkflowError ? error : new WorkflowError("RPC_LIMIT_EXCEEDED", errorText(error)));
4108
4333
  return;
4109
4334
  }
4110
- resolve7(result);
4335
+ resolve9(result);
4111
4336
  });
4112
4337
  if (signal.aborted) {
4113
4338
  onAbort();
@@ -4131,8 +4356,8 @@ function workflowErrorFromWorker(error) {
4131
4356
  function runWorkflow(script, args = null, bridge = {}, signal) {
4132
4357
  encoded(args);
4133
4358
  const config = JSON.stringify({ script: instrumentWorkflow(script), args: structuredClone(args), functions: bridge.functions ?? {} });
4134
- const childDir = realpathSync2(mkdtempSync(join6(tmpdir(), "pi-wf-")));
4135
- const childFile = join6(childDir, "child.cjs");
4359
+ const childDir = realpathSync2(mkdtempSync(join7(tmpdir(), "pi-wf-")));
4360
+ const childFile = join7(childDir, "child.cjs");
4136
4361
  writeFileSync2(childFile, childSource);
4137
4362
  const child = fork(childFile, [String(RPC_LIMIT_BYTES), config], {
4138
4363
  execArgv: (() => {
@@ -4172,7 +4397,7 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
4172
4397
  }
4173
4398
  if (now - lastHeartbeatAt >= HEARTBEAT_TIMEOUT_MS) stop("WORKER_UNRESPONSIVE", "Workflow worker missed its five-second heartbeat");
4174
4399
  }, HEARTBEAT_INTERVAL_MS);
4175
- const result = new Promise((resolve7, reject) => {
4400
+ const result = new Promise((resolve9, reject) => {
4176
4401
  rejectResult = reject;
4177
4402
  child.on("message", (raw) => {
4178
4403
  try {
@@ -4187,7 +4412,7 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
4187
4412
  if (message.type === "result") {
4188
4413
  encoded(message.value);
4189
4414
  finish();
4190
- resolve7(message.value);
4415
+ resolve9(message.value);
4191
4416
  return;
4192
4417
  }
4193
4418
  if (message.type === "error") {
@@ -4285,6 +4510,11 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
4285
4510
  if (!OUTCOME_ERRORS.has(typed.code)) throw typed;
4286
4511
  value = branded({ name, ok: false, failedAt: name, error: workerErrorShape(typed) });
4287
4512
  }
4513
+ } else if (method === "tool") {
4514
+ if (typeof values[0] !== "string" || !object(values[1])) fail("INTERNAL_ERROR", "tool requires an identifier and object arguments");
4515
+ const identity = readToolIdentity(values[2]);
4516
+ if (!bridge.tool) fail("UNKNOWN_TOOL", `tools.${values[0]} is unavailable: this run has no session tools`);
4517
+ value = await bridge.tool(values[0], values[1], controller.signal, identity);
4288
4518
  } else if (method === "worktree") {
4289
4519
  if (!bridge.worktree || typeof values[0] !== "string" || !values[0]) fail("INTERNAL_ERROR", "worktree requires an active host bridge and scope");
4290
4520
  value = await bridge.worktree(values[0], controller.signal);
@@ -4343,20 +4573,20 @@ async function persistAgentAttempts(store, id, attempts) {
4343
4573
  import { randomUUID as randomUUID5 } from "node:crypto";
4344
4574
  import { existsSync as existsSync2 } from "node:fs";
4345
4575
  import { copyFile as copyFile2, mkdir as mkdir4 } from "node:fs/promises";
4346
- import { dirname as dirname7, join as join15 } from "node:path";
4576
+ import { dirname as dirname8, join as join16 } from "node:path";
4347
4577
  import { fileURLToPath as fileURLToPath5 } from "node:url";
4348
4578
  import { Type as Type3 } from "@earendil-works/pi-ai";
4349
- import { copyToClipboard, getAgentDir as getAgentDir4, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
4579
+ import { copyToClipboard, getAgentDir as getAgentDir5, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
4350
4580
 
4351
4581
  // src/agent-execution.ts
4352
- import { existsSync, realpathSync as realpathSync3, readFileSync as readFileSync3 } from "node:fs";
4582
+ import { existsSync, realpathSync as realpathSync3, readFileSync as readFileSync5 } from "node:fs";
4353
4583
  import { copyFile } from "node:fs/promises";
4354
4584
  import { homedir as homedir5 } from "node:os";
4355
- import { basename as basename4, dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
4585
+ import { basename as basename4, dirname as dirname5, join as join8, resolve as resolve6 } from "node:path";
4356
4586
  import { fileURLToPath as fileURLToPath3 } from "node:url";
4357
4587
  import { Type } from "@earendil-works/pi-ai";
4358
4588
  import { Compile } from "typebox/compile";
4359
- import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir2, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
4589
+ import { createAgentSession, createCodemodeExtension, createMcpExtension, createToolSearchExtension, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir3, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
4360
4590
 
4361
4591
  // src/pi-runtime-adapter.ts
4362
4592
  var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
@@ -4631,8 +4861,8 @@ function createLiveSessionHandoff() {
4631
4861
  let request;
4632
4862
  const waitForBoundary = () => {
4633
4863
  if (!turnActive) return Promise.resolve();
4634
- boundary ??= new Promise((resolve7) => {
4635
- resolveBoundary = resolve7;
4864
+ boundary ??= new Promise((resolve9) => {
4865
+ resolveBoundary = resolve9;
4636
4866
  });
4637
4867
  return boundary;
4638
4868
  };
@@ -4664,11 +4894,11 @@ function createLiveSessionHandoff() {
4664
4894
  if (request) return request;
4665
4895
  if (isReleased()) return;
4666
4896
  state = "handoff-pending";
4667
- takeover = new Promise((resolve7) => {
4668
- resolveTakeover = resolve7;
4897
+ takeover = new Promise((resolve9) => {
4898
+ resolveTakeover = resolve9;
4669
4899
  });
4670
- resume = new Promise((resolve7) => {
4671
- resolveResume = resolve7;
4900
+ resume = new Promise((resolve9) => {
4901
+ resolveResume = resolve9;
4672
4902
  });
4673
4903
  request = (async () => {
4674
4904
  await waitForBoundary();
@@ -5195,36 +5425,37 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
5195
5425
  function accounting(stats) {
5196
5426
  return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
5197
5427
  }
5198
- var extensionDirectory = dirname4(fileURLToPath3(import.meta.url));
5199
- var workflowPackageRoot = basename4(dirname4(extensionDirectory)) === "dist" ? resolve4(extensionDirectory, "../..") : resolve4(extensionDirectory, "..");
5200
- var WORKFLOW_HOST_ENTRIES = /* @__PURE__ */ new Set([
5201
- canonicalPath(resolve4(workflowPackageRoot, "src/index.ts")),
5202
- canonicalPath(resolve4(workflowPackageRoot, "dist/src/index.js")),
5203
- canonicalPath(resolve4(workflowPackageRoot, "starter/index.ts")),
5204
- canonicalPath(resolve4(workflowPackageRoot, "dist/starter/index.js")),
5205
- canonicalPath(resolve4(workflowPackageRoot, "subagents/index.ts")),
5206
- canonicalPath(resolve4(workflowPackageRoot, "dist/subagents/index.js"))
5207
- ]);
5208
- function canonicalExtensionSelector(selector, base = process.cwd()) {
5209
- const negated = selector.startsWith("!");
5210
- const body = negated ? selector.slice(1) : selector;
5211
- if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
5212
- const resolved = resolve4(base, body);
5213
- if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
5214
- return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
5428
+ var WORKFLOW_HOST_ENTRY_PATHS = ["src/index.ts", "dist/src/index.js", "starter/index.ts", "dist/starter/index.js", "subagents/index.ts", "dist/subagents/index.js"];
5429
+ function isWorkflowHostEntry(path) {
5430
+ const entryPath = canonicalPath(path);
5431
+ let packageRoot2 = dirname5(entryPath);
5432
+ while (packageRoot2 !== dirname5(packageRoot2)) {
5433
+ const packageMetadataPath = join8(packageRoot2, "package.json");
5434
+ if (existsSync(packageMetadataPath)) {
5435
+ try {
5436
+ const metadata = JSON.parse(readFileSync5(packageMetadataPath, "utf8"));
5437
+ if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !isCorePackageName(metadata.name)) return false;
5438
+ return WORKFLOW_HOST_ENTRY_PATHS.some((entry) => canonicalPath(join8(packageRoot2, entry)) === entryPath);
5439
+ } catch {
5440
+ return false;
5441
+ }
5442
+ }
5443
+ packageRoot2 = dirname5(packageRoot2);
5444
+ }
5445
+ return false;
5215
5446
  }
5216
5447
  var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
5217
5448
  function workflowSystemPromptPath(cwd, agentDir, projectTrusted2) {
5218
- const projectPath = join7(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
5449
+ const projectPath = join8(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
5219
5450
  if (projectTrusted2 && existsSync(projectPath)) return projectPath;
5220
- const globalPaths = [join7(homedir5(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join7(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
5451
+ const globalPaths = [join8(homedir5(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join8(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
5221
5452
  return globalPaths.find((path) => existsSync(path));
5222
5453
  }
5223
5454
  function filterContextFiles(base, scopes, cwd, agentDir) {
5224
5455
  const globalPath = canonicalPath(agentDir);
5225
5456
  const cwdPath = canonicalPath(cwd);
5226
5457
  return base.filter(({ path }) => {
5227
- const resolvedPath = canonicalPath(dirname4(path));
5458
+ const resolvedPath = canonicalPath(dirname5(path));
5228
5459
  return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
5229
5460
  });
5230
5461
  }
@@ -5245,33 +5476,40 @@ function loadPiPackageDirectory() {
5245
5476
  }
5246
5477
  }).catch((error) => ({ error: `could not load @earendil-works/pi-coding-agent package metadata: ${errorText(error)}` }));
5247
5478
  }
5248
- var nativePromptTemplateModule;
5249
- async function loadNativePromptTemplateModule() {
5479
+ var nativeCoreModules = /* @__PURE__ */ new Map();
5480
+ async function loadNativeCoreModule(file, label) {
5250
5481
  const packageDirectory = await loadPiPackageDirectory();
5251
- let promptTemplatePath;
5482
+ let modulePath;
5252
5483
  if (packageDirectory.directory) {
5253
- const candidate = resolve4(packageDirectory.directory, "dist/core/prompt-templates.js");
5254
- if (existsSync(candidate)) promptTemplatePath = candidate;
5484
+ const candidate = resolve6(packageDirectory.directory, "dist/core", file);
5485
+ if (existsSync(candidate)) modulePath = candidate;
5255
5486
  }
5256
- promptTemplatePath ??= resolve4(dirname4(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
5487
+ modulePath ??= resolve6(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core", file);
5257
5488
  const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
5258
- return nativePromptTemplateModule ??= import(promptTemplatePath).then((module) => module).catch((error) => {
5259
- throw new Error(`Could not load Pi prompt templates.${packageDirectoryError}`, { cause: error });
5260
- });
5489
+ let loaded = nativeCoreModules.get(file);
5490
+ if (!loaded) {
5491
+ loaded = import(modulePath).catch((error) => {
5492
+ throw new Error(`Could not load Pi ${label}.${packageDirectoryError}`, { cause: error });
5493
+ });
5494
+ nativeCoreModules.set(file, loaded);
5495
+ }
5496
+ return loaded;
5261
5497
  }
5262
5498
  async function expandPromptTemplateForInspection(text, templates) {
5263
- return (await loadNativePromptTemplateModule()).expandPromptTemplate(text, templates);
5499
+ return (await loadNativeCoreModule("prompt-templates.js", "prompt templates")).expandPromptTemplate(text, templates);
5500
+ }
5501
+ async function buildSystemPromptForInspection(options) {
5502
+ return (await loadNativeCoreModule("system-prompt.js", "system prompt builder")).buildSystemPrompt(options);
5264
5503
  }
5265
5504
  async function preparePiPrompt(native, text) {
5266
5505
  const session = native;
5267
5506
  const diagnostics = [];
5268
5507
  const runner = session._extensionRunner;
5269
- const baseSystemPrompt = typeof session._baseSystemPrompt === "string" ? session._baseSystemPrompt : session.systemPrompt ?? "";
5508
+ const baseSystemPrompt = session.systemPrompt ?? "";
5270
5509
  const baseOptions = session._baseSystemPromptOptions;
5271
5510
  const expandSkillCommand = session._expandSkillCommand;
5272
5511
  const templates = session.promptTemplates;
5273
5512
  if (!runner) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: extension runner is missing", source: "Pi session" });
5274
- if (typeof session._baseSystemPrompt !== "string") diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: base system prompt is missing", source: "Pi session" });
5275
5513
  if (baseOptions === void 0) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: base system prompt options are missing", source: "Pi session" });
5276
5514
  if (typeof expandSkillCommand !== "function") diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: skill expansion is missing", source: "Pi session" });
5277
5515
  if (!Array.isArray(templates)) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: prompt templates are missing", source: "Pi session" });
@@ -5296,9 +5534,11 @@ async function preparePiPrompt(native, text) {
5296
5534
  diagnostics.push({ type: "error", message: `Pi prompt template expansion is unavailable: ${errorText(error)}`, source: "Pi session" });
5297
5535
  }
5298
5536
  }
5299
- const result = !inputHandled && runner && baseOptions !== void 0 ? await runner.emitBeforeAgentStart(expandedPrompt, void 0, baseSystemPrompt, baseOptions) : void 0;
5537
+ const result = !inputHandled && runner && baseOptions !== void 0 ? await runner.emitBeforeAgentStart(expandedPrompt, void 0, baseOptions) : void 0;
5300
5538
  const prepared = result;
5301
- return { prompt: text, expandedPrompt, systemPrompt: typeof prepared?.systemPrompt === "string" ? prepared.systemPrompt : baseSystemPrompt, systemPromptOptions: baseOptions, messages: prepared?.messages ?? [], inputHandled, diagnostics };
5539
+ const systemPromptOptions = prepared?.systemPromptOptions ?? baseOptions;
5540
+ const systemPrompt = prepared?.systemPromptOptions === void 0 ? baseSystemPrompt : await buildSystemPromptForInspection(prepared.systemPromptOptions);
5541
+ return { prompt: text, expandedPrompt, systemPrompt, systemPromptOptions, messages: prepared?.messages ?? [], inputHandled, diagnostics };
5302
5542
  } finally {
5303
5543
  unsubscribe?.();
5304
5544
  }
@@ -5327,40 +5567,56 @@ function flushExtensionProviders(resourceLoader, modelRuntime) {
5327
5567
  runtime.pendingNativeProviderRegistrations = [];
5328
5568
  return failures;
5329
5569
  }
5570
+ function builtinExtensions() {
5571
+ return [
5572
+ { name: "codemode", factory: createCodemodeExtension(), replaceable: true, builtin: true },
5573
+ { name: "tool-search", factory: createToolSearchExtension(), replaceable: true, builtin: true },
5574
+ { name: "mcp", factory: createMcpExtension(), replaceable: true, builtin: true }
5575
+ ];
5576
+ }
5577
+ var BUILTIN_EXTENSION_PREFIX = "builtin:";
5578
+ function isLlamaModel(model) {
5579
+ return model.provider === "llama.cpp";
5580
+ }
5330
5581
  async function createLocalPiSessionHandle(input, sessionStartEvent) {
5331
- const agentDir = input.agentDir ?? getAgentDir2();
5582
+ const agentDir = input.agentDir ?? getAgentDir3();
5332
5583
  const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
5333
5584
  const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
5334
5585
  const contextFilesOverride = input.contextFiles === void 0 ? void 0 : (base) => ({ ...base, agentsFiles: filterContextFiles(base.agentsFiles, input.contextFiles ?? [], input.cwd, agentDir) });
5335
- const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join7(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join7(agentDir, "sessions")) : SessionManager.create(input.cwd));
5586
+ const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join8(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join8(agentDir, "sessions")) : SessionManager.create(input.cwd));
5336
5587
  if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
5337
- const modelRuntime = await ModelRuntime.create({ authPath: join7(agentDir, "auth.json"), modelsPath: join7(agentDir, "models.json") });
5588
+ const modelRuntime = await ModelRuntime.create({ authPath: join8(agentDir, "auth.json"), modelsPath: join8(agentDir, "models.json") });
5338
5589
  const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
5339
5590
  const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
5340
5591
  let settingsManager;
5341
5592
  let resourceLoader;
5593
+ const builtins = builtinExtensions();
5594
+ const builtinNames = builtins.flatMap((extension) => typeof extension === "function" ? [] : [extension.name]);
5595
+ const extensionFactories = [...builtins, ...input.extensionFactories ?? []];
5342
5596
  const policy = input.resourcePolicy;
5343
5597
  if (policy) {
5344
5598
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: false });
5345
5599
  settingsManager.setProjectTrusted(policy.projectTrusted);
5346
- const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
5600
+ const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager, builtinExtensions: builtinNames });
5347
5601
  const resolved = await packageManager.resolve();
5348
- const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
5602
+ const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => extensionIdentity(path)))];
5349
5603
  const selectorSources = policy.selectorSources;
5350
- const extensionLayers = [selectorSources.global.extensions, selectorSources.project.extensions, selectorSources.role?.extensions, selectorSources.call?.extensions];
5351
- const extensionSelectors = extensionLayers.flatMap((layer) => (layer ?? []).map((original) => ({ original, matching: canonicalExtensionSelector(original, input.cwd) })));
5352
- const selectedExtensions = selectResourcesByLayers(extensionLayers.map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, input.cwd))), discoveredExtensions);
5353
- const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
5354
- const unmatchedExtensions = extensionSelectors.filter(({ matching }) => unmatchedResourcePatterns([matching], discoveredExtensions).length > 0).map(({ original }) => original);
5604
+ const selection = resolveRole(void 0, {
5605
+ cwd: input.cwd,
5606
+ selectorSources,
5607
+ resources: { extensions: discoveredExtensions }
5608
+ });
5609
+ const selectedExtensions = selection.selectedExtensions ?? [];
5610
+ const extensionPaths = selectedExtensions.filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path));
5355
5611
  policy.selectedExtensions = selectedExtensions;
5356
- policy.unmatchedExtensions = unmatchedExtensions;
5612
+ policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
5357
5613
  const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
5358
5614
  const updateSkillMatches = (skills) => {
5359
5615
  const names = [...new Set(skills.map(({ name }) => name))];
5360
- const skillLayers = [selectorSources.global.skills, selectorSources.project.skills, selectorSources.role?.skills, selectorSources.call?.skills];
5361
- const selectedSkills = selectResourcesByLayers(skillLayers, names);
5616
+ const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
5617
+ const selectedSkills = selected.selectedSkills ?? [];
5362
5618
  policy.selectedSkills = selectedSkills;
5363
- policy.unmatchedSkills = unmatchedResourcePatterns(skillLayers.flatMap((layer) => layer ?? []), names);
5619
+ policy.unmatchedSkills = selected.unmatchedSkills ?? [];
5364
5620
  return new Set(names.filter((name) => !selectedSkills.includes(name)));
5365
5621
  };
5366
5622
  resourceLoader = new DefaultResourceLoader({
@@ -5371,7 +5627,7 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
5371
5627
  additionalExtensionPaths: extensionPaths,
5372
5628
  noSkills: true,
5373
5629
  additionalSkillPaths: [.../* @__PURE__ */ new Set([...skillPaths, ...input.additionalSkillPaths ?? []])],
5374
- ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {},
5630
+ extensionFactories,
5375
5631
  ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {},
5376
5632
  skillsOverride: (base) => {
5377
5633
  const disabledSkills = updateSkillMatches(base.skills);
@@ -5383,17 +5639,17 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
5383
5639
  await resourceLoader.reload();
5384
5640
  } else {
5385
5641
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: true });
5386
- const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
5642
+ const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager, builtinExtensions: builtinNames });
5387
5643
  const resolved = await packageManager.resolve();
5388
- const extensionPaths = [...new Set(resolved.extensions.filter(({ enabled }) => enabled).map(({ path }) => canonicalPath(path)).filter((path) => !WORKFLOW_HOST_ENTRIES.has(path)))];
5389
- resourceLoader = new DefaultResourceLoader({ cwd: input.cwd, agentDir, settingsManager, noExtensions: true, additionalExtensionPaths: extensionPaths, ...input.additionalSkillPaths?.length ? { additionalSkillPaths: [...input.additionalSkillPaths] } : {}, ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}, ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {}, ...systemPromptOptions, ...input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {} });
5644
+ const extensionPaths = [...new Set(resolved.extensions.filter(({ enabled }) => enabled).map(({ path }) => extensionIdentity(path)).filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path)))];
5645
+ resourceLoader = new DefaultResourceLoader({ cwd: input.cwd, agentDir, settingsManager, noExtensions: true, additionalExtensionPaths: extensionPaths, ...input.additionalSkillPaths?.length ? { additionalSkillPaths: [...input.additionalSkillPaths] } : {}, extensionFactories, ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {}, ...systemPromptOptions, ...input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {} });
5390
5646
  await resourceLoader.reload();
5391
5647
  }
5392
5648
  const providerFailures = flushExtensionProviders(resourceLoader, modelRuntime);
5393
5649
  await modelRuntime.refresh({ allowNetwork: false });
5394
5650
  const model = modelRuntime.getModel(input.model.provider, input.model.model);
5395
5651
  if (!model) {
5396
- const because = providerFailures.length > 0 ? ` (provider registration failed \u2014 ${providerFailures.join("; ")})` : "";
5652
+ const because = providerFailures.length > 0 ? ` (provider registration failed \u2014 ${providerFailures.join("; ")})` : isLlamaModel(input.model) ? " (Pi does not expose its built-in llama.cpp provider to extension sessions; declare the llama.cpp server as a provider in models.json and use that model instead)" : "";
5397
5653
  throw new WorkflowError("UNKNOWN_MODEL", `Unknown model: ${input.model.provider}/${input.model.model}${because}`);
5398
5654
  }
5399
5655
  const effectiveSessionStartEvent = input.settings === void 0 && sessionStartEvent === void 0 ? void 0 : { ...sessionStartEvent ?? { type: "session_start", reason: "startup" }, settings: input.settings ?? Object.freeze({}) };
@@ -5418,13 +5674,13 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
5418
5674
  await shutdown("quit").catch(() => void 0);
5419
5675
  throw error;
5420
5676
  }
5421
- const resourcePaths = { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => canonicalPath(resolvedPath)), skills: resourceLoader.getSkills().skills.map(({ filePath }) => canonicalPath(filePath)) };
5677
+ const resourcePaths = { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => extensionIdentity(resolvedPath)), skills: resourceLoader.getSkills().skills.map(({ filePath }) => canonicalPath(filePath)) };
5422
5678
  const resourceInspection = () => {
5423
5679
  const extensions = resourceLoader.getExtensions();
5424
5680
  const skills = resourceLoader.getSkills();
5425
5681
  const diagnostics = [...extensions.errors.map(({ path, error }) => ({ type: "error", message: error, source: path })), ...skills.diagnostics.map(({ type, path, message }) => ({ type, message, ...path ? { source: path } : {} }))];
5426
5682
  const systemSource = systemPromptSource;
5427
- return { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => canonicalPath(resolvedPath)), skills: skills.skills.map(({ name }) => name), diagnostics, ...systemSource ? { systemPromptSource: systemSource } : resourceLoader.getSystemPrompt() !== void 0 ? { systemPromptSource: "Pi resource loader" } : {} };
5683
+ return { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => extensionIdentity(resolvedPath)), skills: skills.skills.map(({ name }) => name), diagnostics, ...systemSource ? { systemPromptSource: systemSource } : resourceLoader.getSystemPrompt() !== void 0 ? { systemPromptSource: "Pi resource loader" } : {} };
5428
5684
  };
5429
5685
  const managedSession = Object.assign(session, {
5430
5686
  getLeafId: () => manager.getLeafId(),
@@ -5852,27 +6108,27 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
5852
6108
  };
5853
6109
  }
5854
6110
  function packageRoot(start) {
5855
- let current = dirname4(realpathSync3(start));
6111
+ let current = dirname5(realpathSync3(start));
5856
6112
  for (; ; ) {
5857
- const candidates = [current, join7(current, "..", "@earendil-works", "pi-coding-agent"), join7(current, "..", "pi-coding-agent")];
6113
+ const candidates = [current, join8(current, "..", "@earendil-works", "pi-coding-agent"), join8(current, "..", "pi-coding-agent")];
5858
6114
  for (const candidate of candidates) {
5859
- const packageJsonPath = join7(candidate, "package.json");
6115
+ const packageJsonPath = join8(candidate, "package.json");
5860
6116
  if (!existsSync(packageJsonPath)) continue;
5861
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
6117
+ const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
5862
6118
  if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
5863
6119
  }
5864
- const parent = dirname4(current);
6120
+ const parent = dirname5(current);
5865
6121
  if (parent === current) return void 0;
5866
6122
  current = parent;
5867
6123
  }
5868
6124
  }
5869
6125
  function piPackageEntrypoint(root) {
5870
- const packageJson = JSON.parse(readFileSync3(join7(root, "package.json"), "utf8"));
6126
+ const packageJson = JSON.parse(readFileSync5(join8(root, "package.json"), "utf8"));
5871
6127
  if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
5872
6128
  const bin = packageJson.bin;
5873
6129
  const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
5874
6130
  if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
5875
- const entrypoint = resolve4(root, cli);
6131
+ const entrypoint = resolve6(root, cli);
5876
6132
  if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
5877
6133
  return entrypoint;
5878
6134
  }
@@ -5913,7 +6169,7 @@ ${instruction}` : ""}`;
5913
6169
  }
5914
6170
  async function preparedAgentSession(input, initialPrompt) {
5915
6171
  const { runtime: piRuntime, error: piRuntimeError } = await piRuntimeResolution;
5916
- const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir2(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
6172
+ const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir3(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
5917
6173
  const prepared = {
5918
6174
  cwd: input.cwd,
5919
6175
  model: Object.freeze({ ...input.model }),
@@ -5943,7 +6199,7 @@ function agentSetupSummary(setup, hookNames) {
5943
6199
  return { hookNames: [...hookNames], model: { provider: model.provider, model: model.model, ...model.thinking ? { thinking: model.thinking } : {} }, tools: [...setup.sessionInput.tools, ...setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : []], cwd: setup.sessionInput.cwd, ...setup.sessionInput.resourcePolicy ? { resourceSelectors: resourcePolicySummary(setup.sessionInput.resourcePolicy, setup.sessionInput.tools) } : {} };
5944
6200
  }
5945
6201
  async function attemptSessionInput(source, attempt) {
5946
- const target = join7(dirname4(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
6202
+ const target = join8(dirname5(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
5947
6203
  await copyFile(source, target);
5948
6204
  return target;
5949
6205
  }
@@ -6053,7 +6309,6 @@ var WorkflowAgentExecutor = class {
6053
6309
  const roleName = typeof options.role === "string" ? options.role : void 0;
6054
6310
  const roleDefinition = roleName ? this.root.agentDefinitions?.[roleName] : void 0;
6055
6311
  if (roleName && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName}`);
6056
- const candidateTools = inheritedTools !== void 0 ? [...inheritedTools] : [...this.root.tools];
6057
6312
  if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
6058
6313
  const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
6059
6314
  if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
@@ -6064,33 +6319,30 @@ var WorkflowAgentExecutor = class {
6064
6319
  }
6065
6320
  }
6066
6321
  }
6067
- const selectorLayers = [
6068
- this.root.resourceSelectors?.tools,
6069
- roleDefinition?.tools,
6070
- options.tools
6071
- ];
6072
- for (const pattern of options.tools ?? []) {
6073
- const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
6074
- if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !candidateTools.includes(body)) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${body}`);
6075
- }
6076
- const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools.includes(tool));
6077
- if (outsideEffectiveTool) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
6078
- const requested = options.effectiveTools ?? selectResourcesByLayers(selectorLayers, candidateTools);
6079
- const forbidden = requested.find((tool) => !this.root.tools.has(tool));
6080
- if (forbidden) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${forbidden}`);
6081
- const requestedModel = options.model ?? (roleDefinition?.model && roleDefinition.thinking && !roleDefinition.model.includes(":") ? `${roleDefinition.model}:${roleDefinition.thinking}` : roleDefinition?.model);
6082
- const alias = requestedModel === void 0 ? void 0 : modelAliasName(requestedModel, this.root.modelAliases ?? {});
6083
- const blockedAlias = requestedModel?.split(":", 1)[0];
6084
- if (requestedModel !== void 0 && blockedAlias && this.root.blockedAliases?.has(blockedAlias) && !alias) {
6085
- const target = this.root.blockedAliasTargets?.[blockedAlias];
6086
- throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
6087
- }
6088
- const model = options.modelOverride ?? parseModel(requestedModel, this.root.model, this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
6089
- const availableModels = this.root.knownModels ?? this.root.availableModels ?? /* @__PURE__ */ new Set([modelCapability(this.root.model)]);
6090
- if (!availableModels.has(modelCapability(model))) throw new WorkflowError("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
6091
- const overrideSystemPrompt = roleDefinition?.overrideSystemPrompt === true;
6092
- const contextFiles = options.contextFiles ?? roleDefinition?.contextFiles;
6093
- return { model, ...alias && requestedModel ? { requestedModel } : {}, tools: requested, ...overrideSystemPrompt ? { systemPrompt: roleDefinition.prompt ?? "" } : {}, systemPromptAppend: overrideSystemPrompt ? "" : roleDefinition?.prompt ?? "", ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] } };
6322
+ const resolved = resolveRole(roleName, {
6323
+ cwd: this.root.cwd,
6324
+ ...roleDefinition === void 0 ? {} : { definition: roleDefinition },
6325
+ ...this.root.agentDefinitions === void 0 ? {} : { definitions: this.root.agentDefinitions },
6326
+ ...this.root.agentDir === void 0 ? {} : { agentDir: this.root.agentDir },
6327
+ selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
6328
+ ...this.root.modelAliases === void 0 ? {} : { modelAliases: this.root.modelAliases },
6329
+ ...this.root.knownModels === void 0 ? {} : { knownModels: this.root.knownModels },
6330
+ ...this.root.availableModels === void 0 ? {} : { availableModels: this.root.availableModels },
6331
+ ...this.root.blockedAliases === void 0 ? {} : { blockedAliases: this.root.blockedAliases },
6332
+ ...this.root.blockedAliasTargets === void 0 ? {} : { blockedAliasTargets: this.root.blockedAliasTargets },
6333
+ ...this.root.settingsPath === void 0 ? {} : { settingsPath: this.root.settingsPath },
6334
+ rootModel: this.root.model,
6335
+ rootTools: this.root.tools,
6336
+ ...inheritedTools === void 0 ? {} : { inheritedTools },
6337
+ ...options.model === void 0 ? {} : { model: options.model },
6338
+ ...options.modelOverride === void 0 ? {} : { modelOverride: options.modelOverride },
6339
+ ...options.tools === void 0 ? {} : { tools: options.tools },
6340
+ ...options.skills === void 0 ? {} : { skills: options.skills },
6341
+ ...options.extensions === void 0 ? {} : { extensions: options.extensions },
6342
+ ...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles },
6343
+ ...options.effectiveTools === void 0 ? {} : { effectiveTools: options.effectiveTools }
6344
+ });
6345
+ return { model: physicalModel(resolved.model ?? this.root.model, this.root.modelAliases, this.root.knownModels, this.root.settingsPath), ...resolved.requestedModel === void 0 ? {} : { requestedModel: resolved.requestedModel }, tools: resolved.tools ?? [], ...resolved.overrideSystemPrompt ? { systemPrompt: resolved.prompt } : {}, systemPromptAppend: resolved.overrideSystemPrompt ? "" : resolved.prompt, ...resolved.contextFiles === void 0 ? {} : { contextFiles: resolved.contextFiles } };
6094
6346
  }
6095
6347
  async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
6096
6348
  const executionSignal = signal ?? this.root.runContext?.signal;
@@ -6376,12 +6628,12 @@ var FairAgentScheduler = class {
6376
6628
  const id = `${runId}:${String(++this.#nextId)}`;
6377
6629
  const tuiIndex = ++run.nextIndex;
6378
6630
  let resolveResult = () => void 0;
6379
- const promise = new Promise((resolve7) => {
6380
- resolveResult = resolve7;
6631
+ const promise = new Promise((resolve9) => {
6632
+ resolveResult = resolve9;
6381
6633
  });
6382
6634
  let resolveCompletion = () => void 0;
6383
- const completion = new Promise((resolve7) => {
6384
- resolveCompletion = resolve7;
6635
+ const completion = new Promise((resolve9) => {
6636
+ resolveCompletion = resolve9;
6385
6637
  });
6386
6638
  const node = { id, runId, ...parentId ? { parentId } : {}, prompt, options: effective, children: /* @__PURE__ */ new Set(), collected: false, collecting: false, resultQueue: Promise.resolve(), state: "queued", controller: new AbortController(), promise, resolve: resolveResult, completion, resolveCompletion, task: async () => void 0, restored: false };
6387
6639
  node.task = async () => {
@@ -6419,8 +6671,8 @@ var FairAgentScheduler = class {
6419
6671
  const childPromise = child.promise;
6420
6672
  const previousCollection = parent.resultQueue;
6421
6673
  let releaseCollection = () => void 0;
6422
- parent.resultQueue = new Promise((resolve7) => {
6423
- releaseCollection = resolve7;
6674
+ parent.resultQueue = new Promise((resolve9) => {
6675
+ releaseCollection = resolve9;
6424
6676
  });
6425
6677
  try {
6426
6678
  await previousCollection;
@@ -6429,9 +6681,9 @@ var FairAgentScheduler = class {
6429
6681
  this.#persist(parent.runId);
6430
6682
  this.#release(parent.runId);
6431
6683
  const outcome = await childPromise;
6432
- await new Promise((resolve7) => {
6684
+ await new Promise((resolve9) => {
6433
6685
  this.#enqueue(parent.runId, void 0, () => {
6434
- resolve7();
6686
+ resolve9();
6435
6687
  });
6436
6688
  });
6437
6689
  if (this.#cancelledOrSettled(parent)) {
@@ -6519,6 +6771,7 @@ var FairAgentScheduler = class {
6519
6771
  label: "Child Agent",
6520
6772
  description: "Start a direct child agent",
6521
6773
  parameters: Type.Object({ prompt: Type.String(), label: Type.String(), tools: Type.Optional(Type.Array(Type.String())), skills: Type.Optional(Type.Array(Type.String())), extensions: Type.Optional(Type.Array(Type.String())), model: Type.Optional(Type.String()), role: Type.Optional(Type.String()), contextFiles: Type.Optional(Type.Array(Type.String())), outputSchema: Type.Optional(Type.Record(Type.String(), Type.Unknown())), retries: Type.Optional(Type.Integer({ minimum: 0 })), timeoutMs: Type.Optional(Type.Union([Type.Integer({ minimum: 1 }), Type.Null()])) }, { additionalProperties: true }),
6774
+ outputSchema: Type.Object({ id: Type.String() }),
6522
6775
  execute: async (_id, params) => {
6523
6776
  if (!isChildAgentToolParams(params)) throw new WorkflowError("INVALID_METADATA", "Invalid child agent parameters");
6524
6777
  validateAgentOptions(params);
@@ -6529,7 +6782,7 @@ var FairAgentScheduler = class {
6529
6782
  Reflect.deleteProperty(agentOptions, "prompt");
6530
6783
  const options = { label: params.label, requestedLabel: params.label, cwd: parent.options.cwd, tools, agentOptions, ...params.skills ? { skills: params.skills } : {}, ...params.extensions ? { extensions: params.extensions } : {}, ...params.model ? { model: params.model } : {}, ...params.role ? { role: params.role } : {}, ...params.contextFiles ? { contextFiles: params.contextFiles } : {}, ...outputSchema === void 0 ? {} : { schema: outputSchema }, ...params.retries === void 0 ? {} : { retries: params.retries }, ...params.timeoutMs === void 0 ? {} : { timeoutMs: params.timeoutMs } };
6531
6784
  const child = this.spawn(parent.runId, params.prompt, options, parentId);
6532
- return { content: [{ type: "text", text: JSON.stringify({ id: child.id }) }], details: { id: child.id } };
6785
+ return { content: [{ type: "text", text: JSON.stringify({ id: child.id }) }], details: { id: child.id }, structuredContent: { id: child.id } };
6533
6786
  }
6534
6787
  });
6535
6788
  const resultTool = defineTool({
@@ -6537,10 +6790,11 @@ var FairAgentScheduler = class {
6537
6790
  label: "Child Result",
6538
6791
  description: "Wait for a direct child and return its result once; repeated retrieval fails with AGENT_RESULT_COLLECTED",
6539
6792
  parameters: Type.Object({ id: Type.String() }),
6793
+ outputSchema: Type.Object({ id: Type.String(), ok: Type.Boolean(), value: Type.Optional(Type.Unknown()), error: Type.Optional(Type.Object({ code: Type.String(), message: Type.String() })) }),
6540
6794
  execute: async (_id, params) => {
6541
6795
  const value = await this.result(parentId, params.id);
6542
6796
  if (!value.ok && value.error.code === "BUDGET_EXHAUSTED") throw new WorkflowError("BUDGET_EXHAUSTED", value.error.message);
6543
- return { content: [{ type: "text", text: JSON.stringify(value) }], details: value };
6797
+ return { content: [{ type: "text", text: JSON.stringify(value) }], details: value, structuredContent: value };
6544
6798
  }
6545
6799
  });
6546
6800
  const steerTool = defineTool({
@@ -6564,12 +6818,12 @@ var FairAgentScheduler = class {
6564
6818
  for (const record3 of ownership) {
6565
6819
  if (record3.id.split(":").slice(0, -1).join(":") !== runId) throw new WorkflowError("RESUME_INCOMPATIBLE", `Persisted agent belongs to another run: ${record3.id}`);
6566
6820
  let resolveResult = () => void 0;
6567
- const promise = new Promise((resolve7) => {
6568
- resolveResult = resolve7;
6821
+ const promise = new Promise((resolve9) => {
6822
+ resolveResult = resolve9;
6569
6823
  });
6570
6824
  let resolveCompletion = () => void 0;
6571
- const completion = new Promise((resolve7) => {
6572
- resolveCompletion = resolve7;
6825
+ const completion = new Promise((resolve9) => {
6826
+ resolveCompletion = resolve9;
6573
6827
  });
6574
6828
  const node = { id: record3.id, runId, ...record3.parentId ? { parentId: record3.parentId } : {}, ...record3.prompt === void 0 ? {} : { prompt: record3.prompt }, options: this.#inherit(run, void 0, record3.options), children: /* @__PURE__ */ new Set(), collected: false, collecting: false, resultQueue: Promise.resolve(), state: record3.state, controller: new AbortController(), promise, resolve: resolveResult, completion, resolveCompletion, task: async () => void 0, restored: true };
6575
6829
  this.#nodes.set(node.id, node);
@@ -6658,9 +6912,9 @@ var FairAgentScheduler = class {
6658
6912
  const child = this.#nodes.get(childId);
6659
6913
  if (child && !child.collected) this.#cancelTree(child);
6660
6914
  }
6661
- const resolve7 = node.resolve;
6915
+ const resolve9 = node.resolve;
6662
6916
  node.resolve = void 0;
6663
- resolve7?.(result);
6917
+ resolve9?.(result);
6664
6918
  node.resolveCompletion();
6665
6919
  }
6666
6920
  #cancelTree(node) {
@@ -6723,8 +6977,8 @@ var RunLifecycle = class {
6723
6977
  return this.#state;
6724
6978
  }
6725
6979
  async enter() {
6726
- while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input") await new Promise((resolve7) => {
6727
- this.#waiters.push(resolve7);
6980
+ while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input") await new Promise((resolve9) => {
6981
+ this.#waiters.push(resolve9);
6728
6982
  });
6729
6983
  if (this.#state !== "running") throw new WorkflowError("CANCELLED", `Run is ${this.#state}`);
6730
6984
  this.#active += 1;
@@ -6734,8 +6988,8 @@ var RunLifecycle = class {
6734
6988
  if (this.#state === "pausing" && this.#active === 0) await this.#set("paused", "pause");
6735
6989
  }
6736
6990
  async enterAwaitingInput() {
6737
- while (this.#state === "pausing" || this.#state === "paused") await new Promise((resolve7) => {
6738
- this.#waiters.push(resolve7);
6991
+ while (this.#state === "pausing" || this.#state === "paused") await new Promise((resolve9) => {
6992
+ this.#waiters.push(resolve9);
6739
6993
  });
6740
6994
  if (this.#state === "awaiting_input") return;
6741
6995
  if (this.#state !== "running") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot await input for ${this.#state} run`);
@@ -6744,7 +6998,7 @@ var RunLifecycle = class {
6744
6998
  async resolveAwaitingInput() {
6745
6999
  if (this.#state !== "awaiting_input") return;
6746
7000
  await this.#set("running", "checkpoint_resolved");
6747
- for (const resolve7 of this.#waiters.splice(0)) resolve7();
7001
+ for (const resolve9 of this.#waiters.splice(0)) resolve9();
6748
7002
  }
6749
7003
  async pause() {
6750
7004
  if (this.#state !== "running") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot pause ${this.#state} run`);
@@ -6755,7 +7009,7 @@ var RunLifecycle = class {
6755
7009
  async resume() {
6756
7010
  if (this.#state !== "pausing" && this.#state !== "paused" && this.#state !== "interrupted" && this.#state !== "budget_exhausted") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot resume ${this.#state} run`);
6757
7011
  await this.#set("running", "resume");
6758
- for (const resolve7 of this.#waiters.splice(0)) resolve7();
7012
+ for (const resolve9 of this.#waiters.splice(0)) resolve9();
6759
7013
  }
6760
7014
  async providerPause() {
6761
7015
  await this.leave();
@@ -6765,7 +7019,7 @@ var RunLifecycle = class {
6765
7019
  async terminal(state, reason) {
6766
7020
  if (HARD_TERMINAL_RUN_STATES.has(this.#state)) throw new WorkflowError("RESUME_INCOMPATIBLE", `${this.#state} runs are terminal`);
6767
7021
  await this.#set(state, reason ?? state);
6768
- for (const resolve7 of this.#waiters.splice(0)) resolve7();
7022
+ for (const resolve9 of this.#waiters.splice(0)) resolve9();
6769
7023
  }
6770
7024
  async #set(state, reason) {
6771
7025
  const previousState = this.#state;
@@ -7041,7 +7295,7 @@ function withWorkflowFunctions(bridge, store, runContext, registry, settings = O
7041
7295
  import { randomUUID as randomUUID4 } from "node:crypto";
7042
7296
 
7043
7297
  // src/host-delivery.ts
7044
- import { join as join8 } from "node:path";
7298
+ import { join as join9 } from "node:path";
7045
7299
  import { DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
7046
7300
  var FAILURE_DELIVERY_STATES = /* @__PURE__ */ new Set(["failed", "stopped", "interrupted", "budget_exhausted"]);
7047
7301
  var ForegroundDeliveryController = class {
@@ -7143,6 +7397,7 @@ var WORKFLOW_ERROR_PROSE = {
7143
7397
  RUN_NOT_FOUND: (detail) => /^Unknown workflow run\b/.test(detail) ? "The workflow run was not found." : `The workflow run was not found: ${detail}.`,
7144
7398
  RPC_LIMIT_EXCEEDED: (detail) => `The workflow communication data exceeded its size limit: ${detail}.`,
7145
7399
  SHELL_FAILED: (detail) => `The workflow shell command failed: ${detail}.`,
7400
+ TOOL_FAILED: (detail) => `The workflow tool call failed: ${detail}.`,
7146
7401
  AGENT_TIMEOUT: (detail) => `The workflow agent timed out: ${detail}.`,
7147
7402
  AGENT_FAILED: (detail) => `The workflow agent failed: ${detail}.`,
7148
7403
  AGENT_RESULT_COLLECTED: (detail) => `The nested agent result was already collected: ${detail}.`,
@@ -7366,7 +7621,7 @@ async function createWorkflowFailureDiagnostics(store, metadata, error, run) {
7366
7621
  completedSiblingAgents,
7367
7622
  completedSiblingPaths,
7368
7623
  ...retry ? { retry } : {},
7369
- artifacts: { runDirectory: store.directory, statePath: join8(store.directory, "state.json"), journalPath: join8(store.directory, "journal.json") }
7624
+ artifacts: { runDirectory: store.directory, statePath: join9(store.directory, "state.json"), journalPath: join9(store.directory, "journal.json") }
7370
7625
  });
7371
7626
  }
7372
7627
  function formatWorkflowFailureDiagnostics(diagnostic) {
@@ -7393,7 +7648,7 @@ function formatWorkflowFailureDeliveryFallback(workflowName, runId, runDirectory
7393
7648
  const code = errorCode(error) ?? "INTERNAL_ERROR";
7394
7649
  const failedPath = workflowFailedAt(error);
7395
7650
  const nextAction = retryable && code !== "BUDGET_EXHAUSTED" && code !== "CANCELLED" ? `; next action: workflow_retry({ runId: ${JSON.stringify(runId)} })` : "";
7396
- const line = `Workflow ${deliveryPart(workflowName, 128)} failed (runId=${deliveryPart(runId, 128)}): error=${code}: ${deliveryPart(formatWorkflowFailure(error), 768)}${failedPath ? `; failed path=${deliveryPart(failedPath, 512)}` : ""}${nextAction}; artifacts: runDirectory=${deliveryPart(runDirectory, 512)} statePath=${deliveryPart(join8(runDirectory, "state.json"), 512)} journalPath=${deliveryPart(join8(runDirectory, "journal.json"), 512)}`;
7651
+ const line = `Workflow ${deliveryPart(workflowName, 128)} failed (runId=${deliveryPart(runId, 128)}): error=${code}: ${deliveryPart(formatWorkflowFailure(error), 768)}${failedPath ? `; failed path=${deliveryPart(failedPath, 512)}` : ""}${nextAction}; artifacts: runDirectory=${deliveryPart(runDirectory, 512)} statePath=${deliveryPart(join9(runDirectory, "state.json"), 512)} journalPath=${deliveryPart(join9(runDirectory, "journal.json"), 512)}`;
7397
7652
  return Buffer.byteLength(line) <= DELIVERY_LIMIT_BYTES ? line : utf8Prefix(line, DELIVERY_LIMIT_BYTES);
7398
7653
  }
7399
7654
  function serializeWorkflowFailureDiagnostics(diagnostic) {
@@ -7433,7 +7688,7 @@ function persistedFailure(run, error) {
7433
7688
  return { ...run, error: { code: error.code, message: error.message, ...failedAt ? { failedAt } : {} }, ...failedAt ? { failedAt } : {} };
7434
7689
  }
7435
7690
  function createWorkflowRecovery(deps) {
7436
- const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted: projectTrusted2, resumeHostContext: resumeHostContext2, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy: frozenResourcePolicy2, resolveLaunchPrologue, resumeRoles, workflowAgentHandler, shellForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver: deliver2, deliverTerminal, workflowToolUpdate: workflowToolUpdate2, registry, modelSpec: modelSpec2 } = deps;
7691
+ const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted: projectTrusted2, resumeHostContext: resumeHostContext2, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy: frozenResourcePolicy2, resolveLaunchPrologue, resumeRoles, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver: deliver2, deliverTerminal, workflowToolUpdate: workflowToolUpdate2, registry, modelSpec: modelSpec2 } = deps;
7437
7692
  const coordinateRunMutation = deps.coordinateRunMutation ?? ((task) => task());
7438
7693
  const budgetDecisionDelivery = (metadata, request) => `Workflow ${metadata.name} budget adjustment ${request.proposalId} for run ${request.runId} requires approval. Consumed usage: ${JSON.stringify(request.consumed)}. Previous limits: ${JSON.stringify(request.previous)}. Proposed limits: ${JSON.stringify(request.proposed)}. Respond with workflow_respond using proposalId ${request.proposalId}.`;
7439
7694
  const appendBudgetDecisionEvent = async (run, request, type) => {
@@ -7505,7 +7760,7 @@ function createWorkflowRecovery(deps) {
7505
7760
  run.executor.setRunContext(runContext);
7506
7761
  await scheduler.cancelRun(run.store.runId);
7507
7762
  await run.lifecycle.resume();
7508
- const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId, roles.capture), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : void 0), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
7763
+ const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ tool: (identifier, args, signal, identity) => toolForRun(run.store, run.lifecycle, context?.toolContext, identifier, args, signal, identity), shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId, roles.capture), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : void 0), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
7509
7764
  run.execution = execution;
7510
7765
  const completion = execution.result.then(async (value) => {
7511
7766
  await scheduler.flush(run.store.runId);
@@ -7704,7 +7959,7 @@ function createWorkflowRecovery(deps) {
7704
7959
  await eventPublisher.runStarted(prepared.childRun.store, prepared.childRun.metadata);
7705
7960
  const { hasUI, ui } = recoveryUi(context);
7706
7961
  const recoveryContext = resumeHostContext2(context);
7707
- const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted2(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, resolvedAliases: prepared.currentAliases, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...signal ? { signal } : {} }, modeOverride);
7962
+ const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted2(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, toolContext: recoveryContext.toolContext, resolvedAliases: prepared.currentAliases, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...signal ? { signal } : {} }, modeOverride);
7708
7963
  const completion = runs.get(prepared.childRun.store.runId)?.completion;
7709
7964
  if (completion) {
7710
7965
  childStarted = true;
@@ -7728,14 +7983,14 @@ function createWorkflowRecovery(deps) {
7728
7983
 
7729
7984
  // src/host-navigator.ts
7730
7985
  import { readFile as readFile5 } from "node:fs/promises";
7731
- import { join as join12 } from "node:path";
7986
+ import { join as join13 } from "node:path";
7732
7987
  import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateToVisualLines2 } from "@earendil-works/pi-coding-agent";
7733
7988
 
7734
7989
  // src/workflow-artifacts.ts
7735
7990
  import { spawn as spawn2 } from "node:child_process";
7736
7991
  import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
7737
7992
  import { tmpdir as tmpdir2 } from "node:os";
7738
- import { join as join9 } from "node:path";
7993
+ import { join as join10 } from "node:path";
7739
7994
  function workflowScriptArtifact(script) {
7740
7995
  return { extension: ".js", content: script };
7741
7996
  }
@@ -7749,23 +8004,23 @@ function workflowResultArtifact(value) {
7749
8004
  async function spawnWorkflowEditor(command, path) {
7750
8005
  const [editor, ...editorArgs] = command.split(" ");
7751
8006
  if (!editor) return null;
7752
- return new Promise((resolve7) => {
8007
+ return new Promise((resolve9) => {
7753
8008
  try {
7754
8009
  const child = spawn2(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
7755
8010
  child.once("error", () => {
7756
- resolve7(null);
8011
+ resolve9(null);
7757
8012
  });
7758
8013
  child.once("close", (code) => {
7759
- resolve7(code);
8014
+ resolve9(code);
7760
8015
  });
7761
8016
  } catch {
7762
- resolve7(null);
8017
+ resolve9(null);
7763
8018
  }
7764
8019
  });
7765
8020
  }
7766
8021
  async function openWorkflowArtifact(tui, command, artifact) {
7767
- const directory = await mkdtemp(join9(tmpdir2(), "pi-workflow-editor-"));
7768
- const path = join9(directory, `artifact${artifact.extension}`);
8022
+ const directory = await mkdtemp(join10(tmpdir2(), "pi-workflow-editor-"));
8023
+ const path = join10(directory, `artifact${artifact.extension}`);
7769
8024
  try {
7770
8025
  await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
7771
8026
  tui.stop();
@@ -7782,11 +8037,11 @@ async function openWorkflowArtifact(tui, command, artifact) {
7782
8037
 
7783
8038
  // src/host-view.ts
7784
8039
  import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
7785
- import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
8040
+ import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
7786
8041
 
7787
8042
  // src/background-widget.ts
7788
- import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
7789
- import { join as join10 } from "node:path";
8043
+ import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
8044
+ import { join as join11 } from "node:path";
7790
8045
  import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
7791
8046
  var KEY = "piewf-widget";
7792
8047
  var ENTRY_TYPE = "piewf-run-receipt";
@@ -7876,7 +8131,7 @@ function renderableRun(value) {
7876
8131
  }
7877
8132
  function readRun(directory) {
7878
8133
  try {
7879
- const state = JSON.parse(readFileSync4(join10(directory, "state.json"), "utf8"));
8134
+ const state = JSON.parse(readFileSync6(join11(directory, "state.json"), "utf8"));
7880
8135
  if (!renderableRun(state)) return void 0;
7881
8136
  const startedAt = statSync2(directory).birthtimeMs;
7882
8137
  return { ...state, directory, startedAt };
@@ -8105,7 +8360,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
8105
8360
  const title = live.length === 1 ? "Workflow" : `Workflows \xB7 ${String(live.length)} runs`;
8106
8361
  sizeOut.rows = rows.length;
8107
8362
  const body = collapse(rows, MAX_ROWS - 2, paint, offset, scrolling);
8108
- const inner = Math.max(20, width - 4);
8109
8363
  const total = rows.length;
8110
8364
  const shown = body.length;
8111
8365
  const scrollbar = (index) => {
@@ -8117,6 +8371,11 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
8117
8371
  const top = limit === 0 ? 0 : start >= limit ? shown - size : Math.floor(start / limit * (shown - size));
8118
8372
  return index >= top && index < top + size ? "\u2588" : "\u2591";
8119
8373
  };
8374
+ const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0 ? "" : scrolling ? "\u2191/\u2193 to scroll \xB7 Esc to exit" : "Alt+O to scroll";
8375
+ return drawFrame(title, hint, body, width, paint, accent, scrollbar);
8376
+ }
8377
+ function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => void 0) {
8378
+ const inner = Math.max(20, width - 4);
8120
8379
  const lines = body.map((row, index) => {
8121
8380
  const right = row.right ?? "";
8122
8381
  const rightWidth = right === "" ? 0 : visibleLength(right) + 2;
@@ -8128,7 +8387,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
8128
8387
  const bar = scrollbar(index);
8129
8388
  return `${rule} ${text} ${bar === void 0 ? rule : accent(bar)}`;
8130
8389
  });
8131
- const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0 ? "" : scrolling ? "\u2191/\u2193 to scroll \xB7 Esc to exit" : "Alt+O to scroll";
8132
8390
  const label = ` ${title} `;
8133
8391
  const tail = hint === "" ? "" : ` ${hint} `;
8134
8392
  const spare = inner + 2 - visibleLength(label) - visibleLength(tail);
@@ -8507,7 +8765,7 @@ function widget(pi, enabled = true) {
8507
8765
  continue;
8508
8766
  }
8509
8767
  try {
8510
- const mtime = statSync2(join10(run.directory, "state.json")).mtimeMs;
8768
+ const mtime = statSync2(join11(run.directory, "state.json")).mtimeMs;
8511
8769
  if (mtime === seen.get(runId)) continue;
8512
8770
  seen.set(runId, mtime);
8513
8771
  const fresh = readRun(run.directory);
@@ -8624,7 +8882,7 @@ function widget(pi, enabled = true) {
8624
8882
  const waiting = runs.get(runId)?.waiting;
8625
8883
  runs.set(runId, waiting ? { ...run, waiting } : run);
8626
8884
  try {
8627
- seen.set(runId, statSync2(join10(directory, "state.json")).mtimeMs);
8885
+ seen.set(runId, statSync2(join11(directory, "state.json")).mtimeMs);
8628
8886
  } catch {
8629
8887
  }
8630
8888
  if (isRunTerminal(run.state) && !awaitingDedicatedReceipt.has(runId)) receipt(runId);
@@ -9014,13 +9272,22 @@ function formatShellActivity(activeShells, startedAt, spinner2, styles, now) {
9014
9272
  const timing = started && startedAt !== void 0 && !Number.isNaN(started.getTime()) ? ` ${styles.dim(`started=${started.toISOString()} elapsed=${formatWorkflowRuntime(Math.max(0, now - startedAt))}`)}` : "";
9015
9273
  return `${styles.accent(spinner2)} shell ${styles.accent("[running]")} ${styles.dim(`(${String(count)} active)`)}${timing}`;
9016
9274
  }
9275
+ function formatToolActivity(run, spinner2, styles, now) {
9276
+ const calls = run.activeTools ?? [];
9277
+ if (!calls.length) return void 0;
9278
+ const counts = /* @__PURE__ */ new Map();
9279
+ for (const { name } of calls) counts.set(name, (counts.get(name) ?? 0) + 1);
9280
+ const names = [...counts].map(([name, count]) => count > 1 ? `${name} \xD7${String(count)}` : name).join(", ");
9281
+ const elapsed = formatWorkflowRuntime(Math.max(0, now - Math.min(...calls.map(({ startedAt }) => startedAt))));
9282
+ return `${styles.accent(spinner2)} tools ${styles.accent("[running]")} ${sanitizeDisplayText(names)} ${styles.dim(`elapsed=${elapsed}`)}`;
9283
+ }
9017
9284
  function formatLogTimestamp(timestamp) {
9018
9285
  if (timestamp === void 0 || !Number.isFinite(timestamp)) return "--:--:--";
9019
9286
  const date = new Date(timestamp);
9020
9287
  if (Number.isNaN(date.getTime())) return "--:--:--";
9021
9288
  return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
9022
9289
  }
9023
- function workflowLogLines(run, styles, expanded, width, showHint = false) {
9290
+ function workflowLogLines(run, styles, expanded, width, showHint = false, tail = 5) {
9024
9291
  const events = (run.events ?? []).filter((event) => event.type === "log");
9025
9292
  if (!events.length) return [];
9026
9293
  const indent = " ".repeat(11);
@@ -9030,7 +9297,7 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
9030
9297
  const chunks = truncateToVisualLines(message, Number.MAX_SAFE_INTEGER, Math.max(1, width - 11), 0).visualLines;
9031
9298
  return chunks.map((chunk, chunkIndex) => `${chunkIndex === 0 ? prefix : indent}${chunk}`);
9032
9299
  }));
9033
- const visible = expanded ? visualLines : visualLines.slice(-5);
9300
+ const visible = expanded ? visualLines : visualLines.slice(-tail);
9034
9301
  let hint = "";
9035
9302
  if (!expanded && showHint) {
9036
9303
  try {
@@ -9040,7 +9307,47 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
9040
9307
  }
9041
9308
  return [expanded ? ` ${styles.muted("Logs")}` : ` ${styles.muted(`Logs${hint}`)}`, ...visible];
9042
9309
  }
9043
- function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false) {
9310
+ var ACTIVE_AGENT_STATES = /* @__PURE__ */ new Set(["running", "waiting_for_child", "retrying", "paused"]);
9311
+ var COMPACT_RECENT_SETTLED = 3;
9312
+ var COMPACT_LOG_LINES = 3;
9313
+ function expandHint() {
9314
+ try {
9315
+ return ` (${keyHint("app.tools.expand", "to expand")})`;
9316
+ } catch {
9317
+ return "";
9318
+ }
9319
+ }
9320
+ function agentEnd(agent) {
9321
+ return (agent.startedAt ?? 0) + (agent.durationMs ?? 0);
9322
+ }
9323
+ function phaseSummary(agents, styles) {
9324
+ const count = (state) => agents.filter((agent) => agent.state === state).length;
9325
+ const starts = agents.map((agent) => agent.startedAt).filter((value) => value !== void 0);
9326
+ const span = starts.length ? Math.max(...agents.map(agentEnd)) - Math.min(...starts) : 0;
9327
+ const accounting2 = sumAccounting(agents.map((agent) => agent.accounting ?? zeroAccounting()));
9328
+ const failed = count("failed");
9329
+ const parts = [`${String(count("completed"))} done`, ...failed ? [styles.error(`${String(failed)} failed`)] : [], ...count("cancelled") ? [`${String(count("cancelled"))} cancelled`] : [], ...span > 0 ? [formatWorkflowRuntime(span)] : [], formatTokens(accounting2.input + accounting2.output), formatCost(accounting2.cost)].filter(Boolean);
9330
+ return styles.muted(parts.join(" \xB7 "));
9331
+ }
9332
+ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false, maxLines) {
9333
+ const full = workflowProgressLines(run, spinner2, styles, now, expanded, width, showHint);
9334
+ if (expanded || maxLines === void 0 || full.length <= maxLines) return full.join("\n");
9335
+ const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
9336
+ const currentStart = Math.min(run.agents.length, phases.at(-1)?.afterAgent ?? 0);
9337
+ const current = run.agents.slice(currentStart);
9338
+ const recent = current.filter((agent) => SETTLED_AGENT_STATES.has(agent.state) && agent.state !== "failed").sort((left, right) => agentEnd(right) - agentEnd(left)).slice(0, COMPACT_RECENT_SETTLED);
9339
+ const candidates = [...current.filter((agent) => agent.state === "failed"), ...current.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)), ...recent];
9340
+ const render = (count) => workflowProgressLines(run, spinner2, styles, now, false, width, showHint, new Set(candidates.slice(0, count)));
9341
+ let low = 0;
9342
+ let high = candidates.length;
9343
+ while (low < high) {
9344
+ const middle = Math.ceil((low + high) / 2);
9345
+ if (render(middle).length <= maxLines) low = middle;
9346
+ else high = middle - 1;
9347
+ }
9348
+ return render(low).join("\n");
9349
+ }
9350
+ function workflowProgressLines(run, spinner2, styles, now, expanded, width, showHint, shown) {
9044
9351
  const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
9045
9352
  const workflowIcon = runStateGlyph(run.state, spinner2);
9046
9353
  const iconStyle = workflowIconStyle(run.state, styles);
@@ -9054,15 +9361,19 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
9054
9361
  const scopedShells = (run.activeShellsByPhase?.length ?? 0) > 0;
9055
9362
  const shellActivity = scopedShells ? void 0 : formatShellActivity(run.activeShells, run.activeShellStartedAt, spinner2, styles, now);
9056
9363
  if (shellActivity) lines.push(` ${shellActivity}`);
9364
+ const toolActivity = formatToolActivity(run, spinner2, styles, now);
9365
+ if (toolActivity) lines.push(` ${toolActivity}`);
9057
9366
  const byId = new Map(run.agents.map((agent) => [agent.id, agent]));
9058
- const renderAgents = (agents, offset, nested2) => renderGroupedAgents(agents, ({ agent, index, depth }, grouped) => {
9059
- const icon = agentStateGlyph(agent.state, spinner2);
9367
+ const position = new Map(run.agents.map((agent, index) => [agent, index]));
9368
+ const renderAgents = (agents, nested2) => renderGroupedAgents(agents, ({ agent, depth }, grouped) => {
9369
+ const icon = agentStateGlyph(agent.state, "\u25CF");
9060
9370
  const indent = " ".repeat((grouped ? 2 : 1) + depth);
9061
- const activity = SETTLED_AGENT_STATES.has(agent.state) ? "" : formatAgentActivity(agent, spinner2, styles, now);
9371
+ const idleSpinner = agent.state === "running" && !agentActivityLabel(agent) ? styles.accent(spinner2) : "";
9372
+ const activity = SETTLED_AGENT_STATES.has(agent.state) ? "" : [idleSpinner, formatAgentActivity(agent, spinner2, styles, now)].filter(Boolean).join(" ");
9062
9373
  const name = grouped ? agent.label ?? agent.name : styledAgentBreadcrumb(agent, byId, styles);
9063
9374
  const state2 = progressStyleForState(agent.state, styles);
9064
9375
  const detail = expanded ? formatWorkflowAgentDetail(agent, now) : "";
9065
- return `${indent}#${String(offset + index + 1)} ${state2(icon)} ${name} ${state2(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
9376
+ return `${indent}#${String((position.get(agent) ?? 0) + 1)} ${state2(icon)} ${name} ${state2(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
9066
9377
  }, run.agents, (label) => styles.muted(label)).map((line) => nested2 ? ` ${line}` : line);
9067
9378
  const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
9068
9379
  if (scopedShells) {
@@ -9075,10 +9386,22 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
9075
9386
  }
9076
9387
  let renderedAgents = 0;
9077
9388
  let nested = false;
9389
+ const segment = (agents, closed) => {
9390
+ if (!shown) return renderAgents(agents, nested);
9391
+ const listed = agents.filter((agent) => closed ? agent.state === "failed" : shown.has(agent));
9392
+ const hidden = closed ? [] : agents.filter((agent) => !shown.has(agent));
9393
+ const counts = [["queued", hidden.filter((agent) => !SETTLED_AGENT_STATES.has(agent.state) && !ACTIVE_AGENT_STATES.has(agent.state)).length], ["running", hidden.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)).length], ["done", hidden.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length]];
9394
+ const summary = counts.filter(([, count]) => count > 0).map(([label, count]) => `+${String(count)} ${label}`).join(" \xB7 ");
9395
+ return [...renderAgents(listed, nested), ...summary ? [`${nested ? " " : " "}${styles.muted(`\u2026 ${summary}${showHint ? expandHint() : ""}`)}`] : []];
9396
+ };
9078
9397
  for (const [phaseIndex, phase] of phases.entries()) {
9079
9398
  const boundary = Math.max(renderedAgents, Math.min(run.agents.length, phase.afterAgent));
9080
- lines.push(...renderAgents(run.agents.slice(renderedAgents, boundary), renderedAgents, nested));
9081
- lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}`);
9399
+ const before = run.agents.slice(renderedAgents, boundary);
9400
+ if (shown && !nested && before.length) lines.push(` ${phaseSummary(before, styles)}`);
9401
+ lines.push(...segment(before, true));
9402
+ const next = phases[phaseIndex + 1];
9403
+ const phaseAgents = next ? run.agents.slice(boundary, Math.max(boundary, Math.min(run.agents.length, next.afterAgent))) : [];
9404
+ lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}${shown && next ? ` ${phaseSummary(phaseAgents, styles)}` : ""}`);
9082
9405
  const phaseShell = scopedShells ? shellActivityFor(run, phaseIndex) : void 0;
9083
9406
  if (phaseShell) {
9084
9407
  const rendered = formatShellActivity(phaseShell.active, phaseShell.startedAt, spinner2, styles, now);
@@ -9087,9 +9410,9 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
9087
9410
  renderedAgents = boundary;
9088
9411
  nested = true;
9089
9412
  }
9090
- lines.push(...renderAgents(run.agents.slice(renderedAgents), renderedAgents, nested));
9091
- lines.push(...workflowLogLines(run, styles, expanded, width, showHint));
9092
- return lines.join("\n");
9413
+ lines.push(...segment(run.agents.slice(renderedAgents), false));
9414
+ lines.push(...workflowLogLines(run, styles, expanded, width, showHint, shown ? COMPACT_LOG_LINES : 5));
9415
+ return lines;
9093
9416
  }
9094
9417
  var workflowSpinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9095
9418
  var WORKFLOW_PROGRESS_REFRESH_MS = 1e3;
@@ -9331,7 +9654,9 @@ function workflowProgressBlock(run, theme, progress, refresh, invalidate, prefix
9331
9654
  }
9332
9655
  previousState = displayed.state;
9333
9656
  const frame = displayed.state === "running" ? workflowSpinner[Math.floor(now / 80) % workflowSpinner.length] ?? "\u25C7" : "\u25C7";
9334
- const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true);
9657
+ const rows = freezeAt === void 0 && !terminal ? process.stdout.rows : void 0;
9658
+ const maxLines = rows ? Math.max(6, rows - 10) - (prefix ? prefix.split("\n").length + 1 : 0) : void 0;
9659
+ const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true, maxLines);
9335
9660
  return truncateWorkflowProgress(prefix ? `${prefix}
9336
9661
 
9337
9662
  ${progressText}` : progressText, width);
@@ -9342,7 +9667,7 @@ ${progressText}` : progressText, width);
9342
9667
  invalidate() {
9343
9668
  const displayed = currentRun();
9344
9669
  const now = Date.now();
9345
- if (!progress || !refresh || displayed.state !== "running" || !displayed.agents.some((agent) => agent.state === "running") && (displayed.activeShells ?? 0) <= 0) return;
9670
+ if (!progress || !refresh || displayed.state !== "running" || !displayed.agents.some((agent) => agent.state === "running") && (displayed.activeShells ?? 0) <= 0 && !displayed.activeTools?.length) return;
9346
9671
  if (progress.refresh || now - progress.lastRefreshAt < WORKFLOW_PROGRESS_REFRESH_MS) return;
9347
9672
  progress.lastRefreshAt = now;
9348
9673
  const inputRun = progress.inputRun;
@@ -9555,6 +9880,8 @@ function formatNavigatorDashboard(run, checkpoints, worktrees, now = Date.now())
9555
9880
  const lines = [header, meta, ...formatCompactBudgetStatus(run)];
9556
9881
  const shellActivity = formatShellActivity(run.activeShells, run.activeShellStartedAt, "\u2826", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
9557
9882
  if (shellActivity) lines.push(` ${shellActivity}`);
9883
+ const toolActivity = formatToolActivity(run, "\u2826", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
9884
+ if (toolActivity) lines.push(` ${toolActivity}`);
9558
9885
  if (run.error) lines.push(`Error: ${run.error.code}: ${run.error.message}`);
9559
9886
  if (run.events?.length) lines.push(...run.events.filter((event) => event.type === "warning").map((event) => `Warning: ${event.message}`));
9560
9887
  lines.push("");
@@ -9594,6 +9921,8 @@ function formatNavigatorRun(loaded, checkpoints, worktrees, now = Date.now()) {
9594
9921
  ];
9595
9922
  const shellActivity = formatShellActivity(run.activeShells, run.activeShellStartedAt, "\u2826", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
9596
9923
  if (shellActivity) lines.push(` ${shellActivity}`);
9924
+ const toolActivity = formatToolActivity(run, "\u2826", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
9925
+ if (toolActivity) lines.push(` ${toolActivity}`);
9597
9926
  if (run.error) lines.push(`Run error: ${run.error.code}: ${run.error.message}`);
9598
9927
  if (run.events?.length) lines.push(...run.events.filter((event) => event.type === "warning").map((event) => `Warning: ${event.message}`));
9599
9928
  const aliases = snapshot.modelAliases ?? snapshot.settings.modelAliases;
@@ -9627,10 +9956,7 @@ function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {}, styl
9627
9956
  const model = buildWorkflowPhaseModel(run, snapshot);
9628
9957
  const tree = buildWorkflowPhaseTree(model);
9629
9958
  const expanded = selection.expandedNodeIds === void 0 ? workflowPhaseTreeInitialExpanded(tree) : new Set(selection.expandedNodeIds);
9630
- const wrap = (text, limit = safeWidth) => truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, limit), 0).visualLines.map((line) => line.trimEnd());
9631
- const ansiPattern = new RegExp(ANSI_SGR_SOURCE, "g");
9632
- const visibleLength2 = (text) => text.replace(ansiPattern, "").length;
9633
- const padTo = (text, limit) => `${text}${" ".repeat(Math.max(0, limit - visibleLength2(text)))}`;
9959
+ const wrap = (text) => wrapNavigatorLine(text, safeWidth);
9634
9960
  const phaseStyle = (state) => phaseStyleForState(state, styles);
9635
9961
  const phase = selection.phaseId ? model.phases.find((candidate) => candidate.id === selection.phaseId) : void 0;
9636
9962
  const selectedByAgent = selection.agentId ? tree.nodes.find((node) => node.kind === "agent" && node.agentId === selection.agentId && (!selection.phaseId || node.phaseId === selection.phaseId)) : void 0;
@@ -9696,28 +10022,35 @@ function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {}, styl
9696
10022
  const scopedShells = (run.activeShellsByPhase?.length ?? 0) > 0;
9697
10023
  const shellActivity = scopedShells ? void 0 : formatShellActivity(run.activeShells, run.activeShellStartedAt, "\u2826", styles, now);
9698
10024
  if (shellActivity) lines.push(` ${shellActivity}`);
9699
- const renderTree = (limit) => [styles.bold("Tree"), ...visibleNodes.length ? visibleNodes.flatMap((node) => wrap(treeLine(node), limit)) : [styles.muted("(empty)")]];
10025
+ const toolActivity = formatToolActivity(run, "\u2826", styles, now);
10026
+ if (toolActivity) lines.push(` ${toolActivity}`);
9700
10027
  const actionRows = () => {
9701
10028
  const actions = selection.actions;
9702
10029
  if (!actions) return [];
9703
10030
  return ["", styles.bold(actions.title), ...actions.options.map((option, index) => `${index === actions.index ? "\u2192 " : " "}${index === actions.index ? styles.accent(option) : option}`)];
9704
10031
  };
9705
- const detailRows = () => [...details(selectedNode), ...actionRows()];
10032
+ const treeRows = [styles.bold("Tree"), ...visibleNodes.length ? visibleNodes.map(treeLine) : [styles.muted("(empty)")]];
10033
+ lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, selection));
10034
+ if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned")) lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
10035
+ return lines.flatMap((line) => wrap(line));
10036
+ }
10037
+ function wrapNavigatorLine(text, width) {
10038
+ return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
10039
+ }
10040
+ function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
10041
+ const safeWidth = Math.max(1, width);
9706
10042
  if (safeWidth >= 80) {
9707
10043
  const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
9708
- const detailWidth = Math.max(1, safeWidth - sidebarWidth - 3);
9709
- const sidebar = renderTree(sidebarWidth).flatMap((line) => wrap(line, sidebarWidth));
9710
- const detail = detailRows().flatMap((line) => wrap(line, detailWidth));
9711
- const rows = Math.max(sidebar.length, detail.length);
9712
- for (let index = 0; index < rows; index += 1) lines.push(`${padTo(sidebar[index] ?? "", sidebarWidth)} | ${detail[index] ?? ""}`);
9713
- } else if (selection.detailsOnly) {
9714
- lines.push(...detailRows().flatMap((line) => wrap(line)));
9715
- } else {
9716
- lines.push(...renderTree(safeWidth));
9717
- if (!selection.treeOnly) lines.push("", ...detailRows().flatMap((line) => wrap(line)));
10044
+ const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
10045
+ const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
10046
+ return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
10047
+ const cell = left2[index] ?? "";
10048
+ return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
10049
+ });
9718
10050
  }
9719
- if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned")) lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
9720
- return lines.flatMap((line) => wrap(line));
10051
+ if (layout.detailsOnly) return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
10052
+ const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
10053
+ return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
9721
10054
  }
9722
10055
  function formatCheckpointReview(checkpoint) {
9723
10056
  const context = JSON.stringify(checkpoint.context, null, 2);
@@ -9727,7 +10060,7 @@ function formatCheckpointReview(checkpoint) {
9727
10060
  // src/retention.ts
9728
10061
  import { lstat, readdir as readdir4, readFile as readFile4, stat as stat3 } from "node:fs/promises";
9729
10062
  import { homedir as homedir6 } from "node:os";
9730
- import { join as join11, resolve as resolve5 } from "node:path";
10063
+ import { join as join12, resolve as resolve7 } from "node:path";
9731
10064
  var DAY_MS = 24 * 60 * 60 * 1e3;
9732
10065
  var REQUIRED_FILES = /* @__PURE__ */ new Set(["workflow.js", "state.json", "snapshot.json", "journal.json", "ownership.json", "worktrees.json", "borrowed-worktrees.json", "system-prompts.json"]);
9733
10066
  var OPTIONAL_FILES = /* @__PURE__ */ new Set(["result.json", "summary.json"]);
@@ -9736,21 +10069,21 @@ async function validateInventory(store, loaded) {
9736
10069
  const entries = await readdir4(store.directory, { withFileTypes: true });
9737
10070
  for (const entry of entries) {
9738
10071
  if (REQUIRED_DIRECTORIES.has(entry.name)) {
9739
- if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular directory: ${join11(store.directory, entry.name)}`);
10072
+ if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular directory: ${join12(store.directory, entry.name)}`);
9740
10073
  continue;
9741
10074
  }
9742
- if (!REQUIRED_FILES.has(entry.name) && !OPTIONAL_FILES.has(entry.name)) throw new Error(`Run inventory contains an unrecognized artifact: ${join11(store.directory, entry.name)}`);
9743
- if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join11(store.directory, entry.name)}`);
10075
+ if (!REQUIRED_FILES.has(entry.name) && !OPTIONAL_FILES.has(entry.name)) throw new Error(`Run inventory contains an unrecognized artifact: ${join12(store.directory, entry.name)}`);
10076
+ if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, entry.name)}`);
9744
10077
  }
9745
10078
  for (const name of REQUIRED_FILES) {
9746
- const entry = await lstat(join11(store.directory, name));
9747
- if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join11(store.directory, name)}`);
10079
+ const entry = await lstat(join12(store.directory, name));
10080
+ if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, name)}`);
9748
10081
  }
9749
10082
  if (loaded.run.state === "completed") {
9750
- const result = await lstat(join11(store.directory, "result.json"));
9751
- if (!result.isFile() || result.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join11(store.directory, "result.json")}`);
10083
+ const result = await lstat(join12(store.directory, "result.json"));
10084
+ if (!result.isFile() || result.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, "result.json")}`);
9752
10085
  }
9753
- if (await readFile4(join11(store.directory, "workflow.js"), "utf8") !== loaded.snapshot.script) throw new Error("Persisted workflow source does not match its launch snapshot");
10086
+ if (await readFile4(join12(store.directory, "workflow.js"), "utf8") !== loaded.snapshot.script) throw new Error("Persisted workflow source does not match its launch snapshot");
9754
10087
  }
9755
10088
  function runDependencyIds(run, borrowed) {
9756
10089
  const dependencies = /* @__PURE__ */ new Set();
@@ -9772,7 +10105,7 @@ async function scanRun(cwd, sessionId, runId, home) {
9772
10105
  await store.validateDeletionWorktrees();
9773
10106
  await store.validateBorrowedWorktrees();
9774
10107
  if (loaded.run.retry) await store.validateRetrySource();
9775
- const stateMtimeMs = (await stat3(join11(store.directory, "state.json"))).mtimeMs;
10108
+ const stateMtimeMs = (await stat3(join12(store.directory, "state.json"))).mtimeMs;
9776
10109
  const dependencies = runDependencyIds(loaded.run, await store.borrowedWorktrees());
9777
10110
  if (dependencies.includes(runId)) throw new Error(`Run ${runId} depends on itself`);
9778
10111
  return { sessionId, runId, store, run: loaded.run, stateMtimeMs, dependencies };
@@ -9827,9 +10160,9 @@ function cleanupOrder(runs, candidates) {
9827
10160
  return ordered;
9828
10161
  }
9829
10162
  async function recheck(run) {
9830
- const before = await stat3(join11(run.store.directory, "state.json"));
10163
+ const before = await stat3(join12(run.store.directory, "state.json"));
9831
10164
  const loaded = await run.store.load();
9832
- const after = await stat3(join11(run.store.directory, "state.json"));
10165
+ const after = await stat3(join12(run.store.directory, "state.json"));
9833
10166
  if (loaded.run.id !== run.runId || !HARD_TERMINAL_RUN_STATES.has(loaded.run.state) || before.mtimeMs !== after.mtimeMs || after.mtimeMs !== run.stateMtimeMs) throw new Error(`Retention candidate ${run.runId} changed before deletion`);
9834
10167
  }
9835
10168
  async function releaseLeases(leases) {
@@ -9837,8 +10170,8 @@ async function releaseLeases(leases) {
9837
10170
  for (const release of releases) if (release.status === "rejected") throw release.reason;
9838
10171
  }
9839
10172
  async function retainTerminalRuns(options) {
9840
- const home = resolve5(options.home ?? homedir6());
9841
- const cwd = resolve5(options.cwd);
10173
+ const home = resolve7(options.home ?? homedir6());
10174
+ const cwd = resolve7(options.cwd);
9842
10175
  const now = options.now ?? Date.now();
9843
10176
  if (!Number.isFinite(now)) throw new Error("Retention start time is invalid");
9844
10177
  if (options.retention.olderThanDays === void 0 && options.retention.maxTerminalRuns === void 0) return { candidates: [], deleted: [] };
@@ -9999,12 +10332,54 @@ var WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.edi
9999
10332
  function workflowKeyMatches(keybindings, data, binding) {
10000
10333
  return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data;
10001
10334
  }
10002
- function workflowKeyLabel(keybindings, binding, fallback, labels) {
10335
+ var WORKFLOW_KEY_LABELS = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
10336
+ function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
10003
10337
  const keys = keybindingKeys(keybindings, binding);
10004
10338
  const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
10005
10339
  const vim = WORKFLOW_VIM_KEYS[binding];
10006
10340
  return [...new Set(vim ? [...configured, vim] : configured)].join("/");
10007
10341
  }
10342
+ function createInlineConfirm(requestRender) {
10343
+ let pending;
10344
+ const answer = (value) => {
10345
+ const current = pending;
10346
+ pending = void 0;
10347
+ current?.resolve(value);
10348
+ requestRender();
10349
+ };
10350
+ return {
10351
+ ask(title, message) {
10352
+ answer(false);
10353
+ return new Promise((resolve9) => {
10354
+ pending = { title, message, yes: true, resolve: resolve9 };
10355
+ requestRender();
10356
+ });
10357
+ },
10358
+ active() {
10359
+ return pending !== void 0;
10360
+ },
10361
+ rows(width, styles) {
10362
+ if (!pending) return [];
10363
+ const yes = pending.yes;
10364
+ const message = truncateToVisualLines2(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
10365
+ return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => option === "Yes" === yes ? `\u2192 ${styles.accent(option)}` : ` ${option}`)];
10366
+ },
10367
+ hint(keybindings) {
10368
+ return `${workflowKeyLabel(keybindings, "tui.select.up", "\u2191")}/${workflowKeyLabel(keybindings, "tui.select.down", "\u2193")} select \xB7 ${workflowKeyLabel(keybindings, "tui.select.confirm", "enter")} confirm \xB7 ${workflowKeyLabel(keybindings, "tui.select.cancel", "esc")} cancel`;
10369
+ },
10370
+ handleInput(keybindings, data) {
10371
+ if (!pending) return;
10372
+ if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
10373
+ pending.yes = !pending.yes;
10374
+ requestRender();
10375
+ } else if (workflowKeyMatches(keybindings, data, "tui.select.confirm")) answer(pending.yes);
10376
+ else if (workflowKeyMatches(keybindings, data, "tui.select.cancel")) answer(false);
10377
+ },
10378
+ cancel() {
10379
+ answer(false);
10380
+ }
10381
+ };
10382
+ }
10008
10383
  function registerWorkflowNavigator(deps) {
10009
10384
  const { pi, home, clipboard, extensionAgentDir, runs, terminalRunStates, hardTerminalRunStates, ensureSessionLease, answerCheckpoint, recovery, stopWorkflowRun, moveForegroundToBackground, isForegroundAttached, liveAgents, registry, projectTrusted: projectTrusted2, resumeHostContext: resumeHostContext2, resumeSelectedWorkflow, reportBlocked, setNavigatorOpen, trajectoryProvider, coordinateRunMutation: coordinateRunMutationDependency } = deps;
10010
10385
  const coordinateRunMutation = coordinateRunMutationDependency ?? ((task) => task());
@@ -10046,7 +10421,7 @@ function registerWorkflowNavigator(deps) {
10046
10421
  const setStatus = uiHostCapabilities(ctx.ui)?.setStatus;
10047
10422
  setStatus?.call(ctx.ui, "workflow-stop", text);
10048
10423
  };
10049
- const runAction = async (actionCommand, status = setWorkflowStatus) => {
10424
+ const runAction = async (actionCommand, status = setWorkflowStatus, confirm = (title, message) => confirmWithBlocked(ctx.ui, reportBlocked, title, message)) => {
10050
10425
  const [action, runId, ...rest] = actionCommand.split(/\s+/);
10051
10426
  try {
10052
10427
  const run = runId ? runs.get(runId) : void 0;
@@ -10133,7 +10508,7 @@ function registerWorkflowNavigator(deps) {
10133
10508
  }
10134
10509
  if (action === "stop" && run) {
10135
10510
  const workflowName = stored?.loaded.run.workflowName ?? run.metadata.name;
10136
- if (!await confirmWithBlocked(ctx.ui, reportBlocked, "Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`)) return "dashboard";
10511
+ if (!await confirm("Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`)) return "dashboard";
10137
10512
  status(`Stopping workflow ${workflowName}...`);
10138
10513
  await stopWorkflowRun(run.store.runId);
10139
10514
  status(`Workflow ${run.store.runId} stopped.`);
@@ -10494,8 +10869,18 @@ Mutations are available through workflow tools.`, "info");
10494
10869
  let selectedNodeId = tree.nodes[0]?.id;
10495
10870
  let expandedNodeIds = new Set(workflowPhaseTreeInitialExpanded(tree));
10496
10871
  const terminalRows = () => Math.max(1, tuiRows(tui) - WORKFLOW_PANEL_FOOTER_ROWS);
10497
- const keyLabels = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
10498
- const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
10872
+ const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
10873
+ const inlineConfirm = createInlineConfirm(() => {
10874
+ if (!disposed) tui.requestRender();
10875
+ });
10876
+ const confirmInDashboard = async (title, message) => {
10877
+ reportBlocked?.(true, title);
10878
+ try {
10879
+ return await inlineConfirm.ask(title, message);
10880
+ } finally {
10881
+ reportBlocked?.(false);
10882
+ }
10883
+ };
10499
10884
  const progressNow = () => {
10500
10885
  const now = Date.now();
10501
10886
  if (hardTerminalRunStates.has(view.run.state)) {
@@ -10572,7 +10957,7 @@ Mutations are available through workflow tools.`, "info");
10572
10957
  stopStatus = status;
10573
10958
  setWorkflowStatus(status);
10574
10959
  if (!disposed) tui.requestRender();
10575
- }).then(async (outcome2) => {
10960
+ }, confirmInDashboard).then(async (outcome2) => {
10576
10961
  if (outcome2 === "stopped") {
10577
10962
  done("__stopped__");
10578
10963
  return;
@@ -10609,7 +10994,8 @@ Mutations are available through workflow tools.`, "info");
10609
10994
  const content = [...statusLines, ...phaseLines];
10610
10995
  const rows = terminalRows();
10611
10996
  const hintRows = rows >= 3 ? 1 : 0;
10612
- const viewport = Math.max(1, rows - hintRows);
10997
+ const confirmRows = inlineConfirm.rows(width, styles);
10998
+ const viewport = Math.max(1, rows - hintRows - confirmRows.length);
10613
10999
  const maxOffset2 = Math.max(0, content.length - viewport);
10614
11000
  dashboardOffset = Math.max(0, Math.min(maxOffset2, dashboardOffset));
10615
11001
  if (actionMode) {
@@ -10630,12 +11016,16 @@ Mutations are available through workflow tools.`, "info");
10630
11016
  dashboardOffset = Math.max(0, Math.min(maxOffset2, dashboardOffset));
10631
11017
  const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : void 0;
10632
11018
  const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
10633
- const hint = truncateToVisualLines2(theme.fg("dim", actionMode ? `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} actions \xB7 ${keyLabel("tui.select.confirm", "enter")} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} tree \xB7 ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} tree \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")}/${keyLabel("tui.editor.cursorRight", "\u2192")} collapse/expand \xB7 ${keyLabel("tui.select.confirm", "enter")} ${enterAction} \xB7 a actions \xB7 ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} \xB7 auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
10634
- return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...hintRows ? [hint] : []];
11019
+ const hint = truncateToVisualLines2(theme.fg("dim", inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} actions \xB7 ${keyLabel("tui.select.confirm", "enter")} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} tree \xB7 ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} tree \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")}/${keyLabel("tui.editor.cursorRight", "\u2192")} collapse/expand \xB7 ${keyLabel("tui.select.confirm", "enter")} ${enterAction} \xB7 a actions \xB7 ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} \xB7 auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
11020
+ return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...confirmRows, ...hintRows ? [hint] : []];
10635
11021
  },
10636
11022
  invalidate() {
10637
11023
  },
10638
11024
  handleInput(data) {
11025
+ if (inlineConfirm.active()) {
11026
+ inlineConfirm.handleInput(keybindings, data);
11027
+ return;
11028
+ }
10639
11029
  if (stopRequested || editorRunning) return;
10640
11030
  const narrow = renderedWidth < 80;
10641
11031
  if (!actionMode && (data === "a" || data === "A")) {
@@ -10687,7 +11077,7 @@ Mutations are available through workflow tools.`, "info");
10687
11077
  });
10688
11078
  }
10689
11079
  }
10690
- } else if (action === "Open script in editor") void openArtifact(readFile5(join12(store.directory, "workflow.js"), "utf8").then(workflowScriptArtifact), "workflow script");
11080
+ } else if (action === "Open script in editor") void openArtifact(readFile5(join13(store.directory, "workflow.js"), "utf8").then(workflowScriptArtifact), "workflow script");
10691
11081
  else if (action === "Stop") requestStop();
10692
11082
  else done(action);
10693
11083
  }
@@ -10755,6 +11145,7 @@ Mutations are available through workflow tools.`, "info");
10755
11145
  disposed = true;
10756
11146
  stopTimer();
10757
11147
  setWorkflowStatus(void 0);
11148
+ inlineConfirm.cancel();
10758
11149
  }
10759
11150
  };
10760
11151
  }) : await ctx.ui.select(view.dashboard, [...view.actions.keys(), "Back"]);
@@ -10809,8 +11200,7 @@ Mutations are available through workflow tools.`, "info");
10809
11200
  const currentLayout = layout();
10810
11201
  const maxOffset2 = Math.max(0, renderedLines.length - currentLayout.contentViewport);
10811
11202
  offset = Math.min(offset, maxOffset2);
10812
- const keyLabels = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
10813
- const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
11203
+ const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
10814
11204
  const hint = truncateToVisualLines2(theme.fg("dim", `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")}/pgup/pgdn scroll \xB7 enter select \xB7 esc cancel`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
10815
11205
  const controls = currentLayout.compactControls ? [options.map((option, index) => `${index === selectedIndex ? "[" : " "}${option}${index === selectedIndex ? "]" : " "}`).join(" ")] : options.map((option, index) => `${index === selectedIndex ? "\u2192 " : " "}${option}`);
10816
11206
  return [
@@ -10870,9 +11260,69 @@ Mutations are available through workflow tools.`, "info");
10870
11260
  });
10871
11261
  }
10872
11262
 
11263
+ // src/script-tools.ts
11264
+ function scriptToolExcluded(name) {
11265
+ return name === "workflow" || name.startsWith("workflow_") || name.startsWith("subagents_");
11266
+ }
11267
+ function scriptToolIdentifier(name) {
11268
+ let identifier = "";
11269
+ for (const char of name) identifier += (identifier === "" ? /^[A-Za-z_$]$/ : /^[A-Za-z0-9_$]$/).test(char) ? char : "_";
11270
+ return identifier || "_";
11271
+ }
11272
+ function scriptToolContext(ctx) {
11273
+ return object(ctx) && typeof ctx.executeTool === "function" && Array.isArray(ctx.tools) ? ctx : void 0;
11274
+ }
11275
+ function scriptToolIdentifiers(context) {
11276
+ return (context?.tools ?? []).filter(({ name }) => !scriptToolExcluded(name)).map(({ name }) => scriptToolIdentifier(name));
11277
+ }
11278
+ function scriptTool(context, identifier) {
11279
+ return context.tools.find(({ name }) => !scriptToolExcluded(name) && scriptToolIdentifier(name) === identifier);
11280
+ }
11281
+ function scriptToolValue(tool, outcome) {
11282
+ const structured = tool.outputSchema === void 0 ? void 0 : outcome.result.structuredContent;
11283
+ const text = outcome.result.content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n");
11284
+ if (structured === void 0 && outcome.isError) throw new WorkflowError("TOOL_FAILED", `${tool.name}: ${text || "failed"}`);
11285
+ let value;
11286
+ try {
11287
+ value = JSON.parse(JSON.stringify(structured ?? text));
11288
+ } catch (error) {
11289
+ throw new WorkflowError("TOOL_FAILED", `${tool.name} returned a result that is not JSON: ${errorText(error)}`);
11290
+ }
11291
+ if (!jsonValue(value)) throw new WorkflowError("TOOL_FAILED", `${tool.name} returned a result that is not JSON`);
11292
+ if (Buffer.byteLength(JSON.stringify(value)) > RPC_LIMIT_BYTES) throw new WorkflowError("TOOL_FAILED", `${tool.name} returned more than the 10 MB workflow RPC limit`);
11293
+ return value;
11294
+ }
11295
+ function validateScriptToolReferences(script, context) {
11296
+ const references = scriptToolReferences(script);
11297
+ if (!references?.length) return;
11298
+ if (!context) throw new WorkflowError("UNKNOWN_TOOL", `Workflow scripts can call session tools only when launched by the workflow tool; this launch has none for ${references.map((name) => `tools.${name}`).join(", ")}`);
11299
+ const identifiers = scriptToolIdentifiers(context);
11300
+ const unknown = references.filter((name) => !identifiers.includes(name));
11301
+ if (unknown.length) throw new WorkflowError("UNKNOWN_TOOL", `Workflow script calls tools this session cannot call: ${unknown.map((name) => `tools.${name}`).join(", ")}`);
11302
+ const ambiguous = references.filter((name) => identifiers.indexOf(name) !== identifiers.lastIndexOf(name));
11303
+ if (ambiguous.length) throw new WorkflowError("UNKNOWN_TOOL", `Workflow script calls tools whose names collide after normalization: ${ambiguous.map((name) => `tools.${name}`).join(", ")}`);
11304
+ }
11305
+ function describeScriptOutput(schema) {
11306
+ if (!object(schema) || schema.type !== "object" || !object(schema.properties)) return schema === void 0 ? void 0 : "a JSON value";
11307
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
11308
+ return `\`{ ${Object.keys(schema.properties).map((name) => required.has(name) ? name : `${name}?`).join(", ")} }\``;
11309
+ }
11310
+ function prepareScriptToolLoadout(loadout) {
11311
+ if (loadout.declared.some(({ name }) => name === "codemode")) return void 0;
11312
+ const callable = new Set(loadout.callable.map(({ name }) => name));
11313
+ const descriptions = {};
11314
+ for (const tool of loadout.declared) {
11315
+ const output = callable.has(tool.name) && !scriptToolExcluded(tool.name) ? describeScriptOutput(tool.outputSchema) : void 0;
11316
+ if (output) descriptions[tool.name] = `${tool.description.trim()}
11317
+
11318
+ Workflow scripts: \`tools.${scriptToolIdentifier(tool.name)}(args)\` resolves to ${output}.`;
11319
+ }
11320
+ return { descriptions };
11321
+ }
11322
+
10873
11323
  // src/changelog.ts
10874
11324
  import { mkdir as mkdir3, readFile as readFile6 } from "node:fs/promises";
10875
- import { dirname as dirname5, join as join13 } from "node:path";
11325
+ import { dirname as dirname6, join as join14 } from "node:path";
10876
11326
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10877
11327
  var STATE_DIRECTORY = "pi-extensible-workflows";
10878
11328
  var STATE_FILE = "changelog-state.json";
@@ -10883,15 +11333,15 @@ function packageMetadata(value, directory) {
10883
11333
  }
10884
11334
  async function packageMetadataAt(directory) {
10885
11335
  try {
10886
- const value = JSON.parse(await readFile6(join13(directory, "package.json"), "utf8"));
11336
+ const value = JSON.parse(await readFile6(join14(directory, "package.json"), "utf8"));
10887
11337
  return packageMetadata(value, directory);
10888
11338
  } catch {
10889
11339
  return void 0;
10890
11340
  }
10891
11341
  }
10892
11342
  async function installedPackageMetadata() {
10893
- const moduleDirectory = dirname5(fileURLToPath4(import.meta.url));
10894
- for (const directory of [join13(moduleDirectory, ".."), join13(moduleDirectory, "../..")]) {
11343
+ const moduleDirectory = dirname6(fileURLToPath4(import.meta.url));
11344
+ for (const directory of [join14(moduleDirectory, ".."), join14(moduleDirectory, "../..")]) {
10895
11345
  const metadata = await packageMetadataAt(directory);
10896
11346
  if (metadata) return metadata;
10897
11347
  }
@@ -10918,7 +11368,7 @@ ${line}` : line;
10918
11368
  }
10919
11369
  async function readChangelog(directory) {
10920
11370
  try {
10921
- return parseChangelog(await readFile6(join13(directory, "CHANGELOG.md"), "utf8"));
11371
+ return parseChangelog(await readFile6(join14(directory, "CHANGELOG.md"), "utf8"));
10922
11372
  } catch {
10923
11373
  return [];
10924
11374
  }
@@ -10955,14 +11405,14 @@ async function showChangelogNotice(context, agentDir, packageDirectory) {
10955
11405
  try {
10956
11406
  const metadata = packageDirectory === void 0 ? await installedPackageMetadata() : await packageMetadataAt(packageDirectory);
10957
11407
  if (!metadata) return;
10958
- const statePath = join13(agentDir, STATE_DIRECTORY, STATE_FILE);
11408
+ const statePath = join14(agentDir, STATE_DIRECTORY, STATE_FILE);
10959
11409
  const previousVersion = await lastNotifiedVersion(statePath);
10960
11410
  if (previousVersion === metadata.version) return;
10961
11411
  const entries = releaseEntries(await readChangelog(metadata.directory), metadata.version, previousVersion);
10962
11412
  if (!entries.length) return;
10963
11413
  ui.notify(noticeText(metadata.version, entries), "info");
10964
11414
  try {
10965
- await mkdir3(dirname5(statePath), { recursive: true, mode: 448 });
11415
+ await mkdir3(dirname6(statePath), { recursive: true, mode: 448 });
10966
11416
  await atomicJson(statePath, { lastNotifiedVersion: metadata.version });
10967
11417
  } catch {
10968
11418
  }
@@ -10971,11 +11421,11 @@ async function showChangelogNotice(context, agentDir, packageDirectory) {
10971
11421
  }
10972
11422
 
10973
11423
  // src/trajectory.ts
10974
- import { createHash as createHash3 } from "node:crypto";
11424
+ import { createHash as createHash4 } from "node:crypto";
10975
11425
  import { open as open2, readFile as readFile7, readdir as readdir5, stat as stat4 } from "node:fs/promises";
10976
11426
  import { homedir as homedir7 } from "node:os";
10977
- import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
10978
- import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as getAgentDir3, SettingsManager as SettingsManager3, DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2 } from "@earendil-works/pi-coding-agent";
11427
+ import { basename as basename5, dirname as dirname7, join as join15, resolve as resolve8 } from "node:path";
11428
+ import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as getAgentDir4, SettingsManager as SettingsManager3, DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2 } from "@earendil-works/pi-coding-agent";
10979
11429
 
10980
11430
  // subagents/src/contracts.ts
10981
11431
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -11003,7 +11453,8 @@ var SUBAGENTS_RUN_PARAMETERS = Type2.Object({
11003
11453
  timeoutMs: Type2.Optional(Type2.Union([Type2.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type2.Null()]))
11004
11454
  }, { additionalProperties: false });
11005
11455
  var SUBAGENTS_INSPECT_PARAMETERS = Type2.Object({
11006
- id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" }))
11456
+ id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" })),
11457
+ scope: Type2.Optional(Type2.Union([Type2.Literal("session"), Type2.Literal("all")], { description: "List scope when id is omitted: session (default) lists runs from the current Pi session; all lists every stored run" }))
11007
11458
  }, { additionalProperties: false });
11008
11459
  var SUBAGENTS_ID_PARAMETERS = Type2.Object({
11009
11460
  id: Type2.String({ description: "Subagent ID" })
@@ -11018,6 +11469,21 @@ var SUBAGENTS_STOP_PARAMETERS = Type2.Object({
11018
11469
  var SUBAGENTS_RETRY_PARAMETERS = Type2.Object({
11019
11470
  id: Type2.String({ description: "Failed or stopped subagent ID to retry" })
11020
11471
  }, { additionalProperties: false });
11472
+ var SUBAGENT_STATUS_OUTPUT = Type2.Object({
11473
+ id: Type2.String(),
11474
+ state: Type2.String(),
11475
+ sessionId: Type2.Optional(Type2.String()),
11476
+ startedAt: Type2.Optional(Type2.Number()),
11477
+ finishedAt: Type2.Optional(Type2.Number()),
11478
+ attempts: Type2.Optional(Type2.Number()),
11479
+ attemptDetails: Type2.Optional(Type2.Array(Type2.Unknown())),
11480
+ worktree: Type2.Optional(Type2.Object({ path: Type2.String(), branch: Type2.String() })),
11481
+ error: Type2.Optional(Type2.Object({ code: Type2.String(), message: Type2.String() })),
11482
+ progress: Type2.Optional(Type2.Unknown()),
11483
+ value: Type2.Optional(Type2.Unknown())
11484
+ });
11485
+ var SUBAGENTS_INSPECT_OUTPUT = Type2.Union([SUBAGENT_STATUS_OUTPUT, Type2.Array(Type2.Object({ id: Type2.String(), state: Type2.String() }))]);
11486
+ var SUBAGENTS_STEER_OUTPUT = Type2.Object({ id: Type2.String(), accepted: Type2.Boolean() });
11021
11487
  function normalizeSubagentRunRequest(value) {
11022
11488
  if (!Value2.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
11023
11489
  if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
@@ -11193,6 +11659,18 @@ function statusValue(value) {
11193
11659
  // src/trajectory.ts
11194
11660
  var TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
11195
11661
  var TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
11662
+ function subagentOutput(options) {
11663
+ if (options.resultTruncated) return { status: "truncated", kind: "result", path: options.resultPath, bytes: options.resultBytes ?? 0 };
11664
+ if (options.result !== void 0) return { status: "available", value: options.result, bytes: serializedJsonBytes(options.result) };
11665
+ if (options.failureTruncated) return { status: "truncated", kind: "failure", path: options.failurePath, bytes: options.failureBytes ?? 0 };
11666
+ if (options.failure !== void 0) return { status: "failed", code: options.failure.code, message: options.failure.message };
11667
+ if (options.state === "failed" || options.state === "stopped") {
11668
+ const error = options.statusError ?? options.attempt?.error;
11669
+ return { status: options.state === "stopped" ? "cancelled" : "failed", code: error?.code ?? (options.state === "stopped" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Subagent ${options.state}` };
11670
+ }
11671
+ if (options.state === "completed") return { status: "unavailable" };
11672
+ return { status: "pending" };
11673
+ }
11196
11674
  function sessionFile(reference) {
11197
11675
  const locator = reference?.locator;
11198
11676
  if (!object(locator) || typeof locator.sessionFile !== "string" || !locator.sessionFile) return void 0;
@@ -11208,7 +11686,7 @@ function transcriptToolCallId(value) {
11208
11686
  return void 0;
11209
11687
  }
11210
11688
  function isTimingTranscriptEntry(value) {
11211
- return object(value) && value.type === "custom" && value.customType === "pi-workflows:tool-timing";
11689
+ return object(value) && value.type === "custom" && value.customType === TOOL_TIMING_ENTRY_TYPE;
11212
11690
  }
11213
11691
  function timingToolCallId(value) {
11214
11692
  if (!object(value) || !isTimingTranscriptEntry(value) || !object(value.data)) return void 0;
@@ -11329,21 +11807,21 @@ function withPiToolDescriptions(run) {
11329
11807
  function canonicalExtensionSelector2(selector, base) {
11330
11808
  const negated = selector.startsWith("!");
11331
11809
  const body = negated ? selector.slice(1) : selector;
11332
- if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
11333
- const resolved = resolve6(base, body);
11810
+ if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
11811
+ const resolved = resolve8(base, body);
11334
11812
  if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
11335
11813
  return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
11336
11814
  }
11337
11815
  function skillNameFromPath(path) {
11338
11816
  const file = basename5(path);
11339
- return file.toLowerCase() === "skill.md" ? basename5(dirname6(path)) : file;
11817
+ return file.toLowerCase() === "skill.md" ? basename5(dirname7(path)) : file;
11340
11818
  }
11341
11819
  var discoveredResourcesByCwd = /* @__PURE__ */ new Map();
11342
11820
  async function discoveredResources(cwd) {
11343
11821
  const cached = discoveredResourcesByCwd.get(cwd);
11344
11822
  if (cached) return cached;
11345
11823
  const pending = (async () => {
11346
- const agentDir = getAgentDir3();
11824
+ const agentDir = getAgentDir4();
11347
11825
  const settingsManager = SettingsManager3.create(cwd, agentDir, { projectTrusted: false });
11348
11826
  const packageManager = new DefaultPackageManager2({ cwd, agentDir, settingsManager });
11349
11827
  const resolved = await packageManager.resolve();
@@ -11395,11 +11873,62 @@ function withoutAgentActivities(run) {
11395
11873
  });
11396
11874
  return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
11397
11875
  }
11876
+ function serializedJsonBytes(value) {
11877
+ const serialized = JSON.stringify(value);
11878
+ return Buffer.byteLength(serialized, "utf8");
11879
+ }
11880
+ function failedAgentOutput(agent) {
11881
+ const error = [...agent.attemptDetails ?? []].reverse().find((attempt) => attempt.error)?.error;
11882
+ const status = agent.state === "cancelled" ? "cancelled" : "failed";
11883
+ return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
11884
+ }
11885
+ function operationOutput(operation) {
11886
+ const bytes = serializedJsonBytes(operation.value);
11887
+ return bytes > DEFAULT_MAX_BYTES2 ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
11888
+ }
11889
+ function outputForCall(call, operation) {
11890
+ if (operation !== void 0) return call.kind === "tool" ? { status: "completed" } : operationOutput(operation);
11891
+ if (call.error) return { status: call.error.code === "CANCELLED" ? "cancelled" : "failed", code: call.error.code, ...call.kind === "shell" ? { message: call.error.message ?? "Shell failed" } : {} };
11892
+ return call.finishedAt === void 0 ? { status: "pending" } : { status: "unavailable" };
11893
+ }
11894
+ function outputForAgent(agent, operation) {
11895
+ if (operation !== void 0) return operationOutput(operation);
11896
+ if (agent.state === "failed" || agent.state === "cancelled") return failedAgentOutput(agent);
11897
+ if (agent.state === "completed") return { status: "unavailable" };
11898
+ return { status: "pending" };
11899
+ }
11900
+ function applyTrajectoryAgentOutputs(run, operations) {
11901
+ const byPath = new Map(operations.map((operation) => [operation.path, operation]));
11902
+ const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === void 0 ? void 0 : byPath.get(agent.resultPath)) }));
11903
+ const scriptCalls = run.scriptCalls?.map((call) => {
11904
+ const recorded = { ...call, output: outputForCall(call, byPath.get(call.path)) };
11905
+ if (call.kind === "tool") {
11906
+ delete recorded.input;
11907
+ delete recorded.inputBytes;
11908
+ if (call.error) recorded.error = { code: call.error.code };
11909
+ }
11910
+ return recorded;
11911
+ });
11912
+ const rest = run;
11913
+ return { ...rest, agents, ...scriptCalls === void 0 ? {} : { scriptCalls } };
11914
+ }
11915
+ function overlayTrajectoryRun(run, overlay) {
11916
+ const overlaid = overlay(run);
11917
+ const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
11918
+ const agents = overlaid.agents.map((agent) => {
11919
+ const previous = outputs.get(agent.id);
11920
+ const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, void 0);
11921
+ return { ...agent, output };
11922
+ });
11923
+ return { ...overlaid, agents, ...run.scriptCalls === void 0 ? {} : { scriptCalls: run.scriptCalls } };
11924
+ }
11398
11925
  async function loadTrajectoryRun(store, includeTranscripts = true) {
11399
11926
  const value = await store.load();
11400
11927
  const summary = await store.loadSummary().catch(() => void 0);
11401
11928
  const prompts = await store.systemPrompts().catch(() => []);
11402
- const run = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
11929
+ const prepared = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
11930
+ const operations = await store.replayableOperations().catch(() => []);
11931
+ const run = applyTrajectoryAgentOutputs(prepared, operations);
11403
11932
  return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...summary?.createdAt === void 0 ? {} : { createdAt: summary.createdAt }, transcripts: includeTranscripts ? await runTranscripts(run) : {} };
11404
11933
  }
11405
11934
  async function fileMtime(path) {
@@ -11408,8 +11937,8 @@ async function fileMtime(path) {
11408
11937
  function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature, overlay) {
11409
11938
  const cache = /* @__PURE__ */ new Map();
11410
11939
  const changed = async (store, entry) => {
11411
- if (await fileMtime(join14(store.directory, "state.json")) !== entry.stateMtimeMs) return true;
11412
- if (await fileMtime(join14(store.directory, "journal.json")) !== entry.journalMtimeMs) return true;
11940
+ if (await fileMtime(join15(store.directory, "state.json")) !== entry.stateMtimeMs) return true;
11941
+ if (await fileMtime(join15(store.directory, "journal.json")) !== entry.journalMtimeMs) return true;
11413
11942
  for (const [agentId, path] of transcriptPaths(entry.value.run)) if (await transcriptSignature(path) !== entry.transcripts.get(agentId)) return true;
11414
11943
  return false;
11415
11944
  };
@@ -11426,10 +11955,10 @@ function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature,
11426
11955
  const value = await load(store);
11427
11956
  const transcripts = /* @__PURE__ */ new Map();
11428
11957
  for (const [agentId, path] of transcriptPaths(value.run)) transcripts.set(agentId, await transcriptSignature(path));
11429
- entry = { value, stateMtimeMs: await fileMtime(join14(store.directory, "state.json")) ?? 0, journalMtimeMs: await fileMtime(join14(store.directory, "journal.json")) ?? 0, transcripts };
11958
+ entry = { value, stateMtimeMs: await fileMtime(join15(store.directory, "state.json")) ?? 0, journalMtimeMs: await fileMtime(join15(store.directory, "journal.json")) ?? 0, transcripts };
11430
11959
  cache.set(runId, entry);
11431
11960
  }
11432
- loaded.push(overlay ? { ...entry.value, run: overlay(entry.value.run) } : entry.value);
11961
+ loaded.push(overlay ? { ...entry.value, run: overlayTrajectoryRun(entry.value.run, overlay) } : entry.value);
11433
11962
  } catch {
11434
11963
  cache.delete(runId);
11435
11964
  }
@@ -11441,7 +11970,7 @@ function createTrajectoryRunLoader(cwd, sessionId, home = homedir7(), overlay) {
11441
11970
  return createCachedRunLoader(cwd, sessionId, home, (store) => loadTrajectoryRun(store), fileMtime, overlay);
11442
11971
  }
11443
11972
  function transcriptRevision(info) {
11444
- const digest = createHash3("sha256").update(`${String(info.mtimeNs)}:${String(info.size)}`).digest();
11973
+ const digest = createHash4("sha256").update(`${String(info.mtimeNs)}:${String(info.size)}`).digest();
11445
11974
  return Math.max(1, digest.readUIntBE(0, 6));
11446
11975
  }
11447
11976
  async function transcriptMetadata(path) {
@@ -11512,7 +12041,7 @@ function createTrajectoryTranscriptLoader(cwd, sessionId, home = homedir7(), age
11512
12041
  path = sessionFile(value.run.agents.find((agent) => agent.id === request.agentId)?.attemptDetails?.at(-1)?.session);
11513
12042
  } else if (request.subagentId !== void 0 && request.runId === void 0 && request.agentId === void 0) {
11514
12043
  if (!safeTrajectorySubagentId(request.subagentId)) return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
11515
- const rawStatus = await readSubagentJson(join14(agentDir, "subagents", request.subagentId, "status.json"));
12044
+ const rawStatus = await readSubagentJson(join15(agentDir, "subagents", request.subagentId, "status.json"));
11516
12045
  const status = statusValue(rawStatus);
11517
12046
  const statusRecord = object(rawStatus) ? rawStatus : void 0;
11518
12047
  const statusCwd = statusRecord === void 0 ? void 0 : statusRecord.cwd;
@@ -11573,13 +12102,13 @@ function withoutProgressActivity(progress) {
11573
12102
  async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscript = true) {
11574
12103
  try {
11575
12104
  const id = basename5(directory);
11576
- const status = statusValue(await readSubagentJson(join14(directory, "status.json")));
12105
+ const status = statusValue(await readSubagentJson(join15(directory, "status.json")));
11577
12106
  if (!status || status.id !== id) return void 0;
11578
12107
  if (status.sessionId !== sessionId) return "foreign";
11579
12108
  const progress = status.progress === void 0 ? void 0 : withoutProgressActivity(status.progress);
11580
- const request = normalizeSubagentRunRequest(await readSubagentJson(join14(directory, "request.json")));
11581
- const resultPath = join14(directory, "result.json");
11582
- const failurePath = join14(directory, "failure.json");
12109
+ const request = normalizeSubagentRunRequest(await readSubagentJson(join15(directory, "request.json")));
12110
+ const resultPath = join15(directory, "result.json");
12111
+ const failurePath = join15(directory, "failure.json");
11583
12112
  const resultBytes = await subagentFileBytes(resultPath);
11584
12113
  const failureBytes = await subagentFileBytes(failurePath);
11585
12114
  const resultValue = await readSubagentJson(resultPath, true, DEFAULT_MAX_BYTES2);
@@ -11619,19 +12148,20 @@ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscri
11619
12148
  ...progress === void 0 ? {} : { progress },
11620
12149
  ...attempt === void 0 ? {} : { attempt },
11621
12150
  ...result === void 0 ? {} : { result },
11622
- ...failure === void 0 ? {} : { failure }
12151
+ ...failure === void 0 ? {} : { failure },
12152
+ output: subagentOutput({ state: status.state, statusError: status.error, attempt, result: resultValue === void 0 ? void 0 : boundedSubagentJson(resultValue), resultTruncated, resultPath, resultBytes, failure: failureValue === void 0 ? void 0 : subagentErrorValue(failureValue), failureTruncated, failurePath, failureBytes })
11623
12153
  };
11624
12154
  } catch {
11625
12155
  return void 0;
11626
12156
  }
11627
12157
  }
11628
12158
  async function subagentCacheEntryChanged(directory, entry) {
11629
- for (const file of TRAJECTORY_SUBAGENT_FILES) if (await fileMtime(join14(directory, file)) !== entry.fileMtimes.get(file)) return true;
12159
+ for (const file of TRAJECTORY_SUBAGENT_FILES) if (await fileMtime(join15(directory, file)) !== entry.fileMtimes.get(file)) return true;
11630
12160
  const transcriptPath = sessionFile(entry.value.attempt?.session);
11631
12161
  return transcriptPath !== void 0 && await fileMtime(transcriptPath) !== entry.transcriptMtime;
11632
12162
  }
11633
12163
  function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, includeTranscript = true) {
11634
- const root = join14(agentDir, "subagents");
12164
+ const root = join15(agentDir, "subagents");
11635
12165
  const cache = /* @__PURE__ */ new Map();
11636
12166
  const negativeCache = /* @__PURE__ */ new Map();
11637
12167
  return async () => {
@@ -11648,8 +12178,8 @@ function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, inclu
11648
12178
  for (const id of negativeCache.keys()) if (!current.has(id)) negativeCache.delete(id);
11649
12179
  const loaded = [];
11650
12180
  for (const id of ids) {
11651
- const directory = join14(root, id);
11652
- const statusPath = join14(directory, "status.json");
12181
+ const directory = join15(root, id);
12182
+ const statusPath = join15(directory, "status.json");
11653
12183
  try {
11654
12184
  const statusMtime = await fileMtime(statusPath);
11655
12185
  if (negativeCache.has(id) && negativeCache.get(id) === statusMtime) continue;
@@ -11667,7 +12197,7 @@ function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, inclu
11667
12197
  continue;
11668
12198
  }
11669
12199
  const fileMtimes = /* @__PURE__ */ new Map();
11670
- for (const file of TRAJECTORY_SUBAGENT_FILES) fileMtimes.set(file, await fileMtime(join14(directory, file)));
12200
+ for (const file of TRAJECTORY_SUBAGENT_FILES) fileMtimes.set(file, await fileMtime(join15(directory, file)));
11671
12201
  const transcriptPath = sessionFile(value.attempt?.session);
11672
12202
  entry = { value, fileMtimes, transcriptMtime: transcriptPath === void 0 ? void 0 : await fileMtime(transcriptPath) };
11673
12203
  cache.set(id, entry);
@@ -11723,6 +12253,12 @@ function mainAgentError(error) {
11723
12253
  Object.assign(presented, typed);
11724
12254
  return presented;
11725
12255
  }
12256
+ function completionControlValue(result, controlRunId) {
12257
+ const record3 = { ...controlRunId === void 0 ? {} : { runId: controlRunId }, ...result };
12258
+ delete record3.run;
12259
+ delete record3.completion;
12260
+ return record3;
12261
+ }
11726
12262
  function completionControlContent(result, controlRunId) {
11727
12263
  const record3 = object(result) ? { ...result } : void 0;
11728
12264
  if (record3 && controlRunId !== void 0 && record3.runId === void 0) record3.runId = controlRunId;
@@ -11768,7 +12304,7 @@ function formatWorkflowPreview(args) {
11768
12304
  return [`workflow ${name}`, typeof args.description === "string" && args.description.trim() ? args.description.trim() : ""].filter(Boolean).join("\n");
11769
12305
  }
11770
12306
  var WORKFLOW_TOOL_LABEL = "Workflow";
11771
- var WORKFLOW_TOOL_DESCRIPTION = "Run a deterministic JavaScript workflow with a named inline or file-backed parallel-to-summary path by default";
12307
+ var WORKFLOW_TOOL_DESCRIPTION = "Run a deterministic JavaScript workflow with a named inline or file-backed parallel-to-summary path by default. Scripts call this session's tools as `await tools.<name>(args)`, outside withWorktree: a tool resolves to its text unless its description gives a `tools.<name>(args)` result shape, and failures reject";
11772
12308
  var WORKFLOW_TOOL_PROMPT_SNIPPET = "Run a deterministic, resumable JavaScript workflow. Prefer a named inline script that fans out independent work with parallel(...), awaits the keyed results before interpolating them into one summarizing agent(...), and returns. Provide exactly one of script or scriptPath and a non-empty name. Registered catalog functions are available as globals inside the script; call them there, for example return await someFunction(args). Advanced controls include registered functions, outputSchema, budgets, checkpoints, worktrees, retry/resume, CLI export, and pipelines. Runs are in the background by default; completion arrives as a follow-up message. Set foreground: true when the caller must wait for the final value. Manage runs from the interactive /workflow picker; use workflow_status, workflow_resume, workflow_retry, workflow_stop, and workflow_respond for explicit tool controls. If a foreground call detaches before its result is accepted, its terminal success or failure is promoted to one follow-up message. Foreground results include the completed run ID. Recovery inherits the source launch mode; legacy snapshots without launchMode recover in the background. Set foreground: true or false on workflow_resume/workflow_retry to override it; foreground recovery waits for terminal value and run details, while background recovery returns immediately and delivers completion or failure as a follow-up. After failure follow-ups, especially CANCELLED or interrupted runs, call workflow_status({ runId }) before recovery or replacement work, then pass its state as expectedState to workflow_retry/workflow_resume so recovery cannot act on a state that changed. Recovery map: agent(..., { retries }) reruns one agent call in the same run for transient failures; workflow_retry({ runId, expectedState?, foreground? }) replays a failed run into a child; workflow_resume({ runId, expectedState?, budget?, foreground? }) continues a budget_exhausted run; parentRunId on a new launch only borrows named worktrees and never replays or resumes.";
11773
12309
  var WORKFLOW_TOOL_PARAMETERS = Type3.Object({
11774
12310
  name: Type3.String({ description: "Required non-empty workflow name" }),
@@ -11783,8 +12319,22 @@ var WORKFLOW_TOOL_PARAMETERS = Type3.Object({
11783
12319
  }, { additionalProperties: false });
11784
12320
  var WORKFLOW_STATUS_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Workflow run ID visible in the current project" }) }, { additionalProperties: false });
11785
12321
  var WORKFLOW_RETRY_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Explicit failed workflow run ID" }), expectedState: Type3.Optional(Type3.String({ description: "Persisted source state observed before recovery" })), foreground: Type3.Optional(Type3.Boolean({ description: "Override the source launch mode for this recovery" })) });
12322
+ var WORKFLOW_ERROR_OUTPUT = Type3.Object({ code: Type3.String(), message: Type3.String() });
12323
+ var WORKFLOW_LAUNCH_OUTPUT = Type3.Object({ runId: Type3.String(), state: Type3.String(), detached: Type3.Optional(Type3.Boolean()), value: Type3.Optional(Type3.Unknown()) });
12324
+ var WORKFLOW_STATUS_OUTPUT = Type3.Object({ runId: Type3.String(), workflowName: Type3.String(), state: Type3.String(), error: Type3.Optional(WORKFLOW_ERROR_OUTPUT), failedAt: Type3.Optional(Type3.String()), budget: Type3.Optional(Type3.Unknown()), usage: Type3.Optional(Type3.Unknown()), phase: Type3.Optional(Type3.String()), delivery: Type3.Optional(Type3.Object({ mode: Type3.String(), state: Type3.String() })), agents: Type3.Array(Type3.Object({ id: Type3.String(), label: Type3.Optional(Type3.String()), path: Type3.String(), state: Type3.String(), lastEventAt: Type3.Optional(Type3.Number()), accounting: Type3.Optional(Type3.Unknown()) })) });
12325
+ var WORKFLOW_STOP_OUTPUT = Type3.Object({ runId: Type3.String(), state: Type3.String(), stopped: Type3.Boolean(), reason: Type3.Optional(Type3.String()) });
12326
+ var WORKFLOW_RECOVERY_OUTPUT = Type3.Object({ state: Type3.String(), runId: Type3.Optional(Type3.String()), parentRunId: Type3.Optional(Type3.String()), proposalId: Type3.Optional(Type3.String()), value: Type3.Optional(Type3.Unknown()) });
12327
+ var WORKFLOW_RESPOND_OUTPUT = Type3.Object({ state: Type3.String(), approved: Type3.Boolean(), reason: Type3.String(), accepted: Type3.Optional(Type3.Boolean()), runId: Type3.Optional(Type3.String()), value: Type3.Optional(Type3.Unknown()) });
12328
+ var WORKFLOW_CATALOG_ALIAS_OUTPUT = Type3.Object({ name: Type3.String(), kind: Type3.String(), provenance: Type3.String(), version: Type3.Optional(Type3.String()), headline: Type3.Optional(Type3.String()) });
12329
+ var WORKFLOW_CATALOG_OUTPUT = Type3.Union([
12330
+ Type3.Object({ functions: Type3.Array(Type3.Object({ name: Type3.String(), description: Type3.String(), input: Type3.Unknown() })), modelAliasEntries: Type3.Optional(Type3.Array(WORKFLOW_CATALOG_ALIAS_OUTPUT)), settings: Type3.Optional(Type3.Unknown()) }),
12331
+ Type3.Object({ name: Type3.String(), version: Type3.String(), headline: Type3.String(), description: Type3.String(), input: Type3.Unknown(), output: Type3.Unknown() }),
12332
+ WORKFLOW_CATALOG_ALIAS_OUTPUT,
12333
+ Type3.Object({ error: Type3.Object({ code: Type3.String(), name: Type3.String(), message: Type3.String() }) })
12334
+ ]);
11786
12335
  function workflowToolUpdate(run) {
11787
- return { content: [{ type: "text", text: formatWorkflowProgress(run) }], details: { runId: run.id, run } };
12336
+ const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
12337
+ return { content: [{ type: "text", text: `Workflow: ${run.workflowName} (${String(done)}/${String(run.agents.length)} done) [${run.state}]` }], details: { runId: run.id, run } };
11788
12338
  }
11789
12339
  function agentWithProgress(agent, progress) {
11790
12340
  const next = { ...agent, accounting: progress.accounting, toolCalls: progress.toolCalls };
@@ -11805,6 +12355,7 @@ function deliver(pi, content) {
11805
12355
  pi.sendMessage({ customType: "workflow", content, display: true }, { deliverAs: "followUp", triggerTurn: true });
11806
12356
  }
11807
12357
  var WORKFLOW_WARNING_ENTRY = "workflow-warning";
12358
+ var PROGRESS_COALESCE_MS = 100;
11808
12359
  function deliverWarning(pi, content) {
11809
12360
  pi.appendEntry(WORKFLOW_WARNING_ENTRY, { message: content });
11810
12361
  }
@@ -11863,7 +12414,42 @@ function modelInventory(root, registry) {
11863
12414
  }
11864
12415
  function resumeHostContext(ctx) {
11865
12416
  const model = object(ctx) && object(ctx.model) && typeof ctx.model.provider === "string" && typeof ctx.model.id === "string" ? { provider: ctx.model.provider, id: ctx.model.id } : void 0;
11866
- return { model, modelRegistry: contextHostCapabilities(ctx).modelRegistry, deliveryContext: completionContext(ctx) };
12417
+ return { model, modelRegistry: contextHostCapabilities(ctx).modelRegistry, deliveryContext: completionContext(ctx), toolContext: scriptToolContext(ctx) };
12418
+ }
12419
+ var SCRIPT_CALL_TEXT_LIMIT_BYTES = 16 * 1024;
12420
+ function scriptCallInput(input) {
12421
+ const text = JSON.stringify(input);
12422
+ const bytes = Buffer.byteLength(text);
12423
+ return bytes <= SCRIPT_CALL_TEXT_LIMIT_BYTES ? { input } : { input: utf8Prefix(text, SCRIPT_CALL_TEXT_LIMIT_BYTES), inputBytes: bytes };
12424
+ }
12425
+ function startScriptCall(run, call) {
12426
+ const recorded = { ...call, ...run.phase === void 0 ? {} : { phase: run.phase } };
12427
+ const calls = run.scriptCalls ?? [];
12428
+ return { ...run, scriptCalls: calls.some(({ path }) => path === call.path) ? calls.map((current) => current.path === call.path ? recorded : current) : [...calls, recorded] };
12429
+ }
12430
+ function finishScriptCall(run, path, error) {
12431
+ const finishedAt = Date.now();
12432
+ return { ...run, scriptCalls: (run.scriptCalls ?? []).map((call) => call.path === path ? { ...call, finishedAt, ...error ? { error: { code: error.code, ...call.kind === "shell" ? { message: utf8Prefix(error.message, SCRIPT_CALL_TEXT_LIMIT_BYTES) } : {} } } : {} } : call) };
12433
+ }
12434
+ function registerAliasVirtualModels(pi, names, settingsPath) {
12435
+ const register = object(pi) ? asFn(pi.registerVirtualModel) : void 0;
12436
+ if (!register) return;
12437
+ for (const name of names) {
12438
+ const definition = {
12439
+ provider: VIRTUAL_MODEL_PROVIDER,
12440
+ id: name,
12441
+ name: `${name} (workflow alias)`,
12442
+ thinkingLevels: THINKING_LEVELS,
12443
+ route: (request, ctx) => {
12444
+ const aliases = resolveWorkflowSettings(ctx.cwd, projectTrusted(ctx), settingsPath).effective.modelAliases ?? {};
12445
+ const target = resolveModelReference(name, aliases, new Set(ctx.modelRegistry.getAll().map((model2) => `${model2.provider}/${model2.id}`)), settingsPath);
12446
+ const model = ctx.modelRegistry.find(target.provider, target.model);
12447
+ if (!model) throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${name} resolved to ${target.provider}/${target.model}`);
12448
+ return { model, thinkingLevel: request.thinkingLevel };
12449
+ }
12450
+ };
12451
+ Reflect.apply(register, pi, [definition]);
12452
+ }
11867
12453
  }
11868
12454
  async function resolveLaunchAliases(registry, staticAliases, context, availableModels, knownModels, settingsPath) {
11869
12455
  const dynamic = typeof registry.resolveModelAliases === "function" ? await registry.resolveModelAliases(context, new Set(Object.keys(staticAliases))) : {};
@@ -11882,7 +12468,7 @@ async function resolveLaunchAliases(registry, staticAliases, context, availableM
11882
12468
  function workflowExtension(pi, home, clipboard = copyToClipboard, transport = localAgentTransport, agentDir, additionalSkillPaths = []) {
11883
12469
  beginWorkflowExtensionLoading();
11884
12470
  const registry = loadingRegistry();
11885
- const extensionAgentDir = agentDir ?? getAgentDir4();
12471
+ const extensionAgentDir = agentDir ?? getAgentDir5();
11886
12472
  const registerEntryRenderer = piHostCapabilities(pi).registerEntryRenderer;
11887
12473
  registerEntryRenderer?.(WORKFLOW_LOG_ENTRY, (entry) => {
11888
12474
  const data = entry.data;
@@ -11894,10 +12480,13 @@ function workflowExtension(pi, home, clipboard = copyToClipboard, transport = lo
11894
12480
  });
11895
12481
  let backgroundWidgetEnabled = true;
11896
12482
  try {
11897
- backgroundWidgetEnabled = loadSettings(workflowSettingsPath(extensionAgentDir)).backgroundWidget ?? true;
12483
+ const globalSettings = loadSettings(workflowSettingsPath(extensionAgentDir));
12484
+ backgroundWidgetEnabled = globalSettings.backgroundWidget ?? true;
12485
+ registerAliasVirtualModels(pi, Object.keys(globalSettings.modelAliases ?? {}), workflowSettingsPath(extensionAgentDir));
11898
12486
  } catch {
11899
12487
  }
11900
12488
  const backgroundWidgetController = widget(pi, backgroundWidgetEnabled);
12489
+ const codemodeTools = loadCodemodeToolsSetting(extensionAgentDir);
11901
12490
  const logBridge = (store, lifecycle, workflowName) => async (message) => {
11902
12491
  const timestamp = Date.now();
11903
12492
  const bounded = utf8Prefix(message, DELIVERY_LIMIT_BYTES);
@@ -11925,8 +12514,8 @@ function workflowExtension(pi, home, clipboard = copyToClipboard, transport = lo
11925
12514
  };
11926
12515
  pi.on("resources_discover", () => {
11927
12516
  if (!pi.getActiveTools().includes("workflow")) return;
11928
- const extensionDir = dirname7(fileURLToPath5(import.meta.url));
11929
- const skillPath = [join15(extensionDir, "../skills"), join15(extensionDir, "../../skills")].find((path) => existsSync2(path));
12517
+ const extensionDir = dirname8(fileURLToPath5(import.meta.url));
12518
+ const skillPath = [join16(extensionDir, "../skills"), join16(extensionDir, "../../skills")].find((path) => existsSync2(path));
11930
12519
  return skillPath ? { skillPaths: [skillPath] } : void 0;
11931
12520
  });
11932
12521
  const runs = /* @__PURE__ */ new Map();
@@ -12064,7 +12653,6 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12064
12653
  const trusted = projectTrusted(context);
12065
12654
  const settings = resolveWorkflowSettings(cwd, trusted, workflowSettingsPath(extensionAgentDir)).effective.extensionSettings?.trajectory;
12066
12655
  const port = settings?.port;
12067
- const themes = settings?.themes ?? false;
12068
12656
  const loadRuns = createTrajectoryRunLoader(cwd, sessionId, home, overlayLiveRun);
12069
12657
  const loadSubagents = createTrajectorySubagentLoader(cwd, sessionId, extensionAgentDir, overlayLiveSubagent);
12070
12658
  const loadRunsMetadata = createTrajectoryRunMetadataLoader(cwd, sessionId, home, overlayLiveRun);
@@ -12073,7 +12661,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12073
12661
  const live = liveSubagents.get(subagentId);
12074
12662
  return live?.status.sessionId === sessionId ? live.status.attemptDetails?.at(-1)?.session : void 0;
12075
12663
  });
12076
- return { cwd, sessionId, ...port === void 0 ? {} : { port }, themes, loadRuns, loadSubagents, loadMetadata: async () => ({ runs: await loadRunsMetadata(), subagents: await loadSubagentsMetadata() }), loadTranscript, handleAction: (request) => trajectoryAction(request, context) };
12664
+ return { cwd, sessionId, ...port === void 0 ? {} : { port }, loadRuns, loadSubagents, loadMetadata: async () => ({ runs: await loadRunsMetadata(), subagents: await loadSubagentsMetadata() }), loadTranscript, handleAction: (request) => trajectoryAction(request, context) };
12077
12665
  };
12078
12666
  const withLiveActivities = (run) => liveAgents.overlay(run);
12079
12667
  const overlayLiveRun = (run) => {
@@ -12190,7 +12778,10 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12190
12778
  if (replayed) return readShellResult(replayed.value);
12191
12779
  const shellStartedAt = Date.now();
12192
12780
  let shellPhaseIndex = -1;
12193
- const started = await persistRunState(store, metadata, (current) => {
12781
+ const call = { kind: "shell", name: "shell", path, ...scriptCallInput({ command, ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }, ...options.env === void 0 ? {} : { env: Object.keys(options.env) }, ...identity.worktreeOwner ? { worktree: identity.worktreeOwner } : {} }), startedAt: shellStartedAt };
12782
+ let failure;
12783
+ const started = await persistRunState(store, metadata, (run) => {
12784
+ const current = startScriptCall(run, call);
12194
12785
  const history = current.phaseHistory ?? [];
12195
12786
  if (current.phase !== void 0) shellPhaseIndex = current.phaseHistoryIndex ?? (history.length ? history.length - 1 : 0);
12196
12787
  const phaseActivities = [...current.activeShellsByPhase ?? []];
@@ -12206,8 +12797,12 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12206
12797
  if (!jsonValue(result)) fail("SHELL_FAILED", "Shell result is not JSON-compatible");
12207
12798
  await store.complete(path, result);
12208
12799
  return result;
12800
+ } catch (error) {
12801
+ failure = asWorkflowError(error);
12802
+ throw error;
12209
12803
  } finally {
12210
- const stopped = await persistRunState(store, metadata, (current) => {
12804
+ const stopped = await persistRunState(store, metadata, (run) => {
12805
+ const current = finishScriptCall(run, path, failure);
12211
12806
  const phaseActivities = [...current.activeShellsByPhase ?? []];
12212
12807
  const phaseActivityIndex = phaseActivities.findIndex(({ phaseIndex }) => phaseIndex === shellPhaseIndex);
12213
12808
  const phaseActivity = phaseActivities[phaseActivityIndex];
@@ -12227,6 +12822,55 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12227
12822
  await lifecycle.leave();
12228
12823
  }
12229
12824
  };
12825
+ const toolForRun = async (store, lifecycle, context, identifier, args, signal, identity) => {
12826
+ await lifecycle.enter();
12827
+ try {
12828
+ const path = toolIdentityPath(identifier, args, identity);
12829
+ const replayed = await store.replay(path);
12830
+ if (replayed) return replayed.value;
12831
+ if (!context) fail("UNKNOWN_TOOL", `tools.${identifier} needs a run launched or recovered by a Pi tool call; runs resumed from /workflow or at session start, and headless launches, only replay journaled tool calls`);
12832
+ const render = async () => {
12833
+ try {
12834
+ const update = runs.get(store.runId)?.update;
12835
+ if (update) update(workflowToolUpdate(withLiveActivities((await store.load()).run)));
12836
+ } catch {
12837
+ }
12838
+ };
12839
+ const record3 = async (update) => {
12840
+ try {
12841
+ await store.updateState(update);
12842
+ } catch {
12843
+ }
12844
+ };
12845
+ let value;
12846
+ try {
12847
+ const tool = scriptTool(context, identifier);
12848
+ if (!tool) fail("UNKNOWN_TOOL", `tools.${identifier} is not a tool this session can call`);
12849
+ const cancelled = () => signal.aborted;
12850
+ if (cancelled()) fail("CANCELLED", "Workflow cancelled");
12851
+ await record3((run) => startScriptCall(run, { kind: "tool", name: tool.name, path, startedAt: Date.now() }));
12852
+ const finished = liveAgents.toolStarted(store.runId, tool.name);
12853
+ try {
12854
+ await render();
12855
+ const outcome = await context.executeTool(tool.name, args, { signal });
12856
+ if (cancelled()) fail("CANCELLED", "Workflow cancelled");
12857
+ value = scriptToolValue(tool, outcome);
12858
+ } finally {
12859
+ finished();
12860
+ }
12861
+ } catch (error) {
12862
+ const failure = error instanceof WorkflowError ? error : new WorkflowError("TOOL_FAILED", `tools.${identifier}: ${errorText(error)}`);
12863
+ await record3((run) => finishScriptCall(run, path, failure));
12864
+ throw failure;
12865
+ }
12866
+ await store.complete(path, value);
12867
+ await record3((run) => finishScriptCall(run, path));
12868
+ await render();
12869
+ return value;
12870
+ } finally {
12871
+ await lifecycle.leave();
12872
+ }
12873
+ };
12230
12874
  const lifecycleFor = (store, state, metadata) => new RunLifecycle(state, async (next, previous, reason) => {
12231
12875
  const run = runs.get(store.runId);
12232
12876
  if (!run) throw new WorkflowError("INTERNAL_ERROR", `Unknown production run: ${store.runId}`);
@@ -12246,22 +12890,48 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12246
12890
  const scheduler = new FairAgentScheduler(async ({ id, runId, tuiIndex, parentId, prompt, options, signal, setSteer }) => {
12247
12891
  const run = runs.get(runId);
12248
12892
  if (!run) throw new WorkflowError("INTERNAL_ERROR", `Unknown production run: ${runId}`);
12893
+ const applyProgress = async (progress) => {
12894
+ let runState;
12895
+ if (progress.persist) {
12896
+ runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
12897
+ } else {
12898
+ const loaded = await run.store.loadStatus();
12899
+ if (!loaded.agents.some((agent) => agent.id === id)) return;
12900
+ runState = { ...loaded, ...run.budget.snapshot(), agents: loaded.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
12901
+ }
12902
+ if (!runState.agents.some((agent) => agent.id === id)) return;
12903
+ liveAgents.setActivity(runId, id, progress.activity);
12904
+ liveAgents.setEventTime(runId, id, progress.lastEventAt);
12905
+ run.update?.(workflowToolUpdate(withLiveActivities(runState)));
12906
+ };
12907
+ let deferred;
12908
+ let deferredTimer;
12909
+ let appliedAt = 0;
12910
+ const cancelDeferredProgress = () => {
12911
+ if (deferredTimer) clearTimeout(deferredTimer);
12912
+ deferredTimer = void 0;
12913
+ deferred = void 0;
12914
+ };
12915
+ const onProgress = async (progress) => {
12916
+ const now = Date.now();
12917
+ if (progress.persist || !deferredTimer && now - appliedAt >= PROGRESS_COALESCE_MS) {
12918
+ cancelDeferredProgress();
12919
+ appliedAt = now;
12920
+ await applyProgress(progress);
12921
+ return;
12922
+ }
12923
+ deferred = progress;
12924
+ deferredTimer ??= setTimeout(() => {
12925
+ deferredTimer = void 0;
12926
+ const next = deferred;
12927
+ deferred = void 0;
12928
+ if (!next) return;
12929
+ appliedAt = Date.now();
12930
+ void applyProgress(next).catch(() => void 0);
12931
+ }, Math.max(0, PROGRESS_COALESCE_MS - (now - appliedAt))).unref();
12932
+ };
12249
12933
  try {
12250
12934
  const budget = run.budget.forAgent(id);
12251
- const onProgress = async (progress) => {
12252
- let runState;
12253
- if (progress.persist) {
12254
- runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
12255
- } else {
12256
- const loaded = await run.store.load();
12257
- if (!loaded.run.agents.some((agent) => agent.id === id)) return;
12258
- runState = { ...loaded.run, ...run.budget.snapshot(), agents: loaded.run.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
12259
- }
12260
- if (!runState.agents.some((agent) => agent.id === id)) return;
12261
- liveAgents.setActivity(runId, id, progress.activity);
12262
- liveAgents.setEventTime(runId, id, progress.lastEventAt);
12263
- run.update?.(workflowToolUpdate(withLiveActivities(runState)));
12264
- };
12265
12935
  const onAttempt = async (attempt) => {
12266
12936
  liveAgents.setSession(runId, id, attempt.liveSession);
12267
12937
  liveAgents.setHandoff(runId, id, attempt);
@@ -12281,6 +12951,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12281
12951
  scheduler.cancelChildren(id);
12282
12952
  scheduler.retry(id);
12283
12953
  });
12954
+ cancelDeferredProgress();
12284
12955
  const before = (await run.store.load()).run;
12285
12956
  await persistAgentAttempts(run.store, id, result.attempts);
12286
12957
  const completed = (await run.store.load()).run;
@@ -12291,6 +12962,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12291
12962
  run.update?.(workflowToolUpdate(withLiveActivities(persisted)));
12292
12963
  return result.value;
12293
12964
  } catch (error) {
12965
+ cancelDeferredProgress();
12294
12966
  liveAgents.setSession(runId, id);
12295
12967
  const attempts = getAgentAttempts(error);
12296
12968
  if (attempts?.length) {
@@ -12426,8 +13098,8 @@ Respond with workflow_respond.`);
12426
13098
  const run = runs.get(runId);
12427
13099
  await run?.lifecycle.enterAwaitingInput();
12428
13100
  if (!alreadyAwaiting && (!isForeground() || !ui?.select)) deliverBackgroundCheckpoint(metadata.name, runId, { ...input, name: label, path });
12429
- const decision = new Promise((resolve7, reject) => {
12430
- run?.checkpointResolvers.set(path, resolve7);
13101
+ const decision = new Promise((resolve9, reject) => {
13102
+ run?.checkpointResolvers.set(path, resolve9);
12431
13103
  if (signal.aborted) reject(new WorkflowError("CANCELLED", "Workflow cancelled"));
12432
13104
  else signal.addEventListener("abort", () => {
12433
13105
  run?.checkpointResolvers.delete(path);
@@ -12455,16 +13127,18 @@ Respond with workflow_respond.`);
12455
13127
  return decision;
12456
13128
  };
12457
13129
  };
12458
- const registerControlTool = (name, label, description, parameters, run) => {
13130
+ const registerControlTool = (name, label, description, parameters, outputSchema, run) => {
12459
13131
  pi.registerTool({
12460
13132
  name,
12461
13133
  label,
12462
13134
  description,
12463
13135
  parameters,
13136
+ outputSchema,
13137
+ ...workflowToolExposure(name, codemodeTools),
12464
13138
  async execute(_id, params, signal, _onUpdate, ctx) {
12465
13139
  try {
12466
13140
  const result = await run(params, signal, ctx);
12467
- return { content: [{ type: "text", text: result.text }], details: result.details };
13141
+ return { content: [{ type: "text", text: result.text }], details: result.details, structuredContent: result.structured };
12468
13142
  } catch (error) {
12469
13143
  throw mainAgentError(error);
12470
13144
  }
@@ -12482,18 +13156,21 @@ Respond with workflow_respond.`);
12482
13156
  "Workflow Respond",
12483
13157
  "Approve or reject one pending workflow checkpoint or budget decision",
12484
13158
  Type3.Object({ runId: Type3.String(), name: Type3.Optional(Type3.String()), proposalId: Type3.Optional(Type3.String()), approved: Type3.Boolean() }, { additionalProperties: false }),
13159
+ WORKFLOW_RESPOND_OUTPUT,
12485
13160
  async (params, signal, ctx) => {
12486
13161
  if (params.proposalId) {
12487
13162
  const result = await recovery.answerBudgetDecision(params.runId, params.proposalId, params.approved, false, ctx, signal);
12488
13163
  if (!result) {
12489
13164
  const denied = { state: "budget_exhausted", approved: false, reason: "proposal_not_pending" };
12490
- return { text: JSON.stringify(denied), details: denied };
13165
+ return { text: JSON.stringify(denied), details: denied, structured: denied };
12491
13166
  }
12492
- return { text: completionControlContent(result, params.runId), details: { ...result, reason: params.approved ? "approved" : "rejected" } };
13167
+ const reason = params.approved ? "approved" : "rejected";
13168
+ return { text: completionControlContent(result, params.runId), details: { ...result, reason }, structured: { ...completionControlValue(result, params.runId), reason } };
12493
13169
  }
12494
13170
  if (!params.name) throw new WorkflowError("INVALID_METADATA", "workflow_respond requires name or proposalId");
12495
13171
  const accepted = await answerCheckpoint(params.runId, params.name, params.approved);
12496
- return { text: accepted ? "Checkpoint response accepted." : "Checkpoint is not awaiting a response.", details: { accepted, state: accepted ? "checkpoint_answered" : "not_pending", approved: params.approved, reason: "checkpoint" } };
13172
+ const details = { accepted, state: accepted ? "checkpoint_answered" : "not_pending", approved: params.approved, reason: "checkpoint" };
13173
+ return { text: accepted ? "Checkpoint response accepted." : "Checkpoint is not awaiting a response.", details, structured: details };
12497
13174
  }
12498
13175
  );
12499
13176
  registerControlTool(
@@ -12501,9 +13178,10 @@ Respond with workflow_respond.`);
12501
13178
  "Workflow Stop",
12502
13179
  "Stop an active workflow run by ID",
12503
13180
  Type3.Object({ runId: Type3.String() }, { additionalProperties: false }),
13181
+ WORKFLOW_STOP_OUTPUT,
12504
13182
  async (params) => {
12505
13183
  const result = await stopWorkflowRun(params.runId);
12506
- return { text: JSON.stringify(result), details: result };
13184
+ return { text: JSON.stringify(result), details: result, structured: result };
12507
13185
  }
12508
13186
  );
12509
13187
  registerControlTool(
@@ -12511,9 +13189,10 @@ Respond with workflow_respond.`);
12511
13189
  "Workflow Status",
12512
13190
  "Read a compact summary of a workflow run in the current project",
12513
13191
  WORKFLOW_STATUS_PARAMETERS,
13192
+ WORKFLOW_STATUS_OUTPUT,
12514
13193
  async (params, _signal, ctx) => {
12515
13194
  const result = await workflowStatusRun(params.runId, ctx);
12516
- return { text: JSON.stringify(result), details: result };
13195
+ return { text: JSON.stringify(result), details: result, structured: result };
12517
13196
  }
12518
13197
  );
12519
13198
  let catalogRegistered = false;
@@ -12526,13 +13205,15 @@ Respond with workflow_respond.`);
12526
13205
  if (!catalog.functions.length && !hasAliases && !hasSettings) return;
12527
13206
  pi.registerTool({
12528
13207
  name: "workflow_catalog",
13208
+ ...workflowToolExposure("workflow_catalog", codemodeTools),
13209
+ outputSchema: WORKFLOW_CATALOG_OUTPUT,
12529
13210
  label: "Workflow Catalog",
12530
13211
  description: "List reusable workflow functions and model aliases; pass `name` to load one entry in full",
12531
13212
  parameters: Type3.Object({ name: Type3.Optional(Type3.String({ description: "Registered function or model alias name for full detail" })) }, { additionalProperties: false }),
12532
13213
  async execute(_id, params = {}) {
12533
13214
  const context = { cwd, projectTrusted: trustedProject, globalSettingsPath: workflowSettingsPath(extensionAgentDir) };
12534
13215
  const result = params.name === void 0 ? registry.catalogIndex(context) : registry.catalogDetail(params.name, context);
12535
- return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
13216
+ return { content: [{ type: "text", text: JSON.stringify(result) }], details: result, structuredContent: result };
12536
13217
  },
12537
13218
  renderCall(args, theme) {
12538
13219
  const title = theme.fg("toolTitle", theme.bold("workflow_catalog"));
@@ -12561,9 +13242,9 @@ Respond with workflow_respond.`);
12561
13242
  const captured = roleCapture(store, snapshot, definitions, project);
12562
13243
  return { definitions, capture: (role, model) => captured.capture(role, model) };
12563
13244
  };
12564
- const activeSnapshotTools = (tools, active) => active === "session" ? new Set(tools.filter((tool) => pi.getActiveTools().includes(tool) && tool !== "workflow_catalog")) : new Set(tools.filter((tool) => active.has(tool) || tool === "workflow_catalog"));
13245
+ const activeSnapshotTools = (tools, active) => active === "session" ? new Set(tools.filter((tool) => reachableTools(pi).includes(tool) && tool !== "workflow_catalog")) : new Set(tools.filter((tool) => active.has(tool) || tool === "workflow_catalog"));
12565
13246
  const resumeLaunchPrologue = async (input) => {
12566
- const active = new Set(pi.getActiveTools().filter((tool) => !INTERNAL_WORKFLOW_TOOLS.includes(tool)));
13247
+ const active = new Set(reachableTools(pi).filter((tool) => !INTERNAL_WORKFLOW_TOOLS.includes(tool)));
12567
13248
  const missing = input.snapshot.tools.filter((tool) => tool !== "workflow_catalog").find((tool) => !active.has(tool));
12568
13249
  if (missing) throw new WorkflowError("RESUME_INCOMPATIBLE", `Required tool is unavailable: ${missing}`);
12569
13250
  const settingsPath = workflowSettingsPath(extensionAgentDir);
@@ -12653,6 +13334,7 @@ Respond with workflow_respond.`);
12653
13334
  resumeRoles,
12654
13335
  workflowAgentHandler,
12655
13336
  shellForRun,
13337
+ toolForRun,
12656
13338
  resolveWorktree,
12657
13339
  checkpointBridge,
12658
13340
  phaseBridge,
@@ -12727,9 +13409,10 @@ Respond with workflow_respond.`);
12727
13409
  "Workflow Retry",
12728
13410
  "Retry a failed workflow run by replaying its completed structural operations",
12729
13411
  WORKFLOW_RETRY_PARAMETERS,
13412
+ WORKFLOW_RECOVERY_OUTPUT,
12730
13413
  async (params, signal, ctx) => {
12731
13414
  const result = await recovery.retryWorkflowRun(params.runId, ctx, signal, params.foreground, params.expectedState);
12732
- return { text: completionControlContent(result), details: result };
13415
+ return { text: completionControlContent(result), details: result, structured: completionControlValue(result) };
12733
13416
  }
12734
13417
  );
12735
13418
  registerControlTool(
@@ -12737,15 +13420,16 @@ Respond with workflow_respond.`);
12737
13420
  "Workflow Resume",
12738
13421
  "Resume an exhausted workflow with unchanged or patched aggregate budgets",
12739
13422
  Type3.Object({ runId: Type3.String(), expectedState: Type3.Optional(Type3.String({ description: "Persisted source state observed before recovery" })), budget: Type3.Optional(Type3.Unknown()), foreground: Type3.Optional(Type3.Boolean({ description: "Override the source launch mode for this recovery" })) }, { additionalProperties: false }),
13423
+ WORKFLOW_RECOVERY_OUTPUT,
12740
13424
  async (params, signal, ctx) => {
12741
13425
  const result = await recovery.resumeWorkflowRun(params.runId, params.budget, ctx, signal, params.foreground, true, params.expectedState);
12742
- return { text: completionControlContent(result), details: result };
13426
+ return { text: completionControlContent(result), details: result, structured: completionControlValue(result) };
12743
13427
  }
12744
13428
  );
12745
13429
  const deliverStaleTerminal = async (store, run) => {
12746
13430
  if (!(run.delivery?.mode === "foreground" && run.delivery.state === "attached" || run.delivery?.mode === "background" && run.delivery.state === "pending")) return;
12747
13431
  if (run.state === "completed") {
12748
- const resultPath = join15(store.directory, "result.json");
13432
+ const resultPath = join16(store.directory, "result.json");
12749
13433
  let resultBytes = 0;
12750
13434
  let hasResult = true;
12751
13435
  try {
@@ -12871,16 +13555,18 @@ ${content}` };
12871
13555
  description: WORKFLOW_TOOL_DESCRIPTION,
12872
13556
  promptSnippet: WORKFLOW_TOOL_PROMPT_SNIPPET,
12873
13557
  parameters: WORKFLOW_TOOL_PARAMETERS,
13558
+ outputSchema: WORKFLOW_LAUNCH_OUTPUT,
13559
+ prepareLoadout: prepareScriptToolLoadout,
12874
13560
  async execute(toolCallId, params, signal, onUpdate, ctx) {
12875
13561
  let resolveDetached;
12876
13562
  let foregroundStore;
12877
13563
  let completionInstalled = false;
12878
- const detachedResult = params.foreground ? new Promise((resolve7) => {
12879
- resolveDetached = resolve7;
13564
+ const detachedResult = params.foreground ? new Promise((resolve9) => {
13565
+ resolveDetached = resolve9;
12880
13566
  }) : void 0;
12881
13567
  const detachedToolResult = (run) => {
12882
13568
  const detached = { runId: run.id, state: "running", detached: true };
12883
- return { content: [{ type: "text", text: JSON.stringify(detached) }], details: { ...detached, run, preview: `Moved workflow ${run.id} to background.` } };
13569
+ return { content: [{ type: "text", text: JSON.stringify(detached) }], details: { ...detached, run, preview: `Moved workflow ${run.id} to background.` }, structuredContent: detached };
12884
13570
  };
12885
13571
  try {
12886
13572
  const headless = object(ctx) && ctx.headless === true;
@@ -12893,7 +13579,7 @@ ${content}` };
12893
13579
  const inventory = modelInventory(rootModel, modelRegistry);
12894
13580
  const knownModels = inventory.knownModels;
12895
13581
  const availableModels = inventory.availableModels;
12896
- const rootTools = pi.getActiveTools().filter((name) => !INTERNAL_WORKFLOW_TOOLS.includes(name));
13582
+ const rootTools = reachableTools(pi).filter((name) => !INTERNAL_WORKFLOW_TOOLS.includes(name));
12897
13583
  const trustedProject = projectTrusted(ctx);
12898
13584
  const launchCwd = typeof ctx.cwd === "string" ? ctx.cwd : process.cwd();
12899
13585
  const launch = workflowLaunchSettings(launchCwd, trustedProject, settingsPath, params.concurrency);
@@ -12911,6 +13597,8 @@ ${content}` };
12911
13597
  if (trustedProject && launch.resolution.project.extensionSettings !== void 0) registry.validateExtensionSettings(launch.resolution.project.extensionSettings, { source: "project", cwd: ctx.cwd, projectTrusted: true, settingsPath: launch.resolution.projectSettingsPath });
12912
13598
  const validated = validateWorkflowLaunchWithRegistry(params, { cwd: ctx.cwd, agentDir: extensionAgentDir, projectTrusted: trustedProject, availableModels, rootTools: new Set(rootTools), modelAliases, knownModels, settingsPath, ...settings.extensionSettings === void 0 ? {} : { extensionSettings: settings.extensionSettings } }, registry);
12913
13599
  const { script, checked, agentDefinitions, projectAgentDefinitions, roleNames } = validated;
13600
+ const toolContext = scriptToolContext(ctx);
13601
+ validateScriptToolReferences(script, toolContext);
12914
13602
  await ensureSessionLease(ctx.cwd, ctx.sessionManager.getSessionId());
12915
13603
  const runId = randomUUID5();
12916
13604
  const args = params.args ?? null;
@@ -12981,7 +13669,7 @@ ${content}` };
12981
13669
  runs.set(runId, runRecord);
12982
13670
  if (params.foreground && onUpdate) onUpdate(workflowToolUpdate((await store.load()).run));
12983
13671
  scheduler.addRun(runId, settings.concurrency, () => runs.get(runId)?.budget.checkAgentLaunch(), settings.extensionSettings);
12984
- const execution = runWorkflow(script, args, withWorkflowFunctions({ shell: (command, options, signal2, identity) => shellForRun(store, checked.metadata, lifecycle, command, options, signal2, identity), agent: workflowAgentHandler(store, checked.metadata, lifecycle, executor, ctx.cwd, runId, (role, model) => capturedRoles.capture(role, model)), worktree: async (owner) => resolveWorktree(store, checked.metadata, owner), checkpoint: checkpointBridge(runId, store, checked.metadata, () => runs.get(runId)?.foreground ?? foregroundAttached, ctx.hasUI ? ctx.ui : void 0, headless), phase: phaseBridge(store, checked.metadata, lifecycle), log: logBridge(store, lifecycle, checked.metadata.name) }, store, runContext, registry, settings.extensionSettings), runController.signal);
13672
+ const execution = runWorkflow(script, args, withWorkflowFunctions({ tool: (identifier, toolArgs, signal2, identity) => toolForRun(store, lifecycle, toolContext, identifier, toolArgs, signal2, identity), shell: (command, options, signal2, identity) => shellForRun(store, checked.metadata, lifecycle, command, options, signal2, identity), agent: workflowAgentHandler(store, checked.metadata, lifecycle, executor, ctx.cwd, runId, (role, model) => capturedRoles.capture(role, model)), worktree: async (owner) => resolveWorktree(store, checked.metadata, owner), checkpoint: checkpointBridge(runId, store, checked.metadata, () => runs.get(runId)?.foreground ?? foregroundAttached, ctx.hasUI ? ctx.ui : void 0, headless), phase: phaseBridge(store, checked.metadata, lifecycle), log: logBridge(store, lifecycle, checked.metadata.name) }, store, runContext, registry, settings.extensionSettings), runController.signal);
12985
13673
  runRecord.execution = execution;
12986
13674
  await eventPublisher.runStarted(store, checked.metadata);
12987
13675
  const finish = execution.result.then(async (value2) => {
@@ -13019,7 +13707,7 @@ ${content}` };
13019
13707
  }, async (error) => {
13020
13708
  await deliveryController.deliverTerminal(store, deliverFailureContent(error), true);
13021
13709
  });
13022
- return { content: [{ type: "text", text: JSON.stringify({ runId, state: "running" }) }], details: { runId, preview: `Started workflow ${runId}.` } };
13710
+ return { content: [{ type: "text", text: JSON.stringify({ runId, state: "running" }) }], details: { runId, preview: `Started workflow ${runId}.` }, structuredContent: { runId, state: "running" } };
13023
13711
  }
13024
13712
  void completion.then(async (result) => {
13025
13713
  await deliveryController.deliverDetachedTerminal(toolCallId, completionContent("background", result));
@@ -13041,7 +13729,7 @@ ${content}` };
13041
13729
  }
13042
13730
  deliveryController.foregroundDeliveries.delete(toolCallId);
13043
13731
  const run = (await store.load()).run;
13044
- return { content: [{ type: "text", text: foregroundDelivery.content }, ...foregroundDelivery.inlined ? [{ type: "text", text: `Workflow run ID: ${runId}` }] : []], details: { runId, value, run } };
13732
+ return { content: [{ type: "text", text: foregroundDelivery.content }, ...foregroundDelivery.inlined ? [{ type: "text", text: `Workflow run ID: ${runId}` }] : []], details: { runId, value, run }, structuredContent: { runId, state: "completed", value } };
13045
13733
  } catch (error) {
13046
13734
  if (params.foreground && foregroundStore && completionInstalled) {
13047
13735
  const claim = await deliveryController.claimForegroundDelivery(foregroundStore, toolCallId);
@@ -13109,8 +13797,7 @@ ${content}` };
13109
13797
  state.workflowProgressComponent = workflowProgressBlock(currentProgress?.run ?? incoming, theme, currentProgress, async () => {
13110
13798
  const active = runs.get(incoming.id);
13111
13799
  const store = active?.store ?? new RunStore(incoming.cwd, incoming.sessionId, incoming.id, home);
13112
- const loaded = await store.load();
13113
- return withLiveActivities(loaded.run);
13800
+ return withLiveActivities(await store.loadStatus());
13114
13801
  }, () => {
13115
13802
  if (state.workflowProgress === currentProgress) requestRender();
13116
13803
  }, void 0, state.workflowProgressFrozenAt);
@@ -13122,7 +13809,7 @@ ${content}` };
13122
13809
  return textBlock(isPartial ? "Workflow starting..." : runDetails?.preview ?? (content?.type === "text" ? content.text : "Workflow finished"));
13123
13810
  }
13124
13811
  };
13125
- pi.registerTool(workflowTool);
13812
+ pi.registerTool({ ...workflowTool, ...workflowToolExposure(workflowTool.name, codemodeTools) });
13126
13813
  registerWorkflowNavigator({ pi, home, clipboard, extensionAgentDir, runs, terminalRunStates, hardTerminalRunStates: HARD_TERMINAL_RUN_STATES, ensureSessionLease, coordinateRunMutation, answerCheckpoint, recovery, stopWorkflowRun, moveForegroundToBackground: deliveryController.moveForegroundToBackground, isForegroundAttached: deliveryController.isForegroundAttached, liveAgents, registry, projectTrusted, resumeHostContext, resumeSelectedWorkflow, reportBlocked: reportWorkflowBlocked, trajectoryProvider, setNavigatorOpen: (open3) => {
13127
13814
  if (open3) backgroundWidgetController.suspend();
13128
13815
  else backgroundWidgetController.resume();
@@ -13151,6 +13838,7 @@ ${content}` };
13151
13838
  }
13152
13839
  var LiveAgentRegistry = class {
13153
13840
  #byRun = /* @__PURE__ */ new Map();
13841
+ #tools = /* @__PURE__ */ new Map();
13154
13842
  get(runId, agentId) {
13155
13843
  return this.#byRun.get(runId)?.get(agentId);
13156
13844
  }
@@ -13179,19 +13867,32 @@ var LiveAgentRegistry = class {
13179
13867
  setEventTime(runId, agentId, timestamp) {
13180
13868
  if (timestamp !== void 0) this.#state(runId, agentId).lastEventAt = timestamp;
13181
13869
  }
13870
+ /** Marks a script tool call in flight until the returned callback runs. */
13871
+ toolStarted(runId, name) {
13872
+ const call = { name, startedAt: Date.now() };
13873
+ const calls = this.#tools.get(runId) ?? /* @__PURE__ */ new Set();
13874
+ this.#tools.set(runId, calls.add(call));
13875
+ return () => {
13876
+ calls.delete(call);
13877
+ if (!calls.size && this.#tools.get(runId) === calls) this.#tools.delete(runId);
13878
+ };
13879
+ }
13182
13880
  deleteRun(runId) {
13183
13881
  this.#byRun.delete(runId);
13882
+ this.#tools.delete(runId);
13184
13883
  }
13185
- /** Overlays live activity and freshness onto a persisted run; returns the input when nothing is live. */
13884
+ /** Overlays live activity, freshness, and script tool calls onto a persisted run; returns the input when nothing is live. */
13186
13885
  overlay(run) {
13886
+ const tools = this.#tools.get(run.id);
13887
+ const withTools = tools?.size ? { ...run, activeTools: [...tools].map((call) => ({ ...call })) } : run;
13187
13888
  const agents = this.#byRun.get(run.id);
13188
- if (!agents?.size) return run;
13889
+ if (!agents?.size) return withTools;
13189
13890
  const next = run.agents.map((agent) => {
13190
13891
  const live = agents.get(agent.id);
13191
13892
  if (!live || live.activity === void 0 && live.lastEventAt === void 0) return agent;
13192
13893
  return { ...agent, ...live.activity === void 0 ? {} : { activity: live.activity }, ...live.lastEventAt === void 0 ? {} : { lastEventAt: live.lastEventAt } };
13193
13894
  });
13194
- return next.some((agent, index) => agent !== run.agents[index]) ? { ...run, agents: next } : run;
13895
+ return next.some((agent, index) => agent !== run.agents[index]) ? { ...withTools, agents: next } : withTools;
13195
13896
  }
13196
13897
  #state(runId, agentId) {
13197
13898
  const agents = this.#byRun.get(runId) ?? /* @__PURE__ */ new Map();
@@ -13223,9 +13924,9 @@ async function handleTurnInput(store, handle, turn) {
13223
13924
  }
13224
13925
  if (!completed) return void 0;
13225
13926
  if (source === void 0) fail("AGENT_FAILED", `Agent handle ${handle} cannot continue from its previous turn: the completed turn recorded no session file`);
13226
- const target = join15(store.directory, "handles", encodeURIComponent(handle), `turn-${String(turn)}-input.jsonl`);
13927
+ const target = join16(store.directory, "handles", encodeURIComponent(handle), `turn-${String(turn)}-input.jsonl`);
13227
13928
  try {
13228
- await mkdir4(dirname7(target), { recursive: true });
13929
+ await mkdir4(dirname8(target), { recursive: true });
13229
13930
  await copyFile2(source, target);
13230
13931
  } catch (error) {
13231
13932
  fail("AGENT_FAILED", `Agent handle ${handle} cannot continue from its previous turn: ${errorText(error)}`);
@@ -13246,13 +13947,13 @@ function modelSpec(value, fallback) {
13246
13947
 
13247
13948
  // src/herdr.ts
13248
13949
  import { execFile as execFile3 } from "node:child_process";
13249
- var herdrCommandRunner = (args) => new Promise((resolve7, reject) => {
13950
+ var herdrCommandRunner = (args) => new Promise((resolve9, reject) => {
13250
13951
  execFile3("herdr", [...args], { encoding: "utf8", maxBuffer: 1024 * 1024 }, (error, stdout) => {
13251
13952
  if (error) {
13252
13953
  reject(new Error(error.message));
13253
13954
  return;
13254
13955
  }
13255
- resolve7(stdout);
13956
+ resolve9(stdout);
13256
13957
  });
13257
13958
  });
13258
13959
  function herdrPaneId(env = process.env) {
@@ -13384,9 +14085,9 @@ async function waitForHerdrPane(paneId, runner = herdrCommandRunner, options = {
13384
14085
  const isAborted = () => monitorController.signal.aborted;
13385
14086
  let markAborted = () => {
13386
14087
  };
13387
- const aborted = new Promise((resolve7) => {
14088
+ const aborted = new Promise((resolve9) => {
13388
14089
  markAborted = () => {
13389
- resolve7("aborted");
14090
+ resolve9("aborted");
13390
14091
  };
13391
14092
  });
13392
14093
  const abortMonitor = () => {
@@ -13395,16 +14096,16 @@ async function waitForHerdrPane(paneId, runner = herdrCommandRunner, options = {
13395
14096
  };
13396
14097
  if (options.signal?.aborted) abortMonitor();
13397
14098
  else options.signal?.addEventListener("abort", abortMonitor, { once: true });
13398
- const delay = () => new Promise((resolve7) => {
14099
+ const delay = () => new Promise((resolve9) => {
13399
14100
  if (isAborted()) {
13400
- resolve7();
14101
+ resolve9();
13401
14102
  return;
13402
14103
  }
13403
14104
  const timer = setTimeout(finish, intervalMs);
13404
14105
  function finish() {
13405
14106
  clearTimeout(timer);
13406
14107
  monitorController.signal.removeEventListener("abort", finish);
13407
- resolve7();
14108
+ resolve9();
13408
14109
  }
13409
14110
  monitorController.signal.addEventListener("abort", finish, { once: true });
13410
14111
  });
@@ -13516,6 +14217,7 @@ export {
13516
14217
  HEARTBEAT_TIMEOUT_MS,
13517
14218
  LAUNCH_SNAPSHOT_IDENTITY_VERSION,
13518
14219
  MODEL_ALIAS_NAME,
14220
+ PLAIN_WORKFLOW_PROGRESS_STYLES,
13519
14221
  PiRuntimeAgentRunner,
13520
14222
  RPC_LIMIT_BYTES,
13521
14223
  RUN_STATES,
@@ -13527,6 +14229,7 @@ export {
13527
14229
  SessionLease,
13528
14230
  THINKING_LEVELS,
13529
14231
  TOOL_TIMING_ENTRY_TYPE,
14232
+ VIRTUAL_MODEL_PROVIDER,
13530
14233
  WORKFLOW_AGENT_STALL_THRESHOLD_MS,
13531
14234
  WORKFLOW_AGENT_STATE_CHANGED_EVENT,
13532
14235
  WORKFLOW_BLOCKED_EVENT,
@@ -13569,9 +14272,11 @@ export {
13569
14272
  buildWorkflowPhaseModel,
13570
14273
  buildWorkflowPhaseTree,
13571
14274
  byPriorityThenName,
14275
+ canonicalExtensionSelector,
13572
14276
  canonicalPath,
13573
14277
  coerceWorkflowError,
13574
14278
  createHerdrAgentReporter,
14279
+ createInlineConfirm,
13575
14280
  createLaunchSnapshot,
13576
14281
  createLiveSessionHandoff,
13577
14282
  createLocalPiSession,
@@ -13583,6 +14288,7 @@ export {
13583
14288
  deepFreeze,
13584
14289
  workflowExtension as default,
13585
14290
  defineWorkflowFunction,
14291
+ discoverRoles,
13586
14292
  encoded,
13587
14293
  errorCode,
13588
14294
  errorText,
@@ -13591,8 +14297,10 @@ export {
13591
14297
  fail,
13592
14298
  finiteNumber,
13593
14299
  formatAgentDetail,
14300
+ formatAgentError,
13594
14301
  formatBudgetStatus,
13595
14302
  formatCost,
14303
+ formatNavigatorColumns,
13596
14304
  formatNavigatorDashboard,
13597
14305
  formatNavigatorRun,
13598
14306
  formatStalledDuration,
@@ -13602,6 +14310,7 @@ export {
13602
14310
  formatWorkflowPhaseDashboard,
13603
14311
  formatWorkflowPreview,
13604
14312
  formatWorkflowProgress,
14313
+ formatWorkflowRuntime,
13605
14314
  hasLiveSessionLease,
13606
14315
  herdrAvailable,
13607
14316
  herdrCommandRunner,
@@ -13622,12 +14331,15 @@ export {
13622
14331
  isTurnEnd,
13623
14332
  isWorkflowAuthored,
13624
14333
  isWorkflowErrorCode,
14334
+ jsonDigest,
13625
14335
  jsonObject,
13626
14336
  jsonValue,
13627
14337
  listPersistedSessionIds,
13628
14338
  loadAgentDefinitions,
14339
+ loadCodemodeToolsSetting,
13629
14340
  loadLaunchSnapshot,
13630
14341
  loadProjectAgentDefinitions,
14342
+ loadRole,
13631
14343
  loadSettings,
13632
14344
  loadSettingsOverrides,
13633
14345
  loadingRegistry,
@@ -13654,13 +14366,16 @@ export {
13654
14366
  parseThinking,
13655
14367
  persistActiveAgentAttempt,
13656
14368
  persistAgentAttempts,
14369
+ physicalModel,
13657
14370
  positiveInteger,
13658
14371
  preflight,
13659
14372
  prepareAgentSetupForInspection,
13660
14373
  preserveWorkflowPhaseSelection,
13661
14374
  preserveWorkflowPhaseTreeSelection,
14375
+ progressStyleForState,
13662
14376
  projectSessionsDirectory,
13663
14377
  projectStorageKey,
14378
+ reachableTools,
13664
14379
  readShellResult,
13665
14380
  registerWorkflowExtension,
13666
14381
  registeredWorkflowFunctionSources,
@@ -13671,6 +14386,7 @@ export {
13671
14386
  resetWorkflowRegistryIfIdle,
13672
14387
  resolveAgentResourcePolicy,
13673
14388
  resolveModelReference,
14389
+ resolveRole,
13674
14390
  resolveWorkflowSettings,
13675
14391
  resourcePatternHasMagic,
13676
14392
  resourcePatternMatches,
@@ -13678,17 +14394,20 @@ export {
13678
14394
  retainTerminalRuns,
13679
14395
  retainWorkflowRegistry,
13680
14396
  roleNameOf,
14397
+ runStateGlyph,
13681
14398
  runWorkflow,
13682
14399
  runsDirectory,
13683
14400
  runtimeProgressToAgentProgress,
13684
14401
  sameFilesystemPath,
13685
14402
  sanitizeDisplayText,
13686
14403
  saveModelAliases,
14404
+ scriptToolReferences,
13687
14405
  selectResourcesByLayers,
13688
14406
  shellIdentityPath,
13689
14407
  structuralPath,
13690
14408
  sumAccounting,
13691
14409
  themeWorkflowProgressStyles,
14410
+ toolIdentityPath,
13692
14411
  truncateWorkflowProgress,
13693
14412
  unknownModel,
13694
14413
  unmatchedResourcePatterns,
@@ -13697,10 +14416,12 @@ export {
13697
14416
  validateBudget,
13698
14417
  validateBudgetPatch,
13699
14418
  validateCheckpoint,
14419
+ validateContextFileScopes,
13700
14420
  validateModelAliasAvailability,
13701
14421
  validateModelAliases,
13702
14422
  validateResourcePattern,
13703
14423
  validateSchema,
14424
+ validateSelectorList,
13704
14425
  validateShellCommand,
13705
14426
  validateShellOptions,
13706
14427
  validateWorkflowExtensionSettings,
@@ -13711,6 +14432,8 @@ export {
13711
14432
  workflowCatalog,
13712
14433
  workflowCatalogDetail,
13713
14434
  workflowCatalogIndex,
14435
+ workflowKeyLabel,
14436
+ workflowKeyMatches,
13714
14437
  workflowPhaseTreeInitialExpanded,
13715
14438
  workflowPhaseTreeVisibleNodes,
13716
14439
  workflowProjectSettingsPath,
@@ -13720,6 +14443,7 @@ export {
13720
14443
  workflowRoleDirectories,
13721
14444
  workflowScriptArtifact,
13722
14445
  workflowSettingsPath,
14446
+ workflowToolExposure,
13723
14447
  zeroAccounting
13724
14448
  };
13725
14449
  //# sourceMappingURL=index.js.map