@circuitwall/jarela 1.2.0 → 1.4.0
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/chats/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/lookup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/pair/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/bridges/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +218 -7
- package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/events/route.js +3 -3
- package/.next/standalone/.next/server/app/api/v1/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/agents/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/fill/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/refine/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js +8 -1
- package/.next/standalone/.next/server/app/api/v1/extension/turn/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/extensions/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/extensions/tools/[name]/secrets/route.js +2 -2
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +37 -3
- package/.next/standalone/.next/server/app/api/v1/page-capture/route.js.map +1 -1
- package/.next/standalone/.next/server/app/api/v1/tools/route.js +2 -2
- package/.next/standalone/.next/server/app/page.js +10 -18
- package/.next/standalone/.next/server/app/page.js.map +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/210.js +1 -1
- package/.next/standalone/.next/server/chunks/239.js +5335 -5230
- package/.next/standalone/.next/server/chunks/239.js.map +1 -1
- package/.next/standalone/.next/server/chunks/{1683.js → 241.js} +210 -36
- package/.next/standalone/.next/server/chunks/241.js.map +1 -0
- package/.next/standalone/.next/server/chunks/{8135.js → 2539.js} +218 -36
- package/.next/standalone/.next/server/chunks/2539.js.map +1 -0
- package/.next/standalone/.next/server/chunks/4631.js +218 -7
- package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
- package/.next/standalone/.next/server/chunks/8866.js +13389 -13073
- package/.next/standalone/.next/server/chunks/8866.js.map +1 -1
- package/.next/standalone/.next/server/chunks/9032.js +1 -1
- package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/app/{page-62e0d5f2404b403b.js → page-74846c864241b96d.js} +11 -19
- package/.next/standalone/.next/static/chunks/app/page-74846c864241b96d.js.map +1 -0
- package/.next/standalone/package.json +2 -1
- package/CHANGELOG.md +98 -0
- package/README.md +51 -26
- package/components/chat/InputBar.tsx +10 -1
- package/components/ui/BootScreen.tsx +0 -10
- package/lib/agents/agent-turn.ts +9 -0
- package/lib/agents/prepare/request.ts +9 -0
- package/lib/agents/run-thread.ts +9 -1
- package/lib/api/extension-turn.ts +7 -0
- package/lib/api/page-capture.test.ts +58 -0
- package/lib/api/page-capture.ts +31 -1
- package/lib/bridges/attachment-store.test.ts +440 -0
- package/lib/bridges/attachment-store.ts +184 -0
- package/lib/bridges/whatsapp.ts +50 -32
- package/lib/tools/async-results-tool.ts +114 -0
- package/lib/tools/async-results.test.ts +481 -0
- package/lib/tools/async-results.ts +165 -0
- package/lib/tools/builtins.ts +1 -0
- package/lib/tools/wallclock.ts +114 -8
- package/package.json +2 -1
- package/.next/standalone/.next/server/chunks/1683.js.map +0 -1
- package/.next/standalone/.next/server/chunks/8135.js.map +0 -1
- package/.next/standalone/.next/static/chunks/app/page-62e0d5f2404b403b.js.map +0 -1
- /package/.next/standalone/.next/static/{2xWP8843jbntFGKLnHK6R → AV5AO0yTRABo-NgwxhDe7}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{2xWP8843jbntFGKLnHK6R → AV5AO0yTRABo-NgwxhDe7}/_ssgManifest.js +0 -0
|
@@ -47,6 +47,8 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
47
47
|
var schemas = __webpack_require__(49883);
|
|
48
48
|
// EXTERNAL MODULE: ./node_modules/@langchain/core/dist/tools/index.js
|
|
49
49
|
var tools = __webpack_require__(71890);
|
|
50
|
+
// EXTERNAL MODULE: ./lib/tools/async-results.ts
|
|
51
|
+
var async_results = __webpack_require__(95579);
|
|
50
52
|
;// ./lib/tools/wallclock.ts
|
|
51
53
|
// Per-call wall-clock budgets for tools.
|
|
52
54
|
//
|
|
@@ -71,19 +73,43 @@ var tools = __webpack_require__(71890);
|
|
|
71
73
|
// mechanism.
|
|
72
74
|
|
|
73
75
|
|
|
76
|
+
|
|
74
77
|
const DEFAULT_DEADLINE_MS = 120000;
|
|
75
|
-
|
|
78
|
+
// Hard ceiling so a runaway `deadline_ms` from the LLM (or a typo of
|
|
79
|
+
// "10 minutes" as 100_000_000) can't park a single turn for hours. The
|
|
80
|
+
// operator can raise or lower this with JARELA_TOOL_MAX_DEADLINE_MS
|
|
81
|
+
// (integer milliseconds). Anything above the ceiling is clamped and a
|
|
82
|
+
// warning is logged once per call.
|
|
83
|
+
const DEFAULT_MAX_DEADLINE_MS = 30 * 60 * 1000;
|
|
84
|
+
function getMaxDeadlineMs() {
|
|
85
|
+
const raw = process.env.JARELA_TOOL_MAX_DEADLINE_MS;
|
|
86
|
+
if (!raw) return DEFAULT_MAX_DEADLINE_MS;
|
|
87
|
+
const n = Number(raw);
|
|
88
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_MAX_DEADLINE_MS;
|
|
89
|
+
}
|
|
90
|
+
const DEADLINE_DESCRIPTION = "Optional wall-clock budget for this tool call in milliseconds (default 120000, hard ceiling 1800000 / 30 min). " + "When the budget is exceeded the call returns a structured timeout result " + "and the turn continues so you can recover — pick a value that matches " + "the expected duration (5000-15000 for fast local ops, 30000-90000 for " + "network/web calls, larger for shell commands that may build or install). " + "Values above the ceiling are clamped; use `async_run: true` for work that genuinely needs more.";
|
|
91
|
+
const ASYNC_RUN_DESCRIPTION = "Optional. Set true to fire this tool in the background and get back a " + "tracking key immediately instead of waiting for the result. Use for " + "long-running calls (large fetches, slow shell builds) when you want to " + "keep the conversation moving. Retrieve the result later by calling " + "`tool_result_get` with the returned key. The original `deadline_ms` " + "still applies — it just runs in the background.";
|
|
76
92
|
function wrapWithWallclock(t) {
|
|
77
93
|
// Only zod-object schemas can be extended with `deadline_ms`. Other
|
|
78
94
|
// schema shapes (raw JSON Schema, ZodString) pass through unchanged.
|
|
79
95
|
// Those tools still get the wallclock race using the default budget.
|
|
80
96
|
const schema = t.schema;
|
|
81
97
|
const extendedSchema = schema instanceof schemas/* ZodObject */.bv ? schema.extend({
|
|
82
|
-
deadline_ms: schemas/* number */.ai().int().positive().optional().describe(DEADLINE_DESCRIPTION)
|
|
98
|
+
deadline_ms: schemas/* number */.ai().int().positive().optional().describe(DEADLINE_DESCRIPTION),
|
|
99
|
+
async_run: schemas/* boolean */.zM().optional().describe(ASYNC_RUN_DESCRIPTION)
|
|
83
100
|
}) : null;
|
|
84
101
|
const wrappedFunc = async (args, config)=>{
|
|
85
|
-
const
|
|
86
|
-
const
|
|
102
|
+
const requested = readDeadlineMs(args) ?? DEFAULT_DEADLINE_MS;
|
|
103
|
+
const ceiling = getMaxDeadlineMs();
|
|
104
|
+
const deadlineMs = Math.min(requested, ceiling);
|
|
105
|
+
if (requested > ceiling) {
|
|
106
|
+
console.warn(`[wallclock] tool="${t.name}" requested deadline_ms=${requested} exceeds ceiling ${ceiling}; clamped. ` + `Use async_run: true for work that genuinely needs more, or raise JARELA_TOOL_MAX_DEADLINE_MS.`);
|
|
107
|
+
}
|
|
108
|
+
const asyncRun = readAsyncRun(args);
|
|
109
|
+
const innerArgs = stripWrapperFields(args);
|
|
110
|
+
if (asyncRun) {
|
|
111
|
+
return runAsync(t, innerArgs, config, deadlineMs);
|
|
112
|
+
}
|
|
87
113
|
let timer;
|
|
88
114
|
const timeoutPromise = new Promise((resolve)=>{
|
|
89
115
|
timer = setTimeout(()=>{
|
|
@@ -125,12 +151,56 @@ function readDeadlineMs(args) {
|
|
|
125
151
|
const v = args.deadline_ms;
|
|
126
152
|
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null;
|
|
127
153
|
}
|
|
128
|
-
function
|
|
154
|
+
function readAsyncRun(args) {
|
|
155
|
+
if (!args || typeof args !== "object") return false;
|
|
156
|
+
return args.async_run === true;
|
|
157
|
+
}
|
|
158
|
+
function stripWrapperFields(args) {
|
|
129
159
|
if (!args || typeof args !== "object") return args;
|
|
130
|
-
const { deadline_ms:
|
|
131
|
-
void
|
|
160
|
+
const { deadline_ms: _d, async_run: _a, ...rest } = args;
|
|
161
|
+
void _d;
|
|
162
|
+
void _a;
|
|
132
163
|
return rest;
|
|
133
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Fire the tool detached and return a pointer the agent can poll via
|
|
167
|
+
* `tool_result_get`. The same `deadline_ms` still applies — when the
|
|
168
|
+
* timer wins, the slot is marked errored with a timeout message.
|
|
169
|
+
*/ function runAsync(t, innerArgs, config, deadlineMs) {
|
|
170
|
+
const key = (0,async_results/* startAsyncCall */.f3)(t.name);
|
|
171
|
+
const startedAt = Date.now();
|
|
172
|
+
let settled = false;
|
|
173
|
+
const timer = setTimeout(()=>{
|
|
174
|
+
if (settled) return;
|
|
175
|
+
settled = true;
|
|
176
|
+
(0,async_results/* failAsyncCall */.zc)(key, `Tool "${t.name}" exceeded its background wall-clock budget of ${deadlineMs}ms. ` + "The underlying operation may still be running but its result is discarded.");
|
|
177
|
+
}, deadlineMs);
|
|
178
|
+
timer.unref?.();
|
|
179
|
+
void (async ()=>{
|
|
180
|
+
try {
|
|
181
|
+
const work = t.invoke(innerArgs, config);
|
|
182
|
+
const result = await work;
|
|
183
|
+
if (settled) return;
|
|
184
|
+
settled = true;
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
(0,async_results/* completeAsyncCall */.eN)(key, typeof result === "string" ? result : JSON.stringify(result));
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (settled) return;
|
|
189
|
+
settled = true;
|
|
190
|
+
clearTimeout(timer);
|
|
191
|
+
(0,async_results/* failAsyncCall */.zc)(key, err);
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
return JSON.stringify({
|
|
195
|
+
ok: true,
|
|
196
|
+
async: true,
|
|
197
|
+
key,
|
|
198
|
+
tool: t.name,
|
|
199
|
+
started_at: startedAt,
|
|
200
|
+
deadline_ms: deadlineMs,
|
|
201
|
+
hint: `Call tool_result_get with key="${key}" to retrieve the result. Pass wait_ms to short-poll.`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
134
204
|
/** Exposed for the tests. */ const __DEFAULT_DEADLINE_MS = (/* unused pure expression or super */ null && (DEFAULT_DEADLINE_MS));
|
|
135
205
|
|
|
136
206
|
;// ./lib/tools/registry.ts
|
|
@@ -6539,6 +6609,147 @@ function getDocumentSourceStats(sourceId) {
|
|
|
6539
6609
|
}
|
|
6540
6610
|
|
|
6541
6611
|
|
|
6612
|
+
/***/ }),
|
|
6613
|
+
|
|
6614
|
+
/***/ 95579:
|
|
6615
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
6616
|
+
|
|
6617
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
6618
|
+
/* harmony export */ H0: () => (/* binding */ listAsyncResults),
|
|
6619
|
+
/* harmony export */ Ss: () => (/* binding */ getAsyncResult),
|
|
6620
|
+
/* harmony export */ eN: () => (/* binding */ completeAsyncCall),
|
|
6621
|
+
/* harmony export */ f3: () => (/* binding */ startAsyncCall),
|
|
6622
|
+
/* harmony export */ m3: () => (/* binding */ consumeAsyncResult),
|
|
6623
|
+
/* harmony export */ zc: () => (/* binding */ failAsyncCall)
|
|
6624
|
+
/* harmony export */ });
|
|
6625
|
+
/* unused harmony exports DEFAULT_TTL_MS, MAX_ENTRIES, sweepExpired, __resetStore, __backdateFinished */
|
|
6626
|
+
/* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(77598);
|
|
6627
|
+
/* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_crypto__WEBPACK_IMPORTED_MODULE_0__);
|
|
6628
|
+
// In-process keyed store for async tool results.
|
|
6629
|
+
//
|
|
6630
|
+
// When the wallclock wrapper sees `async_run: true` on a tool call it
|
|
6631
|
+
// returns immediately with a key, kicks the real invocation off in the
|
|
6632
|
+
// background, and parks the eventual result here. The agent later
|
|
6633
|
+
// retrieves the result via the `tool_result_get` built-in.
|
|
6634
|
+
//
|
|
6635
|
+
// Scope is deliberately per-process, in-memory:
|
|
6636
|
+
// - The data lives only as long as the Next.js server process. A
|
|
6637
|
+
// restart wipes both the agent's in-context keys and these results
|
|
6638
|
+
// simultaneously, so we can't end up with the LLM holding a key
|
|
6639
|
+
// that survived the value.
|
|
6640
|
+
// - No on-disk persistence means no schema migration, no PII spill,
|
|
6641
|
+
// no risk of leaking long-running secrets between sessions.
|
|
6642
|
+
//
|
|
6643
|
+
// Memory hygiene:
|
|
6644
|
+
// - TTL (DEFAULT_TTL_MS) caps how long a finished result hangs around
|
|
6645
|
+
// unread. A background sweeper runs on a slow interval.
|
|
6646
|
+
// - Cap on concurrent entries (MAX_ENTRIES). When exceeded, the
|
|
6647
|
+
// oldest *finished* entry is evicted first; if none, the oldest
|
|
6648
|
+
// pending entry is dropped (with a console warn).
|
|
6649
|
+
|
|
6650
|
+
/** How long a finished result stays around if nobody reads it. */ const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
6651
|
+
/** Soft cap on total entries (pending + finished). */ const MAX_ENTRIES = 256;
|
|
6652
|
+
/** How often the background sweeper runs. */ const SWEEP_INTERVAL_MS = 60 * 1000;
|
|
6653
|
+
const STORE = new Map();
|
|
6654
|
+
let sweeper = null;
|
|
6655
|
+
function ensureSweeper() {
|
|
6656
|
+
if (sweeper) return;
|
|
6657
|
+
sweeper = setInterval(()=>{
|
|
6658
|
+
sweepExpired(DEFAULT_TTL_MS);
|
|
6659
|
+
}, SWEEP_INTERVAL_MS);
|
|
6660
|
+
sweeper.unref?.();
|
|
6661
|
+
}
|
|
6662
|
+
/**
|
|
6663
|
+
* Carve out a slot for a new async tool call and return its key.
|
|
6664
|
+
* The key is opaque and URL-safe — the agent treats it as a token.
|
|
6665
|
+
*/ function startAsyncCall(tool) {
|
|
6666
|
+
ensureSweeper();
|
|
6667
|
+
enforceCap();
|
|
6668
|
+
const key = `async_${node_crypto__WEBPACK_IMPORTED_MODULE_0___default().randomBytes(8).toString("hex")}`;
|
|
6669
|
+
STORE.set(key, {
|
|
6670
|
+
key,
|
|
6671
|
+
tool,
|
|
6672
|
+
status: "pending",
|
|
6673
|
+
started_at: Date.now(),
|
|
6674
|
+
finished_at: null,
|
|
6675
|
+
result: null,
|
|
6676
|
+
error: null
|
|
6677
|
+
});
|
|
6678
|
+
return key;
|
|
6679
|
+
}
|
|
6680
|
+
/** Mark a pending call as completed successfully. */ function completeAsyncCall(key, result) {
|
|
6681
|
+
const rec = STORE.get(key);
|
|
6682
|
+
if (!rec) return;
|
|
6683
|
+
rec.status = "done";
|
|
6684
|
+
rec.result = result;
|
|
6685
|
+
rec.finished_at = Date.now();
|
|
6686
|
+
}
|
|
6687
|
+
/** Mark a pending call as failed. */ function failAsyncCall(key, err) {
|
|
6688
|
+
const rec = STORE.get(key);
|
|
6689
|
+
if (!rec) return;
|
|
6690
|
+
rec.status = "error";
|
|
6691
|
+
rec.error = err instanceof Error ? err.message : String(err);
|
|
6692
|
+
rec.finished_at = Date.now();
|
|
6693
|
+
}
|
|
6694
|
+
/** Read a record without consuming it. */ function getAsyncResult(key) {
|
|
6695
|
+
return STORE.get(key) ?? null;
|
|
6696
|
+
}
|
|
6697
|
+
/** Read and immediately delete a record. */ function consumeAsyncResult(key) {
|
|
6698
|
+
const rec = STORE.get(key);
|
|
6699
|
+
if (!rec) return null;
|
|
6700
|
+
STORE.delete(key);
|
|
6701
|
+
return rec;
|
|
6702
|
+
}
|
|
6703
|
+
/** Snapshot of all current records (newest first). For tool_result_list. */ function listAsyncResults() {
|
|
6704
|
+
return [
|
|
6705
|
+
...STORE.values()
|
|
6706
|
+
].sort((a, b)=>b.started_at - a.started_at);
|
|
6707
|
+
}
|
|
6708
|
+
/**
|
|
6709
|
+
* Drop finished entries older than `ttlMs` (measured from `finished_at`).
|
|
6710
|
+
* Pending entries are never expired here — a stuck tool would otherwise
|
|
6711
|
+
* vanish out from under the agent.
|
|
6712
|
+
*/ function sweepExpired(ttlMs) {
|
|
6713
|
+
const now = Date.now();
|
|
6714
|
+
let removed = 0;
|
|
6715
|
+
for (const [k, r] of STORE){
|
|
6716
|
+
if (r.status === "pending") continue;
|
|
6717
|
+
if (r.finished_at == null) continue;
|
|
6718
|
+
if (now - r.finished_at >= ttlMs) {
|
|
6719
|
+
STORE.delete(k);
|
|
6720
|
+
removed++;
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
return removed;
|
|
6724
|
+
}
|
|
6725
|
+
function enforceCap() {
|
|
6726
|
+
if (STORE.size < MAX_ENTRIES) return;
|
|
6727
|
+
// Prefer evicting finished entries (oldest first). Only if every entry
|
|
6728
|
+
// is pending do we drop a pending one.
|
|
6729
|
+
const sorted = [
|
|
6730
|
+
...STORE.values()
|
|
6731
|
+
].sort((a, b)=>a.started_at - b.started_at);
|
|
6732
|
+
const finished = sorted.find((r)=>r.status !== "pending");
|
|
6733
|
+
const victim = finished ?? sorted[0];
|
|
6734
|
+
if (!victim) return;
|
|
6735
|
+
STORE.delete(victim.key);
|
|
6736
|
+
if (!finished) {
|
|
6737
|
+
console.warn(`[async-results] evicted pending entry ${victim.key} (tool=${victim.tool}) ` + `to make room — STORE cap of ${MAX_ENTRIES} hit.`);
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
/** Test-only helper. */ function __resetStore() {
|
|
6741
|
+
STORE.clear();
|
|
6742
|
+
if (sweeper) {
|
|
6743
|
+
clearInterval(sweeper);
|
|
6744
|
+
sweeper = null;
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
/** Test-only helper. */ function __backdateFinished(key, finishedAt) {
|
|
6748
|
+
const rec = STORE.get(key);
|
|
6749
|
+
if (rec) rec.finished_at = finishedAt;
|
|
6750
|
+
}
|
|
6751
|
+
|
|
6752
|
+
|
|
6542
6753
|
/***/ })
|
|
6543
6754
|
|
|
6544
6755
|
};
|