@wrongstack/core 0.282.2 → 0.283.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 (47) hide show
  1. package/dist/{agent-subagent-runner-DCczSoQj.d.ts → agent-subagent-runner-BsuWhB28.d.ts} +2 -2
  2. package/dist/coordination/index.d.ts +12 -12
  3. package/dist/coordination/index.js +315 -41
  4. package/dist/coordination/index.js.map +1 -1
  5. package/dist/defaults/index.d.ts +12 -12
  6. package/dist/defaults/index.js +316 -53
  7. package/dist/defaults/index.js.map +1 -1
  8. package/dist/{events-BOv8h6I1.d.ts → events-KmxSmvho.d.ts} +5 -0
  9. package/dist/execution/index.d.ts +7 -7
  10. package/dist/extension/index.d.ts +2 -2
  11. package/dist/{global-mailbox-MDDFhLYh.d.ts → global-mailbox-rWVt8YlO.d.ts} +70 -7
  12. package/dist/{goal-store-BEmDmSKF.d.ts → goal-store-8owBXJT2.d.ts} +1 -1
  13. package/dist/hq/index.d.ts +3 -3
  14. package/dist/hq/index.js +106 -32
  15. package/dist/hq/index.js.map +1 -1
  16. package/dist/{index-Dd-PJJ8A.d.ts → index-BhCteHAF.d.ts} +1 -1
  17. package/dist/index.d.ts +289 -19
  18. package/dist/index.js +919 -110
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +1 -1
  21. package/dist/kernel/index.d.ts +4 -4
  22. package/dist/kernel/index.js.map +1 -1
  23. package/dist/models/index.js +105 -42
  24. package/dist/models/index.js.map +1 -1
  25. package/dist/{multi-agent-coordinator-BRqtpn-a.d.ts → multi-agent-coordinator-CrjeTB9i.d.ts} +1 -1
  26. package/dist/{null-fleet-bus-BkptvIVo.d.ts → null-fleet-bus-D4e9R7Qc.d.ts} +9 -4
  27. package/dist/observability/index.d.ts +1 -1
  28. package/dist/{parallel-eternal-engine-BXECuOVG.d.ts → parallel-eternal-engine-iY2uxocj.d.ts} +4 -4
  29. package/dist/{provider-runner-CJtCs1Rw.d.ts → provider-runner-oQhDaZmH.d.ts} +1 -1
  30. package/dist/sdd/index.d.ts +4 -4
  31. package/dist/sdd/index.js.map +1 -1
  32. package/dist/storage/index.d.ts +5 -5
  33. package/dist/storage/index.js.map +1 -1
  34. package/dist/{todos-checkpoint-Bw83WMh9.d.ts → todos-checkpoint-BwCrj4Cb.d.ts} +1 -1
  35. package/dist/{tool-executor-4mWN2vHW.d.ts → tool-executor-kElSEgO0.d.ts} +4 -4
  36. package/dist/types/index.d.ts +8 -8
  37. package/dist/types/index.js +105 -45
  38. package/dist/types/index.js.map +1 -1
  39. package/dist/{worktree-manager-BrtjUFYk.d.ts → worktree-manager-BjyAW30D.d.ts} +1 -1
  40. package/instructions/modes/audit-lite.md +13 -0
  41. package/instructions/modes/debug-lite.md +13 -0
  42. package/instructions/modes/plan-lite.md +14 -0
  43. package/instructions/modes/refactor-lite.md +13 -0
  44. package/instructions/modes/research-lite.md +13 -0
  45. package/instructions/modes/review-lite.md +14 -0
  46. package/instructions/modes/test-lite.md +13 -0
  47. package/package.json +1 -1
@@ -6689,6 +6689,16 @@ function getKanbanPath(projectRoot, boardId) {
6689
6689
  }
6690
6690
  return resolved;
6691
6691
  }
6692
+ function getKanbanEventsPath(projectRoot, boardId) {
6693
+ assertValidBoardId(boardId);
6694
+ const dir = path4.resolve(getKanbanDir(projectRoot));
6695
+ const resolved = path4.resolve(dir, `${boardId}.events.jsonl`);
6696
+ const rel = path4.relative(dir, resolved);
6697
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
6698
+ throw invalidBoardId(boardId);
6699
+ }
6700
+ return resolved;
6701
+ }
6692
6702
  function isValidBoardId(boardId) {
6693
6703
  return BOARD_ID_RE.test(boardId) && !boardId.includes("..");
6694
6704
  }
@@ -6741,6 +6751,14 @@ async function readBoard(projectRoot, boardRef) {
6741
6751
  throw err;
6742
6752
  }
6743
6753
  }
6754
+ async function appendKanbanEvent(projectRoot, boardId, event) {
6755
+ const filePath = getKanbanEventsPath(projectRoot, boardId);
6756
+ await withFileLock(filePath, async () => {
6757
+ await fsp7.mkdir(path4.dirname(filePath), { recursive: true });
6758
+ await fsp7.appendFile(filePath, `${JSON.stringify(event)}
6759
+ `, "utf8");
6760
+ });
6761
+ }
6744
6762
  async function mutateBoard(projectRoot, boardRef, mutator) {
6745
6763
  const boardId = await resolveBoardRef(projectRoot, boardRef);
6746
6764
  if (!boardId) return null;
@@ -6839,9 +6857,12 @@ async function listBoards(projectRoot) {
6839
6857
  return listBoardSummaries(projectRoot);
6840
6858
  }
6841
6859
  async function updateTaskAssignment(projectRoot, boardId, taskId, patch) {
6860
+ let event;
6842
6861
  const updated = await mutateBoard(projectRoot, boardId, (board) => {
6843
6862
  const task = findTask(board, taskId);
6844
6863
  if (!task) return null;
6864
+ const previousColumnId = task.columnId;
6865
+ const beforeAssignment = task.assignment ? { ...task.assignment } : void 0;
6845
6866
  const nextAssignment = {
6846
6867
  ...task.assignment ?? { status: "assigned" }
6847
6868
  };
@@ -6879,10 +6900,17 @@ async function updateTaskAssignment(projectRoot, boardId, taskId, patch) {
6879
6900
  }
6880
6901
  delete task.completedAt;
6881
6902
  }
6903
+ syncTaskColumnForStatus(board, task, previousColumnId);
6882
6904
  task.updatedAt = nowIso();
6883
6905
  board.updatedAt = task.updatedAt;
6906
+ event = createKanbanEvent(board.id, task, assignmentEventType(task.assignment.status), {
6907
+ before: beforeAssignment,
6908
+ after: { ...task.assignment },
6909
+ note: patch.error ?? patch.lastResult
6910
+ });
6884
6911
  return task;
6885
6912
  });
6913
+ if (updated && event) await emitKanbanEvent(projectRoot, event);
6886
6914
  return updated?.result ? updated.board : null;
6887
6915
  }
6888
6916
  async function claimReadyTask(projectRoot, input = {}) {
@@ -6929,14 +6957,25 @@ function buildAssignment(input) {
6929
6957
  ...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
6930
6958
  ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
6931
6959
  ...input.tools !== void 0 ? { tools: input.tools } : {},
6932
- ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {}
6960
+ ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
6961
+ ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
6962
+ ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
6963
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
6964
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
6965
+ ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
6966
+ ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
6967
+ ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
6968
+ ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
6969
+ ...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
6933
6970
  };
6934
6971
  }
6935
6972
  async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
6973
+ let event;
6936
6974
  const updated = await mutateBoard(projectRoot, boardId, (board) => {
6937
6975
  const candidates = input.taskId ? [findTask(board, input.taskId)].filter((task2) => Boolean(task2)) : board.tasks.filter((task2) => isTaskReadyForWork(board, task2)).sort(compareTasksForWork);
6938
6976
  const task = candidates.find((candidate) => isTaskReadyForWork(board, candidate));
6939
6977
  if (!task) return null;
6978
+ const previousColumnId = task.columnId;
6940
6979
  const current = task.assignment;
6941
6980
  const assignment = buildAssignment({
6942
6981
  ...current?.agentId !== void 0 ? { agentId: current.agentId } : {},
@@ -6948,6 +6987,15 @@ async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
6948
6987
  ...current?.fallbackModels !== void 0 ? { fallbackModels: current.fallbackModels } : {},
6949
6988
  ...current?.tools !== void 0 ? { tools: current.tools } : {},
6950
6989
  ...current?.allowedCapabilities !== void 0 ? { allowedCapabilities: current.allowedCapabilities } : {},
6990
+ ...current?.leaseId !== void 0 ? { leaseId: current.leaseId } : {},
6991
+ ...current?.claimedAt !== void 0 ? { claimedAt: current.claimedAt } : {},
6992
+ ...current?.heartbeatAt !== void 0 ? { heartbeatAt: current.heartbeatAt } : {},
6993
+ ...current?.leaseExpiresAt !== void 0 ? { leaseExpiresAt: current.leaseExpiresAt } : {},
6994
+ ...current?.attempt !== void 0 ? { attempt: current.attempt } : {},
6995
+ ...current?.maxAttempts !== void 0 ? { maxAttempts: current.maxAttempts } : {},
6996
+ ...current?.costCeilingUsd !== void 0 ? { costCeilingUsd: current.costCeilingUsd } : {},
6997
+ ...current?.retryPolicy !== void 0 ? { retryPolicy: current.retryPolicy } : {},
6998
+ ...current?.lastFailureKind !== void 0 ? { lastFailureKind: current.lastFailureKind } : {},
6951
6999
  ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
6952
7000
  ...input.name !== void 0 ? { name: input.name } : {},
6953
7001
  ...input.role !== void 0 ? { role: input.role } : {},
@@ -6957,10 +7005,20 @@ async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
6957
7005
  ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
6958
7006
  ...input.tools !== void 0 ? { tools: input.tools } : {},
6959
7007
  ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
7008
+ ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
7009
+ ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
7010
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
7011
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
7012
+ ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
7013
+ ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
7014
+ ...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
7015
+ ...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
7016
+ ...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {},
6960
7017
  ...input.assignee !== void 0 ? { assignee: input.assignee } : {},
6961
7018
  status: input.status ?? "queued"
6962
7019
  });
6963
- assignment.dispatchedAt = assignment.dispatchedAt ?? nowIso();
7020
+ assignment.claimedAt = assignment.claimedAt ?? nowIso();
7021
+ assignment.dispatchedAt = assignment.dispatchedAt ?? assignment.claimedAt;
6964
7022
  task.assignment = assignment;
6965
7023
  if (assignment.agentId ?? assignment.role ?? assignment.name) {
6966
7024
  task.assignedAgent = assignment.agentId ?? assignment.role ?? assignment.name;
@@ -6970,10 +7028,16 @@ async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
6970
7028
  }
6971
7029
  task.status = assignment.status === "running" ? "in_progress" : "ready";
6972
7030
  delete task.completedAt;
7031
+ syncTaskColumnForStatus(board, task, previousColumnId);
6973
7032
  task.updatedAt = nowIso();
6974
7033
  board.updatedAt = task.updatedAt;
7034
+ event = createKanbanEvent(board.id, task, "task.claimed", {
7035
+ before: current ? { ...current } : void 0,
7036
+ after: { ...assignment }
7037
+ });
6975
7038
  return task;
6976
7039
  });
7040
+ if (updated && event) await emitKanbanEvent(projectRoot, event);
6977
7041
  return updated?.result ? { board: updated.board, task: updated.result } : null;
6978
7042
  }
6979
7043
  function compareTasksForWork(a, b) {
@@ -7007,6 +7071,18 @@ function findTask(board, taskId) {
7007
7071
  }
7008
7072
  return matches[0];
7009
7073
  }
7074
+ function existingColumnId(board, columnId) {
7075
+ if (!columnId) return void 0;
7076
+ const exact = board.columns.find((column) => column.id === columnId);
7077
+ if (exact) return exact.id;
7078
+ const matches = board.columns.filter((column) => column.id.startsWith(columnId));
7079
+ if (matches.length > 1) {
7080
+ throw new Error(
7081
+ `Ambiguous kanban column id "${columnId}": ${matches.slice(0, 5).map((column) => column.id).join(", ")}`
7082
+ );
7083
+ }
7084
+ return matches[0]?.id;
7085
+ }
7010
7086
  function isTaskReadyForWork(board, task) {
7011
7087
  if (!["pending", "ready"].includes(task.status)) return false;
7012
7088
  if (task.assignment && ["queued", "running"].includes(task.assignment.status)) return false;
@@ -7014,6 +7090,60 @@ function isTaskReadyForWork(board, task) {
7014
7090
  if (!areDependenciesMet(board, task.id)) return false;
7015
7091
  return true;
7016
7092
  }
7093
+ function createKanbanEvent(boardId, task, type, details = {}) {
7094
+ return {
7095
+ id: randomUUID(),
7096
+ boardId,
7097
+ taskId: task.id,
7098
+ type,
7099
+ ts: nowIso(),
7100
+ ...task.assignment?.agentId !== void 0 ? { actor: task.assignment.agentId } : {},
7101
+ ...task.assignment?.subagentId !== void 0 ? { subagentId: task.assignment.subagentId } : {},
7102
+ ...task.assignment?.runTaskId !== void 0 ? { runTaskId: task.assignment.runTaskId } : {},
7103
+ ...details
7104
+ };
7105
+ }
7106
+ async function emitKanbanEvent(projectRoot, event) {
7107
+ try {
7108
+ await appendKanbanEvent(projectRoot, event.boardId, event);
7109
+ } catch {
7110
+ }
7111
+ }
7112
+ function assignmentEventType(status) {
7113
+ return status === "completed" ? "task.assignment.completed" : status === "failed" ? "task.assignment.failed" : status === "running" ? "task.assignment.running" : status === "cancelled" ? "task.assignment.cancelled" : "task.assignment.updated";
7114
+ }
7115
+ function columnIdForKanbanStatus(board, status) {
7116
+ const preferred = status === "completed" ? ["done", "completed"] : status === "in_progress" ? ["in-progress", "progress", "doing"] : status === "review" || status === "failed" ? ["review"] : status === "blocked" ? ["blocked", "backlog"] : status === "ready" ? ["todo", "ready", "backlog"] : status === "archived" ? ["done", "archive", "backlog"] : ["todo", "backlog"];
7117
+ for (const columnRef of preferred) {
7118
+ const columnId = existingColumnId(board, columnRef);
7119
+ if (columnId) return columnId;
7120
+ }
7121
+ return board.columns[0]?.id;
7122
+ }
7123
+ function normalizeColumnTaskOrders(board, columnId) {
7124
+ board.tasks.filter((task) => task.columnId === columnId).sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt)).forEach((task, index) => {
7125
+ task.order = index;
7126
+ });
7127
+ }
7128
+ function syncTaskColumnForStatus(board, task, previousColumnId) {
7129
+ const nextColumnId = columnIdForKanbanStatus(board, task.status);
7130
+ if (!nextColumnId || nextColumnId === task.columnId) return;
7131
+ task.columnId = nextColumnId;
7132
+ normalizeColumnTaskOrders(board, previousColumnId);
7133
+ placeTaskInColumn(board, task, nextColumnId, void 0);
7134
+ }
7135
+ function placeTaskInColumn(board, task, columnId, targetOrder) {
7136
+ const tasks = board.tasks.filter((candidate) => candidate.columnId === columnId && candidate.id !== task.id).sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt));
7137
+ const index = clampOrder(targetOrder, tasks.length);
7138
+ tasks.splice(index, 0, task);
7139
+ tasks.forEach((candidate, order) => {
7140
+ candidate.order = order;
7141
+ });
7142
+ }
7143
+ function clampOrder(order, max) {
7144
+ if (!Number.isFinite(order)) return max;
7145
+ return Math.max(0, Math.min(Math.trunc(order), max));
7146
+ }
7017
7147
  function nowIso() {
7018
7148
  return (/* @__PURE__ */ new Date()).toISOString();
7019
7149
  }
@@ -7145,6 +7275,9 @@ function makeLLMClassifier(complete) {
7145
7275
  }
7146
7276
 
7147
7277
  // src/coordination/director-tools.ts
7278
+ function nowIso2() {
7279
+ return (/* @__PURE__ */ new Date()).toISOString();
7280
+ }
7148
7281
  function makeSpawnTool(director, roster) {
7149
7282
  const inputSchema = {
7150
7283
  type: "object",
@@ -7702,8 +7835,8 @@ function makeAssignTool(director) {
7702
7835
  function makeKanbanQueueTool(director, roster) {
7703
7836
  return {
7704
7837
  name: "kanban_queue",
7705
- description: "Claim dependency-ready Kanban tasks and dispatch them into the Director fleet. Preserves per-task provider/model/role/tool routing metadata, assigns each claimed task to a spawned subagent, and can await results while writing completion back to Kanban.",
7706
- usageHint: 'Use action:"dispatch_ready" with optional boardId/taskId/maxTasks. Set awaitCompletion:true when you want this call to update Kanban to completed/failed before returning; otherwise use await_tasks and kanban mark_assignment later.',
7838
+ description: "Claim dependency-ready Kanban tasks and dispatch them into the Director fleet. Preserves per-task provider/model/role/tool routing metadata, seeds lease metadata (claimedAt, leaseExpiresAt, heartbeatAt), assigns each claimed task to a spawned subagent, instructs workers to call kanban heartbeat_assignment periodically, and can await results while writing completion back to Kanban.",
7839
+ usageHint: 'Use action:"dispatch_ready" with optional boardId/taskId/maxTasks. Set heartbeatIntervalMs (default 60s) and leaseTtlMs (default 5m) to size stale-recovery windows. Set awaitCompletion:true when you want this call to update Kanban to completed/failed before returning; otherwise use await_tasks and kanban mark_assignment later.',
7707
7840
  permission: "auto",
7708
7841
  mutating: true,
7709
7842
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN, ToolCapabilities.FS_WRITE],
@@ -7734,6 +7867,16 @@ function makeKanbanQueueTool(director, roster) {
7734
7867
  minimum: 1,
7735
7868
  description: "Optional per-assigned-task tool-call cap."
7736
7869
  },
7870
+ heartbeatIntervalMs: {
7871
+ type: "number",
7872
+ minimum: 1e3,
7873
+ description: "Suggested heartbeat interval for the worker. Default 60000 (60s). Workers are instructed to call kanban heartbeat_assignment at this cadence."
7874
+ },
7875
+ leaseTtlMs: {
7876
+ type: "number",
7877
+ minimum: 1e3,
7878
+ description: "Lease time-to-live seeded on dispatch. Default 300000 (5m). Workers should refresh via heartbeat_assignment before expiry."
7879
+ },
7737
7880
  agentId: { type: "string" },
7738
7881
  name: { type: "string" },
7739
7882
  role: { type: "string" },
@@ -7756,6 +7899,14 @@ function makeKanbanQueueTool(director, roster) {
7756
7899
  const projectRoot = ctx.projectRoot;
7757
7900
  if (!projectRoot) return { error: "kanban_queue requires ctx.projectRoot." };
7758
7901
  const maxTasks = Math.max(1, Math.min(20, Math.floor(i.maxTasks ?? 1)));
7902
+ const leaseTtlMs = Math.max(1e3, Math.floor(i.leaseTtlMs ?? 5 * 60 * 1e3));
7903
+ const claimedAt = nowIso2();
7904
+ const leaseSeeding = {
7905
+ leaseId: randomUUID(),
7906
+ claimedAt,
7907
+ heartbeatAt: claimedAt,
7908
+ leaseExpiresAt: new Date(Date.now() + leaseTtlMs).toISOString()
7909
+ };
7759
7910
  const candidateTaskIds = i.taskId !== void 0 ? [i.taskId] : i.query ? (await listReadyTasks(projectRoot, {
7760
7911
  ...i.boardId !== void 0 ? { boardId: i.boardId } : {}
7761
7912
  })).filter((candidate) => matchesKanbanQueueQuery(candidate.task, i.query ?? "")).slice(0, maxTasks).map((candidate) => candidate.task.id) : void 0;
@@ -7776,6 +7927,7 @@ function makeKanbanQueueTool(director, roster) {
7776
7927
  ...i.fallbackModels !== void 0 ? { fallbackModels: i.fallbackModels } : {},
7777
7928
  ...i.tools !== void 0 ? { tools: i.tools } : {},
7778
7929
  ...i.allowedCapabilities !== void 0 ? { allowedCapabilities: i.allowedCapabilities } : {},
7930
+ ...leaseSeeding !== void 0 ? leaseSeeding : {},
7779
7931
  status: "queued"
7780
7932
  });
7781
7933
  if (!claim) {
@@ -7784,13 +7936,34 @@ function makeKanbanQueueTool(director, roster) {
7784
7936
  }
7785
7937
  let subagentId;
7786
7938
  let runTaskId;
7939
+ const costCeiling = claim.task.assignment?.costCeilingUsd;
7940
+ if (costCeiling !== void 0) {
7941
+ const remaining = director.getRemainingBudgetUsd();
7942
+ if (remaining !== void 0 && remaining < costCeiling) {
7943
+ await updateTaskAssignment(projectRoot, claim.board.id, claim.task.id, {
7944
+ status: "failed",
7945
+ error: `Cost ceiling ${costCeiling} exceeds remaining budget ${remaining.toFixed(4)}`,
7946
+ lastResult: "Skipped by kanban_queue cost gate (Sprint 3)"
7947
+ });
7948
+ errors.push({
7949
+ taskId: claim.task.id,
7950
+ error: `Cost ceiling ${costCeiling} exceeds remaining budget ${remaining.toFixed(4)}`
7951
+ });
7952
+ continue;
7953
+ }
7954
+ }
7787
7955
  try {
7788
7956
  const config = buildKanbanSubagentConfig(claim.task, i, roster);
7789
7957
  subagentId = await director.spawn(config);
7790
7958
  runTaskId = await director.assign({
7791
7959
  id: randomUUID(),
7792
7960
  subagentId,
7793
- description: buildKanbanFleetTaskPrompt(claim.board, claim.task),
7961
+ description: buildKanbanFleetTaskPrompt(claim.board, claim.task, {
7962
+ heartbeatIntervalMs: i.heartbeatIntervalMs ?? 6e4,
7963
+ leaseTtlMs,
7964
+ leaseId: leaseSeeding.leaseId,
7965
+ leaseExpiresAt: leaseSeeding.leaseExpiresAt
7966
+ }),
7794
7967
  ...i.maxToolCalls !== void 0 ? { maxToolCalls: i.maxToolCalls } : {},
7795
7968
  ...i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : {},
7796
7969
  context: {
@@ -7888,7 +8061,9 @@ function normalizeKanbanQueueInput(input) {
7888
8061
  fallbackModels: stringArray(raw.fallbackModels),
7889
8062
  tools: stringArray(raw.tools),
7890
8063
  allowedCapabilities: stringArray(raw.allowedCapabilities),
7891
- worktree: normalizeWorktreeOverride(raw.worktree)
8064
+ worktree: normalizeWorktreeOverride(raw.worktree),
8065
+ heartbeatIntervalMs: typeof raw.heartbeatIntervalMs === "number" ? raw.heartbeatIntervalMs : void 0,
8066
+ leaseTtlMs: typeof raw.leaseTtlMs === "number" ? raw.leaseTtlMs : void 0
7892
8067
  };
7893
8068
  }
7894
8069
  function buildKanbanSubagentConfig(task, input, roster) {
@@ -7906,7 +8081,8 @@ function buildKanbanSubagentConfig(task, input, roster) {
7906
8081
  ...input.fallbackModels ?? assignment?.fallbackModels ? { fallbackModels: input.fallbackModels ?? assignment?.fallbackModels } : {},
7907
8082
  ...tools ? { tools: ensureKanbanTool(tools) } : {},
7908
8083
  ...input.allowedCapabilities ?? assignment?.allowedCapabilities ? { allowedCapabilities: input.allowedCapabilities ?? assignment?.allowedCapabilities } : {},
7909
- ...input.worktree !== void 0 ? { worktree: input.worktree } : {}
8084
+ ...input.worktree !== void 0 ? { worktree: input.worktree } : {},
8085
+ ...assignment?.costCeilingUsd !== void 0 ? { maxCostUsd: assignment.costCeilingUsd } : {}
7910
8086
  };
7911
8087
  }
7912
8088
  function ensureKanbanTool(tools) {
@@ -7926,7 +8102,7 @@ function matchesKanbanQueueQuery(task, query) {
7926
8102
  ...task.labels ?? []
7927
8103
  ].filter(Boolean).some((value) => String(value).toLowerCase().includes(normalized));
7928
8104
  }
7929
- function buildKanbanFleetTaskPrompt(board, task) {
8105
+ function buildKanbanFleetTaskPrompt(board, task, lease) {
7930
8106
  const dependencyLines = (task.dependsOn ?? []).map((depId) => board.tasks.find((candidate) => candidate.id === depId)).filter((dep) => Boolean(dep)).map((dep) => `- ${dep.title} [${dep.status}] (${dep.id})`);
7931
8107
  const checks = task.successCriteria?.map((check) => `- ${check.description}`).join("\n");
7932
8108
  const metrics = task.goalMetrics?.map(
@@ -7973,9 +8149,24 @@ ${checks}` : "",
7973
8149
  metrics ? `Goal metrics:
7974
8150
  ${metrics}` : "",
7975
8151
  task.labels?.length ? `Labels: ${task.labels.join(", ")}` : "",
8152
+ "Lease contract (Sprint 1):",
8153
+ `- leaseId: ${lease.leaseId}`,
8154
+ `- claimedAt: ${task.assignment?.claimedAt ?? "<unknown>"}`,
8155
+ `- leaseExpiresAt: ${lease.leaseExpiresAt}`,
8156
+ `- expected heartbeatIntervalMs: ${lease.heartbeatIntervalMs}`,
8157
+ `- expected leaseTtlMs: ${lease.leaseTtlMs}`,
8158
+ "",
8159
+ "Retry policy:",
8160
+ `- retryPolicy: ${task.assignment?.retryPolicy ?? task.retryPolicy ?? "<unset>"}`,
8161
+ `- maxAttempts: ${task.assignment?.maxAttempts ?? "<unset>"}`,
8162
+ `- costCeilingUsd: ${task.assignment?.costCeilingUsd ?? task.costCeilingUsd ?? "<unset>"}`,
7976
8163
  "",
7977
8164
  "Work this task end-to-end. If scope is too broad or too small, use the kanban tool to split_task or merge_tasks instead of losing traceability.",
7978
- `When you start or finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "running", "completed", or "failed". Include lastResult or error when you finish.`,
8165
+ `When you start, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "running". Include subagentId and runTaskId when you have them.`,
8166
+ `To stay alive in the queue, call kanban with action "heartbeat_assignment", boardId "${board.id}", taskId "${task.id}", and heartbeatAt set to the current time. Cadence must be <= heartbeatIntervalMs and well before leaseExpiresAt.`,
8167
+ `When you finish, call kanban with action "mark_assignment", boardId "${board.id}", taskId "${task.id}", and assignmentStatus "completed" or "failed". Include lastResult or error.`,
8168
+ `If you cannot finish in time, call kanban with action "heartbeat_assignment" to extend the lease, or with action "release_task" to release so another worker can claim. Do NOT silently abandon the assignment.`,
8169
+ 'On failure the host may call kanban with action "recover_stale" (mode: retry/release/fail); respect its decisions and do not duplicate work in parallel.',
7979
8170
  "When finished, report what changed, what you verified, and any remaining blockers."
7980
8171
  ].filter(Boolean).join("\n");
7981
8172
  }
@@ -10018,6 +10209,15 @@ var Director = class _Director {
10018
10209
  getLeaderContextPressure() {
10019
10210
  return this.leaderContextPressure;
10020
10211
  }
10212
+ /**
10213
+ * Remaining USD budget for the entire fleet (when a cap is configured).
10214
+ * Returns `undefined` when no cap was set (Infinity).
10215
+ */
10216
+ getRemainingBudgetUsd() {
10217
+ if (this.maxFleetCostUsd === Number.POSITIVE_INFINITY) return void 0;
10218
+ const totalCost = this.usage.snapshot().total?.cost ?? 0;
10219
+ return Math.max(0, this.maxFleetCostUsd - totalCost);
10220
+ }
10021
10221
  resolveMaxContext() {
10022
10222
  const resolved = typeof this.maxContext === "function" ? this.maxContext() : this.maxContext;
10023
10223
  return resolved && resolved > 0 ? resolved : 128e3;
@@ -19889,107 +20089,170 @@ var DEFAULT_MODES = [
19889
20089
  {
19890
20090
  id: "default",
19891
20091
  name: "Default",
19892
- description: "General-purpose coding assistant",
20092
+ description: "Balanced general-purpose mode; use when no special token/coverage trade-off is needed",
19893
20093
  prompt: "",
19894
- tags: ["general"]
20094
+ tags: ["general", "balanced"]
20095
+ },
20096
+ {
20097
+ id: "brief",
20098
+ name: "Brief",
20099
+ description: "Ultra-compact responses for low-context, high-speed work",
20100
+ prompt: modePrompt("brief"),
20101
+ tags: ["lite", "fast", "concise", "token-saving"],
20102
+ toolPreferences: ["read", "edit", "bash"],
20103
+ suggestedSkills: []
20104
+ },
20105
+ {
20106
+ id: "review-lite",
20107
+ name: "Review Lite",
20108
+ description: "Token-saving code review: changed files only, top correctness/security risks",
20109
+ prompt: modePrompt("review-lite"),
20110
+ tags: ["lite", "review", "quality", "token-saving"],
20111
+ toolPreferences: ["git", "diff", "read", "grep"],
20112
+ suggestedSkills: ["bug-hunter", "typescript-strict"]
20113
+ },
20114
+ {
20115
+ id: "audit-lite",
20116
+ name: "Audit Lite",
20117
+ description: "Token-saving security triage for a small diff or named file",
20118
+ prompt: modePrompt("audit-lite"),
20119
+ tags: ["lite", "security", "audit", "token-saving"],
20120
+ toolPreferences: ["grep", "read", "git"],
20121
+ suggestedSkills: ["security-scanner"]
20122
+ },
20123
+ {
20124
+ id: "plan-lite",
20125
+ name: "Plan Lite",
20126
+ description: "Token-saving planning: 3-6 actionable steps, minimal design debate",
20127
+ prompt: modePrompt("plan-lite"),
20128
+ tags: ["lite", "planning", "architecture", "token-saving"],
20129
+ toolPreferences: ["tree", "glob", "read", "grep"],
20130
+ suggestedSkills: ["refactor-planner"]
20131
+ },
20132
+ {
20133
+ id: "debug-lite",
20134
+ name: "Debug Lite",
20135
+ description: "Token-saving bug triage: one hypothesis, nearest evidence, narrow check",
20136
+ prompt: modePrompt("debug-lite"),
20137
+ tags: ["lite", "debug", "triage", "token-saving"],
20138
+ toolPreferences: ["read", "grep", "test", "logs"],
20139
+ suggestedSkills: ["bug-hunter"]
20140
+ },
20141
+ {
20142
+ id: "test-lite",
20143
+ name: "Test Lite",
20144
+ description: "Token-saving tests: one focused regression or narrow verification target",
20145
+ prompt: modePrompt("test-lite"),
20146
+ tags: ["lite", "testing", "qa", "token-saving"],
20147
+ toolPreferences: ["test", "read", "grep"],
20148
+ suggestedSkills: ["testing"]
20149
+ },
20150
+ {
20151
+ id: "refactor-lite",
20152
+ name: "Refactor Lite",
20153
+ description: "Token-saving cleanup: small scoped behavior-preserving changes",
20154
+ prompt: modePrompt("refactor-lite"),
20155
+ tags: ["lite", "refactor", "token-saving"],
20156
+ toolPreferences: ["read", "edit", "test"],
20157
+ suggestedSkills: ["typescript-strict"]
20158
+ },
20159
+ {
20160
+ id: "research-lite",
20161
+ name: "Research Lite",
20162
+ description: "Token-saving web research: one search, one authoritative fetch, short answer",
20163
+ prompt: modePrompt("research-lite"),
20164
+ tags: ["lite", "research", "web", "token-saving"],
20165
+ toolPreferences: ["search", "fetch"],
20166
+ suggestedSkills: ["research-web"]
19895
20167
  },
19896
20168
  {
19897
20169
  id: "code-reviewer",
19898
- name: "Code Reviewer",
19899
- description: "Focus on code quality, best practices, and potential bugs",
20170
+ name: "Review Deep",
20171
+ description: "Comprehensive code review across contracts, edge cases, lifecycle, errors, concurrency",
19900
20172
  prompt: modePrompt("code-reviewer"),
19901
- tags: ["review", "quality", "security"],
20173
+ tags: ["deep", "review", "quality", "security"],
19902
20174
  toolPreferences: ["read", "grep", "git", "diff", "test"],
19903
20175
  suggestedSkills: ["bug-hunter", "security-scanner", "typescript-strict", "testing"]
19904
20176
  },
19905
20177
  {
19906
20178
  id: "code-auditor",
19907
- name: "Code Auditor",
19908
- description: "Security-focused code analysis",
20179
+ name: "Audit Deep",
20180
+ description: "Comprehensive security audit with category coverage and exploitability notes",
19909
20181
  prompt: modePrompt("code-auditor"),
19910
- tags: ["security", "audit", "compliance"],
20182
+ tags: ["deep", "security", "audit", "compliance"],
19911
20183
  toolPreferences: ["grep", "read", "audit", "bash"],
19912
20184
  suggestedSkills: ["security-scanner", "bug-hunter", "audit-log"]
19913
20185
  },
19914
20186
  {
19915
20187
  id: "architect",
19916
- name: "Software Architect",
19917
- description: "Design patterns, scalability, and system design",
20188
+ name: "Architecture Deep",
20189
+ description: "Comprehensive architecture and cross-module contract analysis",
19918
20190
  prompt: modePrompt("architect"),
19919
- tags: ["architecture", "design", "scalability"],
20191
+ tags: ["deep", "architecture", "design", "scalability"],
19920
20192
  toolPreferences: ["read", "glob", "tree", "diff"],
19921
20193
  suggestedSkills: ["api-design", "refactor-planner", "node-modern", "docker-deploy"]
19922
20194
  },
19923
20195
  {
19924
20196
  id: "debugger",
19925
- name: "Debugger",
19926
- description: "Root cause analysis and error investigation",
20197
+ name: "Debug Deep",
20198
+ description: "Comprehensive root-cause analysis with traces, logs, assumptions, and verification",
19927
20199
  prompt: modePrompt("debugger"),
19928
- tags: ["debug", "investigation", "error-resolution"],
20200
+ tags: ["deep", "debug", "investigation", "error-resolution"],
19929
20201
  toolPreferences: ["read", "grep", "bash", "logs", "test"],
19930
20202
  suggestedSkills: ["bug-hunter", "audit-log", "observability"]
19931
20203
  },
19932
20204
  {
19933
20205
  id: "tester",
19934
- name: "QA Engineer",
19935
- description: "Test coverage, edge cases, and quality assurance",
20206
+ name: "Test Deep",
20207
+ description: "Comprehensive QA mode for coverage, boundaries, isolation, and integration gaps",
19936
20208
  prompt: modePrompt("tester"),
19937
- tags: ["testing", "qa", "quality"],
20209
+ tags: ["deep", "testing", "qa", "quality"],
19938
20210
  toolPreferences: ["read", "grep", "test", "bash"],
19939
20211
  suggestedSkills: ["testing", "bug-hunter", "typescript-strict"]
19940
20212
  },
19941
20213
  {
19942
20214
  id: "devops",
19943
- name: "DevOps Engineer",
19944
- description: "Infrastructure, deployment, and operations",
20215
+ name: "DevOps Deep",
20216
+ description: "Comprehensive infrastructure, deployment, observability, and operations review",
19945
20217
  prompt: modePrompt("devops"),
19946
- tags: ["devops", "infrastructure", "operations"],
20218
+ tags: ["deep", "devops", "infrastructure", "operations"],
19947
20219
  toolPreferences: ["read", "bash", "grep", "logs", "git"],
19948
20220
  suggestedSkills: ["docker-deploy", "observability", "security-scanner"]
19949
20221
  },
19950
20222
  {
19951
20223
  id: "refactorer",
19952
- name: "Refactorer",
19953
- description: "Code improvement and modernization",
20224
+ name: "Refactor Deep",
20225
+ description: "Comprehensive modernization/refactor mode with contracts and verification discipline",
19954
20226
  prompt: modePrompt("refactorer"),
19955
- tags: ["refactor", "modernization", "improvement"],
20227
+ tags: ["deep", "refactor", "modernization", "improvement"],
19956
20228
  toolPreferences: ["read", "edit", "test", "git", "grep"],
19957
20229
  suggestedSkills: ["refactor-planner", "typescript-strict", "node-modern", "testing"]
19958
20230
  },
19959
20231
  {
19960
20232
  id: "ui-design",
19961
- name: "UI Design",
19962
- description: "Design-first frontend & mobile UI work (Design Studio)",
20233
+ name: "UI Design Deep",
20234
+ description: "Comprehensive design-first frontend/mobile UI work with kit, tokens, and accessibility",
19963
20235
  prompt: modePrompt("ui-design"),
19964
- tags: ["ui", "frontend", "mobile", "design"],
20236
+ tags: ["deep", "ui", "frontend", "mobile", "design"],
19965
20237
  toolPreferences: ["design", "write", "edit", "read", "scaffold"],
19966
20238
  suggestedSkills: ["react-modern"]
19967
20239
  },
19968
- {
19969
- id: "brief",
19970
- name: "Brief",
19971
- description: "Fast, no-nonsense \u2014 get to the point",
19972
- prompt: modePrompt("brief"),
19973
- tags: ["fast", "concise", "direct"],
19974
- toolPreferences: ["read", "edit", "bash"],
19975
- suggestedSkills: []
19976
- },
19977
20240
  {
19978
20241
  id: "teach",
19979
- name: "Teach",
19980
- description: "Mentor mode \u2014 explains why, not just what",
20242
+ name: "Teach Deep",
20243
+ description: "Mentor mode with explanations, mental models, trade-offs, and takeaways",
19981
20244
  prompt: modePrompt("teach"),
19982
- tags: ["teaching", "mentor", "learning"],
20245
+ tags: ["deep", "teaching", "mentor", "learning"],
19983
20246
  toolPreferences: ["read", "edit", "explain"],
19984
20247
  suggestedSkills: ["prompt-engineering", "skill-creator", "node-modern", "typescript-strict"]
19985
20248
  },
19986
20249
  {
19987
20250
  id: "research-web",
19988
- name: "Research Web",
19989
- description: "Current-data research \u2014 search web, verify, inject findings into context",
20251
+ name: "Research Deep",
20252
+ description: "Comprehensive current-data research with cross-checking and reusable findings",
19990
20253
  prompt: modePrompt("research-web"),
19991
- tags: ["research", "web", "current-data", "up-to-date"],
19992
- toolPreferences: ["search/fetch", "search/fetch", "search", "fetch", "context_manager"],
20254
+ tags: ["deep", "research", "web", "current-data", "up-to-date"],
20255
+ toolPreferences: ["search", "fetch", "context_manager"],
19993
20256
  suggestedSkills: ["research-web", "tech-stack", "node-modern", "security-scanner", "react-modern"]
19994
20257
  }
19995
20258
  ];
@@ -29930,8 +30193,8 @@ ${body.trim()}`);
29930
30193
  if (!this.persistBackup || scope === "project-agents") return;
29931
30194
  try {
29932
30195
  const content = await this.backend.readAll(scope, this.files[scope]);
29933
- const { writeFile: writeFile9, mkdir: mkdir10 } = await import('fs/promises');
29934
- await mkdir10(this.backupDir, { recursive: true });
30196
+ const { writeFile: writeFile9, mkdir: mkdir11 } = await import('fs/promises');
30197
+ await mkdir11(this.backupDir, { recursive: true });
29935
30198
  await writeFile9(`${this.backupDir}/${scope}.md`, content, "utf8");
29936
30199
  } catch {
29937
30200
  }