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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/dist/src/agent-execution.js +114 -87
  3. package/dist/src/background-widget.d.ts +61 -0
  4. package/dist/src/background-widget.js +23 -16
  5. package/dist/src/decoders.js +28 -1
  6. package/dist/src/execution.d.ts +4 -2
  7. package/dist/src/execution.js +43 -2
  8. package/dist/src/host-delivery.js +1 -0
  9. package/dist/src/host-navigator.d.ts +18 -0
  10. package/dist/src/host-navigator.js +71 -13
  11. package/dist/src/host-recovery.d.ts +5 -2
  12. package/dist/src/host-recovery.js +3 -3
  13. package/dist/src/host-view.d.ts +21 -1
  14. package/dist/src/host-view.js +138 -47
  15. package/dist/src/host.d.ts +2 -2
  16. package/dist/src/host.js +246 -60
  17. package/dist/src/index.d.ts +3 -1
  18. package/dist/src/index.js +1346 -622
  19. package/dist/src/index.js.map +3 -3
  20. package/dist/src/paths.d.ts +2 -0
  21. package/dist/src/paths.js +2 -0
  22. package/dist/src/registry.js +3 -2
  23. package/dist/src/roles.d.ts +70 -0
  24. package/dist/src/roles.js +326 -0
  25. package/dist/src/script-tools.d.ts +27 -0
  26. package/dist/src/script-tools.js +87 -0
  27. package/dist/src/settings.d.ts +26 -0
  28. package/dist/src/settings.js +277 -0
  29. package/dist/src/trajectory.d.ts +39 -4
  30. package/dist/src/trajectory.js +82 -5
  31. package/dist/src/types.d.ts +30 -3
  32. package/dist/src/types.js +1 -1
  33. package/dist/src/utils.d.ts +21 -1
  34. package/dist/src/utils.js +40 -0
  35. package/dist/src/validation.d.ts +10 -17
  36. package/dist/src/validation.js +29 -458
  37. package/dist/starter/index.js +0 -2
  38. package/dist/starter/index.js.map +1 -1
  39. package/dist/starter/review-loop.d.ts +0 -13
  40. package/dist/starter/review-loop.js +0 -2
  41. package/dist/subagents/index.js +1344 -798
  42. package/dist/subagents/index.js.map +3 -3
  43. package/dist/subagents/src/contracts.d.ts +49 -29
  44. package/dist/subagents/src/contracts.js +18 -7
  45. package/dist/subagents/src/index.d.ts +1 -1
  46. package/dist/subagents/src/index.js +15 -7
  47. package/dist/subagents/src/manager.js +32 -13
  48. package/dist/subagents/src/navigator.js +396 -247
  49. package/dist/subagents/src/view.d.ts +24 -2
  50. package/dist/subagents/src/view.js +107 -80
  51. package/dist/trajectory/assets/index.html +370 -232
  52. package/dist/trajectory/assets/prism.min.js +13 -0
  53. package/dist/trajectory/index.js +445 -277
  54. package/dist/trajectory/index.js.map +3 -3
  55. package/dist/trajectory/src/assets/index.html +370 -232
  56. package/dist/trajectory/src/assets/prism.min.js +13 -0
  57. package/dist/trajectory/src/export.js +4 -2
  58. package/dist/trajectory/src/index.d.ts +2 -0
  59. package/dist/trajectory/src/index.js +151 -60
  60. package/dist/trajectory/src/server.js +138 -11
  61. package/dist/trajectory/src/server.js.map +2 -2
  62. package/package.json +7 -6
  63. package/skills/pi-extensible-workflows/SKILL.md +2 -0
  64. package/src/agent-execution.ts +110 -80
  65. package/src/background-widget.ts +26 -19
  66. package/src/decoders.ts +22 -1
  67. package/src/execution.ts +41 -4
  68. package/src/host-delivery.ts +1 -0
  69. package/src/host-navigator.ts +58 -14
  70. package/src/host-recovery.ts +8 -7
  71. package/src/host-view.ts +116 -42
  72. package/src/host.ts +223 -55
  73. package/src/index.ts +3 -1
  74. package/src/paths.ts +3 -0
  75. package/src/registry.ts +3 -2
  76. package/src/roles.ts +327 -0
  77. package/src/script-tools.ts +76 -0
  78. package/src/settings.ts +224 -0
  79. package/src/trajectory.ts +97 -9
  80. package/src/types.ts +12 -5
  81. package/src/utils.ts +36 -1
  82. package/src/validation.ts +30 -356
  83. package/starter/review-loop.ts +0 -2
  84. package/subagents/README.md +3 -3
  85. package/subagents/src/contracts.ts +22 -7
  86. package/subagents/src/index.ts +18 -7
  87. package/subagents/src/manager.ts +31 -13
  88. package/subagents/src/navigator.ts +336 -180
  89. package/subagents/src/view.ts +108 -76
  90. package/trajectory/src/assets/index.html +370 -232
  91. package/trajectory/src/assets/prism.min.js +13 -0
  92. package/trajectory/src/export.ts +4 -2
  93. package/trajectory/src/index.ts +124 -47
  94. package/trajectory/src/server.ts +114 -14
@@ -1,6 +1,6 @@
1
1
  // subagents/src/index.ts
2
- import { join as join10 } from "node:path";
3
- import { defineTool as defineTool2, getAgentDir as getAgentDir7 } from "@earendil-works/pi-coding-agent";
2
+ import { join as join11 } from "node:path";
3
+ import { defineTool as defineTool2, getAgentDir as getAgentDir8 } from "@earendil-works/pi-coding-agent";
4
4
  import { Value as Value3 } from "typebox/value";
5
5
 
6
6
  // src/types.ts
@@ -28,6 +28,7 @@ var ERROR_CODES = [
28
28
  "AGENT_FAILED",
29
29
  "AGENT_RESULT_COLLECTED",
30
30
  "RESULT_INVALID",
31
+ "TOOL_FAILED",
31
32
  "CANCELLED",
32
33
  "WORKER_UNRESPONSIVE",
33
34
  "WORKTREE_FAILED",
@@ -284,6 +285,16 @@ function resolveModelReference(value, aliases = {}, knownModels, settingsPath) {
284
285
  };
285
286
  return resolveReference(value, []);
286
287
  }
288
+ function reachableTools(pi) {
289
+ const undeclared = pi.getAllTools?.().filter(({ exposure }) => exposure === "codemode" || exposure === "deferred").map(({ name }) => name) ?? [];
290
+ return [.../* @__PURE__ */ new Set([...pi.getActiveTools(), ...undeclared])];
291
+ }
292
+ var VIRTUAL_MODEL_PROVIDER = "workflow";
293
+ function physicalModel(spec, aliases = {}, knownModels, settingsPath) {
294
+ if (spec.provider !== VIRTUAL_MODEL_PROVIDER || !modelAliasName(spec.model, aliases)) return spec;
295
+ const target = resolveModelReference(spec.model, aliases, knownModels, settingsPath);
296
+ return spec.thinking === void 0 ? target : { ...target, thinking: spec.thinking };
297
+ }
287
298
  function modelCapability(value, aliases, knownModels, settingsPath) {
288
299
  const parsed = typeof value === "string" ? resolveModelReference(value, aliases, knownModels, settingsPath) : value;
289
300
  return `${parsed.provider}/${parsed.model}`;
@@ -328,6 +339,12 @@ function unmatchedResourcePatterns(patterns, resources) {
328
339
  function loadLaunchSnapshot(input) {
329
340
  return deepFreeze(structuredClone(input));
330
341
  }
342
+ function validateSchema(schema, at = "schema") {
343
+ if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
344
+ if (typeof schema.type !== "string" && !Array.isArray(schema.type) && schema.$ref === void 0 && schema.anyOf === void 0 && schema.oneOf === void 0 && schema.allOf === void 0 && schema.const === void 0 && schema.enum === void 0) fail("INVALID_SCHEMA", `${at} has no JSON Schema shape`);
345
+ if (schema.required !== void 0 && (!Array.isArray(schema.required) || schema.required.some((key) => typeof key !== "string"))) fail("INVALID_SCHEMA", `${at}.required must be an array of strings`);
346
+ if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
347
+ }
331
348
 
332
349
  // src/paths.ts
333
350
  import { createHash } from "node:crypto";
@@ -345,6 +362,9 @@ function canonicalPath(path) {
345
362
  return absolute;
346
363
  }
347
364
  }
365
+ function extensionIdentity(path) {
366
+ return path.startsWith("builtin:") ? path : canonicalPath(path);
367
+ }
348
368
  function sameFilesystemPath(left, right) {
349
369
  return canonicalPath(left) === canonicalPath(right);
350
370
  }
@@ -372,6 +392,20 @@ function budgetUsage(value) {
372
392
  return { tokens: value?.tokens ?? 0, costUsd: value?.costUsd ?? 0, durationMs: value?.durationMs ?? 0, agentLaunches: value?.agentLaunches ?? 0 };
373
393
  }
374
394
 
395
+ // src/validation.ts
396
+ import * as acorn from "acorn";
397
+
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
+
375
409
  // src/io.ts
376
410
  import { execFile } from "node:child_process";
377
411
  import { randomUUID } from "node:crypto";
@@ -392,7 +426,7 @@ async function renameWithRetry(from, to) {
392
426
  } catch (error) {
393
427
  const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
394
428
  if (!retryableRenameError(error) || delayMs === void 0) throw error;
395
- await new Promise((resolve5) => setTimeout(resolve5, delayMs));
429
+ await new Promise((resolve6) => setTimeout(resolve6, delayMs));
396
430
  }
397
431
  }
398
432
  }
@@ -593,7 +627,7 @@ function decodeWorkflowExtensions(value) {
593
627
  }
594
628
  if (namespace === "trajectory") {
595
629
  if (!object(raw) || Object.keys(raw).some((key) => key !== "port" && key !== "themes") || raw.port !== void 0 && (typeof raw.port !== "number" || !Number.isSafeInteger(raw.port) || raw.port < 1 || raw.port > 65535) || raw.themes !== void 0 && typeof raw.themes !== "boolean") return void 0;
596
- normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? {} : { themes: raw.themes } };
630
+ normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port } };
597
631
  continue;
598
632
  }
599
633
  if (!jsonValue(raw)) return void 0;
@@ -775,9 +809,9 @@ function decodeAgentAttempt(value) {
775
809
  const session = value.session === void 0 ? void 0 : decodeAgentSession(value.session);
776
810
  const setup = decodeAgentSetupSummary(value.setup);
777
811
  const error = value.error === void 0 ? void 0 : decodeAttemptError(value.error);
778
- const accounting3 = decodeAgentAccounting(value.accounting);
779
- if (!setup || !accounting3 || value.session !== void 0 && !session || value.error !== void 0 && !error) return void 0;
780
- return { attempt: value.attempt, transport: value.transport, setup, accounting: accounting3, ...session === void 0 ? {} : { session }, ...error === void 0 ? {} : { error } };
812
+ const accounting2 = decodeAgentAccounting(value.accounting);
813
+ if (!setup || !accounting2 || value.session !== void 0 && !session || value.error !== void 0 && !error) return void 0;
814
+ return { attempt: value.attempt, transport: value.transport, setup, accounting: accounting2, ...session === void 0 ? {} : { session }, ...error === void 0 ? {} : { error } };
781
815
  }
782
816
  function decodeAgent(value) {
783
817
  if (!object(value) || typeof value.id !== "string" || typeof value.name !== "string" || typeof value.path !== "string" || !isAgentState(value.state) || !integer(value.attempts)) return void 0;
@@ -798,12 +832,12 @@ function decodeAgent(value) {
798
832
  const startedAt = optionalNumber(value.startedAt);
799
833
  const durationMs = optionalNumber(value.durationMs);
800
834
  const attemptDetails = value.attemptDetails === void 0 ? void 0 : decodeArray(value.attemptDetails, decodeAgentAttempt);
801
- const accounting3 = value.accounting === void 0 ? void 0 : decodeAgentAccounting(value.accounting);
835
+ const accounting2 = value.accounting === void 0 ? void 0 : decodeAgentAccounting(value.accounting);
802
836
  const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
803
- const activity2 = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
837
+ const activity = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
804
838
  const lastEventAt = optionalNumber(value.lastEventAt);
805
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;
806
- if (!model || !tools || value.structuralPath !== void 0 && !structuralPath2 || value.attemptDetails !== void 0 && !attemptDetails || value.accounting !== void 0 && !accounting3 || value.toolCalls !== void 0 && !toolCalls || value.activity !== void 0 && !activity2) return void 0;
840
+ 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;
807
841
  return {
808
842
  ...systemPrompt === void 0 ? {} : { systemPrompt },
809
843
  ...prompt === void 0 ? {} : { prompt },
@@ -827,9 +861,9 @@ function decodeAgent(value) {
827
861
  ...startedAt === void 0 ? {} : { startedAt },
828
862
  ...durationMs === void 0 ? {} : { durationMs },
829
863
  ...attemptDetails === void 0 ? {} : { attemptDetails },
830
- ...accounting3 === void 0 ? {} : { accounting: accounting3 },
864
+ ...accounting2 === void 0 ? {} : { accounting: accounting2 },
831
865
  ...toolCalls === void 0 ? {} : { toolCalls },
832
- ...activity2 === void 0 ? {} : { activity: activity2 },
866
+ ...activity === void 0 ? {} : { activity },
833
867
  ...lastEventAt === void 0 ? {} : { lastEventAt }
834
868
  };
835
869
  }
@@ -873,9 +907,25 @@ function decodeShellActivity(value) {
873
907
  }
874
908
  function decodeRunEvent(value) {
875
909
  if (!object(value) || typeof value.type !== "string" || typeof value.message !== "string") return void 0;
876
- const timestamp3 = optionalNumber(value.timestamp);
877
- if (timestamp3 === INVALID_PERSISTED_VALUE) return void 0;
878
- return { type: value.type, message: value.message, ...timestamp3 === void 0 ? {} : { timestamp: timestamp3 } };
910
+ const timestamp2 = optionalNumber(value.timestamp);
911
+ if (timestamp2 === INVALID_PERSISTED_VALUE) return void 0;
912
+ return { type: value.type, message: value.message, ...timestamp2 === void 0 ? {} : { timestamp: timestamp2 } };
913
+ }
914
+ function decodeScriptCall(value) {
915
+ if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt)) return void 0;
916
+ const shell = value.kind === "shell";
917
+ if (shell && !jsonValue(value.input)) return void 0;
918
+ const inputBytes = shell ? optionalNumber(value.inputBytes) : void 0;
919
+ const phase = optionalString(value.phase);
920
+ const finishedAt = optionalNumber(value.finishedAt);
921
+ let error;
922
+ if (value.error !== void 0) {
923
+ if (shell) error = decodeWorkflowError(value.error);
924
+ else if (object(value.error) && typeof value.error.code === "string") error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
925
+ if (!error) return void 0;
926
+ }
927
+ if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE) return void 0;
928
+ return { kind: value.kind, name: value.name, path: value.path, ...shell && jsonValue(value.input) ? { input: value.input } : {}, ...inputBytes === void 0 ? {} : { inputBytes }, ...phase === void 0 ? {} : { phase }, startedAt: value.startedAt, ...finishedAt === void 0 ? {} : { finishedAt }, ...error === void 0 ? {} : { error } };
879
929
  }
880
930
  function decodeDelivery(value) {
881
931
  if (!object(value)) return void 0;
@@ -906,6 +956,8 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
906
956
  const budgetEvents = value.budgetEvents === void 0 ? void 0 : decodeArray(value.budgetEvents, decodeBudgetEvent);
907
957
  const events = value.events === void 0 ? void 0 : decodeArray(value.events, decodeRunEvent);
908
958
  const delivery = value.delivery === void 0 ? void 0 : decodeDelivery(value.delivery);
959
+ const scriptCalls = value.scriptCalls === void 0 ? void 0 : decodeArray(value.scriptCalls, decodeScriptCall);
960
+ if (value.scriptCalls !== void 0 && !scriptCalls) return void 0;
909
961
  if (!agentSessions || !agents || parentRunId === INVALID_PERSISTED_VALUE || value.retry !== void 0 && !retry || phase === INVALID_PERSISTED_VALUE || value.phaseHistory !== void 0 && !phaseHistory || phaseHistoryIndex === INVALID_PERSISTED_VALUE || activeShells === INVALID_PERSISTED_VALUE || activeShellStartedAt === INVALID_PERSISTED_VALUE || value.activeShellsByPhase !== void 0 && !activeShellsByPhase || value.error !== void 0 && !error || failedAt === INVALID_PERSISTED_VALUE || value.budget !== void 0 && !budget || budgetVersion === INVALID_PERSISTED_VALUE || value.budgetVersion !== void 0 && !integer(budgetVersion) || value.usage !== void 0 && !usage || value.budgetEvents !== void 0 && !budgetEvents || value.events !== void 0 && !events || value.delivery !== void 0 && !delivery) return void 0;
910
962
  if (value.agentSessions === void 0 && !allowLegacyAgentSessions) return void 0;
911
963
  return {
@@ -924,6 +976,7 @@ function decodePersistedRun(value, allowLegacyAgentSessions = false) {
924
976
  ...activeShells === void 0 ? {} : { activeShells },
925
977
  ...activeShellStartedAt === void 0 ? {} : { activeShellStartedAt },
926
978
  ...activeShellsByPhase === void 0 ? {} : { activeShellsByPhase },
979
+ ...scriptCalls === void 0 ? {} : { scriptCalls },
927
980
  ...error === void 0 ? {} : { error },
928
981
  ...failedAt === void 0 ? {} : { failedAt },
929
982
  ...budget === void 0 ? {} : { budget },
@@ -1920,20 +1973,236 @@ var RunStore = class _RunStore {
1920
1973
  }
1921
1974
  };
1922
1975
 
1923
- // src/validation.ts
1924
- import { mkdirSync, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
1976
+ // src/settings.ts
1977
+ import { mkdirSync, readFileSync } from "node:fs";
1925
1978
  import { homedir as homedir3 } from "node:os";
1926
- import { basename as basename3, dirname as dirname3, extname, join as join4, resolve as resolve3 } from "node:path";
1927
- import { fileURLToPath as fileURLToPath2 } from "node:url";
1928
- import * as acorn from "acorn";
1929
- import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
1979
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
1980
+ import { fileURLToPath } from "node:url";
1981
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
1982
+ var ROLE_DIRECTORY = "pi-extensible-workflows";
1983
+ var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
1984
+ function workflowSettingsPath(agentDir = getAgentDir()) {
1985
+ return join3(agentDir, ROLE_DIRECTORY, "settings.json");
1986
+ }
1987
+ function normalizedResourcePath(value, settingsPath) {
1988
+ 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);
1991
+ const resolved = resolve3(dirname2(settingsPath), expanded);
1992
+ if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
1993
+ if (resourcePatternHasMagic(expanded)) {
1994
+ const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
1995
+ const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
1996
+ const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
1997
+ const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
1998
+ const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
1999
+ return `${canonicalPath(prefix)}${suffix}`;
2000
+ }
2001
+ return canonicalPath(resolved);
2002
+ }
2003
+ function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
2004
+ if (value === void 0) return void 0;
2005
+ if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
2006
+ const normalized = [];
2007
+ for (const [index, entry] of value.entries()) {
2008
+ if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
2009
+ let selector = entry.trim();
2010
+ if (kind === "extensions" && normalizeExtensions) {
2011
+ const negated = selector.startsWith("!");
2012
+ const body = negated ? selector.slice(1) : selector;
2013
+ if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
2014
+ try {
2015
+ selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
2016
+ } catch (error) {
2017
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
2018
+ }
2019
+ }
2020
+ try {
2021
+ validateResourcePattern(selector);
2022
+ } catch (error) {
2023
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
2024
+ }
2025
+ normalized.push(selector);
2026
+ }
2027
+ return Object.freeze(normalized);
2028
+ }
2029
+ function validateWorktreePostCreateCommand(value, settingsPath) {
2030
+ if (value === void 0) return void 0;
2031
+ if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
2032
+ return Object.freeze(value.map((entry, index) => {
2033
+ if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2034
+ return entry;
2035
+ }));
2036
+ }
2037
+ function selectorsFromSettings(settings) {
2038
+ return {
2039
+ ...settings.skills === void 0 ? {} : { skills: settings.skills },
2040
+ ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2041
+ ...settings.tools === void 0 ? {} : { tools: settings.tools }
2042
+ };
2043
+ }
2044
+ function selectorSet(value) {
2045
+ return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2046
+ }
2047
+ function validateContextFileScopes(value, rolePath) {
2048
+ 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`);
2050
+ return [...value];
2051
+ }
2052
+ function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
2053
+ if (value === void 0) return void 0;
2054
+ const base = `${settingsPath}.extensionSettings`;
2055
+ if (!object(value)) fail(errorCode2, `${base} must be an object`);
2056
+ const normalized = {};
2057
+ for (const [namespace, raw] of Object.entries(value)) {
2058
+ if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2059
+ if (namespace === "herdr") {
2060
+ if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2061
+ if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2062
+ if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2063
+ normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2064
+ continue;
2065
+ }
2066
+ if (namespace === "trajectory") {
2067
+ if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2068
+ if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2069
+ if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2070
+ normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
2071
+ continue;
2072
+ }
2073
+ if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2074
+ normalized[namespace] = structuredClone(raw);
2075
+ }
2076
+ return deepFreeze(normalized);
2077
+ }
2078
+ function positiveRetentionInteger(value) {
2079
+ return positiveInteger(value) && Number.isSafeInteger(value);
2080
+ }
2081
+ function validateRetention(value, settingsPath) {
2082
+ if (value === void 0) return void 0;
2083
+ const base = `${settingsPath}.retention`;
2084
+ if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2085
+ const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2086
+ if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2087
+ if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2088
+ if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2089
+ return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2090
+ }
2091
+ function parseSettings(path, partial) {
2092
+ let parsed;
2093
+ try {
2094
+ parsed = JSON.parse(readFileSync(path, "utf8"));
2095
+ } catch (error) {
2096
+ if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2097
+ fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2098
+ }
2099
+ if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2100
+ const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget", "codemodeTools"]]);
2101
+ const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2102
+ if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2103
+ if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2104
+ const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2105
+ if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2106
+ const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2107
+ if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2108
+ const codemodeTools = parsed.codemodeTools;
2109
+ if (codemodeTools !== void 0 && codemodeTools !== "all" && codemodeTools !== "read-only" && codemodeTools !== "none") fail("INVALID_SETTINGS", `${path}.codemodeTools must be "all", "read-only", or "none"`);
2110
+ const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2111
+ const skills = validateSelectorList(parsed.skills, path, "skills");
2112
+ const tools = validateSelectorList(parsed.tools, path, "tools");
2113
+ const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2114
+ const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
2115
+ const retention = validateRetention(parsed.retention, path);
2116
+ const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2117
+ return Object.freeze({
2118
+ ...concurrency === void 0 ? {} : { concurrency },
2119
+ ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2120
+ ...codemodeTools === void 0 ? {} : { codemodeTools },
2121
+ ...modelAliases === void 0 ? {} : { modelAliases },
2122
+ ...skills === void 0 ? {} : { skills },
2123
+ ...extensions === void 0 ? {} : { extensions },
2124
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2125
+ ...tools === void 0 ? {} : { tools },
2126
+ ...retention === void 0 ? {} : { retention },
2127
+ ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2128
+ });
2129
+ }
2130
+ function loadSettings(path = workflowSettingsPath()) {
2131
+ return parseSettings(path, false);
2132
+ }
2133
+ var READ_ONLY_WORKFLOW_TOOLS = /* @__PURE__ */ new Set(["workflow_status", "workflow_catalog", "subagents_inspect"]);
2134
+ function workflowToolExposure(name, codemodeTools) {
2135
+ const readOnly = READ_ONLY_WORKFLOW_TOOLS.has(name);
2136
+ return { ...codemodeTools === "none" || codemodeTools === "read-only" && !readOnly ? { exposure: "model-only" } : {}, ...readOnly ? { annotations: { readOnlyHint: true } } : {} };
2137
+ }
2138
+ function loadCodemodeToolsSetting(agentDir) {
2139
+ try {
2140
+ return loadSettings(workflowSettingsPath(agentDir)).codemodeTools;
2141
+ } catch {
2142
+ return void 0;
2143
+ }
2144
+ }
2145
+ function loadSettingsOverrides(path) {
2146
+ return parseSettings(path, true);
2147
+ }
2148
+ function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2149
+ 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
+ };
2173
+ 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 }
2183
+ });
2184
+ return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
2185
+ }
2186
+ function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2187
+ for (const name of names) {
2188
+ try {
2189
+ const target = modelCapability(name, aliases, knownModels, settingsPath);
2190
+ if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2191
+ } catch (error) {
2192
+ throw annotateModelAliasError(error, name);
2193
+ }
2194
+ }
2195
+ }
2196
+ function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2197
+ const resolved = resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath);
2198
+ const global = selectorSet(selectorsFromSettings(resolved.global));
2199
+ const project = selectorSet(selectorsFromSettings(resolved.project));
2200
+ 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) } };
2202
+ }
1930
2203
 
1931
2204
  // src/registry.ts
1932
- import { readFileSync } from "node:fs";
1933
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join3 } from "node:path";
1934
- import { fileURLToPath } from "node:url";
1935
- import { Value } from "typebox/value";
1936
- var RESERVED_GLOBALS = /* @__PURE__ */ new Set(["agent", "shell", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
2205
+ 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"]);
1937
2206
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
1938
2207
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
1939
2208
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
@@ -1941,10 +2210,10 @@ function normalizeRoleDirectory(value) {
1941
2210
  try {
1942
2211
  if (value instanceof URL) {
1943
2212
  if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
1944
- return fileURLToPath(value);
2213
+ return fileURLToPath2(value);
1945
2214
  }
1946
2215
  if (typeof value === "string") {
1947
- if (value.startsWith("file:")) return fileURLToPath(value);
2216
+ if (value.startsWith("file:")) return fileURLToPath2(value);
1948
2217
  if (isAbsolute(value)) return value;
1949
2218
  }
1950
2219
  } catch (error) {
@@ -1954,12 +2223,12 @@ function normalizeRoleDirectory(value) {
1954
2223
  }
1955
2224
  function isBuiltinRoleDirectory(path) {
1956
2225
  const canonical = canonicalPath(path);
1957
- const starterDirectory = dirname2(canonical);
2226
+ const starterDirectory = dirname3(canonical);
1958
2227
  if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
1959
- const distributionDirectory = dirname2(starterDirectory);
1960
- const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname2(distributionDirectory) : distributionDirectory;
2228
+ const distributionDirectory = dirname3(starterDirectory);
2229
+ const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
1961
2230
  try {
1962
- const metadata = JSON.parse(readFileSync(join3(packageRoot2, "package.json"), "utf8"));
2231
+ const metadata = JSON.parse(readFileSync2(join4(packageRoot2, "package.json"), "utf8"));
1963
2232
  return object(metadata) && isCorePackageName(metadata.name);
1964
2233
  } catch {
1965
2234
  return false;
@@ -2240,213 +2509,9 @@ function registeredWorkflowRoleDirectoryRegistrations() {
2240
2509
  return typeof registrations === "function" ? registrations() : [];
2241
2510
  }
2242
2511
 
2243
- // src/validation.ts
2244
- var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
2245
- function workflowSettingsPath(agentDir = getAgentDir()) {
2246
- return join4(agentDir, ROLE_DIRECTORY, "settings.json");
2247
- }
2248
- function normalizedResourcePath(value, settingsPath) {
2249
- if (value === "*") return value;
2250
- let expanded = value === "~" ? homedir3() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir3(), value.slice(2)) : value;
2251
- if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
2252
- const resolved = resolve3(dirname3(settingsPath), expanded);
2253
- if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
2254
- if (resourcePatternHasMagic(expanded)) {
2255
- const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
2256
- const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
2257
- const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
2258
- const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
2259
- const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
2260
- return `${canonicalPath(prefix)}${suffix}`;
2261
- }
2262
- return canonicalPath(resolved);
2263
- }
2264
- function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
2265
- if (value === void 0) return void 0;
2266
- if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
2267
- const normalized = [];
2268
- for (const [index, entry] of value.entries()) {
2269
- if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
2270
- let selector = entry.trim();
2271
- if (kind === "extensions" && normalizeExtensions) {
2272
- const negated = selector.startsWith("!");
2273
- const body = negated ? selector.slice(1) : selector;
2274
- if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
2275
- try {
2276
- selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
2277
- } catch (error) {
2278
- fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
2279
- }
2280
- }
2281
- try {
2282
- validateResourcePattern(selector);
2283
- } catch (error) {
2284
- fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
2285
- }
2286
- normalized.push(selector);
2287
- }
2288
- return Object.freeze(normalized);
2289
- }
2290
- function validateWorktreePostCreateCommand(value, settingsPath) {
2291
- if (value === void 0) return void 0;
2292
- if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
2293
- return Object.freeze(value.map((entry, index) => {
2294
- if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2295
- return entry;
2296
- }));
2297
- }
2298
- function selectorsFromSettings(settings) {
2299
- return {
2300
- ...settings.skills === void 0 ? {} : { skills: settings.skills },
2301
- ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2302
- ...settings.tools === void 0 ? {} : { tools: settings.tools }
2303
- };
2304
- }
2305
- function selectorSet(value) {
2306
- return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2307
- }
2308
- function validateContextFileScopes(value, rolePath) {
2309
- if (value === void 0) return void 0;
2310
- if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2311
- return [...value];
2312
- }
2313
- function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS", applyDefaults = true) {
2314
- if (value === void 0) return void 0;
2315
- const base = `${settingsPath}.extensionSettings`;
2316
- if (!object(value)) fail(errorCode2, `${base} must be an object`);
2317
- const normalized = {};
2318
- for (const [namespace, raw] of Object.entries(value)) {
2319
- if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2320
- if (namespace === "herdr") {
2321
- if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2322
- if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2323
- if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2324
- normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2325
- continue;
2326
- }
2327
- if (namespace === "trajectory") {
2328
- if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2329
- if (Object.keys(raw).some((key) => key !== "port" && key !== "themes")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2330
- if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2331
- if (raw.themes !== void 0 && typeof raw.themes !== "boolean") fail(errorCode2, `${base}.trajectory.themes must be a boolean`);
2332
- normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? applyDefaults ? { themes: false } : {} : { themes: raw.themes } });
2333
- continue;
2334
- }
2335
- if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2336
- normalized[namespace] = structuredClone(raw);
2337
- }
2338
- return deepFreeze(normalized);
2339
- }
2340
- function positiveRetentionInteger(value) {
2341
- return positiveInteger(value) && Number.isSafeInteger(value);
2342
- }
2343
- function validateRetention(value, settingsPath) {
2344
- if (value === void 0) return void 0;
2345
- const base = `${settingsPath}.retention`;
2346
- if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2347
- const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2348
- if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2349
- if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2350
- if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2351
- return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2352
- }
2353
- function parseSettings(path, partial) {
2354
- let parsed;
2355
- try {
2356
- parsed = JSON.parse(readFileSync2(path, "utf8"));
2357
- } catch (error) {
2358
- if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2359
- fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2360
- }
2361
- if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2362
- const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
2363
- const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2364
- if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2365
- if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2366
- const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2367
- if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2368
- const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2369
- if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2370
- const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2371
- const skills = validateSelectorList(parsed.skills, path, "skills");
2372
- const tools = validateSelectorList(parsed.tools, path, "tools");
2373
- const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2374
- const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS", !partial);
2375
- const retention = validateRetention(parsed.retention, path);
2376
- const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2377
- return Object.freeze({
2378
- ...concurrency === void 0 ? {} : { concurrency },
2379
- ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2380
- ...modelAliases === void 0 ? {} : { modelAliases },
2381
- ...skills === void 0 ? {} : { skills },
2382
- ...extensions === void 0 ? {} : { extensions },
2383
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2384
- ...tools === void 0 ? {} : { tools },
2385
- ...retention === void 0 ? {} : { retention },
2386
- ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2387
- });
2388
- }
2389
- function loadSettings(path = workflowSettingsPath()) {
2390
- return parseSettings(path, false);
2391
- }
2392
- function loadSettingsOverrides(path) {
2393
- return parseSettings(path, true);
2394
- }
2395
- function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2396
- const projectSettingsPath = workflowProjectSettingsPath(cwd);
2397
- const global = loadSettings(globalSettingsPath);
2398
- const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2399
- const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2400
- const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2401
- const globalSelectors = selectorsFromSettings(global);
2402
- const projectSelectors = selectorsFromSettings(project);
2403
- const effectiveSelectors = selectorSet({
2404
- skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2405
- extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2406
- ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2407
- });
2408
- const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2409
- const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2410
- const sources = {
2411
- concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2412
- modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2413
- skills: sourceFor("skills"),
2414
- extensions: sourceFor("extensions"),
2415
- tools: sourceFor("tools"),
2416
- ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2417
- ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2418
- ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2419
- };
2420
- const effective = Object.freeze({
2421
- concurrency: project.concurrency ?? global.concurrency,
2422
- backgroundWidget: global.backgroundWidget ?? true,
2423
- ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2424
- ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2425
- ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2426
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2427
- ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2428
- ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2429
- ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2430
- });
2431
- return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
2432
- }
2433
- function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2434
- for (const name of names) {
2435
- try {
2436
- const target = modelCapability(name, aliases, knownModels, settingsPath);
2437
- if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2438
- } catch (error) {
2439
- throw annotateModelAliasError(error, name);
2440
- }
2441
- }
2442
- }
2443
- function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2444
- const resolved = resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath);
2445
- const global = selectorSet(selectorsFromSettings(resolved.global));
2446
- const project = selectorSet(selectorsFromSettings(resolved.project));
2447
- const effective = selectorSet(selectorsFromSettings(resolved.effective));
2448
- return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2449
- }
2512
+ // src/roles.ts
2513
+ var ROLE_DIRECTORY2 = "pi-extensible-workflows";
2514
+ var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
2450
2515
  function parseRoleMarkdown(content, strict = false, rolePath) {
2451
2516
  if (!strict) {
2452
2517
  if (!content.startsWith("---\n")) return { prompt: content };
@@ -2473,7 +2538,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2473
2538
  }
2474
2539
  }
2475
2540
  const rolePathValue2 = rolePath ?? "<role>";
2476
- const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA", false);
2541
+ const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
2477
2542
  const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
2478
2543
  if (meta.model) {
2479
2544
  const model2 = unquote(meta.model);
@@ -2513,7 +2578,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2513
2578
  const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
2514
2579
  const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
2515
2580
  const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
2516
- const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA", false);
2581
+ const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
2517
2582
  const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
2518
2583
  const normalizedModel = typeof model === "string" ? model.trim() : void 0;
2519
2584
  if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
@@ -2528,12 +2593,11 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2528
2593
  if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
2529
2594
  return definition;
2530
2595
  }
2531
- var ROLE_DIRECTORY = "pi-extensible-workflows";
2532
- function workflowRoleDirectories(agentDir = getAgentDir()) {
2533
- return [join4(agentDir, ROLE_DIRECTORY, "roles")];
2596
+ function workflowRoleDirectories(agentDir = getAgentDir2()) {
2597
+ return [join5(agentDir, ROLE_DIRECTORY2, "roles")];
2534
2598
  }
2535
2599
  function projectRoleDirectories(root) {
2536
- return [join4(root, ROLE_DIRECTORY, "roles")];
2600
+ return [join5(root, ROLE_DIRECTORY2, "roles")];
2537
2601
  }
2538
2602
  function roleDirectorySources(dirs) {
2539
2603
  const seen = /* @__PURE__ */ new Set();
@@ -2552,7 +2616,7 @@ function isRoleFile(dir, entry) {
2552
2616
  if (entry.isFile()) return true;
2553
2617
  if (!entry.isSymbolicLink()) return false;
2554
2618
  try {
2555
- return statSync(join4(dir, entry.name)).isFile();
2619
+ return statSync(join5(dir, entry.name)).isFile();
2556
2620
  } catch (error) {
2557
2621
  if (isNodeError(error, "ENOENT")) return false;
2558
2622
  throw error;
@@ -2568,11 +2632,11 @@ function scanRoleFiles(dirs, extension2) {
2568
2632
  if (!extension2 && isNodeError(error, "ENOENT")) continue;
2569
2633
  fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension2)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
2570
2634
  }
2571
- for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join4(source.path, entry.name), source });
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 });
2572
2636
  }
2573
2637
  return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
2574
2638
  }
2575
- function readRoleDefinitions(dirs, extension2 = false) {
2639
+ function roleEntries(dirs, extension2) {
2576
2640
  const files = scanRoleFiles(dirs, extension2);
2577
2641
  const starterFiles = files.filter(({ source }) => source.builtin === true);
2578
2642
  const regularFiles = files.filter(({ source }) => source.builtin !== true);
@@ -2583,7 +2647,9 @@ function readRoleDefinitions(dirs, extension2 = false) {
2583
2647
  }
2584
2648
  const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
2585
2649
  try {
2586
- return [name, parseRoleMarkdown(readFileSync2(path, "utf8"), true, path)];
2650
+ const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
2651
+ roleDefinitionPaths.set(definition, path);
2652
+ return [name, { name, path, source, definition }];
2587
2653
  } catch (error) {
2588
2654
  if (extension2) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
2589
2655
  throw error;
@@ -2591,15 +2657,114 @@ function readRoleDefinitions(dirs, extension2 = false) {
2591
2657
  }));
2592
2658
  return extension2 ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
2593
2659
  }
2594
- function loadAgentDefinitions(cwd, agentDir = getAgentDir(), projectTrusted = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
2595
- return deepFreeze({ ...readRoleDefinitions(extensionRoleDirectories, true), ...readRoleDefinitions(workflowRoleDirectories(agentDir)), ...projectTrusted ? readRoleDefinitions(projectRoleDirectories(join4(cwd, ".pi"))) : {} });
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
+ };
2596
2670
  }
2597
- function validateSchema(schema, at = "schema") {
2598
- if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
2599
- if (typeof schema.type !== "string" && !Array.isArray(schema.type) && schema.$ref === void 0 && schema.anyOf === void 0 && schema.oneOf === void 0 && schema.allOf === void 0 && schema.const === void 0 && schema.enum === void 0) fail("INVALID_SCHEMA", `${at} has no JSON Schema shape`);
2600
- if (schema.required !== void 0 && (!Array.isArray(schema.required) || schema.required.some((key) => typeof key !== "string"))) fail("INVALID_SCHEMA", `${at}.required must be an array of strings`);
2601
- if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
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 } };
2602
2765
  }
2766
+
2767
+ // src/validation.ts
2603
2768
  var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
2604
2769
  function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
2605
2770
  switch (key) {
@@ -2876,6 +3041,30 @@ const functions = Object.freeze(Object.fromEntries(Object.entries(config.functio
2876
3041
  Object.defineProperty(result, "toJSON", { value() { throw workError("INVALID_METADATA", "Workflow function result is a Promise; await it before serialization"); } });
2877
3042
  return result;
2878
3043
  }])));
3044
+ // tools.<id>(args) runs a Pi session tool through the host. Any identifier resolves, so a resumed run
3045
+ // replays journaled calls even when the tool is gone; the host rejects identifiers it cannot call.
3046
+ // A call is the n-th call with the same arguments in its scope, so replay does not depend on the
3047
+ // order in which concurrent calls start.
3048
+ const NOT_TOOLS = new Set(["then", "toJSON", "toString", "valueOf", "constructor"]);
3049
+ const sortedJson = value => Array.isArray(value) ? value.map(sortedJson) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(key => [key, sortedJson(value[key])])) : value;
3050
+ const toolOccurrences = new Map();
3051
+ const toolCall = id => (...values) => {
3052
+ if (worktreeOwners.getStore()) throw workError("INVALID_METADATA", "tools." + id + " is unavailable inside withWorktree: session tools resolve paths against the session working directory");
3053
+ if (values.length > 1 || values[0] !== undefined && (!values[0] || typeof values[0] !== "object" || Array.isArray(values[0]))) throw workError("INVALID_METADATA", "tools." + id + " takes one arguments object");
3054
+ const args = values[0] === undefined ? {} : values[0];
3055
+ const structuralPath = inheritedAgentPath.getStore() || [];
3056
+ const key = JSON.stringify([structuralPath, id, sortedJson(args)]);
3057
+ const occurrence = (toolOccurrences.get(key) || 0) + 1;
3058
+ toolOccurrences.set(key, occurrence);
3059
+ return guardedResult(rpc("tool", [id, args, { structuralPath: [...structuralPath], occurrence }]), "tool");
3060
+ };
3061
+ const tools = new Proxy(Object.create(null), {
3062
+ get: (_target, key) => typeof key === "string" && !NOT_TOOLS.has(key) ? toolCall(key) : undefined,
3063
+ // Probing would branch on the session that resumed the run; the launch already checks named tools.
3064
+ has: () => { throw workError("INVALID_METADATA", "Workflow scripts cannot probe tools with 'in'; call tools.<name>(...) directly, and the launch checks that the tool exists"); },
3065
+ set: () => false,
3066
+ defineProperty: () => false,
3067
+ });
2879
3068
  const recordEntries = (value, kind) => {
2880
3069
  if (!value || typeof value !== "object" || Array.isArray(value)) throw workError("INVALID_METADATA", kind + " must be a record");
2881
3070
  return Object.entries(value);
@@ -2936,7 +3125,7 @@ const pipeline = async (operationName, items, stages) => {
2936
3125
  return Object.fromEntries(results.map(result => [result.name, result.value]));
2937
3126
  };
2938
3127
  const safeMath = Object.fromEntries(Object.getOwnPropertyNames(Math).filter(name => name !== "random").map(name => [name, Math[name]]));
2939
- const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
3128
+ const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, tools, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
2940
3129
  for (const [name, fn] of Object.entries(functions)) Object.defineProperty(sandbox, name, { value: fn, writable: false, configurable: false });
2941
3130
  for (const name of ["Date","eval","Function","WebAssembly","process","require","module","exports","console","fetch","XMLHttpRequest","WebSocket","performance","crypto","setTimeout","setInterval","setImmediate","queueMicrotask","Intl","SharedArrayBuffer","Atomics"]) sandbox[name] = undefined;
2942
3131
  const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
@@ -2949,17 +3138,17 @@ Promise.resolve().then(() => new vm.Script("(async(__pi_extensible_workflows_age
2949
3138
 
2950
3139
  // src/host.ts
2951
3140
  import { Type as Type3 } from "@earendil-works/pi-ai";
2952
- import { copyToClipboard, getAgentDir as getAgentDir4, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
3141
+ import { copyToClipboard, getAgentDir as getAgentDir5, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
2953
3142
 
2954
3143
  // src/agent-execution.ts
2955
- import { existsSync, realpathSync as realpathSync2, readFileSync as readFileSync3 } from "node:fs";
3144
+ import { existsSync, realpathSync as realpathSync2, readFileSync as readFileSync4 } from "node:fs";
2956
3145
  import { copyFile } from "node:fs/promises";
2957
3146
  import { homedir as homedir4 } from "node:os";
2958
- import { basename as basename4, dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
3147
+ import { basename as basename4, dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
2959
3148
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2960
3149
  import { Type } from "@earendil-works/pi-ai";
2961
3150
  import { Compile } from "typebox/compile";
2962
- import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir2, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
3151
+ import { createAgentSession, createCodemodeExtension, createMcpExtension, createToolSearchExtension, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir3, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
2963
3152
 
2964
3153
  // src/pi-runtime-adapter.ts
2965
3154
  var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
@@ -3007,16 +3196,16 @@ function normalizeState(value) {
3007
3196
  const candidate = record(value);
3008
3197
  const model = record(candidate?.model);
3009
3198
  const provider = stringValue(model?.provider);
3010
- const modelName = stringValue(model?.model);
3199
+ const modelName2 = stringValue(model?.model);
3011
3200
  const rawTools = candidate?.tools;
3012
3201
  const tools = Array.isArray(rawTools) ? rawTools.filter((tool) => typeof tool === "string") : void 0;
3013
- if (!candidate || !model || provider === void 0 || modelName === void 0 || tools === void 0 || !Array.isArray(rawTools) || tools.length !== rawTools.length) return void 0;
3202
+ if (!candidate || !model || provider === void 0 || modelName2 === void 0 || tools === void 0 || !Array.isArray(rawTools) || tools.length !== rawTools.length) return void 0;
3014
3203
  const thinking = candidate.thinking === void 0 ? model.thinking : candidate.thinking;
3015
3204
  if (thinking !== void 0 && !isThinkingLevel(thinking)) return void 0;
3016
3205
  const systemPrompt = candidate.systemPrompt;
3017
3206
  if (systemPrompt !== void 0 && typeof systemPrompt !== "string") return void 0;
3018
3207
  return {
3019
- model: { provider, model: modelName, ...thinking === void 0 ? {} : { thinking } },
3208
+ model: { provider, model: modelName2, ...thinking === void 0 ? {} : { thinking } },
3020
3209
  ...thinking === void 0 ? {} : { thinking },
3021
3210
  tools: [...tools],
3022
3211
  ...systemPrompt === void 0 ? {} : { systemPrompt }
@@ -3116,14 +3305,14 @@ function createRuntimeHandoffAdapter(handoff) {
3116
3305
  function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3117
3306
  const neutralHandoff = createRuntimeHandoffAdapter(handoff);
3118
3307
  const toolCalls = /* @__PURE__ */ new Map();
3119
- let activity2;
3308
+ let activity;
3120
3309
  let streamText = "";
3121
3310
  let lastEventAt;
3122
3311
  let lastReportedEventAt;
3123
3312
  let disposed = false;
3124
3313
  let toolCallsView = [];
3125
3314
  const progressAt = (persist, stateOverride, toolCallsValue3, activityValue2, eventAtValue) => ({ usage: runtimeUsage(session), toolCalls: toolCallsValue3, state: stateOverride ?? stateFromSession(session), ...activityValue2 === void 0 ? {} : { activity: activityValue2 }, ...eventAtValue === void 0 ? {} : { lastEventAt: eventAtValue }, persist });
3126
- const snapshot = (persist) => progressAt(persist, void 0, [...toolCalls.values()], activity2, lastEventAt);
3315
+ const snapshot = (persist) => progressAt(persist, void 0, [...toolCalls.values()], activity, lastEventAt);
3127
3316
  return {
3128
3317
  handoff: neutralHandoff,
3129
3318
  observe(event) {
@@ -3134,7 +3323,7 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3134
3323
  const eventState = event.state === void 0 ? void 0 : runtimeState(event.state);
3135
3324
  let report = false;
3136
3325
  let persist = false;
3137
- const previousActivity = activity2;
3326
+ const previousActivity = activity;
3138
3327
  let eventToolCalls = toolCallsView;
3139
3328
  if (event.type === "state_changed") {
3140
3329
  report = true;
@@ -3142,7 +3331,7 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3142
3331
  }
3143
3332
  if (event.type === "message_start" && event.message?.role === "assistant") {
3144
3333
  streamText = "";
3145
- activity2 = { kind: "text", text: "" };
3334
+ activity = { kind: "text", text: "" };
3146
3335
  report = true;
3147
3336
  }
3148
3337
  if (event.type === "message_update") {
@@ -3150,14 +3339,14 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3150
3339
  if (updateType === "thinking_start" || updateType === "text_start") streamText = "";
3151
3340
  const delta = event.assistantMessageEvent?.delta;
3152
3341
  if ((updateType === "thinking_delta" || updateType === "text_delta") && delta) streamText = `${streamText}${delta}`.replace(/\s+/g, " ").slice(-MAX_ACTIVITY_TEXT_CHARS);
3153
- if (updateType && ["thinking_start", "thinking_delta", "thinking_end"].includes(updateType)) activity2 = { kind: "reasoning", text: streamText };
3154
- else if (updateType && ["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(updateType)) activity2 = { kind: "text", text: streamText };
3155
- report = previousActivity?.kind !== activity2?.kind;
3342
+ if (updateType && ["thinking_start", "thinking_delta", "thinking_end"].includes(updateType)) activity = { kind: "reasoning", text: streamText };
3343
+ else if (updateType && ["text_start", "text_delta", "text_end", "toolcall_start", "toolcall_delta", "toolcall_end"].includes(updateType)) activity = { kind: "text", text: streamText };
3344
+ report = previousActivity?.kind !== activity?.kind;
3156
3345
  }
3157
3346
  if (event.type === "message_end") {
3158
3347
  streamText = "";
3159
- activity2 = void 0;
3160
- report = !sameActivity(previousActivity, activity2);
3348
+ activity = void 0;
3349
+ report = !sameActivity(previousActivity, activity);
3161
3350
  if (event.message?.role === "assistant") {
3162
3351
  persist = true;
3163
3352
  report = true;
@@ -3166,24 +3355,24 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3166
3355
  if (event.type === "tool_execution_start" && event.toolCallId && event.toolName) {
3167
3356
  toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: "running" });
3168
3357
  toolCallsView = [...toolCalls.values()];
3169
- activity2 = { kind: "tool", text: event.toolName };
3358
+ activity = { kind: "tool", text: event.toolName };
3170
3359
  report = true;
3171
3360
  }
3172
3361
  if (event.type === "tool_execution_update" && event.toolName) {
3173
- activity2 = { kind: "tool", text: event.toolName };
3174
- report = !sameActivity(previousActivity, activity2);
3362
+ activity = { kind: "tool", text: event.toolName };
3363
+ report = !sameActivity(previousActivity, activity);
3175
3364
  }
3176
3365
  let removeToolCallId;
3177
3366
  if (event.type === "tool_execution_end" && event.toolCallId && event.toolName) {
3178
3367
  toolCalls.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, state: event.isError ? "failed" : "completed" });
3179
3368
  eventToolCalls = [...toolCalls.values()];
3180
- if (activity2?.kind === "tool" && activity2.text === event.toolName) activity2 = void 0;
3369
+ if (activity?.kind === "tool" && activity.text === event.toolName) activity = void 0;
3181
3370
  report = true;
3182
3371
  removeToolCallId = event.toolCallId;
3183
3372
  }
3184
3373
  if (removeToolCallId === void 0) eventToolCalls = toolCallsView;
3185
3374
  if (!report && (lastReportedEventAt === void 0 || eventAt - lastReportedEventAt >= 1e3)) report = true;
3186
- const eventActivity = activity2;
3375
+ const eventActivity = activity;
3187
3376
  let materialized;
3188
3377
  const result = { event, get progress() {
3189
3378
  return materialized ??= progressAt(persist, eventState, eventToolCalls, eventActivity, eventAt);
@@ -3200,7 +3389,7 @@ function createPiRuntimeSessionAdapter(session, handoff, clock = Date.now) {
3200
3389
  disposed = true;
3201
3390
  toolCalls.clear();
3202
3391
  toolCallsView = [];
3203
- activity2 = void 0;
3392
+ activity = void 0;
3204
3393
  }
3205
3394
  };
3206
3395
  }
@@ -3234,8 +3423,8 @@ function createLiveSessionHandoff() {
3234
3423
  let request;
3235
3424
  const waitForBoundary = () => {
3236
3425
  if (!turnActive) return Promise.resolve();
3237
- boundary ??= new Promise((resolve5) => {
3238
- resolveBoundary = resolve5;
3426
+ boundary ??= new Promise((resolve6) => {
3427
+ resolveBoundary = resolve6;
3239
3428
  });
3240
3429
  return boundary;
3241
3430
  };
@@ -3267,11 +3456,11 @@ function createLiveSessionHandoff() {
3267
3456
  if (request) return request;
3268
3457
  if (isReleased()) return;
3269
3458
  state = "handoff-pending";
3270
- takeover = new Promise((resolve5) => {
3271
- resolveTakeover = resolve5;
3459
+ takeover = new Promise((resolve6) => {
3460
+ resolveTakeover = resolve6;
3272
3461
  });
3273
- resume = new Promise((resolve5) => {
3274
- resolveResume = resolve5;
3462
+ resume = new Promise((resolve6) => {
3463
+ resolveResume = resolve6;
3275
3464
  });
3276
3465
  request = (async () => {
3277
3466
  await waitForBoundary();
@@ -3798,36 +3987,37 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
3798
3987
  function accounting(stats) {
3799
3988
  return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
3800
3989
  }
3801
- var extensionDirectory = dirname4(fileURLToPath3(import.meta.url));
3802
- var workflowPackageRoot = basename4(dirname4(extensionDirectory)) === "dist" ? resolve4(extensionDirectory, "../..") : resolve4(extensionDirectory, "..");
3803
- var WORKFLOW_HOST_ENTRIES = /* @__PURE__ */ new Set([
3804
- canonicalPath(resolve4(workflowPackageRoot, "src/index.ts")),
3805
- canonicalPath(resolve4(workflowPackageRoot, "dist/src/index.js")),
3806
- canonicalPath(resolve4(workflowPackageRoot, "starter/index.ts")),
3807
- canonicalPath(resolve4(workflowPackageRoot, "dist/starter/index.js")),
3808
- canonicalPath(resolve4(workflowPackageRoot, "subagents/index.ts")),
3809
- canonicalPath(resolve4(workflowPackageRoot, "dist/subagents/index.js"))
3810
- ]);
3811
- function canonicalExtensionSelector(selector, base = process.cwd()) {
3812
- const negated = selector.startsWith("!");
3813
- const body = negated ? selector.slice(1) : selector;
3814
- if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
3815
- const resolved = resolve4(base, body);
3816
- if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
3817
- return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
3990
+ 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
+ function isWorkflowHostEntry(path) {
3992
+ const entryPath = canonicalPath(path);
3993
+ let packageRoot2 = dirname5(entryPath);
3994
+ while (packageRoot2 !== dirname5(packageRoot2)) {
3995
+ const packageMetadataPath = join6(packageRoot2, "package.json");
3996
+ if (existsSync(packageMetadataPath)) {
3997
+ try {
3998
+ const metadata = JSON.parse(readFileSync4(packageMetadataPath, "utf8"));
3999
+ 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);
4001
+ } catch {
4002
+ return false;
4003
+ }
4004
+ }
4005
+ packageRoot2 = dirname5(packageRoot2);
4006
+ }
4007
+ return false;
3818
4008
  }
3819
4009
  var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
3820
4010
  function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
3821
- const projectPath = join5(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
4011
+ const projectPath = join6(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
3822
4012
  if (projectTrusted && existsSync(projectPath)) return projectPath;
3823
- const globalPaths = [join5(homedir4(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join5(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
4013
+ const globalPaths = [join6(homedir4(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join6(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
3824
4014
  return globalPaths.find((path) => existsSync(path));
3825
4015
  }
3826
4016
  function filterContextFiles(base, scopes, cwd, agentDir) {
3827
4017
  const globalPath = canonicalPath(agentDir);
3828
4018
  const cwdPath = canonicalPath(cwd);
3829
4019
  return base.filter(({ path }) => {
3830
- const resolvedPath = canonicalPath(dirname4(path));
4020
+ const resolvedPath = canonicalPath(dirname5(path));
3831
4021
  return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
3832
4022
  });
3833
4023
  }
@@ -3848,33 +4038,40 @@ function loadPiPackageDirectory() {
3848
4038
  }
3849
4039
  }).catch((error) => ({ error: `could not load @earendil-works/pi-coding-agent package metadata: ${errorText(error)}` }));
3850
4040
  }
3851
- var nativePromptTemplateModule;
3852
- async function loadNativePromptTemplateModule() {
4041
+ var nativeCoreModules = /* @__PURE__ */ new Map();
4042
+ async function loadNativeCoreModule(file, label2) {
3853
4043
  const packageDirectory = await loadPiPackageDirectory();
3854
- let promptTemplatePath;
4044
+ let modulePath;
3855
4045
  if (packageDirectory.directory) {
3856
- const candidate = resolve4(packageDirectory.directory, "dist/core/prompt-templates.js");
3857
- if (existsSync(candidate)) promptTemplatePath = candidate;
4046
+ const candidate = resolve5(packageDirectory.directory, "dist/core", file);
4047
+ if (existsSync(candidate)) modulePath = candidate;
3858
4048
  }
3859
- promptTemplatePath ??= resolve4(dirname4(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
4049
+ modulePath ??= resolve5(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core", file);
3860
4050
  const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
3861
- return nativePromptTemplateModule ??= import(promptTemplatePath).then((module) => module).catch((error) => {
3862
- throw new Error(`Could not load Pi prompt templates.${packageDirectoryError}`, { cause: error });
3863
- });
4051
+ let loaded = nativeCoreModules.get(file);
4052
+ if (!loaded) {
4053
+ loaded = import(modulePath).catch((error) => {
4054
+ throw new Error(`Could not load Pi ${label2}.${packageDirectoryError}`, { cause: error });
4055
+ });
4056
+ nativeCoreModules.set(file, loaded);
4057
+ }
4058
+ return loaded;
3864
4059
  }
3865
4060
  async function expandPromptTemplateForInspection(text, templates) {
3866
- return (await loadNativePromptTemplateModule()).expandPromptTemplate(text, templates);
4061
+ return (await loadNativeCoreModule("prompt-templates.js", "prompt templates")).expandPromptTemplate(text, templates);
4062
+ }
4063
+ async function buildSystemPromptForInspection(options) {
4064
+ return (await loadNativeCoreModule("system-prompt.js", "system prompt builder")).buildSystemPrompt(options);
3867
4065
  }
3868
4066
  async function preparePiPrompt(native, text) {
3869
4067
  const session = native;
3870
4068
  const diagnostics = [];
3871
4069
  const runner = session._extensionRunner;
3872
- const baseSystemPrompt = typeof session._baseSystemPrompt === "string" ? session._baseSystemPrompt : session.systemPrompt ?? "";
4070
+ const baseSystemPrompt = session.systemPrompt ?? "";
3873
4071
  const baseOptions = session._baseSystemPromptOptions;
3874
4072
  const expandSkillCommand = session._expandSkillCommand;
3875
4073
  const templates = session.promptTemplates;
3876
4074
  if (!runner) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: extension runner is missing", source: "Pi session" });
3877
- if (typeof session._baseSystemPrompt !== "string") diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: base system prompt is missing", source: "Pi session" });
3878
4075
  if (baseOptions === void 0) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: base system prompt options are missing", source: "Pi session" });
3879
4076
  if (typeof expandSkillCommand !== "function") diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: skill expansion is missing", source: "Pi session" });
3880
4077
  if (!Array.isArray(templates)) diagnostics.push({ type: "error", message: "Pi prompt inspection seam is unavailable: prompt templates are missing", source: "Pi session" });
@@ -3899,68 +4096,86 @@ async function preparePiPrompt(native, text) {
3899
4096
  diagnostics.push({ type: "error", message: `Pi prompt template expansion is unavailable: ${errorText(error)}`, source: "Pi session" });
3900
4097
  }
3901
4098
  }
3902
- const result = !inputHandled && runner && baseOptions !== void 0 ? await runner.emitBeforeAgentStart(expandedPrompt, void 0, baseSystemPrompt, baseOptions) : void 0;
4099
+ const result = !inputHandled && runner && baseOptions !== void 0 ? await runner.emitBeforeAgentStart(expandedPrompt, void 0, baseOptions) : void 0;
3903
4100
  const prepared = result;
3904
- return { prompt: text, expandedPrompt, systemPrompt: typeof prepared?.systemPrompt === "string" ? prepared.systemPrompt : baseSystemPrompt, systemPromptOptions: baseOptions, messages: prepared?.messages ?? [], inputHandled, diagnostics };
4101
+ const systemPromptOptions = prepared?.systemPromptOptions ?? baseOptions;
4102
+ const systemPrompt = prepared?.systemPromptOptions === void 0 ? baseSystemPrompt : await buildSystemPromptForInspection(prepared.systemPromptOptions);
4103
+ return { prompt: text, expandedPrompt, systemPrompt, systemPromptOptions, messages: prepared?.messages ?? [], inputHandled, diagnostics };
3905
4104
  } finally {
3906
4105
  unsubscribe?.();
3907
4106
  }
3908
4107
  }
3909
4108
  function flushExtensionProviders(resourceLoader, modelRuntime) {
3910
- const { runtime: runtime2 } = resourceLoader.getExtensions();
4109
+ const { runtime } = resourceLoader.getExtensions();
3911
4110
  const failures = [];
3912
- for (const { name, config } of runtime2.pendingProviderRegistrations) {
4111
+ for (const { name, config } of runtime.pendingProviderRegistrations) {
3913
4112
  try {
3914
4113
  modelRuntime.registerProvider(name, config);
3915
4114
  } catch (error) {
3916
4115
  failures.push(`${name}: ${errorText(error)}`);
3917
4116
  }
3918
4117
  }
3919
- runtime2.pendingProviderRegistrations = [];
3920
- for (const { provider } of runtime2.pendingNativeProviderRegistrations) {
4118
+ runtime.pendingProviderRegistrations = [];
4119
+ for (const { provider } of runtime.pendingNativeProviderRegistrations) {
3921
4120
  try {
3922
4121
  modelRuntime.registerNativeProvider(provider);
3923
4122
  } catch (error) {
3924
4123
  failures.push(`${provider.id || "native provider"}: ${errorText(error)}`);
3925
4124
  }
3926
4125
  }
3927
- runtime2.pendingNativeProviderRegistrations = [];
4126
+ runtime.pendingNativeProviderRegistrations = [];
3928
4127
  return failures;
3929
4128
  }
4129
+ function builtinExtensions() {
4130
+ return [
4131
+ { name: "codemode", factory: createCodemodeExtension(), replaceable: true, builtin: true },
4132
+ { name: "tool-search", factory: createToolSearchExtension(), replaceable: true, builtin: true },
4133
+ { name: "mcp", factory: createMcpExtension(), replaceable: true, builtin: true }
4134
+ ];
4135
+ }
4136
+ var BUILTIN_EXTENSION_PREFIX = "builtin:";
4137
+ function isLlamaModel(model) {
4138
+ return model.provider === "llama.cpp";
4139
+ }
3930
4140
  async function createLocalPiSessionHandle(input, sessionStartEvent) {
3931
- const agentDir = input.agentDir ?? getAgentDir2();
4141
+ const agentDir = input.agentDir ?? getAgentDir3();
3932
4142
  const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
3933
4143
  const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
3934
4144
  const contextFilesOverride = input.contextFiles === void 0 ? void 0 : (base) => ({ ...base, agentsFiles: filterContextFiles(base.agentsFiles, input.contextFiles ?? [], input.cwd, agentDir) });
3935
- 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));
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));
3936
4146
  if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
3937
- const modelRuntime = await ModelRuntime.create({ authPath: join5(agentDir, "auth.json"), modelsPath: join5(agentDir, "models.json") });
4147
+ const modelRuntime = await ModelRuntime.create({ authPath: join6(agentDir, "auth.json"), modelsPath: join6(agentDir, "models.json") });
3938
4148
  const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
3939
4149
  const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
3940
4150
  let settingsManager;
3941
4151
  let resourceLoader;
4152
+ const builtins = builtinExtensions();
4153
+ const builtinNames = builtins.flatMap((extension2) => typeof extension2 === "function" ? [] : [extension2.name]);
4154
+ const extensionFactories = [...builtins, ...input.extensionFactories ?? []];
3942
4155
  const policy = input.resourcePolicy;
3943
4156
  if (policy) {
3944
4157
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: false });
3945
4158
  settingsManager.setProjectTrusted(policy.projectTrusted);
3946
- const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
4159
+ const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager, builtinExtensions: builtinNames });
3947
4160
  const resolved = await packageManager.resolve();
3948
- const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
4161
+ const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => extensionIdentity(path)))];
3949
4162
  const selectorSources = policy.selectorSources;
3950
- const extensionLayers = [selectorSources.global.extensions, selectorSources.project.extensions, selectorSources.role?.extensions, selectorSources.call?.extensions];
3951
- const extensionSelectors = extensionLayers.flatMap((layer) => (layer ?? []).map((original) => ({ original, matching: canonicalExtensionSelector(original, input.cwd) })));
3952
- const selectedExtensions = selectResourcesByLayers(extensionLayers.map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, input.cwd))), discoveredExtensions);
3953
- const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
3954
- const unmatchedExtensions = extensionSelectors.filter(({ matching }) => unmatchedResourcePatterns([matching], discoveredExtensions).length > 0).map(({ original }) => original);
4163
+ const selection = resolveRole(void 0, {
4164
+ cwd: input.cwd,
4165
+ selectorSources,
4166
+ resources: { extensions: discoveredExtensions }
4167
+ });
4168
+ const selectedExtensions = selection.selectedExtensions ?? [];
4169
+ const extensionPaths = selectedExtensions.filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path));
3955
4170
  policy.selectedExtensions = selectedExtensions;
3956
- policy.unmatchedExtensions = unmatchedExtensions;
4171
+ policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
3957
4172
  const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
3958
4173
  const updateSkillMatches = (skills) => {
3959
4174
  const names = [...new Set(skills.map(({ name }) => name))];
3960
- const skillLayers = [selectorSources.global.skills, selectorSources.project.skills, selectorSources.role?.skills, selectorSources.call?.skills];
3961
- const selectedSkills = selectResourcesByLayers(skillLayers, names);
4175
+ const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
4176
+ const selectedSkills = selected.selectedSkills ?? [];
3962
4177
  policy.selectedSkills = selectedSkills;
3963
- policy.unmatchedSkills = unmatchedResourcePatterns(skillLayers.flatMap((layer) => layer ?? []), names);
4178
+ policy.unmatchedSkills = selected.unmatchedSkills ?? [];
3964
4179
  return new Set(names.filter((name) => !selectedSkills.includes(name)));
3965
4180
  };
3966
4181
  resourceLoader = new DefaultResourceLoader({
@@ -3971,7 +4186,7 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
3971
4186
  additionalExtensionPaths: extensionPaths,
3972
4187
  noSkills: true,
3973
4188
  additionalSkillPaths: [.../* @__PURE__ */ new Set([...skillPaths, ...input.additionalSkillPaths ?? []])],
3974
- ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {},
4189
+ extensionFactories,
3975
4190
  ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {},
3976
4191
  skillsOverride: (base) => {
3977
4192
  const disabledSkills = updateSkillMatches(base.skills);
@@ -3983,17 +4198,17 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
3983
4198
  await resourceLoader.reload();
3984
4199
  } else {
3985
4200
  settingsManager = SettingsManager.create(input.cwd, agentDir, { projectTrusted: true });
3986
- const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
4201
+ const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager, builtinExtensions: builtinNames });
3987
4202
  const resolved = await packageManager.resolve();
3988
- const extensionPaths = [...new Set(resolved.extensions.filter(({ enabled }) => enabled).map(({ path }) => canonicalPath(path)).filter((path) => !WORKFLOW_HOST_ENTRIES.has(path)))];
3989
- resourceLoader = new DefaultResourceLoader({ cwd: input.cwd, agentDir, settingsManager, noExtensions: true, additionalExtensionPaths: extensionPaths, ...input.additionalSkillPaths?.length ? { additionalSkillPaths: [...input.additionalSkillPaths] } : {}, ...input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}, ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {}, ...systemPromptOptions, ...input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {} });
4203
+ const extensionPaths = [...new Set(resolved.extensions.filter(({ enabled }) => enabled).map(({ path }) => extensionIdentity(path)).filter((path) => path.startsWith(BUILTIN_EXTENSION_PREFIX) || !isWorkflowHostEntry(path)))];
4204
+ resourceLoader = new DefaultResourceLoader({ cwd: input.cwd, agentDir, settingsManager, noExtensions: true, additionalExtensionPaths: extensionPaths, ...input.additionalSkillPaths?.length ? { additionalSkillPaths: [...input.additionalSkillPaths] } : {}, extensionFactories, ...contextFilesOverride ? { agentsFilesOverride: contextFilesOverride } : {}, ...systemPromptOptions, ...input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {} });
3990
4205
  await resourceLoader.reload();
3991
4206
  }
3992
4207
  const providerFailures = flushExtensionProviders(resourceLoader, modelRuntime);
3993
4208
  await modelRuntime.refresh({ allowNetwork: false });
3994
4209
  const model = modelRuntime.getModel(input.model.provider, input.model.model);
3995
4210
  if (!model) {
3996
- const because = providerFailures.length > 0 ? ` (provider registration failed \u2014 ${providerFailures.join("; ")})` : "";
4211
+ const because = providerFailures.length > 0 ? ` (provider registration failed \u2014 ${providerFailures.join("; ")})` : isLlamaModel(input.model) ? " (Pi does not expose its built-in llama.cpp provider to extension sessions; declare the llama.cpp server as a provider in models.json and use that model instead)" : "";
3997
4212
  throw new WorkflowError("UNKNOWN_MODEL", `Unknown model: ${input.model.provider}/${input.model.model}${because}`);
3998
4213
  }
3999
4214
  const effectiveSessionStartEvent = input.settings === void 0 && sessionStartEvent === void 0 ? void 0 : { ...sessionStartEvent ?? { type: "session_start", reason: "startup" }, settings: input.settings ?? Object.freeze({}) };
@@ -4018,13 +4233,13 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
4018
4233
  await shutdown("quit").catch(() => void 0);
4019
4234
  throw error;
4020
4235
  }
4021
- const resourcePaths = { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => canonicalPath(resolvedPath)), skills: resourceLoader.getSkills().skills.map(({ filePath }) => canonicalPath(filePath)) };
4236
+ const resourcePaths = { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => extensionIdentity(resolvedPath)), skills: resourceLoader.getSkills().skills.map(({ filePath }) => canonicalPath(filePath)) };
4022
4237
  const resourceInspection = () => {
4023
4238
  const extensions = resourceLoader.getExtensions();
4024
4239
  const skills = resourceLoader.getSkills();
4025
4240
  const diagnostics = [...extensions.errors.map(({ path, error }) => ({ type: "error", message: error, source: path })), ...skills.diagnostics.map(({ type, path, message }) => ({ type, message, ...path ? { source: path } : {} }))];
4026
4241
  const systemSource = systemPromptSource;
4027
- return { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => canonicalPath(resolvedPath)), skills: skills.skills.map(({ name }) => name), diagnostics, ...systemSource ? { systemPromptSource: systemSource } : resourceLoader.getSystemPrompt() !== void 0 ? { systemPromptSource: "Pi resource loader" } : {} };
4242
+ return { extensions: resourceLoader.getExtensions().extensions.filter(({ path }) => !path.startsWith("<")).map(({ resolvedPath }) => extensionIdentity(resolvedPath)), skills: skills.skills.map(({ name }) => name), diagnostics, ...systemSource ? { systemPromptSource: systemSource } : resourceLoader.getSystemPrompt() !== void 0 ? { systemPromptSource: "Pi resource loader" } : {} };
4028
4243
  };
4029
4244
  const managedSession = Object.assign(session, {
4030
4245
  getLeafId: () => manager.getLeafId(),
@@ -4439,27 +4654,27 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
4439
4654
  };
4440
4655
  }
4441
4656
  function packageRoot(start) {
4442
- let current = dirname4(realpathSync2(start));
4657
+ let current = dirname5(realpathSync2(start));
4443
4658
  for (; ; ) {
4444
- const candidates = [current, join5(current, "..", "@earendil-works", "pi-coding-agent"), join5(current, "..", "pi-coding-agent")];
4659
+ const candidates = [current, join6(current, "..", "@earendil-works", "pi-coding-agent"), join6(current, "..", "pi-coding-agent")];
4445
4660
  for (const candidate of candidates) {
4446
- const packageJsonPath = join5(candidate, "package.json");
4661
+ const packageJsonPath = join6(candidate, "package.json");
4447
4662
  if (!existsSync(packageJsonPath)) continue;
4448
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
4663
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
4449
4664
  if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
4450
4665
  }
4451
- const parent = dirname4(current);
4666
+ const parent = dirname5(current);
4452
4667
  if (parent === current) return void 0;
4453
4668
  current = parent;
4454
4669
  }
4455
4670
  }
4456
4671
  function piPackageEntrypoint(root) {
4457
- const packageJson = JSON.parse(readFileSync3(join5(root, "package.json"), "utf8"));
4672
+ const packageJson = JSON.parse(readFileSync4(join6(root, "package.json"), "utf8"));
4458
4673
  if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4459
4674
  const bin = packageJson.bin;
4460
4675
  const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
4461
4676
  if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4462
- const entrypoint = resolve4(root, cli);
4677
+ const entrypoint = resolve5(root, cli);
4463
4678
  if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
4464
4679
  return entrypoint;
4465
4680
  }
@@ -4500,7 +4715,7 @@ ${instruction}` : ""}`;
4500
4715
  }
4501
4716
  async function preparedAgentSession(input, initialPrompt) {
4502
4717
  const { runtime: piRuntime, error: piRuntimeError } = await piRuntimeResolution;
4503
- const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir2(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
4718
+ const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir3(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
4504
4719
  const prepared = {
4505
4720
  cwd: input.cwd,
4506
4721
  model: Object.freeze({ ...input.model }),
@@ -4530,7 +4745,7 @@ function agentSetupSummary(setup, hookNames) {
4530
4745
  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) } : {} };
4531
4746
  }
4532
4747
  async function attemptSessionInput(source, attempt) {
4533
- const target = join5(dirname4(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
4748
+ const target = join6(dirname5(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
4534
4749
  await copyFile(source, target);
4535
4750
  return target;
4536
4751
  }
@@ -4632,7 +4847,6 @@ var WorkflowAgentExecutor = class {
4632
4847
  const roleName2 = typeof options.role === "string" ? options.role : void 0;
4633
4848
  const roleDefinition = roleName2 ? this.root.agentDefinitions?.[roleName2] : void 0;
4634
4849
  if (roleName2 && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName2}`);
4635
- const candidateTools = inheritedTools !== void 0 ? [...inheritedTools] : [...this.root.tools];
4636
4850
  if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
4637
4851
  const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
4638
4852
  if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
@@ -4643,33 +4857,30 @@ var WorkflowAgentExecutor = class {
4643
4857
  }
4644
4858
  }
4645
4859
  }
4646
- const selectorLayers = [
4647
- this.root.resourceSelectors?.tools,
4648
- roleDefinition?.tools,
4649
- options.tools
4650
- ];
4651
- for (const pattern of options.tools ?? []) {
4652
- const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
4653
- if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !candidateTools.includes(body)) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${body}`);
4654
- }
4655
- const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools.includes(tool));
4656
- if (outsideEffectiveTool) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
4657
- const requested = options.effectiveTools ?? selectResourcesByLayers(selectorLayers, candidateTools);
4658
- const forbidden = requested.find((tool) => !this.root.tools.has(tool));
4659
- if (forbidden) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${forbidden}`);
4660
- const requestedModel = options.model ?? (roleDefinition?.model && roleDefinition.thinking && !roleDefinition.model.includes(":") ? `${roleDefinition.model}:${roleDefinition.thinking}` : roleDefinition?.model);
4661
- const alias = requestedModel === void 0 ? void 0 : modelAliasName(requestedModel, this.root.modelAliases ?? {});
4662
- const blockedAlias = requestedModel?.split(":", 1)[0];
4663
- if (requestedModel !== void 0 && blockedAlias && this.root.blockedAliases?.has(blockedAlias) && !alias) {
4664
- const target = this.root.blockedAliasTargets?.[blockedAlias];
4665
- throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
4666
- }
4667
- const model = options.modelOverride ?? parseModel(requestedModel, this.root.model, this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
4668
- const availableModels = this.root.knownModels ?? this.root.availableModels ?? /* @__PURE__ */ new Set([modelCapability(this.root.model)]);
4669
- if (!availableModels.has(modelCapability(model))) throw new WorkflowError("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
4670
- const overrideSystemPrompt = roleDefinition?.overrideSystemPrompt === true;
4671
- const contextFiles = options.contextFiles ?? roleDefinition?.contextFiles;
4672
- return { model, ...alias && requestedModel ? { requestedModel } : {}, tools: requested, ...overrideSystemPrompt ? { systemPrompt: roleDefinition.prompt ?? "" } : {}, systemPromptAppend: overrideSystemPrompt ? "" : roleDefinition?.prompt ?? "", ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] } };
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 },
4875
+ ...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 } };
4673
4884
  }
4674
4885
  async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
4675
4886
  const executionSignal = signal ?? this.root.runContext?.signal;
@@ -4926,7 +5137,7 @@ import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateT
4926
5137
  import { spawn } from "node:child_process";
4927
5138
  import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
4928
5139
  import { tmpdir } from "node:os";
4929
- import { join as join6 } from "node:path";
5140
+ import { join as join7 } from "node:path";
4930
5141
  function workflowPromptArtifact(prompt) {
4931
5142
  return { extension: ".md", content: prompt };
4932
5143
  }
@@ -4937,23 +5148,23 @@ function workflowResultArtifact(value) {
4937
5148
  async function spawnWorkflowEditor(command, path) {
4938
5149
  const [editor, ...editorArgs] = command.split(" ");
4939
5150
  if (!editor) return null;
4940
- return new Promise((resolve5) => {
5151
+ return new Promise((resolve6) => {
4941
5152
  try {
4942
5153
  const child = spawn(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
4943
5154
  child.once("error", () => {
4944
- resolve5(null);
5155
+ resolve6(null);
4945
5156
  });
4946
5157
  child.once("close", (code) => {
4947
- resolve5(code);
5158
+ resolve6(code);
4948
5159
  });
4949
5160
  } catch {
4950
- resolve5(null);
5161
+ resolve6(null);
4951
5162
  }
4952
5163
  });
4953
5164
  }
4954
5165
  async function openWorkflowArtifact(tui, command, artifact) {
4955
- const directory = await mkdtemp(join6(tmpdir(), "pi-workflow-editor-"));
4956
- const path = join6(directory, `artifact${artifact.extension}`);
5166
+ const directory = await mkdtemp(join7(tmpdir(), "pi-workflow-editor-"));
5167
+ const path = join7(directory, `artifact${artifact.extension}`);
4957
5168
  try {
4958
5169
  await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
4959
5170
  tui.stop();
@@ -4970,23 +5181,88 @@ async function openWorkflowArtifact(tui, command, artifact) {
4970
5181
 
4971
5182
  // src/host-view.ts
4972
5183
  import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
4973
- import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
5184
+ import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
4974
5185
 
4975
5186
  // src/background-widget.ts
4976
5187
  import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
5188
+ var REPAINT_MS = 125;
4977
5189
  var QUIET_MS = 3 * 60 * 1e3;
5190
+ var ROLE = {
5191
+ live: "accent",
5192
+ done: "success",
5193
+ failed: "error",
5194
+ warn: "warning",
5195
+ quiet: "muted"
5196
+ };
5197
+ var RESET = "\x1B[0m";
5198
+ var SPINNER = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FD", "\u28FB"];
5199
+ function spinner(now) {
5200
+ return SPINNER[Math.floor(now / REPAINT_MS) % SPINNER.length] ?? SPINNER[0];
5201
+ }
5202
+ function mark(state, now, paint, runState) {
5203
+ if (state === "running" && (runState === void 0 || runState === "running")) return paint(ROLE.live, spinner(now));
5204
+ if (state === "completed") return paint(ROLE.done, "\u2713");
5205
+ if (state === "failed" || state === "budget_exhausted") return paint(ROLE.failed, "\u2717");
5206
+ return paint(ROLE.quiet, "\xB7");
5207
+ }
5208
+ function formatElapsed(ms) {
5209
+ const total = Math.max(0, Math.floor(ms / 1e3));
5210
+ const minutes = Math.floor(total / 60);
5211
+ const seconds = total % 60;
5212
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
5213
+ }
5214
+ function formatTokens(tokens) {
5215
+ if (!tokens) return "";
5216
+ if (tokens < 1e3) return `${String(tokens)}t`;
5217
+ const thousands = tokens / 1e3;
5218
+ return `${thousands < 10 ? thousands.toFixed(1) : String(Math.round(thousands))}kt`;
5219
+ }
4978
5220
  function formatCost(cost) {
4979
5221
  if (!cost) return "";
4980
5222
  return cost < 0.01 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`;
4981
5223
  }
5224
+ function visibleLength(text) {
5225
+ return visibleWidth(text);
5226
+ }
5227
+ function truncate(text, limit) {
5228
+ return `${truncateToWidth(text, limit, "\u2026")}${RESET}`;
5229
+ }
5230
+ function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => void 0) {
5231
+ const inner = Math.max(20, width - 4);
5232
+ const lines = body.map((row, index) => {
5233
+ const right = row.right ?? "";
5234
+ const rightWidth = right === "" ? 0 : visibleLength(right) + 2;
5235
+ const room = Math.max(0, inner - rightWidth);
5236
+ const left = visibleLength(row.text) > room ? truncate(row.text, room) : row.text;
5237
+ const gap = Math.max(0, inner - visibleLength(left) - (right === "" ? 0 : visibleLength(right)));
5238
+ const text = right === "" ? left + " ".repeat(gap) : `${left}${" ".repeat(gap)}${paint(ROLE.quiet, right)}`;
5239
+ const rule = accent("\u2502");
5240
+ const bar = scrollbar(index);
5241
+ return `${rule} ${text} ${bar === void 0 ? rule : accent(bar)}`;
5242
+ });
5243
+ const label2 = ` ${title} `;
5244
+ const tail = hint === "" ? "" : ` ${hint} `;
5245
+ const spare = inner + 2 - visibleLength(label2) - visibleLength(tail);
5246
+ const heading = spare >= 2 ? accent(`\u256D${label2}${"\u2500".repeat(spare)}${tail}\u256E`) : accent(`\u256D${label2}${"\u2500".repeat(Math.max(0, inner + 2 - visibleLength(label2)))}\u256E`);
5247
+ const foot = accent(`\u2570${"\u2500".repeat(inner + 2)}\u256F`);
5248
+ return [heading, ...lines, foot];
5249
+ }
4982
5250
 
4983
5251
  // src/host-view.ts
4984
5252
  var PLAIN_WORKFLOW_PROGRESS_STYLES = { accent: (text) => text, success: (text) => text, error: (text) => text, warning: (text) => text, muted: (text) => text, dim: (text) => text, bold: (text) => text };
5253
+ var RUN_STATE_GLYPH = { "not started": "\u25CB", queued: "\u25CB", pausing: "\u23F8", paused: "\u23F8", completed: "\u2713", failed: "\u2717", stopped: "\u2717", interrupted: "\u21AF", budget_exhausted: "!", awaiting_input: "?" };
5254
+ function runStateGlyph(state, running) {
5255
+ return state === "running" ? running : RUN_STATE_GLYPH[state] ?? "\u25C6";
5256
+ }
5257
+ var PROGRESS_STATE_STYLE = { completed: "success", failed: "error", cancelled: "error", running: "accent", paused: "warning", pausing: "warning", interrupted: "warning", retrying: "accent", budget_exhausted: "warning", awaiting_input: "warning" };
4985
5258
  var PHASE_STATE_STYLE = { completed: "success", failed: "error", cancelled: "error", stopped: "error", running: "accent", paused: "warning", pausing: "warning", interrupted: "warning", budget_exhausted: "warning" };
4986
5259
  function styleForState(map, state, styles) {
4987
5260
  const key = map[state] ?? "muted";
4988
5261
  return (text) => styles[key](text);
4989
5262
  }
5263
+ function progressStyleForState(state, styles) {
5264
+ return styleForState(PROGRESS_STATE_STYLE, state, styles);
5265
+ }
4990
5266
  function phaseStyleForState(state, styles) {
4991
5267
  return styleForState(PHASE_STATE_STYLE, state, styles);
4992
5268
  }
@@ -5000,6 +5276,15 @@ function formatWorkflowRuntime(durationMs) {
5000
5276
  const remainingMinutes = minutes % 60;
5001
5277
  return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
5002
5278
  }
5279
+ function textBlock(text) {
5280
+ return {
5281
+ render(width) {
5282
+ return text.split("\n").map((line) => truncateToWidth2(line, Math.max(1, width), "\u2026"));
5283
+ },
5284
+ invalidate() {
5285
+ }
5286
+ };
5287
+ }
5003
5288
  var ANSI_SGR_SOURCE = `${String.fromCharCode(27)}\\[[0-9;]*m`;
5004
5289
  var ANSI_SGR = new RegExp(ANSI_SGR_SOURCE);
5005
5290
  function themeWorkflowProgressStyles(theme) {
@@ -5043,13 +5328,13 @@ function elapsedDurationMs(agent, end) {
5043
5328
  return agent.startedAt === void 0 || !Number.isFinite(agent.startedAt) ? void 0 : Math.max(0, end - agent.startedAt);
5044
5329
  }
5045
5330
  function agentActivityLabel(agent) {
5046
- const activity2 = agent.activity;
5047
- if (activity2?.kind === "reasoning" || activity2?.kind === "text") {
5048
- const name = activity2.kind === "reasoning" ? "reasoning" : "responding";
5049
- const text = sanitizeDisplayText(activity2.text);
5331
+ const activity = agent.activity;
5332
+ if (activity?.kind === "reasoning" || activity?.kind === "text") {
5333
+ const name = activity.kind === "reasoning" ? "reasoning" : "responding";
5334
+ const text = sanitizeDisplayText(activity.text);
5050
5335
  return text && text !== name ? `${name} \xB7 ${text}` : name;
5051
5336
  }
5052
- if (activity2?.kind === "tool") return sanitizeDisplayText(activity2.text);
5337
+ if (activity?.kind === "tool") return sanitizeDisplayText(activity.text);
5053
5338
  return sanitizeDisplayText([...agent.toolCalls ?? []].reverse().find(({ state }) => state === "running")?.name ?? "");
5054
5339
  }
5055
5340
  function formatAccountingValue(value) {
@@ -5101,16 +5386,96 @@ function visibleStandaloneAgentAttemptActions(actions, context) {
5101
5386
  }
5102
5387
  });
5103
5388
  }
5104
- function formatAgentAccounting(accounting3) {
5105
- const total = accounting3.input + accounting3.output + accounting3.cacheRead + accounting3.cacheWrite;
5106
- return [`Tokens: \u2211${formatAccountingValue(total)} \u2191${formatAccountingValue(accounting3.input)} \u2193${formatAccountingValue(accounting3.output)} \u21E2${formatAccountingValue(accounting3.cacheRead)} \u21E0${formatAccountingValue(accounting3.cacheWrite)}`, `Cost: ${formatCost(accounting3.cost) || "$0.00"}`];
5389
+ function formatAgentAccounting(accounting2) {
5390
+ const total = accounting2.input + accounting2.output + accounting2.cacheRead + accounting2.cacheWrite;
5391
+ return [`Tokens: \u2211${formatAccountingValue(total)} \u2191${formatAccountingValue(accounting2.input)} \u2193${formatAccountingValue(accounting2.output)} \u21E2${formatAccountingValue(accounting2.cacheRead)} \u21E0${formatAccountingValue(accounting2.cacheWrite)}`, `Cost: ${formatCost(accounting2.cost) || "$0.00"}`];
5392
+ }
5393
+ function wrapNavigatorLine(text, width) {
5394
+ return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
5395
+ }
5396
+ function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
5397
+ const safeWidth = Math.max(1, width);
5398
+ if (safeWidth >= 80) {
5399
+ const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
5400
+ const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
5401
+ const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
5402
+ return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
5403
+ const cell = left2[index] ?? "";
5404
+ return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
5405
+ });
5406
+ }
5407
+ if (layout.detailsOnly) return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
5408
+ const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
5409
+ return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
5107
5410
  }
5108
5411
 
5109
5412
  // src/retention.ts
5110
5413
  var DAY_MS = 24 * 60 * 60 * 1e3;
5111
5414
 
5415
+ // src/host-navigator.ts
5416
+ function isKeybindingGetKeys(value) {
5417
+ return typeof value === "function";
5418
+ }
5419
+ function keybindingKeys(keybindings, name) {
5420
+ if (!object(keybindings)) return void 0;
5421
+ const getKeys = keybindings.getKeys;
5422
+ return isKeybindingGetKeys(getKeys) ? getKeys.call(keybindings, name) : void 0;
5423
+ }
5424
+ var WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.editor.cursorLeft": "h", "tui.editor.cursorRight": "l" };
5425
+ function workflowKeyMatches(keybindings, data, binding) {
5426
+ return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data;
5427
+ }
5428
+ var WORKFLOW_KEY_LABELS = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
5429
+ function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
5430
+ const keys = keybindingKeys(keybindings, binding);
5431
+ const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
5432
+ const vim = WORKFLOW_VIM_KEYS[binding];
5433
+ return [...new Set(vim ? [...configured, vim] : configured)].join("/");
5434
+ }
5435
+ function createInlineConfirm(requestRender) {
5436
+ let pending;
5437
+ const answer = (value) => {
5438
+ const current = pending;
5439
+ pending = void 0;
5440
+ current?.resolve(value);
5441
+ requestRender();
5442
+ };
5443
+ return {
5444
+ ask(title, message) {
5445
+ answer(false);
5446
+ return new Promise((resolve6) => {
5447
+ pending = { title, message, yes: true, resolve: resolve6 };
5448
+ requestRender();
5449
+ });
5450
+ },
5451
+ active() {
5452
+ return pending !== void 0;
5453
+ },
5454
+ rows(width, styles) {
5455
+ if (!pending) return [];
5456
+ const yes = pending.yes;
5457
+ const message = truncateToVisualLines2(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
5458
+ return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => option === "Yes" === yes ? `\u2192 ${styles.accent(option)}` : ` ${option}`)];
5459
+ },
5460
+ hint(keybindings) {
5461
+ return `${workflowKeyLabel(keybindings, "tui.select.up", "\u2191")}/${workflowKeyLabel(keybindings, "tui.select.down", "\u2193")} select \xB7 ${workflowKeyLabel(keybindings, "tui.select.confirm", "enter")} confirm \xB7 ${workflowKeyLabel(keybindings, "tui.select.cancel", "esc")} cancel`;
5462
+ },
5463
+ handleInput(keybindings, data) {
5464
+ if (!pending) return;
5465
+ if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
5466
+ pending.yes = !pending.yes;
5467
+ requestRender();
5468
+ } else if (workflowKeyMatches(keybindings, data, "tui.select.confirm")) answer(pending.yes);
5469
+ else if (workflowKeyMatches(keybindings, data, "tui.select.cancel")) answer(false);
5470
+ },
5471
+ cancel() {
5472
+ answer(false);
5473
+ }
5474
+ };
5475
+ }
5476
+
5112
5477
  // src/trajectory.ts
5113
- 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";
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";
5114
5479
 
5115
5480
  // subagents/src/contracts.ts
5116
5481
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -5145,7 +5510,8 @@ var SUBAGENTS_RUN_PARAMETERS = Type2.Object({
5145
5510
  timeoutMs: Type2.Optional(Type2.Union([Type2.Integer({ minimum: 1, description: "Optional execution timeout in milliseconds" }), Type2.Null()]))
5146
5511
  }, { additionalProperties: false });
5147
5512
  var SUBAGENTS_INSPECT_PARAMETERS = Type2.Object({
5148
- id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" }))
5513
+ id: Type2.Optional(Type2.String({ description: "Subagent ID; omit to list ordered run summaries" })),
5514
+ 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" }))
5149
5515
  }, { additionalProperties: false });
5150
5516
  var SUBAGENTS_ID_PARAMETERS = Type2.Object({
5151
5517
  id: Type2.String({ description: "Subagent ID" })
@@ -5160,13 +5526,21 @@ var SUBAGENTS_STOP_PARAMETERS = Type2.Object({
5160
5526
  var SUBAGENTS_RETRY_PARAMETERS = Type2.Object({
5161
5527
  id: Type2.String({ description: "Failed or stopped subagent ID to retry" })
5162
5528
  }, { additionalProperties: false });
5163
- var SUBAGENTS_TOOL_SCHEMAS = {
5164
- subagents_run: SUBAGENTS_RUN_PARAMETERS,
5165
- subagents_inspect: SUBAGENTS_INSPECT_PARAMETERS,
5166
- subagents_steer: SUBAGENTS_STEER_PARAMETERS,
5167
- subagents_stop: SUBAGENTS_STOP_PARAMETERS,
5168
- subagents_retry: SUBAGENTS_RETRY_PARAMETERS
5169
- };
5529
+ var SUBAGENT_STATUS_OUTPUT = Type2.Object({
5530
+ id: Type2.String(),
5531
+ state: Type2.String(),
5532
+ sessionId: Type2.Optional(Type2.String()),
5533
+ startedAt: Type2.Optional(Type2.Number()),
5534
+ finishedAt: Type2.Optional(Type2.Number()),
5535
+ attempts: Type2.Optional(Type2.Number()),
5536
+ attemptDetails: Type2.Optional(Type2.Array(Type2.Unknown())),
5537
+ worktree: Type2.Optional(Type2.Object({ path: Type2.String(), branch: Type2.String() })),
5538
+ error: Type2.Optional(Type2.Object({ code: Type2.String(), message: Type2.String() })),
5539
+ progress: Type2.Optional(Type2.Unknown()),
5540
+ value: Type2.Optional(Type2.Unknown())
5541
+ });
5542
+ var SUBAGENTS_INSPECT_OUTPUT = Type2.Union([SUBAGENT_STATUS_OUTPUT, Type2.Array(Type2.Object({ id: Type2.String(), state: Type2.String() }))]);
5543
+ var SUBAGENTS_STEER_OUTPUT = Type2.Object({ id: Type2.String(), accepted: Type2.Boolean() });
5170
5544
  function normalizeSubagentRunRequest(value) {
5171
5545
  if (!Value2.Check(SUBAGENTS_RUN_PARAMETERS, value)) throw new WorkflowError("INVALID_METADATA", "Invalid subagents_run parameters");
5172
5546
  if (typeof value.worktree === "string" && !value.worktree.trim()) throw new WorkflowError("INVALID_METADATA", "worktree name must be a non-empty string");
@@ -5214,14 +5588,14 @@ function accountingValue(value) {
5214
5588
  return { input, output, cacheRead, cacheWrite, cost };
5215
5589
  }
5216
5590
  function legacyAccountingValue(record2) {
5217
- const accounting3 = accountingValue(record2.accounting);
5218
- if (record2.accounting !== void 0 && !accounting3) return void 0;
5591
+ const accounting2 = accountingValue(record2.accounting);
5592
+ if (record2.accounting !== void 0 && !accounting2) return void 0;
5219
5593
  const usage = record2.usage;
5220
- if (usage === void 0) return accounting3;
5594
+ if (usage === void 0) return accounting2;
5221
5595
  const usageRecord = objectValue(usage);
5222
5596
  const tokens = objectValue(usageRecord?.tokens);
5223
5597
  if (!usageRecord || !tokens || !finiteNumber(tokens.input) || !finiteNumber(tokens.output) || !finiteNumber(tokens.cacheRead) || !finiteNumber(tokens.cacheWrite) || !finiteNumber(tokens.total) || !finiteNumber(usageRecord.cost)) return void 0;
5224
- return accounting3 ?? { input: tokens.input, output: tokens.output, cacheRead: tokens.cacheRead, cacheWrite: tokens.cacheWrite, cost: usageRecord.cost };
5598
+ return accounting2 ?? { input: tokens.input, output: tokens.output, cacheRead: tokens.cacheRead, cacheWrite: tokens.cacheWrite, cost: usageRecord.cost };
5225
5599
  }
5226
5600
  function activityValue(value) {
5227
5601
  const record2 = objectValue(value);
@@ -5255,11 +5629,11 @@ function worktreeValue(value) {
5255
5629
  function progressValue(value) {
5256
5630
  const record2 = objectValue(value);
5257
5631
  if (!record2) return void 0;
5258
- const accounting3 = accountingValue(record2.accounting);
5632
+ const accounting2 = accountingValue(record2.accounting);
5259
5633
  const toolCalls = toolCallsValue(record2.toolCalls);
5260
- if (!accounting3 || !toolCalls) return void 0;
5261
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5262
- if (record2.activity !== void 0 && activity2 === void 0) return void 0;
5634
+ if (!accounting2 || !toolCalls) return void 0;
5635
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5636
+ if (record2.activity !== void 0 && activity === void 0) return void 0;
5263
5637
  const rawState = record2.state;
5264
5638
  let state;
5265
5639
  if (rawState !== void 0) {
@@ -5273,16 +5647,16 @@ function progressValue(value) {
5273
5647
  }
5274
5648
  const lastEventAt = record2.lastEventAt;
5275
5649
  if (lastEventAt !== void 0 && !nonnegativeInteger(lastEventAt)) return void 0;
5276
- return { accounting: accounting3, toolCalls, ...state === void 0 ? {} : { state }, ...activity2 === void 0 ? {} : { activity: activity2 }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5650
+ return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity === void 0 ? {} : { activity }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5277
5651
  }
5278
5652
  function legacyProgressValue(record2) {
5279
- const accounting3 = legacyAccountingValue(record2);
5653
+ const accounting2 = legacyAccountingValue(record2);
5280
5654
  const toolCalls = record2.toolCalls === void 0 ? [] : toolCallsValue(record2.toolCalls);
5281
- if (!accounting3 || !toolCalls) return void 0;
5282
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5655
+ if (!accounting2 || !toolCalls) return void 0;
5656
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5283
5657
  const lastEventAt = record2.lastEventAt;
5284
- if (record2.activity !== void 0 && activity2 === void 0 || lastEventAt !== void 0 && !nonnegativeInteger(lastEventAt)) return void 0;
5285
- return { accounting: accounting3, toolCalls, ...activity2 === void 0 ? {} : { activity: activity2 }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5658
+ if (record2.activity !== void 0 && activity === void 0 || lastEventAt !== void 0 && !nonnegativeInteger(lastEventAt)) return void 0;
5659
+ return { accounting: accounting2, toolCalls, ...activity === void 0 ? {} : { activity }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5286
5660
  }
5287
5661
  function attemptValue(value) {
5288
5662
  const record2 = objectValue(value);
@@ -5291,16 +5665,16 @@ function attemptValue(value) {
5291
5665
  const hookNames = stringArrayValue(setup?.hookNames);
5292
5666
  const tools = stringArrayValue(setup?.tools);
5293
5667
  const resources = setup?.resourceSelectors === void 0 ? void 0 : resourceSummaryValue(setup.resourceSelectors);
5294
- const accounting3 = accountingValue(record2?.accounting);
5668
+ const accounting2 = accountingValue(record2?.accounting);
5295
5669
  const session = record2?.session === void 0 ? void 0 : sessionReferenceValue(record2.session);
5296
5670
  const error = record2?.error === void 0 ? void 0 : subagentErrorValue(record2.error);
5297
5671
  const thinking = parseThinking(model?.thinking);
5298
- if (!record2 || !nonnegativeInteger(record2.attempt) || record2.attempt < 1 || typeof record2.transport !== "string" || !record2.transport.trim() || !setup || typeof setup.cwd !== "string" || !setup.cwd.trim() || !model || typeof model.provider !== "string" || !model.provider.trim() || typeof model.model !== "string" || !model.model.trim() || model.thinking !== void 0 && thinking === void 0 || !hookNames || !tools || setup.resourceSelectors !== void 0 && resources === void 0 || !accounting3 || record2.session !== void 0 && session === void 0 || record2.error !== void 0 && error === void 0) return void 0;
5672
+ if (!record2 || !nonnegativeInteger(record2.attempt) || record2.attempt < 1 || typeof record2.transport !== "string" || !record2.transport.trim() || !setup || typeof setup.cwd !== "string" || !setup.cwd.trim() || !model || typeof model.provider !== "string" || !model.provider.trim() || typeof model.model !== "string" || !model.model.trim() || model.thinking !== void 0 && thinking === void 0 || !hookNames || !tools || setup.resourceSelectors !== void 0 && resources === void 0 || !accounting2 || record2.session !== void 0 && session === void 0 || record2.error !== void 0 && error === void 0) return void 0;
5299
5673
  return {
5300
5674
  attempt: record2.attempt,
5301
5675
  transport: record2.transport,
5302
5676
  setup: { hookNames, model: { provider: model.provider, model: model.model, ...thinking === void 0 ? {} : { thinking } }, tools, cwd: setup.cwd, ...resources === void 0 ? {} : { resourceSelectors: resources } },
5303
- accounting: accounting3,
5677
+ accounting: accounting2,
5304
5678
  ...session === void 0 ? {} : { session },
5305
5679
  ...error === void 0 ? {} : { error }
5306
5680
  };
@@ -5366,15 +5740,29 @@ var WORKFLOW_TOOL_PARAMETERS = Type3.Object({
5366
5740
  }, { additionalProperties: false });
5367
5741
  var WORKFLOW_STATUS_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Workflow run ID visible in the current project" }) }, { additionalProperties: false });
5368
5742
  var WORKFLOW_RETRY_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Explicit failed workflow run ID" }), expectedState: Type3.Optional(Type3.String({ description: "Persisted source state observed before recovery" })), foreground: Type3.Optional(Type3.Boolean({ description: "Override the source launch mode for this recovery" })) });
5743
+ var WORKFLOW_ERROR_OUTPUT = Type3.Object({ code: Type3.String(), message: Type3.String() });
5744
+ var WORKFLOW_LAUNCH_OUTPUT = Type3.Object({ runId: Type3.String(), state: Type3.String(), detached: Type3.Optional(Type3.Boolean()), value: Type3.Optional(Type3.Unknown()) });
5745
+ var WORKFLOW_STATUS_OUTPUT = Type3.Object({ runId: Type3.String(), workflowName: Type3.String(), state: Type3.String(), error: Type3.Optional(WORKFLOW_ERROR_OUTPUT), failedAt: Type3.Optional(Type3.String()), budget: Type3.Optional(Type3.Unknown()), usage: Type3.Optional(Type3.Unknown()), phase: Type3.Optional(Type3.String()), delivery: Type3.Optional(Type3.Object({ mode: Type3.String(), state: Type3.String() })), agents: Type3.Array(Type3.Object({ id: Type3.String(), label: Type3.Optional(Type3.String()), path: Type3.String(), state: Type3.String(), lastEventAt: Type3.Optional(Type3.Number()), accounting: Type3.Optional(Type3.Unknown()) })) });
5746
+ var WORKFLOW_STOP_OUTPUT = Type3.Object({ runId: Type3.String(), state: Type3.String(), stopped: Type3.Boolean(), reason: Type3.Optional(Type3.String()) });
5747
+ var WORKFLOW_RECOVERY_OUTPUT = Type3.Object({ state: Type3.String(), runId: Type3.Optional(Type3.String()), parentRunId: Type3.Optional(Type3.String()), proposalId: Type3.Optional(Type3.String()), value: Type3.Optional(Type3.Unknown()) });
5748
+ var WORKFLOW_RESPOND_OUTPUT = Type3.Object({ state: Type3.String(), approved: Type3.Boolean(), reason: Type3.String(), accepted: Type3.Optional(Type3.Boolean()), runId: Type3.Optional(Type3.String()), value: Type3.Optional(Type3.Unknown()) });
5749
+ var WORKFLOW_CATALOG_ALIAS_OUTPUT = Type3.Object({ name: Type3.String(), kind: Type3.String(), provenance: Type3.String(), version: Type3.Optional(Type3.String()), headline: Type3.Optional(Type3.String()) });
5750
+ var WORKFLOW_CATALOG_OUTPUT = Type3.Union([
5751
+ Type3.Object({ functions: Type3.Array(Type3.Object({ name: Type3.String(), description: Type3.String(), input: Type3.Unknown() })), modelAliasEntries: Type3.Optional(Type3.Array(WORKFLOW_CATALOG_ALIAS_OUTPUT)), settings: Type3.Optional(Type3.Unknown()) }),
5752
+ Type3.Object({ name: Type3.String(), version: Type3.String(), headline: Type3.String(), description: Type3.String(), input: Type3.Unknown(), output: Type3.Unknown() }),
5753
+ WORKFLOW_CATALOG_ALIAS_OUTPUT,
5754
+ Type3.Object({ error: Type3.Object({ code: Type3.String(), name: Type3.String(), message: Type3.String() }) })
5755
+ ]);
5756
+ var SCRIPT_CALL_TEXT_LIMIT_BYTES = 16 * 1024;
5369
5757
 
5370
5758
  // subagents/src/manager.ts
5371
5759
  import { randomUUID as randomUUID3 } from "node:crypto";
5372
5760
  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";
5373
- import { join as join8 } from "node:path";
5374
- import { getAgentDir as getAgentDir5 } from "@earendil-works/pi-coding-agent";
5761
+ import { join as join9 } from "node:path";
5762
+ import { getAgentDir as getAgentDir6 } from "@earendil-works/pi-coding-agent";
5375
5763
 
5376
5764
  // subagents/src/worktree.ts
5377
- import { join as join7 } from "node:path";
5765
+ import { join as join8 } from "node:path";
5378
5766
  function syntheticRun(context) {
5379
5767
  return {
5380
5768
  id: context.runId,
@@ -5436,7 +5824,7 @@ function createRunStoreWorktreeAdapter(home) {
5436
5824
  };
5437
5825
  }
5438
5826
  function defaultWorktreeHome(storageDirectory3) {
5439
- return join7(storageDirectory3, ".worktrees");
5827
+ return join8(storageDirectory3, ".worktrees");
5440
5828
  }
5441
5829
 
5442
5830
  // subagents/src/manager.ts
@@ -5489,7 +5877,7 @@ function validateStandaloneExtensionSources(context, external, registry, agentDi
5489
5877
  function executionRoot(context, dependencies, signal, runId, worktree, external) {
5490
5878
  const extensionContext = context.extensionContext;
5491
5879
  const model = rootModel(context);
5492
- const agentDir = dependencies.agentDir ?? getAgentDir5();
5880
+ const agentDir = dependencies.agentDir ?? getAgentDir6();
5493
5881
  const currentTrustedProject = extensionContext.isProjectTrusted();
5494
5882
  if (external.projectTrusted && !currentTrustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
5495
5883
  const trustedProject = currentTrustedProject;
@@ -5569,25 +5957,25 @@ function executionOptions(request, onAttempt, onProgress) {
5569
5957
  };
5570
5958
  }
5571
5959
  function storageDirectory(dependencies) {
5572
- return dependencies.storageDir ?? join8(dependencies.agentDir ?? getAgentDir5(), STORAGE_DIRECTORY);
5960
+ return dependencies.storageDir ?? join9(dependencies.agentDir ?? getAgentDir6(), STORAGE_DIRECTORY);
5573
5961
  }
5574
5962
  function runDirectory(root, id) {
5575
- return join8(root, id);
5963
+ return join9(root, id);
5576
5964
  }
5577
5965
  function requestPath(directory) {
5578
- return join8(directory, "request.json");
5966
+ return join9(directory, "request.json");
5579
5967
  }
5580
5968
  function configurationPath(directory) {
5581
- return join8(directory, CONFIGURATION_FILE);
5969
+ return join9(directory, CONFIGURATION_FILE);
5582
5970
  }
5583
5971
  function statusPath(directory) {
5584
- return join8(directory, "status.json");
5972
+ return join9(directory, "status.json");
5585
5973
  }
5586
5974
  function resultPath(directory) {
5587
- return join8(directory, "result.json");
5975
+ return join9(directory, "result.json");
5588
5976
  }
5589
5977
  function failurePath(directory) {
5590
- return join8(directory, "failure.json");
5978
+ return join9(directory, "failure.json");
5591
5979
  }
5592
5980
  async function secureDirectory(directory) {
5593
5981
  await mkdir3(directory, { recursive: true, mode: 448 });
@@ -5670,7 +6058,7 @@ async function releaseOwnerMarker(path, token) {
5670
6058
  }
5671
6059
  async function acquireStorageOwner(root, owner, liveness) {
5672
6060
  await secureDirectory(root);
5673
- const path = join8(root, OWNER_FILE);
6061
+ const path = join9(root, OWNER_FILE);
5674
6062
  for (let attempt = 0; attempt < MAX_STORAGE_OWNER_ATTEMPTS; attempt += 1) {
5675
6063
  const marker = { ...owner, acquiredAt: Date.now() };
5676
6064
  try {
@@ -5757,7 +6145,7 @@ function decodePersistedStandaloneExternalConfiguration(value) {
5757
6145
  function standaloneExternalConfiguration(context, dependencies) {
5758
6146
  const cwd = context.extensionContext.cwd;
5759
6147
  const trustedProject = context.extensionContext.isProjectTrusted();
5760
- const agentDir = dependencies.agentDir ?? getAgentDir5();
6148
+ const agentDir = dependencies.agentDir ?? getAgentDir6();
5761
6149
  const resolution = resolveWorkflowSettings(cwd, trustedProject, workflowSettingsPath(agentDir));
5762
6150
  const globalExtensionSettings = resolution.global.extensionSettings;
5763
6151
  const projectExtensionSettings = resolution.project.extensionSettings;
@@ -5923,15 +6311,15 @@ function attemptSummaryValue(value) {
5923
6311
  const transport = record2.transport;
5924
6312
  const session = record2.session === void 0 ? void 0 : sessionReferenceValue2(record2.session);
5925
6313
  const setup = setupSummaryValue(record2.setup);
5926
- const accounting3 = accountingValue(record2.accounting);
6314
+ const accounting2 = accountingValue(record2.accounting);
5927
6315
  const error = record2.error === void 0 ? void 0 : decodeFailure(record2.error);
5928
- if (typeof attempt !== "number" || !Number.isSafeInteger(attempt) || attempt < 1 || typeof transport !== "string" || !transport.trim() || !setup || !accounting3 || record2.session !== void 0 && !session || record2.error !== void 0 && !error) return void 0;
6316
+ if (typeof attempt !== "number" || !Number.isSafeInteger(attempt) || attempt < 1 || typeof transport !== "string" || !transport.trim() || !setup || !accounting2 || record2.session !== void 0 && !session || record2.error !== void 0 && !error) return void 0;
5929
6317
  return {
5930
6318
  attempt,
5931
6319
  transport: boundedAttemptText(transport),
5932
6320
  ...session === void 0 ? {} : { session },
5933
6321
  ...error === void 0 ? {} : { error: { code: boundedAttemptText(error.code), message: boundedAttemptText(error.message) } },
5934
- accounting: { ...accounting3 },
6322
+ accounting: { ...accounting2 },
5935
6323
  setup: boundedAttemptSetup(setup)
5936
6324
  };
5937
6325
  }
@@ -5951,8 +6339,8 @@ function isPreparedSession(value) {
5951
6339
  const textFields = ["initialPrompt", "agentDir", "systemPromptPath", "systemPromptAppend", "piRuntimeError"];
5952
6340
  if (textFields.some((field) => record2[field] !== void 0 && typeof record2[field] !== "string")) return false;
5953
6341
  if (record2.systemPrompt !== void 0 && (typeof record2.systemPrompt !== "string" || record2.systemPrompt.length > SUBAGENT_SYSTEM_PROMPT_LIMIT)) return false;
5954
- const runtime2 = recordValue(record2.piRuntime);
5955
- if (record2.piRuntime !== void 0 && (!runtime2 || typeof runtime2.executable !== "string" || !runtime2.executable.trim() || runtime2.entrypoint !== void 0 && (typeof runtime2.entrypoint !== "string" || !runtime2.entrypoint.trim()))) return false;
6342
+ const runtime = recordValue(record2.piRuntime);
6343
+ if (record2.piRuntime !== void 0 && (!runtime || typeof runtime.executable !== "string" || !runtime.executable.trim() || runtime.entrypoint !== void 0 && (typeof runtime.entrypoint !== "string" || !runtime.entrypoint.trim()))) return false;
5956
6344
  return true;
5957
6345
  }
5958
6346
  function isLiveSessionHandoff(value) {
@@ -5982,23 +6370,23 @@ function sessionStateValue(value) {
5982
6370
  function progressValue2(value) {
5983
6371
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
5984
6372
  const record2 = value;
5985
- const accounting3 = accountingValue(record2.accounting);
6373
+ const accounting2 = accountingValue(record2.accounting);
5986
6374
  const toolCalls = toolCallsValue2(record2.toolCalls);
5987
6375
  const state = record2.state === void 0 ? void 0 : sessionStateValue(record2.state);
5988
- if (!accounting3 || !toolCalls || record2.state !== void 0 && state === void 0) return void 0;
5989
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5990
- if (record2.activity !== void 0 && activity2 === void 0) return void 0;
6376
+ if (!accounting2 || !toolCalls || record2.state !== void 0 && state === void 0) return void 0;
6377
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
6378
+ if (record2.activity !== void 0 && activity === void 0) return void 0;
5991
6379
  if (record2.lastEventAt !== void 0 && (!Number.isSafeInteger(record2.lastEventAt) || record2.lastEventAt < 0)) return void 0;
5992
- return { accounting: accounting3, toolCalls, ...state === void 0 ? {} : { state }, ...activity2 === void 0 ? {} : { activity: activity2 }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6380
+ return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity === void 0 ? {} : { activity }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
5993
6381
  }
5994
6382
  function legacyProgressValue2(record2) {
5995
- const accounting3 = legacyAccountingValue(record2);
6383
+ const accounting2 = legacyAccountingValue(record2);
5996
6384
  const toolCalls = record2.toolCalls === void 0 ? [] : toolCallsValue2(record2.toolCalls);
5997
- if (!accounting3 || !toolCalls) return void 0;
5998
- const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5999
- if (record2.activity !== void 0 && activity2 === void 0) return void 0;
6385
+ if (!accounting2 || !toolCalls) return void 0;
6386
+ const activity = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
6387
+ if (record2.activity !== void 0 && activity === void 0) return void 0;
6000
6388
  if (record2.lastEventAt !== void 0 && (!Number.isSafeInteger(record2.lastEventAt) || record2.lastEventAt < 0)) return void 0;
6001
- return { accounting: accounting3, toolCalls, ...activity2 === void 0 ? {} : { activity: activity2 }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6389
+ return { accounting: accounting2, toolCalls, ...activity === void 0 ? {} : { activity }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6002
6390
  }
6003
6391
  function worktreeContextValue(value) {
6004
6392
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -6310,7 +6698,7 @@ var PersistentSubagentManager = class {
6310
6698
  if (!sessionId.trim()) throw new WorkflowError("INTERNAL_ERROR", "Current Pi session identity is unavailable");
6311
6699
  const owner = this.runOwner;
6312
6700
  if (!owner) throw new WorkflowError("INTERNAL_ERROR", "Subagent storage owner identity is unavailable");
6313
- const settings = resolveWorkflowSettings(context.extensionContext.cwd, context.extensionContext.isProjectTrusted(), workflowSettingsPath(this.dependencies.agentDir ?? getAgentDir5()));
6701
+ const settings = resolveWorkflowSettings(context.extensionContext.cwd, context.extensionContext.isProjectTrusted(), workflowSettingsPath(this.dependencies.agentDir ?? getAgentDir6()));
6314
6702
  const concurrency = settings.effective.concurrency;
6315
6703
  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.`);
6316
6704
  this.activeRunCount += 1;
@@ -6326,8 +6714,8 @@ var PersistentSubagentManager = class {
6326
6714
  }
6327
6715
  const current = {};
6328
6716
  let resolveTerminal;
6329
- const terminal = new Promise((resolve5) => {
6330
- resolveTerminal = resolve5;
6717
+ const terminal = new Promise((resolve6) => {
6718
+ resolveTerminal = resolve6;
6331
6719
  });
6332
6720
  const executorOwnership = { default: true };
6333
6721
  const execution = Promise.resolve().then(async () => {
@@ -6441,8 +6829,9 @@ var PersistentSubagentManager = class {
6441
6829
  if (request.id !== void 0) {
6442
6830
  const id = checkedId({ id: request.id });
6443
6831
  const active = this.activeRuns.get(id);
6444
- const status = active ? persistedStatus(active) : this.terminalSummaries.get(id) ?? await loadPersistedStatus(storageDirectory(this.dependencies), id);
6445
- const inspection = publicStatus(status, context.includeAttemptMetadata === true);
6832
+ const includeActivity = context.includeActivity === true;
6833
+ const status = active ? includeActivity ? liveStatus(active) : persistedStatus(active) : this.terminalSummaries.get(id) ?? await loadPersistedStatus(storageDirectory(this.dependencies), id);
6834
+ const inspection = publicStatus(status, context.includeAttemptMetadata === true, includeActivity);
6446
6835
  if (status.state === "completed") return { ...inspection, value: await loadPersistedResult(storageDirectory(this.dependencies), id) };
6447
6836
  if (status.state === "failed") {
6448
6837
  const failure = status.error ?? await loadPersistedFailure(storageDirectory(this.dependencies), id);
@@ -6450,9 +6839,9 @@ var PersistentSubagentManager = class {
6450
6839
  }
6451
6840
  return inspection;
6452
6841
  }
6453
- return this.inspectList();
6842
+ return this.inspectList(request.scope === "all" ? void 0 : context.extensionContext.sessionManager.getSessionId());
6454
6843
  }
6455
- async inspectList() {
6844
+ async inspectList(sessionId) {
6456
6845
  const root = storageDirectory(this.dependencies);
6457
6846
  await secureDirectory(root);
6458
6847
  const entries = await readdir3(root, { withFileTypes: true });
@@ -6463,7 +6852,7 @@ var PersistentSubagentManager = class {
6463
6852
  try {
6464
6853
  const active = this.activeRuns.get(entry.name);
6465
6854
  const status = publicStatus(active ? persistedStatus(active) : this.terminalSummaries.get(entry.name) ?? await loadPersistedStatus(root, entry.name, false));
6466
- if (status.startedAt === void 0) continue;
6855
+ if (status.startedAt === void 0 || sessionId !== void 0 && status.sessionId !== sessionId) continue;
6467
6856
  statuses.push({ status, startedAt: status.startedAt });
6468
6857
  } catch (error) {
6469
6858
  if (error instanceof WorkflowError && (error.code === "RUN_NOT_FOUND" || error instanceof InvalidPersistedSubagentStatusError)) continue;
@@ -6493,6 +6882,18 @@ var PersistentSubagentManager = class {
6493
6882
  await this.stopRun(run, false);
6494
6883
  return publicStatus(persistedStatus(run));
6495
6884
  }
6885
+ /** Removes a settled record for the /subagents navigator, as /workflow deletes a terminal run; not a model tool. */
6886
+ async delete(request) {
6887
+ const id = checkedId(request);
6888
+ await this.ensureInitialized();
6889
+ const root = storageDirectory(this.dependencies);
6890
+ const status = this.activeRuns.has(id) ? void 0 : this.terminalSummaries.get(id) ?? await loadPersistedStatus(root, id, false);
6891
+ if (status === void 0 || status.state === "running") throw new WorkflowError("RUN_OWNED", `Stop subagent ${id} before deleting it`);
6892
+ if (status.worktree !== void 0) throw new WorkflowError("WORKTREE_FAILED", `Subagent ${id} still has a worktree to clean up`);
6893
+ await rm5(runDirectory(root, id), { recursive: true, force: true });
6894
+ this.terminalSummaries.delete(id);
6895
+ return { id, deleted: true };
6896
+ }
6496
6897
  async dispose() {
6497
6898
  if (this.disposePromise) return this.disposePromise;
6498
6899
  this.disposePromise = (async () => {
@@ -6589,8 +6990,8 @@ var PersistentSubagentManager = class {
6589
6990
  if (!run || !this.canSteer(run)) return;
6590
6991
  if (run.activeAttempt !== void 0 && run.finalizedAttemptAccounting.has(run.activeAttempt)) return;
6591
6992
  const snapshot = portableProgress(progress, true);
6592
- const accounting3 = addAccounting(sumAccounting(run.finalizedAttemptAccounting.values()), progress.accounting);
6593
- run.progress = { ...snapshot, accounting: accounting3 };
6993
+ const accounting2 = addAccounting(sumAccounting(run.finalizedAttemptAccounting.values()), progress.accounting);
6994
+ run.progress = { ...snapshot, accounting: accounting2 };
6594
6995
  emitUpdate(run);
6595
6996
  if (!progress.persist) return;
6596
6997
  await enqueueWrite(run, () => atomicJson(statusPath(run.directory), persistedStatus(run)));
@@ -6605,10 +7006,10 @@ var PersistentSubagentManager = class {
6605
7006
  run.attemptDetails = [...run.attemptDetails ?? [], summary].slice(-SUBAGENT_ATTEMPT_DETAILS_LIMIT);
6606
7007
  }
6607
7008
  if (!finalized) return;
6608
- const accounting3 = summary?.accounting ?? accountingValue(record2?.accounting);
6609
- if (attempt === void 0 || accounting3 === void 0) return;
7009
+ const accounting2 = summary?.accounting ?? accountingValue(record2?.accounting);
7010
+ if (attempt === void 0 || accounting2 === void 0) return;
6610
7011
  if (run.finalizedAttemptAccounting.size >= MAX_PERSISTED_ATTEMPT_ARRAY_ITEMS && !run.finalizedAttemptAccounting.has(attempt)) return;
6611
- run.finalizedAttemptAccounting.set(attempt, accounting3);
7012
+ run.finalizedAttemptAccounting.set(attempt, accounting2);
6612
7013
  const cumulative = sumAccounting(run.finalizedAttemptAccounting.values());
6613
7014
  run.progress = run.progress === void 0 ? { accounting: cumulative, toolCalls: [] } : { ...run.progress, accounting: cumulative };
6614
7015
  }
@@ -6663,8 +7064,8 @@ var PersistentSubagentManager = class {
6663
7064
  run.disposed = true;
6664
7065
  this.clearSteering(run);
6665
7066
  await this.cleanupSessions(run, true);
6666
- const worktreeCleaned = await this.cleanupWorktree(run);
6667
- if (worktreeCleaned) await enqueueWrite(run, () => atomicJson(statusPath(run.directory), persistedStatus(run)));
7067
+ await this.cleanupWorktree(run);
7068
+ await enqueueWrite(run, () => atomicJson(statusPath(run.directory), persistedStatus(run)));
6668
7069
  this.resolveTerminal(run);
6669
7070
  this.removeLiveRun(run);
6670
7071
  }
@@ -6867,23 +7268,23 @@ function createSubagentManager(dependencies = {}) {
6867
7268
  return {
6868
7269
  run: (request, context) => manager.run(request, context),
6869
7270
  inspect: (request, context) => manager.inspect(request, context),
6870
- getAttemptActionData: (id) => manager.getAttemptActionData?.(id),
6871
- steer: (request, context) => manager.steer(request, context),
6872
- stop: (request, context) => manager.stop(request, context),
7271
+ getAttemptActionData: (id) => manager.getAttemptActionData(id),
7272
+ steer: (request) => manager.steer(request),
7273
+ stop: (request) => manager.stop(request),
6873
7274
  retry: (request, context) => manager.retry(request, context),
7275
+ delete: (request) => manager.delete(request),
6874
7276
  dispose: async () => {
6875
- await manager.dispose?.();
7277
+ await manager.dispose();
6876
7278
  }
6877
7279
  };
6878
7280
  }
6879
7281
 
6880
7282
  // subagents/src/navigator.ts
6881
7283
  import { readFile as readFile5 } from "node:fs/promises";
6882
- import { join as join9 } from "node:path";
6883
- import { copyToClipboard as copyToClipboard2, getAgentDir as getAgentDir6, SettingsManager as SettingsManager4 } from "@earendil-works/pi-coding-agent";
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";
6884
7286
  import { Editor, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
6885
7287
  var MAX_DETAIL_TEXT = 4e3;
6886
- var MAX_DETAIL_TOOL_CALLS = 32;
6887
7288
  function objectValue2(value) {
6888
7289
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6889
7290
  }
@@ -6899,12 +7300,12 @@ function inspectionValue(value) {
6899
7300
  return record2 && status ? { status, record: record2 } : void 0;
6900
7301
  }
6901
7302
  function managerContext(context, waitForForeground = true, includeAttemptMetadata = false) {
6902
- return { toolCallId: "subagents-command", signal: void 0, onUpdate: void 0, ...waitForForeground ? {} : { waitForForeground: false }, ...includeAttemptMetadata ? { includeAttemptMetadata: true } : {}, extensionContext: context };
7303
+ return { toolCallId: "subagents-command", signal: void 0, onUpdate: void 0, ...waitForForeground ? {} : { waitForForeground: false }, ...includeAttemptMetadata ? { includeAttemptMetadata: true, includeActivity: true } : {}, extensionContext: context };
6903
7304
  }
6904
7305
  async function loadRequest(storageDirectory3, id) {
6905
7306
  if (!safeRunId(id)) return {};
6906
7307
  try {
6907
- const value = JSON.parse(await readFile5(join9(storageDirectory3, id, "request.json"), "utf8"));
7308
+ const value = JSON.parse(await readFile5(join10(storageDirectory3, id, "request.json"), "utf8"));
6908
7309
  return { request: normalizeSubagentRunRequest(value) };
6909
7310
  } catch (error) {
6910
7311
  if (isFileNotFound(error)) return {};
@@ -6937,9 +7338,6 @@ async function inspectEntry(manager, storageDirectory3, entry, context) {
6937
7338
  entry: { status: inspected.status, ...request.request === void 0 ? {} : { request: request.request }, ...request.error === void 0 ? {} : { requestError: request.error } }
6938
7339
  };
6939
7340
  }
6940
- function requestLabel(request) {
6941
- return request?.label?.trim() || "none";
6942
- }
6943
7341
  function requestRole(request) {
6944
7342
  const role = request?.role;
6945
7343
  if (typeof role === "string" && role.trim()) return role.trim();
@@ -6949,13 +7347,31 @@ function requestRole(request) {
6949
7347
  function shortId(id) {
6950
7348
  return id.length > 12 ? id.slice(0, 8) : id;
6951
7349
  }
6952
- function pickerLabel(entry, index) {
6953
- return `${String(index + 1)}. label=${boundedText(requestLabel(entry.request), 256)} role=${boundedText(requestRole(entry.request), 256)} [${entry.status.state}] ${shortId(entry.status.id)}`;
7350
+ function entryName(entry) {
7351
+ const role = requestRole(entry.request);
7352
+ return boundedText(entry.request?.label?.trim() || (role === "none" ? shortId(entry.status.id) : role), 256);
7353
+ }
7354
+ function listNames(entries) {
7355
+ const names = entries.map(entryName);
7356
+ return entries.map(({ status }, index) => {
7357
+ const name = names[index] ?? "";
7358
+ return names.indexOf(name) === names.lastIndexOf(name) ? name : `${name} ${shortId(status.id)}`;
7359
+ });
7360
+ }
7361
+ function pickerLabels(entries) {
7362
+ const names = listNames(entries);
7363
+ return entries.map(({ status }, index) => {
7364
+ const name = names[index] ?? "";
7365
+ const model = status.progress?.state?.model;
7366
+ const cost = formatCost(status.progress?.accounting.cost);
7367
+ const runtime = status.startedAt === void 0 ? "" : ` runtime=${formatWorkflowRuntime((status.finishedAt ?? Date.now()) - status.startedAt)}`;
7368
+ return `${runStateGlyph(status.state, "\u2826")} ${name} ${status.state}${model ? ` ${boundedText(model.model, 256)}${model.thinking ? `:${model.thinking}` : ""}` : ""}${cost ? ` ${cost}` : ""}${runtime}`;
7369
+ });
6954
7370
  }
6955
7371
  function boundedText(value, limit = MAX_DETAIL_TEXT) {
6956
7372
  const text = typeof value === "string" ? value : (() => {
6957
7373
  try {
6958
- const serialized = JSON.stringify(value);
7374
+ const serialized = JSON.stringify(value, null, 2);
6959
7375
  return typeof serialized === "string" ? serialized : String(value);
6960
7376
  } catch {
6961
7377
  return String(value);
@@ -6963,13 +7379,6 @@ function boundedText(value, limit = MAX_DETAIL_TEXT) {
6963
7379
  })();
6964
7380
  return text.length > limit ? `${text.slice(0, limit)}\u2026` : text;
6965
7381
  }
6966
- function timestamp(value) {
6967
- return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : void 0;
6968
- }
6969
- function appendValue(lines, title, value) {
6970
- lines.push(`${title}:`);
6971
- for (const line of boundedText(value).split("\n")) lines.push(` ${line}`);
6972
- }
6973
7382
  function latestAttempt(status) {
6974
7383
  return [...status.attemptDetails ?? []].sort((left, right) => right.attempt - left.attempt)[0];
6975
7384
  }
@@ -6980,17 +7389,17 @@ function detailPresentation(inspection) {
6980
7389
  const { status, request } = inspection.entry;
6981
7390
  const attempt = latestAttempt(status);
6982
7391
  const state = status.progress?.state;
6983
- const activity2 = status.progress?.activity;
7392
+ const activity = status.progress?.activity;
6984
7393
  const model = state?.model ?? attempt?.setup.model;
6985
7394
  const tools = state?.tools ?? attempt?.setup.tools;
6986
7395
  const lastEventAt = status.progress?.lastEventAt;
6987
7396
  const attempts = status.attempts ?? attempt?.attempt;
6988
- const accounting3 = usageAccounting(status);
7397
+ const accounting2 = usageAccounting(status);
6989
7398
  const role = requestRole(request);
6990
7399
  const error = status.error ?? attempt?.error;
6991
7400
  return {
6992
7401
  state: status.state,
6993
- ...activity2 === void 0 ? {} : { activity: { kind: activity2.kind, text: boundedText(activity2.text) } },
7402
+ ...activity === void 0 ? {} : { activity: { kind: activity.kind, text: boundedText(activity.text) } },
6994
7403
  ...lastEventAt === void 0 ? {} : { lastEventAt },
6995
7404
  ...model === void 0 ? {} : { model: { provider: boundedText(model.provider, 256), model: boundedText(model.model, 256), ...model.thinking === void 0 ? {} : { thinking: model.thinking } } },
6996
7405
  ...role === "none" ? {} : { role: boundedText(role, 256) },
@@ -6998,33 +7407,34 @@ function detailPresentation(inspection) {
6998
7407
  ...attempts === void 0 ? {} : { attempts },
6999
7408
  ...status.startedAt === void 0 ? {} : { startedAt: status.startedAt },
7000
7409
  ...status.finishedAt === void 0 ? {} : { finishedAt: status.finishedAt },
7001
- ...accounting3 === void 0 ? {} : { accounting: accounting3 },
7410
+ ...accounting2 === void 0 ? {} : { accounting: accounting2 },
7002
7411
  ...error === void 0 ? {} : { error: { code: boundedText(error.code, 256), message: boundedText(error.message) } }
7003
7412
  };
7004
7413
  }
7005
- function detailLines(inspection, theme) {
7006
- const { entry, record: record2 } = inspection;
7007
- const { status, request } = entry;
7008
- const styles = theme === void 0 ? void 0 : themeWorkflowProgressStyles(theme);
7009
- const lines = [
7010
- theme?.bold(theme.fg("accent", `Subagent ${boundedText(status.id, 256)}`)) ?? `Subagent ${boundedText(status.id, 256)}`,
7011
- `label=${boundedText(requestLabel(request), 256)} role=${boundedText(requestRole(request), 256)}`,
7012
- ...formatAgentDetail(detailPresentation(inspection), styles, status.finishedAt ?? Date.now())
7414
+ function detailRows(inspection, styles, menu) {
7415
+ const { entry } = inspection;
7416
+ const presentation = detailPresentation(inspection);
7417
+ return [
7418
+ styles.bold(`Selected subagent: ${entryName(entry)}`),
7419
+ `ID: ${boundedText(entry.status.id, 256)}`,
7420
+ ...formatAgentDetail(presentation, styles, entry.status.finishedAt ?? Date.now(), { includeError: false }),
7421
+ ...entry.requestError === void 0 ? [] : [styles.warning(`Request unavailable: ${boundedText(entry.requestError)}`)],
7422
+ ...menu === "hint" ? [styles.muted("enter agent actions")] : [],
7423
+ ...presentation.error === void 0 ? [] : [formatAgentError(presentation.error, styles)],
7424
+ ...typeof menu === "object" ? [styles.bold("Agent actions"), ...menu.options.map((option, index) => index === menu.index ? `\u2192 ${styles.accent(option)}` : ` ${option}`)] : []
7013
7425
  ];
7014
- const startedAt = timestamp(status.startedAt);
7015
- const finishedAt = timestamp(status.finishedAt);
7016
- if (startedAt) lines.push(`startedAt=${startedAt}`);
7017
- if (finishedAt) lines.push(`finishedAt=${finishedAt}`);
7018
- if (request?.prompt) appendValue(lines, "prompt", request.prompt);
7019
- if (entry.requestError) lines.push(`request=unavailable: ${boundedText(entry.requestError)}`);
7020
- if (status.worktree) lines.push(`worktree=${boundedText(status.worktree.path)} branch=${boundedText(status.worktree.branch)}`);
7021
- const toolCalls = status.progress?.toolCalls;
7022
- if (toolCalls?.length) {
7023
- lines.push(`toolCalls=${String(toolCalls.length)}`);
7024
- for (const call of toolCalls.slice(-MAX_DETAIL_TOOL_CALLS)) lines.push(` ${boundedText(call.name, 256)} [${call.state}]`);
7025
- }
7026
- if (Object.prototype.hasOwnProperty.call(record2, "value")) appendValue(lines, "value", record2.value);
7027
- return lines;
7426
+ }
7427
+ function listRows(entries, selectedId, styles) {
7428
+ const names = listNames(entries);
7429
+ return [styles.bold("Runs"), ...entries.map((entry, index) => `${entry.status.id === selectedId ? "\u2192" : " "} \u2022 ${names[index] ?? ""} \xB7 ${progressStyleForState(entry.status.state, styles)(runStateGlyph(entry.status.state, "\u2826"))}`)];
7430
+ }
7431
+ function headerRows(entries, styles) {
7432
+ const counts = ["running", "failed", "stopped", "completed"].flatMap((state) => {
7433
+ const count = entries.filter((entry) => entry.status.state === state).length;
7434
+ return count ? [`${String(count)} ${state}`] : [];
7435
+ });
7436
+ const cost = formatCost(entries.reduce((sum, entry) => sum + (entry.status.progress?.accounting.cost ?? 0), 0));
7437
+ return [styles.bold(styles.accent("Subagents")), [...counts, ...cost ? [cost] : []].join(" \xB7 ")];
7028
7438
  }
7029
7439
  function tuiRows(tui) {
7030
7440
  return typeof tui.terminal?.rows === "number" && Number.isFinite(tui.terminal.rows) ? tui.terminal.rows : 24;
@@ -7034,6 +7444,7 @@ function unrefTimer(timer) {
7034
7444
  if (typeof value !== "object" || value === null || !("unref" in value) || typeof value.unref !== "function") return;
7035
7445
  Reflect.apply(value.unref, value, []);
7036
7446
  }
7447
+ var DASHBOARD_FOOTER_ROWS = 2;
7037
7448
  function standaloneActionContext(manager, inspection, context) {
7038
7449
  const status = inspection.entry.status;
7039
7450
  const request = inspection.entry.request;
@@ -7070,11 +7481,16 @@ function standaloneActionContext(manager, inspection, context) {
7070
7481
  function liveSystemPrompt(manager, status) {
7071
7482
  return manager.getAttemptActionData?.(status.id)?.prepared?.systemPrompt;
7072
7483
  }
7073
- function actionOptions(manager, inspection, context) {
7484
+ var BULK_DELETE = { "Delete all completed": "completed", "Delete all failed": "failed" };
7485
+ function bulkDeleteLabels(manager, entries) {
7486
+ if (manager.delete === void 0) return [];
7487
+ return Object.entries(BULK_DELETE).filter(([, state]) => entries.some((entry) => entry.status.state === state)).map(([label2]) => label2);
7488
+ }
7489
+ function actionOptions(manager, inspection, context, bulk = []) {
7074
7490
  const actionContext = standaloneActionContext(manager, inspection, context);
7075
7491
  const extensionLabels = actionContext === void 0 ? [] : visibleStandaloneAgentAttemptActions(loadingRegistry().agentAttemptActions(), actionContext).map(([, action]) => action.label);
7076
7492
  const value = inspection.record.value;
7077
- return agentActionLabels({
7493
+ const labels = agentActionLabels({
7078
7494
  extensionLabels,
7079
7495
  hasWorktree: inspection.entry.status.worktree !== void 0,
7080
7496
  openPrompt: context.mode === "tui" && inspection.entry.request?.prompt !== void 0,
@@ -7082,13 +7498,33 @@ function actionOptions(manager, inspection, context) {
7082
7498
  openResult: context.mode === "tui" && Object.prototype.hasOwnProperty.call(inspection.record, "value") && jsonValue(value),
7083
7499
  standaloneState: inspection.entry.status.state
7084
7500
  });
7501
+ const runActions = [...manager.delete !== void 0 && inspection.entry.status.state !== "running" ? ["Delete"] : [], ...bulk, ...context.mode === "tui" ? ["Copy run path"] : []];
7502
+ labels.splice(labels.indexOf("Copy agent ID"), 0, ...runActions);
7503
+ return labels;
7504
+ }
7505
+ async function deleteAll(manager, storageDirectory3, context, state, confirm) {
7506
+ if (!await confirm(`Delete ${state} runs?`, `Delete all ${state} subagent runs and their records? This cannot be undone.`)) return false;
7507
+ const skipped = [];
7508
+ let deleted = 0;
7509
+ for (const entry of await loadEntries(manager, storageDirectory3, context)) {
7510
+ if (entry.status.state !== state) continue;
7511
+ try {
7512
+ await manager.delete?.({ id: entry.status.id }, managerContext(context));
7513
+ deleted += 1;
7514
+ } catch (error) {
7515
+ skipped.push(`${entry.status.id} (${errorText(error)})`);
7516
+ }
7517
+ }
7518
+ if (skipped.length) context.ui.notify(`Skipped ${state} runs: ${skipped.join(", ")}.`, "warning");
7519
+ if (deleted) context.ui.notify(`Deleted ${String(deleted)} ${state} subagent run(s).`, "info");
7520
+ return true;
7085
7521
  }
7086
7522
  function retryResult(value) {
7087
7523
  const record2 = objectValue2(value);
7088
7524
  return record2 && typeof record2.id === "string" && record2.id.trim() && record2.state === "running" ? { id: record2.id, state: "running" } : void 0;
7089
7525
  }
7090
7526
  async function openNavigatorArtifact(context, tui, artifact, label2) {
7091
- const command = SettingsManager4.create(context.cwd, getAgentDir6(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7527
+ const command = SettingsManager4.create(context.cwd, getAgentDir7(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7092
7528
  if (!command) {
7093
7529
  context.ui.notify(`Cannot open ${label2}: no external editor is configured.`, "warning");
7094
7530
  return;
@@ -7102,7 +7538,7 @@ async function steerSubagent(manager, storageDirectory3, entry, context, message
7102
7538
  await manager.steer({ id: entry.status.id, message }, managerContext(context));
7103
7539
  context.ui.notify(`Steered subagent ${entry.status.id}.`, "info");
7104
7540
  }
7105
- async function performAction(manager, storageDirectory3, entry, action, context, tui, clipboard) {
7541
+ async function performAction(manager, storageDirectory3, entry, action, context, tui, clipboard, confirm) {
7106
7542
  const fresh = await inspectEntry(manager, storageDirectory3, entry, context);
7107
7543
  const available = actionOptions(manager, fresh, context);
7108
7544
  if (!available.includes(action)) throw new Error(`Action ${action} is no longer available`);
@@ -7116,40 +7552,52 @@ async function performAction(manager, storageDirectory3, entry, action, context,
7116
7552
  if (action === "Copy agent ID") {
7117
7553
  await clipboard(fresh.entry.status.id);
7118
7554
  context.ui.notify("Copied agent ID.", "info");
7119
- return "stay";
7555
+ return "unchanged";
7556
+ }
7557
+ if (action === "Copy run path") {
7558
+ await clipboard(join10(storageDirectory3, fresh.entry.status.id));
7559
+ context.ui.notify("Copied run path.", "info");
7560
+ return "unchanged";
7561
+ }
7562
+ if (action === "Delete" && manager.delete !== void 0) {
7563
+ if (!await confirm("Delete subagent?", `Delete ${entryName(fresh.entry)} (${fresh.entry.status.id}) and its record? This cannot be undone.`)) return "unchanged";
7564
+ await manager.delete({ id: fresh.entry.status.id }, managerContext(context));
7565
+ context.ui.notify(`Deleted subagent ${fresh.entry.status.id}.`, "info");
7566
+ return "deleted";
7120
7567
  }
7121
7568
  if (action === "Copy branch" && fresh.entry.status.worktree) {
7122
7569
  await clipboard(fresh.entry.status.worktree.branch);
7123
7570
  context.ui.notify("Copied branch.", "info");
7124
- return "stay";
7571
+ return "unchanged";
7125
7572
  }
7126
7573
  if (action === "Copy worktree path" && fresh.entry.status.worktree) {
7127
7574
  await clipboard(fresh.entry.status.worktree.path);
7128
7575
  context.ui.notify("Copied worktree path.", "info");
7129
- return "stay";
7576
+ return "unchanged";
7130
7577
  }
7131
7578
  if (action === "Open prompt in editor" && tui && fresh.entry.request?.prompt !== void 0) {
7132
7579
  await openNavigatorArtifact(context, tui, workflowPromptArtifact(fresh.entry.request.prompt), "agent prompt");
7133
- return "stay";
7580
+ return "unchanged";
7134
7581
  }
7135
7582
  if (action === "Open system prompt in editor" && tui) {
7136
7583
  const systemPrompt = liveSystemPrompt(manager, fresh.entry.status);
7137
7584
  if (systemPrompt !== void 0) {
7138
7585
  await openNavigatorArtifact(context, tui, workflowPromptArtifact(systemPrompt), "agent system prompt");
7139
- return "stay";
7586
+ return "unchanged";
7140
7587
  }
7141
7588
  }
7142
7589
  if (action === "Open result in editor" && tui && Object.prototype.hasOwnProperty.call(fresh.record, "value") && jsonValue(fresh.record.value)) {
7143
7590
  await openNavigatorArtifact(context, tui, workflowResultArtifact(fresh.record.value), "agent result");
7144
- return "stay";
7591
+ return "unchanged";
7145
7592
  }
7146
7593
  if (action === "Steer") {
7147
7594
  const message = await context.ui.input("Steer subagent", "Message for the running subagent");
7148
- if (message === void 0) return "stay";
7595
+ if (message === void 0) return "unchanged";
7149
7596
  await steerSubagent(manager, storageDirectory3, entry, context, message);
7150
7597
  return "stay";
7151
7598
  }
7152
7599
  if (action === "Stop") {
7600
+ if (!await confirm("Stop subagent?", `Stop subagent ${entryName(fresh.entry)} (${fresh.entry.status.id})? This cannot be undone.`)) return "unchanged";
7153
7601
  const current = await inspectEntry(manager, storageDirectory3, entry, context);
7154
7602
  if (current.entry.status.state !== "running") throw new Error(`Subagent ${entry.status.id} is no longer running`);
7155
7603
  await manager.stop({ id: entry.status.id }, managerContext(context));
@@ -7162,31 +7610,36 @@ async function performAction(manager, storageDirectory3, entry, action, context,
7162
7610
  const result = retryResult(await manager.retry({ id: entry.status.id }, managerContext(context, false)));
7163
7611
  if (!result) throw new Error("Retry returned an invalid subagent result");
7164
7612
  context.ui.notify(`Retried subagent ${entry.status.id} as ${result.id}.`, "info");
7165
- return "retry";
7613
+ return { retryId: result.id };
7166
7614
  }
7167
7615
  return "stay";
7168
7616
  }
7169
7617
  async function showDetail(manager, storageDirectory3, entry, context, clipboard) {
7170
7618
  let inspection = await inspectEntry(manager, storageDirectory3, entry, context);
7171
- if (context.mode !== "tui") {
7172
- for (; ; ) {
7173
- const action = await context.ui.select(detailLines(inspection).join("\n"), actionOptions(manager, inspection, context));
7174
- if (!action || action === "Back") return;
7175
- try {
7176
- if (await performAction(manager, storageDirectory3, entry, action, context, void 0, clipboard) === "retry") return;
7177
- inspection = await inspectEntry(manager, storageDirectory3, entry, context);
7178
- } catch (error) {
7179
- context.ui.notify(`Cannot ${action.toLowerCase()}: ${errorText(error)}`, "warning");
7180
- }
7619
+ for (; ; ) {
7620
+ const action = await context.ui.select(detailRows(inspection, PLAIN_WORKFLOW_PROGRESS_STYLES).join("\n"), actionOptions(manager, inspection, context));
7621
+ if (!action || action === "Back") return;
7622
+ try {
7623
+ const outcome = await performAction(manager, storageDirectory3, entry, action, context, void 0, clipboard, (title, message) => context.ui.confirm(title, message));
7624
+ if (outcome === "deleted" || typeof outcome === "object") return;
7625
+ inspection = await inspectEntry(manager, storageDirectory3, entry, context);
7626
+ } catch (error) {
7627
+ context.ui.notify(`Cannot ${action.toLowerCase()}: ${errorText(error)}`, "warning");
7181
7628
  }
7182
7629
  }
7630
+ }
7631
+ async function showDashboard(manager, storageDirectory3, initial, context, clipboard) {
7632
+ const first = initial[0];
7633
+ if (first === void 0) return;
7634
+ let initialInspection;
7635
+ try {
7636
+ initialInspection = await inspectEntry(manager, storageDirectory3, first, context);
7637
+ } catch (error) {
7638
+ context.ui.notify(`Cannot inspect subagent ${first.status.id}: ${errorText(error)}`, "warning");
7639
+ initialInspection = { entry: first, record: {} };
7640
+ }
7183
7641
  const result = await context.ui.custom((tui, theme, keybindings, done) => {
7184
- let offset = 0;
7185
- let actionMode = false;
7186
- let actionIndex = 0;
7187
- let actionRunning = false;
7188
- let disposed = false;
7189
- let steerMode = false;
7642
+ const styles = themeWorkflowProgressStyles(theme);
7190
7643
  const editorTheme = {
7191
7644
  borderColor: (text) => theme.fg("accent", text),
7192
7645
  selectList: {
@@ -7198,226 +7651,273 @@ async function showDetail(manager, storageDirectory3, entry, context, clipboard)
7198
7651
  }
7199
7652
  };
7200
7653
  const steerEditor = new Editor(tui, editorTheme);
7201
- let refreshTimer;
7654
+ let entries = initial;
7655
+ let inspection = initialInspection;
7656
+ let offset = 0;
7657
+ let detailsMode = false;
7658
+ let actionMode = false;
7659
+ let actionIndex = 0;
7660
+ let steerMode = false;
7661
+ const inlineConfirm = createInlineConfirm(() => {
7662
+ if (!disposed) tui.requestRender();
7663
+ });
7664
+ const confirmInline = (title, message) => inlineConfirm.ask(title, message);
7665
+ let actionRunning = false;
7202
7666
  let refreshing = false;
7203
- let refreshGeneration = 0;
7667
+ let disposed = false;
7668
+ let generation = 0;
7669
+ let renderedWidth = 80;
7670
+ let selectionNeedsScroll = true;
7671
+ let timer;
7672
+ const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
7673
+ const matches = (data, binding) => workflowKeyMatches(keybindings, data, binding);
7204
7674
  const requestRender = () => {
7205
7675
  if (!disposed) tui.requestRender();
7206
7676
  };
7207
- const stopRefresh = () => {
7208
- if (refreshTimer !== void 0) {
7209
- clearInterval(refreshTimer);
7210
- refreshTimer = void 0;
7677
+ const options = () => actionOptions(manager, inspection, context, bulkDeleteLabels(manager, entries));
7678
+ const warn = (message) => {
7679
+ if (disposed) return;
7680
+ try {
7681
+ context.ui.notify(message, "warning");
7682
+ } catch {
7211
7683
  }
7212
- refreshGeneration += 1;
7213
7684
  };
7214
- const buildActionRows = (detail, options) => [
7215
- ...detail,
7216
- "",
7217
- theme.bold("Agent actions"),
7218
- ...options.map((option, index) => `${index === actionIndex ? "\u2192 " : " "}${index === actionIndex ? theme.fg("accent", option) : option}`)
7219
- ];
7220
- const viewportRows = () => Math.max(1, tuiRows(tui) - 1);
7221
- const maxOffsetFor = (rows) => Math.max(0, rows.length - viewportRows());
7222
- const clampOffset = (rows) => {
7223
- offset = Math.max(0, Math.min(maxOffsetFor(rows), offset));
7685
+ const stopTimer = () => {
7686
+ if (timer === void 0) return;
7687
+ clearInterval(timer);
7688
+ timer = void 0;
7224
7689
  };
7225
- const actionView = () => {
7226
- const detail = detailLines(inspection, theme);
7227
- const options = actionOptions(manager, inspection, context);
7228
- return { detail, options, rows: buildActionRows(detail, options) };
7690
+ const syncTimer = () => {
7691
+ if (!entries.some((entry) => entry.status.state === "running") && inspection.entry.status.state !== "running") stopTimer();
7692
+ else if (timer === void 0 && !disposed) {
7693
+ timer = setInterval(() => {
7694
+ if (timer !== void 0) void refresh();
7695
+ }, 1e3);
7696
+ unrefTimer(timer);
7697
+ }
7229
7698
  };
7230
- const scrollActionIntoView = (view = actionView()) => {
7231
- const { options, rows } = view;
7232
- if (actionIndex >= options.length) {
7233
- clampOffset(rows);
7234
- return;
7699
+ const apply = (nextEntries, next) => {
7700
+ const selectedAction = actionMode ? options()[actionIndex] : void 0;
7701
+ const position = (list) => list.findIndex((entry) => entry.status.id === next.entry.status.id);
7702
+ if (position(entries) !== position(nextEntries)) selectionNeedsScroll = true;
7703
+ entries = nextEntries;
7704
+ inspection = next;
7705
+ if (selectedAction !== void 0) {
7706
+ const nextOptions = options();
7707
+ const kept = nextOptions.indexOf(selectedAction);
7708
+ actionIndex = kept >= 0 ? kept : Math.min(actionIndex, Math.max(0, nextOptions.length - 1));
7235
7709
  }
7236
- const viewport = viewportRows();
7237
- const actionRow = rows.length - options.length + actionIndex;
7238
- if (actionRow < offset) offset = actionRow;
7239
- else if (actionRow >= offset + viewport) offset = actionRow - viewport + 1;
7240
- clampOffset(rows);
7710
+ syncTimer();
7711
+ requestRender();
7241
7712
  };
7242
- const reportRefreshError = (error) => {
7243
- if (disposed) return;
7713
+ const reload = async (id = inspection.entry.status.id) => {
7714
+ const current = ++generation;
7715
+ const nextEntries = await loadEntries(manager, storageDirectory3, context);
7716
+ if (current !== generation) return;
7717
+ const selected = nextEntries.find((entry) => entry.status.id === id) ?? nextEntries[0];
7718
+ if (selected === void 0) {
7719
+ context.ui.notify("No durable subagent runs in this session.", "info");
7720
+ close(void 0);
7721
+ return;
7722
+ }
7723
+ let next;
7244
7724
  try {
7245
- context.ui.notify(`Cannot refresh subagent ${entry.status.id}: ${errorText(error)}`, "warning");
7246
- } catch {
7725
+ next = await inspectEntry(manager, storageDirectory3, selected, context);
7726
+ } catch (error) {
7727
+ if (current === generation) apply(nextEntries, { entry: selected, record: {} });
7728
+ throw error;
7247
7729
  }
7730
+ if (current === generation) apply(nextEntries, next);
7248
7731
  };
7249
- const refreshInspection = async () => {
7250
- if (disposed || actionRunning || refreshing || inspection.entry.status.state !== "running") return;
7732
+ const refresh = async () => {
7733
+ if (disposed || actionRunning || refreshing) return;
7251
7734
  refreshing = true;
7252
- const generation = ++refreshGeneration;
7253
- const actionIndexBeforeRefresh = actionIndex;
7254
- const viewBeforeRefresh = actionMode ? actionView() : void 0;
7255
- const actionRowBeforeRefresh = viewBeforeRefresh === void 0 ? -1 : viewBeforeRefresh.rows.length - viewBeforeRefresh.options.length + actionIndex;
7256
- const wasVisible = actionRowBeforeRefresh >= offset && actionRowBeforeRefresh < offset + viewportRows();
7735
+ const current = generation;
7736
+ const selectedId = inspection.entry.status.id;
7257
7737
  try {
7258
- const next = await inspectEntry(manager, storageDirectory3, entry, context);
7259
- if (generation !== refreshGeneration) return;
7260
- inspection = next;
7261
- if (inspection.entry.status.state !== "running") stopRefresh();
7262
- if (actionMode) {
7263
- const options = actionOptions(manager, inspection, context);
7264
- actionIndex = Math.min(actionIndex, Math.max(0, options.length - 1));
7265
- const view = actionView();
7266
- if (wasVisible || actionIndex !== actionIndexBeforeRefresh) scrollActionIntoView(view);
7267
- else clampOffset(view.rows);
7268
- }
7269
- requestRender();
7270
- } catch (error) {
7271
- if (generation === refreshGeneration) reportRefreshError(error);
7738
+ const stale = entries.filter((entry) => entry.status.state === "running" || entry.status.id === selectedId);
7739
+ const results = await Promise.allSettled(stale.map((entry) => inspectEntry(manager, storageDirectory3, entry, context)));
7740
+ if (current !== generation) return;
7741
+ const fresh = new Map(results.flatMap((result2) => result2.status === "fulfilled" ? [[result2.value.entry.status.id, result2.value]] : []));
7742
+ apply(attentionSort(entries.map((entry) => fresh.get(entry.status.id)?.entry ?? entry)), fresh.get(selectedId) ?? inspection);
7272
7743
  } finally {
7273
7744
  refreshing = false;
7274
7745
  }
7275
7746
  };
7747
+ const select = (delta) => {
7748
+ const index = Math.max(0, entries.findIndex((entry2) => entry2.status.id === inspection.entry.status.id));
7749
+ const entry = entries[(index + delta + entries.length) % entries.length];
7750
+ if (entry === void 0 || entry.status.id === inspection.entry.status.id) return;
7751
+ inspection = { entry, record: {} };
7752
+ selectionNeedsScroll = true;
7753
+ const current = ++generation;
7754
+ void inspectEntry(manager, storageDirectory3, entry, context).then((next) => {
7755
+ if (current === generation) apply(entries, next);
7756
+ }, (error) => {
7757
+ if (current === generation) warn(`Cannot inspect subagent ${entry.status.id}: ${errorText(error)}`);
7758
+ });
7759
+ };
7760
+ const shutDown = () => {
7761
+ disposed = true;
7762
+ generation += 1;
7763
+ stopTimer();
7764
+ inlineConfirm.cancel();
7765
+ };
7276
7766
  const close = (value) => {
7277
7767
  if (disposed) return;
7278
- disposed = true;
7279
- stopRefresh();
7768
+ shutDown();
7280
7769
  done(value);
7281
7770
  };
7282
7771
  steerEditor.onSubmit = (value) => {
7283
7772
  const message = value.trim();
7284
- if (message) close({ kind: "steer", message });
7773
+ if (message) close({ entry: inspection.entry, message });
7285
7774
  };
7286
- const isDisposed = () => disposed;
7287
- const renderLines = (width) => {
7288
- if (disposed) return [];
7289
- const renderWidth = Math.max(1, width);
7290
- const action = actionMode ? actionView() : void 0;
7291
- const detail = (action?.detail ?? detailLines(inspection, theme)).map((line) => truncateToWidth3(line, renderWidth, "\u2026"));
7292
- const rows = steerMode ? [...detail, "", theme.bold("Steer subagent"), ...steerEditor.render(renderWidth)] : action?.rows ?? detail;
7293
- const viewport = viewportRows();
7294
- const maxOffset = maxOffsetFor(rows);
7295
- const visibleOffset = steerMode ? maxOffset : Math.max(0, Math.min(maxOffset, offset));
7296
- const hint = theme.fg("dim", steerMode ? "enter submit \xB7 esc back" : actionMode ? "\u2191/\u2193 actions \xB7 enter run \xB7 esc back" : "\u2191/\u2193 scroll \xB7 a actions \xB7 enter actions \xB7 esc back");
7297
- return [...rows.slice(visibleOffset, visibleOffset + viewport), hint].map((line) => truncateToWidth3(line, renderWidth, "\u2026"));
7775
+ const reloadAfterAction = async (id) => {
7776
+ try {
7777
+ await reload(id);
7778
+ } catch (error) {
7779
+ warn(`Cannot refresh subagents: ${errorText(error)}`);
7780
+ }
7298
7781
  };
7299
7782
  const runAction = (action) => {
7300
- if (disposed) return;
7301
7783
  actionRunning = true;
7302
7784
  requestRender();
7303
- void performAction(manager, storageDirectory3, entry, action, context, tui, clipboard).then(async (outcome) => {
7304
- if (disposed) return;
7305
- if (outcome === "retry") {
7306
- close("retry");
7307
- return;
7308
- }
7309
- const next = await inspectEntry(manager, storageDirectory3, entry, context);
7310
- if (isDisposed()) return;
7311
- inspection = next;
7312
- if (inspection.entry.status.state !== "running") stopRefresh();
7785
+ const bulk = BULK_DELETE[action];
7786
+ const pending = bulk === void 0 ? performAction(manager, storageDirectory3, inspection.entry, action, context, tui, clipboard, confirmInline) : deleteAll(manager, storageDirectory3, context, bulk, confirmInline).then((done2) => done2 ? "stay" : "unchanged");
7787
+ void pending.then(async (outcome) => {
7788
+ if (disposed || outcome === "unchanged") return;
7789
+ await reloadAfterAction(typeof outcome === "object" ? outcome.retryId : void 0);
7313
7790
  actionMode = false;
7314
7791
  actionIndex = 0;
7315
7792
  offset = 0;
7316
- }).catch((error) => {
7317
- if (!disposed) context.ui.notify(`Cannot ${action.toLowerCase()}: ${errorText(error)}`, "warning");
7793
+ selectionNeedsScroll = true;
7794
+ }, async (error) => {
7795
+ warn(`Cannot ${action.toLowerCase()}: ${errorText(error)}`);
7796
+ if (!disposed) await reloadAfterAction();
7318
7797
  }).finally(() => {
7319
7798
  actionRunning = false;
7320
- if (!disposed) requestRender();
7799
+ requestRender();
7321
7800
  });
7322
7801
  };
7323
- if (inspection.entry.status.state === "running") {
7324
- refreshTimer = setInterval(() => {
7325
- void refreshInspection().catch(reportRefreshError);
7326
- }, 1e3);
7327
- unrefTimer(refreshTimer);
7328
- }
7802
+ syncTimer();
7329
7803
  return {
7330
- render: renderLines,
7804
+ render(width) {
7805
+ if (disposed) return [];
7806
+ renderedWidth = width;
7807
+ const narrow = width < 80;
7808
+ const actions = actionMode ? options() : [];
7809
+ const menu = actionMode ? { options: actions, index: actionIndex } : steerMode ? void 0 : "hint";
7810
+ const layout = narrow ? detailsMode || actionMode || steerMode ? { detailsOnly: true } : { treeOnly: true } : {};
7811
+ const content = [...headerRows(entries, styles), ...formatNavigatorColumns(listRows(entries, inspection.entry.status.id, styles), detailRows(inspection, styles, menu), width, layout)];
7812
+ const footer = steerMode ? [styles.bold("Steer subagent"), ...steerEditor.render(width)] : inlineConfirm.rows(width, styles);
7813
+ const rows = Math.max(1, tuiRows(tui) - DASHBOARD_FOOTER_ROWS);
7814
+ const hintRows = rows >= 3 ? 1 : 0;
7815
+ const viewport = Math.max(1, rows - hintRows - footer.length);
7816
+ const keepVisible = (row) => {
7817
+ if (row < 0) return;
7818
+ if (row < offset) offset = row;
7819
+ else if (row >= offset + viewport) offset = row - viewport + 1;
7820
+ };
7821
+ if (actionMode) keepVisible(content.findIndex((line) => narrow ? line.startsWith("\u2192 ") : line.includes(" | \u2192 ")));
7822
+ else if (selectionNeedsScroll && !(narrow && detailsMode)) {
7823
+ keepVisible(content.findIndex((line) => line.startsWith("\u2192")));
7824
+ selectionNeedsScroll = false;
7825
+ }
7826
+ offset = Math.max(0, Math.min(Math.max(0, content.length - viewport), offset));
7827
+ const up = keyLabel("tui.select.up", "\u2191");
7828
+ const down = keyLabel("tui.select.down", "\u2193");
7829
+ const enter = keyLabel("tui.select.confirm", "enter");
7830
+ const esc = keyLabel("tui.select.cancel", "esc");
7831
+ const scroll = content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : "";
7832
+ const refreshHint = timer === void 0 ? "" : " \xB7 auto-refresh 1s";
7833
+ const back = narrow && detailsMode ? "details" : "list";
7834
+ const hint = steerMode ? "enter submit \xB7 esc back" : inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${up}/${down} actions \xB7 ${enter} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} ${back} \xB7 ${esc} ${back}` : narrow && detailsMode ? `${up}/${down} scroll \xB7 ${enter} actions \xB7 a actions \xB7 ${esc} list${scroll}${refreshHint}` : `${up}/${down} select \xB7 ${enter} ${narrow ? "details" : "actions"} \xB7 a actions \xB7 ${esc} close${scroll}${refreshHint}`;
7835
+ return [...content.slice(offset, offset + viewport), ...footer, ...hintRows ? [styles.dim(hint)] : []].map((line) => truncateToWidth3(line, Math.max(1, width), "\u2026"));
7836
+ },
7331
7837
  invalidate() {
7332
7838
  },
7333
7839
  handleInput(data) {
7334
- if (disposed || actionRunning) return;
7840
+ if (disposed) return;
7841
+ if (inlineConfirm.active()) {
7842
+ inlineConfirm.handleInput(keybindings, data);
7843
+ return;
7844
+ }
7845
+ if (actionRunning) return;
7335
7846
  if (steerMode) {
7336
7847
  if (keybindings.matches(data, "tui.select.cancel")) {
7337
7848
  steerMode = false;
7338
7849
  actionMode = true;
7339
7850
  steerEditor.setText("");
7340
- offset = 0;
7341
- scrollActionIntoView();
7342
7851
  } else steerEditor.handleInput(data);
7343
7852
  requestRender();
7344
7853
  return;
7345
7854
  }
7346
- if (!actionMode && data === "a") {
7855
+ const narrow = renderedWidth < 80;
7856
+ const page = Math.max(1, tuiRows(tui) - DASHBOARD_FOOTER_ROWS - 1);
7857
+ if (matches(data, "tui.select.pageUp")) offset = Math.max(0, offset - page);
7858
+ else if (matches(data, "tui.select.pageDown")) offset += page;
7859
+ else if (actionMode) {
7860
+ const actions = options();
7861
+ if (matches(data, "tui.select.cancel") || matches(data, "tui.editor.cursorLeft")) {
7862
+ actionMode = false;
7863
+ offset = 0;
7864
+ selectionNeedsScroll = true;
7865
+ } else if (matches(data, "tui.select.up")) actionIndex = (actionIndex + actions.length - 1) % actions.length;
7866
+ else if (matches(data, "tui.select.down")) actionIndex = (actionIndex + 1) % actions.length;
7867
+ else if (matches(data, "tui.select.confirm")) {
7868
+ const action = actions[actionIndex];
7869
+ if (action === "Steer") {
7870
+ steerMode = true;
7871
+ actionMode = false;
7872
+ steerEditor.setText("");
7873
+ } else if (action && action !== "Back") runAction(action);
7874
+ else {
7875
+ actionMode = false;
7876
+ offset = 0;
7877
+ selectionNeedsScroll = true;
7878
+ }
7879
+ }
7880
+ } else if (data === "a" || data === "A") {
7347
7881
  actionMode = true;
7348
7882
  actionIndex = 0;
7349
7883
  offset = 0;
7350
- scrollActionIntoView();
7351
- requestRender();
7352
- return;
7353
- }
7354
- if (keybindings.matches(data, "tui.select.cancel")) {
7355
- if (actionMode) {
7356
- actionMode = false;
7357
- actionIndex = 0;
7358
- offset = 0;
7359
- requestRender();
7360
- } else close(void 0);
7361
- return;
7362
- }
7363
- if (!actionMode) {
7364
- if (keybindings.matches(data, "tui.select.confirm")) {
7884
+ } else if (matches(data, "tui.select.cancel")) {
7885
+ if (!narrow || !detailsMode) {
7886
+ close(void 0);
7887
+ return;
7888
+ }
7889
+ detailsMode = false;
7890
+ offset = 0;
7891
+ selectionNeedsScroll = true;
7892
+ } else if (narrow && detailsMode) {
7893
+ if (matches(data, "tui.select.up")) offset = Math.max(0, offset - 1);
7894
+ else if (matches(data, "tui.select.down")) offset += 1;
7895
+ else if (matches(data, "tui.select.confirm")) {
7365
7896
  actionMode = true;
7366
7897
  actionIndex = 0;
7367
7898
  offset = 0;
7368
- scrollActionIntoView();
7369
- requestRender();
7370
- } else if (keybindings.matches(data, "tui.select.up")) {
7371
- offset = Math.max(0, offset - 1);
7372
- clampOffset(detailLines(inspection, theme));
7373
- requestRender();
7374
- } else if (keybindings.matches(data, "tui.select.down")) {
7375
- offset += 1;
7376
- clampOffset(detailLines(inspection, theme));
7377
- requestRender();
7378
7899
  }
7379
- return;
7380
- }
7381
- const options = actionOptions(manager, inspection, context);
7382
- if (keybindings.matches(data, "tui.select.up")) {
7383
- actionIndex = (actionIndex + options.length - 1) % options.length;
7384
- scrollActionIntoView();
7385
- } else if (keybindings.matches(data, "tui.select.down")) {
7386
- actionIndex = (actionIndex + 1) % options.length;
7387
- scrollActionIntoView();
7388
- } else if (keybindings.matches(data, "tui.select.confirm")) {
7389
- const action = options[actionIndex];
7390
- if (action === "Steer") {
7391
- steerMode = true;
7392
- actionMode = false;
7393
- steerEditor.setText("");
7394
- offset = 0;
7395
- } else if (action && action !== "Back") runAction(action);
7396
- else actionMode = false;
7397
- } else if (keybindings.matches(data, "tui.select.pageUp")) {
7398
- offset = Math.max(0, offset - viewportRows());
7399
- clampOffset(actionView().rows);
7400
- } else if (keybindings.matches(data, "tui.select.pageDown")) {
7401
- offset += viewportRows();
7402
- clampOffset(actionView().rows);
7900
+ } else if (matches(data, "tui.select.up")) select(-1);
7901
+ else if (matches(data, "tui.select.down")) select(1);
7902
+ else if (matches(data, "tui.select.confirm")) {
7903
+ if (narrow) detailsMode = true;
7904
+ else actionMode = true;
7905
+ actionIndex = 0;
7906
+ offset = 0;
7403
7907
  }
7404
7908
  requestRender();
7405
7909
  },
7406
7910
  dispose() {
7407
- if (!disposed) {
7408
- disposed = true;
7409
- stopRefresh();
7410
- }
7911
+ shutDown();
7411
7912
  }
7412
7913
  };
7413
7914
  });
7414
- if (!result || result === "retry") return;
7915
+ if (result === void 0) return;
7415
7916
  try {
7416
- await steerSubagent(manager, storageDirectory3, entry, context, result.message);
7917
+ await steerSubagent(manager, storageDirectory3, result.entry, context, result.message);
7417
7918
  } catch (error) {
7418
7919
  context.ui.notify(`Cannot steer: ${errorText(error)}`, "warning");
7419
7920
  }
7420
- return "exit";
7421
7921
  }
7422
7922
  async function runNavigator(manager, storageDirectory3, args, context, clipboard) {
7423
7923
  if (args.trim()) {
@@ -7430,17 +7930,30 @@ async function runNavigator(manager, storageDirectory3, args, context, clipboard
7430
7930
  context.ui.notify("No durable subagent runs in this session.", "info");
7431
7931
  return;
7432
7932
  }
7933
+ const labels = pickerLabels(entries);
7433
7934
  if (!context.hasUI) {
7434
- context.ui.notify(entries.map((entry, index) => pickerLabel(entry, index)).join("\n"), "info");
7935
+ context.ui.notify(labels.join("\n"), "info");
7936
+ return;
7937
+ }
7938
+ if (context.mode === "tui") {
7939
+ await showDashboard(manager, storageDirectory3, entries, context, clipboard);
7435
7940
  return;
7436
7941
  }
7437
- const labels = entries.map(pickerLabel);
7438
- const choice = await context.ui.select("Subagents\n", [...labels, "Close"]);
7942
+ const choice = await context.ui.select("Subagents\n", [...labels, "Close", ...bulkDeleteLabels(manager, entries)]);
7439
7943
  if (!choice || choice === "Close") return;
7944
+ const bulk = BULK_DELETE[choice];
7945
+ if (bulk !== void 0) {
7946
+ try {
7947
+ await deleteAll(manager, storageDirectory3, context, bulk, (title, message) => context.ui.confirm(title, message));
7948
+ } catch (error) {
7949
+ context.ui.notify(`Cannot delete ${bulk} runs: ${errorText(error)}`, "warning");
7950
+ }
7951
+ continue;
7952
+ }
7440
7953
  const selected = entries[labels.indexOf(choice)];
7441
7954
  if (!selected) return;
7442
7955
  try {
7443
- if (await showDetail(manager, storageDirectory3, selected, context, clipboard) === "exit") return;
7956
+ await showDetail(manager, storageDirectory3, selected, context, clipboard);
7444
7957
  } catch (error) {
7445
7958
  context.ui.notify(`Cannot inspect subagent ${selected.status.id}: ${errorText(error)}`, "warning");
7446
7959
  }
@@ -7461,20 +7974,11 @@ function registerSubagentNavigator(registerCommand, manager, storageDirectory3,
7461
7974
 
7462
7975
  // subagents/src/view.ts
7463
7976
  import { truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
7464
- var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7977
+ var SPINNER2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7465
7978
  var TERMINAL_STATES = /* @__PURE__ */ new Set(["completed", "failed", "stopped"]);
7466
7979
  var MAX_INSPECT_VALUE_CHARS = 4e3;
7467
7980
  var MAX_INSPECT_TOOL_CALLS = 32;
7468
7981
  var MAX_BACKGROUND_WIDGET_ROWS = 10;
7469
- function textBlock2(text) {
7470
- return {
7471
- render(width) {
7472
- return text.split("\n").map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026"));
7473
- },
7474
- invalidate() {
7475
- }
7476
- };
7477
- }
7478
7982
  function roleName(value) {
7479
7983
  if (typeof value === "string") return value.trim() || void 0;
7480
7984
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -7516,49 +8020,23 @@ function statusValue2(value) {
7516
8020
  if (typeof record2.id !== "string" || record2.state !== "running" && record2.state !== "completed" && record2.state !== "failed" && record2.state !== "stopped") return void 0;
7517
8021
  return value;
7518
8022
  }
7519
- function runtime(startedAt, finishedAt, now) {
7520
- if (startedAt === void 0) return "";
7521
- const seconds = Math.max(0, Math.floor(((finishedAt ?? now) - startedAt) / 1e3));
7522
- if (seconds < 60) return `${String(seconds)}s`;
7523
- const minutes = Math.floor(seconds / 60);
7524
- const remainingSeconds = seconds % 60;
7525
- if (minutes < 60) return `${String(minutes)}m${remainingSeconds ? ` ${String(remainingSeconds)}s` : ""}`;
7526
- const hours = Math.floor(minutes / 60);
7527
- const remainingMinutes = minutes % 60;
7528
- return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
7529
- }
7530
- function stateGlyph(state, spinner) {
7531
- if (state === "running") return spinner;
8023
+ function stateGlyph(state, spinner2) {
8024
+ if (state === "running") return spinner2;
7532
8025
  return state === "completed" ? "\u2713" : "\u2717";
7533
8026
  }
7534
8027
  function stateColor(state) {
7535
8028
  if (state === "running") return "accent";
7536
8029
  return state === "completed" ? "success" : "error";
7537
8030
  }
7538
- function activity(status) {
7539
- if (status.state !== "running") return void 0;
7540
- const current = status.progress?.activity;
7541
- if (current?.kind === "reasoning" || current?.kind === "text") {
7542
- const name = current.kind === "reasoning" ? "reasoning" : "responding";
7543
- const text = sanitizeDisplayText(current.text);
7544
- return text && text !== name ? `${name} \xB7 ${text}` : name;
7545
- }
7546
- if (current?.kind === "tool") return sanitizeDisplayText(current.text);
7547
- return sanitizeDisplayText([...status.progress?.toolCalls ?? []].reverse().find(({ state }) => state === "running")?.name ?? "");
7548
- }
7549
- function stalledDuration2(status, now) {
7550
- const lastEventAt = status.progress?.lastEventAt;
7551
- if (status.state !== "running" || lastEventAt === void 0 || !Number.isFinite(lastEventAt)) return void 0;
7552
- const duration = now - lastEventAt;
7553
- return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : void 0;
7554
- }
7555
- function accounting2(status) {
8031
+ function usageStats(status) {
7556
8032
  const value = status.progress?.accounting;
7557
- if (!value) return void 0;
7558
- const total = value.input + value.output + value.cacheRead + value.cacheWrite;
7559
- return `tokens=${String(total)} cost=${formatCost(value.cost) || "$0.00"}`;
8033
+ return value ? [formatTokens(value.input + value.output), formatCost(value.cost)].filter(Boolean).join(" \xB7 ") : "";
7560
8034
  }
7561
- function timestamp2(value) {
8035
+ function modelName(status) {
8036
+ const model = status.progress?.state?.model ?? status.attemptDetails?.at(-1)?.setup.model;
8037
+ return model ? `${model.model}${model.thinking ? `:${model.thinking}` : ""}` : "";
8038
+ }
8039
+ function timestamp(value) {
7562
8040
  if (value === void 0 || !Number.isFinite(value) || value < 0) return void 0;
7563
8041
  const date = new Date(value);
7564
8042
  return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
@@ -7573,24 +8051,23 @@ function boundedValue(value) {
7573
8051
  }
7574
8052
  return text.length > MAX_INSPECT_VALUE_CHARS ? `${text.slice(0, MAX_INSPECT_VALUE_CHARS)}\u2026` : text;
7575
8053
  }
7576
- function formatSubagentProgress(status, args, theme, spinner, now, expanded) {
8054
+ function formatSubagentProgress(status, args, theme, spinner2, now, expanded) {
7577
8055
  const color = stateColor(status.state);
7578
- const elapsed = runtime(status.startedAt, status.finishedAt, now);
8056
+ const elapsed = status.startedAt === void 0 ? "" : formatWorkflowRuntime((status.finishedAt ?? now) - status.startedAt);
7579
8057
  const metadata = requestMetadata(args, args.id === void 0);
8058
+ const stats = usageStats(status);
7580
8059
  const lines = [
7581
- `${theme.fg(color, stateGlyph(status.state, spinner))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`
8060
+ `${theme.fg(color, stateGlyph(status.state, "\u25CF"))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${stats ? ` ${stats}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`
7582
8061
  ];
7583
- const current = activity(status);
7584
- const stalled = stalledDuration2(status, now);
7585
- if (current) lines.push(` ${theme.fg("accent", spinner)} ${theme.fg("dim", current)}${stalled === void 0 ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
8062
+ const current = status.state === "running" ? agentActivityLabel(status.progress ?? {}) : void 0;
8063
+ const stalled = stalledDuration({ ...status.progress, state: status.state }, now);
8064
+ if (current || status.state === "running") lines.push(` ${theme.fg("accent", spinner2)}${current ? ` ${theme.fg("dim", current)}` : ""}${stalled === void 0 ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
7586
8065
  else if (stalled !== void 0) lines.push(` ${theme.fg("warning", `stalled? ${formatStalledDuration(stalled)}`)}`);
7587
8066
  if (status.error) lines.push(` ${theme.fg("error", `${status.error.code}: ${status.error.message}`)}`);
7588
8067
  if (expanded) {
7589
8068
  lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
7590
8069
  const model = status.progress?.state?.model;
7591
8070
  if (model) lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
7592
- const usage = accounting2(status);
7593
- if (usage) lines.push(` ${theme.fg("dim", usage)}`);
7594
8071
  if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
7595
8072
  }
7596
8073
  return lines.join("\n");
@@ -7607,9 +8084,9 @@ function formatSubagentInspection(status, args, details, theme, expanded) {
7607
8084
  const lines = [formatSubagentProgress(status, { id: args.id ?? status.id }, theme, "\u25C7", Date.now(), false)];
7608
8085
  if (!expanded) return lines[0] ?? "";
7609
8086
  lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
7610
- const startedAt = timestamp2(status.startedAt);
7611
- const finishedAt = timestamp2(status.finishedAt);
7612
- const lastEventAt = timestamp2(status.progress?.lastEventAt);
8087
+ const startedAt = timestamp(status.startedAt);
8088
+ const finishedAt = timestamp(status.finishedAt);
8089
+ const lastEventAt = timestamp(status.progress?.lastEventAt);
7613
8090
  if (startedAt) lines.push(` ${theme.fg("dim", `startedAt=${startedAt}`)}`);
7614
8091
  if (finishedAt) lines.push(` ${theme.fg("dim", `finishedAt=${finishedAt}`)}`);
7615
8092
  if (lastEventAt) lines.push(` ${theme.fg("dim", `lastEventAt=${lastEventAt}`)}`);
@@ -7617,8 +8094,8 @@ function formatSubagentInspection(status, args, details, theme, expanded) {
7617
8094
  if (model) lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
7618
8095
  const currentActivity = status.progress?.activity;
7619
8096
  if (currentActivity) lines.push(` ${theme.fg("dim", `activity=${currentActivity.kind}${currentActivity.text ? ` ${sanitizeDisplayText(currentActivity.text)}` : ""}`)}`);
7620
- const accounting3 = status.progress?.accounting;
7621
- if (accounting3) lines.push(` ${theme.fg("dim", `accounting=input=${String(accounting3.input)} output=${String(accounting3.output)} cacheRead=${String(accounting3.cacheRead)} cacheWrite=${String(accounting3.cacheWrite)} cost=${formatCost(accounting3.cost) || "$0.00"}`)}`);
8097
+ const accounting2 = status.progress?.accounting;
8098
+ if (accounting2) lines.push(` ${theme.fg("dim", `accounting=input=${String(accounting2.input)} output=${String(accounting2.output)} cacheRead=${String(accounting2.cacheRead)} cacheWrite=${String(accounting2.cacheWrite)} cost=${formatCost(accounting2.cost) || "$0.00"}`)}`);
7622
8099
  if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
7623
8100
  const toolCalls = status.progress?.toolCalls;
7624
8101
  if (toolCalls?.length) {
@@ -7650,7 +8127,7 @@ function subagentProgressBlock(status, args, theme, freezeAt) {
7650
8127
  render(width) {
7651
8128
  const now = TERMINAL_STATES.has(current.state) ? frozenAt : Date.now();
7652
8129
  if (!TERMINAL_STATES.has(current.state)) frozenAt = now;
7653
- const frame = SPINNER[Math.floor(now / 80) % SPINNER.length] ?? "\u25C7";
8130
+ const frame = SPINNER2[Math.floor(now / REPAINT_MS) % SPINNER2.length] ?? "\u25C7";
7654
8131
  return formatSubagentProgress(current, args, currentTheme, frame, now, expanded).split("\n").map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026"));
7655
8132
  },
7656
8133
  invalidate() {
@@ -7668,14 +8145,14 @@ function renderSubagentResult(result, options, theme, context) {
7668
8145
  if (status?.state === "running" && options.isPartial && !state.subagentSpinner) {
7669
8146
  state.subagentSpinner = setInterval(() => {
7670
8147
  context.invalidate();
7671
- }, 80);
8148
+ }, REPAINT_MS);
7672
8149
  state.subagentSpinner.unref();
7673
8150
  } else if ((!options.isPartial || status?.state !== "running") && state.subagentSpinner) {
7674
8151
  clearInterval(state.subagentSpinner);
7675
8152
  delete state.subagentSpinner;
7676
8153
  }
7677
- if (!status || context.isError) return textBlock2(resultText(result));
7678
- if (!options.isPartial && status.state === "running") return textBlock2(formatSubagentLaunch(status, context.args, theme, options.expanded));
8154
+ if (!status || context.isError) return textBlock(resultText(result));
8155
+ if (!options.isPartial && status.state === "running") return textBlock(formatSubagentLaunch(status, context.args, theme, options.expanded));
7679
8156
  if (TERMINAL_STATES.has(status.state)) state.subagentProgressFrozenAt ??= Date.now();
7680
8157
  else delete state.subagentProgressFrozenAt;
7681
8158
  let component = state.subagentProgressComponent;
@@ -7688,9 +8165,68 @@ function renderSubagentResult(result, options, theme, context) {
7688
8165
  component.setExpanded(options.expanded);
7689
8166
  return component;
7690
8167
  }
7691
- function createSubagentBackgroundWidget() {
8168
+ function widgetRow({ status, request }, now, paint) {
8169
+ const lastEventAt = status.progress?.lastEventAt;
8170
+ const silent = lastEventAt === void 0 || !Number.isFinite(lastEventAt) || now - lastEventAt < QUIET_MS ? void 0 : now - lastEventAt;
8171
+ const role = silent !== void 0 && silent >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? "error" : "warning";
8172
+ const icon = silent === void 0 ? mark(status.state, now, paint) : paint(role, "\u26A0");
8173
+ const warning = silent === void 0 ? "" : ` ${paint(role, `${role === "error" ? "stalled" : "quiet"} ${formatElapsed(silent)}`)}`;
8174
+ const accounting2 = status.progress?.accounting;
8175
+ const attempts = status.attempts ?? 1;
8176
+ const right = [
8177
+ modelName(status),
8178
+ formatTokens(accounting2 === void 0 ? void 0 : accounting2.input + accounting2.output),
8179
+ formatCost(accounting2?.cost),
8180
+ status.startedAt === void 0 ? "" : formatElapsed(now - status.startedAt),
8181
+ attempts > 1 ? `attempt ${String(attempts)}` : ""
8182
+ ].filter(Boolean).join(" \xB7 ");
8183
+ return { text: `${icon} ${sanitizeDisplayText(label({ ...request, id: status.id }))}${warning}`, right };
8184
+ }
8185
+ var RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
8186
+ function receiptFor(status, request) {
8187
+ const accounting2 = status.progress?.accounting;
8188
+ const model = modelName(status);
8189
+ const role = roleName(request.role);
8190
+ const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
8191
+ return {
8192
+ id: status.id,
8193
+ label: label({ ...request, id: status.id }),
8194
+ state: status.state,
8195
+ ...model ? { model } : {},
8196
+ ...role === void 0 ? {} : { role },
8197
+ ...tools === void 0 ? {} : { tools: [...tools] },
8198
+ input: accounting2?.input ?? 0,
8199
+ output: accounting2?.output ?? 0,
8200
+ cacheRead: accounting2?.cacheRead ?? 0,
8201
+ costUsd: accounting2?.cost ?? 0,
8202
+ durationMs: status.startedAt === void 0 ? 0 : Math.max(0, (status.finishedAt ?? Date.now()) - status.startedAt),
8203
+ attempts: status.attempts ?? 1,
8204
+ ...status.error === void 0 ? {} : { error: `${status.error.code}: ${status.error.message}` }
8205
+ };
8206
+ }
8207
+ function renderSubagentReceipt(data, expanded, theme) {
8208
+ const glyph = data.state === "completed" ? theme.fg("success", "\u2713") : data.state === "failed" ? theme.fg("error", "\u2717") : theme.fg("muted", "\xB7");
8209
+ const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" \xB7 ");
8210
+ const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
8211
+ 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);
8213
+ if (meta.length > 0) lines.push(theme.fg("muted", ` ${meta.join(" \xB7 ")}`));
8214
+ const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
8215
+ if (split.length > 0) lines.push(theme.fg("muted", ` ${split.join(" \xB7 ")}`));
8216
+ if (data.error !== void 0) lines.push(theme.fg("error", ` ${sanitizeDisplayText(data.error)}`));
8217
+ lines.push(theme.fg("muted", ` run ${data.id}`));
8218
+ return lines;
8219
+ }
8220
+ function createSubagentBackgroundWidget(host = {}) {
7692
8221
  const key = "piewf-subagents-background";
7693
8222
  const runs = /* @__PURE__ */ new Map();
8223
+ const receipted = /* @__PURE__ */ new Set();
8224
+ host.registerEntryRenderer?.(RECEIPT_ENTRY_TYPE, (entry, options, theme) => {
8225
+ if (!entry.data) return void 0;
8226
+ const lines = renderSubagentReceipt(entry.data, options.expanded, theme);
8227
+ return { render: (width) => lines.map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026")), invalidate() {
8228
+ } };
8229
+ });
7694
8230
  let context;
7695
8231
  let requestRender;
7696
8232
  let timer;
@@ -7724,23 +8260,12 @@ function createSubagentBackgroundWidget() {
7724
8260
  return {
7725
8261
  render(width) {
7726
8262
  const now = Date.now();
7727
- const frame = SPINNER[Math.floor(now / 80) % SPINNER.length] ?? "\u25C7";
7728
- const title = theme.bold(theme.fg("accent", `Subagents (${String(runs.size)} running)`));
7729
- const blocks = [...runs.values()].map(({ status, request }) => formatSubagentProgress(status, request, theme, frame, now, false).split("\n"));
7730
- const allRunsFit = blocks.reduce((rows, block) => rows + block.length, 1) <= MAX_BACKGROUND_WIDGET_ROWS;
7731
- const runRowBudget = allRunsFit ? MAX_BACKGROUND_WIDGET_ROWS - 1 : MAX_BACKGROUND_WIDGET_ROWS - 2;
7732
- const lines = [title];
7733
- let runRows = 0;
7734
- let displayedRuns = 0;
7735
- for (const block of blocks) {
7736
- if (runRows + block.length > runRowBudget) break;
7737
- lines.push(...block);
7738
- runRows += block.length;
7739
- displayedRuns += 1;
7740
- }
7741
- const hiddenRuns = blocks.length - displayedRuns;
7742
- if (hiddenRuns > 0) lines.push(`\u2026 ${String(hiddenRuns)} more`);
7743
- return lines.map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026"));
8263
+ const paintRole = (role, text) => theme.fg(role, text);
8264
+ const budget = MAX_BACKGROUND_WIDGET_ROWS - 2;
8265
+ const rows = [...runs.values()].map((run) => widgetRow(run, now, paintRole));
8266
+ const body = rows.length <= budget ? rows : [...rows.slice(0, budget - 1), { text: theme.fg("muted", `\u2026 ${String(rows.length - budget + 1)} more`) }];
8267
+ const title = runs.size === 1 ? "Subagent" : `Subagents \xB7 ${String(runs.size)} runs`;
8268
+ return drawFrame(title, "", body, width, paintRole, (text) => theme.fg("accent", text)).map((line) => truncateToWidth4(line, Math.max(1, width), ""));
7744
8269
  },
7745
8270
  invalidate() {
7746
8271
  }
@@ -7749,7 +8274,7 @@ function createSubagentBackgroundWidget() {
7749
8274
  showing = true;
7750
8275
  timer = setInterval(() => {
7751
8276
  requestRender?.();
7752
- }, 80);
8277
+ }, REPAINT_MS);
7753
8278
  timer.unref();
7754
8279
  } catch {
7755
8280
  hide();
@@ -7759,12 +8284,21 @@ function createSubagentBackgroundWidget() {
7759
8284
  start(next) {
7760
8285
  hide();
7761
8286
  runs.clear();
8287
+ receipted.clear();
7762
8288
  context = next.mode === "tui" ? next : void 0;
7763
8289
  },
7764
8290
  update(status, request) {
7765
8291
  if (request.mode !== "background" || context === void 0) return;
7766
- if (TERMINAL_STATES.has(status.state)) runs.delete(status.id);
7767
- else runs.set(status.id, { status, request });
8292
+ if (TERMINAL_STATES.has(status.state)) {
8293
+ runs.delete(status.id);
8294
+ if (!receipted.has(status.id)) {
8295
+ receipted.add(status.id);
8296
+ try {
8297
+ host.appendEntry?.(RECEIPT_ENTRY_TYPE, receiptFor(status, request));
8298
+ } catch {
8299
+ }
8300
+ }
8301
+ } else runs.set(status.id, { status, request });
7768
8302
  paint();
7769
8303
  },
7770
8304
  dispose() {
@@ -7776,35 +8310,35 @@ function createSubagentBackgroundWidget() {
7776
8310
  }
7777
8311
  function renderSubagentInspectCall(args, theme) {
7778
8312
  const title = theme.fg("toolTitle", theme.bold("subagents_inspect"));
7779
- return textBlock2(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", "all")}`);
8313
+ return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", args.scope === "all" ? "all" : "session")}`);
7780
8314
  }
7781
8315
  function renderSubagentInspectResult(result, options, theme, args) {
7782
8316
  const status = statusValue2(result.details);
7783
- if (status) return textBlock2(formatSubagentInspection(status, args, result.details, theme, options.expanded));
8317
+ if (status) return textBlock(formatSubagentInspection(status, args, result.details, theme, options.expanded));
7784
8318
  if (Array.isArray(result.details)) {
7785
8319
  const statuses = result.details.map(statusValue2).filter((value) => value !== void 0);
7786
8320
  const lines = [theme.bold(theme.fg("accent", `Subagents (${String(statuses.length)})`)), ...statuses.map((entry) => {
7787
8321
  const color = stateColor(entry.state);
7788
8322
  return ` ${theme.fg(color, stateGlyph(entry.state, "\u25C7"))} ${entry.id.slice(0, 8)} ${theme.fg(color, `[${entry.state}]`)}`;
7789
8323
  })];
7790
- return textBlock2(lines.join("\n"));
8324
+ return textBlock(lines.join("\n"));
7791
8325
  }
7792
- return textBlock2(resultText(result));
8326
+ return textBlock(resultText(result));
7793
8327
  }
7794
8328
  function renderSubagentControlCall(name, args, theme) {
7795
8329
  const title = theme.fg("toolTitle", theme.bold(name));
7796
8330
  const message = args.message ? ` ${theme.fg("dim", args.message)}` : "";
7797
- return textBlock2(`${title} ${theme.fg("accent", args.id)}${message}`);
8331
+ return textBlock(`${title} ${theme.fg("accent", args.id)}${message}`);
7798
8332
  }
7799
8333
  function renderSubagentControlResult(result, theme) {
7800
8334
  const status = statusValue2(result.details);
7801
8335
  if (status) {
7802
8336
  const color = stateColor(status.state);
7803
- return textBlock2(`${theme.fg(color, stateGlyph(status.state, "\u25C7"))} ${status.id.slice(0, 8)} ${theme.fg(color, `[${status.state}]`)}`);
8337
+ return textBlock(`${theme.fg(color, stateGlyph(status.state, "\u25C7"))} ${status.id.slice(0, 8)} ${theme.fg(color, `[${status.state}]`)}`);
7804
8338
  }
7805
8339
  const record2 = typeof result.details === "object" && result.details !== null && !Array.isArray(result.details) ? result.details : void 0;
7806
- if (record2 && typeof record2.id === "string") return textBlock2(`${theme.fg("success", "\u2713")} ${record2.id.slice(0, 8)}`);
7807
- return textBlock2(resultText(result));
8340
+ if (record2 && typeof record2.id === "string") return textBlock(`${theme.fg("success", "\u2713")} ${record2.id.slice(0, 8)}`);
8341
+ return textBlock(resultText(result));
7808
8342
  }
7809
8343
 
7810
8344
  // subagents/src/index.ts
@@ -7833,7 +8367,7 @@ function serialize(value) {
7833
8367
  }
7834
8368
  }
7835
8369
  function toolResult(value) {
7836
- return { content: [{ type: "text", text: serialize(value) }], details: value };
8370
+ return { content: [{ type: "text", text: serialize(value) }], details: value, structuredContent: value };
7837
8371
  }
7838
8372
  function managerContext2(toolCallId, signal, onUpdate, context) {
7839
8373
  return {
@@ -7857,6 +8391,7 @@ function createSubagentTools(manager) {
7857
8391
  "Do not poll a running ID; use subagents_inspect({ id }) when you need its current status or terminal result."
7858
8392
  ],
7859
8393
  parameters: SUBAGENTS_RUN_PARAMETERS,
8394
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
7860
8395
  async execute(toolCallId, params, signal, onUpdate, context) {
7861
8396
  return toolResult(await manager.run(validateSubagentRunRequest(params), managerContext2(toolCallId, signal, onUpdate, context)));
7862
8397
  },
@@ -7870,8 +8405,9 @@ function createSubagentTools(manager) {
7870
8405
  defineTool2({
7871
8406
  name: "subagents_inspect",
7872
8407
  label: "Subagents Inspect",
7873
- description: "Inspect durable subagent runs. Omit id for ordered run summaries, or provide id for detailed status, progress, activity, accounting, tool calls, timestamps, worktree metadata, and a terminal value or error when available.",
8408
+ description: "Inspect durable subagent runs. Omit id for ordered run summaries of the current session (scope all for every session), or provide id for detailed status, progress, activity, accounting, tool calls, timestamps, worktree metadata, and a terminal value or error when available.",
7874
8409
  parameters: SUBAGENTS_INSPECT_PARAMETERS,
8410
+ outputSchema: SUBAGENTS_INSPECT_OUTPUT,
7875
8411
  async execute(toolCallId, params, signal, onUpdate, context) {
7876
8412
  return toolResult(await manager.inspect(validateSubagentInspectRequest(params), managerContext2(toolCallId, signal, onUpdate, context)));
7877
8413
  },
@@ -7887,6 +8423,7 @@ function createSubagentTools(manager) {
7887
8423
  label: "Subagents Steer",
7888
8424
  description: "Send a message to a running subagent. The message is queued safely if its steering handler is not ready; settled runs cannot be steered.",
7889
8425
  parameters: SUBAGENTS_STEER_PARAMETERS,
8426
+ outputSchema: SUBAGENTS_STEER_OUTPUT,
7890
8427
  async execute(toolCallId, params, signal, onUpdate, context) {
7891
8428
  return toolResult(await manager.steer(validateSubagentSteerRequest(params), managerContext2(toolCallId, signal, onUpdate, context)));
7892
8429
  },
@@ -7902,6 +8439,7 @@ function createSubagentTools(manager) {
7902
8439
  label: "Subagents Stop",
7903
8440
  description: 'Stop one running subagent, abort its active session, persist state "stopped", and clean its worktree without affecting sibling runs.',
7904
8441
  parameters: SUBAGENTS_STOP_PARAMETERS,
8442
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
7905
8443
  async execute(toolCallId, params, signal, onUpdate, context) {
7906
8444
  return toolResult(await manager.stop(validateSubagentIdRequest(params, "subagents_stop"), managerContext2(toolCallId, signal, onUpdate, context)));
7907
8445
  },
@@ -7917,6 +8455,7 @@ function createSubagentTools(manager) {
7917
8455
  label: "Subagents Retry",
7918
8456
  description: "Start a fresh run from a failed or stopped subagent's persisted request. The new run gets a new ID and preserves its original background or foreground mode.",
7919
8457
  parameters: SUBAGENTS_RETRY_PARAMETERS,
8458
+ outputSchema: SUBAGENT_STATUS_OUTPUT,
7920
8459
  async execute(toolCallId, params, signal, onUpdate, context) {
7921
8460
  return toolResult(await manager.retry(validateSubagentIdRequest(params, "subagents_retry"), managerContext2(toolCallId, signal, onUpdate, context)));
7922
8461
  },
@@ -7949,7 +8488,7 @@ function managerDependencies(options, activeTools, notify, onStatus, onResourceW
7949
8488
  }
7950
8489
  function storageDirectory2(options) {
7951
8490
  const dependencies = options.managerDependencies;
7952
- return dependencies?.storageDir ?? join10(dependencies?.agentDir ?? getAgentDir7(), "subagents");
8491
+ return dependencies?.storageDir ?? join11(dependencies?.agentDir ?? getAgentDir8(), "subagents");
7953
8492
  }
7954
8493
  function createSubagentsExtension(options = {}, activeTools, notify, onStatus, onResourceWarning) {
7955
8494
  const manager = options.manager ?? createSubagentManager(managerDependencies(options, activeTools, notify, onStatus, onResourceWarning));
@@ -7958,7 +8497,8 @@ function createSubagentsExtension(options = {}, activeTools, notify, onStatus, o
7958
8497
  }
7959
8498
  function registerSubagentsExtension(pi, options = {}) {
7960
8499
  const getActiveTools = pi.getActiveTools;
7961
- const activeTools = getActiveTools === void 0 ? void 0 : () => getActiveTools.call(pi);
8500
+ const getAllTools = pi.getAllTools;
8501
+ const activeTools = getActiveTools === void 0 ? void 0 : () => reachableTools({ getActiveTools: () => getActiveTools.call(pi), ...getAllTools === void 0 ? {} : { getAllTools: () => getAllTools.call(pi) } });
7962
8502
  const sendMessage = pi.sendMessage;
7963
8503
  const notify = sendMessage === void 0 ? void 0 : (notification) => {
7964
8504
  sendMessage.call(pi, { customType: "subagents", content: notificationContent(notification), display: true, details: notification }, { deliverAs: "steer", triggerTurn: true });
@@ -7966,13 +8506,17 @@ function registerSubagentsExtension(pi, options = {}) {
7966
8506
  const onResourceWarning = sendMessage === void 0 ? void 0 : (message) => {
7967
8507
  sendMessage.call(pi, { customType: "subagents", content: `Warning: ${message}`, display: true }, { deliverAs: "steer" });
7968
8508
  };
7969
- const widget2 = createSubagentBackgroundWidget();
8509
+ const appendEntry = pi.appendEntry;
8510
+ const widget2 = createSubagentBackgroundWidget({ ...appendEntry === void 0 ? {} : { appendEntry: (customType, data) => {
8511
+ appendEntry.call(pi, customType, data);
8512
+ } }, ...pi.registerEntryRenderer === void 0 ? {} : { registerEntryRenderer: pi.registerEntryRenderer.bind(pi) } });
7970
8513
  const extension2 = createSubagentsExtension(options, activeTools, notify, (status, request) => {
7971
8514
  widget2.update(status, request);
7972
8515
  const registry = loadingRegistry();
7973
8516
  if (typeof registry.observeSubagentStatus === "function") registry.observeSubagentStatus(status, request);
7974
8517
  }, onResourceWarning);
7975
- for (const tool of extension2.tools) pi.registerTool(tool);
8518
+ const codemodeTools = loadCodemodeToolsSetting(options.managerDependencies?.agentDir);
8519
+ for (const tool of extension2.tools) pi.registerTool({ ...tool, ...workflowToolExposure(tool.name, codemodeTools) });
7976
8520
  if (pi.registerCommand !== void 0) registerSubagentNavigator(pi.registerCommand.bind(pi), extension2.manager, storageDirectory2(options), options.clipboard);
7977
8521
  if (pi.on !== void 0) {
7978
8522
  setSubagentManager(extension2.manager);
@@ -7995,15 +8539,17 @@ function extension(pi, options = {}) {
7995
8539
  }
7996
8540
  export {
7997
8541
  SUBAGENTS_ID_PARAMETERS,
8542
+ SUBAGENTS_INSPECT_OUTPUT,
7998
8543
  SUBAGENTS_INSPECT_PARAMETERS,
7999
8544
  SUBAGENTS_RETRY_PARAMETERS,
8000
8545
  SUBAGENTS_RUN_PARAMETERS,
8546
+ SUBAGENTS_STEER_OUTPUT,
8001
8547
  SUBAGENTS_STEER_PARAMETERS,
8002
8548
  SUBAGENTS_STOP_PARAMETERS,
8003
8549
  SUBAGENTS_TOOL_NAMES,
8004
- SUBAGENTS_TOOL_SCHEMAS,
8005
8550
  SUBAGENT_ATTEMPT_DETAILS_LIMIT,
8006
8551
  SUBAGENT_MAX_RETRIES,
8552
+ SUBAGENT_STATUS_OUTPUT,
8007
8553
  SUBAGENT_SYSTEM_PROMPT_LIMIT,
8008
8554
  attemptValue,
8009
8555
  createRunStoreWorktreeAdapter,