sensorium-mcp 3.1.2 → 3.1.3-board.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.
- package/Install-Sensorium.ps1 +61 -0
- package/dist/board/api/api.js +61 -0
- package/dist/board/api/client.js +96 -0
- package/dist/board/components/BottomNav.js +32 -0
- package/dist/board/components/Card.js +44 -0
- package/dist/board/components/CommentComposer.js +26 -0
- package/dist/board/components/CommentList.js +39 -0
- package/dist/board/components/Dropdown.js +66 -0
- package/dist/board/components/Lane.js +40 -0
- package/dist/board/components/PriorityChip.js +17 -0
- package/dist/board/components/ProfileSummary.js +39 -0
- package/dist/board/components/StateBanner.js +22 -0
- package/dist/board/components/StatusChip.js +21 -0
- package/dist/board/components/TokenEntry.js +42 -0
- package/dist/board/config.js +56 -0
- package/dist/board/index.html +18 -0
- package/dist/board/main.js +37 -0
- package/dist/board/mock/api.mock.js +193 -0
- package/dist/board/mock/fixtures.js +164 -0
- package/dist/board/schema/policy.js +192 -0
- package/dist/board/state/auth.js +55 -0
- package/dist/board/state/useBoard.js +149 -0
- package/dist/board/state/useBuilder.js +98 -0
- package/dist/board/state/useCard.js +67 -0
- package/dist/board/styles/board.css +1760 -0
- package/dist/board/styles/tokens.css +71 -0
- package/dist/board/vendor/vue.esm-browser.js +9 -0
- package/dist/board/views/App.js +65 -0
- package/dist/board/views/BoardView.js +84 -0
- package/dist/board/views/BuilderView.js +303 -0
- package/dist/board/views/CardView.js +245 -0
- package/dist/board/views/SettingsView.js +145 -0
- package/dist/config/agent-settings.d.ts +2 -0
- package/dist/config/agent-settings.d.ts.map +1 -1
- package/dist/config/agent-settings.js +23 -0
- package/dist/config/agent-settings.js.map +1 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/misc-settings.d.ts +14 -0
- package/dist/config/misc-settings.d.ts.map +1 -1
- package/dist/config/misc-settings.js +53 -0
- package/dist/config/misc-settings.js.map +1 -1
- package/dist/dashboard/board-static.d.ts +23 -0
- package/dist/dashboard/board-static.d.ts.map +1 -0
- package/dist/dashboard/board-static.js +131 -0
- package/dist/dashboard/board-static.js.map +1 -0
- package/dist/dashboard/routes/board.d.ts +73 -0
- package/dist/dashboard/routes/board.d.ts.map +1 -0
- package/dist/dashboard/routes/board.js +623 -0
- package/dist/dashboard/routes/board.js.map +1 -0
- package/dist/dashboard/routes/claude.d.ts.map +1 -1
- package/dist/dashboard/routes/claude.js +10 -1
- package/dist/dashboard/routes/claude.js.map +1 -1
- package/dist/dashboard/routes/settings.d.ts +2 -0
- package/dist/dashboard/routes/settings.d.ts.map +1 -1
- package/dist/dashboard/routes/settings.js +24 -1
- package/dist/dashboard/routes/settings.js.map +1 -1
- package/dist/dashboard/routes/threads-read.d.ts +7 -3
- package/dist/dashboard/routes/threads-read.d.ts.map +1 -1
- package/dist/dashboard/routes/threads-read.js +19 -4
- package/dist/dashboard/routes/threads-read.js.map +1 -1
- package/dist/dashboard/routes/types.d.ts +6 -0
- package/dist/dashboard/routes/types.d.ts.map +1 -1
- package/dist/dashboard/routes/types.js.map +1 -1
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +42 -1
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/spa.html +12 -12
- package/dist/data/board/board-schema.d.ts +22 -0
- package/dist/data/board/board-schema.d.ts.map +1 -0
- package/dist/data/board/board-schema.js +103 -0
- package/dist/data/board/board-schema.js.map +1 -0
- package/dist/data/board/board-types.d.ts +117 -0
- package/dist/data/board/board-types.d.ts.map +1 -0
- package/dist/data/board/board-types.js +9 -0
- package/dist/data/board/board-types.js.map +1 -0
- package/dist/data/board/card.repository.d.ts +64 -0
- package/dist/data/board/card.repository.d.ts.map +1 -0
- package/dist/data/board/card.repository.js +272 -0
- package/dist/data/board/card.repository.js.map +1 -0
- package/dist/data/board/comment.repository.d.ts +12 -0
- package/dist/data/board/comment.repository.d.ts.map +1 -0
- package/dist/data/board/comment.repository.js +31 -0
- package/dist/data/board/comment.repository.js.map +1 -0
- package/dist/data/board/lane.repository.d.ts +18 -0
- package/dist/data/board/lane.repository.d.ts.map +1 -0
- package/dist/data/board/lane.repository.js +44 -0
- package/dist/data/board/lane.repository.js.map +1 -0
- package/dist/data/board/profile-defaults.d.ts +19 -0
- package/dist/data/board/profile-defaults.d.ts.map +1 -0
- package/dist/data/board/profile-defaults.js +118 -0
- package/dist/data/board/profile-defaults.js.map +1 -0
- package/dist/data/memory/migration-runner.d.ts.map +1 -1
- package/dist/data/memory/migration-runner.js +4 -0
- package/dist/data/memory/migration-runner.js.map +1 -1
- package/dist/data/memory/narrative-store.d.ts +14 -0
- package/dist/data/memory/narrative-store.d.ts.map +1 -1
- package/dist/data/memory/narrative-store.js +36 -0
- package/dist/data/memory/narrative-store.js.map +1 -1
- package/dist/data/memory/narrative.d.ts.map +1 -1
- package/dist/data/memory/narrative.js +13 -1
- package/dist/data/memory/narrative.js.map +1 -1
- package/dist/data/memory/schema.d.ts.map +1 -1
- package/dist/data/memory/schema.js +4 -0
- package/dist/data/memory/schema.js.map +1 -1
- package/dist/index.js +84 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/openai/speech.d.ts.map +1 -1
- package/dist/integrations/openai/speech.js +52 -19
- package/dist/integrations/openai/speech.js.map +1 -1
- package/dist/lib/http-retry.d.ts +91 -0
- package/dist/lib/http-retry.d.ts.map +1 -0
- package/dist/lib/http-retry.js +177 -0
- package/dist/lib/http-retry.js.map +1 -0
- package/dist/lib/usage-metering/pricing.js +1 -1
- package/dist/lib/usage-metering/pricing.js.map +1 -1
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +22 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/server/http-server.d.ts +3 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +6 -2
- package/dist/server/http-server.js.map +1 -1
- package/dist/services/board/board-signals.d.ts +25 -0
- package/dist/services/board/board-signals.d.ts.map +1 -0
- package/dist/services/board/board-signals.js +83 -0
- package/dist/services/board/board-signals.js.map +1 -0
- package/dist/services/board/card-briefing.d.ts +17 -0
- package/dist/services/board/card-briefing.d.ts.map +1 -0
- package/dist/services/board/card-briefing.js +86 -0
- package/dist/services/board/card-briefing.js.map +1 -0
- package/dist/services/board/orchestrator.service.d.ts +55 -0
- package/dist/services/board/orchestrator.service.d.ts.map +1 -0
- package/dist/services/board/orchestrator.service.js +198 -0
- package/dist/services/board/orchestrator.service.js.map +1 -0
- package/dist/services/board/release-worker.d.ts +32 -0
- package/dist/services/board/release-worker.d.ts.map +1 -0
- package/dist/services/board/release-worker.js +49 -0
- package/dist/services/board/release-worker.js.map +1 -0
- package/dist/services/board/report-capture.d.ts +48 -0
- package/dist/services/board/report-capture.d.ts.map +1 -0
- package/dist/services/board/report-capture.js +172 -0
- package/dist/services/board/report-capture.js.map +1 -0
- package/dist/services/board/worker-spawn.d.ts +30 -0
- package/dist/services/board/worker-spawn.d.ts.map +1 -0
- package/dist/services/board/worker-spawn.js +90 -0
- package/dist/services/board/worker-spawn.js.map +1 -0
- package/dist/services/broker-client.d.ts +30 -0
- package/dist/services/broker-client.d.ts.map +1 -1
- package/dist/services/broker-client.js +23 -0
- package/dist/services/broker-client.js.map +1 -1
- package/dist/services/codex-spawn.service.d.ts +2 -2
- package/dist/services/codex-spawn.service.d.ts.map +1 -1
- package/dist/services/codex-spawn.service.js +54 -54
- package/dist/services/codex-spawn.service.js.map +1 -1
- package/dist/services/http-retry.d.ts +1 -90
- package/dist/services/http-retry.d.ts.map +1 -1
- package/dist/services/http-retry.js +1 -176
- package/dist/services/http-retry.js.map +1 -1
- package/dist/services/mcp-config.service.d.ts.map +1 -1
- package/dist/services/mcp-config.service.js +5 -1
- package/dist/services/mcp-config.service.js.map +1 -1
- package/dist/services/passive-notice.service.d.ts +39 -0
- package/dist/services/passive-notice.service.d.ts.map +1 -0
- package/dist/services/passive-notice.service.js +274 -0
- package/dist/services/passive-notice.service.js.map +1 -0
- package/dist/services/spawn-common.d.ts.map +1 -1
- package/dist/services/spawn-common.js +14 -1
- package/dist/services/spawn-common.js.map +1 -1
- package/dist/services/telegram.d.ts +7 -5
- package/dist/services/telegram.d.ts.map +1 -1
- package/dist/services/telegram.js +18 -13
- package/dist/services/telegram.js.map +1 -1
- package/dist/services/usage/usage-inbox-notifier.d.ts +50 -0
- package/dist/services/usage/usage-inbox-notifier.d.ts.map +1 -0
- package/dist/services/usage/usage-inbox-notifier.js +118 -0
- package/dist/services/usage/usage-inbox-notifier.js.map +1 -0
- package/dist/services/usage/usage-poller.service.d.ts +41 -0
- package/dist/services/usage/usage-poller.service.d.ts.map +1 -0
- package/dist/services/usage/usage-poller.service.js +66 -0
- package/dist/services/usage/usage-poller.service.js.map +1 -0
- package/dist/services/usage/usage-snapshot-provider.d.ts +29 -0
- package/dist/services/usage/usage-snapshot-provider.d.ts.map +1 -0
- package/dist/services/usage/usage-snapshot-provider.js +59 -0
- package/dist/services/usage/usage-snapshot-provider.js.map +1 -0
- package/dist/services/usage/usage-types.d.ts +23 -0
- package/dist/services/usage/usage-types.d.ts.map +1 -0
- package/dist/services/usage/usage-types.js +14 -0
- package/dist/services/usage/usage-types.js.map +1 -0
- package/dist/services/worker-cleanup.service.d.ts +6 -1
- package/dist/services/worker-cleanup.service.d.ts.map +1 -1
- package/dist/services/worker-cleanup.service.js +123 -10
- package/dist/services/worker-cleanup.service.js.map +1 -1
- package/dist/tools/delegate-tool.d.ts.map +1 -1
- package/dist/tools/delegate-tool.js +34 -2
- package/dist/tools/delegate-tool.js.map +1 -1
- package/dist/tools/utility-tools.d.ts.map +1 -1
- package/dist/tools/utility-tools.js +7 -3
- package/dist/tools/utility-tools.js.map +1 -1
- package/dist/tools/wait/consolidation-strategy.d.ts +42 -0
- package/dist/tools/wait/consolidation-strategy.d.ts.map +1 -0
- package/dist/tools/wait/consolidation-strategy.js +50 -0
- package/dist/tools/wait/consolidation-strategy.js.map +1 -0
- package/dist/tools/wait/drive-handler.d.ts +5 -7
- package/dist/tools/wait/drive-handler.d.ts.map +1 -1
- package/dist/tools/wait/drive-handler.js +30 -48
- package/dist/tools/wait/drive-handler.js.map +1 -1
- package/dist/tools/wait/message-delivery.d.ts.map +1 -1
- package/dist/tools/wait/message-delivery.js +13 -0
- package/dist/tools/wait/message-delivery.js.map +1 -1
- package/package.json +2 -2
- package/templates/atomic-build-spec.default.md +57 -0
- package/templates/buildless-vue-preview.default.md +47 -0
- package/templates/phased-build.default.md +59 -0
|
@@ -0,0 +1,198 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.service.js","sourceRoot":"","sources":["../../../src/services/board/orchestrator.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
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 { log } from "../../lib/logger.js";
|
|
17
|
+
import { errorMessage } from "../../lib/utils.js";
|
|
18
|
+
import { buildDonePatch, blockBoundWorkerCard, blockUnboundCard, findCardByWorkerThread, updateCard, } from "../../data/board/card.repository.js";
|
|
19
|
+
import { addComment } from "../../data/board/comment.repository.js";
|
|
20
|
+
import { isThreadAlive } from "../process.service.js";
|
|
21
|
+
import { mirrorEscalatedUpdate, sendBoardSignal } from "./board-signals.js";
|
|
22
|
+
/** Parse the report contract's first non-empty line. */
|
|
23
|
+
export function parseReportStatus(message) {
|
|
24
|
+
const firstLine = message
|
|
25
|
+
.split("\n")
|
|
26
|
+
.map((l) => l.trim())
|
|
27
|
+
.find((l) => l.length > 0);
|
|
28
|
+
const match = firstLine ? /^STATUS:\s*(DONE|BLOCKED)\s*:?\s*(.*)$/i.exec(firstLine) : null;
|
|
29
|
+
if (!match)
|
|
30
|
+
return { kind: "interim" };
|
|
31
|
+
if (match[1].toUpperCase() === "DONE")
|
|
32
|
+
return { kind: "done" };
|
|
33
|
+
return { kind: "blocked", reason: match[2].trim() || "no reason given" };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Intercept a worker→lane send. Returns true when the board consumed the
|
|
37
|
+
* message (the caller must NOT write it to the lane's mailbox).
|
|
38
|
+
*/
|
|
39
|
+
export function captureCardWorkerReport(db, senderThreadId, targetThreadId, message) {
|
|
40
|
+
let card;
|
|
41
|
+
try {
|
|
42
|
+
card = findCardByWorkerThread(db, senderThreadId);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
log.warn(`[board] report-capture lookup failed: ${errorMessage(err)}`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (!card || card.laneThreadId !== targetThreadId)
|
|
49
|
+
return false;
|
|
50
|
+
try {
|
|
51
|
+
addComment(db, card.cardId, "worker", message);
|
|
52
|
+
mirrorEscalatedUpdate(card, message);
|
|
53
|
+
const status = parseReportStatus(message);
|
|
54
|
+
if (status.kind === "done") {
|
|
55
|
+
const updated = updateCard(db, card.cardId, buildDonePatch(card));
|
|
56
|
+
if (updated)
|
|
57
|
+
sendBoardSignal(updated, "DONE");
|
|
58
|
+
log.info(`[board] card ${card.cardId} DONE (worker ${senderThreadId} reported)`);
|
|
59
|
+
}
|
|
60
|
+
else if (status.kind === "blocked") {
|
|
61
|
+
const updated = updateCard(db, card.cardId, {
|
|
62
|
+
status: "blocked",
|
|
63
|
+
blockedReason: status.reason,
|
|
64
|
+
workerThreadId: null,
|
|
65
|
+
});
|
|
66
|
+
if (updated)
|
|
67
|
+
sendBoardSignal(updated, "BLOCKED", status.reason);
|
|
68
|
+
log.info(`[board] card ${card.cardId} BLOCKED (worker ${senderThreadId}): ${status.reason}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// No STATUS line — an interim update. It lives on the card (its own
|
|
72
|
+
// inbox); the card stays in_progress and the worker stays bound so the
|
|
73
|
+
// exit safety net still applies.
|
|
74
|
+
log.info(`[board] card ${card.cardId} interim comment from worker ${senderThreadId}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// The message was NOT durably captured — let it flow to the mailbox rather
|
|
79
|
+
// than vanish (the lane agent seeing one raw report beats losing it).
|
|
80
|
+
log.error(`[board] report capture failed for card ${card.cardId}: ${errorMessage(err)}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Idempotent exit safety net: a card-worker died/teardown'd while its card is
|
|
87
|
+
* still in_progress and bound to it → the card is Blocked with the exit as the
|
|
88
|
+
* reason. No-op when the worker already reported (capture unbinds the worker).
|
|
89
|
+
*/
|
|
90
|
+
export function handleCardWorkerExit(db, workerThreadId, exitCode) {
|
|
91
|
+
try {
|
|
92
|
+
const card = findCardByWorkerThread(db, workerThreadId);
|
|
93
|
+
if (!card)
|
|
94
|
+
return;
|
|
95
|
+
const reason = exitCode == null
|
|
96
|
+
? "Worker is gone (exit code unknown — found dead by a sweep) without a final report."
|
|
97
|
+
: exitCode === 0
|
|
98
|
+
? "Worker exited cleanly (code 0) WITHOUT posting its STATUS report — likely forgot the report contract."
|
|
99
|
+
: `Worker crashed (exit code ${exitCode}) without a final report.`;
|
|
100
|
+
// Compare-and-set: only this caller that actually flips the card (still
|
|
101
|
+
// in_progress AND still bound to this worker) emits the BLOCKED signal.
|
|
102
|
+
// A racing caller (worker teardown vs reconcile tick, cross-process) that
|
|
103
|
+
// changes 0 rows means another path already handled it → no-op.
|
|
104
|
+
const updated = blockBoundWorkerCard(db, card.cardId, workerThreadId, reason);
|
|
105
|
+
if (!updated)
|
|
106
|
+
return;
|
|
107
|
+
addComment(db, card.cardId, "board", reason);
|
|
108
|
+
mirrorEscalatedUpdate(updated, reason);
|
|
109
|
+
sendBoardSignal(updated, "BLOCKED", reason);
|
|
110
|
+
log.warn(`[board] card ${card.cardId}: ${reason} (worker ${workerThreadId})`);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
log.warn(`[board] card-worker exit handling failed for ${workerThreadId}: ${errorMessage(err)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Ignore cards whose worker was spawned less than this long ago (spawn races). */
|
|
117
|
+
const RECONCILE_MIN_AGE_MS = 2 * 60_000;
|
|
118
|
+
/**
|
|
119
|
+
* A claimed-but-unbound card (in_progress, no worker) is only reconciled once
|
|
120
|
+
* it is well past the worst-case spawn latency — comfortably above the 2-min
|
|
121
|
+
* bound-worker liveness check so a still-in-flight dispatch is never mistaken
|
|
122
|
+
* for a wedged one.
|
|
123
|
+
*/
|
|
124
|
+
const RECONCILE_UNBOUND_MIN_AGE_MS = 15 * 60_000;
|
|
125
|
+
/**
|
|
126
|
+
* Block a claimed-but-unbound card whose dispatch never completed (the claim
|
|
127
|
+
* set started_at + in_progress, then the process died or the spawn threw before
|
|
128
|
+
* bindWorkerToCard ran). Without this the card would eat a lane + global cap
|
|
129
|
+
* slot forever. CAS-guarded so a late bind / concurrent tick cannot double-block.
|
|
130
|
+
*/
|
|
131
|
+
function reconcileUnboundCard(db, card) {
|
|
132
|
+
const reason = "dispatch did not complete (no worker bound)";
|
|
133
|
+
const updated = blockUnboundCard(db, card.cardId, reason);
|
|
134
|
+
if (!updated)
|
|
135
|
+
return false;
|
|
136
|
+
addComment(db, card.cardId, "board", reason);
|
|
137
|
+
mirrorEscalatedUpdate(updated, reason);
|
|
138
|
+
sendBoardSignal(updated, "BLOCKED", reason);
|
|
139
|
+
log.warn(`[board] card ${card.cardId}: ${reason} (claimed but never bound a worker)`);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Orchestrator-tick backstop (covers server restarts, where no exit event ever
|
|
144
|
+
* fires): any in_progress card bound to a worker that is not running — and old
|
|
145
|
+
* enough that a spawn race is impossible — goes to Blocked. Also frees cards
|
|
146
|
+
* that were claimed but never bound a worker (dispatch died mid-flight).
|
|
147
|
+
*/
|
|
148
|
+
export async function reconcileInProgressCards(db, listInProgress) {
|
|
149
|
+
let blocked = 0;
|
|
150
|
+
for (const card of listInProgress()) {
|
|
151
|
+
const ageMs = card.startedAt ? Date.now() - Date.parse(card.startedAt) : Infinity;
|
|
152
|
+
if (card.workerThreadId == null) {
|
|
153
|
+
// Claimed-but-unbound: reconcile only well past worst-case spawn latency.
|
|
154
|
+
if (ageMs > RECONCILE_UNBOUND_MIN_AGE_MS && reconcileUnboundCard(db, card))
|
|
155
|
+
blocked++;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!(ageMs > RECONCILE_MIN_AGE_MS))
|
|
159
|
+
continue;
|
|
160
|
+
try {
|
|
161
|
+
if (await isThreadAlive(card.workerThreadId))
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
continue; // cannot confirm → never block on uncertainty
|
|
166
|
+
}
|
|
167
|
+
handleCardWorkerExit(db, card.workerThreadId, null);
|
|
168
|
+
blocked++;
|
|
169
|
+
}
|
|
170
|
+
return blocked;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=report-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-capture.js","sourceRoot":"","sources":["../../../src/services/board/report-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card-worker spawn path (design §3.1) — the board-side equivalent of
|
|
3
|
+
* handleStartThread's mode=worker branch (delegate-tool.ts), reusing the same
|
|
4
|
+
* primitives in the same order: topic create → agent-type set → pre-queue
|
|
5
|
+
* payload → dispatchSpawn(memorySourceThreadId=lane) → registerThread.
|
|
6
|
+
* The worker inherits the FULL lane-thread briefing via memorySourceThreadId
|
|
7
|
+
* (assembleBootstrap), with the card payload as its pre-queued task on top.
|
|
8
|
+
*/
|
|
9
|
+
import type { initMemoryDb } from "../../data/memory/index.js";
|
|
10
|
+
import type { BoardCard, BoardComment } from "../../data/board/board-types.js";
|
|
11
|
+
import type { ThreadLifecycleService } from "../thread-lifecycle.service.js";
|
|
12
|
+
export interface CardWorkerSpawnDeps {
|
|
13
|
+
db: ReturnType<typeof initMemoryDb>;
|
|
14
|
+
telegram: {
|
|
15
|
+
createForumTopic(chatId: string, name: string): Promise<{
|
|
16
|
+
message_thread_id: number;
|
|
17
|
+
}>;
|
|
18
|
+
sendMessage(chatId: string, text: string, parseMode?: string, threadId?: number): Promise<unknown>;
|
|
19
|
+
deleteForumTopic(chatId: string, threadId: number): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
chatId: string;
|
|
22
|
+
threadLifecycle: ThreadLifecycleService;
|
|
23
|
+
}
|
|
24
|
+
export type CardWorkerSpawnResult = {
|
|
25
|
+
workerThreadId: number;
|
|
26
|
+
} | {
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function spawnCardWorker(deps: CardWorkerSpawnDeps, card: BoardCard, comments: BoardComment[]): Promise<CardWorkerSpawnResult>;
|
|
30
|
+
//# sourceMappingURL=worker-spawn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-spawn.d.ts","sourceRoot":"","sources":["../../../src/services/board/worker-spawn.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAK/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACpC,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;CACzC;AAED,MAAM,MAAM,qBAAqB,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAKnF,wBAAsB,eAAe,CACnC,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,YAAY,EAAE,GACvB,OAAO,CAAC,qBAAqB,CAAC,CAwFhC"}
|