@apnex/network-adapter 0.1.6 → 0.1.9
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 +3 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel/state-sync.js +6 -2
- package/dist/kernel/state-sync.js.map +1 -1
- package/dist/prompt-format.js +22 -1
- package/dist/prompt-format.js.map +1 -1
- package/dist/tool-manager/catalog/tool-catalog-cache.d.ts +20 -12
- package/dist/tool-manager/catalog/tool-catalog-cache.js +22 -12
- package/dist/tool-manager/catalog/tool-catalog-cache.js.map +1 -1
- package/dist/tool-manager/catalog/tool-surface-reconciler.d.ts +120 -50
- package/dist/tool-manager/catalog/tool-surface-reconciler.js +179 -59
- package/dist/tool-manager/catalog/tool-surface-reconciler.js.map +1 -1
- package/dist/tool-manager/orchestrator/dispatcher.d.ts +16 -0
- package/dist/tool-manager/orchestrator/dispatcher.js +96 -8
- package/dist/tool-manager/orchestrator/dispatcher.js.map +1 -1
- package/dist/tool-manager/work-protocol/claimable-digest-tracker.d.ts +37 -0
- package/dist/tool-manager/work-protocol/claimable-digest-tracker.js +61 -6
- package/dist/tool-manager/work-protocol/claimable-digest-tracker.js.map +1 -1
- package/dist/tool-manager/work-protocol/work-lease-tracker.d.ts +29 -0
- package/dist/tool-manager/work-protocol/work-lease-tracker.js +56 -3
- package/dist/tool-manager/work-protocol/work-lease-tracker.js.map +1 -1
- package/package.json +1 -1
|
@@ -39,6 +39,13 @@ export interface ClaimableDigestInput {
|
|
|
39
39
|
/** The idle-gate: false = the agent is actively mid-task (never interrupt it). */
|
|
40
40
|
isIdle: boolean;
|
|
41
41
|
}
|
|
42
|
+
export interface ReadFailureDecision {
|
|
43
|
+
/** True on the SINGLE tick where consecutive failures first reach the threshold
|
|
44
|
+
* (emit-once per streak; re-arms after the next successful read). */
|
|
45
|
+
degraded: boolean;
|
|
46
|
+
/** Current consecutive-failure count (for the host's log/notification). */
|
|
47
|
+
consecutiveFailures: number;
|
|
48
|
+
}
|
|
42
49
|
export interface ClaimableDigestDecision {
|
|
43
50
|
/** True iff an upward edge was detected while idle → surface the digest. */
|
|
44
51
|
emit: boolean;
|
|
@@ -46,10 +53,40 @@ export interface ClaimableDigestDecision {
|
|
|
46
53
|
count: number;
|
|
47
54
|
/** Number of newly-appeared claimable ids since the last surfaced set. */
|
|
48
55
|
newCount: number;
|
|
56
|
+
/** bug-226: what fired this emit — "edge" = a genuinely-new claimable id;
|
|
57
|
+
* "level" = the idle-ENTRY re-surface of a standing claimable set. Both in
|
|
58
|
+
* one tick collapse into a single "level" emit (the same reconcile). */
|
|
59
|
+
trigger: "edge" | "level" | null;
|
|
49
60
|
}
|
|
50
61
|
export declare class ClaimableDigestTracker {
|
|
51
62
|
/** The claimable id set the agent was last woken about (the de-dup baseline). */
|
|
52
63
|
private lastSurfaced;
|
|
64
|
+
/** bug-226: the prior tick's idle state — false at construction so the FIRST
|
|
65
|
+
* idle tick after adapter boot counts as an idle-entry (a restarted process
|
|
66
|
+
* must be re-told about standing work; the in-memory baseline it lost was
|
|
67
|
+
* exactly the bug). */
|
|
68
|
+
private prevIdle;
|
|
69
|
+
/** work-165 (idea-358): consecutive failed list_ready_work reads (silent-wake-
|
|
70
|
+
* death detector). Reset by recordReadSuccess; incremented by recordReadFailure. */
|
|
71
|
+
private consecutiveReadFailures;
|
|
72
|
+
/** Emit-once latch for the degraded notification — set when the threshold is
|
|
73
|
+
* first crossed, cleared by the next successful read (so a persistent outage
|
|
74
|
+
* emits once, not every tick). */
|
|
75
|
+
private degradedNotified;
|
|
76
|
+
/**
|
|
77
|
+
* work-165 (idea-358): record a SUCCESSFUL list_ready_work read — the wake is
|
|
78
|
+
* alive, so clear the failure streak + re-arm the degraded latch. Call on any
|
|
79
|
+
* non-throwing read (independent of idle-gate / reconcile).
|
|
80
|
+
*/
|
|
81
|
+
recordReadSuccess(): void;
|
|
82
|
+
/**
|
|
83
|
+
* work-165 (idea-358): record a FAILED list_ready_work read. The inbound wake
|
|
84
|
+
* only surfaces on a successful read, so a run of failures silently kills it.
|
|
85
|
+
* Returns `degraded:true` exactly once — on the tick the count first reaches
|
|
86
|
+
* `threshold` — so the host emits a single degraded-mode notification per
|
|
87
|
+
* outage (re-armed by the next recordReadSuccess).
|
|
88
|
+
*/
|
|
89
|
+
recordReadFailure(threshold?: number): ReadFailureDecision;
|
|
53
90
|
/**
|
|
54
91
|
* Decide whether this tick should surface a claimable-digest wake.
|
|
55
92
|
* @param input current claimable ids + idle state for this tick.
|
|
@@ -33,9 +33,50 @@
|
|
|
33
33
|
* in the host tick; this class holds only the de-dup state + the decision, so
|
|
34
34
|
* the storm-proof contract is unit-testable without a live Hub (AC3/AC4).
|
|
35
35
|
*/
|
|
36
|
+
/** work-165 (idea-358): consecutive failed `list_ready_work` reads before the
|
|
37
|
+
* inbound wake is declared degraded. The wake only surfaces on a SUCCESSFUL read,
|
|
38
|
+
* so a run of failed reads (Hub unreachable, stream wedged) silently kills the
|
|
39
|
+
* wake with no signal. After this many misses the host emits a degraded-mode
|
|
40
|
+
* notification so the dead wake is visible instead of silent. */
|
|
41
|
+
const DEFAULT_READ_FAILURE_THRESHOLD = 3;
|
|
36
42
|
export class ClaimableDigestTracker {
|
|
37
43
|
/** The claimable id set the agent was last woken about (the de-dup baseline). */
|
|
38
44
|
lastSurfaced = new Set();
|
|
45
|
+
/** bug-226: the prior tick's idle state — false at construction so the FIRST
|
|
46
|
+
* idle tick after adapter boot counts as an idle-entry (a restarted process
|
|
47
|
+
* must be re-told about standing work; the in-memory baseline it lost was
|
|
48
|
+
* exactly the bug). */
|
|
49
|
+
prevIdle = false;
|
|
50
|
+
/** work-165 (idea-358): consecutive failed list_ready_work reads (silent-wake-
|
|
51
|
+
* death detector). Reset by recordReadSuccess; incremented by recordReadFailure. */
|
|
52
|
+
consecutiveReadFailures = 0;
|
|
53
|
+
/** Emit-once latch for the degraded notification — set when the threshold is
|
|
54
|
+
* first crossed, cleared by the next successful read (so a persistent outage
|
|
55
|
+
* emits once, not every tick). */
|
|
56
|
+
degradedNotified = false;
|
|
57
|
+
/**
|
|
58
|
+
* work-165 (idea-358): record a SUCCESSFUL list_ready_work read — the wake is
|
|
59
|
+
* alive, so clear the failure streak + re-arm the degraded latch. Call on any
|
|
60
|
+
* non-throwing read (independent of idle-gate / reconcile).
|
|
61
|
+
*/
|
|
62
|
+
recordReadSuccess() {
|
|
63
|
+
this.consecutiveReadFailures = 0;
|
|
64
|
+
this.degradedNotified = false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* work-165 (idea-358): record a FAILED list_ready_work read. The inbound wake
|
|
68
|
+
* only surfaces on a successful read, so a run of failures silently kills it.
|
|
69
|
+
* Returns `degraded:true` exactly once — on the tick the count first reaches
|
|
70
|
+
* `threshold` — so the host emits a single degraded-mode notification per
|
|
71
|
+
* outage (re-armed by the next recordReadSuccess).
|
|
72
|
+
*/
|
|
73
|
+
recordReadFailure(threshold = DEFAULT_READ_FAILURE_THRESHOLD) {
|
|
74
|
+
this.consecutiveReadFailures += 1;
|
|
75
|
+
const degraded = this.consecutiveReadFailures >= threshold && !this.degradedNotified;
|
|
76
|
+
if (degraded)
|
|
77
|
+
this.degradedNotified = true;
|
|
78
|
+
return { degraded, consecutiveFailures: this.consecutiveReadFailures };
|
|
79
|
+
}
|
|
39
80
|
/**
|
|
40
81
|
* Decide whether this tick should surface a claimable-digest wake.
|
|
41
82
|
* @param input current claimable ids + idle state for this tick.
|
|
@@ -43,20 +84,34 @@ export class ClaimableDigestTracker {
|
|
|
43
84
|
reconcile(input) {
|
|
44
85
|
const count = input.claimableIds.length;
|
|
45
86
|
// AC4 idle-gate: never surface while mid-task, and do NOT advance the
|
|
46
|
-
// baseline — accumulate so the first idle tick
|
|
47
|
-
// while the agent was busy.
|
|
87
|
+
// baseline (or the idle latch) — accumulate so the first idle tick
|
|
88
|
+
// surfaces what appeared while the agent was busy.
|
|
48
89
|
if (!input.isIdle) {
|
|
49
|
-
|
|
90
|
+
this.prevIdle = false;
|
|
91
|
+
return { emit: false, count, newCount: 0, trigger: null };
|
|
50
92
|
}
|
|
93
|
+
// bug-226 fix shape (a): LEVEL trigger on idle-ENTRY. The edge-only
|
|
94
|
+
// baseline meant an agent that was told about work BEFORE going busy was
|
|
95
|
+
// never re-told at the next idle — it sat beside claimable work until a
|
|
96
|
+
// human probed (3× in one day). On the busy→idle transition (and on the
|
|
97
|
+
// first tick after adapter boot), clear the baseline so the STANDING
|
|
98
|
+
// claimable set re-surfaces. Fires at most once per idle-entry by
|
|
99
|
+
// construction — continuous idle never re-enters; the aging/nudge
|
|
100
|
+
// machinery owns longer-horizon persistence.
|
|
101
|
+
const idleEntry = !this.prevIdle;
|
|
102
|
+
this.prevIdle = true;
|
|
103
|
+
if (idleEntry)
|
|
104
|
+
this.lastSurfaced.clear();
|
|
51
105
|
const newIds = input.claimableIds.filter((id) => !this.lastSurfaced.has(id));
|
|
52
|
-
// AC3 level-trigger: emit only on an upward edge (0→N or a new id)
|
|
53
|
-
//
|
|
106
|
+
// AC3 level-trigger: emit only on an upward edge (0→N or a new id) — which
|
|
107
|
+
// an idle-entry reset widens to the full standing set. Edge + level in the
|
|
108
|
+
// same tick is ONE reconcile → ONE emit (the bug-226 dedupe requirement).
|
|
54
109
|
const emit = count > 0 && newIds.length > 0;
|
|
55
110
|
// Advance the baseline to the current set on every IDLE pass (emit or not),
|
|
56
111
|
// so a claimed-away item doesn't wedge the de-dup and a later re-appearance
|
|
57
112
|
// re-edges correctly.
|
|
58
113
|
this.lastSurfaced = new Set(input.claimableIds);
|
|
59
|
-
return { emit, count, newCount: newIds.length };
|
|
114
|
+
return { emit, count, newCount: newIds.length, trigger: emit ? (idleEntry ? "level" : "edge") : null };
|
|
60
115
|
}
|
|
61
116
|
/** Diagnostic/test accessor for the current de-dup baseline size. */
|
|
62
117
|
getSurfacedCount() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claimable-digest-tracker.js","sourceRoot":"","sources":["../../../src/tool-manager/work-protocol/claimable-digest-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;
|
|
1
|
+
{"version":3,"file":"claimable-digest-tracker.js","sourceRoot":"","sources":["../../../src/tool-manager/work-protocol/claimable-digest-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AASH;;;;kEAIkE;AAClE,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAuBzC,MAAM,OAAO,sBAAsB;IACjC,iFAAiF;IACzE,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC;;;4BAGwB;IAChB,QAAQ,GAAG,KAAK,CAAC;IACzB;yFACqF;IAC7E,uBAAuB,GAAG,CAAC,CAAC;IACpC;;uCAEmC;IAC3B,gBAAgB,GAAG,KAAK,CAAC;IAEjC;;;;OAIG;IACH,iBAAiB;QACf,IAAI,CAAC,uBAAuB,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;IAChC,CAAC;IAED;;;;;;OAMG;IACH,iBAAiB,CAAC,SAAS,GAAG,8BAA8B;QAC1D,IAAI,CAAC,uBAAuB,IAAI,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,uBAAuB,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC;QACrF,IAAI,QAAQ;YAAE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC3C,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACzE,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,KAA2B;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC;QAExC,sEAAsE;QACtE,mEAAmE;QACnE,mDAAmD;QACnD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5D,CAAC;QAED,oEAAoE;QACpE,yEAAyE;QACzE,wEAAwE;QACxE,wEAAwE;QACxE,qEAAqE;QACrE,kEAAkE;QAClE,kEAAkE;QAClE,6CAA6C;QAC7C,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,SAAS;YAAE,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAEzC,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7E,2EAA2E;QAC3E,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAE5C,4EAA4E;QAC5E,4EAA4E;QAC5E,sBAAsB;QACtB,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAEhD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzG,CAAC;IAED,qEAAqE;IACrE,gBAAgB;QACd,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -35,6 +35,14 @@ export interface StallPrompt {
|
|
|
35
35
|
/** Ms remaining until the Hub sweeper may reap the lease. */
|
|
36
36
|
msUntilExpiry: number;
|
|
37
37
|
}
|
|
38
|
+
/** work-164 (idea-395): a held lease due for an auto-heartbeat renew — carries the
|
|
39
|
+
* token the host needs to call `renew_lease` on the holder's behalf. */
|
|
40
|
+
export interface RenewDue {
|
|
41
|
+
workId: string;
|
|
42
|
+
token: string;
|
|
43
|
+
/** Ms remaining until expiry (for the host's log). */
|
|
44
|
+
msUntilExpiry: number;
|
|
45
|
+
}
|
|
38
46
|
export declare class WorkLeaseTracker {
|
|
39
47
|
private leases;
|
|
40
48
|
/**
|
|
@@ -43,6 +51,18 @@ export declare class WorkLeaseTracker {
|
|
|
43
51
|
* unparseable result is a no-op.
|
|
44
52
|
*/
|
|
45
53
|
observe(method: string, args: Record<string, unknown> | undefined, result: unknown, nowMs: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* work-164 (idea-395): held leases past `thresholdFraction` of their window and
|
|
56
|
+
* not yet expired, WITH a known token — candidates for an auto-heartbeat renew.
|
|
57
|
+
* No prompt-latch: a successful renew resets the window via observe (so it won't
|
|
58
|
+
* re-fire until the next threshold crossing), and a failed renew leaves it due so
|
|
59
|
+
* the next tick retries. Only the host's ACTIVE-WORK gate decides whether to
|
|
60
|
+
* actually renew — a stalled/crashed holder is left to the stall-prompt + sweeper.
|
|
61
|
+
*
|
|
62
|
+
* @param thresholdFraction renew once past this fraction of the window
|
|
63
|
+
* (default 0.5 — renew around the halfway mark while genuinely working).
|
|
64
|
+
*/
|
|
65
|
+
dueForRenew(nowMs: number, thresholdFraction?: number): RenewDue[];
|
|
46
66
|
/**
|
|
47
67
|
* Held leases that have crossed `thresholdFraction` of their current window
|
|
48
68
|
* but not yet expired, and have not been prompted this window. The host
|
|
@@ -54,6 +74,15 @@ export declare class WorkLeaseTracker {
|
|
|
54
74
|
dueForStallPrompt(nowMs: number, thresholdFraction?: number): StallPrompt[];
|
|
55
75
|
/** Latch a lease as prompted for its current window (call after emitting). */
|
|
56
76
|
markPrompted(workId: string): void;
|
|
77
|
+
/**
|
|
78
|
+
* work-165 (idea-358): drop every lease whose expiry has passed as of `nowMs`.
|
|
79
|
+
* Without this the Map only shrinks on an explicit close verb, so a lease reaped
|
|
80
|
+
* server-side (holder went silent) lingers forever — `size()`/`snapshot()` then
|
|
81
|
+
* mis-report a stale "holding", and long-lived processes leak the Map unbounded.
|
|
82
|
+
* Called on every `observe` and on the host heartbeat tick. Returns the count
|
|
83
|
+
* pruned (host may log it). Deleting the current key mid-Map-iteration is safe.
|
|
84
|
+
*/
|
|
85
|
+
prune(nowMs: number): number;
|
|
57
86
|
/** Diagnostic/test accessor: number of currently-held tracked leases. */
|
|
58
87
|
size(): number;
|
|
59
88
|
/**
|
|
@@ -42,8 +42,8 @@ const LEASE_CLOSE_VERBS = new Set([
|
|
|
42
42
|
/** A renew (or claim) reopens the window + clears the prompt latch; start_work
|
|
43
43
|
* only refreshes the known expiry without resetting the window. */
|
|
44
44
|
const WINDOW_RESET_VERBS = new Set(["claim_work", "renew_lease"]);
|
|
45
|
-
/** Pull `{ workId, expiresAtMs }` out of an already-unwrapped work-verb
|
|
46
|
-
* Tolerant: returns null on any shape that isn't a lease-bearing workItem. */
|
|
45
|
+
/** Pull `{ workId, expiresAtMs, token }` out of an already-unwrapped work-verb
|
|
46
|
+
* result. Tolerant: returns null on any shape that isn't a lease-bearing workItem. */
|
|
47
47
|
function parseLease(result) {
|
|
48
48
|
const wi = result?.workItem;
|
|
49
49
|
if (!wi || typeof wi.id !== "string")
|
|
@@ -54,7 +54,8 @@ function parseLease(result) {
|
|
|
54
54
|
const expiresAtMs = Date.parse(expiresAt);
|
|
55
55
|
if (Number.isNaN(expiresAtMs))
|
|
56
56
|
return null;
|
|
57
|
-
|
|
57
|
+
const token = typeof wi.lease?.token === "string" ? wi.lease.token : "";
|
|
58
|
+
return { workId: wi.id, expiresAtMs, token };
|
|
58
59
|
}
|
|
59
60
|
/** Pull a workId for a CLOSE verb — prefer the result workItem, fall back to args. */
|
|
60
61
|
function parseWorkId(result, args) {
|
|
@@ -73,6 +74,11 @@ export class WorkLeaseTracker {
|
|
|
73
74
|
* unparseable result is a no-op.
|
|
74
75
|
*/
|
|
75
76
|
observe(method, args, result, nowMs) {
|
|
77
|
+
// work-165 (idea-358): drop any lease whose expiry has already passed before
|
|
78
|
+
// (re)observing. The Hub sweeper reaps an expired lease server-side, but if the
|
|
79
|
+
// holder never issues a close verb (the exact silent-reap case this arc fixes)
|
|
80
|
+
// the local entry would linger forever — so every observation self-cleans.
|
|
81
|
+
this.prune(nowMs);
|
|
76
82
|
if (LEASE_CLOSE_VERBS.has(method)) {
|
|
77
83
|
const workId = parseWorkId(result, args);
|
|
78
84
|
if (workId)
|
|
@@ -93,8 +99,37 @@ export class WorkLeaseTracker {
|
|
|
93
99
|
windowStartMs: resetWindow ? nowMs : existing.windowStartMs,
|
|
94
100
|
expiresAtMs: parsed.expiresAtMs,
|
|
95
101
|
prompted: resetWindow ? false : existing.prompted,
|
|
102
|
+
// work-164: keep the freshest token; fall back to the prior one if a result
|
|
103
|
+
// (e.g. some start_work shapes) carried none.
|
|
104
|
+
token: parsed.token || existing?.token || "",
|
|
96
105
|
});
|
|
97
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* work-164 (idea-395): held leases past `thresholdFraction` of their window and
|
|
109
|
+
* not yet expired, WITH a known token — candidates for an auto-heartbeat renew.
|
|
110
|
+
* No prompt-latch: a successful renew resets the window via observe (so it won't
|
|
111
|
+
* re-fire until the next threshold crossing), and a failed renew leaves it due so
|
|
112
|
+
* the next tick retries. Only the host's ACTIVE-WORK gate decides whether to
|
|
113
|
+
* actually renew — a stalled/crashed holder is left to the stall-prompt + sweeper.
|
|
114
|
+
*
|
|
115
|
+
* @param thresholdFraction renew once past this fraction of the window
|
|
116
|
+
* (default 0.5 — renew around the halfway mark while genuinely working).
|
|
117
|
+
*/
|
|
118
|
+
dueForRenew(nowMs, thresholdFraction = 0.5) {
|
|
119
|
+
const due = [];
|
|
120
|
+
for (const lease of this.leases.values()) {
|
|
121
|
+
if (!lease.token)
|
|
122
|
+
continue; // can't renew without a token
|
|
123
|
+
const windowLen = lease.expiresAtMs - lease.windowStartMs;
|
|
124
|
+
if (windowLen <= 0)
|
|
125
|
+
continue;
|
|
126
|
+
const elapsed = nowMs - lease.windowStartMs;
|
|
127
|
+
if (elapsed >= thresholdFraction * windowLen && nowMs < lease.expiresAtMs) {
|
|
128
|
+
due.push({ workId: lease.workId, token: lease.token, msUntilExpiry: lease.expiresAtMs - nowMs });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return due;
|
|
132
|
+
}
|
|
98
133
|
/**
|
|
99
134
|
* Held leases that have crossed `thresholdFraction` of their current window
|
|
100
135
|
* but not yet expired, and have not been prompted this window. The host
|
|
@@ -126,6 +161,24 @@ export class WorkLeaseTracker {
|
|
|
126
161
|
if (lease)
|
|
127
162
|
lease.prompted = true;
|
|
128
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* work-165 (idea-358): drop every lease whose expiry has passed as of `nowMs`.
|
|
166
|
+
* Without this the Map only shrinks on an explicit close verb, so a lease reaped
|
|
167
|
+
* server-side (holder went silent) lingers forever — `size()`/`snapshot()` then
|
|
168
|
+
* mis-report a stale "holding", and long-lived processes leak the Map unbounded.
|
|
169
|
+
* Called on every `observe` and on the host heartbeat tick. Returns the count
|
|
170
|
+
* pruned (host may log it). Deleting the current key mid-Map-iteration is safe.
|
|
171
|
+
*/
|
|
172
|
+
prune(nowMs) {
|
|
173
|
+
let pruned = 0;
|
|
174
|
+
for (const [workId, lease] of this.leases) {
|
|
175
|
+
if (nowMs >= lease.expiresAtMs) {
|
|
176
|
+
this.leases.delete(workId);
|
|
177
|
+
pruned += 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return pruned;
|
|
181
|
+
}
|
|
129
182
|
/** Diagnostic/test accessor: number of currently-held tracked leases. */
|
|
130
183
|
size() {
|
|
131
184
|
return this.leases.size;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-lease-tracker.js","sourceRoot":"","sources":["../../../src/tool-manager/work-protocol/work-lease-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,wEAAwE;AACxE,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;AAC9E,uFAAuF;AACvF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,eAAe;IACf,cAAc;IACd,cAAc;IACd,YAAY;CACb,CAAC,CAAC;AAEH;oEACoE;AACpE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"work-lease-tracker.js","sourceRoot":"","sources":["../../../src/tool-manager/work-protocol/work-lease-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,wEAAwE;AACxE,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;AAC9E,uFAAuF;AACvF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,eAAe;IACf,cAAc;IACd,cAAc;IACd,YAAY;CACb,CAAC,CAAC;AAEH;oEACoE;AACpE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;AA8BlE;uFACuF;AACvF,SAAS,UAAU,CACjB,MAAe;IAEf,MAAM,EAAE,GAAI,MAAwC,EAAE,QAEzC,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClD,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC;IACtC,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAC/C,CAAC;AAED,sFAAsF;AACtF,SAAS,WAAW,CAClB,MAAe,EACf,IAAyC;IAEzC,MAAM,EAAE,GAAI,MAAiD,EAAE,QAAQ,EAAE,EAAE,CAAC;IAC5E,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,OAAO,gBAAgB;IACnB,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;IAEjD;;;;OAIG;IACH,OAAO,CACL,MAAc,EACd,IAAyC,EACzC,MAAe,EACf,KAAa;QAEb,6EAA6E;QAC7E,gFAAgF;QAChF,+EAA+E;QAC/E,2EAA2E;QAC3E,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACzC,IAAI,MAAM;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO;QAC1C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE;YAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,sEAAsE;YACtE,wDAAwD;YACxD,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAS,CAAC,aAAa;YAC5D,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAS,CAAC,QAAQ;YAClD,4EAA4E;YAC5E,8CAA8C;YAC9C,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,QAAQ,EAAE,KAAK,IAAI,EAAE;SAC7C,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;OAUG;IACH,WAAW,CAAC,KAAa,EAAE,iBAAiB,GAAG,GAAG;QAChD,MAAM,GAAG,GAAe,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,CAAC,KAAK;gBAAE,SAAS,CAAC,8BAA8B;YAC1D,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC;YAC1D,IAAI,SAAS,IAAI,CAAC;gBAAE,SAAS;YAC7B,MAAM,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC;YAC5C,IAAI,OAAO,IAAI,iBAAiB,GAAG,SAAS,IAAI,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;gBAC1E,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,WAAW,GAAG,KAAK,EAAE,CAAC,CAAC;YACnG,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,KAAa,EAAE,iBAAiB,GAAG,GAAG;QACtD,MAAM,GAAG,GAAkB,EAAE,CAAC;QAC9B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,IAAI,KAAK,CAAC,QAAQ;gBAAE,SAAS;YAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC;YAC1D,IAAI,SAAS,IAAI,CAAC;gBAAE,SAAS;YAC7B,MAAM,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC;YAC5C,qEAAqE;YACrE,sEAAsE;YACtE,IAAI,OAAO,IAAI,iBAAiB,GAAG,SAAS,IAAI,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;gBAC1E,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,WAAW,GAAG,KAAK,EAAE,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,8EAA8E;IAC9E,YAAY,CAAC,MAAc;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,KAAK;YAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;IACnC,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAa;QACjB,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1C,IAAI,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC3B,MAAM,IAAI,CAAC,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,yEAAyE;IACzE,IAAI;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED;;;;;;OAMG;IACH,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;aAC7B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa,CAAC;aACjD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACpE,CAAC;CACF"}
|