@adhdev/mesh-shared 1.0.56-rc.8 → 1.0.56

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.
@@ -18,7 +18,7 @@
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_list_nodes", "mesh_enqueue_batch", "mesh_enqueue_task", "mesh_view_queue", "mesh_graph_view", "mesh_graph_gate_claim", "mesh_graph_gate_release", "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_plan_onboarding", "mesh_create", "mesh_add_node", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_refine_config", "mesh_change_impact_config", "mesh_init", "mesh_reinit", "mesh_write_mesh_json_config", "mesh_refine_plan", "mesh_cleanup_sessions", "mesh_prune_stale_direct", "mesh_task_history", "mesh_ledger_query", "mesh_record_note", "mesh_forget_note", "mesh_reconcile_ledger", "mesh_requeue_held_events", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_magi_review", "mesh_magi_collect", "mesh_magi_kind_panel_set", "mesh_magi_kind_panel_list", "mesh_node_slots_set", "mesh_node_slots_list", "mesh_node_slots_propose", "mesh_coordinator_prompt_append_get", "mesh_coordinator_prompt_append_set"];
21
+ export declare const CANONICAL_MESH_TOOL_NAMES: readonly ["mesh_status", "mesh_list_nodes", "mesh_enqueue_batch", "mesh_enqueue_task", "mesh_view_queue", "mesh_graph_view", "mesh_graph_gate_claim", "mesh_graph_gate_release", "mesh_graph_gate_abandon", "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_plan_onboarding", "mesh_create", "mesh_add_node", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_refine_config", "mesh_change_impact_config", "mesh_init", "mesh_reinit", "mesh_write_mesh_json_config", "mesh_refine_plan", "mesh_cleanup_sessions", "mesh_prune_stale_direct", "mesh_task_history", "mesh_ledger_query", "mesh_record_note", "mesh_forget_note", "mesh_reconcile_ledger", "mesh_requeue_held_events", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_magi_review", "mesh_magi_collect", "mesh_magi_kind_panel_set", "mesh_magi_kind_panel_list", "mesh_node_slots_set", "mesh_node_slots_list", "mesh_node_slots_propose", "mesh_coordinator_prompt_append_get", "mesh_coordinator_prompt_append_set"];
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: 58;
24
+ export declare const CANONICAL_MESH_TOOL_COUNT: 59;
@@ -75,10 +75,42 @@ export interface MeshNodeFactsProviderQuota {
75
75
  * rather than presenting them as a freshly measured value.
76
76
  */
77
77
  lastGoodWindows?: boolean;
78
+ /**
79
+ * Unix ms when the reporting node last ATTEMPTED a refresh of this
80
+ * provider — deliberately distinct from `updatedAt`, which dates the
81
+ * DATA. They differ for file-source providers (claude-cli reports its
82
+ * statusline snapshot's capture time, codex-cli the rollout entry's),
83
+ * whose `updatedAt` does not move while the source file is unchanged
84
+ * however often it is successfully re-read.
85
+ *
86
+ * A reader judging FRESHNESS wants `updatedAt`. This field answers the
87
+ * different question "is that node still looking?", which is what makes
88
+ * "3h old but re-checked a minute ago" distinguishable from "3h old and
89
+ * nobody has looked since". Absent on entries written by daemons
90
+ * predating the field.
91
+ */
92
+ fetchedAt?: number;
78
93
  [extra: string]: unknown;
79
94
  };
80
95
  [extra: string]: unknown;
81
96
  }
97
+ /**
98
+ * One provider's enablement state on the reporting machine — see
99
+ * `MeshNodeFacts.providerEnablement`.
100
+ *
101
+ * Both fields are REQUIRED booleans on the wire even though the underlying
102
+ * config defaults are asymmetric (`enabled` defaults false, `quotaEnabled`
103
+ * defaults true). The producer resolves those defaults before stamping, so a
104
+ * reader never re-derives them — a second copy of the default rules is exactly
105
+ * the drift this shape avoids. Absence is expressed by omitting the whole
106
+ * provider entry (or the whole bundle field), never by a missing sub-field.
107
+ */
108
+ export interface MeshNodeFactsProviderEnablement {
109
+ /** "This machine uses provider X" — gates launching and mesh claims. */
110
+ enabled: boolean;
111
+ /** "...and its quota is probed here" — an independent user opt-out. */
112
+ quotaEnabled: boolean;
113
+ }
82
114
  export interface MeshNodeFacts {
83
115
  schemaVersion: number;
84
116
  reportedAt: number;
@@ -118,6 +150,31 @@ export interface MeshNodeFacts {
118
150
  * rule: quotaSnapshotAgeMs in daemon-core mesh-quota-routing.ts).
119
151
  */
120
152
  quota?: Record<string, MeshNodeFactsProviderQuota>;
153
+ /**
154
+ * Per-provider ENABLEMENT state on the reporting machine, keyed the same
155
+ * way as `quota`. Exists because `quota` alone cannot answer "why is there
156
+ * no snapshot": a provider that is disabled — on either axis — is pruned
157
+ * from the quota cache entirely (daemon-core quota/refresh.ts drops it and
158
+ * refuses to restore it from disk), so a deliberate opt-out and a
159
+ * never-yet-measured provider both arrive as the SAME absent entry. On the
160
+ * node that owns the config that ambiguity is resolvable by reading the
161
+ * config; for every OTHER node in the mesh it was not resolvable at all,
162
+ * which is what this field fixes.
163
+ *
164
+ * The two axes mirror ProviderLoader exactly and are INDEPENDENT:
165
+ * `enabled` is "this machine uses provider X" (gates launching and mesh
166
+ * claims), `quotaEnabled` gates ONLY the quota probe — a machine can use a
167
+ * provider and still opt out of having its usage read.
168
+ *
169
+ * ★An ABSENT bundle field means "this node did not tell us" — a daemon too
170
+ * old to send it — and must NEVER be read as "disabled". The consumer
171
+ * (daemon-core mesh-quota-routing.ts classifyAbsentQuotaReason) keeps its
172
+ * unclassified fallback for exactly that case; treating absence as
173
+ * disabled would invent a fail-closed verdict out of a missing field.
174
+ *
175
+ * Booleans keyed by provider type only — no free text, no credentials.
176
+ */
177
+ providerEnablement?: Record<string, MeshNodeFactsProviderEnablement>;
121
178
  /** Future fields ride through opaquely — do not enumerate them in relays. */
122
179
  [extra: string]: unknown;
123
180
  }
@@ -159,11 +216,13 @@ export declare function normalizeMeshNodeFacts(raw: unknown): MeshNodeFacts | un
159
216
  * ★antigravity-cli was likewise twice judged impossible, from reading
160
217
  * `agy --help` where the usage view does not appear because it is a TUI view.
161
218
  * Its quota comes from the SHARED Gemini Code Assist backend
162
- * (`cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary`), and its
163
- * credential lives in the OS keyring, not the stale on-disk token file — see
164
- * the provenance note in daemon-core `quota/fetchers/antigravity.ts`. It is
165
- * macOS-only by design; other platforms report `unsupported` rather than guess
166
- * at a keyring backend nobody has verified.
219
+ * (`daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary`
220
+ * the host `agy` itself uses; the unprefixed `cloudcode-pa` host 429s Google
221
+ * AI Pro Antigravity accounts), and its credential lives in the OS keyring,
222
+ * not the stale on-disk token file see the provenance note in daemon-core
223
+ * `quota/fetchers/antigravity.ts`. Supported on macOS and Windows; other
224
+ * platforms report `unsupported` rather than guess at a keyring backend
225
+ * nobody has verified.
167
226
  *
168
227
  * ★Adding a provider here without adding its fetcher to REFRESHERS re-creates
169
228
  * exactly the "switch that does nothing" this constant prevents.
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Mesh RPC frame chunking — split/reassemble oversized mesh envelopes.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * The mesh DataChannel path (`daemon-mesh-manager`) writes every RPC envelope as a
6
+ * SINGLE `dc.sendMessage(JSON.stringify(envelope))` frame. That was fine while every
7
+ * mesh arg was small text, but a coordinator dispatching an IMAGE to a worker puts a
8
+ * multi-MB base64 part inside `args` — far past what one DataChannel frame carries, so
9
+ * the send throws or the frame is dropped and the dispatch silently dies.
10
+ *
11
+ * The dashboard P2P path already solved exactly this problem and has been running in
12
+ * production: `packages/daemon-cloud/src/daemon-p2p/data-channel-router.ts` (send +
13
+ * reassemble) and `packages/web-cloud/src/p2p.ts` (browser side). This module is a PORT
14
+ * of that proven scheme onto the mesh envelope shape — deliberately NOT a new protocol.
15
+ * The boundary values are carried over unchanged (see the constants below for why each
16
+ * one is what it is).
17
+ *
18
+ * It lives in mesh-shared because it is pure string/JSON work with no transport, no
19
+ * Node API and no DOM API — the same reason the normalizers live here. `daemon-cloud`
20
+ * (sender/receiver) is the only current consumer, but keeping it in the pure leaf means
21
+ * the standalone side can reassemble with the identical code rather than a hand-synced
22
+ * copy, which is the drift bug class this package was created to kill.
23
+ *
24
+ * FAILURE POLICY (DoD): reassembly never degrades silently. A malformed, out-of-range,
25
+ * over-budget or unparseable chunk stream yields an explicit typed failure that the
26
+ * caller turns into an RPC error — never a partially-applied or truncated envelope.
27
+ */
28
+ /**
29
+ * Inline ceiling: an envelope whose JSON is at or below this goes out as one frame,
30
+ * exactly as before this module existed. 60_000 (not 65_536) matches the dashboard
31
+ * path and leaves headroom under the common 64KiB SCTP message limit so the frame is
32
+ * never the thing that trips a transport-level cap.
33
+ */
34
+ export declare const MESH_MAX_INLINE_FRAME_BYTES = 60000;
35
+ /**
36
+ * Per-chunk payload slice, in CHARACTERS. Ported unchanged from the dashboard path.
37
+ *
38
+ * Why 16_000 chars is safe under a 60_000-BYTE ceiling, with ~3.75x of headroom:
39
+ * `splitMeshFrame` slices the ALREADY-SERIALIZED outer frame, so the text being cut
40
+ * is JSON output — control characters are pre-expanded to `\u00XX` ASCII before they
41
+ * ever reach the slicer. Re-escaping that slice inside the chunk envelope can at worst
42
+ * double the backslashes. Measured worst cases for a 16_000-char slice:
43
+ * backslashes / astral emoji → 32,073 bytes
44
+ * Korean (3-byte, unescaped) → 48,073 bytes ← densest observed
45
+ * plain ASCII → 16,073 bytes
46
+ * All are under MESH_MAX_INLINE_FRAME_BYTES. `splitMeshFrame` still MEASURES each
47
+ * envelope and shrinks on overflow, so the guarantee is enforced rather than assumed
48
+ * if either constant is ever retuned — but at these values that path is unreachable,
49
+ * which is exactly what `assertMeshChunkConstantsAreSafe` pins down.
50
+ */
51
+ export declare const MESH_CHUNK_PAYLOAD_CHARS = 16000;
52
+ /**
53
+ * Hard cap on chunk count for one frame. 1024 × ~16KB ≈ 16MB of transferable payload,
54
+ * which comfortably covers a screenshot while bounding what a single peer can make the
55
+ * receiver buffer. Exceeding it is an explicit refusal, never a truncated send.
56
+ */
57
+ export declare const MESH_MAX_CHUNKS = 1024;
58
+ /**
59
+ * Reassembly budget for one frame, in bytes. Bounds receiver memory independently of
60
+ * chunk count so a peer cannot send 1024 maximally-large chunks to force an oversized
61
+ * allocation. Mirrors the dashboard receiver's MAX_REASSEMBLED_JSON_BYTES.
62
+ */
63
+ export declare const MESH_MAX_REASSEMBLED_BYTES = 16000000;
64
+ /**
65
+ * How long a partially-received frame is retained. A sender that dies mid-stream must
66
+ * not pin receiver memory forever; the partial is swept and the frame simply never
67
+ * completes (the RPC's own deadline then reports it).
68
+ */
69
+ export declare const MESH_CHUNK_TTL_MS = 60000;
70
+ /** Envelope `kind` for a chunk of a larger mesh frame. */
71
+ export declare const MESH_CHUNK_KIND = "rpc_chunk";
72
+ export interface MeshChunkEnvelope {
73
+ v: number;
74
+ kind: typeof MESH_CHUNK_KIND;
75
+ /** Groups the chunks of one logical frame. */
76
+ chunkId: string;
77
+ /** 0-based position of this chunk. */
78
+ index: number;
79
+ /** Total chunk count for this frame; constant across the group. */
80
+ total: number;
81
+ /** The slice of the original envelope JSON. */
82
+ data: string;
83
+ }
84
+ export type MeshChunkSplitResult = {
85
+ ok: true;
86
+ chunks: MeshChunkEnvelope[];
87
+ } | {
88
+ ok: false;
89
+ reason: 'too_many_chunks' | 'chunk_too_large';
90
+ detail: string;
91
+ };
92
+ export type MeshChunkAcceptResult =
93
+ /** Chunk stored; the frame is not complete yet. */
94
+ {
95
+ status: 'partial';
96
+ received: number;
97
+ total: number;
98
+ }
99
+ /** Final chunk landed and the frame parsed cleanly. */
100
+ | {
101
+ status: 'complete';
102
+ frame: unknown;
103
+ }
104
+ /** Explicitly rejected — the caller must surface this, never ignore it. */
105
+ | {
106
+ status: 'failed';
107
+ reason: MeshChunkFailureReason;
108
+ detail: string;
109
+ chunkId: string;
110
+ };
111
+ export type MeshChunkFailureReason = 'malformed_chunk' | 'too_many_chunks' | 'inconsistent_total' | 'duplicate_chunk_mismatch' | 'budget_exceeded' | 'reassembled_parse_failed';
112
+ /** UTF-8 byte length without assuming Buffer or TextEncoder is present. */
113
+ export declare function meshUtf8ByteLength(value: string): number;
114
+ /**
115
+ * Worst-case size of one chunk envelope at the current constants.
116
+ *
117
+ * Exported so the safety margin documented on MESH_CHUNK_PAYLOAD_CHARS is a CHECKED
118
+ * invariant rather than a comment that rots. The densest slice the splitter can produce
119
+ * is 3-byte unescaped text (JSON.stringify leaves non-ASCII as-is), so that is what this
120
+ * measures. If someone raises MESH_CHUNK_PAYLOAD_CHARS or lowers the frame ceiling past
121
+ * the safe point, the accompanying test fails loudly instead of the overflow only
122
+ * showing up as dropped frames on a live mesh.
123
+ */
124
+ export declare function measureWorstCaseChunkEnvelopeBytes(): number;
125
+ /** True when the serialized frame must be chunked rather than sent inline. */
126
+ export declare function meshFrameNeedsChunking(json: string): boolean;
127
+ /**
128
+ * Split a serialized mesh envelope into chunk envelopes.
129
+ *
130
+ * Each slice is measured AS ITS FINAL SERIALIZED ENVELOPE and shrunk (×0.8, as in the
131
+ * dashboard implementation) until it fits the inline ceiling — so multi-byte UTF-8 and
132
+ * the envelope overhead are both accounted for rather than assumed away. `total` is
133
+ * stamped only after the full split is known, so every chunk in a group agrees.
134
+ */
135
+ export declare function splitMeshFrame(json: string, chunkId: string, version: number): MeshChunkSplitResult;
136
+ /**
137
+ * Receiver-side reassembly buffer, one per peer connection.
138
+ *
139
+ * Keyed by chunkId only — callers construct one assembler per peer, so chunk groups
140
+ * from different peers can never collide in the same map.
141
+ */
142
+ export declare class MeshChunkAssembler {
143
+ private readonly now;
144
+ private readonly buffers;
145
+ constructor(now?: () => number);
146
+ /** True when the frame is a chunk envelope this assembler should handle. */
147
+ static isChunkFrame(frame: unknown): boolean;
148
+ /** Drop partials older than the TTL so a dead sender cannot pin memory. */
149
+ private sweep;
150
+ /** Discard any partial state for a peer (call on disconnect). */
151
+ reset(): void;
152
+ /** Number of frames currently mid-reassembly — for tests and diagnostics. */
153
+ get pendingCount(): number;
154
+ /**
155
+ * Accept one chunk envelope.
156
+ *
157
+ * Never throws and never returns a partially-applied frame: the result is exactly one
158
+ * of partial / complete / failed, and `failed` carries a typed reason the transport
159
+ * turns into an explicit RPC error.
160
+ */
161
+ accept(frame: unknown): MeshChunkAcceptResult;
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/mesh-shared",
3
- "version": "1.0.56-rc.8",
3
+ "version": "1.0.56",
4
4
  "description": "ADHDev mesh-shared — pure mesh/git status normalizers shared by daemon-core and web-core",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -25,3 +25,4 @@ export * from './slot-proposal'
25
25
  export * from './interpolation'
26
26
  export * from './mesh-tool-names'
27
27
  export * from './mesh-status-probe'
28
+ export * from './rpc-chunking'
@@ -29,6 +29,7 @@ export const CANONICAL_MESH_TOOL_NAMES = [
29
29
  'mesh_graph_view',
30
30
  'mesh_graph_gate_claim',
31
31
  'mesh_graph_gate_release',
32
+ 'mesh_graph_gate_abandon',
32
33
  'mesh_queue_cancel',
33
34
  'mesh_queue_requeue',
34
35
  'mesh_send_task',
package/src/node-facts.ts CHANGED
@@ -78,11 +78,44 @@ export interface MeshNodeFactsProviderQuota {
78
78
  * rather than presenting them as a freshly measured value.
79
79
  */
80
80
  lastGoodWindows?: boolean
81
+ /**
82
+ * Unix ms when the reporting node last ATTEMPTED a refresh of this
83
+ * provider — deliberately distinct from `updatedAt`, which dates the
84
+ * DATA. They differ for file-source providers (claude-cli reports its
85
+ * statusline snapshot's capture time, codex-cli the rollout entry's),
86
+ * whose `updatedAt` does not move while the source file is unchanged
87
+ * however often it is successfully re-read.
88
+ *
89
+ * A reader judging FRESHNESS wants `updatedAt`. This field answers the
90
+ * different question "is that node still looking?", which is what makes
91
+ * "3h old but re-checked a minute ago" distinguishable from "3h old and
92
+ * nobody has looked since". Absent on entries written by daemons
93
+ * predating the field.
94
+ */
95
+ fetchedAt?: number
81
96
  [extra: string]: unknown
82
97
  }
83
98
  [extra: string]: unknown
84
99
  }
85
100
 
101
+ /**
102
+ * One provider's enablement state on the reporting machine — see
103
+ * `MeshNodeFacts.providerEnablement`.
104
+ *
105
+ * Both fields are REQUIRED booleans on the wire even though the underlying
106
+ * config defaults are asymmetric (`enabled` defaults false, `quotaEnabled`
107
+ * defaults true). The producer resolves those defaults before stamping, so a
108
+ * reader never re-derives them — a second copy of the default rules is exactly
109
+ * the drift this shape avoids. Absence is expressed by omitting the whole
110
+ * provider entry (or the whole bundle field), never by a missing sub-field.
111
+ */
112
+ export interface MeshNodeFactsProviderEnablement {
113
+ /** "This machine uses provider X" — gates launching and mesh claims. */
114
+ enabled: boolean
115
+ /** "...and its quota is probed here" — an independent user opt-out. */
116
+ quotaEnabled: boolean
117
+ }
118
+
86
119
  export interface MeshNodeFacts {
87
120
  schemaVersion: number
88
121
  reportedAt: number
@@ -122,6 +155,31 @@ export interface MeshNodeFacts {
122
155
  * rule: quotaSnapshotAgeMs in daemon-core mesh-quota-routing.ts).
123
156
  */
124
157
  quota?: Record<string, MeshNodeFactsProviderQuota>
158
+ /**
159
+ * Per-provider ENABLEMENT state on the reporting machine, keyed the same
160
+ * way as `quota`. Exists because `quota` alone cannot answer "why is there
161
+ * no snapshot": a provider that is disabled — on either axis — is pruned
162
+ * from the quota cache entirely (daemon-core quota/refresh.ts drops it and
163
+ * refuses to restore it from disk), so a deliberate opt-out and a
164
+ * never-yet-measured provider both arrive as the SAME absent entry. On the
165
+ * node that owns the config that ambiguity is resolvable by reading the
166
+ * config; for every OTHER node in the mesh it was not resolvable at all,
167
+ * which is what this field fixes.
168
+ *
169
+ * The two axes mirror ProviderLoader exactly and are INDEPENDENT:
170
+ * `enabled` is "this machine uses provider X" (gates launching and mesh
171
+ * claims), `quotaEnabled` gates ONLY the quota probe — a machine can use a
172
+ * provider and still opt out of having its usage read.
173
+ *
174
+ * ★An ABSENT bundle field means "this node did not tell us" — a daemon too
175
+ * old to send it — and must NEVER be read as "disabled". The consumer
176
+ * (daemon-core mesh-quota-routing.ts classifyAbsentQuotaReason) keeps its
177
+ * unclassified fallback for exactly that case; treating absence as
178
+ * disabled would invent a fail-closed verdict out of a missing field.
179
+ *
180
+ * Booleans keyed by provider type only — no free text, no credentials.
181
+ */
182
+ providerEnablement?: Record<string, MeshNodeFactsProviderEnablement>
125
183
  /** Future fields ride through opaquely — do not enumerate them in relays. */
126
184
  [extra: string]: unknown
127
185
  }
@@ -173,11 +231,13 @@ export function normalizeMeshNodeFacts(raw: unknown): MeshNodeFacts | undefined
173
231
  * ★antigravity-cli was likewise twice judged impossible, from reading
174
232
  * `agy --help` where the usage view does not appear because it is a TUI view.
175
233
  * Its quota comes from the SHARED Gemini Code Assist backend
176
- * (`cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary`), and its
177
- * credential lives in the OS keyring, not the stale on-disk token file — see
178
- * the provenance note in daemon-core `quota/fetchers/antigravity.ts`. It is
179
- * macOS-only by design; other platforms report `unsupported` rather than guess
180
- * at a keyring backend nobody has verified.
234
+ * (`daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary`
235
+ * the host `agy` itself uses; the unprefixed `cloudcode-pa` host 429s Google
236
+ * AI Pro Antigravity accounts), and its credential lives in the OS keyring,
237
+ * not the stale on-disk token file see the provenance note in daemon-core
238
+ * `quota/fetchers/antigravity.ts`. Supported on macOS and Windows; other
239
+ * platforms report `unsupported` rather than guess at a keyring backend
240
+ * nobody has verified.
181
241
  *
182
242
  * ★Adding a provider here without adding its fetcher to REFRESHERS re-creates
183
243
  * exactly the "switch that does nothing" this constant prevents.