@adhdev/mesh-shared 1.0.82-rc.4 → 1.0.82-rc.40

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.
Files changed (65) hide show
  1. package/dist/assistant-protocol.d.ts +161 -6
  2. package/dist/decision-push-relay.d.ts +20 -0
  3. package/dist/index.d.ts +11 -0
  4. package/dist/index.js +1197 -118
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1107 -118
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/launch-defaults.d.ts +196 -0
  9. package/dist/mesh-tool-names.d.ts +2 -2
  10. package/dist/mesh-vocabulary.d.ts +13 -4
  11. package/dist/mission-git-state.d.ts +83 -0
  12. package/dist/mission-items.d.ts +202 -0
  13. package/dist/node-facts.d.ts +75 -1
  14. package/dist/outbound-message.d.ts +9 -2
  15. package/dist/path-ownership.d.ts +1 -1
  16. package/dist/protocol/daemon-server.d.ts +42 -2
  17. package/dist/protocol/dashboard-daemon-p2p.d.ts +1 -1
  18. package/dist/protocol/dashboard-server.d.ts +20 -2
  19. package/dist/provider-approval.d.ts +36 -0
  20. package/dist/provider-question.d.ts +19 -0
  21. package/dist/quota-freshness.d.ts +65 -0
  22. package/dist/relay-reprobe.d.ts +52 -0
  23. package/dist/release.d.ts +90 -0
  24. package/dist/role-launch.d.ts +11 -0
  25. package/dist/session-launch.d.ts +10 -22
  26. package/dist/slot-proposal.d.ts +14 -0
  27. package/dist/task-source-phase.d.ts +75 -0
  28. package/dist/turn-evidence.d.ts +29 -13
  29. package/dist/turn-ipc-mission.d.ts +68 -1
  30. package/dist/turn-ipc-queue.d.ts +19 -0
  31. package/dist/turn-ipc.d.ts +2 -2
  32. package/dist/types.d.ts +1 -0
  33. package/dist/worker-protocol.d.ts +1 -1
  34. package/package.json +1 -1
  35. package/src/assistant-protocol.ts +302 -6
  36. package/src/daemon-normalize.ts +7 -5
  37. package/src/decision-push-relay.ts +23 -0
  38. package/src/index.ts +12 -0
  39. package/src/keyed-doc-delta.ts +9 -1
  40. package/src/launch-defaults.ts +170 -0
  41. package/src/mesh-tool-names.ts +16 -0
  42. package/src/mesh-vocabulary.ts +12 -2
  43. package/src/mission-git-state.ts +112 -0
  44. package/src/mission-items.ts +421 -0
  45. package/src/node-facts.ts +70 -1
  46. package/src/outbound-message.ts +19 -0
  47. package/src/path-ownership.ts +4 -6
  48. package/src/protocol/daemon-server.ts +37 -2
  49. package/src/protocol/dashboard-server.ts +13 -2
  50. package/src/provider-approval.ts +59 -0
  51. package/src/provider-question.ts +31 -0
  52. package/src/quota-freshness.ts +139 -0
  53. package/src/relay-reprobe.ts +117 -0
  54. package/src/release.ts +121 -0
  55. package/src/role-launch.ts +51 -0
  56. package/src/session-launch.ts +17 -1
  57. package/src/slot-proposal.ts +61 -2
  58. package/src/task-source-phase.ts +61 -0
  59. package/src/turn-evidence.ts +27 -20
  60. package/src/turn-ipc-mission.ts +121 -11
  61. package/src/turn-ipc-queue.ts +29 -1
  62. package/src/turn-ipc-turn.ts +1 -1
  63. package/src/turn-ipc.ts +1 -0
  64. package/src/types.ts +1 -0
  65. package/src/worker-protocol.ts +15 -0
@@ -126,7 +126,7 @@ export type SendPolicy = {
126
126
  * `unsupported_input`), which `satisfies readonly SendRefusal[]` there keeps
127
127
  * honest at compile time.
128
128
  */
129
- export type SendRefusal = 'not_ready' | 'send_in_flight' | 'not_generating' | 'not_supported' | 'platform_unsupported' | 'modal_parked' | 'interrupt_not_implemented' | 'interrupt_refused' | 'idle_timeout' | 'session_exited' | 'unsupported_input' | 'no_target' | 'bootstrap_pending' | 'duplicate_dispatch' | 'internal_error';
129
+ export type SendRefusal = 'not_ready' | 'send_in_flight' | 'not_generating' | 'not_supported' | 'platform_unsupported' | 'modal_parked' | 'interrupt_not_implemented' | 'interrupt_refused' | 'idle_timeout' | 'session_exited' | 'unsupported_input' | 'no_target' | 'bootstrap_pending' | 'duplicate_dispatch' | 'new_work_held' | 'internal_error';
130
130
  /**
131
131
  * Which write path carried a delivered/queued message (Phase D2, additive
132
132
  * detail — callers branch on `kind`; `route` only refines the report):
@@ -197,9 +197,16 @@ export declare function isOutboundMessageOrigin(value: unknown): value is Outbou
197
197
  export declare const SEND_POLICY_MODES: readonly ["queue", "send_now", "interrupt"];
198
198
  export declare function isSendPolicyMode(value: unknown): value is SendPolicy['mode'];
199
199
  export declare function isSendPolicy(value: unknown): value is SendPolicy;
200
- export declare const SEND_REFUSAL_REASONS: readonly ["not_ready", "send_in_flight", "not_generating", "not_supported", "platform_unsupported", "modal_parked", "interrupt_not_implemented", "interrupt_refused", "idle_timeout", "session_exited", "unsupported_input", "no_target", "bootstrap_pending", "duplicate_dispatch", "internal_error"];
200
+ export declare const SEND_REFUSAL_REASONS: readonly ["not_ready", "send_in_flight", "not_generating", "not_supported", "platform_unsupported", "modal_parked", "interrupt_not_implemented", "interrupt_refused", "idle_timeout", "session_exited", "unsupported_input", "no_target", "bootstrap_pending", "duplicate_dispatch", "new_work_held", "internal_error"];
201
201
  export declare function isSendRefusal(value: unknown): value is SendRefusal;
202
202
  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;
@@ -33,7 +33,7 @@
33
33
  */
34
34
  /** One normalized ownership entry: a repo-relative path, optionally a `**` subtree prefix. */
35
35
  export interface OwnedPathEntry {
36
- /** Repo-relative path with forward slashes, no leading `./`, no trailing slash. */
36
+ /** Repo-relative path with forward slashes, no leading `./`, no trailing slash; empty for the whole-repo subtree. */
37
37
  readonly path: string;
38
38
  /** True when this entry was declared as `<path>/**` (owns the whole subtree). */
39
39
  readonly subtree: boolean;
@@ -32,15 +32,38 @@ import { type CommandPayload, type Envelope, type MessageId } from './envelope';
32
32
  * time (UserSession.ts), so adding a member here without adding it there — or
33
33
  * vice versa — fails `tsc`.
34
34
  */
35
- export declare const DAEMON_STATUS_EVENT_NAMES: readonly ["agent:generating_started", "agent:waiting_approval", "agent:waiting_choice", "agent:generating_completed", "agent:stopped", "monitor:no_progress"];
35
+ export declare const DAEMON_STATUS_EVENT_NAMES: readonly ["agent:generating_started", "agent:waiting_approval", "agent:waiting_choice", "agent:generating_completed", "agent:stopped", "monitor:no_progress", "agent:decision_needed"];
36
36
  export type DaemonStatusEventName = typeof DAEMON_STATUS_EVENT_NAMES[number];
37
- export declare const isDaemonStatusEventName: (value: unknown) => value is "agent:generating_started" | "agent:waiting_approval" | "agent:waiting_choice" | "agent:generating_completed" | "agent:stopped" | "monitor:no_progress";
37
+ export declare const isDaemonStatusEventName: (value: unknown) => value is "agent:generating_started" | "agent:waiting_approval" | "agent:waiting_choice" | "agent:generating_completed" | "agent:stopped" | "monitor:no_progress" | "agent:decision_needed";
38
38
  /**
39
39
  * Server-bound status event. Content boundary: identifiers, enums, counters
40
40
  * and booleans only — plus the approved approval-modal exception
41
41
  * (`modalMessage`/`modalButtons`, CLAUDE.md "Push notification exception").
42
42
  * Mirror of daemon-core `DaemonStatusEventPayload`.
43
43
  */
44
+ /**
45
+ * Owner-decision push payload (design §9, owner decision D1 2026-10-28). The ONLY mission text that
46
+ * reaches the server: the question (<=80 chars) and up to 3 option labels (<=24 chars). Never
47
+ * `context`, free-text answers, mission goal/summary or chat content.
48
+ */
49
+ export type DecisionPushWire = {
50
+ meshId: string;
51
+ missionId: string;
52
+ itemId: string;
53
+ question: string;
54
+ options: Array<{
55
+ id: string;
56
+ label: string;
57
+ }>;
58
+ recommended?: string;
59
+ urgency: 'normal' | 'blocking';
60
+ dueAt?: string;
61
+ /** `new` = freshly asked; `remind` = a blocking decision crossed its due time. */
62
+ reason: 'new' | 'remind';
63
+ };
64
+ export declare const DECISION_PUSH_QUESTION_MAX = 80;
65
+ export declare const DECISION_PUSH_OPTION_LABEL_MAX = 24;
66
+ export declare const DECISION_PUSH_OPTIONS_MAX = 3;
44
67
  export type DaemonStatusEventWirePayload = {
45
68
  event: DaemonStatusEventName;
46
69
  timestamp: number;
@@ -52,8 +75,13 @@ export type DaemonStatusEventWirePayload = {
52
75
  elapsedSec?: number;
53
76
  modalMessage?: string;
54
77
  modalButtons?: string[];
78
+ modalKey?: string;
79
+ approvalEvidence?: 'live' | 'cached' | 'unavailable';
80
+ waitKind?: 'external';
55
81
  surfaceHidden?: boolean;
56
82
  muted?: boolean;
83
+ /** Present only on `agent:decision_needed`. */
84
+ decision?: DecisionPushWire;
57
85
  };
58
86
  /** Routing-only session entry the daemon reports over the server WS. Mirror of daemon-core `RoutingSessionEntry`. */
59
87
  export type RoutingSessionWireEntry = {
@@ -213,11 +241,17 @@ export type P2POfferWirePayload = {
213
241
  sdp: string;
214
242
  type: string;
215
243
  peerId: string;
244
+ attemptId?: number;
245
+ attemptEpoch?: string;
246
+ probe?: boolean;
216
247
  };
217
248
  export type P2PIceWirePayload = {
218
249
  candidate: string;
219
250
  mid: string;
220
251
  peerId: string;
252
+ attemptId?: number;
253
+ attemptEpoch?: string;
254
+ probe?: boolean;
221
255
  };
222
256
  /**
223
257
  * A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
@@ -229,6 +263,12 @@ export type P2PIceWirePayload = {
229
263
  export type RelayedP2PSignalWirePayload = {
230
264
  peerId?: string;
231
265
  daemonId?: string;
266
+ attemptId?: number;
267
+ attemptEpoch?: string;
268
+ /** p2p_ready only: the dashboard's stale-connect backoff index (see DashboardP2PReadyWirePayload). */
269
+ backoffStep?: number;
270
+ /** Make-before-break relay->direct re-probe of an existing peer (see DashboardP2PReadyWirePayload). */
271
+ probe?: boolean;
232
272
  sharePermission?: string;
233
273
  shareTargetSessionId?: string;
234
274
  sdp?: string;
@@ -34,7 +34,7 @@ export type ChunkFrame<T extends string> = {
34
34
  export declare function isChunkType(value: unknown): value is `${string}_chunk`;
35
35
  export declare const DASHBOARD_TO_DAEMON_P2P_TYPES: readonly ["ping", "pong", "command", "input", "pty_input", "pty_resize", "screenshot_start", "screenshot_stop", "subscribe", "unsubscribe", "seqscribe_session_interest", "read", "write", "list"];
36
36
  export type DashboardToDaemonP2PType = typeof DASHBOARD_TO_DAEMON_P2P_TYPES[number];
37
- export declare const isDashboardToDaemonP2PType: (value: unknown) => value is "list" | "command" | "ping" | "pong" | "input" | "pty_input" | "pty_resize" | "screenshot_start" | "screenshot_stop" | "subscribe" | "unsubscribe" | "seqscribe_session_interest" | "read" | "write";
37
+ export declare const isDashboardToDaemonP2PType: (value: unknown) => value is "read" | "list" | "command" | "ping" | "pong" | "input" | "pty_input" | "pty_resize" | "screenshot_start" | "screenshot_stop" | "subscribe" | "unsubscribe" | "seqscribe_session_interest" | "write";
38
38
  export type DashboardToDaemonChunkType = `${DashboardToDaemonP2PType}_chunk`;
39
39
  export declare function isDashboardToDaemonChunkType(value: unknown): value is DashboardToDaemonChunkType;
40
40
  export type P2PPingMsg = {
@@ -21,9 +21,9 @@ import { type DaemonStatusEventName, type P2PIceWirePayload, type P2POfferWirePa
21
21
  /** Server-originated events the dashboard receives in addition to the daemon set. */
22
22
  export declare const SERVER_STATUS_EVENT_NAMES: readonly ["daemon:disconnect", "team:session_viewed", "team:view_request", "team:view_request_approved", "team:view_request_rejected"];
23
23
  /** Mirror of daemon-core `DashboardStatusEventName`; pinned equal by the server at compile time. */
24
- export declare const DASHBOARD_STATUS_EVENT_NAMES: readonly ["agent:generating_started", "agent:waiting_approval", "agent:waiting_choice", "agent:generating_completed", "agent:stopped", "monitor:no_progress", "daemon:disconnect", "team:session_viewed", "team:view_request", "team:view_request_approved", "team:view_request_rejected"];
24
+ export declare const DASHBOARD_STATUS_EVENT_NAMES: readonly ["agent:generating_started", "agent:waiting_approval", "agent:waiting_choice", "agent:generating_completed", "agent:stopped", "monitor:no_progress", "agent:decision_needed", "daemon:disconnect", "team:session_viewed", "team:view_request", "team:view_request_approved", "team:view_request_rejected"];
25
25
  export type DashboardStatusEventName = DaemonStatusEventName | typeof SERVER_STATUS_EVENT_NAMES[number];
26
- export declare const isDashboardStatusEventName: (value: unknown) => value is "agent:generating_started" | "agent:waiting_approval" | "agent:waiting_choice" | "agent:generating_completed" | "agent:stopped" | "monitor:no_progress" | "daemon:disconnect" | "team:session_viewed" | "team:view_request" | "team:view_request_approved" | "team:view_request_rejected";
26
+ export declare const isDashboardStatusEventName: (value: unknown) => value is "agent:generating_started" | "agent:waiting_approval" | "agent:waiting_choice" | "agent:generating_completed" | "agent:stopped" | "monitor:no_progress" | "agent:decision_needed" | "daemon:disconnect" | "team:session_viewed" | "team:view_request" | "team:view_request_approved" | "team:view_request_rejected";
27
27
  /**
28
28
  * Sanitized status event as the server relays it (UserSession
29
29
  * `buildDashboardStatusEvent`). Mirror of daemon-core
@@ -109,12 +109,18 @@ export type SystemAnnouncementWirePayload = {
109
109
  export type DashboardP2POfferWirePayload = {
110
110
  daemonId: string;
111
111
  peerId?: string;
112
+ attemptId?: number;
113
+ attemptEpoch?: string;
114
+ probe?: boolean;
112
115
  sdp: string;
113
116
  type: string;
114
117
  };
115
118
  export type DashboardP2PIceWirePayload = {
116
119
  daemonId: string;
117
120
  peerId?: string;
121
+ attemptId?: number;
122
+ attemptEpoch?: string;
123
+ probe?: boolean;
118
124
  candidate: string;
119
125
  mid?: string;
120
126
  sdpMid?: string | null;
@@ -151,19 +157,31 @@ export declare const isDashboardToUserSessionType: (value: unknown) => value is
151
157
  export declare const DASHBOARD_P2P_SIGNAL_TYPES: readonly ["p2p_ready", "p2p_answer", "p2p_ice"];
152
158
  export type DashboardP2PSignalType = typeof DASHBOARD_P2P_SIGNAL_TYPES[number];
153
159
  export declare const isDashboardP2PSignalType: (value: unknown) => value is "p2p_ready" | "p2p_answer" | "p2p_ice";
160
+ /** `backoffStep`: the dashboard's stale-connect backoff index (0 = 15s, 1 = 30s, 2 = 60s), so the daemon holds the connecting slot for the same window. */
161
+ /** `probe`: identifier-only flag — this signal belongs to a make-before-break relay->direct re-probe of the SAME logical peer (never a new peer for the connection cap). */
154
162
  export type DashboardP2PReadyWirePayload = {
155
163
  daemonId: string;
156
164
  peerId: string;
165
+ attemptId?: number;
166
+ attemptEpoch?: string;
167
+ backoffStep?: number;
168
+ probe?: boolean;
157
169
  };
158
170
  export type DashboardP2PAnswerWirePayload = {
159
171
  daemonId: string;
160
172
  peerId: string;
161
173
  sdp: string;
162
174
  type: string;
175
+ attemptId?: number;
176
+ attemptEpoch?: string;
177
+ probe?: boolean;
163
178
  };
164
179
  export type DashboardP2PLocalIceWirePayload = {
165
180
  daemonId: string;
166
181
  peerId: string;
182
+ attemptId?: number;
183
+ attemptEpoch?: string;
184
+ probe?: boolean;
167
185
  candidate: string;
168
186
  sdpMid: string | null;
169
187
  sdpMLineIndex: number | null;
@@ -0,0 +1,36 @@
1
+ /** Provider permissions are runtime decisions, never owner write ratification. */
2
+ export interface ProviderApproval {
3
+ source: 'provider_modal' | 'legacy_provider' | 'status_only' | 'ledger_approval';
4
+ /** Exact Inbox actions or the existing session handler; never inferred consent. */
5
+ compatibility?: 'exact' | 'session_only' | 'unknown';
6
+ type: 'tool_permission' | 'provider_setup' | 'unknown';
7
+ evidence: 'live' | 'cached' | 'unavailable';
8
+ lifecycle: 'pending' | 'resolved';
9
+ modalKey: string | null;
10
+ createdAt: number | null;
11
+ resolvedAt: number | null;
12
+ description: string;
13
+ runtimeDirectory?: string;
14
+ displayedDirectory?: string | null;
15
+ buttons: string[];
16
+ actionable: boolean;
17
+ providerType?: string;
18
+ sessionId?: string;
19
+ nodeId?: string | null;
20
+ meshId?: string | null;
21
+ taskId?: string | null;
22
+ attemptId?: string | null;
23
+ generation?: number | null;
24
+ }
25
+ /** Exact resource identity required by the approvals-only action path. */
26
+ export interface ProviderApprovalTarget {
27
+ modalKey: string;
28
+ attemptId: string | null;
29
+ generation: number | null;
30
+ }
31
+ /** Confirmed authority for the new Inbox path. Legacy handlers retain their own checks. */
32
+ export declare function isActionableProviderApproval(approval: ProviderApproval | null | undefined): boolean;
33
+ /** An explicit null suppresses the resource; only missing metadata uses compatibility. */
34
+ export declare function readProviderDecision(approval: ProviderApproval | null | undefined, waiting: boolean, hasModal?: boolean): ProviderApproval | null;
35
+ /** Host metadata is durable: identity and clocks only, never modal text or directories. */
36
+ export declare function providerApprovalSnapshot(approval: ProviderApproval | null | undefined): ProviderApproval | null;
@@ -0,0 +1,19 @@
1
+ /** Ephemeral provenance supplied by the owning daemon, never inferred from prompt text. */
2
+ export interface ProviderQuestionBinding {
3
+ transport: 'codex_rpc' | 'codex_async';
4
+ sessionId: string;
5
+ generation: string;
6
+ threadId: string;
7
+ turnId: string;
8
+ itemId: string;
9
+ requestId?: string | number;
10
+ }
11
+ export type ProviderQuestionLifecycle = 'pending' | 'submitted' | 'resolved' | 'completed' | 'cancelled' | 'expired' | 'unknown';
12
+ export interface ProviderQuestionEpisode {
13
+ blocking?: boolean;
14
+ binding?: ProviderQuestionBinding;
15
+ lifecycle?: ProviderQuestionLifecycle;
16
+ /** Async dismiss is product-local; it is not a native tool cancellation. */
17
+ support?: 'request_bound' | 'product_local' | 'uncertain';
18
+ }
19
+ export declare function isProviderQuestionBinding(value: unknown): value is ProviderQuestionBinding;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Quota snapshot freshness — the ONE decision every display surface shares.
3
+ *
4
+ * Why this lives in the leaf package: the dashboard chips (web-core
5
+ * `quotaWindowCue`), the `adhdev quota` CLI (daemon-core `quota/cli.ts`) and the
6
+ * coordinator-facing `mesh_status` fold (mcp-server `summarizeNodeQuota`) each
7
+ * carried a hand-copied version of this rule, and a fix to one left the other
8
+ * two telling a different story about the same snapshot. They all call
9
+ * `assessQuotaFreshness` now; only the WORDING stays per-surface.
10
+ *
11
+ * Pure: plain objects in, plain object out, no clock except the `now` argument.
12
+ */
13
+ import type { MeshNodeFactsProviderQuota } from './node-facts';
14
+ export type QuotaFreshnessCue = 'refreshing' | 'stale';
15
+ export interface QuotaFreshness {
16
+ /** Undefined = nothing to say (a fresh reading, or no retained numbers). */
17
+ cue?: QuotaFreshnessCue;
18
+ /**
19
+ * Only the USER can produce a new reading (aged-out Claude statusline;
20
+ * expired CLI access token — the daemon never redeems it). The surface
21
+ * should say what to run, not promise a self-heal.
22
+ */
23
+ userMustAct: boolean;
24
+ /**
25
+ * Unix ms by which the daemon will have looked again, for a `stale` reading
26
+ * the daemon re-checks on its own. Absent when the user must act, when the
27
+ * reading is `refreshing` (a retry is already scheduled), or when the check
28
+ * is already due.
29
+ */
30
+ nextCheckAt?: number;
31
+ }
32
+ /**
33
+ * How long past `metadata.retryAtMs` a retry still reads as "in flight". The
34
+ * retry timer fires at retryAtMs and the probe itself takes a few seconds; a
35
+ * retryAtMs well in the past with no newer attempt means no retry is coming.
36
+ */
37
+ export declare const QUOTA_RETRY_INFLIGHT_GRACE_MS = 60000;
38
+ /**
39
+ * The daemon's backfill horizon: a snapshot is re-probed once it is this old
40
+ * even on an idle machine. Mirrors daemon-core `QUOTA_ROUTABLE_MAX_AGE_MS`
41
+ * (quota/refresh.ts); daemon-core's quota-freshness test pins the two equal.
42
+ */
43
+ export declare const QUOTA_BACKFILL_HORIZON_MS: number;
44
+ /**
45
+ * Which freshness cue a snapshot's retained numbers should carry.
46
+ *
47
+ * - `stale` + `userMustAct` — nothing will refresh it without the user:
48
+ * · `no-data` on any provider EXCEPT codex (Claude's statusline aged out —
49
+ * a session must run). Codex's `no-data` is the daemon re-reading the
50
+ * rollout files itself (≈60s while a CLI is active, ≤60 min idle), so it
51
+ * is `stale` WITHOUT userMustAct;
52
+ * · `expired-token` on Grok/Kimi/antigravity — only the owning CLI
53
+ * renews it on use. An idle CLI has no background renewal cadence.
54
+ * A scheduled quota retry does not itself renew credentials.
55
+ * - `refreshing` — retained numbers after a transient failure with a retry
56
+ * actually pending. ★Honest about the end of the retry budget: once the
57
+ * daemon has stopped retrying (`metadata.retryExhausted`, or `retryAtMs`
58
+ * long past with no newer attempt) the numbers are `stale` with the next
59
+ * scheduled check, not "refreshing" for the up-to-60 minutes until the
60
+ * hourly backfill. A snapshot with NO retry bookkeeping at all (a daemon
61
+ * predating the stamp) keeps the old `refreshing` reading.
62
+ */
63
+ export declare function assessQuotaFreshness(quota: MeshNodeFactsProviderQuota, now?: number): QuotaFreshness;
64
+ /** Whole minutes (min 1) until `nextCheckAt`, for the "next check ~Nm" wording. */
65
+ export declare function minutesUntilQuotaCheck(nextCheckAt: number, now?: number): number;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Relay -> direct re-probe scheduler (pure, dependency-free).
3
+ *
4
+ * A P2P connection that settled on a TURN relay never looks for a direct path
5
+ * again. The owning transport (dashboard `P2PConnection`, daemon peer manager)
6
+ * uses this scheduler to periodically attempt a make-before-break probe: a
7
+ * SECOND peer connection (STUN-only) is built next to the working relay one and
8
+ * only swapped in once it is verified direct. The scheduler owns nothing but
9
+ * timing: backoff 60s -> 5min (doubling), jitter, and the stop conditions. The
10
+ * transport supplies `shouldProbe` (open + idle + visible) and `probe`.
11
+ */
12
+ export type RelayReprobeOutcome = 'direct' | 'relay' | 'failed' | 'skipped';
13
+ export interface RelayReprobeTiming {
14
+ /** First delay and the unit of growth (default 60s). */
15
+ baseDelayMs?: number;
16
+ /** Backoff ceiling before jitter (default 5min). */
17
+ maxDelayMs?: number;
18
+ /** +/- fraction applied to every delay (default 0.2). */
19
+ jitterRatio?: number;
20
+ }
21
+ export declare const RELAY_REPROBE_BASE_DELAY_MS = 60000;
22
+ export declare const RELAY_REPROBE_MAX_DELAY_MS: number;
23
+ export declare const RELAY_REPROBE_JITTER_RATIO = 0.2;
24
+ /** Delay before the next attempt after `failures` consecutive non-direct outcomes. */
25
+ export declare function computeRelayReprobeDelay(failures: number, random: () => number, timing?: RelayReprobeTiming): number;
26
+ export interface RelayReprobeSchedulerOptions extends RelayReprobeTiming {
27
+ random: () => number;
28
+ setTimer: (callback: () => void, ms: number) => unknown;
29
+ clearTimer: (handle: unknown) => void;
30
+ /** Connected over relay, idle, tab visible, auth epoch unchanged... Re-checked on every tick. */
31
+ shouldProbe: () => boolean;
32
+ /** One make-before-break attempt. Must never throw into the scheduler; a rejection counts as 'failed'. */
33
+ probe: () => Promise<RelayReprobeOutcome>;
34
+ }
35
+ export declare class RelayReprobeScheduler {
36
+ private readonly options;
37
+ private handle;
38
+ private running;
39
+ private inFlight;
40
+ private failureCount;
41
+ private generation;
42
+ constructor(options: RelayReprobeSchedulerOptions);
43
+ get isRunning(): boolean;
44
+ get failures(): number;
45
+ get probing(): boolean;
46
+ /** Begin probing (idempotent). Call when a connection is selected as relay. */
47
+ start(): void;
48
+ /** Stop for good (direct path found, connection closed, auth epoch changed). */
49
+ stop(): void;
50
+ private schedule;
51
+ private tick;
52
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * release — types, limits and pure helpers for release objects
3
+ * (docs/design/2026-11-01-release-objects.md).
4
+ *
5
+ * A release is a mesh-scoped entity that groups MISSIONS (explicit membership) and records
6
+ * deploy facts. It is distinct from the vendor release stamp (a build channel stamp) and
7
+ * from `.adhdev/preview-deploy.json` (the per-checkout deploy record it snapshots).
8
+ * Membership is only ever created by an explicit call; git evaluation and deploy heads
9
+ * produce suggestions, never members. Dependency-free leaf.
10
+ */
11
+ export declare const MESH_RELEASE_CHANNELS: readonly ["preview", "stable"];
12
+ export type MeshReleaseChannel = typeof MESH_RELEASE_CHANNELS[number];
13
+ export declare function isMeshReleaseChannel(value: unknown): value is MeshReleaseChannel;
14
+ export declare const MESH_RELEASE_STATUSES: readonly ["planned", "frozen", "deployed", "verified", "superseded", "cancelled"];
15
+ export type ReleaseStatus = typeof MESH_RELEASE_STATUSES[number];
16
+ export declare function isReleaseStatus(value: unknown): value is ReleaseStatus;
17
+ export declare const RELEASE_MEMBER_STATES: readonly ["member", "moved_out", "removed"];
18
+ export type ReleaseMemberState = typeof RELEASE_MEMBER_STATES[number];
19
+ /** Who may write release membership. The daemon never adds a member on its own. */
20
+ export declare const RELEASE_ACTORS: readonly ["coordinator", "owner", "assistant", "script"];
21
+ export type ReleaseActor = typeof RELEASE_ACTORS[number];
22
+ export declare const RELEASE_EVENT_TYPES: readonly ["created", "status_changed", "scope_added", "scope_moved", "scope_removed", "deploy_recorded", "changelog_drafted", "changelog_applied"];
23
+ export type ReleaseEventType = typeof RELEASE_EVENT_TYPES[number];
24
+ /** Mission-side events recorded when membership changes (so the mission history shows it). */
25
+ export declare const MISSION_RELEASE_EVENT_TYPES: readonly ["release_attached", "release_moved", "release_detached"];
26
+ export declare const RELEASE_LIMITS: {
27
+ readonly name: 80;
28
+ readonly note: 500;
29
+ /** `mesh_missions.release_note`: one user/operator-visible sentence. */
30
+ readonly releaseNote: 400;
31
+ readonly missionsPerCall: 100;
32
+ readonly changelogEntries: 300;
33
+ };
34
+ /** Deploy targets snapshot (a copy of `.adhdev/preview-deploy.json` `targets`). */
35
+ export interface ReleaseDeployTarget {
36
+ commit?: string;
37
+ deployedAt?: string;
38
+ previewVersion?: string;
39
+ [key: string]: unknown;
40
+ }
41
+ export type ReleaseTargets = Partial<Record<'npm' | 'server' | 'web', ReleaseDeployTarget>> & Record<string, ReleaseDeployTarget | undefined>;
42
+ export declare const CHANGELOG_SECTIONS: readonly ["Added", "Fixed", "Changed"];
43
+ export type ChangelogSection = typeof CHANGELOG_SECTIONS[number];
44
+ export interface ChangelogDraftEntry {
45
+ missionId: string;
46
+ /** `<!-- m:<id8> -->` — the idempotency marker `--apply` writes after the line. */
47
+ marker: string;
48
+ section: ChangelogSection;
49
+ /** The sentence that would be written to CHANGELOG.md. */
50
+ text: string;
51
+ source: 'release_note' | 'summary';
52
+ /** True when `text` is not user-facing wording yet (carries the needs-wording marker). */
53
+ needsWording?: boolean;
54
+ /** Evidence kept in the draft JSON only; never written to CHANGELOG.md. */
55
+ evidence?: {
56
+ gitState?: string;
57
+ outcome?: string;
58
+ shas?: string[];
59
+ };
60
+ /** Non-blocking lint warnings (internal ids / wave numbers / forbidden phrases). */
61
+ lint?: string[];
62
+ }
63
+ export interface ChangelogDraftExcluded {
64
+ missionId: string;
65
+ reason: string;
66
+ }
67
+ export interface ChangelogDraft {
68
+ generatedAt: string;
69
+ entries: ChangelogDraftEntry[];
70
+ /** Member missions without a `release_note` (target 0 at deploy time). */
71
+ needsNote: string[];
72
+ excluded: ChangelogDraftExcluded[];
73
+ /** Member missions whose refs are not confirmed on the default branch (`not_landed` / `unknown`). */
74
+ warnings: Array<{
75
+ missionId: string;
76
+ gitState: string;
77
+ message: string;
78
+ }>;
79
+ appliedAt?: string;
80
+ }
81
+ export declare const NEEDS_WORDING_MARKER = "[needs user-facing wording]";
82
+ /** Marker that makes `--apply` idempotent: one per mission entry inside CHANGELOG.md. */
83
+ export declare function changelogMissionMarker(missionId: string): string;
84
+ /** Default section for an entry; `[Fixed]`/`[Added]`/`[Changed]` prefixes on the note pin it. */
85
+ export declare function classifyChangelogSection(text: string, hint?: string): {
86
+ section: ChangelogSection;
87
+ text: string;
88
+ };
89
+ /** Phrases that must not reach a public CHANGELOG (internal waves, rc numbering, ids). Warning-only. */
90
+ export declare function lintChangelogText(text: string): string[];
@@ -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;
@@ -1,25 +1,5 @@
1
- /**
2
- * session-launch — how a session was launched: provider, model and thinking
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
  /**
@@ -61,10 +41,18 @@ export interface ModelSelection {
61
41
  /** Last value the provider itself reported (statusline, native history). */
62
42
  observed?: string;
63
43
  observedAt?: number;
44
+ /**
45
+ * The provider SILENTLY substituted another model for the requested one
46
+ * (antigravity: `model X is not recognized … Using "Y" instead`). `observed`
47
+ * then holds the model actually running. P2P/launch-record only — the
48
+ * server path never carries it (see the content boundary above).
49
+ */
50
+ fallback?: boolean;
64
51
  /** Every value change in order. The last entry is the value in force. */
65
52
  history: ModelSelectionHistoryEntry[];
66
53
  }
67
54
  export interface SessionLaunchRecord {
55
+ roleLaunch?: RoleLaunchSelection;
68
56
  sessionId: string;
69
57
  providerType: string;
70
58
  providerVersion?: string;
@@ -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 {
@@ -0,0 +1,75 @@
1
+ import type { OwnedPathEntry } from './path-ownership';
2
+ export type MeshTaskPhase = 'source' | 'integration';
3
+ /** Host-produced evidence. Never accepted from MCP/peer-supplied paths or branch labels. */
4
+ export interface PhysicalOwnershipScope {
5
+ version: 1;
6
+ repoIdentity: string;
7
+ machineId: string;
8
+ nodeId: string;
9
+ worktree: string;
10
+ worktreeIdentity: string;
11
+ gitDir: string;
12
+ commonGitDir: string;
13
+ phase: MeshTaskPhase;
14
+ gitResources: {
15
+ index: string;
16
+ refs: string;
17
+ hooks: string;
18
+ config: string;
19
+ };
20
+ resources: Array<{
21
+ path: string;
22
+ identity?: string;
23
+ subtree: boolean;
24
+ }>;
25
+ declaration: readonly OwnedPathEntry[];
26
+ }
27
+ /** A source packet is an input to integration, never release/feature acceptance. */
28
+ export interface MeshSourceCandidate {
29
+ version: 1;
30
+ readiness: 'uncommitted' | 'verified_source_candidate';
31
+ taskId: string;
32
+ missionId: string;
33
+ revision: number;
34
+ repoIdentity: string;
35
+ machineId: string;
36
+ canonicalWorktree: string;
37
+ rootBaseSha: string;
38
+ ossBaseSha: string;
39
+ rootCandidateSha?: string;
40
+ ossCandidateSha?: string;
41
+ patchSha256: string;
42
+ manifestSha256: string;
43
+ touchedPaths: string[];
44
+ tests: Array<{
45
+ command: string;
46
+ exit: number;
47
+ logSha256: string;
48
+ }>;
49
+ settledCommands: boolean;
50
+ integrationTaskId: string;
51
+ logicalOverlapTaskIds: string[];
52
+ }
53
+ /** Product B consumes one frozen batch; normal hooks/parity/gates remain required. */
54
+ export interface MeshIntegrationBatchContract {
55
+ version: 1;
56
+ batchId: string;
57
+ candidates: MeshSourceCandidate[];
58
+ integrationOrder: string[];
59
+ approvedRootSha: string;
60
+ approvedOssSha: string;
61
+ bundleInvocationCount: 1;
62
+ requiredGates: string[];
63
+ normalHooksRequired: true;
64
+ independentReviewRequired: true;
65
+ ownerLandingApprovalRequired: true;
66
+ }
67
+ /** These only rank legal targets; targetNodeId/SessionId and requiredTags remain hard. */
68
+ export interface MeshRoutingPreferences {
69
+ nodeIds?: string[];
70
+ providers?: string[];
71
+ }
72
+ export interface MeshReviewConstraint {
73
+ sourceTaskId: string;
74
+ differentProvider: true;
75
+ }