@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.
- package/dist/index.d.ts +10 -0
- package/dist/index.js +2742 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2420 -17
- package/dist/index.mjs.map +1 -1
- package/dist/mesh-tool-names.d.ts +2 -2
- package/dist/mesh-vocabulary.d.ts +70 -0
- package/dist/mission-brief.d.ts +62 -0
- package/dist/outbound-message.d.ts +202 -0
- package/dist/path-ownership.d.ts +101 -0
- package/dist/protocol/daemon-server.d.ts +388 -0
- package/dist/protocol/dashboard-daemon-p2p.d.ts +212 -0
- package/dist/protocol/dashboard-server.d.ts +240 -0
- package/dist/protocol/envelope.d.ts +55 -0
- package/dist/protocol/index.d.ts +15 -0
- package/dist/session-launch.d.ts +113 -0
- package/dist/session-status.d.ts +61 -0
- package/dist/turn-evidence.d.ts +393 -0
- package/dist/turn-ipc.d.ts +974 -0
- package/dist/worker-protocol.d.ts +69 -0
- package/dist/ws-protocol.d.ts +26 -43
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/mesh-tool-names.ts +0 -1
- package/src/mesh-vocabulary.ts +98 -0
- package/src/mission-brief.ts +153 -0
- package/src/outbound-message.ts +320 -0
- package/src/path-ownership.ts +271 -0
- package/src/protocol/daemon-server.ts +552 -0
- package/src/protocol/dashboard-daemon-p2p.ts +272 -0
- package/src/protocol/dashboard-server.ts +340 -0
- package/src/protocol/envelope.ts +75 -0
- package/src/protocol/index.ts +16 -0
- package/src/session-launch.ts +247 -0
- package/src/session-status.ts +140 -0
- package/src/turn-evidence.ts +542 -0
- package/src/turn-ipc.ts +2231 -0
- package/src/worker-protocol.ts +142 -0
- 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
|
+
}
|
package/src/ws-protocol.ts
CHANGED
|
@@ -1,110 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ws-protocol —
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
/**
|
|
61
|
-
export type
|
|
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
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* `
|
|
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
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
106
|
-
|
|
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
|