@adhdev/mesh-shared 1.0.60-rc.33 → 1.0.60-rc.35

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 (39) hide show
  1. package/dist/index.d.ts +10 -0
  2. package/dist/index.js +2742 -17
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +2420 -17
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/mesh-tool-names.d.ts +2 -2
  7. package/dist/mesh-vocabulary.d.ts +70 -0
  8. package/dist/mission-brief.d.ts +62 -0
  9. package/dist/outbound-message.d.ts +202 -0
  10. package/dist/path-ownership.d.ts +101 -0
  11. package/dist/protocol/daemon-server.d.ts +388 -0
  12. package/dist/protocol/dashboard-daemon-p2p.d.ts +212 -0
  13. package/dist/protocol/dashboard-server.d.ts +240 -0
  14. package/dist/protocol/envelope.d.ts +55 -0
  15. package/dist/protocol/index.d.ts +15 -0
  16. package/dist/session-launch.d.ts +113 -0
  17. package/dist/session-status.d.ts +61 -0
  18. package/dist/turn-evidence.d.ts +393 -0
  19. package/dist/turn-ipc.d.ts +974 -0
  20. package/dist/worker-protocol.d.ts +69 -0
  21. package/dist/ws-protocol.d.ts +26 -43
  22. package/package.json +1 -1
  23. package/src/index.ts +10 -0
  24. package/src/mesh-tool-names.ts +0 -1
  25. package/src/mesh-vocabulary.ts +98 -0
  26. package/src/mission-brief.ts +153 -0
  27. package/src/outbound-message.ts +320 -0
  28. package/src/path-ownership.ts +271 -0
  29. package/src/protocol/daemon-server.ts +552 -0
  30. package/src/protocol/dashboard-daemon-p2p.ts +272 -0
  31. package/src/protocol/dashboard-server.ts +340 -0
  32. package/src/protocol/envelope.ts +75 -0
  33. package/src/protocol/index.ts +16 -0
  34. package/src/session-launch.ts +247 -0
  35. package/src/session-status.ts +140 -0
  36. package/src/turn-evidence.ts +542 -0
  37. package/src/turn-ipc.ts +2231 -0
  38. package/src/worker-protocol.ts +142 -0
  39. package/src/ws-protocol.ts +41 -95
@@ -0,0 +1,142 @@
1
+ /**
2
+ * worker-protocol — what a delegated worker is told, and what it may call.
3
+ *
4
+ * Wiring-unification Phase F1 (docs/design/2026-09-23-wiring-unification.md §7b).
5
+ *
6
+ * Measured 2026-09-23 on the live preview ledger: 635 worker attempts in 14
7
+ * days, 3 `report_completion` calls. The worker MCP was delivered to every
8
+ * provider and never used, because nothing ever told the worker it existed —
9
+ * the dispatched task body carried no instruction and the coordinator prompt
10
+ * did not mention the worker tools at all. The only text a worker saw about
11
+ * its tools was the tool description.
12
+ *
13
+ * This module is the single source for BOTH sides of that contract:
14
+ * - `WORKER_TOOLS` — exactly what `adhdev mcp --mode worker` advertises
15
+ * (a parity test in mcp-server pins ListTools to this tuple);
16
+ * - `renderWorkerProtocolFooter()` — the block appended to EVERY dispatched
17
+ * task body, so a worker learns the protocol from the task itself;
18
+ * - `renderCoordinatorWorkerSection()` — the matching paragraph the
19
+ * coordinator prompt carries, so the coordinator expects structured reports
20
+ * and stops polling for them.
21
+ *
22
+ * Wiring-unification Phase H2 adds an optional mission-brief block: when a
23
+ * dispatched task belongs to a mission that carries a `MissionBrief`
24
+ * (`./mission-brief.ts`), `renderWorkerProtocolFooter` renders it ABOVE the
25
+ * `WORKER_PROTOCOL_FOOTER_MARKER` line. This is deliberate placement, not an
26
+ * accident of insertion order: `hasWorkerProtocolFooter` / `stripWorkerProtocolFooter`
27
+ * both key off the marker line alone, so a body with a brief block strips
28
+ * identically to one without — the brief is authored content from the
29
+ * coordinator's point of view (like the task message itself), never part of
30
+ * the protocol contract the marker demarcates.
31
+ */
32
+
33
+ import { renderMissionBriefBlock, type MissionBrief } from './mission-brief'
34
+
35
+ export const WORKER_TOOLS = [
36
+ 'report_completion',
37
+ 'progress_update',
38
+ 'peer_context_pull',
39
+ 'git_status',
40
+ 'git_log',
41
+ 'git_diff',
42
+ ] as const
43
+
44
+ export type WorkerTool = typeof WORKER_TOOLS[number]
45
+
46
+ const WORKER_TOOL_SET: ReadonlySet<string> = new Set(WORKER_TOOLS)
47
+
48
+ export function isWorkerTool(value: unknown): value is WorkerTool {
49
+ return typeof value === 'string' && WORKER_TOOL_SET.has(value)
50
+ }
51
+
52
+ /** Stable marker line so a body is never footered twice and tests can find it. */
53
+ export const WORKER_PROTOCOL_FOOTER_MARKER = '--- adhdev worker protocol ---'
54
+
55
+ export interface WorkerProtocolFooterInput {
56
+ taskId?: string
57
+ taskMode?: string
58
+ difficulty?: string
59
+ readonly?: boolean
60
+ /** Number of handoff notes enclosed above the footer, when any. */
61
+ enclosedHandoffNotes?: number
62
+ /** H2: the owning mission's brief, when this task belongs to one. Rendered above the marker line — see module doc. */
63
+ missionBrief?: MissionBrief
64
+ }
65
+
66
+ export function hasWorkerProtocolFooter(body: string): boolean {
67
+ return typeof body === 'string' && body.includes(WORKER_PROTOCOL_FOOTER_MARKER)
68
+ }
69
+
70
+ /**
71
+ * The protocol block appended to a dispatched task. Written for the model that
72
+ * receives it: imperative, short, and explicit about what is NOT available so
73
+ * the worker does not go looking for coordinator tools.
74
+ */
75
+ export function renderWorkerProtocolFooter(input: WorkerProtocolFooterInput = {}): string {
76
+ const briefBlock = input.missionBrief ? renderMissionBriefBlock(input.missionBrief) : null
77
+ const lines: string[] = briefBlock ? [briefBlock, '', WORKER_PROTOCOL_FOOTER_MARKER] : [WORKER_PROTOCOL_FOOTER_MARKER]
78
+ const scope: string[] = []
79
+ if (input.taskId) scope.push(`task ${input.taskId}`)
80
+ if (input.taskMode) scope.push(`mode ${input.taskMode}`)
81
+ if (input.difficulty) scope.push(`difficulty ${input.difficulty}`)
82
+ if (input.readonly) scope.push('read-only')
83
+ if (scope.length) lines.push(`You are a delegated worker (${scope.join(', ')}).`)
84
+ else lines.push('You are a delegated worker.')
85
+ lines.push(
86
+ 'When your work is finished, blocked, or has failed, call `report_completion` exactly once. '
87
+ + 'Its `summary` is recorded verbatim as the authoritative record of this task — your terminal is not '
88
+ + 'scraped for it — so state what you did, what you found, and where you left the branch.',
89
+ 'For work that runs longer than a few minutes, call `progress_update` at natural checkpoints so the '
90
+ + 'coordinator can see you are alive without polling.',
91
+ '`peer_context_pull` shows what sibling tasks in this mission have reported; `git_status` / `git_diff` / '
92
+ + '`git_log` inspect your own workspace (pass its absolute path as `workspace`).',
93
+ 'You have no coordinator tools (no `mesh_*`) by design: do not enqueue, dispatch, or restart anything. '
94
+ + 'If you need a decision from the coordinator, finish with `report_completion` and outcome `blocked`, '
95
+ + 'listing what you need in `blockers`.',
96
+ )
97
+ if (input.enclosedHandoffNotes && input.enclosedHandoffNotes > 0) {
98
+ lines.push(
99
+ `The ${input.enclosedHandoffNotes} handoff note(s) above were written by agents who touched this code before you; `
100
+ + 'honour their conflict guidance and add your own in `handoff_notes` when you report.',
101
+ )
102
+ }
103
+ return lines.join('\n')
104
+ }
105
+
106
+ /**
107
+ * The authored text of a dispatched body: everything before the footer marker.
108
+ * Ledger descriptors, replay fixtures and tests compare authored text, never
109
+ * the protocol block.
110
+ */
111
+ export function stripWorkerProtocolFooter(body: string): string {
112
+ if (typeof body !== 'string') return body
113
+ const at = body.indexOf(WORKER_PROTOCOL_FOOTER_MARKER)
114
+ if (at < 0) return body
115
+ return body.slice(0, at).replace(/\s+$/, '')
116
+ }
117
+
118
+ /**
119
+ * Append the footer to a body that does not carry one yet. Idempotent.
120
+ */
121
+ export function appendWorkerProtocolFooter(body: string, input: WorkerProtocolFooterInput = {}): string {
122
+ if (hasWorkerProtocolFooter(body)) return body
123
+ const trimmed = body.replace(/\s+$/, '')
124
+ return `${trimmed}\n\n${renderWorkerProtocolFooter(input)}`
125
+ }
126
+
127
+ /**
128
+ * The paragraph the COORDINATOR prompt carries about its workers. Kept next to
129
+ * the footer so the two halves of the protocol cannot drift apart.
130
+ */
131
+ export function renderCoordinatorWorkerSection(): string {
132
+ return [
133
+ '## Workers',
134
+ '',
135
+ `Every task you dispatch is delivered with a worker protocol footer. Workers hold exactly these tools: ${WORKER_TOOLS.map((tool) => `\`${tool}\``).join(', ')} — and no \`mesh_*\` tools.`,
136
+ '- A worker finishes by calling `report_completion`; its structured report (outcome, summary, touched files, branch state, handoff notes) is what reaches you as the completion event. Read that report — do not re-derive the outcome from `mesh_read_chat` or the terminal unless the report is missing.',
137
+ '- `progress_update` from a worker arrives as a lightweight progress note. Do NOT poll `mesh_status`, `mesh_view_queue` or `mesh_read_chat` to check on a running worker; completion, progress, blocked and failure all arrive as events.',
138
+ '- To reach a busy worker mid-task use `mesh_notify_worker`: the memo is delivered on the worker\'s next tool call (or when it becomes idle). A worker that reports `blocked` is asking you for a decision — answer it with `mesh_send_task` to the same session.',
139
+ '- `mesh_status` is for node health and capacity before delegating, never for progress.',
140
+ '- A `code_change` task\'s declared `owned_paths` (H1) is enforced at claim time: a second `code_change` task whose `owned_paths` overlaps an already-claimed task\'s is refused rather than silently racing it. A worker\'s `report_completion.touched_files` is compared against its own task\'s declaration afterward and any mismatch is surfaced back to you as evidence — it is never a validation failure, since a worker often cannot know its exact final file list at enqueue time.',
141
+ ].join('\n')
142
+ }
@@ -1,110 +1,56 @@
1
1
  /**
2
- * ws-protocol — shared string-literal unions for the daemon↔server WS surface
3
- * and the daemon↔dashboard P2P DataChannel surface.
2
+ * ws-protocol — name-only aliases for the daemon↔server WS surface and the
3
+ * daemon↔dashboard P2P DataChannel surface, plus the `auth_ok.payload.limits`
4
+ * contract.
4
5
  *
5
- * Fragmentation audit: these message types existed as a TypeScript union in
6
- * exactly ONE package (the proprietary daemon-cloud's server-connection.ts),
7
- * which is a leaf CONSUMER — the other two participants (the Workers server
8
- * and OSS daemon-core, which is the primary `status_report` producer) matched
9
- * bare string literals by hand. Renaming `auth_ok` server-side would compile
10
- * everywhere and leave the daemon reconnecting forever. Pure literals, zero
11
- * runtime deps — the textbook mesh-shared leaf.
12
- *
13
- * SCOPE HONESTY: this file declares the OSS-visible protocol surface. The
14
- * proprietary repo's server-connection.ts remains the authority for the full
15
- * ServerToDaemon command set; it should adopt these unions and extend them
16
- * (`ServerToDaemonMsg | <proprietary extras>`) rather than re-declaring the
17
- * shared members. Members here are the ones OSS daemon-core itself produces
18
- * or matches.
6
+ * Wiring-unification Phase A2: the typed frame unions now live in ./protocol
7
+ * (one file per link, payloads included). This file keeps the symbols that
8
+ * pre-dated them — `DaemonToServerWsMsg`, `ServerToDaemonWsMsg`,
9
+ * `P2PSignalingWsMsg`, `DashboardP2PMessageKind` and the two name arrays —
10
+ * DERIVED from the protocol unions rather than re-declared, so they cannot
11
+ * drift again. Before this derivation the list here said `offer|answer|ice`
12
+ * while the wire said `p2p_offer|p2p_answer|p2p_ice`, declared a `log` frame
13
+ * nothing sent or handled, omitted `daemon_mesh_command` (sent by a raw
14
+ * `ws.send`), and listed 12 of the ~27 P2P kinds — and nothing imported the
15
+ * drifted members, so nothing noticed.
19
16
  */
20
17
 
21
- /** Messages the daemon sends UP to the Workers server over the WS bridge. */
22
- export type DaemonToServerWsMsg =
23
- | 'auth'
24
- | 'status_report'
25
- | 'status_heartbeat'
26
- | 'status_event'
27
- | 'command_result'
28
- | 'error'
29
- | 'agent_event'
30
- | 'log'
31
- /**
32
- * seqscribe Beacon vectors (design §7.1). ONE daemon-initiated frame carries
33
- * both directions — `op: 'put'` stores this node's content-free vector
34
- * report, `op: 'get'` asks for the board — because the server has no way to
35
- * wake itself: `DaemonConnectionDO` has neither an alarm nor a timer, and
36
- * adding one would hit the most request-quota-pressured axis in the system.
37
- */
38
- | 'beacon_vectors';
39
-
40
- /** Server→daemon control messages the OSS engine reacts to. */
41
- export type ServerToDaemonWsMsg =
42
- | 'auth_ok'
43
- | 'auth_error'
44
- | 'machine_evicted'
45
- | 'force_disconnect'
46
- | 'token_revoked'
47
- | 'version_mismatch'
48
- | 'force_update_required'
49
- | 'command'
50
- | 'agent_command'
51
- | 'resolve_action'
52
- /**
53
- * Reply to a `beacon_vectors` GET, correlated by `requestId`. A PUT is
54
- * fire-and-forget and gets no reply at all — the beacon is advisory, so a
55
- * lost report costs one debounce cycle of prediction accuracy and nothing
56
- * else, which is not worth an ack round trip.
57
- */
58
- | 'beacon_vectors_result';
18
+ import {
19
+ DAEMON_TO_SERVER_TYPES,
20
+ SERVER_TO_DAEMON_CONTROL_TYPES,
21
+ isDaemonToServerType,
22
+ isServerToDaemonControlType,
23
+ type DaemonToServerType,
24
+ type ServerToDaemonControlType,
25
+ type P2PSignalType,
26
+ type MeshP2PSignalType,
27
+ type DashboardToDaemonP2PType,
28
+ type DaemonToDashboardP2PType,
29
+ } from './protocol';
59
30
 
60
- /** P2P signaling relayed through the server WS. */
61
- export type P2PSignalingWsMsg =
62
- | 'p2p_ready'
63
- | 'offer'
64
- | 'answer'
65
- | 'ice'
66
- | 'mesh_p2p_ready'
67
- | 'mesh_p2p_offer'
68
- | 'mesh_p2p_answer'
69
- | 'mesh_p2p_ice';
31
+ /** Messages the daemon sends UP to the Workers server over the WS bridge (see protocol/daemon-server.ts). */
32
+ export type DaemonToServerWsMsg = DaemonToServerType;
70
33
 
71
34
  /**
72
- * Dashboard↔daemon P2P DataChannel JSON message kinds. Previously matched as
73
- * hand-synced literals on both ends with NO shared symbol anywhere —
74
- * `p2p_evicted` had exactly two occurrences repo-wide (emit + handle).
35
+ * Server→daemon CONTROL messages (see protocol/daemon-server.ts). The server
36
+ * additionally relays an open command namespace as the frame `type`
37
+ * (`ServerDirectCommandMsg`), which is deliberately not a member here.
75
38
  */
76
- export type DashboardP2PMessageKind =
77
- | 'ping'
78
- | 'pong'
79
- | 'status_report'
80
- | 'status_event'
81
- | 'p2p_evicted'
82
- | 'command'
83
- | 'command_result'
84
- | 'command_result_chunk'
85
- | 'screenshot_start'
86
- | 'screenshot_stop'
87
- | 'pty_input'
88
- | 'pty_resize';
39
+ export type ServerToDaemonWsMsg = ServerToDaemonControlType;
89
40
 
90
- export const DAEMON_TO_SERVER_WS_MSGS: readonly DaemonToServerWsMsg[] = [
91
- 'auth', 'status_report', 'status_heartbeat', 'status_event', 'command_result', 'error', 'agent_event', 'log',
92
- 'beacon_vectors',
93
- ];
41
+ /** P2P signaling relayed through the server WS — the `p2p_`/`mesh_p2p_` spellings the wire actually uses. */
42
+ export type P2PSignalingWsMsg = P2PSignalType | MeshP2PSignalType;
94
43
 
95
- export const SERVER_TO_DAEMON_WS_MSGS: readonly ServerToDaemonWsMsg[] = [
96
- 'auth_ok', 'auth_error', 'machine_evicted', 'force_disconnect', 'token_revoked',
97
- 'version_mismatch', 'force_update_required', 'command', 'agent_command', 'resolve_action',
98
- 'beacon_vectors_result',
99
- ];
44
+ /** Every dashboard↔daemon P2P DataChannel JSON kind, both directions (see protocol/dashboard-daemon-p2p.ts). */
45
+ export type DashboardP2PMessageKind = DashboardToDaemonP2PType | DaemonToDashboardP2PType;
100
46
 
101
- export function isDaemonToServerWsMsg(value: unknown): value is DaemonToServerWsMsg {
102
- return typeof value === 'string' && (DAEMON_TO_SERVER_WS_MSGS as readonly string[]).includes(value);
103
- }
47
+ export const DAEMON_TO_SERVER_WS_MSGS: readonly DaemonToServerWsMsg[] = DAEMON_TO_SERVER_TYPES;
104
48
 
105
- export function isServerToDaemonWsMsg(value: unknown): value is ServerToDaemonWsMsg {
106
- return typeof value === 'string' && (SERVER_TO_DAEMON_WS_MSGS as readonly string[]).includes(value);
107
- }
49
+ export const SERVER_TO_DAEMON_WS_MSGS: readonly ServerToDaemonWsMsg[] = SERVER_TO_DAEMON_CONTROL_TYPES;
50
+
51
+ export const isDaemonToServerWsMsg: (value: unknown) => value is DaemonToServerWsMsg = isDaemonToServerType;
52
+
53
+ export const isServerToDaemonWsMsg: (value: unknown) => value is ServerToDaemonWsMsg = isServerToDaemonControlType;
108
54
 
109
55
  /**
110
56
  * `auth_ok.payload.limits` — the plan-limit contract the server sends DOWN to