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,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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"}