@mjasnikovs/pi-task 0.18.37 → 0.18.39
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/README.md +2 -1
- package/dist/config/config.d.ts +32 -0
- package/dist/config/config.js +22 -0
- package/dist/config/register.js +25 -1
- package/dist/index.js +2 -0
- package/dist/shared/child-process.d.ts +20 -0
- package/dist/shared/child-process.js +46 -2
- package/dist/shared/stream-watchdog.d.ts +119 -0
- package/dist/shared/stream-watchdog.js +182 -0
- package/dist/task/auto-orchestrator.js +7 -5
- package/dist/task/child-runner.js +18 -2
- package/dist/task/gate-deps.js +8 -0
- package/dist/task/stream-watchdog.d.ts +32 -0
- package/dist/task/stream-watchdog.js +90 -0
- package/dist/workers/pi-worker-core.d.ts +20 -0
- package/dist/workers/pi-worker-core.js +14 -0
- package/dist/workers/pi-worker-docs.d.ts +7 -0
- package/dist/workers/pi-worker-docs.js +16 -0
- package/dist/workers/research-cache.d.ts +25 -17
- package/dist/workers/research-cache.js +122 -58
- package/dist/workers/shared.d.ts +8 -0
- package/dist/workers/shared.js +0 -0
- package/package.json +1 -1
|
@@ -12,6 +12,8 @@ import { childBaseArgs } from '../shared/child-extensions.js';
|
|
|
12
12
|
import { LoopDetector } from './loop-detector.js';
|
|
13
13
|
import { detectLeakedToolCall, leakedToolCallHint, MAX_LEAK_RETRIES } from '../shared/leaked-tool-call.js';
|
|
14
14
|
import { readSection, setTaskSection } from './task-io.js';
|
|
15
|
+
import { streamStallCause } from '../shared/stream-watchdog.js';
|
|
16
|
+
import { getConfig } from '../config/config.js';
|
|
15
17
|
// ─── Loop detection constants ────────────────────────────────────────────────
|
|
16
18
|
// Defined here (not in phases.ts) to avoid a circular dependency:
|
|
17
19
|
// phases.ts → child-runner.ts → phases.ts
|
|
@@ -77,6 +79,11 @@ export async function runChild(cwd, tools, prompt, signal, onLine, onContextUsag
|
|
|
77
79
|
let loopHit;
|
|
78
80
|
const result = await runChildUnified(spawnFn ?? spawn, invocation, cwd, signal, {
|
|
79
81
|
mode: 'json-events',
|
|
82
|
+
// A hung model stream reports nothing at all, so without this the
|
|
83
|
+
// phase child waits forever (mx5 run 14: ~2.9h of dead air). The kill
|
|
84
|
+
// is reported below as a connection-class cause, which routes it into
|
|
85
|
+
// the retry/backoff path this file already has for a LOUD disconnect.
|
|
86
|
+
streamInactivityMs: getConfig().streamInactivityMs,
|
|
80
87
|
onLine,
|
|
81
88
|
onContextUsage,
|
|
82
89
|
onToolCall: call => {
|
|
@@ -95,12 +102,21 @@ export async function runChild(cwd, tools, prompt, signal, onLine, onContextUsag
|
|
|
95
102
|
// fails with the unhelpful "X child produced no output" — the raw
|
|
96
103
|
// stdout/stderr that might contain the real error is discarded.
|
|
97
104
|
const text = result.text || result.stdout.trim();
|
|
105
|
+
// The stream watchdog's kill leaves no provider error to report (that is the
|
|
106
|
+
// whole failure mode), so name it here rather than letting it surface as the
|
|
107
|
+
// meaningless "produced no output". Never overwrite a real reported cause.
|
|
108
|
+
const modelError = result.modelError
|
|
109
|
+
?? (result.streamStalled ? streamStallCause(result.streamStalled.idleMs) : undefined);
|
|
98
110
|
return {
|
|
99
111
|
text,
|
|
100
|
-
|
|
112
|
+
// WE killed this child, so its exit status describes our own SIGTERM, not
|
|
113
|
+
// the child's verdict. Report 0 and let `modelError` carry the cause —
|
|
114
|
+
// otherwise the wrappers' `exitCode !== 0` guard throws a bare "child
|
|
115
|
+
// failed" before the connection-error retry ever gets to look.
|
|
116
|
+
exitCode: result.streamStalled ? 0 : result.exitCode,
|
|
101
117
|
stderr: result.stderr.trim(),
|
|
102
118
|
loopHit,
|
|
103
|
-
modelError
|
|
119
|
+
modelError,
|
|
104
120
|
// A tool call the model wrote as text (wrong dialect) never executed and
|
|
105
121
|
// sailed past the structured-event guards above; flag it so the wrappers
|
|
106
122
|
// can re-prompt instead of accepting the unexecuted call. Only meaningful
|
package/dist/task/gate-deps.js
CHANGED
|
@@ -391,6 +391,11 @@ export function buildGateDeps(params) {
|
|
|
391
391
|
// ceiling the main session uses, so one /task-config knob
|
|
392
392
|
// covers implementation and gates alike.
|
|
393
393
|
commandTimeoutMs: getConfig().requestTimeoutMs,
|
|
394
|
+
// Same reasoning one level up: a gate child with no
|
|
395
|
+
// wall-clock cap also needs the HUNG-STREAM bound, which
|
|
396
|
+
// the probe-based stall guard structurally cannot supply
|
|
397
|
+
// (a healthy endpoint reads as proof of life).
|
|
398
|
+
streamInactivityMs: getConfig().streamInactivityMs,
|
|
394
399
|
loop: { pathThreshold: Number.POSITIVE_INFINITY },
|
|
395
400
|
onLine: line => {
|
|
396
401
|
lastLine = line;
|
|
@@ -538,6 +543,9 @@ export function buildGateDeps(params) {
|
|
|
538
543
|
// bash — wired anyway so a future tool grant can't quietly
|
|
539
544
|
// re-open the hole.
|
|
540
545
|
commandTimeoutMs: getConfig().requestTimeoutMs,
|
|
546
|
+
// Unbounded wall clock here too — the hung-stream
|
|
547
|
+
// bound is the only thing that ends a dead stream.
|
|
548
|
+
streamInactivityMs: getConfig().streamInactivityMs,
|
|
541
549
|
// Exact-match loop guard only: pathThreshold Infinity
|
|
542
550
|
// disables the path-revisit heuristic, so revisiting one
|
|
543
551
|
// file (which IS this pass's job) never trips — only a
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
/**
|
|
3
|
+
* MAIN-SESSION adapter for the model-stream watchdog.
|
|
4
|
+
*
|
|
5
|
+
* WHY (mx5 run 14): three implementation turns died mid-turn — the session jsonl's
|
|
6
|
+
* last record is an ordinary assistant message, then silence forever, while the
|
|
7
|
+
* model container stayed Up(healthy). No error is ever thrown for this shape, so
|
|
8
|
+
* the connection-error retry (which needs a reported ModelError) cannot fire and
|
|
9
|
+
* the command watchdog, which only covers tool executions, never arms. The run sat
|
|
10
|
+
* dead for ~2.9h across the three until a human restarted it.
|
|
11
|
+
*
|
|
12
|
+
* HOW: pi's extension events ARE the stream. Any of them — a token delta, a
|
|
13
|
+
* thinking delta, a tool-call delta, the provider's response headers — resets the
|
|
14
|
+
* idle clock; only total silence for the configured window fires. On fire the turn
|
|
15
|
+
* is aborted and a follow-up user turn tells the model to CONTINUE from the
|
|
16
|
+
* transcript (its completed tool calls and results are already recorded, so a
|
|
17
|
+
* blind re-send would re-run them).
|
|
18
|
+
*
|
|
19
|
+
* ONE ABORT CHANNEL: the fire path goes through the command watchdog's existing
|
|
20
|
+
* {@link noteWatchdogAbort} flag and its WATCHDOG_CANCEL_MARKER, so
|
|
21
|
+
* steerUntilDone's already-fixed abort/steer race (b543d15) covers this watchdog
|
|
22
|
+
* too instead of racing a second, parallel abort mechanism.
|
|
23
|
+
*
|
|
24
|
+
* SUSPENDED DURING TOOLS: while a tool executes the model stream is legitimately
|
|
25
|
+
* idle — a 12-minute build emits nothing. That window belongs to the command
|
|
26
|
+
* watchdog (requestTimeoutMs); this one pauses between tool_execution_start and
|
|
27
|
+
* tool_execution_end so the two can never double-fire on the same silence.
|
|
28
|
+
*
|
|
29
|
+
* SCOPE: main session only. Children run `--no-extensions`, so their equivalent
|
|
30
|
+
* guard lives in runChild (shared/child-process.ts) and shares the same machine.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerStreamWatchdog(pi: ExtensionAPI): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getConfig } from '../config/config.js';
|
|
2
|
+
import { realStreamTimerDeps, StreamWatchdog, streamStallReminder } from '../shared/stream-watchdog.js';
|
|
3
|
+
import { noteWatchdogAbort, WATCHDOG_CANCEL_MARKER } from './command-watchdog.js';
|
|
4
|
+
/**
|
|
5
|
+
* MAIN-SESSION adapter for the model-stream watchdog.
|
|
6
|
+
*
|
|
7
|
+
* WHY (mx5 run 14): three implementation turns died mid-turn — the session jsonl's
|
|
8
|
+
* last record is an ordinary assistant message, then silence forever, while the
|
|
9
|
+
* model container stayed Up(healthy). No error is ever thrown for this shape, so
|
|
10
|
+
* the connection-error retry (which needs a reported ModelError) cannot fire and
|
|
11
|
+
* the command watchdog, which only covers tool executions, never arms. The run sat
|
|
12
|
+
* dead for ~2.9h across the three until a human restarted it.
|
|
13
|
+
*
|
|
14
|
+
* HOW: pi's extension events ARE the stream. Any of them — a token delta, a
|
|
15
|
+
* thinking delta, a tool-call delta, the provider's response headers — resets the
|
|
16
|
+
* idle clock; only total silence for the configured window fires. On fire the turn
|
|
17
|
+
* is aborted and a follow-up user turn tells the model to CONTINUE from the
|
|
18
|
+
* transcript (its completed tool calls and results are already recorded, so a
|
|
19
|
+
* blind re-send would re-run them).
|
|
20
|
+
*
|
|
21
|
+
* ONE ABORT CHANNEL: the fire path goes through the command watchdog's existing
|
|
22
|
+
* {@link noteWatchdogAbort} flag and its WATCHDOG_CANCEL_MARKER, so
|
|
23
|
+
* steerUntilDone's already-fixed abort/steer race (b543d15) covers this watchdog
|
|
24
|
+
* too instead of racing a second, parallel abort mechanism.
|
|
25
|
+
*
|
|
26
|
+
* SUSPENDED DURING TOOLS: while a tool executes the model stream is legitimately
|
|
27
|
+
* idle — a 12-minute build emits nothing. That window belongs to the command
|
|
28
|
+
* watchdog (requestTimeoutMs); this one pauses between tool_execution_start and
|
|
29
|
+
* tool_execution_end so the two can never double-fire on the same silence.
|
|
30
|
+
*
|
|
31
|
+
* SCOPE: main session only. Children run `--no-extensions`, so their equivalent
|
|
32
|
+
* guard lives in runChild (shared/child-process.ts) and shares the same machine.
|
|
33
|
+
*/
|
|
34
|
+
export function registerStreamWatchdog(pi) {
|
|
35
|
+
// The ctx whose abort() ends the in-flight turn, refreshed on every event so
|
|
36
|
+
// the fire (which happens outside any handler) aborts the CURRENT operation.
|
|
37
|
+
let liveCtx;
|
|
38
|
+
const watchdog = new StreamWatchdog({
|
|
39
|
+
getTimeoutMs: () => getConfig().streamInactivityMs,
|
|
40
|
+
...realStreamTimerDeps,
|
|
41
|
+
onFire: idleMs => {
|
|
42
|
+
const ctx = liveCtx;
|
|
43
|
+
liveCtx = undefined;
|
|
44
|
+
// Flag BEFORE the abort, exactly as the command watchdog does: the
|
|
45
|
+
// steer loop can otherwise observe the 'aborted' turn first and show
|
|
46
|
+
// a steering prompt to an empty room, wedging an unattended run.
|
|
47
|
+
if (ctx) {
|
|
48
|
+
noteWatchdogAbort();
|
|
49
|
+
ctx.abort();
|
|
50
|
+
}
|
|
51
|
+
pi.sendUserMessage(streamStallReminder(idleMs, WATCHDOG_CANCEL_MARKER), {
|
|
52
|
+
deliverAs: 'followUp'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Any event proves the stream is alive. `arm` also (re)starts the machine, so
|
|
57
|
+
// a request that begins after a previous turn ended is watched again without
|
|
58
|
+
// needing a single canonical "request started" event.
|
|
59
|
+
const arm = (ctx) => {
|
|
60
|
+
if (ctx)
|
|
61
|
+
liveCtx = ctx;
|
|
62
|
+
watchdog.start();
|
|
63
|
+
watchdog.note();
|
|
64
|
+
};
|
|
65
|
+
pi.on('before_provider_request', (_e, ctx) => arm(ctx));
|
|
66
|
+
pi.on('after_provider_response', (_e, ctx) => arm(ctx));
|
|
67
|
+
pi.on('turn_start', (_e, ctx) => arm(ctx));
|
|
68
|
+
pi.on('message_start', (_e, ctx) => arm(ctx));
|
|
69
|
+
pi.on('message_update', (_e, ctx) => arm(ctx));
|
|
70
|
+
pi.on('message_end', (_e, ctx) => arm(ctx));
|
|
71
|
+
pi.on('tool_execution_start', (_e, ctx) => {
|
|
72
|
+
liveCtx = ctx;
|
|
73
|
+
watchdog.suspend();
|
|
74
|
+
});
|
|
75
|
+
pi.on('tool_execution_update', (_e, ctx) => {
|
|
76
|
+
liveCtx = ctx;
|
|
77
|
+
});
|
|
78
|
+
pi.on('tool_execution_end', (_e, ctx) => {
|
|
79
|
+
liveCtx = ctx;
|
|
80
|
+
watchdog.resume();
|
|
81
|
+
});
|
|
82
|
+
// Nothing is streaming between agent loops; stop so no timer can fire into an
|
|
83
|
+
// idle session (which would abort nothing and post a reminder to no one).
|
|
84
|
+
const stop = () => {
|
|
85
|
+
watchdog.stop();
|
|
86
|
+
liveCtx = undefined;
|
|
87
|
+
};
|
|
88
|
+
pi.on('agent_end', stop);
|
|
89
|
+
pi.on('session_shutdown', stop);
|
|
90
|
+
}
|
|
@@ -74,6 +74,16 @@ export interface RunWorkerInput {
|
|
|
74
74
|
afterMs?: number;
|
|
75
75
|
probe?: () => Promise<boolean>;
|
|
76
76
|
} | false;
|
|
77
|
+
/**
|
|
78
|
+
* Stream-inactivity ceiling in ms (shared/stream-watchdog.ts). The stall guard
|
|
79
|
+
* above cannot catch a HUNG stream on a HEALTHY backend — it reads a reachable
|
|
80
|
+
* endpoint as proof of life, which is exactly what run 14's three hangs looked
|
|
81
|
+
* like. This one asks nothing of the backend: no output for this long (with
|
|
82
|
+
* tool executions excluded) ⇒ kill and restart the attempt with
|
|
83
|
+
* {@link streamStallHint}, inside the same shared restart budget.
|
|
84
|
+
* 0 / omitted = off.
|
|
85
|
+
*/
|
|
86
|
+
streamInactivityMs?: number;
|
|
77
87
|
}
|
|
78
88
|
export interface RunWorkerResult {
|
|
79
89
|
text: string;
|
|
@@ -129,6 +139,16 @@ export interface RunWorkerResult {
|
|
|
129
139
|
toolName: string;
|
|
130
140
|
timeoutMs: number;
|
|
131
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Set when the stream watchdog killed the worker's FINAL attempt: the model
|
|
144
|
+
* stream produced nothing for the configured window while no tool was running.
|
|
145
|
+
* Like loopHit/timedOut the text is partial — treat as a failure, and check it
|
|
146
|
+
* BEFORE `aborted` (the kill aborts too), or a hung backend is mislabeled a
|
|
147
|
+
* user cancel.
|
|
148
|
+
*/
|
|
149
|
+
streamStalled?: {
|
|
150
|
+
idleMs: number;
|
|
151
|
+
};
|
|
132
152
|
}
|
|
133
153
|
/**
|
|
134
154
|
* The per-command ceiling for attempt N, halving each time a hang recurs.
|
|
@@ -6,6 +6,7 @@ import { LoopDetector } from '../task/loop-detector.js';
|
|
|
6
6
|
import { LOOP_WINDOW, LOOP_THRESHOLD, MAX_LOOP_RESTARTS, formatLoopHint } from '../task/child-runner.js';
|
|
7
7
|
import { detectLeakedToolCall, leakedToolCallHint, MAX_LEAK_RETRIES } from '../shared/leaked-tool-call.js';
|
|
8
8
|
import { discoverModelEndpoints, probeModelEndpoints } from '../shared/model-endpoint.js';
|
|
9
|
+
import { streamStallHint } from '../shared/stream-watchdog.js';
|
|
9
10
|
// `--mode json` makes pi emit structured events as they happen instead of
|
|
10
11
|
// buffering the assistant text and flushing on exit. That matters for the
|
|
11
12
|
// wait/work timing split: in text mode the first stdout chunk only arrives at
|
|
@@ -207,6 +208,9 @@ export async function runWorker(input) {
|
|
|
207
208
|
?? (() => probeModelEndpoints(discoverModelEndpoints()))
|
|
208
209
|
}
|
|
209
210
|
}),
|
|
211
|
+
...(input.streamInactivityMs ?
|
|
212
|
+
{ streamInactivityMs: input.streamInactivityMs }
|
|
213
|
+
: {}),
|
|
210
214
|
onFirstByte: () => (tFirstByte = Date.now()),
|
|
211
215
|
onToolCall: call => {
|
|
212
216
|
cmdWatch?.onStart(call);
|
|
@@ -240,6 +244,7 @@ export async function runWorker(input) {
|
|
|
240
244
|
const text = result.text ?? '';
|
|
241
245
|
const timedOut = timeout.timedOut();
|
|
242
246
|
const commandKill = cmdWatch?.killed();
|
|
247
|
+
const streamStalled = result.streamStalled;
|
|
243
248
|
// A loop-kill gets the same restart-with-hint treatment every other phase
|
|
244
249
|
// already gets (runPhaseWithLoopGuard) — name the offending call so the
|
|
245
250
|
// re-spawn avoids it. Bounded by the shared restart budget.
|
|
@@ -265,6 +270,14 @@ export async function runWorker(input) {
|
|
|
265
270
|
hangKills++;
|
|
266
271
|
continue;
|
|
267
272
|
}
|
|
273
|
+
// A hung model stream is restartable on the same budget. Checked before
|
|
274
|
+
// the wall-clock timeout because it is the more specific diagnosis (and
|
|
275
|
+
// its hint does not blame the model: nothing it did caused the hang).
|
|
276
|
+
if (streamStalled && !loopHit && restarts < MAX_LOOP_RESTARTS) {
|
|
277
|
+
hint = streamStallHint(streamStalled.idleMs);
|
|
278
|
+
restarts++;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
268
281
|
// A wall-clock timeout (the backstop for varied thrash the exact-match
|
|
269
282
|
// detector misses) is also restartable, sharing the same budget. Skip when
|
|
270
283
|
// a loop also tripped — the loop hint above is more specific.
|
|
@@ -293,6 +306,7 @@ export async function runWorker(input) {
|
|
|
293
306
|
...(loopHit ? { loopHit } : {}),
|
|
294
307
|
...(timedOut ? { timedOut: true } : {}),
|
|
295
308
|
...(result.stalled ? { stalled: true } : {}),
|
|
309
|
+
...(streamStalled ? { streamStalled } : {}),
|
|
296
310
|
...(commandKill ?
|
|
297
311
|
{
|
|
298
312
|
commandTimedOut: {
|
|
@@ -5,6 +5,13 @@ import { resolvePackage as defaultResolvePackage } from './docs-resolve.js';
|
|
|
5
5
|
import { retrieveChunks as defaultRetrieveChunks } from './docs-retrieve.js';
|
|
6
6
|
import { npmVersionLookup as defaultNpmVersionLookup } from './npm-version.js';
|
|
7
7
|
import { type SpawnFn } from '../shared/child-process.js';
|
|
8
|
+
/**
|
|
9
|
+
* The package NAME a module specifier belongs to — `hono/client` → `hono`,
|
|
10
|
+
* `@scope/name/sub` → `@scope/name`. The cache stores this (not the raw specifier) as an
|
|
11
|
+
* entry's package provenance, so a subpath lookup is matched against package.json's key
|
|
12
|
+
* and invalidated with its package rather than living forever unmatched.
|
|
13
|
+
*/
|
|
14
|
+
export declare function packageRootOf(module: string): string;
|
|
8
15
|
export interface PiWorkerDocsInternals {
|
|
9
16
|
resolvePackage?: typeof defaultResolvePackage;
|
|
10
17
|
ensureIndexed?: typeof defaultEnsureIndexed;
|
|
@@ -21,6 +21,16 @@ const Params = Type.Object({
|
|
|
21
21
|
description: 'What to extract from the docs. The child pi reads ranked chunks and returns ONLY content answering this.'
|
|
22
22
|
})
|
|
23
23
|
});
|
|
24
|
+
/**
|
|
25
|
+
* The package NAME a module specifier belongs to — `hono/client` → `hono`,
|
|
26
|
+
* `@scope/name/sub` → `@scope/name`. The cache stores this (not the raw specifier) as an
|
|
27
|
+
* entry's package provenance, so a subpath lookup is matched against package.json's key
|
|
28
|
+
* and invalidated with its package rather than living forever unmatched.
|
|
29
|
+
*/
|
|
30
|
+
export function packageRootOf(module) {
|
|
31
|
+
const parts = module.trim().split('/');
|
|
32
|
+
return parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
33
|
+
}
|
|
24
34
|
function pinDetails(pin) {
|
|
25
35
|
return pin ? { versionSource: pin.source, declaredRange: pin.range } : {};
|
|
26
36
|
}
|
|
@@ -249,6 +259,12 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
249
259
|
cacheKey: params => params.module === '.' ?
|
|
250
260
|
null
|
|
251
261
|
: `${normalizeQuery(params.module)}::${normalizeQuery(params.query)}`,
|
|
262
|
+
// Package provenance for per-entry resume invalidation: a docs digest describes
|
|
263
|
+
// one package at one declared version, so a resume drops it only when THAT
|
|
264
|
+
// package moves — an unrelated install no longer discards it. Package names are
|
|
265
|
+
// matched against package.json verbatim (npm names are case-sensitive), unlike
|
|
266
|
+
// the cache key, which normalises for phrasing collisions.
|
|
267
|
+
cachePkg: params => (params.module === '.' ? undefined : packageRootOf(params.module)),
|
|
252
268
|
// Only a completed lookup (child exited 0) is a real answer; not-installed,
|
|
253
269
|
// no-chunks, resolve/cache errors, and aborts omit childExitCode:0 and fall
|
|
254
270
|
// through to a live retry next time.
|
|
@@ -23,31 +23,32 @@ export declare function configureResearchRun(enabled: boolean): string | undefin
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function normalizeQuery(s: string): string;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* while any add/remove/version-bump does.
|
|
26
|
+
* The project's declared dependency surface as a name→range map, flattened across every
|
|
27
|
+
* dependency block (a package listed in two blocks resolves to the first range seen, in
|
|
28
|
+
* block order — a real manifest does not disagree with itself, and a disagreement can
|
|
29
|
+
* only make the comparison stricter, i.e. re-fetch).
|
|
31
30
|
*
|
|
32
|
-
* Returns undefined when the manifest is missing or unparseable
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* version, which would
|
|
31
|
+
* Returns undefined when the manifest is missing or unparseable: the caller then cannot
|
|
32
|
+
* prove any package's version and must treat every package-scoped entry as unprovable.
|
|
33
|
+
* Deliberately NOT the lockfile: it is rewritten by installs that change no resolved
|
|
34
|
+
* version, which would drop digests for no correctness gain.
|
|
36
35
|
*/
|
|
37
|
-
export declare function
|
|
36
|
+
export declare function depsMap(cwd: string): Promise<Record<string, string> | undefined>;
|
|
38
37
|
/**
|
|
39
|
-
* Resume hook:
|
|
40
|
-
*
|
|
41
|
-
* environment (reused or fresh),
|
|
38
|
+
* Resume hook: keep the interrupted run's cache id and PRUNE the entries the manifest
|
|
39
|
+
* has invalidated, rather than discarding the whole cache because anything moved. Returns
|
|
40
|
+
* the id now stamped into the environment (reused or fresh), how many entries survived,
|
|
41
|
+
* and how many were dropped; undefined id when caching is off.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
* caching disabled, no/corrupt cache file, a file
|
|
45
|
-
*
|
|
43
|
+
* Falls through to a fresh id only where there is nothing to reuse or nothing to reason
|
|
44
|
+
* with: caching disabled, no/corrupt cache file, or a file predating per-entry package
|
|
45
|
+
* provenance (`pkgv`) whose docs entries cannot be told apart from its search entries.
|
|
46
46
|
*/
|
|
47
47
|
export declare function resumeResearchRun(cwd: string, enabled: boolean): Promise<{
|
|
48
48
|
runId: string | undefined;
|
|
49
49
|
reused: boolean;
|
|
50
50
|
entries: number;
|
|
51
|
+
dropped: number;
|
|
51
52
|
}>;
|
|
52
53
|
/**
|
|
53
54
|
* Look up a cached result for `key` in the current run. Returns undefined on a miss,
|
|
@@ -61,6 +62,13 @@ export declare function lookupResearch(cwd: string, runId: string, key: string):
|
|
|
61
62
|
* Store a successful result under `key` for the current run. A file written for a
|
|
62
63
|
* different run id is discarded and started fresh (first write of a new run drops the
|
|
63
64
|
* prior run's contents — self-healing per-run isolation without an explicit clear).
|
|
65
|
+
*
|
|
66
|
+
* `pkg` is the npm package the answer is ABOUT, supplied by package-scoped tools (docs).
|
|
67
|
+
* Its declared version is resolved HERE, at store time, so a package installed mid-run
|
|
68
|
+
* is stamped with the version its own digest was taken against — not with whatever the
|
|
69
|
+
* manifest happened to say at the run's first write. That is what makes a later resume
|
|
70
|
+
* able to prune this one entry instead of the whole file.
|
|
71
|
+
*
|
|
64
72
|
* Best-effort: any failure is swallowed, leaving the caller's live result untouched.
|
|
65
73
|
*/
|
|
66
|
-
export declare function storeResearch(cwd: string, runId: string, key: string, text: string, details: unknown): Promise<void>;
|
|
74
|
+
export declare function storeResearch(cwd: string, runId: string, key: string, text: string, details: unknown, pkg?: string): Promise<void>;
|