sensorium-mcp 3.1.2-board.1 → 3.1.2

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 (89) hide show
  1. package/dist/dashboard/routes/types.d.ts +0 -6
  2. package/dist/dashboard/routes/types.d.ts.map +1 -1
  3. package/dist/dashboard/routes/types.js.map +1 -1
  4. package/dist/dashboard/routes.d.ts.map +1 -1
  5. package/dist/dashboard/routes.js +0 -32
  6. package/dist/dashboard/routes.js.map +1 -1
  7. package/dist/dashboard/spa.html +12 -12
  8. package/dist/data/memory/migration-runner.d.ts.map +1 -1
  9. package/dist/data/memory/migration-runner.js +0 -4
  10. package/dist/data/memory/migration-runner.js.map +1 -1
  11. package/dist/data/memory/schema.d.ts.map +1 -1
  12. package/dist/data/memory/schema.js +0 -4
  13. package/dist/data/memory/schema.js.map +1 -1
  14. package/dist/index.js +2 -39
  15. package/dist/index.js.map +1 -1
  16. package/dist/server/http-server.d.ts +1 -3
  17. package/dist/server/http-server.d.ts.map +1 -1
  18. package/dist/server/http-server.js +1 -3
  19. package/dist/server/http-server.js.map +1 -1
  20. package/dist/services/spawn-common.d.ts.map +1 -1
  21. package/dist/services/spawn-common.js +1 -3
  22. package/dist/services/spawn-common.js.map +1 -1
  23. package/dist/services/worker-cleanup.service.d.ts +1 -6
  24. package/dist/services/worker-cleanup.service.d.ts.map +1 -1
  25. package/dist/services/worker-cleanup.service.js +10 -123
  26. package/dist/services/worker-cleanup.service.js.map +1 -1
  27. package/dist/tools/delegate-tool.d.ts.map +1 -1
  28. package/dist/tools/delegate-tool.js +0 -32
  29. package/dist/tools/delegate-tool.js.map +1 -1
  30. package/dist/tools/wait/message-delivery.d.ts.map +1 -1
  31. package/dist/tools/wait/message-delivery.js +0 -13
  32. package/dist/tools/wait/message-delivery.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/dashboard/routes/board.d.ts +0 -67
  35. package/dist/dashboard/routes/board.d.ts.map +0 -1
  36. package/dist/dashboard/routes/board.js +0 -612
  37. package/dist/dashboard/routes/board.js.map +0 -1
  38. package/dist/data/board/board-schema.d.ts +0 -22
  39. package/dist/data/board/board-schema.d.ts.map +0 -1
  40. package/dist/data/board/board-schema.js +0 -103
  41. package/dist/data/board/board-schema.js.map +0 -1
  42. package/dist/data/board/board-types.d.ts +0 -117
  43. package/dist/data/board/board-types.d.ts.map +0 -1
  44. package/dist/data/board/board-types.js +0 -9
  45. package/dist/data/board/board-types.js.map +0 -1
  46. package/dist/data/board/card.repository.d.ts +0 -64
  47. package/dist/data/board/card.repository.d.ts.map +0 -1
  48. package/dist/data/board/card.repository.js +0 -272
  49. package/dist/data/board/card.repository.js.map +0 -1
  50. package/dist/data/board/comment.repository.d.ts +0 -12
  51. package/dist/data/board/comment.repository.d.ts.map +0 -1
  52. package/dist/data/board/comment.repository.js +0 -31
  53. package/dist/data/board/comment.repository.js.map +0 -1
  54. package/dist/data/board/lane.repository.d.ts +0 -18
  55. package/dist/data/board/lane.repository.d.ts.map +0 -1
  56. package/dist/data/board/lane.repository.js +0 -44
  57. package/dist/data/board/lane.repository.js.map +0 -1
  58. package/dist/data/board/profile-defaults.d.ts +0 -19
  59. package/dist/data/board/profile-defaults.d.ts.map +0 -1
  60. package/dist/data/board/profile-defaults.js +0 -118
  61. package/dist/data/board/profile-defaults.js.map +0 -1
  62. package/dist/services/board/board-signals.d.ts +0 -25
  63. package/dist/services/board/board-signals.d.ts.map +0 -1
  64. package/dist/services/board/board-signals.js +0 -83
  65. package/dist/services/board/board-signals.js.map +0 -1
  66. package/dist/services/board/card-briefing.d.ts +0 -17
  67. package/dist/services/board/card-briefing.d.ts.map +0 -1
  68. package/dist/services/board/card-briefing.js +0 -86
  69. package/dist/services/board/card-briefing.js.map +0 -1
  70. package/dist/services/board/orchestrator.service.d.ts +0 -55
  71. package/dist/services/board/orchestrator.service.d.ts.map +0 -1
  72. package/dist/services/board/orchestrator.service.js +0 -198
  73. package/dist/services/board/orchestrator.service.js.map +0 -1
  74. package/dist/services/board/release-worker.d.ts +0 -32
  75. package/dist/services/board/release-worker.d.ts.map +0 -1
  76. package/dist/services/board/release-worker.js +0 -49
  77. package/dist/services/board/release-worker.js.map +0 -1
  78. package/dist/services/board/report-capture.d.ts +0 -48
  79. package/dist/services/board/report-capture.d.ts.map +0 -1
  80. package/dist/services/board/report-capture.js +0 -172
  81. package/dist/services/board/report-capture.js.map +0 -1
  82. package/dist/services/board/worker-spawn.d.ts +0 -30
  83. package/dist/services/board/worker-spawn.d.ts.map +0 -1
  84. package/dist/services/board/worker-spawn.js +0 -90
  85. package/dist/services/board/worker-spawn.js.map +0 -1
  86. package/dist/services/passive-notice.service.d.ts +0 -39
  87. package/dist/services/passive-notice.service.d.ts.map +0 -1
  88. package/dist/services/passive-notice.service.js +0 -274
  89. package/dist/services/passive-notice.service.js.map +0 -1
@@ -1,83 +0,0 @@
1
- /**
2
- * Board awareness signals — exactly three kinds, one line each, no description
3
- * (invariant #4). STARTED / DONE / BLOCKED go to the card's LANE thread.
4
- *
5
- * Delivery mode is deliberately isolated in this one function while decision
6
- * #2 is with the operator (thread 1327, 2026-08-01):
7
- * - "passive" (default, orchestrator's lean): rides the passive-notice
8
- * channel — seen on the lane's next delivered turn, never acted on.
9
- * - "mailbox": appended to the lane's pending-task mailbox (spec §3 wording)
10
- * — wakes the lane agent with an FYI-framed line.
11
- * Flip via BOARD_SIGNAL_DELIVERY=mailbox; no other code changes.
12
- */
13
- import { appendFileSync } from "node:fs";
14
- import { join } from "node:path";
15
- import { log } from "../../lib/logger.js";
16
- import { errorMessage } from "../../lib/utils.js";
17
- import { ensureDirs, PENDING_TASKS_DIR } from "../process.service.js";
18
- import { pushPassiveNotice } from "../passive-notice.service.js";
19
- /** Worker-authored text entering another thread's context block is an
20
- * instruction-injection surface: strip control chars (no fake newline-framed
21
- * "directives"), drop backticks, and cap length so the excerpt reads as an
22
- * inert quoted fragment, never as markup or a fresh instruction line. */
23
- function sanitizeExcerpt(text, maxLen) {
24
- return (text
25
- // eslint-disable-next-line no-control-regex -- stripping control chars is the point
26
- .replace(/[\x00-\x1f\x7f]+/g, " ")
27
- .replace(/`/g, "'")
28
- .replace(/\s+/g, " ")
29
- .trim()
30
- .slice(0, maxLen));
31
- }
32
- function deliveryMode() {
33
- return process.env.BOARD_SIGNAL_DELIVERY === "mailbox" ? "mailbox" : "passive";
34
- }
35
- /** One-line, FYI-framed state transition. `detail` is used ONLY for BLOCKED
36
- * (a one-line reason is what makes the ping actionable for the operator). */
37
- export function sendBoardSignal(card, kind, detail) {
38
- const suffix = kind === "STARTED" && card.workerThreadId != null
39
- ? ` (worker ${card.workerThreadId})`
40
- : kind === "BLOCKED" && detail
41
- ? ` — "${sanitizeExcerpt(detail.split("\n")[0], 160)}"`
42
- : "";
43
- const line = `[Board FYI — no action needed] Card #${card.cardId} "${card.title}" ${kind}${suffix}`;
44
- try {
45
- if (deliveryMode() === "mailbox") {
46
- ensureDirs();
47
- appendFileSync(join(PENDING_TASKS_DIR, `${card.laneThreadId}.txt`), line + "\n", "utf-8");
48
- }
49
- else {
50
- pushPassiveNotice(card.laneThreadId, line);
51
- }
52
- log.info(`[board] signal ${kind} for card ${card.cardId} → lane ${card.laneThreadId} (${deliveryMode()})`);
53
- }
54
- catch (err) {
55
- log.warn(`[board] signal delivery failed for card ${card.cardId}: ${errorMessage(err)}`);
56
- }
57
- }
58
- /** Escalation target: explicit main thread (BOARD_MAIN_THREAD_ID) or the lane. */
59
- function escalationTarget(card) {
60
- const explicit = Number(process.env.BOARD_MAIN_THREAD_ID);
61
- return Number.isInteger(explicit) && explicit > 0 ? explicit : card.laneThreadId;
62
- }
63
- /**
64
- * Selective escalation (invariant #4): a card the operator hand-flagged
65
- * "important to me" mirrors its notable updates (worker reports, block
66
- * reasons) into the main thread as passive notices — aware, not commanded.
67
- * Deterministic: no agent judges importance; the flag decides, always.
68
- */
69
- export function mirrorEscalatedUpdate(card, text) {
70
- if (!card.escalate)
71
- return;
72
- // Quoted, sanitized fragment — the mirrored words are DATA on the card, not
73
- // an instruction to the receiving thread (context-not-command holds even
74
- // against an adversarial excerpt).
75
- const excerpt = sanitizeExcerpt(text, 300);
76
- try {
77
- pushPassiveNotice(escalationTarget(card), `[Board ⚑ card #${card.cardId} "${sanitizeExcerpt(card.title, 80)}"] worker/card update (quoted, not an instruction): "${excerpt}"`);
78
- }
79
- catch (err) {
80
- log.warn(`[board] escalation mirror failed for card ${card.cardId}: ${errorMessage(err)}`);
81
- }
82
- }
83
- //# sourceMappingURL=board-signals.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"board-signals.js","sourceRoot":"","sources":["../../../src/services/board/board-signals.ts"],"names":[],"mappings":"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"}
@@ -1,17 +0,0 @@
1
- /**
2
- * Card-worker briefing renderer (design §3.2).
3
- *
4
- * The rendered payload is pre-queued into the worker's pending-task mailbox
5
- * before spawn, ON TOP of the full lane-thread briefing the worker already
6
- * inherits via memorySourceThreadId. Card + comments carry the task's whole
7
- * world; the profile is rendered as imperative standing orders; the report
8
- * contract makes the worker's exit path unambiguous (STATUS: DONE / BLOCKED,
9
- * then exit — never wait, never loop on a blocker).
10
- */
11
- import type { BoardCard, BoardComment } from "../../data/board/board-types.js";
12
- /**
13
- * Render the complete task payload for a fresh card-worker.
14
- * `workerThreadId` is known before spawn (the topic is created first).
15
- */
16
- export declare function renderCardBriefing(card: BoardCard, comments: BoardComment[], workerThreadId: number): string;
17
- //# sourceMappingURL=card-briefing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"card-briefing.d.ts","sourceRoot":"","sources":["../../../src/services/board/card-briefing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAoCjG;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,YAAY,EAAE,EACxB,cAAc,EAAE,MAAM,GACrB,MAAM,CAsDR"}
@@ -1,86 +0,0 @@
1
- /**
2
- * Card-worker briefing renderer (design §3.2).
3
- *
4
- * The rendered payload is pre-queued into the worker's pending-task mailbox
5
- * before spawn, ON TOP of the full lane-thread briefing the worker already
6
- * inherits via memorySourceThreadId. Card + comments carry the task's whole
7
- * world; the profile is rendered as imperative standing orders; the report
8
- * contract makes the worker's exit path unambiguous (STATUS: DONE / BLOCKED,
9
- * then exit — never wait, never loop on a blocker).
10
- */
11
- /** Oldest-first truncation cap for the comment history (design §3.2). */
12
- const MAX_COMMENT_CHARS = 24_000;
13
- function renderProfileDirectives(profile, cardId) {
14
- const d = [];
15
- if (profile.repository)
16
- d.push(`Work in repository: ${profile.repository}.`);
17
- d.push(profile.createBranch
18
- ? `Create a work branch (suggested: card-${cardId}) before changing anything.`
19
- : "Do NOT create a new branch unless the current branch is unusable.");
20
- if (profile.skills?.length)
21
- d.push(`Load these skills via get_skill first: ${profile.skills.join(", ")}.`);
22
- d.push(profile.runTests
23
- ? "Run the relevant tests and make them pass."
24
- : "Do not run test suites unless a change demands it.");
25
- if (profile.build)
26
- d.push("Run the build and make it pass.");
27
- d.push(profile.push ? "Push your commits." : "Do NOT push.");
28
- d.push(profile.openPr ? "Open a pull request with the changes." : "Do NOT open a pull request.");
29
- if (profile.expertReview)
30
- d.push(`Before finishing, delegate an expert code review of your diff to a ${profile.reviewAgentType ?? "claude"} worker and address its findings.`);
31
- if (profile.ciCheck && profile.ciCheck !== "none")
32
- d.push(`After pushing, verify CI is green on ${profile.ciCheck === "github" ? "GitHub" : "Azure DevOps"} before reporting DONE.`);
33
- if (profile.operatorReview)
34
- d.push("The operator wants to review the result — say so explicitly in your final report.");
35
- return d;
36
- }
37
- /**
38
- * Render the complete task payload for a fresh card-worker.
39
- * `workerThreadId` is known before spawn (the topic is created first).
40
- */
41
- export function renderCardBriefing(card, comments, workerThreadId) {
42
- const lines = [];
43
- lines.push(`# Task Board card #${card.cardId}: ${card.title}`);
44
- lines.push(`You are a STATELESS card-worker (thread ${workerThreadId}) executing this card for lane thread ${card.laneThreadId}. ` +
45
- `This card plus the history below is the task's complete context on top of your inherited thread briefing.`);
46
- lines.push("");
47
- lines.push(`Type: ${card.type} | Priority: ${card.priority}`);
48
- lines.push("");
49
- lines.push("## Description");
50
- lines.push(card.description || "(none — the title is the task)");
51
- if (/implementation_plans[\\/][\w.-]+\.md/.test(card.description)) {
52
- lines.push("");
53
- lines.push("The description references an implementation_plans/ file — READ THAT FILE FIRST and in full; it is the task's spec.");
54
- }
55
- if (card.acceptance) {
56
- lines.push("");
57
- lines.push("## Acceptance criteria");
58
- lines.push(card.acceptance);
59
- }
60
- lines.push("");
61
- lines.push("## Standing orders (execution profile — do not ask, these ARE the answers)");
62
- for (const directive of renderProfileDirectives(card.profile, card.cardId))
63
- lines.push(`- ${directive}`);
64
- if (comments.length > 0) {
65
- let history = comments
66
- .map((c) => `[${c.createdAt}] ${c.author.toUpperCase()}:\n${c.content}`)
67
- .join("\n---\n");
68
- if (history.length > MAX_COMMENT_CHARS) {
69
- history = "[…older history truncated…]\n" + history.slice(history.length - MAX_COMMENT_CHARS);
70
- }
71
- lines.push("");
72
- lines.push("## Card history (oldest first — prior workers' reports and operator guidance)");
73
- lines.push(history);
74
- }
75
- lines.push("");
76
- lines.push("## Report contract (MANDATORY)");
77
- lines.push(`When finished, call send_message_to_thread with mode:"reply", targetThreadId:${card.laneThreadId}, ` +
78
- `senderThreadId:${workerThreadId}, senderName:"card-${card.cardId}". The message MUST start with its first line being exactly ` +
79
- `"STATUS: DONE" or "STATUS: BLOCKED: <one-line reason>", followed by your real report (what you did, ` +
80
- `commits/PR links, review results, anything the next worker or the operator needs).`);
81
- lines.push("Then EXIT: do not call wait_for_instructions again, do not wait for replies, do not loop on a blocker — " +
82
- "if you are stuck or need a decision, report STATUS: BLOCKED with the reason and exit. A fresh worker " +
83
- "will be re-briefed from the card when the operator responds.");
84
- return lines.join("\n");
85
- }
86
- //# sourceMappingURL=card-briefing.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"card-briefing.js","sourceRoot":"","sources":["../../../src/services/board/card-briefing.ts"],"names":[],"mappings":"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"}
@@ -1,55 +0,0 @@
1
- /**
2
- * Board orchestrator — the server-side loop that drains the board (design §3).
3
- *
4
- * Tick (default 30 s): for each CONNECTED lane, while the lane has free
5
- * concurrency slots (default 1) and the global cap (default 3) is not reached,
6
- * dispatch the top-priority Backlog card: spawn a stateless card-worker,
7
- * move the card to In Progress, and emit the STARTED signal. No idle-gate on
8
- * the lane thread — workers are independent processes; the lane may be dormant.
9
- *
10
- * Spawn failure is an immediate BLOCKED (reason on the card) — the operator
11
- * hears about it once, on the card, per the fire-and-forget metric.
12
- *
13
- * Report capture / exit reconciliation / recurrence land in Phases 5 and 7.
14
- */
15
- import type { initMemoryDb } from "../../data/memory/index.js";
16
- import type { ThreadLifecycleService } from "../thread-lifecycle.service.js";
17
- import { spawnCardWorker } from "./worker-spawn.js";
18
- export declare function boardTickMs(): number;
19
- export declare function boardGlobalMaxConcurrent(): number;
20
- /** Kill-switch: BOARD_ENABLED=false skips orchestrator start entirely (the
21
- * board stays a dormant UI/API). Default: enabled. */
22
- export declare function boardEnabled(): boolean;
23
- export interface BoardOrchestratorDeps {
24
- getMemoryDb: () => ReturnType<typeof initMemoryDb>;
25
- telegram: {
26
- createForumTopic(chatId: string, name: string): Promise<{
27
- message_thread_id: number;
28
- }>;
29
- sendMessage(chatId: string, text: string, parseMode?: string, threadId?: number): Promise<unknown>;
30
- deleteForumTopic(chatId: string, threadId: number): Promise<void>;
31
- };
32
- chatId: string;
33
- threadLifecycle: ThreadLifecycleService;
34
- /** Test seam — defaults to the real card-worker spawn path. */
35
- spawnFn?: typeof spawnCardWorker;
36
- }
37
- export declare class BoardOrchestrator {
38
- private readonly deps;
39
- private timer;
40
- private tickInFlight;
41
- constructor(deps: BoardOrchestratorDeps);
42
- start(): void;
43
- stop(): void;
44
- tick(): Promise<void>;
45
- /**
46
- * Board-owned recurrence (design §4): a Done recurring card whose delay has
47
- * elapsed — and whose group has no live instance (enforced in SQL) — puts a
48
- * fresh identical copy back into Backlog. The original's next_spawn_at is
49
- * cleared so it never respawns twice; the copy carries the group id forward.
50
- * schedule_wake_up is untouched — this is a different mechanism by design.
51
- */
52
- private respawnDueRecurrences;
53
- private dispatchDueCards;
54
- }
55
- //# sourceMappingURL=orchestrator.service.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"orchestrator.service.d.ts","sourceRoot":"","sources":["../../../src/services/board/orchestrator.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAiB7E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOpD,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;uDACuD;AACvD,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACnD,QAAQ,EAAE;QACR,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;YAAE,iBAAiB,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACvF,WAAW,CACT,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAAC;QACpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnE,CAAC;IACF,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,sBAAsB,CAAC;IACxC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,eAAe,CAAC;CAClC;AAED,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAS;gBAEA,IAAI,EAAE,qBAAqB;IAExD,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAKN,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;YAiDf,gBAAgB;CAyD/B"}
@@ -1,198 +0,0 @@
1
- /**
2
- * Board orchestrator — the server-side loop that drains the board (design §3).
3
- *
4
- * Tick (default 30 s): for each CONNECTED lane, while the lane has free
5
- * concurrency slots (default 1) and the global cap (default 3) is not reached,
6
- * dispatch the top-priority Backlog card: spawn a stateless card-worker,
7
- * move the card to In Progress, and emit the STARTED signal. No idle-gate on
8
- * the lane thread — workers are independent processes; the lane may be dormant.
9
- *
10
- * Spawn failure is an immediate BLOCKED (reason on the card) — the operator
11
- * hears about it once, on the card, per the fire-and-forget metric.
12
- *
13
- * Report capture / exit reconciliation / recurrence land in Phases 5 and 7.
14
- */
15
- import { log } from "../../lib/logger.js";
16
- import { errorMessage } from "../../lib/utils.js";
17
- import { archiveDoneOlderThan, bindWorkerToCard, claimCardForDispatch, countInProgress, createCard, getCard, getNextDispatchableCard, listCards, listDueRecurrences, updateCard, } from "../../data/board/card.repository.js";
18
- import { reconcileInProgressCards } from "./report-capture.js";
19
- import { addComment, listComments } from "../../data/board/comment.repository.js";
20
- import { listConnectedLanes } from "../../data/board/lane.repository.js";
21
- import { sendBoardSignal } from "./board-signals.js";
22
- import { spawnCardWorker } from "./worker-spawn.js";
23
- // Board env knobs are read PER CALL, consistently with BOARD_SIGNAL_DELIVERY /
24
- // BOARD_MAIN_THREAD_ID in board-signals (a load-time snapshot for some knobs
25
- // and live reads for others was a review nit — and per-call lets the cap be
26
- // changed without a restart). The tick interval alone is bound at start().
27
- export function boardTickMs() {
28
- const s = Number(process.env.BOARD_TICK_SECONDS);
29
- return Number.isFinite(s) && s >= 5 ? s * 1000 : 30_000;
30
- }
31
- export function boardGlobalMaxConcurrent() {
32
- const n = Number(process.env.BOARD_MAX_CONCURRENT);
33
- return Number.isInteger(n) && n > 0 ? n : 3;
34
- }
35
- /** Kill-switch: BOARD_ENABLED=false skips orchestrator start entirely (the
36
- * board stays a dormant UI/API). Default: enabled. */
37
- export function boardEnabled() {
38
- return process.env.BOARD_ENABLED !== "false";
39
- }
40
- export class BoardOrchestrator {
41
- deps;
42
- timer = null;
43
- tickInFlight = false;
44
- constructor(deps) {
45
- this.deps = deps;
46
- }
47
- start() {
48
- if (this.timer)
49
- return;
50
- if (!boardEnabled()) {
51
- log.info("[board] orchestrator disabled via BOARD_ENABLED=false — board is UI/API only");
52
- return;
53
- }
54
- const tickMs = boardTickMs();
55
- this.timer = setInterval(() => void this.tick(), tickMs);
56
- this.timer.unref();
57
- void this.tick();
58
- log.info(`[board] orchestrator started (tick ${tickMs / 1000}s, global cap ${boardGlobalMaxConcurrent()})`);
59
- }
60
- stop() {
61
- if (this.timer)
62
- clearInterval(this.timer);
63
- this.timer = null;
64
- }
65
- async tick() {
66
- if (this.tickInFlight)
67
- return; // a slow spawn must not stack ticks
68
- this.tickInFlight = true;
69
- try {
70
- // Backstop first (covers server restarts and silent worker deaths), so a
71
- // freed lane slot can be re-used by the dispatch pass in the same tick.
72
- const db = this.deps.getMemoryDb();
73
- const blocked = await reconcileInProgressCards(db, () => listCards(db, { status: "in_progress" }));
74
- if (blocked > 0)
75
- log.warn(`[board] reconciled ${blocked} orphaned in_progress card(s) → blocked`);
76
- this.respawnDueRecurrences(db);
77
- await this.dispatchDueCards();
78
- // Done-column hygiene: hide (never delete) finished one-shots after 14 days.
79
- const archived = archiveDoneOlderThan(db, 14);
80
- if (archived > 0)
81
- log.info(`[board] auto-archived ${archived} old done card(s)`);
82
- }
83
- catch (err) {
84
- log.error(`[board] tick failed: ${errorMessage(err)}`);
85
- }
86
- finally {
87
- this.tickInFlight = false;
88
- }
89
- }
90
- /**
91
- * Board-owned recurrence (design §4): a Done recurring card whose delay has
92
- * elapsed — and whose group has no live instance (enforced in SQL) — puts a
93
- * fresh identical copy back into Backlog. The original's next_spawn_at is
94
- * cleared so it never respawns twice; the copy carries the group id forward.
95
- * schedule_wake_up is untouched — this is a different mechanism by design.
96
- */
97
- respawnDueRecurrences(db) {
98
- // One respawn per group per tick even if the query returns several due
99
- // done rows of the same group (crash legacy can leave two rows with
100
- // next_spawn_at set — see the group-wide clear below, which heals that).
101
- const seenGroups = new Set();
102
- const nowIso = new Date().toISOString();
103
- for (const original of listDueRecurrences(db, nowIso)) {
104
- const groupId = original.recurGroupId ?? original.cardId;
105
- if (seenGroups.has(groupId))
106
- continue;
107
- seenGroups.add(groupId);
108
- // Copy-insert and spawn-time clear are one atomic unit: a crash between
109
- // them must never leave BOTH a live copy and a still-due original (H3).
110
- const respawn = db.transaction(() => {
111
- // M6: the group-wide clear runs FIRST as a compare-and-set — its WHERE
112
- // carries the due predicate, so it updates rows only while a due
113
- // original still exists. Two overlapping orchestrators that both read
114
- // the same due original race here; exactly one wins (changes > 0) and
115
- // creates the copy — the loser sees changes === 0 and skips, so no
116
- // duplicate copy is inserted. The group-wide (not one-row) scope also
117
- // heals legacy state where a crash left several due rows in the group.
118
- const cleared = db
119
- .prepare(`UPDATE board_cards SET next_spawn_at = NULL
120
- WHERE recur_group_id = ? AND status = 'done'
121
- AND next_spawn_at IS NOT NULL AND next_spawn_at <= ?`)
122
- .run(groupId, nowIso);
123
- if (cleared.changes === 0)
124
- return undefined;
125
- const copy = createCard(db, {
126
- title: original.title,
127
- description: original.description,
128
- acceptance: original.acceptance,
129
- type: original.type,
130
- laneThreadId: original.laneThreadId,
131
- priority: original.priority,
132
- profile: original.profile,
133
- escalate: original.escalate,
134
- recurDelayMin: original.recurDelayMin ?? undefined,
135
- recurGroupId: groupId,
136
- });
137
- addComment(db, copy.cardId, "board", `Respawned from recurring card #${original.cardId}.`);
138
- addComment(db, original.cardId, "board", `Recurring: fresh copy #${copy.cardId} queued.`);
139
- return copy;
140
- })();
141
- if (respawn)
142
- log.info(`[board] recurring card ${original.cardId} respawned as ${respawn.cardId}`);
143
- }
144
- }
145
- async dispatchDueCards() {
146
- const db = this.deps.getMemoryDb();
147
- const globalCap = boardGlobalMaxConcurrent();
148
- let totalInProgress = countInProgress(db);
149
- for (const lane of listConnectedLanes(db)) {
150
- while (totalInProgress < globalCap) {
151
- if (countInProgress(db, lane.threadId) >= lane.maxConcurrent)
152
- break;
153
- const card = getNextDispatchableCard(db, lane.threadId);
154
- if (!card)
155
- break;
156
- // Atomic compare-and-set claim BEFORE the (slow, async) spawn: a
157
- // concurrent orchestrator (self-update overlap) or an operator move
158
- // loses or wins here exactly once — never both spawning (H1).
159
- if (!claimCardForDispatch(db, card.cardId))
160
- continue;
161
- const spawn = await (this.deps.spawnFn ?? spawnCardWorker)({
162
- db,
163
- telegram: this.deps.telegram,
164
- chatId: this.deps.chatId,
165
- threadLifecycle: this.deps.threadLifecycle,
166
- }, card, listComments(db, card.cardId));
167
- if ("error" in spawn) {
168
- // One BLOCKED touch instead of a silent retry loop: the reason is on
169
- // the card; a later operator resume re-dispatches cleanly.
170
- log.warn(`[board] dispatch of card ${card.cardId} failed: ${spawn.error}`);
171
- addComment(db, card.cardId, "board", `Dispatch failed: ${spawn.error}`);
172
- const blocked = updateCard(db, card.cardId, {
173
- status: "blocked",
174
- blockedReason: `Dispatch failed: ${spawn.error}`,
175
- });
176
- if (blocked)
177
- sendBoardSignal(blocked, "BLOCKED", spawn.error);
178
- continue;
179
- }
180
- // Guarded bind: only while the claim still stands. If the operator
181
- // moved/deleted the card during the spawn, their action wins — log the
182
- // orphaned worker instead of clobbering the card (TOCTOU close).
183
- if (!bindWorkerToCard(db, card.cardId, spawn.workerThreadId)) {
184
- log.warn(`[board] card ${card.cardId} changed during spawn — worker ${spawn.workerThreadId} left to idle out (worker TTL reaps it)`);
185
- continue;
186
- }
187
- addComment(db, card.cardId, "board", `Worker ${spawn.workerThreadId} spawned.`);
188
- const updated = getCard(db, card.cardId);
189
- if (updated)
190
- sendBoardSignal(updated, "STARTED");
191
- totalInProgress++;
192
- }
193
- if (totalInProgress >= globalCap)
194
- break;
195
- }
196
- }
197
- }
198
- //# sourceMappingURL=orchestrator.service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"orchestrator.service.js","sourceRoot":"","sources":["../../../src/services/board/orchestrator.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,UAAU,EACV,OAAO,EACP,uBAAuB,EACvB,SAAS,EACT,kBAAkB,EAClB,UAAU,GACX,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,2EAA2E;AAE3E,MAAM,UAAU,WAAW;IACzB,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;uDACuD;AACvD,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,OAAO,CAAC;AAC/C,CAAC;AAoBD,MAAM,OAAO,iBAAiB;IAIC;IAHrB,KAAK,GAA0C,IAAI,CAAC;IACpD,YAAY,GAAG,KAAK,CAAC;IAE7B,YAA6B,IAA2B;QAA3B,SAAI,GAAJ,IAAI,CAAuB;IAAG,CAAC;IAE5D,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACjB,GAAG,CAAC,IAAI,CACN,sCAAsC,MAAM,GAAG,IAAI,iBAAiB,wBAAwB,EAAE,GAAG,CAClG,CAAC;IACJ,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK;YAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,CAAC,oCAAoC;QACnE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,yEAAyE;YACzE,wEAAwE;YACxE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC,EAAE,EAAE,GAAG,EAAE,CACtD,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CACzC,CAAC;YACF,IAAI,OAAO,GAAG,CAAC;gBACb,GAAG,CAAC,IAAI,CAAC,sBAAsB,OAAO,yCAAyC,CAAC,CAAC;YACnF,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;YAC/B,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC9B,6EAA6E;YAC7E,MAAM,QAAQ,GAAG,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9C,IAAI,QAAQ,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,yBAAyB,QAAQ,mBAAmB,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,KAAK,CAAC,wBAAwB,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACK,qBAAqB,CAAC,EAAoD;QAChF,uEAAuE;QACvE,oEAAoE;QACpE,yEAAyE;QACzE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACxC,KAAK,MAAM,QAAQ,IAAI,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;YACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,IAAI,QAAQ,CAAC,MAAM,CAAC;YACzD,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YACtC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACxB,wEAAwE;YACxE,wEAAwE;YACxE,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;gBAClC,uEAAuE;gBACvE,iEAAiE;gBACjE,sEAAsE;gBACtE,sEAAsE;gBACtE,mEAAmE;gBACnE,sEAAsE;gBACtE,uEAAuE;gBACvE,MAAM,OAAO,GAAG,EAAE;qBACf,OAAO,CACN;;oEAEwD,CACzD;qBACA,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBACxB,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;oBAAE,OAAO,SAAS,CAAC;gBAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,EAAE;oBAC1B,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,WAAW,EAAE,QAAQ,CAAC,WAAW;oBACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,YAAY,EAAE,QAAQ,CAAC,YAAY;oBACnC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;oBAC3B,OAAO,EAAE,QAAQ,CAAC,OAAO;oBACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;oBAC3B,aAAa,EAAE,QAAQ,CAAC,aAAa,IAAI,SAAS;oBAClD,YAAY,EAAE,OAAO;iBACtB,CAAC,CAAC;gBACH,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,kCAAkC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC3F,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,0BAA0B,IAAI,CAAC,MAAM,UAAU,CAAC,CAAC;gBAC1F,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,EAAE,CAAC;YACL,IAAI,OAAO;gBACT,GAAG,CAAC,IAAI,CAAC,0BAA0B,QAAQ,CAAC,MAAM,iBAAiB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,wBAAwB,EAAE,CAAC;QAC7C,IAAI,eAAe,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;QAE1C,KAAK,MAAM,IAAI,IAAI,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1C,OAAO,eAAe,GAAG,SAAS,EAAE,CAAC;gBACnC,IAAI,eAAe,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,aAAa;oBAAE,MAAM;gBACpE,MAAM,IAAI,GAAG,uBAAuB,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACxD,IAAI,CAAC,IAAI;oBAAE,MAAM;gBAEjB,iEAAiE;gBACjE,oEAAoE;gBACpE,8DAA8D;gBAC9D,IAAI,CAAC,oBAAoB,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC;oBAAE,SAAS;gBAErD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC,CACxD;oBACE,EAAE;oBACF,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ;oBAC5B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;oBACxB,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe;iBAC3C,EACD,IAAI,EACJ,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAC9B,CAAC;gBAEF,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;oBACrB,qEAAqE;oBACrE,2DAA2D;oBAC3D,GAAG,CAAC,IAAI,CAAC,4BAA4B,IAAI,CAAC,MAAM,YAAY,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;oBAC3E,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;oBACxE,MAAM,OAAO,GAAG,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE;wBAC1C,MAAM,EAAE,SAAS;wBACjB,aAAa,EAAE,oBAAoB,KAAK,CAAC,KAAK,EAAE;qBACjD,CAAC,CAAC;oBACH,IAAI,OAAO;wBAAE,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC9D,SAAS;gBACX,CAAC;gBAED,mEAAmE;gBACnE,uEAAuE;gBACvE,iEAAiE;gBACjE,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;oBAC7D,GAAG,CAAC,IAAI,CACN,gBAAgB,IAAI,CAAC,MAAM,kCAAkC,KAAK,CAAC,cAAc,yCAAyC,CAC3H,CAAC;oBACF,SAAS;gBACX,CAAC;gBACD,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,KAAK,CAAC,cAAc,WAAW,CAAC,CAAC;gBAChF,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;gBACzC,IAAI,OAAO;oBAAE,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;gBACjD,eAAe,EAAE,CAAC;YACpB,CAAC;YACD,IAAI,eAAe,IAAI,SAAS;gBAAE,MAAM;QAC1C,CAAC;IACH,CAAC;CACF"}
@@ -1,32 +0,0 @@
1
- /**
2
- * Board-side "release worker" (R1) — the single decommission path the board
3
- * owns (design §2 R1). Whenever an operator action moves a card OUT of
4
- * in_progress by ANY path (Stop, re-run→backlog, →done, delete), the bound
5
- * worker must be killed AND torn down first, or the next tick spawns a second
6
- * worker on the same card → duplicate branch/PR/push and a lost report.
7
- *
8
- * The route layer has DB pointers but not the telegram/threadLifecycle deps
9
- * `decommissionWorker` needs; those are carried on DashboardContext so this one
10
- * function can wire them through. It is idempotent and safe to call for a card
11
- * with no live worker (already reported/dead) — decommission just does teardown.
12
- */
13
- import type { initMemoryDb } from "../../data/memory/index.js";
14
- import type { ThreadLifecycleService } from "../thread-lifecycle.service.js";
15
- export interface ReleaseWorkerDeps {
16
- db: ReturnType<typeof initMemoryDb>;
17
- telegram: {
18
- deleteForumTopic(chatId: string, threadId: number): Promise<void>;
19
- };
20
- chatId: string;
21
- threadLifecycle: ThreadLifecycleService;
22
- }
23
- /**
24
- * Decommission (kill + full teardown) the worker bound to a card, if any.
25
- * Pass the card's current workerThreadId; a null/undefined value is a no-op.
26
- *
27
- * Resolves the live PID via findAliveThread so the kill actually lands — passing
28
- * only { threadId } would make decommissionWorker treat the process as already
29
- * gone and skip the kill, leaving a live worker running (the exact B1 bug).
30
- */
31
- export declare function releaseWorkerForCard(deps: ReleaseWorkerDeps, workerThreadId: number | null | undefined): Promise<void>;
32
- //# sourceMappingURL=release-worker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-worker.d.ts","sourceRoot":"","sources":["../../../src/services/board/release-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAK7E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACpC,QAAQ,EAAE;QAAE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,sBAAsB,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,iBAAiB,EACvB,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACxC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
@@ -1,49 +0,0 @@
1
- /**
2
- * Board-side "release worker" (R1) — the single decommission path the board
3
- * owns (design §2 R1). Whenever an operator action moves a card OUT of
4
- * in_progress by ANY path (Stop, re-run→backlog, →done, delete), the bound
5
- * worker must be killed AND torn down first, or the next tick spawns a second
6
- * worker on the same card → duplicate branch/PR/push and a lost report.
7
- *
8
- * The route layer has DB pointers but not the telegram/threadLifecycle deps
9
- * `decommissionWorker` needs; those are carried on DashboardContext so this one
10
- * function can wire them through. It is idempotent and safe to call for a card
11
- * with no live worker (already reported/dead) — decommission just does teardown.
12
- */
13
- import { log } from "../../lib/logger.js";
14
- import { errorMessage } from "../../lib/utils.js";
15
- import { findAliveThread } from "../process.service.js";
16
- import { getThread } from "../../data/memory/thread-registry.js";
17
- import { decommissionWorker } from "../worker-cleanup.service.js";
18
- /**
19
- * Decommission (kill + full teardown) the worker bound to a card, if any.
20
- * Pass the card's current workerThreadId; a null/undefined value is a no-op.
21
- *
22
- * Resolves the live PID via findAliveThread so the kill actually lands — passing
23
- * only { threadId } would make decommissionWorker treat the process as already
24
- * gone and skip the kill, leaving a live worker running (the exact B1 bug).
25
- */
26
- export async function releaseWorkerForCard(deps, workerThreadId) {
27
- if (workerThreadId == null)
28
- return;
29
- try {
30
- const alive = await findAliveThread(workerThreadId);
31
- const target = alive ?? {
32
- threadId: workerThreadId,
33
- name: getThread(deps.db, workerThreadId)?.name ?? `Thread ${workerThreadId}`,
34
- };
35
- await decommissionWorker(target, {
36
- db: deps.db,
37
- telegram: deps.telegram,
38
- chatId: deps.chatId,
39
- threadLifecycle: deps.threadLifecycle,
40
- });
41
- }
42
- catch (err) {
43
- // Never let a teardown hiccup abort the operator's move — the card is about
44
- // to leave in_progress regardless; a stragging worker is caught by the TTL
45
- // sweep / dead-worker reconcile. Surface it for diagnosis.
46
- log.warn(`[board] releaseWorkerForCard(${workerThreadId}) failed: ${errorMessage(err)}`);
47
- }
48
- }
49
- //# sourceMappingURL=release-worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-worker.js","sourceRoot":"","sources":["../../../src/services/board/release-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AASlE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAuB,EACvB,cAAyC;IAEzC,IAAI,cAAc,IAAI,IAAI;QAAE,OAAO;IACnC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,KAAK,IAAI;YACtB,QAAQ,EAAE,cAAc;YACxB,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,EAAE,IAAI,IAAI,UAAU,cAAc,EAAE;SAC7E,CAAC;QACF,MAAM,kBAAkB,CAAC,MAAM,EAAE;YAC/B,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,eAAe,EAAE,IAAI,CAAC,eAAe;SACtC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,4EAA4E;QAC5E,2EAA2E;QAC3E,2DAA2D;QAC3D,GAAG,CAAC,IAAI,CAAC,gCAAgC,cAAc,aAAa,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC"}
@@ -1,48 +0,0 @@
1
- /**
2
- * Card-worker report capture (design §3.3–§3.4) — zero new MCP tools.
3
- *
4
- * A card-worker reports through the ordinary send_message_to_thread(reply)
5
- * path; the board intercepts it server-side when (and only when) the sender is
6
- * the bound worker of an in_progress card AND the target is that card's lane.
7
- * The body becomes a card comment, the STATUS first line moves the card, the
8
- * lane gets its single one-line DONE/BLOCKED signal — nothing reaches the lane
9
- * mailbox. Any other send from the worker flows untouched.
10
- *
11
- * Safety nets: handleCardWorkerExit (idempotent) blocks a card whose worker
12
- * died without a final report — fired from the worker teardown paths; and
13
- * reconcileInProgressCards is the orchestrator-tick backstop that catches
14
- * silent deaths across server restarts.
15
- */
16
- import type { initMemoryDb } from "../../data/memory/index.js";
17
- import type { BoardCard } from "../../data/board/board-types.js";
18
- type Db = ReturnType<typeof initMemoryDb>;
19
- type ReportStatus = {
20
- kind: "done";
21
- } | {
22
- kind: "blocked";
23
- reason: string;
24
- } | {
25
- kind: "interim";
26
- };
27
- /** Parse the report contract's first non-empty line. */
28
- export declare function parseReportStatus(message: string): ReportStatus;
29
- /**
30
- * Intercept a worker→lane send. Returns true when the board consumed the
31
- * message (the caller must NOT write it to the lane's mailbox).
32
- */
33
- export declare function captureCardWorkerReport(db: Db, senderThreadId: number, targetThreadId: number, message: string): boolean;
34
- /**
35
- * Idempotent exit safety net: a card-worker died/teardown'd while its card is
36
- * still in_progress and bound to it → the card is Blocked with the exit as the
37
- * reason. No-op when the worker already reported (capture unbinds the worker).
38
- */
39
- export declare function handleCardWorkerExit(db: Db, workerThreadId: number, exitCode: number | null): void;
40
- /**
41
- * Orchestrator-tick backstop (covers server restarts, where no exit event ever
42
- * fires): any in_progress card bound to a worker that is not running — and old
43
- * enough that a spawn race is impossible — goes to Blocked. Also frees cards
44
- * that were claimed but never bound a worker (dispatch died mid-flight).
45
- */
46
- export declare function reconcileInProgressCards(db: Db, listInProgress: () => BoardCard[]): Promise<number>;
47
- export {};
48
- //# sourceMappingURL=report-capture.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"report-capture.d.ts","sourceRoot":"","sources":["../../../src/services/board/report-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAW/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAE1C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjG,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAS/D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,EAAE,EACN,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAuCT;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,EAAE,EACN,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,IAAI,CAyBN;AA8BD;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,cAAc,EAAE,MAAM,SAAS,EAAE,GAChC,OAAO,CAAC,MAAM,CAAC,CAmBjB"}