@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/mesh-tool-names.ts
CHANGED
|
@@ -22,14 +22,10 @@ export const CANONICAL_MESH_TOOL_NAMES = [
|
|
|
22
22
|
'mesh_status',
|
|
23
23
|
'mesh_route_preview',
|
|
24
24
|
'mesh_list_nodes',
|
|
25
|
-
//
|
|
25
|
+
// Batch before task, mirroring ALL_MESH_TOOLS.
|
|
26
26
|
'mesh_enqueue_batch',
|
|
27
27
|
'mesh_enqueue_task',
|
|
28
28
|
'mesh_view_queue',
|
|
29
|
-
// GRAPH-ORCHESTRATION Phase E — the coordinator gate + graph view surface.
|
|
30
|
-
'mesh_graph_view',
|
|
31
|
-
'mesh_graph_gate',
|
|
32
|
-
'mesh_graph_node_patch',
|
|
33
29
|
'mesh_queue_cancel',
|
|
34
30
|
'mesh_queue_requeue',
|
|
35
31
|
'mesh_send_task',
|
|
@@ -64,9 +60,6 @@ export const CANONICAL_MESH_TOOL_NAMES = [
|
|
|
64
60
|
'mesh_mission_upsert',
|
|
65
61
|
'mesh_mission_list',
|
|
66
62
|
'mesh_review_inbox',
|
|
67
|
-
'mesh_magi_review',
|
|
68
|
-
'mesh_magi_collect',
|
|
69
|
-
'mesh_magi_kind_panel',
|
|
70
63
|
'mesh_node_slots',
|
|
71
64
|
'mesh_coordinator_prompt_append',
|
|
72
65
|
] as const;
|
|
@@ -87,29 +80,16 @@ export const CANONICAL_MESH_TOOL_COUNT = CANONICAL_MESH_TOOL_NAMES.length;
|
|
|
87
80
|
* test asserts none of these names is advertised anywhere in the prompt.
|
|
88
81
|
*
|
|
89
82
|
* `args` is the discriminator to add; every other argument keeps its name.
|
|
90
|
-
* `note`
|
|
91
|
-
* `mesh_graph_gate_claim({ extend_seconds })` extend-only mode).
|
|
83
|
+
* `note` carries any extra guidance when an argument moved in a rename.
|
|
92
84
|
*/
|
|
93
85
|
export const RETIRED_MESH_TOOLS: Readonly<Record<string, {
|
|
94
86
|
readonly tool: CanonicalMeshToolName;
|
|
95
87
|
readonly args: Readonly<Record<string, string>>;
|
|
96
88
|
readonly note?: string;
|
|
97
89
|
}>> = {
|
|
98
|
-
mesh_graph_gate_claim: {
|
|
99
|
-
tool: 'mesh_graph_gate',
|
|
100
|
-
args: { action: 'claim' },
|
|
101
|
-
note: 'The extend-only form (claim with extend_seconds) is now action: "extend" with the same extend_seconds.',
|
|
102
|
-
},
|
|
103
|
-
mesh_graph_gate_release: { tool: 'mesh_graph_gate', args: { action: 'release' } },
|
|
104
|
-
mesh_graph_gate_abandon: { tool: 'mesh_graph_gate', args: { action: 'abandon' } },
|
|
105
|
-
// Never a published tool (it is the daemon command behind the extend verb),
|
|
106
|
-
// but an older gate-expiry notice told coordinators to call it by this name.
|
|
107
|
-
mesh_graph_gate_extend: { tool: 'mesh_graph_gate', args: { action: 'extend' } },
|
|
108
90
|
mesh_node_slots_set: { tool: 'mesh_node_slots', args: { action: 'set' } },
|
|
109
91
|
mesh_node_slots_list: { tool: 'mesh_node_slots', args: { action: 'list' } },
|
|
110
92
|
mesh_node_slots_propose: { tool: 'mesh_node_slots', args: { action: 'propose' } },
|
|
111
|
-
mesh_magi_kind_panel_set: { tool: 'mesh_magi_kind_panel', args: { action: 'set' } },
|
|
112
|
-
mesh_magi_kind_panel_list: { tool: 'mesh_magi_kind_panel', args: { action: 'list' } },
|
|
113
93
|
mesh_coordinator_prompt_append_get: { tool: 'mesh_coordinator_prompt_append', args: { action: 'get' } },
|
|
114
94
|
mesh_coordinator_prompt_append_set: { tool: 'mesh_coordinator_prompt_append', args: { action: 'set' } },
|
|
115
95
|
mesh_record_note: { tool: 'mesh_note', args: { action: 'record' } },
|
|
@@ -123,11 +103,26 @@ export const RETIRED_MESH_TOOLS: Readonly<Record<string, {
|
|
|
123
103
|
};
|
|
124
104
|
|
|
125
105
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
|
|
106
|
+
* Tool names removed outright (no merged replacement), mapped to the guidance a
|
|
107
|
+
* coordinator on an old prompt needs to do the same job with the remaining tools.
|
|
108
|
+
*/
|
|
109
|
+
export const REMOVED_MESH_TOOLS: Readonly<Record<string, string>> = {
|
|
110
|
+
mesh_magi_review: 'For a multi-perspective review, send the same question to 2-3 workers on different providers with mesh_send_task, wait for their report_completion, then synthesize the answers yourself.',
|
|
111
|
+
mesh_magi_collect: 'Worker answers arrive as report_completion events; synthesize them yourself.',
|
|
112
|
+
mesh_magi_kind_panel: 'There are no review panels any more; pick the workers per review with mesh_send_task.',
|
|
113
|
+
mesh_magi_kind_panel_set: 'There are no review panels any more; pick the workers per review with mesh_send_task.',
|
|
114
|
+
mesh_magi_kind_panel_list: 'There are no review panels any more; pick the workers per review with mesh_send_task.',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The error text for a call to a retired or removed tool name, or null when
|
|
119
|
+
* `name` is neither. One wording shared by every MCP mode so the redirect reads
|
|
120
|
+
* the same wherever an old prompt calls it.
|
|
129
121
|
*/
|
|
130
122
|
export function retiredMeshToolError(name: string): string | null {
|
|
123
|
+
if (Object.prototype.hasOwnProperty.call(REMOVED_MESH_TOOLS, name)) {
|
|
124
|
+
return `Tool "${name}" was removed. ${REMOVED_MESH_TOOLS[name]}`;
|
|
125
|
+
}
|
|
131
126
|
const entry = Object.prototype.hasOwnProperty.call(RETIRED_MESH_TOOLS, name) ? RETIRED_MESH_TOOLS[name] : undefined;
|
|
132
127
|
if (!entry) return null;
|
|
133
128
|
const discriminator = Object.entries(entry.args).map(([k, v]) => `${k}: "${v}"`).join(', ');
|
package/src/mesh-vocabulary.ts
CHANGED
|
@@ -46,8 +46,7 @@ export const MESH_DELIVERY_MODES = ['when_idle', 'interrupt'] as const
|
|
|
46
46
|
export type MeshDeliveryMode = typeof MESH_DELIVERY_MODES[number]
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* What to do with a node's sessions when the node is removed
|
|
50
|
-
* settles. Declared here so daemon-core (`RepoMeshSessionCleanupMode`), the
|
|
49
|
+
* What to do with a node's sessions when the node is removed. Declared here so daemon-core (`RepoMeshSessionCleanupMode`), the
|
|
51
50
|
* web dashboard picker and the MCP schemas all read one list.
|
|
52
51
|
*/
|
|
53
52
|
export const MESH_SESSION_CLEANUP_MODES = ['preserve', 'stop', 'delete_stopped', 'stop_and_delete'] as const
|
package/src/outbound-message.ts
CHANGED
|
@@ -167,9 +167,8 @@ export type SendRefusal =
|
|
|
167
167
|
* a turn is generating (`send_now`). Written, not yet answered;
|
|
168
168
|
* `interrupt` — the turn in flight was stopped first, then this body was
|
|
169
169
|
* written (or re-parked) as a new turn;
|
|
170
|
-
* `acp` — handed to an ACP agent's `session/prompt`.
|
|
171
170
|
*/
|
|
172
|
-
export type SubmitRoute = 'pty' | 'agent_queue' | 'interrupt'
|
|
171
|
+
export type SubmitRoute = 'pty' | 'agent_queue' | 'interrupt'
|
|
173
172
|
|
|
174
173
|
/** The stop key pressed by an `interrupt` submit (logs / operator display). */
|
|
175
174
|
export interface SubmitInterruptDetail {
|
|
@@ -195,8 +195,6 @@ export type AuthOkWirePayload = {
|
|
|
195
195
|
machineNickname: string | null
|
|
196
196
|
seqscribeFleetSecret?: string
|
|
197
197
|
seqscribeFleetSecretVersion?: number
|
|
198
|
-
/** Beacon board seed for cold start (advisory, may be absent). */
|
|
199
|
-
beaconSeed?: { reports: unknown[]; truncated: number }
|
|
200
198
|
limits: AuthOkPlanLimits
|
|
201
199
|
}
|
|
202
200
|
|
|
@@ -251,12 +249,6 @@ export type DaemonErrorWirePayload = {
|
|
|
251
249
|
detail?: unknown
|
|
252
250
|
}
|
|
253
251
|
|
|
254
|
-
// ─── Beacon (seqscribe vector board, design §7.1) ──────────────────────────
|
|
255
|
-
|
|
256
|
-
export type BeaconVectorsWirePayload =
|
|
257
|
-
| { op: 'put'; report: unknown }
|
|
258
|
-
| { op: 'get'; topics: string[]; requestId: string }
|
|
259
|
-
|
|
260
252
|
// ─── P2P signaling (dashboard ↔ daemon through the server) ─────────────────
|
|
261
253
|
|
|
262
254
|
export const P2P_SIGNAL_TYPES = ['p2p_ready', 'p2p_offer', 'p2p_answer', 'p2p_ice'] as const
|
|
@@ -308,7 +300,6 @@ export const DAEMON_TO_SERVER_TYPES = [
|
|
|
308
300
|
'command_result',
|
|
309
301
|
'error',
|
|
310
302
|
'agent_event',
|
|
311
|
-
'beacon_vectors',
|
|
312
303
|
'daemon_mesh_command',
|
|
313
304
|
'p2p_offer',
|
|
314
305
|
'p2p_ice',
|
|
@@ -324,7 +315,6 @@ export type CommandResultMsg = Envelope<'command_result', CommandResultWirePaylo
|
|
|
324
315
|
export type DaemonErrorMsg = Envelope<'error', DaemonErrorWirePayload>
|
|
325
316
|
/** Agent-level event forwarded verbatim to the `agent:status` webhook (throttled server-side). Dynamic by design. */
|
|
326
317
|
export type AgentEventMsg = Envelope<'agent_event', CommandPayload>
|
|
327
|
-
export type BeaconVectorsMsg = Envelope<'beacon_vectors', BeaconVectorsWirePayload>
|
|
328
318
|
export type DaemonP2POfferMsg = Envelope<'p2p_offer', P2POfferWirePayload>
|
|
329
319
|
export type DaemonP2PIceMsg = Envelope<'p2p_ice', P2PIceWirePayload>
|
|
330
320
|
/**
|
|
@@ -349,7 +339,6 @@ export type DaemonToServerMsg =
|
|
|
349
339
|
| CommandResultMsg
|
|
350
340
|
| DaemonErrorMsg
|
|
351
341
|
| AgentEventMsg
|
|
352
|
-
| BeaconVectorsMsg
|
|
353
342
|
| DaemonMeshCommandMsg
|
|
354
343
|
| DaemonP2POfferMsg
|
|
355
344
|
| DaemonP2PIceMsg
|
|
@@ -398,7 +387,6 @@ export const SERVER_TO_DAEMON_CONTROL_TYPES = [
|
|
|
398
387
|
'command',
|
|
399
388
|
'agent_command',
|
|
400
389
|
'resolve_action',
|
|
401
|
-
'beacon_vectors_result',
|
|
402
390
|
'daemon_mesh_result',
|
|
403
391
|
'p2p_ready',
|
|
404
392
|
'p2p_offer',
|
|
@@ -428,15 +416,6 @@ export type ServerAgentCommandMsg = Envelope<'agent_command', { action: string }
|
|
|
428
416
|
export type ServerResolveActionMsg = Envelope<'resolve_action', CommandPayload> & ServerRelayFields
|
|
429
417
|
export type ServerP2PSignalMsg = Envelope<P2PSignalType, RelayedP2PSignalWirePayload> & ServerRelayFields
|
|
430
418
|
export type ServerMeshP2PSignalMsg = Envelope<MeshP2PSignalType, MeshP2PSignalWirePayload> & ServerRelayFields
|
|
431
|
-
/** Reply to a `beacon_vectors` GET, correlated by `requestId` (top-level, no payload). A PUT gets no reply. */
|
|
432
|
-
export type BeaconVectorsResultMsg = {
|
|
433
|
-
type: 'beacon_vectors_result'
|
|
434
|
-
requestId: string
|
|
435
|
-
success: boolean
|
|
436
|
-
reports?: unknown[]
|
|
437
|
-
truncated?: number
|
|
438
|
-
error?: string
|
|
439
|
-
}
|
|
440
419
|
/** Correlated ack/nack of a `daemon_mesh_command` (top-level, no payload). */
|
|
441
420
|
export type DaemonMeshResultMsg = {
|
|
442
421
|
type: 'daemon_mesh_result'
|
|
@@ -457,7 +436,6 @@ export type ServerToDaemonControlMsg =
|
|
|
457
436
|
| ServerCommandMsg
|
|
458
437
|
| ServerAgentCommandMsg
|
|
459
438
|
| ServerResolveActionMsg
|
|
460
|
-
| BeaconVectorsResultMsg
|
|
461
439
|
| DaemonMeshResultMsg
|
|
462
440
|
| ServerP2PSignalMsg
|
|
463
441
|
| ServerMeshP2PSignalMsg
|
|
@@ -491,7 +469,7 @@ export function isServerDirectCommandMsg(msg: ServerToDaemonMsg): msg is ServerD
|
|
|
491
469
|
}
|
|
492
470
|
|
|
493
471
|
/** Members of the control set that carry a `payload` object. */
|
|
494
|
-
export type ServerToDaemonPayloadType = Exclude<ServerToDaemonControlMsg,
|
|
472
|
+
export type ServerToDaemonPayloadType = Exclude<ServerToDaemonControlMsg, DaemonMeshResultMsg>['type']
|
|
495
473
|
export type ServerToDaemonPayloadOf<T extends ServerToDaemonPayloadType> = Extract<ServerToDaemonControlMsg, { type: T }>['payload']
|
|
496
474
|
|
|
497
475
|
/** Command types a daemon must never receive as a direct command: they are daemon→server frames or reserved envelope words. */
|
|
@@ -504,7 +482,7 @@ export function isDaemonDirectCommandType(value: unknown): value is DaemonDirect
|
|
|
504
482
|
/**
|
|
505
483
|
* Parse boundary for the daemon: is `raw` a frame this link accepts from the server?
|
|
506
484
|
* Control members are checked for their discriminant and envelope shape; the
|
|
507
|
-
*
|
|
485
|
+
* payload-less `daemon_mesh_result` reply needs its `requestId`; any other well-formed
|
|
508
486
|
* command name becomes a `ServerDirectCommandMsg`.
|
|
509
487
|
*/
|
|
510
488
|
export function decodeServerToDaemon(raw: unknown): ServerToDaemonMsg | null {
|
|
@@ -513,18 +491,9 @@ export function decodeServerToDaemon(raw: unknown): ServerToDaemonMsg | null {
|
|
|
513
491
|
...readEnvelopeFields(raw),
|
|
514
492
|
...(isNonEmptyString(raw.source) ? { source: raw.source } : {}),
|
|
515
493
|
}
|
|
516
|
-
if (raw.type === '
|
|
494
|
+
if (raw.type === 'daemon_mesh_result') {
|
|
517
495
|
if (!isNonEmptyString(raw.requestId)) return null
|
|
518
496
|
const base = { requestId: raw.requestId, success: raw.success === true }
|
|
519
|
-
if (raw.type === 'beacon_vectors_result') {
|
|
520
|
-
return {
|
|
521
|
-
type: 'beacon_vectors_result',
|
|
522
|
-
...base,
|
|
523
|
-
...(Array.isArray(raw.reports) ? { reports: raw.reports } : {}),
|
|
524
|
-
...(typeof raw.truncated === 'number' ? { truncated: raw.truncated } : {}),
|
|
525
|
-
...(typeof raw.error === 'string' ? { error: raw.error } : {}),
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
497
|
return {
|
|
529
498
|
type: 'daemon_mesh_result',
|
|
530
499
|
...base,
|
package/src/session-launch.ts
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
* only `SessionLaunchRecord.launchedBy` records that the session came back via
|
|
29
29
|
* restore.
|
|
30
30
|
*/
|
|
31
|
+
import { readString } from './json'
|
|
32
|
+
|
|
31
33
|
export const MODEL_AXIS_SOURCES = [
|
|
32
34
|
/** Picked explicitly for this launch (dashboard dropdown, coordinator dialog). */
|
|
33
35
|
'user',
|
|
@@ -173,10 +175,6 @@ export function sanitizeModelIdentifier(value: unknown): string | undefined {
|
|
|
173
175
|
return MODEL_IDENTIFIER_PATTERN.test(trimmed) ? trimmed : undefined
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
function readString(value: unknown): string | undefined {
|
|
177
|
-
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
178
|
-
}
|
|
179
|
-
|
|
180
178
|
function readFiniteNumber(value: unknown): number | undefined {
|
|
181
179
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
182
180
|
}
|
package/src/slot-proposal.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI auto-detect → capability-slot
|
|
2
|
+
* CLI auto-detect → capability-slot PROPOSAL generator.
|
|
3
3
|
*
|
|
4
4
|
* Detection of installed CLI providers already exists per node (the status
|
|
5
5
|
* snapshot's `availableProviders`), and applying a slot profile already exists
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
type MeshTaskDifficulty,
|
|
32
32
|
type NodeCapabilitySlot,
|
|
33
33
|
} from './brain-routing'
|
|
34
|
-
import type { MagiSlot } from './magi'
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
36
|
* One provider's seeded slot recipe. A provider may map to MORE THAN ONE slot
|
|
@@ -280,48 +279,3 @@ export function buildSlotProposal(
|
|
|
280
279
|
destructive: droppedSlots.length > 0,
|
|
281
280
|
}
|
|
282
281
|
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Build a MAGI panel proposal from the same detections.
|
|
286
|
-
*
|
|
287
|
-
* ─── Deliberately narrow ──────────────────────────────────────────────────────
|
|
288
|
-
*
|
|
289
|
-
* MAGI's value is provider INDEPENDENCE: replicas from different providers
|
|
290
|
-
* (ideally different machines) answering the same question, so agreement means
|
|
291
|
-
* something. Detection tells us which providers exist — that is exactly enough
|
|
292
|
-
* to propose one panel of distinct providers, and no more.
|
|
293
|
-
*
|
|
294
|
-
* What detection does NOT tell us is which provider suits which review KIND
|
|
295
|
-
* (rca vs design vs claim_audit). Nothing in any manifest grades a provider for
|
|
296
|
-
* root-cause analysis over design review, and inventing a per-kind assignment
|
|
297
|
-
* would fabricate a rationale that does not exist. So this proposes ONE panel of
|
|
298
|
-
* the detected providers and leaves the kind binding to the operator; the caller
|
|
299
|
-
* decides which `task_kind` to bind it to via the existing dry-run tool.
|
|
300
|
-
*
|
|
301
|
-
* Ordering follows {@link CLI_SLOT_RECIPES} insertion order (recipe-known
|
|
302
|
-
* providers first, in table order), so the panel leads with the providers whose
|
|
303
|
-
* suitability is actually attested.
|
|
304
|
-
*/
|
|
305
|
-
export function buildMagiPanelProposal(
|
|
306
|
-
detected: readonly DetectedCliProvider[],
|
|
307
|
-
opts: { nodeId?: string; maxSlots?: number } = {},
|
|
308
|
-
): MagiSlot[] {
|
|
309
|
-
const providers = dedupeDetected(detected ?? [])
|
|
310
|
-
const tableOrder = Object.keys(CLI_SLOT_RECIPES)
|
|
311
|
-
const rank = (type: string): number => {
|
|
312
|
-
const i = tableOrder.indexOf(type)
|
|
313
|
-
return i === -1 ? Number.MAX_SAFE_INTEGER : i
|
|
314
|
-
}
|
|
315
|
-
const ordered = [...providers].sort((a, b) => rank(a.type) - rank(b.type))
|
|
316
|
-
const limit = Number.isFinite(opts.maxSlots) && (opts.maxSlots as number) > 0
|
|
317
|
-
? Math.floor(opts.maxSlots as number)
|
|
318
|
-
: ordered.length
|
|
319
|
-
|
|
320
|
-
return ordered.slice(0, limit).map((p): MagiSlot => ({
|
|
321
|
-
...(opts.nodeId ? { nodeId: opts.nodeId } : {}),
|
|
322
|
-
provider: p.type,
|
|
323
|
-
// A model is intentionally NOT pinned: the panel's job is cross-provider
|
|
324
|
-
// independence, and pinning models here would silently couple the panel
|
|
325
|
-
// to this table's cost assumptions rather than to review quality.
|
|
326
|
-
}))
|
|
327
|
-
}
|
package/src/turn-ipc-mission.ts
CHANGED
|
@@ -44,10 +44,6 @@ export const MESH_MISSION_STATUSES = ['active', 'paused', 'completed', 'abandone
|
|
|
44
44
|
export type MeshMissionStatusValue = typeof MESH_MISSION_STATUSES[number]
|
|
45
45
|
export const isMeshMissionStatusValue = makeGuard(MESH_MISSION_STATUSES)
|
|
46
46
|
|
|
47
|
-
export const MESH_MISSION_SOURCES = ['magi', 'coordinator'] as const
|
|
48
|
-
export type MeshMissionSourceValue = typeof MESH_MISSION_SOURCES[number]
|
|
49
|
-
const isMeshMissionSourceValue = makeGuard(MESH_MISSION_SOURCES)
|
|
50
|
-
|
|
51
47
|
// H2 (mission brief, wiring-unification Phase H — docs/design/2026-09-23-wiring-
|
|
52
48
|
// unification.md §7c): free text, same "local IPC is inside the boundary" rationale
|
|
53
49
|
// as `goal` above (section note). `MissionBriefWire` is a structural mirror of
|
|
@@ -82,7 +78,6 @@ export interface MissionUpsertRequest {
|
|
|
82
78
|
/** Free text — see file-header note on why this is fine over local IPC. */
|
|
83
79
|
goal?: string
|
|
84
80
|
status?: MeshMissionStatusValue
|
|
85
|
-
source?: MeshMissionSourceValue
|
|
86
81
|
/**
|
|
87
82
|
* H2: `undefined` = do not touch the stored brief. `null` = explicitly clear it.
|
|
88
83
|
* A present object with no usable `goal` is treated by the daemon's own
|
|
@@ -98,7 +93,6 @@ export interface MeshMissionRecordWire {
|
|
|
98
93
|
title: string
|
|
99
94
|
goal: string
|
|
100
95
|
status: MeshMissionStatusValue
|
|
101
|
-
source?: MeshMissionSourceValue
|
|
102
96
|
/** H2: absent = no brief attached to this mission. */
|
|
103
97
|
brief?: MissionBriefWire
|
|
104
98
|
}
|
|
@@ -108,24 +102,22 @@ export interface MissionUpsertResponse {
|
|
|
108
102
|
}
|
|
109
103
|
|
|
110
104
|
function isMeshMissionRecordWire(value: unknown): value is MeshMissionRecordWire {
|
|
111
|
-
if (!isRecord(value) || !hasOnlyKeys(value, ['id', 'meshId', 'title', 'goal', 'status', '
|
|
105
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['id', 'meshId', 'title', 'goal', 'status', 'brief'])) return false
|
|
112
106
|
if (!isEvidenceIdentifier(value.id) || !isEvidenceIdentifier(value.meshId)) return false
|
|
113
107
|
if (typeof value.title !== 'string' || typeof value.goal !== 'string') return false
|
|
114
108
|
if (!isMeshMissionStatusValue(value.status)) return false
|
|
115
|
-
if (value.source !== undefined && !isMeshMissionSourceValue(value.source)) return false
|
|
116
109
|
if (value.brief !== undefined && !isMissionBriefWire(value.brief)) return false
|
|
117
110
|
return true
|
|
118
111
|
}
|
|
119
112
|
|
|
120
113
|
function isMissionUpsertRequest(value: unknown): value is MissionUpsertRequest {
|
|
121
|
-
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'id', 'title', 'goal', 'status', '
|
|
114
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'id', 'title', 'goal', 'status', 'brief'])) return false
|
|
122
115
|
if (value.v !== TURN_IPC_PROTOCOL_VERSION) return false
|
|
123
116
|
if (!isEvidenceIdentifier(value.meshId)) return false
|
|
124
117
|
if (value.id !== undefined && !isEvidenceIdentifier(value.id)) return false
|
|
125
118
|
if (typeof value.title !== 'string' || value.title.trim().length === 0) return false
|
|
126
119
|
if (value.goal !== undefined && typeof value.goal !== 'string') return false
|
|
127
120
|
if (value.status !== undefined && !isMeshMissionStatusValue(value.status)) return false
|
|
128
|
-
if (value.source !== undefined && !isMeshMissionSourceValue(value.source)) return false
|
|
129
121
|
if (value.brief !== undefined && value.brief !== null && !isMissionBriefWire(value.brief)) return false
|
|
130
122
|
return true
|
|
131
123
|
}
|
|
@@ -186,7 +178,7 @@ export function decodeMissionQueryResponse(value: unknown): MissionQueryResponse
|
|
|
186
178
|
//
|
|
187
179
|
// `mesh-tools-mission.ts`'s own file-header note (2026-09-24, C-W6) flagged
|
|
188
180
|
// this exact gap: `mission_query`'s landed response is `{missions:
|
|
189
|
-
// MeshMissionRecordWire[]}` only — no `verbose`/`
|
|
181
|
+
// MeshMissionRecordWire[]}` only — no `verbose`/`withStats`/
|
|
190
182
|
// `limit`/`truncated`/`overflowIds`/`historyFold`, all of which
|
|
191
183
|
// `listMeshMissionsForTool` (daemon-core `mesh-missions.ts`) computes for the
|
|
192
184
|
// `mesh_mission_list` tool. Rather than widen `mission_query` itself (an
|
|
@@ -202,7 +194,6 @@ export interface MissionListQueryRequest {
|
|
|
202
194
|
meshId: string
|
|
203
195
|
statuses?: readonly MeshMissionStatusValue[]
|
|
204
196
|
verbose?: boolean
|
|
205
|
-
includeMagi?: boolean
|
|
206
197
|
withStats?: boolean
|
|
207
198
|
limit?: number
|
|
208
199
|
historyIdLimit?: number
|
|
@@ -247,7 +238,6 @@ export interface MissionListSummaryVerboseWire {
|
|
|
247
238
|
title: string
|
|
248
239
|
goal: string
|
|
249
240
|
status: MeshMissionStatusValue
|
|
250
|
-
source?: MeshMissionSourceValue
|
|
251
241
|
tasks: MeshMissionTaskAggregateWire
|
|
252
242
|
stats?: MeshMissionStatsWire
|
|
253
243
|
/** H2: absent = no brief attached. */
|
|
@@ -266,7 +256,6 @@ export interface MissionListSummarySlimWire {
|
|
|
266
256
|
goalPreview: string
|
|
267
257
|
goalTruncated: boolean
|
|
268
258
|
status: MeshMissionStatusValue
|
|
269
|
-
source?: MeshMissionSourceValue
|
|
270
259
|
tasks: MeshMissionTaskAggregateWire
|
|
271
260
|
stats?: MeshMissionStatsWire
|
|
272
261
|
/** H2: absent = no brief attached. */
|
|
@@ -295,7 +284,7 @@ export interface MissionListQueryResponse {
|
|
|
295
284
|
}
|
|
296
285
|
|
|
297
286
|
function isMissionListQueryRequest(value: unknown): value is MissionListQueryRequest {
|
|
298
|
-
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'statuses', 'verbose', '
|
|
287
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ['v', 'meshId', 'statuses', 'verbose', 'withStats', 'limit', 'historyIdLimit', 'meshStatusView'])) return false
|
|
299
288
|
if (value.meshStatusView !== undefined && value.meshStatusView !== 'compact' && value.meshStatusView !== 'verbose') return false
|
|
300
289
|
if (value.v !== TURN_IPC_PROTOCOL_VERSION) return false
|
|
301
290
|
if (!isEvidenceIdentifier(value.meshId)) return false
|
|
@@ -304,7 +293,6 @@ function isMissionListQueryRequest(value: unknown): value is MissionListQueryReq
|
|
|
304
293
|
if (!value.statuses.every(isMeshMissionStatusValue)) return false
|
|
305
294
|
}
|
|
306
295
|
if (value.verbose !== undefined && typeof value.verbose !== 'boolean') return false
|
|
307
|
-
if (value.includeMagi !== undefined && typeof value.includeMagi !== 'boolean') return false
|
|
308
296
|
if (value.withStats !== undefined && typeof value.withStats !== 'boolean') return false
|
|
309
297
|
if (value.limit !== undefined && !isNonNegativeInt(value.limit)) return false
|
|
310
298
|
if (value.historyIdLimit !== undefined && !isNonNegativeInt(value.historyIdLimit)) return false
|
|
@@ -341,13 +329,12 @@ function isMissionListSummaryWire(value: unknown): value is MissionListSummaryWi
|
|
|
341
329
|
const hasGoal = 'goal' in value
|
|
342
330
|
const hasPreview = 'goalPreview' in value && 'goalTruncated' in value
|
|
343
331
|
if (hasGoal === hasPreview) return false // exactly one of the two shapes
|
|
344
|
-
const baseKeys = ['id', 'meshId', 'title', 'status', '
|
|
332
|
+
const baseKeys = ['id', 'meshId', 'title', 'status', 'tasks', 'stats', 'brief', 'createdAt', 'updatedAt', 'closeCandidateEmittedAt'] as const
|
|
345
333
|
const allowed = hasGoal ? [...baseKeys, 'goal'] : [...baseKeys, 'goalPreview', 'goalTruncated']
|
|
346
334
|
if (!hasOnlyKeys(value, allowed)) return false
|
|
347
335
|
if (!isEvidenceIdentifier(value.id) || !isEvidenceIdentifier(value.meshId)) return false
|
|
348
336
|
if (typeof value.title !== 'string') return false
|
|
349
337
|
if (!isMeshMissionStatusValue(value.status)) return false
|
|
350
|
-
if (value.source !== undefined && !isMeshMissionSourceValue(value.source)) return false
|
|
351
338
|
if (!isMeshMissionTaskAggregateWire(value.tasks)) return false
|
|
352
339
|
if (value.stats !== undefined && !isMeshMissionStatsWire(value.stats)) return false
|
|
353
340
|
if (value.brief !== undefined && !isMissionBriefWire(value.brief)) return false
|
|
@@ -553,11 +540,10 @@ export function decodeToolCallRecordResponse(value: unknown): ToolCallRecordResp
|
|
|
553
540
|
// scoped to the turn-ledger's own attempt/event rows; `ledger_query` is the
|
|
554
541
|
// general record browse surface (C-W9a: the daemon's `mesh_local_records` plus
|
|
555
542
|
// the turn ledger's task outcomes, `readLocalRecords`) `meshTaskHistory` /
|
|
556
|
-
// `meshLedgerQuery`
|
|
543
|
+
// `meshLedgerQuery` need.
|
|
557
544
|
//
|
|
558
545
|
// CONTENT BOUNDARY: `MeshLedgerEntry.payload` (daemon-core `mesh-ledger.ts`)
|
|
559
|
-
// is `Record<string, unknown>` — genuinely unbounded JSON (a
|
|
560
|
-
// object, a graph plan, a free-text checkpoint message), NOT constrained to
|
|
546
|
+
// is `Record<string, unknown>` — genuinely unbounded JSON (a graph plan, a free-text checkpoint message), NOT constrained to
|
|
561
547
|
// `mesh_record`'s scalar allow-list. This is fine for the SAME reason
|
|
562
548
|
// `mission_upsert`'s `goal` and `note_upsert`'s `text` are fine (see that
|
|
563
549
|
// section's note above): this is local IPC between mcp-server and the
|