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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +442 -162
  5. package/dist/src/background-widget.d.ts +5 -1
  6. package/dist/src/background-widget.js +5 -10
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +59 -37
  9. package/dist/src/execution.d.ts +6 -2
  10. package/dist/src/execution.js +48 -7
  11. package/dist/src/host-delivery.js +5 -8
  12. package/dist/src/host-navigator.js +5 -2
  13. package/dist/src/host-phases.d.ts +1 -0
  14. package/dist/src/host-recovery.d.ts +11 -8
  15. package/dist/src/host-recovery.js +14 -22
  16. package/dist/src/host-runtime.js +56 -5
  17. package/dist/src/host-view.d.ts +6 -1
  18. package/dist/src/host-view.js +46 -20
  19. package/dist/src/host.d.ts +2 -2
  20. package/dist/src/host.js +246 -118
  21. package/dist/src/index.d.ts +1 -2
  22. package/dist/src/index.js +1985 -1690
  23. package/dist/src/index.js.map +3 -3
  24. package/dist/src/paths.d.ts +8 -5
  25. package/dist/src/paths.js +24 -13
  26. package/dist/src/persistence.d.ts +1 -0
  27. package/dist/src/persistence.js +1 -0
  28. package/dist/src/pi-runtime-runner.js +4 -1
  29. package/dist/src/registry.d.ts +9 -7
  30. package/dist/src/registry.js +29 -71
  31. package/dist/src/runtime/agent-runner.d.ts +2 -0
  32. package/dist/src/script-tools.d.ts +27 -0
  33. package/dist/src/script-tools.js +87 -0
  34. package/dist/src/session-lease.d.ts +1 -1
  35. package/dist/src/session-lease.js +45 -11
  36. package/dist/src/settings.d.ts +19 -3
  37. package/dist/src/settings.js +63 -50
  38. package/dist/src/store.d.ts +3 -1
  39. package/dist/src/store.js +17 -1
  40. package/dist/src/trajectory.d.ts +12 -3
  41. package/dist/src/trajectory.js +32 -11
  42. package/dist/src/types.d.ts +129 -45
  43. package/dist/src/types.js +5 -8
  44. package/dist/src/utils.d.ts +21 -1
  45. package/dist/src/utils.js +46 -6
  46. package/dist/src/validation.d.ts +14 -3
  47. package/dist/src/validation.js +143 -87
  48. package/dist/starter/index.js +8 -11
  49. package/dist/starter/index.js.map +1 -1
  50. package/dist/starter/review-loop.d.ts +0 -13
  51. package/dist/starter/review-loop.js +8 -9
  52. package/dist/subagents/index.js +904 -956
  53. package/dist/subagents/index.js.map +3 -3
  54. package/dist/subagents/src/contracts.d.ts +48 -31
  55. package/dist/subagents/src/contracts.js +20 -9
  56. package/dist/subagents/src/index.d.ts +1 -1
  57. package/dist/subagents/src/index.js +14 -8
  58. package/dist/subagents/src/manager.js +52 -73
  59. package/dist/subagents/src/navigator.js +3 -14
  60. package/dist/subagents/src/view.d.ts +1 -1
  61. package/dist/subagents/src/view.js +15 -68
  62. package/dist/subagents/src/worktree.js +1 -1
  63. package/dist/trajectory/assets/index.html +35 -13
  64. package/dist/trajectory/index.js +576 -932
  65. package/dist/trajectory/index.js.map +3 -3
  66. package/dist/trajectory/src/assets/index.html +35 -13
  67. package/dist/trajectory/src/index.js +134 -104
  68. package/dist/trajectory/src/server.js +268 -27
  69. package/dist/trajectory/src/server.js.map +3 -3
  70. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  71. package/dist/trajectory/src/startup-mutex.js +172 -0
  72. package/examples/workflow-extension-template/README.md +4 -7
  73. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  74. package/examples/workflow-extension-template/index.js +0 -3
  75. package/package.json +10 -10
  76. package/skills/pi-extensible-workflows/SKILL.md +12 -8
  77. package/src/agent-execution.ts +412 -164
  78. package/src/background-widget.ts +5 -11
  79. package/src/decoders.ts +51 -37
  80. package/src/execution.ts +46 -9
  81. package/src/host-delivery.ts +5 -8
  82. package/src/host-navigator.ts +4 -2
  83. package/src/host-phases.ts +1 -1
  84. package/src/host-recovery.ts +23 -28
  85. package/src/host-runtime.ts +44 -6
  86. package/src/host-view.ts +41 -16
  87. package/src/host.ts +229 -142
  88. package/src/index.ts +1 -2
  89. package/src/paths.ts +18 -9
  90. package/src/persistence.ts +1 -0
  91. package/src/pi-runtime-runner.ts +3 -1
  92. package/src/registry.ts +28 -64
  93. package/src/runtime/agent-runner.ts +2 -0
  94. package/src/script-tools.ts +76 -0
  95. package/src/session-lease.ts +34 -6
  96. package/src/settings.ts +56 -50
  97. package/src/store.ts +18 -2
  98. package/src/trajectory.ts +32 -15
  99. package/src/types.ts +82 -34
  100. package/src/utils.ts +42 -5
  101. package/src/validation.ts +121 -80
  102. package/starter/index.ts +0 -1
  103. package/starter/review-loop.ts +8 -9
  104. package/starter/roles/developer.md +0 -1
  105. package/starter/roles/oracle.md +0 -1
  106. package/starter/roles/researcher.md +0 -1
  107. package/starter/roles/reviewer.md +0 -1
  108. package/starter/roles/scout.md +0 -1
  109. package/subagents/README.md +10 -16
  110. package/subagents/src/contracts.ts +21 -11
  111. package/subagents/src/index.ts +17 -8
  112. package/subagents/src/manager.ts +48 -128
  113. package/subagents/src/navigator.ts +4 -13
  114. package/subagents/src/view.ts +16 -63
  115. package/subagents/src/worktree.ts +1 -1
  116. package/trajectory/src/assets/index.html +35 -13
  117. package/trajectory/src/index.ts +112 -91
  118. package/trajectory/src/server.ts +46 -10
  119. package/trajectory/src/startup-mutex.ts +127 -0
  120. package/dist/src/roles.d.ts +0 -70
  121. package/dist/src/roles.js +0 -325
  122. package/dist/starter/roles/developer.md +0 -6
  123. package/dist/starter/roles/oracle.md +0 -18
  124. package/dist/starter/roles/researcher.md +0 -20
  125. package/dist/starter/roles/reviewer.md +0 -19
  126. package/dist/starter/roles/scout.md +0 -18
  127. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  128. package/src/roles.ts +0 -326
@@ -1,7 +1,7 @@
1
1
  // subagents/src/index.ts
2
- import { join as join11 } from "node:path";
3
- import { defineTool as defineTool2, getAgentDir as getAgentDir8 } from "@earendil-works/pi-coding-agent";
4
- import { Value as Value3 } from "typebox/value";
2
+ import { join as join10 } from "node:path";
3
+ import { defineTool as defineTool2, getAgentDir as getAgentDir7 } from "@earendil-works/pi-coding-agent";
4
+ import { Value as Value4 } from "typebox/value";
5
5
 
6
6
  // src/types.ts
7
7
  var RUN_STATES = ["queued", "running", "pausing", "paused", "awaiting_input", "completed", "failed", "stopped", "interrupted", "budget_exhausted"];
@@ -16,7 +16,7 @@ var ERROR_CODES = [
16
16
  "INVALID_SCHEMA",
17
17
  "UNKNOWN_MODEL",
18
18
  "UNKNOWN_TOOL",
19
- "UNKNOWN_AGENT_TYPE",
19
+ "UNKNOWN_AGENT",
20
20
  "RUN_OWNED",
21
21
  "RUN_NOT_FOUND",
22
22
  "REGISTRY_FROZEN",
@@ -28,6 +28,7 @@ var ERROR_CODES = [
28
28
  "AGENT_FAILED",
29
29
  "AGENT_RESULT_COLLECTED",
30
30
  "RESULT_INVALID",
31
+ "TOOL_FAILED",
31
32
  "CANCELLED",
32
33
  "WORKER_UNRESPONSIVE",
33
34
  "WORKTREE_FAILED",
@@ -36,15 +37,12 @@ var ERROR_CODES = [
36
37
  "INTERNAL_ERROR"
37
38
  ];
38
39
  var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
39
- function roleNameOf(value) {
40
- return typeof value === "string" ? value : void 0;
41
- }
42
- var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
43
- var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
44
40
  var CONTEXT_FILE_SCOPES = ["global", "project", "cwd"];
45
41
  function isContextFileScope(value) {
46
42
  return CONTEXT_FILE_SCOPES.some((scope) => scope === value);
47
43
  }
44
+ var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
45
+ var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
48
46
  var WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1e3;
49
47
  function zeroAccounting() {
50
48
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
@@ -57,7 +55,7 @@ function sumAccounting(values) {
57
55
  for (const value of values) total = addAccounting(total, value);
58
56
  return total;
59
57
  }
60
- var LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
58
+ var LAUNCH_SNAPSHOT_IDENTITY_VERSION = 6;
61
59
  var WORKFLOW_ERROR_BRAND = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-error");
62
60
  var WorkflowError = class extends Error {
63
61
  constructor(code, message) {
@@ -155,15 +153,8 @@ function jsonValue(value, seen = /* @__PURE__ */ new Set()) {
155
153
  seen.delete(value);
156
154
  return valid;
157
155
  }
158
- function mergeWorkflowExtensionSettings(...layers) {
159
- const merged = {};
160
- let present = false;
161
- for (const layer of layers) {
162
- if (layer === void 0) continue;
163
- present = true;
164
- for (const [namespace, value] of Object.entries(layer)) Object.defineProperty(merged, namespace, { value: structuredClone(value), enumerable: true, configurable: true, writable: true });
165
- }
166
- return present ? deepFreeze(merged) : void 0;
156
+ function jsonObject(value) {
157
+ return jsonValue(value) && object(value);
167
158
  }
168
159
  function positiveInteger(value) {
169
160
  return typeof value === "number" && Number.isInteger(value) && value > 0;
@@ -213,11 +204,16 @@ function parseThinking(value) {
213
204
  return isThinkingLevel(value) ? value : void 0;
214
205
  }
215
206
  function parseModelReference(value) {
216
- const match = /^([^/:\s]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
207
+ const match = /^([^/:\s]+)\/([^\s]+)$/.exec(value);
217
208
  if (!match?.[1] || !match[2]) fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
218
- const thinking = match[3];
219
- if (thinking !== void 0 && !isThinkingLevel(thinking)) fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
220
- return { provider: match[1], model: match[2], ...thinking !== void 0 ? { thinking } : {} };
209
+ const reference = match[2];
210
+ const separator = reference.lastIndexOf(":");
211
+ const suffix = separator === -1 ? void 0 : reference.slice(separator + 1);
212
+ if (suffix === "") fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
213
+ const thinking = suffix !== void 0 && isThinkingLevel(suffix) ? suffix : void 0;
214
+ const model = thinking === void 0 ? reference : reference.slice(0, separator);
215
+ if (model === "") fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
216
+ return { provider: match[1], model, ...thinking === void 0 ? {} : { thinking } };
221
217
  }
222
218
  function assertModelThinking(value, path = "model") {
223
219
  if (value.includes("/") && parseModelReference(value).thinking === void 0) fail("INVALID_METADATA", `${path} must be provider/model:thinking`);
@@ -288,6 +284,16 @@ function modelCapability(value, aliases, knownModels, settingsPath) {
288
284
  const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
289
285
  return `${parsed.provider}/${parsed.model}`;
290
286
  }
287
+ function reachableTools(pi) {
288
+ const undeclared = pi.getAllTools?.().filter(({ exposure }) => exposure === "codemode" || exposure === "deferred").map(({ name }) => name) ?? [];
289
+ return [.../* @__PURE__ */ new Set([...pi.getActiveTools(), ...undeclared])];
290
+ }
291
+ var VIRTUAL_MODEL_PROVIDER = "workflow";
292
+ function physicalModel(spec, aliases = {}, knownModels, settingsPath) {
293
+ if (spec.provider !== VIRTUAL_MODEL_PROVIDER || !modelAliasName(spec.model, aliases)) return spec;
294
+ const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
295
+ return spec.thinking === void 0 ? target : { ...target, thinking: spec.thinking };
296
+ }
291
297
  var RESOURCE_PATTERN_OPTIONS = { dot: true, nonegate: true, nocomment: true };
292
298
  function resourcePatternBody(pattern) {
293
299
  return pattern.startsWith("!") ? pattern.slice(1) : pattern;
@@ -338,8 +344,9 @@ function validateSchema(schema, at = "schema") {
338
344
  // src/paths.ts
339
345
  import { createHash } from "node:crypto";
340
346
  import { realpathSync } from "node:fs";
347
+ import { fileURLToPath } from "node:url";
341
348
  import { homedir } from "node:os";
342
- import { basename, join, resolve } from "node:path";
349
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
343
350
  function safePart(value) {
344
351
  return value.replace(/[^a-zA-Z0-9._-]/g, "_");
345
352
  }
@@ -351,9 +358,22 @@ function canonicalPath(path) {
351
358
  return absolute;
352
359
  }
353
360
  }
361
+ function extensionIdentity(path) {
362
+ return path.startsWith("builtin:") ? path : canonicalPath(path.startsWith("file:") ? fileURLToPath(path) : path);
363
+ }
354
364
  function sameFilesystemPath(left, right) {
355
365
  return canonicalPath(left) === canonicalPath(right);
356
366
  }
367
+ function piResourcePath(path, cwd) {
368
+ if (path.startsWith("<") || path.startsWith("builtin:")) return path;
369
+ const trimmed = path.trim();
370
+ const expanded = trimmed === "~" ? homedir() : trimmed.startsWith("~/") ? join(homedir(), trimmed.slice(2)) : /^file:\/\//.test(trimmed) ? fileURLToPath(trimmed) : trimmed;
371
+ return isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
372
+ }
373
+ function piResourceContains(root, path) {
374
+ const relativePath = relative(resolve(root), resolve(path));
375
+ return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
376
+ }
357
377
  function workflowProjectSettingsPath(cwd) {
358
378
  return join(cwd, ".pi", "pi-extensible-workflows", "settings.json");
359
379
  }
@@ -381,17 +401,6 @@ function budgetUsage(value) {
381
401
  // src/validation.ts
382
402
  import * as acorn from "acorn";
383
403
 
384
- // src/roles.ts
385
- import { readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
386
- import { basename as basename3, dirname as dirname4, extname, join as join5, resolve as resolve4 } from "node:path";
387
- import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
388
-
389
- // src/registry.ts
390
- import { readFileSync as readFileSync2 } from "node:fs";
391
- import { basename as basename2, dirname as dirname3, isAbsolute, join as join4 } from "node:path";
392
- import { fileURLToPath as fileURLToPath2 } from "node:url";
393
- import { Value } from "typebox/value";
394
-
395
404
  // src/io.ts
396
405
  import { execFile } from "node:child_process";
397
406
  import { randomUUID } from "node:crypto";
@@ -412,7 +421,7 @@ async function renameWithRetry(from, to) {
412
421
  } catch (error) {
413
422
  const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
414
423
  if (!retryableRenameError(error) || delayMs === void 0) throw error;
415
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
424
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
416
425
  }
417
426
  }
418
427
  }
@@ -478,7 +487,9 @@ async function git(cwd, args, extraEnv = {}) {
478
487
  }
479
488
 
480
489
  // src/session-lease.ts
490
+ import { execFileSync } from "node:child_process";
481
491
  import { link, mkdir, open, readFile as readFile2, readdir, rename as rename2, rm as rm2, stat } from "node:fs/promises";
492
+ import { join as join2 } from "node:path";
482
493
 
483
494
  // src/decoders.ts
484
495
  var INVALID_PERSISTED_VALUE = /* @__PURE__ */ Symbol("invalid persisted value");
@@ -568,32 +579,19 @@ function decodeAgentResourceSelectors(value) {
568
579
  function decodeContextFileScopes(value) {
569
580
  return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : void 0);
570
581
  }
571
- function decodeAgentDefinition(value) {
572
- if (!object(value)) return void 0;
573
- const prompt = optionalString(value.prompt);
574
- const description = optionalString(value.description);
575
- const model = optionalString(value.model);
576
- const thinking = value.thinking;
577
- const tools = value.tools === void 0 ? void 0 : decodeStringArray(value.tools);
578
- const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
579
- const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
580
- const overrideSystemPrompt = optionalBoolean(value.overrideSystemPrompt);
582
+ function decodePreparedAgentConfiguration(value) {
583
+ if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean" || typeof value.systemPromptAppend !== "string") return void 0;
584
+ const model = decodeModelSpec(value.model);
585
+ const tools = decodeStringArray(value.tools);
586
+ const skills = decodeStringArray(value.skills);
587
+ const extensions = decodeStringArray(value.extensions);
588
+ const settings = decodeWorkflowExtensions(value.settings);
581
589
  const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
582
- const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
583
- if (prompt === INVALID_PERSISTED_VALUE || description === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || overrideSystemPrompt === INVALID_PERSISTED_VALUE) return void 0;
584
- if (thinking !== void 0 && !isThinkingLevel(thinking) || value.tools !== void 0 && !tools || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.contextFiles !== void 0 && !contextFiles || value.extensionSettings !== void 0 && !extensionSettings) return void 0;
585
- const foldedModel = typeof model === "string" && thinking !== void 0 && !model.includes(":") ? `${model}:${thinking}` : model;
586
- return {
587
- ...prompt === void 0 ? {} : { prompt },
588
- ...description === void 0 ? {} : { description },
589
- ...foldedModel === void 0 ? {} : { model: foldedModel },
590
- ...tools === void 0 ? {} : { tools },
591
- ...skills === void 0 ? {} : { skills },
592
- ...extensions === void 0 ? {} : { extensions },
593
- ...overrideSystemPrompt === void 0 ? {} : { overrideSystemPrompt },
594
- ...contextFiles === void 0 ? {} : { contextFiles },
595
- ...extensionSettings === void 0 ? {} : { extensionSettings }
596
- };
590
+ const label2 = optionalString(value.label);
591
+ const systemPrompt = optionalString(value.systemPrompt);
592
+ const excludeTools = value.excludeTools === void 0 ? void 0 : decodeStringArray(value.excludeTools);
593
+ if (!model || !tools || !skills || !extensions || !settings || label2 === INVALID_PERSISTED_VALUE || systemPrompt === INVALID_PERSISTED_VALUE || value.contextFiles !== void 0 && !contextFiles || value.excludeTools !== void 0 && !excludeTools) return void 0;
594
+ return { model, tools, skills, extensions, settings, projectTrusted: value.projectTrusted, systemPromptAppend: value.systemPromptAppend, ...contextFiles === void 0 ? {} : { contextFiles }, ...excludeTools === void 0 ? {} : { excludeTools }, ...label2 === void 0 ? {} : { label: label2 }, ...systemPrompt === void 0 ? {} : { systemPrompt } };
597
595
  }
598
596
  function decodeWorkflowMetadata(value) {
599
597
  if (!object(value) || typeof value.name !== "string") return void 0;
@@ -702,17 +700,22 @@ function decodeScheduledAgentOptions(value) {
702
700
  const parentBreadcrumb = optionalString(value.parentBreadcrumb);
703
701
  const worktreeOwner = optionalString(value.worktreeOwner);
704
702
  const model = optionalString(value.model);
705
- const role = typeof value.role === "string" ? value.role : void 0;
706
703
  const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
707
704
  const schema = value.schema === void 0 ? void 0 : decodeJsonObject(value.schema);
708
705
  const retries = optionalNumber(value.retries);
709
706
  const timeoutMs = value.timeoutMs;
707
+ if (value.projectTrusted !== void 0 && typeof value.projectTrusted !== "boolean") return void 0;
708
+ const rawCapabilities = value.capabilities;
709
+ const capabilities = rawCapabilities === void 0 ? void 0 : object(rawCapabilities) ? { tools: decodeStringArray(rawCapabilities.tools), skills: decodeStringArray(rawCapabilities.skills), extensions: decodeStringArray(rawCapabilities.extensions) } : void 0;
710
+ if (rawCapabilities !== void 0 && (!capabilities?.tools || !capabilities.skills || !capabilities.extensions)) return void 0;
711
+ const configuration = value.configuration === void 0 ? void 0 : decodePreparedAgentConfiguration(value.configuration);
712
+ if (value.configuration !== void 0 && configuration === void 0) return void 0;
710
713
  const agentOptions = value.agentOptions === void 0 ? void 0 : decodeJsonObject(value.agentOptions);
711
714
  const agentIdentity = value.agentIdentity === void 0 ? void 0 : decodeIdentity(value.agentIdentity);
712
715
  const sessionPath = optionalString(value.sessionPath);
713
716
  const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
714
717
  const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
715
- if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.role !== void 0 && typeof value.role !== "string" || value.contextFiles !== void 0 && !contextFiles || value.schema !== void 0 && !schema || retries === INVALID_PERSISTED_VALUE || retries !== void 0 && !integer(retries) || timeoutMs !== void 0 && timeoutMs !== null && !finiteNumber(timeoutMs) || value.agentOptions !== void 0 && !agentOptions || value.agentIdentity !== void 0 && !agentIdentity || value.extensionSettings !== void 0 && !extensionSettings) return void 0;
718
+ if (sessionPath === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || requestedLabel === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || value.skills !== void 0 && !skills || value.extensions !== void 0 && !extensions || value.contextFiles !== void 0 && !contextFiles || value.schema !== void 0 && !schema || retries === INVALID_PERSISTED_VALUE || retries !== void 0 && !integer(retries) || timeoutMs !== void 0 && timeoutMs !== null && !finiteNumber(timeoutMs) || value.agentOptions !== void 0 && !agentOptions || value.agentIdentity !== void 0 && !agentIdentity || value.extensionSettings !== void 0 && !extensionSettings) return void 0;
716
719
  return {
717
720
  label: value.label,
718
721
  ...requestedLabel === void 0 ? {} : { requestedLabel },
@@ -723,7 +726,6 @@ function decodeScheduledAgentOptions(value) {
723
726
  ...extensions === void 0 ? {} : { extensions },
724
727
  ...worktreeOwner === void 0 ? {} : { worktreeOwner },
725
728
  ...model === void 0 ? {} : { model },
726
- ...role === void 0 ? {} : { role },
727
729
  ...contextFiles === void 0 ? {} : { contextFiles },
728
730
  ...schema === void 0 ? {} : { schema },
729
731
  ...retries === void 0 ? {} : { retries },
@@ -731,6 +733,9 @@ function decodeScheduledAgentOptions(value) {
731
733
  ...sessionPath === void 0 ? {} : { sessionPath },
732
734
  ...continuity === void 0 ? {} : { continuity },
733
735
  ...agentOptions === void 0 ? {} : { agentOptions },
736
+ ...configuration === void 0 ? {} : { configuration },
737
+ ...value.projectTrusted === void 0 ? {} : { projectTrusted: value.projectTrusted },
738
+ ...capabilities?.tools && capabilities.skills && capabilities.extensions ? { capabilities: { tools: capabilities.tools, skills: capabilities.skills, extensions: capabilities.extensions } } : {},
734
739
  ...agentIdentity === void 0 ? {} : { agentIdentity },
735
740
  ...extensionSettings === void 0 ? {} : { extensionSettings }
736
741
  };
@@ -771,11 +776,10 @@ function decodeAgentResourceInspection(value) {
771
776
  const sources = sourceRecord === void 0 ? void 0 : {
772
777
  global: decodeAgentResourceSelectors(sourceRecord.global),
773
778
  project: decodeAgentResourceSelectors(sourceRecord.project),
774
- ...sourceRecord.role === void 0 ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) },
775
779
  ...sourceRecord.call === void 0 ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }
776
780
  };
777
- if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== void 0 && (!sources || !sources.global || !sources.project || sources.role === void 0 && sourceRecord?.role !== void 0 || sources.call === void 0 && sourceRecord?.call !== void 0)) return void 0;
778
- return { selectors: { skills: [...selectors.skills ?? []], extensions: [...selectors.extensions ?? []], tools: [...selectors.tools ?? []] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...sources === void 0 ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...sources.role === void 0 ? {} : { role: sources.role }, ...sources.call === void 0 ? {} : { call: sources.call } } } };
781
+ if (!selectors || !skills || !extensions || !tools || !unmatchedSkills || !unmatchedExtensions || !unmatchedTools || rawSources !== void 0 && (!sources || !sources.global || !sources.project || sources.call === void 0 && sourceRecord?.call !== void 0)) return void 0;
782
+ return { selectors: { skills: [...selectors.skills ?? []], extensions: [...selectors.extensions ?? []], tools: [...selectors.tools ?? []] }, skills, extensions, tools, unmatchedSkills, unmatchedExtensions, unmatchedTools, ...sources === void 0 ? {} : { selectorSources: { global: sources.global ?? {}, project: sources.project ?? {}, ...sources.call === void 0 ? {} : { call: sources.call } } } };
779
783
  }
780
784
  function decodeAgentSetupSummary(value) {
781
785
  if (!object(value) || typeof value.cwd !== "string") return void 0;
@@ -813,17 +817,16 @@ function decodeAgent(value) {
813
817
  const worktreeOwner = optionalString(value.worktreeOwner);
814
818
  const handle = optionalString(value.handle);
815
819
  const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
816
- const role = optionalString(value.role);
817
820
  const requestedModel = optionalString(value.requestedModel);
818
821
  const startedAt = optionalNumber(value.startedAt);
819
822
  const durationMs = optionalNumber(value.durationMs);
820
823
  const attemptDetails = value.attemptDetails === void 0 ? void 0 : decodeArray(value.attemptDetails, decodeAgentAttempt);
821
824
  const accounting2 = value.accounting === void 0 ? void 0 : decodeAgentAccounting(value.accounting);
822
825
  const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
823
- const activity2 = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
826
+ const activity = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
824
827
  const lastEventAt = optionalNumber(value.lastEventAt);
825
- if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label2 === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath2 === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || role === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== void 0 && !positiveInteger(value.turn)) return void 0;
826
- if (!model || !tools || value.structuralPath !== void 0 && !structuralPath2 || value.attemptDetails !== void 0 && !attemptDetails || value.accounting !== void 0 && !accounting2 || value.toolCalls !== void 0 && !toolCalls || value.activity !== void 0 && !activity2) return void 0;
828
+ if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label2 === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath2 === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== void 0 && !positiveInteger(value.turn)) return void 0;
829
+ if (!model || !tools || value.structuralPath !== void 0 && !structuralPath2 || value.attemptDetails !== void 0 && !attemptDetails || value.accounting !== void 0 && !accounting2 || value.toolCalls !== void 0 && !toolCalls || value.activity !== void 0 && !activity) return void 0;
827
830
  return {
828
831
  ...systemPrompt === void 0 ? {} : { systemPrompt },
829
832
  ...prompt === void 0 ? {} : { prompt },
@@ -839,7 +842,6 @@ function decodeAgent(value) {
839
842
  ...worktreeOwner === void 0 ? {} : { worktreeOwner },
840
843
  ...handle === void 0 ? {} : { handle, turn: value.turn },
841
844
  ...continuity === void 0 ? {} : { continuity },
842
- ...role === void 0 ? {} : { role },
843
845
  ...requestedModel === void 0 ? {} : { requestedModel },
844
846
  model,
845
847
  tools,
@@ -849,7 +851,7 @@ function decodeAgent(value) {
849
851
  ...attemptDetails === void 0 ? {} : { attemptDetails },
850
852
  ...accounting2 === void 0 ? {} : { accounting: accounting2 },
851
853
  ...toolCalls === void 0 ? {} : { toolCalls },
852
- ...activity2 === void 0 ? {} : { activity: activity2 },
854
+ ...activity === void 0 ? {} : { activity },
853
855
  ...lastEventAt === void 0 ? {} : { lastEventAt }
854
856
  };
855
857
  }
@@ -897,6 +899,22 @@ function decodeRunEvent(value) {
897
899
  if (timestamp2 === INVALID_PERSISTED_VALUE) return void 0;
898
900
  return { type: value.type, message: value.message, ...timestamp2 === void 0 ? {} : { timestamp: timestamp2 } };
899
901
  }
902
+ function decodeScriptCall(value) {
903
+ if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt)) return void 0;
904
+ const shell = value.kind === "shell";
905
+ if (shell && !jsonValue(value.input)) return void 0;
906
+ const inputBytes = shell ? optionalNumber(value.inputBytes) : void 0;
907
+ const phase = optionalString(value.phase);
908
+ const finishedAt = optionalNumber(value.finishedAt);
909
+ let error;
910
+ if (value.error !== void 0) {
911
+ if (shell) error = decodeWorkflowError(value.error);
912
+ else if (object(value.error) && typeof value.error.code === "string") error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
913
+ if (!error) return void 0;
914
+ }
915
+ if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE) return void 0;
916
+ 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 } };
917
+ }
900
918
  function decodeDelivery(value) {
901
919
  if (!object(value)) return void 0;
902
920
  const mode = value.mode;
@@ -926,6 +944,8 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
926
944
  const budgetEvents = value.budgetEvents === void 0 ? void 0 : decodeArray(value.budgetEvents, decodeBudgetEvent);
927
945
  const events = value.events === void 0 ? void 0 : decodeArray(value.events, decodeRunEvent);
928
946
  const delivery = value.delivery === void 0 ? void 0 : decodeDelivery(value.delivery);
947
+ const scriptCalls = value.scriptCalls === void 0 ? void 0 : decodeArray(value.scriptCalls, decodeScriptCall);
948
+ if (value.scriptCalls !== void 0 && !scriptCalls) return void 0;
929
949
  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;
930
950
  if (value.agentSessions === void 0 && !allowLegacyAgentSessions) return void 0;
931
951
  return {
@@ -944,6 +964,7 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
944
964
  ...activeShells === void 0 ? {} : { activeShells },
945
965
  ...activeShellStartedAt === void 0 ? {} : { activeShellStartedAt },
946
966
  ...activeShellsByPhase === void 0 ? {} : { activeShellsByPhase },
967
+ ...scriptCalls === void 0 ? {} : { scriptCalls },
947
968
  ...error === void 0 ? {} : { error },
948
969
  ...failedAt === void 0 ? {} : { failedAt },
949
970
  ...budget === void 0 ? {} : { budget },
@@ -968,13 +989,11 @@ function decodeLaunchSnapshot(value) {
968
989
  const phases = value.phases === void 0 ? void 0 : decodeStringArray(value.phases);
969
990
  const models = decodeStringArray(value.models);
970
991
  const tools = decodeStringArray(value.tools);
971
- const agentTypes = decodeStringArray(value.agentTypes);
972
- const roles = value.roles === void 0 ? void 0 : decodeRecord(value.roles, decodeAgentDefinition);
973
- const projectRoles = value.projectRoles === void 0 ? void 0 : decodeStringArray(value.projectRoles);
992
+ const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
974
993
  const schemas = decodeArray(value.schemas, decodeJsonObject);
975
- if (identityVersion === INVALID_PERSISTED_VALUE || value.identityVersion !== void 0 && !integer(identityVersion) || launchMode !== void 0 && !isLaunchMode(launchMode) || args === void 0 || !metadata || !settings || value.settingsSources !== void 0 && !settingsSources || value.budget !== void 0 && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== void 0 && !modelAliases || value.phases !== void 0 && !phases || !models || !tools || !agentTypes || value.roles !== void 0 && !roles || value.projectRoles !== void 0 && !projectRoles || !schemas) return void 0;
994
+ if (identityVersion !== LAUNCH_SNAPSHOT_IDENTITY_VERSION || launchMode !== void 0 && !isLaunchMode(launchMode) || args === void 0 || !metadata || !settings || value.settingsSources !== void 0 && !settingsSources || value.budget !== void 0 && !budget || settingsPath === INVALID_PERSISTED_VALUE || value.modelAliases !== void 0 && !modelAliases || value.phases !== void 0 && !phases || !models || !tools || !agentConfigurations || !schemas) return void 0;
976
995
  return {
977
- ...identityVersion === void 0 ? {} : { identityVersion },
996
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
978
997
  ...launchMode === void 0 ? {} : { launchMode },
979
998
  script: value.script,
980
999
  args,
@@ -987,9 +1006,7 @@ function decodeLaunchSnapshot(value) {
987
1006
  ...phases === void 0 ? {} : { phases },
988
1007
  models,
989
1008
  tools,
990
- agentTypes,
991
- ...roles === void 0 ? {} : { roles },
992
- ...projectRoles === void 0 ? {} : { projectRoles },
1009
+ agentConfigurations,
993
1010
  schemas
994
1011
  };
995
1012
  }
@@ -1070,18 +1087,43 @@ function decodeSystemPromptArtifact(value) {
1070
1087
  }
1071
1088
 
1072
1089
  // src/session-lease.ts
1073
- async function processAlive(pid, startedAt) {
1090
+ var START_MARGIN_MS = 6e4;
1091
+ var clockTicks;
1092
+ function clockTickRate() {
1093
+ if (clockTicks !== void 0) return clockTicks;
1094
+ try {
1095
+ const rate = Number(execFileSync("getconf", ["CLK_TCK"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim());
1096
+ clockTicks = Number.isInteger(rate) && rate > 0 ? rate : 100;
1097
+ } catch {
1098
+ clockTicks = 100;
1099
+ }
1100
+ return clockTicks;
1101
+ }
1102
+ async function linuxProcessStart(pid, procRoot) {
1103
+ try {
1104
+ const text = await readFile2(join2(procRoot, String(pid), "stat"), "utf8");
1105
+ const close = text.lastIndexOf(")");
1106
+ const ticks = close < 0 ? Number.NaN : Number(text.slice(close + 2).split(" ")[19]);
1107
+ const btime = /^btime (\d+)$/m.exec(await readFile2(join2(procRoot, "stat"), "utf8"))?.[1];
1108
+ if (btime === void 0 || !Number.isInteger(ticks) || ticks < 0) return void 0;
1109
+ return Number(btime) * 1e3 + ticks / clockTickRate() * 1e3;
1110
+ } catch {
1111
+ return void 0;
1112
+ }
1113
+ }
1114
+ async function processAlive(pid, startedAt, procRoot = "/proc") {
1074
1115
  try {
1075
1116
  process.kill(pid, 0);
1076
1117
  } catch (error) {
1077
1118
  return !isNodeError(error, "ESRCH");
1078
1119
  }
1079
- if (startedAt !== void 0 && process.platform === "linux") {
1080
- try {
1081
- if ((await stat(`/proc/${String(pid)}`)).ctimeMs > startedAt) return false;
1082
- } catch (error) {
1083
- if (isNodeError(error, "ENOENT")) return false;
1084
- }
1120
+ if (startedAt === void 0 || process.platform !== "linux") return true;
1121
+ const start = await linuxProcessStart(pid, procRoot);
1122
+ if (start !== void 0) return start <= startedAt + START_MARGIN_MS;
1123
+ try {
1124
+ process.kill(pid, 0);
1125
+ } catch (error) {
1126
+ return !isNodeError(error, "ESRCH");
1085
1127
  }
1086
1128
  return true;
1087
1129
  }
@@ -1091,7 +1133,7 @@ import { execFile as execFile2 } from "node:child_process";
1091
1133
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
1092
1134
  import { access, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, realpath, rename as rename3, rm as rm3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
1093
1135
  import { homedir as homedir2 } from "node:os";
1094
- import { dirname, join as join2, relative, resolve as resolve2, sep } from "node:path";
1136
+ import { dirname, join as join3, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
1095
1137
  import { promisify as promisify2 } from "node:util";
1096
1138
  var executeFile = promisify2(execFile2);
1097
1139
  var WORKTREE_POST_CREATE_TIMEOUT_MS = 6e4;
@@ -1102,36 +1144,36 @@ var SYSTEM_PROMPT_BODIES = "bodies";
1102
1144
  var SYSTEM_PROMPT_SEQUENCE = "sequence";
1103
1145
  var SYSTEM_PROMPT_ARTIFACT = { version: 2, format: "append-only", storage: SYSTEM_PROMPT_STORAGE };
1104
1146
  function summaryArtifacts(directory) {
1105
- return { runDirectory: directory, statePath: join2(directory, "state.json"), journalPath: join2(directory, "journal.json"), snapshotPath: join2(directory, "snapshot.json"), workflowPath: join2(directory, "workflow.js"), resultPath: join2(directory, "result.json"), summaryPath: join2(directory, "summary.json") };
1147
+ return { runDirectory: directory, statePath: join3(directory, "state.json"), journalPath: join3(directory, "journal.json"), snapshotPath: join3(directory, "snapshot.json"), workflowPath: join3(directory, "workflow.js"), resultPath: join3(directory, "result.json"), summaryPath: join3(directory, "summary.json") };
1106
1148
  }
1107
1149
  function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, now = (/* @__PURE__ */ new Date()).toISOString()) {
1108
1150
  const createdAt = typeof previous?.createdAt === "string" ? previous.createdAt : fallbackCreatedAt;
1109
1151
  const failedAt = run.failedAt ?? run.error?.failedAt;
1110
1152
  const replayablePaths = [.../* @__PURE__ */ new Set([...run.retry?.completedPaths ?? [], ...Object.keys(journal.completed)])];
1111
1153
  const incompletePaths = [.../* @__PURE__ */ new Set([...run.retry?.incompletePaths ?? [], ...failedAt ? [failedAt] : []])];
1112
- return { schemaVersion: 1, runId: run.id, sessionId: run.sessionId, workflowName: run.workflowName, state: run.state, createdAt, updatedAt: now, ...previous?.terminalAt || HARD_TERMINAL_RUN_STATES.has(run.state) ? { terminalAt: previous?.terminalAt ?? now } : {}, usage: budgetUsage(run.usage), agents: run.agents.map(({ id, name, label: label2, state, role, attempts }) => ({ id, name, ...label2 ? { label: label2 } : {}, state, ...role ? { role } : {}, attempts })), ...run.error ? { error: run.error } : {}, ...failedAt ? { failedAt } : {}, replayablePaths, incompletePaths, artifacts: summaryArtifacts(directory) };
1154
+ return { schemaVersion: 1, runId: run.id, sessionId: run.sessionId, workflowName: run.workflowName, state: run.state, createdAt, updatedAt: now, ...previous?.terminalAt || HARD_TERMINAL_RUN_STATES.has(run.state) ? { terminalAt: previous?.terminalAt ?? now } : {}, usage: budgetUsage(run.usage), agents: run.agents.map(({ id, name, label: label2, state, attempts }) => ({ id, name, ...label2 ? { label: label2 } : {}, state, attempts })), ...run.error ? { error: run.error } : {}, ...failedAt ? { failedAt } : {}, replayablePaths, incompletePaths, artifacts: summaryArtifacts(directory) };
1113
1155
  }
1114
1156
  function systemPromptStoragePath(directory) {
1115
- return join2(directory, SYSTEM_PROMPT_STORAGE);
1157
+ return join3(directory, SYSTEM_PROMPT_STORAGE);
1116
1158
  }
1117
1159
  function systemPromptRecordsPath(directory) {
1118
- return join2(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS);
1160
+ return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS);
1119
1161
  }
1120
1162
  function systemPromptBodiesPath(directory) {
1121
- return join2(systemPromptStoragePath(directory), SYSTEM_PROMPT_BODIES);
1163
+ return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_BODIES);
1122
1164
  }
1123
1165
  function systemPromptSequencePath(directory) {
1124
- return join2(systemPromptStoragePath(directory), SYSTEM_PROMPT_SEQUENCE);
1166
+ return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_SEQUENCE);
1125
1167
  }
1126
1168
  function systemPromptRecordName(sequence) {
1127
1169
  return `${String(sequence).padStart(20, "0")}.json`;
1128
1170
  }
1129
1171
  async function createSystemPromptStorage(directory, writeArtifact) {
1130
1172
  const storage = systemPromptStoragePath(directory);
1131
- await mkdir2(join2(storage, SYSTEM_PROMPT_RECORDS), { recursive: true, mode: 448 });
1132
- await mkdir2(join2(storage, SYSTEM_PROMPT_BODIES), { recursive: true, mode: 448 });
1173
+ await mkdir2(join3(storage, SYSTEM_PROMPT_RECORDS), { recursive: true, mode: 448 });
1174
+ await mkdir2(join3(storage, SYSTEM_PROMPT_BODIES), { recursive: true, mode: 448 });
1133
1175
  await atomicWriteFile(systemPromptSequencePath(directory), "0\n");
1134
- if (writeArtifact) await atomicJson(join2(directory, "system-prompts.json"), SYSTEM_PROMPT_ARTIFACT);
1176
+ if (writeArtifact) await atomicJson(join3(directory, "system-prompts.json"), SYSTEM_PROMPT_ARTIFACT);
1135
1177
  }
1136
1178
  var RunStore = class _RunStore {
1137
1179
  constructor(cwd, sessionId, runId, home = homedir2()) {
@@ -1140,7 +1182,7 @@ var RunStore = class _RunStore {
1140
1182
  this.runId = runId;
1141
1183
  this.home = home;
1142
1184
  this.cwd = resolve2(cwd);
1143
- this.directory = join2(runsDirectory(this.cwd, sessionId, home), safePart(runId));
1185
+ this.directory = join3(runsDirectory(this.cwd, sessionId, home), safePart(runId));
1144
1186
  }
1145
1187
  cwd;
1146
1188
  sessionId;
@@ -1162,19 +1204,19 @@ var RunStore = class _RunStore {
1162
1204
  }
1163
1205
  async create(run, snapshot) {
1164
1206
  this.#assertRunIdentity(run, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
1165
- const temporary = join2(dirname(this.directory), `.${safePart(this.runId)}.${String(process.pid)}.${randomUUID2()}.tmp`);
1207
+ const temporary = join3(dirname(this.directory), `.${safePart(this.runId)}.${String(process.pid)}.${randomUUID2()}.tmp`);
1166
1208
  await mkdir2(dirname(this.directory), { recursive: true, mode: 448 });
1167
1209
  await mkdir2(temporary, { mode: 448 });
1168
1210
  try {
1169
- await writeFile2(join2(temporary, "workflow.js"), snapshot.script, { encoding: "utf8", mode: 384 });
1170
- await atomicJson(join2(temporary, "snapshot.json"), snapshot);
1171
- await atomicJson(join2(temporary, "journal.json"), { completed: {}, awaiting: {}, decisions: {} });
1172
- await atomicJson(join2(temporary, "ownership.json"), []);
1173
- await atomicJson(join2(temporary, "worktrees.json"), []);
1174
- await atomicJson(join2(temporary, "borrowed-worktrees.json"), []);
1175
- await atomicJson(join2(temporary, "state.json"), run);
1211
+ await writeFile2(join3(temporary, "workflow.js"), snapshot.script, { encoding: "utf8", mode: 384 });
1212
+ await atomicJson(join3(temporary, "snapshot.json"), snapshot);
1213
+ await atomicJson(join3(temporary, "journal.json"), { completed: {}, awaiting: {}, decisions: {} });
1214
+ await atomicJson(join3(temporary, "ownership.json"), []);
1215
+ await atomicJson(join3(temporary, "worktrees.json"), []);
1216
+ await atomicJson(join3(temporary, "borrowed-worktrees.json"), []);
1217
+ await atomicJson(join3(temporary, "state.json"), run);
1176
1218
  await createSystemPromptStorage(temporary, true);
1177
- await atomicJson(join2(temporary, "summary.json"), summaryFromRun(run, this.directory, { completed: {} }, void 0, (/* @__PURE__ */ new Date()).toISOString()));
1219
+ await atomicJson(join3(temporary, "summary.json"), summaryFromRun(run, this.directory, { completed: {} }, void 0, (/* @__PURE__ */ new Date()).toISOString()));
1178
1220
  await rename3(temporary, this.directory);
1179
1221
  } catch (error) {
1180
1222
  await rm3(temporary, { recursive: true, force: true });
@@ -1183,12 +1225,12 @@ var RunStore = class _RunStore {
1183
1225
  }
1184
1226
  async refreshSummary() {
1185
1227
  const write = this.summaryLane.run(async () => {
1186
- const run = decodePersistedRun(await json(join2(this.directory, "state.json")), true);
1187
- const journal = decodeJournal(await json(join2(this.directory, "journal.json")));
1188
- const previous = await json(join2(this.directory, "summary.json")).then(decodeSummaryProjection).catch(() => void 0);
1228
+ const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
1229
+ const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
1230
+ const previous = await json(join3(this.directory, "summary.json")).then(decodeSummaryProjection).catch(() => void 0);
1189
1231
  if (!run || !journal) throw new Error("Persisted run or journal is invalid");
1190
- const fallbackCreatedAt = await stat2(join2(this.directory, "state.json")).then((value) => new Date(value.mtimeMs).toISOString());
1191
- await atomicJson(join2(this.directory, "summary.json"), summaryFromRun(run, this.directory, journal, previous, fallbackCreatedAt));
1232
+ const fallbackCreatedAt = await stat2(join3(this.directory, "state.json")).then((value) => new Date(value.mtimeMs).toISOString());
1233
+ await atomicJson(join3(this.directory, "summary.json"), summaryFromRun(run, this.directory, journal, previous, fallbackCreatedAt));
1192
1234
  });
1193
1235
  await write;
1194
1236
  }
@@ -1198,7 +1240,7 @@ var RunStore = class _RunStore {
1198
1240
  }
1199
1241
  async isComplete() {
1200
1242
  try {
1201
- await Promise.all([access(join2(this.directory, "snapshot.json")), access(join2(this.directory, "journal.json")), access(join2(this.directory, "ownership.json")), access(join2(this.directory, "state.json"))]);
1243
+ await Promise.all([access(join3(this.directory, "snapshot.json")), access(join3(this.directory, "journal.json")), access(join3(this.directory, "ownership.json")), access(join3(this.directory, "state.json"))]);
1202
1244
  return true;
1203
1245
  } catch {
1204
1246
  return false;
@@ -1206,18 +1248,18 @@ var RunStore = class _RunStore {
1206
1248
  }
1207
1249
  async load() {
1208
1250
  await this.stateLane.run(async () => void 0);
1209
- const rawRun = await json(join2(this.directory, "state.json"));
1251
+ const rawRun = await json(join3(this.directory, "state.json"));
1210
1252
  const run = decodePersistedRun(rawRun, true);
1211
1253
  if (!run) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run is invalid");
1212
1254
  this.#assertRunIdentity(run, "RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
1213
1255
  if (!object(rawRun) || !Array.isArray(rawRun.agentSessions) || Object.hasOwn(rawRun, "nativeSessions")) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run uses an unsupported agent session format");
1214
- const snapshot = decodeLaunchSnapshot(await json(join2(this.directory, "snapshot.json")));
1256
+ const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
1215
1257
  if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted launch snapshot is invalid");
1216
1258
  return { run, snapshot: loadLaunchSnapshot(snapshot) };
1217
1259
  }
1218
1260
  async loadStatus() {
1219
1261
  await this.stateLane.run(async () => void 0);
1220
- const run = decodePersistedRun(await json(join2(this.directory, "state.json")), true);
1262
+ const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
1221
1263
  if (!run) throw new WorkflowError("RUN_NOT_FOUND", "Persisted run is invalid");
1222
1264
  this.#assertRunIdentity(run, "RUN_NOT_FOUND", "Persisted run does not belong to this project");
1223
1265
  return run;
@@ -1226,11 +1268,11 @@ var RunStore = class _RunStore {
1226
1268
  await this.stateLane.run(async () => void 0);
1227
1269
  await this.journalLane.run(async () => void 0);
1228
1270
  await this.summaryLane.run(async () => void 0);
1229
- const run = decodePersistedRun(await json(join2(this.directory, "state.json")), true);
1230
- const journal = decodeJournal(await json(join2(this.directory, "journal.json")));
1231
- const previous = await json(join2(this.directory, "summary.json")).then(decodeSummaryProjection).catch(() => void 0);
1271
+ const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
1272
+ const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
1273
+ const previous = await json(join3(this.directory, "summary.json")).then(decodeSummaryProjection).catch(() => void 0);
1232
1274
  if (!run || !journal) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run or journal is invalid");
1233
- const [stateStat, journalStat] = await Promise.all([stat2(join2(this.directory, "state.json")), stat2(join2(this.directory, "journal.json"))]);
1275
+ const [stateStat, journalStat] = await Promise.all([stat2(join3(this.directory, "state.json")), stat2(join3(this.directory, "journal.json"))]);
1234
1276
  const fallbackCreatedAt = new Date(stateStat.mtimeMs).toISOString();
1235
1277
  const previousUpdatedAt = previous?.updatedAt === void 0 ? Number.NaN : Date.parse(previous.updatedAt);
1236
1278
  const updatedAt = new Date(Math.max(stateStat.mtimeMs, journalStat.mtimeMs, Number.isNaN(previousUpdatedAt) ? 0 : previousUpdatedAt)).toISOString();
@@ -1239,41 +1281,55 @@ var RunStore = class _RunStore {
1239
1281
  async saveState(run) {
1240
1282
  const write = this.stateLane.run(async () => {
1241
1283
  this.#assertRunIdentity(run, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
1242
- await atomicJson(join2(this.directory, "state.json"), run);
1284
+ await atomicJson(join3(this.directory, "state.json"), run);
1243
1285
  this.refreshSummaryBestEffort();
1244
1286
  });
1245
1287
  await write;
1246
1288
  }
1247
1289
  async updateState(update) {
1248
1290
  const write = this.stateLane.run(async () => {
1249
- const current = decodePersistedRun(await json(join2(this.directory, "state.json")));
1291
+ const current = decodePersistedRun(await json(join3(this.directory, "state.json")));
1250
1292
  if (!current) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run is invalid");
1251
1293
  this.#assertRunIdentity(current, "RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
1252
1294
  const result = await update(current);
1253
1295
  this.#assertRunIdentity(result, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
1254
- await atomicJson(join2(this.directory, "state.json"), result);
1296
+ await atomicJson(join3(this.directory, "state.json"), result);
1255
1297
  this.refreshSummaryBestEffort();
1256
1298
  return result;
1257
1299
  });
1258
1300
  return write;
1259
1301
  }
1302
+ async saveAgentConfiguration(key, configuration) {
1303
+ await this.launchSnapshotLane.run(async () => {
1304
+ const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
1305
+ if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
1306
+ await atomicJson(join3(this.directory, "snapshot.json"), { ...snapshot, agentConfigurations: { ...snapshot.agentConfigurations, [key]: configuration } });
1307
+ });
1308
+ }
1309
+ async setLaunchMode(launchMode) {
1310
+ await this.launchSnapshotLane.run(async () => {
1311
+ const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
1312
+ if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Workflow launch snapshot identity version is incompatible");
1313
+ await atomicJson(join3(this.directory, "snapshot.json"), { ...snapshot, launchMode });
1314
+ });
1315
+ }
1260
1316
  async saveSnapshot(snapshot) {
1261
- const write = this.launchSnapshotLane.run(() => atomicJson(join2(this.directory, "snapshot.json"), snapshot));
1317
+ const write = this.launchSnapshotLane.run(() => atomicJson(join3(this.directory, "snapshot.json"), snapshot));
1262
1318
  await write;
1263
1319
  }
1264
1320
  async appendEvent(event) {
1265
1321
  await this.updateState((run) => ({ ...run, events: [...run.events ?? [], ...event.type !== "log" && run.events?.some((current) => current.type === event.type && current.message === event.message) ? [] : [event]] }));
1266
1322
  }
1267
1323
  async saveOwnership(nodes) {
1268
- await atomicJson(join2(this.directory, "ownership.json"), nodes);
1324
+ await atomicJson(join3(this.directory, "ownership.json"), nodes);
1269
1325
  }
1270
1326
  async loadOwnership() {
1271
- const nodes = decodeOwnershipRecords(await json(join2(this.directory, "ownership.json")));
1327
+ const nodes = decodeOwnershipRecords(await json(join3(this.directory, "ownership.json")));
1272
1328
  if (!nodes) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted ownership records are invalid");
1273
1329
  return nodes;
1274
1330
  }
1275
1331
  systemPromptPath() {
1276
- return join2(this.directory, "system-prompts.json");
1332
+ return join3(this.directory, "system-prompts.json");
1277
1333
  }
1278
1334
  async readSystemPromptArtifact() {
1279
1335
  try {
@@ -1287,7 +1343,7 @@ var RunStore = class _RunStore {
1287
1343
  }
1288
1344
  async appendSystemPromptV2(entry) {
1289
1345
  const sha256 = createHash2("sha256").update(entry.prompt).digest("hex");
1290
- const bodyPath = join2(systemPromptBodiesPath(this.directory), sha256);
1346
+ const bodyPath = join3(systemPromptBodiesPath(this.directory), sha256);
1291
1347
  try {
1292
1348
  await access(bodyPath);
1293
1349
  } catch (error) {
@@ -1299,7 +1355,7 @@ var RunStore = class _RunStore {
1299
1355
  const sequence = previous + 1;
1300
1356
  await atomicWriteFile(systemPromptSequencePath(this.directory), `${String(sequence)}
1301
1357
  `);
1302
- await atomicJson(join2(systemPromptRecordsPath(this.directory), systemPromptRecordName(sequence)), { sessionId: entry.sessionId, attempt: entry.attempt, turn: entry.turn, sha256 });
1358
+ await atomicJson(join3(systemPromptRecordsPath(this.directory), systemPromptRecordName(sequence)), { sessionId: entry.sessionId, attempt: entry.attempt, turn: entry.turn, sha256 });
1303
1359
  }
1304
1360
  async migrateSystemPrompts(legacy) {
1305
1361
  await rm3(systemPromptStoragePath(this.directory), { recursive: true, force: true });
@@ -1331,14 +1387,14 @@ var RunStore = class _RunStore {
1331
1387
  const recordSequence = Number(match[1]);
1332
1388
  if (!Number.isSafeInteger(recordSequence) || recordSequence < 1) throw new Error("Persisted system-prompt records are invalid");
1333
1389
  highest = Math.max(highest, recordSequence);
1334
- const record2 = await json(join2(systemPromptRecordsPath(this.directory), name));
1390
+ const record2 = await json(join3(systemPromptRecordsPath(this.directory), name));
1335
1391
  if (!object(record2)) throw new Error("Persisted system-prompt record is invalid");
1336
1392
  const sessionId = record2.sessionId;
1337
1393
  const attempt = record2.attempt;
1338
1394
  const turn = record2.turn;
1339
1395
  const sha256 = record2.sha256;
1340
1396
  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");
1341
- const prompt = await readFile3(join2(systemPromptBodiesPath(this.directory), sha256), "utf8");
1397
+ const prompt = await readFile3(join3(systemPromptBodiesPath(this.directory), sha256), "utf8");
1342
1398
  if (createHash2("sha256").update(prompt).digest("hex") !== sha256) throw new Error("Persisted system-prompt body is invalid");
1343
1399
  entries.push({ sessionId, attempt, turn, sha256, prompt });
1344
1400
  }
@@ -1361,7 +1417,7 @@ var RunStore = class _RunStore {
1361
1417
  throw new Error("Persisted system prompts are invalid");
1362
1418
  }
1363
1419
  async #decodeJournal() {
1364
- const journal = decodeJournal(await json(join2(this.directory, "journal.json")));
1420
+ const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
1365
1421
  if (!journal) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted journal is invalid");
1366
1422
  return journal;
1367
1423
  }
@@ -1375,7 +1431,7 @@ var RunStore = class _RunStore {
1375
1431
  const journal = await this.#decodeJournal();
1376
1432
  journal.awaiting ??= {};
1377
1433
  const result = await update(journal);
1378
- await atomicJson(join2(this.directory, "journal.json"), journal);
1434
+ await atomicJson(join3(this.directory, "journal.json"), journal);
1379
1435
  this.refreshSummaryBestEffort();
1380
1436
  return result;
1381
1437
  });
@@ -1485,10 +1541,10 @@ var RunStore = class _RunStore {
1485
1541
  }
1486
1542
  expectedWorktree(owner) {
1487
1543
  const key = this.#worktreeKey(owner);
1488
- return { path: join2(this.directory, "worktrees", key), branch: `pi-extensible-workflows/${safePart(this.runId)}/${key}` };
1544
+ return { path: join3(this.directory, "worktrees", key), branch: `pi-extensible-workflows/${safePart(this.runId)}/${key}` };
1489
1545
  }
1490
1546
  markerPath(owner) {
1491
- return join2(this.directory, `worktree-${this.#worktreeKey(owner)}.creating`);
1547
+ return join3(this.directory, `worktree-${this.#worktreeKey(owner)}.creating`);
1492
1548
  }
1493
1549
  namedWorktreeOwner(name) {
1494
1550
  if (!name.trim()) throw new WorkflowError("WORKTREE_FAILED", "Named worktree names must be non-empty");
@@ -1531,13 +1587,13 @@ var RunStore = class _RunStore {
1531
1587
  }
1532
1588
  structuralWorktree(owner, record2) {
1533
1589
  const expected = this.expectedWorktree(owner);
1534
- const relativePath = relative(this.directory, record2.path);
1535
- const relativeCwd = relative(record2.path, record2.cwd);
1536
- if (record2.owner !== owner || resolve2(record2.path) !== expected.path || record2.branch !== expected.branch || relativePath === ".." || relativePath.startsWith(`..${sep}`) || relativeCwd === ".." || relativeCwd.startsWith(`..${sep}`)) throw new Error(`Invalid worktree record for ${owner}`);
1590
+ const relativePath = relative2(this.directory, record2.path);
1591
+ const relativeCwd = relative2(record2.path, record2.cwd);
1592
+ if (record2.owner !== owner || resolve2(record2.path) !== expected.path || record2.branch !== expected.branch || relativePath === ".." || relativePath.startsWith(`..${sep2}`) || relativeCwd === ".." || relativeCwd.startsWith(`..${sep2}`)) throw new Error(`Invalid worktree record for ${owner}`);
1537
1593
  return record2;
1538
1594
  }
1539
1595
  async #loadWorktreeRecords(missingOk = true) {
1540
- const rawRecords = await json(join2(this.directory, "worktrees.json")).catch((error) => {
1596
+ const rawRecords = await json(join3(this.directory, "worktrees.json")).catch((error) => {
1541
1597
  if (missingOk && isNodeError(error, "ENOENT")) return [];
1542
1598
  throw error;
1543
1599
  });
@@ -1547,7 +1603,7 @@ var RunStore = class _RunStore {
1547
1603
  }
1548
1604
  async borrowedWorktreeRecords(wait = true) {
1549
1605
  if (wait) await this.borrowedWorktreeLane.run(async () => void 0);
1550
- const rawRecords = await json(join2(this.directory, "borrowed-worktrees.json")).catch((error) => {
1606
+ const rawRecords = await json(join3(this.directory, "borrowed-worktrees.json")).catch((error) => {
1551
1607
  if (isNodeError(error, "ENOENT")) return [];
1552
1608
  throw error;
1553
1609
  });
@@ -1672,11 +1728,11 @@ var RunStore = class _RunStore {
1672
1728
  const reference = this.structuralWorktree(owner, record2);
1673
1729
  paths.add(resolve2(reference.path));
1674
1730
  }
1675
- const entries = await readdir2(join2(this.directory, "worktrees"), { withFileTypes: true }).catch((error) => {
1731
+ const entries = await readdir2(join3(this.directory, "worktrees"), { withFileTypes: true }).catch((error) => {
1676
1732
  if (isNodeError(error, "ENOENT")) return [];
1677
1733
  throw error;
1678
1734
  });
1679
- for (const entry of entries) if (!entry.isDirectory() || entry.isSymbolicLink() || !paths.has(resolve2(join2(this.directory, "worktrees", entry.name)))) throw new Error(`Unrecorded worktree artifact: ${join2(this.directory, "worktrees", entry.name)}`);
1735
+ for (const entry of entries) if (!entry.isDirectory() || entry.isSymbolicLink() || !paths.has(resolve2(join3(this.directory, "worktrees", entry.name)))) throw new Error(`Unrecorded worktree artifact: ${join3(this.directory, "worktrees", entry.name)}`);
1680
1736
  } catch (error) {
1681
1737
  throw coerceWorkflowError("WORKTREE_FAILED", error);
1682
1738
  }
@@ -1728,7 +1784,7 @@ var RunStore = class _RunStore {
1728
1784
  }
1729
1785
  async cleanupOrphanWorktrees() {
1730
1786
  const entries = await readdir2(this.directory).catch(() => []);
1731
- for (const entry of entries.filter((name) => name.endsWith(".creating"))) await this.cleanupMarker(join2(this.directory, entry));
1787
+ for (const entry of entries.filter((name) => name.endsWith(".creating"))) await this.cleanupMarker(join3(this.directory, entry));
1732
1788
  }
1733
1789
  async validateWorktree(owner, cwd) {
1734
1790
  try {
@@ -1771,8 +1827,8 @@ var RunStore = class _RunStore {
1771
1827
  try {
1772
1828
  const root = (await git(this.cwd, ["rev-parse", "--show-toplevel"])).trim();
1773
1829
  const [canonicalRoot, canonicalCwd] = await Promise.all([realpath(root), realpath(this.cwd)]);
1774
- const launchRelative = relative(canonicalRoot, canonicalCwd);
1775
- if (launchRelative === ".." || launchRelative.startsWith(`..${sep}`)) throw new Error("launch cwd is outside the repository");
1830
+ const launchRelative = relative2(canonicalRoot, canonicalCwd);
1831
+ if (launchRelative === ".." || launchRelative.startsWith(`..${sep2}`)) throw new Error("launch cwd is outside the repository");
1776
1832
  await this.cleanupMarker(markerPath);
1777
1833
  const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=normal"])).trim();
1778
1834
  if (dirty) {
@@ -1788,7 +1844,7 @@ var RunStore = class _RunStore {
1788
1844
  throw new Error("repository has no commits");
1789
1845
  }
1790
1846
  await mkdir2(dirname(path), { recursive: true, mode: 448 });
1791
- const record2 = { owner, path, branch, cwd: join2(path, launchRelative), base: commit };
1847
+ const record2 = { owner, path, branch, cwd: join3(path, launchRelative), base: commit };
1792
1848
  await atomicJson(markerPath, { owner, path, branch, base: commit });
1793
1849
  markerWritten = true;
1794
1850
  await git(root, ["branch", branch, commit]);
@@ -1833,7 +1889,7 @@ var RunStore = class _RunStore {
1833
1889
  return;
1834
1890
  }
1835
1891
  records.push(binding);
1836
- await atomicJson(join2(this.directory, "borrowed-worktrees.json"), records);
1892
+ await atomicJson(join3(this.directory, "borrowed-worktrees.json"), records);
1837
1893
  });
1838
1894
  await write;
1839
1895
  }
@@ -1914,12 +1970,12 @@ var RunStore = class _RunStore {
1914
1970
  return changed;
1915
1971
  }
1916
1972
  async saveResult(value) {
1917
- const path = join2(this.directory, "result.json");
1973
+ const path = join3(this.directory, "result.json");
1918
1974
  await atomicPrettyJson(path, value);
1919
1975
  return path;
1920
1976
  }
1921
1977
  async resultBytes() {
1922
- return (await stat2(join2(this.directory, "result.json"))).size;
1978
+ return (await stat2(join3(this.directory, "result.json"))).size;
1923
1979
  }
1924
1980
  async delete(confirmed) {
1925
1981
  if (!confirmed) throw new WorkflowError("CANCELLED", "Run deletion requires confirmation");
@@ -1942,19 +1998,20 @@ var RunStore = class _RunStore {
1942
1998
 
1943
1999
  // src/settings.ts
1944
2000
  import { mkdirSync, readFileSync } from "node:fs";
1945
- import { homedir as homedir3 } from "node:os";
1946
- import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
1947
- import { fileURLToPath } from "node:url";
2001
+ import { dirname as dirname2, join as join4 } from "node:path";
1948
2002
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
1949
- var ROLE_DIRECTORY = "pi-extensible-workflows";
2003
+ import { homedir as homedir3 } from "node:os";
2004
+ import { resolve as resolve3 } from "node:path";
2005
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2006
+ var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
1950
2007
  var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
1951
2008
  function workflowSettingsPath(agentDir = getAgentDir()) {
1952
- return join3(agentDir, ROLE_DIRECTORY, "settings.json");
2009
+ return join4(agentDir, WORKFLOW_DIRECTORY, "settings.json");
1953
2010
  }
1954
2011
  function normalizedResourcePath(value, settingsPath) {
1955
- if (value === "*") return value;
1956
- let expanded = value === "~" ? homedir3() : value.startsWith("~/") || value.startsWith("~\\") ? join3(homedir3(), value.slice(2)) : value;
1957
- if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
2012
+ if (value === "*" || value.startsWith("builtin:")) return value;
2013
+ let expanded = value === "~" ? homedir3() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir3(), value.slice(2)) : value;
2014
+ if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
1958
2015
  const resolved = resolve3(dirname2(settingsPath), expanded);
1959
2016
  if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
1960
2017
  if (resourcePatternHasMagic(expanded)) {
@@ -2011,18 +2068,19 @@ function selectorsFromSettings(settings) {
2011
2068
  function selectorSet(value) {
2012
2069
  return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2013
2070
  }
2014
- function validateContextFileScopes(value, rolePath) {
2071
+ function validateContextFileScopes(value, sourcePath) {
2015
2072
  if (value === void 0) return void 0;
2016
- if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2073
+ if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${sourcePath}.contextFiles must be an array containing only global, project, or cwd`);
2017
2074
  return [...value];
2018
2075
  }
2019
2076
  function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
2020
2077
  if (value === void 0) return void 0;
2078
+ if (!object(value) || !jsonValue(value)) fail(errorCode2, `${settingsPath}.extensionSettings must be a JSON object`);
2079
+ for (const namespace of Object.keys(value)) if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${settingsPath}.extensionSettings contains an invalid namespace: ${namespace}`);
2080
+ const generic = value;
2021
2081
  const base = `${settingsPath}.extensionSettings`;
2022
- if (!object(value)) fail(errorCode2, `${base} must be an object`);
2023
2082
  const normalized = {};
2024
- for (const [namespace, raw] of Object.entries(value)) {
2025
- if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2083
+ for (const [namespace, raw] of Object.entries(generic)) {
2026
2084
  if (namespace === "herdr") {
2027
2085
  if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2028
2086
  if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
@@ -2037,7 +2095,6 @@ function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "IN
2037
2095
  normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
2038
2096
  continue;
2039
2097
  }
2040
- if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2041
2098
  normalized[namespace] = structuredClone(raw);
2042
2099
  }
2043
2100
  return deepFreeze(normalized);
@@ -2064,7 +2121,7 @@ function parseSettings(path, partial) {
2064
2121
  fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2065
2122
  }
2066
2123
  if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2067
- const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
2124
+ const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget", "codemodeTools"]]);
2068
2125
  const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2069
2126
  if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2070
2127
  if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
@@ -2072,6 +2129,8 @@ function parseSettings(path, partial) {
2072
2129
  if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2073
2130
  const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2074
2131
  if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2132
+ const codemodeTools = parsed.codemodeTools;
2133
+ if (codemodeTools !== void 0 && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none") fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
2075
2134
  const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2076
2135
  const skills = validateSelectorList(parsed.skills, path, "skills");
2077
2136
  const tools = validateSelectorList(parsed.tools, path, "tools");
@@ -2082,6 +2141,7 @@ function parseSettings(path, partial) {
2082
2141
  return Object.freeze({
2083
2142
  ...concurrency === void 0 ? {} : { concurrency },
2084
2143
  ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2144
+ ...codemodeTools === void 0 ? {} : { codemodeTools },
2085
2145
  ...modelAliases === void 0 ? {} : { modelAliases },
2086
2146
  ...skills === void 0 ? {} : { skills },
2087
2147
  ...extensions === void 0 ? {} : { extensions },
@@ -2094,46 +2154,38 @@ function parseSettings(path, partial) {
2094
2154
  function loadSettings(path = workflowSettingsPath()) {
2095
2155
  return parseSettings(path, false);
2096
2156
  }
2157
+ var READ_ONLY_WORKFLOW_TOOLS = /* @__PURE__ */ new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
2158
+ function workflowToolExposure(name, codemodeTools) {
2159
+ const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
2160
+ return { ...codemodeTools === "none" || codemodeTools === "read-only" && !readOnly ? { exposure: "model-only" } : {}, ...readOnly ? { annotations: { readOnlyHint: true } } : {} };
2161
+ }
2162
+ function loadCodemodeToolsSetting(agentDir) {
2163
+ try {
2164
+ return loadSettings(workflowSettingsPath(agentDir)).codemodeTools;
2165
+ } catch {
2166
+ return void 0;
2167
+ }
2168
+ }
2097
2169
  function loadSettingsOverrides(path) {
2098
2170
  return parseSettings(path, true);
2099
2171
  }
2100
2172
  function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2101
2173
  const projectSettingsPath = workflowProjectSettingsPath(cwd);
2102
- const global = loadSettings(globalSettingsPath);
2103
- const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2104
- const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2105
- const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2106
- const globalSelectors = selectorsFromSettings(global);
2107
- const projectSelectors = selectorsFromSettings(project);
2108
- const effectiveSelectors = selectorSet({
2109
- skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2110
- extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2111
- ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2112
- });
2113
- const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2114
- const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2115
- const sources = {
2116
- concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2117
- modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2118
- skills: sourceFor("skills"),
2119
- extensions: sourceFor("extensions"),
2120
- tools: sourceFor("tools"),
2121
- ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2122
- ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2123
- ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2124
- };
2174
+ const consumerGlobal = loadSettings(globalSettingsPath);
2175
+ const consumerProject = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2176
+ const global = consumerGlobal;
2177
+ const project = consumerProject;
2178
+ const selectorSources = { global: selectorsFromSettings(global), project: selectorsFromSettings(project) };
2125
2179
  const effective = Object.freeze({
2126
- concurrency: project.concurrency ?? global.concurrency,
2127
- backgroundWidget: global.backgroundWidget ?? true,
2128
- ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2129
- ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2130
- ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2131
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2132
- ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2133
- ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2134
- ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2180
+ ...global,
2181
+ ...project,
2182
+ skills: [...global.skills ?? [], ...project.skills ?? []],
2183
+ extensions: [...global.extensions ?? [], ...project.extensions ?? []],
2184
+ ...global.tools === void 0 && project.tools === void 0 ? {} : { tools: [...global.tools ?? [], ...project.tools ?? []] }
2135
2185
  });
2136
- return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
2186
+ const sources = { concurrency: project.concurrency === void 0 ? globalSettingsPath : projectSettingsPath, modelAliases: project.modelAliases === void 0 ? globalSettingsPath : projectSettingsPath };
2187
+ for (const key of ["skills", "extensions", "tools", "extensionSettings", "retention", "worktreePostCreateCommand"]) if (effective[key] !== void 0) sources[key] = project[key] === void 0 ? globalSettingsPath : projectSettingsPath;
2188
+ return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources, selectorSources };
2137
2189
  }
2138
2190
  function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2139
2191
  for (const name of names) {
@@ -2150,42 +2202,63 @@ function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = wo
2150
2202
  const global = selectorSet(selectorsFromSettings(resolved.global));
2151
2203
  const project = selectorSet(selectorsFromSettings(resolved.project));
2152
2204
  const effective = selectorSet(selectorsFromSettings(resolved.effective));
2153
- return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2205
+ return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: resolved.selectorSources };
2206
+ }
2207
+ function canonicalExtensionSelector(selector, cwd) {
2208
+ const negated = selector.startsWith("!");
2209
+ return `${negated ? "!" : ""}${normalizedResourcePath(negated ? selector.slice(1) : selector, join4(cwd, "settings.json"))}`;
2210
+ }
2211
+
2212
+ // src/validation.ts
2213
+ var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "excludeTools", "skills", "extensions", "contextFiles", "outputSchema", "retries", "timeoutMs"]);
2214
+ function isCoreAgentOption(key) {
2215
+ return AGENT_OPTION_KEYS.has(key);
2216
+ }
2217
+ function validateAgentOption(key, value) {
2218
+ switch (key) {
2219
+ case "label":
2220
+ if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent label must be a non-empty string");
2221
+ break;
2222
+ case "model":
2223
+ if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent model must be a non-empty string");
2224
+ assertModelThinking(value, "agent model");
2225
+ break;
2226
+ case "tools":
2227
+ case "skills":
2228
+ case "extensions":
2229
+ validateSelectorList(value, "agent options", key, "INVALID_METADATA", key !== "extensions");
2230
+ break;
2231
+ case "excludeTools":
2232
+ if (!Array.isArray(value) || value.some((tool) => typeof tool !== "string" || !tool.trim() || tool !== tool.trim() || tool.startsWith("!") || resourcePatternHasMagic(tool))) fail("INVALID_METADATA", "agent excludeTools must be an array of exact tool names");
2233
+ if (value.includes("workflow_result")) fail("INVALID_METADATA", "agent excludeTools cannot remove workflow_result");
2234
+ break;
2235
+ case "contextFiles":
2236
+ validateContextFileScopes(value, "agent options");
2237
+ break;
2238
+ case "outputSchema":
2239
+ validateSchema(value, "agent outputSchema");
2240
+ break;
2241
+ case "retries":
2242
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) fail("INVALID_METADATA", "agent retries must be a non-negative integer");
2243
+ break;
2244
+ case "timeoutMs":
2245
+ if (value !== null && !positiveInteger(value)) fail("INVALID_METADATA", "agent timeoutMs must be null or a positive integer");
2246
+ break;
2247
+ }
2248
+ }
2249
+ function validateAgentOptions(value) {
2250
+ if (!object(value) || !jsonValue(value)) fail("INVALID_METADATA", "agent options must be a JSON object");
2251
+ if (Object.prototype.hasOwnProperty.call(value, "thinking")) fail("INVALID_METADATA", "agent thinking is not supported; use model provider/model:thinking");
2252
+ for (const [key, option] of Object.entries(value)) if (AGENT_OPTION_KEYS.has(key)) validateAgentOption(key, option);
2253
+ return value;
2154
2254
  }
2155
2255
 
2156
2256
  // src/registry.ts
2157
- 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"]);
2257
+ import { Value } from "typebox/value";
2258
+ 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"]);
2158
2259
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
2159
2260
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2160
2261
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
2161
- function normalizeRoleDirectory(value) {
2162
- try {
2163
- if (value instanceof URL) {
2164
- if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
2165
- return fileURLToPath2(value);
2166
- }
2167
- if (typeof value === "string") {
2168
- if (value.startsWith("file:")) return fileURLToPath2(value);
2169
- if (isAbsolute(value)) return value;
2170
- }
2171
- } catch (error) {
2172
- fail("INVALID_METADATA", `Invalid workflow role directory: ${errorText(error)}`);
2173
- }
2174
- fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
2175
- }
2176
- function isBuiltinRoleDirectory(path) {
2177
- const canonical = canonicalPath(path);
2178
- const starterDirectory = dirname3(canonical);
2179
- if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
2180
- const distributionDirectory = dirname3(starterDirectory);
2181
- const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
2182
- try {
2183
- const metadata = JSON.parse(readFileSync2(join4(packageRoot2, "package.json"), "utf8"));
2184
- return object(metadata) && isCorePackageName(metadata.name);
2185
- } catch {
2186
- return false;
2187
- }
2188
- }
2189
2262
  function catalogSchema(schema, at) {
2190
2263
  validateSchema(schema, at);
2191
2264
  return structuredClone(schema);
@@ -2194,9 +2267,9 @@ var WorkflowRegistry = class {
2194
2267
  #extensions = /* @__PURE__ */ new Set();
2195
2268
  #functionSources = /* @__PURE__ */ new Map();
2196
2269
  #globals = /* @__PURE__ */ new Map();
2270
+ #preparationHooks = /* @__PURE__ */ new Map();
2197
2271
  #hooks = /* @__PURE__ */ new Map();
2198
2272
  #agentAttemptActions = /* @__PURE__ */ new Map();
2199
- #roleDirectories = /* @__PURE__ */ new Map();
2200
2273
  #modelAliases = /* @__PURE__ */ new Map();
2201
2274
  #settingsValidators = /* @__PURE__ */ new Set();
2202
2275
  #frozen = false;
@@ -2216,9 +2289,10 @@ var WorkflowRegistry = class {
2216
2289
  register(extension2) {
2217
2290
  if (this.#frozen) fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
2218
2291
  if (object(extension2) && Object.prototype.hasOwnProperty.call(extension2, "workflows")) fail("INVALID_METADATA", "Separate registered workflow definitions were removed; register a function with input and output schemas instead");
2219
- if (!object(extension2) || Object.keys(extension2).some((key) => !["version", "headline", "description", "source", "dependencies", "validateSettings", "functions", "modelAliases", "agentSetupHooks", "agentAttemptActions", "roleDirectories"].includes(key)) || typeof extension2.version !== "string" || !SEMVER.test(extension2.version) || typeof extension2.headline !== "string" || !extension2.headline.trim()) fail("INVALID_METADATA", "Workflow extensions require a semantic version and non-empty headline");
2292
+ if (!object(extension2) || Object.keys(extension2).some((key) => !["version", "headline", "description", "source", "dependencies", "validateSettings", "functions", "modelAliases", "agentPreparationHooks", "agentSetupHooks", "agentAttemptActions"].includes(key)) || typeof extension2.version !== "string" || !SEMVER.test(extension2.version) || typeof extension2.headline !== "string" || !extension2.headline.trim()) fail("INVALID_METADATA", "Workflow extensions require a semantic version and non-empty headline");
2220
2293
  const functions = extension2.functions ?? {};
2221
2294
  const modelAliases = extension2.modelAliases ?? {};
2295
+ const agentPreparationHooks = extension2.agentPreparationHooks ?? {};
2222
2296
  const agentSetupHooks = extension2.agentSetupHooks ?? {};
2223
2297
  const agentAttemptActions = extension2.agentAttemptActions ?? {};
2224
2298
  const source = extension2.source;
@@ -2228,10 +2302,7 @@ var WorkflowRegistry = class {
2228
2302
  if (validateSettings !== void 0 && typeof validateSettings !== "function") fail("INVALID_METADATA", "Workflow extension validateSettings must be a function");
2229
2303
  if (source !== void 0 && (typeof source !== "string" || !source.trim())) fail("INVALID_METADATA", "Workflow extension source must be a non-empty module URL");
2230
2304
  if (dependencyValues !== void 0 && (!Array.isArray(dependencyValues) || dependencyValues.some((dependency) => typeof dependency !== "string" || dependency.trim() !== dependency || !PACKAGE_NAME.test(dependency)) || new Set(dependencyValues).size !== dependencyValues.length)) fail("INVALID_METADATA", "Workflow extension dependencies must be unique, non-empty package names");
2231
- const roleDirectoryValues = extension2.roleDirectories === void 0 ? [] : extension2.roleDirectories;
2232
- if (!Array.isArray(roleDirectoryValues)) fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
2233
- const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
2234
- if (!object(functions) || !object(modelAliases) || !object(agentSetupHooks) || !object(agentAttemptActions) || Object.keys(functions).length === 0 && Object.keys(modelAliases).length === 0 && Object.keys(agentSetupHooks).length === 0 && Object.keys(agentAttemptActions).length === 0 && roleDirectories.length === 0 && validateSettings === void 0) fail("INVALID_METADATA", "Workflow extensions require functions, model aliases, agent setup hooks, agent attempt actions, role directories, or a settings validator");
2305
+ if (!object(functions) || !object(modelAliases) || !object(agentPreparationHooks) || !object(agentSetupHooks) || !object(agentAttemptActions) || Object.keys(functions).length === 0 && Object.keys(modelAliases).length === 0 && Object.keys(agentPreparationHooks).length === 0 && Object.keys(agentSetupHooks).length === 0 && Object.keys(agentAttemptActions).length === 0 && validateSettings === void 0) fail("INVALID_METADATA", "Workflow extensions require functions, model aliases, agent preparation hooks, agent setup hooks, agent attempt actions, or a settings validator");
2235
2306
  const names = Object.keys(functions);
2236
2307
  if (new Set(names).size !== names.length) fail("GLOBAL_COLLISION", "Global name collision inside extension");
2237
2308
  for (const name of names) {
@@ -2250,6 +2321,11 @@ var WorkflowRegistry = class {
2250
2321
  if (!object(alias) || Object.keys(alias).some((key) => key !== "resolve") || typeof alias.resolve !== "function") fail("INVALID_METADATA", `Invalid model alias resolver: ${name}`);
2251
2322
  if (this.#modelAliases.has(name)) fail("DUPLICATE_NAME", `Model alias already registered: ${name}`);
2252
2323
  }
2324
+ for (const [name, hook] of Object.entries(agentPreparationHooks)) {
2325
+ if (!IDENTIFIER.test(name) || !object(hook) || Object.keys(hook).some((key) => !["priority", "optionsSchema", "prepare"].includes(key)) || typeof hook.prepare !== "function" || hook.priority !== void 0 && (typeof hook.priority !== "number" || !Number.isFinite(hook.priority))) fail("INVALID_METADATA", `Invalid agent preparation hook: ${name}`);
2326
+ if (this.#preparationHooks.has(name)) fail("DUPLICATE_NAME", `Agent preparation hook already registered: ${name}`);
2327
+ if (hook.optionsSchema !== void 0) validateSchema(hook.optionsSchema, `${name} options schema`);
2328
+ }
2253
2329
  for (const [name, hook] of Object.entries(agentSetupHooks)) {
2254
2330
  if (!IDENTIFIER.test(name) || !object(hook) || Object.keys(hook).some((key) => !["priority", "setup"].includes(key)) || typeof hook.setup !== "function" || hook.priority !== void 0 && (typeof hook.priority !== "number" || !Number.isFinite(hook.priority))) fail("INVALID_METADATA", `Invalid agent setup hook: ${name}`);
2255
2331
  if (this.#hooks.has(name)) fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
@@ -2259,16 +2335,20 @@ var WorkflowRegistry = class {
2259
2335
  if (action.visibleStandalone !== void 0 !== (action.runStandalone !== void 0)) fail("INVALID_METADATA", `Standalone agent attempt actions require visibleStandalone and runStandalone: ${name}`);
2260
2336
  if (this.#agentAttemptActions.has(name)) fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
2261
2337
  }
2262
- const stored = deepFreeze({ ...extension2, functions, modelAliases, agentSetupHooks, agentAttemptActions, ...roleDirectories.length ? { roleDirectories } : {} });
2338
+ const stored = deepFreeze({ ...extension2, functions, modelAliases, agentPreparationHooks, agentSetupHooks, agentAttemptActions });
2263
2339
  if (source !== void 0) for (const name of names) this.#functionSources.set(name, Object.freeze({ module: source, export: "default", dependencies: Object.freeze([...dependencies]) }));
2264
2340
  this.#extensions.add(stored);
2265
- for (const directory of roleDirectories) if (!this.#roleDirectories.has(directory)) this.#roleDirectories.set(directory, deepFreeze({ path: directory, extension: { version: extension2.version, headline: extension2.headline }, ...isBuiltinRoleDirectory(directory) ? { builtin: true } : {} }));
2266
2341
  for (const name of names) this.#globals.set(name, name);
2267
2342
  for (const [name, alias] of Object.entries(modelAliases)) this.#modelAliases.set(name, { name, version: extension2.version, headline: extension2.headline, resolve: alias.resolve });
2343
+ for (const [name, hook] of Object.entries(agentPreparationHooks)) this.#preparationHooks.set(name, { name, priority: hook.priority ?? 10, prepare: hook.prepare, ...hook.optionsSchema === void 0 ? {} : { optionsSchema: structuredClone(hook.optionsSchema) }, ...source === void 0 ? {} : { source } });
2268
2344
  for (const [name, hook] of Object.entries(agentSetupHooks)) this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
2269
2345
  for (const [name, action] of Object.entries(agentAttemptActions)) this.#agentAttemptActions.set(name, action);
2270
2346
  if (validateSettings !== void 0) this.#settingsValidators.add({ headline: extension2.headline, validate: validateSettings });
2271
2347
  }
2348
+ /** Every registration's headline and declared module provenance, for hosts that must prove which extension made it. */
2349
+ registrations() {
2350
+ return [...this.#extensions].map(({ headline, source }) => Object.freeze({ headline, ...source === void 0 ? {} : { source } }));
2351
+ }
2272
2352
  function(name) {
2273
2353
  if (!IDENTIFIER.test(name)) fail("MISSING_WORKFLOW", `Registered functions require an unqualified name: ${name}`);
2274
2354
  const fn = [...this.#extensions].find((extension2) => extension2.functions?.[name])?.functions?.[name];
@@ -2291,7 +2371,7 @@ var WorkflowRegistry = class {
2291
2371
  let source = "global settings";
2292
2372
  try {
2293
2373
  const resolved = context ? resolveWorkflowSettings(context.cwd, context.projectTrusted, context.globalSettingsPath) : void 0;
2294
- aliases = resolved?.effective.modelAliases ?? loadSettings().modelAliases;
2374
+ aliases = resolved?.effective.modelAliases ?? (context ? loadSettings().modelAliases : resolveWorkflowSettings(process.cwd(), false).effective.modelAliases);
2295
2375
  if (resolved) {
2296
2376
  source = resolved.projectTrusted && [resolved.sources.modelAliases, resolved.sources.skills, resolved.sources.extensions, resolved.sources.tools].some((path) => path === resolved.projectSettingsPath) ? "trusted project settings" : "global settings";
2297
2377
  settings = { concurrency: resolved.effective.concurrency, backgroundWidget: resolved.effective.backgroundWidget ?? true, modelAliases: resolved.effective.modelAliases ?? {}, skills: resolved.effective.skills ?? [], extensions: Array.isArray(resolved.effective.extensions) ? resolved.effective.extensions : [], tools: resolved.effective.tools ?? [], ...resolved.effective.extensionSettings === void 0 ? {} : { extensionSettings: resolved.effective.extensionSettings }, ...resolved.effective.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: resolved.effective.worktreePostCreateCommand }, globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: resolved.projectTrusted, sources: resolved.sources };
@@ -2350,7 +2430,7 @@ var WorkflowRegistry = class {
2350
2430
  try {
2351
2431
  validate(frozenSettings, Object.freeze({ ...context }));
2352
2432
  } catch (error) {
2353
- const location = context.source === "role" && context.role ? `role ${context.role}` : context.source;
2433
+ const location = context.source;
2354
2434
  fail("INVALID_SETTINGS", `Invalid extension settings (${headline}; ${location}): ${errorText(error)}`);
2355
2435
  }
2356
2436
  }
@@ -2369,18 +2449,15 @@ var WorkflowRegistry = class {
2369
2449
  journal.put(path, stored);
2370
2450
  return structuredClone(stored);
2371
2451
  }
2452
+ agentPreparationHooks() {
2453
+ return [...this.#preparationHooks.values()].sort(byPriorityThenName);
2454
+ }
2372
2455
  agentSetupHooks() {
2373
2456
  return [...this.#hooks.values()].sort(byPriorityThenName);
2374
2457
  }
2375
2458
  agentAttemptActions() {
2376
2459
  return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries()));
2377
2460
  }
2378
- roleDirectories() {
2379
- return [...this.#roleDirectories.keys()];
2380
- }
2381
- roleDirectoryRegistrations() {
2382
- return [...this.#roleDirectories.values()];
2383
- }
2384
2461
  modelAliases() {
2385
2462
  return [...this.#modelAliases.values()].sort((left, right) => left.name.localeCompare(right.name));
2386
2463
  }
@@ -2436,10 +2513,10 @@ function createWorkflowRegistryApi(registry) {
2436
2513
  },
2437
2514
  modelAliases: () => registry.modelAliases(),
2438
2515
  resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
2439
- roleDirectories: () => registry.roleDirectories(),
2440
- roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
2516
+ agentPreparationHooks: () => registry.agentPreparationHooks(),
2441
2517
  agentSetupHooks: () => registry.agentSetupHooks(),
2442
- agentAttemptActions: () => registry.agentAttemptActions()
2518
+ agentAttemptActions: () => registry.agentAttemptActions(),
2519
+ registrations: () => registry.registrations()
2443
2520
  };
2444
2521
  }
2445
2522
  function workflowRegistryHost() {
@@ -2456,310 +2533,11 @@ function loadingRegistry() {
2456
2533
  return workflowRegistryHost().api;
2457
2534
  }
2458
2535
  beginWorkflowExtensionLoading();
2459
- function registeredWorkflowRoleDirectoryRegistrations() {
2460
- const registrations = loadingRegistry().roleDirectoryRegistrations;
2461
- return typeof registrations === "function" ? registrations() : [];
2462
- }
2463
-
2464
- // src/roles.ts
2465
- var ROLE_DIRECTORY2 = "pi-extensible-workflows";
2466
- var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
2467
- function parseRoleMarkdown(content, strict = false, rolePath) {
2468
- if (!strict) {
2469
- if (!content.startsWith("---\n")) return { prompt: content };
2470
- const end = content.indexOf("\n---", 4);
2471
- if (end < 0) return { prompt: content };
2472
- const meta = {};
2473
- for (const line of content.slice(4, end).split("\n")) {
2474
- const match = /^(model|tools|skills|extensions|description|overrideSystemPrompt|override_system_prompt|is_system_prompt|contextFiles|extensionSettings|disabledAgentResources|thinking)\s*:\s*(.+)$/.exec(line.trim());
2475
- if (match?.[1] === "disabledAgentResources") fail("INVALID_METADATA", "disabledAgentResources is no longer supported; use skills, extensions, and tools selectors");
2476
- if (match?.[1] === "thinking") fail("INVALID_METADATA", "Role thinking is not supported; put it on model as provider/model:thinking");
2477
- if (match?.[1] && match[2]) meta[match[1]] = match[2].trim();
2478
- }
2479
- const unquote = (v) => v.replace(/^['"]|['"]$/g, "");
2480
- const parseList = (value) => value === void 0 ? void 0 : value.replace(/^\[|\]$/g, "").split(",").map((entry) => unquote(entry.trim())).filter(Boolean);
2481
- const tools2 = parseList(meta.tools);
2482
- const skills2 = parseList(meta.skills);
2483
- const extensions2 = parseList(meta.extensions);
2484
- let extensionSettings2;
2485
- if (meta.extensionSettings !== void 0) {
2486
- try {
2487
- extensionSettings2 = JSON.parse(meta.extensionSettings);
2488
- } catch (error) {
2489
- fail("INVALID_METADATA", `Invalid role extensionSettings: ${errorText(error)}`);
2490
- }
2491
- }
2492
- const rolePathValue2 = rolePath ?? "<role>";
2493
- const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
2494
- const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
2495
- if (meta.model) {
2496
- const model2 = unquote(meta.model);
2497
- assertModelThinking(model2, "Role model");
2498
- definition2.model = model2;
2499
- }
2500
- if (meta.description) definition2.description = unquote(meta.description);
2501
- if (tools2) definition2.tools = tools2;
2502
- if (skills2) definition2.skills = skills2;
2503
- if (extensions2) definition2.extensions = extensions2;
2504
- if (normalizedExtensionSettings2 !== void 0) definition2.extensionSettings = normalizedExtensionSettings2;
2505
- const overrideSystemPrompt2 = meta.overrideSystemPrompt ?? meta.override_system_prompt ?? meta.is_system_prompt;
2506
- const contextFiles2 = meta.contextFiles ? meta.contextFiles.replace(/^\[|\]$/g, "").split(",").map((scope) => unquote(scope.trim())).filter(Boolean) : void 0;
2507
- const normalizedContextFiles2 = validateContextFileScopes(contextFiles2, "role");
2508
- if (overrideSystemPrompt2) definition2.overrideSystemPrompt = overrideSystemPrompt2 === "true";
2509
- if (normalizedContextFiles2) definition2.contextFiles = normalizedContextFiles2;
2510
- return definition2;
2511
- }
2512
- const normalized = content.replace(/\r\n?/g, "\n");
2513
- if (normalized.startsWith("---\n") && normalized.indexOf("\n---", 3) < 0) fail("INVALID_METADATA", "Role frontmatter is missing its closing delimiter");
2514
- let parsed;
2515
- try {
2516
- parsed = parseFrontmatter(content);
2517
- } catch (error) {
2518
- fail("INVALID_METADATA", `Invalid role frontmatter: ${errorText(error)}`);
2519
- }
2520
- if (!object(parsed.frontmatter)) fail("INVALID_METADATA", "Role frontmatter must be an object");
2521
- const { model, tools, skills, extensions, extensionSettings, description, contextFiles } = parsed.frontmatter;
2522
- if (Object.prototype.hasOwnProperty.call(parsed.frontmatter, "disabledAgentResources")) fail("INVALID_METADATA", "disabledAgentResources is no longer supported; use skills, extensions, and tools selectors");
2523
- if (Object.prototype.hasOwnProperty.call(parsed.frontmatter, "thinking")) fail("INVALID_METADATA", "Role thinking is not supported; put it on model as provider/model:thinking");
2524
- const overrideSystemPrompt = parsed.frontmatter.overrideSystemPrompt ?? parsed.frontmatter.override_system_prompt ?? parsed.frontmatter.is_system_prompt;
2525
- if (model !== void 0 && (typeof model !== "string" || model.trim() === "")) fail("INVALID_METADATA", "Role model must be a non-empty string");
2526
- if (description !== void 0 && (typeof description !== "string" || description.trim() === "" || description.length > 1024 || /[\r\n]/.test(description))) fail("INVALID_METADATA", "Role description must be a non-empty single-line string of at most 1024 characters");
2527
- if (overrideSystemPrompt !== void 0 && typeof overrideSystemPrompt !== "boolean") fail("INVALID_METADATA", "Role overrideSystemPrompt must be a boolean");
2528
- const normalizedContextFiles = validateContextFileScopes(contextFiles, rolePath ?? "<role>");
2529
- const rolePathValue = rolePath ?? "<role>";
2530
- const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
2531
- const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
2532
- const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
2533
- const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
2534
- const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
2535
- const normalizedModel = typeof model === "string" ? model.trim() : void 0;
2536
- if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
2537
- const definition = { prompt: parsed.body };
2538
- if (normalizedDescription !== void 0) definition.description = normalizedDescription;
2539
- if (normalizedModel !== void 0) definition.model = normalizedModel;
2540
- if (normalizedTools !== void 0) definition.tools = normalizedTools;
2541
- if (normalizedSkills !== void 0) definition.skills = normalizedSkills;
2542
- if (normalizedExtensions !== void 0) definition.extensions = normalizedExtensions;
2543
- if (normalizedExtensionSettings !== void 0) definition.extensionSettings = normalizedExtensionSettings;
2544
- if (typeof overrideSystemPrompt === "boolean") definition.overrideSystemPrompt = overrideSystemPrompt;
2545
- if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
2546
- return definition;
2547
- }
2548
- function workflowRoleDirectories(agentDir = getAgentDir2()) {
2549
- return [join5(agentDir, ROLE_DIRECTORY2, "roles")];
2550
- }
2551
- function projectRoleDirectories(root) {
2552
- return [join5(root, ROLE_DIRECTORY2, "roles")];
2553
- }
2554
- function roleDirectorySources(dirs) {
2555
- const seen = /* @__PURE__ */ new Set();
2556
- return dirs.flatMap((value) => {
2557
- const source = typeof value === "string" ? { path: value } : { path: value.path, extension: value.extension, ...value.builtin === true ? { builtin: true } : {} };
2558
- if (seen.has(source.path)) return [];
2559
- seen.add(source.path);
2560
- return [source];
2561
- });
2562
- }
2563
- function roleDirectoryLabel(source, extension2 = true) {
2564
- return source.extension ? `Extension "${source.extension.headline}" (${source.extension.version})` : extension2 ? "Registered workflow extension" : "Standard workflow";
2565
- }
2566
- function isRoleFile(dir, entry) {
2567
- if (extname(entry.name) !== ".md") return false;
2568
- if (entry.isFile()) return true;
2569
- if (!entry.isSymbolicLink()) return false;
2570
- try {
2571
- return statSync(join5(dir, entry.name)).isFile();
2572
- } catch (error) {
2573
- if (isNodeError(error, "ENOENT")) return false;
2574
- throw error;
2575
- }
2576
- }
2577
- function scanRoleFiles(dirs, extension2) {
2578
- const files = [];
2579
- for (const source of roleDirectorySources(dirs)) {
2580
- let entries;
2581
- try {
2582
- entries = readdirSync(source.path, { withFileTypes: true });
2583
- } catch (error) {
2584
- if (!extension2 && isNodeError(error, "ENOENT")) continue;
2585
- fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension2)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
2586
- }
2587
- for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join5(source.path, entry.name), source });
2588
- }
2589
- return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
2590
- }
2591
- function roleEntries(dirs, extension2) {
2592
- const files = scanRoleFiles(dirs, extension2);
2593
- const starterFiles = files.filter(({ source }) => source.builtin === true);
2594
- const regularFiles = files.filter(({ source }) => source.builtin !== true);
2595
- if (extension2) {
2596
- const byName = /* @__PURE__ */ new Map();
2597
- for (const file of regularFiles) byName.set(file.name, [...byName.get(file.name) ?? [], file]);
2598
- for (const [name, matches] of byName) if (matches.length > 1) fail("INVALID_METADATA", `Duplicate extension role "${name}": ${matches.map(({ path, source }) => `${roleDirectoryLabel(source)} role directory "${source.path}" (${path})`).join("; ")}`);
2599
- }
2600
- const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
2601
- try {
2602
- const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
2603
- roleDefinitionPaths.set(definition, path);
2604
- return [name, { name, path, source, definition }];
2605
- } catch (error) {
2606
- if (extension2) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
2607
- throw error;
2608
- }
2609
- }));
2610
- return extension2 ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
2611
- }
2612
- function discoveryInput(input) {
2613
- return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir2(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
2614
- }
2615
- function discoveredRoleEntries(input) {
2616
- const resolved = discoveryInput(input);
2617
- return {
2618
- ...roleEntries(resolved.extensionRoleDirectories, true),
2619
- ...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
2620
- ...resolved.projectTrusted ? roleEntries(projectRoleDirectories(join5(resolved.cwd, ".pi")), false) : {}
2621
- };
2622
- }
2623
- function discoverRoles(input) {
2624
- return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
2625
- }
2626
- function loadAgentDefinitions(cwd, agentDir = getAgentDir2(), projectTrusted = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
2627
- return discoverRoles({ cwd, agentDir, projectTrusted, extensionRoleDirectories });
2628
- }
2629
- function canonicalExtensionSelector(selector, base = process.cwd()) {
2630
- const negated = selector.startsWith("!");
2631
- const body = negated ? selector.slice(1) : selector;
2632
- if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
2633
- const resolved = resolve4(base, body);
2634
- if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
2635
- return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
2636
- }
2637
- function selectorsFromSettings2(settings) {
2638
- return { ...settings.skills === void 0 ? {} : { skills: settings.skills }, ...settings.extensions === void 0 ? {} : { extensions: settings.extensions }, ...settings.tools === void 0 ? {} : { tools: settings.tools } };
2639
- }
2640
- function rootToolNames(value) {
2641
- return value === void 0 ? void 0 : value instanceof Set ? [...value] : [...value];
2642
- }
2643
- function roleDefinitionFor(name, options) {
2644
- if (name === void 0) return {};
2645
- if (options.definition !== void 0) {
2646
- const path = roleDefinitionPaths.get(options.definition);
2647
- return { definition: options.definition, ...path === void 0 ? {} : { path } };
2648
- }
2649
- if (options.definitions?.[name] !== void 0) {
2650
- const definition = options.definitions[name];
2651
- const path = roleDefinitionPaths.get(definition);
2652
- return { definition, ...path === void 0 ? {} : { path } };
2653
- }
2654
- const entries = discoveredRoleEntries(options);
2655
- const entry = entries[name];
2656
- if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
2657
- return entry;
2658
- }
2659
- function resolveRole(name, options) {
2660
- const { definition, path } = roleDefinitionFor(name, options);
2661
- const settings = options.selectorSources === void 0 ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir2())) : void 0;
2662
- const baseSources = options.selectorSources ?? { global: selectorsFromSettings2(settings?.global ?? {}), project: selectorsFromSettings2(settings?.project ?? {}) };
2663
- const canonicalSources = (sources) => ({
2664
- ...sources,
2665
- global: { ...sources.global, ...sources.global.extensions === void 0 ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
2666
- project: { ...sources.project, ...sources.project.extensions === void 0 ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
2667
- ...sources.role === void 0 ? {} : { role: { ...sources.role, ...sources.role.extensions === void 0 ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } },
2668
- ...sources.call === void 0 ? {} : { call: { ...sources.call, ...sources.call.extensions === void 0 ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } }
2669
- });
2670
- 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;
2671
- 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;
2672
- const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...roleLayer === void 0 ? {} : { role: roleLayer }, ...callLayer === void 0 ? {} : { call: callLayer } });
2673
- const callSelectors = selectorSources.call;
2674
- const roleSelectors = selectorSources.role;
2675
- const selectorLayers = {
2676
- skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
2677
- extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
2678
- tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools]
2679
- };
2680
- const resources = options.resources ?? {};
2681
- const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
2682
- const tools = options.effectiveTools ?? (candidateTools === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.tools, candidateTools));
2683
- const rootTools = rootToolNames(options.rootTools);
2684
- if (rootTools !== void 0) {
2685
- const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
2686
- if (outsideEffectiveTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
2687
- const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
2688
- if (outsideCallTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
2689
- const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
2690
- if (outsideRootTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
2691
- }
2692
- const selectedSkills = resources.skills === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.skills, resources.skills);
2693
- const extensionResources = resources.extensions?.map((extension2) => canonicalPath(extension2));
2694
- const selectedExtensions = extensionResources === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
2695
- const unmatchedSkills = resources.skills === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
2696
- const unmatchedExtensions = extensionResources === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
2697
- const unmatchedTools = candidateTools === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
2698
- const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
2699
- const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
2700
- const blockedAlias = requestedModel?.split(":", 1)[0];
2701
- if (requestedModel !== void 0 && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
2702
- const target = options.blockedAliasTargets?.[blockedAlias];
2703
- fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
2704
- }
2705
- let model = options.modelOverride;
2706
- if (model === void 0 && requestedModel !== void 0) {
2707
- assertModelThinking(requestedModel);
2708
- model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
2709
- }
2710
- if (model === void 0) model = options.rootModel;
2711
- const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === void 0 ? void 0 : /* @__PURE__ */ new Set([modelCapability(options.rootModel)]));
2712
- 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})` : ""}`);
2713
- const prompt = definition?.prompt ?? "";
2714
- const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
2715
- const contextFiles = options.contextFiles ?? definition?.contextFiles;
2716
- 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 } };
2717
- }
2718
-
2719
- // src/validation.ts
2720
- var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
2721
- function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
2722
- switch (key) {
2723
- case "label":
2724
- if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent label must be a non-empty string");
2725
- break;
2726
- case "model":
2727
- if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent model must be a non-empty string");
2728
- assertModelThinking(value, "agent model");
2729
- if (aliases !== void 0) resolveModelReference(value, aliases, knownModels, settingsPath);
2730
- break;
2731
- case "tools":
2732
- case "skills":
2733
- case "extensions":
2734
- validateSelectorList(value, "agent options", key, "INVALID_METADATA", key !== "extensions");
2735
- break;
2736
- case "contextFiles":
2737
- validateContextFileScopes(value, "agent options");
2738
- break;
2739
- case "role":
2740
- if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent role must be a non-empty string");
2741
- break;
2742
- case "outputSchema":
2743
- validateSchema(value, "agent outputSchema");
2744
- break;
2745
- case "retries":
2746
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) fail("INVALID_METADATA", "agent retries must be a non-negative integer");
2747
- break;
2748
- case "timeoutMs":
2749
- if (value !== null && !positiveInteger(value)) fail("INVALID_METADATA", "agent timeoutMs must be null or a positive integer");
2750
- break;
2751
- }
2752
- }
2753
- function validateAgentOptions(value) {
2754
- if (!object(value) || !jsonValue(value)) fail("INVALID_METADATA", "agent options must be a JSON object");
2755
- if (Object.prototype.hasOwnProperty.call(value, "thinking")) fail("INVALID_METADATA", "agent thinking is not supported; use model provider/model:thinking");
2756
- for (const [key, option] of Object.entries(value)) if (AGENT_OPTION_KEYS.has(key)) validateAgentOption(key, option);
2757
- return value;
2758
- }
2759
2536
 
2760
2537
  // src/execution.ts
2761
2538
  var RPC_LIMIT_BYTES = 10 * 1024 * 1024;
2762
2539
  var HEARTBEAT_INTERVAL_MS = 1e3;
2540
+ var FUNCTION_HANDLE_PREFIX = "function-handle";
2763
2541
  var WORK_RESULT_BRAND = "__workResult";
2764
2542
  var childSource = String.raw`
2765
2543
  "use strict";
@@ -2870,15 +2648,13 @@ const internalAgent = (...values) => {
2870
2648
  void result.then(() => agentInflight.delete(occurrenceKey), () => agentInflight.delete(occurrenceKey));
2871
2649
  return guardedAgentResult(result);
2872
2650
  };
2873
- const AGENT_HANDLE_KEYS = ["name", "role", "model", "tools", "skills", "extensions", "contextFiles", "label"];
2874
2651
  const agentHandleNames = new Set();
2875
2652
  const agentHandleInflight = new Set();
2876
2653
  const internalAgentCreate = (...values) => {
2877
2654
  if (values.length !== 1 || !values[0] || typeof values[0] !== "object" || Array.isArray(values[0])) throw workError("INVALID_METADATA", "agent.create requires one options object");
2878
2655
  const options = values[0];
2879
2656
  const name = named(options.name, "agent.create");
2880
- const unsupported = Object.keys(options).find(key => !AGENT_HANDLE_KEYS.includes(key));
2881
- if (unsupported) throw workError("INVALID_METADATA", "agent.create option is unsupported: " + unsupported);
2657
+ if (name.startsWith("${FUNCTION_HANDLE_PREFIX}/")) throw workError("INVALID_METADATA", "Agent handle names may not start with ${FUNCTION_HANDLE_PREFIX}/");
2882
2658
  if (agentHandleNames.has(name)) throw workError("INVALID_METADATA", "Duplicate agent handle name: " + name);
2883
2659
  agentHandleNames.add(name);
2884
2660
  const config = Object.freeze(Object.fromEntries(Object.entries(options).filter(([key]) => key !== "name")));
@@ -2993,6 +2769,30 @@ const functions = Object.freeze(Object.fromEntries(Object.entries(config.functio
2993
2769
  Object.defineProperty(result, "toJSON", { value() { throw workError("INVALID_METADATA", "Workflow function result is a Promise; await it before serialization"); } });
2994
2770
  return result;
2995
2771
  }])));
2772
+ // tools.<id>(args) runs a Pi session tool through the host. Any identifier resolves, so a resumed run
2773
+ // replays journaled calls even when the tool is gone; the host rejects identifiers it cannot call.
2774
+ // A call is the n-th call with the same arguments in its scope, so replay does not depend on the
2775
+ // order in which concurrent calls start.
2776
+ const NOT_TOOLS = new Set(["then", "toJSON", "toString", "valueOf", "constructor"]);
2777
+ 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;
2778
+ const toolOccurrences = new Map();
2779
+ const toolCall = id => (...values) => {
2780
+ if (worktreeOwners.getStore()) throw workError("INVALID_METADATA", "tools." + id + " is unavailable inside withWorktree: session tools resolve paths against the session working directory");
2781
+ 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");
2782
+ const args = values[0] === undefined ? {} : values[0];
2783
+ const structuralPath = inheritedAgentPath.getStore() || [];
2784
+ const key = JSON.stringify([structuralPath, id, sortedJson(args)]);
2785
+ const occurrence = (toolOccurrences.get(key) || 0) + 1;
2786
+ toolOccurrences.set(key, occurrence);
2787
+ return guardedResult(rpc("tool", [id, args, { structuralPath: [...structuralPath], occurrence }]), "tool");
2788
+ };
2789
+ const tools = new Proxy(Object.create(null), {
2790
+ get: (_target, key) => typeof key === "string" && !NOT_TOOLS.has(key) ? toolCall(key) : undefined,
2791
+ // Probing would branch on the session that resumed the run; the launch already checks named tools.
2792
+ has: () => { throw workError("INVALID_METADATA", "Workflow scripts cannot probe tools with 'in'; call tools.<name>(...) directly, and the launch checks that the tool exists"); },
2793
+ set: () => false,
2794
+ defineProperty: () => false,
2795
+ });
2996
2796
  const recordEntries = (value, kind) => {
2997
2797
  if (!value || typeof value !== "object" || Array.isArray(value)) throw workError("INVALID_METADATA", kind + " must be a record");
2998
2798
  return Object.entries(value);
@@ -3053,7 +2853,7 @@ const pipeline = async (operationName, items, stages) => {
3053
2853
  return Object.fromEntries(results.map(result => [result.name, result.value]));
3054
2854
  };
3055
2855
  const safeMath = Object.fromEntries(Object.getOwnPropertyNames(Math).filter(name => name !== "random").map(name => [name, Math[name]]));
3056
- const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
2856
+ const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, tools, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
3057
2857
  for (const [name, fn] of Object.entries(functions)) Object.defineProperty(sandbox, name, { value: fn, writable: false, configurable: false });
3058
2858
  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;
3059
2859
  const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
@@ -3063,20 +2863,28 @@ Promise.resolve().then(() => new vm.Script("(async(__pi_extensible_workflows_age
3063
2863
  .catch(error => send({ type: "error", error: workerError(error) }))
3064
2864
  .finally(() => clearInterval(heartbeat));
3065
2865
  `;
2866
+ function agentHandleTurnPath(handle, turn) {
2867
+ return structuralPath("agent", "handle", handle, `turn:${String(turn)}`);
2868
+ }
2869
+ function agentIdentityPath(identity) {
2870
+ if (identity.handle !== void 0 && identity.turn !== void 0) return agentHandleTurnPath(identity.handle, identity.turn);
2871
+ return structuralPath("agent", ...identity.structuralPath, `callsite:${identity.callSite}`, `occurrence:${String(identity.occurrence)}`);
2872
+ }
3066
2873
 
3067
2874
  // src/host.ts
3068
2875
  import { Type as Type3 } from "@earendil-works/pi-ai";
3069
- import { copyToClipboard, getAgentDir as getAgentDir5, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
2876
+ import { copyToClipboard, getAgentDir as getAgentDir4, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
3070
2877
 
3071
2878
  // src/agent-execution.ts
3072
- import { existsSync, realpathSync as realpathSync2, readFileSync as readFileSync4 } from "node:fs";
2879
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
3073
2880
  import { copyFile } from "node:fs/promises";
3074
2881
  import { homedir as homedir4 } from "node:os";
3075
- import { basename as basename4, dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
2882
+ import { basename as basename2, dirname as dirname3, join as join5, resolve as resolve4 } from "node:path";
3076
2883
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3077
2884
  import { Type } from "@earendil-works/pi-ai";
2885
+ import { Value as Value2 } from "typebox/value";
3078
2886
  import { Compile } from "typebox/compile";
3079
- import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir3, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
2887
+ import { createAgentSession, createCodemodeExtension, createMcpExtension, createToolSearchExtension, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir2, ModelRuntime, SessionManager, SettingsManager, loadSkills } from "@earendil-works/pi-coding-agent";
3080
2888
 
3081
2889
  // src/pi-runtime-adapter.ts
3082
2890
  var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
@@ -3233,14 +3041,14 @@ function createRuntimeHandoffAdapter(handoff) {
3233
3041
  function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3234
3042
  const neutralHandoff = createRuntimeHandoffAdapter(handoff);
3235
3043
  const toolCalls = /* @__PURE__ */ new Map();
3236
- let activity2;
3044
+ let activity;
3237
3045
  let streamText = "";
3238
3046
  let lastEventAt;
3239
3047
  let lastReportedEventAt;
3240
3048
  let disposed = false;
3241
3049
  let toolCallsView = [];
3242
3050
  const progressAt = (persist, stateOverride, toolCallsValue3, activityValue2, eventAtValue) => ({ usage: runtimeUsage(session), toolCalls: toolCallsValue3, state: stateOverride ?? stateFromSession(session), ...activityValue2 === void 0 ? {} : { activity: activityValue2 }, ...eventAtValue === void 0 ? {} : { lastEventAt: eventAtValue }, persist });
3243
- const snapshot = (persist) => progressAt(persist, void 0, [...toolCalls.values()], activity2, lastEventAt);
3051
+ const snapshot = (persist) => progressAt(persist, void 0, [...toolCalls.values()], activity, lastEventAt);
3244
3052
  return {
3245
3053
  handoff: neutralHandoff,
3246
3054
  observe(event) {
@@ -3251,7 +3059,7 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3251
3059
  const eventState = event.state === void 0 ? void 0 : runtimeState(event.state);
3252
3060
  let report = false;
3253
3061
  let persist = false;
3254
- const previousActivity = activity2;
3062
+ const previousActivity = activity;
3255
3063
  let eventToolCalls = toolCallsView;
3256
3064
  if (event.type === "state_changed") {
3257
3065
  report = true;
@@ -3259,7 +3067,7 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3259
3067
  }
3260
3068
  if (event.type === "message_start" && event.message?.role === "assistant") {
3261
3069
  streamText = "";
3262
- activity2 = { kind: "text", text: "" };
3070
+ activity = { kind: "text", text: "" };
3263
3071
  report = true;
3264
3072
  }
3265
3073
  if (event.type === "message_update") {
@@ -3267,14 +3075,14 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3267
3075
  if (updateType === "thinking_start" || updateType === "text_start") streamText = "";
3268
3076
  const delta = event.assistantMessageEvent?.delta;
3269
3077
  if ((updateType === "thinking_delta" || updateType === "text_delta") && delta) streamText = `${streamText}${delta}`.replace(/\s+/g, " ").slice(-MAX_ACTIVITY_TEXT_CHARS);
3270
- if (updateType && ["thinking_start", "thinking_delta", "thinking_end"].includes(updateType)) activity2 = { kind: "reasoning", text: streamText };
3271
- else if (updateType && ["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(updateType)) activity2 = { kind: "text", text: streamText };
3272
- report = previousActivity?.kind !== activity2?.kind;
3078
+ if (updateType && ["thinking_start", "thinking_delta", "thinking_end"].includes(updateType)) activity = { kind: "reasoning", text: streamText };
3079
+ else if (updateType && ["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(updateType)) activity = { kind: "text", text: streamText };
3080
+ report = previousActivity?.kind !== activity?.kind;
3273
3081
  }
3274
3082
  if (event.type === "message_end") {
3275
3083
  streamText = "";
3276
- activity2 = void 0;
3277
- report = !sameActivity(previousActivity, activity2);
3084
+ activity = void 0;
3085
+ report = !sameActivity(previousActivity, activity);
3278
3086
  if (event.message?.role === "assistant") {
3279
3087
  persist = true;
3280
3088
  report = true;
@@ -3283,24 +3091,24 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3283
3091
  if (event.type === "tool_execution_start" && event.toolCallId && event.toolName) {
3284
3092
  toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: "running" });
3285
3093
  toolCallsView = [...toolCalls.values()];
3286
- activity2 = { kind: "tool", text: event.toolName };
3094
+ activity = { kind: "tool", text: event.toolName };
3287
3095
  report = true;
3288
3096
  }
3289
3097
  if (event.type === "tool_execution_update" && event.toolName) {
3290
- activity2 = { kind: "tool", text: event.toolName };
3291
- report = !sameActivity(previousActivity, activity2);
3098
+ activity = { kind: "tool", text: event.toolName };
3099
+ report = !sameActivity(previousActivity, activity);
3292
3100
  }
3293
3101
  let removeToolCallId;
3294
3102
  if (event.type === "tool_execution_end" && event.toolCallId && event.toolName) {
3295
3103
  toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: event.isError ? "failed" : "completed" });
3296
3104
  eventToolCalls = [...toolCalls.values()];
3297
- if (activity2?.kind === "tool" && activity2.text === event.toolName) activity2 = void 0;
3105
+ if (activity?.kind === "tool" && activity.text === event.toolName) activity = void 0;
3298
3106
  report = true;
3299
3107
  removeToolCallId = event.toolCallId;
3300
3108
  }
3301
3109
  if (removeToolCallId === void 0) eventToolCalls = toolCallsView;
3302
3110
  if (!report && (lastReportedEventAt === void 0 || eventAt - lastReportedEventAt >= 1e3)) report = true;
3303
- const eventActivity = activity2;
3111
+ const eventActivity = activity;
3304
3112
  let materialized;
3305
3113
  const result = { event, get progress() {
3306
3114
  return materialized ??= progressAt(persist, eventState, eventToolCalls, eventActivity, eventAt);
@@ -3317,7 +3125,7 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3317
3125
  disposed = true;
3318
3126
  toolCalls.clear();
3319
3127
  toolCallsView = [];
3320
- activity2 = void 0;
3128
+ activity = void 0;
3321
3129
  }
3322
3130
  };
3323
3131
  }
@@ -3351,8 +3159,8 @@ function createLiveSessionHandoff() {
3351
3159
  let request;
3352
3160
  const waitForBoundary = () => {
3353
3161
  if (!turnActive) return Promise.resolve();
3354
- boundary ??= new Promise((resolve6) => {
3355
- resolveBoundary = resolve6;
3162
+ boundary ??= new Promise((resolve5) => {
3163
+ resolveBoundary = resolve5;
3356
3164
  });
3357
3165
  return boundary;
3358
3166
  };
@@ -3384,11 +3192,11 @@ function createLiveSessionHandoff() {
3384
3192
  if (request) return request;
3385
3193
  if (isReleased()) return;
3386
3194
  state = "handoff-pending";
3387
- takeover = new Promise((resolve6) => {
3388
- resolveTakeover = resolve6;
3195
+ takeover = new Promise((resolve5) => {
3196
+ resolveTakeover = resolve5;
3389
3197
  });
3390
- resume = new Promise((resolve6) => {
3391
- resolveResume = resolve6;
3198
+ resume = new Promise((resolve5) => {
3199
+ resolveResume = resolve5;
3392
3200
  });
3393
3201
  request = (async () => {
3394
3202
  await waitForBoundary();
@@ -3587,9 +3395,9 @@ var PiRuntimeAgentRunner = class {
3587
3395
  let structuredResult;
3588
3396
  const resultTool = prepared.resultTool;
3589
3397
  if (!resultTool) throw new WorkflowError("INTERNAL_ERROR", "Workflow agent session is missing its workflow_result tool");
3590
- const preparedForSession = preparedForRuntimeRequest(prepared, request.customTools, request.signal, (value) => {
3398
+ const preparedForSession = Object.freeze({ ...preparedForRuntimeRequest(prepared, request.customTools, request.signal, (value) => {
3591
3399
  structuredResult = value;
3592
- }, resultTool);
3400
+ }, resultTool), ...request.turnControl ? { turnControl: request.turnControl } : {} });
3593
3401
  const callbacks = this.#options.callbacks;
3594
3402
  const handoff = this.#options.handoff;
3595
3403
  let adapter;
@@ -3822,6 +3630,7 @@ var PiRuntimeAgentRunner = class {
3822
3630
  throwIfProgressFailed();
3823
3631
  beginTurn(false);
3824
3632
  throwIfTurnPolicyFailed();
3633
+ if (request.turnControl && !session.turnControlDelivered) await activeSession.steer(request.turnControl);
3825
3634
  await promptAndRecover(request.task, true);
3826
3635
  completeTurn(hasResult() || !hasToolCall(lastAssistant), false, true);
3827
3636
  if (turnPolicyFailure) throw turnPolicyFailure;
@@ -3912,31 +3721,83 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
3912
3721
  assertModelThinking(value);
3913
3722
  return resolveModelReference(value, aliases, knownModels, settingsPath);
3914
3723
  }
3724
+ async function authorizedResources(root, cwd, options) {
3725
+ const agentDir = root.agentDir ?? getAgentDir2();
3726
+ const trusted = root.projectTrusted ?? false;
3727
+ const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
3728
+ settingsManager.setProjectTrusted(trusted);
3729
+ const resolved = await new DefaultPackageManager({ cwd, agentDir, settingsManager, builtinExtensions: ["codemode", "tool-search", "mcp"] }).resolve();
3730
+ const extensions = resolved.extensions.filter(({ enabled, metadata }) => enabled && (trusted || metadata.scope !== "project")).map(({ path }) => extensionIdentity(path));
3731
+ const skillPaths = resolved.skills.filter(({ enabled, metadata }) => enabled && (trusted || metadata.scope !== "project")).map(({ path }) => path);
3732
+ const skills = loadSkills({ cwd, agentDir, skillPaths: [...skillPaths, ...root.additionalSkillPaths ?? []], includeDefaults: false }).skills.map(({ name }) => name);
3733
+ const parent = options.capabilities;
3734
+ return { tools: [...root.tools].filter((tool) => options.effectiveTools === void 0 || options.effectiveTools.includes(tool)).filter((tool) => parent === void 0 || parent.tools.includes(tool)), skills: [...new Set(skills)].filter((name) => parent === void 0 || parent.skills.includes(name)), extensions: [...new Set(extensions)].filter((path) => parent === void 0 || parent.extensions.includes(path)) };
3735
+ }
3736
+ function validatePreparedConfiguration(root, configuration, capabilities, cwd, resumed) {
3737
+ const code = resumed ? "RESUME_INCOMPATIBLE" : "INVALID_METADATA";
3738
+ if (!jsonObject(configuration) || typeof configuration.projectTrusted !== "boolean" || typeof configuration.systemPromptAppend !== "string" || configuration.systemPrompt !== void 0 && typeof configuration.systemPrompt !== "string") throw new WorkflowError(code, "Invalid prepared agent configuration");
3739
+ if (configuration.projectTrusted && !root.projectTrusted) throw new WorkflowError("RESUME_INCOMPATIBLE", "Prepared agent configuration lost project trust");
3740
+ if (!object(configuration.model) || typeof configuration.model.provider !== "string" || typeof configuration.model.model !== "string") throw new WorkflowError(code, "Invalid prepared model");
3741
+ const model = resolveModelReference(`${configuration.model.provider}/${configuration.model.model}${configuration.model.thinking === void 0 ? "" : `:${configuration.model.thinking}`}`);
3742
+ if (root.availableModels !== void 0 && !root.availableModels.has(modelCapability(model))) throw new WorkflowError(resumed ? "RESUME_INCOMPATIBLE" : "UNKNOWN_MODEL", `Required model is unavailable: ${modelCapability(model)}`);
3743
+ const tools = validateSelectorList(configuration.tools, "configuration", "tools", "INVALID_METADATA", false);
3744
+ if (tools === void 0) throw new WorkflowError(code, "Prepared tools are missing");
3745
+ const missing = tools.find((tool) => !capabilities.tools.includes(tool));
3746
+ if (missing) throw new WorkflowError(resumed ? "RESUME_INCOMPATIBLE" : "UNKNOWN_TOOL", `Required tool is unavailable: ${missing}`);
3747
+ const excludeTools = configuration.excludeTools;
3748
+ try {
3749
+ validateAgentOptions({ excludeTools });
3750
+ } catch {
3751
+ throw new WorkflowError(code, "Invalid prepared excludeTools");
3752
+ }
3753
+ if (!Array.isArray(excludeTools) || tools.some((tool) => excludeTools.includes(tool))) throw new WorkflowError(code, "Prepared excludeTools contradict the prepared tools");
3754
+ validateSelectorList(configuration.skills, "configuration", "skills", "INVALID_METADATA", false);
3755
+ validateSelectorList(configuration.extensions, "configuration", "extensions", "INVALID_METADATA", false);
3756
+ if (resumed) for (const key of ["skills", "extensions"]) {
3757
+ const selectors = configuration[key];
3758
+ const selected = selectors.slice(selectors.lastIndexOf("!*") + 1);
3759
+ const missing2 = selected.find((name) => !capabilities[key].includes(name));
3760
+ if (missing2) throw new WorkflowError("RESUME_INCOMPATIBLE", `Required ${key} capability is unavailable: ${missing2}`);
3761
+ }
3762
+ validateContextFileScopes(configuration.contextFiles, "configuration");
3763
+ validateWorkflowExtensionSettings(configuration.settings, "configuration", "INVALID_METADATA");
3764
+ root.validateExtensionSettings?.(configuration.settings, { source: "effective", cwd, projectTrusted: root.projectTrusted ?? false });
3765
+ return deepFreeze(structuredClone(configuration));
3766
+ }
3915
3767
  function accounting(stats) {
3916
3768
  return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
3917
3769
  }
3918
- var extensionDirectory = dirname5(fileURLToPath3(import.meta.url));
3919
- var workflowPackageRoot = basename4(dirname5(extensionDirectory)) === "dist" ? resolve5(extensionDirectory, "../..") : resolve5(extensionDirectory, "..");
3920
- var WORKFLOW_HOST_ENTRIES = /* @__PURE__ */ new Set([
3921
- canonicalPath(resolve5(workflowPackageRoot, "src/index.ts")),
3922
- canonicalPath(resolve5(workflowPackageRoot, "dist/src/index.js")),
3923
- canonicalPath(resolve5(workflowPackageRoot, "starter/index.ts")),
3924
- canonicalPath(resolve5(workflowPackageRoot, "dist/starter/index.js")),
3925
- canonicalPath(resolve5(workflowPackageRoot, "subagents/index.ts")),
3926
- canonicalPath(resolve5(workflowPackageRoot, "dist/subagents/index.js"))
3927
- ]);
3928
- var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
3770
+ 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"];
3771
+ function isWorkflowHostEntry(path) {
3772
+ const entryPath = canonicalPath(path);
3773
+ let packageRoot2 = dirname3(entryPath);
3774
+ while (packageRoot2 !== dirname3(packageRoot2)) {
3775
+ const packageMetadataPath = join5(packageRoot2, "package.json");
3776
+ if (existsSync(packageMetadataPath)) {
3777
+ try {
3778
+ const metadata = JSON.parse(readFileSync2(packageMetadataPath, "utf8"));
3779
+ if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !isCorePackageName(metadata.name)) return false;
3780
+ return WORKFLOW_HOST_ENTRY_PATHS.some((entry) => canonicalPath(join5(packageRoot2, entry)) === entryPath);
3781
+ } catch {
3782
+ return false;
3783
+ }
3784
+ }
3785
+ packageRoot2 = dirname3(packageRoot2);
3786
+ }
3787
+ return false;
3788
+ }
3789
+ var WORKFLOW_DIRECTORY2 = "pi-extensible-workflows";
3929
3790
  function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
3930
- const projectPath = join6(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
3791
+ const projectPath = join5(cwd, ".pi", WORKFLOW_DIRECTORY2, "SYSTEM.md");
3931
3792
  if (projectTrusted && existsSync(projectPath)) return projectPath;
3932
- const globalPaths = [join6(homedir4(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join6(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
3793
+ const globalPaths = [join5(homedir4(), ".pi", WORKFLOW_DIRECTORY2, "SYSTEM.md"), join5(agentDir, WORKFLOW_DIRECTORY2, "SYSTEM.md")];
3933
3794
  return globalPaths.find((path) => existsSync(path));
3934
3795
  }
3935
3796
  function filterContextFiles(base, scopes, cwd, agentDir) {
3936
3797
  const globalPath = canonicalPath(agentDir);
3937
3798
  const cwdPath = canonicalPath(cwd);
3938
3799
  return base.filter(({ path }) => {
3939
- const resolvedPath = canonicalPath(dirname5(path));
3800
+ const resolvedPath = canonicalPath(dirname3(path));
3940
3801
  return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
3941
3802
  });
3942
3803
  }
@@ -3957,33 +3818,40 @@ function loadPiPackageDirectory() {
3957
3818
  }
3958
3819
  }).catch((error) => ({ error: `could not load @earendil-works/pi-coding-agent package metadata: ${errorText(error)}` }));
3959
3820
  }
3960
- var nativePromptTemplateModule;
3961
- async function loadNativePromptTemplateModule() {
3821
+ var nativeCoreModules = /* @__PURE__ */ new Map();
3822
+ async function loadNativeCoreModule(file, label2) {
3962
3823
  const packageDirectory = await loadPiPackageDirectory();
3963
- let promptTemplatePath;
3824
+ let modulePath;
3964
3825
  if (packageDirectory.directory) {
3965
- const candidate = resolve5(packageDirectory.directory, "dist/core/prompt-templates.js");
3966
- if (existsSync(candidate)) promptTemplatePath = candidate;
3826
+ const candidate = resolve4(packageDirectory.directory, "dist/core", file);
3827
+ if (existsSync(candidate)) modulePath = candidate;
3967
3828
  }
3968
- promptTemplatePath ??= resolve5(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
3829
+ modulePath ??= resolve4(dirname3(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core", file);
3969
3830
  const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
3970
- return nativePromptTemplateModule ??= import(promptTemplatePath).then((module) => module).catch((error) => {
3971
- throw new Error(`Could not load Pi prompt templates.${packageDirectoryError}`, { cause: error });
3972
- });
3831
+ let loaded = nativeCoreModules.get(file);
3832
+ if (!loaded) {
3833
+ loaded = import(modulePath).catch((error) => {
3834
+ throw new Error(`Could not load Pi ${label2}.${packageDirectoryError}`, { cause: error });
3835
+ });
3836
+ nativeCoreModules.set(file, loaded);
3837
+ }
3838
+ return loaded;
3973
3839
  }
3974
3840
  async function expandPromptTemplateForInspection(text, templates) {
3975
- return (await loadNativePromptTemplateModule()).expandPromptTemplate(text, templates);
3841
+ return (await loadNativeCoreModule("prompt-templates.js", "prompt templates")).expandPromptTemplate(text, templates);
3842
+ }
3843
+ async function buildSystemPromptForInspection(options) {
3844
+ return (await loadNativeCoreModule("system-prompt.js", "system prompt builder")).buildSystemPrompt(options);
3976
3845
  }
3977
3846
  async function preparePiPrompt(native, text) {
3978
3847
  const session = native;
3979
3848
  const diagnostics = [];
3980
3849
  const runner = session._extensionRunner;
3981
- const baseSystemPrompt = typeof session._baseSystemPrompt === "string" ? session._baseSystemPrompt : session.systemPrompt ?? "";
3850
+ const baseSystemPrompt = session.systemPrompt ?? "";
3982
3851
  const baseOptions = session._baseSystemPromptOptions;
3983
3852
  const expandSkillCommand = session._expandSkillCommand;
3984
3853
  const templates = session.promptTemplates;
3985
3854
  if (!runner) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: extension runner is missing", source: "Pi session" });
3986
- if (typeof session._baseSystemPrompt !== "string") diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: base system prompt is missing", source: "Pi session" });
3987
3855
  if (baseOptions === void 0) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: base system prompt options are missing", source: "Pi session" });
3988
3856
  if (typeof expandSkillCommand !== "function") diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: skill expansion is missing", source: "Pi session" });
3989
3857
  if (!Array.isArray(templates)) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: prompt templates are missing", source: "Pi session" });
@@ -4008,73 +3876,116 @@ async function preparePiPrompt(native, text) {
4008
3876
  diagnostics.push({ type: "error", message: `Pi prompt template expansion is unavailable: ${errorText(error)}`, source: "Pi session" });
4009
3877
  }
4010
3878
  }
4011
- const result = !inputHandled && runner && baseOptions !== void 0 ? await runner.emitBeforeAgentStart(expandedPrompt, void 0, baseSystemPrompt, baseOptions) : void 0;
3879
+ const result = !inputHandled && runner && baseOptions !== void 0 ? await runner.emitBeforeAgentStart(expandedPrompt, void 0, baseOptions) : void 0;
4012
3880
  const prepared = result;
4013
- return { prompt: text, expandedPrompt, systemPrompt: typeof prepared?.systemPrompt === "string" ? prepared.systemPrompt : baseSystemPrompt, systemPromptOptions: baseOptions, messages: prepared?.messages ?? [], inputHandled, diagnostics };
3881
+ const systemPromptOptions = prepared?.systemPromptOptions ?? baseOptions;
3882
+ const systemPrompt = prepared?.systemPromptOptions === void 0 ? baseSystemPrompt : await buildSystemPromptForInspection(prepared.systemPromptOptions);
3883
+ return { prompt: text, expandedPrompt, systemPrompt, systemPromptOptions, messages: prepared?.messages ?? [], inputHandled, diagnostics };
4014
3884
  } finally {
4015
3885
  unsubscribe?.();
4016
3886
  }
4017
3887
  }
4018
3888
  function flushExtensionProviders(resourceLoader, modelRuntime) {
4019
- const { runtime: runtime2 } = resourceLoader.getExtensions();
3889
+ const { runtime } = resourceLoader.getExtensions();
4020
3890
  const failures = [];
4021
- for (const { name, config } of runtime2.pendingProviderRegistrations) {
3891
+ for (const { name, config } of runtime.pendingProviderRegistrations) {
4022
3892
  try {
4023
3893
  modelRuntime.registerProvider(name, config);
4024
3894
  } catch (error) {
4025
3895
  failures.push(`${name}: ${errorText(error)}`);
4026
3896
  }
4027
3897
  }
4028
- runtime2.pendingProviderRegistrations = [];
4029
- for (const { provider } of runtime2.pendingNativeProviderRegistrations) {
3898
+ runtime.pendingProviderRegistrations = [];
3899
+ for (const { provider } of runtime.pendingNativeProviderRegistrations) {
4030
3900
  try {
4031
3901
  modelRuntime.registerNativeProvider(provider);
4032
3902
  } catch (error) {
4033
3903
  failures.push(`${provider.id || "native provider"}: ${errorText(error)}`);
4034
3904
  }
4035
3905
  }
4036
- runtime2.pendingNativeProviderRegistrations = [];
3906
+ runtime.pendingNativeProviderRegistrations = [];
4037
3907
  return failures;
4038
3908
  }
3909
+ function builtinExtensions() {
3910
+ return [
3911
+ { name: "codemode", factory: createCodemodeExtension(), replaceable: true, builtin: true },
3912
+ { name: "tool-search", factory: createToolSearchExtension(), replaceable: true, builtin: true },
3913
+ { name: "mcp", factory: createMcpExtension(), replaceable: true, builtin: true }
3914
+ ];
3915
+ }
3916
+ var BUILTIN_EXTENSION_PREFIX = "builtin:";
3917
+ function isLlamaModel(model) {
3918
+ return model.provider === "llama.cpp";
3919
+ }
3920
+ var WorkflowResourceLoader = class extends DefaultResourceLoader {
3921
+ constructor(loaderCwd, options) {
3922
+ super(options);
3923
+ this.loaderCwd = loaderCwd;
3924
+ }
3925
+ loaderCwd;
3926
+ extensionSkillPaths = [];
3927
+ async reload(...args) {
3928
+ this.extensionSkillPaths.length = 0;
3929
+ await super.reload(...args);
3930
+ }
3931
+ extendResources(paths) {
3932
+ this.extensionSkillPaths.push(...(paths.skillPaths ?? []).map(({ path }) => piResourcePath(path, this.loaderCwd)));
3933
+ super.extendResources(paths);
3934
+ }
3935
+ };
3936
+ function discoveredSkillSelectors(selectors, inventory) {
3937
+ if (selectors === void 0 || inventory === void 0) return selectors;
3938
+ const frozen = /* @__PURE__ */ new Set();
3939
+ let index = selectors.length;
3940
+ for (const name of [...inventory].reverse().concat("!*")) {
3941
+ do
3942
+ index -= 1;
3943
+ while (index >= 0 && selectors[index] !== name);
3944
+ if (index < 0) return selectors;
3945
+ frozen.add(index);
3946
+ }
3947
+ return selectors.filter((_selector, position) => !frozen.has(position));
3948
+ }
4039
3949
  async function createLocalPiSessionHandle(input, sessionStartEvent) {
4040
- const agentDir = input.agentDir ?? getAgentDir3();
3950
+ const agentDir = input.agentDir ?? getAgentDir2();
4041
3951
  const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
4042
3952
  const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
4043
3953
  const contextFilesOverride = input.contextFiles === void 0 ? void 0 : (base) => ({ ...base, agentsFiles: filterContextFiles(base.agentsFiles, input.contextFiles ?? [], input.cwd, agentDir) });
4044
- const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join6(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join6(agentDir, "sessions")) : SessionManager.create(input.cwd));
3954
+ const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join5(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join5(agentDir, "sessions")) : SessionManager.create(input.cwd));
4045
3955
  if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
4046
- const modelRuntime = await ModelRuntime.create({ authPath: join6(agentDir, "auth.json"), modelsPath: join6(agentDir, "models.json") });
3956
+ const modelRuntime = await ModelRuntime.create({ authPath: join5(agentDir, "auth.json"), modelsPath: join5(agentDir, "models.json") });
4047
3957
  const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
4048
3958
  const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
4049
3959
  let settingsManager;
4050
3960
  let resourceLoader;
3961
+ const builtins = builtinExtensions();
3962
+ const builtinNames = builtins.flatMap((extension2) => typeof extension2 === "function" ? [] : [extension2.name]);
3963
+ const extensionFactories = [...builtins, ...input.extensionFactories ?? []];
4051
3964
  const policy = input.resourcePolicy;
4052
3965
  if (policy) {
4053
3966
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: false });
4054
3967
  settingsManager.setProjectTrusted(policy.projectTrusted);
4055
- const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
3968
+ const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager, builtinExtensions: builtinNames });
4056
3969
  const resolved = await packageManager.resolve();
4057
- const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
3970
+ const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => extensionIdentity(path)))];
4058
3971
  const selectorSources = policy.selectorSources;
4059
- const selection = resolveRole(void 0, {
4060
- cwd: input.cwd,
4061
- selectorSources,
4062
- resources: { extensions: discoveredExtensions }
4063
- });
4064
- const selectedExtensions = selection.selectedExtensions ?? [];
4065
- const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
3972
+ const selectedExtensions = selectResourcesByLayers(resourceSelectorLayers(selectorSources, "extensions"), discoveredExtensions).filter((path) => policy.capabilities === void 0 || policy.capabilities.extensions.includes(path));
3973
+ const extensionPaths = selectedExtensions.filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path));
4066
3974
  policy.selectedExtensions = selectedExtensions;
4067
- policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
3975
+ policy.unmatchedExtensions = unmatchedResourcePatterns(policy.effective.extensions, discoveredExtensions);
4068
3976
  const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
4069
- const updateSkillMatches = (skills) => {
3977
+ const updateSkillMatches = (skills, extensionSkillPaths) => {
4070
3978
  const names = [...new Set(skills.map(({ name }) => name))];
4071
- const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
4072
- const selectedSkills = selected.selectedSkills ?? [];
3979
+ const discovered = new Set(skills.filter(({ filePath }) => extensionSkillPaths.some((path) => piResourceContains(path, filePath))).map(({ name }) => name));
3980
+ const [global, project, call] = resourceSelectorLayers(selectorSources, "skills");
3981
+ const selectedStatic = selectResourcesByLayers([global, project, call], names.filter((name) => !discovered.has(name))).filter((name) => policy.capabilities === void 0 || policy.capabilities.skills.includes(name));
3982
+ const selectedDiscovered = selectResourcesByLayers([discoveredSkillSelectors(global, policy.capabilities?.skills), project, call], [...discovered]).filter((name) => policy.parentSkills === void 0 || policy.parentSkills.includes(name));
3983
+ const selectedSkills = [...selectedStatic, ...selectedDiscovered];
4073
3984
  policy.selectedSkills = selectedSkills;
4074
- policy.unmatchedSkills = selected.unmatchedSkills ?? [];
3985
+ policy.unmatchedSkills = unmatchedResourcePatterns(policy.effective.skills, names);
4075
3986
  return new Set(names.filter((name) => !selectedSkills.includes(name)));
4076
3987
  };
4077
- resourceLoader = new DefaultResourceLoader({
3988
+ const workflowLoader = new WorkflowResourceLoader(input.cwd, {
4078
3989
  cwd: input.cwd,
4079
3990
  agentDir,
4080
3991
  settingsManager,
@@ -4082,33 +3993,34 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
4082
3993
  additionalExtensionPaths: extensionPaths,
4083
3994
  noSkills: true,
4084
3995
  additionalSkillPaths: [.../* @__PURE__ */ new Set([...skillPaths, ...input.additionalSkillPaths ?? []])],
4085
- ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {},
3996
+ extensionFactories,
4086
3997
  ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {},
4087
3998
  skillsOverride: (base) => {
4088
- const disabledSkills = updateSkillMatches(base.skills);
3999
+ const disabledSkills = updateSkillMatches(base.skills, workflowLoader.extensionSkillPaths);
4089
4000
  return { ...base, skills: base.skills.filter(({ name }) => !disabledSkills.has(name)) };
4090
4001
  },
4091
4002
  ...input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {},
4092
4003
  ...systemPromptOptions
4093
4004
  });
4005
+ resourceLoader = workflowLoader;
4094
4006
  await resourceLoader.reload();
4095
4007
  } else {
4096
4008
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: true });
4097
- const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
4009
+ const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager, builtinExtensions: builtinNames });
4098
4010
  const resolved = await packageManager.resolve();
4099
- const extensionPaths = [...new Set(resolved.extensions.filter(({ enabled }) => enabled).map(({ path }) => canonicalPath(path)).filter((path) => !WORKFLOW_HOST_ENTRIES.has(path)))];
4100
- 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 ?? ""] } : {} });
4011
+ const extensionPaths = [...new Set(resolved.extensions.filter(({ enabled }) => enabled).map(({ path }) => extensionIdentity(path)).filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path)))];
4012
+ 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 ?? ""] } : {} });
4101
4013
  await resourceLoader.reload();
4102
4014
  }
4103
4015
  const providerFailures = flushExtensionProviders(resourceLoader, modelRuntime);
4104
4016
  await modelRuntime.refresh({ allowNetwork: false });
4105
4017
  const model = modelRuntime.getModel(input.model.provider, input.model.model);
4106
4018
  if (!model) {
4107
- const because = providerFailures.length > 0 ? ` (provider registration failed \u2014 ${providerFailures.join("; ")})` : "";
4019
+ 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)" : "";
4108
4020
  throw new WorkflowError("UNKNOWN_MODEL", `Unknown model: ${input.model.provider}/${input.model.model}${because}`);
4109
4021
  }
4110
4022
  const effectiveSessionStartEvent = input.settings === void 0 && sessionStartEvent === void 0 ? void 0 : { ...sessionStartEvent ?? { type: "session_start", reason: "startup" }, settings: input.settings ?? Object.freeze({}) };
4111
- const { session } = await createAgentSession({ ...input.options ?? {}, cwd: input.cwd, agentDir, modelRuntime, model, settingsManager, ...input.model.thinking ? { thinkingLevel: input.model.thinking } : {}, tools, ...customTools.length ? { customTools } : {}, ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}, resourceLoader, ...effectiveSessionStartEvent ? { sessionStartEvent: effectiveSessionStartEvent } : {}, sessionManager: manager });
4023
+ const { session } = await createAgentSession({ cwd: input.cwd, agentDir, modelRuntime, model, settingsManager, ...input.model.thinking ? { thinkingLevel: input.model.thinking } : {}, tools, ...customTools.length ? { customTools } : {}, ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}, resourceLoader, ...effectiveSessionStartEvent ? { sessionStartEvent: effectiveSessionStartEvent } : {}, sessionManager: manager });
4112
4024
  const nativeDispose = session.dispose.bind(session);
4113
4025
  let disposal;
4114
4026
  const shutdown = (reason, targetSessionFile) => {
@@ -4129,13 +4041,13 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
4129
4041
  await shutdown("quit").catch(() => void 0);
4130
4042
  throw error;
4131
4043
  }
4132
- const resourcePaths = { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => canonicalPath(resolvedPath)), skills: resourceLoader.getSkills().skills.map(({ filePath }) => canonicalPath(filePath)) };
4044
+ const resourcePaths = { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => extensionIdentity(resolvedPath)), skills: resourceLoader.getSkills().skills.map(({ filePath }) => canonicalPath(filePath)) };
4133
4045
  const resourceInspection = () => {
4134
4046
  const extensions = resourceLoader.getExtensions();
4135
4047
  const skills = resourceLoader.getSkills();
4136
4048
  const diagnostics = [...extensions.errors.map(({ path, error }) => ({ type: "error", message: error, source: path })), ...skills.diagnostics.map(({ type, path, message }) => ({ type, message, ...path ? { source: path } : {} }))];
4137
4049
  const systemSource = systemPromptSource;
4138
- 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" } : {} };
4050
+ 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" } : {} };
4139
4051
  };
4140
4052
  const managedSession = Object.assign(session, {
4141
4053
  getLeafId: () => manager.getLeafId(),
@@ -4471,6 +4383,10 @@ async function createLocalWorkflowAgentSession(prepared, context) {
4471
4383
  return session;
4472
4384
  }
4473
4385
  var localAgentTransport = Object.freeze({ id: "local", createSession: createLocalWorkflowAgentSession });
4386
+ function excludedToolNames(options) {
4387
+ const own = options.agentOptions?.excludeTools;
4388
+ return /* @__PURE__ */ new Set([...options.inheritedExcludeTools ?? [], ...Array.isArray(own) ? own.filter((tool) => typeof tool === "string") : []]);
4389
+ }
4474
4390
  function changedOption(options, baseline, key) {
4475
4391
  return JSON.stringify(options[key]) !== JSON.stringify(baseline[key]);
4476
4392
  }
@@ -4493,7 +4409,7 @@ function resourcePolicySummary(policy, tools = policy.selectedTools ?? []) {
4493
4409
  }
4494
4410
  var RESOURCE_SELECTOR_KEYS = ["skills", "extensions", "tools"];
4495
4411
  function resourceSelectorLayers(sources, key) {
4496
- return [sources.global[key], sources.project[key], sources.role?.[key], sources.call?.[key]];
4412
+ return [sources.global[key], sources.project[key], sources.call?.[key]];
4497
4413
  }
4498
4414
  function selectorListWidened(ceiling, candidate) {
4499
4415
  let candidateIndex = 0;
@@ -4550,32 +4466,32 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
4550
4466
  };
4551
4467
  }
4552
4468
  function packageRoot(start) {
4553
- let current = dirname5(realpathSync2(start));
4469
+ let current = dirname3(canonicalPath(start));
4554
4470
  for (; ; ) {
4555
- const candidates = [current, join6(current, "..", "@earendil-works", "pi-coding-agent"), join6(current, "..", "pi-coding-agent")];
4471
+ const candidates = [current, join5(current, "..", "@earendil-works", "pi-coding-agent"), join5(current, "..", "pi-coding-agent")];
4556
4472
  for (const candidate of candidates) {
4557
- const packageJsonPath = join6(candidate, "package.json");
4473
+ const packageJsonPath = join5(candidate, "package.json");
4558
4474
  if (!existsSync(packageJsonPath)) continue;
4559
- const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
4475
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
4560
4476
  if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
4561
4477
  }
4562
- const parent = dirname5(current);
4478
+ const parent = dirname3(current);
4563
4479
  if (parent === current) return void 0;
4564
4480
  current = parent;
4565
4481
  }
4566
4482
  }
4567
4483
  function piPackageEntrypoint(root) {
4568
- const packageJson = JSON.parse(readFileSync4(join6(root, "package.json"), "utf8"));
4484
+ const packageJson = JSON.parse(readFileSync2(join5(root, "package.json"), "utf8"));
4569
4485
  if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4570
4486
  const bin = packageJson.bin;
4571
4487
  const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
4572
4488
  if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4573
- const entrypoint = resolve5(root, cli);
4489
+ const entrypoint = resolve4(root, cli);
4574
4490
  if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
4575
4491
  return entrypoint;
4576
4492
  }
4577
4493
  async function resolvePiRuntime() {
4578
- const executableName = basename4(process.execPath).toLowerCase();
4494
+ const executableName = basename2(process.execPath).toLowerCase();
4579
4495
  const bunBinary = ["$bunfs", "~BUN", "%7EBUN"].some((marker) => import.meta.url.includes(marker)) || Boolean(process.versions.bun && !["bun", "bun.exe"].includes(executableName));
4580
4496
  if (bunBinary) return { runtime: Object.freeze({ executable: process.execPath }) };
4581
4497
  const errors = [];
@@ -4599,19 +4515,12 @@ async function resolvePiRuntime() {
4599
4515
  }
4600
4516
  }
4601
4517
  var piRuntimeResolution = resolvePiRuntime();
4602
- function workflowAgentPrompt(task, options, attempt, previousError) {
4603
- const basePrompt = [`Workflow: ${options.workflowName}`, `Agent: ${options.label}`, options.phase ? `Phase: ${options.phase}` : "", options.parent ? `Parent: ${options.parent}` : "", "You own this task and any direct child agents you create. Return child results to your parent; do not leave descendants running.", options.schema ? "Call workflow_result exactly once with the final result matching its schema. Do not return prose as the final result." : 'Call workflow_result exactly once with { result: "final response" } to submit the final response. Do not return prose as the final result.', attempt > 1 ? `Retry attempt ${String(attempt)}. Previous state: ${options.retryState ?? previousError ?? "failed attempt"}` : ""].filter(Boolean).join("\n");
4604
- const instruction = options.budget?.instruction();
4605
- return `${basePrompt}
4606
-
4607
- Task:
4608
- ${task}${instruction ? `
4609
-
4610
- ${instruction}` : ""}`;
4518
+ function attemptControlText(options, attempt, previousError) {
4519
+ return [attempt > 1 ? `Retry attempt ${String(attempt)}. Previous state: ${options.retryState ?? previousError ?? "failed attempt"}` : "", options.budget?.instruction() ?? ""].filter(Boolean).join("\n");
4611
4520
  }
4612
4521
  async function preparedAgentSession(input, initialPrompt) {
4613
4522
  const { runtime: piRuntime, error: piRuntimeError } = await piRuntimeResolution;
4614
- const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir3(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
4523
+ const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir2(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
4615
4524
  const prepared = {
4616
4525
  cwd: input.cwd,
4617
4526
  model: Object.freeze({ ...input.model }),
@@ -4641,49 +4550,30 @@ function agentSetupSummary(setup, hookNames) {
4641
4550
  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) } : {} };
4642
4551
  }
4643
4552
  async function attemptSessionInput(source, attempt) {
4644
- const target = join6(dirname5(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
4553
+ const target = join5(dirname3(source), `${basename2(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
4645
4554
  await copyFile(source, target);
4646
4555
  return target;
4647
4556
  }
4648
- async function prepareAgentSetup(root, transport, task, options, resolved, cwd, attempt, signal, customTools, resultTool, inspection = false, previousError) {
4557
+ async function prepareAgentSetup(root, transport, task, options, resolved, cwd, attempt, signal, customTools, resultTool, inspection = false) {
4649
4558
  const setupSignal = signal ?? root.runContext?.signal ?? new AbortController().signal;
4650
4559
  const baselineOptions = structuredClone(options.agentOptions ?? {});
4651
4560
  const baseResourcePolicy = await root.agentResourcePolicy?.();
4652
- const roleName2 = typeof options.role === "string" ? options.role : void 0;
4653
- const roleDefinition = roleName2 ? root.agentDefinitions?.[roleName2] : void 0;
4654
- const roleSelectors = roleDefinition ? {
4655
- ...roleDefinition.skills === void 0 ? {} : { skills: roleDefinition.skills },
4656
- ...roleDefinition.extensions === void 0 ? {} : { extensions: roleDefinition.extensions.map((selector) => canonicalExtensionSelector(selector, cwd)) },
4657
- ...roleDefinition.tools === void 0 ? {} : { tools: roleDefinition.tools }
4658
- } : void 0;
4659
- const selectorValue = (key, fallback) => {
4660
- const value = baselineOptions[key];
4661
- if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return value;
4662
- return options.agentOptions === void 0 ? fallback : void 0;
4561
+ const configuration = options.configuration;
4562
+ const resourcePolicy = {
4563
+ ...baseResourcePolicy ?? { globalSettingsPath: "", projectSettingsPath: "", projectTrusted: root.projectTrusted ?? false, global: { skills: [], extensions: [] }, project: { skills: [], extensions: [] }, unmatchedSkills: [], unmatchedExtensions: [] },
4564
+ projectTrusted: configuration?.projectTrusted ?? root.projectTrusted ?? baseResourcePolicy?.projectTrusted ?? false,
4565
+ effective: { skills: configuration?.skills ?? [], extensions: configuration?.extensions ?? [], tools: resolved.tools },
4566
+ selectorSources: { global: { skills: configuration?.skills ?? [], extensions: configuration?.extensions ?? [], tools: resolved.tools }, project: {} },
4567
+ ...options.capabilities ? { capabilities: options.capabilities } : {},
4568
+ ...options.parentSkills ? { parentSkills: [...options.parentSkills] } : {},
4569
+ selectedTools: resolved.tools
4663
4570
  };
4664
- const rawCallSkills = selectorValue("skills", options.skills);
4665
- const rawCallExtensions = selectorValue("extensions", options.extensions)?.map((selector) => canonicalExtensionSelector(selector, cwd));
4666
- const rawCallTools = selectorValue("tools", options.tools);
4667
- const callSelectors = { ...rawCallSkills === void 0 ? {} : { skills: rawCallSkills }, ...rawCallExtensions === void 0 ? {} : { extensions: rawCallExtensions }, ...rawCallTools === void 0 ? {} : { tools: rawCallTools } };
4668
- const resourcePolicy = baseResourcePolicy ? {
4669
- ...baseResourcePolicy,
4670
- effective: {
4671
- skills: [...baseResourcePolicy.effective.skills, ...roleSelectors?.skills ?? [], ...rawCallSkills ?? []],
4672
- extensions: [...baseResourcePolicy.effective.extensions, ...roleSelectors?.extensions ?? [], ...rawCallExtensions ?? []],
4673
- ...baseResourcePolicy.effective.tools === void 0 && roleSelectors?.tools === void 0 && rawCallTools === void 0 ? {} : { tools: [...baseResourcePolicy.effective.tools ?? [], ...roleSelectors?.tools ?? [], ...rawCallTools ?? []] }
4674
- },
4675
- selectorSources: { global: baseResourcePolicy.selectorSources.global, project: baseResourcePolicy.selectorSources.project, ...roleSelectors ? { role: roleSelectors } : {}, ...Object.keys(callSelectors).length ? { call: callSelectors } : {} }
4676
- } : void 0;
4677
- if (resourcePolicy) {
4678
- resourcePolicy.selectedTools = resolved.tools;
4679
- resourcePolicy.unmatchedTools = unmatchedResourcePatterns(resourceSelectorLayers(resourcePolicy.selectorSources, "tools").flatMap((layer) => layer ?? []), [...root.tools]);
4680
- }
4681
- const resourcePolicyCeiling = resourcePolicy ? structuredClone(resourcePolicy) : void 0;
4571
+ const resourcePolicyCeiling = structuredClone(resourcePolicy);
4682
4572
  const sessionPath = options.sessionPath === void 0 || inspection ? options.sessionPath : await attemptSessionInput(options.sessionPath, attempt);
4683
- const extensionSettings = mergeWorkflowExtensionSettings(options.inheritedExtensionSettings ?? root.extensionSettings, roleDefinition?.extensionSettings) ?? Object.freeze({});
4684
- root.validateExtensionSettings?.(extensionSettings, { source: roleName2 === void 0 ? "effective" : "role", cwd, projectTrusted: resourcePolicy?.projectTrusted ?? true, ...root.extensionSettingsPath ?? root.settingsPath ? { settingsPath: root.extensionSettingsPath ?? root.settingsPath } : {}, ...roleName2 === void 0 ? {} : { role: roleName2 } });
4573
+ const extensionSettings = configuration?.settings ?? options.inheritedExtensionSettings ?? root.extensionSettings ?? Object.freeze({});
4574
+ root.validateExtensionSettings?.(extensionSettings, { source: "effective", cwd, projectTrusted: resourcePolicy.projectTrusted, ...root.extensionSettingsPath ?? root.settingsPath ? { settingsPath: root.extensionSettingsPath ?? root.settingsPath } : {} });
4685
4575
  if (options.agentNodeId !== void 0) root.onAgentSettings?.(options.agentNodeId, extensionSettings);
4686
- const sessionInput = { cwd, model: { ...resolved.model }, tools: [...resolved.tools], sessionLabel: `${options.workflowName}:${options.label}:attempt-${String(attempt)}`, ...sessionPath ? { sessionPath } : {}, ...root.agentDir ? { agentDir: root.agentDir } : {}, ...root.additionalSkillPaths?.length ? { additionalSkillPaths: [...root.additionalSkillPaths] } : {}, ...resolved.contextFiles === void 0 ? {} : { contextFiles: [...resolved.contextFiles] }, ...customTools.length ? { customTools: [...customTools] } : {}, ...resultTool ? { resultTool } : {}, ...resolved.systemPrompt !== void 0 ? { systemPrompt: resolved.systemPrompt } : {}, systemPromptAppend: resolved.systemPromptAppend, ...resourcePolicy ? { resourcePolicy } : {}, settings: extensionSettings, options: structuredClone(baselineOptions) };
4576
+ const sessionInput = { cwd, model: { ...resolved.model }, tools: [...resolved.tools], sessionLabel: `${options.workflowName}:${options.label}:attempt-${String(attempt)}`, ...sessionPath ? { sessionPath } : {}, ...root.agentDir ? { agentDir: root.agentDir } : {}, ...root.additionalSkillPaths?.length ? { additionalSkillPaths: [...root.additionalSkillPaths] } : {}, ...resolved.contextFiles === void 0 ? {} : { contextFiles: [...resolved.contextFiles] }, ...customTools.length ? { customTools: [...customTools] } : {}, ...resultTool ? { resultTool } : {}, ...resolved.systemPrompt !== void 0 ? { systemPrompt: resolved.systemPrompt } : {}, systemPromptAppend: resolved.systemPromptAppend, resourcePolicy, settings: extensionSettings, options: structuredClone(baselineOptions) };
4687
4577
  const setup = { prompt: task, options: sessionInput.options ?? {}, sessionInput, prepared: await preparedAgentSession(sessionInput, task), transport };
4688
4578
  const base = fallbackSetupContext(root, options, setupSignal);
4689
4579
  const context = Object.freeze({ run: base.run, identity: base.identity, attempt, signal: setupSignal, settings: extensionSettings, ...base.tuiIndex === void 0 ? {} : { tuiIndex: base.tuiIndex }, ...base.tuiLabel === void 0 ? {} : { tuiLabel: base.tuiLabel }, ...inspection ? { mode: "inspection" } : {} });
@@ -4703,19 +4593,42 @@ async function prepareAgentSetup(root, transport, task, options, resolved, cwd,
4703
4593
  try {
4704
4594
  if (resourcePolicyWidened(resourcePolicyCeiling, setup.sessionInput.resourcePolicy)) throw new WorkflowError("INVALID_METADATA", "Agent setup widened the prepared resource policy");
4705
4595
  appendResourcePolicyNarrowing(resourcePolicyCeiling, setup.sessionInput.resourcePolicy);
4596
+ if (setup.sessionInput.resourcePolicy) {
4597
+ const policy = setup.sessionInput.resourcePolicy;
4598
+ policy.effective.extensions = policy.effective.extensions.map((selector) => canonicalExtensionSelector(selector, cwd));
4599
+ for (const layer of [policy.selectorSources.global, policy.selectorSources.project, policy.selectorSources.call]) if (layer?.extensions !== void 0) layer.extensions = layer.extensions.map((selector) => canonicalExtensionSelector(selector, cwd));
4600
+ if (resourcePolicyCeiling.parentSkills === void 0) Reflect.deleteProperty(policy, "parentSkills");
4601
+ else policy.parentSkills = [...resourcePolicyCeiling.parentSkills];
4602
+ }
4706
4603
  setup.sessionInput.options = setup.options;
4707
4604
  if (changedOption(setup.options, baselineOptions, "model") && typeof setup.options.model === "string") setup.sessionInput.model = parseModel(setup.options.model, setup.sessionInput.model, root.modelAliases, root.knownModels ?? root.availableModels, root.settingsPath);
4708
4605
  if (changedOption(setup.options, baselineOptions, "tools") && Array.isArray(setup.options.tools) && setup.options.tools.every((tool) => typeof tool === "string")) setup.sessionInput.tools = [...setup.options.tools];
4709
- if (changedOption(setup.options, baselineOptions, "cwd") && typeof setup.options.cwd === "string") setup.sessionInput.cwd = setup.options.cwd;
4606
+ if (changedOption(setup.options, baselineOptions, "cwd") || !sameFilesystemPath(cwd, setup.sessionInput.cwd)) throw new WorkflowError("INVALID_METADATA", "Agent setup cannot change cwd");
4607
+ if (!root.availableModels?.has(modelCapability(setup.sessionInput.model)) && root.availableModels !== void 0) throw new WorkflowError("UNKNOWN_MODEL", "Agent setup selected an unavailable model");
4608
+ const shadowed = (setup.sessionInput.customTools ?? []).map(({ name }) => name).find((name) => root.tools.has(name) && !resolved.tools.includes(name));
4609
+ if (shadowed) throw new WorkflowError("UNKNOWN_TOOL", `Custom tool reintroduces a tool outside the prepared agent policy: ${shadowed}`);
4610
+ const excludedTools = new Set(configuration?.excludeTools ?? excludedToolNames(options));
4611
+ const excludedCustomTools = (setup.sessionInput.customTools ?? []).filter(({ name }) => excludedTools.has(name)).map(({ name }) => name);
4612
+ if (excludedCustomTools.length) {
4613
+ setup.sessionInput.customTools = setup.sessionInput.customTools?.filter(({ name }) => !excludedTools.has(name)) ?? [];
4614
+ setup.sessionInput.tools = setup.sessionInput.tools.filter((tool) => root.tools.has(tool) || !excludedCustomTools.includes(tool));
4615
+ }
4616
+ for (const tool of excludedTools) if (!options.inheritedExcludeTools?.includes(tool) && !root.tools.has(tool) && !excludedCustomTools.includes(tool)) root.onResourceWarning?.(`excludeTools names no root or custom tool of this agent: ${tool}`);
4710
4617
  const customToolNames = /* @__PURE__ */ new Set([...(setup.sessionInput.customTools ?? []).map(({ name }) => name), ...setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : []]);
4711
4618
  const widened = setup.sessionInput.tools.find((tool) => !resolved.tools.includes(tool) && !customToolNames.has(tool));
4712
4619
  const outsideTool = widened ?? setup.sessionInput.tools.find((tool) => !root.tools.has(tool) && !customToolNames.has(tool));
4713
4620
  if (outsideTool) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the prepared agent policy: ${outsideTool}`);
4621
+ if (setup.sessionInput.resourcePolicy) {
4622
+ const policy = setup.sessionInput.resourcePolicy;
4623
+ setup.sessionInput.tools = selectResourcesByLayers(resourceSelectorLayers(policy.selectorSources, "tools"), setup.sessionInput.tools);
4624
+ if (policy.projectTrusted !== resourcePolicyCeiling.projectTrusted) policy.capabilities = await authorizedResources({ ...root, projectTrusted: policy.projectTrusted }, cwd, options);
4625
+ }
4626
+ if (!setup.sessionInput.tools.includes("agent")) setup.sessionInput.customTools = setup.sessionInput.customTools?.filter(({ name }) => name !== "agent" && name !== "get_subagent_result" && name !== "steer_subagent") ?? [];
4714
4627
  } catch (error) {
4715
4628
  setup.prepared = await preparedAgentSession(setup.sessionInput, task);
4716
4629
  return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error } };
4717
4630
  }
4718
- setup.prepared = await preparedAgentSession(setup.sessionInput, inspection ? task : workflowAgentPrompt(setup.prompt, options, attempt, previousError));
4631
+ setup.prepared = await preparedAgentSession(setup.sessionInput, inspection ? task : setup.prompt);
4719
4632
  return { setup, summary: agentSetupSummary(setup, hookNames) };
4720
4633
  }
4721
4634
  function attemptRecord(transport, attempt, session, setup, stats, result, error) {
@@ -4735,54 +4648,93 @@ var WorkflowAgentExecutor = class {
4735
4648
  }
4736
4649
  root;
4737
4650
  transport;
4738
- warnedRoleTools = /* @__PURE__ */ new Set();
4739
4651
  setRunContext(runContext) {
4740
4652
  this.root.runContext = runContext;
4741
4653
  }
4742
4654
  resolve(options, inheritedTools) {
4743
- const roleName2 = typeof options.role === "string" ? options.role : void 0;
4744
- const roleDefinition = roleName2 ? this.root.agentDefinitions?.[roleName2] : void 0;
4745
- if (roleName2 && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName2}`);
4746
- if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
4747
- const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
4748
- if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
4749
- const key = `${roleName2 ?? "agent"}:${body}`;
4750
- if (!this.warnedRoleTools.has(key)) {
4751
- this.warnedRoleTools.add(key);
4752
- this.root.onResourceWarning?.(`Tool not available in this session for role ${roleName2 ?? "agent"}: ${body}. The agent runs without it.`);
4753
- }
4754
- }
4755
- }
4756
- const resolved = resolveRole(roleName2, {
4757
- cwd: this.root.cwd,
4758
- ...roleDefinition === void 0 ? {} : { definition: roleDefinition },
4759
- ...this.root.agentDefinitions === void 0 ? {} : { definitions: this.root.agentDefinitions },
4760
- ...this.root.agentDir === void 0 ? {} : { agentDir: this.root.agentDir },
4761
- selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
4762
- ...this.root.modelAliases === void 0 ? {} : { modelAliases: this.root.modelAliases },
4763
- ...this.root.knownModels === void 0 ? {} : { knownModels: this.root.knownModels },
4764
- ...this.root.availableModels === void 0 ? {} : { availableModels: this.root.availableModels },
4765
- ...this.root.blockedAliases === void 0 ? {} : { blockedAliases: this.root.blockedAliases },
4766
- ...this.root.blockedAliasTargets === void 0 ? {} : { blockedAliasTargets: this.root.blockedAliasTargets },
4767
- ...this.root.settingsPath === void 0 ? {} : { settingsPath: this.root.settingsPath },
4768
- rootModel: this.root.model,
4769
- rootTools: this.root.tools,
4770
- ...inheritedTools === void 0 ? {} : { inheritedTools },
4655
+ if (options.configuration) return { ...options.configuration, ...options.modelOverride ? { model: options.modelOverride } : {} };
4656
+ const ceiling = inheritedTools ?? [...this.root.tools];
4657
+ for (const pattern of options.tools ?? []) if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !ceiling.includes(pattern)) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the prepared agent policy: ${pattern}`);
4658
+ const tools = options.effectiveTools ?? selectResourcesByLayers([this.root.resourceSelectors?.tools, options.tools], [...inheritedTools ?? this.root.tools]);
4659
+ const missing = tools.find((tool) => !ceiling.includes(tool));
4660
+ if (missing) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the prepared agent policy: ${missing}`);
4661
+ const model = options.modelOverride ?? parseModel(options.model, this.root.model, this.root.modelAliases, this.root.knownModels, this.root.settingsPath);
4662
+ return { model: physicalModel(model, this.root.modelAliases, this.root.knownModels, this.root.settingsPath), tools, systemPromptAppend: "", ...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles } };
4663
+ }
4664
+ async prepare(options, cwd = this.root.cwd, signal = this.root.runContext?.signal ?? new AbortController().signal, mode = "execution") {
4665
+ const root = { ...this.root, projectTrusted: Boolean(this.root.projectTrusted) && options.projectTrusted !== false };
4666
+ const policy = await this.root.agentResourcePolicy?.();
4667
+ const capabilities = await authorizedResources(root, cwd, options);
4668
+ const persistedKey = options.parent !== void 0 ? options.agentNodeId : options.agentIdentity?.handle !== void 0 ? `handle:${options.agentIdentity.handle}` : options.agentIdentity ? agentIdentityPath(options.agentIdentity) : options.agentNodeId;
4669
+ const stored = options.configuration ?? (mode === "execution" && persistedKey && this.root.runStore?.load ? (await this.root.runStore.load()).snapshot.agentConfigurations[persistedKey] : void 0);
4670
+ if (stored) {
4671
+ if (stored.excludeTools === void 0) throw new WorkflowError("RESUME_INCOMPATIBLE", "Prepared agent configuration predates frozen excludeTools; relaunch the workflow");
4672
+ return validatePreparedConfiguration(root, stored, capabilities, cwd, true);
4673
+ }
4674
+ const original = deepFreeze(structuredClone(validateAgentOptions(options.agentOptions ?? {
4771
4675
  ...options.model === void 0 ? {} : { model: options.model },
4772
- ...options.modelOverride === void 0 ? {} : { modelOverride: options.modelOverride },
4773
- ...options.tools === void 0 ? {} : { tools: options.tools },
4774
- ...options.skills === void 0 ? {} : { skills: options.skills },
4775
- ...options.extensions === void 0 ? {} : { extensions: options.extensions },
4776
- ...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles },
4777
- ...options.effectiveTools === void 0 ? {} : { effectiveTools: options.effectiveTools }
4778
- });
4779
- return { model: resolved.model ?? this.root.model, ...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 } };
4676
+ ...options.tools === void 0 ? {} : { tools: [...options.tools] },
4677
+ ...options.skills === void 0 ? {} : { skills: [...options.skills] },
4678
+ ...options.extensions === void 0 ? {} : { extensions: [...options.extensions] },
4679
+ ...options.contextFiles === void 0 ? {} : { contextFiles: [...options.contextFiles] }
4680
+ })));
4681
+ const dynamicModelAliasNames = this.root.dynamicModelAliasNames;
4682
+ const defaults = deepFreeze({ model: { ...this.root.model }, modelAliases: { ...this.root.modelAliases }, ...dynamicModelAliasNames === void 0 ? {} : { dynamicModelAliasNames: [...dynamicModelAliasNames] }, selectorSources: structuredClone(policy?.selectorSources ?? { global: this.root.resourceSelectors ?? {}, project: {} }), settings: structuredClone(options.inheritedExtensionSettings ?? this.root.extensionSettings ?? {}) });
4683
+ const context = Object.freeze({ options: original, cwd, projectCwd: this.root.cwd, agentDir: this.root.agentDir ?? getAgentDir2(), projectTrusted: root.projectTrusted, defaults, capabilities: deepFreeze(structuredClone(capabilities)), knownModels: new Set(this.root.knownModels), availableModels: new Set(this.root.availableModels), signal, mode });
4684
+ const configuration = {
4685
+ ...typeof original.label === "string" ? { label: original.label } : {},
4686
+ ...typeof original.model === "string" ? { model: original.model } : {},
4687
+ tools: [...policy?.effective.tools ?? this.root.resourceSelectors?.tools ?? [], ...options.tools ?? []],
4688
+ skills: [...policy?.effective.skills ?? this.root.resourceSelectors?.skills ?? [], ...options.skills ?? []],
4689
+ extensions: [...policy?.effective.extensions ?? this.root.resourceSelectors?.extensions ?? [], ...(options.extensions ?? []).map((selector) => canonicalExtensionSelector(selector, cwd))],
4690
+ ...options.contextFiles === void 0 ? {} : { contextFiles: [...options.contextFiles] },
4691
+ systemPromptAppend: "",
4692
+ settings: structuredClone(defaults.settings)
4693
+ };
4694
+ for (const key of ["tools", "skills", "extensions"]) if (Array.isArray(original[key])) {
4695
+ const selectors = validateSelectorList(original[key], "agent options", key, "INVALID_METADATA", false) ?? [];
4696
+ configuration[key] = [...policy?.effective[key] ?? this.root.resourceSelectors?.[key] ?? [], ...selectors.map((selector) => key === "extensions" ? canonicalExtensionSelector(selector, cwd) : selector)];
4697
+ }
4698
+ if (original.contextFiles !== void 0) configuration.contextFiles = [...validateContextFileScopes(original.contextFiles, "agent options") ?? []];
4699
+ const hooks = [...this.root.agentPreparationHooks ?? []].sort(byPriorityThenName);
4700
+ const load = this.root.extensionLoad;
4701
+ if (load?.errors.length) {
4702
+ const loaded = new Set(load.loaded.map(extensionIdentity));
4703
+ const declared = new Set(hooks.flatMap(({ optionsSchema, source }) => source !== void 0 && loaded.has(extensionIdentity(source)) && object(optionsSchema?.properties) ? Object.keys(optionsSchema.properties) : []));
4704
+ const unowned = Object.keys(original).find((key) => !isCoreAgentOption(key) && !declared.has(key));
4705
+ if (unowned !== void 0) throw new WorkflowError("INVALID_METADATA", `Agent option ${unowned} has no owner proven to have loaded while extensions failed to load: ${load.errors.join("; ")}`);
4706
+ }
4707
+ for (const hook of hooks) if (hook.optionsSchema !== void 0 && !Value2.Check(hook.optionsSchema, original)) throw new WorkflowError("INVALID_METADATA", `Agent options do not match preparation schema: ${hook.name}`);
4708
+ if (typeof original.model === "string" && this.root.blockedAliases?.has(original.model.split(":")[0] ?? "")) throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${original.model} resolved to ${this.root.blockedAliasTargets?.[original.model.split(":")[0] ?? ""] ?? "an unavailable target"}`);
4709
+ for (const hook of hooks) {
4710
+ if (signal.aborted) throw new WorkflowError("CANCELLED", "Agent preparation cancelled");
4711
+ await hook.prepare(configuration, context);
4712
+ }
4713
+ if (signal.aborted) throw new WorkflowError("CANCELLED", "Agent preparation cancelled");
4714
+ const { model: preparedModel, ...preparedOptions } = configuration;
4715
+ validateAgentOptions(preparedOptions);
4716
+ if (preparedModel !== void 0 && (typeof preparedModel !== "string" || !preparedModel.trim())) throw new WorkflowError("INVALID_METADATA", "Prepared agent model must be a non-empty string");
4717
+ if (preparedModel && this.root.blockedAliases?.has(preparedModel.split(":")[0] ?? "")) throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${preparedModel} resolved to ${this.root.blockedAliasTargets?.[preparedModel.split(":")[0] ?? ""] ?? "an unavailable target"}`);
4718
+ configuration.extensions = configuration.extensions.map((selector) => canonicalExtensionSelector(selector, cwd));
4719
+ const model = physicalModel(preparedModel === void 0 ? this.root.model : resolveModelReference(preparedModel, this.root.modelAliases, this.root.knownModels, this.root.settingsPath), this.root.modelAliases, this.root.knownModels, this.root.settingsPath);
4720
+ const excludedTools = excludedToolNames({ agentOptions: original, ...options.inheritedExcludeTools ? { inheritedExcludeTools: options.inheritedExcludeTools } : {} });
4721
+ const tools = selectResourcesByLayers([configuration.tools], capabilities.tools).filter((tool) => !excludedTools.has(tool));
4722
+ for (const pattern of validateSelectorList(original.tools, "agent options", "tools", "INVALID_METADATA", false) ?? []) if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !capabilities.tools.includes(pattern)) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the prepared agent policy: ${pattern}`);
4723
+ for (const pattern of unmatchedResourcePatterns(configuration.tools, capabilities.tools)) this.root.onResourceWarning?.(`Tool selector currently matches no authorized tool: ${pattern}`);
4724
+ const skills = selectResourcesByLayers([configuration.skills], capabilities.skills);
4725
+ const extensions = selectResourcesByLayers([configuration.extensions], capabilities.extensions);
4726
+ const concrete = validatePreparedConfiguration(root, { ...configuration, model, tools, excludeTools: [...excludedTools], skills: [...configuration.skills, "!*", ...skills], extensions: [...configuration.extensions, "!*", ...extensions], projectTrusted: context.projectTrusted }, capabilities, cwd, false);
4727
+ if (mode === "execution") {
4728
+ if (persistedKey && this.root.runStore?.saveAgentConfiguration) await this.root.runStore.saveAgentConfiguration(persistedKey, concrete);
4729
+ await options.onConfiguration?.(concrete);
4730
+ }
4731
+ return concrete;
4780
4732
  }
4781
4733
  async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
4782
4734
  const executionSignal = signal ?? this.root.runContext?.signal;
4783
4735
  if (!Number.isInteger(options.retries ?? 0) || (options.retries ?? 0) < 0) throw new WorkflowError("INVALID_METADATA", "retries must be a non-negative integer");
4784
4736
  if (options.timeoutMs !== void 0 && options.timeoutMs !== null && (!Number.isInteger(options.timeoutMs) || options.timeoutMs <= 0)) throw new WorkflowError("INVALID_METADATA", "timeoutMs must be null or a positive integer");
4785
- let resolved = this.resolve(options);
4737
+ let resolved;
4786
4738
  let recoveryModel;
4787
4739
  let cwd;
4788
4740
  if (options.parent) {
@@ -4803,6 +4755,16 @@ var WorkflowAgentExecutor = class {
4803
4755
  if (options.cwd) throw new WorkflowError("INVALID_METADATA", "Only child agents or worktree scopes may provide a cwd");
4804
4756
  cwd = this.root.cwd;
4805
4757
  }
4758
+ const configuration = await this.prepare(options, cwd, executionSignal);
4759
+ const parentSkills = options.parent === void 0 ? void 0 : options.capabilities?.skills;
4760
+ options = { ...options, label: configuration.label ?? options.label, configuration, ...parentSkills ? { parentSkills } : {}, capabilities: { tools: configuration.tools, skills: configuration.skills.slice(configuration.skills.lastIndexOf("!*") + 1), extensions: configuration.extensions.slice(configuration.extensions.lastIndexOf("!*") + 1) } };
4761
+ resolved = this.resolve(options);
4762
+ if (!resolved.tools.includes("agent")) customTools = customTools.filter(({ name }) => name !== "agent" && name !== "get_subagent_result" && name !== "steer_subagent");
4763
+ let publishLoadedSkills;
4764
+ customTools = customTools.map((tool) => tool.name !== "agent" ? tool : { ...tool, execute: (...args) => {
4765
+ publishLoadedSkills?.();
4766
+ return tool.execute(...args);
4767
+ } });
4806
4768
  const attempts = [];
4807
4769
  let maxAttempts = (options.retries ?? 0) + 1;
4808
4770
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
@@ -4832,7 +4794,7 @@ var WorkflowAgentExecutor = class {
4832
4794
  const resultTool = defineTool({
4833
4795
  name: "workflow_result",
4834
4796
  label: "Workflow Result",
4835
- description: "Submit the terminal workflow result",
4797
+ description: options.schema ? "Submit the terminal workflow result. Call exactly once to finish the current task or message, with a value matching the required schema. Do not return prose as the final result." : 'Submit the terminal workflow result. Call exactly once to finish the current task or message, with { result: "final response" }. Do not return prose as the final result.',
4836
4798
  parameters: resultSchema.Type(),
4837
4799
  async execute(_id, value) {
4838
4800
  if (!resultSchema.Check(value) || !jsonValue(value)) return { content: [{ type: "text", text: "Result does not match the required schema." }], details: {}, isError: true };
@@ -4864,11 +4826,23 @@ var WorkflowAgentExecutor = class {
4864
4826
  } : void 0;
4865
4827
  try {
4866
4828
  setupFailed = true;
4867
- const prepared = await prepareAgentSetup(this.root, this.transport, task, options, resolved, cwd, attempt, attemptSignal, customTools, resultTool, false, attempts.at(-1)?.error?.message);
4829
+ const prepared = await prepareAgentSetup(this.root, this.transport, task, options, resolved, cwd, attempt, attemptSignal, customTools, resultTool, false);
4868
4830
  setup = prepared.setup;
4869
4831
  const attemptSetup = prepared.setup;
4870
4832
  setupSummary = prepared.summary;
4871
4833
  if (prepared.failure) throw prepared.failure.error;
4834
+ let attemptCapabilities;
4835
+ if (options.agentNodeId !== void 0) {
4836
+ const policy = setup.prepared.resourcePolicy;
4837
+ const sources = policy?.selectorSources;
4838
+ const capabilities = policy?.capabilities ?? options.capabilities;
4839
+ attemptCapabilities = {
4840
+ tools: setup.prepared.tools,
4841
+ skills: selectResourcesByLayers(sources ? resourceSelectorLayers(sources, "skills") : [], capabilities?.skills ?? []),
4842
+ extensions: selectResourcesByLayers(sources ? resourceSelectorLayers(sources, "extensions") : [], capabilities?.extensions ?? [])
4843
+ };
4844
+ this.root.onAgentCapabilities?.(options.agentNodeId, attemptCapabilities, policy?.projectTrusted ?? false);
4845
+ }
4872
4846
  setupFailed = false;
4873
4847
  if (attemptSignal.aborted) throw new WorkflowError("CANCELLED", "Agent cancelled");
4874
4848
  await options.onAttempt?.({ attempt, transport: attemptSetup.transport.id, accounting: zeroAccounting(), setup: setupSummary });
@@ -4904,6 +4878,12 @@ var WorkflowAgentExecutor = class {
4904
4878
  }
4905
4879
  }
4906
4880
  if (policy) setupSummary = { ...setupSummary, resourceSelectors: { ...resourcePolicySummary(policy, attemptSetup.sessionInput.tools), ...inspection ? { skills: [...inspection.skills], extensions: [...inspection.extensions] } : {} } };
4881
+ const nodeId = options.agentNodeId, selected = attemptCapabilities, live = session;
4882
+ publishLoadedSkills = nodeId === void 0 || selected === void 0 || live?.getResourceInspection === void 0 ? void 0 : () => {
4883
+ const loaded = live.getResourceInspection?.();
4884
+ if (loaded) this.root.onAgentCapabilities?.(nodeId, { ...selected, skills: [...loaded.skills] }, attemptSetup.prepared.resourcePolicy?.projectTrusted ?? false);
4885
+ };
4886
+ publishLoadedSkills?.();
4907
4887
  },
4908
4888
  onBeforeComplete: async () => {
4909
4889
  if (options.worktreeOwner) await runStore?.snapshotWorktree(options.worktreeOwner);
@@ -4939,6 +4919,7 @@ var WorkflowAgentExecutor = class {
4939
4919
  const runtimeRun = { id: transportBase.run.runId, namespaceId: transportBase.run.sessionId, workflowName: transportBase.run.workflow.name };
4940
4920
  const providerRecovery = options.providerErrorRecovery;
4941
4921
  const runtimeAgent = { id: transportBase.identity.callSite, structuralPath: [...transportBase.identity.structuralPath], ...options.parent ? { parentId: options.parent } : {} };
4922
+ const attemptControl = attemptControlText(options, attempt, attempts.at(-1)?.error?.message);
4942
4923
  const runtimeRequest = {
4943
4924
  task: setup.prepared.initialPrompt ?? setup.prompt,
4944
4925
  cwd: setup.prepared.cwd,
@@ -4947,6 +4928,7 @@ var WorkflowAgentExecutor = class {
4947
4928
  // Pi sessions execute the prepared native definitions; the executor does not duplicate them as neutral request tools.
4948
4929
  customTools: [],
4949
4930
  ...options.schema ? { resultSchema: options.schema } : {},
4931
+ ...attemptControl ? { turnControl: attemptControl } : {},
4950
4932
  run: runtimeRun,
4951
4933
  agent: runtimeAgent,
4952
4934
  signal: attemptSignal,
@@ -5033,7 +5015,7 @@ import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateT
5033
5015
  import { spawn } from "node:child_process";
5034
5016
  import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
5035
5017
  import { tmpdir } from "node:os";
5036
- import { join as join7 } from "node:path";
5018
+ import { join as join6 } from "node:path";
5037
5019
  function workflowPromptArtifact(prompt) {
5038
5020
  return { extension: ".md", content: prompt };
5039
5021
  }
@@ -5044,23 +5026,23 @@ function workflowResultArtifact(value) {
5044
5026
  async function spawnWorkflowEditor(command, path) {
5045
5027
  const [editor, ...editorArgs] = command.split(" ");
5046
5028
  if (!editor) return null;
5047
- return new Promise((resolve6) => {
5029
+ return new Promise((resolve5) => {
5048
5030
  try {
5049
5031
  const child = spawn(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
5050
5032
  child.once("error", () => {
5051
- resolve6(null);
5033
+ resolve5(null);
5052
5034
  });
5053
5035
  child.once("close", (code) => {
5054
- resolve6(code);
5036
+ resolve5(code);
5055
5037
  });
5056
5038
  } catch {
5057
- resolve6(null);
5039
+ resolve5(null);
5058
5040
  }
5059
5041
  });
5060
5042
  }
5061
5043
  async function openWorkflowArtifact(tui, command, artifact) {
5062
- const directory = await mkdtemp(join7(tmpdir(), "pi-workflow-editor-"));
5063
- const path = join7(directory, `artifact${artifact.extension}`);
5044
+ const directory = await mkdtemp(join6(tmpdir(), "pi-workflow-editor-"));
5045
+ const path = join6(directory, `artifact${artifact.extension}`);
5064
5046
  try {
5065
5047
  await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
5066
5048
  tui.stop();
@@ -5172,6 +5154,15 @@ function formatWorkflowRuntime(durationMs) {
5172
5154
  const remainingMinutes = minutes % 60;
5173
5155
  return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
5174
5156
  }
5157
+ function textBlock(text) {
5158
+ return {
5159
+ render(width) {
5160
+ return text.split("\n").map((line) => truncateToWidth2(line, Math.max(1, width), "\u2026"));
5161
+ },
5162
+ invalidate() {
5163
+ }
5164
+ };
5165
+ }
5175
5166
  var ANSI_SGR_SOURCE = `${String.fromCharCode(27)}\\[[0-9;]*m`;
5176
5167
  var ANSI_SGR = new RegExp(ANSI_SGR_SOURCE);
5177
5168
  function themeWorkflowProgressStyles(theme) {
@@ -5215,13 +5206,13 @@ function elapsedDurationMs(agent, end) {
5215
5206
  return agent.startedAt === void 0 || !Number.isFinite(agent.startedAt) ? void 0 : Math.max(0, end - agent.startedAt);
5216
5207
  }
5217
5208
  function agentActivityLabel(agent) {
5218
- const activity2 = agent.activity;
5219
- if (activity2?.kind === "reasoning" || activity2?.kind === "text") {
5220
- const name = activity2.kind === "reasoning" ? "reasoning" : "responding";
5221
- const text = sanitizeDisplayText(activity2.text);
5209
+ const activity = agent.activity;
5210
+ if (activity?.kind === "reasoning" || activity?.kind === "text") {
5211
+ const name = activity.kind === "reasoning" ? "reasoning" : "responding";
5212
+ const text = sanitizeDisplayText(activity.text);
5222
5213
  return text && text !== name ? `${name} \xB7 ${text}` : name;
5223
5214
  }
5224
- if (activity2?.kind === "tool") return sanitizeDisplayText(activity2.text);
5215
+ if (activity?.kind === "tool") return sanitizeDisplayText(activity.text);
5225
5216
  return sanitizeDisplayText([...agent.toolCalls ?? []].reverse().find(({ state }) => state === "running")?.name ?? "");
5226
5217
  }
5227
5218
  function formatAccountingValue(value) {
@@ -5242,7 +5233,6 @@ function formatAgentDetail(agent, styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now =
5242
5233
  `State: ${state(agent.state)}`,
5243
5234
  ...agent.structuralPath?.length ? [`Structural path: ${agent.structuralPath.join(" > ")}`] : [],
5244
5235
  ...model === void 0 ? [] : [`Model: ${model}`],
5245
- `Role: ${agent.role ?? "(none)"}`,
5246
5236
  `Tools: ${tools}`,
5247
5237
  ...agent.attempts !== void 0 && Number.isFinite(agent.attempts) && agent.attempts > 1 ? [`Attempts: ${String(agent.attempts)}`] : [],
5248
5238
  ...duration === void 0 ? [] : [`Duration: ${formatWorkflowRuntime(duration)}`],
@@ -5285,7 +5275,9 @@ function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
5285
5275
  if (safeWidth >= 80) {
5286
5276
  const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
5287
5277
  const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
5288
- const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
5278
+ const wrapped = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
5279
+ const shift = Math.min(Math.max(0, layout.detailOffset ?? 0), Math.max(0, left2.length - wrapped.length));
5280
+ const right = [...Array(shift).fill(""), ...wrapped];
5289
5281
  return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
5290
5282
  const cell = left2[index] ?? "";
5291
5283
  return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
@@ -5330,8 +5322,8 @@ function createInlineConfirm(requestRender) {
5330
5322
  return {
5331
5323
  ask(title, message) {
5332
5324
  answer(false);
5333
- return new Promise((resolve6) => {
5334
- pending = { title, message, yes: true, resolve: resolve6 };
5325
+ return new Promise((resolve5) => {
5326
+ pending = { title, message, yes: true, resolve: resolve5 };
5335
5327
  requestRender();
5336
5328
  });
5337
5329
  },
@@ -5362,11 +5354,11 @@ function createInlineConfirm(requestRender) {
5362
5354
  }
5363
5355
 
5364
5356
  // src/trajectory.ts
5365
- 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";
5357
+ 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";
5366
5358
 
5367
5359
  // subagents/src/contracts.ts
5368
5360
  import { Type as Type2 } from "@earendil-works/pi-ai";
5369
- import { Value as Value2 } from "typebox/value";
5361
+ import { Value as Value3 } from "typebox/value";
5370
5362
  var SUBAGENTS_TOOL_NAMES = [
5371
5363
  "subagents_run",
5372
5364
  "subagents_inspect",
@@ -5387,17 +5379,18 @@ var SUBAGENTS_RUN_PARAMETERS = Type2.Object({
5387
5379
  label: Type2.Optional(Type2.String({ description: "Optional display label for the subagent" })),
5388
5380
  model: Type2.Optional(Type2.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
5389
5381
  tools: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional ordered tool selectors; candidates start enabled and !* restricts the set" })),
5382
+ excludeTools: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional exact tool names removed after preparation; no patterns, cannot remove workflow_result" })),
5390
5383
  skills: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
5391
5384
  extensions: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
5392
5385
  contextFiles: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional context-file scopes: global, project, cwd" })),
5393
- role: Type2.Optional(Type2.String({ description: "Workflow role name" })),
5394
5386
  worktree: Type2.Optional(Type2.String({ description: "Optional named worktree; requires a clean working tree" })),
5395
5387
  outputSchema: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown(), { description: "Optional JSON schema for the result" })),
5396
5388
  retries: Type2.Optional(Type2.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
5397
5389
  timeoutMs: Type2.Optional(Type2.Union([Type2.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type2.Null()]))
5398
- }, { additionalProperties: false });
5390
+ }, { additionalProperties: true });
5399
5391
  var SUBAGENTS_INSPECT_PARAMETERS = Type2.Object({
5400
- id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" }))
5392
+ id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" })),
5393
+ 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" }))
5401
5394
  }, { additionalProperties: false });
5402
5395
  var SUBAGENTS_ID_PARAMETERS = Type2.Object({
5403
5396
  id: Type2.String({ description: "Subagent ID" })
@@ -5412,15 +5405,23 @@ var SUBAGENTS_STOP_PARAMETERS = Type2.Object({
5412
5405
  var SUBAGENTS_RETRY_PARAMETERS = Type2.Object({
5413
5406
  id: Type2.String({ description: "Failed or stopped subagent ID to retry" })
5414
5407
  }, { additionalProperties: false });
5415
- var SUBAGENTS_TOOL_SCHEMAS = {
5416
- subagents_run: SUBAGENTS_RUN_PARAMETERS,
5417
- subagents_inspect: SUBAGENTS_INSPECT_PARAMETERS,
5418
- subagents_steer: SUBAGENTS_STEER_PARAMETERS,
5419
- subagents_stop: SUBAGENTS_STOP_PARAMETERS,
5420
- subagents_retry: SUBAGENTS_RETRY_PARAMETERS
5421
- };
5408
+ var SUBAGENT_STATUS_OUTPUT = Type2.Object({
5409
+ id: Type2.String(),
5410
+ state: Type2.String(),
5411
+ sessionId: Type2.Optional(Type2.String()),
5412
+ startedAt: Type2.Optional(Type2.Number()),
5413
+ finishedAt: Type2.Optional(Type2.Number()),
5414
+ attempts: Type2.Optional(Type2.Number()),
5415
+ attemptDetails: Type2.Optional(Type2.Array(Type2.Unknown())),
5416
+ worktree: Type2.Optional(Type2.Object({ path: Type2.String(), branch: Type2.String() })),
5417
+ error: Type2.Optional(Type2.Object({ code: Type2.String(), message: Type2.String() })),
5418
+ progress: Type2.Optional(Type2.Unknown()),
5419
+ value: Type2.Optional(Type2.Unknown())
5420
+ });
5421
+ var SUBAGENTS_INSPECT_OUTPUT = Type2.Union([SUBAGENT_STATUS_OUTPUT, Type2.Array(Type2.Object({ id: Type2.String(), state: Type2.String() }))]);
5422
+ var SUBAGENTS_STEER_OUTPUT = Type2.Object({ id: Type2.String(), accepted: Type2.Boolean() });
5422
5423
  function normalizeSubagentRunRequest(value) {
5423
- if (!Value2.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
5424
+ if (!Value3.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
5424
5425
  if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
5425
5426
  const snapshot = structuredClone(value);
5426
5427
  snapshot.mode ??= "background";
@@ -5510,8 +5511,8 @@ function progressValue(value) {
5510
5511
  const accounting2 = accountingValue(record2.accounting);
5511
5512
  const toolCalls = toolCallsValue(record2.toolCalls);
5512
5513
  if (!accounting2 || !toolCalls) return void 0;
5513
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5514
- if (record2.activity !== void 0 && activity2 === void 0) return void 0;
5514
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5515
+ if (record2.activity !== void 0 && activity === void 0) return void 0;
5515
5516
  const rawState = record2.state;
5516
5517
  let state;
5517
5518
  if (rawState !== void 0) {
@@ -5525,16 +5526,16 @@ function progressValue(value) {
5525
5526
  }
5526
5527
  const lastEventAt = record2.lastEventAt;
5527
5528
  if (lastEventAt !== void 0 && !nonnegativeInteger(lastEventAt)) return void 0;
5528
- return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity2 === void 0 ? {} : { activity: activity2 }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5529
+ return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity === void 0 ? {} : { activity }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5529
5530
  }
5530
5531
  function legacyProgressValue(record2) {
5531
5532
  const accounting2 = legacyAccountingValue(record2);
5532
5533
  const toolCalls = record2.toolCalls === void 0 ? [] : toolCallsValue(record2.toolCalls);
5533
5534
  if (!accounting2 || !toolCalls) return void 0;
5534
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5535
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5535
5536
  const lastEventAt = record2.lastEventAt;
5536
- if (record2.activity !== void 0 && activity2 === void 0 || lastEventAt !== void 0 && !nonnegativeInteger(lastEventAt)) return void 0;
5537
- return { accounting: accounting2, toolCalls, ...activity2 === void 0 ? {} : { activity: activity2 }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5537
+ if (record2.activity !== void 0 && activity === void 0 || lastEventAt !== void 0 && !nonnegativeInteger(lastEventAt)) return void 0;
5538
+ return { accounting: accounting2, toolCalls, ...activity === void 0 ? {} : { activity }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5538
5539
  }
5539
5540
  function attemptValue(value) {
5540
5541
  const record2 = objectValue(value);
@@ -5618,15 +5619,29 @@ var WORKFLOW_TOOL_PARAMETERS = Type3.Object({
5618
5619
  }, { additionalProperties: false });
5619
5620
  var WORKFLOW_STATUS_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Workflow run ID visible in the current project" }) }, { additionalProperties: false });
5620
5621
  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" })) });
5622
+ var WORKFLOW_ERROR_OUTPUT = Type3.Object({ code: Type3.String(), message: Type3.String() });
5623
+ var WORKFLOW_LAUNCH_OUTPUT = Type3.Object({ runId: Type3.String(), state: Type3.String(), detached: Type3.Optional(Type3.Boolean()), value: Type3.Optional(Type3.Unknown()) });
5624
+ 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()) })) });
5625
+ var WORKFLOW_STOP_OUTPUT = Type3.Object({ runId: Type3.String(), state: Type3.String(), stopped: Type3.Boolean(), reason: Type3.Optional(Type3.String()) });
5626
+ 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()) });
5627
+ 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()) });
5628
+ 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()) });
5629
+ var WORKFLOW_CATALOG_OUTPUT = Type3.Union([
5630
+ 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()) }),
5631
+ Type3.Object({ name: Type3.String(), version: Type3.String(), headline: Type3.String(), description: Type3.String(), input: Type3.Unknown(), output: Type3.Unknown() }),
5632
+ WORKFLOW_CATALOG_ALIAS_OUTPUT,
5633
+ Type3.Object({ error: Type3.Object({ code: Type3.String(), name: Type3.String(), message: Type3.String() }) })
5634
+ ]);
5635
+ var SCRIPT_CALL_TEXT_LIMIT_BYTES = 16 * 1024;
5621
5636
 
5622
5637
  // subagents/src/manager.ts
5623
5638
  import { randomUUID as randomUUID3 } from "node:crypto";
5624
5639
  import { chmod, link as link2, mkdir as mkdir3, open as open2, readFile as readFile4, readdir as readdir3, rename as rename4, rm as rm5, stat as stat3 } from "node:fs/promises";
5625
- import { join as join9 } from "node:path";
5626
- import { getAgentDir as getAgentDir6 } from "@earendil-works/pi-coding-agent";
5640
+ import { join as join8 } from "node:path";
5641
+ import { getAgentDir as getAgentDir5 } from "@earendil-works/pi-coding-agent";
5627
5642
 
5628
5643
  // subagents/src/worktree.ts
5629
- import { join as join8 } from "node:path";
5644
+ import { join as join7 } from "node:path";
5630
5645
  function syntheticRun(context) {
5631
5646
  return {
5632
5647
  id: context.runId,
@@ -5649,7 +5664,7 @@ function syntheticSnapshot(context) {
5649
5664
  ...context.worktreePostCreateCommandSource === void 0 ? {} : { settingsSources: { concurrency: "subagents", modelAliases: "subagents", worktreePostCreateCommand: context.worktreePostCreateCommandSource } },
5650
5665
  models: [],
5651
5666
  tools: [],
5652
- agentTypes: [],
5667
+ agentConfigurations: {},
5653
5668
  schemas: []
5654
5669
  };
5655
5670
  }
@@ -5688,7 +5703,7 @@ function createRunStoreWorktreeAdapter(home) {
5688
5703
  };
5689
5704
  }
5690
5705
  function defaultWorktreeHome(storageDirectory3) {
5691
- return join8(storageDirectory3, ".worktrees");
5706
+ return join7(storageDirectory3, ".worktrees");
5692
5707
  }
5693
5708
 
5694
5709
  // subagents/src/manager.ts
@@ -5728,22 +5743,11 @@ function rootModel(context) {
5728
5743
  const thinking = context.extensionContext.thinkingLevel;
5729
5744
  return { provider: model.provider, model: model.id, ...thinking === void 0 ? {} : { thinking } };
5730
5745
  }
5731
- function standaloneExtensionSettingsPath(cwd, agentDir, trustedProject, external) {
5732
- return trustedProject && external.projectExtensionSettings !== void 0 ? workflowProjectSettingsPath(cwd) : workflowSettingsPath(agentDir);
5733
- }
5734
- function validateStandaloneExtensionSources(context, external, registry, agentDir) {
5735
- const cwd = context.extensionContext.cwd;
5736
- const globalSettingsPath = workflowSettingsPath(agentDir);
5737
- const trustedProject = context.extensionContext.isProjectTrusted();
5738
- if (external.globalExtensionSettings !== void 0) registry.validateExtensionSettings(external.globalExtensionSettings, { source: "global", cwd, projectTrusted: trustedProject, settingsPath: globalSettingsPath });
5739
- if (trustedProject && external.projectExtensionSettings !== void 0) registry.validateExtensionSettings(external.projectExtensionSettings, { source: "project", cwd, projectTrusted: true, settingsPath: workflowProjectSettingsPath(cwd) });
5740
- }
5741
- function executionRoot(context, dependencies, signal, runId, worktree, external) {
5746
+ function executionRoot(context, dependencies, signal, runId, worktree) {
5742
5747
  const extensionContext = context.extensionContext;
5743
5748
  const model = rootModel(context);
5744
- const agentDir = dependencies.agentDir ?? getAgentDir6();
5749
+ const agentDir = dependencies.agentDir ?? getAgentDir5();
5745
5750
  const currentTrustedProject = extensionContext.isProjectTrusted();
5746
- if (external.projectTrusted && !currentTrustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
5747
5751
  const trustedProject = currentTrustedProject;
5748
5752
  const settingsPath = workflowSettingsPath(agentDir);
5749
5753
  const settings = resolveWorkflowSettings(extensionContext.cwd, trustedProject, settingsPath);
@@ -5766,11 +5770,13 @@ function executionRoot(context, dependencies, signal, runId, worktree, external)
5766
5770
  signal
5767
5771
  };
5768
5772
  const registry = loadingRegistry();
5769
- validateStandaloneExtensionSources(context, external, registry, agentDir);
5770
- const extensionSettings = trustedProject && external.projectExtensionSettings !== void 0 ? external.projectExtensionSettings : external.globalExtensionSettings;
5771
- const extensionSettingsPath = standaloneExtensionSettingsPath(extensionContext.cwd, agentDir, trustedProject, external);
5773
+ if (settings.global.extensionSettings !== void 0) registry.validateExtensionSettings(settings.global.extensionSettings, { source: "global", cwd: extensionContext.cwd, projectTrusted: trustedProject, settingsPath: settings.globalSettingsPath });
5774
+ if (trustedProject && settings.project.extensionSettings !== void 0) registry.validateExtensionSettings(settings.project.extensionSettings, { source: "project", cwd: extensionContext.cwd, projectTrusted: true, settingsPath: settings.projectSettingsPath });
5775
+ const extensionSettings = settings.effective.extensionSettings;
5776
+ const extensionSettingsPath = settings.sources.extensionSettings ?? settingsPath;
5772
5777
  return {
5773
5778
  cwd: extensionContext.cwd,
5779
+ projectTrusted: trustedProject,
5774
5780
  model,
5775
5781
  tools,
5776
5782
  resourceSelectors: resourcePolicy.effective,
@@ -5780,9 +5786,10 @@ function executionRoot(context, dependencies, signal, runId, worktree, external)
5780
5786
  availableModels,
5781
5787
  knownModels,
5782
5788
  ...Object.keys(staticAliases).length ? { modelAliases: staticAliases } : {},
5789
+ dynamicModelAliasNames: [],
5783
5790
  settingsPath: settings.sources.modelAliases,
5784
5791
  extensionSettingsPath,
5785
- agentDefinitions: external.agentDefinitions,
5792
+ agentPreparationHooks: registry.agentPreparationHooks(),
5786
5793
  agentSetupHooks: registry.agentSetupHooks(),
5787
5794
  validateExtensionSettings: registry.validateExtensionSettings,
5788
5795
  agentResourcePolicy: () => structuredClone(resourcePolicy),
@@ -5798,11 +5805,10 @@ async function addDynamicAliases(context, signal, root) {
5798
5805
  const trustedProject = resourcePolicy?.projectTrusted ?? context.extensionContext.isProjectTrusted();
5799
5806
  const dynamicAliases = await registry.resolveModelAliases({ cwd: context.extensionContext.cwd, projectTrusted: trustedProject, rootModel: root.model, knownModels: root.knownModels ?? /* @__PURE__ */ new Set(), availableModels: root.availableModels ?? /* @__PURE__ */ new Set(), signal }, new Set(Object.keys(staticAliases)));
5800
5807
  validateModelAliasAvailability(dynamicAliases, Object.keys(dynamicAliases), root.availableModels ?? /* @__PURE__ */ new Set(), root.knownModels ?? /* @__PURE__ */ new Set(), root.settingsPath);
5801
- return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases } };
5808
+ return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases }, dynamicModelAliasNames: Object.keys(dynamicAliases) };
5802
5809
  }
5803
5810
  function executionOptions(request, onAttempt, onProgress) {
5804
- const role = request.role;
5805
- const label2 = request.label ?? roleNameOf(role) ?? "subagent";
5811
+ const label2 = request.label ?? "subagent";
5806
5812
  return {
5807
5813
  label: label2,
5808
5814
  workflowName: WORKFLOW_NAME,
@@ -5811,35 +5817,35 @@ function executionOptions(request, onAttempt, onProgress) {
5811
5817
  ...request.skills === void 0 ? {} : { skills: request.skills },
5812
5818
  ...request.extensions === void 0 ? {} : { extensions: request.extensions },
5813
5819
  ...request.contextFiles === void 0 ? {} : { contextFiles: request.contextFiles },
5814
- ...role === void 0 ? {} : { role },
5815
5820
  ...request.worktree === void 0 ? {} : { worktreeOwner: structuralPath("worktree", "named", request.worktree) },
5816
5821
  ...request.outputSchema === void 0 ? {} : { schema: request.outputSchema },
5817
5822
  ...request.retries === void 0 ? {} : { retries: request.retries },
5818
5823
  ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
5824
+ agentOptions: Object.fromEntries(Object.entries(request).filter(([key]) => !["prompt", "mode", "worktree"].includes(key))),
5819
5825
  onAttempt,
5820
5826
  onProgress
5821
5827
  };
5822
5828
  }
5823
5829
  function storageDirectory(dependencies) {
5824
- return dependencies.storageDir ?? join9(dependencies.agentDir ?? getAgentDir6(), STORAGE_DIRECTORY);
5830
+ return dependencies.storageDir ?? join8(dependencies.agentDir ?? getAgentDir5(), STORAGE_DIRECTORY);
5825
5831
  }
5826
5832
  function runDirectory(root, id) {
5827
- return join9(root, id);
5833
+ return join8(root, id);
5828
5834
  }
5829
5835
  function requestPath(directory) {
5830
- return join9(directory, "request.json");
5836
+ return join8(directory, "request.json");
5831
5837
  }
5832
5838
  function configurationPath(directory) {
5833
- return join9(directory, CONFIGURATION_FILE);
5839
+ return join8(directory, CONFIGURATION_FILE);
5834
5840
  }
5835
5841
  function statusPath(directory) {
5836
- return join9(directory, "status.json");
5842
+ return join8(directory, "status.json");
5837
5843
  }
5838
5844
  function resultPath(directory) {
5839
- return join9(directory, "result.json");
5845
+ return join8(directory, "result.json");
5840
5846
  }
5841
5847
  function failurePath(directory) {
5842
- return join9(directory, "failure.json");
5848
+ return join8(directory, "failure.json");
5843
5849
  }
5844
5850
  async function secureDirectory(directory) {
5845
5851
  await mkdir3(directory, { recursive: true, mode: 448 });
@@ -5922,7 +5928,7 @@ async function releaseOwnerMarker(path, token) {
5922
5928
  }
5923
5929
  async function acquireStorageOwner(root, owner, liveness) {
5924
5930
  await secureDirectory(root);
5925
- const path = join9(root, OWNER_FILE);
5931
+ const path = join8(root, OWNER_FILE);
5926
5932
  for (let attempt = 0; attempt < MAX_STORAGE_OWNER_ATTEMPTS; attempt += 1) {
5927
5933
  const marker = { ...owner, acquiredAt: Date.now() };
5928
5934
  try {
@@ -5993,32 +5999,8 @@ function checkedRequest(request) {
5993
5999
  return normalizeSubagentRunRequest(request);
5994
6000
  }
5995
6001
  function decodePersistedStandaloneExternalConfiguration(value) {
5996
- if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean") return void 0;
5997
- const globalExtensionSettings = value.globalExtensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.globalExtensionSettings);
5998
- const projectExtensionSettings = value.projectExtensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.projectExtensionSettings);
5999
- if (value.globalExtensionSettings !== void 0 && globalExtensionSettings === void 0 || value.projectExtensionSettings !== void 0 && projectExtensionSettings === void 0) return void 0;
6000
- if (!object(value.agentDefinitions)) return void 0;
6001
- const agentDefinitions = /* @__PURE__ */ Object.create(null);
6002
- for (const [name, definition] of Object.entries(value.agentDefinitions)) {
6003
- const decoded = decodeAgentDefinition(definition);
6004
- if (decoded === void 0) return void 0;
6005
- agentDefinitions[name] = decoded;
6006
- }
6007
- return deepFreeze({ projectTrusted: value.projectTrusted, ...globalExtensionSettings === void 0 ? {} : { globalExtensionSettings }, ...projectExtensionSettings === void 0 ? {} : { projectExtensionSettings }, agentDefinitions });
6008
- }
6009
- function standaloneExternalConfiguration(context, dependencies) {
6010
- const cwd = context.extensionContext.cwd;
6011
- const trustedProject = context.extensionContext.isProjectTrusted();
6012
- const agentDir = dependencies.agentDir ?? getAgentDir6();
6013
- const resolution = resolveWorkflowSettings(cwd, trustedProject, workflowSettingsPath(agentDir));
6014
- const globalExtensionSettings = resolution.global.extensionSettings;
6015
- const projectExtensionSettings = resolution.project.extensionSettings;
6016
- return deepFreeze({
6017
- projectTrusted: trustedProject,
6018
- ...globalExtensionSettings === void 0 ? {} : { globalExtensionSettings: structuredClone(globalExtensionSettings) },
6019
- ...projectExtensionSettings === void 0 ? {} : { projectExtensionSettings: structuredClone(projectExtensionSettings) },
6020
- agentDefinitions: structuredClone(loadAgentDefinitions(cwd, agentDir, trustedProject))
6021
- });
6002
+ if (!object(value) || value.version !== 1) return void 0;
6003
+ return decodePreparedAgentConfiguration(value.configuration);
6022
6004
  }
6023
6005
  function checkedId(request) {
6024
6006
  if (typeof request.id !== "string" || !validSubagentId(request.id)) throw new WorkflowError("RUN_NOT_FOUND", `Unknown subagent ${request.id}`);
@@ -6203,8 +6185,8 @@ function isPreparedSession(value) {
6203
6185
  const textFields = ["initialPrompt", "agentDir", "systemPromptPath", "systemPromptAppend", "piRuntimeError"];
6204
6186
  if (textFields.some((field) => record2[field] !== void 0 && typeof record2[field] !== "string")) return false;
6205
6187
  if (record2.systemPrompt !== void 0 && (typeof record2.systemPrompt !== "string" || record2.systemPrompt.length > SUBAGENT_SYSTEM_PROMPT_LIMIT)) return false;
6206
- const runtime2 = recordValue(record2.piRuntime);
6207
- if (record2.piRuntime !== void 0 && (!runtime2 || typeof runtime2.executable !== "string" || !runtime2.executable.trim() || runtime2.entrypoint !== void 0 && (typeof runtime2.entrypoint !== "string" || !runtime2.entrypoint.trim()))) return false;
6188
+ const runtime = recordValue(record2.piRuntime);
6189
+ if (record2.piRuntime !== void 0 && (!runtime || typeof runtime.executable !== "string" || !runtime.executable.trim() || runtime.entrypoint !== void 0 && (typeof runtime.entrypoint !== "string" || !runtime.entrypoint.trim()))) return false;
6208
6190
  return true;
6209
6191
  }
6210
6192
  function isLiveSessionHandoff(value) {
@@ -6238,19 +6220,19 @@ function progressValue2(value) {
6238
6220
  const toolCalls = toolCallsValue2(record2.toolCalls);
6239
6221
  const state = record2.state === void 0 ? void 0 : sessionStateValue(record2.state);
6240
6222
  if (!accounting2 || !toolCalls || record2.state !== void 0 && state === void 0) return void 0;
6241
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
6242
- if (record2.activity !== void 0 && activity2 === void 0) return void 0;
6223
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
6224
+ if (record2.activity !== void 0 && activity === void 0) return void 0;
6243
6225
  if (record2.lastEventAt !== void 0 && (!Number.isSafeInteger(record2.lastEventAt) || record2.lastEventAt < 0)) return void 0;
6244
- return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity2 === void 0 ? {} : { activity: activity2 }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6226
+ return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity === void 0 ? {} : { activity }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6245
6227
  }
6246
6228
  function legacyProgressValue2(record2) {
6247
6229
  const accounting2 = legacyAccountingValue(record2);
6248
6230
  const toolCalls = record2.toolCalls === void 0 ? [] : toolCallsValue2(record2.toolCalls);
6249
6231
  if (!accounting2 || !toolCalls) return void 0;
6250
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
6251
- if (record2.activity !== void 0 && activity2 === void 0) return void 0;
6232
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
6233
+ if (record2.activity !== void 0 && activity === void 0) return void 0;
6252
6234
  if (record2.lastEventAt !== void 0 && (!Number.isSafeInteger(record2.lastEventAt) || record2.lastEventAt < 0)) return void 0;
6253
- return { accounting: accounting2, toolCalls, ...activity2 === void 0 ? {} : { activity: activity2 }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6235
+ return { accounting: accounting2, toolCalls, ...activity === void 0 ? {} : { activity }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6254
6236
  }
6255
6237
  function worktreeContextValue(value) {
6256
6238
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -6329,7 +6311,7 @@ async function createRunStorage(root, id, request, status, external) {
6329
6311
  created = true;
6330
6312
  await chmod(directory, 448);
6331
6313
  await atomicJson(requestPath(directory), request);
6332
- await atomicJson(configurationPath(directory), external);
6314
+ if (external !== void 0) await atomicJson(configurationPath(directory), { version: 1, configuration: external });
6333
6315
  await atomicJson(statusPath(directory), status);
6334
6316
  return directory;
6335
6317
  } catch (error) {
@@ -6511,6 +6493,7 @@ var PersistentSubagentManager = class {
6511
6493
  }
6512
6494
  dependencies;
6513
6495
  activeRuns = /* @__PURE__ */ new Map();
6496
+ pendingStarts = /* @__PURE__ */ new Set();
6514
6497
  activeRunCount = 0;
6515
6498
  terminalSummaries = /* @__PURE__ */ new Map();
6516
6499
  reconciliationErrors = /* @__PURE__ */ new Map();
@@ -6554,32 +6537,44 @@ var PersistentSubagentManager = class {
6554
6537
  await this.ensureInitialized();
6555
6538
  if (this.disposed) throw new WorkflowError("CANCELLED", "Subagent manager is disposed");
6556
6539
  if (context.signal?.aborted) throw new WorkflowError("CANCELLED", "Subagent cancelled");
6557
- const external = frozenExternal ?? standaloneExternalConfiguration(context, this.dependencies);
6558
- if (external.projectTrusted && !context.extensionContext.isProjectTrusted()) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
6540
+ const external = frozenExternal;
6541
+ if (external?.projectTrusted && !context.extensionContext.isProjectTrusted()) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
6559
6542
  const id = randomUUID3();
6560
6543
  const startedAt = Date.now();
6561
6544
  const sessionId = context.extensionContext.sessionManager.getSessionId();
6562
6545
  if (!sessionId.trim()) throw new WorkflowError("INTERNAL_ERROR", "Current Pi session identity is unavailable");
6563
6546
  const owner = this.runOwner;
6564
6547
  if (!owner) throw new WorkflowError("INTERNAL_ERROR", "Subagent storage owner identity is unavailable");
6565
- const settings = resolveWorkflowSettings(context.extensionContext.cwd, context.extensionContext.isProjectTrusted(), workflowSettingsPath(this.dependencies.agentDir ?? getAgentDir6()));
6548
+ const settings = resolveWorkflowSettings(context.extensionContext.cwd, context.extensionContext.isProjectTrusted(), workflowSettingsPath(this.dependencies.agentDir ?? getAgentDir5()));
6566
6549
  const concurrency = settings.effective.concurrency;
6567
6550
  if (this.activeRunCount >= concurrency) throw new WorkflowError("AGENT_FAILED", `Subagent concurrency limit reached (${String(this.activeRunCount)}/${String(concurrency)} active runs); no queue is maintained. Retry after an active run settles.`);
6568
6551
  this.activeRunCount += 1;
6569
6552
  const controller = new AbortController();
6570
6553
  const initialStatus = { id, sessionId, state: "running", startedAt, owner: { ...owner } };
6554
+ let finishStart;
6555
+ const pendingStart = new Promise((resolve5) => {
6556
+ finishStart = resolve5;
6557
+ });
6558
+ this.pendingStarts.add(pendingStart);
6571
6559
  let directory;
6572
6560
  try {
6573
6561
  directory = await createRunStorage(storageDirectory(this.dependencies), id, snapshot, initialStatus, external);
6562
+ if (this.disposePromise !== void 0 || context.signal?.aborted) {
6563
+ await atomicJson(statusPath(directory), reconciledStatus(initialStatus, "stopped", Date.now()));
6564
+ throw new WorkflowError("CANCELLED", "Subagent cancelled");
6565
+ }
6574
6566
  } catch (error) {
6575
6567
  this.activeRunCount -= 1;
6576
6568
  if (error instanceof WorkflowError) throw error;
6577
6569
  throw internalStorageError(error, `Unable to start subagent ${id}`);
6570
+ } finally {
6571
+ this.pendingStarts.delete(pendingStart);
6572
+ finishStart();
6578
6573
  }
6579
6574
  const current = {};
6580
6575
  let resolveTerminal;
6581
- const terminal = new Promise((resolve6) => {
6582
- resolveTerminal = resolve6;
6576
+ const terminal = new Promise((resolve5) => {
6577
+ resolveTerminal = resolve5;
6583
6578
  });
6584
6579
  const executorOwnership = { default: true };
6585
6580
  const execution = Promise.resolve().then(async () => {
@@ -6607,12 +6602,9 @@ var PersistentSubagentManager = class {
6607
6602
  throw new WorkflowError("CANCELLED", "Subagent cancelled");
6608
6603
  }
6609
6604
  }
6610
- const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree, external);
6611
- const root = loadingRegistry().modelAliases().length === 0 ? baseRoot : await addDynamicAliases(context, controller.signal, baseRoot);
6605
+ const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree);
6606
+ const root = external !== void 0 || loadingRegistry().modelAliases().length === 0 ? baseRoot : await addDynamicAliases(context, controller.signal, baseRoot);
6612
6607
  const transport = this.dependencies.transport ?? localAgentTransport;
6613
- const injectedExecutor = this.dependencies.createExecutor?.(root, transport);
6614
- executorOwnership.default = injectedExecutor === void 0;
6615
- if (current.run) current.run.executorOwnsSession = executorOwnership.default;
6616
6608
  const setSteer = (handler) => {
6617
6609
  const run2 = current.run;
6618
6610
  if (run2) this.registerSteerHandler(run2, handler);
@@ -6653,6 +6645,13 @@ var PersistentSubagentManager = class {
6653
6645
  if (run2.state !== "running") return;
6654
6646
  emitUpdate(run2);
6655
6647
  }, (progress) => this.recordProgress(current.run, progress));
6648
+ if (external !== void 0) options.configuration = structuredClone(external);
6649
+ options.onConfiguration = (configuration) => atomicJson(configurationPath(directory), { version: 1, configuration });
6650
+ const cwd = worktree?.cwd ?? root.cwd;
6651
+ options.configuration = await new WorkflowAgentExecutor(root, transport).prepare(options, cwd, controller.signal);
6652
+ const injectedExecutor = this.dependencies.createExecutor?.({ ...root, extensionSettings: options.configuration.settings }, transport);
6653
+ executorOwnership.default = injectedExecutor === void 0;
6654
+ if (current.run) current.run.executorOwnsSession = executorOwnership.default;
6656
6655
  if (injectedExecutor) return injectedExecutor.execute(snapshot.prompt, options, controller.signal, setSteer);
6657
6656
  return new WorkflowAgentExecutor(root, transport).execute(snapshot.prompt, options, controller.signal, [], setSteer);
6658
6657
  });
@@ -6703,9 +6702,9 @@ var PersistentSubagentManager = class {
6703
6702
  }
6704
6703
  return inspection;
6705
6704
  }
6706
- return this.inspectList();
6705
+ return this.inspectList(request.scope === "all" ? void 0 : context.extensionContext.sessionManager.getSessionId());
6707
6706
  }
6708
- async inspectList() {
6707
+ async inspectList(sessionId) {
6709
6708
  const root = storageDirectory(this.dependencies);
6710
6709
  await secureDirectory(root);
6711
6710
  const entries = await readdir3(root, { withFileTypes: true });
@@ -6716,7 +6715,7 @@ var PersistentSubagentManager = class {
6716
6715
  try {
6717
6716
  const active = this.activeRuns.get(entry.name);
6718
6717
  const status = publicStatus(active ? persistedStatus(active) : this.terminalSummaries.get(entry.name) ?? await loadPersistedStatus(root, entry.name, false));
6719
- if (status.startedAt === void 0) continue;
6718
+ if (status.startedAt === void 0 || sessionId !== void 0 && status.sessionId !== sessionId) continue;
6720
6719
  statuses.push({ status, startedAt: status.startedAt });
6721
6720
  } catch (error) {
6722
6721
  if (error instanceof WorkflowError && (error.code === "RUN_NOT_FOUND" || error instanceof InvalidPersistedSubagentStatusError)) continue;
@@ -6760,11 +6759,11 @@ var PersistentSubagentManager = class {
6760
6759
  }
6761
6760
  async dispose() {
6762
6761
  if (this.disposePromise) return this.disposePromise;
6762
+ this.disposed = true;
6763
6763
  this.disposePromise = (async () => {
6764
6764
  await this.initialization;
6765
- this.disposed = true;
6766
6765
  const runs = [...this.activeRuns.values()];
6767
- await Promise.allSettled(runs.map((run) => this.stopRun(run, true)));
6766
+ await Promise.allSettled([...this.pendingStarts, ...runs.map((run) => this.stopRun(run, true))]);
6768
6767
  await Promise.allSettled([...this.notificationPromises]);
6769
6768
  const owner = this.storageOwner;
6770
6769
  this.storageOwner = void 0;
@@ -7095,9 +7094,8 @@ var PersistentSubagentManager = class {
7095
7094
  const notify = this.dependencies.notify;
7096
7095
  if (!notify || run.notificationSent || run.disposed) return;
7097
7096
  run.notificationSent = true;
7098
- const role = roleNameOf(run.request.role) ?? "none";
7099
- const label2 = run.request.label?.trim() || (role === "none" ? "subagent" : role);
7100
- const notification = { id: run.id, label: label2, role, state: run.state, ...run.error === void 0 ? {} : { error: run.error } };
7097
+ const label2 = run.request.label?.trim() || "subagent";
7098
+ const notification = { id: run.id, label: label2, state: run.state, ...run.error === void 0 ? {} : { error: run.error } };
7101
7099
  const pending = Promise.resolve().then(() => notify(notification));
7102
7100
  this.notificationPromises.add(pending);
7103
7101
  void pending.then(() => {
@@ -7145,8 +7143,8 @@ function createSubagentManager(dependencies = {}) {
7145
7143
 
7146
7144
  // subagents/src/navigator.ts
7147
7145
  import { readFile as readFile5 } from "node:fs/promises";
7148
- import { join as join10 } from "node:path";
7149
- import { copyToClipboard as copyToClipboard2, getAgentDir as getAgentDir7, SettingsManager as SettingsManager4 } from "@earendil-works/pi-coding-agent";
7146
+ import { join as join9 } from "node:path";
7147
+ import { copyToClipboard as copyToClipboard2, getAgentDir as getAgentDir6, SettingsManager as SettingsManager4 } from "@earendil-works/pi-coding-agent";
7150
7148
  import { Editor, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
7151
7149
  var MAX_DETAIL_TEXT = 4e3;
7152
7150
  function objectValue2(value) {
@@ -7169,7 +7167,7 @@ function managerContext(context, waitForForeground = true, includeAttemptMetadat
7169
7167
  async function loadRequest(storageDirectory3, id) {
7170
7168
  if (!safeRunId(id)) return {};
7171
7169
  try {
7172
- const value = JSON.parse(await readFile5(join10(storageDirectory3, id, "request.json"), "utf8"));
7170
+ const value = JSON.parse(await readFile5(join9(storageDirectory3, id, "request.json"), "utf8"));
7173
7171
  return { request: normalizeSubagentRunRequest(value) };
7174
7172
  } catch (error) {
7175
7173
  if (isFileNotFound(error)) return {};
@@ -7202,18 +7200,11 @@ async function inspectEntry(manager, storageDirectory3, entry, context) {
7202
7200
  entry: { status: inspected.status, ...request.request === void 0 ? {} : { request: request.request }, ...request.error === void 0 ? {} : { requestError: request.error } }
7203
7201
  };
7204
7202
  }
7205
- function requestRole(request) {
7206
- const role = request?.role;
7207
- if (typeof role === "string" && role.trim()) return role.trim();
7208
- const override = objectValue2(role);
7209
- return typeof override?.name === "string" && override.name.trim() ? override.name.trim() : "none";
7210
- }
7211
7203
  function shortId(id) {
7212
7204
  return id.length > 12 ? id.slice(0, 8) : id;
7213
7205
  }
7214
7206
  function entryName(entry) {
7215
- const role = requestRole(entry.request);
7216
- return boundedText(entry.request?.label?.trim() || (role === "none" ? shortId(entry.status.id) : role), 256);
7207
+ return boundedText(entry.request?.label?.trim() || shortId(entry.status.id), 256);
7217
7208
  }
7218
7209
  function listNames(entries) {
7219
7210
  const names = entries.map(entryName);
@@ -7228,8 +7219,8 @@ function pickerLabels(entries) {
7228
7219
  const name = names[index] ?? "";
7229
7220
  const model = status.progress?.state?.model;
7230
7221
  const cost = formatCost(status.progress?.accounting.cost);
7231
- const runtime2 = status.startedAt === void 0 ? "" : ` runtime=${formatWorkflowRuntime((status.finishedAt ?? Date.now()) - status.startedAt)}`;
7232
- return `${runStateGlyph(status.state, "\u2826")} ${name} ${status.state}${model ? ` ${boundedText(model.model, 256)}${model.thinking ? `:${model.thinking}` : ""}` : ""}${cost ? ` ${cost}` : ""}${runtime2}`;
7222
+ const runtime = status.startedAt === void 0 ? "" : ` runtime=${formatWorkflowRuntime((status.finishedAt ?? Date.now()) - status.startedAt)}`;
7223
+ return `${runStateGlyph(status.state, "\u2826")} ${name} ${status.state}${model ? ` ${boundedText(model.model, 256)}${model.thinking ? `:${model.thinking}` : ""}` : ""}${cost ? ` ${cost}` : ""}${runtime}`;
7233
7224
  });
7234
7225
  }
7235
7226
  function boundedText(value, limit = MAX_DETAIL_TEXT) {
@@ -7250,23 +7241,21 @@ function usageAccounting(status) {
7250
7241
  return status.progress?.accounting;
7251
7242
  }
7252
7243
  function detailPresentation(inspection) {
7253
- const { status, request } = inspection.entry;
7244
+ const { status } = inspection.entry;
7254
7245
  const attempt = latestAttempt(status);
7255
7246
  const state = status.progress?.state;
7256
- const activity2 = status.progress?.activity;
7247
+ const activity = status.progress?.activity;
7257
7248
  const model = state?.model ?? attempt?.setup.model;
7258
7249
  const tools = state?.tools ?? attempt?.setup.tools;
7259
7250
  const lastEventAt = status.progress?.lastEventAt;
7260
7251
  const attempts = status.attempts ?? attempt?.attempt;
7261
7252
  const accounting2 = usageAccounting(status);
7262
- const role = requestRole(request);
7263
7253
  const error = status.error ?? attempt?.error;
7264
7254
  return {
7265
7255
  state: status.state,
7266
- ...activity2 === void 0 ? {} : { activity: { kind: activity2.kind, text: boundedText(activity2.text) } },
7256
+ ...activity === void 0 ? {} : { activity: { kind: activity.kind, text: boundedText(activity.text) } },
7267
7257
  ...lastEventAt === void 0 ? {} : { lastEventAt },
7268
7258
  ...model === void 0 ? {} : { model: { provider: boundedText(model.provider, 256), model: boundedText(model.model, 256), ...model.thinking === void 0 ? {} : { thinking: model.thinking } } },
7269
- ...role === "none" ? {} : { role: boundedText(role, 256) },
7270
7259
  ...tools === void 0 ? {} : { tools: tools.slice(0, 256).map((tool) => boundedText(tool, 256)) },
7271
7260
  ...attempts === void 0 ? {} : { attempts },
7272
7261
  ...status.startedAt === void 0 ? {} : { startedAt: status.startedAt },
@@ -7318,8 +7307,7 @@ function standaloneActionContext(manager, inspection, context) {
7318
7307
  const session = attempt.session;
7319
7308
  const live = liveData?.liveSession && session && liveData.liveSession.reference.transport === session.transport && liveData.liveSession.reference.sessionId === session.sessionId ? liveData.liveSession : void 0;
7320
7309
  const label2 = request?.label?.trim();
7321
- const role = requestRole(request);
7322
- const name = label2 || (role === "none" ? "subagent" : role);
7310
+ const name = label2 || "subagent";
7323
7311
  const ui = {
7324
7312
  notify: (message, level = "info") => {
7325
7313
  context.ui.notify(message, level);
@@ -7388,7 +7376,7 @@ function retryResult(value) {
7388
7376
  return record2 && typeof record2.id === "string" && record2.id.trim() && record2.state === "running" ? { id: record2.id, state: "running" } : void 0;
7389
7377
  }
7390
7378
  async function openNavigatorArtifact(context, tui, artifact, label2) {
7391
- const command = SettingsManager4.create(context.cwd, getAgentDir7(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7379
+ const command = SettingsManager4.create(context.cwd, getAgentDir6(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7392
7380
  if (!command) {
7393
7381
  context.ui.notify(`Cannot open ${label2}: no external editor is configured.`, "warning");
7394
7382
  return;
@@ -7419,7 +7407,7 @@ async function performAction(manager, storageDirectory3, entry, action, context,
7419
7407
  return "unchanged";
7420
7408
  }
7421
7409
  if (action === "Copy run path") {
7422
- await clipboard(join10(storageDirectory3, fresh.entry.status.id));
7410
+ await clipboard(join9(storageDirectory3, fresh.entry.status.id));
7423
7411
  context.ui.notify("Copied run path.", "info");
7424
7412
  return "unchanged";
7425
7413
  }
@@ -7843,33 +7831,15 @@ var TERMINAL_STATES = /* @__PURE__ */ new Set(["completed", "failed", "stopped"]
7843
7831
  var MAX_INSPECT_VALUE_CHARS = 4e3;
7844
7832
  var MAX_INSPECT_TOOL_CALLS = 32;
7845
7833
  var MAX_BACKGROUND_WIDGET_ROWS = 10;
7846
- function textBlock2(text) {
7847
- return {
7848
- render(width) {
7849
- return text.split("\n").map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026"));
7850
- },
7851
- invalidate() {
7852
- }
7853
- };
7854
- }
7855
- function roleName(value) {
7856
- if (typeof value === "string") return value.trim() || void 0;
7857
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
7858
- const name = value.name;
7859
- return typeof name === "string" ? name.trim() || void 0 : void 0;
7860
- }
7861
7834
  function modeName(value) {
7862
7835
  return value === "background" || value === "foreground" ? value : void 0;
7863
7836
  }
7864
7837
  function requestMetadata(args, metadataAvailable) {
7865
7838
  const mode = modeName(args.mode) ?? (metadataAvailable ? "background" : void 0);
7866
- const role = metadataAvailable ? roleName(args.role) ?? "none" : void 0;
7867
- return [mode === void 0 ? void 0 : `mode=${mode}`, role === void 0 ? void 0 : `role=${role}`].filter((value) => value !== void 0).join(" ");
7839
+ return [mode === void 0 ? void 0 : `mode=${mode}`].filter((value) => value !== void 0).join(" ");
7868
7840
  }
7869
7841
  function label(args) {
7870
7842
  if (typeof args.label === "string" && args.label.trim()) return args.label.trim();
7871
- const role = roleName(args.role);
7872
- if (role) return role;
7873
7843
  if (typeof args.id === "string" && args.id) return args.id.slice(0, 8);
7874
7844
  return "subagent";
7875
7845
  }
@@ -7893,17 +7863,6 @@ function statusValue2(value) {
7893
7863
  if (typeof record2.id !== "string" || record2.state !== "running" && record2.state !== "completed" && record2.state !== "failed" && record2.state !== "stopped") return void 0;
7894
7864
  return value;
7895
7865
  }
7896
- function runtime(startedAt, finishedAt, now) {
7897
- if (startedAt === void 0) return "";
7898
- const seconds = Math.max(0, Math.floor(((finishedAt ?? now) - startedAt) / 1e3));
7899
- if (seconds < 60) return `${String(seconds)}s`;
7900
- const minutes = Math.floor(seconds / 60);
7901
- const remainingSeconds = seconds % 60;
7902
- if (minutes < 60) return `${String(minutes)}m${remainingSeconds ? ` ${String(remainingSeconds)}s` : ""}`;
7903
- const hours = Math.floor(minutes / 60);
7904
- const remainingMinutes = minutes % 60;
7905
- return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
7906
- }
7907
7866
  function stateGlyph(state, spinner2) {
7908
7867
  if (state === "running") return spinner2;
7909
7868
  return state === "completed" ? "\u2713" : "\u2717";
@@ -7912,23 +7871,6 @@ function stateColor(state) {
7912
7871
  if (state === "running") return "accent";
7913
7872
  return state === "completed" ? "success" : "error";
7914
7873
  }
7915
- function activity(status) {
7916
- if (status.state !== "running") return void 0;
7917
- const current = status.progress?.activity;
7918
- if (current?.kind === "reasoning" || current?.kind === "text") {
7919
- const name = current.kind === "reasoning" ? "reasoning" : "responding";
7920
- const text = sanitizeDisplayText(current.text);
7921
- return text && text !== name ? `${name} \xB7 ${text}` : name;
7922
- }
7923
- if (current?.kind === "tool") return sanitizeDisplayText(current.text);
7924
- return sanitizeDisplayText([...status.progress?.toolCalls ?? []].reverse().find(({ state }) => state === "running")?.name ?? "");
7925
- }
7926
- function stalledDuration2(status, now) {
7927
- const lastEventAt = status.progress?.lastEventAt;
7928
- if (status.state !== "running" || lastEventAt === void 0 || !Number.isFinite(lastEventAt)) return void 0;
7929
- const duration = now - lastEventAt;
7930
- return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : void 0;
7931
- }
7932
7874
  function usageStats(status) {
7933
7875
  const value = status.progress?.accounting;
7934
7876
  return value ? [formatTokens(value.input + value.output), formatCost(value.cost)].filter(Boolean).join(" \xB7 ") : "";
@@ -7954,15 +7896,15 @@ function boundedValue(value) {
7954
7896
  }
7955
7897
  function formatSubagentProgress(status, args, theme, spinner2, now, expanded) {
7956
7898
  const color = stateColor(status.state);
7957
- const elapsed = runtime(status.startedAt, status.finishedAt, now);
7899
+ const elapsed = status.startedAt === void 0 ? "" : formatWorkflowRuntime((status.finishedAt ?? now) - status.startedAt);
7958
7900
  const metadata = requestMetadata(args, args.id === void 0);
7959
7901
  const stats = usageStats(status);
7960
7902
  const lines = [
7961
- `${theme.fg(color, stateGlyph(status.state, spinner2))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${stats ? ` ${stats}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`
7903
+ `${theme.fg(color, stateGlyph(status.state, "\u25CF"))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${stats ? ` ${stats}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`
7962
7904
  ];
7963
- const current = activity(status);
7964
- const stalled = stalledDuration2(status, now);
7965
- if (current) lines.push(` ${theme.fg("accent", spinner2)} ${theme.fg("dim", current)}${stalled === void 0 ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
7905
+ const current = status.state === "running" ? agentActivityLabel(status.progress ?? {}) : void 0;
7906
+ const stalled = stalledDuration({ ...status.progress, state: status.state }, now);
7907
+ if (current || status.state === "running") lines.push(` ${theme.fg("accent", spinner2)}${current ? ` ${theme.fg("dim", current)}` : ""}${stalled === void 0 ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
7966
7908
  else if (stalled !== void 0) lines.push(` ${theme.fg("warning", `stalled? ${formatStalledDuration(stalled)}`)}`);
7967
7909
  if (status.error) lines.push(` ${theme.fg("error", `${status.error.code}: ${status.error.message}`)}`);
7968
7910
  if (expanded) {
@@ -8028,7 +7970,7 @@ function subagentProgressBlock(status, args, theme, freezeAt) {
8028
7970
  render(width) {
8029
7971
  const now = TERMINAL_STATES.has(current.state) ? frozenAt : Date.now();
8030
7972
  if (!TERMINAL_STATES.has(current.state)) frozenAt = now;
8031
- const frame = SPINNER2[Math.floor(now / 80) % SPINNER2.length] ?? "\u25C7";
7973
+ const frame = SPINNER2[Math.floor(now / REPAINT_MS) % SPINNER2.length] ?? "\u25C7";
8032
7974
  return formatSubagentProgress(current, args, currentTheme, frame, now, expanded).split("\n").map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026"));
8033
7975
  },
8034
7976
  invalidate() {
@@ -8046,14 +7988,14 @@ function renderSubagentResult(result, options, theme, context) {
8046
7988
  if (status?.state === "running" && options.isPartial && !state.subagentSpinner) {
8047
7989
  state.subagentSpinner = setInterval(() => {
8048
7990
  context.invalidate();
8049
- }, 80);
7991
+ }, REPAINT_MS);
8050
7992
  state.subagentSpinner.unref();
8051
7993
  } else if ((!options.isPartial || status?.state !== "running") && state.subagentSpinner) {
8052
7994
  clearInterval(state.subagentSpinner);
8053
7995
  delete state.subagentSpinner;
8054
7996
  }
8055
- if (!status || context.isError) return textBlock2(resultText(result));
8056
- if (!options.isPartial && status.state === "running") return textBlock2(formatSubagentLaunch(status, context.args, theme, options.expanded));
7997
+ if (!status || context.isError) return textBlock(resultText(result));
7998
+ if (!options.isPartial && status.state === "running") return textBlock(formatSubagentLaunch(status, context.args, theme, options.expanded));
8057
7999
  if (TERMINAL_STATES.has(status.state)) state.subagentProgressFrozenAt ??= Date.now();
8058
8000
  else delete state.subagentProgressFrozenAt;
8059
8001
  let component = state.subagentProgressComponent;
@@ -8087,14 +8029,12 @@ var RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
8087
8029
  function receiptFor(status, request) {
8088
8030
  const accounting2 = status.progress?.accounting;
8089
8031
  const model = modelName(status);
8090
- const role = roleName(request.role);
8091
8032
  const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
8092
8033
  return {
8093
8034
  id: status.id,
8094
8035
  label: label({ ...request, id: status.id }),
8095
8036
  state: status.state,
8096
8037
  ...model ? { model } : {},
8097
- ...role === void 0 ? {} : { role },
8098
8038
  ...tools === void 0 ? {} : { tools: [...tools] },
8099
8039
  input: accounting2?.input ?? 0,
8100
8040
  output: accounting2?.output ?? 0,
@@ -8110,7 +8050,7 @@ function renderSubagentReceipt(data, expanded, theme) {
8110
8050
  const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" \xB7 ");
8111
8051
  const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
8112
8052
  if (!expanded) return lines;
8113
- const meta = [data.model ?? "", data.role ? `role ${data.role}` : "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
8053
+ const meta = [data.model ?? "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
8114
8054
  if (meta.length > 0) lines.push(theme.fg("muted", ` ${meta.join(" \xB7 ")}`));
8115
8055
  const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
8116
8056
  if (split.length > 0) lines.push(theme.fg("muted", ` ${split.join(" \xB7 ")}`));
@@ -8175,7 +8115,7 @@ function createSubagentBackgroundWidget(host = {}) {
8175
8115
  showing = true;
8176
8116
  timer = setInterval(() => {
8177
8117
  requestRender?.();
8178
- }, 80);
8118
+ }, REPAINT_MS);
8179
8119
  timer.unref();
8180
8120
  } catch {
8181
8121
  hide();
@@ -8211,35 +8151,35 @@ function createSubagentBackgroundWidget(host = {}) {
8211
8151
  }
8212
8152
  function renderSubagentInspectCall(args, theme) {
8213
8153
  const title = theme.fg("toolTitle", theme.bold("subagents_inspect"));
8214
- return textBlock2(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", "all")}`);
8154
+ return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", args.scope === "all" ? "all" : "session")}`);
8215
8155
  }
8216
8156
  function renderSubagentInspectResult(result, options, theme, args) {
8217
8157
  const status = statusValue2(result.details);
8218
- if (status) return textBlock2(formatSubagentInspection(status, args, result.details, theme, options.expanded));
8158
+ if (status) return textBlock(formatSubagentInspection(status, args, result.details, theme, options.expanded));
8219
8159
  if (Array.isArray(result.details)) {
8220
8160
  const statuses = result.details.map(statusValue2).filter((value) => value !== void 0);
8221
8161
  const lines = [theme.bold(theme.fg("accent", `Subagents (${String(statuses.length)})`)), ...statuses.map((entry) => {
8222
8162
  const color = stateColor(entry.state);
8223
8163
  return ` ${theme.fg(color, stateGlyph(entry.state, "\u25C7"))} ${entry.id.slice(0, 8)} ${theme.fg(color, `[${entry.state}]`)}`;
8224
8164
  })];
8225
- return textBlock2(lines.join("\n"));
8165
+ return textBlock(lines.join("\n"));
8226
8166
  }
8227
- return textBlock2(resultText(result));
8167
+ return textBlock(resultText(result));
8228
8168
  }
8229
8169
  function renderSubagentControlCall(name, args, theme) {
8230
8170
  const title = theme.fg("toolTitle", theme.bold(name));
8231
8171
  const message = args.message ? ` ${theme.fg("dim", args.message)}` : "";
8232
- return textBlock2(`${title} ${theme.fg("accent", args.id)}${message}`);
8172
+ return textBlock(`${title} ${theme.fg("accent", args.id)}${message}`);
8233
8173
  }
8234
8174
  function renderSubagentControlResult(result, theme) {
8235
8175
  const status = statusValue2(result.details);
8236
8176
  if (status) {
8237
8177
  const color = stateColor(status.state);
8238
- return textBlock2(`${theme.fg(color, stateGlyph(status.state, "\u25C7"))} ${status.id.slice(0, 8)} ${theme.fg(color, `[${status.state}]`)}`);
8178
+ return textBlock(`${theme.fg(color, stateGlyph(status.state, "\u25C7"))} ${status.id.slice(0, 8)} ${theme.fg(color, `[${status.state}]`)}`);
8239
8179
  }
8240
8180
  const record2 = typeof result.details === "object" && result.details !== null && !Array.isArray(result.details) ? result.details : void 0;
8241
- if (record2 && typeof record2.id === "string") return textBlock2(`${theme.fg("success", "\u2713")} ${record2.id.slice(0, 8)}`);
8242
- return textBlock2(resultText(result));
8181
+ if (record2 && typeof record2.id === "string") return textBlock(`${theme.fg("success", "\u2713")} ${record2.id.slice(0, 8)}`);
8182
+ return textBlock(resultText(result));
8243
8183
  }
8244
8184
 
8245
8185
  // subagents/src/index.ts
@@ -8247,15 +8187,15 @@ function validateSubagentRunRequest(value) {
8247
8187
  return normalizeSubagentRunRequest(value);
8248
8188
  }
8249
8189
  function validateSubagentIdRequest(value, operation) {
8250
- if (!Value3.Check(SUBAGENTS_ID_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", `Invalid ${operation} parameters`);
8190
+ if (!Value4.Check(SUBAGENTS_ID_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", `Invalid ${operation} parameters`);
8251
8191
  return value;
8252
8192
  }
8253
8193
  function validateSubagentInspectRequest(value) {
8254
- if (!Value3.Check(SUBAGENTS_INSPECT_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_inspect parameters");
8194
+ if (!Value4.Check(SUBAGENTS_INSPECT_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_inspect parameters");
8255
8195
  return value;
8256
8196
  }
8257
8197
  function validateSubagentSteerRequest(value) {
8258
- if (!Value3.Check(SUBAGENTS_STEER_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_steer parameters");
8198
+ if (!Value4.Check(SUBAGENTS_STEER_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_steer parameters");
8259
8199
  return value;
8260
8200
  }
8261
8201
  function serialize(value) {
@@ -8268,7 +8208,7 @@ function serialize(value) {
8268
8208
  }
8269
8209
  }
8270
8210
  function toolResult(value) {
8271
- return { content: [{ type: "text", text: serialize(value) }], details: value };
8211
+ return { content: [{ type: "text", text: serialize(value) }], details: value, structuredContent: value };
8272
8212
  }
8273
8213
  function managerContext2(toolCallId, signal, onUpdate, context) {
8274
8214
  return {
@@ -8292,6 +8232,7 @@ function createSubagentTools(manager) {
8292
8232
  "Do not poll a running ID; use subagents_inspect({ id }) when you need its current status or terminal result."
8293
8233
  ],
8294
8234
  parameters: SUBAGENTS_RUN_PARAMETERS,
8235
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
8295
8236
  async execute(toolCallId, params, signal, onUpdate, context) {
8296
8237
  return toolResult(await manager.run(validateSubagentRunRequest(params), managerContext2(toolCallId, signal, onUpdate, context)));
8297
8238
  },
@@ -8305,8 +8246,9 @@ function createSubagentTools(manager) {
8305
8246
  defineTool2({
8306
8247
  name: "subagents_inspect",
8307
8248
  label: "Subagents Inspect",
8308
- description: "Inspect durable subagent runs. Omit id for ordered run summaries, or provide id for detailed status, progress, activity, accounting, tool calls, timestamps, worktree metadata, and a terminal value or error when available.",
8249
+ description: "Inspect durable subagent runs. Omit id for ordered run summaries of the current session (scope all for every session), or provide id for detailed status, progress, activity, accounting, tool calls, timestamps, worktree metadata, and a terminal value or error when available.",
8309
8250
  parameters: SUBAGENTS_INSPECT_PARAMETERS,
8251
+ outputSchema: SUBAGENTS_INSPECT_OUTPUT,
8310
8252
  async execute(toolCallId, params, signal, onUpdate, context) {
8311
8253
  return toolResult(await manager.inspect(validateSubagentInspectRequest(params), managerContext2(toolCallId, signal, onUpdate, context)));
8312
8254
  },
@@ -8322,6 +8264,7 @@ function createSubagentTools(manager) {
8322
8264
  label: "Subagents Steer",
8323
8265
  description: "Send a message to a running subagent. The message is queued safely if its steering handler is not ready; settled runs cannot be steered.",
8324
8266
  parameters: SUBAGENTS_STEER_PARAMETERS,
8267
+ outputSchema: SUBAGENTS_STEER_OUTPUT,
8325
8268
  async execute(toolCallId, params, signal, onUpdate, context) {
8326
8269
  return toolResult(await manager.steer(validateSubagentSteerRequest(params), managerContext2(toolCallId, signal, onUpdate, context)));
8327
8270
  },
@@ -8337,6 +8280,7 @@ function createSubagentTools(manager) {
8337
8280
  label: "Subagents Stop",
8338
8281
  description: 'Stop one running subagent, abort its active session, persist state "stopped", and clean its worktree without affecting sibling runs.',
8339
8282
  parameters: SUBAGENTS_STOP_PARAMETERS,
8283
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
8340
8284
  async execute(toolCallId, params, signal, onUpdate, context) {
8341
8285
  return toolResult(await manager.stop(validateSubagentIdRequest(params, "subagents_stop"), managerContext2(toolCallId, signal, onUpdate, context)));
8342
8286
  },
@@ -8352,6 +8296,7 @@ function createSubagentTools(manager) {
8352
8296
  label: "Subagents Retry",
8353
8297
  description: "Start a fresh run from a failed or stopped subagent's persisted request. The new run gets a new ID and preserves its original background or foreground mode.",
8354
8298
  parameters: SUBAGENTS_RETRY_PARAMETERS,
8299
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
8355
8300
  async execute(toolCallId, params, signal, onUpdate, context) {
8356
8301
  return toolResult(await manager.retry(validateSubagentIdRequest(params, "subagents_retry"), managerContext2(toolCallId, signal, onUpdate, context)));
8357
8302
  },
@@ -8366,8 +8311,7 @@ function createSubagentTools(manager) {
8366
8311
  }
8367
8312
  function notificationContent(notification) {
8368
8313
  const label2 = notification.label?.trim();
8369
- const role = notification.role?.trim() || "none";
8370
- const identity = label2 ? `${label2} role=${role} (${notification.id})` : `${notification.id} role=${role}`;
8314
+ const identity = label2 ? `${label2} (${notification.id})` : notification.id;
8371
8315
  if (notification.state === "completed") return `Subagent ${identity} completed. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
8372
8316
  return `Subagent ${identity} failed: ${notification.error?.message ?? "unknown error"}. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
8373
8317
  }
@@ -8384,7 +8328,7 @@ function managerDependencies(options, activeTools, notify, onStatus, onResourceW
8384
8328
  }
8385
8329
  function storageDirectory2(options) {
8386
8330
  const dependencies = options.managerDependencies;
8387
- return dependencies?.storageDir ?? join11(dependencies?.agentDir ?? getAgentDir8(), "subagents");
8331
+ return dependencies?.storageDir ?? join10(dependencies?.agentDir ?? getAgentDir7(), "subagents");
8388
8332
  }
8389
8333
  function createSubagentsExtension(options = {}, activeTools, notify, onStatus, onResourceWarning) {
8390
8334
  const manager = options.manager ?? createSubagentManager(managerDependencies(options, activeTools, notify, onStatus, onResourceWarning));
@@ -8393,7 +8337,8 @@ function createSubagentsExtension(options = {}, activeTools, notify, onStatus, o
8393
8337
  }
8394
8338
  function registerSubagentsExtension(pi, options = {}) {
8395
8339
  const getActiveTools = pi.getActiveTools;
8396
- const activeTools = getActiveTools === void 0 ? void 0 : () => getActiveTools.call(pi);
8340
+ const getAllTools = pi.getAllTools;
8341
+ const activeTools = getActiveTools === void 0 ? void 0 : () => reachableTools({ getActiveTools: () => getActiveTools.call(pi), ...getAllTools === void 0 ? {} : { getAllTools: () => getAllTools.call(pi) } });
8397
8342
  const sendMessage = pi.sendMessage;
8398
8343
  const notify = sendMessage === void 0 ? void 0 : (notification) => {
8399
8344
  sendMessage.call(pi, { customType: "subagents", content: notificationContent(notification), display: true, details: notification }, { deliverAs: "steer", triggerTurn: true });
@@ -8410,7 +8355,8 @@ function registerSubagentsExtension(pi, options = {}) {
8410
8355
  const registry = loadingRegistry();
8411
8356
  if (typeof registry.observeSubagentStatus === "function") registry.observeSubagentStatus(status, request);
8412
8357
  }, onResourceWarning);
8413
- for (const tool of extension2.tools) pi.registerTool(tool);
8358
+ const codemodeTools = loadCodemodeToolsSetting(options.managerDependencies?.agentDir);
8359
+ for (const tool of extension2.tools) pi.registerTool({ ...tool, ...workflowToolExposure(tool.name, codemodeTools) });
8414
8360
  if (pi.registerCommand !== void 0) registerSubagentNavigator(pi.registerCommand.bind(pi), extension2.manager, storageDirectory2(options), options.clipboard);
8415
8361
  if (pi.on !== void 0) {
8416
8362
  setSubagentManager(extension2.manager);
@@ -8433,15 +8379,17 @@ function extension(pi, options = {}) {
8433
8379
  }
8434
8380
  export {
8435
8381
  SUBAGENTS_ID_PARAMETERS,
8382
+ SUBAGENTS_INSPECT_OUTPUT,
8436
8383
  SUBAGENTS_INSPECT_PARAMETERS,
8437
8384
  SUBAGENTS_RETRY_PARAMETERS,
8438
8385
  SUBAGENTS_RUN_PARAMETERS,
8386
+ SUBAGENTS_STEER_OUTPUT,
8439
8387
  SUBAGENTS_STEER_PARAMETERS,
8440
8388
  SUBAGENTS_STOP_PARAMETERS,
8441
8389
  SUBAGENTS_TOOL_NAMES,
8442
- SUBAGENTS_TOOL_SCHEMAS,
8443
8390
  SUBAGENT_ATTEMPT_DETAILS_LIMIT,
8444
8391
  SUBAGENT_MAX_RETRIES,
8392
+ SUBAGENT_STATUS_OUTPUT,
8445
8393
  SUBAGENT_SYSTEM_PROMPT_LIMIT,
8446
8394
  attemptValue,
8447
8395
  createRunStoreWorktreeAdapter,