@ziggs-ai/agent-sdk 0.10.0 → 0.10.2
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/CHANGELOG.md +23 -0
- package/README.md +9 -1
- package/dist/AgentHost.d.ts +50 -14
- package/dist/AgentHost.js +129 -7
- package/dist/agent/Agent.js +2 -2
- package/dist/agent/ClaudeSdkAgent.d.ts +23 -1
- package/dist/agent/ClaudeSdkAgent.js +156 -84
- package/dist/agent/claudeCognition.d.ts +25 -3
- package/dist/agent/claudeCognition.js +33 -1
- package/dist/agent/claudeSdkTools.d.ts +2 -2
- package/dist/agent/claudeSdkTools.js +1 -1
- package/dist/agent/toolManagerToSdkMcp.js +1 -1
- package/dist/context/applyEffects.js +4 -3
- package/dist/index.d.ts +6 -4
- package/dist/index.js +7 -3
- package/dist/ingress/normalizeIncoming.d.ts +2 -2
- package/dist/ingress/normalizeIncoming.js +1 -1
- package/dist/runtime/AgentMachine.d.ts +4 -4
- package/dist/runtime/AgentMachine.js +6 -6
- package/dist/runtime/bootstrapCtx.js +6 -8
- package/dist/runtime/buildOutcome.d.ts +9 -1
- package/dist/runtime/buildOutcome.js +29 -6
- package/dist/runtime/{wakeWriteKeys.d.ts → deliveryWriteKeys.d.ts} +2 -2
- package/dist/runtime/{wakeWriteKeys.js → deliveryWriteKeys.js} +15 -22
- package/dist/runtime/runMechanical.d.ts +2 -2
- package/dist/runtime/runMechanical.js +2 -2
- package/dist/runtime/runTurn.d.ts +2 -2
- package/dist/runtime/runTurn.js +5 -5
- package/dist/runtime/taskIntake.js +6 -4
- package/dist/server/ConnectionPool.d.ts +11 -53
- package/dist/server/ConnectionPool.js +29 -60
- package/dist/server/EventQueue.d.ts +6 -0
- package/dist/server/EventQueue.js +21 -6
- package/dist/server/SeenMessages.d.ts +7 -0
- package/dist/server/SeenMessages.js +15 -0
- package/dist/server/ZiggsEffectHandler.d.ts +1 -0
- package/dist/server/ZiggsEffectHandler.js +18 -11
- package/dist/server/createHealthServer.js +1 -1
- package/dist/server/inboxLoop.d.ts +17 -2
- package/dist/server/inboxLoop.js +92 -16
- package/dist/server/polls/PollKind.d.ts +72 -0
- package/dist/server/polls/PollKind.js +53 -0
- package/dist/server/polls/PollRunner.d.ts +62 -0
- package/dist/server/polls/PollRunner.js +138 -0
- package/dist/server/questMatch.d.ts +5 -0
- package/dist/server/questMatch.js +14 -0
- package/dist/server/runLauncher.d.ts +0 -6
- package/dist/server/runLauncher.js +54 -27
- package/dist/server/tools/tier1/protocolRunner.d.ts +4 -2
- package/dist/server/tools/tier1/protocolRunner.js +30 -34
- package/dist/server/tools/tier1/protocolTools.js +23 -13
- package/dist/server/tools/tier2/connectionTools.js +12 -1
- package/dist/tasks/protocolRegistry.d.ts +27 -0
- package/dist/tasks/protocolRegistry.js +36 -0
- package/dist/types.d.ts +4 -13
- package/package.json +2 -2
- package/dist/server/staleBacklogGate.d.ts +0 -57
- package/dist/server/staleBacklogGate.js +0 -100
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The protocol verbs and the state operation each one performs.
|
|
3
|
+
*
|
|
4
|
+
* This is the single place the set is written down (ZIG-1110). It used to be
|
|
5
|
+
* restated: the tool definitions passed their operation string to `handlerFor`
|
|
6
|
+
* by hand, and this table listed them again. `agreement_fulfill` was in the
|
|
7
|
+
* first list and not the second, so it dispatched fine but
|
|
8
|
+
* `isProtocolToolName` said no — and at every consumer (applyEffects,
|
|
9
|
+
* routingLabels, buildOutcome) the verb that ends an engagement permanently
|
|
10
|
+
* degraded to an unlabelled plain tool-result whose outcome never reached
|
|
11
|
+
* context.
|
|
12
|
+
*
|
|
13
|
+
* A protocol tool now takes its handler from this table BY TOOL NAME, and a
|
|
14
|
+
* name that is absent throws while the module is loading. Adding a verb in one
|
|
15
|
+
* place can no longer half-register it: the tool cannot be constructed at all
|
|
16
|
+
* until it appears here.
|
|
17
|
+
*
|
|
18
|
+
* This module deliberately imports nothing. It is read from the context and
|
|
19
|
+
* runtime paths, which must not pull in the server tool chain.
|
|
20
|
+
*/
|
|
1
21
|
export declare const PROTOCOL_TOOL_TO_OPERATION: Readonly<{
|
|
2
22
|
readonly agreement_propose: "agreement-propose";
|
|
3
23
|
readonly agreement_subcontract: "agreement-subcontract";
|
|
@@ -6,6 +26,7 @@ export declare const PROTOCOL_TOOL_TO_OPERATION: Readonly<{
|
|
|
6
26
|
readonly agreement_counter: "agreement-counter-proposal";
|
|
7
27
|
readonly agreement_get: "agreement-check-proposal";
|
|
8
28
|
readonly agreement_revoke: "agreement-revoke";
|
|
29
|
+
readonly agreement_fulfill: "agreement-fulfill";
|
|
9
30
|
readonly task_create: "task-spawn";
|
|
10
31
|
readonly task_set_result: "task-update";
|
|
11
32
|
readonly task_update_plan_step: "task-update-plan-step";
|
|
@@ -15,3 +36,9 @@ export type ProtocolOperation = (typeof PROTOCOL_TOOL_TO_OPERATION)[ProtocolTool
|
|
|
15
36
|
export declare const PROTOCOL_TOOL_NAMES: readonly ProtocolToolName[];
|
|
16
37
|
export declare function mapProtocolToolToOperation(toolName: string): ProtocolOperation | null;
|
|
17
38
|
export declare function isProtocolToolName(toolName: string): toolName is ProtocolToolName;
|
|
39
|
+
/**
|
|
40
|
+
* The operation a protocol tool performs, or a throw. Called at module load by
|
|
41
|
+
* the tool definitions, so a verb defined without a registry entry takes the
|
|
42
|
+
* import down instead of shipping half-registered.
|
|
43
|
+
*/
|
|
44
|
+
export declare function requireProtocolOperation(toolName: string): ProtocolOperation;
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The protocol verbs and the state operation each one performs.
|
|
3
|
+
*
|
|
4
|
+
* This is the single place the set is written down (ZIG-1110). It used to be
|
|
5
|
+
* restated: the tool definitions passed their operation string to `handlerFor`
|
|
6
|
+
* by hand, and this table listed them again. `agreement_fulfill` was in the
|
|
7
|
+
* first list and not the second, so it dispatched fine but
|
|
8
|
+
* `isProtocolToolName` said no — and at every consumer (applyEffects,
|
|
9
|
+
* routingLabels, buildOutcome) the verb that ends an engagement permanently
|
|
10
|
+
* degraded to an unlabelled plain tool-result whose outcome never reached
|
|
11
|
+
* context.
|
|
12
|
+
*
|
|
13
|
+
* A protocol tool now takes its handler from this table BY TOOL NAME, and a
|
|
14
|
+
* name that is absent throws while the module is loading. Adding a verb in one
|
|
15
|
+
* place can no longer half-register it: the tool cannot be constructed at all
|
|
16
|
+
* until it appears here.
|
|
17
|
+
*
|
|
18
|
+
* This module deliberately imports nothing. It is read from the context and
|
|
19
|
+
* runtime paths, which must not pull in the server tool chain.
|
|
20
|
+
*/
|
|
1
21
|
export const PROTOCOL_TOOL_TO_OPERATION = Object.freeze({
|
|
2
22
|
agreement_propose: 'agreement-propose',
|
|
3
23
|
agreement_subcontract: 'agreement-subcontract',
|
|
@@ -6,6 +26,7 @@ export const PROTOCOL_TOOL_TO_OPERATION = Object.freeze({
|
|
|
6
26
|
agreement_counter: 'agreement-counter-proposal',
|
|
7
27
|
agreement_get: 'agreement-check-proposal',
|
|
8
28
|
agreement_revoke: 'agreement-revoke',
|
|
29
|
+
agreement_fulfill: 'agreement-fulfill',
|
|
9
30
|
task_create: 'task-spawn',
|
|
10
31
|
task_set_result: 'task-update',
|
|
11
32
|
task_update_plan_step: 'task-update-plan-step',
|
|
@@ -19,3 +40,18 @@ export function mapProtocolToolToOperation(toolName) {
|
|
|
19
40
|
export function isProtocolToolName(toolName) {
|
|
20
41
|
return mapProtocolToolToOperation(toolName) != null;
|
|
21
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The operation a protocol tool performs, or a throw. Called at module load by
|
|
45
|
+
* the tool definitions, so a verb defined without a registry entry takes the
|
|
46
|
+
* import down instead of shipping half-registered.
|
|
47
|
+
*/
|
|
48
|
+
export function requireProtocolOperation(toolName) {
|
|
49
|
+
const operation = mapProtocolToolToOperation(toolName);
|
|
50
|
+
if (!operation) {
|
|
51
|
+
throw new Error(`Unknown protocol tool "${toolName}": add it to PROTOCOL_TOOL_TO_OPERATION ` +
|
|
52
|
+
'(agent-sdk/src/tasks/protocolRegistry.ts). Every consumer of the protocol ' +
|
|
53
|
+
'set reads that table — a tool missing from it dispatches but is never ' +
|
|
54
|
+
'recognised as protocol, so its result never reaches context.');
|
|
55
|
+
}
|
|
56
|
+
return operation;
|
|
57
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -187,15 +187,6 @@ export interface Ctx {
|
|
|
187
187
|
/** Focus agreement — sync'd with tasks[activeTaskId] when a task is focused. */
|
|
188
188
|
activeAgreementId: string | null;
|
|
189
189
|
pendingProposalAgreementId: string | null;
|
|
190
|
-
/**
|
|
191
|
-
* @deprecated ZIG-1057 — unused mirrors; always `{}` / `[]` after bootstrap.
|
|
192
|
-
* Read via `allPendingSubtasks` / `allCollectedResults` / `allDelegatedAgreementIds`.
|
|
193
|
-
*/
|
|
194
|
-
pendingSubtasks: Record<string, PendingSubtask>;
|
|
195
|
-
/** @deprecated ZIG-1057 — see `pendingSubtasks`. */
|
|
196
|
-
collectedResults: CollectedSubtaskResult[];
|
|
197
|
-
/** @deprecated ZIG-1057 — see `pendingSubtasks`. */
|
|
198
|
-
delegatedAgreementIds: string[];
|
|
199
190
|
toolResults: ToolResultEntry[];
|
|
200
191
|
lastOutcome: Outcome;
|
|
201
192
|
/** Claude Agent SDK session id for resume across ticks (claude-sdk cognition only). */
|
|
@@ -206,7 +197,7 @@ export interface Ctx {
|
|
|
206
197
|
* traffic could drive it, the brain skips the LLM instead of burning the
|
|
207
198
|
* fleet key on a wake storm.
|
|
208
199
|
*/
|
|
209
|
-
|
|
200
|
+
recentDeliveries?: number[];
|
|
210
201
|
}
|
|
211
202
|
export interface PromptDef {
|
|
212
203
|
role?: string;
|
|
@@ -378,11 +369,11 @@ export interface RecordedEntry {
|
|
|
378
369
|
/**
|
|
379
370
|
* Deterministic idempotency keys for one wake's writes (delivery law, item 2).
|
|
380
371
|
* Created once per tick from (laneKey, triggering event) — see
|
|
381
|
-
* `
|
|
372
|
+
* `createDeliveryWriteKeys` — and threaded to every artifact / task-state write the
|
|
382
373
|
* wake performs, so a crash-replay of the same wake reproduces the same keys
|
|
383
374
|
* and the backend drops the duplicates. Never LLM-supplied, never random.
|
|
384
375
|
*/
|
|
385
|
-
export interface
|
|
376
|
+
export interface DeliveryWriteKeys {
|
|
386
377
|
/** Stable id of this wake, or null when its trigger has no durable delivery identity (timer/synthetic frames) — null means writes go keyless, as before. */
|
|
387
378
|
readonly wakeKey: string | null;
|
|
388
379
|
/**
|
|
@@ -472,7 +463,7 @@ export type Effect = {
|
|
|
472
463
|
/** FSM state name (ZIG-440). */
|
|
473
464
|
stateId?: string;
|
|
474
465
|
/** Replay-stable write keys for this wake — write-capable tools derive their idempotency key from here, never from the LLM. */
|
|
475
|
-
|
|
466
|
+
writeKeys?: DeliveryWriteKeys;
|
|
476
467
|
} | {
|
|
477
468
|
kind: 'send-message';
|
|
478
469
|
sessionId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@anthropic-ai/claude-agent-sdk": "^0.3.204",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "^0.9.
|
|
29
|
+
"@ziggs-ai/api-client": "^0.9.1",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Launcher startup backlog policy (ZIG-910).
|
|
3
|
-
*
|
|
4
|
-
* "Work waits" has no statute of limitations on the backend — the inbox never
|
|
5
|
-
* expires anything. But when a fleet comes up after a dark period, replaying
|
|
6
|
-
* every weeks-old actionable slice means a mass of LLM turns on stale work at
|
|
7
|
-
* real token cost. The consumer chooses what to pick up: this gate keeps the
|
|
8
|
-
* operator-poll lazy start from resurrecting agents whose newest actionable
|
|
9
|
-
* item is older than a cutoff. Skips are never silent — one loud aggregated
|
|
10
|
-
* summary line announces what was left down and how to opt back in.
|
|
11
|
-
*
|
|
12
|
-
* Env:
|
|
13
|
-
* FLEET_REPLAY_MAX_AGE cutoff, e.g. "3d" | "12h" | "30m" | bare number =
|
|
14
|
-
* days (default "3d")
|
|
15
|
-
* FLEET_REPLAY_STALE=1 replay everything (disable the gate)
|
|
16
|
-
*
|
|
17
|
-
* Scope is the launcher only. The backend inbox is untouched, explicit
|
|
18
|
-
* `pool.startAgent()` / preStart are untouched, and an agent with ANY fresh item
|
|
19
|
-
* starts normally (its own inbox loop then drains the stale backlog too).
|
|
20
|
-
*/
|
|
21
|
-
import type { InboxOperatorAgentEntry } from '@ziggs-ai/api-client';
|
|
22
|
-
export declare const DEFAULT_REPLAY_MAX_AGE_MS: number;
|
|
23
|
-
export interface ReplayPolicy {
|
|
24
|
-
/** Skip slices whose newest actionable item is older than this. */
|
|
25
|
-
maxAgeMs: number;
|
|
26
|
-
/** FLEET_REPLAY_STALE=1 — replay everything, gate disabled. */
|
|
27
|
-
replayAll: boolean;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* "3d" | "12h" | "30m" | "45s"; a bare number means days (the knob's natural
|
|
31
|
-
* granularity). Returns null on garbage so the caller can warn and fall back.
|
|
32
|
-
*/
|
|
33
|
-
export declare function parseReplayMaxAge(raw: string): number | null;
|
|
34
|
-
export declare function resolveReplayPolicy(env?: NodeJS.ProcessEnv): ReplayPolicy;
|
|
35
|
-
/**
|
|
36
|
-
* Newest actionable instant for an agent, from its operator-sweep entry.
|
|
37
|
-
*
|
|
38
|
-
* The sweep hands this over directly now — it is the agent's delivery
|
|
39
|
-
* watermark — where this used to fold together the max timestamp across every
|
|
40
|
-
* channel of a whole envelope. An agent surfaced only for open work carries no
|
|
41
|
-
* delivery instant: age is unprovable, so it is treated as fresh and started.
|
|
42
|
-
* Stale-skipping a half-finished task would strand it.
|
|
43
|
-
*/
|
|
44
|
-
export declare function newestActionableAt(entry: InboxOperatorAgentEntry): number | null;
|
|
45
|
-
export declare function formatAge(ms: number): string;
|
|
46
|
-
export interface StaleBacklogGateOptions extends ReplayPolicy {
|
|
47
|
-
label?: string;
|
|
48
|
-
now?: () => number;
|
|
49
|
-
log?: (line: string) => void;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Build the `shouldLazyStart` gate for the operator poll. Skipped agents
|
|
53
|
-
* resurface in every envelope (reading never acks), so announcements are
|
|
54
|
-
* per-agent-once: each poll pass that skips agents not yet announced emits one
|
|
55
|
-
* aggregated warn line covering them.
|
|
56
|
-
*/
|
|
57
|
-
export declare function createStaleBacklogGate({ maxAgeMs, replayAll, label, now, log, }: StaleBacklogGateOptions): (entry: InboxOperatorAgentEntry) => boolean;
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
|
-
export const DEFAULT_REPLAY_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1000;
|
|
3
|
-
/**
|
|
4
|
-
* "3d" | "12h" | "30m" | "45s"; a bare number means days (the knob's natural
|
|
5
|
-
* granularity). Returns null on garbage so the caller can warn and fall back.
|
|
6
|
-
*/
|
|
7
|
-
export function parseReplayMaxAge(raw) {
|
|
8
|
-
const m = /^\s*(\d+(?:\.\d+)?)\s*([dhms]?)\s*$/i.exec(raw);
|
|
9
|
-
if (!m)
|
|
10
|
-
return null;
|
|
11
|
-
const value = Number(m[1]);
|
|
12
|
-
if (!Number.isFinite(value) || value <= 0)
|
|
13
|
-
return null;
|
|
14
|
-
const unitMs = { d: 86_400_000, h: 3_600_000, m: 60_000, s: 1_000 }[(m[2] || 'd').toLowerCase()];
|
|
15
|
-
return value * unitMs;
|
|
16
|
-
}
|
|
17
|
-
export function resolveReplayPolicy(env = process.env) {
|
|
18
|
-
const replayAll = env.FLEET_REPLAY_STALE === '1' || env.FLEET_REPLAY_STALE === 'true';
|
|
19
|
-
const raw = env.FLEET_REPLAY_MAX_AGE?.trim();
|
|
20
|
-
let maxAgeMs = DEFAULT_REPLAY_MAX_AGE_MS;
|
|
21
|
-
if (raw) {
|
|
22
|
-
const parsed = parseReplayMaxAge(raw);
|
|
23
|
-
if (parsed == null) {
|
|
24
|
-
runtimeLog.warn('staleBacklogGate', `FLEET_REPLAY_MAX_AGE="${raw}" is not a duration (use e.g. "3d", "12h", "30m") — using default ${formatAge(DEFAULT_REPLAY_MAX_AGE_MS)}`);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
maxAgeMs = parsed;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return { maxAgeMs, replayAll };
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Newest actionable instant for an agent, from its operator-sweep entry.
|
|
34
|
-
*
|
|
35
|
-
* The sweep hands this over directly now — it is the agent's delivery
|
|
36
|
-
* watermark — where this used to fold together the max timestamp across every
|
|
37
|
-
* channel of a whole envelope. An agent surfaced only for open work carries no
|
|
38
|
-
* delivery instant: age is unprovable, so it is treated as fresh and started.
|
|
39
|
-
* Stale-skipping a half-finished task would strand it.
|
|
40
|
-
*/
|
|
41
|
-
export function newestActionableAt(entry) {
|
|
42
|
-
if (!entry.deliveredUpTo)
|
|
43
|
-
return null;
|
|
44
|
-
const t = Date.parse(entry.deliveredUpTo);
|
|
45
|
-
return Number.isNaN(t) ? null : t;
|
|
46
|
-
}
|
|
47
|
-
export function formatAge(ms) {
|
|
48
|
-
if (ms >= 86_400_000)
|
|
49
|
-
return `${trimNum(ms / 86_400_000)}d`;
|
|
50
|
-
if (ms >= 3_600_000)
|
|
51
|
-
return `${trimNum(ms / 3_600_000)}h`;
|
|
52
|
-
if (ms >= 60_000)
|
|
53
|
-
return `${trimNum(ms / 60_000)}m`;
|
|
54
|
-
return `${trimNum(ms / 1_000)}s`;
|
|
55
|
-
}
|
|
56
|
-
function trimNum(n) {
|
|
57
|
-
return Number(n.toFixed(1)).toString();
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Build the `shouldLazyStart` gate for the operator poll. Skipped agents
|
|
61
|
-
* resurface in every envelope (reading never acks), so announcements are
|
|
62
|
-
* per-agent-once: each poll pass that skips agents not yet announced emits one
|
|
63
|
-
* aggregated warn line covering them.
|
|
64
|
-
*/
|
|
65
|
-
export function createStaleBacklogGate({ maxAgeMs, replayAll, label = 'launcher', now = Date.now, log, }) {
|
|
66
|
-
const emit = log ?? ((line) => runtimeLog.warn(label, line));
|
|
67
|
-
const announced = new Set();
|
|
68
|
-
let pendingAgents = [];
|
|
69
|
-
let flushScheduled = false;
|
|
70
|
-
// One envelope's entries are gated synchronously; a microtask-deferred
|
|
71
|
-
// flush turns a whole boot flood into a single summary line.
|
|
72
|
-
const flush = () => {
|
|
73
|
-
flushScheduled = false;
|
|
74
|
-
if (pendingAgents.length === 0)
|
|
75
|
-
return;
|
|
76
|
-
const preview = pendingAgents.slice(0, 5).join(', ');
|
|
77
|
-
const more = pendingAgents.length > 5 ? `, +${pendingAgents.length - 5} more` : '';
|
|
78
|
-
emit(`startup backlog policy: skipped ${pendingAgents.length} agent(s) whose newest mail is older than ${formatAge(maxAgeMs)} (${preview}${more}) — set FLEET_REPLAY_STALE=1 to replay them`);
|
|
79
|
-
pendingAgents = [];
|
|
80
|
-
};
|
|
81
|
-
return (entry) => {
|
|
82
|
-
if (replayAll)
|
|
83
|
-
return true;
|
|
84
|
-
// Open work always starts, however old: the task is unfinished, not stale.
|
|
85
|
-
if (entry.hasOpenTasks)
|
|
86
|
-
return true;
|
|
87
|
-
const newest = newestActionableAt(entry);
|
|
88
|
-
if (newest == null || now() - newest <= maxAgeMs)
|
|
89
|
-
return true;
|
|
90
|
-
if (!announced.has(entry.agentId)) {
|
|
91
|
-
announced.add(entry.agentId);
|
|
92
|
-
pendingAgents.push(entry.agentId);
|
|
93
|
-
if (!flushScheduled) {
|
|
94
|
-
flushScheduled = true;
|
|
95
|
-
queueMicrotask(flush);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return false;
|
|
99
|
-
};
|
|
100
|
-
}
|