@adhdev/mesh-shared 1.0.60-rc.72 → 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 +116 -425
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -339
- 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-guards.d.ts +18 -0
- package/dist/turn-ipc-mission.d.ts +250 -0
- package/dist/turn-ipc-queue.d.ts +196 -0
- package/dist/turn-ipc-stats.d.ts +72 -0
- package/dist/turn-ipc-turn.d.ts +176 -0
- package/dist/turn-ipc.d.ts +13 -963
- 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-guards.ts +61 -0
- package/src/turn-ipc-mission.ts +647 -0
- package/src/turn-ipc-queue.ts +493 -0
- package/src/turn-ipc-stats.ts +193 -0
- package/src/turn-ipc-turn.ts +453 -0
- package/src/turn-ipc.ts +43 -2236
- package/dist/magi.d.ts +0 -281
- package/src/magi.ts +0 -310
package/package.json
CHANGED
package/src/brain-routing.ts
CHANGED
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
* per-difficulty "brain" preset resolves that into a concrete provider / model /
|
|
6
6
|
* thinking-level for the launched session. The goal is token economy: an `easy`
|
|
7
7
|
* task runs on a cheaper model at low reasoning effort; a `difficult` task gets a
|
|
8
|
-
* stronger model at high effort. This
|
|
9
|
-
* (
|
|
10
|
-
* *executes* a task — but it reuses the same slot shape (provider/model) and the
|
|
11
|
-
* same machine-local storage (`~/.adhdev/meshes.json`).
|
|
8
|
+
* stronger model at high effort. This picks the single brain that *executes* a
|
|
9
|
+
* task, stored machine-local (`~/.adhdev/meshes.json`).
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
12
|
/** The fixed difficulty axis the coordinator classifies a task into. */
|
|
@@ -33,8 +31,8 @@ export interface BrainSlot {
|
|
|
33
31
|
|
|
34
32
|
/**
|
|
35
33
|
* Per-difficulty brain bindings for ONE mesh, stored machine-local in
|
|
36
|
-
* `~/.adhdev/meshes.json` under that mesh's entry (`meshes[].difficultyBrains
|
|
37
|
-
*
|
|
34
|
+
* `~/.adhdev/meshes.json` under that mesh's entry (`meshes[].difficultyBrains`).
|
|
35
|
+
* The scope is per mesh: two meshes on the same
|
|
38
36
|
* machine hold independent presets, so one can pin `difficult` to a cheaper model
|
|
39
37
|
* without changing what any other mesh runs. (It formerly sat at the config root
|
|
40
38
|
* keyed by difficulty alone — and since this map picks the MODEL a task runs on,
|
|
@@ -118,8 +116,8 @@ export function normalizeDifficultyBrainMap(raw: unknown): DifficultyBrainMap {
|
|
|
118
116
|
// capability slots. Each slot bundles what used to be scattered across
|
|
119
117
|
// providerPriority (order), a per-provider maxParallel cap, and the
|
|
120
118
|
// the owning mesh's difficultyBrains (difficulty → model/thinking). Slot order =
|
|
121
|
-
// preference. This single profile is the source of truth for task routing
|
|
122
|
-
//
|
|
119
|
+
// preference. This single profile is the source of truth for task routing and
|
|
120
|
+
// orchestrator-proposed edits.
|
|
123
121
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
122
|
|
|
125
123
|
/**
|
package/src/git-diff.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection of a daemon `git_diff_file` answer into the per-file row the MCP
|
|
3
|
+
* `git_diff` tool and the cloud git-diff shortcut both return: the diff text
|
|
4
|
+
* capped at `maxLines` (with a visible truncation marker), plus the file's
|
|
5
|
+
* change metadata from the diff summary.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readRecord } from './json'
|
|
9
|
+
|
|
10
|
+
export interface GitFileDiffRow {
|
|
11
|
+
path: string
|
|
12
|
+
old_path?: string | null
|
|
13
|
+
status?: string
|
|
14
|
+
diff: string
|
|
15
|
+
truncated: boolean
|
|
16
|
+
binary: boolean
|
|
17
|
+
error?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Cap diff text at `maxLines`; a truncated diff ends with a `... (truncated)` marker line. */
|
|
21
|
+
function truncateDiffText(text: string, maxLines: number): { diff: string; truncated: boolean } {
|
|
22
|
+
const lines = text.split('\n')
|
|
23
|
+
const truncated = lines.length > maxLines
|
|
24
|
+
return {
|
|
25
|
+
diff: truncated ? lines.slice(0, maxLines).join('\n') + '\n... (truncated)' : text,
|
|
26
|
+
truncated,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One changed file's row from its `git_diff_file` answer (either `{ diff: {...} }` or the bare diff record). */
|
|
31
|
+
export function projectFileDiffRow(
|
|
32
|
+
file: { path: string; oldPath?: string | null; status?: string },
|
|
33
|
+
rawResult: unknown,
|
|
34
|
+
maxLines: number,
|
|
35
|
+
): GitFileDiffRow {
|
|
36
|
+
const raw = readRecord(rawResult)
|
|
37
|
+
const d = readRecord(raw.diff ?? rawResult)
|
|
38
|
+
const text = typeof d.diff === 'string' ? d.diff : ''
|
|
39
|
+
const { diff, truncated } = truncateDiffText(text, maxLines)
|
|
40
|
+
return {
|
|
41
|
+
path: file.path,
|
|
42
|
+
old_path: file.oldPath ?? null,
|
|
43
|
+
status: file.status ?? 'M',
|
|
44
|
+
diff,
|
|
45
|
+
truncated,
|
|
46
|
+
binary: typeof d.binary === 'boolean' ? d.binary : false,
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ export * from './node-facts'
|
|
|
19
19
|
export * from './workspace-normalize'
|
|
20
20
|
export * from './daemon-normalize'
|
|
21
21
|
export * from './git-summarize'
|
|
22
|
-
export * from './
|
|
22
|
+
export * from './git-diff'
|
|
23
23
|
export * from './brain-routing'
|
|
24
24
|
export * from './slot-proposal'
|
|
25
25
|
export * from './interpolation'
|
package/src/json.ts
CHANGED
|
@@ -14,6 +14,11 @@ export function readRecord(value: unknown): JsonRecord {
|
|
|
14
14
|
return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonRecord : {}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** A plain-object record, or undefined — for callers that must tell "absent" from "empty". */
|
|
18
|
+
export function readOptionalRecord(value: unknown): JsonRecord | undefined {
|
|
19
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonRecord : undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export function readString(...values: unknown[]): string | undefined {
|
|
18
23
|
for (const value of values) {
|
|
19
24
|
if (typeof value !== 'string') continue
|
|
@@ -23,6 +28,11 @@ export function readString(...values: unknown[]): string | undefined {
|
|
|
23
28
|
return undefined
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
/** The first non-blank trimmed string, or '' — readString for callers that want a plain string. */
|
|
32
|
+
export function readText(...values: unknown[]): string {
|
|
33
|
+
return readString(...values) ?? ''
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
export function readNumber(...values: unknown[]): number | undefined {
|
|
27
37
|
for (const value of values) {
|
|
28
38
|
if (typeof value === 'number' && Number.isFinite(value)) return value
|
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
|
-
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turn-ipc shared runtime-guard primitives (the protocol version stamp and the
|
|
3
|
+
* small structural checks every command decoder composes). Part of the turn-ipc
|
|
4
|
+
* wire contract (./turn-ipc.ts).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isEvidenceIdentifier } from './turn-evidence';
|
|
8
|
+
import { isRecord } from './protocol/envelope';
|
|
9
|
+
|
|
10
|
+
// ─── shared primitives ─────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/** Every request in this file is versioned so a responder can reject a shape it predates. */
|
|
13
|
+
export const TURN_IPC_PROTOCOL_VERSION = 1 as const
|
|
14
|
+
|
|
15
|
+
export function isFiniteNumber(value: unknown): value is number {
|
|
16
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isNonNegativeInt(value: unknown): value is number {
|
|
20
|
+
return Number.isSafeInteger(value) && (value as number) >= 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isOptionalId(value: unknown): boolean {
|
|
24
|
+
return value === undefined || isEvidenceIdentifier(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function hasOnlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {
|
|
28
|
+
for (const key of Object.keys(value)) {
|
|
29
|
+
if (!allowed.includes(key)) return false
|
|
30
|
+
}
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function makeGuard<T extends readonly string[]>(values: T): (value: unknown) => value is T[number] {
|
|
35
|
+
const set: ReadonlySet<string> = new Set(values)
|
|
36
|
+
return (value: unknown): value is T[number] => typeof value === 'string' && set.has(value)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isStringArray(value: unknown): value is readonly string[] {
|
|
40
|
+
return Array.isArray(value) && value.every((v) => typeof v === 'string')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isOptionalShortString(value: unknown, max = 512): boolean {
|
|
44
|
+
return value === undefined || (typeof value === 'string' && value.trim().length > 0 && value.length <= max)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isRecordArray(value: unknown): value is readonly Record<string, unknown>[] {
|
|
48
|
+
return Array.isArray(value) && value.every(isRecord)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isOptionalRecord(value: unknown): boolean {
|
|
52
|
+
return value === undefined || isRecord(value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isOptionalBoolean(value: unknown): boolean {
|
|
56
|
+
return value === undefined || typeof value === 'boolean'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isOptionalString(value: unknown): boolean {
|
|
60
|
+
return value === undefined || typeof value === 'string'
|
|
61
|
+
}
|