@apnex/network-adapter 0.1.1 → 0.1.5
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 +93 -4
- package/dist/kernel/poll-backstop.js +193 -25
- 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 +59 -0
- package/dist/tool-manager/work-protocol/work-lease-tracker.js +134 -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
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dispatcher.ts — tool-manager handler factory (Layer 1c).
|
|
3
|
+
*
|
|
4
|
+
* Host-independent shared abstraction that owns the MCP server's
|
|
5
|
+
* Initialize / ListTools / CallTool handlers and the supporting
|
|
6
|
+
* pending-action-queueItemId tracking + tool-catalog cache fallback +
|
|
7
|
+
* clientInfo capture + error-envelope normalization.
|
|
8
|
+
*
|
|
9
|
+
* Mounted by per-host shims (Layer 3) which add host-specific transport
|
|
10
|
+
* plumbing (stdio / Bun-HTTP / future) and host-specific render-surface
|
|
11
|
+
* via the `notificationHooks` callback bag (Universal Adapter
|
|
12
|
+
* notification contract).
|
|
13
|
+
*
|
|
14
|
+
* This module is the "tool-manager" per Design v1.2 §4 naming discipline
|
|
15
|
+
* (Director-ratified rename from "MCP-boundary dispatcher" 2026-04-26)
|
|
16
|
+
* — distinct from the "Message-router" which is sovereign-package #6
|
|
17
|
+
* (`@apnex/message-router`) landing in M-Push-Foundation W4. Always
|
|
18
|
+
* qualify ("tool-manager" or "Message-router") in new code; avoid bare
|
|
19
|
+
* "dispatcher".
|
|
20
|
+
*/
|
|
21
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, InitializeRequestSchema, McpError, ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
+
import { MessageRouter, SeenIdCache } from "@apnex/message-router";
|
|
24
|
+
import { PollBackstop, resolveRole } from "../../kernel/poll-backstop.js";
|
|
25
|
+
import { ClaimableDigestTracker } from "../work-protocol/claimable-digest-tracker.js";
|
|
26
|
+
import { WorkLeaseTracker } from "../work-protocol/work-lease-tracker.js";
|
|
27
|
+
import { TOOL_CALL_SIGNAL_SKIP, pendingKey, injectQueueItemId, } from "../dispatch/tool-call-policy.js";
|
|
28
|
+
import { runToolDispatch } from "../dispatch/dispatch.js";
|
|
29
|
+
// M-Tool-Manager Slice B: the pure OIS tool-call policy helpers
|
|
30
|
+
// (TOOL_CALL_SIGNAL_SKIP / pendingKey / injectQueueItemId) moved to
|
|
31
|
+
// `./tool-call-policy.js` so the dispatch authority (`./dispatch.js`) and this
|
|
32
|
+
// god-object shell share them without a circular import. They are imported
|
|
33
|
+
// above (used internally) and RE-EXPORTED here so existing consumers
|
|
34
|
+
// (index.ts, tests) are unaffected.
|
|
35
|
+
export { TOOL_CALL_SIGNAL_SKIP, pendingKey, injectQueueItemId };
|
|
36
|
+
// idea-355 §4.3 (review fix) — tight timeout on the wake/stall reconcile's Hub
|
|
37
|
+
// read. The liveness heartbeat now runs concurrently with the reconcile (see
|
|
38
|
+
// PollBackstop.tickHeartbeat), but this still bounds how long the reconcile can
|
|
39
|
+
// hold the heartbeat cadence (heartbeatInFlight) and resource use. On timeout
|
|
40
|
+
// the read is treated as a failed read (AC3 — tracker skipped, no false replay).
|
|
41
|
+
const WAKE_STALL_READ_TIMEOUT_MS = 10_000;
|
|
42
|
+
function withTimeout(p, ms, label) {
|
|
43
|
+
let timer;
|
|
44
|
+
const timeout = new Promise((_, reject) => {
|
|
45
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
46
|
+
});
|
|
47
|
+
return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
|
|
48
|
+
}
|
|
49
|
+
export function createSharedDispatcher(opts) {
|
|
50
|
+
const log = opts.log ?? (() => { });
|
|
51
|
+
const serverName = opts.serverName ?? "proxy";
|
|
52
|
+
const serverCapabilities = opts.serverCapabilities ?? { tools: {}, logging: {} };
|
|
53
|
+
const pendingActionMap = new Map();
|
|
54
|
+
// idea-353 W1 idle-gate: count of host-driven CallTool requests currently in
|
|
55
|
+
// flight. The wake/stall reconciler reads this on the heartbeat tick to avoid
|
|
56
|
+
// interrupting a mid-task agent (AC4). The tick's own internal `agent.call`s
|
|
57
|
+
// (list_ready_work etc.) do NOT pass through this handler, so an idle poll
|
|
58
|
+
// never self-gates.
|
|
59
|
+
let activeCallCount = 0;
|
|
60
|
+
let capturedClientInfo = {
|
|
61
|
+
name: "unknown",
|
|
62
|
+
version: "0.0.0",
|
|
63
|
+
};
|
|
64
|
+
const getClientInfo = () => capturedClientInfo;
|
|
65
|
+
function isUsableAgent(agent) {
|
|
66
|
+
return !!agent && agent.isConnected !== false;
|
|
67
|
+
}
|
|
68
|
+
// bug-141 retry budget. Bootstrap-path ListTools races the Hub
|
|
69
|
+
// handshake on cold-start; this retry window absorbs the gap before
|
|
70
|
+
// raising a structured error. Worst-case latency:
|
|
71
|
+
// (LIST_TOOLS_RETRY_ATTEMPTS - 1) × LIST_TOOLS_RETRY_DELAY_MS
|
|
72
|
+
// (~2.8s) before the error surfaces. Probe path remains <50ms via
|
|
73
|
+
// cached catalog.
|
|
74
|
+
//
|
|
75
|
+
// Pass-1 (2026-05-26, bug-141 initial fix) set 4 × 200ms = 600ms
|
|
76
|
+
// budget. Pass-2 (2026-05-28) bumped to 15 × 200ms = 2.8s after
|
|
77
|
+
// observing greg-side cold-starts where the McpAgentClient reached
|
|
78
|
+
// `streaming` state (agent.isConnected=true) ~1.3s after
|
|
79
|
+
// identityReady resolved — 4 attempts wasn't enough to bridge the
|
|
80
|
+
// race. The architecturally-correct fix is for hosts to wire
|
|
81
|
+
// listToolsGate to syncReady instead of identityReady (claude-
|
|
82
|
+
// plugin shim does this); the wider retry budget defends opencode-
|
|
83
|
+
// plugin + any host that doesn't wire the gate, and is a belt-and-
|
|
84
|
+
// suspenders defense against future race-window widening.
|
|
85
|
+
const LIST_TOOLS_RETRY_ATTEMPTS = 15;
|
|
86
|
+
const LIST_TOOLS_RETRY_DELAY_MS = 200;
|
|
87
|
+
// ADR-017 Phase 1.1: SSE thread_message events carry queueItemId
|
|
88
|
+
// inline. Capture into pendingActionMap so the next settling
|
|
89
|
+
// create_thread_reply can auto-inject sourceQueueItemId — even if
|
|
90
|
+
// no drain ever populated the map. Eliminates the SSE-vs-drain race
|
|
91
|
+
// that caused false-positive escalations on early thread tests.
|
|
92
|
+
const captureQueueItemFromEvent = (event) => {
|
|
93
|
+
if (event.event !== "thread_message")
|
|
94
|
+
return;
|
|
95
|
+
const data = event.data;
|
|
96
|
+
const qid = data.queueItemId;
|
|
97
|
+
const threadId = data.threadId;
|
|
98
|
+
if (typeof qid === "string" && typeof threadId === "string") {
|
|
99
|
+
pendingActionMap.set(pendingKey("thread_message", threadId), qid);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// Mission-56 W2.2: Layer-2 routing. Every classified event goes
|
|
103
|
+
// through `@apnex/message-router` so Message-ID dedup (push+poll
|
|
104
|
+
// race) + kind→hook mapping live in one place. The host's
|
|
105
|
+
// `notificationHooks` bag is the router's hook surface — no
|
|
106
|
+
// shape adapter needed (the router's NotificationHooks interface
|
|
107
|
+
// mirrors DispatcherNotificationHooks exactly).
|
|
108
|
+
//
|
|
109
|
+
// The seen-id cache is shared across the construction-time router
|
|
110
|
+
// and any per-call routers minted by `makePendingActionItemHandler`,
|
|
111
|
+
// so a Message ID seen on the SSE inline path will dedup a later
|
|
112
|
+
// drain-path replay (and vice-versa).
|
|
113
|
+
const seenIdCache = new SeenIdCache();
|
|
114
|
+
const router = new MessageRouter({
|
|
115
|
+
hooks: opts.notificationHooks ?? {},
|
|
116
|
+
seenIdCache,
|
|
117
|
+
});
|
|
118
|
+
// Mission-56 W3.3: post-render claim. Extracts the Message ID from
|
|
119
|
+
// `message_arrived` events (W1a SSE shape: event.data.message.id)
|
|
120
|
+
// and fires `claim_message(id)` against the Hub via the agent. Per
|
|
121
|
+
// architect-issued W3 directive: claim happens AFTER the host hook
|
|
122
|
+
// renders (the ordering matches "shim calls after successful render
|
|
123
|
+
// to host" from Design v1.2 commitment #6). Errors are swallowed +
|
|
124
|
+
// logged — claim failure is non-fatal (the SSE path still rendered;
|
|
125
|
+
// the next poll-tick will pick up any unclaimed Message in the
|
|
126
|
+
// status === "new" set if needed).
|
|
127
|
+
//
|
|
128
|
+
// Multi-agent same-role: the Hub-side CAS enforces winner-takes-all
|
|
129
|
+
// (mission-56 W3.2). The wonClaim signal is informational; even if
|
|
130
|
+
// we lost, the host has already rendered (claim is post-render), so
|
|
131
|
+
// the loser still sees the Message — but only the winner's claim
|
|
132
|
+
// flips status to `received`, gating subsequent ack to a single
|
|
133
|
+
// canonical actor.
|
|
134
|
+
function fireClaimMessage(event) {
|
|
135
|
+
if (event.event !== "message_arrived")
|
|
136
|
+
return;
|
|
137
|
+
const data = event.data;
|
|
138
|
+
const message = data?.message;
|
|
139
|
+
const messageId = message?.id;
|
|
140
|
+
if (typeof messageId !== "string")
|
|
141
|
+
return;
|
|
142
|
+
const agent = opts.getAgent();
|
|
143
|
+
if (!agent || agent.state !== "streaming")
|
|
144
|
+
return;
|
|
145
|
+
void agent
|
|
146
|
+
.call("claim_message", { id: messageId })
|
|
147
|
+
.catch((err) => {
|
|
148
|
+
log(`[claim_message] non-fatal failure for ${messageId}: ${err?.message ?? String(err)}`);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Phase-1.5 #1 (M-SSE-Filter-List-Adapter-Consumption Design v1.0
|
|
152
|
+
// §1.3): when Hub has set `payload.suppress_peek_line: true` (per
|
|
153
|
+
// §1.5 filter-list at sse-peek-line-render.ts), downgrade the event
|
|
154
|
+
// to informational routing — the host's onInformationalEvent hook
|
|
155
|
+
// logs but does NOT call pushChannelNotification (no LLM wake; no
|
|
156
|
+
// peek-line surface). State-machine consumption preserved via the
|
|
157
|
+
// log path. Operator-visible noise reduction for filter-listed
|
|
158
|
+
// events. Back-compat: undefined / false → current Phase-1 flow
|
|
159
|
+
// unchanged.
|
|
160
|
+
function isSuppressed(event) {
|
|
161
|
+
const data = event.data;
|
|
162
|
+
return data?.suppress_peek_line === true;
|
|
163
|
+
}
|
|
164
|
+
const callbacks = {
|
|
165
|
+
onActionableEvent: (event) => {
|
|
166
|
+
if (isSuppressed(event)) {
|
|
167
|
+
// Filter-listed event: route as informational; skip
|
|
168
|
+
// captureQueueItemFromEvent + fireClaimMessage (actionable-only
|
|
169
|
+
// side effects). State-machine consumption preserved via the
|
|
170
|
+
// informational hook's log path.
|
|
171
|
+
router.route({ kind: "notification.informational", event });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
captureQueueItemFromEvent(event);
|
|
175
|
+
router.route({ kind: "notification.actionable", event });
|
|
176
|
+
// Mission-56 W3.3: post-render claim (replaces W2.2 stub-claim TODO).
|
|
177
|
+
fireClaimMessage(event);
|
|
178
|
+
},
|
|
179
|
+
onInformationalEvent: (event) => {
|
|
180
|
+
router.route({ kind: "notification.informational", event });
|
|
181
|
+
},
|
|
182
|
+
onStateChange: (state, previous, reason) => {
|
|
183
|
+
log(`Connection: ${previous} → ${state}${reason ? ` (${reason})` : ""}`);
|
|
184
|
+
router.route({ kind: "state.change", state, previous, reason });
|
|
185
|
+
// bug-103: on every transition into `streaming` (first connect AND every
|
|
186
|
+
// reconnect), trigger an immediate poll-backstop catch-up tick — so
|
|
187
|
+
// role-targeted kind:note notifications missed while the adapter was
|
|
188
|
+
// disconnected are recovered promptly, not only on the next ≤cadence
|
|
189
|
+
// interval tick. No-op when the first-timer is disabled or already
|
|
190
|
+
// in-flight (tick() guards both).
|
|
191
|
+
if (state === "streaming")
|
|
192
|
+
void pollBackstop?.tick(opts.getAgent);
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
// idea-355 §4.3 — kernel-internal queue wake/stall reconcile. Hoisted from the
|
|
196
|
+
// claude shim so EVERY host gets it via the dispatcher heartbeat tick with ZERO
|
|
197
|
+
// shim wiring (the shim that wires none of these seams still emits via
|
|
198
|
+
// notificationHooks on a tick). The trackers are per-dispatcher-instance and
|
|
199
|
+
// persist ACROSS ticks — the level-triggered, ID-keyed dedup state must survive
|
|
200
|
+
// tick-to-tick or every tick re-emits (digest spam).
|
|
201
|
+
const claimableDigest = new ClaimableDigestTracker();
|
|
202
|
+
// Slice D: use the injected tracker when a native binding shares one (so its
|
|
203
|
+
// runToolDispatch observations land where the reconcile reads); else own it.
|
|
204
|
+
const workLeases = opts.sharedWorkLeases ?? new WorkLeaseTracker();
|
|
205
|
+
let wakeStallInFlight = false;
|
|
206
|
+
// The reconcile body. Drives W1 (inbound claimable digest), W2 (outbound
|
|
207
|
+
// stall-prompt), W3 (emit-only status log). Invoked from the heartbeat tick
|
|
208
|
+
// below (wrapped in its own try/catch alongside the host live-refresh hook).
|
|
209
|
+
async function runWakeStallReconcile() {
|
|
210
|
+
// Invariant: in-flight latch so overlapping ticks don't double-emit. The
|
|
211
|
+
// gates below run BEFORE the latch is taken, so an early return never needs
|
|
212
|
+
// to release it.
|
|
213
|
+
if (wakeStallInFlight)
|
|
214
|
+
return;
|
|
215
|
+
const agent = opts.getAgent();
|
|
216
|
+
// Gate on a live streaming agent (the call path requires it anyway).
|
|
217
|
+
if (!agent || agent.state !== "streaming")
|
|
218
|
+
return;
|
|
219
|
+
// Invariant: gate on identityReady — role + agentId come from the post-
|
|
220
|
+
// handshake identity; a pre-handshake tick would read an undefined identity
|
|
221
|
+
// and list_ready_work the wrong (or empty) set.
|
|
222
|
+
const agentId = agent.getMetrics?.().agentId;
|
|
223
|
+
if (!agentId)
|
|
224
|
+
return;
|
|
225
|
+
// bug-173 — resolve the role at USE-time (it may be a `() => string` thunk
|
|
226
|
+
// for hosts whose dispatcher is constructed before config.role loads), so the
|
|
227
|
+
// idea-353 wake/stall reconcile filters on the SAME configured role the
|
|
228
|
+
// PollBackstop tick uses — not a frozen module-init env default.
|
|
229
|
+
const roleOpt = opts.pollBackstop?.role;
|
|
230
|
+
const role = roleOpt === undefined ? undefined : resolveRole(roleOpt);
|
|
231
|
+
if (!role)
|
|
232
|
+
return;
|
|
233
|
+
wakeStallInFlight = true;
|
|
234
|
+
try {
|
|
235
|
+
// Slice D: a native host (pi) supplies its own idle probe because its tool
|
|
236
|
+
// calls bypass this dispatcher's CallTool handler (so activeCallCount is
|
|
237
|
+
// always 0 for it). MCP hosts leave externalIdle undefined → counter wins.
|
|
238
|
+
const idle = opts.externalIdle ? opts.externalIdle() : activeCallCount === 0;
|
|
239
|
+
const nowMs = Date.now();
|
|
240
|
+
let claimableCount = 0;
|
|
241
|
+
// W1 — inbound claimable digest. Read the CALLER-CLAIMABLE set via the
|
|
242
|
+
// stable list_ready_work contract with scopeToCaller (idea-353 WI-2.1 /
|
|
243
|
+
// audit-4265: the Hub applies claim_work's FULL predicate — deps + role +
|
|
244
|
+
// WIP-cap + quarantine — so the digest never over-reports what this agent
|
|
245
|
+
// can actually claim; AC5 strict parity). On a failed read, skip the
|
|
246
|
+
// tracker entirely so a transient empty/aborted read cannot manufacture a
|
|
247
|
+
// false 0→N replay (AC3).
|
|
248
|
+
try {
|
|
249
|
+
const raw = await withTimeout(agent.call("list_ready_work", { role, scopeToCaller: true }, { internal: true }), WAKE_STALL_READ_TIMEOUT_MS, "list_ready_work wake/stall read");
|
|
250
|
+
const items = raw?.items;
|
|
251
|
+
if (Array.isArray(items)) {
|
|
252
|
+
const claimableIds = items
|
|
253
|
+
.map((i) => i?.id)
|
|
254
|
+
.filter((id) => typeof id === "string");
|
|
255
|
+
claimableCount = claimableIds.length;
|
|
256
|
+
const decision = claimableDigest.reconcile({ claimableIds, isIdle: idle });
|
|
257
|
+
if (decision.emit) {
|
|
258
|
+
router.route({
|
|
259
|
+
kind: "notification.actionable",
|
|
260
|
+
event: {
|
|
261
|
+
event: "work_claimable_digest",
|
|
262
|
+
data: { role, count: decision.count, newCount: decision.newCount },
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
log(`[idea-353] inbound digest emitted — ${decision.count} claimable (${decision.newCount} new) for ${role}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
log(`[idea-353] list_ready_work tick failed (non-fatal): ${err?.message ?? err}`);
|
|
271
|
+
}
|
|
272
|
+
// W2 — outbound stall-prompt. Idle-gated: never pester a visibly-
|
|
273
|
+
// progressing holder (an in-flight CallTool = active progress). A held
|
|
274
|
+
// lease past ~60% of its window without a renew gets ONE nudge.
|
|
275
|
+
if (idle) {
|
|
276
|
+
for (const due of workLeases.dueForStallPrompt(nowMs)) {
|
|
277
|
+
router.route({
|
|
278
|
+
kind: "notification.actionable",
|
|
279
|
+
event: {
|
|
280
|
+
event: "work_lease_stall",
|
|
281
|
+
data: { workId: due.workId, msUntilExpiry: due.msUntilExpiry },
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
workLeases.markPrompted(due.workId);
|
|
285
|
+
log(`[idea-353] outbound stall-prompt emitted — ${due.workId} (~${Math.round(due.msUntilExpiry / 60000)}m left)`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// W3 — emit-only Agent.status idle/stall telemetry seam. Thin + non-gating.
|
|
289
|
+
const heldLeases = workLeases.size();
|
|
290
|
+
const statusState = !idle ? "working" : heldLeases > 0 ? "holding" : "idle";
|
|
291
|
+
log(`[idea-353][agent-status] state=${statusState} idle=${idle} claimable=${claimableCount} heldLeases=${heldLeases}`);
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
// Invariant: release the latch on BOTH success and error — a thrown
|
|
295
|
+
// reconcile must not wedge the latch (that would silently disable the
|
|
296
|
+
// wake forever).
|
|
297
|
+
wakeStallInFlight = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Mission-56 W3.3: PollBackstop construction (opt-in via opts.pollBackstop).
|
|
301
|
+
// The backstop fires `list_messages({status:"new", since:<lastSeen>})`
|
|
302
|
+
// periodically and routes each delta Message through the same
|
|
303
|
+
// MessageRouter as the SSE inline path so seen-id LRU dedup catches
|
|
304
|
+
// push+poll race overlap. Polled Messages also fire claim_message
|
|
305
|
+
// (the router invocation goes through onActionableEvent which
|
|
306
|
+
// already includes fireClaimMessage).
|
|
307
|
+
// idea-355 §4.3 — the kernel owns the heartbeat tick. The host's optional
|
|
308
|
+
// onHeartbeatTick (e.g. claude's bug-180 tool-surface live-refresh, which is
|
|
309
|
+
// host-coupled via mcpServer.sendToolListChanged) and the kernel wake/stall
|
|
310
|
+
// reconcile SHARE the tick; each runs in its OWN try/catch (invariant #5) so a
|
|
311
|
+
// failing or slow one cannot break or block the other.
|
|
312
|
+
const hostHeartbeatTick = opts.pollBackstop?.onHeartbeatTick;
|
|
313
|
+
const pollBackstop = opts.pollBackstop
|
|
314
|
+
? new PollBackstop({
|
|
315
|
+
...opts.pollBackstop,
|
|
316
|
+
log: opts.pollBackstop.log ?? log,
|
|
317
|
+
onPolledMessage: (event) => {
|
|
318
|
+
router.route({ kind: "notification.actionable", event });
|
|
319
|
+
fireClaimMessage(event);
|
|
320
|
+
},
|
|
321
|
+
onHeartbeatTick: async () => {
|
|
322
|
+
if (hostHeartbeatTick) {
|
|
323
|
+
try {
|
|
324
|
+
await hostHeartbeatTick();
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
log(`[tick] host heartbeat hook threw (non-fatal): ${err?.message ?? err}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
await runWakeStallReconcile();
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
log(`[tick] wake/stall reconcile threw (non-fatal): ${err?.message ?? err}`);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
: undefined;
|
|
339
|
+
// Mission-56 W3.3: explicit-ack-on-action helper. Host shims wire
|
|
340
|
+
// this to fire when the LLM consumer has acted on (or actively
|
|
341
|
+
// deferred) a Message — per Option (i) ratified at thread-325 round-2,
|
|
342
|
+
// ack is tied to consumer-action, not auto-on-render.
|
|
343
|
+
async function ackMessage(messageId) {
|
|
344
|
+
const agent = opts.getAgent();
|
|
345
|
+
if (!agent || agent.state !== "streaming")
|
|
346
|
+
return;
|
|
347
|
+
try {
|
|
348
|
+
await agent.call("ack_message", { id: messageId });
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
log(`[ack_message] non-fatal failure for ${messageId}: ${err?.message ?? String(err)}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const makePendingActionItemHandler = (hooks) => {
|
|
355
|
+
// Per-call hooks override the construction-time bag for the
|
|
356
|
+
// drain path (preserves the original makePendingActionItemHandler
|
|
357
|
+
// contract — claude-plugin shim uses this to bind a custom log
|
|
358
|
+
// sink). Share the seen-id cache so drain-path replays dedup
|
|
359
|
+
// against SSE-path inline deliveries.
|
|
360
|
+
const drainRouter = new MessageRouter({
|
|
361
|
+
hooks: hooks ?? {},
|
|
362
|
+
seenIdCache,
|
|
363
|
+
});
|
|
364
|
+
return (item) => {
|
|
365
|
+
pendingActionMap.set(pendingKey(item.dispatchType, item.entityRef), item.id);
|
|
366
|
+
drainRouter.route({ kind: "pending-action.dispatch", item });
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
function createMcpServer() {
|
|
370
|
+
const server = new Server({ name: serverName, version: opts.proxyVersion }, { capabilities: serverCapabilities });
|
|
371
|
+
// Initialize handler is intentionally NOT gated — host MCP clients
|
|
372
|
+
// (e.g. Claude Code) have a tight initialize timeout that's faster
|
|
373
|
+
// than a full Hub handshake. The Initialize handler captures
|
|
374
|
+
// clientInfo for downstream handshake passthrough.
|
|
375
|
+
server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
376
|
+
try {
|
|
377
|
+
const ci = request.params
|
|
378
|
+
.clientInfo;
|
|
379
|
+
if (ci &&
|
|
380
|
+
typeof ci.name === "string" &&
|
|
381
|
+
typeof ci.version === "string") {
|
|
382
|
+
capturedClientInfo = { name: ci.name, version: ci.version };
|
|
383
|
+
log(`[Handshake] Captured clientInfo: ${ci.name}@${ci.version}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
log(`[Handshake] clientInfo capture failed (non-fatal): ${err}`);
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
protocolVersion: request.params.protocolVersion,
|
|
391
|
+
capabilities: serverCapabilities,
|
|
392
|
+
serverInfo: { name: serverName, version: opts.proxyVersion },
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
396
|
+
// Probe-safe cache fallback. When identity hasn't yet resolved
|
|
397
|
+
// (e.g. `claude mcp list` spawned the adapter and will exit
|
|
398
|
+
// before the handshake completes), serve the persisted catalog
|
|
399
|
+
// if available + valid against the current Hub version. Probe
|
|
400
|
+
// returns in <50ms with zero Hub round-trips.
|
|
401
|
+
if (opts.getCachedCatalog &&
|
|
402
|
+
opts.getIsIdentityReady &&
|
|
403
|
+
!opts.getIsIdentityReady()) {
|
|
404
|
+
const cached = opts.getCachedCatalog();
|
|
405
|
+
if (cached) {
|
|
406
|
+
const currentRevision = opts.getCurrentToolSurfaceRevision?.() ?? null;
|
|
407
|
+
const valid = (opts.isCacheValid ?? (() => false))(cached, currentRevision);
|
|
408
|
+
if (valid) {
|
|
409
|
+
log(`[ListTools] served from cache (${cached.catalog.length} tools)`);
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
411
|
+
return { tools: cached.catalog };
|
|
412
|
+
}
|
|
413
|
+
log(`[ListTools] cache stale (cached.toolSurfaceRevision=${cached.toolSurfaceRevision}, current=${currentRevision ?? "unknown"}) — bootstrapping`);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
log("[ListTools] no cache (bootstrapping cache from Hub)");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (opts.listToolsGate)
|
|
420
|
+
await opts.listToolsGate;
|
|
421
|
+
// bug-114 fallback-gap fix (2026-05-26): on cold-start the
|
|
422
|
+
// bootstrap path can race the Hub transport's connection
|
|
423
|
+
// completion — `listToolsGate` is currently undefined in every
|
|
424
|
+
// host shim, so the await above is a no-op. Pre-fix the
|
|
425
|
+
// handler returned `{ tools: [] }` silently when
|
|
426
|
+
// `isUsableAgent(agent)` was false, and host MCP clients
|
|
427
|
+
// (Claude Code) cached the empty list as the authoritative
|
|
428
|
+
// tool surface for the session — invisible comms-loss across
|
|
429
|
+
// the agent population, especially after a
|
|
430
|
+
// CATALOG_SCHEMA_VERSION bump invalidated all on-disk caches.
|
|
431
|
+
//
|
|
432
|
+
// Post-fix: short bounded retry (LIST_TOOLS_RETRY_ATTEMPTS ×
|
|
433
|
+
// LIST_TOOLS_RETRY_DELAY_MS) to absorb the narrow handshake
|
|
434
|
+
// race, then raise a structured MCP error (-32603 InternalError)
|
|
435
|
+
// so the host surfaces an actionable failure instead of
|
|
436
|
+
// caching `{ tools: [] }`. Bootstrap-completed telemetry emits
|
|
437
|
+
// on every path exit so silent zero-returns are architecturally
|
|
438
|
+
// impossible.
|
|
439
|
+
let agent = null;
|
|
440
|
+
for (let attempt = 1; attempt <= LIST_TOOLS_RETRY_ATTEMPTS; attempt++) {
|
|
441
|
+
const candidate = opts.getAgent();
|
|
442
|
+
if (isUsableAgent(candidate)) {
|
|
443
|
+
agent = candidate;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
if (attempt < LIST_TOOLS_RETRY_ATTEMPTS) {
|
|
447
|
+
log(`[ListTools] adapter not ready (attempt ${attempt}/${LIST_TOOLS_RETRY_ATTEMPTS}) — retrying in ${LIST_TOOLS_RETRY_DELAY_MS}ms`);
|
|
448
|
+
await new Promise((resolve) => setTimeout(resolve, LIST_TOOLS_RETRY_DELAY_MS));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (!agent) {
|
|
452
|
+
log(`[ListTools] bootstrap failed: adapter not ready after ${LIST_TOOLS_RETRY_ATTEMPTS} attempts — raising structured error`);
|
|
453
|
+
throw new McpError(ErrorCode.InternalError, "Adapter not ready: Hub transport has not connected yet. Retry the request.");
|
|
454
|
+
}
|
|
455
|
+
// Route through agent.listTools() so any configured cognitive
|
|
456
|
+
// pipeline's onListTools middleware (ToolDescriptionEnricher,
|
|
457
|
+
// ResponseSummarizer, etc.) observes and modifies the surface.
|
|
458
|
+
const tools = await agent.listTools();
|
|
459
|
+
// Best-effort cache write-back. Skip on empty results — never
|
|
460
|
+
// poison the cache with a zero-tool catalog (bug-114 hardening).
|
|
461
|
+
if (opts.persistCatalog && tools.length > 0) {
|
|
462
|
+
try {
|
|
463
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
464
|
+
opts.persistCatalog(tools);
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
log(`[ListTools] persistCatalog hook threw (non-fatal): ${err.message ?? err}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else if (opts.persistCatalog && tools.length === 0) {
|
|
471
|
+
log("[ListTools] persistCatalog skipped (zero tools — refusing to poison cache)");
|
|
472
|
+
}
|
|
473
|
+
log(`[ListTools] bootstrap completed: ${tools.length} tools surfaced`);
|
|
474
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
475
|
+
return { tools: tools };
|
|
476
|
+
});
|
|
477
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
478
|
+
// M-Tool-Manager Slice B: the CallTool handler body is now the shared
|
|
479
|
+
// dispatch authority (`runToolDispatch`). This MCP handler is a thin
|
|
480
|
+
// caller that supplies the closure state as an explicit dispatch context
|
|
481
|
+
// and returns the (Slice-B faithful, still MCP-shaped) result. The
|
|
482
|
+
// per-call behavior — idle-gate, callToolGate+timeout, Hub-not-connected
|
|
483
|
+
// precheck, queueItemId injection, signal-FSM wrap, lease observe,
|
|
484
|
+
// onToolCallResult, error normalization — all lives in `dispatch.ts` and
|
|
485
|
+
// is shared byte-for-byte with any future host binding (pi native, ACP).
|
|
486
|
+
const dispatchCtx = {
|
|
487
|
+
getAgent: opts.getAgent,
|
|
488
|
+
pendingActionMap,
|
|
489
|
+
workLeases,
|
|
490
|
+
onCallStart: () => {
|
|
491
|
+
activeCallCount++;
|
|
492
|
+
},
|
|
493
|
+
onCallEnd: () => {
|
|
494
|
+
activeCallCount--;
|
|
495
|
+
},
|
|
496
|
+
callToolGate: opts.callToolGate,
|
|
497
|
+
callToolGateTimeoutMs: opts.callToolGateTimeoutMs,
|
|
498
|
+
onToolCallResult: opts.onToolCallResult,
|
|
499
|
+
log,
|
|
500
|
+
};
|
|
501
|
+
const incomingArgs = (request.params.arguments ?? {});
|
|
502
|
+
// The dispatch authority returns the Slice-B-faithful MCP-shaped result
|
|
503
|
+
// ({ content, isError }); the SDK's CallTool result type is broader
|
|
504
|
+
// (optional task/_meta fields the original literal also omitted). Cast at
|
|
505
|
+
// this MCP-binding boundary — MCP-shape concerns are the binding's job.
|
|
506
|
+
const dispatchResult = await runToolDispatch(dispatchCtx, request.params.name, incomingArgs);
|
|
507
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
508
|
+
return dispatchResult;
|
|
509
|
+
});
|
|
510
|
+
return server;
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
pendingActionMap,
|
|
514
|
+
createMcpServer,
|
|
515
|
+
callbacks,
|
|
516
|
+
getClientInfo,
|
|
517
|
+
makePendingActionItemHandler,
|
|
518
|
+
pollBackstop,
|
|
519
|
+
ackMessage,
|
|
520
|
+
getActiveCallCount: () => activeCallCount,
|
|
521
|
+
isIdle: () => activeCallCount === 0,
|
|
522
|
+
workLeases,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* bug-53 boot-time fail-fast: assert host adapter has opted into
|
|
527
|
+
* pollBackstop wiring (transport_heartbeat periodic timer). Throws if
|
|
528
|
+
* `dispatcher.pollBackstop === undefined` UNLESS `TRANSPORT_HEARTBEAT_ENABLED=false`
|
|
529
|
+
* is set explicitly (opt-out path).
|
|
530
|
+
*
|
|
531
|
+
* Class: substrate-runtime-gap (sister to bug-49/50/51). Mission-75 §3.3
|
|
532
|
+
* substrate (PollBackstop heartbeat-second-timer) shipped at thread-472 with
|
|
533
|
+
* unit tests green, but the host integration was never written — both
|
|
534
|
+
* adapters call `createSharedDispatcher({...})` without supplying
|
|
535
|
+
* `opts.pollBackstop`, so the dispatcher's pollBackstop field stayed
|
|
536
|
+
* `undefined` and neither the poll-timer nor the heartbeat-timer ever
|
|
537
|
+
* scheduled. ZERO `transport_heartbeat` MCP tool calls fired in 96 minutes
|
|
538
|
+
* post Hub-restart (5127-line shim.log + 1789-event ndjson confirmed).
|
|
539
|
+
*
|
|
540
|
+
* This assertion is a §6.4-equivalent gate: each host MUST call
|
|
541
|
+
* `assertHostWiringComplete(dispatcher)` post-startup so the bug-53 class
|
|
542
|
+
* cannot recur silently. Misconfiguration fails fast at boot, not invisibly
|
|
543
|
+
* after 96 minutes of clinical observability degradation.
|
|
544
|
+
*/
|
|
545
|
+
export function assertHostWiringComplete(dispatcher, log = (m) => console.error(m)) {
|
|
546
|
+
if (dispatcher.pollBackstop !== undefined) {
|
|
547
|
+
return; // wiring complete
|
|
548
|
+
}
|
|
549
|
+
if (process.env.TRANSPORT_HEARTBEAT_ENABLED === "false") {
|
|
550
|
+
// Explicit opt-out path — log info-level so operators have forensics.
|
|
551
|
+
log("[adapter] pollBackstop intentionally disabled via TRANSPORT_HEARTBEAT_ENABLED=false");
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
throw new Error("[adapter] HOST WIRING ERROR: pollBackstop not configured in createSharedDispatcher opts. " +
|
|
555
|
+
"Transport heartbeat will not fire (lastHeartbeatAt will stay frozen at adapter-startup). " +
|
|
556
|
+
"Per mission-75 §3.3 + bug-53 closure, host adapters MUST opt in to pollBackstop. " +
|
|
557
|
+
"Set TRANSPORT_HEARTBEAT_ENABLED=false to explicitly disable.");
|
|
558
|
+
}
|
|
559
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../../src/tool-manager/orchestrator/dispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claimable-digest-tracker.ts — idea-353 W1 inbound wake (level-triggered,
|
|
3
|
+
* idle-gated, idempotent claimable-digest).
|
|
4
|
+
*
|
|
5
|
+
* The C1 work-queue is a pull model with no wake-on-ready signal: an idle
|
|
6
|
+
* eligible agent never learns that newly-claimable work appeared and sits
|
|
7
|
+
* unclaimed until someone manually nudges it (proven 3× in dogfood-1). This
|
|
8
|
+
* tracker is the **decision core** of the inbound wake — given the set of
|
|
9
|
+
* truly-claimable work ids for the agent's role (read via the stable
|
|
10
|
+
* `list_ready_work` contract) and whether the agent is idle, it decides whether
|
|
11
|
+
* to surface a digest ("N items claimable for your role").
|
|
12
|
+
*
|
|
13
|
+
* The design's ONE genuinely-new substrate is this **level-trigger / de-dup
|
|
14
|
+
* state** (the FR-22 storm-risk locus). The rules:
|
|
15
|
+
*
|
|
16
|
+
* - **Idle-gated (AC4):** never surface while the agent is mid-task. A busy
|
|
17
|
+
* tick neither emits NOR advances the baseline — so items that appeared
|
|
18
|
+
* while the agent was working are surfaced on the FIRST idle tick, not
|
|
19
|
+
* silently swallowed.
|
|
20
|
+
* - **Level-triggered / idempotent (AC3):** emit ONLY on an upward edge —
|
|
21
|
+
* 0→N, or a genuinely-NEW claimable id appeared since the last surfaced
|
|
22
|
+
* set. A steady N>0, a re-tick, or a Hub restart (the in-memory baseline
|
|
23
|
+
* survives, since the adapter process does not restart) yields no new ids
|
|
24
|
+
* → no emit. ID-keyed, not count-keyed, so a swap (one item claimed away,
|
|
25
|
+
* another appearing at the same count) still edges.
|
|
26
|
+
*
|
|
27
|
+
* Pairs with the at-tick guard in the host: the tracker is only fed on a
|
|
28
|
+
* SUCCESSFUL `list_ready_work` read — a failed read (Hub mid-restart, agent not
|
|
29
|
+
* streaming) skips the tick entirely and leaves the baseline untouched, so a
|
|
30
|
+
* transient empty/aborted read cannot manufacture a false 0→N replay.
|
|
31
|
+
*
|
|
32
|
+
* Pure + synchronous: all I/O (the list_ready_work read, the host emit) lives
|
|
33
|
+
* in the host tick; this class holds only the de-dup state + the decision, so
|
|
34
|
+
* the storm-proof contract is unit-testable without a live Hub (AC3/AC4).
|
|
35
|
+
*/
|
|
36
|
+
export interface ClaimableDigestInput {
|
|
37
|
+
/** The truly-claimable work ids for the agent's role this tick (post-bug-181). */
|
|
38
|
+
claimableIds: string[];
|
|
39
|
+
/** The idle-gate: false = the agent is actively mid-task (never interrupt it). */
|
|
40
|
+
isIdle: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface ClaimableDigestDecision {
|
|
43
|
+
/** True iff an upward edge was detected while idle → surface the digest. */
|
|
44
|
+
emit: boolean;
|
|
45
|
+
/** Current claimable count (whether or not we emit). */
|
|
46
|
+
count: number;
|
|
47
|
+
/** Number of newly-appeared claimable ids since the last surfaced set. */
|
|
48
|
+
newCount: number;
|
|
49
|
+
}
|
|
50
|
+
export declare class ClaimableDigestTracker {
|
|
51
|
+
/** The claimable id set the agent was last woken about (the de-dup baseline). */
|
|
52
|
+
private lastSurfaced;
|
|
53
|
+
/**
|
|
54
|
+
* Decide whether this tick should surface a claimable-digest wake.
|
|
55
|
+
* @param input current claimable ids + idle state for this tick.
|
|
56
|
+
*/
|
|
57
|
+
reconcile(input: ClaimableDigestInput): ClaimableDigestDecision;
|
|
58
|
+
/** Diagnostic/test accessor for the current de-dup baseline size. */
|
|
59
|
+
getSurfacedCount(): number;
|
|
60
|
+
}
|