@apnex/network-adapter 0.1.2 → 0.1.6
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/build-info.json +6 -0
- package/dist/file-logger.d.ts +42 -0
- package/dist/file-logger.js +122 -0
- package/dist/file-logger.js.map +1 -0
- package/dist/index.d.ts +37 -13
- package/dist/index.js +42 -15
- package/dist/index.js.map +1 -1
- package/dist/kernel/adapter-config.d.ts +47 -0
- package/dist/kernel/adapter-config.js +90 -0
- package/dist/kernel/adapter-config.js.map +1 -0
- package/dist/kernel/agent-client.d.ts +25 -20
- package/dist/kernel/build-identity.d.ts +24 -0
- package/dist/kernel/build-identity.js +45 -0
- package/dist/kernel/build-identity.js.map +1 -0
- package/dist/kernel/event-router.d.ts +22 -1
- package/dist/kernel/event-router.js +45 -7
- package/dist/kernel/event-router.js.map +1 -1
- package/dist/kernel/handshake.d.ts +40 -4
- package/dist/kernel/handshake.js +46 -7
- package/dist/kernel/handshake.js.map +1 -1
- package/dist/kernel/harness-manifest.d.ts +51 -0
- package/dist/kernel/harness-manifest.js +96 -0
- package/dist/kernel/harness-manifest.js.map +1 -0
- package/dist/kernel/liveness-signal.d.ts +35 -0
- package/dist/kernel/liveness-signal.js +61 -0
- package/dist/kernel/liveness-signal.js.map +1 -0
- package/dist/kernel/liveness-watchdog.d.ts +78 -0
- package/dist/kernel/liveness-watchdog.js +115 -0
- package/dist/kernel/liveness-watchdog.js.map +1 -0
- package/dist/kernel/mcp-agent-client.d.ts +2 -2
- package/dist/kernel/mcp-agent-client.js +49 -3
- package/dist/kernel/mcp-agent-client.js.map +1 -1
- package/dist/kernel/poll-backstop.d.ts +134 -4
- package/dist/kernel/poll-backstop.js +254 -24
- package/dist/kernel/poll-backstop.js.map +1 -1
- package/dist/kernel/state-sync.d.ts +2 -13
- package/dist/kernel/state-sync.js.map +1 -1
- package/dist/notification-log.d.ts +8 -0
- package/dist/notification-log.js +10 -0
- package/dist/notification-log.js.map +1 -1
- package/dist/observability.d.ts +24 -0
- package/dist/observability.js +52 -0
- package/dist/observability.js.map +1 -0
- package/dist/prompt-format.js +82 -9
- package/dist/prompt-format.js.map +1 -1
- package/dist/tool-manager/catalog/health-revision.d.ts +43 -0
- package/dist/tool-manager/catalog/health-revision.js +61 -0
- package/dist/tool-manager/catalog/health-revision.js.map +1 -0
- package/dist/tool-manager/{tool-catalog-cache.d.ts → catalog/tool-catalog-cache.d.ts} +39 -19
- package/dist/tool-manager/{tool-catalog-cache.js → catalog/tool-catalog-cache.js} +53 -26
- package/dist/tool-manager/catalog/tool-catalog-cache.js.map +1 -0
- package/dist/tool-manager/catalog/tool-surface-reconciler.d.ts +93 -0
- package/dist/tool-manager/catalog/tool-surface-reconciler.js +101 -0
- package/dist/tool-manager/catalog/tool-surface-reconciler.js.map +1 -0
- package/dist/tool-manager/contracts.d.ts +91 -0
- package/dist/tool-manager/contracts.js +20 -0
- package/dist/tool-manager/contracts.js.map +1 -0
- package/dist/tool-manager/dispatch/dispatch.d.ts +77 -0
- package/dist/tool-manager/dispatch/dispatch.js +170 -0
- package/dist/tool-manager/dispatch/dispatch.js.map +1 -0
- package/dist/tool-manager/dispatch/tool-call-policy.d.ts +32 -0
- package/dist/tool-manager/dispatch/tool-call-policy.js +56 -0
- package/dist/tool-manager/dispatch/tool-call-policy.js.map +1 -0
- package/dist/tool-manager/{dispatcher.d.ts → orchestrator/dispatcher.d.ts} +110 -31
- package/dist/tool-manager/orchestrator/dispatcher.js +559 -0
- package/dist/tool-manager/orchestrator/dispatcher.js.map +1 -0
- package/dist/tool-manager/work-protocol/claimable-digest-tracker.d.ts +60 -0
- package/dist/tool-manager/work-protocol/claimable-digest-tracker.js +66 -0
- package/dist/tool-manager/work-protocol/claimable-digest-tracker.js.map +1 -0
- package/dist/tool-manager/work-protocol/work-lease-tracker.d.ts +70 -0
- package/dist/tool-manager/work-protocol/work-lease-tracker.js +146 -0
- package/dist/tool-manager/work-protocol/work-lease-tracker.js.map +1 -0
- package/dist/wire/mcp-transport.d.ts +1 -1
- package/dist/wire/mcp-transport.js +31 -7
- package/dist/wire/mcp-transport.js.map +1 -1
- package/dist/wire/slot-gate.d.ts +40 -0
- package/dist/wire/slot-gate.js +61 -0
- package/dist/wire/slot-gate.js.map +1 -0
- package/dist/wire/transport.d.ts +18 -0
- package/package.json +6 -3
- package/dist/kernel/instance.d.ts +0 -40
- package/dist/kernel/instance.js +0 -79
- package/dist/kernel/instance.js.map +0 -1
- package/dist/tool-manager/dispatcher.js +0 -379
- package/dist/tool-manager/dispatcher.js.map +0 -1
- package/dist/tool-manager/tool-catalog-cache.js.map +0 -1
package/dist/prompt-format.js
CHANGED
|
@@ -41,6 +41,10 @@ export function getActionText(event, data) {
|
|
|
41
41
|
return `Read with get_review(taskId="${data.taskId}")`;
|
|
42
42
|
case "proposal_decided":
|
|
43
43
|
return "Review decision and act if needed";
|
|
44
|
+
case "work_claimable_digest":
|
|
45
|
+
return "Claimable work available — list_ready_work then claim_work";
|
|
46
|
+
case "work_lease_stall":
|
|
47
|
+
return `Held work ${data.workId ?? ""} nearing lease expiry — renew_lease / block_work / abandon_work`;
|
|
44
48
|
default:
|
|
45
49
|
return "Review and act as needed";
|
|
46
50
|
}
|
|
@@ -49,16 +53,39 @@ export function getActionText(event, data) {
|
|
|
49
53
|
function renderThreadMessage(data, cfg) {
|
|
50
54
|
// calibration #20 retire (mission-63 W3): surface the body inline so the
|
|
51
55
|
// LLM has the actual message content, not just the envelope-shell. The
|
|
52
|
-
// Hub-side dispatch payload pre-truncates to
|
|
53
|
-
//
|
|
56
|
+
// Hub-side dispatch payload pre-truncates to THREAD_MESSAGE_PREVIEW_CHARS
|
|
57
|
+
// chars (thread-policy.ts; mission-66 commit 6 ratified at 200 chars);
|
|
58
|
+
// adapter renders that preview + truncation marker (mission-66 #26).
|
|
59
|
+
// Full body still requires get_thread.
|
|
60
|
+
//
|
|
61
|
+
// mission-66 commit 6 (#26 marker-protocol; closes calibration #26):
|
|
62
|
+
// Hub-side envelope-builder surfaces `truncated: true` + `fullBytes: <n>`
|
|
63
|
+
// on dispatchPayload when body exceeds preview threshold. Adapter
|
|
64
|
+
// render-template appends marker per Design §2.1.2 architect-lean (b):
|
|
65
|
+
// `[…<N> bytes truncated; query thread for full content]`.
|
|
66
|
+
//
|
|
67
|
+
// M-SSE-Peek-Line-Cleanup Phase 1 (Design v1.1 §0.5.3): when Hub has
|
|
68
|
+
// rendered the canonical peek-line via `payload.body` (auto-derived
|
|
69
|
+
// from event-name + data at emit-time per `deriveRenderContext`),
|
|
70
|
+
// prefer it as the summary line. Falls back to the legacy author-label
|
|
71
|
+
// composition when payload.body is absent (cutover window).
|
|
54
72
|
const p = cfg.toolPrefix;
|
|
55
|
-
const
|
|
56
|
-
: data.author === "engineer" ? "Engineer peer"
|
|
57
|
-
: "Peer";
|
|
73
|
+
const hubBody = typeof data.body === "string" && data.body.length > 0 ? data.body : null;
|
|
58
74
|
const title = data.title || data.threadId || "(unknown)";
|
|
59
75
|
const body = data.message || "";
|
|
60
|
-
const
|
|
61
|
-
|
|
76
|
+
const truncated = data.truncated === true;
|
|
77
|
+
const fullBytes = typeof data.fullBytes === "number" ? data.fullBytes : undefined;
|
|
78
|
+
const truncationMarker = truncated && fullBytes !== undefined
|
|
79
|
+
? ` […${fullBytes} bytes truncated; query thread for full content]`
|
|
80
|
+
: "";
|
|
81
|
+
const bodyLine = body ? `\n\nMessage preview: ${body}${truncationMarker}` : "";
|
|
82
|
+
const summaryLine = hubBody ?? (() => {
|
|
83
|
+
const authorLabel = data.author === "architect" ? "Architect"
|
|
84
|
+
: data.author === "engineer" ? "Engineer peer"
|
|
85
|
+
: "Peer";
|
|
86
|
+
return `[${authorLabel}] Replied to thread "${title}".`;
|
|
87
|
+
})();
|
|
88
|
+
return (`${summaryLine}${bodyLine} ` +
|
|
62
89
|
`\n\nIt is your turn. Call ${p}get_thread with threadId="${data.threadId}" ` +
|
|
63
90
|
`to read the full thread, then reply using ${p}create_thread_reply. ` +
|
|
64
91
|
`Threads 2.0 discipline: when you signal converged=true you MUST also populate ` +
|
|
@@ -147,9 +174,19 @@ function renderMessageArrived(data, cfg) {
|
|
|
147
174
|
const pMissionId = payload.missionId || "?";
|
|
148
175
|
const pMessage = payload.message || "(empty pulse message)";
|
|
149
176
|
const pResponseShape = payload.responseShape || "short_status";
|
|
177
|
+
// mission-66 W1+W2 commit 5b-final (architect-portion; canonical shape per
|
|
178
|
+
// hub/src/policy/note-schema.ts ratified at thread-428 round 3; landed in
|
|
179
|
+
// commit 5 at 8193061). Canonical kind=note payload shape: { body: string
|
|
180
|
+
// (REQUIRED, non-empty), ...optional metadata }. Hub-side reject-mode at
|
|
181
|
+
// canonical repository write-path (messageRepository.createMessage) throws
|
|
182
|
+
// NoteSchemaValidationError on invalid payload; LLM-callers see error nack
|
|
183
|
+
// via MCP entry-point — caller-side feedback restores bilateral-blind class
|
|
184
|
+
// closure (#41 STRUCTURAL ANCHOR). Per anti-goal #8 coordinated-upgrade
|
|
185
|
+
// discipline + Calibration #48 (Director ratification 2026-04-29).
|
|
150
186
|
return (`[Hub] Pulse fired (${payload.pulseKind}) for mission ${pMissionId}. ${pMessage} ` +
|
|
151
187
|
`Respond with shape "${pResponseShape}" via the appropriate channel ` +
|
|
152
|
-
`(typically ${p}create_message kind=note
|
|
188
|
+
`(typically ${p}create_message kind=note with payload {body: "<your status text>"}, ` +
|
|
189
|
+
`OR a short status reply on the active coord-thread). ` +
|
|
153
190
|
`Message ID: ${msgId}.`);
|
|
154
191
|
}
|
|
155
192
|
if (payload.event && typeof payload.event === "string") {
|
|
@@ -164,12 +201,42 @@ function renderMessageArrived(data, cfg) {
|
|
|
164
201
|
"(empty note body)";
|
|
165
202
|
const sender = msg.authorAgentId || "unknown";
|
|
166
203
|
const senderRole = msg.authorRole || "agent";
|
|
204
|
+
// mission-66 W1+W2 commit 5b-final (architect-portion; canonical shape per
|
|
205
|
+
// hub/src/policy/note-schema.ts; see pulse-template comment above for context).
|
|
167
206
|
return (`[${senderRole}/${sender}] Note: ${body} ` +
|
|
168
|
-
`(Message ID: ${msgId}; respond via ${p}create_message kind=note
|
|
207
|
+
`(Message ID: ${msgId}; respond via ${p}create_message kind=note ` +
|
|
208
|
+
`with payload {body: "<your reply text>"} targeting the sender.)`);
|
|
169
209
|
}
|
|
170
210
|
return (`[Hub] Message ${msgId} (kind=${kind}) arrived. ` +
|
|
171
211
|
`Use ${p}list_messages or ${p}claim_message to fetch full content + ack.`);
|
|
172
212
|
}
|
|
213
|
+
function renderWorkClaimableDigest(data, cfg) {
|
|
214
|
+
// idea-353 W1 — inbound idle-wake digest. Surfaced ONLY on an upward edge
|
|
215
|
+
// (0→N or a new claimable item) WHILE the agent is idle; the level-trigger
|
|
216
|
+
// de-dup means a steady N>0 / re-tick / Hub-restart does not re-fire.
|
|
217
|
+
const p = cfg.toolPrefix;
|
|
218
|
+
const role = data.role || "your";
|
|
219
|
+
const count = typeof data.count === "number" ? data.count : 0;
|
|
220
|
+
const newCount = typeof data.newCount === "number" ? data.newCount : count;
|
|
221
|
+
const noun = count === 1 ? "item" : "items";
|
|
222
|
+
const newClause = newCount > 0 && newCount < count ? ` (${newCount} new)` : "";
|
|
223
|
+
return (`[Hub] ${count} ${noun} now claimable for the ${role} role${newClause}. ` +
|
|
224
|
+
`Call ${p}list_ready_work to view, then ${p}claim_work to pick one up. ` +
|
|
225
|
+
`(Idle-wake digest: surfaced because you are idle and newly-claimable work appeared.)`);
|
|
226
|
+
}
|
|
227
|
+
function renderWorkLeaseStall(data, cfg) {
|
|
228
|
+
// idea-353 W2 — outbound stall-prompt: the gentle first rung below the
|
|
229
|
+
// lease-TTL → sweeper → poison → thrash ladder. Fires once per lease window
|
|
230
|
+
// when a held lease crosses ~60% of its life without a renew; renewing
|
|
231
|
+
// reopens the window + clears this prompt.
|
|
232
|
+
const p = cfg.toolPrefix;
|
|
233
|
+
const workId = data.workId || "(unknown)";
|
|
234
|
+
const msUntilExpiry = typeof data.msUntilExpiry === "number" ? data.msUntilExpiry : 0;
|
|
235
|
+
const mins = Math.max(0, Math.round(msUntilExpiry / 60000));
|
|
236
|
+
return (`[Hub] Your held work ${workId} is approaching lease expiry (~${mins}m left) without a recent renew. ` +
|
|
237
|
+
`Before the sweeper reaps it: ${p}renew_lease (still working), ${p}block_work (blocked on something), ` +
|
|
238
|
+
`or ${p}abandon_work (giving it up). Renewing clears this prompt.`);
|
|
239
|
+
}
|
|
173
240
|
// ── Registry ────────────────────────────────────────────────────────────
|
|
174
241
|
const RENDER_REGISTRY = new Map([
|
|
175
242
|
["thread_message", renderThreadMessage],
|
|
@@ -181,6 +248,8 @@ const RENDER_REGISTRY = new Map([
|
|
|
181
248
|
["review_completed", renderReviewCompleted],
|
|
182
249
|
["revision_required", renderRevisionRequired],
|
|
183
250
|
["proposal_decided", renderProposalDecided],
|
|
251
|
+
["work_claimable_digest", renderWorkClaimableDigest],
|
|
252
|
+
["work_lease_stall", renderWorkLeaseStall],
|
|
184
253
|
]);
|
|
185
254
|
function defaultTemplate(event, _data, _cfg) {
|
|
186
255
|
return `[Hub] Notification: ${event}.`;
|
|
@@ -213,6 +282,10 @@ export function buildToastMessage(event, data) {
|
|
|
213
282
|
const cause = data.cause || "unknown";
|
|
214
283
|
return `Agent ${id} state-changed (${cause})`;
|
|
215
284
|
}
|
|
285
|
+
case "work_claimable_digest":
|
|
286
|
+
return `${data.count ?? 0} work item(s) claimable for ${data.role ?? "your role"}`;
|
|
287
|
+
case "work_lease_stall":
|
|
288
|
+
return `Lease expiring: ${data.workId ?? "held work"}`;
|
|
216
289
|
default:
|
|
217
290
|
return `Hub: ${event}`;
|
|
218
291
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-format.js","sourceRoot":"","sources":["../src/prompt-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,UAAU,aAAa,CAC3B,KAAa,EACb,IAA6B;IAE7B,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,aAAa;YAChB,OAAO,uBAAuB,CAAC;QACjC,KAAK,mBAAmB;YACtB,OAAO,wCAAwC,CAAC;QAClD,KAAK,gBAAgB;YACnB,OAAO,kCAAkC,IAAI,CAAC,QAAQ,yCAAyC,CAAC;QAClG,KAAK,8BAA8B;YACjC,OAAO,0DAA0D,CAAC;QACpE,KAAK,wBAAwB;YAC3B,OAAO,uCAAuC,IAAI,CAAC,MAAM,sBAAsB,CAAC;QAClF,KAAK,kBAAkB;YACrB,OAAO,gCAAgC,IAAI,CAAC,MAAM,IAAI,CAAC;QACzD,KAAK,kBAAkB;YACrB,OAAO,mCAAmC,CAAC;QAC7C;YACE,OAAO,0BAA0B,CAAC;IACtC,CAAC;AACH,CAAC;AAmBD,0EAA0E;AAE1E,SAAS,mBAAmB,CAAC,IAA6B,EAAE,GAAuB;IACjF,yEAAyE;IACzE,uEAAuE;IACvE,
|
|
1
|
+
{"version":3,"file":"prompt-format.js","sourceRoot":"","sources":["../src/prompt-format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health-revision.ts — the Hub /health `toolSurfaceRevision` fetcher,
|
|
3
|
+
* hoisted to the kernel (idea-355 SLICE-1T).
|
|
4
|
+
*
|
|
5
|
+
* This is the network mechanism the bug-180 `ToolSurfaceReconciler` injects as
|
|
6
|
+
* its `fetchLiveRevision` dependency: resolve the Hub's live tool-surface
|
|
7
|
+
* revision (an opaque ETag served on /health since bug-114) over plain HTTP,
|
|
8
|
+
* independent of the MCP transport.
|
|
9
|
+
*
|
|
10
|
+
* Previously this lived inline in each shim (the claude shim's
|
|
11
|
+
* `fetchLiveToolSurfaceRevision`). Hoisting it single-homes the network
|
|
12
|
+
* mechanism so both shims share ONE implementation. The factory is PURE — it
|
|
13
|
+
* holds no module state and carries NO cache side-effect: callers that want a
|
|
14
|
+
* cached-revision marker (the claude shim's `cachedToolSurfaceRevision`) wrap
|
|
15
|
+
* the returned function and apply the side-effect on a non-null result.
|
|
16
|
+
*
|
|
17
|
+
* Contract:
|
|
18
|
+
* - derive `healthUrl` by replacing a trailing `/mcp` (optionally with a
|
|
19
|
+
* further path) on `hubUrl` with `/health`;
|
|
20
|
+
* - fetch it (global `fetch` only — opencode bundles via esbuild with no
|
|
21
|
+
* node_modules, so no node-fetch/undici);
|
|
22
|
+
* - return `json.toolSurfaceRevision` when it is a non-empty string;
|
|
23
|
+
* - return null on `!res.ok`, a missing/empty/non-string field, or any throw
|
|
24
|
+
* (never reject — the reconciler treats null as "unknown, trust the cache"
|
|
25
|
+
* and never emits a spurious list_changed).
|
|
26
|
+
*/
|
|
27
|
+
export interface FetchLiveToolSurfaceRevisionOptions {
|
|
28
|
+
/** The Hub MCP URL (…/mcp). The /health URL is derived from it. */
|
|
29
|
+
hubUrl: string;
|
|
30
|
+
/**
|
|
31
|
+
* Fetch implementation. Defaults to the global `fetch`. Injectable so the
|
|
32
|
+
* factory is unit-testable without a live Hub.
|
|
33
|
+
*/
|
|
34
|
+
fetch?: typeof globalThis.fetch;
|
|
35
|
+
/** Diagnostic logger. No-op default. */
|
|
36
|
+
log?: (msg: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build a pure `fetchLiveRevision` function for the bug-180
|
|
40
|
+
* `ToolSurfaceReconciler`. Resolves the Hub's live /health
|
|
41
|
+
* `toolSurfaceRevision`, or null on any failure (never rejects).
|
|
42
|
+
*/
|
|
43
|
+
export declare function makeFetchLiveToolSurfaceRevision(opts: FetchLiveToolSurfaceRevisionOptions): () => Promise<string | null>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health-revision.ts — the Hub /health `toolSurfaceRevision` fetcher,
|
|
3
|
+
* hoisted to the kernel (idea-355 SLICE-1T).
|
|
4
|
+
*
|
|
5
|
+
* This is the network mechanism the bug-180 `ToolSurfaceReconciler` injects as
|
|
6
|
+
* its `fetchLiveRevision` dependency: resolve the Hub's live tool-surface
|
|
7
|
+
* revision (an opaque ETag served on /health since bug-114) over plain HTTP,
|
|
8
|
+
* independent of the MCP transport.
|
|
9
|
+
*
|
|
10
|
+
* Previously this lived inline in each shim (the claude shim's
|
|
11
|
+
* `fetchLiveToolSurfaceRevision`). Hoisting it single-homes the network
|
|
12
|
+
* mechanism so both shims share ONE implementation. The factory is PURE — it
|
|
13
|
+
* holds no module state and carries NO cache side-effect: callers that want a
|
|
14
|
+
* cached-revision marker (the claude shim's `cachedToolSurfaceRevision`) wrap
|
|
15
|
+
* the returned function and apply the side-effect on a non-null result.
|
|
16
|
+
*
|
|
17
|
+
* Contract:
|
|
18
|
+
* - derive `healthUrl` by replacing a trailing `/mcp` (optionally with a
|
|
19
|
+
* further path) on `hubUrl` with `/health`;
|
|
20
|
+
* - fetch it (global `fetch` only — opencode bundles via esbuild with no
|
|
21
|
+
* node_modules, so no node-fetch/undici);
|
|
22
|
+
* - return `json.toolSurfaceRevision` when it is a non-empty string;
|
|
23
|
+
* - return null on `!res.ok`, a missing/empty/non-string field, or any throw
|
|
24
|
+
* (never reject — the reconciler treats null as "unknown, trust the cache"
|
|
25
|
+
* and never emits a spurious list_changed).
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Build a pure `fetchLiveRevision` function for the bug-180
|
|
29
|
+
* `ToolSurfaceReconciler`. Resolves the Hub's live /health
|
|
30
|
+
* `toolSurfaceRevision`, or null on any failure (never rejects).
|
|
31
|
+
*/
|
|
32
|
+
export function makeFetchLiveToolSurfaceRevision(opts) {
|
|
33
|
+
const log = opts.log ?? (() => { });
|
|
34
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
35
|
+
const healthUrl = opts.hubUrl.replace(/\/mcp(\/.*)?$/, "/health");
|
|
36
|
+
return async () => {
|
|
37
|
+
try {
|
|
38
|
+
const res = await doFetch(healthUrl);
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
log(`[Cache] /health fetch returned status ${res.status} — cache invalidation will trust existing cache`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const json = (await res.json());
|
|
44
|
+
if (typeof json.version === "string") {
|
|
45
|
+
log(`[Cache] Hub version: ${json.version}`);
|
|
46
|
+
}
|
|
47
|
+
if (typeof json.toolSurfaceRevision === "string" &&
|
|
48
|
+
json.toolSurfaceRevision !== "") {
|
|
49
|
+
log(`[Cache] Tool-surface revision resolved: ${json.toolSurfaceRevision}`);
|
|
50
|
+
return json.toolSurfaceRevision;
|
|
51
|
+
}
|
|
52
|
+
log(`[Cache] /health returned no toolSurfaceRevision field — cache invalidation will trust existing cache`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
log(`[Cache] /health fetch failed (non-fatal): ${err?.message ?? String(err)}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=health-revision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-revision.js","sourceRoot":"","sources":["../../../src/tool-manager/catalog/health-revision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAcH;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAC9C,IAAyC;IAEzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAElE,OAAO,KAAK,IAA4B,EAAE;QACxC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,GAAG,CACD,yCAAyC,GAAG,CAAC,MAAM,iDAAiD,CACrG,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAG7B,CAAC;YACF,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACrC,GAAG,CAAC,wBAAwB,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9C,CAAC;YACD,IACE,OAAO,IAAI,CAAC,mBAAmB,KAAK,QAAQ;gBAC5C,IAAI,CAAC,mBAAmB,KAAK,EAAE,EAC/B,CAAC;gBACD,GAAG,CAAC,2CAA2C,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;gBAC3E,OAAO,IAAI,CAAC,mBAAmB,CAAC;YAClC,CAAC;YACD,GAAG,CACD,sGAAsG,CACvG,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,6CAA8C,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3F,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Storage: $WORK_DIR/.ois/tool-catalog.json
|
|
12
12
|
* {
|
|
13
|
-
* schemaVersion:
|
|
14
|
-
*
|
|
13
|
+
* schemaVersion: 2,
|
|
14
|
+
* toolSurfaceRevision: "a1b2c3d4e5f6a7b8",
|
|
15
15
|
* fetchedAt: "2026-04-22T...Z",
|
|
16
16
|
* catalog: [...]
|
|
17
17
|
* }
|
|
18
18
|
*
|
|
19
|
-
* Invalidation:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Invalidation: tool-surface-revision mismatch (bug-114). The cache is
|
|
20
|
+
* keyed off a Hub-owned ETag (`/health` `toolSurfaceRevision`) that
|
|
21
|
+
* tracks the actual tool surface — added/removed tools, description or
|
|
22
|
+
* schema changes — rather than `hubVersion`, which was a hardcoded
|
|
23
|
+
* `"1.0.0"` literal that never changed (so the cache never invalidated).
|
|
24
|
+
* No TTL — the revision detects drift directly. Schema-version mismatch
|
|
25
|
+
* on read returns null (cache treated as invalid; schema evolution bumps
|
|
26
|
+
* CATALOG_SCHEMA_VERSION).
|
|
23
27
|
*
|
|
24
28
|
* Atomicity: writeCache uses tmp-file + rename so partial writes on
|
|
25
29
|
* crash don't corrupt the cache. Parse errors on read also return
|
|
@@ -29,7 +33,10 @@
|
|
|
29
33
|
* the primary flow):
|
|
30
34
|
* - missing file: readCache returns null
|
|
31
35
|
* - parse error: readCache returns null + logs
|
|
32
|
-
* - schema-version mismatch: readCache returns null
|
|
36
|
+
* - schema-version mismatch: readCache returns null + logs +
|
|
37
|
+
* unlinks the stale file (bug-114
|
|
38
|
+
* fallback-gap hardening — forces clean
|
|
39
|
+
* rebuild on next bootstrap)
|
|
33
40
|
* - $WORK_DIR readonly: writeCache logs + no-ops
|
|
34
41
|
* - disk full: writeCache logs + no-ops
|
|
35
42
|
*/
|
|
@@ -37,8 +44,14 @@
|
|
|
37
44
|
* Bumping CATALOG_SCHEMA_VERSION forces all existing cache files to
|
|
38
45
|
* be treated as invalid + re-bootstrapped. Use when changing the
|
|
39
46
|
* cache file shape.
|
|
47
|
+
*
|
|
48
|
+
* 1 → 2 (bug-114): `hubVersion` field replaced by `toolSurfaceRevision`.
|
|
49
|
+
* The bump is the state-migration step — every on-disk cache written in
|
|
50
|
+
* the v1 (`hubVersion`) shape fails the read-time schema check, returns
|
|
51
|
+
* null, and re-bootstraps. Free retroactive cleanup of the whole fleet's
|
|
52
|
+
* stale caches.
|
|
40
53
|
*/
|
|
41
|
-
export declare const CATALOG_SCHEMA_VERSION =
|
|
54
|
+
export declare const CATALOG_SCHEMA_VERSION = 2;
|
|
42
55
|
/**
|
|
43
56
|
* MCP tool catalog entry shape. Kept loose (`unknown[]`) since the
|
|
44
57
|
* cache is opaque storage; the dispatcher hands the catalog back to
|
|
@@ -47,7 +60,12 @@ export declare const CATALOG_SCHEMA_VERSION = 1;
|
|
|
47
60
|
export type ToolCatalog = unknown[];
|
|
48
61
|
export interface CachedCatalog {
|
|
49
62
|
schemaVersion: number;
|
|
50
|
-
|
|
63
|
+
/**
|
|
64
|
+
* bug-114 — the Hub-owned tool-surface ETag seen at fetch-time
|
|
65
|
+
* (`/health` `toolSurfaceRevision`). The cache is valid while this
|
|
66
|
+
* matches the live revision; treated as opaque (record-then-compare).
|
|
67
|
+
*/
|
|
68
|
+
toolSurfaceRevision: string;
|
|
51
69
|
fetchedAt: string;
|
|
52
70
|
catalog: ToolCatalog;
|
|
53
71
|
}
|
|
@@ -66,20 +84,22 @@ export declare function readCache(workDir: string, log?: (msg: string) => void):
|
|
|
66
84
|
* intact OR the new cache fully landed. Best-effort: failures log
|
|
67
85
|
* and return; the caller's primary flow continues.
|
|
68
86
|
*/
|
|
69
|
-
export declare function writeCache(workDir: string, catalog: ToolCatalog,
|
|
87
|
+
export declare function writeCache(workDir: string, catalog: ToolCatalog, toolSurfaceRevision: string, log?: (msg: string) => void): void;
|
|
70
88
|
/**
|
|
71
|
-
* Check cache validity against the current Hub
|
|
89
|
+
* Check cache validity against the current Hub tool-surface revision.
|
|
72
90
|
*
|
|
73
91
|
* Two semantics:
|
|
74
|
-
* -
|
|
75
|
-
* cached.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
92
|
+
* - currentRevision is a non-empty string: strict equality vs
|
|
93
|
+
* cached.toolSurfaceRevision. Mismatch → invalid → caller
|
|
94
|
+
* re-bootstraps.
|
|
95
|
+
* - currentRevision is null/undefined/empty: caller doesn't know the
|
|
96
|
+
* current revision yet (e.g. /health fetch in flight at startup, or
|
|
97
|
+
* an old Hub that doesn't return the field). Trust the cache
|
|
98
|
+
* (probe-friendly default) — worst case is serving a stale catalog
|
|
99
|
+
* ONCE until the next /health fetch completes and a real session
|
|
100
|
+
* refreshes the cache.
|
|
81
101
|
*
|
|
82
102
|
* Schema-version check is enforced inside readCache, so isCacheValid
|
|
83
103
|
* never sees a wrong-schema cached object.
|
|
84
104
|
*/
|
|
85
|
-
export declare function isCacheValid(cached: CachedCatalog,
|
|
105
|
+
export declare function isCacheValid(cached: CachedCatalog, currentRevision: string | null | undefined): boolean;
|
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Storage: $WORK_DIR/.ois/tool-catalog.json
|
|
12
12
|
* {
|
|
13
|
-
* schemaVersion:
|
|
14
|
-
*
|
|
13
|
+
* schemaVersion: 2,
|
|
14
|
+
* toolSurfaceRevision: "a1b2c3d4e5f6a7b8",
|
|
15
15
|
* fetchedAt: "2026-04-22T...Z",
|
|
16
16
|
* catalog: [...]
|
|
17
17
|
* }
|
|
18
18
|
*
|
|
19
|
-
* Invalidation:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Invalidation: tool-surface-revision mismatch (bug-114). The cache is
|
|
20
|
+
* keyed off a Hub-owned ETag (`/health` `toolSurfaceRevision`) that
|
|
21
|
+
* tracks the actual tool surface — added/removed tools, description or
|
|
22
|
+
* schema changes — rather than `hubVersion`, which was a hardcoded
|
|
23
|
+
* `"1.0.0"` literal that never changed (so the cache never invalidated).
|
|
24
|
+
* No TTL — the revision detects drift directly. Schema-version mismatch
|
|
25
|
+
* on read returns null (cache treated as invalid; schema evolution bumps
|
|
26
|
+
* CATALOG_SCHEMA_VERSION).
|
|
23
27
|
*
|
|
24
28
|
* Atomicity: writeCache uses tmp-file + rename so partial writes on
|
|
25
29
|
* crash don't corrupt the cache. Parse errors on read also return
|
|
@@ -29,7 +33,10 @@
|
|
|
29
33
|
* the primary flow):
|
|
30
34
|
* - missing file: readCache returns null
|
|
31
35
|
* - parse error: readCache returns null + logs
|
|
32
|
-
* - schema-version mismatch: readCache returns null
|
|
36
|
+
* - schema-version mismatch: readCache returns null + logs +
|
|
37
|
+
* unlinks the stale file (bug-114
|
|
38
|
+
* fallback-gap hardening — forces clean
|
|
39
|
+
* rebuild on next bootstrap)
|
|
33
40
|
* - $WORK_DIR readonly: writeCache logs + no-ops
|
|
34
41
|
* - disk full: writeCache logs + no-ops
|
|
35
42
|
*/
|
|
@@ -39,8 +46,14 @@ import { join, dirname } from "node:path";
|
|
|
39
46
|
* Bumping CATALOG_SCHEMA_VERSION forces all existing cache files to
|
|
40
47
|
* be treated as invalid + re-bootstrapped. Use when changing the
|
|
41
48
|
* cache file shape.
|
|
49
|
+
*
|
|
50
|
+
* 1 → 2 (bug-114): `hubVersion` field replaced by `toolSurfaceRevision`.
|
|
51
|
+
* The bump is the state-migration step — every on-disk cache written in
|
|
52
|
+
* the v1 (`hubVersion`) shape fails the read-time schema check, returns
|
|
53
|
+
* null, and re-bootstraps. Free retroactive cleanup of the whole fleet's
|
|
54
|
+
* stale caches.
|
|
42
55
|
*/
|
|
43
|
-
export const CATALOG_SCHEMA_VERSION =
|
|
56
|
+
export const CATALOG_SCHEMA_VERSION = 2;
|
|
44
57
|
/** Compute the canonical cache path for a given WORK_DIR. */
|
|
45
58
|
export function cachePathFor(workDir) {
|
|
46
59
|
return join(workDir, ".ois", "tool-catalog.json");
|
|
@@ -60,15 +73,27 @@ export function readCache(workDir, log) {
|
|
|
60
73
|
const parsed = JSON.parse(raw);
|
|
61
74
|
if (typeof parsed.schemaVersion !== "number" ||
|
|
62
75
|
parsed.schemaVersion !== CATALOG_SCHEMA_VERSION ||
|
|
63
|
-
typeof parsed.
|
|
76
|
+
typeof parsed.toolSurfaceRevision !== "string" ||
|
|
64
77
|
typeof parsed.fetchedAt !== "string" ||
|
|
65
78
|
!Array.isArray(parsed.catalog)) {
|
|
66
|
-
log?.(`[tool-catalog-cache] readCache: cache invalid (schema/shape mismatch) at ${path}`);
|
|
79
|
+
log?.(`[tool-catalog-cache] readCache: cache invalid (schema/shape mismatch) at ${path} — unlinking to force clean rebuild`);
|
|
80
|
+
// bug-114 fallback-gap hardening (2026-05-26): unlink the stale
|
|
81
|
+
// file so the next bootstrap doesn't repeatedly read-then-discard
|
|
82
|
+
// it. Without this, every cold-start after a
|
|
83
|
+
// CATALOG_SCHEMA_VERSION bump re-encounters the same stale file
|
|
84
|
+
// until operator intervention. Best-effort; primary flow tolerates
|
|
85
|
+
// unlink failures.
|
|
86
|
+
try {
|
|
87
|
+
unlinkSync(path);
|
|
88
|
+
}
|
|
89
|
+
catch (unlinkErr) {
|
|
90
|
+
log?.(`[tool-catalog-cache] readCache: unlink failed (non-fatal): ${unlinkErr.message ?? unlinkErr}`);
|
|
91
|
+
}
|
|
67
92
|
return null;
|
|
68
93
|
}
|
|
69
94
|
return {
|
|
70
95
|
schemaVersion: parsed.schemaVersion,
|
|
71
|
-
|
|
96
|
+
toolSurfaceRevision: parsed.toolSurfaceRevision,
|
|
72
97
|
fetchedAt: parsed.fetchedAt,
|
|
73
98
|
catalog: parsed.catalog,
|
|
74
99
|
};
|
|
@@ -84,12 +109,12 @@ export function readCache(workDir, log) {
|
|
|
84
109
|
* intact OR the new cache fully landed. Best-effort: failures log
|
|
85
110
|
* and return; the caller's primary flow continues.
|
|
86
111
|
*/
|
|
87
|
-
export function writeCache(workDir, catalog,
|
|
112
|
+
export function writeCache(workDir, catalog, toolSurfaceRevision, log) {
|
|
88
113
|
const path = cachePathFor(workDir);
|
|
89
114
|
const tmpPath = `${path}.tmp.${process.pid}`;
|
|
90
115
|
const body = {
|
|
91
116
|
schemaVersion: CATALOG_SCHEMA_VERSION,
|
|
92
|
-
|
|
117
|
+
toolSurfaceRevision,
|
|
93
118
|
fetchedAt: new Date().toISOString(),
|
|
94
119
|
catalog,
|
|
95
120
|
};
|
|
@@ -112,26 +137,28 @@ export function writeCache(workDir, catalog, hubVersion, log) {
|
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
139
|
/**
|
|
115
|
-
* Check cache validity against the current Hub
|
|
140
|
+
* Check cache validity against the current Hub tool-surface revision.
|
|
116
141
|
*
|
|
117
142
|
* Two semantics:
|
|
118
|
-
* -
|
|
119
|
-
* cached.
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
143
|
+
* - currentRevision is a non-empty string: strict equality vs
|
|
144
|
+
* cached.toolSurfaceRevision. Mismatch → invalid → caller
|
|
145
|
+
* re-bootstraps.
|
|
146
|
+
* - currentRevision is null/undefined/empty: caller doesn't know the
|
|
147
|
+
* current revision yet (e.g. /health fetch in flight at startup, or
|
|
148
|
+
* an old Hub that doesn't return the field). Trust the cache
|
|
149
|
+
* (probe-friendly default) — worst case is serving a stale catalog
|
|
150
|
+
* ONCE until the next /health fetch completes and a real session
|
|
151
|
+
* refreshes the cache.
|
|
125
152
|
*
|
|
126
153
|
* Schema-version check is enforced inside readCache, so isCacheValid
|
|
127
154
|
* never sees a wrong-schema cached object.
|
|
128
155
|
*/
|
|
129
|
-
export function isCacheValid(cached,
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
export function isCacheValid(cached, currentRevision) {
|
|
157
|
+
if (currentRevision === null ||
|
|
158
|
+
currentRevision === undefined ||
|
|
159
|
+
currentRevision === "") {
|
|
133
160
|
return true;
|
|
134
161
|
}
|
|
135
|
-
return cached.
|
|
162
|
+
return cached.toolSurfaceRevision === currentRevision;
|
|
136
163
|
}
|
|
137
164
|
//# sourceMappingURL=tool-catalog-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-catalog-cache.js","sourceRoot":"","sources":["../../../src/tool-manager/catalog/tool-catalog-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-surface-reconciler.ts — declared-vs-applied reconcile for the Hub
|
|
3
|
+
* MCP tool surface (bug-180 / FR-21).
|
|
4
|
+
*
|
|
5
|
+
* Problem this closes: the host (e.g. Claude Code) enumerates `tools/list`
|
|
6
|
+
* ONCE at startup. On the pre-identity probe path the dispatcher serves the
|
|
7
|
+
* persisted on-disk catalog (tool-catalog-cache). When a Hub redeploy changes
|
|
8
|
+
* the tool surface, the in-memory catalog the host holds goes stale and
|
|
9
|
+
* nothing re-enumerates it — neither a client hard-reset nor /reload-plugins,
|
|
10
|
+
* because the long-lived proxy process never re-fetches and the redeploy emits
|
|
11
|
+
* no in-life `notifications/tools/list_changed` to already-connected hosts.
|
|
12
|
+
*
|
|
13
|
+
* The fix treats tool-surface delivery as a reconcile, mirroring the storage
|
|
14
|
+
* substrate's SchemaDef-reconciler:
|
|
15
|
+
* - desired-state = the Hub's tool surface, versioned by `toolSurfaceRevision`
|
|
16
|
+
* (a deterministic hash of the registered tool set, served
|
|
17
|
+
* on /health; Hub-side since bug-114).
|
|
18
|
+
* - applied-state = the revision the host most recently enumerated.
|
|
19
|
+
* - watch/notify = MCP `notifications/tools/list_changed` (the Hub already
|
|
20
|
+
* advertises `tools.listChanged: true`).
|
|
21
|
+
*
|
|
22
|
+
* On drift between applied and desired, emit `list_changed`; the host re-calls
|
|
23
|
+
* `tools/list` and — identity now resolved — the dispatcher serves the live
|
|
24
|
+
* surface (it skips the probe-cache path once identityReady is true).
|
|
25
|
+
*
|
|
26
|
+
* This class owns ONLY the reconcile decision + the applied-revision marker.
|
|
27
|
+
* All I/O is injected so it is unit-testable without a live Hub:
|
|
28
|
+
* - fetchLiveRevision: resolve the Hub's current /health `toolSurfaceRevision`
|
|
29
|
+
* (null on fetch failure / a Hub that doesn't return the field).
|
|
30
|
+
* - readServedRevision: the revision the host's startup enumeration was
|
|
31
|
+
* served from — the on-disk cache's `toolSurfaceRevision` (null when no
|
|
32
|
+
* cache exists, i.e. a fresh install that bootstrapped live).
|
|
33
|
+
* - emitListChanged: emit `notifications/tools/list_changed` to the host.
|
|
34
|
+
*
|
|
35
|
+
* Two trigger points share one `reconcile()`:
|
|
36
|
+
* - L1 (primary): on the shim's `identityReady` event — catches the
|
|
37
|
+
* redeploy-then-reconnect case that caused bug-180.
|
|
38
|
+
* - L2 (backstop): on the existing PollBackstop heartbeat cadence — catches
|
|
39
|
+
* a redeploy WHILE a session stays connected (no reconnect, so no fresh
|
|
40
|
+
* identityReady).
|
|
41
|
+
*
|
|
42
|
+
* The applied-revision baseline is established on the first successful
|
|
43
|
+
* reconcile (whichever trigger fires first), seeded from the on-disk cache so
|
|
44
|
+
* a stale-cache-vs-live delta is detected even if L2 races ahead of L1.
|
|
45
|
+
*/
|
|
46
|
+
export interface ToolSurfaceReconcilerDeps {
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the Hub's live tool-surface revision (the /health
|
|
49
|
+
* `toolSurfaceRevision` ETag). Returns null when the fetch fails or the Hub
|
|
50
|
+
* doesn't return the field — the reconcile then no-ops (fail-safe: never
|
|
51
|
+
* emit a spurious `list_changed` on an unknown live state).
|
|
52
|
+
*/
|
|
53
|
+
fetchLiveRevision: () => Promise<string | null>;
|
|
54
|
+
/**
|
|
55
|
+
* The revision the host's current tool view was enumerated from — the
|
|
56
|
+
* on-disk cache's `toolSurfaceRevision`. Used to seed the applied-state
|
|
57
|
+
* baseline on the first reconcile (the pre-identity probe served this).
|
|
58
|
+
* null when no cache exists (fresh install bootstrapped live → no drift).
|
|
59
|
+
*/
|
|
60
|
+
readServedRevision: () => string | null;
|
|
61
|
+
/** Emit `notifications/tools/list_changed` to the host. */
|
|
62
|
+
emitListChanged: () => void;
|
|
63
|
+
/** Diagnostic logger. No-op default. */
|
|
64
|
+
log?: (msg: string) => void;
|
|
65
|
+
}
|
|
66
|
+
export interface ReconcileOutcome {
|
|
67
|
+
/** True iff a drift was detected and `list_changed` was emitted this pass. */
|
|
68
|
+
emitted: boolean;
|
|
69
|
+
/** The live revision resolved this pass (null on fetch failure). */
|
|
70
|
+
live: string | null;
|
|
71
|
+
}
|
|
72
|
+
export declare class ToolSurfaceReconciler {
|
|
73
|
+
private readonly deps;
|
|
74
|
+
/**
|
|
75
|
+
* The revision the host is believed to currently have applied. null until
|
|
76
|
+
* the first successful reconcile establishes the baseline.
|
|
77
|
+
*/
|
|
78
|
+
private appliedRevision;
|
|
79
|
+
private readonly log;
|
|
80
|
+
constructor(deps: ToolSurfaceReconcilerDeps);
|
|
81
|
+
/** Test/diagnostic accessor for the current applied-state marker. */
|
|
82
|
+
getAppliedRevision(): string | null;
|
|
83
|
+
/**
|
|
84
|
+
* One reconcile pass. Idempotent and safe to call from either trigger:
|
|
85
|
+
* 1. Resolve the live revision; no-op on unknown (fetch failed).
|
|
86
|
+
* 2. On first pass, seed the applied baseline from the on-disk cache
|
|
87
|
+
* (what the host's startup probe served) — or from live if no cache.
|
|
88
|
+
* 3. On applied-vs-live drift, emit `list_changed` + advance the baseline.
|
|
89
|
+
*
|
|
90
|
+
* @param reason short label for logs (e.g. "identityReady", "heartbeat").
|
|
91
|
+
*/
|
|
92
|
+
reconcile(reason?: string): Promise<ReconcileOutcome>;
|
|
93
|
+
}
|