@adhdev/mesh-shared 1.0.82-rc.21 → 1.0.82-rc.23
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/assistant-protocol.d.ts +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +209 -108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +207 -108
- package/dist/index.mjs.map +1 -1
- package/dist/launch-defaults.d.ts +1 -1
- package/dist/mesh-tool-names.d.ts +2 -2
- package/dist/outbound-message.d.ts +7 -0
- package/dist/protocol/daemon-server.d.ts +6 -0
- package/dist/protocol/dashboard-server.d.ts +10 -0
- package/dist/role-launch.d.ts +11 -0
- package/dist/session-launch.d.ts +3 -22
- package/dist/slot-proposal.d.ts +14 -0
- package/dist/worker-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/src/assistant-protocol.ts +8 -3
- package/src/index.ts +2 -0
- package/src/launch-defaults.ts +1 -1
- package/src/mesh-tool-names.ts +2 -0
- package/src/outbound-message.ts +17 -0
- package/src/protocol/daemon-server.ts +4 -2
- package/src/protocol/dashboard-server.ts +8 -2
- package/src/role-launch.ts +51 -0
- package/src/session-launch.ts +8 -1
- package/src/slot-proposal.ts +61 -2
- package/src/worker-protocol.ts +3 -0
|
@@ -51,7 +51,7 @@ export interface LaunchDefaultAxis {
|
|
|
51
51
|
defaultRef?: LaunchDefaultRef;
|
|
52
52
|
}
|
|
53
53
|
export type LaunchDefaultWarning = 'catalog_unverified' | 'manual_mcp_setup_required';
|
|
54
|
-
export type LaunchDefaultValidationReason = 'invalid_schema' | 'provider_unknown' | 'provider_not_cli' | 'provider_disabled' | 'provider_not_detected' | 'provider_not_eligible' | 'model_unsupported' | 'model_provider_mismatch' | 'model_not_offered' | 'thinking_unsupported' | 'thinking_not_offered' | 'catalog_unavailable';
|
|
54
|
+
export type LaunchDefaultValidationReason = 'invalid_schema' | 'provider_unknown' | 'provider_not_cli' | 'provider_disabled' | 'provider_not_detected' | 'provider_not_eligible' | 'provider_quota_gated' | 'model_unsupported' | 'model_provider_mismatch' | 'model_not_offered' | 'thinking_unsupported' | 'thinking_not_offered' | 'catalog_unavailable';
|
|
55
55
|
export type LaunchDefaultResolution = {
|
|
56
56
|
ok: true;
|
|
57
57
|
selection: LaunchDefaultSelection;
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* Order mirrors mcp-server `ALL_MESH_TOOLS` for easy visual diffing, but the consistency
|
|
19
19
|
* checks are set-based (order-insensitive).
|
|
20
20
|
*/
|
|
21
|
-
export declare const CANONICAL_MESH_TOOL_NAMES: readonly ["mesh_status", "mesh_route_preview", "mesh_list_nodes", "mesh_enqueue_batch", "mesh_enqueue_task", "mesh_view_queue", "mesh_queue_cancel", "mesh_queue_requeue", "mesh_send_task", "mesh_read_chat", "mesh_read_debug", "mesh_read_terminal", "mesh_send_keys", "mesh_launch_session", "mesh_git_status", "mesh_read_node_logs", "mesh_fast_forward_node", "mesh_restart_daemon", "mesh_checkpoint", "mesh_approve", "mesh_answer_question", "mesh_list_pending_approvals", "mesh_create", "mesh_add_node", "mesh_attach_remote", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_config", "mesh_init", "mesh_cleanup_sessions", "mesh_task_history", "mesh_ledger_query", "mesh_note", "mesh_reconcile_ledger", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_node_slots"];
|
|
21
|
+
export declare const CANONICAL_MESH_TOOL_NAMES: readonly ["mesh_status", "mesh_route_preview", "mesh_list_nodes", "mesh_enqueue_batch", "mesh_enqueue_task", "mesh_view_queue", "mesh_queue_cancel", "mesh_queue_requeue", "mesh_send_task", "mesh_read_chat", "mesh_read_debug", "mesh_read_terminal", "mesh_send_keys", "mesh_launch_session", "mesh_git_status", "mesh_read_node_logs", "mesh_fast_forward_node", "mesh_restart_daemon", "mesh_checkpoint", "mesh_approve", "mesh_answer_question", "mesh_list_pending_approvals", "mesh_create", "mesh_add_node", "mesh_attach_remote", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_config", "mesh_init", "mesh_cleanup_sessions", "mesh_task_history", "mesh_ledger_query", "mesh_note", "mesh_reconcile_ledger", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_node_slots", "driver_list", "driver_describe"];
|
|
22
22
|
export type CanonicalMeshToolName = typeof CANONICAL_MESH_TOOL_NAMES[number];
|
|
23
23
|
/** The count the `NN tools` barrel doc comments and consistency test assert against. */
|
|
24
|
-
export declare const CANONICAL_MESH_TOOL_COUNT:
|
|
24
|
+
export declare const CANONICAL_MESH_TOOL_COUNT: 43;
|
|
25
25
|
/**
|
|
26
26
|
* Tool names retired by the 2026-09-26 tool-surface consolidation (60 → 48).
|
|
27
27
|
* Every capability behind them is still reachable: each retired name maps to the
|
|
@@ -203,3 +203,10 @@ export declare function isSubmitOutcome(value: unknown): value is SubmitOutcome;
|
|
|
203
203
|
export declare function isInputEnvelopeWire(value: unknown): value is InputEnvelopeWire;
|
|
204
204
|
/** Minimal structural check for the wire boundary — not a full decoder (mesh-shared stays a pure leaf; semantic validation stays with the handler that owns it). */
|
|
205
205
|
export declare function isOutboundMessage(value: unknown): value is OutboundMessage;
|
|
206
|
+
/**
|
|
207
|
+
* Drop the `<pasted_content>` envelope lines Claude Code wraps around a pasted
|
|
208
|
+
* input. Only a text that OPENS with the envelope is touched; anything else is
|
|
209
|
+
* returned unchanged. Shared across daemon transcript merge and web echo/display
|
|
210
|
+
* normalization so wrapped turns correlate identically.
|
|
211
|
+
*/
|
|
212
|
+
export declare function unwrapPastedContent(text: string): string;
|
|
@@ -213,11 +213,15 @@ export type P2POfferWirePayload = {
|
|
|
213
213
|
sdp: string;
|
|
214
214
|
type: string;
|
|
215
215
|
peerId: string;
|
|
216
|
+
attemptId?: number;
|
|
217
|
+
attemptEpoch?: string;
|
|
216
218
|
};
|
|
217
219
|
export type P2PIceWirePayload = {
|
|
218
220
|
candidate: string;
|
|
219
221
|
mid: string;
|
|
220
222
|
peerId: string;
|
|
223
|
+
attemptId?: number;
|
|
224
|
+
attemptEpoch?: string;
|
|
221
225
|
};
|
|
222
226
|
/**
|
|
223
227
|
* A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
|
|
@@ -229,6 +233,8 @@ export type P2PIceWirePayload = {
|
|
|
229
233
|
export type RelayedP2PSignalWirePayload = {
|
|
230
234
|
peerId?: string;
|
|
231
235
|
daemonId?: string;
|
|
236
|
+
attemptId?: number;
|
|
237
|
+
attemptEpoch?: string;
|
|
232
238
|
sharePermission?: string;
|
|
233
239
|
shareTargetSessionId?: string;
|
|
234
240
|
sdp?: string;
|
|
@@ -109,12 +109,16 @@ export type SystemAnnouncementWirePayload = {
|
|
|
109
109
|
export type DashboardP2POfferWirePayload = {
|
|
110
110
|
daemonId: string;
|
|
111
111
|
peerId?: string;
|
|
112
|
+
attemptId?: number;
|
|
113
|
+
attemptEpoch?: string;
|
|
112
114
|
sdp: string;
|
|
113
115
|
type: string;
|
|
114
116
|
};
|
|
115
117
|
export type DashboardP2PIceWirePayload = {
|
|
116
118
|
daemonId: string;
|
|
117
119
|
peerId?: string;
|
|
120
|
+
attemptId?: number;
|
|
121
|
+
attemptEpoch?: string;
|
|
118
122
|
candidate: string;
|
|
119
123
|
mid?: string;
|
|
120
124
|
sdpMid?: string | null;
|
|
@@ -154,16 +158,22 @@ export declare const isDashboardP2PSignalType: (value: unknown) => value is "p2p
|
|
|
154
158
|
export type DashboardP2PReadyWirePayload = {
|
|
155
159
|
daemonId: string;
|
|
156
160
|
peerId: string;
|
|
161
|
+
attemptId?: number;
|
|
162
|
+
attemptEpoch?: string;
|
|
157
163
|
};
|
|
158
164
|
export type DashboardP2PAnswerWirePayload = {
|
|
159
165
|
daemonId: string;
|
|
160
166
|
peerId: string;
|
|
161
167
|
sdp: string;
|
|
162
168
|
type: string;
|
|
169
|
+
attemptId?: number;
|
|
170
|
+
attemptEpoch?: string;
|
|
163
171
|
};
|
|
164
172
|
export type DashboardP2PLocalIceWirePayload = {
|
|
165
173
|
daemonId: string;
|
|
166
174
|
peerId: string;
|
|
175
|
+
attemptId?: number;
|
|
176
|
+
attemptEpoch?: string;
|
|
167
177
|
candidate: string;
|
|
168
178
|
sdpMid: string | null;
|
|
169
179
|
sdpMLineIndex: number | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Persisted role-launch intent. Restart replays this selection rather than
|
|
2
|
+
* consulting newer machine defaults; restore preserves its original provenance. */
|
|
3
|
+
import { type LaunchDefaultRole, type LaunchDefaultSelection, type LaunchDefaultAxis, type LaunchDefaultRef } from './launch-defaults';
|
|
4
|
+
export interface RoleLaunchSelection {
|
|
5
|
+
role: LaunchDefaultRole;
|
|
6
|
+
selection: LaunchDefaultSelection;
|
|
7
|
+
applied: Record<keyof LaunchDefaultSelection, LaunchDefaultAxis>;
|
|
8
|
+
defaultRef?: LaunchDefaultRef;
|
|
9
|
+
documentRevision: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseRoleLaunchSelection(value: unknown): RoleLaunchSelection | undefined;
|
package/dist/session-launch.d.ts
CHANGED
|
@@ -1,25 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* level, and WHERE each value came from.
|
|
4
|
-
*
|
|
5
|
-
* Wiring-unification Phase E (docs/design/2026-09-23-wiring-unification.md §7,
|
|
6
|
-
* RC5). Before this record the only trace of a session's model was a display
|
|
7
|
-
* string in recent-activity (`buildLegacyModelModeSummaryMetadata`), so nobody
|
|
8
|
-
* could answer "did I pick this model, did the dashboard restore it from last
|
|
9
|
-
* time, did a mesh slot choose it, or is it just the provider default?" — and
|
|
10
|
-
* after a daemon restart not even the value survived.
|
|
11
|
-
*
|
|
12
|
-
* The daemon owns the record (daemon-core `SessionRegistry`); this file holds the
|
|
13
|
-
* shape and the pure readers so daemon-core, web-core, the server and the MCP
|
|
14
|
-
* server all read one definition. It is a dependency-free leaf, like the rest of
|
|
15
|
-
* mesh-shared.
|
|
16
|
-
*
|
|
17
|
-
* ★Server content boundary (CLAUDE.md): the full record rides P2P only. The
|
|
18
|
-
* server path carries exactly two derived fields — `model` (an identifier,
|
|
19
|
-
* passed through {@link sanitizeModelIdentifier}) and `modelSource` (an enum,
|
|
20
|
-
* passed through {@link isModelAxisSource}). Never widen that to the record.
|
|
21
|
-
*/
|
|
22
|
-
export declare const MODEL_AXIS_SOURCES: readonly ["user", "remembered", "mesh_slot", "task_override", "provider_default", "unspecified"];
|
|
1
|
+
import { type RoleLaunchSelection } from './role-launch';
|
|
2
|
+
export declare const MODEL_AXIS_SOURCES: readonly ["user", "remembered", "stored_default", "project_default", "mesh_slot", "task_override", "provider_default", "unspecified"];
|
|
23
3
|
export type ModelAxisSource = typeof MODEL_AXIS_SOURCES[number];
|
|
24
4
|
export declare function isModelAxisSource(value: unknown): value is ModelAxisSource;
|
|
25
5
|
/**
|
|
@@ -65,6 +45,7 @@ export interface ModelSelection {
|
|
|
65
45
|
history: ModelSelectionHistoryEntry[];
|
|
66
46
|
}
|
|
67
47
|
export interface SessionLaunchRecord {
|
|
48
|
+
roleLaunch?: RoleLaunchSelection;
|
|
68
49
|
sessionId: string;
|
|
69
50
|
providerType: string;
|
|
70
51
|
providerVersion?: string;
|
package/dist/slot-proposal.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ import { type MeshTaskDifficulty, type NodeCapabilitySlot } from './brain-routin
|
|
|
35
35
|
export interface CliSlotRecipe {
|
|
36
36
|
/** Optional model to pin on the slot. Best-effort at launch. */
|
|
37
37
|
model?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Optional ordered candidate models to prefer when the provider's live
|
|
40
|
+
* `modelOptions` (from model discovery) is present on `DetectedCliProvider`.
|
|
41
|
+
* The first candidate present in `modelOptions` wins; falls back to `model`
|
|
42
|
+
* when `modelOptions` is absent or contains none of the candidates.
|
|
43
|
+
*/
|
|
44
|
+
modelCandidates?: readonly string[];
|
|
38
45
|
/** Optional thinking level, in the provider's own vocabulary. */
|
|
39
46
|
thinkingLevel?: string;
|
|
40
47
|
/** Difficulty range this slot handles. Empty = general-purpose. */
|
|
@@ -76,6 +83,13 @@ export interface DetectedCliProvider {
|
|
|
76
83
|
displayName?: string;
|
|
77
84
|
/** Detected version, when known. Display only — never affects the mapping. */
|
|
78
85
|
version?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Live discovered / resolved `modelOptions` for this provider on the node,
|
|
88
|
+
* when available on the status catalog. When present and non-empty, recipes
|
|
89
|
+
* prefer the first matching model from `[recipe.model, ...recipe.modelCandidates]`
|
|
90
|
+
* that exists in `modelOptions`.
|
|
91
|
+
*/
|
|
92
|
+
modelOptions?: readonly string[];
|
|
79
93
|
}
|
|
80
94
|
/** One proposed slot plus why it was proposed. */
|
|
81
95
|
export interface ProposedSlotEntry {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the protocol contract the marker demarcates.
|
|
31
31
|
*/
|
|
32
32
|
import { type MissionBrief } from './mission-brief';
|
|
33
|
-
export declare const WORKER_TOOLS: readonly ["report_completion", "progress_update", "peer_context_pull", "git_status", "git_log", "git_diff"];
|
|
33
|
+
export declare const WORKER_TOOLS: readonly ["report_completion", "progress_update", "peer_context_pull", "git_status", "git_log", "git_diff", "driver_list", "driver_describe", "driver_invoke"];
|
|
34
34
|
export type WorkerTool = typeof WORKER_TOOLS[number];
|
|
35
35
|
export declare function isWorkerTool(value: unknown): value is WorkerTool;
|
|
36
36
|
/** Stable marker line so a body is never footered twice and tests can find it. */
|
package/package.json
CHANGED
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* - the daemon-only verbs (launch / MCP-only pull / owner actions) that have
|
|
12
12
|
* no tool.
|
|
13
13
|
*
|
|
14
|
-
* Assistant tools
|
|
15
|
-
*
|
|
16
|
-
* never publish them.
|
|
14
|
+
* Assistant-specific tools remain separate from mesh tools. Driver discovery
|
|
15
|
+
* (list/describe) is shared across all three modes; invoke remains Worker-only.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
export const ASSISTANT_TOOLS = [
|
|
@@ -33,6 +32,8 @@ export const ASSISTANT_TOOLS = [
|
|
|
33
32
|
'project_note',
|
|
34
33
|
'launch_defaults',
|
|
35
34
|
'launch_defaults_set',
|
|
35
|
+
'driver_list',
|
|
36
|
+
'driver_describe',
|
|
36
37
|
] as const
|
|
37
38
|
|
|
38
39
|
export type AssistantTool = typeof ASSISTANT_TOOLS[number]
|
|
@@ -46,6 +47,8 @@ export function isAssistantTool(value: unknown): value is AssistantTool {
|
|
|
46
47
|
/** Daemon verb names (§4.4 table + the five verbs below it). */
|
|
47
48
|
export const ASSISTANT_VERB = {
|
|
48
49
|
// tool verbs
|
|
50
|
+
driverList: 'driver_list',
|
|
51
|
+
driverDescribe: 'driver_describe',
|
|
49
52
|
projects: 'assistant_projects',
|
|
50
53
|
environment: 'assistant_environment',
|
|
51
54
|
action: 'assistant_action',
|
|
@@ -80,6 +83,8 @@ export type AssistantVerb = typeof ASSISTANT_VERB[keyof typeof ASSISTANT_VERB]
|
|
|
80
83
|
|
|
81
84
|
/** The daemon verb each assistant tool calls. */
|
|
82
85
|
export const ASSISTANT_TOOL_VERBS: Readonly<Record<AssistantTool, AssistantVerb>> = {
|
|
86
|
+
driver_list: ASSISTANT_VERB.driverList,
|
|
87
|
+
driver_describe: ASSISTANT_VERB.driverDescribe,
|
|
83
88
|
projects: ASSISTANT_VERB.projects,
|
|
84
89
|
environment: ASSISTANT_VERB.environment,
|
|
85
90
|
assistant_action: ASSISTANT_VERB.action,
|
package/src/index.ts
CHANGED
package/src/launch-defaults.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface LaunchDefaultAxis {
|
|
|
57
57
|
export type LaunchDefaultWarning = 'catalog_unverified' | 'manual_mcp_setup_required';
|
|
58
58
|
export type LaunchDefaultValidationReason =
|
|
59
59
|
| 'invalid_schema' | 'provider_unknown' | 'provider_not_cli' | 'provider_disabled'
|
|
60
|
-
| 'provider_not_detected' | 'provider_not_eligible' | 'model_unsupported'
|
|
60
|
+
| 'provider_not_detected' | 'provider_not_eligible' | 'provider_quota_gated' | 'model_unsupported'
|
|
61
61
|
| 'model_provider_mismatch' | 'model_not_offered' | 'thinking_unsupported'
|
|
62
62
|
| 'thinking_not_offered' | 'catalog_unavailable';
|
|
63
63
|
export type LaunchDefaultResolution =
|
package/src/mesh-tool-names.ts
CHANGED
package/src/outbound-message.ts
CHANGED
|
@@ -321,3 +321,20 @@ export function isOutboundMessage(value: unknown): value is OutboundMessage {
|
|
|
321
321
|
&& isSendPolicy(candidate.policy)
|
|
322
322
|
&& typeof candidate.createdAt === 'number'
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
const PASTED_OPEN_RE = /^\s*<pasted_content(?:\s+id="[^"]*")?\s*>\s*$/
|
|
326
|
+
const PASTED_CLOSE_RE = /^\s*<\/pasted_content(?:\s+id="[^"]*")?\s*>\s*$/
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Drop the `<pasted_content>` envelope lines Claude Code wraps around a pasted
|
|
330
|
+
* input. Only a text that OPENS with the envelope is touched; anything else is
|
|
331
|
+
* returned unchanged. Shared across daemon transcript merge and web echo/display
|
|
332
|
+
* normalization so wrapped turns correlate identically.
|
|
333
|
+
*/
|
|
334
|
+
export function unwrapPastedContent(text: string): string {
|
|
335
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n')
|
|
336
|
+
const first = lines.findIndex((line) => line.trim())
|
|
337
|
+
if (first < 0 || !PASTED_OPEN_RE.test(lines[first])) return text
|
|
338
|
+
return lines.filter((line) => !PASTED_OPEN_RE.test(line) && !PASTED_CLOSE_RE.test(line)).join('\n')
|
|
339
|
+
}
|
|
340
|
+
|
|
@@ -260,8 +260,8 @@ export const DAEMON_P2P_SIGNAL_TYPES = ['p2p_offer', 'p2p_ice'] as const
|
|
|
260
260
|
export type DaemonP2PSignalType = typeof DAEMON_P2P_SIGNAL_TYPES[number]
|
|
261
261
|
export const isDaemonP2PSignalType = makeTypeGuard(DAEMON_P2P_SIGNAL_TYPES)
|
|
262
262
|
|
|
263
|
-
export type P2POfferWirePayload = { sdp: string; type: string; peerId: string }
|
|
264
|
-
export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string }
|
|
263
|
+
export type P2POfferWirePayload = { sdp: string; type: string; peerId: string; attemptId?: number; attemptEpoch?: string }
|
|
264
|
+
export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string; attemptId?: number; attemptEpoch?: string }
|
|
265
265
|
|
|
266
266
|
/**
|
|
267
267
|
* A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
|
|
@@ -273,6 +273,8 @@ export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string
|
|
|
273
273
|
export type RelayedP2PSignalWirePayload = {
|
|
274
274
|
peerId?: string
|
|
275
275
|
daemonId?: string
|
|
276
|
+
attemptId?: number
|
|
277
|
+
attemptEpoch?: string
|
|
276
278
|
sharePermission?: string
|
|
277
279
|
shareTargetSessionId?: string
|
|
278
280
|
sdp?: string
|
|
@@ -152,6 +152,8 @@ export type SystemAnnouncementWirePayload = {
|
|
|
152
152
|
export type DashboardP2POfferWirePayload = {
|
|
153
153
|
daemonId: string
|
|
154
154
|
peerId?: string
|
|
155
|
+
attemptId?: number
|
|
156
|
+
attemptEpoch?: string
|
|
155
157
|
sdp: string
|
|
156
158
|
type: string
|
|
157
159
|
}
|
|
@@ -159,6 +161,8 @@ export type DashboardP2POfferWirePayload = {
|
|
|
159
161
|
export type DashboardP2PIceWirePayload = {
|
|
160
162
|
daemonId: string
|
|
161
163
|
peerId?: string
|
|
164
|
+
attemptId?: number
|
|
165
|
+
attemptEpoch?: string
|
|
162
166
|
candidate: string
|
|
163
167
|
mid?: string
|
|
164
168
|
sdpMid?: string | null
|
|
@@ -234,11 +238,13 @@ export const DASHBOARD_P2P_SIGNAL_TYPES = ['p2p_ready', 'p2p_answer', 'p2p_ice']
|
|
|
234
238
|
export type DashboardP2PSignalType = typeof DASHBOARD_P2P_SIGNAL_TYPES[number]
|
|
235
239
|
export const isDashboardP2PSignalType = makeTypeGuard(DASHBOARD_P2P_SIGNAL_TYPES)
|
|
236
240
|
|
|
237
|
-
export type DashboardP2PReadyWirePayload = { daemonId: string; peerId: string }
|
|
238
|
-
export type DashboardP2PAnswerWirePayload = { daemonId: string; peerId: string; sdp: string; type: string }
|
|
241
|
+
export type DashboardP2PReadyWirePayload = { daemonId: string; peerId: string; attemptId?: number; attemptEpoch?: string }
|
|
242
|
+
export type DashboardP2PAnswerWirePayload = { daemonId: string; peerId: string; sdp: string; type: string; attemptId?: number; attemptEpoch?: string }
|
|
239
243
|
export type DashboardP2PLocalIceWirePayload = {
|
|
240
244
|
daemonId: string
|
|
241
245
|
peerId: string
|
|
246
|
+
attemptId?: number
|
|
247
|
+
attemptEpoch?: string
|
|
242
248
|
candidate: string
|
|
243
249
|
sdpMid: string | null
|
|
244
250
|
sdpMLineIndex: number | null
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Persisted role-launch intent. Restart replays this selection rather than
|
|
2
|
+
* consulting newer machine defaults; restore preserves its original provenance. */
|
|
3
|
+
import { isLaunchDefaultRole, isLaunchDefaultSource, type LaunchDefaultRole,
|
|
4
|
+
type LaunchDefaultSelection, type LaunchDefaultAxis, type LaunchDefaultRef } from './launch-defaults';
|
|
5
|
+
export interface RoleLaunchSelection {
|
|
6
|
+
role: LaunchDefaultRole;
|
|
7
|
+
selection: LaunchDefaultSelection;
|
|
8
|
+
applied: Record<keyof LaunchDefaultSelection, LaunchDefaultAxis>;
|
|
9
|
+
defaultRef?: LaunchDefaultRef;
|
|
10
|
+
documentRevision: number;
|
|
11
|
+
}
|
|
12
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
13
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
14
|
+
? value as Record<string, unknown> : undefined;
|
|
15
|
+
}
|
|
16
|
+
function ref(value: unknown): LaunchDefaultRef | undefined {
|
|
17
|
+
const r = record(value);
|
|
18
|
+
if (!r || !isLaunchDefaultRole(r.role) || (r.scope !== 'global' && r.scope !== 'project')
|
|
19
|
+
|| !Number.isSafeInteger(r.revision) || (r.revision as number) < 1
|
|
20
|
+
|| (r.scope === 'project' && (typeof r.projectId !== 'string' || !r.projectId))) return undefined;
|
|
21
|
+
return { role: r.role, scope: r.scope, revision: r.revision as number,
|
|
22
|
+
...(r.scope === 'project' ? { projectId: r.projectId as string } : {}) };
|
|
23
|
+
}
|
|
24
|
+
function axis(value: unknown): LaunchDefaultAxis | undefined {
|
|
25
|
+
const a = record(value);
|
|
26
|
+
if (!a || !isLaunchDefaultSource(a.source) || (a.value !== null && typeof a.value !== 'string')) return undefined;
|
|
27
|
+
const defaultRef = ref(a.defaultRef);
|
|
28
|
+
if (a.source === 'stored_default' && defaultRef?.scope !== 'global'
|
|
29
|
+
|| a.source === 'project_default' && defaultRef?.scope !== 'project'
|
|
30
|
+
|| (a.source === 'user' || a.source === 'provider_default') && a.defaultRef !== undefined) return undefined;
|
|
31
|
+
return { value: a.value as string | null, source: a.source, ...(defaultRef ? { defaultRef } : {}) };
|
|
32
|
+
}
|
|
33
|
+
export function parseRoleLaunchSelection(value: unknown): RoleLaunchSelection | undefined {
|
|
34
|
+
const r = record(value), s = record(r?.selection), a = record(r?.applied);
|
|
35
|
+
if (!r || !isLaunchDefaultRole(r.role) || !s || typeof s.provider !== 'string' || !s.provider
|
|
36
|
+
|| !Number.isSafeInteger(r.documentRevision) || (r.documentRevision as number) < 1
|
|
37
|
+
|| (s.model !== undefined && typeof s.model !== 'string')
|
|
38
|
+
|| (s.thinkingLevel !== undefined && typeof s.thinkingLevel !== 'string')) return undefined;
|
|
39
|
+
const provider = axis(a?.provider), model = axis(a?.model), thinkingLevel = axis(a?.thinkingLevel);
|
|
40
|
+
if (!provider || !model || !thinkingLevel
|
|
41
|
+
|| [provider, model, thinkingLevel].some(axis => axis.defaultRef && (axis.defaultRef.role !== r.role
|
|
42
|
+
|| axis.defaultRef.scope === 'global' && axis.defaultRef.revision > (r.documentRevision as number)))
|
|
43
|
+
|| provider.value !== s.provider
|
|
44
|
+
|| model.value !== (s.model ?? null) || thinkingLevel.value !== (s.thinkingLevel ?? null)) return undefined;
|
|
45
|
+
const defaultRef = ref(r.defaultRef);
|
|
46
|
+
return { role: r.role, selection: { provider: s.provider,
|
|
47
|
+
...(typeof s.model === 'string' ? { model: s.model } : {}),
|
|
48
|
+
...(typeof s.thinkingLevel === 'string' ? { thinkingLevel: s.thinkingLevel } : {}) },
|
|
49
|
+
applied: { provider, model, thinkingLevel }, ...(defaultRef ? { defaultRef } : {}),
|
|
50
|
+
documentRevision: r.documentRevision as number };
|
|
51
|
+
}
|
package/src/session-launch.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseRoleLaunchSelection, type RoleLaunchSelection } from './role-launch'
|
|
1
2
|
/**
|
|
2
3
|
* session-launch — how a session was launched: provider, model and thinking
|
|
3
4
|
* level, and WHERE each value came from.
|
|
@@ -35,11 +36,14 @@ export const MODEL_AXIS_SOURCES = [
|
|
|
35
36
|
'user',
|
|
36
37
|
/** The dashboard dialog restored it from the last launch (browser-local memory). */
|
|
37
38
|
'remembered',
|
|
39
|
+
/** The authoritative daemon role default or host project override. */
|
|
40
|
+
'stored_default',
|
|
41
|
+
'project_default',
|
|
38
42
|
/** A mesh capability slot supplied it (or a difficulty preset the slot outranked). */
|
|
39
43
|
'mesh_slot',
|
|
40
44
|
/** An explicit per-task model / thinking level on a mesh queue task. */
|
|
41
45
|
'task_override',
|
|
42
|
-
/** Nothing
|
|
46
|
+
/** Nothing requested: the provider chooses; resolvedDefault records it when known. */
|
|
43
47
|
'provider_default',
|
|
44
48
|
/** A value was requested without saying where it came from, or nothing is known. */
|
|
45
49
|
'unspecified',
|
|
@@ -102,6 +106,7 @@ export interface ModelSelection {
|
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
export interface SessionLaunchRecord {
|
|
109
|
+
roleLaunch?: RoleLaunchSelection
|
|
105
110
|
sessionId: string
|
|
106
111
|
providerType: string
|
|
107
112
|
providerVersion?: string
|
|
@@ -230,7 +235,9 @@ export function parseSessionLaunchRecord(raw: unknown): SessionLaunchRecord | un
|
|
|
230
235
|
const providerVersion = readString(record.providerVersion)
|
|
231
236
|
const workspace = readString(record.workspace)
|
|
232
237
|
const autoApproveModeId = readString(record.autoApproveModeId)
|
|
238
|
+
const roleLaunch = parseRoleLaunchSelection(record.roleLaunch)
|
|
233
239
|
return {
|
|
240
|
+
...(roleLaunch ? { roleLaunch } : {}),
|
|
234
241
|
sessionId,
|
|
235
242
|
providerType,
|
|
236
243
|
...(providerVersion ? { providerVersion } : {}),
|
package/src/slot-proposal.ts
CHANGED
|
@@ -40,6 +40,13 @@ import {
|
|
|
40
40
|
export interface CliSlotRecipe {
|
|
41
41
|
/** Optional model to pin on the slot. Best-effort at launch. */
|
|
42
42
|
model?: string
|
|
43
|
+
/**
|
|
44
|
+
* Optional ordered candidate models to prefer when the provider's live
|
|
45
|
+
* `modelOptions` (from model discovery) is present on `DetectedCliProvider`.
|
|
46
|
+
* The first candidate present in `modelOptions` wins; falls back to `model`
|
|
47
|
+
* when `modelOptions` is absent or contains none of the candidates.
|
|
48
|
+
*/
|
|
49
|
+
modelCandidates?: readonly string[]
|
|
43
50
|
/** Optional thinking level, in the provider's own vocabulary. */
|
|
44
51
|
thinkingLevel?: string
|
|
45
52
|
/** Difficulty range this slot handles. Empty = general-purpose. */
|
|
@@ -101,11 +108,26 @@ export const CLI_SLOT_RECIPES: Readonly<Record<string, readonly CliSlotRecipe[]>
|
|
|
101
108
|
],
|
|
102
109
|
'antigravity-cli': [
|
|
103
110
|
{
|
|
104
|
-
model: 'Gemini 3.
|
|
111
|
+
model: 'Gemini 3.8 Flash (High)',
|
|
112
|
+
modelCandidates: ['Gemini 3.8 Flash (High)', 'Gemini 4 Argon (Low)', 'Gemini 3.7 Flash (High)', 'Gemini 3.1 Pro (High)'],
|
|
105
113
|
difficulty: ['easy'],
|
|
106
114
|
maxParallel: 2,
|
|
107
115
|
rationale: 'Cheap capacity for easy tasks.',
|
|
108
116
|
},
|
|
117
|
+
{
|
|
118
|
+
model: 'Gemini 4 Argon (Medium)',
|
|
119
|
+
modelCandidates: ['Gemini 4 Argon (Medium)', 'Gemini 3.8 Flash (High)', 'Gemini 3.7 Flash (High)', 'Gemini 3.1 Pro (High)'],
|
|
120
|
+
difficulty: ['medium'],
|
|
121
|
+
maxParallel: 2,
|
|
122
|
+
rationale: 'Balanced Argon tier for medium tasks.',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
model: 'Gemini 4 Argon (High)',
|
|
126
|
+
modelCandidates: ['Gemini 4 Argon (High)', 'Gemini 3.8 Flash (High)', 'Gemini 3.1 Pro (High)'],
|
|
127
|
+
difficulty: ['difficult'],
|
|
128
|
+
maxParallel: 1,
|
|
129
|
+
rationale: 'Reserved for hard tasks; capped at 1 to bound cost.',
|
|
130
|
+
},
|
|
109
131
|
],
|
|
110
132
|
'cursor-cli': [
|
|
111
133
|
{
|
|
@@ -137,6 +159,13 @@ export interface DetectedCliProvider {
|
|
|
137
159
|
displayName?: string
|
|
138
160
|
/** Detected version, when known. Display only — never affects the mapping. */
|
|
139
161
|
version?: string
|
|
162
|
+
/**
|
|
163
|
+
* Live discovered / resolved `modelOptions` for this provider on the node,
|
|
164
|
+
* when available on the status catalog. When present and non-empty, recipes
|
|
165
|
+
* prefer the first matching model from `[recipe.model, ...recipe.modelCandidates]`
|
|
166
|
+
* that exists in `modelOptions`.
|
|
167
|
+
*/
|
|
168
|
+
modelOptions?: readonly string[]
|
|
140
169
|
}
|
|
141
170
|
|
|
142
171
|
/** One proposed slot plus why it was proposed. */
|
|
@@ -197,6 +226,36 @@ function dedupeDetected(detected: readonly DetectedCliProvider[]): DetectedCliPr
|
|
|
197
226
|
return out
|
|
198
227
|
}
|
|
199
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Resolve a recipe's pinned model against the provider's live `modelOptions`
|
|
231
|
+
* (populated from the daemon's model-discovery overlay when available). If
|
|
232
|
+
* `modelOptions` is absent or none of the recipe's candidates appear in it,
|
|
233
|
+
* falls back to `recipe.model`.
|
|
234
|
+
*/
|
|
235
|
+
function resolveRecipeModel(
|
|
236
|
+
recipe: CliSlotRecipe,
|
|
237
|
+
modelOptions: readonly string[] | undefined,
|
|
238
|
+
): string | undefined {
|
|
239
|
+
if (!Array.isArray(modelOptions) || modelOptions.length === 0) {
|
|
240
|
+
return recipe.model
|
|
241
|
+
}
|
|
242
|
+
const available = new Set(
|
|
243
|
+
modelOptions
|
|
244
|
+
.filter((m): m is string => typeof m === 'string' && m.trim().length > 0)
|
|
245
|
+
.map(m => m.trim()),
|
|
246
|
+
)
|
|
247
|
+
if (available.size === 0) return recipe.model
|
|
248
|
+
const candidates = [
|
|
249
|
+
...(recipe.model ? [recipe.model] : []),
|
|
250
|
+
...(recipe.modelCandidates ?? []),
|
|
251
|
+
]
|
|
252
|
+
for (const candidate of candidates) {
|
|
253
|
+
const trimmed = typeof candidate === 'string' ? candidate.trim() : ''
|
|
254
|
+
if (trimmed && available.has(trimmed)) return trimmed
|
|
255
|
+
}
|
|
256
|
+
return recipe.model
|
|
257
|
+
}
|
|
258
|
+
|
|
200
259
|
/**
|
|
201
260
|
* Build a capability-slot proposal from a node's detected CLI providers.
|
|
202
261
|
*
|
|
@@ -229,7 +288,7 @@ export function buildSlotProposal(
|
|
|
229
288
|
// a proposal can never preview a shape the write would reshape.
|
|
230
289
|
const slot = normalizeNodeCapabilitySlot({
|
|
231
290
|
provider: provider.type,
|
|
232
|
-
model: recipe.
|
|
291
|
+
model: resolveRecipeModel(recipe, provider.modelOptions),
|
|
233
292
|
thinkingLevel: recipe.thinkingLevel,
|
|
234
293
|
difficulty: recipe.difficulty,
|
|
235
294
|
maxParallel: recipe.maxParallel,
|