@adhdev/mesh-shared 1.0.60-rc.73 → 1.0.60-rc.74
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/brain-routing.d.ts +4 -6
- package/dist/git-diff.d.ts +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +84 -269
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -250
- package/dist/index.mjs.map +1 -1
- package/dist/json.d.ts +4 -0
- package/dist/mesh-tool-names.d.ts +11 -7
- package/dist/mesh-vocabulary.d.ts +1 -2
- package/dist/outbound-message.d.ts +1 -2
- package/dist/protocol/daemon-server.d.ts +8 -31
- package/dist/session-launch.d.ts +0 -8
- package/dist/slot-proposal.d.ts +1 -27
- package/dist/turn-ipc-mission.d.ts +0 -7
- package/dist/turn-ipc-queue.d.ts +10 -35
- package/dist/turn-ipc-stats.d.ts +72 -0
- package/dist/turn-ipc.d.ts +4 -4
- package/package.json +1 -1
- package/src/brain-routing.ts +6 -8
- package/src/git-diff.ts +48 -0
- package/src/index.ts +1 -1
- package/src/json.ts +10 -0
- package/src/mesh-tool-names.ts +20 -25
- package/src/mesh-vocabulary.ts +1 -2
- package/src/outbound-message.ts +1 -2
- package/src/protocol/daemon-server.ts +3 -34
- package/src/session-launch.ts +2 -4
- package/src/slot-proposal.ts +1 -47
- package/src/turn-ipc-mission.ts +7 -21
- package/src/turn-ipc-queue.ts +40 -102
- package/src/turn-ipc-stats.ts +193 -0
- package/src/turn-ipc.ts +5 -11
- package/dist/magi.d.ts +0 -281
- package/dist/turn-ipc-graph.d.ts +0 -195
- package/src/magi.ts +0 -310
- package/src/turn-ipc-graph.ts +0 -477
package/src/turn-ipc-queue.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* turn-ipc — local records, queue composites and active work (C-W9a):
|
|
3
|
-
* record_local, queue_query, queue_enqueue,
|
|
4
|
-
* queue_requeue, direct_dispatch_record,
|
|
3
|
+
* record_local, queue_query, queue_enqueue, queue_enqueue_batch, queue_cancel,
|
|
4
|
+
* queue_requeue, direct_dispatch_record, active_work_query,
|
|
5
5
|
* recovery_context_query. Part of the turn-ipc wire contract (./turn-ipc.ts).
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
//
|
|
17
17
|
// CONTENT BOUNDARY: like `mission_upsert`'s `goal`, `note_upsert`'s `text` and
|
|
18
18
|
// `ledger_query`'s entry payloads, several fields here are free text or
|
|
19
|
-
// unbounded JSON (a task message, a
|
|
19
|
+
// unbounded JSON (a task message, a queue row with its
|
|
20
20
|
// message, an active-work record with its task summary). This is fine for the
|
|
21
21
|
// same reason: local IPC between the mcp-server and the daemon on the
|
|
22
22
|
// operator's own machine — never a cross-machine or server hop. Those fields
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
isOptionalRecord,
|
|
37
37
|
isRecordArray,
|
|
38
38
|
isOptionalString,
|
|
39
|
-
makeGuard,
|
|
40
39
|
} from './turn-ipc-guards';
|
|
41
40
|
|
|
42
41
|
/** A queue row as the daemon's `getQueue` returns it — a JSON passthrough (see the section note). */
|
|
@@ -51,7 +50,7 @@ function isQueueEntryWire(value: unknown): value is QueueEntryWire {
|
|
|
51
50
|
// `meshRecord(meshId, kind, scalars, { local: true })` over IPC: the scalar
|
|
52
51
|
// projection goes to `mesh.<id>.events` (content-free), the FULL payload lands
|
|
53
52
|
// in the daemon's `mesh_local_records` (local-only). Replaces every mcp-server
|
|
54
|
-
// `appendLedgerEntry` (dispatch /
|
|
53
|
+
// `appendLedgerEntry` (dispatch / checkpoint message /
|
|
55
54
|
// reconcile records). `payload` is free-form JSON (see the section note).
|
|
56
55
|
|
|
57
56
|
export interface RecordLocalRequest {
|
|
@@ -116,7 +115,7 @@ export interface QueueQueryRequest {
|
|
|
116
115
|
/** With `withCounts`: also count terminal rows whose `updated_at` is older than this age. */
|
|
117
116
|
historicalOlderThanMs?: number
|
|
118
117
|
/**
|
|
119
|
-
* Also return `dependencyHeads`: id/status/
|
|
118
|
+
* Also return `dependencyHeads`: id/status/cancelReason of every
|
|
120
119
|
* row the returned entries list in `dependsOn` that is not itself returned —
|
|
121
120
|
* enough to annotate dependency state without reading the whole queue.
|
|
122
121
|
*/
|
|
@@ -127,7 +126,6 @@ export interface QueueQueryRequest {
|
|
|
127
126
|
export interface QueueDependencyHeadWire {
|
|
128
127
|
id: string
|
|
129
128
|
status: string
|
|
130
|
-
blockedReason?: string
|
|
131
129
|
cancelReason?: string
|
|
132
130
|
}
|
|
133
131
|
|
|
@@ -155,9 +153,8 @@ export function decodeQueueQueryRequest(value: unknown): QueueQueryRequest | nul
|
|
|
155
153
|
}
|
|
156
154
|
|
|
157
155
|
function isQueueDependencyHeadWire(value: unknown): value is QueueDependencyHeadWire {
|
|
158
|
-
return isRecord(value) && hasOnlyKeys(value, ['id', 'status', '
|
|
156
|
+
return isRecord(value) && hasOnlyKeys(value, ['id', 'status', 'cancelReason'])
|
|
159
157
|
&& typeof value.id === 'string' && typeof value.status === 'string'
|
|
160
|
-
&& (value.blockedReason === undefined || typeof value.blockedReason === 'string')
|
|
161
158
|
&& (value.cancelReason === undefined || typeof value.cancelReason === 'string')
|
|
162
159
|
}
|
|
163
160
|
|
|
@@ -176,9 +173,8 @@ export function decodeQueueQueryResponse(value: unknown): QueueQueryResponse | n
|
|
|
176
173
|
|
|
177
174
|
// ── queue_enqueue ──
|
|
178
175
|
//
|
|
179
|
-
// `enqueueTask(meshId, message, options)`
|
|
180
|
-
//
|
|
181
|
-
// daemon. `options` is `MeshEnqueueTaskOptions` (daemon-core) as a JSON
|
|
176
|
+
// `enqueueTask(meshId, message, options)` in the daemon. `options` is
|
|
177
|
+
// `MeshEnqueueTaskOptions` (daemon-core) as a JSON
|
|
182
178
|
// passthrough; the daemon's enqueue guards (message, task-mode, difficulty,
|
|
183
179
|
// dependency cycle) run unchanged and a refusal comes back as the error.
|
|
184
180
|
|
|
@@ -188,8 +184,6 @@ export interface QueueEnqueueRequest {
|
|
|
188
184
|
/** Free text (the task message — see the section note). */
|
|
189
185
|
message: string
|
|
190
186
|
options?: Record<string, unknown>
|
|
191
|
-
/** `recordSingleEnqueueDecision` args minus `taskId` (the daemon fills it). */
|
|
192
|
-
decision?: Record<string, unknown>
|
|
193
187
|
}
|
|
194
188
|
|
|
195
189
|
export interface QueueEnqueueResponse {
|
|
@@ -197,10 +191,10 @@ export interface QueueEnqueueResponse {
|
|
|
197
191
|
}
|
|
198
192
|
|
|
199
193
|
function isQueueEnqueueRequest(value: unknown): value is QueueEnqueueRequest {
|
|
200
|
-
return isRecord(value) && hasOnlyKeys(value, ['v', 'meshId', 'message', 'options'
|
|
194
|
+
return isRecord(value) && hasOnlyKeys(value, ['v', 'meshId', 'message', 'options'])
|
|
201
195
|
&& value.v === TURN_IPC_PROTOCOL_VERSION && isEvidenceIdentifier(value.meshId)
|
|
202
196
|
&& typeof value.message === 'string'
|
|
203
|
-
&& isOptionalRecord(value.options)
|
|
197
|
+
&& isOptionalRecord(value.options)
|
|
204
198
|
}
|
|
205
199
|
|
|
206
200
|
export function decodeQueueEnqueueRequest(value: unknown): QueueEnqueueRequest | null {
|
|
@@ -215,53 +209,41 @@ export function decodeQueueEnqueueResponse(value: unknown): QueueEnqueueResponse
|
|
|
215
209
|
return isQueueEnqueueResponse(value) ? value : null
|
|
216
210
|
}
|
|
217
211
|
|
|
218
|
-
// ──
|
|
212
|
+
// ── queue_enqueue_batch ──
|
|
219
213
|
//
|
|
220
|
-
// The atomic
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
// `
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// belong to the command envelope (`{ success, error, code }`) the client
|
|
230
|
-
// unwraps, so a result must never reuse them.
|
|
231
|
-
|
|
232
|
-
export interface QueueEnqueueGraphRequest {
|
|
214
|
+
// The atomic multi-task enqueue in the daemon: `enqueueTaskBatch(meshId, specs)`
|
|
215
|
+
// — every task inserts or none does. A refusal answers
|
|
216
|
+
// `{ ok: false, refusalCode?, message, extra? }` (a domain refusal is a RESULT
|
|
217
|
+
// here, not a transport error, so the caller keeps its code/extra for the tool
|
|
218
|
+
// response). NOT `code`/`error`: those two keys belong to the command envelope
|
|
219
|
+
// (`{ success, error, code }`) the client unwraps, so a result must never
|
|
220
|
+
// reuse them.
|
|
221
|
+
|
|
222
|
+
export interface QueueEnqueueBatchRequest {
|
|
233
223
|
v: typeof TURN_IPC_PROTOCOL_VERSION
|
|
234
224
|
meshId: string
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
specs?: readonly Record<string, unknown>[]
|
|
238
|
-
/** graph: `commitMeshGraphPlan` input minus `meshId` (JSON passthrough). */
|
|
239
|
-
plan?: Record<string, unknown>
|
|
240
|
-
/** Audit context: batchId / missionId / coordinatorSessionId / onDependencyFailure / orchestrationDecision / taskCount. */
|
|
241
|
-
audit?: Record<string, unknown>
|
|
225
|
+
/** `MeshTaskBatchEntrySpec[]` (daemon-core, JSON passthrough). */
|
|
226
|
+
specs: readonly Record<string, unknown>[]
|
|
242
227
|
}
|
|
243
228
|
|
|
244
|
-
export type
|
|
245
|
-
| { ok: true; tasks: readonly QueueEntryWire[]
|
|
229
|
+
export type QueueEnqueueBatchResponse =
|
|
230
|
+
| { ok: true; tasks: readonly QueueEntryWire[] }
|
|
246
231
|
| { ok: false; refusalCode?: string; message: string; extra?: Record<string, unknown> }
|
|
247
232
|
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (value.mode === 'graph') return isRecord(value.plan) && value.specs === undefined && isOptionalRecord(value.audit)
|
|
253
|
-
return false
|
|
233
|
+
function isQueueEnqueueBatchRequest(value: unknown): value is QueueEnqueueBatchRequest {
|
|
234
|
+
return isRecord(value) && hasOnlyKeys(value, ['v', 'meshId', 'specs'])
|
|
235
|
+
&& value.v === TURN_IPC_PROTOCOL_VERSION && isEvidenceIdentifier(value.meshId)
|
|
236
|
+
&& isRecordArray(value.specs)
|
|
254
237
|
}
|
|
255
238
|
|
|
256
|
-
export function
|
|
257
|
-
return
|
|
239
|
+
export function decodeQueueEnqueueBatchRequest(value: unknown): QueueEnqueueBatchRequest | null {
|
|
240
|
+
return isQueueEnqueueBatchRequest(value) ? value : null
|
|
258
241
|
}
|
|
259
242
|
|
|
260
|
-
function
|
|
243
|
+
function isQueueEnqueueBatchResponse(value: unknown): value is QueueEnqueueBatchResponse {
|
|
261
244
|
if (!isRecord(value)) return false
|
|
262
245
|
if (value.ok === true) {
|
|
263
|
-
return hasOnlyKeys(value, ['ok', 'tasks'
|
|
264
|
-
&& isOptionalRecord(value.graph)
|
|
246
|
+
return hasOnlyKeys(value, ['ok', 'tasks']) && Array.isArray(value.tasks) && value.tasks.every(isQueueEntryWire)
|
|
265
247
|
}
|
|
266
248
|
if (value.ok === false) {
|
|
267
249
|
return hasOnlyKeys(value, ['ok', 'refusalCode', 'message', 'extra']) && typeof value.message === 'string'
|
|
@@ -270,8 +252,8 @@ function isQueueEnqueueGraphResponse(value: unknown): value is QueueEnqueueGraph
|
|
|
270
252
|
return false
|
|
271
253
|
}
|
|
272
254
|
|
|
273
|
-
export function
|
|
274
|
-
return
|
|
255
|
+
export function decodeQueueEnqueueBatchResponse(value: unknown): QueueEnqueueBatchResponse | null {
|
|
256
|
+
return isQueueEnqueueBatchResponse(value) ? value : null
|
|
275
257
|
}
|
|
276
258
|
|
|
277
259
|
// ── queue_cancel ──
|
|
@@ -347,9 +329,8 @@ export function decodeQueueRequeueResponse(value: unknown): QueueRequeueResponse
|
|
|
347
329
|
//
|
|
348
330
|
// The post-dispatch bookkeeping of `mesh_send_task`'s direct arms, in the
|
|
349
331
|
// daemon: `recordDirectDispatchTask` (materialise the queue row, stamped with
|
|
350
|
-
// the already-open `mesh_direct` attempt) and
|
|
351
|
-
//
|
|
352
|
-
// — a dispatch that already happened must never fail on its bookkeeping.
|
|
332
|
+
// the already-open `mesh_direct` attempt). Best-effort and reported — a
|
|
333
|
+
// dispatch that already happened must never fail on its bookkeeping.
|
|
353
334
|
|
|
354
335
|
export interface DirectDispatchRecordRequest {
|
|
355
336
|
v: typeof TURN_IPC_PROTOCOL_VERSION
|
|
@@ -359,19 +340,16 @@ export interface DirectDispatchRecordRequest {
|
|
|
359
340
|
message: string
|
|
360
341
|
/** `recordDirectDispatchTask` options minus `id` (JSON passthrough). */
|
|
361
342
|
task?: Record<string, unknown>
|
|
362
|
-
/** `recordDirectDispatchDecision` args minus `taskId` (JSON passthrough). */
|
|
363
|
-
decision?: Record<string, unknown>
|
|
364
343
|
}
|
|
365
344
|
|
|
366
345
|
export interface DirectDispatchRecordResponse {
|
|
367
346
|
taskRecorded: boolean
|
|
368
|
-
decisionRecorded: boolean
|
|
369
347
|
}
|
|
370
348
|
|
|
371
349
|
function isDirectDispatchRecordRequest(value: unknown): value is DirectDispatchRecordRequest {
|
|
372
|
-
return isRecord(value) && hasOnlyKeys(value, ['v', 'meshId', 'taskId', 'message', 'task'
|
|
350
|
+
return isRecord(value) && hasOnlyKeys(value, ['v', 'meshId', 'taskId', 'message', 'task'])
|
|
373
351
|
&& value.v === TURN_IPC_PROTOCOL_VERSION && isEvidenceIdentifier(value.meshId) && isEvidenceIdentifier(value.taskId)
|
|
374
|
-
&& typeof value.message === 'string' && isOptionalRecord(value.task)
|
|
352
|
+
&& typeof value.message === 'string' && isOptionalRecord(value.task)
|
|
375
353
|
}
|
|
376
354
|
|
|
377
355
|
export function decodeDirectDispatchRecordRequest(value: unknown): DirectDispatchRecordRequest | null {
|
|
@@ -379,54 +357,14 @@ export function decodeDirectDispatchRecordRequest(value: unknown): DirectDispatc
|
|
|
379
357
|
}
|
|
380
358
|
|
|
381
359
|
function isDirectDispatchRecordResponse(value: unknown): value is DirectDispatchRecordResponse {
|
|
382
|
-
return isRecord(value) && hasOnlyKeys(value, ['taskRecorded'
|
|
383
|
-
&& typeof value.taskRecorded === 'boolean'
|
|
360
|
+
return isRecord(value) && hasOnlyKeys(value, ['taskRecorded'])
|
|
361
|
+
&& typeof value.taskRecorded === 'boolean'
|
|
384
362
|
}
|
|
385
363
|
|
|
386
364
|
export function decodeDirectDispatchRecordResponse(value: unknown): DirectDispatchRecordResponse | null {
|
|
387
365
|
return isDirectDispatchRecordResponse(value) ? value : null
|
|
388
366
|
}
|
|
389
367
|
|
|
390
|
-
// ── graph_audit_record ──
|
|
391
|
-
//
|
|
392
|
-
// The coordinator-gate / node-patch provenance records the `mesh_graph_*`
|
|
393
|
-
// tools write after a gate action (design :740-750), written by the daemon's
|
|
394
|
-
// allow-listed recorders (mesh-graph-provenance.ts) — the mcp-server never
|
|
395
|
-
// builds the record payload itself. `fields` is the recorder's argument object.
|
|
396
|
-
|
|
397
|
-
export const GRAPH_AUDIT_EVENTS = ['gate_claimed', 'gate_released', 'gate_abandoned', 'node_patched'] as const
|
|
398
|
-
export type GraphAuditEvent = typeof GRAPH_AUDIT_EVENTS[number]
|
|
399
|
-
const isGraphAuditEvent = makeGuard(GRAPH_AUDIT_EVENTS)
|
|
400
|
-
|
|
401
|
-
export interface GraphAuditRecordRequest {
|
|
402
|
-
v: typeof TURN_IPC_PROTOCOL_VERSION
|
|
403
|
-
meshId: string
|
|
404
|
-
event: GraphAuditEvent
|
|
405
|
-
fields: Record<string, unknown>
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export interface GraphAuditRecordResponse {
|
|
409
|
-
recorded: boolean
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function isGraphAuditRecordRequest(value: unknown): value is GraphAuditRecordRequest {
|
|
413
|
-
return isRecord(value) && hasOnlyKeys(value, ['v', 'meshId', 'event', 'fields'])
|
|
414
|
-
&& value.v === TURN_IPC_PROTOCOL_VERSION && isEvidenceIdentifier(value.meshId)
|
|
415
|
-
&& isGraphAuditEvent(value.event) && isRecord(value.fields)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
export function decodeGraphAuditRecordRequest(value: unknown): GraphAuditRecordRequest | null {
|
|
419
|
-
return isGraphAuditRecordRequest(value) ? value : null
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function isGraphAuditRecordResponse(value: unknown): value is GraphAuditRecordResponse {
|
|
423
|
-
return isRecord(value) && hasOnlyKeys(value, ['recorded']) && typeof value.recorded === 'boolean'
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export function decodeGraphAuditRecordResponse(value: unknown): GraphAuditRecordResponse | null {
|
|
427
|
-
return isGraphAuditRecordResponse(value) ? value : null
|
|
428
|
-
}
|
|
429
|
-
|
|
430
368
|
// ── active_work_query ──
|
|
431
369
|
//
|
|
432
370
|
// The active-work view, COMPUTED in the daemon: it reads the queue, the open
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turn-ipc — task stats and maintenance commands: task_stats_query,
|
|
3
|
+
* prune_stale_direct, orphaned_pin_notify. Part of the turn-ipc wire contract
|
|
4
|
+
* (./turn-ipc.ts).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
TURN_IPC_PROTOCOL_VERSION,
|
|
9
|
+
hasOnlyKeys,
|
|
10
|
+
isOptionalBoolean,
|
|
11
|
+
isOptionalRecord,
|
|
12
|
+
isNonNegativeInt,
|
|
13
|
+
isRecordArray,
|
|
14
|
+
} from './turn-ipc-guards';
|
|
15
|
+
import { isRecord } from './protocol/envelope';
|
|
16
|
+
import { isEvidenceIdentifier } from './turn-evidence';
|
|
17
|
+
|
|
18
|
+
// ─── task_stats_query ───────────────────────────────────────────────────────
|
|
19
|
+
//
|
|
20
|
+
// C-W9c: replaces the in-process `computeMeshTaskStats` / `computeMeshMissionStats`
|
|
21
|
+
// (daemon-core `mesh-task-stats.ts`) — both scan the daemon's queue + local
|
|
22
|
+
// records, so both move behind one command. Exactly one of `taskIds` /
|
|
23
|
+
// `missionId` selects the per-task mode; `missionId` alone with `rollup: true`
|
|
24
|
+
// additionally returns the mission-level aggregate.
|
|
25
|
+
|
|
26
|
+
export interface TaskStatsQueryRequest {
|
|
27
|
+
v: typeof TURN_IPC_PROTOCOL_VERSION
|
|
28
|
+
meshId: string
|
|
29
|
+
taskIds?: readonly string[]
|
|
30
|
+
missionId?: string
|
|
31
|
+
tail?: number
|
|
32
|
+
/** Also compute `computeMeshMissionStats` — requires `missionId`. */
|
|
33
|
+
rollup?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* Batch rollups: `computeMeshMissionStats` for every id, over ONE queue read and
|
|
36
|
+
* ONE record read (answers `missions`, `tasks` empty). Exclusive with
|
|
37
|
+
* `taskIds`/`missionId`. An older daemon rejects the key.
|
|
38
|
+
*/
|
|
39
|
+
missionIds?: readonly string[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TaskStatsQueryResponse {
|
|
43
|
+
/** `MeshTaskStats[]`, JSON passthrough. */
|
|
44
|
+
tasks: readonly Record<string, unknown>[]
|
|
45
|
+
/** `MeshMissionStats`, JSON passthrough; present only when `rollup: true` was requested and satisfiable. */
|
|
46
|
+
mission?: Record<string, unknown>
|
|
47
|
+
/** Batch mode: `MeshMissionStats` per requested mission id, JSON passthrough. */
|
|
48
|
+
missions?: Record<string, Record<string, unknown>>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isTaskStatsQueryRequest(value: unknown): value is TaskStatsQueryRequest {
|
|
52
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'taskIds', 'missionId', 'tail', 'rollup', 'missionIds'])) return false
|
|
53
|
+
if (value.v !== TURN_IPC_PROTOCOL_VERSION || !isEvidenceIdentifier(value.meshId)) return false
|
|
54
|
+
if (value.taskIds !== undefined) {
|
|
55
|
+
if (!Array.isArray(value.taskIds) || value.taskIds.length === 0 || !value.taskIds.every(isEvidenceIdentifier)) return false
|
|
56
|
+
}
|
|
57
|
+
if (value.missionId !== undefined && !isEvidenceIdentifier(value.missionId)) return false
|
|
58
|
+
if (value.tail !== undefined && !isNonNegativeInt(value.tail)) return false
|
|
59
|
+
if (value.missionIds !== undefined) {
|
|
60
|
+
if (!Array.isArray(value.missionIds) || value.missionIds.length === 0 || !value.missionIds.every(isEvidenceIdentifier)) return false
|
|
61
|
+
if (value.taskIds !== undefined || value.missionId !== undefined) return false
|
|
62
|
+
}
|
|
63
|
+
if (value.rollup === true && value.missionId === undefined) return false
|
|
64
|
+
return isOptionalBoolean(value.rollup)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function decodeTaskStatsQueryRequest(value: unknown): TaskStatsQueryRequest | null {
|
|
68
|
+
return isTaskStatsQueryRequest(value) ? value : null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isTaskStatsQueryResponse(value: unknown): value is TaskStatsQueryResponse {
|
|
72
|
+
return isRecord(value) && hasOnlyKeys(value, ['tasks', 'mission', 'missions']) && isRecordArray(value.tasks) && isOptionalRecord(value.mission)
|
|
73
|
+
&& (value.missions === undefined || (isRecord(value.missions) && Object.values(value.missions).every(isRecord)))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function decodeTaskStatsQueryResponse(value: unknown): TaskStatsQueryResponse | null {
|
|
77
|
+
return isTaskStatsQueryResponse(value) ? value : null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── prune_stale_direct ─────────────────────────────────────────────────────
|
|
81
|
+
//
|
|
82
|
+
// C-W9c: replaces the in-process `pruneStaleDirectDispatches` call
|
|
83
|
+
// (`mesh_cleanup_sessions` mode=prune_stale_direct, `mesh-tools-session.ts`). Unlike the other
|
|
84
|
+
// C-W9 commands this one does NOT take its inputs (queue/records/direct
|
|
85
|
+
// dispatches) on the wire — the daemon already reads them itself
|
|
86
|
+
// (`getQueue`/`readLocalRecords`/`getActiveDirectDispatches`), so the request
|
|
87
|
+
// carries only the decision knobs. `closeDispatches` (the C-W8 `turn_cancel`
|
|
88
|
+
// callback) also runs daemon-side now, since it is the same process.
|
|
89
|
+
|
|
90
|
+
export interface PruneStaleDirectRequest {
|
|
91
|
+
v: typeof TURN_IPC_PROTOCOL_VERSION
|
|
92
|
+
meshId: string
|
|
93
|
+
execute?: boolean
|
|
94
|
+
includeTerminal?: boolean
|
|
95
|
+
source?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface PruneStaleDirectResponse {
|
|
99
|
+
mode: 'execute' | 'dry_run'
|
|
100
|
+
includeTerminal: boolean
|
|
101
|
+
candidateCount: number
|
|
102
|
+
/** `MeshActiveWorkRecord[]`, JSON passthrough. */
|
|
103
|
+
prunable: readonly Record<string, unknown>[]
|
|
104
|
+
prunedCount: number
|
|
105
|
+
preservedUnacknowledged: readonly Record<string, unknown>[]
|
|
106
|
+
preservedLedgerOnly: readonly Record<string, unknown>[]
|
|
107
|
+
preservedNotOrphan: readonly Record<string, unknown>[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPruneStaleDirectRequest(value: unknown): value is PruneStaleDirectRequest {
|
|
111
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'execute', 'includeTerminal', 'source'])) return false
|
|
112
|
+
if (value.v !== TURN_IPC_PROTOCOL_VERSION || !isEvidenceIdentifier(value.meshId)) return false
|
|
113
|
+
if (value.source !== undefined && typeof value.source !== 'string') return false
|
|
114
|
+
return isOptionalBoolean(value.execute) && isOptionalBoolean(value.includeTerminal)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function decodePruneStaleDirectRequest(value: unknown): PruneStaleDirectRequest | null {
|
|
118
|
+
return isPruneStaleDirectRequest(value) ? value : null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isPruneStaleDirectResponse(value: unknown): value is PruneStaleDirectResponse {
|
|
122
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['mode', 'includeTerminal', 'candidateCount', 'prunable', 'prunedCount', 'preservedUnacknowledged', 'preservedLedgerOnly', 'preservedNotOrphan'])) return false
|
|
123
|
+
if (value.mode !== 'execute' && value.mode !== 'dry_run') return false
|
|
124
|
+
if (typeof value.includeTerminal !== 'boolean') return false
|
|
125
|
+
if (!isNonNegativeInt(value.candidateCount) || !isNonNegativeInt(value.prunedCount)) return false
|
|
126
|
+
return isRecordArray(value.prunable) && isRecordArray(value.preservedUnacknowledged)
|
|
127
|
+
&& isRecordArray(value.preservedLedgerOnly) && isRecordArray(value.preservedNotOrphan)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function decodePruneStaleDirectResponse(value: unknown): PruneStaleDirectResponse | null {
|
|
131
|
+
return isPruneStaleDirectResponse(value) ? value : null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── orphaned_pin_notify ────────────────────────────────────────────────────
|
|
135
|
+
//
|
|
136
|
+
// C-W9c: replaces the in-process `notifyCoordinatorOfOrphanedPins` call
|
|
137
|
+
// (`mesh_queue_cancel` tool, `mesh-tools-queue.ts`, CANCEL-ORPHANS-PINNED-TASK).
|
|
138
|
+
// The core reads the daemon's live queue (`getQueue`) and, when it finds
|
|
139
|
+
// orphans, calls `notifyMeshCoordinator` to page the coordinator — both are
|
|
140
|
+
// daemon-only concerns, so the whole call moves, not just its queue read.
|
|
141
|
+
// `title` is a task-message-derived label (first line, truncated) — free text,
|
|
142
|
+
// hence local IPC only, same rationale as `record_local`'s payload.
|
|
143
|
+
|
|
144
|
+
export interface OrphanedPinNotifyRequest {
|
|
145
|
+
v: typeof TURN_IPC_PROTOCOL_VERSION
|
|
146
|
+
meshId: string
|
|
147
|
+
stoppedSessionId: string
|
|
148
|
+
excludeTaskId?: string
|
|
149
|
+
cause?: string
|
|
150
|
+
nodeId?: string
|
|
151
|
+
coordinatorSessionId?: string
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface OrphanedPinnedTaskWire {
|
|
155
|
+
taskId: string
|
|
156
|
+
title: string
|
|
157
|
+
targetSessionId: string
|
|
158
|
+
targetNodeId?: string
|
|
159
|
+
missionId?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface OrphanedPinNotifyResponse {
|
|
163
|
+
orphans: readonly OrphanedPinnedTaskWire[]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isOrphanedPinnedTaskWire(value: unknown): value is OrphanedPinnedTaskWire {
|
|
167
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['taskId', 'title', 'targetSessionId', 'targetNodeId', 'missionId'])) return false
|
|
168
|
+
if (!isEvidenceIdentifier(value.taskId) || !isEvidenceIdentifier(value.targetSessionId)) return false
|
|
169
|
+
if (typeof value.title !== 'string') return false
|
|
170
|
+
if (value.targetNodeId !== undefined && !isEvidenceIdentifier(value.targetNodeId)) return false
|
|
171
|
+
return value.missionId === undefined || isEvidenceIdentifier(value.missionId)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isOrphanedPinNotifyRequest(value: unknown): value is OrphanedPinNotifyRequest {
|
|
175
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'stoppedSessionId', 'excludeTaskId', 'cause', 'nodeId', 'coordinatorSessionId'])) return false
|
|
176
|
+
if (value.v !== TURN_IPC_PROTOCOL_VERSION || !isEvidenceIdentifier(value.meshId) || !isEvidenceIdentifier(value.stoppedSessionId)) return false
|
|
177
|
+
if (value.excludeTaskId !== undefined && !isEvidenceIdentifier(value.excludeTaskId)) return false
|
|
178
|
+
if (value.cause !== undefined && typeof value.cause !== 'string') return false
|
|
179
|
+
if (value.nodeId !== undefined && !isEvidenceIdentifier(value.nodeId)) return false
|
|
180
|
+
return value.coordinatorSessionId === undefined || isEvidenceIdentifier(value.coordinatorSessionId)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function decodeOrphanedPinNotifyRequest(value: unknown): OrphanedPinNotifyRequest | null {
|
|
184
|
+
return isOrphanedPinNotifyRequest(value) ? value : null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isOrphanedPinNotifyResponse(value: unknown): value is OrphanedPinNotifyResponse {
|
|
188
|
+
return isRecord(value) && hasOnlyKeys(value, ['orphans']) && Array.isArray(value.orphans) && value.orphans.every(isOrphanedPinnedTaskWire)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function decodeOrphanedPinNotifyResponse(value: unknown): OrphanedPinNotifyResponse | null {
|
|
192
|
+
return isOrphanedPinNotifyResponse(value) ? value : null
|
|
193
|
+
}
|
package/src/turn-ipc.ts
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* LAYOUT
|
|
53
53
|
* -------
|
|
54
54
|
* This module holds the error codes and the command registry; the per-command
|
|
55
|
-
* contracts live in ./turn-ipc-{turn,mission,queue,
|
|
55
|
+
* contracts live in ./turn-ipc-{turn,mission,queue,stats}.ts (re-exported here)
|
|
56
56
|
* over the shared guard primitives in ./turn-ipc-guards.ts.
|
|
57
57
|
*/
|
|
58
58
|
|
|
@@ -63,7 +63,7 @@ export { TURN_IPC_PROTOCOL_VERSION } from './turn-ipc-guards';
|
|
|
63
63
|
export * from './turn-ipc-turn';
|
|
64
64
|
export * from './turn-ipc-mission';
|
|
65
65
|
export * from './turn-ipc-queue';
|
|
66
|
-
export * from './turn-ipc-
|
|
66
|
+
export * from './turn-ipc-stats';
|
|
67
67
|
|
|
68
68
|
// ─── error codes ────────────────────────────────────────────────────────────
|
|
69
69
|
|
|
@@ -120,21 +120,15 @@ export const TURN_IPC_COMMANDS = [
|
|
|
120
120
|
'record_local',
|
|
121
121
|
'queue_query',
|
|
122
122
|
'queue_enqueue',
|
|
123
|
-
'
|
|
123
|
+
'queue_enqueue_batch',
|
|
124
124
|
'queue_cancel',
|
|
125
125
|
'queue_requeue',
|
|
126
126
|
'direct_dispatch_record',
|
|
127
|
-
'graph_audit_record',
|
|
128
127
|
'active_work_query',
|
|
129
128
|
'recovery_context_query',
|
|
130
129
|
// C-W9c additions (2026-09-24 19:00 stamp — the last mcp-server in-process
|
|
131
|
-
// daemon-core paths:
|
|
132
|
-
//
|
|
133
|
-
'graph_gate_claim',
|
|
134
|
-
'graph_gate_release',
|
|
135
|
-
'graph_gate_abandon',
|
|
136
|
-
'graph_node_patch',
|
|
137
|
-
'graph_view_query',
|
|
130
|
+
// daemon-core paths: mission reads, task/mission stats, orphaned-pin
|
|
131
|
+
// helpers, one prune audit):
|
|
138
132
|
'task_stats_query',
|
|
139
133
|
'prune_stale_direct',
|
|
140
134
|
'orphaned_pin_notify',
|