@wrongstack/core 0.280.0 → 0.281.0

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 (95) hide show
  1. package/dist/{agent-subagent-runner-PoqNKiR4.d.ts → agent-subagent-runner-DyOSK2Wy.d.ts} +39 -8
  2. package/dist/{compactor-U3agvUIG.d.ts → compactor-BxddyCFu.d.ts} +1 -1
  3. package/dist/{config-Cr3312zc.d.ts → config-CN-vdDEI.d.ts} +82 -2
  4. package/dist/coordination/index.d.ts +203 -20
  5. package/dist/coordination/index.js +981 -52
  6. package/dist/coordination/index.js.map +1 -1
  7. package/dist/defaults/index.d.ts +26 -30
  8. package/dist/defaults/index.js +382 -30
  9. package/dist/defaults/index.js.map +1 -1
  10. package/dist/{events-Bs2fmldo.d.ts → events-C5hLU0AT.d.ts} +38 -2
  11. package/dist/execution/index.d.ts +16 -16
  12. package/dist/execution/index.js +161 -18
  13. package/dist/execution/index.js.map +1 -1
  14. package/dist/execution/prompt-enhancer.d.ts +1 -1
  15. package/dist/execution/prompt-enhancer.js +12 -3
  16. package/dist/execution/prompt-enhancer.js.map +1 -1
  17. package/dist/extension/index.d.ts +6 -6
  18. package/dist/{global-mailbox-Ct7IorLJ.d.ts → global-mailbox-CHM1QkCi.d.ts} +5 -2
  19. package/dist/{goal-store-C4F6DjC0.d.ts → goal-store-BLGZq16t.d.ts} +1 -1
  20. package/dist/hq/index.d.ts +6 -6
  21. package/dist/hq/index.js +35 -13
  22. package/dist/hq/index.js.map +1 -1
  23. package/dist/{index-nP09-oP2.d.ts → index-BEFWNZYs.d.ts} +1 -2
  24. package/dist/{index-kidebiDh.d.ts → index-C1E-_71T.d.ts} +6 -6
  25. package/dist/index.d.ts +44 -48
  26. package/dist/index.js +14855 -13944
  27. package/dist/index.js.map +1 -1
  28. package/dist/infrastructure/index.d.ts +60 -8
  29. package/dist/infrastructure/index.js.map +1 -1
  30. package/dist/kernel/index.d.ts +11 -11
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{mcp-servers-D910X5_r.d.ts → mcp-servers-EKRAcJDi.d.ts} +97 -4
  33. package/dist/models/index.d.ts +4 -5
  34. package/dist/models/index.js +22 -5
  35. package/dist/models/index.js.map +1 -1
  36. package/dist/{multi-agent-coordinator-CieyUoEL.d.ts → multi-agent-coordinator-IpHCuHUa.d.ts} +29 -1
  37. package/dist/{null-fleet-bus-DkdmZJ_W.d.ts → null-fleet-bus-ZBkKGR9j.d.ts} +105 -7
  38. package/dist/observability/index.d.ts +3 -3
  39. package/dist/{tool-executor-SiE1wlZo.d.ts → parallel-eternal-engine-Cc2CNgmv.d.ts} +6 -202
  40. package/dist/path-resolver-CMS5307d.d.ts +13 -0
  41. package/dist/{permission-Dx6dIqS2.d.ts → permission-B6YAtNFz.d.ts} +1 -1
  42. package/dist/{pipeline-BwAP21_4.d.ts → pipeline-E7_jqGyV.d.ts} +2 -2
  43. package/dist/{provider-model-resolve-CwQNZWt_.d.ts → provider-model-resolve-KgIdiEQ6.d.ts} +118 -5
  44. package/dist/{provider-runner-CYHFImzV.d.ts → provider-runner-DxWXNHL1.d.ts} +3 -3
  45. package/dist/{retry-policy-D4feSLk3.d.ts → retry-policy-V67lBOJz.d.ts} +2 -2
  46. package/dist/sdd/index.d.ts +9 -9
  47. package/dist/sdd/index.js +94 -10
  48. package/dist/sdd/index.js.map +1 -1
  49. package/dist/{permission-policy-C8vJcnX5.d.ts → secret-vault-D7jeyXUQ.d.ts} +119 -3
  50. package/dist/security/index.d.ts +5 -6
  51. package/dist/security/index.js +4 -1
  52. package/dist/security/index.js.map +1 -1
  53. package/dist/{selector-Dy-MzKp1.d.ts → selector-Bx2HgUkU.d.ts} +1 -1
  54. package/dist/{session-event-bridge-CqdiGnfU.d.ts → session-event-bridge-vA2fLV58.d.ts} +1 -1
  55. package/dist/{session-reader-Hk0WbNm9.d.ts → session-reader-B-Fi9L6I.d.ts} +1 -1
  56. package/dist/session-registry-Dvg7i_IA.d.ts +129 -0
  57. package/dist/storage/index.d.ts +71 -142
  58. package/dist/storage/index.js +161 -13
  59. package/dist/storage/index.js.map +1 -1
  60. package/dist/{todos-checkpoint-Bk2uP7Ex.d.ts → todos-checkpoint-B1QVkoa8.d.ts} +23 -5
  61. package/dist/{tool-BkOgs_KL.d.ts → tool-LVSLiE1s.d.ts} +18 -4
  62. package/dist/{strategy-compactor-CQwhbErd.d.ts → tool-executor-BWtYwuPI.d.ts} +210 -12
  63. package/dist/tools/index.d.ts +2 -2
  64. package/dist/tools/index.js.map +1 -1
  65. package/dist/types/index.d.ts +17 -41
  66. package/dist/types/index.js +182 -5085
  67. package/dist/types/index.js.map +1 -1
  68. package/dist/utils/index.d.ts +3 -3
  69. package/dist/utils/index.js +6 -1
  70. package/dist/utils/index.js.map +1 -1
  71. package/dist/{worktree-manager-BjOFF6bt.d.ts → worktree-manager-C6CIilTT.d.ts} +1 -1
  72. package/dist/{wstack-paths-CMl_cYgq.d.ts → wstack-paths-C3K40Qst.d.ts} +2 -0
  73. package/instructions/agents/shadow-agent.md +23 -84
  74. package/instructions/coordination/director-preamble.md +12 -3
  75. package/instructions/coordination/subagent-baseline.md +39 -46
  76. package/instructions/leader-after-task.md +10 -28
  77. package/instructions/llm/llm-selector.md +1 -2
  78. package/instructions/modes/brief-system.md +7 -33
  79. package/instructions/modes/brief.md +5 -20
  80. package/instructions/modes/teach-system.md +12 -72
  81. package/instructions/modes/teach.md +12 -49
  82. package/instructions/sections/tool/commit-hygiene.md +5 -5
  83. package/instructions/sections/tool/context-management-full.md +6 -6
  84. package/instructions/sections/tool/delegation-compact.md +1 -1
  85. package/instructions/sections/tool/delegation-full.md +19 -23
  86. package/instructions/sections/tool/mailbox-full.md +11 -31
  87. package/instructions/sections/tool/mcp-full-control.md +5 -7
  88. package/instructions/sections/tool/mcp-full-use.md +7 -7
  89. package/instructions/system.md +22 -38
  90. package/package.json +1 -29
  91. package/dist/agent-bridge-DXC6QDJ4.d.ts +0 -35
  92. package/dist/logger-D3lV0cUZ.d.ts +0 -101
  93. package/dist/models-registry-CLkoOcHk.d.ts +0 -108
  94. package/dist/path-resolver-XfZ9eLxG.d.ts +0 -67
  95. package/dist/secret-vault-CImt2XrR.d.ts +0 -118
@@ -1,5 +1,5 @@
1
1
  import * as crypto2 from 'crypto';
2
- import { randomBytes, randomUUID, createCipheriv, createDecipheriv, createHash, scryptSync } from 'crypto';
2
+ import { randomBytes, randomUUID, randomInt, createCipheriv, createDecipheriv, createHash, scryptSync } from 'crypto';
3
3
  import * as fsp7 from 'fs/promises';
4
4
  import { readFile, writeFile, mkdir } from 'fs/promises';
5
5
  import * as path4 from 'path';
@@ -868,7 +868,7 @@ function walk(value, schema, path32, errors, depth) {
868
868
  return;
869
869
  }
870
870
  if (schema.enum !== void 0) {
871
- if (!schema.enum.some((e) => deepEqual(e, value))) {
871
+ if (!enumIncludes(schema.enum, value)) {
872
872
  errors.push({
873
873
  path: path32 || "<root>",
874
874
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
@@ -938,6 +938,10 @@ function joinPath(parent, key) {
938
938
  if (!parent) return key;
939
939
  return `${parent}.${key}`;
940
940
  }
941
+ function enumIncludes(values, value) {
942
+ if (value === null || typeof value !== "object") return values.includes(value);
943
+ return values.some((candidate) => deepEqual(candidate, value));
944
+ }
941
945
  function deepEqual(a, b) {
942
946
  if (a === b) return true;
943
947
  if (typeof a !== typeof b) return false;
@@ -1187,6 +1191,27 @@ function safeParse(input, maxBytes = 5e6) {
1187
1191
  };
1188
1192
  }
1189
1193
  }
1194
+ function safeStringify(value, pretty = false) {
1195
+ const seen = /* @__PURE__ */ new WeakSet();
1196
+ const replacer = (_k, v) => {
1197
+ if (typeof v === "bigint") return v.toString();
1198
+ if (v instanceof Error) {
1199
+ return { name: v.name, message: v.message, stack: v.stack };
1200
+ }
1201
+ if (typeof v === "object" && v !== null) {
1202
+ if (seen.has(v)) return "[Circular]";
1203
+ seen.add(v);
1204
+ }
1205
+ return v;
1206
+ };
1207
+ try {
1208
+ return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
1209
+ } catch (err) {
1210
+ return JSON.stringify({
1211
+ __serialization_error: toErrorMessage(err)
1212
+ });
1213
+ }
1214
+ }
1190
1215
  var init_safe_json = __esm({
1191
1216
  "src/utils/safe-json.ts"() {
1192
1217
  init_error();
@@ -2320,6 +2345,7 @@ function resolveWstackPaths(opts) {
2320
2345
  projectHash: hash,
2321
2346
  projectSlug: slug,
2322
2347
  projectGoal: path4.join(projectDir, "goal.json"),
2348
+ projectInputHistory: path4.join(projectDir, "input-history.json"),
2323
2349
  projectSpecs: path4.join(projectDir, "specs"),
2324
2350
  projectTaskGraphs: path4.join(projectDir, "task-graphs"),
2325
2351
  projectSddSession: path4.join(projectDir, "sdd-session.json"),
@@ -3448,20 +3474,32 @@ var TOOLS = {
3448
3474
  /** Web research. */
3449
3475
  research: ["read", "grep", "glob", "search", "fetch", "mailbox"]
3450
3476
  };
3477
+ var promptCache = /* @__PURE__ */ new Map();
3478
+ var candidateCache = /* @__PURE__ */ new Map();
3451
3479
  function agentPrompt(id) {
3480
+ const envDir = process.env["WRONGSTACK_AGENT_INSTRUCTIONS_DIR"] ?? "";
3481
+ const cacheKey = `${envDir}\0${id}`;
3482
+ const cached = promptCache.get(cacheKey);
3483
+ if (cached !== void 0) return cached;
3452
3484
  const fileName = `${id}.md`;
3453
- for (const dir of agentPromptDirCandidates()) {
3485
+ let resolved = "";
3486
+ for (const dir of agentPromptDirCandidates(envDir)) {
3454
3487
  try {
3455
- return readFileSync(path4.join(dir, fileName), "utf8").trimEnd();
3488
+ resolved = readFileSync(path4.join(dir, fileName), "utf8").trimEnd();
3489
+ break;
3456
3490
  } catch {
3457
3491
  }
3458
3492
  }
3459
- return "";
3493
+ promptCache.set(cacheKey, resolved);
3494
+ return resolved;
3460
3495
  }
3461
- function agentPromptDirCandidates() {
3462
- const here = path4.dirname(fileURLToPath(import.meta.url));
3463
- const explicitDir = process.env["WRONGSTACK_AGENT_INSTRUCTIONS_DIR"];
3496
+ function agentPromptDirCandidates(envDir) {
3464
3497
  const globalRoot = process.env["WRONGSTACK_HOME"] || path4.join(os.homedir(), ".wrongstack");
3498
+ const candKey = `${envDir}\0${globalRoot}`;
3499
+ const cached = candidateCache.get(candKey);
3500
+ if (cached !== void 0) return cached;
3501
+ const here = path4.dirname(fileURLToPath(import.meta.url));
3502
+ const explicitDir = envDir || void 0;
3465
3503
  const candidates = [
3466
3504
  ...explicitDir ? [path4.resolve(explicitDir)] : [],
3467
3505
  path4.join(globalRoot, "instructions", "agents"),
@@ -3471,7 +3509,9 @@ function agentPromptDirCandidates() {
3471
3509
  path4.resolve(here, "../instructions/agents"),
3472
3510
  path4.resolve(here, "instructions/agents")
3473
3511
  ];
3474
- return candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
3512
+ const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
3513
+ candidateCache.set(candKey, ordered);
3514
+ return ordered;
3475
3515
  }
3476
3516
  function isDirectory(candidate) {
3477
3517
  try {
@@ -6328,14 +6368,21 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
6328
6368
  this.snapshot.files.length = 0;
6329
6369
  }
6330
6370
  };
6371
+ var textCache = /* @__PURE__ */ new Map();
6372
+ var rootCandidates;
6331
6373
  function readBundledInstructionText(relativePath) {
6374
+ const cached = textCache.get(relativePath);
6375
+ if (cached !== void 0) return cached;
6376
+ let resolved = "";
6332
6377
  for (const root of instructionRootCandidates()) {
6333
6378
  try {
6334
- return readFileSync(path4.join(root, relativePath), "utf8").trimEnd();
6379
+ resolved = readFileSync(path4.join(root, relativePath), "utf8").trimEnd();
6380
+ break;
6335
6381
  } catch {
6336
6382
  }
6337
6383
  }
6338
- return "";
6384
+ textCache.set(relativePath, resolved);
6385
+ return resolved;
6339
6386
  }
6340
6387
  function renderInstructionTemplate(template, values) {
6341
6388
  return template.replace(
@@ -6344,13 +6391,15 @@ function renderInstructionTemplate(template, values) {
6344
6391
  );
6345
6392
  }
6346
6393
  function instructionRootCandidates() {
6394
+ if (rootCandidates !== void 0) return rootCandidates;
6347
6395
  const here = path4.dirname(fileURLToPath(import.meta.url));
6348
6396
  const candidates = [
6349
6397
  path4.resolve(here, "../../instructions"),
6350
6398
  path4.resolve(here, "../instructions"),
6351
6399
  path4.resolve(here, "instructions")
6352
6400
  ];
6353
- return candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
6401
+ rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
6402
+ return rootCandidates;
6354
6403
  }
6355
6404
  function isDirectory2(candidate) {
6356
6405
  try {
@@ -6734,13 +6783,31 @@ function makeAssignTool(director) {
6734
6783
  function makeAwaitTasksTool(director) {
6735
6784
  return {
6736
6785
  name: "await_tasks",
6737
- description: "Block until every named task completes. Returns the array of TaskResult.",
6786
+ description: 'Wait for assigned tasks. mode:"all" (default) blocks until EVERY named task completes and returns all results. mode:"any" returns as soon as AT LEAST ONE completes \u2014 use it for independent tasks so you can handle each finisher immediately (reassign work, spawn helpers) instead of idling on the slowest; call again with the returned `pending` ids to pick up the next finisher.',
6738
6787
  permission: "auto",
6739
6788
  mutating: false,
6740
6789
  capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
6741
- inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
6790
+ inputSchema: {
6791
+ type: "object",
6792
+ properties: {
6793
+ taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." },
6794
+ mode: { type: "string", enum: ["all", "any"], description: '"all" (default): block until every task completes. "any": return on the first completion with the rest listed as pending.' },
6795
+ timeoutMs: { type: "number", minimum: 1, description: 'mode:"any" only \u2014 return {timedOut:true, completed:[]} if nothing completes within this window instead of blocking.' }
6796
+ },
6797
+ required: ["taskIds"]
6798
+ },
6742
6799
  async execute(input) {
6743
6800
  const i = input;
6801
+ if (i.mode === "any") {
6802
+ const r = await director.awaitTasksAny(i.taskIds, i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : void 0);
6803
+ return {
6804
+ mode: "any",
6805
+ completed: r.completed,
6806
+ pending: r.pending,
6807
+ ...r.timedOut ? { timedOut: true } : {},
6808
+ ...r.pending.length > 0 ? { hint: 'Handle the completed results now. Re-call await_tasks with the pending ids (mode:"any") for the next finisher \u2014 or assign new work to the now-idle subagent first.' } : {}
6809
+ };
6810
+ }
6744
6811
  const results = await director.awaitTasks(i.taskIds);
6745
6812
  return { results };
6746
6813
  }
@@ -7847,6 +7914,30 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
7847
7914
  results() {
7848
7915
  return this.completedResults;
7849
7916
  }
7917
+ /** Defensive snapshot of the still-queued (not yet dispatched) tasks. */
7918
+ listPendingTasks() {
7919
+ return this.pendingTasks.map((t) => ({ ...t }));
7920
+ }
7921
+ /**
7922
+ * Re-pin a still-PENDING task to a different subagent (`undefined` =
7923
+ * unpin, any idle worker may take it), then try to dispatch. Returns
7924
+ * `false` when the task is not in the pending queue — already
7925
+ * dispatched, completed, or unknown. Running tasks can never be pulled;
7926
+ * they can only be steered or terminated. The mutation is synchronous
7927
+ * (same tick as the check), so it cannot race a concurrent dispatch:
7928
+ * `tryDispatchNext` runs on this same call stack or a later one.
7929
+ *
7930
+ * The task keeps its id, so waiters, the report-back notifier, and
7931
+ * checkpoint bookkeeping resolve unchanged when it eventually completes.
7932
+ */
7933
+ retargetPendingTask(taskId, subagentId) {
7934
+ const task = this.pendingTasks.find((t) => t.id === taskId);
7935
+ if (!task) return false;
7936
+ if (subagentId !== void 0 && !this.subagents.has(subagentId)) return false;
7937
+ task.subagentId = subagentId;
7938
+ this.tryDispatchNext();
7939
+ return true;
7940
+ }
7850
7941
  /**
7851
7942
  * Wait for one or more tasks to complete and return their results.
7852
7943
  * If a task is already done when called, returns immediately.
@@ -7874,6 +7965,43 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
7874
7965
  })
7875
7966
  );
7876
7967
  }
7968
+ /**
7969
+ * Wait until AT LEAST ONE of the named tasks completes. Returns every
7970
+ * requested result already available at that moment (drain-what's-done)
7971
+ * plus the ids still outstanding, so callers can loop "handle finishers,
7972
+ * re-await the remainder" instead of blocking on the whole batch.
7973
+ *
7974
+ * Unlike `awaitTasks`, this never rejects and deliberately does NOT
7975
+ * inherit `config.timeoutMs` — a "return whatever is done" call has no
7976
+ * business timing out unless the caller asks for a window explicitly.
7977
+ */
7978
+ async awaitTasksAny(taskIds, opts) {
7979
+ const ids = new Set(taskIds);
7980
+ const completed = this.completedResults.filter((r) => ids.has(r.taskId));
7981
+ if (completed.length > 0 || ids.size === 0) {
7982
+ const done = new Set(completed.map((r) => r.taskId));
7983
+ return { completed, pending: taskIds.filter((id) => !done.has(id)) };
7984
+ }
7985
+ return new Promise((resolve12) => {
7986
+ let timer;
7987
+ const handler = ({ result }) => {
7988
+ if (!ids.has(result.taskId)) return;
7989
+ if (timer) clearTimeout(timer);
7990
+ this.off("task.completed", handler);
7991
+ resolve12({
7992
+ completed: [result],
7993
+ pending: taskIds.filter((id) => id !== result.taskId)
7994
+ });
7995
+ };
7996
+ if (opts?.timeoutMs !== void 0) {
7997
+ timer = setTimeout(() => {
7998
+ this.off("task.completed", handler);
7999
+ resolve12({ completed: [], pending: [...taskIds], timedOut: true });
8000
+ }, opts.timeoutMs);
8001
+ }
8002
+ this.on("task.completed", handler);
8003
+ });
8004
+ }
7877
8005
  /**
7878
8006
  * Manual completion — for callers that drive subagents without a runner
7879
8007
  * (e.g. external orchestrators). When a runner is configured the coordinator
@@ -8498,6 +8626,13 @@ var Director = class _Director {
8498
8626
  * coordinator emits `task.completed` for a given task id. Each entry
8499
8627
  * is created lazily on first poll/await and cleared once consumed. */
8500
8628
  taskWaiters = /* @__PURE__ */ new Map();
8629
+ /** First-completion waiters registered by `awaitTasksAny`. Kept separate
8630
+ * from `taskWaiters` on purpose: a `taskWaiters` entry marks a result as
8631
+ * "will be delivered in-band" and suppresses the fire-and-forget
8632
+ * report-back — but an any-await only consumes ONE of its ids, so the
8633
+ * losers must stay eligible for the mailbox notifier. Entries carry an
8634
+ * optional timeout timer so `shutdown()` can clear them. */
8635
+ anyWaiters = /* @__PURE__ */ new Set();
8501
8636
  /** Cache of completed results in case the consumer asks AFTER the
8502
8637
  * coordinator already fired the event — `awaitTasks(['t-1'])` after
8503
8638
  * t-1 finished should resolve immediately, not hang. */
@@ -8519,6 +8654,8 @@ var Director = class _Director {
8519
8654
  roster;
8520
8655
  directorPreamble;
8521
8656
  subagentBaseline;
8657
+ /** See {@link DirectorOptions.taskResultNotifier}. */
8658
+ taskResultNotifier;
8522
8659
  /** Absolute path to the fleet's shared scratchpad directory, or null
8523
8660
  * when none was configured. Exposed as a readonly getter for callers
8524
8661
  * that need to surface the path to the user (e.g. the CLI logging
@@ -8616,6 +8753,7 @@ var Director = class _Director {
8616
8753
  this.roster = opts.roster;
8617
8754
  this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
8618
8755
  this.subagentBaseline = opts.subagentBaseline ?? DEFAULT_SUBAGENT_BASELINE;
8756
+ this.taskResultNotifier = opts.taskResultNotifier;
8619
8757
  this.sharedScratchpadPath = opts.sharedScratchpadPath ?? null;
8620
8758
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
8621
8759
  this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
@@ -8688,8 +8826,38 @@ var Director = class _Director {
8688
8826
  waiter.resolve(r);
8689
8827
  this.taskWaiters.delete(r.taskId);
8690
8828
  }
8829
+ let anyConsumed = false;
8830
+ for (const aw of [...this.anyWaiters]) {
8831
+ if (!aw.ids.has(r.taskId)) continue;
8832
+ if (aw.timer) clearTimeout(aw.timer);
8833
+ this.anyWaiters.delete(aw);
8834
+ aw.resolve(r);
8835
+ anyConsumed = true;
8836
+ }
8837
+ const consumedInBand = !!waiter || anyConsumed;
8691
8838
  if (internalTask) return;
8692
8839
  const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
8840
+ if (!consumedInBand && this.taskResultNotifier) {
8841
+ const resultText = typeof r.result === "string" ? r.result : r.result !== void 0 ? safeStringify(r.result) : void 0;
8842
+ try {
8843
+ void Promise.resolve(
8844
+ this.taskResultNotifier({
8845
+ taskId: r.taskId,
8846
+ title,
8847
+ status: r.status,
8848
+ subagentId: r.subagentId,
8849
+ subagentName: this.manifestEntries.get(r.subagentId)?.name,
8850
+ resultText,
8851
+ errorText: r.error ? `${r.error.kind}: ${r.error.message}` : void 0,
8852
+ iterations: r.iterations,
8853
+ toolCalls: r.toolCalls,
8854
+ durationMs: r.durationMs
8855
+ })
8856
+ ).catch(() => {
8857
+ });
8858
+ } catch {
8859
+ }
8860
+ }
8693
8861
  const failed = r.status !== "success";
8694
8862
  const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
8695
8863
  this.stateCheckpoint?.recordTaskStatus(r.taskId, {
@@ -9275,6 +9443,10 @@ var Director = class _Director {
9275
9443
  this.coordinator.off("task.completed", this.taskCompletedListener);
9276
9444
  this.taskCompletedListener = null;
9277
9445
  }
9446
+ for (const aw of this.anyWaiters) {
9447
+ if (aw.timer) clearTimeout(aw.timer);
9448
+ }
9449
+ this.anyWaiters.clear();
9278
9450
  if (this.toolExecFilter) {
9279
9451
  this.toolExecFilter();
9280
9452
  this.toolExecFilter = null;
@@ -9343,6 +9515,12 @@ var Director = class _Director {
9343
9515
  waiter.resolve(synthetic);
9344
9516
  this.taskWaiters.delete(taskWithId.id);
9345
9517
  }
9518
+ for (const aw of [...this.anyWaiters]) {
9519
+ if (!aw.ids.has(taskWithId.id)) continue;
9520
+ if (aw.timer) clearTimeout(aw.timer);
9521
+ this.anyWaiters.delete(aw);
9522
+ aw.resolve(synthetic);
9523
+ }
9346
9524
  return taskWithId.id;
9347
9525
  }
9348
9526
  if (task.subagentId) {
@@ -9413,6 +9591,85 @@ var Director = class _Director {
9413
9591
  })
9414
9592
  );
9415
9593
  }
9594
+ /**
9595
+ * Wait until AT LEAST ONE of the named tasks completes, then return every
9596
+ * requested result already cached plus the still-pending ids. This is the
9597
+ * leader's "handle whichever finishes next" primitive — unlike
9598
+ * `awaitTasks` it never blocks on the slowest sibling, and the siblings
9599
+ * that complete later still reach the leader through the fire-and-forget
9600
+ * report-back (see the consumed-in-band rule in `taskCompletedListener`).
9601
+ *
9602
+ * Deliberately does NOT register `taskWaiters` entries for the pending
9603
+ * ids: a `taskWaiters` entry would mark those results as in-band-consumed
9604
+ * and silence the mailbox notifier for results this caller never receives.
9605
+ *
9606
+ * With `timeoutMs`, resolves (never rejects) `{timedOut: true}` and zero
9607
+ * completions when the window elapses first.
9608
+ */
9609
+ awaitTasksAny(taskIds, opts) {
9610
+ const completed = taskIds.map((id) => this.completed.get(id)).filter((r) => r !== void 0);
9611
+ if (completed.length > 0 || taskIds.length === 0) {
9612
+ const done = new Set(completed.map((r) => r.taskId));
9613
+ return Promise.resolve({
9614
+ completed,
9615
+ pending: taskIds.filter((id) => !done.has(id))
9616
+ });
9617
+ }
9618
+ return new Promise((resolve12) => {
9619
+ const entry = {
9620
+ ids: new Set(taskIds),
9621
+ resolve: (r) => resolve12({
9622
+ completed: [r],
9623
+ pending: taskIds.filter((id) => id !== r.taskId)
9624
+ })
9625
+ };
9626
+ if (opts?.timeoutMs !== void 0) {
9627
+ entry.timer = setTimeout(() => {
9628
+ this.anyWaiters.delete(entry);
9629
+ resolve12({ completed: [], pending: [...taskIds], timedOut: true });
9630
+ }, opts.timeoutMs);
9631
+ }
9632
+ this.anyWaiters.add(entry);
9633
+ });
9634
+ }
9635
+ /** Defensive snapshot of tasks still queued (not yet dispatched). */
9636
+ listPendingTasks() {
9637
+ return this.coordinator.listPendingTasks();
9638
+ }
9639
+ /**
9640
+ * Re-pin a still-pending task to a different subagent (`undefined` =
9641
+ * unpin) — the rebalancing primitive used by the FleetSupervisor and
9642
+ * available to hosts. Only queued tasks can move; a dispatched task
9643
+ * returns `false` (steer or terminate its worker instead). Keeps
9644
+ * ownership bookkeeping (taskOwners, checkpoint, fleet manifest) in
9645
+ * sync with the coordinator's queue.
9646
+ */
9647
+ retargetPendingTask(taskId, subagentId) {
9648
+ const ok = this.coordinator.retargetPendingTask(taskId, subagentId);
9649
+ if (!ok) return false;
9650
+ if (subagentId) {
9651
+ this.taskOwners.set(taskId, subagentId);
9652
+ if (this.fleetManager) {
9653
+ this.fleetManager.addTaskToSubagent(subagentId, taskId);
9654
+ } else {
9655
+ const entry = this.manifestEntries.get(subagentId);
9656
+ if (entry && !entry.taskIds.includes(taskId)) entry.taskIds.push(taskId);
9657
+ }
9658
+ } else {
9659
+ this.taskOwners.delete(taskId);
9660
+ }
9661
+ const description = this.taskDescriptions.get(taskId) ?? taskId;
9662
+ this.fleetManager?.addPendingTask(taskId, subagentId ?? "", description);
9663
+ this.stateCheckpoint?.recordTaskAssigned({
9664
+ taskId,
9665
+ subagentId,
9666
+ description,
9667
+ status: "running",
9668
+ assignedAt: (/* @__PURE__ */ new Date()).toISOString()
9669
+ });
9670
+ this.scheduleManifest();
9671
+ return true;
9672
+ }
9416
9673
  async terminate(subagentId) {
9417
9674
  await this.coordinator.stop(subagentId);
9418
9675
  }
@@ -10349,6 +10606,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
10349
10606
  dir;
10350
10607
  events;
10351
10608
  secretScrubber;
10609
+ isSessionInUse;
10352
10610
  /**
10353
10611
  * In-memory cache for load() results, keyed by session ID. The cache is
10354
10612
  * invalidated when the file's mtimeMs or size changes (indicating the
@@ -10368,6 +10626,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
10368
10626
  this.dir = opts.dir;
10369
10627
  this.events = opts.events;
10370
10628
  this.secretScrubber = opts.secretScrubber;
10629
+ this.isSessionInUse = opts.isSessionInUse;
10371
10630
  }
10372
10631
  /**
10373
10632
  * Clear the load() cache. Useful for testing or when the caller knows
@@ -11259,19 +11518,74 @@ var DefaultSessionStore = class _DefaultSessionStore {
11259
11518
  });
11260
11519
  await this.writeTombstone(id);
11261
11520
  }
11521
+ /**
11522
+ * Read the session id currently marked active in `active.json`, or `null`
11523
+ * when the lock is absent/unreadable. Shared by {@link delete} and
11524
+ * {@link prune} to avoid clobbering a session a live process is writing to.
11525
+ */
11526
+ async readActiveSessionId() {
11527
+ try {
11528
+ const raw = await fsp7.readFile(path4.join(this.dir, "active.json"), "utf8");
11529
+ const active = JSON.parse(raw);
11530
+ return active.sessionId ?? null;
11531
+ } catch {
11532
+ return null;
11533
+ }
11534
+ }
11262
11535
  async delete(id) {
11536
+ const activeId = await this.readActiveSessionId();
11537
+ if (activeId && id === activeId) {
11538
+ throw new Error(
11539
+ `Session ${id} is currently active in this project and cannot be deleted. Resume or start another session first.`
11540
+ );
11541
+ }
11542
+ if (this.isSessionInUse) {
11543
+ const reason = await this.isSessionInUse(id);
11544
+ if (reason) {
11545
+ throw new Error(`Session ${id} is in use (${reason}) and cannot be deleted.`);
11546
+ }
11547
+ }
11263
11548
  await this.deleteSession(id);
11264
11549
  }
11550
+ async rename(id, name) {
11551
+ const trimmed = name.trim();
11552
+ const manifest = this.sessionPath(id, ".summary.json");
11553
+ const jsonlPath = this.sessionPath(id, ".jsonl");
11554
+ try {
11555
+ await fsp7.stat(jsonlPath);
11556
+ } catch (err) {
11557
+ const code = err?.code;
11558
+ if (code === "ENOENT") {
11559
+ throw new Error(`Session not found: ${id}`);
11560
+ }
11561
+ throw err;
11562
+ }
11563
+ const summary = await this.summaryFor(id);
11564
+ const { name: _drop, ...rest } = summary;
11565
+ const updated = trimmed ? { ...rest, name: trimmed } : rest;
11566
+ const t0 = Date.now();
11567
+ let outcome = "success";
11568
+ let errorMsg;
11569
+ try {
11570
+ await atomicWrite(manifest, JSON.stringify(updated), { mode: 384 });
11571
+ } catch (err) {
11572
+ outcome = "failure";
11573
+ errorMsg = toErrorMessage(err);
11574
+ this.emitError(id, manifest, "rename", errorMsg, false);
11575
+ throw err;
11576
+ } finally {
11577
+ this.emitWrite(id, manifest, "close", outcome, Date.now() - t0, void 0, errorMsg);
11578
+ }
11579
+ await this.appendToIndex(updated);
11580
+ this.invalidateShardManifestBySessionId(id);
11581
+ this._indexCache = null;
11582
+ this.clearLoadCache(id);
11583
+ return updated;
11584
+ }
11265
11585
  async prune(maxAgeDays = 30) {
11266
11586
  const cutoff = Date.now() - maxAgeDays * 864e5;
11267
11587
  let deleted = 0;
11268
- let activeSessionId = null;
11269
- try {
11270
- const raw = await fsp7.readFile(path4.join(this.dir, "active.json"), "utf8");
11271
- const active = JSON.parse(raw);
11272
- activeSessionId = active.sessionId ?? null;
11273
- } catch {
11274
- }
11588
+ const activeSessionId = await this.readActiveSessionId();
11275
11589
  const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
11276
11590
  const pruneFile = async (dir, name, prefix) => {
11277
11591
  const jsonlPath = path4.join(dir, name);
@@ -15461,7 +15775,9 @@ async function runProviderWithRetry(opts) {
15461
15775
  });
15462
15776
  throw toWrongStackError(err);
15463
15777
  }
15464
- const delay = Math.round(retry.delayMs(attempt));
15778
+ const delay = Math.round(
15779
+ retry.delayMs(attempt, isProviderErr ? err : errAsErr)
15780
+ );
15465
15781
  const attemptNum = attempt + 1;
15466
15782
  const maxAttempts = retry.maxAttempts(isProviderErr ? err : errAsErr);
15467
15783
  logger.warn(`Provider retry ${attemptNum}/${maxAttempts} in ${delay}ms \u2014 ${description}`, {
@@ -15517,8 +15833,7 @@ var DefaultProviderRunner = class {
15517
15833
  return runProviderWithRetry(opts);
15518
15834
  }
15519
15835
  };
15520
-
15521
- // src/execution/retry-policy.ts
15836
+ var MAX_RETRY_AFTER_MS = 6e4;
15522
15837
  var DefaultRetryPolicy = class {
15523
15838
  shouldRetry(err, attempt) {
15524
15839
  if (err instanceof ProviderError) {
@@ -15540,13 +15855,37 @@ var DefaultRetryPolicy = class {
15540
15855
  }
15541
15856
  return 2;
15542
15857
  }
15543
- delayMs(attempt) {
15858
+ /**
15859
+ * Compute the retry delay.
15860
+ *
15861
+ * Precedence:
15862
+ * 1. If `err` is a `ProviderError` with a populated
15863
+ * `body.retryAfterMs`, honour it — clamped into
15864
+ * [0, MAX_RETRY_AFTER_MS]. The provider told us exactly when
15865
+ * to come back; we should listen.
15866
+ * 2. Otherwise fall through to the exponential-with-jitter
15867
+ * schedule (`1000 * 2^attempt + jitter`, capped at 30s).
15868
+ *
15869
+ * `err` is optional for back-compat with the existing interface;
15870
+ * callers should pass it whenever available.
15871
+ */
15872
+ delayMs(attempt, err) {
15873
+ const hint = retryAfterMsFromError(err);
15874
+ if (hint !== void 0) {
15875
+ return Math.max(0, Math.min(hint, MAX_RETRY_AFTER_MS));
15876
+ }
15544
15877
  const base = 1e3;
15545
15878
  const exp = base * 2 ** attempt;
15546
- const jitter = Math.random() * base;
15879
+ const jitter = randomInt(0, base);
15547
15880
  return Math.min(3e4, exp + jitter);
15548
15881
  }
15549
15882
  };
15883
+ function retryAfterMsFromError(err) {
15884
+ if (!err || !(err instanceof ProviderError)) return void 0;
15885
+ const ms = err.body?.retryAfterMs;
15886
+ if (typeof ms !== "number" || !Number.isFinite(ms) || ms <= 0) return void 0;
15887
+ return ms;
15888
+ }
15550
15889
 
15551
15890
  // src/models/llm-selector.ts
15552
15891
  init_expect_defined();
@@ -18641,7 +18980,7 @@ function describeCatalogModel(m) {
18641
18980
  ]
18642
18981
  };
18643
18982
  }
18644
- function resolveProviderModelList(savedModels, catalog) {
18983
+ function resolveProviderModelList(savedModels, catalog, providerHint) {
18645
18984
  if (savedModels && savedModels.length > 0) {
18646
18985
  const byId = new Map((catalog?.models ?? []).map((m) => [m.id, m]));
18647
18986
  return savedModels.map((id) => {
@@ -18657,7 +18996,15 @@ function resolveProviderModelList(savedModels, catalog) {
18657
18996
  return { id, name: id, capabilities: [] };
18658
18997
  });
18659
18998
  }
18660
- if (catalog) return catalog.models.map(describeCatalogModel);
18999
+ if (catalog && catalog.models.length > 0) return catalog.models.map(describeCatalogModel);
19000
+ if (providerHint === "openai-codex") {
19001
+ return CODEX_MODELS.map((m) => ({
19002
+ id: m.id,
19003
+ name: m.name,
19004
+ description: m.description,
19005
+ capabilities: []
19006
+ }));
19007
+ }
18661
19008
  return [];
18662
19009
  }
18663
19010
 
@@ -26437,7 +26784,11 @@ var KNOWN_DENIED_IN_PROJECT = [
26437
26784
  { key: "yolo", reason: "Disables all permission confirmation prompts." },
26438
26785
  { key: "extensions", reason: "Per-plugin config can carry command/credential fields." },
26439
26786
  { key: "hq", reason: "Carries HQ client token credential and endpoint URL." },
26440
- { key: "acp", reason: "Per-agent ACP command/args/env override \u2192 arbitrary command exec (RCE)." }
26787
+ { key: "acp", reason: "Per-agent ACP command/args/env override \u2192 arbitrary command exec (RCE)." },
26788
+ {
26789
+ key: "fleet",
26790
+ reason: "Fleet supervision knobs: a repo-committed config could enable autonomous subagent spawning/steering/termination and mailbox traffic on the victim machine."
26791
+ }
26441
26792
  ];
26442
26793
  var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
26443
26794
  "version",
@@ -26479,7 +26830,8 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
26479
26830
  "hq",
26480
26831
  "sync",
26481
26832
  "extensions",
26482
- "acp"
26833
+ "acp",
26834
+ "fleet"
26483
26835
  ]);
26484
26836
  function assertInProjectAllowListComplete() {
26485
26837
  const missingFromBoth = [];