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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +2 -2
  3. package/dist/src/agent-execution.d.ts +43 -10
  4. package/dist/src/agent-execution.js +365 -127
  5. package/dist/src/background-widget.d.ts +0 -1
  6. package/dist/src/background-widget.js +4 -9
  7. package/dist/src/decoders.d.ts +2 -3
  8. package/dist/src/decoders.js +33 -37
  9. package/dist/src/execution.d.ts +2 -0
  10. package/dist/src/execution.js +5 -5
  11. package/dist/src/host-delivery.js +4 -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 +6 -6
  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 +1 -1
  18. package/dist/src/host-view.js +17 -15
  19. package/dist/src/host.js +62 -87
  20. package/dist/src/index.d.ts +1 -2
  21. package/dist/src/index.js +1523 -1630
  22. package/dist/src/index.js.map +3 -3
  23. package/dist/src/paths.d.ts +7 -6
  24. package/dist/src/paths.js +24 -15
  25. package/dist/src/persistence.d.ts +1 -0
  26. package/dist/src/persistence.js +1 -0
  27. package/dist/src/pi-runtime-runner.js +4 -1
  28. package/dist/src/registry.d.ts +9 -7
  29. package/dist/src/registry.js +28 -70
  30. package/dist/src/runtime/agent-runner.d.ts +2 -0
  31. package/dist/src/session-lease.d.ts +1 -1
  32. package/dist/src/session-lease.js +45 -11
  33. package/dist/src/settings.d.ts +6 -3
  34. package/dist/src/settings.js +36 -47
  35. package/dist/src/store.d.ts +3 -1
  36. package/dist/src/store.js +17 -1
  37. package/dist/src/trajectory.d.ts +0 -1
  38. package/dist/src/trajectory.js +2 -3
  39. package/dist/src/types.d.ts +100 -44
  40. package/dist/src/types.js +4 -7
  41. package/dist/src/utils.d.ts +3 -2
  42. package/dist/src/utils.js +20 -10
  43. package/dist/src/validation.d.ts +8 -2
  44. package/dist/src/validation.js +122 -86
  45. package/dist/starter/index.js +8 -9
  46. package/dist/starter/index.js.map +1 -1
  47. package/dist/starter/review-loop.js +8 -7
  48. package/dist/subagents/index.js +654 -814
  49. package/dist/subagents/index.js.map +3 -3
  50. package/dist/subagents/src/contracts.d.ts +4 -3
  51. package/dist/subagents/src/contracts.js +2 -2
  52. package/dist/subagents/src/index.d.ts +1 -1
  53. package/dist/subagents/src/index.js +1 -2
  54. package/dist/subagents/src/manager.js +49 -70
  55. package/dist/subagents/src/navigator.js +3 -14
  56. package/dist/subagents/src/view.d.ts +0 -1
  57. package/dist/subagents/src/view.js +2 -16
  58. package/dist/subagents/src/worktree.js +1 -1
  59. package/dist/trajectory/assets/index.html +8 -8
  60. package/dist/trajectory/index.js +487 -891
  61. package/dist/trajectory/index.js.map +3 -3
  62. package/dist/trajectory/src/assets/index.html +8 -8
  63. package/dist/trajectory/src/index.js +126 -86
  64. package/dist/trajectory/src/server.js +264 -25
  65. package/dist/trajectory/src/server.js.map +3 -3
  66. package/dist/trajectory/src/startup-mutex.d.ts +17 -0
  67. package/dist/trajectory/src/startup-mutex.js +172 -0
  68. package/examples/workflow-extension-template/README.md +4 -7
  69. package/examples/workflow-extension-template/extension.test.mjs +4 -8
  70. package/examples/workflow-extension-template/index.js +0 -3
  71. package/package.json +5 -5
  72. package/skills/pi-extensible-workflows/SKILL.md +10 -8
  73. package/src/agent-execution.ts +339 -127
  74. package/src/background-widget.ts +4 -10
  75. package/src/decoders.ts +31 -37
  76. package/src/execution.ts +5 -5
  77. package/src/host-delivery.ts +4 -8
  78. package/src/host-navigator.ts +4 -2
  79. package/src/host-phases.ts +1 -1
  80. package/src/host-recovery.ts +20 -26
  81. package/src/host-runtime.ts +44 -6
  82. package/src/host-view.ts +18 -11
  83. package/src/host.ts +62 -113
  84. package/src/index.ts +1 -2
  85. package/src/paths.ts +18 -12
  86. package/src/persistence.ts +1 -0
  87. package/src/pi-runtime-runner.ts +3 -1
  88. package/src/registry.ts +27 -63
  89. package/src/runtime/agent-runner.ts +2 -0
  90. package/src/session-lease.ts +34 -6
  91. package/src/settings.ts +35 -47
  92. package/src/store.ts +18 -2
  93. package/src/trajectory.ts +2 -4
  94. package/src/types.ts +71 -30
  95. package/src/utils.ts +18 -9
  96. package/src/validation.ts +102 -79
  97. package/starter/index.ts +0 -1
  98. package/starter/review-loop.ts +8 -7
  99. package/starter/roles/developer.md +0 -1
  100. package/starter/roles/oracle.md +0 -1
  101. package/starter/roles/researcher.md +0 -1
  102. package/starter/roles/reviewer.md +0 -1
  103. package/starter/roles/scout.md +0 -1
  104. package/subagents/README.md +8 -14
  105. package/subagents/src/contracts.ts +3 -4
  106. package/subagents/src/index.ts +2 -3
  107. package/subagents/src/manager.ts +45 -125
  108. package/subagents/src/navigator.ts +4 -13
  109. package/subagents/src/view.ts +4 -14
  110. package/subagents/src/worktree.ts +1 -1
  111. package/trajectory/src/assets/index.html +8 -8
  112. package/trajectory/src/index.ts +106 -82
  113. package/trajectory/src/server.ts +44 -8
  114. package/trajectory/src/startup-mutex.ts +127 -0
  115. package/dist/src/roles.d.ts +0 -70
  116. package/dist/src/roles.js +0 -326
  117. package/dist/starter/roles/developer.md +0 -6
  118. package/dist/starter/roles/oracle.md +0 -18
  119. package/dist/starter/roles/researcher.md +0 -20
  120. package/dist/starter/roles/reviewer.md +0 -19
  121. package/dist/starter/roles/scout.md +0 -18
  122. package/examples/workflow-extension-template/roles/reviewer.md +0 -4
  123. package/src/roles.ts +0 -327
@@ -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",
@@ -37,15 +37,12 @@ var ERROR_CODES = [
37
37
  "INTERNAL_ERROR"
38
38
  ];
39
39
  var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
40
- function roleNameOf(value) {
41
- return typeof value === "string" ? value : void 0;
42
- }
43
- var BUDGET_DIMENSIONS = ["tokens", "costUsd", "durationMs", "agentLaunches"];
44
- var BUDGET_EVENT_TYPES = ["soft_crossed", "hard_overrun", "hard_exhausted", "adjustment_requested", "adjustment_approved", "adjustment_rejected"];
45
40
  var CONTEXT_FILE_SCOPES = ["global", "project", "cwd"];
46
41
  function isContextFileScope(value) {
47
42
  return CONTEXT_FILE_SCOPES.some((scope) => scope === value);
48
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"];
49
46
  var WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1e3;
50
47
  function zeroAccounting() {
51
48
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
@@ -58,7 +55,7 @@ function sumAccounting(values) {
58
55
  for (const value of values) total = addAccounting(total, value);
59
56
  return total;
60
57
  }
61
- var LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
58
+ var LAUNCH_SNAPSHOT_IDENTITY_VERSION = 6;
62
59
  var WORKFLOW_ERROR_BRAND = /* @__PURE__ */ Symbol.for("pi-extensible-workflows.workflow-error");
63
60
  var WorkflowError = class extends Error {
64
61
  constructor(code, message) {
@@ -156,15 +153,8 @@ function jsonValue(value, seen = /* @__PURE__ */ new Set()) {
156
153
  seen.delete(value);
157
154
  return valid;
158
155
  }
159
- function mergeWorkflowExtensionSettings(...layers) {
160
- const merged = {};
161
- let present = false;
162
- for (const layer of layers) {
163
- if (layer === void 0) continue;
164
- present = true;
165
- for (const [namespace, value] of Object.entries(layer)) Object.defineProperty(merged, namespace, { value: structuredClone(value), enumerable: true, configurable: true, writable: true });
166
- }
167
- return present ? deepFreeze(merged) : void 0;
156
+ function jsonObject(value) {
157
+ return jsonValue(value) && object(value);
168
158
  }
169
159
  function positiveInteger(value) {
170
160
  return typeof value === "number" && Number.isInteger(value) && value > 0;
@@ -214,11 +204,16 @@ function parseThinking(value) {
214
204
  return isThinkingLevel(value) ? value : void 0;
215
205
  }
216
206
  function parseModelReference(value) {
217
- const match = /^([^/:\s]+)\/([^:\s]+)(?::([^:\s]+))?$/.exec(value);
207
+ const match = /^([^/:\s]+)\/([^\s]+)$/.exec(value);
218
208
  if (!match?.[1] || !match[2]) fail("UNKNOWN_MODEL", `Invalid model spec: ${value}`);
219
- const thinking = match[3];
220
- if (thinking !== void 0 && !isThinkingLevel(thinking)) fail("UNKNOWN_MODEL", `Invalid thinking level: ${thinking}`);
221
- 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 } };
222
217
  }
223
218
  function assertModelThinking(value, path = "model") {
224
219
  if (value.includes("/") && parseModelReference(value).thinking === void 0) fail("INVALID_METADATA", `${path} must be provider/model:thinking`);
@@ -285,6 +280,10 @@ function resolveModelReference(value, aliases = {}, knownModels, settingsPath) {
285
280
  };
286
281
  return resolveReference(value, []);
287
282
  }
283
+ function modelCapability(value, aliases, knownModels, settingsPath) {
284
+ const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
285
+ return `${parsed.provider}/${parsed.model}`;
286
+ }
288
287
  function reachableTools(pi) {
289
288
  const undeclared = pi.getAllTools?.().filter(({ exposure }) => exposure === "codemode" || exposure === "deferred").map(({ name }) => name) ?? [];
290
289
  return [.../* @__PURE__ */ new Set([...pi.getActiveTools(), ...undeclared])];
@@ -295,10 +294,6 @@ function physicalModel(spec, aliases = {}, knownModels, settingsPath) {
295
294
  const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
296
295
  return spec.thinking === void 0 ? target : { ...target, thinking: spec.thinking };
297
296
  }
298
- function modelCapability(value, aliases, knownModels, settingsPath) {
299
- const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
300
- return `${parsed.provider}/${parsed.model}`;
301
- }
302
297
  var RESOURCE_PATTERN_OPTIONS = { dot: true, nonegate: true, nocomment: true };
303
298
  function resourcePatternBody(pattern) {
304
299
  return pattern.startsWith("!") ? pattern.slice(1) : pattern;
@@ -349,8 +344,9 @@ function validateSchema(schema, at = "schema") {
349
344
  // src/paths.ts
350
345
  import { createHash } from "node:crypto";
351
346
  import { realpathSync } from "node:fs";
347
+ import { fileURLToPath } from "node:url";
352
348
  import { homedir } from "node:os";
353
- import { basename, join, resolve } from "node:path";
349
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
354
350
  function safePart(value) {
355
351
  return value.replace(/[^a-zA-Z0-9._-]/g, "_");
356
352
  }
@@ -363,11 +359,21 @@ function canonicalPath(path) {
363
359
  }
364
360
  }
365
361
  function extensionIdentity(path) {
366
- return path.startsWith("builtin:") ? path : canonicalPath(path);
362
+ return path.startsWith("builtin:") ? path : canonicalPath(path.startsWith("file:") ? fileURLToPath(path) : path);
367
363
  }
368
364
  function sameFilesystemPath(left, right) {
369
365
  return canonicalPath(left) === canonicalPath(right);
370
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
+ }
371
377
  function workflowProjectSettingsPath(cwd) {
372
378
  return join(cwd, ".pi", "pi-extensible-workflows", "settings.json");
373
379
  }
@@ -395,17 +401,6 @@ function budgetUsage(value) {
395
401
  // src/validation.ts
396
402
  import * as acorn from "acorn";
397
403
 
398
- // src/roles.ts
399
- import { readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
400
- import { basename as basename3, dirname as dirname4, extname, join as join5, resolve as resolve4 } from "node:path";
401
- import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
402
-
403
- // src/registry.ts
404
- import { readFileSync as readFileSync2 } from "node:fs";
405
- import { basename as basename2, dirname as dirname3, isAbsolute, join as join4 } from "node:path";
406
- import { fileURLToPath as fileURLToPath2 } from "node:url";
407
- import { Value } from "typebox/value";
408
-
409
404
  // src/io.ts
410
405
  import { execFile } from "node:child_process";
411
406
  import { randomUUID } from "node:crypto";
@@ -426,7 +421,7 @@ async function renameWithRetry(from, to) {
426
421
  } catch (error) {
427
422
  const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
428
423
  if (!retryableRenameError(error) || delayMs === void 0) throw error;
429
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
424
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
430
425
  }
431
426
  }
432
427
  }
@@ -492,7 +487,9 @@ async function git(cwd, args, extraEnv = {}) {
492
487
  }
493
488
 
494
489
  // src/session-lease.ts
490
+ import { execFileSync } from "node:child_process";
495
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";
496
493
 
497
494
  // src/decoders.ts
498
495
  var INVALID_PERSISTED_VALUE = /* @__PURE__ */ Symbol("invalid persisted value");
@@ -582,32 +579,19 @@ function decodeAgentResourceSelectors(value) {
582
579
  function decodeContextFileScopes(value) {
583
580
  return decodeArray(value, (scope) => isContextFileScope(scope) ? scope : void 0);
584
581
  }
585
- function decodeAgentDefinition(value) {
586
- if (!object(value)) return void 0;
587
- const prompt = optionalString(value.prompt);
588
- const description = optionalString(value.description);
589
- const model = optionalString(value.model);
590
- const thinking = value.thinking;
591
- const tools = value.tools === void 0 ? void 0 : decodeStringArray(value.tools);
592
- const skills = value.skills === void 0 ? void 0 : decodeStringArray(value.skills);
593
- const extensions = value.extensions === void 0 ? void 0 : decodeStringArray(value.extensions);
594
- 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);
595
589
  const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
596
- const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
597
- if (prompt === INVALID_PERSISTED_VALUE || description === INVALID_PERSISTED_VALUE || model === INVALID_PERSISTED_VALUE || overrideSystemPrompt === INVALID_PERSISTED_VALUE) return void 0;
598
- 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;
599
- const foldedModel = typeof model === "string" && thinking !== void 0 && !model.includes(":") ? `${model}:${thinking}` : model;
600
- return {
601
- ...prompt === void 0 ? {} : { prompt },
602
- ...description === void 0 ? {} : { description },
603
- ...foldedModel === void 0 ? {} : { model: foldedModel },
604
- ...tools === void 0 ? {} : { tools },
605
- ...skills === void 0 ? {} : { skills },
606
- ...extensions === void 0 ? {} : { extensions },
607
- ...overrideSystemPrompt === void 0 ? {} : { overrideSystemPrompt },
608
- ...contextFiles === void 0 ? {} : { contextFiles },
609
- ...extensionSettings === void 0 ? {} : { extensionSettings }
610
- };
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 } };
611
595
  }
612
596
  function decodeWorkflowMetadata(value) {
613
597
  if (!object(value) || typeof value.name !== "string") return void 0;
@@ -716,17 +700,22 @@ function decodeScheduledAgentOptions(value) {
716
700
  const parentBreadcrumb = optionalString(value.parentBreadcrumb);
717
701
  const worktreeOwner = optionalString(value.worktreeOwner);
718
702
  const model = optionalString(value.model);
719
- const role = typeof value.role === "string" ? value.role : void 0;
720
703
  const contextFiles = value.contextFiles === void 0 ? void 0 : decodeContextFileScopes(value.contextFiles);
721
704
  const schema = value.schema === void 0 ? void 0 : decodeJsonObject(value.schema);
722
705
  const retries = optionalNumber(value.retries);
723
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;
724
713
  const agentOptions = value.agentOptions === void 0 ? void 0 : decodeJsonObject(value.agentOptions);
725
714
  const agentIdentity = value.agentIdentity === void 0 ? void 0 : decodeIdentity(value.agentIdentity);
726
715
  const sessionPath = optionalString(value.sessionPath);
727
716
  const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
728
717
  const extensionSettings = value.extensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.extensionSettings);
729
- 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;
730
719
  return {
731
720
  label: value.label,
732
721
  ...requestedLabel === void 0 ? {} : { requestedLabel },
@@ -737,7 +726,6 @@ function decodeScheduledAgentOptions(value) {
737
726
  ...extensions === void 0 ? {} : { extensions },
738
727
  ...worktreeOwner === void 0 ? {} : { worktreeOwner },
739
728
  ...model === void 0 ? {} : { model },
740
- ...role === void 0 ? {} : { role },
741
729
  ...contextFiles === void 0 ? {} : { contextFiles },
742
730
  ...schema === void 0 ? {} : { schema },
743
731
  ...retries === void 0 ? {} : { retries },
@@ -745,6 +733,9 @@ function decodeScheduledAgentOptions(value) {
745
733
  ...sessionPath === void 0 ? {} : { sessionPath },
746
734
  ...continuity === void 0 ? {} : { continuity },
747
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 } } : {},
748
739
  ...agentIdentity === void 0 ? {} : { agentIdentity },
749
740
  ...extensionSettings === void 0 ? {} : { extensionSettings }
750
741
  };
@@ -785,11 +776,10 @@ function decodeAgentResourceInspection(value) {
785
776
  const sources = sourceRecord === void 0 ? void 0 : {
786
777
  global: decodeAgentResourceSelectors(sourceRecord.global),
787
778
  project: decodeAgentResourceSelectors(sourceRecord.project),
788
- ...sourceRecord.role === void 0 ? {} : { role: decodeAgentResourceSelectors(sourceRecord.role) },
789
779
  ...sourceRecord.call === void 0 ? {} : { call: decodeAgentResourceSelectors(sourceRecord.call) }
790
780
  };
791
- 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;
792
- 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 } } } };
793
783
  }
794
784
  function decodeAgentSetupSummary(value) {
795
785
  if (!object(value) || typeof value.cwd !== "string") return void 0;
@@ -827,7 +817,6 @@ function decodeAgent(value) {
827
817
  const worktreeOwner = optionalString(value.worktreeOwner);
828
818
  const handle = optionalString(value.handle);
829
819
  const continuity = value.continuity === void 0 ? void 0 : isAgentContinuity(value.continuity) ? value.continuity : INVALID_PERSISTED_VALUE;
830
- const role = optionalString(value.role);
831
820
  const requestedModel = optionalString(value.requestedModel);
832
821
  const startedAt = optionalNumber(value.startedAt);
833
822
  const durationMs = optionalNumber(value.durationMs);
@@ -836,7 +825,7 @@ function decodeAgent(value) {
836
825
  const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
837
826
  const activity = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
838
827
  const lastEventAt = optionalNumber(value.lastEventAt);
839
- 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;
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;
840
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;
841
830
  return {
842
831
  ...systemPrompt === void 0 ? {} : { systemPrompt },
@@ -853,7 +842,6 @@ function decodeAgent(value) {
853
842
  ...worktreeOwner === void 0 ? {} : { worktreeOwner },
854
843
  ...handle === void 0 ? {} : { handle, turn: value.turn },
855
844
  ...continuity === void 0 ? {} : { continuity },
856
- ...role === void 0 ? {} : { role },
857
845
  ...requestedModel === void 0 ? {} : { requestedModel },
858
846
  model,
859
847
  tools,
@@ -1001,13 +989,11 @@ function decodeLaunchSnapshot(value) {
1001
989
  const phases = value.phases === void 0 ? void 0 : decodeStringArray(value.phases);
1002
990
  const models = decodeStringArray(value.models);
1003
991
  const tools = decodeStringArray(value.tools);
1004
- const agentTypes = decodeStringArray(value.agentTypes);
1005
- const roles = value.roles === void 0 ? void 0 : decodeRecord(value.roles, decodeAgentDefinition);
1006
- const projectRoles = value.projectRoles === void 0 ? void 0 : decodeStringArray(value.projectRoles);
992
+ const agentConfigurations = decodeRecord(value.agentConfigurations, decodePreparedAgentConfiguration);
1007
993
  const schemas = decodeArray(value.schemas, decodeJsonObject);
1008
- 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;
1009
995
  return {
1010
- ...identityVersion === void 0 ? {} : { identityVersion },
996
+ identityVersion: LAUNCH_SNAPSHOT_IDENTITY_VERSION,
1011
997
  ...launchMode === void 0 ? {} : { launchMode },
1012
998
  script: value.script,
1013
999
  args,
@@ -1020,9 +1006,7 @@ function decodeLaunchSnapshot(value) {
1020
1006
  ...phases === void 0 ? {} : { phases },
1021
1007
  models,
1022
1008
  tools,
1023
- agentTypes,
1024
- ...roles === void 0 ? {} : { roles },
1025
- ...projectRoles === void 0 ? {} : { projectRoles },
1009
+ agentConfigurations,
1026
1010
  schemas
1027
1011
  };
1028
1012
  }
@@ -1103,18 +1087,43 @@ function decodeSystemPromptArtifact(value) {
1103
1087
  }
1104
1088
 
1105
1089
  // src/session-lease.ts
1106
- 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") {
1107
1115
  try {
1108
1116
  process.kill(pid, 0);
1109
1117
  } catch (error) {
1110
1118
  return !isNodeError(error, "ESRCH");
1111
1119
  }
1112
- if (startedAt !== void 0 && process.platform === "linux") {
1113
- try {
1114
- if ((await stat(`/proc/${String(pid)}`)).ctimeMs > startedAt) return false;
1115
- } catch (error) {
1116
- if (isNodeError(error, "ENOENT")) return false;
1117
- }
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");
1118
1127
  }
1119
1128
  return true;
1120
1129
  }
@@ -1124,7 +1133,7 @@ import { execFile as execFile2 } from "node:child_process";
1124
1133
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
1125
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";
1126
1135
  import { homedir as homedir2 } from "node:os";
1127
- 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";
1128
1137
  import { promisify as promisify2 } from "node:util";
1129
1138
  var executeFile = promisify2(execFile2);
1130
1139
  var WORKTREE_POST_CREATE_TIMEOUT_MS = 6e4;
@@ -1135,36 +1144,36 @@ var SYSTEM_PROMPT_BODIES = "bodies";
1135
1144
  var SYSTEM_PROMPT_SEQUENCE = "sequence";
1136
1145
  var SYSTEM_PROMPT_ARTIFACT = { version: 2, format: "append-only", storage: SYSTEM_PROMPT_STORAGE };
1137
1146
  function summaryArtifacts(directory) {
1138
- 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") };
1139
1148
  }
1140
1149
  function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, now = (/* @__PURE__ */ new Date()).toISOString()) {
1141
1150
  const createdAt = typeof previous?.createdAt === "string" ? previous.createdAt : fallbackCreatedAt;
1142
1151
  const failedAt = run.failedAt ?? run.error?.failedAt;
1143
1152
  const replayablePaths = [.../* @__PURE__ */ new Set([...run.retry?.completedPaths ?? [], ...Object.keys(journal.completed)])];
1144
1153
  const incompletePaths = [.../* @__PURE__ */ new Set([...run.retry?.incompletePaths ?? [], ...failedAt ? [failedAt] : []])];
1145
- 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) };
1146
1155
  }
1147
1156
  function systemPromptStoragePath(directory) {
1148
- return join2(directory, SYSTEM_PROMPT_STORAGE);
1157
+ return join3(directory, SYSTEM_PROMPT_STORAGE);
1149
1158
  }
1150
1159
  function systemPromptRecordsPath(directory) {
1151
- return join2(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS);
1160
+ return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_RECORDS);
1152
1161
  }
1153
1162
  function systemPromptBodiesPath(directory) {
1154
- return join2(systemPromptStoragePath(directory), SYSTEM_PROMPT_BODIES);
1163
+ return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_BODIES);
1155
1164
  }
1156
1165
  function systemPromptSequencePath(directory) {
1157
- return join2(systemPromptStoragePath(directory), SYSTEM_PROMPT_SEQUENCE);
1166
+ return join3(systemPromptStoragePath(directory), SYSTEM_PROMPT_SEQUENCE);
1158
1167
  }
1159
1168
  function systemPromptRecordName(sequence) {
1160
1169
  return `${String(sequence).padStart(20, "0")}.json`;
1161
1170
  }
1162
1171
  async function createSystemPromptStorage(directory, writeArtifact) {
1163
1172
  const storage = systemPromptStoragePath(directory);
1164
- await mkdir2(join2(storage, SYSTEM_PROMPT_RECORDS), { recursive: true, mode: 448 });
1165
- 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 });
1166
1175
  await atomicWriteFile(systemPromptSequencePath(directory), "0\n");
1167
- 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);
1168
1177
  }
1169
1178
  var RunStore = class _RunStore {
1170
1179
  constructor(cwd, sessionId, runId, home = homedir2()) {
@@ -1173,7 +1182,7 @@ var RunStore = class _RunStore {
1173
1182
  this.runId = runId;
1174
1183
  this.home = home;
1175
1184
  this.cwd = resolve2(cwd);
1176
- this.directory = join2(runsDirectory(this.cwd, sessionId, home), safePart(runId));
1185
+ this.directory = join3(runsDirectory(this.cwd, sessionId, home), safePart(runId));
1177
1186
  }
1178
1187
  cwd;
1179
1188
  sessionId;
@@ -1195,19 +1204,19 @@ var RunStore = class _RunStore {
1195
1204
  }
1196
1205
  async create(run, snapshot) {
1197
1206
  this.#assertRunIdentity(run, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
1198
- 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`);
1199
1208
  await mkdir2(dirname(this.directory), { recursive: true, mode: 448 });
1200
1209
  await mkdir2(temporary, { mode: 448 });
1201
1210
  try {
1202
- await writeFile2(join2(temporary, "workflow.js"), snapshot.script, { encoding: "utf8", mode: 384 });
1203
- await atomicJson(join2(temporary, "snapshot.json"), snapshot);
1204
- await atomicJson(join2(temporary, "journal.json"), { completed: {}, awaiting: {}, decisions: {} });
1205
- await atomicJson(join2(temporary, "ownership.json"), []);
1206
- await atomicJson(join2(temporary, "worktrees.json"), []);
1207
- await atomicJson(join2(temporary, "borrowed-worktrees.json"), []);
1208
- 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);
1209
1218
  await createSystemPromptStorage(temporary, true);
1210
- 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()));
1211
1220
  await rename3(temporary, this.directory);
1212
1221
  } catch (error) {
1213
1222
  await rm3(temporary, { recursive: true, force: true });
@@ -1216,12 +1225,12 @@ var RunStore = class _RunStore {
1216
1225
  }
1217
1226
  async refreshSummary() {
1218
1227
  const write = this.summaryLane.run(async () => {
1219
- const run = decodePersistedRun(await json(join2(this.directory, "state.json")), true);
1220
- const journal = decodeJournal(await json(join2(this.directory, "journal.json")));
1221
- 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);
1222
1231
  if (!run || !journal) throw new Error("Persisted run or journal is invalid");
1223
- const fallbackCreatedAt = await stat2(join2(this.directory, "state.json")).then((value) => new Date(value.mtimeMs).toISOString());
1224
- 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));
1225
1234
  });
1226
1235
  await write;
1227
1236
  }
@@ -1231,7 +1240,7 @@ var RunStore = class _RunStore {
1231
1240
  }
1232
1241
  async isComplete() {
1233
1242
  try {
1234
- 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"))]);
1235
1244
  return true;
1236
1245
  } catch {
1237
1246
  return false;
@@ -1239,18 +1248,18 @@ var RunStore = class _RunStore {
1239
1248
  }
1240
1249
  async load() {
1241
1250
  await this.stateLane.run(async () => void 0);
1242
- const rawRun = await json(join2(this.directory, "state.json"));
1251
+ const rawRun = await json(join3(this.directory, "state.json"));
1243
1252
  const run = decodePersistedRun(rawRun, true);
1244
1253
  if (!run) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run is invalid");
1245
1254
  this.#assertRunIdentity(run, "RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
1246
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");
1247
- const snapshot = decodeLaunchSnapshot(await json(join2(this.directory, "snapshot.json")));
1256
+ const snapshot = decodeLaunchSnapshot(await json(join3(this.directory, "snapshot.json")));
1248
1257
  if (!snapshot) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted launch snapshot is invalid");
1249
1258
  return { run, snapshot: loadLaunchSnapshot(snapshot) };
1250
1259
  }
1251
1260
  async loadStatus() {
1252
1261
  await this.stateLane.run(async () => void 0);
1253
- const run = decodePersistedRun(await json(join2(this.directory, "state.json")), true);
1262
+ const run = decodePersistedRun(await json(join3(this.directory, "state.json")), true);
1254
1263
  if (!run) throw new WorkflowError("RUN_NOT_FOUND", "Persisted run is invalid");
1255
1264
  this.#assertRunIdentity(run, "RUN_NOT_FOUND", "Persisted run does not belong to this project");
1256
1265
  return run;
@@ -1259,11 +1268,11 @@ var RunStore = class _RunStore {
1259
1268
  await this.stateLane.run(async () => void 0);
1260
1269
  await this.journalLane.run(async () => void 0);
1261
1270
  await this.summaryLane.run(async () => void 0);
1262
- const run = decodePersistedRun(await json(join2(this.directory, "state.json")), true);
1263
- const journal = decodeJournal(await json(join2(this.directory, "journal.json")));
1264
- 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);
1265
1274
  if (!run || !journal) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run or journal is invalid");
1266
- 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"))]);
1267
1276
  const fallbackCreatedAt = new Date(stateStat.mtimeMs).toISOString();
1268
1277
  const previousUpdatedAt = previous?.updatedAt === void 0 ? Number.NaN : Date.parse(previous.updatedAt);
1269
1278
  const updatedAt = new Date(Math.max(stateStat.mtimeMs, journalStat.mtimeMs, Number.isNaN(previousUpdatedAt) ? 0 : previousUpdatedAt)).toISOString();
@@ -1272,41 +1281,55 @@ var RunStore = class _RunStore {
1272
1281
  async saveState(run) {
1273
1282
  const write = this.stateLane.run(async () => {
1274
1283
  this.#assertRunIdentity(run, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
1275
- await atomicJson(join2(this.directory, "state.json"), run);
1284
+ await atomicJson(join3(this.directory, "state.json"), run);
1276
1285
  this.refreshSummaryBestEffort();
1277
1286
  });
1278
1287
  await write;
1279
1288
  }
1280
1289
  async updateState(update) {
1281
1290
  const write = this.stateLane.run(async () => {
1282
- const current = decodePersistedRun(await json(join2(this.directory, "state.json")));
1291
+ const current = decodePersistedRun(await json(join3(this.directory, "state.json")));
1283
1292
  if (!current) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run is invalid");
1284
1293
  this.#assertRunIdentity(current, "RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
1285
1294
  const result = await update(current);
1286
1295
  this.#assertRunIdentity(result, "INTERNAL_ERROR", "Run identity does not match its session-scoped store");
1287
- await atomicJson(join2(this.directory, "state.json"), result);
1296
+ await atomicJson(join3(this.directory, "state.json"), result);
1288
1297
  this.refreshSummaryBestEffort();
1289
1298
  return result;
1290
1299
  });
1291
1300
  return write;
1292
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
+ }
1293
1316
  async saveSnapshot(snapshot) {
1294
- 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));
1295
1318
  await write;
1296
1319
  }
1297
1320
  async appendEvent(event) {
1298
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]] }));
1299
1322
  }
1300
1323
  async saveOwnership(nodes) {
1301
- await atomicJson(join2(this.directory, "ownership.json"), nodes);
1324
+ await atomicJson(join3(this.directory, "ownership.json"), nodes);
1302
1325
  }
1303
1326
  async loadOwnership() {
1304
- const nodes = decodeOwnershipRecords(await json(join2(this.directory, "ownership.json")));
1327
+ const nodes = decodeOwnershipRecords(await json(join3(this.directory, "ownership.json")));
1305
1328
  if (!nodes) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted ownership records are invalid");
1306
1329
  return nodes;
1307
1330
  }
1308
1331
  systemPromptPath() {
1309
- return join2(this.directory, "system-prompts.json");
1332
+ return join3(this.directory, "system-prompts.json");
1310
1333
  }
1311
1334
  async readSystemPromptArtifact() {
1312
1335
  try {
@@ -1320,7 +1343,7 @@ var RunStore = class _RunStore {
1320
1343
  }
1321
1344
  async appendSystemPromptV2(entry) {
1322
1345
  const sha256 = createHash2("sha256").update(entry.prompt).digest("hex");
1323
- const bodyPath = join2(systemPromptBodiesPath(this.directory), sha256);
1346
+ const bodyPath = join3(systemPromptBodiesPath(this.directory), sha256);
1324
1347
  try {
1325
1348
  await access(bodyPath);
1326
1349
  } catch (error) {
@@ -1332,7 +1355,7 @@ var RunStore = class _RunStore {
1332
1355
  const sequence = previous + 1;
1333
1356
  await atomicWriteFile(systemPromptSequencePath(this.directory), `${String(sequence)}
1334
1357
  `);
1335
- 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 });
1336
1359
  }
1337
1360
  async migrateSystemPrompts(legacy) {
1338
1361
  await rm3(systemPromptStoragePath(this.directory), { recursive: true, force: true });
@@ -1364,14 +1387,14 @@ var RunStore = class _RunStore {
1364
1387
  const recordSequence = Number(match[1]);
1365
1388
  if (!Number.isSafeInteger(recordSequence) || recordSequence < 1) throw new Error("Persisted system-prompt records are invalid");
1366
1389
  highest = Math.max(highest, recordSequence);
1367
- const record2 = await json(join2(systemPromptRecordsPath(this.directory), name));
1390
+ const record2 = await json(join3(systemPromptRecordsPath(this.directory), name));
1368
1391
  if (!object(record2)) throw new Error("Persisted system-prompt record is invalid");
1369
1392
  const sessionId = record2.sessionId;
1370
1393
  const attempt = record2.attempt;
1371
1394
  const turn = record2.turn;
1372
1395
  const sha256 = record2.sha256;
1373
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");
1374
- const prompt = await readFile3(join2(systemPromptBodiesPath(this.directory), sha256), "utf8");
1397
+ const prompt = await readFile3(join3(systemPromptBodiesPath(this.directory), sha256), "utf8");
1375
1398
  if (createHash2("sha256").update(prompt).digest("hex") !== sha256) throw new Error("Persisted system-prompt body is invalid");
1376
1399
  entries.push({ sessionId, attempt, turn, sha256, prompt });
1377
1400
  }
@@ -1394,7 +1417,7 @@ var RunStore = class _RunStore {
1394
1417
  throw new Error("Persisted system prompts are invalid");
1395
1418
  }
1396
1419
  async #decodeJournal() {
1397
- const journal = decodeJournal(await json(join2(this.directory, "journal.json")));
1420
+ const journal = decodeJournal(await json(join3(this.directory, "journal.json")));
1398
1421
  if (!journal) throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted journal is invalid");
1399
1422
  return journal;
1400
1423
  }
@@ -1408,7 +1431,7 @@ var RunStore = class _RunStore {
1408
1431
  const journal = await this.#decodeJournal();
1409
1432
  journal.awaiting ??= {};
1410
1433
  const result = await update(journal);
1411
- await atomicJson(join2(this.directory, "journal.json"), journal);
1434
+ await atomicJson(join3(this.directory, "journal.json"), journal);
1412
1435
  this.refreshSummaryBestEffort();
1413
1436
  return result;
1414
1437
  });
@@ -1518,10 +1541,10 @@ var RunStore = class _RunStore {
1518
1541
  }
1519
1542
  expectedWorktree(owner) {
1520
1543
  const key = this.#worktreeKey(owner);
1521
- 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}` };
1522
1545
  }
1523
1546
  markerPath(owner) {
1524
- return join2(this.directory, `worktree-${this.#worktreeKey(owner)}.creating`);
1547
+ return join3(this.directory, `worktree-${this.#worktreeKey(owner)}.creating`);
1525
1548
  }
1526
1549
  namedWorktreeOwner(name) {
1527
1550
  if (!name.trim()) throw new WorkflowError("WORKTREE_FAILED", "Named worktree names must be non-empty");
@@ -1564,13 +1587,13 @@ var RunStore = class _RunStore {
1564
1587
  }
1565
1588
  structuralWorktree(owner, record2) {
1566
1589
  const expected = this.expectedWorktree(owner);
1567
- const relativePath = relative(this.directory, record2.path);
1568
- const relativeCwd = relative(record2.path, record2.cwd);
1569
- 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}`);
1570
1593
  return record2;
1571
1594
  }
1572
1595
  async #loadWorktreeRecords(missingOk = true) {
1573
- const rawRecords = await json(join2(this.directory, "worktrees.json")).catch((error) => {
1596
+ const rawRecords = await json(join3(this.directory, "worktrees.json")).catch((error) => {
1574
1597
  if (missingOk && isNodeError(error, "ENOENT")) return [];
1575
1598
  throw error;
1576
1599
  });
@@ -1580,7 +1603,7 @@ var RunStore = class _RunStore {
1580
1603
  }
1581
1604
  async borrowedWorktreeRecords(wait = true) {
1582
1605
  if (wait) await this.borrowedWorktreeLane.run(async () => void 0);
1583
- 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) => {
1584
1607
  if (isNodeError(error, "ENOENT")) return [];
1585
1608
  throw error;
1586
1609
  });
@@ -1705,11 +1728,11 @@ var RunStore = class _RunStore {
1705
1728
  const reference = this.structuralWorktree(owner, record2);
1706
1729
  paths.add(resolve2(reference.path));
1707
1730
  }
1708
- 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) => {
1709
1732
  if (isNodeError(error, "ENOENT")) return [];
1710
1733
  throw error;
1711
1734
  });
1712
- 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)}`);
1713
1736
  } catch (error) {
1714
1737
  throw coerceWorkflowError("WORKTREE_FAILED", error);
1715
1738
  }
@@ -1761,7 +1784,7 @@ var RunStore = class _RunStore {
1761
1784
  }
1762
1785
  async cleanupOrphanWorktrees() {
1763
1786
  const entries = await readdir2(this.directory).catch(() => []);
1764
- 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));
1765
1788
  }
1766
1789
  async validateWorktree(owner, cwd) {
1767
1790
  try {
@@ -1804,8 +1827,8 @@ var RunStore = class _RunStore {
1804
1827
  try {
1805
1828
  const root = (await git(this.cwd, ["rev-parse", "--show-toplevel"])).trim();
1806
1829
  const [canonicalRoot, canonicalCwd] = await Promise.all([realpath(root), realpath(this.cwd)]);
1807
- const launchRelative = relative(canonicalRoot, canonicalCwd);
1808
- 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");
1809
1832
  await this.cleanupMarker(markerPath);
1810
1833
  const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=normal"])).trim();
1811
1834
  if (dirty) {
@@ -1821,7 +1844,7 @@ var RunStore = class _RunStore {
1821
1844
  throw new Error("repository has no commits");
1822
1845
  }
1823
1846
  await mkdir2(dirname(path), { recursive: true, mode: 448 });
1824
- const record2 = { owner, path, branch, cwd: join2(path, launchRelative), base: commit };
1847
+ const record2 = { owner, path, branch, cwd: join3(path, launchRelative), base: commit };
1825
1848
  await atomicJson(markerPath, { owner, path, branch, base: commit });
1826
1849
  markerWritten = true;
1827
1850
  await git(root, ["branch", branch, commit]);
@@ -1866,7 +1889,7 @@ var RunStore = class _RunStore {
1866
1889
  return;
1867
1890
  }
1868
1891
  records.push(binding);
1869
- await atomicJson(join2(this.directory, "borrowed-worktrees.json"), records);
1892
+ await atomicJson(join3(this.directory, "borrowed-worktrees.json"), records);
1870
1893
  });
1871
1894
  await write;
1872
1895
  }
@@ -1947,12 +1970,12 @@ var RunStore = class _RunStore {
1947
1970
  return changed;
1948
1971
  }
1949
1972
  async saveResult(value) {
1950
- const path = join2(this.directory, "result.json");
1973
+ const path = join3(this.directory, "result.json");
1951
1974
  await atomicPrettyJson(path, value);
1952
1975
  return path;
1953
1976
  }
1954
1977
  async resultBytes() {
1955
- return (await stat2(join2(this.directory, "result.json"))).size;
1978
+ return (await stat2(join3(this.directory, "result.json"))).size;
1956
1979
  }
1957
1980
  async delete(confirmed) {
1958
1981
  if (!confirmed) throw new WorkflowError("CANCELLED", "Run deletion requires confirmation");
@@ -1975,19 +1998,20 @@ var RunStore = class _RunStore {
1975
1998
 
1976
1999
  // src/settings.ts
1977
2000
  import { mkdirSync, readFileSync } from "node:fs";
1978
- import { homedir as homedir3 } from "node:os";
1979
- import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
1980
- import { fileURLToPath } from "node:url";
2001
+ import { dirname as dirname2, join as join4 } from "node:path";
1981
2002
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
1982
- 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";
1983
2007
  var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
1984
2008
  function workflowSettingsPath(agentDir = getAgentDir()) {
1985
- return join3(agentDir, ROLE_DIRECTORY, "settings.json");
2009
+ return join4(agentDir, WORKFLOW_DIRECTORY, "settings.json");
1986
2010
  }
1987
2011
  function normalizedResourcePath(value, settingsPath) {
1988
2012
  if (value === "*" || value.startsWith("builtin:")) return value;
1989
- let expanded = value === "~" ? homedir3() : value.startsWith("~/") || value.startsWith("~\\") ? join3(homedir3(), value.slice(2)) : value;
1990
- if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
2013
+ let expanded = value === "~" ? homedir3() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir3(), value.slice(2)) : value;
2014
+ if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
1991
2015
  const resolved = resolve3(dirname2(settingsPath), expanded);
1992
2016
  if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
1993
2017
  if (resourcePatternHasMagic(expanded)) {
@@ -2044,18 +2068,19 @@ function selectorsFromSettings(settings) {
2044
2068
  function selectorSet(value) {
2045
2069
  return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2046
2070
  }
2047
- function validateContextFileScopes(value, rolePath) {
2071
+ function validateContextFileScopes(value, sourcePath) {
2048
2072
  if (value === void 0) return void 0;
2049
- 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`);
2050
2074
  return [...value];
2051
2075
  }
2052
2076
  function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
2053
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;
2054
2081
  const base = `${settingsPath}.extensionSettings`;
2055
- if (!object(value)) fail(errorCode2, `${base} must be an object`);
2056
2082
  const normalized = {};
2057
- for (const [namespace, raw] of Object.entries(value)) {
2058
- if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2083
+ for (const [namespace, raw] of Object.entries(generic)) {
2059
2084
  if (namespace === "herdr") {
2060
2085
  if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2061
2086
  if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
@@ -2070,7 +2095,6 @@ function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "IN
2070
2095
  normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
2071
2096
  continue;
2072
2097
  }
2073
- if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2074
2098
  normalized[namespace] = structuredClone(raw);
2075
2099
  }
2076
2100
  return deepFreeze(normalized);
@@ -2147,41 +2171,21 @@ function loadSettingsOverrides(path) {
2147
2171
  }
2148
2172
  function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2149
2173
  const projectSettingsPath = workflowProjectSettingsPath(cwd);
2150
- const global = loadSettings(globalSettingsPath);
2151
- const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2152
- const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2153
- const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2154
- const globalSelectors = selectorsFromSettings(global);
2155
- const projectSelectors = selectorsFromSettings(project);
2156
- const effectiveSelectors = selectorSet({
2157
- skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2158
- extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2159
- ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2160
- });
2161
- const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2162
- const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2163
- const sources = {
2164
- concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2165
- modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2166
- skills: sourceFor("skills"),
2167
- extensions: sourceFor("extensions"),
2168
- tools: sourceFor("tools"),
2169
- ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2170
- ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2171
- ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2172
- };
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) };
2173
2179
  const effective = Object.freeze({
2174
- concurrency: project.concurrency ?? global.concurrency,
2175
- backgroundWidget: global.backgroundWidget ?? true,
2176
- ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2177
- ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2178
- ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2179
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2180
- ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2181
- ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2182
- ...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 ?? []] }
2183
2185
  });
2184
- 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 };
2185
2189
  }
2186
2190
  function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2187
2191
  for (const name of names) {
@@ -2198,42 +2202,63 @@ function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = wo
2198
2202
  const global = selectorSet(selectorsFromSettings(resolved.global));
2199
2203
  const project = selectorSet(selectorsFromSettings(resolved.project));
2200
2204
  const effective = selectorSet(selectorsFromSettings(resolved.effective));
2201
- 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;
2202
2254
  }
2203
2255
 
2204
2256
  // src/registry.ts
2257
+ import { Value } from "typebox/value";
2205
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"]);
2206
2259
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
2207
2260
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2208
2261
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
2209
- function normalizeRoleDirectory(value) {
2210
- try {
2211
- if (value instanceof URL) {
2212
- if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
2213
- return fileURLToPath2(value);
2214
- }
2215
- if (typeof value === "string") {
2216
- if (value.startsWith("file:")) return fileURLToPath2(value);
2217
- if (isAbsolute(value)) return value;
2218
- }
2219
- } catch (error) {
2220
- fail("INVALID_METADATA", `Invalid workflow role directory: ${errorText(error)}`);
2221
- }
2222
- fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
2223
- }
2224
- function isBuiltinRoleDirectory(path) {
2225
- const canonical = canonicalPath(path);
2226
- const starterDirectory = dirname3(canonical);
2227
- if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
2228
- const distributionDirectory = dirname3(starterDirectory);
2229
- const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
2230
- try {
2231
- const metadata = JSON.parse(readFileSync2(join4(packageRoot2, "package.json"), "utf8"));
2232
- return object(metadata) && isCorePackageName(metadata.name);
2233
- } catch {
2234
- return false;
2235
- }
2236
- }
2237
2262
  function catalogSchema(schema, at) {
2238
2263
  validateSchema(schema, at);
2239
2264
  return structuredClone(schema);
@@ -2242,9 +2267,9 @@ var WorkflowRegistry = class {
2242
2267
  #extensions = /* @__PURE__ */ new Set();
2243
2268
  #functionSources = /* @__PURE__ */ new Map();
2244
2269
  #globals = /* @__PURE__ */ new Map();
2270
+ #preparationHooks = /* @__PURE__ */ new Map();
2245
2271
  #hooks = /* @__PURE__ */ new Map();
2246
2272
  #agentAttemptActions = /* @__PURE__ */ new Map();
2247
- #roleDirectories = /* @__PURE__ */ new Map();
2248
2273
  #modelAliases = /* @__PURE__ */ new Map();
2249
2274
  #settingsValidators = /* @__PURE__ */ new Set();
2250
2275
  #frozen = false;
@@ -2264,9 +2289,10 @@ var WorkflowRegistry = class {
2264
2289
  register(extension2) {
2265
2290
  if (this.#frozen) fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
2266
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");
2267
- 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");
2268
2293
  const functions = extension2.functions ?? {};
2269
2294
  const modelAliases = extension2.modelAliases ?? {};
2295
+ const agentPreparationHooks = extension2.agentPreparationHooks ?? {};
2270
2296
  const agentSetupHooks = extension2.agentSetupHooks ?? {};
2271
2297
  const agentAttemptActions = extension2.agentAttemptActions ?? {};
2272
2298
  const source = extension2.source;
@@ -2276,10 +2302,7 @@ var WorkflowRegistry = class {
2276
2302
  if (validateSettings !== void 0 && typeof validateSettings !== "function") fail("INVALID_METADATA", "Workflow extension validateSettings must be a function");
2277
2303
  if (source !== void 0 && (typeof source !== "string" || !source.trim())) fail("INVALID_METADATA", "Workflow extension source must be a non-empty module URL");
2278
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");
2279
- const roleDirectoryValues = extension2.roleDirectories === void 0 ? [] : extension2.roleDirectories;
2280
- if (!Array.isArray(roleDirectoryValues)) fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
2281
- const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
2282
- 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");
2283
2306
  const names = Object.keys(functions);
2284
2307
  if (new Set(names).size !== names.length) fail("GLOBAL_COLLISION", "Global name collision inside extension");
2285
2308
  for (const name of names) {
@@ -2298,6 +2321,11 @@ var WorkflowRegistry = class {
2298
2321
  if (!object(alias) || Object.keys(alias).some((key) => key !== "resolve") || typeof alias.resolve !== "function") fail("INVALID_METADATA", `Invalid model alias resolver: ${name}`);
2299
2322
  if (this.#modelAliases.has(name)) fail("DUPLICATE_NAME", `Model alias already registered: ${name}`);
2300
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
+ }
2301
2329
  for (const [name, hook] of Object.entries(agentSetupHooks)) {
2302
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}`);
2303
2331
  if (this.#hooks.has(name)) fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
@@ -2307,16 +2335,20 @@ var WorkflowRegistry = class {
2307
2335
  if (action.visibleStandalone !== void 0 !== (action.runStandalone !== void 0)) fail("INVALID_METADATA", `Standalone agent attempt actions require visibleStandalone and runStandalone: ${name}`);
2308
2336
  if (this.#agentAttemptActions.has(name)) fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
2309
2337
  }
2310
- const stored = deepFreeze({ ...extension2, functions, modelAliases, agentSetupHooks, agentAttemptActions, ...roleDirectories.length ? { roleDirectories } : {} });
2338
+ const stored = deepFreeze({ ...extension2, functions, modelAliases, agentPreparationHooks, agentSetupHooks, agentAttemptActions });
2311
2339
  if (source !== void 0) for (const name of names) this.#functionSources.set(name, Object.freeze({ module: source, export: "default", dependencies: Object.freeze([...dependencies]) }));
2312
2340
  this.#extensions.add(stored);
2313
- 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 } : {} }));
2314
2341
  for (const name of names) this.#globals.set(name, name);
2315
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 } });
2316
2344
  for (const [name, hook] of Object.entries(agentSetupHooks)) this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
2317
2345
  for (const [name, action] of Object.entries(agentAttemptActions)) this.#agentAttemptActions.set(name, action);
2318
2346
  if (validateSettings !== void 0) this.#settingsValidators.add({ headline: extension2.headline, validate: validateSettings });
2319
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
+ }
2320
2352
  function(name) {
2321
2353
  if (!IDENTIFIER.test(name)) fail("MISSING_WORKFLOW", `Registered functions require an unqualified name: ${name}`);
2322
2354
  const fn = [...this.#extensions].find((extension2) => extension2.functions?.[name])?.functions?.[name];
@@ -2339,7 +2371,7 @@ var WorkflowRegistry = class {
2339
2371
  let source = "global settings";
2340
2372
  try {
2341
2373
  const resolved = context ? resolveWorkflowSettings(context.cwd, context.projectTrusted, context.globalSettingsPath) : void 0;
2342
- aliases = resolved?.effective.modelAliases ?? loadSettings().modelAliases;
2374
+ aliases = resolved?.effective.modelAliases ?? (context ? loadSettings().modelAliases : resolveWorkflowSettings(process.cwd(), false).effective.modelAliases);
2343
2375
  if (resolved) {
2344
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";
2345
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 };
@@ -2398,7 +2430,7 @@ var WorkflowRegistry = class {
2398
2430
  try {
2399
2431
  validate(frozenSettings, Object.freeze({ ...context }));
2400
2432
  } catch (error) {
2401
- const location = context.source === "role" && context.role ? `role ${context.role}` : context.source;
2433
+ const location = context.source;
2402
2434
  fail("INVALID_SETTINGS", `Invalid extension settings (${headline}; ${location}): ${errorText(error)}`);
2403
2435
  }
2404
2436
  }
@@ -2417,18 +2449,15 @@ var WorkflowRegistry = class {
2417
2449
  journal.put(path, stored);
2418
2450
  return structuredClone(stored);
2419
2451
  }
2452
+ agentPreparationHooks() {
2453
+ return [...this.#preparationHooks.values()].sort(byPriorityThenName);
2454
+ }
2420
2455
  agentSetupHooks() {
2421
2456
  return [...this.#hooks.values()].sort(byPriorityThenName);
2422
2457
  }
2423
2458
  agentAttemptActions() {
2424
2459
  return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries()));
2425
2460
  }
2426
- roleDirectories() {
2427
- return [...this.#roleDirectories.keys()];
2428
- }
2429
- roleDirectoryRegistrations() {
2430
- return [...this.#roleDirectories.values()];
2431
- }
2432
2461
  modelAliases() {
2433
2462
  return [...this.#modelAliases.values()].sort((left, right) => left.name.localeCompare(right.name));
2434
2463
  }
@@ -2484,10 +2513,10 @@ function createWorkflowRegistryApi(registry) {
2484
2513
  },
2485
2514
  modelAliases: () => registry.modelAliases(),
2486
2515
  resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
2487
- roleDirectories: () => registry.roleDirectories(),
2488
- roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
2516
+ agentPreparationHooks: () => registry.agentPreparationHooks(),
2489
2517
  agentSetupHooks: () => registry.agentSetupHooks(),
2490
- agentAttemptActions: () => registry.agentAttemptActions()
2518
+ agentAttemptActions: () => registry.agentAttemptActions(),
2519
+ registrations: () => registry.registrations()
2491
2520
  };
2492
2521
  }
2493
2522
  function workflowRegistryHost() {
@@ -2504,310 +2533,11 @@ function loadingRegistry() {
2504
2533
  return workflowRegistryHost().api;
2505
2534
  }
2506
2535
  beginWorkflowExtensionLoading();
2507
- function registeredWorkflowRoleDirectoryRegistrations() {
2508
- const registrations = loadingRegistry().roleDirectoryRegistrations;
2509
- return typeof registrations === "function" ? registrations() : [];
2510
- }
2511
-
2512
- // src/roles.ts
2513
- var ROLE_DIRECTORY2 = "pi-extensible-workflows";
2514
- var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
2515
- function parseRoleMarkdown(content, strict = false, rolePath) {
2516
- if (!strict) {
2517
- if (!content.startsWith("---\n")) return { prompt: content };
2518
- const end = content.indexOf("\n---", 4);
2519
- if (end < 0) return { prompt: content };
2520
- const meta = {};
2521
- for (const line of content.slice(4, end).split("\n")) {
2522
- const match = /^(model|tools|skills|extensions|description|overrideSystemPrompt|override_system_prompt|is_system_prompt|contextFiles|extensionSettings|disabledAgentResources|thinking)\s*:\s*(.+)$/.exec(line.trim());
2523
- if (match?.[1] === "disabledAgentResources") fail("INVALID_METADATA", "disabledAgentResources is no longer supported; use skills, extensions, and tools selectors");
2524
- if (match?.[1] === "thinking") fail("INVALID_METADATA", "Role thinking is not supported; put it on model as provider/model:thinking");
2525
- if (match?.[1] && match[2]) meta[match[1]] = match[2].trim();
2526
- }
2527
- const unquote = (v) => v.replace(/^['"]|['"]$/g, "");
2528
- const parseList = (value) => value === void 0 ? void 0 : value.replace(/^\[|\]$/g, "").split(",").map((entry) => unquote(entry.trim())).filter(Boolean);
2529
- const tools2 = parseList(meta.tools);
2530
- const skills2 = parseList(meta.skills);
2531
- const extensions2 = parseList(meta.extensions);
2532
- let extensionSettings2;
2533
- if (meta.extensionSettings !== void 0) {
2534
- try {
2535
- extensionSettings2 = JSON.parse(meta.extensionSettings);
2536
- } catch (error) {
2537
- fail("INVALID_METADATA", `Invalid role extensionSettings: ${errorText(error)}`);
2538
- }
2539
- }
2540
- const rolePathValue2 = rolePath ?? "<role>";
2541
- const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
2542
- const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
2543
- if (meta.model) {
2544
- const model2 = unquote(meta.model);
2545
- assertModelThinking(model2, "Role model");
2546
- definition2.model = model2;
2547
- }
2548
- if (meta.description) definition2.description = unquote(meta.description);
2549
- if (tools2) definition2.tools = tools2;
2550
- if (skills2) definition2.skills = skills2;
2551
- if (extensions2) definition2.extensions = extensions2;
2552
- if (normalizedExtensionSettings2 !== void 0) definition2.extensionSettings = normalizedExtensionSettings2;
2553
- const overrideSystemPrompt2 = meta.overrideSystemPrompt ?? meta.override_system_prompt ?? meta.is_system_prompt;
2554
- const contextFiles2 = meta.contextFiles ? meta.contextFiles.replace(/^\[|\]$/g, "").split(",").map((scope) => unquote(scope.trim())).filter(Boolean) : void 0;
2555
- const normalizedContextFiles2 = validateContextFileScopes(contextFiles2, "role");
2556
- if (overrideSystemPrompt2) definition2.overrideSystemPrompt = overrideSystemPrompt2 === "true";
2557
- if (normalizedContextFiles2) definition2.contextFiles = normalizedContextFiles2;
2558
- return definition2;
2559
- }
2560
- const normalized = content.replace(/\r\n?/g, "\n");
2561
- if (normalized.startsWith("---\n") && normalized.indexOf("\n---", 3) < 0) fail("INVALID_METADATA", "Role frontmatter is missing its closing delimiter");
2562
- let parsed;
2563
- try {
2564
- parsed = parseFrontmatter(content);
2565
- } catch (error) {
2566
- fail("INVALID_METADATA", `Invalid role frontmatter: ${errorText(error)}`);
2567
- }
2568
- if (!object(parsed.frontmatter)) fail("INVALID_METADATA", "Role frontmatter must be an object");
2569
- const { model, tools, skills, extensions, extensionSettings, description, contextFiles } = parsed.frontmatter;
2570
- if (Object.prototype.hasOwnProperty.call(parsed.frontmatter, "disabledAgentResources")) fail("INVALID_METADATA", "disabledAgentResources is no longer supported; use skills, extensions, and tools selectors");
2571
- if (Object.prototype.hasOwnProperty.call(parsed.frontmatter, "thinking")) fail("INVALID_METADATA", "Role thinking is not supported; put it on model as provider/model:thinking");
2572
- const overrideSystemPrompt = parsed.frontmatter.overrideSystemPrompt ?? parsed.frontmatter.override_system_prompt ?? parsed.frontmatter.is_system_prompt;
2573
- if (model !== void 0 && (typeof model !== "string" || model.trim() === "")) fail("INVALID_METADATA", "Role model must be a non-empty string");
2574
- 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");
2575
- if (overrideSystemPrompt !== void 0 && typeof overrideSystemPrompt !== "boolean") fail("INVALID_METADATA", "Role overrideSystemPrompt must be a boolean");
2576
- const normalizedContextFiles = validateContextFileScopes(contextFiles, rolePath ?? "<role>");
2577
- const rolePathValue = rolePath ?? "<role>";
2578
- const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
2579
- const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
2580
- const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
2581
- const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
2582
- const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
2583
- const normalizedModel = typeof model === "string" ? model.trim() : void 0;
2584
- if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
2585
- const definition = { prompt: parsed.body };
2586
- if (normalizedDescription !== void 0) definition.description = normalizedDescription;
2587
- if (normalizedModel !== void 0) definition.model = normalizedModel;
2588
- if (normalizedTools !== void 0) definition.tools = normalizedTools;
2589
- if (normalizedSkills !== void 0) definition.skills = normalizedSkills;
2590
- if (normalizedExtensions !== void 0) definition.extensions = normalizedExtensions;
2591
- if (normalizedExtensionSettings !== void 0) definition.extensionSettings = normalizedExtensionSettings;
2592
- if (typeof overrideSystemPrompt === "boolean") definition.overrideSystemPrompt = overrideSystemPrompt;
2593
- if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
2594
- return definition;
2595
- }
2596
- function workflowRoleDirectories(agentDir = getAgentDir2()) {
2597
- return [join5(agentDir, ROLE_DIRECTORY2, "roles")];
2598
- }
2599
- function projectRoleDirectories(root) {
2600
- return [join5(root, ROLE_DIRECTORY2, "roles")];
2601
- }
2602
- function roleDirectorySources(dirs) {
2603
- const seen = /* @__PURE__ */ new Set();
2604
- return dirs.flatMap((value) => {
2605
- const source = typeof value === "string" ? { path: value } : { path: value.path, extension: value.extension, ...value.builtin === true ? { builtin: true } : {} };
2606
- if (seen.has(source.path)) return [];
2607
- seen.add(source.path);
2608
- return [source];
2609
- });
2610
- }
2611
- function roleDirectoryLabel(source, extension2 = true) {
2612
- return source.extension ? `Extension "${source.extension.headline}" (${source.extension.version})` : extension2 ? "Registered workflow extension" : "Standard workflow";
2613
- }
2614
- function isRoleFile(dir, entry) {
2615
- if (extname(entry.name) !== ".md") return false;
2616
- if (entry.isFile()) return true;
2617
- if (!entry.isSymbolicLink()) return false;
2618
- try {
2619
- return statSync(join5(dir, entry.name)).isFile();
2620
- } catch (error) {
2621
- if (isNodeError(error, "ENOENT")) return false;
2622
- throw error;
2623
- }
2624
- }
2625
- function scanRoleFiles(dirs, extension2) {
2626
- const files = [];
2627
- for (const source of roleDirectorySources(dirs)) {
2628
- let entries;
2629
- try {
2630
- entries = readdirSync(source.path, { withFileTypes: true });
2631
- } catch (error) {
2632
- if (!extension2 && isNodeError(error, "ENOENT")) continue;
2633
- fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension2)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
2634
- }
2635
- for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join5(source.path, entry.name), source });
2636
- }
2637
- return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
2638
- }
2639
- function roleEntries(dirs, extension2) {
2640
- const files = scanRoleFiles(dirs, extension2);
2641
- const starterFiles = files.filter(({ source }) => source.builtin === true);
2642
- const regularFiles = files.filter(({ source }) => source.builtin !== true);
2643
- if (extension2) {
2644
- const byName = /* @__PURE__ */ new Map();
2645
- for (const file of regularFiles) byName.set(file.name, [...byName.get(file.name) ?? [], file]);
2646
- 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("; ")}`);
2647
- }
2648
- const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
2649
- try {
2650
- const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
2651
- roleDefinitionPaths.set(definition, path);
2652
- return [name, { name, path, source, definition }];
2653
- } catch (error) {
2654
- if (extension2) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
2655
- throw error;
2656
- }
2657
- }));
2658
- return extension2 ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
2659
- }
2660
- function discoveryInput(input) {
2661
- return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir2(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
2662
- }
2663
- function discoveredRoleEntries(input) {
2664
- const resolved = discoveryInput(input);
2665
- return {
2666
- ...roleEntries(resolved.extensionRoleDirectories, true),
2667
- ...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
2668
- ...resolved.projectTrusted ? roleEntries(projectRoleDirectories(join5(resolved.cwd, ".pi")), false) : {}
2669
- };
2670
- }
2671
- function discoverRoles(input) {
2672
- return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
2673
- }
2674
- function loadAgentDefinitions(cwd, agentDir = getAgentDir2(), projectTrusted = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
2675
- return discoverRoles({ cwd, agentDir, projectTrusted, extensionRoleDirectories });
2676
- }
2677
- function canonicalExtensionSelector(selector, base = process.cwd()) {
2678
- const negated = selector.startsWith("!");
2679
- const body = negated ? selector.slice(1) : selector;
2680
- if (body === "*" || body === "**" || body.startsWith("**/") || body.startsWith("builtin:")) return selector;
2681
- const resolved = resolve4(base, body);
2682
- if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
2683
- return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
2684
- }
2685
- function selectorsFromSettings2(settings) {
2686
- return { ...settings.skills === void 0 ? {} : { skills: settings.skills }, ...settings.extensions === void 0 ? {} : { extensions: settings.extensions }, ...settings.tools === void 0 ? {} : { tools: settings.tools } };
2687
- }
2688
- function rootToolNames(value) {
2689
- return value === void 0 ? void 0 : value instanceof Set ? [...value] : [...value];
2690
- }
2691
- function roleDefinitionFor(name, options) {
2692
- if (name === void 0) return {};
2693
- if (options.definition !== void 0) {
2694
- const path = roleDefinitionPaths.get(options.definition);
2695
- return { definition: options.definition, ...path === void 0 ? {} : { path } };
2696
- }
2697
- if (options.definitions?.[name] !== void 0) {
2698
- const definition = options.definitions[name];
2699
- const path = roleDefinitionPaths.get(definition);
2700
- return { definition, ...path === void 0 ? {} : { path } };
2701
- }
2702
- const entries = discoveredRoleEntries(options);
2703
- const entry = entries[name];
2704
- if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
2705
- return entry;
2706
- }
2707
- function resolveRole(name, options) {
2708
- const { definition, path } = roleDefinitionFor(name, options);
2709
- const settings = options.selectorSources === void 0 ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir2())) : void 0;
2710
- const baseSources = options.selectorSources ?? { global: selectorsFromSettings2(settings?.global ?? {}), project: selectorsFromSettings2(settings?.project ?? {}) };
2711
- const canonicalSources = (sources) => ({
2712
- ...sources,
2713
- global: { ...sources.global, ...sources.global.extensions === void 0 ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
2714
- project: { ...sources.project, ...sources.project.extensions === void 0 ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
2715
- ...sources.role === void 0 ? {} : { role: { ...sources.role, ...sources.role.extensions === void 0 ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } },
2716
- ...sources.call === void 0 ? {} : { call: { ...sources.call, ...sources.call.extensions === void 0 ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } }
2717
- });
2718
- 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;
2719
- 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;
2720
- const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...roleLayer === void 0 ? {} : { role: roleLayer }, ...callLayer === void 0 ? {} : { call: callLayer } });
2721
- const callSelectors = selectorSources.call;
2722
- const roleSelectors = selectorSources.role;
2723
- const selectorLayers = {
2724
- skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
2725
- extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
2726
- tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools]
2727
- };
2728
- const resources = options.resources ?? {};
2729
- const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
2730
- const tools = options.effectiveTools ?? (candidateTools === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.tools, candidateTools));
2731
- const rootTools = rootToolNames(options.rootTools);
2732
- if (rootTools !== void 0) {
2733
- const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
2734
- if (outsideEffectiveTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
2735
- const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
2736
- if (outsideCallTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
2737
- const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
2738
- if (outsideRootTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
2739
- }
2740
- const selectedSkills = resources.skills === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.skills, resources.skills);
2741
- const extensionResources = resources.extensions?.map((extension2) => extensionIdentity(extension2));
2742
- const selectedExtensions = extensionResources === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
2743
- const unmatchedSkills = resources.skills === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
2744
- const unmatchedExtensions = extensionResources === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
2745
- const unmatchedTools = candidateTools === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
2746
- const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
2747
- const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
2748
- const blockedAlias = requestedModel?.split(":", 1)[0];
2749
- if (requestedModel !== void 0 && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
2750
- const target = options.blockedAliasTargets?.[blockedAlias];
2751
- fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
2752
- }
2753
- let model = options.modelOverride;
2754
- if (model === void 0 && requestedModel !== void 0) {
2755
- assertModelThinking(requestedModel);
2756
- model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
2757
- }
2758
- if (model === void 0) model = options.rootModel;
2759
- const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === void 0 ? void 0 : /* @__PURE__ */ new Set([modelCapability(options.rootModel)]));
2760
- 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})` : ""}`);
2761
- const prompt = definition?.prompt ?? "";
2762
- const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
2763
- const contextFiles = options.contextFiles ?? definition?.contextFiles;
2764
- 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 } };
2765
- }
2766
-
2767
- // src/validation.ts
2768
- var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
2769
- function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
2770
- switch (key) {
2771
- case "label":
2772
- if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent label must be a non-empty string");
2773
- break;
2774
- case "model":
2775
- if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent model must be a non-empty string");
2776
- assertModelThinking(value, "agent model");
2777
- if (aliases !== void 0) resolveModelReference(value, aliases, knownModels, settingsPath);
2778
- break;
2779
- case "tools":
2780
- case "skills":
2781
- case "extensions":
2782
- validateSelectorList(value, "agent options", key, "INVALID_METADATA", key !== "extensions");
2783
- break;
2784
- case "contextFiles":
2785
- validateContextFileScopes(value, "agent options");
2786
- break;
2787
- case "role":
2788
- if (typeof value !== "string" || !value.trim()) fail("INVALID_METADATA", "agent role must be a non-empty string");
2789
- break;
2790
- case "outputSchema":
2791
- validateSchema(value, "agent outputSchema");
2792
- break;
2793
- case "retries":
2794
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) fail("INVALID_METADATA", "agent retries must be a non-negative integer");
2795
- break;
2796
- case "timeoutMs":
2797
- if (value !== null && !positiveInteger(value)) fail("INVALID_METADATA", "agent timeoutMs must be null or a positive integer");
2798
- break;
2799
- }
2800
- }
2801
- function validateAgentOptions(value) {
2802
- if (!object(value) || !jsonValue(value)) fail("INVALID_METADATA", "agent options must be a JSON object");
2803
- if (Object.prototype.hasOwnProperty.call(value, "thinking")) fail("INVALID_METADATA", "agent thinking is not supported; use model provider/model:thinking");
2804
- for (const [key, option] of Object.entries(value)) if (AGENT_OPTION_KEYS.has(key)) validateAgentOption(key, option);
2805
- return value;
2806
- }
2807
2536
 
2808
2537
  // src/execution.ts
2809
2538
  var RPC_LIMIT_BYTES = 10 * 1024 * 1024;
2810
2539
  var HEARTBEAT_INTERVAL_MS = 1e3;
2540
+ var FUNCTION_HANDLE_PREFIX = "function-handle";
2811
2541
  var WORK_RESULT_BRAND = "__workResult";
2812
2542
  var childSource = String.raw`
2813
2543
  "use strict";
@@ -2918,15 +2648,13 @@ const internalAgent = (...values) => {
2918
2648
  void result.then(() => agentInflight.delete(occurrenceKey), () => agentInflight.delete(occurrenceKey));
2919
2649
  return guardedAgentResult(result);
2920
2650
  };
2921
- const AGENT_HANDLE_KEYS = ["name", "role", "model", "tools", "skills", "extensions", "contextFiles", "label"];
2922
2651
  const agentHandleNames = new Set();
2923
2652
  const agentHandleInflight = new Set();
2924
2653
  const internalAgentCreate = (...values) => {
2925
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");
2926
2655
  const options = values[0];
2927
2656
  const name = named(options.name, "agent.create");
2928
- const unsupported = Object.keys(options).find(key => !AGENT_HANDLE_KEYS.includes(key));
2929
- 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}/");
2930
2658
  if (agentHandleNames.has(name)) throw workError("INVALID_METADATA", "Duplicate agent handle name: " + name);
2931
2659
  agentHandleNames.add(name);
2932
2660
  const config = Object.freeze(Object.fromEntries(Object.entries(options).filter(([key]) => key !== "name")));
@@ -3135,20 +2863,28 @@ Promise.resolve().then(() => new vm.Script("(async(__pi_extensible_workflows_age
3135
2863
  .catch(error => send({ type: "error", error: workerError(error) }))
3136
2864
  .finally(() => clearInterval(heartbeat));
3137
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
+ }
3138
2873
 
3139
2874
  // src/host.ts
3140
2875
  import { Type as Type3 } from "@earendil-works/pi-ai";
3141
- 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";
3142
2877
 
3143
2878
  // src/agent-execution.ts
3144
- import { existsSync, realpathSync as realpathSync2, readFileSync as readFileSync4 } from "node:fs";
2879
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
3145
2880
  import { copyFile } from "node:fs/promises";
3146
2881
  import { homedir as homedir4 } from "node:os";
3147
- 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";
3148
2883
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3149
2884
  import { Type } from "@earendil-works/pi-ai";
2885
+ import { Value as Value2 } from "typebox/value";
3150
2886
  import { Compile } from "typebox/compile";
3151
- import { createAgentSession, createCodemodeExtension, createMcpExtension, createToolSearchExtension, 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";
3152
2888
 
3153
2889
  // src/pi-runtime-adapter.ts
3154
2890
  var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
@@ -3423,8 +3159,8 @@ function createLiveSessionHandoff() {
3423
3159
  let request;
3424
3160
  const waitForBoundary = () => {
3425
3161
  if (!turnActive) return Promise.resolve();
3426
- boundary ??= new Promise((resolve6) => {
3427
- resolveBoundary = resolve6;
3162
+ boundary ??= new Promise((resolve5) => {
3163
+ resolveBoundary = resolve5;
3428
3164
  });
3429
3165
  return boundary;
3430
3166
  };
@@ -3456,11 +3192,11 @@ function createLiveSessionHandoff() {
3456
3192
  if (request) return request;
3457
3193
  if (isReleased()) return;
3458
3194
  state = "handoff-pending";
3459
- takeover = new Promise((resolve6) => {
3460
- resolveTakeover = resolve6;
3195
+ takeover = new Promise((resolve5) => {
3196
+ resolveTakeover = resolve5;
3461
3197
  });
3462
- resume = new Promise((resolve6) => {
3463
- resolveResume = resolve6;
3198
+ resume = new Promise((resolve5) => {
3199
+ resolveResume = resolve5;
3464
3200
  });
3465
3201
  request = (async () => {
3466
3202
  await waitForBoundary();
@@ -3659,9 +3395,9 @@ var PiRuntimeAgentRunner = class {
3659
3395
  let structuredResult;
3660
3396
  const resultTool = prepared.resultTool;
3661
3397
  if (!resultTool) throw new WorkflowError("INTERNAL_ERROR", "Workflow agent session is missing its workflow_result tool");
3662
- const preparedForSession = preparedForRuntimeRequest(prepared, request.customTools, request.signal, (value) => {
3398
+ const preparedForSession = Object.freeze({ ...preparedForRuntimeRequest(prepared, request.customTools, request.signal, (value) => {
3663
3399
  structuredResult = value;
3664
- }, resultTool);
3400
+ }, resultTool), ...request.turnControl ? { turnControl: request.turnControl } : {} });
3665
3401
  const callbacks = this.#options.callbacks;
3666
3402
  const handoff = this.#options.handoff;
3667
3403
  let adapter;
@@ -3894,6 +3630,7 @@ var PiRuntimeAgentRunner = class {
3894
3630
  throwIfProgressFailed();
3895
3631
  beginTurn(false);
3896
3632
  throwIfTurnPolicyFailed();
3633
+ if (request.turnControl && !session.turnControlDelivered) await activeSession.steer(request.turnControl);
3897
3634
  await promptAndRecover(request.task, true);
3898
3635
  completeTurn(hasResult() || !hasToolCall(lastAssistant), false, true);
3899
3636
  if (turnPolicyFailure) throw turnPolicyFailure;
@@ -3984,40 +3721,83 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
3984
3721
  assertModelThinking(value);
3985
3722
  return resolveModelReference(value, aliases, knownModels, settingsPath);
3986
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
+ }
3987
3767
  function accounting(stats) {
3988
3768
  return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
3989
3769
  }
3990
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"];
3991
3771
  function isWorkflowHostEntry(path) {
3992
3772
  const entryPath = canonicalPath(path);
3993
- let packageRoot2 = dirname5(entryPath);
3994
- while (packageRoot2 !== dirname5(packageRoot2)) {
3995
- const packageMetadataPath = join6(packageRoot2, "package.json");
3773
+ let packageRoot2 = dirname3(entryPath);
3774
+ while (packageRoot2 !== dirname3(packageRoot2)) {
3775
+ const packageMetadataPath = join5(packageRoot2, "package.json");
3996
3776
  if (existsSync(packageMetadataPath)) {
3997
3777
  try {
3998
- const metadata = JSON.parse(readFileSync4(packageMetadataPath, "utf8"));
3778
+ const metadata = JSON.parse(readFileSync2(packageMetadataPath, "utf8"));
3999
3779
  if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !isCorePackageName(metadata.name)) return false;
4000
- return WORKFLOW_HOST_ENTRY_PATHS.some((entry) => canonicalPath(join6(packageRoot2, entry)) === entryPath);
3780
+ return WORKFLOW_HOST_ENTRY_PATHS.some((entry) => canonicalPath(join5(packageRoot2, entry)) === entryPath);
4001
3781
  } catch {
4002
3782
  return false;
4003
3783
  }
4004
3784
  }
4005
- packageRoot2 = dirname5(packageRoot2);
3785
+ packageRoot2 = dirname3(packageRoot2);
4006
3786
  }
4007
3787
  return false;
4008
3788
  }
4009
- var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
3789
+ var WORKFLOW_DIRECTORY2 = "pi-extensible-workflows";
4010
3790
  function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
4011
- const projectPath = join6(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
3791
+ const projectPath = join5(cwd, ".pi", WORKFLOW_DIRECTORY2, "SYSTEM.md");
4012
3792
  if (projectTrusted && existsSync(projectPath)) return projectPath;
4013
- 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")];
4014
3794
  return globalPaths.find((path) => existsSync(path));
4015
3795
  }
4016
3796
  function filterContextFiles(base, scopes, cwd, agentDir) {
4017
3797
  const globalPath = canonicalPath(agentDir);
4018
3798
  const cwdPath = canonicalPath(cwd);
4019
3799
  return base.filter(({ path }) => {
4020
- const resolvedPath = canonicalPath(dirname5(path));
3800
+ const resolvedPath = canonicalPath(dirname3(path));
4021
3801
  return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
4022
3802
  });
4023
3803
  }
@@ -4043,10 +3823,10 @@ async function loadNativeCoreModule(file, label2) {
4043
3823
  const packageDirectory = await loadPiPackageDirectory();
4044
3824
  let modulePath;
4045
3825
  if (packageDirectory.directory) {
4046
- const candidate = resolve5(packageDirectory.directory, "dist/core", file);
3826
+ const candidate = resolve4(packageDirectory.directory, "dist/core", file);
4047
3827
  if (existsSync(candidate)) modulePath = candidate;
4048
3828
  }
4049
- modulePath ??= resolve5(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core", file);
3829
+ modulePath ??= resolve4(dirname3(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core", file);
4050
3830
  const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
4051
3831
  let loaded = nativeCoreModules.get(file);
4052
3832
  if (!loaded) {
@@ -4137,14 +3917,43 @@ var BUILTIN_EXTENSION_PREFIX = "builtin:";
4137
3917
  function isLlamaModel(model) {
4138
3918
  return model.provider === "llama.cpp";
4139
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
+ }
4140
3949
  async function createLocalPiSessionHandle(input, sessionStartEvent) {
4141
- const agentDir = input.agentDir ?? getAgentDir3();
3950
+ const agentDir = input.agentDir ?? getAgentDir2();
4142
3951
  const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
4143
3952
  const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
4144
3953
  const contextFilesOverride = input.contextFiles === void 0 ? void 0 : (base) => ({ ...base, agentsFiles: filterContextFiles(base.agentsFiles, input.contextFiles ?? [], input.cwd, agentDir) });
4145
- 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));
4146
3955
  if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
4147
- 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") });
4148
3957
  const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
4149
3958
  const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
4150
3959
  let settingsManager;
@@ -4160,25 +3969,23 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
4160
3969
  const resolved = await packageManager.resolve();
4161
3970
  const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => extensionIdentity(path)))];
4162
3971
  const selectorSources = policy.selectorSources;
4163
- const selection = resolveRole(void 0, {
4164
- cwd: input.cwd,
4165
- selectorSources,
4166
- resources: { extensions: discoveredExtensions }
4167
- });
4168
- const selectedExtensions = selection.selectedExtensions ?? [];
3972
+ const selectedExtensions = selectResourcesByLayers(resourceSelectorLayers(selectorSources, "extensions"), discoveredExtensions).filter((path) => policy.capabilities === void 0 || policy.capabilities.extensions.includes(path));
4169
3973
  const extensionPaths = selectedExtensions.filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path));
4170
3974
  policy.selectedExtensions = selectedExtensions;
4171
- policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
3975
+ policy.unmatchedExtensions = unmatchedResourcePatterns(policy.effective.extensions, discoveredExtensions);
4172
3976
  const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
4173
- const updateSkillMatches = (skills) => {
3977
+ const updateSkillMatches = (skills, extensionSkillPaths) => {
4174
3978
  const names = [...new Set(skills.map(({ name }) => name))];
4175
- const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
4176
- 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];
4177
3984
  policy.selectedSkills = selectedSkills;
4178
- policy.unmatchedSkills = selected.unmatchedSkills ?? [];
3985
+ policy.unmatchedSkills = unmatchedResourcePatterns(policy.effective.skills, names);
4179
3986
  return new Set(names.filter((name) => !selectedSkills.includes(name)));
4180
3987
  };
4181
- resourceLoader = new DefaultResourceLoader({
3988
+ const workflowLoader = new WorkflowResourceLoader(input.cwd, {
4182
3989
  cwd: input.cwd,
4183
3990
  agentDir,
4184
3991
  settingsManager,
@@ -4189,12 +3996,13 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
4189
3996
  extensionFactories,
4190
3997
  ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {},
4191
3998
  skillsOverride: (base) => {
4192
- const disabledSkills = updateSkillMatches(base.skills);
3999
+ const disabledSkills = updateSkillMatches(base.skills, workflowLoader.extensionSkillPaths);
4193
4000
  return { ...base, skills: base.skills.filter(({ name }) => !disabledSkills.has(name)) };
4194
4001
  },
4195
4002
  ...input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {},
4196
4003
  ...systemPromptOptions
4197
4004
  });
4005
+ resourceLoader = workflowLoader;
4198
4006
  await resourceLoader.reload();
4199
4007
  } else {
4200
4008
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: true });
@@ -4212,7 +4020,7 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
4212
4020
  throw new WorkflowError("UNKNOWN_MODEL", `Unknown model: ${input.model.provider}/${input.model.model}${because}`);
4213
4021
  }
4214
4022
  const effectiveSessionStartEvent = input.settings === void 0 && sessionStartEvent === void 0 ? void 0 : { ...sessionStartEvent ?? { type: "session_start", reason: "startup" }, settings: input.settings ?? Object.freeze({}) };
4215
- 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 });
4216
4024
  const nativeDispose = session.dispose.bind(session);
4217
4025
  let disposal;
4218
4026
  const shutdown = (reason, targetSessionFile) => {
@@ -4575,6 +4383,10 @@ async function createLocalWorkflowAgentSession(prepared, context) {
4575
4383
  return session;
4576
4384
  }
4577
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
+ }
4578
4390
  function changedOption(options, baseline, key) {
4579
4391
  return JSON.stringify(options[key]) !== JSON.stringify(baseline[key]);
4580
4392
  }
@@ -4597,7 +4409,7 @@ function resourcePolicySummary(policy, tools = policy.selectedTools ?? []) {
4597
4409
  }
4598
4410
  var RESOURCE_SELECTOR_KEYS = ["skills", "extensions", "tools"];
4599
4411
  function resourceSelectorLayers(sources, key) {
4600
- return [sources.global[key], sources.project[key], sources.role?.[key], sources.call?.[key]];
4412
+ return [sources.global[key], sources.project[key], sources.call?.[key]];
4601
4413
  }
4602
4414
  function selectorListWidened(ceiling, candidate) {
4603
4415
  let candidateIndex = 0;
@@ -4654,32 +4466,32 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
4654
4466
  };
4655
4467
  }
4656
4468
  function packageRoot(start) {
4657
- let current = dirname5(realpathSync2(start));
4469
+ let current = dirname3(canonicalPath(start));
4658
4470
  for (; ; ) {
4659
- 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")];
4660
4472
  for (const candidate of candidates) {
4661
- const packageJsonPath = join6(candidate, "package.json");
4473
+ const packageJsonPath = join5(candidate, "package.json");
4662
4474
  if (!existsSync(packageJsonPath)) continue;
4663
- const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
4475
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
4664
4476
  if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
4665
4477
  }
4666
- const parent = dirname5(current);
4478
+ const parent = dirname3(current);
4667
4479
  if (parent === current) return void 0;
4668
4480
  current = parent;
4669
4481
  }
4670
4482
  }
4671
4483
  function piPackageEntrypoint(root) {
4672
- const packageJson = JSON.parse(readFileSync4(join6(root, "package.json"), "utf8"));
4484
+ const packageJson = JSON.parse(readFileSync2(join5(root, "package.json"), "utf8"));
4673
4485
  if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4674
4486
  const bin = packageJson.bin;
4675
4487
  const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
4676
4488
  if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4677
- const entrypoint = resolve5(root, cli);
4489
+ const entrypoint = resolve4(root, cli);
4678
4490
  if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
4679
4491
  return entrypoint;
4680
4492
  }
4681
4493
  async function resolvePiRuntime() {
4682
- const executableName = basename4(process.execPath).toLowerCase();
4494
+ const executableName = basename2(process.execPath).toLowerCase();
4683
4495
  const bunBinary = ["$bunfs", "~BUN", "%7EBUN"].some((marker) => import.meta.url.includes(marker)) || Boolean(process.versions.bun && !["bun", "bun.exe"].includes(executableName));
4684
4496
  if (bunBinary) return { runtime: Object.freeze({ executable: process.execPath }) };
4685
4497
  const errors = [];
@@ -4703,19 +4515,12 @@ async function resolvePiRuntime() {
4703
4515
  }
4704
4516
  }
4705
4517
  var piRuntimeResolution = resolvePiRuntime();
4706
- function workflowAgentPrompt(task, options, attempt, previousError) {
4707
- 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");
4708
- const instruction = options.budget?.instruction();
4709
- return `${basePrompt}
4710
-
4711
- Task:
4712
- ${task}${instruction ? `
4713
-
4714
- ${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");
4715
4520
  }
4716
4521
  async function preparedAgentSession(input, initialPrompt) {
4717
4522
  const { runtime: piRuntime, error: piRuntimeError } = await piRuntimeResolution;
4718
- 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;
4719
4524
  const prepared = {
4720
4525
  cwd: input.cwd,
4721
4526
  model: Object.freeze({ ...input.model }),
@@ -4745,49 +4550,30 @@ function agentSetupSummary(setup, hookNames) {
4745
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) } : {} };
4746
4551
  }
4747
4552
  async function attemptSessionInput(source, attempt) {
4748
- 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`);
4749
4554
  await copyFile(source, target);
4750
4555
  return target;
4751
4556
  }
4752
- 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) {
4753
4558
  const setupSignal = signal ?? root.runContext?.signal ?? new AbortController().signal;
4754
4559
  const baselineOptions = structuredClone(options.agentOptions ?? {});
4755
4560
  const baseResourcePolicy = await root.agentResourcePolicy?.();
4756
- const roleName2 = typeof options.role === "string" ? options.role : void 0;
4757
- const roleDefinition = roleName2 ? root.agentDefinitions?.[roleName2] : void 0;
4758
- const roleSelectors = roleDefinition ? {
4759
- ...roleDefinition.skills === void 0 ? {} : { skills: roleDefinition.skills },
4760
- ...roleDefinition.extensions === void 0 ? {} : { extensions: roleDefinition.extensions.map((selector) => canonicalExtensionSelector(selector, cwd)) },
4761
- ...roleDefinition.tools === void 0 ? {} : { tools: roleDefinition.tools }
4762
- } : void 0;
4763
- const selectorValue = (key, fallback) => {
4764
- const value = baselineOptions[key];
4765
- if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return value;
4766
- 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
4767
4570
  };
4768
- const rawCallSkills = selectorValue("skills", options.skills);
4769
- const rawCallExtensions = selectorValue("extensions", options.extensions)?.map((selector) => canonicalExtensionSelector(selector, cwd));
4770
- const rawCallTools = selectorValue("tools", options.tools);
4771
- const callSelectors = { ...rawCallSkills === void 0 ? {} : { skills: rawCallSkills }, ...rawCallExtensions === void 0 ? {} : { extensions: rawCallExtensions }, ...rawCallTools === void 0 ? {} : { tools: rawCallTools } };
4772
- const resourcePolicy = baseResourcePolicy ? {
4773
- ...baseResourcePolicy,
4774
- effective: {
4775
- skills: [...baseResourcePolicy.effective.skills, ...roleSelectors?.skills ?? [], ...rawCallSkills ?? []],
4776
- extensions: [...baseResourcePolicy.effective.extensions, ...roleSelectors?.extensions ?? [], ...rawCallExtensions ?? []],
4777
- ...baseResourcePolicy.effective.tools === void 0 && roleSelectors?.tools === void 0 && rawCallTools === void 0 ? {} : { tools: [...baseResourcePolicy.effective.tools ?? [], ...roleSelectors?.tools ?? [], ...rawCallTools ?? []] }
4778
- },
4779
- selectorSources: { global: baseResourcePolicy.selectorSources.global, project: baseResourcePolicy.selectorSources.project, ...roleSelectors ? { role: roleSelectors } : {}, ...Object.keys(callSelectors).length ? { call: callSelectors } : {} }
4780
- } : void 0;
4781
- if (resourcePolicy) {
4782
- resourcePolicy.selectedTools = resolved.tools;
4783
- resourcePolicy.unmatchedTools = unmatchedResourcePatterns(resourceSelectorLayers(resourcePolicy.selectorSources, "tools").flatMap((layer) => layer ?? []), [...root.tools]);
4784
- }
4785
- const resourcePolicyCeiling = resourcePolicy ? structuredClone(resourcePolicy) : void 0;
4571
+ const resourcePolicyCeiling = structuredClone(resourcePolicy);
4786
4572
  const sessionPath = options.sessionPath === void 0 || inspection ? options.sessionPath : await attemptSessionInput(options.sessionPath, attempt);
4787
- const extensionSettings = mergeWorkflowExtensionSettings(options.inheritedExtensionSettings ?? root.extensionSettings, roleDefinition?.extensionSettings) ?? Object.freeze({});
4788
- 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 } : {} });
4789
4575
  if (options.agentNodeId !== void 0) root.onAgentSettings?.(options.agentNodeId, extensionSettings);
4790
- 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) };
4791
4577
  const setup = { prompt: task, options: sessionInput.options ?? {}, sessionInput, prepared: await preparedAgentSession(sessionInput, task), transport };
4792
4578
  const base = fallbackSetupContext(root, options, setupSignal);
4793
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" } : {} });
@@ -4807,19 +4593,42 @@ async function prepareAgentSetup(root, transport, task, options, resolved, cwd,
4807
4593
  try {
4808
4594
  if (resourcePolicyWidened(resourcePolicyCeiling, setup.sessionInput.resourcePolicy)) throw new WorkflowError("INVALID_METADATA", "Agent setup widened the prepared resource policy");
4809
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
+ }
4810
4603
  setup.sessionInput.options = setup.options;
4811
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);
4812
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];
4813
- 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}`);
4814
4617
  const customToolNames = /* @__PURE__ */ new Set([...(setup.sessionInput.customTools ?? []).map(({ name }) => name), ...setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : []]);
4815
4618
  const widened = setup.sessionInput.tools.find((tool) => !resolved.tools.includes(tool) && !customToolNames.has(tool));
4816
4619
  const outsideTool = widened ?? setup.sessionInput.tools.find((tool) => !root.tools.has(tool) && !customToolNames.has(tool));
4817
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") ?? [];
4818
4627
  } catch (error) {
4819
4628
  setup.prepared = await preparedAgentSession(setup.sessionInput, task);
4820
4629
  return { setup, summary: agentSetupSummary(setup, hookNames), failure: { error } };
4821
4630
  }
4822
- 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);
4823
4632
  return { setup, summary: agentSetupSummary(setup, hookNames) };
4824
4633
  }
4825
4634
  function attemptRecord(transport, attempt, session, setup, stats, result, error) {
@@ -4839,54 +4648,93 @@ var WorkflowAgentExecutor = class {
4839
4648
  }
4840
4649
  root;
4841
4650
  transport;
4842
- warnedRoleTools = /* @__PURE__ */ new Set();
4843
4651
  setRunContext(runContext) {
4844
4652
  this.root.runContext = runContext;
4845
4653
  }
4846
4654
  resolve(options, inheritedTools) {
4847
- const roleName2 = typeof options.role === "string" ? options.role : void 0;
4848
- const roleDefinition = roleName2 ? this.root.agentDefinitions?.[roleName2] : void 0;
4849
- if (roleName2 && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName2}`);
4850
- if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
4851
- const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
4852
- if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
4853
- const key = `${roleName2 ?? "agent"}:${body}`;
4854
- if (!this.warnedRoleTools.has(key)) {
4855
- this.warnedRoleTools.add(key);
4856
- this.root.onResourceWarning?.(`Tool not available in this session for role ${roleName2 ?? "agent"}: ${body}. The agent runs without it.`);
4857
- }
4858
- }
4859
- }
4860
- const resolved = resolveRole(roleName2, {
4861
- cwd: this.root.cwd,
4862
- ...roleDefinition === void 0 ? {} : { definition: roleDefinition },
4863
- ...this.root.agentDefinitions === void 0 ? {} : { definitions: this.root.agentDefinitions },
4864
- ...this.root.agentDir === void 0 ? {} : { agentDir: this.root.agentDir },
4865
- selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
4866
- ...this.root.modelAliases === void 0 ? {} : { modelAliases: this.root.modelAliases },
4867
- ...this.root.knownModels === void 0 ? {} : { knownModels: this.root.knownModels },
4868
- ...this.root.availableModels === void 0 ? {} : { availableModels: this.root.availableModels },
4869
- ...this.root.blockedAliases === void 0 ? {} : { blockedAliases: this.root.blockedAliases },
4870
- ...this.root.blockedAliasTargets === void 0 ? {} : { blockedAliasTargets: this.root.blockedAliasTargets },
4871
- ...this.root.settingsPath === void 0 ? {} : { settingsPath: this.root.settingsPath },
4872
- rootModel: this.root.model,
4873
- rootTools: this.root.tools,
4874
- ...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 ?? {
4875
4675
  ...options.model === void 0 ? {} : { model: options.model },
4876
- ...options.modelOverride === void 0 ? {} : { modelOverride: options.modelOverride },
4877
- ...options.tools === void 0 ? {} : { tools: options.tools },
4878
- ...options.skills === void 0 ? {} : { skills: options.skills },
4879
- ...options.extensions === void 0 ? {} : { extensions: options.extensions },
4880
- ...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles },
4881
- ...options.effectiveTools === void 0 ? {} : { effectiveTools: options.effectiveTools }
4882
- });
4883
- return { model: physicalModel(resolved.model ?? this.root.model, this.root.modelAliases, this.root.knownModels, this.root.settingsPath), ...resolved.requestedModel === void 0 ? {} : { requestedModel: resolved.requestedModel }, tools: resolved.tools ?? [], ...resolved.overrideSystemPrompt ? { systemPrompt: resolved.prompt } : {}, systemPromptAppend: resolved.overrideSystemPrompt ? "" : resolved.prompt, ...resolved.contextFiles === void 0 ? {} : { contextFiles: resolved.contextFiles } };
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;
4884
4732
  }
4885
4733
  async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
4886
4734
  const executionSignal = signal ?? this.root.runContext?.signal;
4887
4735
  if (!Number.isInteger(options.retries ?? 0) || (options.retries ?? 0) < 0) throw new WorkflowError("INVALID_METADATA", "retries must be a non-negative integer");
4888
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");
4889
- let resolved = this.resolve(options);
4737
+ let resolved;
4890
4738
  let recoveryModel;
4891
4739
  let cwd;
4892
4740
  if (options.parent) {
@@ -4907,6 +4755,16 @@ var WorkflowAgentExecutor = class {
4907
4755
  if (options.cwd) throw new WorkflowError("INVALID_METADATA", "Only child agents or worktree scopes may provide a cwd");
4908
4756
  cwd = this.root.cwd;
4909
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
+ } });
4910
4768
  const attempts = [];
4911
4769
  let maxAttempts = (options.retries ?? 0) + 1;
4912
4770
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
@@ -4936,7 +4794,7 @@ var WorkflowAgentExecutor = class {
4936
4794
  const resultTool = defineTool({
4937
4795
  name: "workflow_result",
4938
4796
  label: "Workflow Result",
4939
- 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.',
4940
4798
  parameters: resultSchema.Type(),
4941
4799
  async execute(_id, value) {
4942
4800
  if (!resultSchema.Check(value) || !jsonValue(value)) return { content: [{ type: "text", text: "Result does not match the required schema." }], details: {}, isError: true };
@@ -4968,11 +4826,23 @@ var WorkflowAgentExecutor = class {
4968
4826
  } : void 0;
4969
4827
  try {
4970
4828
  setupFailed = true;
4971
- 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);
4972
4830
  setup = prepared.setup;
4973
4831
  const attemptSetup = prepared.setup;
4974
4832
  setupSummary = prepared.summary;
4975
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
+ }
4976
4846
  setupFailed = false;
4977
4847
  if (attemptSignal.aborted) throw new WorkflowError("CANCELLED", "Agent cancelled");
4978
4848
  await options.onAttempt?.({ attempt, transport: attemptSetup.transport.id, accounting: zeroAccounting(), setup: setupSummary });
@@ -5008,6 +4878,12 @@ var WorkflowAgentExecutor = class {
5008
4878
  }
5009
4879
  }
5010
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?.();
5011
4887
  },
5012
4888
  onBeforeComplete: async () => {
5013
4889
  if (options.worktreeOwner) await runStore?.snapshotWorktree(options.worktreeOwner);
@@ -5043,6 +4919,7 @@ var WorkflowAgentExecutor = class {
5043
4919
  const runtimeRun = { id: transportBase.run.runId, namespaceId: transportBase.run.sessionId, workflowName: transportBase.run.workflow.name };
5044
4920
  const providerRecovery = options.providerErrorRecovery;
5045
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);
5046
4923
  const runtimeRequest = {
5047
4924
  task: setup.prepared.initialPrompt ?? setup.prompt,
5048
4925
  cwd: setup.prepared.cwd,
@@ -5051,6 +4928,7 @@ var WorkflowAgentExecutor = class {
5051
4928
  // Pi sessions execute the prepared native definitions; the executor does not duplicate them as neutral request tools.
5052
4929
  customTools: [],
5053
4930
  ...options.schema ? { resultSchema: options.schema } : {},
4931
+ ...attemptControl ? { turnControl: attemptControl } : {},
5054
4932
  run: runtimeRun,
5055
4933
  agent: runtimeAgent,
5056
4934
  signal: attemptSignal,
@@ -5137,7 +5015,7 @@ import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateT
5137
5015
  import { spawn } from "node:child_process";
5138
5016
  import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
5139
5017
  import { tmpdir } from "node:os";
5140
- import { join as join7 } from "node:path";
5018
+ import { join as join6 } from "node:path";
5141
5019
  function workflowPromptArtifact(prompt) {
5142
5020
  return { extension: ".md", content: prompt };
5143
5021
  }
@@ -5148,23 +5026,23 @@ function workflowResultArtifact(value) {
5148
5026
  async function spawnWorkflowEditor(command, path) {
5149
5027
  const [editor, ...editorArgs] = command.split(" ");
5150
5028
  if (!editor) return null;
5151
- return new Promise((resolve6) => {
5029
+ return new Promise((resolve5) => {
5152
5030
  try {
5153
5031
  const child = spawn(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
5154
5032
  child.once("error", () => {
5155
- resolve6(null);
5033
+ resolve5(null);
5156
5034
  });
5157
5035
  child.once("close", (code) => {
5158
- resolve6(code);
5036
+ resolve5(code);
5159
5037
  });
5160
5038
  } catch {
5161
- resolve6(null);
5039
+ resolve5(null);
5162
5040
  }
5163
5041
  });
5164
5042
  }
5165
5043
  async function openWorkflowArtifact(tui, command, artifact) {
5166
- const directory = await mkdtemp(join7(tmpdir(), "pi-workflow-editor-"));
5167
- 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}`);
5168
5046
  try {
5169
5047
  await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
5170
5048
  tui.stop();
@@ -5355,7 +5233,6 @@ function formatAgentDetail(agent, styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now =
5355
5233
  `State: ${state(agent.state)}`,
5356
5234
  ...agent.structuralPath?.length ? [`Structural path: ${agent.structuralPath.join(" > ")}`] : [],
5357
5235
  ...model === void 0 ? [] : [`Model: ${model}`],
5358
- `Role: ${agent.role ?? "(none)"}`,
5359
5236
  `Tools: ${tools}`,
5360
5237
  ...agent.attempts !== void 0 && Number.isFinite(agent.attempts) && agent.attempts > 1 ? [`Attempts: ${String(agent.attempts)}`] : [],
5361
5238
  ...duration === void 0 ? [] : [`Duration: ${formatWorkflowRuntime(duration)}`],
@@ -5398,7 +5275,9 @@ function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
5398
5275
  if (safeWidth >= 80) {
5399
5276
  const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
5400
5277
  const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
5401
- 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];
5402
5281
  return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
5403
5282
  const cell = left2[index] ?? "";
5404
5283
  return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
@@ -5443,8 +5322,8 @@ function createInlineConfirm(requestRender) {
5443
5322
  return {
5444
5323
  ask(title, message) {
5445
5324
  answer(false);
5446
- return new Promise((resolve6) => {
5447
- pending = { title, message, yes: true, resolve: resolve6 };
5325
+ return new Promise((resolve5) => {
5326
+ pending = { title, message, yes: true, resolve: resolve5 };
5448
5327
  requestRender();
5449
5328
  });
5450
5329
  },
@@ -5475,11 +5354,11 @@ function createInlineConfirm(requestRender) {
5475
5354
  }
5476
5355
 
5477
5356
  // src/trajectory.ts
5478
- 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";
5479
5358
 
5480
5359
  // subagents/src/contracts.ts
5481
5360
  import { Type as Type2 } from "@earendil-works/pi-ai";
5482
- import { Value as Value2 } from "typebox/value";
5361
+ import { Value as Value3 } from "typebox/value";
5483
5362
  var SUBAGENTS_TOOL_NAMES = [
5484
5363
  "subagents_run",
5485
5364
  "subagents_inspect",
@@ -5500,15 +5379,15 @@ var SUBAGENTS_RUN_PARAMETERS = Type2.Object({
5500
5379
  label: Type2.Optional(Type2.String({ description: "Optional display label for the subagent" })),
5501
5380
  model: Type2.Optional(Type2.String({ description: "Optional model as provider/model:thinking or alias[:thinking]" })),
5502
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" })),
5503
5383
  skills: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional ordered skill selectors; candidates start enabled and !* restricts the set" })),
5504
5384
  extensions: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional ordered extension selectors; candidates start enabled and !* restricts the set" })),
5505
5385
  contextFiles: Type2.Optional(Type2.Array(Type2.String(), { description: "Optional context-file scopes: global, project, cwd" })),
5506
- role: Type2.Optional(Type2.String({ description: "Workflow role name" })),
5507
5386
  worktree: Type2.Optional(Type2.String({ description: "Optional named worktree; requires a clean working tree" })),
5508
5387
  outputSchema: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown(), { description: "Optional JSON schema for the result" })),
5509
5388
  retries: Type2.Optional(Type2.Integer({ minimum: 0, maximum: SUBAGENT_MAX_RETRIES, description: "Optional retry count; at most 255 retries" })),
5510
5389
  timeoutMs: Type2.Optional(Type2.Union([Type2.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type2.Null()]))
5511
- }, { additionalProperties: false });
5390
+ }, { additionalProperties: true });
5512
5391
  var SUBAGENTS_INSPECT_PARAMETERS = Type2.Object({
5513
5392
  id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" })),
5514
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" }))
@@ -5542,7 +5421,7 @@ var SUBAGENT_STATUS_OUTPUT = Type2.Object({
5542
5421
  var SUBAGENTS_INSPECT_OUTPUT = Type2.Union([SUBAGENT_STATUS_OUTPUT, Type2.Array(Type2.Object({ id: Type2.String(), state: Type2.String() }))]);
5543
5422
  var SUBAGENTS_STEER_OUTPUT = Type2.Object({ id: Type2.String(), accepted: Type2.Boolean() });
5544
5423
  function normalizeSubagentRunRequest(value) {
5545
- 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");
5546
5425
  if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
5547
5426
  const snapshot = structuredClone(value);
5548
5427
  snapshot.mode ??= "background";
@@ -5758,11 +5637,11 @@ var SCRIPT_CALL_TEXT_LIMIT_BYTES = 16 * 1024;
5758
5637
  // subagents/src/manager.ts
5759
5638
  import { randomUUID as randomUUID3 } from "node:crypto";
5760
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";
5761
- import { join as join9 } from "node:path";
5762
- 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";
5763
5642
 
5764
5643
  // subagents/src/worktree.ts
5765
- import { join as join8 } from "node:path";
5644
+ import { join as join7 } from "node:path";
5766
5645
  function syntheticRun(context) {
5767
5646
  return {
5768
5647
  id: context.runId,
@@ -5785,7 +5664,7 @@ function syntheticSnapshot(context) {
5785
5664
  ...context.worktreePostCreateCommandSource === void 0 ? {} : { settingsSources: { concurrency: "subagents", modelAliases: "subagents", worktreePostCreateCommand: context.worktreePostCreateCommandSource } },
5786
5665
  models: [],
5787
5666
  tools: [],
5788
- agentTypes: [],
5667
+ agentConfigurations: {},
5789
5668
  schemas: []
5790
5669
  };
5791
5670
  }
@@ -5824,7 +5703,7 @@ function createRunStoreWorktreeAdapter(home) {
5824
5703
  };
5825
5704
  }
5826
5705
  function defaultWorktreeHome(storageDirectory3) {
5827
- return join8(storageDirectory3, ".worktrees");
5706
+ return join7(storageDirectory3, ".worktrees");
5828
5707
  }
5829
5708
 
5830
5709
  // subagents/src/manager.ts
@@ -5864,22 +5743,11 @@ function rootModel(context) {
5864
5743
  const thinking = context.extensionContext.thinkingLevel;
5865
5744
  return { provider: model.provider, model: model.id, ...thinking === void 0 ? {} : { thinking } };
5866
5745
  }
5867
- function standaloneExtensionSettingsPath(cwd, agentDir, trustedProject, external) {
5868
- return trustedProject && external.projectExtensionSettings !== void 0 ? workflowProjectSettingsPath(cwd) : workflowSettingsPath(agentDir);
5869
- }
5870
- function validateStandaloneExtensionSources(context, external, registry, agentDir) {
5871
- const cwd = context.extensionContext.cwd;
5872
- const globalSettingsPath = workflowSettingsPath(agentDir);
5873
- const trustedProject = context.extensionContext.isProjectTrusted();
5874
- if (external.globalExtensionSettings !== void 0) registry.validateExtensionSettings(external.globalExtensionSettings, { source: "global", cwd, projectTrusted: trustedProject, settingsPath: globalSettingsPath });
5875
- if (trustedProject && external.projectExtensionSettings !== void 0) registry.validateExtensionSettings(external.projectExtensionSettings, { source: "project", cwd, projectTrusted: true, settingsPath: workflowProjectSettingsPath(cwd) });
5876
- }
5877
- function executionRoot(context, dependencies, signal, runId, worktree, external) {
5746
+ function executionRoot(context, dependencies, signal, runId, worktree) {
5878
5747
  const extensionContext = context.extensionContext;
5879
5748
  const model = rootModel(context);
5880
- const agentDir = dependencies.agentDir ?? getAgentDir6();
5749
+ const agentDir = dependencies.agentDir ?? getAgentDir5();
5881
5750
  const currentTrustedProject = extensionContext.isProjectTrusted();
5882
- if (external.projectTrusted && !currentTrustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
5883
5751
  const trustedProject = currentTrustedProject;
5884
5752
  const settingsPath = workflowSettingsPath(agentDir);
5885
5753
  const settings = resolveWorkflowSettings(extensionContext.cwd, trustedProject, settingsPath);
@@ -5902,11 +5770,13 @@ function executionRoot(context, dependencies, signal, runId, worktree, external)
5902
5770
  signal
5903
5771
  };
5904
5772
  const registry = loadingRegistry();
5905
- validateStandaloneExtensionSources(context, external, registry, agentDir);
5906
- const extensionSettings = trustedProject && external.projectExtensionSettings !== void 0 ? external.projectExtensionSettings : external.globalExtensionSettings;
5907
- 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;
5908
5777
  return {
5909
5778
  cwd: extensionContext.cwd,
5779
+ projectTrusted: trustedProject,
5910
5780
  model,
5911
5781
  tools,
5912
5782
  resourceSelectors: resourcePolicy.effective,
@@ -5916,9 +5786,10 @@ function executionRoot(context, dependencies, signal, runId, worktree, external)
5916
5786
  availableModels,
5917
5787
  knownModels,
5918
5788
  ...Object.keys(staticAliases).length ? { modelAliases: staticAliases } : {},
5789
+ dynamicModelAliasNames: [],
5919
5790
  settingsPath: settings.sources.modelAliases,
5920
5791
  extensionSettingsPath,
5921
- agentDefinitions: external.agentDefinitions,
5792
+ agentPreparationHooks: registry.agentPreparationHooks(),
5922
5793
  agentSetupHooks: registry.agentSetupHooks(),
5923
5794
  validateExtensionSettings: registry.validateExtensionSettings,
5924
5795
  agentResourcePolicy: () => structuredClone(resourcePolicy),
@@ -5934,11 +5805,10 @@ async function addDynamicAliases(context, signal, root) {
5934
5805
  const trustedProject = resourcePolicy?.projectTrusted ?? context.extensionContext.isProjectTrusted();
5935
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)));
5936
5807
  validateModelAliasAvailability(dynamicAliases, Object.keys(dynamicAliases), root.availableModels ?? /* @__PURE__ */ new Set(), root.knownModels ?? /* @__PURE__ */ new Set(), root.settingsPath);
5937
- return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases } };
5808
+ return { ...root, modelAliases: { ...dynamicAliases, ...staticAliases }, dynamicModelAliasNames: Object.keys(dynamicAliases) };
5938
5809
  }
5939
5810
  function executionOptions(request, onAttempt, onProgress) {
5940
- const role = request.role;
5941
- const label2 = request.label ?? roleNameOf(role) ?? "subagent";
5811
+ const label2 = request.label ?? "subagent";
5942
5812
  return {
5943
5813
  label: label2,
5944
5814
  workflowName: WORKFLOW_NAME,
@@ -5947,35 +5817,35 @@ function executionOptions(request, onAttempt, onProgress) {
5947
5817
  ...request.skills === void 0 ? {} : { skills: request.skills },
5948
5818
  ...request.extensions === void 0 ? {} : { extensions: request.extensions },
5949
5819
  ...request.contextFiles === void 0 ? {} : { contextFiles: request.contextFiles },
5950
- ...role === void 0 ? {} : { role },
5951
5820
  ...request.worktree === void 0 ? {} : { worktreeOwner: structuralPath("worktree", "named", request.worktree) },
5952
5821
  ...request.outputSchema === void 0 ? {} : { schema: request.outputSchema },
5953
5822
  ...request.retries === void 0 ? {} : { retries: request.retries },
5954
5823
  ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
5824
+ agentOptions: Object.fromEntries(Object.entries(request).filter(([key]) => !["prompt", "mode", "worktree"].includes(key))),
5955
5825
  onAttempt,
5956
5826
  onProgress
5957
5827
  };
5958
5828
  }
5959
5829
  function storageDirectory(dependencies) {
5960
- return dependencies.storageDir ?? join9(dependencies.agentDir ?? getAgentDir6(), STORAGE_DIRECTORY);
5830
+ return dependencies.storageDir ?? join8(dependencies.agentDir ?? getAgentDir5(), STORAGE_DIRECTORY);
5961
5831
  }
5962
5832
  function runDirectory(root, id) {
5963
- return join9(root, id);
5833
+ return join8(root, id);
5964
5834
  }
5965
5835
  function requestPath(directory) {
5966
- return join9(directory, "request.json");
5836
+ return join8(directory, "request.json");
5967
5837
  }
5968
5838
  function configurationPath(directory) {
5969
- return join9(directory, CONFIGURATION_FILE);
5839
+ return join8(directory, CONFIGURATION_FILE);
5970
5840
  }
5971
5841
  function statusPath(directory) {
5972
- return join9(directory, "status.json");
5842
+ return join8(directory, "status.json");
5973
5843
  }
5974
5844
  function resultPath(directory) {
5975
- return join9(directory, "result.json");
5845
+ return join8(directory, "result.json");
5976
5846
  }
5977
5847
  function failurePath(directory) {
5978
- return join9(directory, "failure.json");
5848
+ return join8(directory, "failure.json");
5979
5849
  }
5980
5850
  async function secureDirectory(directory) {
5981
5851
  await mkdir3(directory, { recursive: true, mode: 448 });
@@ -6058,7 +5928,7 @@ async function releaseOwnerMarker(path, token) {
6058
5928
  }
6059
5929
  async function acquireStorageOwner(root, owner, liveness) {
6060
5930
  await secureDirectory(root);
6061
- const path = join9(root, OWNER_FILE);
5931
+ const path = join8(root, OWNER_FILE);
6062
5932
  for (let attempt = 0; attempt < MAX_STORAGE_OWNER_ATTEMPTS; attempt += 1) {
6063
5933
  const marker = { ...owner, acquiredAt: Date.now() };
6064
5934
  try {
@@ -6129,32 +5999,8 @@ function checkedRequest(request) {
6129
5999
  return normalizeSubagentRunRequest(request);
6130
6000
  }
6131
6001
  function decodePersistedStandaloneExternalConfiguration(value) {
6132
- if (!object(value) || !jsonValue(value) || typeof value.projectTrusted !== "boolean") return void 0;
6133
- const globalExtensionSettings = value.globalExtensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.globalExtensionSettings);
6134
- const projectExtensionSettings = value.projectExtensionSettings === void 0 ? void 0 : decodeWorkflowExtensions(value.projectExtensionSettings);
6135
- if (value.globalExtensionSettings !== void 0 && globalExtensionSettings === void 0 || value.projectExtensionSettings !== void 0 && projectExtensionSettings === void 0) return void 0;
6136
- if (!object(value.agentDefinitions)) return void 0;
6137
- const agentDefinitions = /* @__PURE__ */ Object.create(null);
6138
- for (const [name, definition] of Object.entries(value.agentDefinitions)) {
6139
- const decoded = decodeAgentDefinition(definition);
6140
- if (decoded === void 0) return void 0;
6141
- agentDefinitions[name] = decoded;
6142
- }
6143
- return deepFreeze({ projectTrusted: value.projectTrusted, ...globalExtensionSettings === void 0 ? {} : { globalExtensionSettings }, ...projectExtensionSettings === void 0 ? {} : { projectExtensionSettings }, agentDefinitions });
6144
- }
6145
- function standaloneExternalConfiguration(context, dependencies) {
6146
- const cwd = context.extensionContext.cwd;
6147
- const trustedProject = context.extensionContext.isProjectTrusted();
6148
- const agentDir = dependencies.agentDir ?? getAgentDir6();
6149
- const resolution = resolveWorkflowSettings(cwd, trustedProject, workflowSettingsPath(agentDir));
6150
- const globalExtensionSettings = resolution.global.extensionSettings;
6151
- const projectExtensionSettings = resolution.project.extensionSettings;
6152
- return deepFreeze({
6153
- projectTrusted: trustedProject,
6154
- ...globalExtensionSettings === void 0 ? {} : { globalExtensionSettings: structuredClone(globalExtensionSettings) },
6155
- ...projectExtensionSettings === void 0 ? {} : { projectExtensionSettings: structuredClone(projectExtensionSettings) },
6156
- agentDefinitions: structuredClone(loadAgentDefinitions(cwd, agentDir, trustedProject))
6157
- });
6002
+ if (!object(value) || value.version !== 1) return void 0;
6003
+ return decodePreparedAgentConfiguration(value.configuration);
6158
6004
  }
6159
6005
  function checkedId(request) {
6160
6006
  if (typeof request.id !== "string" || !validSubagentId(request.id)) throw new WorkflowError("RUN_NOT_FOUND", `Unknown subagent ${request.id}`);
@@ -6465,7 +6311,7 @@ async function createRunStorage(root, id, request, status, external) {
6465
6311
  created = true;
6466
6312
  await chmod(directory, 448);
6467
6313
  await atomicJson(requestPath(directory), request);
6468
- await atomicJson(configurationPath(directory), external);
6314
+ if (external !== void 0) await atomicJson(configurationPath(directory), { version: 1, configuration: external });
6469
6315
  await atomicJson(statusPath(directory), status);
6470
6316
  return directory;
6471
6317
  } catch (error) {
@@ -6647,6 +6493,7 @@ var PersistentSubagentManager = class {
6647
6493
  }
6648
6494
  dependencies;
6649
6495
  activeRuns = /* @__PURE__ */ new Map();
6496
+ pendingStarts = /* @__PURE__ */ new Set();
6650
6497
  activeRunCount = 0;
6651
6498
  terminalSummaries = /* @__PURE__ */ new Map();
6652
6499
  reconciliationErrors = /* @__PURE__ */ new Map();
@@ -6690,32 +6537,44 @@ var PersistentSubagentManager = class {
6690
6537
  await this.ensureInitialized();
6691
6538
  if (this.disposed) throw new WorkflowError("CANCELLED", "Subagent manager is disposed");
6692
6539
  if (context.signal?.aborted) throw new WorkflowError("CANCELLED", "Subagent cancelled");
6693
- const external = frozenExternal ?? standaloneExternalConfiguration(context, this.dependencies);
6694
- 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");
6695
6542
  const id = randomUUID3();
6696
6543
  const startedAt = Date.now();
6697
6544
  const sessionId = context.extensionContext.sessionManager.getSessionId();
6698
6545
  if (!sessionId.trim()) throw new WorkflowError("INTERNAL_ERROR", "Current Pi session identity is unavailable");
6699
6546
  const owner = this.runOwner;
6700
6547
  if (!owner) throw new WorkflowError("INTERNAL_ERROR", "Subagent storage owner identity is unavailable");
6701
- 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()));
6702
6549
  const concurrency = settings.effective.concurrency;
6703
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.`);
6704
6551
  this.activeRunCount += 1;
6705
6552
  const controller = new AbortController();
6706
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);
6707
6559
  let directory;
6708
6560
  try {
6709
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
+ }
6710
6566
  } catch (error) {
6711
6567
  this.activeRunCount -= 1;
6712
6568
  if (error instanceof WorkflowError) throw error;
6713
6569
  throw internalStorageError(error, `Unable to start subagent ${id}`);
6570
+ } finally {
6571
+ this.pendingStarts.delete(pendingStart);
6572
+ finishStart();
6714
6573
  }
6715
6574
  const current = {};
6716
6575
  let resolveTerminal;
6717
- const terminal = new Promise((resolve6) => {
6718
- resolveTerminal = resolve6;
6576
+ const terminal = new Promise((resolve5) => {
6577
+ resolveTerminal = resolve5;
6719
6578
  });
6720
6579
  const executorOwnership = { default: true };
6721
6580
  const execution = Promise.resolve().then(async () => {
@@ -6743,12 +6602,9 @@ var PersistentSubagentManager = class {
6743
6602
  throw new WorkflowError("CANCELLED", "Subagent cancelled");
6744
6603
  }
6745
6604
  }
6746
- const baseRoot = executionRoot(context, this.dependencies, controller.signal, id, worktree, external);
6747
- 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);
6748
6607
  const transport = this.dependencies.transport ?? localAgentTransport;
6749
- const injectedExecutor = this.dependencies.createExecutor?.(root, transport);
6750
- executorOwnership.default = injectedExecutor === void 0;
6751
- if (current.run) current.run.executorOwnsSession = executorOwnership.default;
6752
6608
  const setSteer = (handler) => {
6753
6609
  const run2 = current.run;
6754
6610
  if (run2) this.registerSteerHandler(run2, handler);
@@ -6789,6 +6645,13 @@ var PersistentSubagentManager = class {
6789
6645
  if (run2.state !== "running") return;
6790
6646
  emitUpdate(run2);
6791
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;
6792
6655
  if (injectedExecutor) return injectedExecutor.execute(snapshot.prompt, options, controller.signal, setSteer);
6793
6656
  return new WorkflowAgentExecutor(root, transport).execute(snapshot.prompt, options, controller.signal, [], setSteer);
6794
6657
  });
@@ -6896,11 +6759,11 @@ var PersistentSubagentManager = class {
6896
6759
  }
6897
6760
  async dispose() {
6898
6761
  if (this.disposePromise) return this.disposePromise;
6762
+ this.disposed = true;
6899
6763
  this.disposePromise = (async () => {
6900
6764
  await this.initialization;
6901
- this.disposed = true;
6902
6765
  const runs = [...this.activeRuns.values()];
6903
- await Promise.allSettled(runs.map((run) => this.stopRun(run, true)));
6766
+ await Promise.allSettled([...this.pendingStarts, ...runs.map((run) => this.stopRun(run, true))]);
6904
6767
  await Promise.allSettled([...this.notificationPromises]);
6905
6768
  const owner = this.storageOwner;
6906
6769
  this.storageOwner = void 0;
@@ -7231,9 +7094,8 @@ var PersistentSubagentManager = class {
7231
7094
  const notify = this.dependencies.notify;
7232
7095
  if (!notify || run.notificationSent || run.disposed) return;
7233
7096
  run.notificationSent = true;
7234
- const role = roleNameOf(run.request.role) ?? "none";
7235
- const label2 = run.request.label?.trim() || (role === "none" ? "subagent" : role);
7236
- 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 } };
7237
7099
  const pending = Promise.resolve().then(() => notify(notification));
7238
7100
  this.notificationPromises.add(pending);
7239
7101
  void pending.then(() => {
@@ -7281,8 +7143,8 @@ function createSubagentManager(dependencies = {}) {
7281
7143
 
7282
7144
  // subagents/src/navigator.ts
7283
7145
  import { readFile as readFile5 } from "node:fs/promises";
7284
- import { join as join10 } from "node:path";
7285
- 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";
7286
7148
  import { Editor, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
7287
7149
  var MAX_DETAIL_TEXT = 4e3;
7288
7150
  function objectValue2(value) {
@@ -7305,7 +7167,7 @@ function managerContext(context, waitForForeground = true, includeAttemptMetadat
7305
7167
  async function loadRequest(storageDirectory3, id) {
7306
7168
  if (!safeRunId(id)) return {};
7307
7169
  try {
7308
- 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"));
7309
7171
  return { request: normalizeSubagentRunRequest(value) };
7310
7172
  } catch (error) {
7311
7173
  if (isFileNotFound(error)) return {};
@@ -7338,18 +7200,11 @@ async function inspectEntry(manager, storageDirectory3, entry, context) {
7338
7200
  entry: { status: inspected.status, ...request.request === void 0 ? {} : { request: request.request }, ...request.error === void 0 ? {} : { requestError: request.error } }
7339
7201
  };
7340
7202
  }
7341
- function requestRole(request) {
7342
- const role = request?.role;
7343
- if (typeof role === "string" && role.trim()) return role.trim();
7344
- const override = objectValue2(role);
7345
- return typeof override?.name === "string" && override.name.trim() ? override.name.trim() : "none";
7346
- }
7347
7203
  function shortId(id) {
7348
7204
  return id.length > 12 ? id.slice(0, 8) : id;
7349
7205
  }
7350
7206
  function entryName(entry) {
7351
- const role = requestRole(entry.request);
7352
- 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);
7353
7208
  }
7354
7209
  function listNames(entries) {
7355
7210
  const names = entries.map(entryName);
@@ -7386,7 +7241,7 @@ function usageAccounting(status) {
7386
7241
  return status.progress?.accounting;
7387
7242
  }
7388
7243
  function detailPresentation(inspection) {
7389
- const { status, request } = inspection.entry;
7244
+ const { status } = inspection.entry;
7390
7245
  const attempt = latestAttempt(status);
7391
7246
  const state = status.progress?.state;
7392
7247
  const activity = status.progress?.activity;
@@ -7395,14 +7250,12 @@ function detailPresentation(inspection) {
7395
7250
  const lastEventAt = status.progress?.lastEventAt;
7396
7251
  const attempts = status.attempts ?? attempt?.attempt;
7397
7252
  const accounting2 = usageAccounting(status);
7398
- const role = requestRole(request);
7399
7253
  const error = status.error ?? attempt?.error;
7400
7254
  return {
7401
7255
  state: status.state,
7402
7256
  ...activity === void 0 ? {} : { activity: { kind: activity.kind, text: boundedText(activity.text) } },
7403
7257
  ...lastEventAt === void 0 ? {} : { lastEventAt },
7404
7258
  ...model === void 0 ? {} : { model: { provider: boundedText(model.provider, 256), model: boundedText(model.model, 256), ...model.thinking === void 0 ? {} : { thinking: model.thinking } } },
7405
- ...role === "none" ? {} : { role: boundedText(role, 256) },
7406
7259
  ...tools === void 0 ? {} : { tools: tools.slice(0, 256).map((tool) => boundedText(tool, 256)) },
7407
7260
  ...attempts === void 0 ? {} : { attempts },
7408
7261
  ...status.startedAt === void 0 ? {} : { startedAt: status.startedAt },
@@ -7454,8 +7307,7 @@ function standaloneActionContext(manager, inspection, context) {
7454
7307
  const session = attempt.session;
7455
7308
  const live = liveData?.liveSession && session && liveData.liveSession.reference.transport === session.transport && liveData.liveSession.reference.sessionId === session.sessionId ? liveData.liveSession : void 0;
7456
7309
  const label2 = request?.label?.trim();
7457
- const role = requestRole(request);
7458
- const name = label2 || (role === "none" ? "subagent" : role);
7310
+ const name = label2 || "subagent";
7459
7311
  const ui = {
7460
7312
  notify: (message, level = "info") => {
7461
7313
  context.ui.notify(message, level);
@@ -7524,7 +7376,7 @@ function retryResult(value) {
7524
7376
  return record2 && typeof record2.id === "string" && record2.id.trim() && record2.state === "running" ? { id: record2.id, state: "running" } : void 0;
7525
7377
  }
7526
7378
  async function openNavigatorArtifact(context, tui, artifact, label2) {
7527
- const command = SettingsManager4.create(context.cwd, getAgentDir7(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7379
+ const command = SettingsManager4.create(context.cwd, getAgentDir6(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7528
7380
  if (!command) {
7529
7381
  context.ui.notify(`Cannot open ${label2}: no external editor is configured.`, "warning");
7530
7382
  return;
@@ -7555,7 +7407,7 @@ async function performAction(manager, storageDirectory3, entry, action, context,
7555
7407
  return "unchanged";
7556
7408
  }
7557
7409
  if (action === "Copy run path") {
7558
- await clipboard(join10(storageDirectory3, fresh.entry.status.id));
7410
+ await clipboard(join9(storageDirectory3, fresh.entry.status.id));
7559
7411
  context.ui.notify("Copied run path.", "info");
7560
7412
  return "unchanged";
7561
7413
  }
@@ -7979,24 +7831,15 @@ var TERMINAL_STATES = /* @__PURE__ */ new Set(["completed", "failed", "stopped"]
7979
7831
  var MAX_INSPECT_VALUE_CHARS = 4e3;
7980
7832
  var MAX_INSPECT_TOOL_CALLS = 32;
7981
7833
  var MAX_BACKGROUND_WIDGET_ROWS = 10;
7982
- function roleName(value) {
7983
- if (typeof value === "string") return value.trim() || void 0;
7984
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
7985
- const name = value.name;
7986
- return typeof name === "string" ? name.trim() || void 0 : void 0;
7987
- }
7988
7834
  function modeName(value) {
7989
7835
  return value === "background" || value === "foreground" ? value : void 0;
7990
7836
  }
7991
7837
  function requestMetadata(args, metadataAvailable) {
7992
7838
  const mode = modeName(args.mode) ?? (metadataAvailable ? "background" : void 0);
7993
- const role = metadataAvailable ? roleName(args.role) ?? "none" : void 0;
7994
- 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(" ");
7995
7840
  }
7996
7841
  function label(args) {
7997
7842
  if (typeof args.label === "string" && args.label.trim()) return args.label.trim();
7998
- const role = roleName(args.role);
7999
- if (role) return role;
8000
7843
  if (typeof args.id === "string" && args.id) return args.id.slice(0, 8);
8001
7844
  return "subagent";
8002
7845
  }
@@ -8186,14 +8029,12 @@ var RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
8186
8029
  function receiptFor(status, request) {
8187
8030
  const accounting2 = status.progress?.accounting;
8188
8031
  const model = modelName(status);
8189
- const role = roleName(request.role);
8190
8032
  const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
8191
8033
  return {
8192
8034
  id: status.id,
8193
8035
  label: label({ ...request, id: status.id }),
8194
8036
  state: status.state,
8195
8037
  ...model ? { model } : {},
8196
- ...role === void 0 ? {} : { role },
8197
8038
  ...tools === void 0 ? {} : { tools: [...tools] },
8198
8039
  input: accounting2?.input ?? 0,
8199
8040
  output: accounting2?.output ?? 0,
@@ -8209,7 +8050,7 @@ function renderSubagentReceipt(data, expanded, theme) {
8209
8050
  const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" \xB7 ");
8210
8051
  const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
8211
8052
  if (!expanded) return lines;
8212
- 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);
8213
8054
  if (meta.length > 0) lines.push(theme.fg("muted", ` ${meta.join(" \xB7 ")}`));
8214
8055
  const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
8215
8056
  if (split.length > 0) lines.push(theme.fg("muted", ` ${split.join(" \xB7 ")}`));
@@ -8346,15 +8187,15 @@ function validateSubagentRunRequest(value) {
8346
8187
  return normalizeSubagentRunRequest(value);
8347
8188
  }
8348
8189
  function validateSubagentIdRequest(value, operation) {
8349
- 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`);
8350
8191
  return value;
8351
8192
  }
8352
8193
  function validateSubagentInspectRequest(value) {
8353
- 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");
8354
8195
  return value;
8355
8196
  }
8356
8197
  function validateSubagentSteerRequest(value) {
8357
- 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");
8358
8199
  return value;
8359
8200
  }
8360
8201
  function serialize(value) {
@@ -8470,8 +8311,7 @@ function createSubagentTools(manager) {
8470
8311
  }
8471
8312
  function notificationContent(notification) {
8472
8313
  const label2 = notification.label?.trim();
8473
- const role = notification.role?.trim() || "none";
8474
- const identity = label2 ? `${label2} role=${role} (${notification.id})` : `${notification.id} role=${role}`;
8314
+ const identity = label2 ? `${label2} (${notification.id})` : notification.id;
8475
8315
  if (notification.state === "completed") return `Subagent ${identity} completed. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
8476
8316
  return `Subagent ${identity} failed: ${notification.error?.message ?? "unknown error"}. Inspect it with subagents_inspect({ id: "${notification.id}" }).`;
8477
8317
  }
@@ -8488,7 +8328,7 @@ function managerDependencies(options, activeTools, notify, onStatus, onResourceW
8488
8328
  }
8489
8329
  function storageDirectory2(options) {
8490
8330
  const dependencies = options.managerDependencies;
8491
- return dependencies?.storageDir ?? join11(dependencies?.agentDir ?? getAgentDir8(), "subagents");
8331
+ return dependencies?.storageDir ?? join10(dependencies?.agentDir ?? getAgentDir7(), "subagents");
8492
8332
  }
8493
8333
  function createSubagentsExtension(options = {}, activeTools, notify, onStatus, onResourceWarning) {
8494
8334
  const manager = options.manager ?? createSubagentManager(managerDependencies(options, activeTools, notify, onStatus, onResourceWarning));