@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.17.0-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 (71) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/src/agent-execution.js +36 -51
  3. package/dist/src/background-widget.d.ts +56 -0
  4. package/dist/src/background-widget.js +22 -15
  5. package/dist/src/decoders.js +2 -1
  6. package/dist/src/host-navigator.d.ts +18 -0
  7. package/dist/src/host-navigator.js +71 -13
  8. package/dist/src/host-view.d.ts +16 -1
  9. package/dist/src/host-view.js +109 -42
  10. package/dist/src/host.js +57 -24
  11. package/dist/src/index.d.ts +3 -1
  12. package/dist/src/index.js +867 -545
  13. package/dist/src/index.js.map +3 -3
  14. package/dist/src/registry.js +2 -1
  15. package/dist/src/roles.d.ts +70 -0
  16. package/dist/src/roles.js +325 -0
  17. package/dist/src/settings.d.ts +13 -0
  18. package/dist/src/settings.js +253 -0
  19. package/dist/src/trajectory.d.ts +29 -4
  20. package/dist/src/trajectory.js +58 -3
  21. package/dist/src/types.d.ts +0 -1
  22. package/dist/src/utils.d.ts +2 -1
  23. package/dist/src/utils.js +10 -0
  24. package/dist/src/validation.d.ts +5 -17
  25. package/dist/src/validation.js +9 -458
  26. package/dist/subagents/index.js +1132 -694
  27. package/dist/subagents/index.js.map +3 -3
  28. package/dist/subagents/src/contracts.d.ts +4 -0
  29. package/dist/subagents/src/index.d.ts +1 -1
  30. package/dist/subagents/src/index.js +2 -1
  31. package/dist/subagents/src/manager.js +29 -10
  32. package/dist/subagents/src/navigator.js +396 -247
  33. package/dist/subagents/src/view.d.ts +23 -2
  34. package/dist/subagents/src/view.js +96 -30
  35. package/dist/trajectory/assets/index.html +347 -231
  36. package/dist/trajectory/assets/prism.min.js +13 -0
  37. package/dist/trajectory/index.js +377 -257
  38. package/dist/trajectory/index.js.map +3 -3
  39. package/dist/trajectory/src/assets/index.html +347 -231
  40. package/dist/trajectory/src/assets/prism.min.js +13 -0
  41. package/dist/trajectory/src/export.js +4 -2
  42. package/dist/trajectory/src/index.d.ts +2 -0
  43. package/dist/trajectory/src/index.js +143 -42
  44. package/dist/trajectory/src/server.js +134 -9
  45. package/dist/trajectory/src/server.js.map +1 -1
  46. package/package.json +2 -1
  47. package/src/agent-execution.ts +36 -42
  48. package/src/background-widget.ts +25 -18
  49. package/src/decoders.ts +2 -1
  50. package/src/host-navigator.ts +58 -14
  51. package/src/host-view.ts +93 -37
  52. package/src/host.ts +51 -21
  53. package/src/index.ts +3 -1
  54. package/src/registry.ts +2 -1
  55. package/src/roles.ts +326 -0
  56. package/src/settings.ts +206 -0
  57. package/src/trajectory.ts +76 -7
  58. package/src/types.ts +1 -1
  59. package/src/utils.ts +8 -1
  60. package/src/validation.ts +12 -356
  61. package/subagents/README.md +1 -1
  62. package/subagents/src/contracts.ts +4 -0
  63. package/subagents/src/index.ts +3 -2
  64. package/subagents/src/manager.ts +28 -10
  65. package/subagents/src/navigator.ts +336 -180
  66. package/subagents/src/view.ts +97 -28
  67. package/trajectory/src/assets/index.html +347 -231
  68. package/trajectory/src/assets/prism.min.js +13 -0
  69. package/trajectory/src/export.ts +4 -2
  70. package/trajectory/src/index.ts +118 -38
  71. package/trajectory/src/server.ts +112 -12
@@ -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
@@ -328,6 +328,12 @@ function unmatchedResourcePatterns(patterns, resources) {
328
328
  function loadLaunchSnapshot(input) {
329
329
  return deepFreeze(structuredClone(input));
330
330
  }
331
+ function validateSchema(schema, at = "schema") {
332
+ if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
333
+ 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`);
334
+ 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`);
335
+ if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
336
+ }
331
337
 
332
338
  // src/paths.ts
333
339
  import { createHash } from "node:crypto";
@@ -372,6 +378,20 @@ function budgetUsage(value) {
372
378
  return { tokens: value?.tokens ?? 0, costUsd: value?.costUsd ?? 0, durationMs: value?.durationMs ?? 0, agentLaunches: value?.agentLaunches ?? 0 };
373
379
  }
374
380
 
381
+ // src/validation.ts
382
+ import * as acorn from "acorn";
383
+
384
+ // src/roles.ts
385
+ import { readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
386
+ import { basename as basename3, dirname as dirname4, extname, join as join5, resolve as resolve4 } from "node:path";
387
+ import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
388
+
389
+ // src/registry.ts
390
+ import { readFileSync as readFileSync2 } from "node:fs";
391
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join4 } from "node:path";
392
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
393
+ import { Value } from "typebox/value";
394
+
375
395
  // src/io.ts
376
396
  import { execFile } from "node:child_process";
377
397
  import { randomUUID } from "node:crypto";
@@ -392,7 +412,7 @@ async function renameWithRetry(from, to) {
392
412
  } catch (error) {
393
413
  const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
394
414
  if (!retryableRenameError(error) || delayMs === void 0) throw error;
395
- await new Promise((resolve5) => setTimeout(resolve5, delayMs));
415
+ await new Promise((resolve6) => setTimeout(resolve6, delayMs));
396
416
  }
397
417
  }
398
418
  }
@@ -593,7 +613,7 @@ function decodeWorkflowExtensions(value) {
593
613
  }
594
614
  if (namespace === "trajectory") {
595
615
  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 } };
616
+ normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port } };
597
617
  continue;
598
618
  }
599
619
  if (!jsonValue(raw)) return void 0;
@@ -775,9 +795,9 @@ function decodeAgentAttempt(value) {
775
795
  const session = value.session === void 0 ? void 0 : decodeAgentSession(value.session);
776
796
  const setup = decodeAgentSetupSummary(value.setup);
777
797
  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 } };
798
+ const accounting2 = decodeAgentAccounting(value.accounting);
799
+ if (!setup || !accounting2 || value.session !== void 0 && !session || value.error !== void 0 && !error) return void 0;
800
+ return { attempt: value.attempt, transport: value.transport, setup, accounting: accounting2, ...session === void 0 ? {} : { session }, ...error === void 0 ? {} : { error } };
781
801
  }
782
802
  function decodeAgent(value) {
783
803
  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 +818,12 @@ function decodeAgent(value) {
798
818
  const startedAt = optionalNumber(value.startedAt);
799
819
  const durationMs = optionalNumber(value.durationMs);
800
820
  const attemptDetails = value.attemptDetails === void 0 ? void 0 : decodeArray(value.attemptDetails, decodeAgentAttempt);
801
- const accounting3 = value.accounting === void 0 ? void 0 : decodeAgentAccounting(value.accounting);
821
+ const accounting2 = value.accounting === void 0 ? void 0 : decodeAgentAccounting(value.accounting);
802
822
  const toolCalls = value.toolCalls === void 0 ? void 0 : decodeArray(value.toolCalls, decodeAgentToolCall);
803
823
  const activity2 = value.activity === void 0 ? void 0 : decodeAgentActivity(value.activity);
804
824
  const lastEventAt = optionalNumber(value.lastEventAt);
805
825
  if (systemPrompt === INVALID_PERSISTED_VALUE || prompt === INVALID_PERSISTED_VALUE || label2 === INVALID_PERSISTED_VALUE || parentId === INVALID_PERSISTED_VALUE || resultPath2 === INVALID_PERSISTED_VALUE || parentBreadcrumb === INVALID_PERSISTED_VALUE || worktreeOwner === INVALID_PERSISTED_VALUE || role === INVALID_PERSISTED_VALUE || requestedModel === INVALID_PERSISTED_VALUE || startedAt === INVALID_PERSISTED_VALUE || durationMs === INVALID_PERSISTED_VALUE || lastEventAt === INVALID_PERSISTED_VALUE || handle === INVALID_PERSISTED_VALUE || continuity === INVALID_PERSISTED_VALUE || handle !== void 0 && !positiveInteger(value.turn)) return void 0;
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;
826
+ if (!model || !tools || value.structuralPath !== void 0 && !structuralPath2 || value.attemptDetails !== void 0 && !attemptDetails || value.accounting !== void 0 && !accounting2 || value.toolCalls !== void 0 && !toolCalls || value.activity !== void 0 && !activity2) return void 0;
807
827
  return {
808
828
  ...systemPrompt === void 0 ? {} : { systemPrompt },
809
829
  ...prompt === void 0 ? {} : { prompt },
@@ -827,7 +847,7 @@ function decodeAgent(value) {
827
847
  ...startedAt === void 0 ? {} : { startedAt },
828
848
  ...durationMs === void 0 ? {} : { durationMs },
829
849
  ...attemptDetails === void 0 ? {} : { attemptDetails },
830
- ...accounting3 === void 0 ? {} : { accounting: accounting3 },
850
+ ...accounting2 === void 0 ? {} : { accounting: accounting2 },
831
851
  ...toolCalls === void 0 ? {} : { toolCalls },
832
852
  ...activity2 === void 0 ? {} : { activity: activity2 },
833
853
  ...lastEventAt === void 0 ? {} : { lastEventAt }
@@ -873,9 +893,9 @@ function decodeShellActivity(value) {
873
893
  }
874
894
  function decodeRunEvent(value) {
875
895
  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 } };
896
+ const timestamp2 = optionalNumber(value.timestamp);
897
+ if (timestamp2 === INVALID_PERSISTED_VALUE) return void 0;
898
+ return { type: value.type, message: value.message, ...timestamp2 === void 0 ? {} : { timestamp: timestamp2 } };
879
899
  }
880
900
  function decodeDelivery(value) {
881
901
  if (!object(value)) return void 0;
@@ -1920,19 +1940,220 @@ var RunStore = class _RunStore {
1920
1940
  }
1921
1941
  };
1922
1942
 
1923
- // src/validation.ts
1924
- import { mkdirSync, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
1943
+ // src/settings.ts
1944
+ import { mkdirSync, readFileSync } from "node:fs";
1925
1945
  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";
1946
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
1947
+ import { fileURLToPath } from "node:url";
1948
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
1949
+ var ROLE_DIRECTORY = "pi-extensible-workflows";
1950
+ var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
1951
+ function workflowSettingsPath(agentDir = getAgentDir()) {
1952
+ return join3(agentDir, ROLE_DIRECTORY, "settings.json");
1953
+ }
1954
+ function normalizedResourcePath(value, settingsPath) {
1955
+ if (value === "*") return value;
1956
+ let expanded = value === "~" ? homedir3() : value.startsWith("~/") || value.startsWith("~\\") ? join3(homedir3(), value.slice(2)) : value;
1957
+ if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
1958
+ const resolved = resolve3(dirname2(settingsPath), expanded);
1959
+ if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
1960
+ if (resourcePatternHasMagic(expanded)) {
1961
+ const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
1962
+ const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
1963
+ const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
1964
+ const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
1965
+ const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
1966
+ return `${canonicalPath(prefix)}${suffix}`;
1967
+ }
1968
+ return canonicalPath(resolved);
1969
+ }
1970
+ function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
1971
+ if (value === void 0) return void 0;
1972
+ if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
1973
+ const normalized = [];
1974
+ for (const [index, entry] of value.entries()) {
1975
+ if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
1976
+ let selector = entry.trim();
1977
+ if (kind === "extensions" && normalizeExtensions) {
1978
+ const negated = selector.startsWith("!");
1979
+ const body = negated ? selector.slice(1) : selector;
1980
+ if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
1981
+ try {
1982
+ selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
1983
+ } catch (error) {
1984
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
1985
+ }
1986
+ }
1987
+ try {
1988
+ validateResourcePattern(selector);
1989
+ } catch (error) {
1990
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
1991
+ }
1992
+ normalized.push(selector);
1993
+ }
1994
+ return Object.freeze(normalized);
1995
+ }
1996
+ function validateWorktreePostCreateCommand(value, settingsPath) {
1997
+ if (value === void 0) return void 0;
1998
+ if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
1999
+ return Object.freeze(value.map((entry, index) => {
2000
+ if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2001
+ return entry;
2002
+ }));
2003
+ }
2004
+ function selectorsFromSettings(settings) {
2005
+ return {
2006
+ ...settings.skills === void 0 ? {} : { skills: settings.skills },
2007
+ ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2008
+ ...settings.tools === void 0 ? {} : { tools: settings.tools }
2009
+ };
2010
+ }
2011
+ function selectorSet(value) {
2012
+ return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2013
+ }
2014
+ function validateContextFileScopes(value, rolePath) {
2015
+ if (value === void 0) return void 0;
2016
+ if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2017
+ return [...value];
2018
+ }
2019
+ function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
2020
+ if (value === void 0) return void 0;
2021
+ const base = `${settingsPath}.extensionSettings`;
2022
+ if (!object(value)) fail(errorCode2, `${base} must be an object`);
2023
+ const normalized = {};
2024
+ for (const [namespace, raw] of Object.entries(value)) {
2025
+ if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2026
+ if (namespace === "herdr") {
2027
+ if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2028
+ if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2029
+ if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2030
+ normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2031
+ continue;
2032
+ }
2033
+ if (namespace === "trajectory") {
2034
+ if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2035
+ if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2036
+ if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2037
+ normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
2038
+ continue;
2039
+ }
2040
+ if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2041
+ normalized[namespace] = structuredClone(raw);
2042
+ }
2043
+ return deepFreeze(normalized);
2044
+ }
2045
+ function positiveRetentionInteger(value) {
2046
+ return positiveInteger(value) && Number.isSafeInteger(value);
2047
+ }
2048
+ function validateRetention(value, settingsPath) {
2049
+ if (value === void 0) return void 0;
2050
+ const base = `${settingsPath}.retention`;
2051
+ if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2052
+ const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2053
+ if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2054
+ if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2055
+ if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2056
+ return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2057
+ }
2058
+ function parseSettings(path, partial) {
2059
+ let parsed;
2060
+ try {
2061
+ parsed = JSON.parse(readFileSync(path, "utf8"));
2062
+ } catch (error) {
2063
+ if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2064
+ fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2065
+ }
2066
+ if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2067
+ const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
2068
+ const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2069
+ if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2070
+ if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2071
+ const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2072
+ if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2073
+ const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2074
+ if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2075
+ const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2076
+ const skills = validateSelectorList(parsed.skills, path, "skills");
2077
+ const tools = validateSelectorList(parsed.tools, path, "tools");
2078
+ const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2079
+ const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
2080
+ const retention = validateRetention(parsed.retention, path);
2081
+ const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2082
+ return Object.freeze({
2083
+ ...concurrency === void 0 ? {} : { concurrency },
2084
+ ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2085
+ ...modelAliases === void 0 ? {} : { modelAliases },
2086
+ ...skills === void 0 ? {} : { skills },
2087
+ ...extensions === void 0 ? {} : { extensions },
2088
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2089
+ ...tools === void 0 ? {} : { tools },
2090
+ ...retention === void 0 ? {} : { retention },
2091
+ ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2092
+ });
2093
+ }
2094
+ function loadSettings(path = workflowSettingsPath()) {
2095
+ return parseSettings(path, false);
2096
+ }
2097
+ function loadSettingsOverrides(path) {
2098
+ return parseSettings(path, true);
2099
+ }
2100
+ function resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2101
+ const projectSettingsPath = workflowProjectSettingsPath(cwd);
2102
+ const global = loadSettings(globalSettingsPath);
2103
+ const project = projectTrusted ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2104
+ const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2105
+ const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2106
+ const globalSelectors = selectorsFromSettings(global);
2107
+ const projectSelectors = selectorsFromSettings(project);
2108
+ const effectiveSelectors = selectorSet({
2109
+ skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2110
+ extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2111
+ ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2112
+ });
2113
+ const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2114
+ const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2115
+ const sources = {
2116
+ concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2117
+ modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2118
+ skills: sourceFor("skills"),
2119
+ extensions: sourceFor("extensions"),
2120
+ tools: sourceFor("tools"),
2121
+ ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2122
+ ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2123
+ ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2124
+ };
2125
+ const effective = Object.freeze({
2126
+ concurrency: project.concurrency ?? global.concurrency,
2127
+ backgroundWidget: global.backgroundWidget ?? true,
2128
+ ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2129
+ ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2130
+ ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2131
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2132
+ ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2133
+ ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2134
+ ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2135
+ });
2136
+ return { globalSettingsPath, projectSettingsPath, projectTrusted, global, project, effective, sources };
2137
+ }
2138
+ function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2139
+ for (const name of names) {
2140
+ try {
2141
+ const target = modelCapability(name, aliases, knownModels, settingsPath);
2142
+ if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2143
+ } catch (error) {
2144
+ throw annotateModelAliasError(error, name);
2145
+ }
2146
+ }
2147
+ }
2148
+ function resolveAgentResourcePolicy(cwd, projectTrusted, globalSettingsPath = workflowSettingsPath()) {
2149
+ const resolved = resolveWorkflowSettings(cwd, projectTrusted, globalSettingsPath);
2150
+ const global = selectorSet(selectorsFromSettings(resolved.global));
2151
+ const project = selectorSet(selectorsFromSettings(resolved.project));
2152
+ const effective = selectorSet(selectorsFromSettings(resolved.effective));
2153
+ return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2154
+ }
1930
2155
 
1931
2156
  // 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
2157
  var RESERVED_GLOBALS = /* @__PURE__ */ new Set(["agent", "shell", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
1937
2158
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
1938
2159
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -1941,10 +2162,10 @@ function normalizeRoleDirectory(value) {
1941
2162
  try {
1942
2163
  if (value instanceof URL) {
1943
2164
  if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
1944
- return fileURLToPath(value);
2165
+ return fileURLToPath2(value);
1945
2166
  }
1946
2167
  if (typeof value === "string") {
1947
- if (value.startsWith("file:")) return fileURLToPath(value);
2168
+ if (value.startsWith("file:")) return fileURLToPath2(value);
1948
2169
  if (isAbsolute(value)) return value;
1949
2170
  }
1950
2171
  } catch (error) {
@@ -1954,12 +2175,12 @@ function normalizeRoleDirectory(value) {
1954
2175
  }
1955
2176
  function isBuiltinRoleDirectory(path) {
1956
2177
  const canonical = canonicalPath(path);
1957
- const starterDirectory = dirname2(canonical);
2178
+ const starterDirectory = dirname3(canonical);
1958
2179
  if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
1959
- const distributionDirectory = dirname2(starterDirectory);
1960
- const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname2(distributionDirectory) : distributionDirectory;
2180
+ const distributionDirectory = dirname3(starterDirectory);
2181
+ const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
1961
2182
  try {
1962
- const metadata = JSON.parse(readFileSync(join3(packageRoot2, "package.json"), "utf8"));
2183
+ const metadata = JSON.parse(readFileSync2(join4(packageRoot2, "package.json"), "utf8"));
1963
2184
  return object(metadata) && isCorePackageName(metadata.name);
1964
2185
  } catch {
1965
2186
  return false;
@@ -2209,244 +2430,40 @@ function createWorkflowRegistryApi(registry) {
2209
2430
  catalogIndex: (context) => registry.catalogIndex(context),
2210
2431
  catalogDetail: (name, context) => registry.catalogDetail(name, context),
2211
2432
  globals: () => registry.globals(),
2212
- invokeFunction: (...args) => registry.invokeFunction(...args),
2213
- validateExtensionSettings: (...args) => {
2214
- registry.validateExtensionSettings(...args);
2215
- },
2216
- modelAliases: () => registry.modelAliases(),
2217
- resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
2218
- roleDirectories: () => registry.roleDirectories(),
2219
- roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
2220
- agentSetupHooks: () => registry.agentSetupHooks(),
2221
- agentAttemptActions: () => registry.agentAttemptActions()
2222
- };
2223
- }
2224
- function workflowRegistryHost() {
2225
- return globalRegistry[WORKFLOW_REGISTRY_KEY] ??= { api: createWorkflowRegistryApi(new WorkflowRegistry()), activeHosts: 0 };
2226
- }
2227
- function resetWorkflowRegistry() {
2228
- workflowRegistryHost().api = createWorkflowRegistryApi(new WorkflowRegistry());
2229
- }
2230
- function beginWorkflowExtensionLoading() {
2231
- const host = workflowRegistryHost();
2232
- if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
2233
- }
2234
- function loadingRegistry() {
2235
- return workflowRegistryHost().api;
2236
- }
2237
- beginWorkflowExtensionLoading();
2238
- function registeredWorkflowRoleDirectoryRegistrations() {
2239
- const registrations = loadingRegistry().roleDirectoryRegistrations;
2240
- return typeof registrations === "function" ? registrations() : [];
2241
- }
2242
-
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 };
2433
+ invokeFunction: (...args) => registry.invokeFunction(...args),
2434
+ validateExtensionSettings: (...args) => {
2435
+ registry.validateExtensionSettings(...args);
2436
+ },
2437
+ modelAliases: () => registry.modelAliases(),
2438
+ resolveModelAliases: (...args) => registry.resolveModelAliases(...args),
2439
+ roleDirectories: () => registry.roleDirectories(),
2440
+ roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
2441
+ agentSetupHooks: () => registry.agentSetupHooks(),
2442
+ agentAttemptActions: () => registry.agentAttemptActions()
2443
+ };
2432
2444
  }
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
- }
2445
+ function workflowRegistryHost() {
2446
+ return globalRegistry[WORKFLOW_REGISTRY_KEY] ??= { api: createWorkflowRegistryApi(new WorkflowRegistry()), activeHosts: 0 };
2442
2447
  }
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) } };
2448
+ function resetWorkflowRegistry() {
2449
+ workflowRegistryHost().api = createWorkflowRegistryApi(new WorkflowRegistry());
2450
+ }
2451
+ function beginWorkflowExtensionLoading() {
2452
+ const host = workflowRegistryHost();
2453
+ if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
2454
+ }
2455
+ function loadingRegistry() {
2456
+ return workflowRegistryHost().api;
2457
+ }
2458
+ beginWorkflowExtensionLoading();
2459
+ function registeredWorkflowRoleDirectoryRegistrations() {
2460
+ const registrations = loadingRegistry().roleDirectoryRegistrations;
2461
+ return typeof registrations === "function" ? registrations() : [];
2449
2462
  }
2463
+
2464
+ // src/roles.ts
2465
+ var ROLE_DIRECTORY2 = "pi-extensible-workflows";
2466
+ var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
2450
2467
  function parseRoleMarkdown(content, strict = false, rolePath) {
2451
2468
  if (!strict) {
2452
2469
  if (!content.startsWith("---\n")) return { prompt: content };
@@ -2473,7 +2490,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2473
2490
  }
2474
2491
  }
2475
2492
  const rolePathValue2 = rolePath ?? "<role>";
2476
- const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA", false);
2493
+ const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
2477
2494
  const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
2478
2495
  if (meta.model) {
2479
2496
  const model2 = unquote(meta.model);
@@ -2513,7 +2530,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2513
2530
  const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
2514
2531
  const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
2515
2532
  const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
2516
- const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA", false);
2533
+ const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
2517
2534
  const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
2518
2535
  const normalizedModel = typeof model === "string" ? model.trim() : void 0;
2519
2536
  if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
@@ -2528,12 +2545,11 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2528
2545
  if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
2529
2546
  return definition;
2530
2547
  }
2531
- var ROLE_DIRECTORY = "pi-extensible-workflows";
2532
- function workflowRoleDirectories(agentDir = getAgentDir()) {
2533
- return [join4(agentDir, ROLE_DIRECTORY, "roles")];
2548
+ function workflowRoleDirectories(agentDir = getAgentDir2()) {
2549
+ return [join5(agentDir, ROLE_DIRECTORY2, "roles")];
2534
2550
  }
2535
2551
  function projectRoleDirectories(root) {
2536
- return [join4(root, ROLE_DIRECTORY, "roles")];
2552
+ return [join5(root, ROLE_DIRECTORY2, "roles")];
2537
2553
  }
2538
2554
  function roleDirectorySources(dirs) {
2539
2555
  const seen = /* @__PURE__ */ new Set();
@@ -2552,7 +2568,7 @@ function isRoleFile(dir, entry) {
2552
2568
  if (entry.isFile()) return true;
2553
2569
  if (!entry.isSymbolicLink()) return false;
2554
2570
  try {
2555
- return statSync(join4(dir, entry.name)).isFile();
2571
+ return statSync(join5(dir, entry.name)).isFile();
2556
2572
  } catch (error) {
2557
2573
  if (isNodeError(error, "ENOENT")) return false;
2558
2574
  throw error;
@@ -2568,11 +2584,11 @@ function scanRoleFiles(dirs, extension2) {
2568
2584
  if (!extension2 && isNodeError(error, "ENOENT")) continue;
2569
2585
  fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension2)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
2570
2586
  }
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 });
2587
+ for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join5(source.path, entry.name), source });
2572
2588
  }
2573
2589
  return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
2574
2590
  }
2575
- function readRoleDefinitions(dirs, extension2 = false) {
2591
+ function roleEntries(dirs, extension2) {
2576
2592
  const files = scanRoleFiles(dirs, extension2);
2577
2593
  const starterFiles = files.filter(({ source }) => source.builtin === true);
2578
2594
  const regularFiles = files.filter(({ source }) => source.builtin !== true);
@@ -2583,7 +2599,9 @@ function readRoleDefinitions(dirs, extension2 = false) {
2583
2599
  }
2584
2600
  const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
2585
2601
  try {
2586
- return [name, parseRoleMarkdown(readFileSync2(path, "utf8"), true, path)];
2602
+ const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
2603
+ roleDefinitionPaths.set(definition, path);
2604
+ return [name, { name, path, source, definition }];
2587
2605
  } catch (error) {
2588
2606
  if (extension2) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
2589
2607
  throw error;
@@ -2591,15 +2609,114 @@ function readRoleDefinitions(dirs, extension2 = false) {
2591
2609
  }));
2592
2610
  return extension2 ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
2593
2611
  }
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"))) : {} });
2612
+ function discoveryInput(input) {
2613
+ return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir2(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
2596
2614
  }
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`);
2615
+ function discoveredRoleEntries(input) {
2616
+ const resolved = discoveryInput(input);
2617
+ return {
2618
+ ...roleEntries(resolved.extensionRoleDirectories, true),
2619
+ ...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
2620
+ ...resolved.projectTrusted ? roleEntries(projectRoleDirectories(join5(resolved.cwd, ".pi")), false) : {}
2621
+ };
2622
+ }
2623
+ function discoverRoles(input) {
2624
+ return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
2625
+ }
2626
+ function loadAgentDefinitions(cwd, agentDir = getAgentDir2(), projectTrusted = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
2627
+ return discoverRoles({ cwd, agentDir, projectTrusted, extensionRoleDirectories });
2628
+ }
2629
+ function canonicalExtensionSelector(selector, base = process.cwd()) {
2630
+ const negated = selector.startsWith("!");
2631
+ const body = negated ? selector.slice(1) : selector;
2632
+ if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
2633
+ const resolved = resolve4(base, body);
2634
+ if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
2635
+ return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
2636
+ }
2637
+ function selectorsFromSettings2(settings) {
2638
+ return { ...settings.skills === void 0 ? {} : { skills: settings.skills }, ...settings.extensions === void 0 ? {} : { extensions: settings.extensions }, ...settings.tools === void 0 ? {} : { tools: settings.tools } };
2639
+ }
2640
+ function rootToolNames(value) {
2641
+ return value === void 0 ? void 0 : value instanceof Set ? [...value] : [...value];
2642
+ }
2643
+ function roleDefinitionFor(name, options) {
2644
+ if (name === void 0) return {};
2645
+ if (options.definition !== void 0) {
2646
+ const path = roleDefinitionPaths.get(options.definition);
2647
+ return { definition: options.definition, ...path === void 0 ? {} : { path } };
2648
+ }
2649
+ if (options.definitions?.[name] !== void 0) {
2650
+ const definition = options.definitions[name];
2651
+ const path = roleDefinitionPaths.get(definition);
2652
+ return { definition, ...path === void 0 ? {} : { path } };
2653
+ }
2654
+ const entries = discoveredRoleEntries(options);
2655
+ const entry = entries[name];
2656
+ if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
2657
+ return entry;
2658
+ }
2659
+ function resolveRole(name, options) {
2660
+ const { definition, path } = roleDefinitionFor(name, options);
2661
+ const settings = options.selectorSources === void 0 ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir2())) : void 0;
2662
+ const baseSources = options.selectorSources ?? { global: selectorsFromSettings2(settings?.global ?? {}), project: selectorsFromSettings2(settings?.project ?? {}) };
2663
+ const canonicalSources = (sources) => ({
2664
+ ...sources,
2665
+ global: { ...sources.global, ...sources.global.extensions === void 0 ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
2666
+ project: { ...sources.project, ...sources.project.extensions === void 0 ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
2667
+ ...sources.role === void 0 ? {} : { role: { ...sources.role, ...sources.role.extensions === void 0 ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } },
2668
+ ...sources.call === void 0 ? {} : { call: { ...sources.call, ...sources.call.extensions === void 0 ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } }
2669
+ });
2670
+ const roleLayer = definition?.skills !== void 0 || definition?.extensions !== void 0 || definition?.tools !== void 0 ? { ...definition.skills === void 0 ? {} : { skills: definition.skills }, ...definition.extensions === void 0 ? {} : { extensions: definition.extensions.map((selector) => canonicalExtensionSelector(selector, path ? dirname4(path) : options.cwd)) }, ...definition.tools === void 0 ? {} : { tools: definition.tools } } : baseSources.role;
2671
+ const callLayer = options.skills !== void 0 || options.extensions !== void 0 || options.tools !== void 0 ? { ...options.skills === void 0 ? {} : { skills: options.skills }, ...options.extensions === void 0 ? {} : { extensions: options.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) }, ...options.tools === void 0 ? {} : { tools: options.tools } } : baseSources.call;
2672
+ const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...roleLayer === void 0 ? {} : { role: roleLayer }, ...callLayer === void 0 ? {} : { call: callLayer } });
2673
+ const callSelectors = selectorSources.call;
2674
+ const roleSelectors = selectorSources.role;
2675
+ const selectorLayers = {
2676
+ skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
2677
+ extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
2678
+ tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools]
2679
+ };
2680
+ const resources = options.resources ?? {};
2681
+ const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
2682
+ const tools = options.effectiveTools ?? (candidateTools === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.tools, candidateTools));
2683
+ const rootTools = rootToolNames(options.rootTools);
2684
+ if (rootTools !== void 0) {
2685
+ const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
2686
+ if (outsideEffectiveTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
2687
+ const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
2688
+ if (outsideCallTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
2689
+ const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
2690
+ if (outsideRootTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
2691
+ }
2692
+ const selectedSkills = resources.skills === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.skills, resources.skills);
2693
+ const extensionResources = resources.extensions?.map((extension2) => canonicalPath(extension2));
2694
+ const selectedExtensions = extensionResources === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
2695
+ const unmatchedSkills = resources.skills === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
2696
+ const unmatchedExtensions = extensionResources === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
2697
+ const unmatchedTools = candidateTools === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
2698
+ const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
2699
+ const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
2700
+ const blockedAlias = requestedModel?.split(":", 1)[0];
2701
+ if (requestedModel !== void 0 && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
2702
+ const target = options.blockedAliasTargets?.[blockedAlias];
2703
+ fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
2704
+ }
2705
+ let model = options.modelOverride;
2706
+ if (model === void 0 && requestedModel !== void 0) {
2707
+ assertModelThinking(requestedModel);
2708
+ model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
2709
+ }
2710
+ if (model === void 0) model = options.rootModel;
2711
+ const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === void 0 ? void 0 : /* @__PURE__ */ new Set([modelCapability(options.rootModel)]));
2712
+ if (model !== void 0 && availableModels !== void 0 && !availableModels.has(modelCapability(model))) fail("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
2713
+ const prompt = definition?.prompt ?? "";
2714
+ const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
2715
+ const contextFiles = options.contextFiles ?? definition?.contextFiles;
2716
+ return { ...name === void 0 ? {} : { name }, ...definition === void 0 ? {} : { definition }, ...model === void 0 ? {} : { model }, ...modelAliasName(requestedModel ?? "", aliases) && requestedModel ? { requestedModel } : {}, ...tools === void 0 ? {} : { tools }, prompt, overrideSystemPrompt, systemPrompt: { mode: overrideSystemPrompt ? "override" : "append", text: prompt }, ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] }, ...definition?.extensionSettings === void 0 ? {} : { extensionSettings: definition.extensionSettings }, selectorSources, selectorLayers, ...selectedSkills === void 0 ? {} : { selectedSkills }, ...selectedExtensions === void 0 ? {} : { selectedExtensions }, ...unmatchedSkills === void 0 ? {} : { unmatchedSkills }, ...unmatchedExtensions === void 0 ? {} : { unmatchedExtensions }, ...unmatchedTools === void 0 ? {} : { unmatchedTools } };
2602
2717
  }
2718
+
2719
+ // src/validation.ts
2603
2720
  var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
2604
2721
  function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
2605
2722
  switch (key) {
@@ -2949,17 +3066,17 @@ Promise.resolve().then(() => new vm.Script("(async(__pi_extensible_workflows_age
2949
3066
 
2950
3067
  // src/host.ts
2951
3068
  import { Type as Type3 } from "@earendil-works/pi-ai";
2952
- import { copyToClipboard, getAgentDir as getAgentDir4, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
3069
+ import { copyToClipboard, getAgentDir as getAgentDir5, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
2953
3070
 
2954
3071
  // src/agent-execution.ts
2955
- import { existsSync, realpathSync as realpathSync2, readFileSync as readFileSync3 } from "node:fs";
3072
+ import { existsSync, realpathSync as realpathSync2, readFileSync as readFileSync4 } from "node:fs";
2956
3073
  import { copyFile } from "node:fs/promises";
2957
3074
  import { homedir as homedir4 } from "node:os";
2958
- import { basename as basename4, dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
3075
+ import { basename as basename4, dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
2959
3076
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2960
3077
  import { Type } from "@earendil-works/pi-ai";
2961
3078
  import { Compile } from "typebox/compile";
2962
- import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir2, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
3079
+ import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir3, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
2963
3080
 
2964
3081
  // src/pi-runtime-adapter.ts
2965
3082
  var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
@@ -3007,16 +3124,16 @@ function normalizeState(value) {
3007
3124
  const candidate = record(value);
3008
3125
  const model = record(candidate?.model);
3009
3126
  const provider = stringValue(model?.provider);
3010
- const modelName = stringValue(model?.model);
3127
+ const modelName2 = stringValue(model?.model);
3011
3128
  const rawTools = candidate?.tools;
3012
3129
  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;
3130
+ if (!candidate || !model || provider === void 0 || modelName2 === void 0 || tools === void 0 || !Array.isArray(rawTools) || tools.length !== rawTools.length) return void 0;
3014
3131
  const thinking = candidate.thinking === void 0 ? model.thinking : candidate.thinking;
3015
3132
  if (thinking !== void 0 && !isThinkingLevel(thinking)) return void 0;
3016
3133
  const systemPrompt = candidate.systemPrompt;
3017
3134
  if (systemPrompt !== void 0 && typeof systemPrompt !== "string") return void 0;
3018
3135
  return {
3019
- model: { provider, model: modelName, ...thinking === void 0 ? {} : { thinking } },
3136
+ model: { provider, model: modelName2, ...thinking === void 0 ? {} : { thinking } },
3020
3137
  ...thinking === void 0 ? {} : { thinking },
3021
3138
  tools: [...tools],
3022
3139
  ...systemPrompt === void 0 ? {} : { systemPrompt }
@@ -3234,8 +3351,8 @@ function createLiveSessionHandoff() {
3234
3351
  let request;
3235
3352
  const waitForBoundary = () => {
3236
3353
  if (!turnActive) return Promise.resolve();
3237
- boundary ??= new Promise((resolve5) => {
3238
- resolveBoundary = resolve5;
3354
+ boundary ??= new Promise((resolve6) => {
3355
+ resolveBoundary = resolve6;
3239
3356
  });
3240
3357
  return boundary;
3241
3358
  };
@@ -3267,11 +3384,11 @@ function createLiveSessionHandoff() {
3267
3384
  if (request) return request;
3268
3385
  if (isReleased()) return;
3269
3386
  state = "handoff-pending";
3270
- takeover = new Promise((resolve5) => {
3271
- resolveTakeover = resolve5;
3387
+ takeover = new Promise((resolve6) => {
3388
+ resolveTakeover = resolve6;
3272
3389
  });
3273
- resume = new Promise((resolve5) => {
3274
- resolveResume = resolve5;
3390
+ resume = new Promise((resolve6) => {
3391
+ resolveResume = resolve6;
3275
3392
  });
3276
3393
  request = (async () => {
3277
3394
  await waitForBoundary();
@@ -3798,36 +3915,28 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
3798
3915
  function accounting(stats) {
3799
3916
  return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
3800
3917
  }
3801
- var extensionDirectory = dirname4(fileURLToPath3(import.meta.url));
3802
- var workflowPackageRoot = basename4(dirname4(extensionDirectory)) === "dist" ? resolve4(extensionDirectory, "../..") : resolve4(extensionDirectory, "..");
3918
+ var extensionDirectory = dirname5(fileURLToPath3(import.meta.url));
3919
+ var workflowPackageRoot = basename4(dirname5(extensionDirectory)) === "dist" ? resolve5(extensionDirectory, "../..") : resolve5(extensionDirectory, "..");
3803
3920
  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"))
3921
+ canonicalPath(resolve5(workflowPackageRoot, "src/index.ts")),
3922
+ canonicalPath(resolve5(workflowPackageRoot, "dist/src/index.js")),
3923
+ canonicalPath(resolve5(workflowPackageRoot, "starter/index.ts")),
3924
+ canonicalPath(resolve5(workflowPackageRoot, "dist/starter/index.js")),
3925
+ canonicalPath(resolve5(workflowPackageRoot, "subagents/index.ts")),
3926
+ canonicalPath(resolve5(workflowPackageRoot, "dist/subagents/index.js"))
3810
3927
  ]);
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)}`;
3818
- }
3819
3928
  var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
3820
3929
  function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
3821
- const projectPath = join5(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
3930
+ const projectPath = join6(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
3822
3931
  if (projectTrusted && existsSync(projectPath)) return projectPath;
3823
- const globalPaths = [join5(homedir4(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join5(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
3932
+ const globalPaths = [join6(homedir4(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join6(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
3824
3933
  return globalPaths.find((path) => existsSync(path));
3825
3934
  }
3826
3935
  function filterContextFiles(base, scopes, cwd, agentDir) {
3827
3936
  const globalPath = canonicalPath(agentDir);
3828
3937
  const cwdPath = canonicalPath(cwd);
3829
3938
  return base.filter(({ path }) => {
3830
- const resolvedPath = canonicalPath(dirname4(path));
3939
+ const resolvedPath = canonicalPath(dirname5(path));
3831
3940
  return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
3832
3941
  });
3833
3942
  }
@@ -3853,10 +3962,10 @@ async function loadNativePromptTemplateModule() {
3853
3962
  const packageDirectory = await loadPiPackageDirectory();
3854
3963
  let promptTemplatePath;
3855
3964
  if (packageDirectory.directory) {
3856
- const candidate = resolve4(packageDirectory.directory, "dist/core/prompt-templates.js");
3965
+ const candidate = resolve5(packageDirectory.directory, "dist/core/prompt-templates.js");
3857
3966
  if (existsSync(candidate)) promptTemplatePath = candidate;
3858
3967
  }
3859
- promptTemplatePath ??= resolve4(dirname4(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
3968
+ promptTemplatePath ??= resolve5(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
3860
3969
  const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
3861
3970
  return nativePromptTemplateModule ??= import(promptTemplatePath).then((module) => module).catch((error) => {
3862
3971
  throw new Error(`Could not load Pi prompt templates.${packageDirectoryError}`, { cause: error });
@@ -3928,13 +4037,13 @@ function flushExtensionProviders(resourceLoader, modelRuntime) {
3928
4037
  return failures;
3929
4038
  }
3930
4039
  async function createLocalPiSessionHandle(input, sessionStartEvent) {
3931
- const agentDir = input.agentDir ?? getAgentDir2();
4040
+ const agentDir = input.agentDir ?? getAgentDir3();
3932
4041
  const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
3933
4042
  const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
3934
4043
  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));
4044
+ const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join6(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join6(agentDir, "sessions")) : SessionManager.create(input.cwd));
3936
4045
  if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
3937
- const modelRuntime = await ModelRuntime.create({ authPath: join5(agentDir, "auth.json"), modelsPath: join5(agentDir, "models.json") });
4046
+ const modelRuntime = await ModelRuntime.create({ authPath: join6(agentDir, "auth.json"), modelsPath: join6(agentDir, "models.json") });
3938
4047
  const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
3939
4048
  const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
3940
4049
  let settingsManager;
@@ -3947,20 +4056,22 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
3947
4056
  const resolved = await packageManager.resolve();
3948
4057
  const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
3949
4058
  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);
4059
+ const selection = resolveRole(void 0, {
4060
+ cwd: input.cwd,
4061
+ selectorSources,
4062
+ resources: { extensions: discoveredExtensions }
4063
+ });
4064
+ const selectedExtensions = selection.selectedExtensions ?? [];
3953
4065
  const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
3954
- const unmatchedExtensions = extensionSelectors.filter(({ matching }) => unmatchedResourcePatterns([matching], discoveredExtensions).length > 0).map(({ original }) => original);
3955
4066
  policy.selectedExtensions = selectedExtensions;
3956
- policy.unmatchedExtensions = unmatchedExtensions;
4067
+ policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
3957
4068
  const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
3958
4069
  const updateSkillMatches = (skills) => {
3959
4070
  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);
4071
+ const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
4072
+ const selectedSkills = selected.selectedSkills ?? [];
3962
4073
  policy.selectedSkills = selectedSkills;
3963
- policy.unmatchedSkills = unmatchedResourcePatterns(skillLayers.flatMap((layer) => layer ?? []), names);
4074
+ policy.unmatchedSkills = selected.unmatchedSkills ?? [];
3964
4075
  return new Set(names.filter((name) => !selectedSkills.includes(name)));
3965
4076
  };
3966
4077
  resourceLoader = new DefaultResourceLoader({
@@ -4439,27 +4550,27 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
4439
4550
  };
4440
4551
  }
4441
4552
  function packageRoot(start) {
4442
- let current = dirname4(realpathSync2(start));
4553
+ let current = dirname5(realpathSync2(start));
4443
4554
  for (; ; ) {
4444
- const candidates = [current, join5(current, "..", "@earendil-works", "pi-coding-agent"), join5(current, "..", "pi-coding-agent")];
4555
+ const candidates = [current, join6(current, "..", "@earendil-works", "pi-coding-agent"), join6(current, "..", "pi-coding-agent")];
4445
4556
  for (const candidate of candidates) {
4446
- const packageJsonPath = join5(candidate, "package.json");
4557
+ const packageJsonPath = join6(candidate, "package.json");
4447
4558
  if (!existsSync(packageJsonPath)) continue;
4448
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
4559
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
4449
4560
  if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
4450
4561
  }
4451
- const parent = dirname4(current);
4562
+ const parent = dirname5(current);
4452
4563
  if (parent === current) return void 0;
4453
4564
  current = parent;
4454
4565
  }
4455
4566
  }
4456
4567
  function piPackageEntrypoint(root) {
4457
- const packageJson = JSON.parse(readFileSync3(join5(root, "package.json"), "utf8"));
4568
+ const packageJson = JSON.parse(readFileSync4(join6(root, "package.json"), "utf8"));
4458
4569
  if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4459
4570
  const bin = packageJson.bin;
4460
4571
  const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
4461
4572
  if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
4462
- const entrypoint = resolve4(root, cli);
4573
+ const entrypoint = resolve5(root, cli);
4463
4574
  if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
4464
4575
  return entrypoint;
4465
4576
  }
@@ -4500,7 +4611,7 @@ ${instruction}` : ""}`;
4500
4611
  }
4501
4612
  async function preparedAgentSession(input, initialPrompt) {
4502
4613
  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;
4614
+ const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir3(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
4504
4615
  const prepared = {
4505
4616
  cwd: input.cwd,
4506
4617
  model: Object.freeze({ ...input.model }),
@@ -4530,7 +4641,7 @@ function agentSetupSummary(setup, hookNames) {
4530
4641
  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
4642
  }
4532
4643
  async function attemptSessionInput(source, attempt) {
4533
- const target = join5(dirname4(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
4644
+ const target = join6(dirname5(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
4534
4645
  await copyFile(source, target);
4535
4646
  return target;
4536
4647
  }
@@ -4632,7 +4743,6 @@ var WorkflowAgentExecutor = class {
4632
4743
  const roleName2 = typeof options.role === "string" ? options.role : void 0;
4633
4744
  const roleDefinition = roleName2 ? this.root.agentDefinitions?.[roleName2] : void 0;
4634
4745
  if (roleName2 && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName2}`);
4635
- const candidateTools = inheritedTools !== void 0 ? [...inheritedTools] : [...this.root.tools];
4636
4746
  if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
4637
4747
  const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
4638
4748
  if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
@@ -4643,33 +4753,30 @@ var WorkflowAgentExecutor = class {
4643
4753
  }
4644
4754
  }
4645
4755
  }
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] } };
4756
+ const resolved = resolveRole(roleName2, {
4757
+ cwd: this.root.cwd,
4758
+ ...roleDefinition === void 0 ? {} : { definition: roleDefinition },
4759
+ ...this.root.agentDefinitions === void 0 ? {} : { definitions: this.root.agentDefinitions },
4760
+ ...this.root.agentDir === void 0 ? {} : { agentDir: this.root.agentDir },
4761
+ selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
4762
+ ...this.root.modelAliases === void 0 ? {} : { modelAliases: this.root.modelAliases },
4763
+ ...this.root.knownModels === void 0 ? {} : { knownModels: this.root.knownModels },
4764
+ ...this.root.availableModels === void 0 ? {} : { availableModels: this.root.availableModels },
4765
+ ...this.root.blockedAliases === void 0 ? {} : { blockedAliases: this.root.blockedAliases },
4766
+ ...this.root.blockedAliasTargets === void 0 ? {} : { blockedAliasTargets: this.root.blockedAliasTargets },
4767
+ ...this.root.settingsPath === void 0 ? {} : { settingsPath: this.root.settingsPath },
4768
+ rootModel: this.root.model,
4769
+ rootTools: this.root.tools,
4770
+ ...inheritedTools === void 0 ? {} : { inheritedTools },
4771
+ ...options.model === void 0 ? {} : { model: options.model },
4772
+ ...options.modelOverride === void 0 ? {} : { modelOverride: options.modelOverride },
4773
+ ...options.tools === void 0 ? {} : { tools: options.tools },
4774
+ ...options.skills === void 0 ? {} : { skills: options.skills },
4775
+ ...options.extensions === void 0 ? {} : { extensions: options.extensions },
4776
+ ...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles },
4777
+ ...options.effectiveTools === void 0 ? {} : { effectiveTools: options.effectiveTools }
4778
+ });
4779
+ return { model: resolved.model ?? this.root.model, ...resolved.requestedModel === void 0 ? {} : { requestedModel: resolved.requestedModel }, tools: resolved.tools ?? [], ...resolved.overrideSystemPrompt ? { systemPrompt: resolved.prompt } : {}, systemPromptAppend: resolved.overrideSystemPrompt ? "" : resolved.prompt, ...resolved.contextFiles === void 0 ? {} : { contextFiles: resolved.contextFiles } };
4673
4780
  }
4674
4781
  async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
4675
4782
  const executionSignal = signal ?? this.root.runContext?.signal;
@@ -4926,7 +5033,7 @@ import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateT
4926
5033
  import { spawn } from "node:child_process";
4927
5034
  import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
4928
5035
  import { tmpdir } from "node:os";
4929
- import { join as join6 } from "node:path";
5036
+ import { join as join7 } from "node:path";
4930
5037
  function workflowPromptArtifact(prompt) {
4931
5038
  return { extension: ".md", content: prompt };
4932
5039
  }
@@ -4937,23 +5044,23 @@ function workflowResultArtifact(value) {
4937
5044
  async function spawnWorkflowEditor(command, path) {
4938
5045
  const [editor, ...editorArgs] = command.split(" ");
4939
5046
  if (!editor) return null;
4940
- return new Promise((resolve5) => {
5047
+ return new Promise((resolve6) => {
4941
5048
  try {
4942
5049
  const child = spawn(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
4943
5050
  child.once("error", () => {
4944
- resolve5(null);
5051
+ resolve6(null);
4945
5052
  });
4946
5053
  child.once("close", (code) => {
4947
- resolve5(code);
5054
+ resolve6(code);
4948
5055
  });
4949
5056
  } catch {
4950
- resolve5(null);
5057
+ resolve6(null);
4951
5058
  }
4952
5059
  });
4953
5060
  }
4954
5061
  async function openWorkflowArtifact(tui, command, artifact) {
4955
- const directory = await mkdtemp(join6(tmpdir(), "pi-workflow-editor-"));
4956
- const path = join6(directory, `artifact${artifact.extension}`);
5062
+ const directory = await mkdtemp(join7(tmpdir(), "pi-workflow-editor-"));
5063
+ const path = join7(directory, `artifact${artifact.extension}`);
4957
5064
  try {
4958
5065
  await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
4959
5066
  tui.stop();
@@ -4970,23 +5077,88 @@ async function openWorkflowArtifact(tui, command, artifact) {
4970
5077
 
4971
5078
  // src/host-view.ts
4972
5079
  import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
4973
- import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
5080
+ import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
4974
5081
 
4975
5082
  // src/background-widget.ts
4976
5083
  import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
5084
+ var REPAINT_MS = 125;
4977
5085
  var QUIET_MS = 3 * 60 * 1e3;
5086
+ var ROLE = {
5087
+ live: "accent",
5088
+ done: "success",
5089
+ failed: "error",
5090
+ warn: "warning",
5091
+ quiet: "muted"
5092
+ };
5093
+ var RESET = "\x1B[0m";
5094
+ var SPINNER = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FD", "\u28FB"];
5095
+ function spinner(now) {
5096
+ return SPINNER[Math.floor(now / REPAINT_MS) % SPINNER.length] ?? SPINNER[0];
5097
+ }
5098
+ function mark(state, now, paint, runState) {
5099
+ if (state === "running" && (runState === void 0 || runState === "running")) return paint(ROLE.live, spinner(now));
5100
+ if (state === "completed") return paint(ROLE.done, "\u2713");
5101
+ if (state === "failed" || state === "budget_exhausted") return paint(ROLE.failed, "\u2717");
5102
+ return paint(ROLE.quiet, "\xB7");
5103
+ }
5104
+ function formatElapsed(ms) {
5105
+ const total = Math.max(0, Math.floor(ms / 1e3));
5106
+ const minutes = Math.floor(total / 60);
5107
+ const seconds = total % 60;
5108
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
5109
+ }
5110
+ function formatTokens(tokens) {
5111
+ if (!tokens) return "";
5112
+ if (tokens < 1e3) return `${String(tokens)}t`;
5113
+ const thousands = tokens / 1e3;
5114
+ return `${thousands < 10 ? thousands.toFixed(1) : String(Math.round(thousands))}kt`;
5115
+ }
4978
5116
  function formatCost(cost) {
4979
5117
  if (!cost) return "";
4980
5118
  return cost < 0.01 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`;
4981
5119
  }
5120
+ function visibleLength(text) {
5121
+ return visibleWidth(text);
5122
+ }
5123
+ function truncate(text, limit) {
5124
+ return `${truncateToWidth(text, limit, "\u2026")}${RESET}`;
5125
+ }
5126
+ function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => void 0) {
5127
+ const inner = Math.max(20, width - 4);
5128
+ const lines = body.map((row, index) => {
5129
+ const right = row.right ?? "";
5130
+ const rightWidth = right === "" ? 0 : visibleLength(right) + 2;
5131
+ const room = Math.max(0, inner - rightWidth);
5132
+ const left = visibleLength(row.text) > room ? truncate(row.text, room) : row.text;
5133
+ const gap = Math.max(0, inner - visibleLength(left) - (right === "" ? 0 : visibleLength(right)));
5134
+ const text = right === "" ? left + " ".repeat(gap) : `${left}${" ".repeat(gap)}${paint(ROLE.quiet, right)}`;
5135
+ const rule = accent("\u2502");
5136
+ const bar = scrollbar(index);
5137
+ return `${rule} ${text} ${bar === void 0 ? rule : accent(bar)}`;
5138
+ });
5139
+ const label2 = ` ${title} `;
5140
+ const tail = hint === "" ? "" : ` ${hint} `;
5141
+ const spare = inner + 2 - visibleLength(label2) - visibleLength(tail);
5142
+ 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`);
5143
+ const foot = accent(`\u2570${"\u2500".repeat(inner + 2)}\u256F`);
5144
+ return [heading, ...lines, foot];
5145
+ }
4982
5146
 
4983
5147
  // src/host-view.ts
4984
5148
  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 };
5149
+ 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: "?" };
5150
+ function runStateGlyph(state, running) {
5151
+ return state === "running" ? running : RUN_STATE_GLYPH[state] ?? "\u25C6";
5152
+ }
5153
+ 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
5154
  var PHASE_STATE_STYLE = { completed: "success", failed: "error", cancelled: "error", stopped: "error", running: "accent", paused: "warning", pausing: "warning", interrupted: "warning", budget_exhausted: "warning" };
4986
5155
  function styleForState(map, state, styles) {
4987
5156
  const key = map[state] ?? "muted";
4988
5157
  return (text) => styles[key](text);
4989
5158
  }
5159
+ function progressStyleForState(state, styles) {
5160
+ return styleForState(PROGRESS_STATE_STYLE, state, styles);
5161
+ }
4990
5162
  function phaseStyleForState(state, styles) {
4991
5163
  return styleForState(PHASE_STATE_STYLE, state, styles);
4992
5164
  }
@@ -5101,16 +5273,96 @@ function visibleStandaloneAgentAttemptActions(actions, context) {
5101
5273
  }
5102
5274
  });
5103
5275
  }
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"}`];
5276
+ function formatAgentAccounting(accounting2) {
5277
+ const total = accounting2.input + accounting2.output + accounting2.cacheRead + accounting2.cacheWrite;
5278
+ 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"}`];
5279
+ }
5280
+ function wrapNavigatorLine(text, width) {
5281
+ return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
5282
+ }
5283
+ function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
5284
+ const safeWidth = Math.max(1, width);
5285
+ if (safeWidth >= 80) {
5286
+ const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
5287
+ const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
5288
+ const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
5289
+ return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
5290
+ const cell = left2[index] ?? "";
5291
+ return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
5292
+ });
5293
+ }
5294
+ if (layout.detailsOnly) return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
5295
+ const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
5296
+ return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
5107
5297
  }
5108
5298
 
5109
5299
  // src/retention.ts
5110
5300
  var DAY_MS = 24 * 60 * 60 * 1e3;
5111
5301
 
5302
+ // src/host-navigator.ts
5303
+ function isKeybindingGetKeys(value) {
5304
+ return typeof value === "function";
5305
+ }
5306
+ function keybindingKeys(keybindings, name) {
5307
+ if (!object(keybindings)) return void 0;
5308
+ const getKeys = keybindings.getKeys;
5309
+ return isKeybindingGetKeys(getKeys) ? getKeys.call(keybindings, name) : void 0;
5310
+ }
5311
+ var WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.editor.cursorLeft": "h", "tui.editor.cursorRight": "l" };
5312
+ function workflowKeyMatches(keybindings, data, binding) {
5313
+ return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data;
5314
+ }
5315
+ var WORKFLOW_KEY_LABELS = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
5316
+ function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
5317
+ const keys = keybindingKeys(keybindings, binding);
5318
+ const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
5319
+ const vim = WORKFLOW_VIM_KEYS[binding];
5320
+ return [...new Set(vim ? [...configured, vim] : configured)].join("/");
5321
+ }
5322
+ function createInlineConfirm(requestRender) {
5323
+ let pending;
5324
+ const answer = (value) => {
5325
+ const current = pending;
5326
+ pending = void 0;
5327
+ current?.resolve(value);
5328
+ requestRender();
5329
+ };
5330
+ return {
5331
+ ask(title, message) {
5332
+ answer(false);
5333
+ return new Promise((resolve6) => {
5334
+ pending = { title, message, yes: true, resolve: resolve6 };
5335
+ requestRender();
5336
+ });
5337
+ },
5338
+ active() {
5339
+ return pending !== void 0;
5340
+ },
5341
+ rows(width, styles) {
5342
+ if (!pending) return [];
5343
+ const yes = pending.yes;
5344
+ const message = truncateToVisualLines2(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
5345
+ return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => option === "Yes" === yes ? `\u2192 ${styles.accent(option)}` : ` ${option}`)];
5346
+ },
5347
+ hint(keybindings) {
5348
+ 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`;
5349
+ },
5350
+ handleInput(keybindings, data) {
5351
+ if (!pending) return;
5352
+ if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
5353
+ pending.yes = !pending.yes;
5354
+ requestRender();
5355
+ } else if (workflowKeyMatches(keybindings, data, "tui.select.confirm")) answer(pending.yes);
5356
+ else if (workflowKeyMatches(keybindings, data, "tui.select.cancel")) answer(false);
5357
+ },
5358
+ cancel() {
5359
+ answer(false);
5360
+ }
5361
+ };
5362
+ }
5363
+
5112
5364
  // 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";
5365
+ import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as getAgentDir4, SettingsManager as SettingsManager3, DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2 } from "@earendil-works/pi-coding-agent";
5114
5366
 
5115
5367
  // subagents/src/contracts.ts
5116
5368
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -5214,14 +5466,14 @@ function accountingValue(value) {
5214
5466
  return { input, output, cacheRead, cacheWrite, cost };
5215
5467
  }
5216
5468
  function legacyAccountingValue(record2) {
5217
- const accounting3 = accountingValue(record2.accounting);
5218
- if (record2.accounting !== void 0 && !accounting3) return void 0;
5469
+ const accounting2 = accountingValue(record2.accounting);
5470
+ if (record2.accounting !== void 0 && !accounting2) return void 0;
5219
5471
  const usage = record2.usage;
5220
- if (usage === void 0) return accounting3;
5472
+ if (usage === void 0) return accounting2;
5221
5473
  const usageRecord = objectValue(usage);
5222
5474
  const tokens = objectValue(usageRecord?.tokens);
5223
5475
  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 };
5476
+ return accounting2 ?? { input: tokens.input, output: tokens.output, cacheRead: tokens.cacheRead, cacheWrite: tokens.cacheWrite, cost: usageRecord.cost };
5225
5477
  }
5226
5478
  function activityValue(value) {
5227
5479
  const record2 = objectValue(value);
@@ -5255,9 +5507,9 @@ function worktreeValue(value) {
5255
5507
  function progressValue(value) {
5256
5508
  const record2 = objectValue(value);
5257
5509
  if (!record2) return void 0;
5258
- const accounting3 = accountingValue(record2.accounting);
5510
+ const accounting2 = accountingValue(record2.accounting);
5259
5511
  const toolCalls = toolCallsValue(record2.toolCalls);
5260
- if (!accounting3 || !toolCalls) return void 0;
5512
+ if (!accounting2 || !toolCalls) return void 0;
5261
5513
  const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5262
5514
  if (record2.activity !== void 0 && activity2 === void 0) return void 0;
5263
5515
  const rawState = record2.state;
@@ -5273,16 +5525,16 @@ function progressValue(value) {
5273
5525
  }
5274
5526
  const lastEventAt = record2.lastEventAt;
5275
5527
  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 } };
5528
+ return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity2 === void 0 ? {} : { activity: activity2 }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5277
5529
  }
5278
5530
  function legacyProgressValue(record2) {
5279
- const accounting3 = legacyAccountingValue(record2);
5531
+ const accounting2 = legacyAccountingValue(record2);
5280
5532
  const toolCalls = record2.toolCalls === void 0 ? [] : toolCallsValue(record2.toolCalls);
5281
- if (!accounting3 || !toolCalls) return void 0;
5533
+ if (!accounting2 || !toolCalls) return void 0;
5282
5534
  const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5283
5535
  const lastEventAt = record2.lastEventAt;
5284
5536
  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 } };
5537
+ return { accounting: accounting2, toolCalls, ...activity2 === void 0 ? {} : { activity: activity2 }, ...lastEventAt === void 0 ? {} : { lastEventAt } };
5286
5538
  }
5287
5539
  function attemptValue(value) {
5288
5540
  const record2 = objectValue(value);
@@ -5291,16 +5543,16 @@ function attemptValue(value) {
5291
5543
  const hookNames = stringArrayValue(setup?.hookNames);
5292
5544
  const tools = stringArrayValue(setup?.tools);
5293
5545
  const resources = setup?.resourceSelectors === void 0 ? void 0 : resourceSummaryValue(setup.resourceSelectors);
5294
- const accounting3 = accountingValue(record2?.accounting);
5546
+ const accounting2 = accountingValue(record2?.accounting);
5295
5547
  const session = record2?.session === void 0 ? void 0 : sessionReferenceValue(record2.session);
5296
5548
  const error = record2?.error === void 0 ? void 0 : subagentErrorValue(record2.error);
5297
5549
  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;
5550
+ 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
5551
  return {
5300
5552
  attempt: record2.attempt,
5301
5553
  transport: record2.transport,
5302
5554
  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,
5555
+ accounting: accounting2,
5304
5556
  ...session === void 0 ? {} : { session },
5305
5557
  ...error === void 0 ? {} : { error }
5306
5558
  };
@@ -5370,11 +5622,11 @@ var WORKFLOW_RETRY_PARAMETERS = Type3.Object({ runId: Type3.String({ description
5370
5622
  // subagents/src/manager.ts
5371
5623
  import { randomUUID as randomUUID3 } from "node:crypto";
5372
5624
  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";
5625
+ import { join as join9 } from "node:path";
5626
+ import { getAgentDir as getAgentDir6 } from "@earendil-works/pi-coding-agent";
5375
5627
 
5376
5628
  // subagents/src/worktree.ts
5377
- import { join as join7 } from "node:path";
5629
+ import { join as join8 } from "node:path";
5378
5630
  function syntheticRun(context) {
5379
5631
  return {
5380
5632
  id: context.runId,
@@ -5436,7 +5688,7 @@ function createRunStoreWorktreeAdapter(home) {
5436
5688
  };
5437
5689
  }
5438
5690
  function defaultWorktreeHome(storageDirectory3) {
5439
- return join7(storageDirectory3, ".worktrees");
5691
+ return join8(storageDirectory3, ".worktrees");
5440
5692
  }
5441
5693
 
5442
5694
  // subagents/src/manager.ts
@@ -5489,7 +5741,7 @@ function validateStandaloneExtensionSources(context, external, registry, agentDi
5489
5741
  function executionRoot(context, dependencies, signal, runId, worktree, external) {
5490
5742
  const extensionContext = context.extensionContext;
5491
5743
  const model = rootModel(context);
5492
- const agentDir = dependencies.agentDir ?? getAgentDir5();
5744
+ const agentDir = dependencies.agentDir ?? getAgentDir6();
5493
5745
  const currentTrustedProject = extensionContext.isProjectTrusted();
5494
5746
  if (external.projectTrusted && !currentTrustedProject) throw new WorkflowError("RESUME_INCOMPATIBLE", "Cannot retry standalone configuration from an untrusted project");
5495
5747
  const trustedProject = currentTrustedProject;
@@ -5569,25 +5821,25 @@ function executionOptions(request, onAttempt, onProgress) {
5569
5821
  };
5570
5822
  }
5571
5823
  function storageDirectory(dependencies) {
5572
- return dependencies.storageDir ?? join8(dependencies.agentDir ?? getAgentDir5(), STORAGE_DIRECTORY);
5824
+ return dependencies.storageDir ?? join9(dependencies.agentDir ?? getAgentDir6(), STORAGE_DIRECTORY);
5573
5825
  }
5574
5826
  function runDirectory(root, id) {
5575
- return join8(root, id);
5827
+ return join9(root, id);
5576
5828
  }
5577
5829
  function requestPath(directory) {
5578
- return join8(directory, "request.json");
5830
+ return join9(directory, "request.json");
5579
5831
  }
5580
5832
  function configurationPath(directory) {
5581
- return join8(directory, CONFIGURATION_FILE);
5833
+ return join9(directory, CONFIGURATION_FILE);
5582
5834
  }
5583
5835
  function statusPath(directory) {
5584
- return join8(directory, "status.json");
5836
+ return join9(directory, "status.json");
5585
5837
  }
5586
5838
  function resultPath(directory) {
5587
- return join8(directory, "result.json");
5839
+ return join9(directory, "result.json");
5588
5840
  }
5589
5841
  function failurePath(directory) {
5590
- return join8(directory, "failure.json");
5842
+ return join9(directory, "failure.json");
5591
5843
  }
5592
5844
  async function secureDirectory(directory) {
5593
5845
  await mkdir3(directory, { recursive: true, mode: 448 });
@@ -5670,7 +5922,7 @@ async function releaseOwnerMarker(path, token) {
5670
5922
  }
5671
5923
  async function acquireStorageOwner(root, owner, liveness) {
5672
5924
  await secureDirectory(root);
5673
- const path = join8(root, OWNER_FILE);
5925
+ const path = join9(root, OWNER_FILE);
5674
5926
  for (let attempt = 0; attempt < MAX_STORAGE_OWNER_ATTEMPTS; attempt += 1) {
5675
5927
  const marker = { ...owner, acquiredAt: Date.now() };
5676
5928
  try {
@@ -5757,7 +6009,7 @@ function decodePersistedStandaloneExternalConfiguration(value) {
5757
6009
  function standaloneExternalConfiguration(context, dependencies) {
5758
6010
  const cwd = context.extensionContext.cwd;
5759
6011
  const trustedProject = context.extensionContext.isProjectTrusted();
5760
- const agentDir = dependencies.agentDir ?? getAgentDir5();
6012
+ const agentDir = dependencies.agentDir ?? getAgentDir6();
5761
6013
  const resolution = resolveWorkflowSettings(cwd, trustedProject, workflowSettingsPath(agentDir));
5762
6014
  const globalExtensionSettings = resolution.global.extensionSettings;
5763
6015
  const projectExtensionSettings = resolution.project.extensionSettings;
@@ -5923,15 +6175,15 @@ function attemptSummaryValue(value) {
5923
6175
  const transport = record2.transport;
5924
6176
  const session = record2.session === void 0 ? void 0 : sessionReferenceValue2(record2.session);
5925
6177
  const setup = setupSummaryValue(record2.setup);
5926
- const accounting3 = accountingValue(record2.accounting);
6178
+ const accounting2 = accountingValue(record2.accounting);
5927
6179
  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;
6180
+ 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
6181
  return {
5930
6182
  attempt,
5931
6183
  transport: boundedAttemptText(transport),
5932
6184
  ...session === void 0 ? {} : { session },
5933
6185
  ...error === void 0 ? {} : { error: { code: boundedAttemptText(error.code), message: boundedAttemptText(error.message) } },
5934
- accounting: { ...accounting3 },
6186
+ accounting: { ...accounting2 },
5935
6187
  setup: boundedAttemptSetup(setup)
5936
6188
  };
5937
6189
  }
@@ -5982,23 +6234,23 @@ function sessionStateValue(value) {
5982
6234
  function progressValue2(value) {
5983
6235
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
5984
6236
  const record2 = value;
5985
- const accounting3 = accountingValue(record2.accounting);
6237
+ const accounting2 = accountingValue(record2.accounting);
5986
6238
  const toolCalls = toolCallsValue2(record2.toolCalls);
5987
6239
  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;
6240
+ if (!accounting2 || !toolCalls || record2.state !== void 0 && state === void 0) return void 0;
5989
6241
  const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5990
6242
  if (record2.activity !== void 0 && activity2 === void 0) return void 0;
5991
6243
  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 } };
6244
+ return { accounting: accounting2, toolCalls, ...state === void 0 ? {} : { state }, ...activity2 === void 0 ? {} : { activity: activity2 }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
5993
6245
  }
5994
6246
  function legacyProgressValue2(record2) {
5995
- const accounting3 = legacyAccountingValue(record2);
6247
+ const accounting2 = legacyAccountingValue(record2);
5996
6248
  const toolCalls = record2.toolCalls === void 0 ? [] : toolCallsValue2(record2.toolCalls);
5997
- if (!accounting3 || !toolCalls) return void 0;
6249
+ if (!accounting2 || !toolCalls) return void 0;
5998
6250
  const activity2 = record2.activity === void 0 ? void 0 : activityValue(record2.activity);
5999
6251
  if (record2.activity !== void 0 && activity2 === void 0) return void 0;
6000
6252
  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 } };
6253
+ return { accounting: accounting2, toolCalls, ...activity2 === void 0 ? {} : { activity: activity2 }, ...record2.lastEventAt === void 0 ? {} : { lastEventAt: record2.lastEventAt } };
6002
6254
  }
6003
6255
  function worktreeContextValue(value) {
6004
6256
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -6310,7 +6562,7 @@ var PersistentSubagentManager = class {
6310
6562
  if (!sessionId.trim()) throw new WorkflowError("INTERNAL_ERROR", "Current Pi session identity is unavailable");
6311
6563
  const owner = this.runOwner;
6312
6564
  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()));
6565
+ const settings = resolveWorkflowSettings(context.extensionContext.cwd, context.extensionContext.isProjectTrusted(), workflowSettingsPath(this.dependencies.agentDir ?? getAgentDir6()));
6314
6566
  const concurrency = settings.effective.concurrency;
6315
6567
  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
6568
  this.activeRunCount += 1;
@@ -6326,8 +6578,8 @@ var PersistentSubagentManager = class {
6326
6578
  }
6327
6579
  const current = {};
6328
6580
  let resolveTerminal;
6329
- const terminal = new Promise((resolve5) => {
6330
- resolveTerminal = resolve5;
6581
+ const terminal = new Promise((resolve6) => {
6582
+ resolveTerminal = resolve6;
6331
6583
  });
6332
6584
  const executorOwnership = { default: true };
6333
6585
  const execution = Promise.resolve().then(async () => {
@@ -6441,8 +6693,9 @@ var PersistentSubagentManager = class {
6441
6693
  if (request.id !== void 0) {
6442
6694
  const id = checkedId({ id: request.id });
6443
6695
  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);
6696
+ const includeActivity = context.includeActivity === true;
6697
+ const status = active ? includeActivity ? liveStatus(active) : persistedStatus(active) : this.terminalSummaries.get(id) ?? await loadPersistedStatus(storageDirectory(this.dependencies), id);
6698
+ const inspection = publicStatus(status, context.includeAttemptMetadata === true, includeActivity);
6446
6699
  if (status.state === "completed") return { ...inspection, value: await loadPersistedResult(storageDirectory(this.dependencies), id) };
6447
6700
  if (status.state === "failed") {
6448
6701
  const failure = status.error ?? await loadPersistedFailure(storageDirectory(this.dependencies), id);
@@ -6493,6 +6746,18 @@ var PersistentSubagentManager = class {
6493
6746
  await this.stopRun(run, false);
6494
6747
  return publicStatus(persistedStatus(run));
6495
6748
  }
6749
+ /** Removes a settled record for the /subagents navigator, as /workflow deletes a terminal run; not a model tool. */
6750
+ async delete(request) {
6751
+ const id = checkedId(request);
6752
+ await this.ensureInitialized();
6753
+ const root = storageDirectory(this.dependencies);
6754
+ const status = this.activeRuns.has(id) ? void 0 : this.terminalSummaries.get(id) ?? await loadPersistedStatus(root, id, false);
6755
+ if (status === void 0 || status.state === "running") throw new WorkflowError("RUN_OWNED", `Stop subagent ${id} before deleting it`);
6756
+ if (status.worktree !== void 0) throw new WorkflowError("WORKTREE_FAILED", `Subagent ${id} still has a worktree to clean up`);
6757
+ await rm5(runDirectory(root, id), { recursive: true, force: true });
6758
+ this.terminalSummaries.delete(id);
6759
+ return { id, deleted: true };
6760
+ }
6496
6761
  async dispose() {
6497
6762
  if (this.disposePromise) return this.disposePromise;
6498
6763
  this.disposePromise = (async () => {
@@ -6589,8 +6854,8 @@ var PersistentSubagentManager = class {
6589
6854
  if (!run || !this.canSteer(run)) return;
6590
6855
  if (run.activeAttempt !== void 0 && run.finalizedAttemptAccounting.has(run.activeAttempt)) return;
6591
6856
  const snapshot = portableProgress(progress, true);
6592
- const accounting3 = addAccounting(sumAccounting(run.finalizedAttemptAccounting.values()), progress.accounting);
6593
- run.progress = { ...snapshot, accounting: accounting3 };
6857
+ const accounting2 = addAccounting(sumAccounting(run.finalizedAttemptAccounting.values()), progress.accounting);
6858
+ run.progress = { ...snapshot, accounting: accounting2 };
6594
6859
  emitUpdate(run);
6595
6860
  if (!progress.persist) return;
6596
6861
  await enqueueWrite(run, () => atomicJson(statusPath(run.directory), persistedStatus(run)));
@@ -6605,10 +6870,10 @@ var PersistentSubagentManager = class {
6605
6870
  run.attemptDetails = [...run.attemptDetails ?? [], summary].slice(-SUBAGENT_ATTEMPT_DETAILS_LIMIT);
6606
6871
  }
6607
6872
  if (!finalized) return;
6608
- const accounting3 = summary?.accounting ?? accountingValue(record2?.accounting);
6609
- if (attempt === void 0 || accounting3 === void 0) return;
6873
+ const accounting2 = summary?.accounting ?? accountingValue(record2?.accounting);
6874
+ if (attempt === void 0 || accounting2 === void 0) return;
6610
6875
  if (run.finalizedAttemptAccounting.size >= MAX_PERSISTED_ATTEMPT_ARRAY_ITEMS && !run.finalizedAttemptAccounting.has(attempt)) return;
6611
- run.finalizedAttemptAccounting.set(attempt, accounting3);
6876
+ run.finalizedAttemptAccounting.set(attempt, accounting2);
6612
6877
  const cumulative = sumAccounting(run.finalizedAttemptAccounting.values());
6613
6878
  run.progress = run.progress === void 0 ? { accounting: cumulative, toolCalls: [] } : { ...run.progress, accounting: cumulative };
6614
6879
  }
@@ -6663,8 +6928,8 @@ var PersistentSubagentManager = class {
6663
6928
  run.disposed = true;
6664
6929
  this.clearSteering(run);
6665
6930
  await this.cleanupSessions(run, true);
6666
- const worktreeCleaned = await this.cleanupWorktree(run);
6667
- if (worktreeCleaned) await enqueueWrite(run, () => atomicJson(statusPath(run.directory), persistedStatus(run)));
6931
+ await this.cleanupWorktree(run);
6932
+ await enqueueWrite(run, () => atomicJson(statusPath(run.directory), persistedStatus(run)));
6668
6933
  this.resolveTerminal(run);
6669
6934
  this.removeLiveRun(run);
6670
6935
  }
@@ -6867,23 +7132,23 @@ function createSubagentManager(dependencies = {}) {
6867
7132
  return {
6868
7133
  run: (request, context) => manager.run(request, context),
6869
7134
  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),
7135
+ getAttemptActionData: (id) => manager.getAttemptActionData(id),
7136
+ steer: (request) => manager.steer(request),
7137
+ stop: (request) => manager.stop(request),
6873
7138
  retry: (request, context) => manager.retry(request, context),
7139
+ delete: (request) => manager.delete(request),
6874
7140
  dispose: async () => {
6875
- await manager.dispose?.();
7141
+ await manager.dispose();
6876
7142
  }
6877
7143
  };
6878
7144
  }
6879
7145
 
6880
7146
  // subagents/src/navigator.ts
6881
7147
  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";
7148
+ import { join as join10 } from "node:path";
7149
+ import { copyToClipboard as copyToClipboard2, getAgentDir as getAgentDir7, SettingsManager as SettingsManager4 } from "@earendil-works/pi-coding-agent";
6884
7150
  import { Editor, truncateToWidth as truncateToWidth3 } from "@earendil-works/pi-tui";
6885
7151
  var MAX_DETAIL_TEXT = 4e3;
6886
- var MAX_DETAIL_TOOL_CALLS = 32;
6887
7152
  function objectValue2(value) {
6888
7153
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6889
7154
  }
@@ -6899,12 +7164,12 @@ function inspectionValue(value) {
6899
7164
  return record2 && status ? { status, record: record2 } : void 0;
6900
7165
  }
6901
7166
  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 };
7167
+ return { toolCallId: "subagents-command", signal: void 0, onUpdate: void 0, ...waitForForeground ? {} : { waitForForeground: false }, ...includeAttemptMetadata ? { includeAttemptMetadata: true, includeActivity: true } : {}, extensionContext: context };
6903
7168
  }
6904
7169
  async function loadRequest(storageDirectory3, id) {
6905
7170
  if (!safeRunId(id)) return {};
6906
7171
  try {
6907
- const value = JSON.parse(await readFile5(join9(storageDirectory3, id, "request.json"), "utf8"));
7172
+ const value = JSON.parse(await readFile5(join10(storageDirectory3, id, "request.json"), "utf8"));
6908
7173
  return { request: normalizeSubagentRunRequest(value) };
6909
7174
  } catch (error) {
6910
7175
  if (isFileNotFound(error)) return {};
@@ -6937,9 +7202,6 @@ async function inspectEntry(manager, storageDirectory3, entry, context) {
6937
7202
  entry: { status: inspected.status, ...request.request === void 0 ? {} : { request: request.request }, ...request.error === void 0 ? {} : { requestError: request.error } }
6938
7203
  };
6939
7204
  }
6940
- function requestLabel(request) {
6941
- return request?.label?.trim() || "none";
6942
- }
6943
7205
  function requestRole(request) {
6944
7206
  const role = request?.role;
6945
7207
  if (typeof role === "string" && role.trim()) return role.trim();
@@ -6949,13 +7211,31 @@ function requestRole(request) {
6949
7211
  function shortId(id) {
6950
7212
  return id.length > 12 ? id.slice(0, 8) : id;
6951
7213
  }
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)}`;
7214
+ function entryName(entry) {
7215
+ const role = requestRole(entry.request);
7216
+ return boundedText(entry.request?.label?.trim() || (role === "none" ? shortId(entry.status.id) : role), 256);
7217
+ }
7218
+ function listNames(entries) {
7219
+ const names = entries.map(entryName);
7220
+ return entries.map(({ status }, index) => {
7221
+ const name = names[index] ?? "";
7222
+ return names.indexOf(name) === names.lastIndexOf(name) ? name : `${name} ${shortId(status.id)}`;
7223
+ });
7224
+ }
7225
+ function pickerLabels(entries) {
7226
+ const names = listNames(entries);
7227
+ return entries.map(({ status }, index) => {
7228
+ const name = names[index] ?? "";
7229
+ const model = status.progress?.state?.model;
7230
+ const cost = formatCost(status.progress?.accounting.cost);
7231
+ const runtime2 = status.startedAt === void 0 ? "" : ` runtime=${formatWorkflowRuntime((status.finishedAt ?? Date.now()) - status.startedAt)}`;
7232
+ return `${runStateGlyph(status.state, "\u2826")} ${name} ${status.state}${model ? ` ${boundedText(model.model, 256)}${model.thinking ? `:${model.thinking}` : ""}` : ""}${cost ? ` ${cost}` : ""}${runtime2}`;
7233
+ });
6954
7234
  }
6955
7235
  function boundedText(value, limit = MAX_DETAIL_TEXT) {
6956
7236
  const text = typeof value === "string" ? value : (() => {
6957
7237
  try {
6958
- const serialized = JSON.stringify(value);
7238
+ const serialized = JSON.stringify(value, null, 2);
6959
7239
  return typeof serialized === "string" ? serialized : String(value);
6960
7240
  } catch {
6961
7241
  return String(value);
@@ -6963,13 +7243,6 @@ function boundedText(value, limit = MAX_DETAIL_TEXT) {
6963
7243
  })();
6964
7244
  return text.length > limit ? `${text.slice(0, limit)}\u2026` : text;
6965
7245
  }
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
7246
  function latestAttempt(status) {
6974
7247
  return [...status.attemptDetails ?? []].sort((left, right) => right.attempt - left.attempt)[0];
6975
7248
  }
@@ -6985,7 +7258,7 @@ function detailPresentation(inspection) {
6985
7258
  const tools = state?.tools ?? attempt?.setup.tools;
6986
7259
  const lastEventAt = status.progress?.lastEventAt;
6987
7260
  const attempts = status.attempts ?? attempt?.attempt;
6988
- const accounting3 = usageAccounting(status);
7261
+ const accounting2 = usageAccounting(status);
6989
7262
  const role = requestRole(request);
6990
7263
  const error = status.error ?? attempt?.error;
6991
7264
  return {
@@ -6998,33 +7271,34 @@ function detailPresentation(inspection) {
6998
7271
  ...attempts === void 0 ? {} : { attempts },
6999
7272
  ...status.startedAt === void 0 ? {} : { startedAt: status.startedAt },
7000
7273
  ...status.finishedAt === void 0 ? {} : { finishedAt: status.finishedAt },
7001
- ...accounting3 === void 0 ? {} : { accounting: accounting3 },
7274
+ ...accounting2 === void 0 ? {} : { accounting: accounting2 },
7002
7275
  ...error === void 0 ? {} : { error: { code: boundedText(error.code, 256), message: boundedText(error.message) } }
7003
7276
  };
7004
7277
  }
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())
7278
+ function detailRows(inspection, styles, menu) {
7279
+ const { entry } = inspection;
7280
+ const presentation = detailPresentation(inspection);
7281
+ return [
7282
+ styles.bold(`Selected subagent: ${entryName(entry)}`),
7283
+ `ID: ${boundedText(entry.status.id, 256)}`,
7284
+ ...formatAgentDetail(presentation, styles, entry.status.finishedAt ?? Date.now(), { includeError: false }),
7285
+ ...entry.requestError === void 0 ? [] : [styles.warning(`Request unavailable: ${boundedText(entry.requestError)}`)],
7286
+ ...menu === "hint" ? [styles.muted("enter agent actions")] : [],
7287
+ ...presentation.error === void 0 ? [] : [formatAgentError(presentation.error, styles)],
7288
+ ...typeof menu === "object" ? [styles.bold("Agent actions"), ...menu.options.map((option, index) => index === menu.index ? `\u2192 ${styles.accent(option)}` : ` ${option}`)] : []
7013
7289
  ];
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;
7290
+ }
7291
+ function listRows(entries, selectedId, styles) {
7292
+ const names = listNames(entries);
7293
+ 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"))}`)];
7294
+ }
7295
+ function headerRows(entries, styles) {
7296
+ const counts = ["running", "failed", "stopped", "completed"].flatMap((state) => {
7297
+ const count = entries.filter((entry) => entry.status.state === state).length;
7298
+ return count ? [`${String(count)} ${state}`] : [];
7299
+ });
7300
+ const cost = formatCost(entries.reduce((sum, entry) => sum + (entry.status.progress?.accounting.cost ?? 0), 0));
7301
+ return [styles.bold(styles.accent("Subagents")), [...counts, ...cost ? [cost] : []].join(" \xB7 ")];
7028
7302
  }
7029
7303
  function tuiRows(tui) {
7030
7304
  return typeof tui.terminal?.rows === "number" && Number.isFinite(tui.terminal.rows) ? tui.terminal.rows : 24;
@@ -7034,6 +7308,7 @@ function unrefTimer(timer) {
7034
7308
  if (typeof value !== "object" || value === null || !("unref" in value) || typeof value.unref !== "function") return;
7035
7309
  Reflect.apply(value.unref, value, []);
7036
7310
  }
7311
+ var DASHBOARD_FOOTER_ROWS = 2;
7037
7312
  function standaloneActionContext(manager, inspection, context) {
7038
7313
  const status = inspection.entry.status;
7039
7314
  const request = inspection.entry.request;
@@ -7070,11 +7345,16 @@ function standaloneActionContext(manager, inspection, context) {
7070
7345
  function liveSystemPrompt(manager, status) {
7071
7346
  return manager.getAttemptActionData?.(status.id)?.prepared?.systemPrompt;
7072
7347
  }
7073
- function actionOptions(manager, inspection, context) {
7348
+ var BULK_DELETE = { "Delete all completed": "completed", "Delete all failed": "failed" };
7349
+ function bulkDeleteLabels(manager, entries) {
7350
+ if (manager.delete === void 0) return [];
7351
+ return Object.entries(BULK_DELETE).filter(([, state]) => entries.some((entry) => entry.status.state === state)).map(([label2]) => label2);
7352
+ }
7353
+ function actionOptions(manager, inspection, context, bulk = []) {
7074
7354
  const actionContext = standaloneActionContext(manager, inspection, context);
7075
7355
  const extensionLabels = actionContext === void 0 ? [] : visibleStandaloneAgentAttemptActions(loadingRegistry().agentAttemptActions(), actionContext).map(([, action]) => action.label);
7076
7356
  const value = inspection.record.value;
7077
- return agentActionLabels({
7357
+ const labels = agentActionLabels({
7078
7358
  extensionLabels,
7079
7359
  hasWorktree: inspection.entry.status.worktree !== void 0,
7080
7360
  openPrompt: context.mode === "tui" && inspection.entry.request?.prompt !== void 0,
@@ -7082,13 +7362,33 @@ function actionOptions(manager, inspection, context) {
7082
7362
  openResult: context.mode === "tui" && Object.prototype.hasOwnProperty.call(inspection.record, "value") && jsonValue(value),
7083
7363
  standaloneState: inspection.entry.status.state
7084
7364
  });
7365
+ const runActions = [...manager.delete !== void 0 && inspection.entry.status.state !== "running" ? ["Delete"] : [], ...bulk, ...context.mode === "tui" ? ["Copy run path"] : []];
7366
+ labels.splice(labels.indexOf("Copy agent ID"), 0, ...runActions);
7367
+ return labels;
7368
+ }
7369
+ async function deleteAll(manager, storageDirectory3, context, state, confirm) {
7370
+ if (!await confirm(`Delete ${state} runs?`, `Delete all ${state} subagent runs and their records? This cannot be undone.`)) return false;
7371
+ const skipped = [];
7372
+ let deleted = 0;
7373
+ for (const entry of await loadEntries(manager, storageDirectory3, context)) {
7374
+ if (entry.status.state !== state) continue;
7375
+ try {
7376
+ await manager.delete?.({ id: entry.status.id }, managerContext(context));
7377
+ deleted += 1;
7378
+ } catch (error) {
7379
+ skipped.push(`${entry.status.id} (${errorText(error)})`);
7380
+ }
7381
+ }
7382
+ if (skipped.length) context.ui.notify(`Skipped ${state} runs: ${skipped.join(", ")}.`, "warning");
7383
+ if (deleted) context.ui.notify(`Deleted ${String(deleted)} ${state} subagent run(s).`, "info");
7384
+ return true;
7085
7385
  }
7086
7386
  function retryResult(value) {
7087
7387
  const record2 = objectValue2(value);
7088
7388
  return record2 && typeof record2.id === "string" && record2.id.trim() && record2.state === "running" ? { id: record2.id, state: "running" } : void 0;
7089
7389
  }
7090
7390
  async function openNavigatorArtifact(context, tui, artifact, label2) {
7091
- const command = SettingsManager4.create(context.cwd, getAgentDir6(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7391
+ const command = SettingsManager4.create(context.cwd, getAgentDir7(), { projectTrusted: context.isProjectTrusted() }).getExternalEditorCommand();
7092
7392
  if (!command) {
7093
7393
  context.ui.notify(`Cannot open ${label2}: no external editor is configured.`, "warning");
7094
7394
  return;
@@ -7102,7 +7402,7 @@ async function steerSubagent(manager, storageDirectory3, entry, context, message
7102
7402
  await manager.steer({ id: entry.status.id, message }, managerContext(context));
7103
7403
  context.ui.notify(`Steered subagent ${entry.status.id}.`, "info");
7104
7404
  }
7105
- async function performAction(manager, storageDirectory3, entry, action, context, tui, clipboard) {
7405
+ async function performAction(manager, storageDirectory3, entry, action, context, tui, clipboard, confirm) {
7106
7406
  const fresh = await inspectEntry(manager, storageDirectory3, entry, context);
7107
7407
  const available = actionOptions(manager, fresh, context);
7108
7408
  if (!available.includes(action)) throw new Error(`Action ${action} is no longer available`);
@@ -7116,40 +7416,52 @@ async function performAction(manager, storageDirectory3, entry, action, context,
7116
7416
  if (action === "Copy agent ID") {
7117
7417
  await clipboard(fresh.entry.status.id);
7118
7418
  context.ui.notify("Copied agent ID.", "info");
7119
- return "stay";
7419
+ return "unchanged";
7420
+ }
7421
+ if (action === "Copy run path") {
7422
+ await clipboard(join10(storageDirectory3, fresh.entry.status.id));
7423
+ context.ui.notify("Copied run path.", "info");
7424
+ return "unchanged";
7425
+ }
7426
+ if (action === "Delete" && manager.delete !== void 0) {
7427
+ if (!await confirm("Delete subagent?", `Delete ${entryName(fresh.entry)} (${fresh.entry.status.id}) and its record? This cannot be undone.`)) return "unchanged";
7428
+ await manager.delete({ id: fresh.entry.status.id }, managerContext(context));
7429
+ context.ui.notify(`Deleted subagent ${fresh.entry.status.id}.`, "info");
7430
+ return "deleted";
7120
7431
  }
7121
7432
  if (action === "Copy branch" && fresh.entry.status.worktree) {
7122
7433
  await clipboard(fresh.entry.status.worktree.branch);
7123
7434
  context.ui.notify("Copied branch.", "info");
7124
- return "stay";
7435
+ return "unchanged";
7125
7436
  }
7126
7437
  if (action === "Copy worktree path" && fresh.entry.status.worktree) {
7127
7438
  await clipboard(fresh.entry.status.worktree.path);
7128
7439
  context.ui.notify("Copied worktree path.", "info");
7129
- return "stay";
7440
+ return "unchanged";
7130
7441
  }
7131
7442
  if (action === "Open prompt in editor" && tui && fresh.entry.request?.prompt !== void 0) {
7132
7443
  await openNavigatorArtifact(context, tui, workflowPromptArtifact(fresh.entry.request.prompt), "agent prompt");
7133
- return "stay";
7444
+ return "unchanged";
7134
7445
  }
7135
7446
  if (action === "Open system prompt in editor" && tui) {
7136
7447
  const systemPrompt = liveSystemPrompt(manager, fresh.entry.status);
7137
7448
  if (systemPrompt !== void 0) {
7138
7449
  await openNavigatorArtifact(context, tui, workflowPromptArtifact(systemPrompt), "agent system prompt");
7139
- return "stay";
7450
+ return "unchanged";
7140
7451
  }
7141
7452
  }
7142
7453
  if (action === "Open result in editor" && tui && Object.prototype.hasOwnProperty.call(fresh.record, "value") && jsonValue(fresh.record.value)) {
7143
7454
  await openNavigatorArtifact(context, tui, workflowResultArtifact(fresh.record.value), "agent result");
7144
- return "stay";
7455
+ return "unchanged";
7145
7456
  }
7146
7457
  if (action === "Steer") {
7147
7458
  const message = await context.ui.input("Steer subagent", "Message for the running subagent");
7148
- if (message === void 0) return "stay";
7459
+ if (message === void 0) return "unchanged";
7149
7460
  await steerSubagent(manager, storageDirectory3, entry, context, message);
7150
7461
  return "stay";
7151
7462
  }
7152
7463
  if (action === "Stop") {
7464
+ if (!await confirm("Stop subagent?", `Stop subagent ${entryName(fresh.entry)} (${fresh.entry.status.id})? This cannot be undone.`)) return "unchanged";
7153
7465
  const current = await inspectEntry(manager, storageDirectory3, entry, context);
7154
7466
  if (current.entry.status.state !== "running") throw new Error(`Subagent ${entry.status.id} is no longer running`);
7155
7467
  await manager.stop({ id: entry.status.id }, managerContext(context));
@@ -7162,31 +7474,36 @@ async function performAction(manager, storageDirectory3, entry, action, context,
7162
7474
  const result = retryResult(await manager.retry({ id: entry.status.id }, managerContext(context, false)));
7163
7475
  if (!result) throw new Error("Retry returned an invalid subagent result");
7164
7476
  context.ui.notify(`Retried subagent ${entry.status.id} as ${result.id}.`, "info");
7165
- return "retry";
7477
+ return { retryId: result.id };
7166
7478
  }
7167
7479
  return "stay";
7168
7480
  }
7169
7481
  async function showDetail(manager, storageDirectory3, entry, context, clipboard) {
7170
7482
  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
- }
7483
+ for (; ; ) {
7484
+ const action = await context.ui.select(detailRows(inspection, PLAIN_WORKFLOW_PROGRESS_STYLES).join("\n"), actionOptions(manager, inspection, context));
7485
+ if (!action || action === "Back") return;
7486
+ try {
7487
+ const outcome = await performAction(manager, storageDirectory3, entry, action, context, void 0, clipboard, (title, message) => context.ui.confirm(title, message));
7488
+ if (outcome === "deleted" || typeof outcome === "object") return;
7489
+ inspection = await inspectEntry(manager, storageDirectory3, entry, context);
7490
+ } catch (error) {
7491
+ context.ui.notify(`Cannot ${action.toLowerCase()}: ${errorText(error)}`, "warning");
7181
7492
  }
7182
7493
  }
7494
+ }
7495
+ async function showDashboard(manager, storageDirectory3, initial, context, clipboard) {
7496
+ const first = initial[0];
7497
+ if (first === void 0) return;
7498
+ let initialInspection;
7499
+ try {
7500
+ initialInspection = await inspectEntry(manager, storageDirectory3, first, context);
7501
+ } catch (error) {
7502
+ context.ui.notify(`Cannot inspect subagent ${first.status.id}: ${errorText(error)}`, "warning");
7503
+ initialInspection = { entry: first, record: {} };
7504
+ }
7183
7505
  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;
7506
+ const styles = themeWorkflowProgressStyles(theme);
7190
7507
  const editorTheme = {
7191
7508
  borderColor: (text) => theme.fg("accent", text),
7192
7509
  selectList: {
@@ -7198,226 +7515,273 @@ async function showDetail(manager, storageDirectory3, entry, context, clipboard)
7198
7515
  }
7199
7516
  };
7200
7517
  const steerEditor = new Editor(tui, editorTheme);
7201
- let refreshTimer;
7518
+ let entries = initial;
7519
+ let inspection = initialInspection;
7520
+ let offset = 0;
7521
+ let detailsMode = false;
7522
+ let actionMode = false;
7523
+ let actionIndex = 0;
7524
+ let steerMode = false;
7525
+ const inlineConfirm = createInlineConfirm(() => {
7526
+ if (!disposed) tui.requestRender();
7527
+ });
7528
+ const confirmInline = (title, message) => inlineConfirm.ask(title, message);
7529
+ let actionRunning = false;
7202
7530
  let refreshing = false;
7203
- let refreshGeneration = 0;
7531
+ let disposed = false;
7532
+ let generation = 0;
7533
+ let renderedWidth = 80;
7534
+ let selectionNeedsScroll = true;
7535
+ let timer;
7536
+ const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
7537
+ const matches = (data, binding) => workflowKeyMatches(keybindings, data, binding);
7204
7538
  const requestRender = () => {
7205
7539
  if (!disposed) tui.requestRender();
7206
7540
  };
7207
- const stopRefresh = () => {
7208
- if (refreshTimer !== void 0) {
7209
- clearInterval(refreshTimer);
7210
- refreshTimer = void 0;
7541
+ const options = () => actionOptions(manager, inspection, context, bulkDeleteLabels(manager, entries));
7542
+ const warn = (message) => {
7543
+ if (disposed) return;
7544
+ try {
7545
+ context.ui.notify(message, "warning");
7546
+ } catch {
7211
7547
  }
7212
- refreshGeneration += 1;
7213
7548
  };
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));
7549
+ const stopTimer = () => {
7550
+ if (timer === void 0) return;
7551
+ clearInterval(timer);
7552
+ timer = void 0;
7224
7553
  };
7225
- const actionView = () => {
7226
- const detail = detailLines(inspection, theme);
7227
- const options = actionOptions(manager, inspection, context);
7228
- return { detail, options, rows: buildActionRows(detail, options) };
7554
+ const syncTimer = () => {
7555
+ if (!entries.some((entry) => entry.status.state === "running") && inspection.entry.status.state !== "running") stopTimer();
7556
+ else if (timer === void 0 && !disposed) {
7557
+ timer = setInterval(() => {
7558
+ if (timer !== void 0) void refresh();
7559
+ }, 1e3);
7560
+ unrefTimer(timer);
7561
+ }
7229
7562
  };
7230
- const scrollActionIntoView = (view = actionView()) => {
7231
- const { options, rows } = view;
7232
- if (actionIndex >= options.length) {
7233
- clampOffset(rows);
7234
- return;
7563
+ const apply = (nextEntries, next) => {
7564
+ const selectedAction = actionMode ? options()[actionIndex] : void 0;
7565
+ const position = (list) => list.findIndex((entry) => entry.status.id === next.entry.status.id);
7566
+ if (position(entries) !== position(nextEntries)) selectionNeedsScroll = true;
7567
+ entries = nextEntries;
7568
+ inspection = next;
7569
+ if (selectedAction !== void 0) {
7570
+ const nextOptions = options();
7571
+ const kept = nextOptions.indexOf(selectedAction);
7572
+ actionIndex = kept >= 0 ? kept : Math.min(actionIndex, Math.max(0, nextOptions.length - 1));
7235
7573
  }
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);
7574
+ syncTimer();
7575
+ requestRender();
7241
7576
  };
7242
- const reportRefreshError = (error) => {
7243
- if (disposed) return;
7577
+ const reload = async (id = inspection.entry.status.id) => {
7578
+ const current = ++generation;
7579
+ const nextEntries = await loadEntries(manager, storageDirectory3, context);
7580
+ if (current !== generation) return;
7581
+ const selected = nextEntries.find((entry) => entry.status.id === id) ?? nextEntries[0];
7582
+ if (selected === void 0) {
7583
+ context.ui.notify("No durable subagent runs in this session.", "info");
7584
+ close(void 0);
7585
+ return;
7586
+ }
7587
+ let next;
7244
7588
  try {
7245
- context.ui.notify(`Cannot refresh subagent ${entry.status.id}: ${errorText(error)}`, "warning");
7246
- } catch {
7589
+ next = await inspectEntry(manager, storageDirectory3, selected, context);
7590
+ } catch (error) {
7591
+ if (current === generation) apply(nextEntries, { entry: selected, record: {} });
7592
+ throw error;
7247
7593
  }
7594
+ if (current === generation) apply(nextEntries, next);
7248
7595
  };
7249
- const refreshInspection = async () => {
7250
- if (disposed || actionRunning || refreshing || inspection.entry.status.state !== "running") return;
7596
+ const refresh = async () => {
7597
+ if (disposed || actionRunning || refreshing) return;
7251
7598
  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();
7599
+ const current = generation;
7600
+ const selectedId = inspection.entry.status.id;
7257
7601
  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);
7602
+ const stale = entries.filter((entry) => entry.status.state === "running" || entry.status.id === selectedId);
7603
+ const results = await Promise.allSettled(stale.map((entry) => inspectEntry(manager, storageDirectory3, entry, context)));
7604
+ if (current !== generation) return;
7605
+ const fresh = new Map(results.flatMap((result2) => result2.status === "fulfilled" ? [[result2.value.entry.status.id, result2.value]] : []));
7606
+ apply(attentionSort(entries.map((entry) => fresh.get(entry.status.id)?.entry ?? entry)), fresh.get(selectedId) ?? inspection);
7272
7607
  } finally {
7273
7608
  refreshing = false;
7274
7609
  }
7275
7610
  };
7611
+ const select = (delta) => {
7612
+ const index = Math.max(0, entries.findIndex((entry2) => entry2.status.id === inspection.entry.status.id));
7613
+ const entry = entries[(index + delta + entries.length) % entries.length];
7614
+ if (entry === void 0 || entry.status.id === inspection.entry.status.id) return;
7615
+ inspection = { entry, record: {} };
7616
+ selectionNeedsScroll = true;
7617
+ const current = ++generation;
7618
+ void inspectEntry(manager, storageDirectory3, entry, context).then((next) => {
7619
+ if (current === generation) apply(entries, next);
7620
+ }, (error) => {
7621
+ if (current === generation) warn(`Cannot inspect subagent ${entry.status.id}: ${errorText(error)}`);
7622
+ });
7623
+ };
7624
+ const shutDown = () => {
7625
+ disposed = true;
7626
+ generation += 1;
7627
+ stopTimer();
7628
+ inlineConfirm.cancel();
7629
+ };
7276
7630
  const close = (value) => {
7277
7631
  if (disposed) return;
7278
- disposed = true;
7279
- stopRefresh();
7632
+ shutDown();
7280
7633
  done(value);
7281
7634
  };
7282
7635
  steerEditor.onSubmit = (value) => {
7283
7636
  const message = value.trim();
7284
- if (message) close({ kind: "steer", message });
7637
+ if (message) close({ entry: inspection.entry, message });
7285
7638
  };
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"));
7639
+ const reloadAfterAction = async (id) => {
7640
+ try {
7641
+ await reload(id);
7642
+ } catch (error) {
7643
+ warn(`Cannot refresh subagents: ${errorText(error)}`);
7644
+ }
7298
7645
  };
7299
7646
  const runAction = (action) => {
7300
- if (disposed) return;
7301
7647
  actionRunning = true;
7302
7648
  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();
7649
+ const bulk = BULK_DELETE[action];
7650
+ 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");
7651
+ void pending.then(async (outcome) => {
7652
+ if (disposed || outcome === "unchanged") return;
7653
+ await reloadAfterAction(typeof outcome === "object" ? outcome.retryId : void 0);
7313
7654
  actionMode = false;
7314
7655
  actionIndex = 0;
7315
7656
  offset = 0;
7316
- }).catch((error) => {
7317
- if (!disposed) context.ui.notify(`Cannot ${action.toLowerCase()}: ${errorText(error)}`, "warning");
7657
+ selectionNeedsScroll = true;
7658
+ }, async (error) => {
7659
+ warn(`Cannot ${action.toLowerCase()}: ${errorText(error)}`);
7660
+ if (!disposed) await reloadAfterAction();
7318
7661
  }).finally(() => {
7319
7662
  actionRunning = false;
7320
- if (!disposed) requestRender();
7663
+ requestRender();
7321
7664
  });
7322
7665
  };
7323
- if (inspection.entry.status.state === "running") {
7324
- refreshTimer = setInterval(() => {
7325
- void refreshInspection().catch(reportRefreshError);
7326
- }, 1e3);
7327
- unrefTimer(refreshTimer);
7328
- }
7666
+ syncTimer();
7329
7667
  return {
7330
- render: renderLines,
7668
+ render(width) {
7669
+ if (disposed) return [];
7670
+ renderedWidth = width;
7671
+ const narrow = width < 80;
7672
+ const actions = actionMode ? options() : [];
7673
+ const menu = actionMode ? { options: actions, index: actionIndex } : steerMode ? void 0 : "hint";
7674
+ const layout = narrow ? detailsMode || actionMode || steerMode ? { detailsOnly: true } : { treeOnly: true } : {};
7675
+ const content = [...headerRows(entries, styles), ...formatNavigatorColumns(listRows(entries, inspection.entry.status.id, styles), detailRows(inspection, styles, menu), width, layout)];
7676
+ const footer = steerMode ? [styles.bold("Steer subagent"), ...steerEditor.render(width)] : inlineConfirm.rows(width, styles);
7677
+ const rows = Math.max(1, tuiRows(tui) - DASHBOARD_FOOTER_ROWS);
7678
+ const hintRows = rows >= 3 ? 1 : 0;
7679
+ const viewport = Math.max(1, rows - hintRows - footer.length);
7680
+ const keepVisible = (row) => {
7681
+ if (row < 0) return;
7682
+ if (row < offset) offset = row;
7683
+ else if (row >= offset + viewport) offset = row - viewport + 1;
7684
+ };
7685
+ if (actionMode) keepVisible(content.findIndex((line) => narrow ? line.startsWith("\u2192 ") : line.includes(" | \u2192 ")));
7686
+ else if (selectionNeedsScroll && !(narrow && detailsMode)) {
7687
+ keepVisible(content.findIndex((line) => line.startsWith("\u2192")));
7688
+ selectionNeedsScroll = false;
7689
+ }
7690
+ offset = Math.max(0, Math.min(Math.max(0, content.length - viewport), offset));
7691
+ const up = keyLabel("tui.select.up", "\u2191");
7692
+ const down = keyLabel("tui.select.down", "\u2193");
7693
+ const enter = keyLabel("tui.select.confirm", "enter");
7694
+ const esc = keyLabel("tui.select.cancel", "esc");
7695
+ const scroll = content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : "";
7696
+ const refreshHint = timer === void 0 ? "" : " \xB7 auto-refresh 1s";
7697
+ const back = narrow && detailsMode ? "details" : "list";
7698
+ 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}`;
7699
+ return [...content.slice(offset, offset + viewport), ...footer, ...hintRows ? [styles.dim(hint)] : []].map((line) => truncateToWidth3(line, Math.max(1, width), "\u2026"));
7700
+ },
7331
7701
  invalidate() {
7332
7702
  },
7333
7703
  handleInput(data) {
7334
- if (disposed || actionRunning) return;
7704
+ if (disposed) return;
7705
+ if (inlineConfirm.active()) {
7706
+ inlineConfirm.handleInput(keybindings, data);
7707
+ return;
7708
+ }
7709
+ if (actionRunning) return;
7335
7710
  if (steerMode) {
7336
7711
  if (keybindings.matches(data, "tui.select.cancel")) {
7337
7712
  steerMode = false;
7338
7713
  actionMode = true;
7339
7714
  steerEditor.setText("");
7340
- offset = 0;
7341
- scrollActionIntoView();
7342
7715
  } else steerEditor.handleInput(data);
7343
7716
  requestRender();
7344
7717
  return;
7345
7718
  }
7346
- if (!actionMode && data === "a") {
7719
+ const narrow = renderedWidth < 80;
7720
+ const page = Math.max(1, tuiRows(tui) - DASHBOARD_FOOTER_ROWS - 1);
7721
+ if (matches(data, "tui.select.pageUp")) offset = Math.max(0, offset - page);
7722
+ else if (matches(data, "tui.select.pageDown")) offset += page;
7723
+ else if (actionMode) {
7724
+ const actions = options();
7725
+ if (matches(data, "tui.select.cancel") || matches(data, "tui.editor.cursorLeft")) {
7726
+ actionMode = false;
7727
+ offset = 0;
7728
+ selectionNeedsScroll = true;
7729
+ } else if (matches(data, "tui.select.up")) actionIndex = (actionIndex + actions.length - 1) % actions.length;
7730
+ else if (matches(data, "tui.select.down")) actionIndex = (actionIndex + 1) % actions.length;
7731
+ else if (matches(data, "tui.select.confirm")) {
7732
+ const action = actions[actionIndex];
7733
+ if (action === "Steer") {
7734
+ steerMode = true;
7735
+ actionMode = false;
7736
+ steerEditor.setText("");
7737
+ } else if (action && action !== "Back") runAction(action);
7738
+ else {
7739
+ actionMode = false;
7740
+ offset = 0;
7741
+ selectionNeedsScroll = true;
7742
+ }
7743
+ }
7744
+ } else if (data === "a" || data === "A") {
7347
7745
  actionMode = true;
7348
7746
  actionIndex = 0;
7349
7747
  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")) {
7748
+ } else if (matches(data, "tui.select.cancel")) {
7749
+ if (!narrow || !detailsMode) {
7750
+ close(void 0);
7751
+ return;
7752
+ }
7753
+ detailsMode = false;
7754
+ offset = 0;
7755
+ selectionNeedsScroll = true;
7756
+ } else if (narrow && detailsMode) {
7757
+ if (matches(data, "tui.select.up")) offset = Math.max(0, offset - 1);
7758
+ else if (matches(data, "tui.select.down")) offset += 1;
7759
+ else if (matches(data, "tui.select.confirm")) {
7365
7760
  actionMode = true;
7366
7761
  actionIndex = 0;
7367
7762
  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
7763
  }
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);
7764
+ } else if (matches(data, "tui.select.up")) select(-1);
7765
+ else if (matches(data, "tui.select.down")) select(1);
7766
+ else if (matches(data, "tui.select.confirm")) {
7767
+ if (narrow) detailsMode = true;
7768
+ else actionMode = true;
7769
+ actionIndex = 0;
7770
+ offset = 0;
7403
7771
  }
7404
7772
  requestRender();
7405
7773
  },
7406
7774
  dispose() {
7407
- if (!disposed) {
7408
- disposed = true;
7409
- stopRefresh();
7410
- }
7775
+ shutDown();
7411
7776
  }
7412
7777
  };
7413
7778
  });
7414
- if (!result || result === "retry") return;
7779
+ if (result === void 0) return;
7415
7780
  try {
7416
- await steerSubagent(manager, storageDirectory3, entry, context, result.message);
7781
+ await steerSubagent(manager, storageDirectory3, result.entry, context, result.message);
7417
7782
  } catch (error) {
7418
7783
  context.ui.notify(`Cannot steer: ${errorText(error)}`, "warning");
7419
7784
  }
7420
- return "exit";
7421
7785
  }
7422
7786
  async function runNavigator(manager, storageDirectory3, args, context, clipboard) {
7423
7787
  if (args.trim()) {
@@ -7430,17 +7794,30 @@ async function runNavigator(manager, storageDirectory3, args, context, clipboard
7430
7794
  context.ui.notify("No durable subagent runs in this session.", "info");
7431
7795
  return;
7432
7796
  }
7797
+ const labels = pickerLabels(entries);
7433
7798
  if (!context.hasUI) {
7434
- context.ui.notify(entries.map((entry, index) => pickerLabel(entry, index)).join("\n"), "info");
7799
+ context.ui.notify(labels.join("\n"), "info");
7800
+ return;
7801
+ }
7802
+ if (context.mode === "tui") {
7803
+ await showDashboard(manager, storageDirectory3, entries, context, clipboard);
7435
7804
  return;
7436
7805
  }
7437
- const labels = entries.map(pickerLabel);
7438
- const choice = await context.ui.select("Subagents\n", [...labels, "Close"]);
7806
+ const choice = await context.ui.select("Subagents\n", [...labels, "Close", ...bulkDeleteLabels(manager, entries)]);
7439
7807
  if (!choice || choice === "Close") return;
7808
+ const bulk = BULK_DELETE[choice];
7809
+ if (bulk !== void 0) {
7810
+ try {
7811
+ await deleteAll(manager, storageDirectory3, context, bulk, (title, message) => context.ui.confirm(title, message));
7812
+ } catch (error) {
7813
+ context.ui.notify(`Cannot delete ${bulk} runs: ${errorText(error)}`, "warning");
7814
+ }
7815
+ continue;
7816
+ }
7440
7817
  const selected = entries[labels.indexOf(choice)];
7441
7818
  if (!selected) return;
7442
7819
  try {
7443
- if (await showDetail(manager, storageDirectory3, selected, context, clipboard) === "exit") return;
7820
+ await showDetail(manager, storageDirectory3, selected, context, clipboard);
7444
7821
  } catch (error) {
7445
7822
  context.ui.notify(`Cannot inspect subagent ${selected.status.id}: ${errorText(error)}`, "warning");
7446
7823
  }
@@ -7461,7 +7838,7 @@ function registerSubagentNavigator(registerCommand, manager, storageDirectory3,
7461
7838
 
7462
7839
  // subagents/src/view.ts
7463
7840
  import { truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
7464
- var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7841
+ var SPINNER2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7465
7842
  var TERMINAL_STATES = /* @__PURE__ */ new Set(["completed", "failed", "stopped"]);
7466
7843
  var MAX_INSPECT_VALUE_CHARS = 4e3;
7467
7844
  var MAX_INSPECT_TOOL_CALLS = 32;
@@ -7527,8 +7904,8 @@ function runtime(startedAt, finishedAt, now) {
7527
7904
  const remainingMinutes = minutes % 60;
7528
7905
  return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
7529
7906
  }
7530
- function stateGlyph(state, spinner) {
7531
- if (state === "running") return spinner;
7907
+ function stateGlyph(state, spinner2) {
7908
+ if (state === "running") return spinner2;
7532
7909
  return state === "completed" ? "\u2713" : "\u2717";
7533
7910
  }
7534
7911
  function stateColor(state) {
@@ -7552,13 +7929,15 @@ function stalledDuration2(status, now) {
7552
7929
  const duration = now - lastEventAt;
7553
7930
  return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : void 0;
7554
7931
  }
7555
- function accounting2(status) {
7932
+ function usageStats(status) {
7556
7933
  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"}`;
7934
+ return value ? [formatTokens(value.input + value.output), formatCost(value.cost)].filter(Boolean).join(" \xB7 ") : "";
7560
7935
  }
7561
- function timestamp2(value) {
7936
+ function modelName(status) {
7937
+ const model = status.progress?.state?.model ?? status.attemptDetails?.at(-1)?.setup.model;
7938
+ return model ? `${model.model}${model.thinking ? `:${model.thinking}` : ""}` : "";
7939
+ }
7940
+ function timestamp(value) {
7562
7941
  if (value === void 0 || !Number.isFinite(value) || value < 0) return void 0;
7563
7942
  const date = new Date(value);
7564
7943
  return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
@@ -7573,24 +7952,23 @@ function boundedValue(value) {
7573
7952
  }
7574
7953
  return text.length > MAX_INSPECT_VALUE_CHARS ? `${text.slice(0, MAX_INSPECT_VALUE_CHARS)}\u2026` : text;
7575
7954
  }
7576
- function formatSubagentProgress(status, args, theme, spinner, now, expanded) {
7955
+ function formatSubagentProgress(status, args, theme, spinner2, now, expanded) {
7577
7956
  const color = stateColor(status.state);
7578
7957
  const elapsed = runtime(status.startedAt, status.finishedAt, now);
7579
7958
  const metadata = requestMetadata(args, args.id === void 0);
7959
+ const stats = usageStats(status);
7580
7960
  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}` : ""}`
7961
+ `${theme.fg(color, stateGlyph(status.state, spinner2))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${stats ? ` ${stats}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`
7582
7962
  ];
7583
7963
  const current = activity(status);
7584
7964
  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)}`)}`}`);
7965
+ if (current) lines.push(` ${theme.fg("accent", spinner2)} ${theme.fg("dim", current)}${stalled === void 0 ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
7586
7966
  else if (stalled !== void 0) lines.push(` ${theme.fg("warning", `stalled? ${formatStalledDuration(stalled)}`)}`);
7587
7967
  if (status.error) lines.push(` ${theme.fg("error", `${status.error.code}: ${status.error.message}`)}`);
7588
7968
  if (expanded) {
7589
7969
  lines.push(` ${theme.fg("dim", `id=${status.id}`)}`);
7590
7970
  const model = status.progress?.state?.model;
7591
7971
  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
7972
  if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
7595
7973
  }
7596
7974
  return lines.join("\n");
@@ -7607,9 +7985,9 @@ function formatSubagentInspection(status, args, details, theme, expanded) {
7607
7985
  const lines = [formatSubagentProgress(status, { id: args.id ?? status.id }, theme, "\u25C7", Date.now(), false)];
7608
7986
  if (!expanded) return lines[0] ?? "";
7609
7987
  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);
7988
+ const startedAt = timestamp(status.startedAt);
7989
+ const finishedAt = timestamp(status.finishedAt);
7990
+ const lastEventAt = timestamp(status.progress?.lastEventAt);
7613
7991
  if (startedAt) lines.push(` ${theme.fg("dim", `startedAt=${startedAt}`)}`);
7614
7992
  if (finishedAt) lines.push(` ${theme.fg("dim", `finishedAt=${finishedAt}`)}`);
7615
7993
  if (lastEventAt) lines.push(` ${theme.fg("dim", `lastEventAt=${lastEventAt}`)}`);
@@ -7617,8 +7995,8 @@ function formatSubagentInspection(status, args, details, theme, expanded) {
7617
7995
  if (model) lines.push(` ${theme.fg("dim", `model=${model.provider}/${model.model}${model.thinking ? `:${model.thinking}` : ""}`)}`);
7618
7996
  const currentActivity = status.progress?.activity;
7619
7997
  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"}`)}`);
7998
+ const accounting2 = status.progress?.accounting;
7999
+ 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
8000
  if (status.worktree) lines.push(` ${theme.fg("dim", `worktree=${status.worktree.path} branch=${status.worktree.branch}`)}`);
7623
8001
  const toolCalls = status.progress?.toolCalls;
7624
8002
  if (toolCalls?.length) {
@@ -7650,7 +8028,7 @@ function subagentProgressBlock(status, args, theme, freezeAt) {
7650
8028
  render(width) {
7651
8029
  const now = TERMINAL_STATES.has(current.state) ? frozenAt : Date.now();
7652
8030
  if (!TERMINAL_STATES.has(current.state)) frozenAt = now;
7653
- const frame = SPINNER[Math.floor(now / 80) % SPINNER.length] ?? "\u25C7";
8031
+ const frame = SPINNER2[Math.floor(now / 80) % SPINNER2.length] ?? "\u25C7";
7654
8032
  return formatSubagentProgress(current, args, currentTheme, frame, now, expanded).split("\n").map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026"));
7655
8033
  },
7656
8034
  invalidate() {
@@ -7688,9 +8066,68 @@ function renderSubagentResult(result, options, theme, context) {
7688
8066
  component.setExpanded(options.expanded);
7689
8067
  return component;
7690
8068
  }
7691
- function createSubagentBackgroundWidget() {
8069
+ function widgetRow({ status, request }, now, paint) {
8070
+ const lastEventAt = status.progress?.lastEventAt;
8071
+ const silent = lastEventAt === void 0 || !Number.isFinite(lastEventAt) || now - lastEventAt < QUIET_MS ? void 0 : now - lastEventAt;
8072
+ const role = silent !== void 0 && silent >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? "error" : "warning";
8073
+ const icon = silent === void 0 ? mark(status.state, now, paint) : paint(role, "\u26A0");
8074
+ const warning = silent === void 0 ? "" : ` ${paint(role, `${role === "error" ? "stalled" : "quiet"} ${formatElapsed(silent)}`)}`;
8075
+ const accounting2 = status.progress?.accounting;
8076
+ const attempts = status.attempts ?? 1;
8077
+ const right = [
8078
+ modelName(status),
8079
+ formatTokens(accounting2 === void 0 ? void 0 : accounting2.input + accounting2.output),
8080
+ formatCost(accounting2?.cost),
8081
+ status.startedAt === void 0 ? "" : formatElapsed(now - status.startedAt),
8082
+ attempts > 1 ? `attempt ${String(attempts)}` : ""
8083
+ ].filter(Boolean).join(" \xB7 ");
8084
+ return { text: `${icon} ${sanitizeDisplayText(label({ ...request, id: status.id }))}${warning}`, right };
8085
+ }
8086
+ var RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
8087
+ function receiptFor(status, request) {
8088
+ const accounting2 = status.progress?.accounting;
8089
+ const model = modelName(status);
8090
+ const role = roleName(request.role);
8091
+ const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
8092
+ return {
8093
+ id: status.id,
8094
+ label: label({ ...request, id: status.id }),
8095
+ state: status.state,
8096
+ ...model ? { model } : {},
8097
+ ...role === void 0 ? {} : { role },
8098
+ ...tools === void 0 ? {} : { tools: [...tools] },
8099
+ input: accounting2?.input ?? 0,
8100
+ output: accounting2?.output ?? 0,
8101
+ cacheRead: accounting2?.cacheRead ?? 0,
8102
+ costUsd: accounting2?.cost ?? 0,
8103
+ durationMs: status.startedAt === void 0 ? 0 : Math.max(0, (status.finishedAt ?? Date.now()) - status.startedAt),
8104
+ attempts: status.attempts ?? 1,
8105
+ ...status.error === void 0 ? {} : { error: `${status.error.code}: ${status.error.message}` }
8106
+ };
8107
+ }
8108
+ function renderSubagentReceipt(data, expanded, theme) {
8109
+ const glyph = data.state === "completed" ? theme.fg("success", "\u2713") : data.state === "failed" ? theme.fg("error", "\u2717") : theme.fg("muted", "\xB7");
8110
+ const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" \xB7 ");
8111
+ const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
8112
+ if (!expanded) return lines;
8113
+ const meta = [data.model ?? "", data.role ? `role ${data.role}` : "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
8114
+ if (meta.length > 0) lines.push(theme.fg("muted", ` ${meta.join(" \xB7 ")}`));
8115
+ const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
8116
+ if (split.length > 0) lines.push(theme.fg("muted", ` ${split.join(" \xB7 ")}`));
8117
+ if (data.error !== void 0) lines.push(theme.fg("error", ` ${sanitizeDisplayText(data.error)}`));
8118
+ lines.push(theme.fg("muted", ` run ${data.id}`));
8119
+ return lines;
8120
+ }
8121
+ function createSubagentBackgroundWidget(host = {}) {
7692
8122
  const key = "piewf-subagents-background";
7693
8123
  const runs = /* @__PURE__ */ new Map();
8124
+ const receipted = /* @__PURE__ */ new Set();
8125
+ host.registerEntryRenderer?.(RECEIPT_ENTRY_TYPE, (entry, options, theme) => {
8126
+ if (!entry.data) return void 0;
8127
+ const lines = renderSubagentReceipt(entry.data, options.expanded, theme);
8128
+ return { render: (width) => lines.map((line) => truncateToWidth4(line, Math.max(1, width), "\u2026")), invalidate() {
8129
+ } };
8130
+ });
7694
8131
  let context;
7695
8132
  let requestRender;
7696
8133
  let timer;
@@ -7724,23 +8161,12 @@ function createSubagentBackgroundWidget() {
7724
8161
  return {
7725
8162
  render(width) {
7726
8163
  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"));
8164
+ const paintRole = (role, text) => theme.fg(role, text);
8165
+ const budget = MAX_BACKGROUND_WIDGET_ROWS - 2;
8166
+ const rows = [...runs.values()].map((run) => widgetRow(run, now, paintRole));
8167
+ const body = rows.length <= budget ? rows : [...rows.slice(0, budget - 1), { text: theme.fg("muted", `\u2026 ${String(rows.length - budget + 1)} more`) }];
8168
+ const title = runs.size === 1 ? "Subagent" : `Subagents \xB7 ${String(runs.size)} runs`;
8169
+ return drawFrame(title, "", body, width, paintRole, (text) => theme.fg("accent", text)).map((line) => truncateToWidth4(line, Math.max(1, width), ""));
7744
8170
  },
7745
8171
  invalidate() {
7746
8172
  }
@@ -7759,12 +8185,21 @@ function createSubagentBackgroundWidget() {
7759
8185
  start(next) {
7760
8186
  hide();
7761
8187
  runs.clear();
8188
+ receipted.clear();
7762
8189
  context = next.mode === "tui" ? next : void 0;
7763
8190
  },
7764
8191
  update(status, request) {
7765
8192
  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 });
8193
+ if (TERMINAL_STATES.has(status.state)) {
8194
+ runs.delete(status.id);
8195
+ if (!receipted.has(status.id)) {
8196
+ receipted.add(status.id);
8197
+ try {
8198
+ host.appendEntry?.(RECEIPT_ENTRY_TYPE, receiptFor(status, request));
8199
+ } catch {
8200
+ }
8201
+ }
8202
+ } else runs.set(status.id, { status, request });
7768
8203
  paint();
7769
8204
  },
7770
8205
  dispose() {
@@ -7949,7 +8384,7 @@ function managerDependencies(options, activeTools, notify, onStatus, onResourceW
7949
8384
  }
7950
8385
  function storageDirectory2(options) {
7951
8386
  const dependencies = options.managerDependencies;
7952
- return dependencies?.storageDir ?? join10(dependencies?.agentDir ?? getAgentDir7(), "subagents");
8387
+ return dependencies?.storageDir ?? join11(dependencies?.agentDir ?? getAgentDir8(), "subagents");
7953
8388
  }
7954
8389
  function createSubagentsExtension(options = {}, activeTools, notify, onStatus, onResourceWarning) {
7955
8390
  const manager = options.manager ?? createSubagentManager(managerDependencies(options, activeTools, notify, onStatus, onResourceWarning));
@@ -7966,7 +8401,10 @@ function registerSubagentsExtension(pi, options = {}) {
7966
8401
  const onResourceWarning = sendMessage === void 0 ? void 0 : (message) => {
7967
8402
  sendMessage.call(pi, { customType: "subagents", content: `Warning: ${message}`, display: true }, { deliverAs: "steer" });
7968
8403
  };
7969
- const widget2 = createSubagentBackgroundWidget();
8404
+ const appendEntry = pi.appendEntry;
8405
+ const widget2 = createSubagentBackgroundWidget({ ...appendEntry === void 0 ? {} : { appendEntry: (customType, data) => {
8406
+ appendEntry.call(pi, customType, data);
8407
+ } }, ...pi.registerEntryRenderer === void 0 ? {} : { registerEntryRenderer: pi.registerEntryRenderer.bind(pi) } });
7970
8408
  const extension2 = createSubagentsExtension(options, activeTools, notify, (status, request) => {
7971
8409
  widget2.update(status, request);
7972
8410
  const registry = loadingRegistry();