sensorium-mcp 3.1.1-board.2 → 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.
- package/dist/dashboard/routes/types.d.ts +0 -6
- 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 +0 -32
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/spa.html +12 -12
- package/dist/data/memory/migration-runner.d.ts.map +1 -1
- package/dist/data/memory/migration-runner.js +0 -4
- package/dist/data/memory/migration-runner.js.map +1 -1
- package/dist/data/memory/schema.d.ts.map +1 -1
- package/dist/data/memory/schema.js +0 -4
- package/dist/data/memory/schema.js.map +1 -1
- package/dist/index.js +2 -39
- package/dist/index.js.map +1 -1
- package/dist/server/http-server.d.ts +1 -3
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +1 -3
- package/dist/server/http-server.js.map +1 -1
- package/dist/services/broker-client.d.ts +17 -4
- package/dist/services/broker-client.d.ts.map +1 -1
- package/dist/services/broker-client.js +14 -3
- package/dist/services/broker-client.js.map +1 -1
- package/dist/services/self-update.service.d.ts +3 -2
- package/dist/services/self-update.service.d.ts.map +1 -1
- package/dist/services/self-update.service.js +20 -21
- package/dist/services/self-update.service.js.map +1 -1
- package/dist/services/spawn-common.d.ts +44 -19
- package/dist/services/spawn-common.d.ts.map +1 -1
- package/dist/services/spawn-common.js +65 -33
- package/dist/services/spawn-common.js.map +1 -1
- package/dist/services/worker-cleanup.service.d.ts +1 -6
- package/dist/services/worker-cleanup.service.d.ts.map +1 -1
- package/dist/services/worker-cleanup.service.js +10 -123
- 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 +0 -32
- package/dist/tools/delegate-tool.js.map +1 -1
- package/dist/tools/wait/message-delivery.d.ts.map +1 -1
- package/dist/tools/wait/message-delivery.js +0 -13
- package/dist/tools/wait/message-delivery.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard/routes/board.d.ts +0 -67
- package/dist/dashboard/routes/board.d.ts.map +0 -1
- package/dist/dashboard/routes/board.js +0 -612
- package/dist/dashboard/routes/board.js.map +0 -1
- package/dist/data/board/board-schema.d.ts +0 -22
- package/dist/data/board/board-schema.d.ts.map +0 -1
- package/dist/data/board/board-schema.js +0 -103
- package/dist/data/board/board-schema.js.map +0 -1
- package/dist/data/board/board-types.d.ts +0 -117
- package/dist/data/board/board-types.d.ts.map +0 -1
- package/dist/data/board/board-types.js +0 -9
- package/dist/data/board/board-types.js.map +0 -1
- package/dist/data/board/card.repository.d.ts +0 -64
- package/dist/data/board/card.repository.d.ts.map +0 -1
- package/dist/data/board/card.repository.js +0 -272
- package/dist/data/board/card.repository.js.map +0 -1
- package/dist/data/board/comment.repository.d.ts +0 -12
- package/dist/data/board/comment.repository.d.ts.map +0 -1
- package/dist/data/board/comment.repository.js +0 -31
- package/dist/data/board/comment.repository.js.map +0 -1
- package/dist/data/board/lane.repository.d.ts +0 -18
- package/dist/data/board/lane.repository.d.ts.map +0 -1
- package/dist/data/board/lane.repository.js +0 -44
- package/dist/data/board/lane.repository.js.map +0 -1
- package/dist/data/board/profile-defaults.d.ts +0 -19
- package/dist/data/board/profile-defaults.d.ts.map +0 -1
- package/dist/data/board/profile-defaults.js +0 -118
- package/dist/data/board/profile-defaults.js.map +0 -1
- package/dist/services/board/board-signals.d.ts +0 -25
- package/dist/services/board/board-signals.d.ts.map +0 -1
- package/dist/services/board/board-signals.js +0 -83
- package/dist/services/board/board-signals.js.map +0 -1
- package/dist/services/board/card-briefing.d.ts +0 -17
- package/dist/services/board/card-briefing.d.ts.map +0 -1
- package/dist/services/board/card-briefing.js +0 -86
- package/dist/services/board/card-briefing.js.map +0 -1
- package/dist/services/board/orchestrator.service.d.ts +0 -55
- package/dist/services/board/orchestrator.service.d.ts.map +0 -1
- package/dist/services/board/orchestrator.service.js +0 -198
- package/dist/services/board/orchestrator.service.js.map +0 -1
- package/dist/services/board/release-worker.d.ts +0 -32
- package/dist/services/board/release-worker.d.ts.map +0 -1
- package/dist/services/board/release-worker.js +0 -49
- package/dist/services/board/release-worker.js.map +0 -1
- package/dist/services/board/report-capture.d.ts +0 -48
- package/dist/services/board/report-capture.d.ts.map +0 -1
- package/dist/services/board/report-capture.js +0 -172
- package/dist/services/board/report-capture.js.map +0 -1
- package/dist/services/board/worker-spawn.d.ts +0 -30
- package/dist/services/board/worker-spawn.d.ts.map +0 -1
- package/dist/services/board/worker-spawn.js +0 -90
- package/dist/services/board/worker-spawn.js.map +0 -1
- package/dist/services/passive-notice.service.d.ts +0 -39
- package/dist/services/passive-notice.service.d.ts.map +0 -1
- package/dist/services/passive-notice.service.js +0 -274
- package/dist/services/passive-notice.service.js.map +0 -1
|
@@ -1,172 +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 { 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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report-capture.js","sourceRoot":"","sources":["../../../src/services/board/report-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,90 +0,0 @@
|
|
|
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 { appendFileSync } from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { getAgentType } from "../../config/index.js";
|
|
12
|
-
import { log } from "../../lib/logger.js";
|
|
13
|
-
import { errorMessage } from "../../lib/utils.js";
|
|
14
|
-
import { getThread } from "../../data/memory/thread-registry.js";
|
|
15
|
-
import { dispatchSpawn } from "../agent-spawn.service.js";
|
|
16
|
-
import { deletePendingTaskMailbox, ensureDirs, PENDING_TASKS_DIR } from "../process.service.js";
|
|
17
|
-
import { createManagedTopic } from "../topic.service.js";
|
|
18
|
-
import { setThreadAgentType } from "../thread-config.service.js";
|
|
19
|
-
import { renderCardBriefing } from "./card-briefing.js";
|
|
20
|
-
/** Windows drive-letter or POSIX absolute path → usable as a working directory. */
|
|
21
|
-
const looksLikeLocalPath = (s) => /^([A-Za-z]:[\\/]|\/)/.test(s);
|
|
22
|
-
export async function spawnCardWorker(deps, card, comments) {
|
|
23
|
-
const { db, telegram, chatId, threadLifecycle } = deps;
|
|
24
|
-
const agentType = card.profile.agentType ?? getAgentType();
|
|
25
|
-
const shortTitle = card.title.length > 40 ? card.title.slice(0, 37) + "…" : card.title;
|
|
26
|
-
const name = `Card #${card.cardId} ${shortTitle}`;
|
|
27
|
-
// 1. Fresh Telegram topic — a fresh stateless worker per (re)dispatch, never reused.
|
|
28
|
-
let workerThreadId;
|
|
29
|
-
try {
|
|
30
|
-
workerThreadId = await createManagedTopic(telegram, chatId, `[Worker] ${name}`, [name]);
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
return { error: `Topic creation failed: ${errorMessage(err)}` };
|
|
34
|
-
}
|
|
35
|
-
setThreadAgentType(workerThreadId, agentType);
|
|
36
|
-
// Every failure after this point must tear the topic + mailbox back down —
|
|
37
|
-
// otherwise each failed dispatch leaks one Telegram topic and a pending-task
|
|
38
|
-
// file that could feed a future thread reusing the id.
|
|
39
|
-
const cleanupAfterFailure = async () => {
|
|
40
|
-
deletePendingTaskMailbox(workerThreadId);
|
|
41
|
-
try {
|
|
42
|
-
await telegram.deleteForumTopic(chatId, workerThreadId);
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
log.warn(`[board] failed-dispatch topic cleanup for ${workerThreadId}: ${errorMessage(err)}`);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
// 2. Working directory: explicit local repo path on the card wins, else the
|
|
49
|
-
// lane thread's own working directory (the worker is branched from it).
|
|
50
|
-
const laneThread = getThread(db, card.laneThreadId);
|
|
51
|
-
const workingDirectory = card.profile.repository && looksLikeLocalPath(card.profile.repository)
|
|
52
|
-
? card.profile.repository
|
|
53
|
-
: (laneThread?.workingDirectory ?? process.cwd());
|
|
54
|
-
// 3. Pre-queue the card payload before the process exists (same guarantee as
|
|
55
|
-
// start_thread's task param — delivery on the worker's first poll).
|
|
56
|
-
try {
|
|
57
|
-
ensureDirs();
|
|
58
|
-
appendFileSync(join(PENDING_TASKS_DIR, `${workerThreadId}.txt`), renderCardBriefing(card, comments, workerThreadId) + "\n", "utf-8");
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
await cleanupAfterFailure();
|
|
62
|
-
return { error: `Failed to pre-queue card payload: ${errorMessage(err)}` };
|
|
63
|
-
}
|
|
64
|
-
// 4. Spawn, briefed from the lane thread (full parent briefing + card).
|
|
65
|
-
const result = await dispatchSpawn(agentType, name, workerThreadId, threadLifecycle, workingDirectory, card.laneThreadId, "worker");
|
|
66
|
-
if ("error" in result) {
|
|
67
|
-
await cleanupAfterFailure();
|
|
68
|
-
return { error: result.error };
|
|
69
|
-
}
|
|
70
|
-
// 5. Register in the thread registry (worker hygiene — TTL, cleanup — applies).
|
|
71
|
-
try {
|
|
72
|
-
threadLifecycle.registerThread(db, {
|
|
73
|
-
threadId: workerThreadId,
|
|
74
|
-
name,
|
|
75
|
-
type: "worker",
|
|
76
|
-
rootThreadId: card.laneThreadId,
|
|
77
|
-
badge: "board-worker",
|
|
78
|
-
client: agentType,
|
|
79
|
-
workingDirectory,
|
|
80
|
-
chatId,
|
|
81
|
-
telegramTopicId: workerThreadId,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
catch (err) {
|
|
85
|
-
log.warn(`[board] registerThread failed for card-worker ${workerThreadId}: ${errorMessage(err)}`);
|
|
86
|
-
}
|
|
87
|
-
log.info(`[board] card ${card.cardId} dispatched → worker thread ${workerThreadId} (PID ${result.pid}, agent ${agentType})`);
|
|
88
|
-
return { workerThreadId };
|
|
89
|
-
}
|
|
90
|
-
//# sourceMappingURL=worker-spawn.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"worker-spawn.js","sourceRoot":"","sources":["../../../src/services/board/worker-spawn.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Passive-notice channel — "context, not command" (design §5).
|
|
3
|
-
*
|
|
4
|
-
* A general capability, independent of the board: notices pushed here are
|
|
5
|
-
* DRAINED into the next delivered turn of the target thread as an FYI block
|
|
6
|
-
* (message-delivery renders them next to the Persistent Context block). They
|
|
7
|
-
* never wake the agent and are never framed as tasks — the agent is aware,
|
|
8
|
-
* not commanded. This fixes the notification/command conflation of the
|
|
9
|
-
* pending-task inbox for updates that carry no instruction.
|
|
10
|
-
*
|
|
11
|
-
* Storage: per-thread JSON queue under ~/.remote-copilot-mcp/passive-notices/,
|
|
12
|
-
* write-then-rename (same durability pattern as schedules). Bounded to the
|
|
13
|
-
* most recent MAX_NOTICES entries.
|
|
14
|
-
*/
|
|
15
|
-
export interface PassiveNotice {
|
|
16
|
-
text: string;
|
|
17
|
-
createdAt: string;
|
|
18
|
-
}
|
|
19
|
-
/** Queue an FYI notice for a thread's next delivered turn. Never wakes the agent. */
|
|
20
|
-
export declare function pushPassiveNotice(threadId: number, text: string): void;
|
|
21
|
-
/**
|
|
22
|
-
* Read-and-clear (delivered-once semantics). Returns oldest→newest.
|
|
23
|
-
*
|
|
24
|
-
* Drain claims the queue file via atomic rename (under the same lock pushes
|
|
25
|
-
* take), so a push landing mid-drain writes a FRESH queue file and survives.
|
|
26
|
-
* Claims are per-pid; claims left by a crashed process are swept in too
|
|
27
|
-
* (redelivery of an FYI beats losing it).
|
|
28
|
-
*/
|
|
29
|
-
export declare function drainPassiveNotices(threadId: number): PassiveNotice[];
|
|
30
|
-
/** Non-destructive peek (dashboard/diagnostics). */
|
|
31
|
-
export declare function peekPassiveNotices(threadId: number): PassiveNotice[];
|
|
32
|
-
/**
|
|
33
|
-
* Drain and render the FYI block appended to a delivered turn (next to the
|
|
34
|
-
* Persistent Context block in message-delivery). Empty string when quiet.
|
|
35
|
-
* The framing does the "aware, not commanded" work: explicit no-action wording
|
|
36
|
-
* so a later operator voice about these makes sense without triggering work.
|
|
37
|
-
*/
|
|
38
|
-
export declare function renderPassiveNoticeBlock(threadId: number): string;
|
|
39
|
-
//# sourceMappingURL=passive-notice.service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"passive-notice.service.d.ts","sourceRoot":"","sources":["../../src/services/passive-notice.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAmBH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AA4FD,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBtE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAgFrE;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6BjE"}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Passive-notice channel — "context, not command" (design §5).
|
|
3
|
-
*
|
|
4
|
-
* A general capability, independent of the board: notices pushed here are
|
|
5
|
-
* DRAINED into the next delivered turn of the target thread as an FYI block
|
|
6
|
-
* (message-delivery renders them next to the Persistent Context block). They
|
|
7
|
-
* never wake the agent and are never framed as tasks — the agent is aware,
|
|
8
|
-
* not commanded. This fixes the notification/command conflation of the
|
|
9
|
-
* pending-task inbox for updates that carry no instruction.
|
|
10
|
-
*
|
|
11
|
-
* Storage: per-thread JSON queue under ~/.remote-copilot-mcp/passive-notices/,
|
|
12
|
-
* write-then-rename (same durability pattern as schedules). Bounded to the
|
|
13
|
-
* most recent MAX_NOTICES entries.
|
|
14
|
-
*/
|
|
15
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
import { log } from "../lib/logger.js";
|
|
19
|
-
import { errorMessage } from "../lib/utils.js";
|
|
20
|
-
const NOTICES_DIR = join(homedir(), ".remote-copilot-mcp", "passive-notices");
|
|
21
|
-
const MAX_NOTICES = 10;
|
|
22
|
-
function noticesFilePath(threadId) {
|
|
23
|
-
return join(NOTICES_DIR, `${threadId}.json`);
|
|
24
|
-
}
|
|
25
|
-
// ── Cross-process safety (H4 of the board kernel review) ───────────────────
|
|
26
|
-
// Writers live in DIFFERENT OS processes (a worker's own MCP tool call vs the
|
|
27
|
-
// server's orchestrator/cleanup loops). The bare read→modify→write→rename pair
|
|
28
|
-
// is atomic only in the rename, so concurrent pushes were last-writer-wins and
|
|
29
|
-
// a push landing inside drain's read→unlink window was destroyed. Same lock
|
|
30
|
-
// pattern as dispatcher/lock.ts (wx-create + dead-pid/stale takeover), scoped
|
|
31
|
-
// per thread; drain claims the file by atomic rename so a concurrent push
|
|
32
|
-
// simply starts a fresh queue file and survives.
|
|
33
|
-
const LOCK_STALE_MS = 10_000;
|
|
34
|
-
const LOCK_RETRY_MS = 5;
|
|
35
|
-
const LOCK_MAX_ATTEMPTS = 60;
|
|
36
|
-
function isPidAlive(pid) {
|
|
37
|
-
try {
|
|
38
|
-
process.kill(pid, 0);
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
return err.code === "EPERM";
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function sleepSync(ms) {
|
|
46
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
47
|
-
}
|
|
48
|
-
/** Acquire the per-thread mutation lock. Hold time is sub-millisecond (tiny
|
|
49
|
-
* file ops), so bounded spinning is fine. Returns false only under pathological
|
|
50
|
-
* contention — callers then proceed unlocked (a rare racy write beats losing
|
|
51
|
-
* the notice outright). */
|
|
52
|
-
function acquireNoticeLock(threadId) {
|
|
53
|
-
const lockPath = join(NOTICES_DIR, `${threadId}.lock`);
|
|
54
|
-
for (let attempt = 0; attempt < LOCK_MAX_ATTEMPTS; attempt++) {
|
|
55
|
-
try {
|
|
56
|
-
writeFileSync(lockPath, JSON.stringify({ pid: process.pid, ts: Date.now() }), {
|
|
57
|
-
encoding: "utf8",
|
|
58
|
-
flag: "wx",
|
|
59
|
-
});
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
try {
|
|
64
|
-
const holder = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
65
|
-
if (!isPidAlive(holder.pid) || Date.now() - holder.ts > LOCK_STALE_MS) {
|
|
66
|
-
unlinkSync(lockPath); // dead or stale holder — take over next attempt
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// Corrupt/vanished lock — remove and retry.
|
|
72
|
-
try {
|
|
73
|
-
unlinkSync(lockPath);
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
/* already gone */
|
|
77
|
-
}
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
sleepSync(LOCK_RETRY_MS);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
log.warn(`[passive-notice] lock contention for thread ${threadId} — proceeding unlocked`);
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
function releaseNoticeLock(threadId) {
|
|
87
|
-
const lockPath = join(NOTICES_DIR, `${threadId}.lock`);
|
|
88
|
-
try {
|
|
89
|
-
const holder = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
90
|
-
if (holder.pid === process.pid)
|
|
91
|
-
unlinkSync(lockPath);
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
/* already gone or not ours */
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function readNotices(threadId) {
|
|
98
|
-
const file = noticesFilePath(threadId);
|
|
99
|
-
try {
|
|
100
|
-
if (!existsSync(file))
|
|
101
|
-
return [];
|
|
102
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
103
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
log.warn(`[passive-notice] Failed to read notices for thread ${threadId}: ${errorMessage(err)}`);
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/** Queue an FYI notice for a thread's next delivered turn. Never wakes the agent. */
|
|
111
|
-
export function pushPassiveNotice(threadId, text) {
|
|
112
|
-
try {
|
|
113
|
-
mkdirSync(NOTICES_DIR, { recursive: true });
|
|
114
|
-
const locked = acquireNoticeLock(threadId);
|
|
115
|
-
try {
|
|
116
|
-
const notices = readNotices(threadId);
|
|
117
|
-
notices.push({ text, createdAt: new Date().toISOString() });
|
|
118
|
-
// Bounded: keep only the freshest MAX_NOTICES — stale FYIs have no value.
|
|
119
|
-
const bounded = notices.slice(-MAX_NOTICES);
|
|
120
|
-
const file = noticesFilePath(threadId);
|
|
121
|
-
const tmp = `${file}.tmp.${process.pid}`;
|
|
122
|
-
writeFileSync(tmp, JSON.stringify(bounded, null, 2), "utf8");
|
|
123
|
-
renameSync(tmp, file);
|
|
124
|
-
}
|
|
125
|
-
finally {
|
|
126
|
-
if (locked)
|
|
127
|
-
releaseNoticeLock(threadId);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch (err) {
|
|
131
|
-
log.warn(`[passive-notice] Failed to push notice for thread ${threadId}: ${errorMessage(err)}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Read-and-clear (delivered-once semantics). Returns oldest→newest.
|
|
136
|
-
*
|
|
137
|
-
* Drain claims the queue file via atomic rename (under the same lock pushes
|
|
138
|
-
* take), so a push landing mid-drain writes a FRESH queue file and survives.
|
|
139
|
-
* Claims are per-pid; claims left by a crashed process are swept in too
|
|
140
|
-
* (redelivery of an FYI beats losing it).
|
|
141
|
-
*/
|
|
142
|
-
export function drainPassiveNotices(threadId) {
|
|
143
|
-
const file = noticesFilePath(threadId);
|
|
144
|
-
const claimPath = `${file}.claim.${process.pid}`;
|
|
145
|
-
const drained = [];
|
|
146
|
-
// Sweep litter left by crashed peers: claims orphaned by a dead drainer AND
|
|
147
|
-
// `.tmp.<pid>` half-writes left by a push that died between write and rename.
|
|
148
|
-
// Both are keyed by the writer's pid; anything owned by a gone pid is dead.
|
|
149
|
-
try {
|
|
150
|
-
const claimPrefix = `${threadId}.json.claim.`;
|
|
151
|
-
const tmpPrefix = `${threadId}.json.tmp.`;
|
|
152
|
-
for (const entry of readdirSync(NOTICES_DIR)) {
|
|
153
|
-
if (entry.startsWith(claimPrefix)) {
|
|
154
|
-
const pid = Number(entry.slice(claimPrefix.length));
|
|
155
|
-
if (!Number.isInteger(pid) || pid === process.pid || isPidAlive(pid))
|
|
156
|
-
continue;
|
|
157
|
-
const orphanPath = join(NOTICES_DIR, entry);
|
|
158
|
-
try {
|
|
159
|
-
const parsed = JSON.parse(readFileSync(orphanPath, "utf8"));
|
|
160
|
-
if (Array.isArray(parsed))
|
|
161
|
-
drained.push(...parsed);
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
/* corrupt orphan — drop it */
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
unlinkSync(orphanPath);
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
/* already gone */
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
else if (entry.startsWith(tmpPrefix)) {
|
|
174
|
-
// A crashed push's temp file: its contents never became the live queue
|
|
175
|
-
// (the rename never ran), so it may be a partial write — discard rather
|
|
176
|
-
// than risk resurrecting corrupt/duplicate notices. Only reap dead pids
|
|
177
|
-
// so a concurrent live push's in-flight temp is never deleted.
|
|
178
|
-
const pid = Number(entry.slice(tmpPrefix.length));
|
|
179
|
-
if (!Number.isInteger(pid) || pid === process.pid || isPidAlive(pid))
|
|
180
|
-
continue;
|
|
181
|
-
try {
|
|
182
|
-
unlinkSync(join(NOTICES_DIR, entry));
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
/* already gone */
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
/* dir missing — nothing queued yet */
|
|
192
|
-
}
|
|
193
|
-
// A same-pid claim left by THIS process's earlier crashed drain (renamed but
|
|
194
|
-
// never consumed) would be silently clobbered by the rename below, losing its
|
|
195
|
-
// notices. Consume it first so the notices survive.
|
|
196
|
-
if (existsSync(claimPath)) {
|
|
197
|
-
try {
|
|
198
|
-
const parsed = JSON.parse(readFileSync(claimPath, "utf8"));
|
|
199
|
-
if (Array.isArray(parsed))
|
|
200
|
-
drained.push(...parsed);
|
|
201
|
-
unlinkSync(claimPath);
|
|
202
|
-
}
|
|
203
|
-
catch (err) {
|
|
204
|
-
if (err.code !== "ENOENT")
|
|
205
|
-
log.warn(`[passive-notice] stale claim recovery failed for thread ${threadId}: ${errorMessage(err)}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (existsSync(file)) {
|
|
209
|
-
const locked = acquireNoticeLock(threadId);
|
|
210
|
-
try {
|
|
211
|
-
renameSync(file, claimPath);
|
|
212
|
-
}
|
|
213
|
-
catch (err) {
|
|
214
|
-
log.debug(`[passive-notice] drain claim failed for thread ${threadId}: ${errorMessage(err)}`);
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
if (locked)
|
|
218
|
-
releaseNoticeLock(threadId);
|
|
219
|
-
}
|
|
220
|
-
try {
|
|
221
|
-
const parsed = JSON.parse(readFileSync(claimPath, "utf8"));
|
|
222
|
-
if (Array.isArray(parsed))
|
|
223
|
-
drained.push(...parsed);
|
|
224
|
-
unlinkSync(claimPath);
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
if (err.code !== "ENOENT")
|
|
228
|
-
log.warn(`[passive-notice] drain read failed for thread ${threadId}: ${errorMessage(err)}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return drained;
|
|
232
|
-
}
|
|
233
|
-
/** Non-destructive peek (dashboard/diagnostics). */
|
|
234
|
-
export function peekPassiveNotices(threadId) {
|
|
235
|
-
return readNotices(threadId);
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Drain and render the FYI block appended to a delivered turn (next to the
|
|
239
|
-
* Persistent Context block in message-delivery). Empty string when quiet.
|
|
240
|
-
* The framing does the "aware, not commanded" work: explicit no-action wording
|
|
241
|
-
* so a later operator voice about these makes sense without triggering work.
|
|
242
|
-
*/
|
|
243
|
-
export function renderPassiveNoticeBlock(threadId) {
|
|
244
|
-
const notices = drainPassiveNotices(threadId);
|
|
245
|
-
if (notices.length === 0)
|
|
246
|
-
return "";
|
|
247
|
-
// Per-item fault isolation: one malformed record (bad createdAt, non-string
|
|
248
|
-
// text) must not throw AFTER the drain already consumed the batch — that
|
|
249
|
-
// would lose every notice in the turn. Bad records are skipped, not fatal.
|
|
250
|
-
const lines = [];
|
|
251
|
-
for (const n of notices) {
|
|
252
|
-
try {
|
|
253
|
-
const stamp = typeof n.createdAt === "string" ? n.createdAt.slice(0, 16).replace("T", " ") : "?";
|
|
254
|
-
if (typeof n.text !== "string" || n.text.length === 0)
|
|
255
|
-
continue;
|
|
256
|
-
lines.push(`- [${stamp}] ${n.text}`);
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
/* skip malformed record */
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (lines.length === 0)
|
|
263
|
-
return "";
|
|
264
|
-
// Delivery-loss note: drain happens at render time, inside the poll loop's
|
|
265
|
-
// transport-liveness guards — the same consumption bar the system applies to
|
|
266
|
-
// operator messages themselves. A deeper delivered-ack protocol was reviewed
|
|
267
|
-
// and deliberately not built (FYI notices must not carry more delivery
|
|
268
|
-
// guarantees than operator messages do).
|
|
269
|
-
return (`\n\n## Situational Notices (FYI — context, not command)\n` +
|
|
270
|
-
`These are awareness-only updates. Do NOT act on them, reply to them, or treat them as tasks; ` +
|
|
271
|
-
`they exist so later operator messages about them make sense.\n` +
|
|
272
|
-
lines.join("\n"));
|
|
273
|
-
}
|
|
274
|
-
//# sourceMappingURL=passive-notice.service.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"passive-notice.service.js","sourceRoot":"","sources":["../../src/services/passive-notice.service.ts"],"names":[],"mappings":"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"}
|