@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
package/dist/src/index.js CHANGED
@@ -370,6 +370,12 @@ function createLaunchSnapshot(input) {
370
370
  function loadLaunchSnapshot(input) {
371
371
  return deepFreeze(structuredClone(input));
372
372
  }
373
+ function validateSchema(schema, at = "schema") {
374
+ if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
375
+ 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`);
376
+ 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`);
377
+ if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
378
+ }
373
379
 
374
380
  // src/paths.ts
375
381
  import { createHash } from "node:crypto";
@@ -670,6 +676,23 @@ function resumeBudgetAllowed(budget, usage) {
670
676
  return exhaustedBudgetDimensions(budget, usage).length === 0;
671
677
  }
672
678
 
679
+ // src/validation.ts
680
+ import { readFileSync as readFileSync4 } from "node:fs";
681
+ import { resolve as resolve5 } from "node:path";
682
+ import * as acorn from "acorn";
683
+ import { Script } from "node:vm";
684
+
685
+ // src/roles.ts
686
+ import { readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
687
+ import { basename as basename3, dirname as dirname4, extname, join as join6, resolve as resolve4 } from "node:path";
688
+ import { getAgentDir as getAgentDir2, parseFrontmatter } from "@earendil-works/pi-coding-agent";
689
+
690
+ // src/registry.ts
691
+ import { readFileSync as readFileSync2 } from "node:fs";
692
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
693
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
694
+ import { Value } from "typebox/value";
695
+
673
696
  // src/io.ts
674
697
  import { execFile } from "node:child_process";
675
698
  import { randomUUID } from "node:crypto";
@@ -690,7 +713,7 @@ async function renameWithRetry(from, to) {
690
713
  } catch (error) {
691
714
  const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
692
715
  if (!retryableRenameError(error) || delayMs === void 0) throw error;
693
- await new Promise((resolve7) => setTimeout(resolve7, delayMs));
716
+ await new Promise((resolve9) => setTimeout(resolve9, delayMs));
694
717
  }
695
718
  }
696
719
  }
@@ -894,7 +917,7 @@ function decodeWorkflowExtensions(value) {
894
917
  }
895
918
  if (namespace === "trajectory") {
896
919
  if (!object(raw) || Object.keys(raw).some((key) => key !== "port" && key !== "themes") || raw.port !== void 0 && (typeof raw.port !== "number" || !Number.isSafeInteger(raw.port) || raw.port < 1 || raw.port > 65535) || raw.themes !== void 0 && typeof raw.themes !== "boolean") return void 0;
897
- normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? {} : { themes: raw.themes } };
920
+ normalized.trajectory = { ...raw.port === void 0 ? {} : { port: raw.port } };
898
921
  continue;
899
922
  }
900
923
  if (!jsonValue(raw)) return void 0;
@@ -2356,20 +2379,234 @@ var RunStore = class _RunStore {
2356
2379
  }
2357
2380
  };
2358
2381
 
2359
- // src/validation.ts
2360
- import { mkdirSync, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
2382
+ // src/settings.ts
2383
+ import { mkdirSync, readFileSync } from "node:fs";
2361
2384
  import { homedir as homedir4 } from "node:os";
2362
- import { basename as basename3, dirname as dirname3, extname, join as join5, resolve as resolve3 } from "node:path";
2363
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2364
- import * as acorn from "acorn";
2365
- import { Script } from "node:vm";
2366
- import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
2385
+ import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
2386
+ import { fileURLToPath } from "node:url";
2387
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2388
+ var ROLE_DIRECTORY = "pi-extensible-workflows";
2389
+ var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
2390
+ function workflowSettingsPath(agentDir = getAgentDir()) {
2391
+ return join4(agentDir, ROLE_DIRECTORY, "settings.json");
2392
+ }
2393
+ function normalizedResourcePath(value, settingsPath) {
2394
+ if (value === "*") return value;
2395
+ let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join4(homedir4(), value.slice(2)) : value;
2396
+ if (expanded.startsWith("file://")) expanded = fileURLToPath(expanded);
2397
+ const resolved = resolve3(dirname2(settingsPath), expanded);
2398
+ if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
2399
+ if (resourcePatternHasMagic(expanded)) {
2400
+ const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
2401
+ const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
2402
+ const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
2403
+ const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
2404
+ const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
2405
+ return `${canonicalPath(prefix)}${suffix}`;
2406
+ }
2407
+ return canonicalPath(resolved);
2408
+ }
2409
+ function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
2410
+ if (value === void 0) return void 0;
2411
+ if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
2412
+ const normalized = [];
2413
+ for (const [index, entry] of value.entries()) {
2414
+ if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
2415
+ let selector = entry.trim();
2416
+ if (kind === "extensions" && normalizeExtensions) {
2417
+ const negated = selector.startsWith("!");
2418
+ const body = negated ? selector.slice(1) : selector;
2419
+ if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
2420
+ try {
2421
+ selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
2422
+ } catch (error) {
2423
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
2424
+ }
2425
+ }
2426
+ try {
2427
+ validateResourcePattern(selector);
2428
+ } catch (error) {
2429
+ fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
2430
+ }
2431
+ normalized.push(selector);
2432
+ }
2433
+ return Object.freeze(normalized);
2434
+ }
2435
+ function validateWorktreePostCreateCommand(value, settingsPath) {
2436
+ if (value === void 0) return void 0;
2437
+ if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
2438
+ return Object.freeze(value.map((entry, index) => {
2439
+ if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2440
+ return entry;
2441
+ }));
2442
+ }
2443
+ function selectorsFromSettings(settings) {
2444
+ return {
2445
+ ...settings.skills === void 0 ? {} : { skills: settings.skills },
2446
+ ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2447
+ ...settings.tools === void 0 ? {} : { tools: settings.tools }
2448
+ };
2449
+ }
2450
+ function selectorSet(value) {
2451
+ return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2452
+ }
2453
+ function validateContextFileScopes(value, rolePath) {
2454
+ if (value === void 0) return void 0;
2455
+ if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2456
+ return [...value];
2457
+ }
2458
+ function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS") {
2459
+ if (value === void 0) return void 0;
2460
+ const base = `${settingsPath}.extensionSettings`;
2461
+ if (!object(value)) fail(errorCode2, `${base} must be an object`);
2462
+ const normalized = {};
2463
+ for (const [namespace, raw] of Object.entries(value)) {
2464
+ if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2465
+ if (namespace === "herdr") {
2466
+ if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2467
+ if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2468
+ if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2469
+ normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2470
+ continue;
2471
+ }
2472
+ if (namespace === "trajectory") {
2473
+ if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2474
+ if (Object.keys(raw).some((key) => key !== "port")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2475
+ if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2476
+ normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port } });
2477
+ continue;
2478
+ }
2479
+ if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2480
+ normalized[namespace] = structuredClone(raw);
2481
+ }
2482
+ return deepFreeze(normalized);
2483
+ }
2484
+ function positiveRetentionInteger(value) {
2485
+ return positiveInteger(value) && Number.isSafeInteger(value);
2486
+ }
2487
+ function validateRetention(value, settingsPath) {
2488
+ if (value === void 0) return void 0;
2489
+ const base = `${settingsPath}.retention`;
2490
+ if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2491
+ const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2492
+ if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2493
+ if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2494
+ if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2495
+ return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2496
+ }
2497
+ function parseSettings(path, partial) {
2498
+ let parsed;
2499
+ try {
2500
+ parsed = JSON.parse(readFileSync(path, "utf8"));
2501
+ } catch (error) {
2502
+ if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2503
+ fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2504
+ }
2505
+ if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2506
+ const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
2507
+ const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2508
+ if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2509
+ if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2510
+ const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2511
+ if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2512
+ const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2513
+ if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2514
+ const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2515
+ const skills = validateSelectorList(parsed.skills, path, "skills");
2516
+ const tools = validateSelectorList(parsed.tools, path, "tools");
2517
+ const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2518
+ const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS");
2519
+ const retention = validateRetention(parsed.retention, path);
2520
+ const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2521
+ return Object.freeze({
2522
+ ...concurrency === void 0 ? {} : { concurrency },
2523
+ ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2524
+ ...modelAliases === void 0 ? {} : { modelAliases },
2525
+ ...skills === void 0 ? {} : { skills },
2526
+ ...extensions === void 0 ? {} : { extensions },
2527
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2528
+ ...tools === void 0 ? {} : { tools },
2529
+ ...retention === void 0 ? {} : { retention },
2530
+ ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2531
+ });
2532
+ }
2533
+ function loadSettings(path = workflowSettingsPath()) {
2534
+ return parseSettings(path, false);
2535
+ }
2536
+ function loadSettingsOverrides(path) {
2537
+ return parseSettings(path, true);
2538
+ }
2539
+ function resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2540
+ const projectSettingsPath = workflowProjectSettingsPath(cwd);
2541
+ const global = loadSettings(globalSettingsPath);
2542
+ const project = projectTrusted2 ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2543
+ const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2544
+ const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2545
+ const globalSelectors = selectorsFromSettings(global);
2546
+ const projectSelectors = selectorsFromSettings(project);
2547
+ const effectiveSelectors = selectorSet({
2548
+ skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2549
+ extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2550
+ ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2551
+ });
2552
+ const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2553
+ const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2554
+ const sources = {
2555
+ concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2556
+ modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2557
+ skills: sourceFor("skills"),
2558
+ extensions: sourceFor("extensions"),
2559
+ tools: sourceFor("tools"),
2560
+ ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2561
+ ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2562
+ ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2563
+ };
2564
+ const effective = Object.freeze({
2565
+ concurrency: project.concurrency ?? global.concurrency,
2566
+ backgroundWidget: global.backgroundWidget ?? true,
2567
+ ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2568
+ ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2569
+ ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2570
+ ...extensionSettings === void 0 ? {} : { extensionSettings },
2571
+ ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2572
+ ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2573
+ ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2574
+ });
2575
+ return { globalSettingsPath, projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, sources };
2576
+ }
2577
+ function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2578
+ for (const name of names) {
2579
+ try {
2580
+ const target = modelCapability(name, aliases, knownModels, settingsPath);
2581
+ if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2582
+ } catch (error) {
2583
+ throw annotateModelAliasError(error, name);
2584
+ }
2585
+ }
2586
+ }
2587
+ function resolveAgentResourcePolicy(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2588
+ const resolved = resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath);
2589
+ const global = selectorSet(selectorsFromSettings(resolved.global));
2590
+ const project = selectorSet(selectorsFromSettings(resolved.project));
2591
+ const effective = selectorSet(selectorsFromSettings(resolved.effective));
2592
+ return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2593
+ }
2594
+ function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
2595
+ const normalized = validateModelAliases(aliases, path);
2596
+ let parsed = {};
2597
+ try {
2598
+ loadSettings(path);
2599
+ parsed = JSON.parse(readFileSync(path, "utf8"));
2600
+ } catch (error) {
2601
+ if (!isNodeError(error, "ENOENT")) throw error;
2602
+ }
2603
+ if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2604
+ mkdirSync(dirname2(path), { recursive: true });
2605
+ atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}
2606
+ `, true);
2607
+ }
2367
2608
 
2368
2609
  // src/registry.ts
2369
- import { readFileSync } from "node:fs";
2370
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join4 } from "node:path";
2371
- import { fileURLToPath } from "node:url";
2372
- import { Value } from "typebox/value";
2373
2610
  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"]);
2374
2611
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
2375
2612
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -2378,10 +2615,10 @@ function normalizeRoleDirectory(value) {
2378
2615
  try {
2379
2616
  if (value instanceof URL) {
2380
2617
  if (value.protocol !== "file:") fail("INVALID_METADATA", "Workflow role directories require file URLs or absolute filesystem paths");
2381
- return fileURLToPath(value);
2618
+ return fileURLToPath2(value);
2382
2619
  }
2383
2620
  if (typeof value === "string") {
2384
- if (value.startsWith("file:")) return fileURLToPath(value);
2621
+ if (value.startsWith("file:")) return fileURLToPath2(value);
2385
2622
  if (isAbsolute(value)) return value;
2386
2623
  }
2387
2624
  } catch (error) {
@@ -2391,12 +2628,12 @@ function normalizeRoleDirectory(value) {
2391
2628
  }
2392
2629
  function isBuiltinRoleDirectory(path) {
2393
2630
  const canonical = canonicalPath(path);
2394
- const starterDirectory = dirname2(canonical);
2631
+ const starterDirectory = dirname3(canonical);
2395
2632
  if (basename2(canonical) !== "roles" || basename2(starterDirectory) !== "starter") return false;
2396
- const distributionDirectory = dirname2(starterDirectory);
2397
- const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname2(distributionDirectory) : distributionDirectory;
2633
+ const distributionDirectory = dirname3(starterDirectory);
2634
+ const packageRoot2 = basename2(distributionDirectory) === "dist" ? dirname3(distributionDirectory) : distributionDirectory;
2398
2635
  try {
2399
- const metadata = JSON.parse(readFileSync(join4(packageRoot2, "package.json"), "utf8"));
2636
+ const metadata = JSON.parse(readFileSync2(join5(packageRoot2, "package.json"), "utf8"));
2400
2637
  return object(metadata) && isCorePackageName(metadata.name);
2401
2638
  } catch {
2402
2639
  return false;
@@ -2671,276 +2908,52 @@ function beginWorkflowExtensionLoading() {
2671
2908
  const host = workflowRegistryHost();
2672
2909
  if (host.api.frozen && host.activeHosts === 0) resetWorkflowRegistry();
2673
2910
  }
2674
- function retainWorkflowRegistry() {
2675
- const host = workflowRegistryHost();
2676
- host.activeHosts += 1;
2677
- let retained = true;
2678
- return () => {
2679
- if (!retained) return;
2680
- retained = false;
2681
- host.activeHosts -= 1;
2682
- if (host.activeHosts === 0) resetWorkflowRegistry();
2683
- };
2684
- }
2685
- function loadingRegistry() {
2686
- return workflowRegistryHost().api;
2687
- }
2688
- beginWorkflowExtensionLoading();
2689
- function registerWorkflowExtension(extension) {
2690
- loadingRegistry().register(extension);
2691
- }
2692
- function workflowCatalog(context) {
2693
- return loadingRegistry().catalog(context);
2694
- }
2695
- function workflowCatalogIndex(context) {
2696
- return loadingRegistry().catalogIndex(context);
2697
- }
2698
- function workflowCatalogDetail(name, context) {
2699
- return loadingRegistry().catalogDetail(name, context);
2700
- }
2701
- function registeredWorkflowFunctions() {
2702
- return loadingRegistry().functions();
2703
- }
2704
- function registeredWorkflowFunctionSources() {
2705
- const sources = loadingRegistry().functionSources;
2706
- return typeof sources === "function" ? sources() : {};
2707
- }
2708
- function registeredWorkflowRoleDirectories() {
2709
- const directories = loadingRegistry().roleDirectories;
2710
- return typeof directories === "function" ? directories() : [];
2711
- }
2712
- function registeredWorkflowRoleDirectoryRegistrations() {
2713
- const registrations = loadingRegistry().roleDirectoryRegistrations;
2714
- return typeof registrations === "function" ? registrations() : [];
2715
- }
2716
-
2717
- // src/validation.ts
2718
- var DEFAULT_SETTINGS = Object.freeze({ concurrency: 8, backgroundWidget: true });
2719
- function validateCheckpoint(value) {
2720
- if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
2721
- if (Buffer.byteLength(value.prompt) > 1024) fail("INVALID_METADATA", "checkpoint prompt exceeds 1024 UTF-8 bytes");
2722
- if (Buffer.byteLength(JSON.stringify(value.context)) > 4096) fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
2723
- return { name: value.name, prompt: value.prompt, context: value.context };
2724
- }
2725
- function workflowSettingsPath(agentDir = getAgentDir()) {
2726
- return join5(agentDir, ROLE_DIRECTORY, "settings.json");
2727
- }
2728
- function normalizedResourcePath(value, settingsPath) {
2729
- if (value === "*") return value;
2730
- let expanded = value === "~" ? homedir4() : value.startsWith("~/") || value.startsWith("~\\") ? join5(homedir4(), value.slice(2)) : value;
2731
- if (expanded.startsWith("file://")) expanded = fileURLToPath2(expanded);
2732
- const resolved = resolve3(dirname3(settingsPath), expanded);
2733
- if (expanded === "**" || expanded.startsWith("**/") || expanded.startsWith("**\\")) return expanded;
2734
- if (resourcePatternHasMagic(expanded)) {
2735
- const magicIndex = resolved.search(/[*?\x5b\x5d{}()]/);
2736
- const separatorIndex = Math.max(resolved.lastIndexOf("/", magicIndex), resolved.lastIndexOf("\\", magicIndex));
2737
- const rootBoundary = separatorIndex === 0 || separatorIndex === 2 && /^[A-Za-z]:[\\/]/.test(resolved);
2738
- const prefix = rootBoundary ? resolved.slice(0, separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(0, separatorIndex) : resolved;
2739
- const suffix = rootBoundary ? resolved.slice(separatorIndex + 1) : separatorIndex >= 0 ? resolved.slice(separatorIndex) : "";
2740
- return `${canonicalPath(prefix)}${suffix}`;
2741
- }
2742
- return canonicalPath(resolved);
2743
- }
2744
- function validateSelectorList(value, path, kind, errorCode2 = "INVALID_SETTINGS", normalizeExtensions = true) {
2745
- if (value === void 0) return void 0;
2746
- if (!Array.isArray(value)) fail(errorCode2, `${path}.${kind} must be an array`);
2747
- const normalized = [];
2748
- for (const [index, entry] of value.entries()) {
2749
- if (typeof entry !== "string" || !entry.trim()) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a non-empty string`);
2750
- let selector = entry.trim();
2751
- if (kind === "extensions" && normalizeExtensions) {
2752
- const negated = selector.startsWith("!");
2753
- const body = negated ? selector.slice(1) : selector;
2754
- if (!body) fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: Empty minimatch pattern ${JSON.stringify(selector)}`);
2755
- try {
2756
- selector = `${negated ? "!" : ""}${normalizedResourcePath(body, path)}`;
2757
- } catch (error) {
2758
- fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid path: ${errorText(error)}`);
2759
- }
2760
- }
2761
- try {
2762
- validateResourcePattern(selector);
2763
- } catch (error) {
2764
- fail(errorCode2, `${path}.${kind}[${String(index)}] must be a valid minimatch pattern: ${errorText(error)}`);
2765
- }
2766
- normalized.push(selector);
2767
- }
2768
- return Object.freeze(normalized);
2769
- }
2770
- function validateWorktreePostCreateCommand(value, settingsPath) {
2771
- if (value === void 0) return void 0;
2772
- if (!Array.isArray(value) || value.length === 0) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand must be a non-empty array`);
2773
- return Object.freeze(value.map((entry, index) => {
2774
- if (typeof entry !== "string" || !entry.trim()) fail("INVALID_SETTINGS", `${settingsPath}.worktreePostCreateCommand[${String(index)}] must be a non-empty string`);
2775
- return entry;
2776
- }));
2777
- }
2778
- function selectorsFromSettings(settings) {
2779
- return {
2780
- ...settings.skills === void 0 ? {} : { skills: settings.skills },
2781
- ...settings.extensions === void 0 ? {} : { extensions: settings.extensions },
2782
- ...settings.tools === void 0 ? {} : { tools: settings.tools }
2783
- };
2784
- }
2785
- function selectorSet(value) {
2786
- return { skills: [...value?.skills ?? []], extensions: [...value?.extensions ?? []], ...value?.tools === void 0 ? {} : { tools: [...value.tools] } };
2787
- }
2788
- function validateContextFileScopes(value, rolePath) {
2789
- if (value === void 0) return void 0;
2790
- if (!Array.isArray(value) || !value.every(isContextFileScope)) fail("INVALID_METADATA", `${rolePath}.contextFiles must be an array containing only global, project, or cwd`);
2791
- return [...value];
2792
- }
2793
- function validateWorkflowExtensionSettings(value, settingsPath, errorCode2 = "INVALID_SETTINGS", applyDefaults = true) {
2794
- if (value === void 0) return void 0;
2795
- const base = `${settingsPath}.extensionSettings`;
2796
- if (!object(value)) fail(errorCode2, `${base} must be an object`);
2797
- const normalized = {};
2798
- for (const [namespace, raw] of Object.entries(value)) {
2799
- if (!validWorkflowExtensionNamespace(namespace)) fail(errorCode2, `${base} contains an invalid namespace: ${namespace}`);
2800
- if (namespace === "herdr") {
2801
- if (!object(raw)) fail(errorCode2, `${base}.herdr must be an object`);
2802
- if (Object.keys(raw).some((key) => key !== "enableFullyInspectableMode")) fail(errorCode2, `${base}.herdr contains an unsupported setting`);
2803
- if (raw.enableFullyInspectableMode !== void 0 && typeof raw.enableFullyInspectableMode !== "boolean") fail(errorCode2, `${base}.herdr.enableFullyInspectableMode must be a boolean`);
2804
- normalized.herdr = Object.freeze({ ...raw.enableFullyInspectableMode === void 0 ? {} : { enableFullyInspectableMode: raw.enableFullyInspectableMode } });
2805
- continue;
2806
- }
2807
- if (namespace === "trajectory") {
2808
- if (!object(raw)) fail(errorCode2, `${base}.trajectory must be an object`);
2809
- if (Object.keys(raw).some((key) => key !== "port" && key !== "themes")) fail(errorCode2, `${base}.trajectory contains an unsupported setting`);
2810
- if (raw.port !== void 0 && (!positiveInteger(raw.port) || raw.port > 65535)) fail(errorCode2, `${base}.trajectory.port must be an integer from 1 to 65535`);
2811
- if (raw.themes !== void 0 && typeof raw.themes !== "boolean") fail(errorCode2, `${base}.trajectory.themes must be a boolean`);
2812
- normalized.trajectory = Object.freeze({ ...raw.port === void 0 ? {} : { port: raw.port }, ...raw.themes === void 0 ? applyDefaults ? { themes: false } : {} : { themes: raw.themes } });
2813
- continue;
2814
- }
2815
- if (!jsonValue(raw)) fail(errorCode2, `${base}.${namespace} must be JSON-compatible`);
2816
- normalized[namespace] = structuredClone(raw);
2817
- }
2818
- return deepFreeze(normalized);
2911
+ function retainWorkflowRegistry() {
2912
+ const host = workflowRegistryHost();
2913
+ host.activeHosts += 1;
2914
+ let retained = true;
2915
+ return () => {
2916
+ if (!retained) return;
2917
+ retained = false;
2918
+ host.activeHosts -= 1;
2919
+ if (host.activeHosts === 0) resetWorkflowRegistry();
2920
+ };
2819
2921
  }
2820
- function positiveRetentionInteger(value) {
2821
- return positiveInteger(value) && Number.isSafeInteger(value);
2922
+ function loadingRegistry() {
2923
+ return workflowRegistryHost().api;
2822
2924
  }
2823
- function validateRetention(value, settingsPath) {
2824
- if (value === void 0) return void 0;
2825
- const base = `${settingsPath}.retention`;
2826
- if (!object(value)) fail("INVALID_SETTINGS", `${base} must be an object`);
2827
- const unknown = Object.keys(value).find((key) => key !== "olderThanDays" && key !== "maxTerminalRuns");
2828
- if (unknown) fail("INVALID_SETTINGS", `Unknown retention setting at ${base}: ${unknown}`);
2829
- if (value.olderThanDays !== void 0 && !positiveRetentionInteger(value.olderThanDays)) fail("INVALID_SETTINGS", `${base}.olderThanDays must be a positive integer`);
2830
- if (value.maxTerminalRuns !== void 0 && !positiveRetentionInteger(value.maxTerminalRuns)) fail("INVALID_SETTINGS", `${base}.maxTerminalRuns must be a positive integer`);
2831
- return Object.freeze({ ...value.olderThanDays === void 0 ? {} : { olderThanDays: value.olderThanDays }, ...value.maxTerminalRuns === void 0 ? {} : { maxTerminalRuns: value.maxTerminalRuns } });
2925
+ beginWorkflowExtensionLoading();
2926
+ function registerWorkflowExtension(extension) {
2927
+ loadingRegistry().register(extension);
2832
2928
  }
2833
- function parseSettings(path, partial) {
2834
- let parsed;
2835
- try {
2836
- parsed = JSON.parse(readFileSync2(path, "utf8"));
2837
- } catch (error) {
2838
- if (isNodeError(error, "ENOENT")) return partial ? Object.freeze({}) : DEFAULT_SETTINGS;
2839
- fail("CONFIG_ERROR", `Invalid workflow settings JSON at ${path}: ${errorText(error)}`);
2840
- }
2841
- if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2842
- const allowed = /* @__PURE__ */ new Set(["concurrency", "modelAliases", "skills", "extensions", "extensionSettings", "tools", "retention", "worktreePostCreateCommand", ...partial ? [] : ["backgroundWidget"]]);
2843
- const unknown = Object.keys(parsed).find((key) => !allowed.has(key));
2844
- if (Object.prototype.hasOwnProperty.call(parsed, "disabledAgentResources")) fail("INVALID_SETTINGS", `disabledAgentResources is no longer supported; use skills, extensions, and tools selectors (settings: ${path})`);
2845
- if (unknown) fail("INVALID_SETTINGS", `Unknown workflow setting at ${path}: ${unknown}`);
2846
- const concurrency = parsed.concurrency === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.concurrency : parsed.concurrency;
2847
- if (concurrency !== void 0 && (!positiveInteger(concurrency) || concurrency > 16)) fail("INVALID_SETTINGS", `${path}.concurrency must be an integer from 1 to 16`);
2848
- const backgroundWidget = parsed.backgroundWidget === void 0 ? partial ? void 0 : DEFAULT_SETTINGS.backgroundWidget : parsed.backgroundWidget;
2849
- if (backgroundWidget !== void 0 && typeof backgroundWidget !== "boolean") fail("INVALID_SETTINGS", `${path}.backgroundWidget must be a boolean`);
2850
- const modelAliases = parsed.modelAliases === void 0 ? void 0 : validateModelAliases(parsed.modelAliases, path);
2851
- const skills = validateSelectorList(parsed.skills, path, "skills");
2852
- const tools = validateSelectorList(parsed.tools, path, "tools");
2853
- const extensions = validateSelectorList(parsed.extensions, path, "extensions");
2854
- const extensionSettings = parsed.extensionSettings === void 0 ? void 0 : validateWorkflowExtensionSettings(parsed.extensionSettings, path, "INVALID_SETTINGS", !partial);
2855
- const retention = validateRetention(parsed.retention, path);
2856
- const worktreePostCreateCommand = validateWorktreePostCreateCommand(parsed.worktreePostCreateCommand, path);
2857
- return Object.freeze({
2858
- ...concurrency === void 0 ? {} : { concurrency },
2859
- ...backgroundWidget === void 0 ? {} : { backgroundWidget },
2860
- ...modelAliases === void 0 ? {} : { modelAliases },
2861
- ...skills === void 0 ? {} : { skills },
2862
- ...extensions === void 0 ? {} : { extensions },
2863
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2864
- ...tools === void 0 ? {} : { tools },
2865
- ...retention === void 0 ? {} : { retention },
2866
- ...worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand }
2867
- });
2929
+ function workflowCatalog(context) {
2930
+ return loadingRegistry().catalog(context);
2868
2931
  }
2869
- function loadSettings(path = workflowSettingsPath()) {
2870
- return parseSettings(path, false);
2932
+ function workflowCatalogIndex(context) {
2933
+ return loadingRegistry().catalogIndex(context);
2871
2934
  }
2872
- function loadSettingsOverrides(path) {
2873
- return parseSettings(path, true);
2935
+ function workflowCatalogDetail(name, context) {
2936
+ return loadingRegistry().catalogDetail(name, context);
2874
2937
  }
2875
- function resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2876
- const projectSettingsPath = workflowProjectSettingsPath(cwd);
2877
- const global = loadSettings(globalSettingsPath);
2878
- const project = projectTrusted2 ? loadSettingsOverrides(projectSettingsPath) : Object.freeze({});
2879
- const projectHas = (key) => Object.prototype.hasOwnProperty.call(project, key);
2880
- const sourceFor = (key) => projectHas(key) ? projectSettingsPath : globalSettingsPath;
2881
- const globalSelectors = selectorsFromSettings(global);
2882
- const projectSelectors = selectorsFromSettings(project);
2883
- const effectiveSelectors = selectorSet({
2884
- skills: [...globalSelectors.skills ?? [], ...projectSelectors.skills ?? []],
2885
- extensions: [...globalSelectors.extensions ?? [], ...projectSelectors.extensions ?? []],
2886
- ...globalSelectors.tools === void 0 && projectSelectors.tools === void 0 ? {} : { tools: [...globalSelectors.tools ?? [], ...projectSelectors.tools ?? []] }
2887
- });
2888
- const hasExtensionSelectors = global.extensions !== void 0 || project.extensions !== void 0;
2889
- const extensionSettings = projectHas("extensionSettings") ? project.extensionSettings : global.extensionSettings;
2890
- const sources = {
2891
- concurrency: projectHas("concurrency") ? projectSettingsPath : globalSettingsPath,
2892
- modelAliases: projectHas("modelAliases") ? projectSettingsPath : globalSettingsPath,
2893
- skills: sourceFor("skills"),
2894
- extensions: sourceFor("extensions"),
2895
- tools: sourceFor("tools"),
2896
- ...extensionSettings === void 0 ? {} : { extensionSettings: projectHas("extensionSettings") ? projectSettingsPath : globalSettingsPath },
2897
- ...project.retention === void 0 && global.retention === void 0 ? {} : { retention: project.retention === void 0 ? globalSettingsPath : projectSettingsPath },
2898
- ...projectHas("worktreePostCreateCommand") || global.worktreePostCreateCommand !== void 0 ? { worktreePostCreateCommand: projectHas("worktreePostCreateCommand") ? projectSettingsPath : globalSettingsPath } : {}
2899
- };
2900
- const effective = Object.freeze({
2901
- concurrency: project.concurrency ?? global.concurrency,
2902
- backgroundWidget: global.backgroundWidget ?? true,
2903
- ...projectHas("modelAliases") ? { modelAliases: project.modelAliases } : global.modelAliases === void 0 ? {} : { modelAliases: global.modelAliases },
2904
- ...effectiveSelectors.skills.length ? { skills: effectiveSelectors.skills } : global.skills !== void 0 || project.skills !== void 0 ? { skills: effectiveSelectors.skills } : {},
2905
- ...hasExtensionSelectors ? { extensions: effectiveSelectors.extensions } : {},
2906
- ...extensionSettings === void 0 ? {} : { extensionSettings },
2907
- ...effectiveSelectors.tools?.length ? { tools: effectiveSelectors.tools } : global.tools !== void 0 || project.tools !== void 0 ? { tools: effectiveSelectors.tools } : {},
2908
- ...(project.retention ?? global.retention) === void 0 ? {} : { retention: project.retention ?? global.retention },
2909
- ...projectHas("worktreePostCreateCommand") ? { worktreePostCreateCommand: project.worktreePostCreateCommand } : global.worktreePostCreateCommand === void 0 ? {} : { worktreePostCreateCommand: global.worktreePostCreateCommand }
2910
- });
2911
- return { globalSettingsPath, projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, sources };
2938
+ function registeredWorkflowFunctions() {
2939
+ return loadingRegistry().functions();
2912
2940
  }
2913
- function validateModelAliasAvailability(aliases, names, availableModels, knownModels, settingsPath) {
2914
- for (const name of names) {
2915
- try {
2916
- const target = modelCapability(name, aliases, knownModels, settingsPath);
2917
- if (!availableModels.has(target)) unknownModel(name, target, settingsPath);
2918
- } catch (error) {
2919
- throw annotateModelAliasError(error, name);
2920
- }
2921
- }
2941
+ function registeredWorkflowFunctionSources() {
2942
+ const sources = loadingRegistry().functionSources;
2943
+ return typeof sources === "function" ? sources() : {};
2922
2944
  }
2923
- function resolveAgentResourcePolicy(cwd, projectTrusted2, globalSettingsPath = workflowSettingsPath()) {
2924
- const resolved = resolveWorkflowSettings(cwd, projectTrusted2, globalSettingsPath);
2925
- const global = selectorSet(selectorsFromSettings(resolved.global));
2926
- const project = selectorSet(selectorsFromSettings(resolved.project));
2927
- const effective = selectorSet(selectorsFromSettings(resolved.effective));
2928
- return { globalSettingsPath: resolved.globalSettingsPath, projectSettingsPath: resolved.projectSettingsPath, projectTrusted: projectTrusted2, global, project, effective, unmatchedSkills: [], unmatchedExtensions: [], unmatchedTools: [], selectorSources: { global: selectorsFromSettings(resolved.global), project: selectorsFromSettings(resolved.project) } };
2945
+ function registeredWorkflowRoleDirectories() {
2946
+ const directories = loadingRegistry().roleDirectories;
2947
+ return typeof directories === "function" ? directories() : [];
2929
2948
  }
2930
- function saveModelAliases(path = workflowSettingsPath(), aliases = {}) {
2931
- const normalized = validateModelAliases(aliases, path);
2932
- let parsed = {};
2933
- try {
2934
- loadSettings(path);
2935
- parsed = JSON.parse(readFileSync2(path, "utf8"));
2936
- } catch (error) {
2937
- if (!isNodeError(error, "ENOENT")) throw error;
2938
- }
2939
- if (!object(parsed)) fail("INVALID_SETTINGS", `Workflow settings at ${path} must be an object`);
2940
- mkdirSync(dirname3(path), { recursive: true });
2941
- atomicWriteFile(path, `${JSON.stringify({ ...parsed, modelAliases: normalized }, null, 2)}
2942
- `, true);
2949
+ function registeredWorkflowRoleDirectoryRegistrations() {
2950
+ const registrations = loadingRegistry().roleDirectoryRegistrations;
2951
+ return typeof registrations === "function" ? registrations() : [];
2943
2952
  }
2953
+
2954
+ // src/roles.ts
2955
+ var ROLE_DIRECTORY2 = "pi-extensible-workflows";
2956
+ var roleDefinitionPaths = /* @__PURE__ */ new WeakMap();
2944
2957
  function parseRoleMarkdown(content, strict = false, rolePath) {
2945
2958
  if (!strict) {
2946
2959
  if (!content.startsWith("---\n")) return { prompt: content };
@@ -2967,7 +2980,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
2967
2980
  }
2968
2981
  }
2969
2982
  const rolePathValue2 = rolePath ?? "<role>";
2970
- const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA", false);
2983
+ const normalizedExtensionSettings2 = validateWorkflowExtensionSettings(extensionSettings2, rolePathValue2, "INVALID_METADATA");
2971
2984
  const definition2 = { prompt: content.slice(end + 4).replace(/^\n/, "") };
2972
2985
  if (meta.model) {
2973
2986
  const model2 = unquote(meta.model);
@@ -3007,7 +3020,7 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
3007
3020
  const normalizedTools = validateSelectorList(tools, rolePathValue, "tools", "INVALID_METADATA");
3008
3021
  const normalizedSkills = validateSelectorList(skills, rolePathValue, "skills", "INVALID_METADATA");
3009
3022
  const normalizedExtensions = validateSelectorList(extensions, rolePathValue, "extensions", "INVALID_METADATA");
3010
- const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA", false);
3023
+ const normalizedExtensionSettings = validateWorkflowExtensionSettings(extensionSettings, rolePathValue, "INVALID_METADATA");
3011
3024
  const normalizedDescription = typeof description === "string" ? description.trim() : void 0;
3012
3025
  const normalizedModel = typeof model === "string" ? model.trim() : void 0;
3013
3026
  if (normalizedModel !== void 0) assertModelThinking(normalizedModel, "Role model");
@@ -3022,12 +3035,11 @@ function parseRoleMarkdown(content, strict = false, rolePath) {
3022
3035
  if (normalizedContextFiles !== void 0) definition.contextFiles = normalizedContextFiles;
3023
3036
  return definition;
3024
3037
  }
3025
- var ROLE_DIRECTORY = "pi-extensible-workflows";
3026
- function workflowRoleDirectories(agentDir = getAgentDir()) {
3027
- return [join5(agentDir, ROLE_DIRECTORY, "roles")];
3038
+ function workflowRoleDirectories(agentDir = getAgentDir2()) {
3039
+ return [join6(agentDir, ROLE_DIRECTORY2, "roles")];
3028
3040
  }
3029
3041
  function projectRoleDirectories(root) {
3030
- return [join5(root, ROLE_DIRECTORY, "roles")];
3042
+ return [join6(root, ROLE_DIRECTORY2, "roles")];
3031
3043
  }
3032
3044
  function roleDirectorySources(dirs) {
3033
3045
  const seen = /* @__PURE__ */ new Set();
@@ -3046,7 +3058,7 @@ function isRoleFile(dir, entry) {
3046
3058
  if (entry.isFile()) return true;
3047
3059
  if (!entry.isSymbolicLink()) return false;
3048
3060
  try {
3049
- return statSync(join5(dir, entry.name)).isFile();
3061
+ return statSync(join6(dir, entry.name)).isFile();
3050
3062
  } catch (error) {
3051
3063
  if (isNodeError(error, "ENOENT")) return false;
3052
3064
  throw error;
@@ -3062,11 +3074,11 @@ function scanRoleFiles(dirs, extension) {
3062
3074
  if (!extension && isNodeError(error, "ENOENT")) continue;
3063
3075
  fail("INVALID_METADATA", `${roleDirectoryLabel(source, extension)} role directory "${source.path}" could not be scanned: ${errorText(error)}`);
3064
3076
  }
3065
- for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join5(source.path, entry.name), source });
3077
+ for (const entry of entries) if (isRoleFile(source.path, entry)) files.push({ name: basename3(entry.name, ".md"), path: join6(source.path, entry.name), source });
3066
3078
  }
3067
3079
  return files.sort((left, right) => left.name.localeCompare(right.name) || left.path.localeCompare(right.path));
3068
3080
  }
3069
- function readRoleDefinitions(dirs, extension = false) {
3081
+ function roleEntries(dirs, extension) {
3070
3082
  const files = scanRoleFiles(dirs, extension);
3071
3083
  const starterFiles = files.filter(({ source }) => source.builtin === true);
3072
3084
  const regularFiles = files.filter(({ source }) => source.builtin !== true);
@@ -3077,7 +3089,9 @@ function readRoleDefinitions(dirs, extension = false) {
3077
3089
  }
3078
3090
  const read = (roleFiles) => Object.fromEntries(roleFiles.map(({ name, path, source }) => {
3079
3091
  try {
3080
- return [name, parseRoleMarkdown(readFileSync2(path, "utf8"), true, path)];
3092
+ const definition = parseRoleMarkdown(readFileSync3(path, "utf8"), true, path);
3093
+ roleDefinitionPaths.set(definition, path);
3094
+ return [name, { name, path, source, definition }];
3081
3095
  } catch (error) {
3082
3096
  if (extension) fail("INVALID_METADATA", `${roleDirectoryLabel(source)} role directory "${source.path}" contains invalid role "${name}" at "${path}": ${errorText(error)}`);
3083
3097
  throw error;
@@ -3085,11 +3099,127 @@ function readRoleDefinitions(dirs, extension = false) {
3085
3099
  }));
3086
3100
  return extension ? { ...read(starterFiles), ...read(regularFiles) } : read(files);
3087
3101
  }
3102
+ function discoveryInput(input) {
3103
+ return { cwd: input.cwd, agentDir: input.agentDir ?? getAgentDir2(), projectTrusted: input.projectTrusted ?? true, extensionRoleDirectories: input.extensionRoleDirectories ?? registeredWorkflowRoleDirectoryRegistrations() };
3104
+ }
3105
+ function discoveredRoleEntries(input) {
3106
+ const resolved = discoveryInput(input);
3107
+ return {
3108
+ ...roleEntries(resolved.extensionRoleDirectories, true),
3109
+ ...roleEntries(workflowRoleDirectories(resolved.agentDir), false),
3110
+ ...resolved.projectTrusted ? roleEntries(projectRoleDirectories(join6(resolved.cwd, ".pi")), false) : {}
3111
+ };
3112
+ }
3088
3113
  function loadProjectAgentDefinitions(cwd) {
3089
- return readRoleDefinitions(projectRoleDirectories(join5(cwd, ".pi")));
3114
+ return deepFreeze(Object.fromEntries(Object.entries(roleEntries(projectRoleDirectories(join6(cwd, ".pi")), false)).map(([name, entry]) => [name, entry.definition])));
3115
+ }
3116
+ function discoverRoles(input) {
3117
+ return deepFreeze(Object.fromEntries(Object.entries(discoveredRoleEntries(input)).map(([name, entry]) => [name, entry.definition])));
3118
+ }
3119
+ function loadRole(name, input) {
3120
+ const entry = discoveredRoleEntries(input)[name];
3121
+ if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
3122
+ return entry.definition;
3123
+ }
3124
+ function loadAgentDefinitions(cwd, agentDir = getAgentDir2(), projectTrusted2 = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
3125
+ return discoverRoles({ cwd, agentDir, projectTrusted: projectTrusted2, extensionRoleDirectories });
3126
+ }
3127
+ function canonicalExtensionSelector(selector, base = process.cwd()) {
3128
+ const negated = selector.startsWith("!");
3129
+ const body = negated ? selector.slice(1) : selector;
3130
+ if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
3131
+ const resolved = resolve4(base, body);
3132
+ if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
3133
+ return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
3090
3134
  }
3091
- function loadAgentDefinitions(cwd, agentDir = getAgentDir(), projectTrusted2 = true, extensionRoleDirectories = registeredWorkflowRoleDirectoryRegistrations()) {
3092
- return deepFreeze({ ...readRoleDefinitions(extensionRoleDirectories, true), ...readRoleDefinitions(workflowRoleDirectories(agentDir)), ...projectTrusted2 ? readRoleDefinitions(projectRoleDirectories(join5(cwd, ".pi"))) : {} });
3135
+ function selectorsFromSettings2(settings) {
3136
+ return { ...settings.skills === void 0 ? {} : { skills: settings.skills }, ...settings.extensions === void 0 ? {} : { extensions: settings.extensions }, ...settings.tools === void 0 ? {} : { tools: settings.tools } };
3137
+ }
3138
+ function rootToolNames(value) {
3139
+ return value === void 0 ? void 0 : value instanceof Set ? [...value] : [...value];
3140
+ }
3141
+ function roleDefinitionFor(name, options) {
3142
+ if (name === void 0) return {};
3143
+ if (options.definition !== void 0) {
3144
+ const path = roleDefinitionPaths.get(options.definition);
3145
+ return { definition: options.definition, ...path === void 0 ? {} : { path } };
3146
+ }
3147
+ if (options.definitions?.[name] !== void 0) {
3148
+ const definition = options.definitions[name];
3149
+ const path = roleDefinitionPaths.get(definition);
3150
+ return { definition, ...path === void 0 ? {} : { path } };
3151
+ }
3152
+ const entries = discoveredRoleEntries(options);
3153
+ const entry = entries[name];
3154
+ if (!entry) fail("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${name}`);
3155
+ return entry;
3156
+ }
3157
+ function resolveRole(name, options) {
3158
+ const { definition, path } = roleDefinitionFor(name, options);
3159
+ const settings = options.selectorSources === void 0 ? resolveWorkflowSettings(options.cwd, options.projectTrusted ?? true, options.settingsPath ?? workflowSettingsPath(options.agentDir ?? getAgentDir2())) : void 0;
3160
+ const baseSources = options.selectorSources ?? { global: selectorsFromSettings2(settings?.global ?? {}), project: selectorsFromSettings2(settings?.project ?? {}) };
3161
+ const canonicalSources = (sources) => ({
3162
+ ...sources,
3163
+ global: { ...sources.global, ...sources.global.extensions === void 0 ? {} : { extensions: sources.global.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
3164
+ project: { ...sources.project, ...sources.project.extensions === void 0 ? {} : { extensions: sources.project.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } },
3165
+ ...sources.role === void 0 ? {} : { role: { ...sources.role, ...sources.role.extensions === void 0 ? {} : { extensions: sources.role.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } },
3166
+ ...sources.call === void 0 ? {} : { call: { ...sources.call, ...sources.call.extensions === void 0 ? {} : { extensions: sources.call.extensions.map((selector) => canonicalExtensionSelector(selector, options.cwd)) } } }
3167
+ });
3168
+ 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;
3169
+ 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;
3170
+ const selectorSources = canonicalSources({ global: baseSources.global, project: baseSources.project, ...roleLayer === void 0 ? {} : { role: roleLayer }, ...callLayer === void 0 ? {} : { call: callLayer } });
3171
+ const callSelectors = selectorSources.call;
3172
+ const roleSelectors = selectorSources.role;
3173
+ const selectorLayers = {
3174
+ skills: [selectorSources.global.skills, selectorSources.project.skills, roleSelectors?.skills, callSelectors?.skills],
3175
+ extensions: [selectorSources.global.extensions, selectorSources.project.extensions, roleSelectors?.extensions, callSelectors?.extensions],
3176
+ tools: [selectorSources.global.tools, selectorSources.project.tools, roleSelectors?.tools, callSelectors?.tools]
3177
+ };
3178
+ const resources = options.resources ?? {};
3179
+ const candidateTools = options.inheritedTools ?? rootToolNames(options.rootTools) ?? resources.tools;
3180
+ const tools = options.effectiveTools ?? (candidateTools === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.tools, candidateTools));
3181
+ const rootTools = rootToolNames(options.rootTools);
3182
+ if (rootTools !== void 0) {
3183
+ const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools?.includes(tool));
3184
+ if (outsideEffectiveTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
3185
+ const outsideCallTool = (options.tools ?? []).find((tool) => !tool.startsWith("!") && !resourcePatternHasMagic(tool) && !candidateTools?.includes(tool));
3186
+ if (outsideCallTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideCallTool}`);
3187
+ const outsideRootTool = tools?.find((tool) => !rootTools.includes(tool));
3188
+ if (outsideRootTool) fail("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideRootTool}`);
3189
+ }
3190
+ const selectedSkills = resources.skills === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.skills, resources.skills);
3191
+ const extensionResources = resources.extensions?.map((extension) => canonicalPath(extension));
3192
+ const selectedExtensions = extensionResources === void 0 ? void 0 : selectResourcesByLayers(selectorLayers.extensions, extensionResources);
3193
+ const unmatchedSkills = resources.skills === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.skills.flatMap((layer) => layer ?? []), resources.skills);
3194
+ const unmatchedExtensions = extensionResources === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.extensions.flatMap((layer) => layer ?? []), extensionResources);
3195
+ const unmatchedTools = candidateTools === void 0 ? void 0 : unmatchedResourcePatterns(selectorLayers.tools.flatMap((layer) => layer ?? []), candidateTools);
3196
+ const aliases = options.modelAliases ?? settings?.effective.modelAliases ?? {};
3197
+ const requestedModel = options.model ?? (definition?.model && definition.thinking && !definition.model.includes(":") ? `${definition.model}:${definition.thinking}` : definition?.model);
3198
+ const blockedAlias = requestedModel?.split(":", 1)[0];
3199
+ if (requestedModel !== void 0 && blockedAlias && options.blockedAliases?.has(blockedAlias) && !modelAliasName(requestedModel, aliases)) {
3200
+ const target = options.blockedAliasTargets?.[blockedAlias];
3201
+ fail("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${options.settingsPath ? ` (settings: ${options.settingsPath})` : ""}`);
3202
+ }
3203
+ let model = options.modelOverride;
3204
+ if (model === void 0 && requestedModel !== void 0) {
3205
+ assertModelThinking(requestedModel);
3206
+ model = resolveModelReference(requestedModel, aliases, options.knownModels ?? options.availableModels, options.settingsPath ?? settings?.sources.modelAliases);
3207
+ }
3208
+ if (model === void 0) model = options.rootModel;
3209
+ const availableModels = options.knownModels ?? options.availableModels ?? (options.rootModel === void 0 ? void 0 : /* @__PURE__ */ new Set([modelCapability(options.rootModel)]));
3210
+ 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})` : ""}`);
3211
+ const prompt = definition?.prompt ?? "";
3212
+ const overrideSystemPrompt = definition?.overrideSystemPrompt === true;
3213
+ const contextFiles = options.contextFiles ?? definition?.contextFiles;
3214
+ 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 } };
3215
+ }
3216
+
3217
+ // src/validation.ts
3218
+ function validateCheckpoint(value) {
3219
+ if (!object(value) || Object.keys(value).some((key) => !["name", "prompt", "context"].includes(key)) || typeof value.name !== "string" || value.name.trim() === "" || typeof value.prompt !== "string" || !jsonValue(value.context)) fail("INVALID_METADATA", "checkpoint requires only name, prompt, and JSON context");
3220
+ if (Buffer.byteLength(value.prompt) > 1024) fail("INVALID_METADATA", "checkpoint prompt exceeds 1024 UTF-8 bytes");
3221
+ if (Buffer.byteLength(JSON.stringify(value.context)) > 4096) fail("INVALID_METADATA", "checkpoint context exceeds 4096 UTF-8 bytes");
3222
+ return { name: value.name, prompt: value.prompt, context: value.context };
3093
3223
  }
3094
3224
  function validateRolePolicies(definitions, roles, availableModels, aliases = {}, knownModels = availableModels, settingsPath) {
3095
3225
  for (const role of roles) {
@@ -3346,12 +3476,6 @@ function workflowPrompt(template, values) {
3346
3476
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
3347
3477
  });
3348
3478
  }
3349
- function validateSchema(schema, at = "schema") {
3350
- if (!object(schema) || Object.getPrototypeOf(schema) !== Object.prototype || !jsonValue(schema)) fail("INVALID_SCHEMA", `${at} must be a plain JSON-compatible Schema object`);
3351
- if (typeof schema.type !== "string" && !Array.isArray(schema.type) && schema.$ref === void 0 && schema.anyOf === void 0 && schema.oneOf === void 0 && schema.allOf === void 0 && schema.const === void 0 && schema.enum === void 0) fail("INVALID_SCHEMA", `${at} has no JSON Schema shape`);
3352
- if (schema.required !== void 0 && (!Array.isArray(schema.required) || schema.required.some((key) => typeof key !== "string"))) fail("INVALID_SCHEMA", `${at}.required must be an array of strings`);
3353
- if (schema.properties !== void 0 && !object(schema.properties)) fail("INVALID_SCHEMA", `${at}.properties must be an object`);
3354
- }
3355
3479
  var AGENT_OPTION_KEYS = /* @__PURE__ */ new Set(["label", "model", "tools", "skills", "extensions", "contextFiles", "role", "outputSchema", "retries", "timeoutMs"]);
3356
3480
  function validateAgentOption(key, value, aliases, knownModels, settingsPath) {
3357
3481
  switch (key) {
@@ -3597,7 +3721,7 @@ function validateWorkflowLaunchWithRegistry(params, context, registry) {
3597
3721
  let fileScript;
3598
3722
  if (scriptPath !== void 0) {
3599
3723
  try {
3600
- fileScript = readFileSync2(resolve3(context.cwd, scriptPath), "utf8");
3724
+ fileScript = readFileSync4(resolve5(context.cwd, scriptPath), "utf8");
3601
3725
  } catch (error) {
3602
3726
  fail("INVALID_SYNTAX", `Cannot read workflow script file ${scriptPath}: ${errorText(error)}`);
3603
3727
  }
@@ -3631,7 +3755,7 @@ function validateWorkflowLaunchWithRegistry(params, context, registry) {
3631
3755
  import { fork, spawn } from "node:child_process";
3632
3756
  import { mkdtempSync, realpathSync as realpathSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
3633
3757
  import { tmpdir } from "node:os";
3634
- import { join as join6 } from "node:path";
3758
+ import { join as join7 } from "node:path";
3635
3759
  import { StringDecoder } from "node:string_decoder";
3636
3760
  var RPC_LIMIT_BYTES = 10 * 1024 * 1024;
3637
3761
  var HEARTBEAT_TIMEOUT_MS = 5e3;
@@ -4037,7 +4161,7 @@ function shellProcessKill(child) {
4037
4161
  forceKill.unref();
4038
4162
  }
4039
4163
  function executeShellCommand(command, options, signal, cwd = process.cwd()) {
4040
- return new Promise((resolve7, reject) => {
4164
+ return new Promise((resolve9, reject) => {
4041
4165
  let child;
4042
4166
  try {
4043
4167
  child = spawn(command, { shell: true, cwd, env: { ...process.env, ...options.env ?? {} }, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"] });
@@ -4107,7 +4231,7 @@ function executeShellCommand(command, options, signal, cwd = process.cwd()) {
4107
4231
  reject(error instanceof WorkflowError ? error : new WorkflowError("RPC_LIMIT_EXCEEDED", errorText(error)));
4108
4232
  return;
4109
4233
  }
4110
- resolve7(result);
4234
+ resolve9(result);
4111
4235
  });
4112
4236
  if (signal.aborted) {
4113
4237
  onAbort();
@@ -4131,8 +4255,8 @@ function workflowErrorFromWorker(error) {
4131
4255
  function runWorkflow(script, args = null, bridge = {}, signal) {
4132
4256
  encoded(args);
4133
4257
  const config = JSON.stringify({ script: instrumentWorkflow(script), args: structuredClone(args), functions: bridge.functions ?? {} });
4134
- const childDir = realpathSync2(mkdtempSync(join6(tmpdir(), "pi-wf-")));
4135
- const childFile = join6(childDir, "child.cjs");
4258
+ const childDir = realpathSync2(mkdtempSync(join7(tmpdir(), "pi-wf-")));
4259
+ const childFile = join7(childDir, "child.cjs");
4136
4260
  writeFileSync2(childFile, childSource);
4137
4261
  const child = fork(childFile, [String(RPC_LIMIT_BYTES), config], {
4138
4262
  execArgv: (() => {
@@ -4172,7 +4296,7 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
4172
4296
  }
4173
4297
  if (now - lastHeartbeatAt >= HEARTBEAT_TIMEOUT_MS) stop("WORKER_UNRESPONSIVE", "Workflow worker missed its five-second heartbeat");
4174
4298
  }, HEARTBEAT_INTERVAL_MS);
4175
- const result = new Promise((resolve7, reject) => {
4299
+ const result = new Promise((resolve9, reject) => {
4176
4300
  rejectResult = reject;
4177
4301
  child.on("message", (raw) => {
4178
4302
  try {
@@ -4187,7 +4311,7 @@ function runWorkflow(script, args = null, bridge = {}, signal) {
4187
4311
  if (message.type === "result") {
4188
4312
  encoded(message.value);
4189
4313
  finish();
4190
- resolve7(message.value);
4314
+ resolve9(message.value);
4191
4315
  return;
4192
4316
  }
4193
4317
  if (message.type === "error") {
@@ -4343,20 +4467,20 @@ async function persistAgentAttempts(store, id, attempts) {
4343
4467
  import { randomUUID as randomUUID5 } from "node:crypto";
4344
4468
  import { existsSync as existsSync2 } from "node:fs";
4345
4469
  import { copyFile as copyFile2, mkdir as mkdir4 } from "node:fs/promises";
4346
- import { dirname as dirname7, join as join15 } from "node:path";
4470
+ import { dirname as dirname8, join as join16 } from "node:path";
4347
4471
  import { fileURLToPath as fileURLToPath5 } from "node:url";
4348
4472
  import { Type as Type3 } from "@earendil-works/pi-ai";
4349
- import { copyToClipboard, getAgentDir as getAgentDir4, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
4473
+ import { copyToClipboard, getAgentDir as getAgentDir5, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
4350
4474
 
4351
4475
  // src/agent-execution.ts
4352
- import { existsSync, realpathSync as realpathSync3, readFileSync as readFileSync3 } from "node:fs";
4476
+ import { existsSync, realpathSync as realpathSync3, readFileSync as readFileSync5 } from "node:fs";
4353
4477
  import { copyFile } from "node:fs/promises";
4354
4478
  import { homedir as homedir5 } from "node:os";
4355
- import { basename as basename4, dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
4479
+ import { basename as basename4, dirname as dirname5, join as join8, resolve as resolve6 } from "node:path";
4356
4480
  import { fileURLToPath as fileURLToPath3 } from "node:url";
4357
4481
  import { Type } from "@earendil-works/pi-ai";
4358
4482
  import { Compile } from "typebox/compile";
4359
- import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir2, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
4483
+ import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir as getAgentDir3, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
4360
4484
 
4361
4485
  // src/pi-runtime-adapter.ts
4362
4486
  var TURN_START_EVENTS = /* @__PURE__ */ new Set(["turn_start", "turn_started", "turnStarted"]);
@@ -4631,8 +4755,8 @@ function createLiveSessionHandoff() {
4631
4755
  let request;
4632
4756
  const waitForBoundary = () => {
4633
4757
  if (!turnActive) return Promise.resolve();
4634
- boundary ??= new Promise((resolve7) => {
4635
- resolveBoundary = resolve7;
4758
+ boundary ??= new Promise((resolve9) => {
4759
+ resolveBoundary = resolve9;
4636
4760
  });
4637
4761
  return boundary;
4638
4762
  };
@@ -4664,11 +4788,11 @@ function createLiveSessionHandoff() {
4664
4788
  if (request) return request;
4665
4789
  if (isReleased()) return;
4666
4790
  state = "handoff-pending";
4667
- takeover = new Promise((resolve7) => {
4668
- resolveTakeover = resolve7;
4791
+ takeover = new Promise((resolve9) => {
4792
+ resolveTakeover = resolve9;
4669
4793
  });
4670
- resume = new Promise((resolve7) => {
4671
- resolveResume = resolve7;
4794
+ resume = new Promise((resolve9) => {
4795
+ resolveResume = resolve9;
4672
4796
  });
4673
4797
  request = (async () => {
4674
4798
  await waitForBoundary();
@@ -5195,36 +5319,28 @@ function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
5195
5319
  function accounting(stats) {
5196
5320
  return { input: stats.tokens.input, output: stats.tokens.output, cacheRead: stats.tokens.cacheRead, cacheWrite: stats.tokens.cacheWrite, cost: stats.cost };
5197
5321
  }
5198
- var extensionDirectory = dirname4(fileURLToPath3(import.meta.url));
5199
- var workflowPackageRoot = basename4(dirname4(extensionDirectory)) === "dist" ? resolve4(extensionDirectory, "../..") : resolve4(extensionDirectory, "..");
5322
+ var extensionDirectory = dirname5(fileURLToPath3(import.meta.url));
5323
+ var workflowPackageRoot = basename4(dirname5(extensionDirectory)) === "dist" ? resolve6(extensionDirectory, "../..") : resolve6(extensionDirectory, "..");
5200
5324
  var WORKFLOW_HOST_ENTRIES = /* @__PURE__ */ new Set([
5201
- canonicalPath(resolve4(workflowPackageRoot, "src/index.ts")),
5202
- canonicalPath(resolve4(workflowPackageRoot, "dist/src/index.js")),
5203
- canonicalPath(resolve4(workflowPackageRoot, "starter/index.ts")),
5204
- canonicalPath(resolve4(workflowPackageRoot, "dist/starter/index.js")),
5205
- canonicalPath(resolve4(workflowPackageRoot, "subagents/index.ts")),
5206
- canonicalPath(resolve4(workflowPackageRoot, "dist/subagents/index.js"))
5325
+ canonicalPath(resolve6(workflowPackageRoot, "src/index.ts")),
5326
+ canonicalPath(resolve6(workflowPackageRoot, "dist/src/index.js")),
5327
+ canonicalPath(resolve6(workflowPackageRoot, "starter/index.ts")),
5328
+ canonicalPath(resolve6(workflowPackageRoot, "dist/starter/index.js")),
5329
+ canonicalPath(resolve6(workflowPackageRoot, "subagents/index.ts")),
5330
+ canonicalPath(resolve6(workflowPackageRoot, "dist/subagents/index.js"))
5207
5331
  ]);
5208
- function canonicalExtensionSelector(selector, base = process.cwd()) {
5209
- const negated = selector.startsWith("!");
5210
- const body = negated ? selector.slice(1) : selector;
5211
- if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
5212
- const resolved = resolve4(base, body);
5213
- if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
5214
- return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
5215
- }
5216
5332
  var WORKFLOW_DIRECTORY = "pi-extensible-workflows";
5217
5333
  function workflowSystemPromptPath(cwd, agentDir, projectTrusted2) {
5218
- const projectPath = join7(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
5334
+ const projectPath = join8(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
5219
5335
  if (projectTrusted2 && existsSync(projectPath)) return projectPath;
5220
- const globalPaths = [join7(homedir5(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join7(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
5336
+ const globalPaths = [join8(homedir5(), ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md"), join8(agentDir, WORKFLOW_DIRECTORY, "SYSTEM.md")];
5221
5337
  return globalPaths.find((path) => existsSync(path));
5222
5338
  }
5223
5339
  function filterContextFiles(base, scopes, cwd, agentDir) {
5224
5340
  const globalPath = canonicalPath(agentDir);
5225
5341
  const cwdPath = canonicalPath(cwd);
5226
5342
  return base.filter(({ path }) => {
5227
- const resolvedPath = canonicalPath(dirname4(path));
5343
+ const resolvedPath = canonicalPath(dirname5(path));
5228
5344
  return scopes.some((scope) => scope === "global" ? resolvedPath === globalPath : scope === "cwd" ? resolvedPath === cwdPath : resolvedPath !== globalPath && resolvedPath !== cwdPath);
5229
5345
  });
5230
5346
  }
@@ -5250,10 +5366,10 @@ async function loadNativePromptTemplateModule() {
5250
5366
  const packageDirectory = await loadPiPackageDirectory();
5251
5367
  let promptTemplatePath;
5252
5368
  if (packageDirectory.directory) {
5253
- const candidate = resolve4(packageDirectory.directory, "dist/core/prompt-templates.js");
5369
+ const candidate = resolve6(packageDirectory.directory, "dist/core/prompt-templates.js");
5254
5370
  if (existsSync(candidate)) promptTemplatePath = candidate;
5255
5371
  }
5256
- promptTemplatePath ??= resolve4(dirname4(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
5372
+ promptTemplatePath ??= resolve6(dirname5(fileURLToPath3(import.meta.resolve("@earendil-works/pi-coding-agent"))), "core/prompt-templates.js");
5257
5373
  const packageDirectoryError = packageDirectory.error ? ` Package directory lookup failed: ${packageDirectory.error}` : "";
5258
5374
  return nativePromptTemplateModule ??= import(promptTemplatePath).then((module) => module).catch((error) => {
5259
5375
  throw new Error(`Could not load Pi prompt templates.${packageDirectoryError}`, { cause: error });
@@ -5328,13 +5444,13 @@ function flushExtensionProviders(resourceLoader, modelRuntime) {
5328
5444
  return failures;
5329
5445
  }
5330
5446
  async function createLocalPiSessionHandle(input, sessionStartEvent) {
5331
- const agentDir = input.agentDir ?? getAgentDir2();
5447
+ const agentDir = input.agentDir ?? getAgentDir3();
5332
5448
  const systemPromptSource = workflowSystemPromptPath(input.cwd, agentDir, input.resourcePolicy?.projectTrusted ?? true);
5333
5449
  const systemPromptOptions = input.systemPrompt !== void 0 ? { systemPromptOverride: () => input.systemPrompt } : systemPromptSource !== void 0 ? { systemPrompt: systemPromptSource } : {};
5334
5450
  const contextFilesOverride = input.contextFiles === void 0 ? void 0 : (base) => ({ ...base, agentsFiles: filterContextFiles(base.agentsFiles, input.contextFiles ?? [], input.cwd, agentDir) });
5335
- const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join7(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join7(agentDir, "sessions")) : SessionManager.create(input.cwd));
5451
+ const manager = input.sessionManager ?? (input.sessionPath ? SessionManager.open(input.sessionPath, join8(agentDir, "sessions"), input.cwd) : input.agentDir ? SessionManager.create(input.cwd, join8(agentDir, "sessions")) : SessionManager.create(input.cwd));
5336
5452
  if (!input.sessionPath) manager.appendSessionInfo(input.sessionLabel);
5337
- const modelRuntime = await ModelRuntime.create({ authPath: join7(agentDir, "auth.json"), modelsPath: join7(agentDir, "models.json") });
5453
+ const modelRuntime = await ModelRuntime.create({ authPath: join8(agentDir, "auth.json"), modelsPath: join8(agentDir, "models.json") });
5338
5454
  const customTools = [...input.customTools ?? [], ...input.resultTool ? [input.resultTool] : []];
5339
5455
  const tools = [.../* @__PURE__ */ new Set([...input.tools, ...customTools.map(({ name }) => name)])];
5340
5456
  let settingsManager;
@@ -5347,20 +5463,22 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
5347
5463
  const resolved = await packageManager.resolve();
5348
5464
  const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
5349
5465
  const selectorSources = policy.selectorSources;
5350
- const extensionLayers = [selectorSources.global.extensions, selectorSources.project.extensions, selectorSources.role?.extensions, selectorSources.call?.extensions];
5351
- const extensionSelectors = extensionLayers.flatMap((layer) => (layer ?? []).map((original) => ({ original, matching: canonicalExtensionSelector(original, input.cwd) })));
5352
- const selectedExtensions = selectResourcesByLayers(extensionLayers.map((layer) => layer?.map((selector) => canonicalExtensionSelector(selector, input.cwd))), discoveredExtensions);
5466
+ const selection = resolveRole(void 0, {
5467
+ cwd: input.cwd,
5468
+ selectorSources,
5469
+ resources: { extensions: discoveredExtensions }
5470
+ });
5471
+ const selectedExtensions = selection.selectedExtensions ?? [];
5353
5472
  const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
5354
- const unmatchedExtensions = extensionSelectors.filter(({ matching }) => unmatchedResourcePatterns([matching], discoveredExtensions).length > 0).map(({ original }) => original);
5355
5473
  policy.selectedExtensions = selectedExtensions;
5356
- policy.unmatchedExtensions = unmatchedExtensions;
5474
+ policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
5357
5475
  const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
5358
5476
  const updateSkillMatches = (skills) => {
5359
5477
  const names = [...new Set(skills.map(({ name }) => name))];
5360
- const skillLayers = [selectorSources.global.skills, selectorSources.project.skills, selectorSources.role?.skills, selectorSources.call?.skills];
5361
- const selectedSkills = selectResourcesByLayers(skillLayers, names);
5478
+ const selected = resolveRole(void 0, { cwd: input.cwd, selectorSources, resources: { skills: names } });
5479
+ const selectedSkills = selected.selectedSkills ?? [];
5362
5480
  policy.selectedSkills = selectedSkills;
5363
- policy.unmatchedSkills = unmatchedResourcePatterns(skillLayers.flatMap((layer) => layer ?? []), names);
5481
+ policy.unmatchedSkills = selected.unmatchedSkills ?? [];
5364
5482
  return new Set(names.filter((name) => !selectedSkills.includes(name)));
5365
5483
  };
5366
5484
  resourceLoader = new DefaultResourceLoader({
@@ -5852,27 +5970,27 @@ function appendResourcePolicyNarrowing(ceiling, candidate) {
5852
5970
  };
5853
5971
  }
5854
5972
  function packageRoot(start) {
5855
- let current = dirname4(realpathSync3(start));
5973
+ let current = dirname5(realpathSync3(start));
5856
5974
  for (; ; ) {
5857
- const candidates = [current, join7(current, "..", "@earendil-works", "pi-coding-agent"), join7(current, "..", "pi-coding-agent")];
5975
+ const candidates = [current, join8(current, "..", "@earendil-works", "pi-coding-agent"), join8(current, "..", "pi-coding-agent")];
5858
5976
  for (const candidate of candidates) {
5859
- const packageJsonPath = join7(candidate, "package.json");
5977
+ const packageJsonPath = join8(candidate, "package.json");
5860
5978
  if (!existsSync(packageJsonPath)) continue;
5861
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
5979
+ const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
5862
5980
  if (object(packageJson) && typeof packageJson.name === "string" && packageJson.name === "@earendil-works/pi-coding-agent") return candidate;
5863
5981
  }
5864
- const parent = dirname4(current);
5982
+ const parent = dirname5(current);
5865
5983
  if (parent === current) return void 0;
5866
5984
  current = parent;
5867
5985
  }
5868
5986
  }
5869
5987
  function piPackageEntrypoint(root) {
5870
- const packageJson = JSON.parse(readFileSync3(join7(root, "package.json"), "utf8"));
5988
+ const packageJson = JSON.parse(readFileSync5(join8(root, "package.json"), "utf8"));
5871
5989
  if (!object(packageJson)) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
5872
5990
  const bin = packageJson.bin;
5873
5991
  const cli = typeof bin === "string" ? bin : object(bin) && typeof bin.pi === "string" ? bin.pi : void 0;
5874
5992
  if (!cli) throw new Error("the @earendil-works/pi-coding-agent package does not declare a pi CLI");
5875
- const entrypoint = resolve4(root, cli);
5993
+ const entrypoint = resolve6(root, cli);
5876
5994
  if (!existsSync(entrypoint)) throw new Error(`the originating Pi CLI entrypoint is unavailable: ${entrypoint}`);
5877
5995
  return entrypoint;
5878
5996
  }
@@ -5913,7 +6031,7 @@ ${instruction}` : ""}`;
5913
6031
  }
5914
6032
  async function preparedAgentSession(input, initialPrompt) {
5915
6033
  const { runtime: piRuntime, error: piRuntimeError } = await piRuntimeResolution;
5916
- const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir2(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
6034
+ const systemPromptPath = input.systemPrompt === void 0 ? workflowSystemPromptPath(input.cwd, input.agentDir ?? getAgentDir3(), input.resourcePolicy?.projectTrusted ?? true) : void 0;
5917
6035
  const prepared = {
5918
6036
  cwd: input.cwd,
5919
6037
  model: Object.freeze({ ...input.model }),
@@ -5943,7 +6061,7 @@ function agentSetupSummary(setup, hookNames) {
5943
6061
  return { hookNames: [...hookNames], model: { provider: model.provider, model: model.model, ...model.thinking ? { thinking: model.thinking } : {} }, tools: [...setup.sessionInput.tools, ...setup.sessionInput.resultTool ? [setup.sessionInput.resultTool.name] : []], cwd: setup.sessionInput.cwd, ...setup.sessionInput.resourcePolicy ? { resourceSelectors: resourcePolicySummary(setup.sessionInput.resourcePolicy, setup.sessionInput.tools) } : {} };
5944
6062
  }
5945
6063
  async function attemptSessionInput(source, attempt) {
5946
- const target = join7(dirname4(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
6064
+ const target = join8(dirname5(source), `${basename4(source, ".jsonl")}-attempt-${String(attempt)}.jsonl`);
5947
6065
  await copyFile(source, target);
5948
6066
  return target;
5949
6067
  }
@@ -6053,7 +6171,6 @@ var WorkflowAgentExecutor = class {
6053
6171
  const roleName = typeof options.role === "string" ? options.role : void 0;
6054
6172
  const roleDefinition = roleName ? this.root.agentDefinitions?.[roleName] : void 0;
6055
6173
  if (roleName && !roleDefinition) throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName}`);
6056
- const candidateTools = inheritedTools !== void 0 ? [...inheritedTools] : [...this.root.tools];
6057
6174
  if (inheritedTools === void 0) for (const pattern of roleDefinition?.tools ?? []) {
6058
6175
  const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
6059
6176
  if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !this.root.tools.has(body)) {
@@ -6064,33 +6181,30 @@ var WorkflowAgentExecutor = class {
6064
6181
  }
6065
6182
  }
6066
6183
  }
6067
- const selectorLayers = [
6068
- this.root.resourceSelectors?.tools,
6069
- roleDefinition?.tools,
6070
- options.tools
6071
- ];
6072
- for (const pattern of options.tools ?? []) {
6073
- const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
6074
- if (!pattern.startsWith("!") && !resourcePatternHasMagic(pattern) && !candidateTools.includes(body)) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${body}`);
6075
- }
6076
- const outsideEffectiveTool = options.effectiveTools?.find((tool) => !candidateTools.includes(tool));
6077
- if (outsideEffectiveTool) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${outsideEffectiveTool}`);
6078
- const requested = options.effectiveTools ?? selectResourcesByLayers(selectorLayers, candidateTools);
6079
- const forbidden = requested.find((tool) => !this.root.tools.has(tool));
6080
- if (forbidden) throw new WorkflowError("UNKNOWN_TOOL", `Tool is outside the launching session boundary: ${forbidden}`);
6081
- const requestedModel = options.model ?? (roleDefinition?.model && roleDefinition.thinking && !roleDefinition.model.includes(":") ? `${roleDefinition.model}:${roleDefinition.thinking}` : roleDefinition?.model);
6082
- const alias = requestedModel === void 0 ? void 0 : modelAliasName(requestedModel, this.root.modelAliases ?? {});
6083
- const blockedAlias = requestedModel?.split(":", 1)[0];
6084
- if (requestedModel !== void 0 && blockedAlias && this.root.blockedAliases?.has(blockedAlias) && !alias) {
6085
- const target = this.root.blockedAliasTargets?.[blockedAlias];
6086
- throw new WorkflowError("UNKNOWN_MODEL", `Unknown model alias ${requestedModel}${target ? ` resolved to ${target}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
6087
- }
6088
- const model = options.modelOverride ?? parseModel(requestedModel, this.root.model, this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
6089
- const availableModels = this.root.knownModels ?? this.root.availableModels ?? /* @__PURE__ */ new Set([modelCapability(this.root.model)]);
6090
- if (!availableModels.has(modelCapability(model))) throw new WorkflowError("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
6091
- const overrideSystemPrompt = roleDefinition?.overrideSystemPrompt === true;
6092
- const contextFiles = options.contextFiles ?? roleDefinition?.contextFiles;
6093
- return { model, ...alias && requestedModel ? { requestedModel } : {}, tools: requested, ...overrideSystemPrompt ? { systemPrompt: roleDefinition.prompt ?? "" } : {}, systemPromptAppend: overrideSystemPrompt ? "" : roleDefinition?.prompt ?? "", ...contextFiles === void 0 ? {} : { contextFiles: [...contextFiles] } };
6184
+ const resolved = resolveRole(roleName, {
6185
+ cwd: this.root.cwd,
6186
+ ...roleDefinition === void 0 ? {} : { definition: roleDefinition },
6187
+ ...this.root.agentDefinitions === void 0 ? {} : { definitions: this.root.agentDefinitions },
6188
+ ...this.root.agentDir === void 0 ? {} : { agentDir: this.root.agentDir },
6189
+ selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
6190
+ ...this.root.modelAliases === void 0 ? {} : { modelAliases: this.root.modelAliases },
6191
+ ...this.root.knownModels === void 0 ? {} : { knownModels: this.root.knownModels },
6192
+ ...this.root.availableModels === void 0 ? {} : { availableModels: this.root.availableModels },
6193
+ ...this.root.blockedAliases === void 0 ? {} : { blockedAliases: this.root.blockedAliases },
6194
+ ...this.root.blockedAliasTargets === void 0 ? {} : { blockedAliasTargets: this.root.blockedAliasTargets },
6195
+ ...this.root.settingsPath === void 0 ? {} : { settingsPath: this.root.settingsPath },
6196
+ rootModel: this.root.model,
6197
+ rootTools: this.root.tools,
6198
+ ...inheritedTools === void 0 ? {} : { inheritedTools },
6199
+ ...options.model === void 0 ? {} : { model: options.model },
6200
+ ...options.modelOverride === void 0 ? {} : { modelOverride: options.modelOverride },
6201
+ ...options.tools === void 0 ? {} : { tools: options.tools },
6202
+ ...options.skills === void 0 ? {} : { skills: options.skills },
6203
+ ...options.extensions === void 0 ? {} : { extensions: options.extensions },
6204
+ ...options.contextFiles === void 0 ? {} : { contextFiles: options.contextFiles },
6205
+ ...options.effectiveTools === void 0 ? {} : { effectiveTools: options.effectiveTools }
6206
+ });
6207
+ 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 } };
6094
6208
  }
6095
6209
  async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
6096
6210
  const executionSignal = signal ?? this.root.runContext?.signal;
@@ -6376,12 +6490,12 @@ var FairAgentScheduler = class {
6376
6490
  const id = `${runId}:${String(++this.#nextId)}`;
6377
6491
  const tuiIndex = ++run.nextIndex;
6378
6492
  let resolveResult = () => void 0;
6379
- const promise = new Promise((resolve7) => {
6380
- resolveResult = resolve7;
6493
+ const promise = new Promise((resolve9) => {
6494
+ resolveResult = resolve9;
6381
6495
  });
6382
6496
  let resolveCompletion = () => void 0;
6383
- const completion = new Promise((resolve7) => {
6384
- resolveCompletion = resolve7;
6497
+ const completion = new Promise((resolve9) => {
6498
+ resolveCompletion = resolve9;
6385
6499
  });
6386
6500
  const node = { id, runId, ...parentId ? { parentId } : {}, prompt, options: effective, children: /* @__PURE__ */ new Set(), collected: false, collecting: false, resultQueue: Promise.resolve(), state: "queued", controller: new AbortController(), promise, resolve: resolveResult, completion, resolveCompletion, task: async () => void 0, restored: false };
6387
6501
  node.task = async () => {
@@ -6419,8 +6533,8 @@ var FairAgentScheduler = class {
6419
6533
  const childPromise = child.promise;
6420
6534
  const previousCollection = parent.resultQueue;
6421
6535
  let releaseCollection = () => void 0;
6422
- parent.resultQueue = new Promise((resolve7) => {
6423
- releaseCollection = resolve7;
6536
+ parent.resultQueue = new Promise((resolve9) => {
6537
+ releaseCollection = resolve9;
6424
6538
  });
6425
6539
  try {
6426
6540
  await previousCollection;
@@ -6429,9 +6543,9 @@ var FairAgentScheduler = class {
6429
6543
  this.#persist(parent.runId);
6430
6544
  this.#release(parent.runId);
6431
6545
  const outcome = await childPromise;
6432
- await new Promise((resolve7) => {
6546
+ await new Promise((resolve9) => {
6433
6547
  this.#enqueue(parent.runId, void 0, () => {
6434
- resolve7();
6548
+ resolve9();
6435
6549
  });
6436
6550
  });
6437
6551
  if (this.#cancelledOrSettled(parent)) {
@@ -6564,12 +6678,12 @@ var FairAgentScheduler = class {
6564
6678
  for (const record3 of ownership) {
6565
6679
  if (record3.id.split(":").slice(0, -1).join(":") !== runId) throw new WorkflowError("RESUME_INCOMPATIBLE", `Persisted agent belongs to another run: ${record3.id}`);
6566
6680
  let resolveResult = () => void 0;
6567
- const promise = new Promise((resolve7) => {
6568
- resolveResult = resolve7;
6681
+ const promise = new Promise((resolve9) => {
6682
+ resolveResult = resolve9;
6569
6683
  });
6570
6684
  let resolveCompletion = () => void 0;
6571
- const completion = new Promise((resolve7) => {
6572
- resolveCompletion = resolve7;
6685
+ const completion = new Promise((resolve9) => {
6686
+ resolveCompletion = resolve9;
6573
6687
  });
6574
6688
  const node = { id: record3.id, runId, ...record3.parentId ? { parentId: record3.parentId } : {}, ...record3.prompt === void 0 ? {} : { prompt: record3.prompt }, options: this.#inherit(run, void 0, record3.options), children: /* @__PURE__ */ new Set(), collected: false, collecting: false, resultQueue: Promise.resolve(), state: record3.state, controller: new AbortController(), promise, resolve: resolveResult, completion, resolveCompletion, task: async () => void 0, restored: true };
6575
6689
  this.#nodes.set(node.id, node);
@@ -6658,9 +6772,9 @@ var FairAgentScheduler = class {
6658
6772
  const child = this.#nodes.get(childId);
6659
6773
  if (child && !child.collected) this.#cancelTree(child);
6660
6774
  }
6661
- const resolve7 = node.resolve;
6775
+ const resolve9 = node.resolve;
6662
6776
  node.resolve = void 0;
6663
- resolve7?.(result);
6777
+ resolve9?.(result);
6664
6778
  node.resolveCompletion();
6665
6779
  }
6666
6780
  #cancelTree(node) {
@@ -6723,8 +6837,8 @@ var RunLifecycle = class {
6723
6837
  return this.#state;
6724
6838
  }
6725
6839
  async enter() {
6726
- while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input") await new Promise((resolve7) => {
6727
- this.#waiters.push(resolve7);
6840
+ while (this.#state === "pausing" || this.#state === "paused" || this.#state === "awaiting_input") await new Promise((resolve9) => {
6841
+ this.#waiters.push(resolve9);
6728
6842
  });
6729
6843
  if (this.#state !== "running") throw new WorkflowError("CANCELLED", `Run is ${this.#state}`);
6730
6844
  this.#active += 1;
@@ -6734,8 +6848,8 @@ var RunLifecycle = class {
6734
6848
  if (this.#state === "pausing" && this.#active === 0) await this.#set("paused", "pause");
6735
6849
  }
6736
6850
  async enterAwaitingInput() {
6737
- while (this.#state === "pausing" || this.#state === "paused") await new Promise((resolve7) => {
6738
- this.#waiters.push(resolve7);
6851
+ while (this.#state === "pausing" || this.#state === "paused") await new Promise((resolve9) => {
6852
+ this.#waiters.push(resolve9);
6739
6853
  });
6740
6854
  if (this.#state === "awaiting_input") return;
6741
6855
  if (this.#state !== "running") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot await input for ${this.#state} run`);
@@ -6744,7 +6858,7 @@ var RunLifecycle = class {
6744
6858
  async resolveAwaitingInput() {
6745
6859
  if (this.#state !== "awaiting_input") return;
6746
6860
  await this.#set("running", "checkpoint_resolved");
6747
- for (const resolve7 of this.#waiters.splice(0)) resolve7();
6861
+ for (const resolve9 of this.#waiters.splice(0)) resolve9();
6748
6862
  }
6749
6863
  async pause() {
6750
6864
  if (this.#state !== "running") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot pause ${this.#state} run`);
@@ -6755,7 +6869,7 @@ var RunLifecycle = class {
6755
6869
  async resume() {
6756
6870
  if (this.#state !== "pausing" && this.#state !== "paused" && this.#state !== "interrupted" && this.#state !== "budget_exhausted") throw new WorkflowError("RESUME_INCOMPATIBLE", `Cannot resume ${this.#state} run`);
6757
6871
  await this.#set("running", "resume");
6758
- for (const resolve7 of this.#waiters.splice(0)) resolve7();
6872
+ for (const resolve9 of this.#waiters.splice(0)) resolve9();
6759
6873
  }
6760
6874
  async providerPause() {
6761
6875
  await this.leave();
@@ -6765,7 +6879,7 @@ var RunLifecycle = class {
6765
6879
  async terminal(state, reason) {
6766
6880
  if (HARD_TERMINAL_RUN_STATES.has(this.#state)) throw new WorkflowError("RESUME_INCOMPATIBLE", `${this.#state} runs are terminal`);
6767
6881
  await this.#set(state, reason ?? state);
6768
- for (const resolve7 of this.#waiters.splice(0)) resolve7();
6882
+ for (const resolve9 of this.#waiters.splice(0)) resolve9();
6769
6883
  }
6770
6884
  async #set(state, reason) {
6771
6885
  const previousState = this.#state;
@@ -7041,7 +7155,7 @@ function withWorkflowFunctions(bridge, store, runContext, registry, settings = O
7041
7155
  import { randomUUID as randomUUID4 } from "node:crypto";
7042
7156
 
7043
7157
  // src/host-delivery.ts
7044
- import { join as join8 } from "node:path";
7158
+ import { join as join9 } from "node:path";
7045
7159
  import { DEFAULT_MAX_BYTES } from "@earendil-works/pi-coding-agent";
7046
7160
  var FAILURE_DELIVERY_STATES = /* @__PURE__ */ new Set(["failed", "stopped", "interrupted", "budget_exhausted"]);
7047
7161
  var ForegroundDeliveryController = class {
@@ -7366,7 +7480,7 @@ async function createWorkflowFailureDiagnostics(store, metadata, error, run) {
7366
7480
  completedSiblingAgents,
7367
7481
  completedSiblingPaths,
7368
7482
  ...retry ? { retry } : {},
7369
- artifacts: { runDirectory: store.directory, statePath: join8(store.directory, "state.json"), journalPath: join8(store.directory, "journal.json") }
7483
+ artifacts: { runDirectory: store.directory, statePath: join9(store.directory, "state.json"), journalPath: join9(store.directory, "journal.json") }
7370
7484
  });
7371
7485
  }
7372
7486
  function formatWorkflowFailureDiagnostics(diagnostic) {
@@ -7393,7 +7507,7 @@ function formatWorkflowFailureDeliveryFallback(workflowName, runId, runDirectory
7393
7507
  const code = errorCode(error) ?? "INTERNAL_ERROR";
7394
7508
  const failedPath = workflowFailedAt(error);
7395
7509
  const nextAction = retryable && code !== "BUDGET_EXHAUSTED" && code !== "CANCELLED" ? `; next action: workflow_retry({ runId: ${JSON.stringify(runId)} })` : "";
7396
- const line = `Workflow ${deliveryPart(workflowName, 128)} failed (runId=${deliveryPart(runId, 128)}): error=${code}: ${deliveryPart(formatWorkflowFailure(error), 768)}${failedPath ? `; failed path=${deliveryPart(failedPath, 512)}` : ""}${nextAction}; artifacts: runDirectory=${deliveryPart(runDirectory, 512)} statePath=${deliveryPart(join8(runDirectory, "state.json"), 512)} journalPath=${deliveryPart(join8(runDirectory, "journal.json"), 512)}`;
7510
+ const line = `Workflow ${deliveryPart(workflowName, 128)} failed (runId=${deliveryPart(runId, 128)}): error=${code}: ${deliveryPart(formatWorkflowFailure(error), 768)}${failedPath ? `; failed path=${deliveryPart(failedPath, 512)}` : ""}${nextAction}; artifacts: runDirectory=${deliveryPart(runDirectory, 512)} statePath=${deliveryPart(join9(runDirectory, "state.json"), 512)} journalPath=${deliveryPart(join9(runDirectory, "journal.json"), 512)}`;
7397
7511
  return Buffer.byteLength(line) <= DELIVERY_LIMIT_BYTES ? line : utf8Prefix(line, DELIVERY_LIMIT_BYTES);
7398
7512
  }
7399
7513
  function serializeWorkflowFailureDiagnostics(diagnostic) {
@@ -7728,14 +7842,14 @@ function createWorkflowRecovery(deps) {
7728
7842
 
7729
7843
  // src/host-navigator.ts
7730
7844
  import { readFile as readFile5 } from "node:fs/promises";
7731
- import { join as join12 } from "node:path";
7845
+ import { join as join13 } from "node:path";
7732
7846
  import { SettingsManager as SettingsManager2, truncateToVisualLines as truncateToVisualLines2 } from "@earendil-works/pi-coding-agent";
7733
7847
 
7734
7848
  // src/workflow-artifacts.ts
7735
7849
  import { spawn as spawn2 } from "node:child_process";
7736
7850
  import { mkdtemp, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
7737
7851
  import { tmpdir as tmpdir2 } from "node:os";
7738
- import { join as join9 } from "node:path";
7852
+ import { join as join10 } from "node:path";
7739
7853
  function workflowScriptArtifact(script) {
7740
7854
  return { extension: ".js", content: script };
7741
7855
  }
@@ -7749,23 +7863,23 @@ function workflowResultArtifact(value) {
7749
7863
  async function spawnWorkflowEditor(command, path) {
7750
7864
  const [editor, ...editorArgs] = command.split(" ");
7751
7865
  if (!editor) return null;
7752
- return new Promise((resolve7) => {
7866
+ return new Promise((resolve9) => {
7753
7867
  try {
7754
7868
  const child = spawn2(editor, [...editorArgs, path], { stdio: "inherit", shell: process.platform === "win32" });
7755
7869
  child.once("error", () => {
7756
- resolve7(null);
7870
+ resolve9(null);
7757
7871
  });
7758
7872
  child.once("close", (code) => {
7759
- resolve7(code);
7873
+ resolve9(code);
7760
7874
  });
7761
7875
  } catch {
7762
- resolve7(null);
7876
+ resolve9(null);
7763
7877
  }
7764
7878
  });
7765
7879
  }
7766
7880
  async function openWorkflowArtifact(tui, command, artifact) {
7767
- const directory = await mkdtemp(join9(tmpdir2(), "pi-workflow-editor-"));
7768
- const path = join9(directory, `artifact${artifact.extension}`);
7881
+ const directory = await mkdtemp(join10(tmpdir2(), "pi-workflow-editor-"));
7882
+ const path = join10(directory, `artifact${artifact.extension}`);
7769
7883
  try {
7770
7884
  await writeFile3(path, artifact.content, { encoding: "utf8", mode: 384 });
7771
7885
  tui.stop();
@@ -7782,11 +7896,11 @@ async function openWorkflowArtifact(tui, command, artifact) {
7782
7896
 
7783
7897
  // src/host-view.ts
7784
7898
  import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
7785
- import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
7899
+ import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
7786
7900
 
7787
7901
  // src/background-widget.ts
7788
- import { readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
7789
- import { join as join10 } from "node:path";
7902
+ import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
7903
+ import { join as join11 } from "node:path";
7790
7904
  import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
7791
7905
  var KEY = "piewf-widget";
7792
7906
  var ENTRY_TYPE = "piewf-run-receipt";
@@ -7876,7 +7990,7 @@ function renderableRun(value) {
7876
7990
  }
7877
7991
  function readRun(directory) {
7878
7992
  try {
7879
- const state = JSON.parse(readFileSync4(join10(directory, "state.json"), "utf8"));
7993
+ const state = JSON.parse(readFileSync6(join11(directory, "state.json"), "utf8"));
7880
7994
  if (!renderableRun(state)) return void 0;
7881
7995
  const startedAt = statSync2(directory).birthtimeMs;
7882
7996
  return { ...state, directory, startedAt };
@@ -8105,7 +8219,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
8105
8219
  const title = live.length === 1 ? "Workflow" : `Workflows \xB7 ${String(live.length)} runs`;
8106
8220
  sizeOut.rows = rows.length;
8107
8221
  const body = collapse(rows, MAX_ROWS - 2, paint, offset, scrolling);
8108
- const inner = Math.max(20, width - 4);
8109
8222
  const total = rows.length;
8110
8223
  const shown = body.length;
8111
8224
  const scrollbar = (index) => {
@@ -8117,6 +8230,11 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
8117
8230
  const top = limit === 0 ? 0 : start >= limit ? shown - size : Math.floor(start / limit * (shown - size));
8118
8231
  return index >= top && index < top + size ? "\u2588" : "\u2591";
8119
8232
  };
8233
+ const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0 ? "" : scrolling ? "\u2191/\u2193 to scroll \xB7 Esc to exit" : "Alt+O to scroll";
8234
+ return drawFrame(title, hint, body, width, paint, accent, scrollbar);
8235
+ }
8236
+ function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => void 0) {
8237
+ const inner = Math.max(20, width - 4);
8120
8238
  const lines = body.map((row, index) => {
8121
8239
  const right = row.right ?? "";
8122
8240
  const rightWidth = right === "" ? 0 : visibleLength(right) + 2;
@@ -8128,7 +8246,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
8128
8246
  const bar = scrollbar(index);
8129
8247
  return `${rule} ${text} ${bar === void 0 ? rule : accent(bar)}`;
8130
8248
  });
8131
- const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0 ? "" : scrolling ? "\u2191/\u2193 to scroll \xB7 Esc to exit" : "Alt+O to scroll";
8132
8249
  const label = ` ${title} `;
8133
8250
  const tail = hint === "" ? "" : ` ${hint} `;
8134
8251
  const spare = inner + 2 - visibleLength(label) - visibleLength(tail);
@@ -8507,7 +8624,7 @@ function widget(pi, enabled = true) {
8507
8624
  continue;
8508
8625
  }
8509
8626
  try {
8510
- const mtime = statSync2(join10(run.directory, "state.json")).mtimeMs;
8627
+ const mtime = statSync2(join11(run.directory, "state.json")).mtimeMs;
8511
8628
  if (mtime === seen.get(runId)) continue;
8512
8629
  seen.set(runId, mtime);
8513
8630
  const fresh = readRun(run.directory);
@@ -8624,7 +8741,7 @@ function widget(pi, enabled = true) {
8624
8741
  const waiting = runs.get(runId)?.waiting;
8625
8742
  runs.set(runId, waiting ? { ...run, waiting } : run);
8626
8743
  try {
8627
- seen.set(runId, statSync2(join10(directory, "state.json")).mtimeMs);
8744
+ seen.set(runId, statSync2(join11(directory, "state.json")).mtimeMs);
8628
8745
  } catch {
8629
8746
  }
8630
8747
  if (isRunTerminal(run.state) && !awaitingDedicatedReceipt.has(runId)) receipt(runId);
@@ -9020,7 +9137,7 @@ function formatLogTimestamp(timestamp) {
9020
9137
  if (Number.isNaN(date.getTime())) return "--:--:--";
9021
9138
  return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
9022
9139
  }
9023
- function workflowLogLines(run, styles, expanded, width, showHint = false) {
9140
+ function workflowLogLines(run, styles, expanded, width, showHint = false, tail = 5) {
9024
9141
  const events = (run.events ?? []).filter((event) => event.type === "log");
9025
9142
  if (!events.length) return [];
9026
9143
  const indent = " ".repeat(11);
@@ -9030,7 +9147,7 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
9030
9147
  const chunks = truncateToVisualLines(message, Number.MAX_SAFE_INTEGER, Math.max(1, width - 11), 0).visualLines;
9031
9148
  return chunks.map((chunk, chunkIndex) => `${chunkIndex === 0 ? prefix : indent}${chunk}`);
9032
9149
  }));
9033
- const visible = expanded ? visualLines : visualLines.slice(-5);
9150
+ const visible = expanded ? visualLines : visualLines.slice(-tail);
9034
9151
  let hint = "";
9035
9152
  if (!expanded && showHint) {
9036
9153
  try {
@@ -9040,7 +9157,47 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
9040
9157
  }
9041
9158
  return [expanded ? ` ${styles.muted("Logs")}` : ` ${styles.muted(`Logs${hint}`)}`, ...visible];
9042
9159
  }
9043
- function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false) {
9160
+ var ACTIVE_AGENT_STATES = /* @__PURE__ */ new Set(["running", "waiting_for_child", "retrying", "paused"]);
9161
+ var COMPACT_RECENT_SETTLED = 3;
9162
+ var COMPACT_LOG_LINES = 3;
9163
+ function expandHint() {
9164
+ try {
9165
+ return ` (${keyHint("app.tools.expand", "to expand")})`;
9166
+ } catch {
9167
+ return "";
9168
+ }
9169
+ }
9170
+ function agentEnd(agent) {
9171
+ return (agent.startedAt ?? 0) + (agent.durationMs ?? 0);
9172
+ }
9173
+ function phaseSummary(agents, styles) {
9174
+ const count = (state) => agents.filter((agent) => agent.state === state).length;
9175
+ const starts = agents.map((agent) => agent.startedAt).filter((value) => value !== void 0);
9176
+ const span = starts.length ? Math.max(...agents.map(agentEnd)) - Math.min(...starts) : 0;
9177
+ const accounting2 = sumAccounting(agents.map((agent) => agent.accounting ?? zeroAccounting()));
9178
+ const failed = count("failed");
9179
+ const parts = [`${String(count("completed"))} done`, ...failed ? [styles.error(`${String(failed)} failed`)] : [], ...count("cancelled") ? [`${String(count("cancelled"))} cancelled`] : [], ...span > 0 ? [formatWorkflowRuntime(span)] : [], formatTokens(accounting2.input + accounting2.output), formatCost(accounting2.cost)].filter(Boolean);
9180
+ return styles.muted(parts.join(" \xB7 "));
9181
+ }
9182
+ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false, maxLines) {
9183
+ const full = workflowProgressLines(run, spinner2, styles, now, expanded, width, showHint);
9184
+ if (expanded || maxLines === void 0 || full.length <= maxLines) return full.join("\n");
9185
+ const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
9186
+ const currentStart = Math.min(run.agents.length, phases.at(-1)?.afterAgent ?? 0);
9187
+ const current = run.agents.slice(currentStart);
9188
+ const recent = current.filter((agent) => SETTLED_AGENT_STATES.has(agent.state) && agent.state !== "failed").sort((left, right) => agentEnd(right) - agentEnd(left)).slice(0, COMPACT_RECENT_SETTLED);
9189
+ const candidates = [...current.filter((agent) => agent.state === "failed"), ...current.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)), ...recent];
9190
+ const render = (count) => workflowProgressLines(run, spinner2, styles, now, false, width, showHint, new Set(candidates.slice(0, count)));
9191
+ let low = 0;
9192
+ let high = candidates.length;
9193
+ while (low < high) {
9194
+ const middle = Math.ceil((low + high) / 2);
9195
+ if (render(middle).length <= maxLines) low = middle;
9196
+ else high = middle - 1;
9197
+ }
9198
+ return render(low).join("\n");
9199
+ }
9200
+ function workflowProgressLines(run, spinner2, styles, now, expanded, width, showHint, shown) {
9044
9201
  const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
9045
9202
  const workflowIcon = runStateGlyph(run.state, spinner2);
9046
9203
  const iconStyle = workflowIconStyle(run.state, styles);
@@ -9055,14 +9212,15 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
9055
9212
  const shellActivity = scopedShells ? void 0 : formatShellActivity(run.activeShells, run.activeShellStartedAt, spinner2, styles, now);
9056
9213
  if (shellActivity) lines.push(` ${shellActivity}`);
9057
9214
  const byId = new Map(run.agents.map((agent) => [agent.id, agent]));
9058
- const renderAgents = (agents, offset, nested2) => renderGroupedAgents(agents, ({ agent, index, depth }, grouped) => {
9215
+ const position = new Map(run.agents.map((agent, index) => [agent, index]));
9216
+ const renderAgents = (agents, nested2) => renderGroupedAgents(agents, ({ agent, depth }, grouped) => {
9059
9217
  const icon = agentStateGlyph(agent.state, spinner2);
9060
9218
  const indent = " ".repeat((grouped ? 2 : 1) + depth);
9061
9219
  const activity = SETTLED_AGENT_STATES.has(agent.state) ? "" : formatAgentActivity(agent, spinner2, styles, now);
9062
9220
  const name = grouped ? agent.label ?? agent.name : styledAgentBreadcrumb(agent, byId, styles);
9063
9221
  const state2 = progressStyleForState(agent.state, styles);
9064
9222
  const detail = expanded ? formatWorkflowAgentDetail(agent, now) : "";
9065
- return `${indent}#${String(offset + index + 1)} ${state2(icon)} ${name} ${state2(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
9223
+ return `${indent}#${String((position.get(agent) ?? 0) + 1)} ${state2(icon)} ${name} ${state2(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
9066
9224
  }, run.agents, (label) => styles.muted(label)).map((line) => nested2 ? ` ${line}` : line);
9067
9225
  const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
9068
9226
  if (scopedShells) {
@@ -9075,10 +9233,22 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
9075
9233
  }
9076
9234
  let renderedAgents = 0;
9077
9235
  let nested = false;
9236
+ const segment = (agents, closed) => {
9237
+ if (!shown) return renderAgents(agents, nested);
9238
+ const listed = agents.filter((agent) => closed ? agent.state === "failed" : shown.has(agent));
9239
+ const hidden = closed ? [] : agents.filter((agent) => !shown.has(agent));
9240
+ const counts = [["queued", hidden.filter((agent) => !SETTLED_AGENT_STATES.has(agent.state) && !ACTIVE_AGENT_STATES.has(agent.state)).length], ["running", hidden.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)).length], ["done", hidden.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length]];
9241
+ const summary = counts.filter(([, count]) => count > 0).map(([label, count]) => `+${String(count)} ${label}`).join(" \xB7 ");
9242
+ return [...renderAgents(listed, nested), ...summary ? [`${nested ? " " : " "}${styles.muted(`\u2026 ${summary}${showHint ? expandHint() : ""}`)}`] : []];
9243
+ };
9078
9244
  for (const [phaseIndex, phase] of phases.entries()) {
9079
9245
  const boundary = Math.max(renderedAgents, Math.min(run.agents.length, phase.afterAgent));
9080
- lines.push(...renderAgents(run.agents.slice(renderedAgents, boundary), renderedAgents, nested));
9081
- lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}`);
9246
+ const before = run.agents.slice(renderedAgents, boundary);
9247
+ if (shown && !nested && before.length) lines.push(` ${phaseSummary(before, styles)}`);
9248
+ lines.push(...segment(before, true));
9249
+ const next = phases[phaseIndex + 1];
9250
+ const phaseAgents = next ? run.agents.slice(boundary, Math.max(boundary, Math.min(run.agents.length, next.afterAgent))) : [];
9251
+ lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}${shown && next ? ` ${phaseSummary(phaseAgents, styles)}` : ""}`);
9082
9252
  const phaseShell = scopedShells ? shellActivityFor(run, phaseIndex) : void 0;
9083
9253
  if (phaseShell) {
9084
9254
  const rendered = formatShellActivity(phaseShell.active, phaseShell.startedAt, spinner2, styles, now);
@@ -9087,9 +9257,9 @@ function formatWorkflowProgress(run, spinner2 = "\u25C7", styles = PLAIN_WORKFLO
9087
9257
  renderedAgents = boundary;
9088
9258
  nested = true;
9089
9259
  }
9090
- lines.push(...renderAgents(run.agents.slice(renderedAgents), renderedAgents, nested));
9091
- lines.push(...workflowLogLines(run, styles, expanded, width, showHint));
9092
- return lines.join("\n");
9260
+ lines.push(...segment(run.agents.slice(renderedAgents), false));
9261
+ lines.push(...workflowLogLines(run, styles, expanded, width, showHint, shown ? COMPACT_LOG_LINES : 5));
9262
+ return lines;
9093
9263
  }
9094
9264
  var workflowSpinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9095
9265
  var WORKFLOW_PROGRESS_REFRESH_MS = 1e3;
@@ -9331,7 +9501,9 @@ function workflowProgressBlock(run, theme, progress, refresh, invalidate, prefix
9331
9501
  }
9332
9502
  previousState = displayed.state;
9333
9503
  const frame = displayed.state === "running" ? workflowSpinner[Math.floor(now / 80) % workflowSpinner.length] ?? "\u25C7" : "\u25C7";
9334
- const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true);
9504
+ const rows = freezeAt === void 0 && !terminal ? process.stdout.rows : void 0;
9505
+ const maxLines = rows ? Math.max(6, rows - 10) - (prefix ? prefix.split("\n").length + 1 : 0) : void 0;
9506
+ const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true, maxLines);
9335
9507
  return truncateWorkflowProgress(prefix ? `${prefix}
9336
9508
 
9337
9509
  ${progressText}` : progressText, width);
@@ -9627,10 +9799,7 @@ function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {}, styl
9627
9799
  const model = buildWorkflowPhaseModel(run, snapshot);
9628
9800
  const tree = buildWorkflowPhaseTree(model);
9629
9801
  const expanded = selection.expandedNodeIds === void 0 ? workflowPhaseTreeInitialExpanded(tree) : new Set(selection.expandedNodeIds);
9630
- const wrap = (text, limit = safeWidth) => truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, limit), 0).visualLines.map((line) => line.trimEnd());
9631
- const ansiPattern = new RegExp(ANSI_SGR_SOURCE, "g");
9632
- const visibleLength2 = (text) => text.replace(ansiPattern, "").length;
9633
- const padTo = (text, limit) => `${text}${" ".repeat(Math.max(0, limit - visibleLength2(text)))}`;
9802
+ const wrap = (text) => wrapNavigatorLine(text, safeWidth);
9634
9803
  const phaseStyle = (state) => phaseStyleForState(state, styles);
9635
9804
  const phase = selection.phaseId ? model.phases.find((candidate) => candidate.id === selection.phaseId) : void 0;
9636
9805
  const selectedByAgent = selection.agentId ? tree.nodes.find((node) => node.kind === "agent" && node.agentId === selection.agentId && (!selection.phaseId || node.phaseId === selection.phaseId)) : void 0;
@@ -9696,28 +9865,33 @@ function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {}, styl
9696
9865
  const scopedShells = (run.activeShellsByPhase?.length ?? 0) > 0;
9697
9866
  const shellActivity = scopedShells ? void 0 : formatShellActivity(run.activeShells, run.activeShellStartedAt, "\u2826", styles, now);
9698
9867
  if (shellActivity) lines.push(` ${shellActivity}`);
9699
- const renderTree = (limit) => [styles.bold("Tree"), ...visibleNodes.length ? visibleNodes.flatMap((node) => wrap(treeLine(node), limit)) : [styles.muted("(empty)")]];
9700
9868
  const actionRows = () => {
9701
9869
  const actions = selection.actions;
9702
9870
  if (!actions) return [];
9703
9871
  return ["", styles.bold(actions.title), ...actions.options.map((option, index) => `${index === actions.index ? "\u2192 " : " "}${index === actions.index ? styles.accent(option) : option}`)];
9704
9872
  };
9705
- const detailRows = () => [...details(selectedNode), ...actionRows()];
9873
+ const treeRows = [styles.bold("Tree"), ...visibleNodes.length ? visibleNodes.map(treeLine) : [styles.muted("(empty)")]];
9874
+ lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, selection));
9875
+ if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned")) lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
9876
+ return lines.flatMap((line) => wrap(line));
9877
+ }
9878
+ function wrapNavigatorLine(text, width) {
9879
+ return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
9880
+ }
9881
+ function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
9882
+ const safeWidth = Math.max(1, width);
9706
9883
  if (safeWidth >= 80) {
9707
9884
  const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
9708
- const detailWidth = Math.max(1, safeWidth - sidebarWidth - 3);
9709
- const sidebar = renderTree(sidebarWidth).flatMap((line) => wrap(line, sidebarWidth));
9710
- const detail = detailRows().flatMap((line) => wrap(line, detailWidth));
9711
- const rows = Math.max(sidebar.length, detail.length);
9712
- for (let index = 0; index < rows; index += 1) lines.push(`${padTo(sidebar[index] ?? "", sidebarWidth)} | ${detail[index] ?? ""}`);
9713
- } else if (selection.detailsOnly) {
9714
- lines.push(...detailRows().flatMap((line) => wrap(line)));
9715
- } else {
9716
- lines.push(...renderTree(safeWidth));
9717
- if (!selection.treeOnly) lines.push("", ...detailRows().flatMap((line) => wrap(line)));
9885
+ const left2 = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
9886
+ const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
9887
+ return Array.from({ length: Math.max(left2.length, right.length) }, (_, index) => {
9888
+ const cell = left2[index] ?? "";
9889
+ return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth2(cell)))} | ${right[index] ?? ""}`;
9890
+ });
9718
9891
  }
9719
- if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned")) lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
9720
- return lines.flatMap((line) => wrap(line));
9892
+ if (layout.detailsOnly) return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
9893
+ const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
9894
+ return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
9721
9895
  }
9722
9896
  function formatCheckpointReview(checkpoint) {
9723
9897
  const context = JSON.stringify(checkpoint.context, null, 2);
@@ -9727,7 +9901,7 @@ function formatCheckpointReview(checkpoint) {
9727
9901
  // src/retention.ts
9728
9902
  import { lstat, readdir as readdir4, readFile as readFile4, stat as stat3 } from "node:fs/promises";
9729
9903
  import { homedir as homedir6 } from "node:os";
9730
- import { join as join11, resolve as resolve5 } from "node:path";
9904
+ import { join as join12, resolve as resolve7 } from "node:path";
9731
9905
  var DAY_MS = 24 * 60 * 60 * 1e3;
9732
9906
  var REQUIRED_FILES = /* @__PURE__ */ new Set(["workflow.js", "state.json", "snapshot.json", "journal.json", "ownership.json", "worktrees.json", "borrowed-worktrees.json", "system-prompts.json"]);
9733
9907
  var OPTIONAL_FILES = /* @__PURE__ */ new Set(["result.json", "summary.json"]);
@@ -9736,21 +9910,21 @@ async function validateInventory(store, loaded) {
9736
9910
  const entries = await readdir4(store.directory, { withFileTypes: true });
9737
9911
  for (const entry of entries) {
9738
9912
  if (REQUIRED_DIRECTORIES.has(entry.name)) {
9739
- if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular directory: ${join11(store.directory, entry.name)}`);
9913
+ if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular directory: ${join12(store.directory, entry.name)}`);
9740
9914
  continue;
9741
9915
  }
9742
- if (!REQUIRED_FILES.has(entry.name) && !OPTIONAL_FILES.has(entry.name)) throw new Error(`Run inventory contains an unrecognized artifact: ${join11(store.directory, entry.name)}`);
9743
- if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join11(store.directory, entry.name)}`);
9916
+ if (!REQUIRED_FILES.has(entry.name) && !OPTIONAL_FILES.has(entry.name)) throw new Error(`Run inventory contains an unrecognized artifact: ${join12(store.directory, entry.name)}`);
9917
+ if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, entry.name)}`);
9744
9918
  }
9745
9919
  for (const name of REQUIRED_FILES) {
9746
- const entry = await lstat(join11(store.directory, name));
9747
- if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join11(store.directory, name)}`);
9920
+ const entry = await lstat(join12(store.directory, name));
9921
+ if (!entry.isFile() || entry.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, name)}`);
9748
9922
  }
9749
9923
  if (loaded.run.state === "completed") {
9750
- const result = await lstat(join11(store.directory, "result.json"));
9751
- if (!result.isFile() || result.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join11(store.directory, "result.json")}`);
9924
+ const result = await lstat(join12(store.directory, "result.json"));
9925
+ if (!result.isFile() || result.isSymbolicLink()) throw new Error(`Run artifact is not a regular file: ${join12(store.directory, "result.json")}`);
9752
9926
  }
9753
- if (await readFile4(join11(store.directory, "workflow.js"), "utf8") !== loaded.snapshot.script) throw new Error("Persisted workflow source does not match its launch snapshot");
9927
+ if (await readFile4(join12(store.directory, "workflow.js"), "utf8") !== loaded.snapshot.script) throw new Error("Persisted workflow source does not match its launch snapshot");
9754
9928
  }
9755
9929
  function runDependencyIds(run, borrowed) {
9756
9930
  const dependencies = /* @__PURE__ */ new Set();
@@ -9772,7 +9946,7 @@ async function scanRun(cwd, sessionId, runId, home) {
9772
9946
  await store.validateDeletionWorktrees();
9773
9947
  await store.validateBorrowedWorktrees();
9774
9948
  if (loaded.run.retry) await store.validateRetrySource();
9775
- const stateMtimeMs = (await stat3(join11(store.directory, "state.json"))).mtimeMs;
9949
+ const stateMtimeMs = (await stat3(join12(store.directory, "state.json"))).mtimeMs;
9776
9950
  const dependencies = runDependencyIds(loaded.run, await store.borrowedWorktrees());
9777
9951
  if (dependencies.includes(runId)) throw new Error(`Run ${runId} depends on itself`);
9778
9952
  return { sessionId, runId, store, run: loaded.run, stateMtimeMs, dependencies };
@@ -9827,9 +10001,9 @@ function cleanupOrder(runs, candidates) {
9827
10001
  return ordered;
9828
10002
  }
9829
10003
  async function recheck(run) {
9830
- const before = await stat3(join11(run.store.directory, "state.json"));
10004
+ const before = await stat3(join12(run.store.directory, "state.json"));
9831
10005
  const loaded = await run.store.load();
9832
- const after = await stat3(join11(run.store.directory, "state.json"));
10006
+ const after = await stat3(join12(run.store.directory, "state.json"));
9833
10007
  if (loaded.run.id !== run.runId || !HARD_TERMINAL_RUN_STATES.has(loaded.run.state) || before.mtimeMs !== after.mtimeMs || after.mtimeMs !== run.stateMtimeMs) throw new Error(`Retention candidate ${run.runId} changed before deletion`);
9834
10008
  }
9835
10009
  async function releaseLeases(leases) {
@@ -9837,8 +10011,8 @@ async function releaseLeases(leases) {
9837
10011
  for (const release of releases) if (release.status === "rejected") throw release.reason;
9838
10012
  }
9839
10013
  async function retainTerminalRuns(options) {
9840
- const home = resolve5(options.home ?? homedir6());
9841
- const cwd = resolve5(options.cwd);
10014
+ const home = resolve7(options.home ?? homedir6());
10015
+ const cwd = resolve7(options.cwd);
9842
10016
  const now = options.now ?? Date.now();
9843
10017
  if (!Number.isFinite(now)) throw new Error("Retention start time is invalid");
9844
10018
  if (options.retention.olderThanDays === void 0 && options.retention.maxTerminalRuns === void 0) return { candidates: [], deleted: [] };
@@ -9999,12 +10173,54 @@ var WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.edi
9999
10173
  function workflowKeyMatches(keybindings, data, binding) {
10000
10174
  return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data;
10001
10175
  }
10002
- function workflowKeyLabel(keybindings, binding, fallback, labels) {
10176
+ var WORKFLOW_KEY_LABELS = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
10177
+ function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
10003
10178
  const keys = keybindingKeys(keybindings, binding);
10004
10179
  const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
10005
10180
  const vim = WORKFLOW_VIM_KEYS[binding];
10006
10181
  return [...new Set(vim ? [...configured, vim] : configured)].join("/");
10007
10182
  }
10183
+ function createInlineConfirm(requestRender) {
10184
+ let pending;
10185
+ const answer = (value) => {
10186
+ const current = pending;
10187
+ pending = void 0;
10188
+ current?.resolve(value);
10189
+ requestRender();
10190
+ };
10191
+ return {
10192
+ ask(title, message) {
10193
+ answer(false);
10194
+ return new Promise((resolve9) => {
10195
+ pending = { title, message, yes: true, resolve: resolve9 };
10196
+ requestRender();
10197
+ });
10198
+ },
10199
+ active() {
10200
+ return pending !== void 0;
10201
+ },
10202
+ rows(width, styles) {
10203
+ if (!pending) return [];
10204
+ const yes = pending.yes;
10205
+ const message = truncateToVisualLines2(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
10206
+ return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => option === "Yes" === yes ? `\u2192 ${styles.accent(option)}` : ` ${option}`)];
10207
+ },
10208
+ hint(keybindings) {
10209
+ 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`;
10210
+ },
10211
+ handleInput(keybindings, data) {
10212
+ if (!pending) return;
10213
+ if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
10214
+ pending.yes = !pending.yes;
10215
+ requestRender();
10216
+ } else if (workflowKeyMatches(keybindings, data, "tui.select.confirm")) answer(pending.yes);
10217
+ else if (workflowKeyMatches(keybindings, data, "tui.select.cancel")) answer(false);
10218
+ },
10219
+ cancel() {
10220
+ answer(false);
10221
+ }
10222
+ };
10223
+ }
10008
10224
  function registerWorkflowNavigator(deps) {
10009
10225
  const { pi, home, clipboard, extensionAgentDir, runs, terminalRunStates, hardTerminalRunStates, ensureSessionLease, answerCheckpoint, recovery, stopWorkflowRun, moveForegroundToBackground, isForegroundAttached, liveAgents, registry, projectTrusted: projectTrusted2, resumeHostContext: resumeHostContext2, resumeSelectedWorkflow, reportBlocked, setNavigatorOpen, trajectoryProvider, coordinateRunMutation: coordinateRunMutationDependency } = deps;
10010
10226
  const coordinateRunMutation = coordinateRunMutationDependency ?? ((task) => task());
@@ -10046,7 +10262,7 @@ function registerWorkflowNavigator(deps) {
10046
10262
  const setStatus = uiHostCapabilities(ctx.ui)?.setStatus;
10047
10263
  setStatus?.call(ctx.ui, "workflow-stop", text);
10048
10264
  };
10049
- const runAction = async (actionCommand, status = setWorkflowStatus) => {
10265
+ const runAction = async (actionCommand, status = setWorkflowStatus, confirm = (title, message) => confirmWithBlocked(ctx.ui, reportBlocked, title, message)) => {
10050
10266
  const [action, runId, ...rest] = actionCommand.split(/\s+/);
10051
10267
  try {
10052
10268
  const run = runId ? runs.get(runId) : void 0;
@@ -10133,7 +10349,7 @@ function registerWorkflowNavigator(deps) {
10133
10349
  }
10134
10350
  if (action === "stop" && run) {
10135
10351
  const workflowName = stored?.loaded.run.workflowName ?? run.metadata.name;
10136
- if (!await confirmWithBlocked(ctx.ui, reportBlocked, "Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`)) return "dashboard";
10352
+ if (!await confirm("Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`)) return "dashboard";
10137
10353
  status(`Stopping workflow ${workflowName}...`);
10138
10354
  await stopWorkflowRun(run.store.runId);
10139
10355
  status(`Workflow ${run.store.runId} stopped.`);
@@ -10494,8 +10710,18 @@ Mutations are available through workflow tools.`, "info");
10494
10710
  let selectedNodeId = tree.nodes[0]?.id;
10495
10711
  let expandedNodeIds = new Set(workflowPhaseTreeInitialExpanded(tree));
10496
10712
  const terminalRows = () => Math.max(1, tuiRows(tui) - WORKFLOW_PANEL_FOOTER_ROWS);
10497
- const keyLabels = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
10498
- const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
10713
+ const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
10714
+ const inlineConfirm = createInlineConfirm(() => {
10715
+ if (!disposed) tui.requestRender();
10716
+ });
10717
+ const confirmInDashboard = async (title, message) => {
10718
+ reportBlocked?.(true, title);
10719
+ try {
10720
+ return await inlineConfirm.ask(title, message);
10721
+ } finally {
10722
+ reportBlocked?.(false);
10723
+ }
10724
+ };
10499
10725
  const progressNow = () => {
10500
10726
  const now = Date.now();
10501
10727
  if (hardTerminalRunStates.has(view.run.state)) {
@@ -10572,7 +10798,7 @@ Mutations are available through workflow tools.`, "info");
10572
10798
  stopStatus = status;
10573
10799
  setWorkflowStatus(status);
10574
10800
  if (!disposed) tui.requestRender();
10575
- }).then(async (outcome2) => {
10801
+ }, confirmInDashboard).then(async (outcome2) => {
10576
10802
  if (outcome2 === "stopped") {
10577
10803
  done("__stopped__");
10578
10804
  return;
@@ -10609,7 +10835,8 @@ Mutations are available through workflow tools.`, "info");
10609
10835
  const content = [...statusLines, ...phaseLines];
10610
10836
  const rows = terminalRows();
10611
10837
  const hintRows = rows >= 3 ? 1 : 0;
10612
- const viewport = Math.max(1, rows - hintRows);
10838
+ const confirmRows = inlineConfirm.rows(width, styles);
10839
+ const viewport = Math.max(1, rows - hintRows - confirmRows.length);
10613
10840
  const maxOffset2 = Math.max(0, content.length - viewport);
10614
10841
  dashboardOffset = Math.max(0, Math.min(maxOffset2, dashboardOffset));
10615
10842
  if (actionMode) {
@@ -10630,12 +10857,16 @@ Mutations are available through workflow tools.`, "info");
10630
10857
  dashboardOffset = Math.max(0, Math.min(maxOffset2, dashboardOffset));
10631
10858
  const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : void 0;
10632
10859
  const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
10633
- const hint = truncateToVisualLines2(theme.fg("dim", actionMode ? `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} actions \xB7 ${keyLabel("tui.select.confirm", "enter")} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} tree \xB7 ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} tree \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")}/${keyLabel("tui.editor.cursorRight", "\u2192")} collapse/expand \xB7 ${keyLabel("tui.select.confirm", "enter")} ${enterAction} \xB7 a actions \xB7 ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} \xB7 auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
10634
- return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...hintRows ? [hint] : []];
10860
+ const hint = truncateToVisualLines2(theme.fg("dim", inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} actions \xB7 ${keyLabel("tui.select.confirm", "enter")} run \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")} tree \xB7 ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")} tree \xB7 ${keyLabel("tui.editor.cursorLeft", "\u2190")}/${keyLabel("tui.editor.cursorRight", "\u2192")} collapse/expand \xB7 ${keyLabel("tui.select.confirm", "enter")} ${enterAction} \xB7 a actions \xB7 ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` \xB7 ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} \xB7 auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
10861
+ return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...confirmRows, ...hintRows ? [hint] : []];
10635
10862
  },
10636
10863
  invalidate() {
10637
10864
  },
10638
10865
  handleInput(data) {
10866
+ if (inlineConfirm.active()) {
10867
+ inlineConfirm.handleInput(keybindings, data);
10868
+ return;
10869
+ }
10639
10870
  if (stopRequested || editorRunning) return;
10640
10871
  const narrow = renderedWidth < 80;
10641
10872
  if (!actionMode && (data === "a" || data === "A")) {
@@ -10687,7 +10918,7 @@ Mutations are available through workflow tools.`, "info");
10687
10918
  });
10688
10919
  }
10689
10920
  }
10690
- } else if (action === "Open script in editor") void openArtifact(readFile5(join12(store.directory, "workflow.js"), "utf8").then(workflowScriptArtifact), "workflow script");
10921
+ } else if (action === "Open script in editor") void openArtifact(readFile5(join13(store.directory, "workflow.js"), "utf8").then(workflowScriptArtifact), "workflow script");
10691
10922
  else if (action === "Stop") requestStop();
10692
10923
  else done(action);
10693
10924
  }
@@ -10755,6 +10986,7 @@ Mutations are available through workflow tools.`, "info");
10755
10986
  disposed = true;
10756
10987
  stopTimer();
10757
10988
  setWorkflowStatus(void 0);
10989
+ inlineConfirm.cancel();
10758
10990
  }
10759
10991
  };
10760
10992
  }) : await ctx.ui.select(view.dashboard, [...view.actions.keys(), "Back"]);
@@ -10809,8 +11041,7 @@ Mutations are available through workflow tools.`, "info");
10809
11041
  const currentLayout = layout();
10810
11042
  const maxOffset2 = Math.max(0, renderedLines.length - currentLayout.contentViewport);
10811
11043
  offset = Math.min(offset, maxOffset2);
10812
- const keyLabels = { up: "\u2191", down: "\u2193", left: "\u2190", right: "\u2192", pageUp: "pgup", pageDown: "pgdn" };
10813
- const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
11044
+ const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
10814
11045
  const hint = truncateToVisualLines2(theme.fg("dim", `${keyLabel("tui.select.up", "\u2191")}/${keyLabel("tui.select.down", "\u2193")}/pgup/pgdn scroll \xB7 enter select \xB7 esc cancel`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
10815
11046
  const controls = currentLayout.compactControls ? [options.map((option, index) => `${index === selectedIndex ? "[" : " "}${option}${index === selectedIndex ? "]" : " "}`).join(" ")] : options.map((option, index) => `${index === selectedIndex ? "\u2192 " : " "}${option}`);
10816
11047
  return [
@@ -10872,7 +11103,7 @@ Mutations are available through workflow tools.`, "info");
10872
11103
 
10873
11104
  // src/changelog.ts
10874
11105
  import { mkdir as mkdir3, readFile as readFile6 } from "node:fs/promises";
10875
- import { dirname as dirname5, join as join13 } from "node:path";
11106
+ import { dirname as dirname6, join as join14 } from "node:path";
10876
11107
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10877
11108
  var STATE_DIRECTORY = "pi-extensible-workflows";
10878
11109
  var STATE_FILE = "changelog-state.json";
@@ -10883,15 +11114,15 @@ function packageMetadata(value, directory) {
10883
11114
  }
10884
11115
  async function packageMetadataAt(directory) {
10885
11116
  try {
10886
- const value = JSON.parse(await readFile6(join13(directory, "package.json"), "utf8"));
11117
+ const value = JSON.parse(await readFile6(join14(directory, "package.json"), "utf8"));
10887
11118
  return packageMetadata(value, directory);
10888
11119
  } catch {
10889
11120
  return void 0;
10890
11121
  }
10891
11122
  }
10892
11123
  async function installedPackageMetadata() {
10893
- const moduleDirectory = dirname5(fileURLToPath4(import.meta.url));
10894
- for (const directory of [join13(moduleDirectory, ".."), join13(moduleDirectory, "../..")]) {
11124
+ const moduleDirectory = dirname6(fileURLToPath4(import.meta.url));
11125
+ for (const directory of [join14(moduleDirectory, ".."), join14(moduleDirectory, "../..")]) {
10895
11126
  const metadata = await packageMetadataAt(directory);
10896
11127
  if (metadata) return metadata;
10897
11128
  }
@@ -10918,7 +11149,7 @@ ${line}` : line;
10918
11149
  }
10919
11150
  async function readChangelog(directory) {
10920
11151
  try {
10921
- return parseChangelog(await readFile6(join13(directory, "CHANGELOG.md"), "utf8"));
11152
+ return parseChangelog(await readFile6(join14(directory, "CHANGELOG.md"), "utf8"));
10922
11153
  } catch {
10923
11154
  return [];
10924
11155
  }
@@ -10955,14 +11186,14 @@ async function showChangelogNotice(context, agentDir, packageDirectory) {
10955
11186
  try {
10956
11187
  const metadata = packageDirectory === void 0 ? await installedPackageMetadata() : await packageMetadataAt(packageDirectory);
10957
11188
  if (!metadata) return;
10958
- const statePath = join13(agentDir, STATE_DIRECTORY, STATE_FILE);
11189
+ const statePath = join14(agentDir, STATE_DIRECTORY, STATE_FILE);
10959
11190
  const previousVersion = await lastNotifiedVersion(statePath);
10960
11191
  if (previousVersion === metadata.version) return;
10961
11192
  const entries = releaseEntries(await readChangelog(metadata.directory), metadata.version, previousVersion);
10962
11193
  if (!entries.length) return;
10963
11194
  ui.notify(noticeText(metadata.version, entries), "info");
10964
11195
  try {
10965
- await mkdir3(dirname5(statePath), { recursive: true, mode: 448 });
11196
+ await mkdir3(dirname6(statePath), { recursive: true, mode: 448 });
10966
11197
  await atomicJson(statePath, { lastNotifiedVersion: metadata.version });
10967
11198
  } catch {
10968
11199
  }
@@ -10974,8 +11205,8 @@ async function showChangelogNotice(context, agentDir, packageDirectory) {
10974
11205
  import { createHash as createHash3 } from "node:crypto";
10975
11206
  import { open as open2, readFile as readFile7, readdir as readdir5, stat as stat4 } from "node:fs/promises";
10976
11207
  import { homedir as homedir7 } from "node:os";
10977
- import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
10978
- import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as getAgentDir3, SettingsManager as SettingsManager3, DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2 } from "@earendil-works/pi-coding-agent";
11208
+ import { basename as basename5, dirname as dirname7, join as join15, resolve as resolve8 } from "node:path";
11209
+ import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DefaultPackageManager as DefaultPackageManager2, getAgentDir as getAgentDir4, SettingsManager as SettingsManager3, DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2 } from "@earendil-works/pi-coding-agent";
10979
11210
 
10980
11211
  // subagents/src/contracts.ts
10981
11212
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -11193,6 +11424,18 @@ function statusValue(value) {
11193
11424
  // src/trajectory.ts
11194
11425
  var TRAJECTORY_MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
11195
11426
  var TRAJECTORY_MAX_NON_TIMING_ENTRIES = 400;
11427
+ function subagentOutput(options) {
11428
+ if (options.resultTruncated) return { status: "truncated", kind: "result", path: options.resultPath, bytes: options.resultBytes ?? 0 };
11429
+ if (options.result !== void 0) return { status: "available", value: options.result, bytes: serializedJsonBytes(options.result) };
11430
+ if (options.failureTruncated) return { status: "truncated", kind: "failure", path: options.failurePath, bytes: options.failureBytes ?? 0 };
11431
+ if (options.failure !== void 0) return { status: "failed", code: options.failure.code, message: options.failure.message };
11432
+ if (options.state === "failed" || options.state === "stopped") {
11433
+ const error = options.statusError ?? options.attempt?.error;
11434
+ return { status: options.state === "stopped" ? "cancelled" : "failed", code: error?.code ?? (options.state === "stopped" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Subagent ${options.state}` };
11435
+ }
11436
+ if (options.state === "completed") return { status: "unavailable" };
11437
+ return { status: "pending" };
11438
+ }
11196
11439
  function sessionFile(reference) {
11197
11440
  const locator = reference?.locator;
11198
11441
  if (!object(locator) || typeof locator.sessionFile !== "string" || !locator.sessionFile) return void 0;
@@ -11330,20 +11573,20 @@ function canonicalExtensionSelector2(selector, base) {
11330
11573
  const negated = selector.startsWith("!");
11331
11574
  const body = negated ? selector.slice(1) : selector;
11332
11575
  if (body === "*" || body === "**" || body.startsWith("**/")) return selector;
11333
- const resolved = resolve6(base, body);
11576
+ const resolved = resolve8(base, body);
11334
11577
  if (resourcePatternHasMagic(body)) return `${negated ? "!" : ""}${resolved}`;
11335
11578
  return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
11336
11579
  }
11337
11580
  function skillNameFromPath(path) {
11338
11581
  const file = basename5(path);
11339
- return file.toLowerCase() === "skill.md" ? basename5(dirname6(path)) : file;
11582
+ return file.toLowerCase() === "skill.md" ? basename5(dirname7(path)) : file;
11340
11583
  }
11341
11584
  var discoveredResourcesByCwd = /* @__PURE__ */ new Map();
11342
11585
  async function discoveredResources(cwd) {
11343
11586
  const cached = discoveredResourcesByCwd.get(cwd);
11344
11587
  if (cached) return cached;
11345
11588
  const pending = (async () => {
11346
- const agentDir = getAgentDir3();
11589
+ const agentDir = getAgentDir4();
11347
11590
  const settingsManager = SettingsManager3.create(cwd, agentDir, { projectTrusted: false });
11348
11591
  const packageManager = new DefaultPackageManager2({ cwd, agentDir, settingsManager });
11349
11592
  const resolved = await packageManager.resolve();
@@ -11395,11 +11638,46 @@ function withoutAgentActivities(run) {
11395
11638
  });
11396
11639
  return agents.every((agent, index) => agent === run.agents[index]) ? run : { ...run, agents };
11397
11640
  }
11641
+ function serializedJsonBytes(value) {
11642
+ const serialized = JSON.stringify(value);
11643
+ return Buffer.byteLength(serialized, "utf8");
11644
+ }
11645
+ function failedAgentOutput(agent) {
11646
+ const error = [...agent.attemptDetails ?? []].reverse().find((attempt) => attempt.error)?.error;
11647
+ const status = agent.state === "cancelled" ? "cancelled" : "failed";
11648
+ return { status, code: error?.code ?? (status === "cancelled" ? "CANCELLED" : "AGENT_FAILED"), message: error?.message ?? `Agent ${agent.state}` };
11649
+ }
11650
+ function outputForAgent(agent, operation) {
11651
+ if (operation !== void 0) {
11652
+ const bytes = serializedJsonBytes(operation.value);
11653
+ return bytes > DEFAULT_MAX_BYTES2 ? { status: "truncated", kind: "result", bytes } : { status: "available", value: operation.value, bytes };
11654
+ }
11655
+ if (agent.state === "failed" || agent.state === "cancelled") return failedAgentOutput(agent);
11656
+ if (agent.state === "completed") return { status: "unavailable" };
11657
+ return { status: "pending" };
11658
+ }
11659
+ function applyTrajectoryAgentOutputs(run, operations) {
11660
+ const byPath = new Map(operations.map((operation) => [operation.path, operation]));
11661
+ const agents = run.agents.map((agent) => ({ ...agent, output: outputForAgent(agent, agent.resultPath === void 0 ? void 0 : byPath.get(agent.resultPath)) }));
11662
+ return { ...run, agents };
11663
+ }
11664
+ function overlayTrajectoryRun(run, overlay) {
11665
+ const overlaid = overlay(run);
11666
+ const outputs = new Map(run.agents.map((agent) => [agent.id, agent.output]));
11667
+ const agents = overlaid.agents.map((agent) => {
11668
+ const previous = outputs.get(agent.id);
11669
+ const output = previous?.status === "available" || previous?.status === "truncated" ? previous : outputForAgent(agent, void 0);
11670
+ return { ...agent, output };
11671
+ });
11672
+ return { ...overlaid, agents };
11673
+ }
11398
11674
  async function loadTrajectoryRun(store, includeTranscripts = true) {
11399
11675
  const value = await store.load();
11400
11676
  const summary = await store.loadSummary().catch(() => void 0);
11401
11677
  const prompts = await store.systemPrompts().catch(() => []);
11402
- const run = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
11678
+ const prepared = withoutAgentActivities(await withResolvedResources(withPiToolDescriptions(applySystemPrompts(value.run, prompts)), store.cwd));
11679
+ const operations = await store.replayableOperations().catch(() => []);
11680
+ const run = applyTrajectoryAgentOutputs(prepared, operations);
11403
11681
  return { run, snapshot: value.snapshot, awaiting: await store.awaitingCheckpoints(), ...summary?.createdAt === void 0 ? {} : { createdAt: summary.createdAt }, transcripts: includeTranscripts ? await runTranscripts(run) : {} };
11404
11682
  }
11405
11683
  async function fileMtime(path) {
@@ -11408,8 +11686,8 @@ async function fileMtime(path) {
11408
11686
  function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature, overlay) {
11409
11687
  const cache = /* @__PURE__ */ new Map();
11410
11688
  const changed = async (store, entry) => {
11411
- if (await fileMtime(join14(store.directory, "state.json")) !== entry.stateMtimeMs) return true;
11412
- if (await fileMtime(join14(store.directory, "journal.json")) !== entry.journalMtimeMs) return true;
11689
+ if (await fileMtime(join15(store.directory, "state.json")) !== entry.stateMtimeMs) return true;
11690
+ if (await fileMtime(join15(store.directory, "journal.json")) !== entry.journalMtimeMs) return true;
11413
11691
  for (const [agentId, path] of transcriptPaths(entry.value.run)) if (await transcriptSignature(path) !== entry.transcripts.get(agentId)) return true;
11414
11692
  return false;
11415
11693
  };
@@ -11426,10 +11704,10 @@ function createCachedRunLoader(cwd, sessionId, home, load, transcriptSignature,
11426
11704
  const value = await load(store);
11427
11705
  const transcripts = /* @__PURE__ */ new Map();
11428
11706
  for (const [agentId, path] of transcriptPaths(value.run)) transcripts.set(agentId, await transcriptSignature(path));
11429
- entry = { value, stateMtimeMs: await fileMtime(join14(store.directory, "state.json")) ?? 0, journalMtimeMs: await fileMtime(join14(store.directory, "journal.json")) ?? 0, transcripts };
11707
+ entry = { value, stateMtimeMs: await fileMtime(join15(store.directory, "state.json")) ?? 0, journalMtimeMs: await fileMtime(join15(store.directory, "journal.json")) ?? 0, transcripts };
11430
11708
  cache.set(runId, entry);
11431
11709
  }
11432
- loaded.push(overlay ? { ...entry.value, run: overlay(entry.value.run) } : entry.value);
11710
+ loaded.push(overlay ? { ...entry.value, run: overlayTrajectoryRun(entry.value.run, overlay) } : entry.value);
11433
11711
  } catch {
11434
11712
  cache.delete(runId);
11435
11713
  }
@@ -11512,7 +11790,7 @@ function createTrajectoryTranscriptLoader(cwd, sessionId, home = homedir7(), age
11512
11790
  path = sessionFile(value.run.agents.find((agent) => agent.id === request.agentId)?.attemptDetails?.at(-1)?.session);
11513
11791
  } else if (request.subagentId !== void 0 && request.runId === void 0 && request.agentId === void 0) {
11514
11792
  if (!safeTrajectorySubagentId(request.subagentId)) return { status: "missing", revision: 0, entries: [], error: "Transcript not found" };
11515
- const rawStatus = await readSubagentJson(join14(agentDir, "subagents", request.subagentId, "status.json"));
11793
+ const rawStatus = await readSubagentJson(join15(agentDir, "subagents", request.subagentId, "status.json"));
11516
11794
  const status = statusValue(rawStatus);
11517
11795
  const statusRecord = object(rawStatus) ? rawStatus : void 0;
11518
11796
  const statusCwd = statusRecord === void 0 ? void 0 : statusRecord.cwd;
@@ -11573,13 +11851,13 @@ function withoutProgressActivity(progress) {
11573
11851
  async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscript = true) {
11574
11852
  try {
11575
11853
  const id = basename5(directory);
11576
- const status = statusValue(await readSubagentJson(join14(directory, "status.json")));
11854
+ const status = statusValue(await readSubagentJson(join15(directory, "status.json")));
11577
11855
  if (!status || status.id !== id) return void 0;
11578
11856
  if (status.sessionId !== sessionId) return "foreign";
11579
11857
  const progress = status.progress === void 0 ? void 0 : withoutProgressActivity(status.progress);
11580
- const request = normalizeSubagentRunRequest(await readSubagentJson(join14(directory, "request.json")));
11581
- const resultPath = join14(directory, "result.json");
11582
- const failurePath = join14(directory, "failure.json");
11858
+ const request = normalizeSubagentRunRequest(await readSubagentJson(join15(directory, "request.json")));
11859
+ const resultPath = join15(directory, "result.json");
11860
+ const failurePath = join15(directory, "failure.json");
11583
11861
  const resultBytes = await subagentFileBytes(resultPath);
11584
11862
  const failureBytes = await subagentFileBytes(failurePath);
11585
11863
  const resultValue = await readSubagentJson(resultPath, true, DEFAULT_MAX_BYTES2);
@@ -11619,19 +11897,20 @@ async function loadTrajectorySubagent(directory, cwd, sessionId, includeTranscri
11619
11897
  ...progress === void 0 ? {} : { progress },
11620
11898
  ...attempt === void 0 ? {} : { attempt },
11621
11899
  ...result === void 0 ? {} : { result },
11622
- ...failure === void 0 ? {} : { failure }
11900
+ ...failure === void 0 ? {} : { failure },
11901
+ output: subagentOutput({ state: status.state, statusError: status.error, attempt, result: resultValue === void 0 ? void 0 : boundedSubagentJson(resultValue), resultTruncated, resultPath, resultBytes, failure: failureValue === void 0 ? void 0 : subagentErrorValue(failureValue), failureTruncated, failurePath, failureBytes })
11623
11902
  };
11624
11903
  } catch {
11625
11904
  return void 0;
11626
11905
  }
11627
11906
  }
11628
11907
  async function subagentCacheEntryChanged(directory, entry) {
11629
- for (const file of TRAJECTORY_SUBAGENT_FILES) if (await fileMtime(join14(directory, file)) !== entry.fileMtimes.get(file)) return true;
11908
+ for (const file of TRAJECTORY_SUBAGENT_FILES) if (await fileMtime(join15(directory, file)) !== entry.fileMtimes.get(file)) return true;
11630
11909
  const transcriptPath = sessionFile(entry.value.attempt?.session);
11631
11910
  return transcriptPath !== void 0 && await fileMtime(transcriptPath) !== entry.transcriptMtime;
11632
11911
  }
11633
11912
  function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, includeTranscript = true) {
11634
- const root = join14(agentDir, "subagents");
11913
+ const root = join15(agentDir, "subagents");
11635
11914
  const cache = /* @__PURE__ */ new Map();
11636
11915
  const negativeCache = /* @__PURE__ */ new Map();
11637
11916
  return async () => {
@@ -11648,8 +11927,8 @@ function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, inclu
11648
11927
  for (const id of negativeCache.keys()) if (!current.has(id)) negativeCache.delete(id);
11649
11928
  const loaded = [];
11650
11929
  for (const id of ids) {
11651
- const directory = join14(root, id);
11652
- const statusPath = join14(directory, "status.json");
11930
+ const directory = join15(root, id);
11931
+ const statusPath = join15(directory, "status.json");
11653
11932
  try {
11654
11933
  const statusMtime = await fileMtime(statusPath);
11655
11934
  if (negativeCache.has(id) && negativeCache.get(id) === statusMtime) continue;
@@ -11667,7 +11946,7 @@ function createTrajectorySubagentLoader(cwd, sessionId, agentDir, overlay, inclu
11667
11946
  continue;
11668
11947
  }
11669
11948
  const fileMtimes = /* @__PURE__ */ new Map();
11670
- for (const file of TRAJECTORY_SUBAGENT_FILES) fileMtimes.set(file, await fileMtime(join14(directory, file)));
11949
+ for (const file of TRAJECTORY_SUBAGENT_FILES) fileMtimes.set(file, await fileMtime(join15(directory, file)));
11671
11950
  const transcriptPath = sessionFile(value.attempt?.session);
11672
11951
  entry = { value, fileMtimes, transcriptMtime: transcriptPath === void 0 ? void 0 : await fileMtime(transcriptPath) };
11673
11952
  cache.set(id, entry);
@@ -11784,7 +12063,8 @@ var WORKFLOW_TOOL_PARAMETERS = Type3.Object({
11784
12063
  var WORKFLOW_STATUS_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Workflow run ID visible in the current project" }) }, { additionalProperties: false });
11785
12064
  var WORKFLOW_RETRY_PARAMETERS = Type3.Object({ runId: Type3.String({ description: "Explicit failed workflow run ID" }), expectedState: Type3.Optional(Type3.String({ description: "Persisted source state observed before recovery" })), foreground: Type3.Optional(Type3.Boolean({ description: "Override the source launch mode for this recovery" })) });
11786
12065
  function workflowToolUpdate(run) {
11787
- return { content: [{ type: "text", text: formatWorkflowProgress(run) }], details: { runId: run.id, run } };
12066
+ const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
12067
+ return { content: [{ type: "text", text: `Workflow: ${run.workflowName} (${String(done)}/${String(run.agents.length)} done) [${run.state}]` }], details: { runId: run.id, run } };
11788
12068
  }
11789
12069
  function agentWithProgress(agent, progress) {
11790
12070
  const next = { ...agent, accounting: progress.accounting, toolCalls: progress.toolCalls };
@@ -11805,6 +12085,7 @@ function deliver(pi, content) {
11805
12085
  pi.sendMessage({ customType: "workflow", content, display: true }, { deliverAs: "followUp", triggerTurn: true });
11806
12086
  }
11807
12087
  var WORKFLOW_WARNING_ENTRY = "workflow-warning";
12088
+ var PROGRESS_COALESCE_MS = 100;
11808
12089
  function deliverWarning(pi, content) {
11809
12090
  pi.appendEntry(WORKFLOW_WARNING_ENTRY, { message: content });
11810
12091
  }
@@ -11882,7 +12163,7 @@ async function resolveLaunchAliases(registry, staticAliases, context, availableM
11882
12163
  function workflowExtension(pi, home, clipboard = copyToClipboard, transport = localAgentTransport, agentDir, additionalSkillPaths = []) {
11883
12164
  beginWorkflowExtensionLoading();
11884
12165
  const registry = loadingRegistry();
11885
- const extensionAgentDir = agentDir ?? getAgentDir4();
12166
+ const extensionAgentDir = agentDir ?? getAgentDir5();
11886
12167
  const registerEntryRenderer = piHostCapabilities(pi).registerEntryRenderer;
11887
12168
  registerEntryRenderer?.(WORKFLOW_LOG_ENTRY, (entry) => {
11888
12169
  const data = entry.data;
@@ -11925,8 +12206,8 @@ function workflowExtension(pi, home, clipboard = copyToClipboard, transport = lo
11925
12206
  };
11926
12207
  pi.on("resources_discover", () => {
11927
12208
  if (!pi.getActiveTools().includes("workflow")) return;
11928
- const extensionDir = dirname7(fileURLToPath5(import.meta.url));
11929
- const skillPath = [join15(extensionDir, "../skills"), join15(extensionDir, "../../skills")].find((path) => existsSync2(path));
12209
+ const extensionDir = dirname8(fileURLToPath5(import.meta.url));
12210
+ const skillPath = [join16(extensionDir, "../skills"), join16(extensionDir, "../../skills")].find((path) => existsSync2(path));
11930
12211
  return skillPath ? { skillPaths: [skillPath] } : void 0;
11931
12212
  });
11932
12213
  const runs = /* @__PURE__ */ new Map();
@@ -12064,7 +12345,6 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12064
12345
  const trusted = projectTrusted(context);
12065
12346
  const settings = resolveWorkflowSettings(cwd, trusted, workflowSettingsPath(extensionAgentDir)).effective.extensionSettings?.trajectory;
12066
12347
  const port = settings?.port;
12067
- const themes = settings?.themes ?? false;
12068
12348
  const loadRuns = createTrajectoryRunLoader(cwd, sessionId, home, overlayLiveRun);
12069
12349
  const loadSubagents = createTrajectorySubagentLoader(cwd, sessionId, extensionAgentDir, overlayLiveSubagent);
12070
12350
  const loadRunsMetadata = createTrajectoryRunMetadataLoader(cwd, sessionId, home, overlayLiveRun);
@@ -12073,7 +12353,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12073
12353
  const live = liveSubagents.get(subagentId);
12074
12354
  return live?.status.sessionId === sessionId ? live.status.attemptDetails?.at(-1)?.session : void 0;
12075
12355
  });
12076
- return { cwd, sessionId, ...port === void 0 ? {} : { port }, themes, loadRuns, loadSubagents, loadMetadata: async () => ({ runs: await loadRunsMetadata(), subagents: await loadSubagentsMetadata() }), loadTranscript, handleAction: (request) => trajectoryAction(request, context) };
12356
+ return { cwd, sessionId, ...port === void 0 ? {} : { port }, loadRuns, loadSubagents, loadMetadata: async () => ({ runs: await loadRunsMetadata(), subagents: await loadSubagentsMetadata() }), loadTranscript, handleAction: (request) => trajectoryAction(request, context) };
12077
12357
  };
12078
12358
  const withLiveActivities = (run) => liveAgents.overlay(run);
12079
12359
  const overlayLiveRun = (run) => {
@@ -12246,22 +12526,48 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12246
12526
  const scheduler = new FairAgentScheduler(async ({ id, runId, tuiIndex, parentId, prompt, options, signal, setSteer }) => {
12247
12527
  const run = runs.get(runId);
12248
12528
  if (!run) throw new WorkflowError("INTERNAL_ERROR", `Unknown production run: ${runId}`);
12529
+ const applyProgress = async (progress) => {
12530
+ let runState;
12531
+ if (progress.persist) {
12532
+ runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
12533
+ } else {
12534
+ const loaded = await run.store.loadStatus();
12535
+ if (!loaded.agents.some((agent) => agent.id === id)) return;
12536
+ runState = { ...loaded, ...run.budget.snapshot(), agents: loaded.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
12537
+ }
12538
+ if (!runState.agents.some((agent) => agent.id === id)) return;
12539
+ liveAgents.setActivity(runId, id, progress.activity);
12540
+ liveAgents.setEventTime(runId, id, progress.lastEventAt);
12541
+ run.update?.(workflowToolUpdate(withLiveActivities(runState)));
12542
+ };
12543
+ let deferred;
12544
+ let deferredTimer;
12545
+ let appliedAt = 0;
12546
+ const cancelDeferredProgress = () => {
12547
+ if (deferredTimer) clearTimeout(deferredTimer);
12548
+ deferredTimer = void 0;
12549
+ deferred = void 0;
12550
+ };
12551
+ const onProgress = async (progress) => {
12552
+ const now = Date.now();
12553
+ if (progress.persist || !deferredTimer && now - appliedAt >= PROGRESS_COALESCE_MS) {
12554
+ cancelDeferredProgress();
12555
+ appliedAt = now;
12556
+ await applyProgress(progress);
12557
+ return;
12558
+ }
12559
+ deferred = progress;
12560
+ deferredTimer ??= setTimeout(() => {
12561
+ deferredTimer = void 0;
12562
+ const next = deferred;
12563
+ deferred = void 0;
12564
+ if (!next) return;
12565
+ appliedAt = Date.now();
12566
+ void applyProgress(next).catch(() => void 0);
12567
+ }, Math.max(0, PROGRESS_COALESCE_MS - (now - appliedAt))).unref();
12568
+ };
12249
12569
  try {
12250
12570
  const budget = run.budget.forAgent(id);
12251
- const onProgress = async (progress) => {
12252
- let runState;
12253
- if (progress.persist) {
12254
- runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
12255
- } else {
12256
- const loaded = await run.store.load();
12257
- if (!loaded.run.agents.some((agent) => agent.id === id)) return;
12258
- runState = { ...loaded.run, ...run.budget.snapshot(), agents: loaded.run.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
12259
- }
12260
- if (!runState.agents.some((agent) => agent.id === id)) return;
12261
- liveAgents.setActivity(runId, id, progress.activity);
12262
- liveAgents.setEventTime(runId, id, progress.lastEventAt);
12263
- run.update?.(workflowToolUpdate(withLiveActivities(runState)));
12264
- };
12265
12571
  const onAttempt = async (attempt) => {
12266
12572
  liveAgents.setSession(runId, id, attempt.liveSession);
12267
12573
  liveAgents.setHandoff(runId, id, attempt);
@@ -12281,6 +12587,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12281
12587
  scheduler.cancelChildren(id);
12282
12588
  scheduler.retry(id);
12283
12589
  });
12590
+ cancelDeferredProgress();
12284
12591
  const before = (await run.store.load()).run;
12285
12592
  await persistAgentAttempts(run.store, id, result.attempts);
12286
12593
  const completed = (await run.store.load()).run;
@@ -12291,6 +12598,7 @@ Choose what to do`, ["Retry", "Change model", "Abort workflow"]);
12291
12598
  run.update?.(workflowToolUpdate(withLiveActivities(persisted)));
12292
12599
  return result.value;
12293
12600
  } catch (error) {
12601
+ cancelDeferredProgress();
12294
12602
  liveAgents.setSession(runId, id);
12295
12603
  const attempts = getAgentAttempts(error);
12296
12604
  if (attempts?.length) {
@@ -12426,8 +12734,8 @@ Respond with workflow_respond.`);
12426
12734
  const run = runs.get(runId);
12427
12735
  await run?.lifecycle.enterAwaitingInput();
12428
12736
  if (!alreadyAwaiting && (!isForeground() || !ui?.select)) deliverBackgroundCheckpoint(metadata.name, runId, { ...input, name: label, path });
12429
- const decision = new Promise((resolve7, reject) => {
12430
- run?.checkpointResolvers.set(path, resolve7);
12737
+ const decision = new Promise((resolve9, reject) => {
12738
+ run?.checkpointResolvers.set(path, resolve9);
12431
12739
  if (signal.aborted) reject(new WorkflowError("CANCELLED", "Workflow cancelled"));
12432
12740
  else signal.addEventListener("abort", () => {
12433
12741
  run?.checkpointResolvers.delete(path);
@@ -12745,7 +13053,7 @@ Respond with workflow_respond.`);
12745
13053
  const deliverStaleTerminal = async (store, run) => {
12746
13054
  if (!(run.delivery?.mode === "foreground" && run.delivery.state === "attached" || run.delivery?.mode === "background" && run.delivery.state === "pending")) return;
12747
13055
  if (run.state === "completed") {
12748
- const resultPath = join15(store.directory, "result.json");
13056
+ const resultPath = join16(store.directory, "result.json");
12749
13057
  let resultBytes = 0;
12750
13058
  let hasResult = true;
12751
13059
  try {
@@ -12875,8 +13183,8 @@ ${content}` };
12875
13183
  let resolveDetached;
12876
13184
  let foregroundStore;
12877
13185
  let completionInstalled = false;
12878
- const detachedResult = params.foreground ? new Promise((resolve7) => {
12879
- resolveDetached = resolve7;
13186
+ const detachedResult = params.foreground ? new Promise((resolve9) => {
13187
+ resolveDetached = resolve9;
12880
13188
  }) : void 0;
12881
13189
  const detachedToolResult = (run) => {
12882
13190
  const detached = { runId: run.id, state: "running", detached: true };
@@ -13109,8 +13417,7 @@ ${content}` };
13109
13417
  state.workflowProgressComponent = workflowProgressBlock(currentProgress?.run ?? incoming, theme, currentProgress, async () => {
13110
13418
  const active = runs.get(incoming.id);
13111
13419
  const store = active?.store ?? new RunStore(incoming.cwd, incoming.sessionId, incoming.id, home);
13112
- const loaded = await store.load();
13113
- return withLiveActivities(loaded.run);
13420
+ return withLiveActivities(await store.loadStatus());
13114
13421
  }, () => {
13115
13422
  if (state.workflowProgress === currentProgress) requestRender();
13116
13423
  }, void 0, state.workflowProgressFrozenAt);
@@ -13223,9 +13530,9 @@ async function handleTurnInput(store, handle, turn) {
13223
13530
  }
13224
13531
  if (!completed) return void 0;
13225
13532
  if (source === void 0) fail("AGENT_FAILED", `Agent handle ${handle} cannot continue from its previous turn: the completed turn recorded no session file`);
13226
- const target = join15(store.directory, "handles", encodeURIComponent(handle), `turn-${String(turn)}-input.jsonl`);
13533
+ const target = join16(store.directory, "handles", encodeURIComponent(handle), `turn-${String(turn)}-input.jsonl`);
13227
13534
  try {
13228
- await mkdir4(dirname7(target), { recursive: true });
13535
+ await mkdir4(dirname8(target), { recursive: true });
13229
13536
  await copyFile2(source, target);
13230
13537
  } catch (error) {
13231
13538
  fail("AGENT_FAILED", `Agent handle ${handle} cannot continue from its previous turn: ${errorText(error)}`);
@@ -13246,13 +13553,13 @@ function modelSpec(value, fallback) {
13246
13553
 
13247
13554
  // src/herdr.ts
13248
13555
  import { execFile as execFile3 } from "node:child_process";
13249
- var herdrCommandRunner = (args) => new Promise((resolve7, reject) => {
13556
+ var herdrCommandRunner = (args) => new Promise((resolve9, reject) => {
13250
13557
  execFile3("herdr", [...args], { encoding: "utf8", maxBuffer: 1024 * 1024 }, (error, stdout) => {
13251
13558
  if (error) {
13252
13559
  reject(new Error(error.message));
13253
13560
  return;
13254
13561
  }
13255
- resolve7(stdout);
13562
+ resolve9(stdout);
13256
13563
  });
13257
13564
  });
13258
13565
  function herdrPaneId(env = process.env) {
@@ -13384,9 +13691,9 @@ async function waitForHerdrPane(paneId, runner = herdrCommandRunner, options = {
13384
13691
  const isAborted = () => monitorController.signal.aborted;
13385
13692
  let markAborted = () => {
13386
13693
  };
13387
- const aborted = new Promise((resolve7) => {
13694
+ const aborted = new Promise((resolve9) => {
13388
13695
  markAborted = () => {
13389
- resolve7("aborted");
13696
+ resolve9("aborted");
13390
13697
  };
13391
13698
  });
13392
13699
  const abortMonitor = () => {
@@ -13395,16 +13702,16 @@ async function waitForHerdrPane(paneId, runner = herdrCommandRunner, options = {
13395
13702
  };
13396
13703
  if (options.signal?.aborted) abortMonitor();
13397
13704
  else options.signal?.addEventListener("abort", abortMonitor, { once: true });
13398
- const delay = () => new Promise((resolve7) => {
13705
+ const delay = () => new Promise((resolve9) => {
13399
13706
  if (isAborted()) {
13400
- resolve7();
13707
+ resolve9();
13401
13708
  return;
13402
13709
  }
13403
13710
  const timer = setTimeout(finish, intervalMs);
13404
13711
  function finish() {
13405
13712
  clearTimeout(timer);
13406
13713
  monitorController.signal.removeEventListener("abort", finish);
13407
- resolve7();
13714
+ resolve9();
13408
13715
  }
13409
13716
  monitorController.signal.addEventListener("abort", finish, { once: true });
13410
13717
  });
@@ -13516,6 +13823,7 @@ export {
13516
13823
  HEARTBEAT_TIMEOUT_MS,
13517
13824
  LAUNCH_SNAPSHOT_IDENTITY_VERSION,
13518
13825
  MODEL_ALIAS_NAME,
13826
+ PLAIN_WORKFLOW_PROGRESS_STYLES,
13519
13827
  PiRuntimeAgentRunner,
13520
13828
  RPC_LIMIT_BYTES,
13521
13829
  RUN_STATES,
@@ -13569,9 +13877,11 @@ export {
13569
13877
  buildWorkflowPhaseModel,
13570
13878
  buildWorkflowPhaseTree,
13571
13879
  byPriorityThenName,
13880
+ canonicalExtensionSelector,
13572
13881
  canonicalPath,
13573
13882
  coerceWorkflowError,
13574
13883
  createHerdrAgentReporter,
13884
+ createInlineConfirm,
13575
13885
  createLaunchSnapshot,
13576
13886
  createLiveSessionHandoff,
13577
13887
  createLocalPiSession,
@@ -13583,6 +13893,7 @@ export {
13583
13893
  deepFreeze,
13584
13894
  workflowExtension as default,
13585
13895
  defineWorkflowFunction,
13896
+ discoverRoles,
13586
13897
  encoded,
13587
13898
  errorCode,
13588
13899
  errorText,
@@ -13591,8 +13902,10 @@ export {
13591
13902
  fail,
13592
13903
  finiteNumber,
13593
13904
  formatAgentDetail,
13905
+ formatAgentError,
13594
13906
  formatBudgetStatus,
13595
13907
  formatCost,
13908
+ formatNavigatorColumns,
13596
13909
  formatNavigatorDashboard,
13597
13910
  formatNavigatorRun,
13598
13911
  formatStalledDuration,
@@ -13602,6 +13915,7 @@ export {
13602
13915
  formatWorkflowPhaseDashboard,
13603
13916
  formatWorkflowPreview,
13604
13917
  formatWorkflowProgress,
13918
+ formatWorkflowRuntime,
13605
13919
  hasLiveSessionLease,
13606
13920
  herdrAvailable,
13607
13921
  herdrCommandRunner,
@@ -13628,6 +13942,7 @@ export {
13628
13942
  loadAgentDefinitions,
13629
13943
  loadLaunchSnapshot,
13630
13944
  loadProjectAgentDefinitions,
13945
+ loadRole,
13631
13946
  loadSettings,
13632
13947
  loadSettingsOverrides,
13633
13948
  loadingRegistry,
@@ -13659,6 +13974,7 @@ export {
13659
13974
  prepareAgentSetupForInspection,
13660
13975
  preserveWorkflowPhaseSelection,
13661
13976
  preserveWorkflowPhaseTreeSelection,
13977
+ progressStyleForState,
13662
13978
  projectSessionsDirectory,
13663
13979
  projectStorageKey,
13664
13980
  readShellResult,
@@ -13671,6 +13987,7 @@ export {
13671
13987
  resetWorkflowRegistryIfIdle,
13672
13988
  resolveAgentResourcePolicy,
13673
13989
  resolveModelReference,
13990
+ resolveRole,
13674
13991
  resolveWorkflowSettings,
13675
13992
  resourcePatternHasMagic,
13676
13993
  resourcePatternMatches,
@@ -13678,6 +13995,7 @@ export {
13678
13995
  retainTerminalRuns,
13679
13996
  retainWorkflowRegistry,
13680
13997
  roleNameOf,
13998
+ runStateGlyph,
13681
13999
  runWorkflow,
13682
14000
  runsDirectory,
13683
14001
  runtimeProgressToAgentProgress,
@@ -13697,10 +14015,12 @@ export {
13697
14015
  validateBudget,
13698
14016
  validateBudgetPatch,
13699
14017
  validateCheckpoint,
14018
+ validateContextFileScopes,
13700
14019
  validateModelAliasAvailability,
13701
14020
  validateModelAliases,
13702
14021
  validateResourcePattern,
13703
14022
  validateSchema,
14023
+ validateSelectorList,
13704
14024
  validateShellCommand,
13705
14025
  validateShellOptions,
13706
14026
  validateWorkflowExtensionSettings,
@@ -13711,6 +14031,8 @@ export {
13711
14031
  workflowCatalog,
13712
14032
  workflowCatalogDetail,
13713
14033
  workflowCatalogIndex,
14034
+ workflowKeyLabel,
14035
+ workflowKeyMatches,
13714
14036
  workflowPhaseTreeInitialExpanded,
13715
14037
  workflowPhaseTreeVisibleNodes,
13716
14038
  workflowProjectSettingsPath,