@namzu/sdk 20.2.0 → 20.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/CHANGELOG.md +185 -0
- package/dist/manager/run/persistence.d.ts +2 -2
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +14 -5
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +6 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +27 -1
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +34 -4
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/context.d.ts +2 -0
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +1 -0
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +32 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +5 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/resume-run.d.ts +9 -1
- package/dist/runtime/query/resume-run.d.ts.map +1 -1
- package/dist/runtime/query/resume-run.js +2 -1
- package/dist/runtime/query/resume-run.js.map +1 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +2 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/run/checkpoint-disk.d.ts +18 -2
- package/dist/store/run/checkpoint-disk.d.ts.map +1 -1
- package/dist/store/run/checkpoint-disk.js +50 -5
- package/dist/store/run/checkpoint-disk.js.map +1 -1
- package/dist/store/run/checkpoint-memory.d.ts +22 -2
- package/dist/store/run/checkpoint-memory.d.ts.map +1 -1
- package/dist/store/run/checkpoint-memory.js +99 -4
- package/dist/store/run/checkpoint-memory.js.map +1 -1
- package/dist/store/run/claim-disk.d.ts +130 -0
- package/dist/store/run/claim-disk.d.ts.map +1 -0
- package/dist/store/run/claim-disk.js +550 -0
- package/dist/store/run/claim-disk.js.map +1 -0
- package/dist/store/run/disk.d.ts +10 -8
- package/dist/store/run/disk.d.ts.map +1 -1
- package/dist/store/run/disk.js.map +1 -1
- package/dist/store/run/listing.d.ts +44 -1
- package/dist/store/run/listing.d.ts.map +1 -1
- package/dist/store/run/listing.js +92 -1
- package/dist/store/run/listing.js.map +1 -1
- package/dist/store/run/memory.d.ts +46 -0
- package/dist/store/run/memory.d.ts.map +1 -0
- package/dist/store/run/memory.js +104 -0
- package/dist/store/run/memory.js.map +1 -0
- package/dist/types/run/checkpoint-store.d.ts +178 -2
- package/dist/types/run/checkpoint-store.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +12 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/index.d.ts +1 -0
- package/dist/types/run/index.d.ts.map +1 -1
- package/dist/types/run/index.js +1 -0
- package/dist/types/run/index.js.map +1 -1
- package/dist/types/run/store.d.ts +103 -0
- package/dist/types/run/store.d.ts.map +1 -0
- package/dist/types/run/store.js +30 -0
- package/dist/types/run/store.js.map +1 -0
- package/package.json +1 -1
- package/src/manager/run/persistence.ts +17 -7
- package/src/public-runtime.ts +6 -0
- package/src/runtime/query/checkpoint.ts +42 -5
- package/src/runtime/query/context.ts +3 -0
- package/src/runtime/query/index.ts +39 -1
- package/src/runtime/query/resume-run.ts +12 -2
- package/src/store/index.ts +5 -0
- package/src/store/run/checkpoint-disk.ts +70 -5
- package/src/store/run/checkpoint-memory.ts +118 -3
- package/src/store/run/claim-disk.ts +593 -0
- package/src/store/run/disk.ts +10 -8
- package/src/store/run/listing.ts +116 -1
- package/src/store/run/memory.ts +121 -0
- package/src/types/run/checkpoint-store.ts +189 -2
- package/src/types/run/config.ts +13 -0
- package/src/types/run/index.ts +1 -0
- package/src/types/run/store.ts +112 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/run/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAK3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,YAAY,EACX,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACzF,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/run/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAK3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,YAAY,EACX,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACzF,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/types/run/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './prepare-step.js';
|
|
|
3
3
|
export * from './stop-reason.js';
|
|
4
4
|
export * from './config.js';
|
|
5
5
|
export * from './checkpoint-store.js';
|
|
6
|
+
export * from './store.js';
|
|
6
7
|
export * from './entity.js';
|
|
7
8
|
export * from './replay.js';
|
|
8
9
|
// Domain `RunStatus` (session-hierarchy.md §4.6 state machine). Safe to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/run/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,sBAAsB;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAQ1B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/run/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,sBAAsB;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAQ1B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunStore — persistence contract for a run's own evidence.
|
|
3
|
+
*
|
|
4
|
+
* The checkpoint store got an injectable seam and this did not, which left
|
|
5
|
+
* the run record, its messages, its transcript and its report reachable only
|
|
6
|
+
* through a concrete filesystem class. For a kernel whose stated purpose is
|
|
7
|
+
* auditable evidence, the evidence was the one thing that could not be
|
|
8
|
+
* pointed at durable storage: on ephemeral infrastructure the transcript dies
|
|
9
|
+
* with the container, and behind a load balancer two replicas write two
|
|
10
|
+
* disjoint run trees for one tenant.
|
|
11
|
+
*
|
|
12
|
+
* The location was already injectable through a path builder — but that
|
|
13
|
+
* returns filesystem path strings, so it relocates the directory without
|
|
14
|
+
* changing the medium.
|
|
15
|
+
*
|
|
16
|
+
* ## Bound to one run, unlike {@link CheckpointStore}
|
|
17
|
+
*
|
|
18
|
+
* Every accessor here addresses the run the store was bound to by
|
|
19
|
+
* {@link RunStore.initRun}, where a `CheckpointStore` takes an explicit scope
|
|
20
|
+
* per call. That asymmetry is inherited rather than chosen: this contract is
|
|
21
|
+
* extracted from a class the runtime already constructs per run and holds for
|
|
22
|
+
* the run's lifetime, and re-keying it would change every call site in the
|
|
23
|
+
* same change that introduces the seam — two risks where one will do.
|
|
24
|
+
*
|
|
25
|
+
* A host implementing a shared backend therefore keys its rows by the
|
|
26
|
+
* attribution it was constructed with plus the bound run id. If this is later
|
|
27
|
+
* re-keyed per call, it happens once, deliberately, as its own change.
|
|
28
|
+
*/
|
|
29
|
+
import type { Run } from './entity.js';
|
|
30
|
+
import type { RunEvent } from './events.js';
|
|
31
|
+
/**
|
|
32
|
+
* One finished tool call, recovered from the run's own transcript.
|
|
33
|
+
*
|
|
34
|
+
* Re-declared here rather than imported from the disk store so the contract
|
|
35
|
+
* does not depend on an implementation of itself.
|
|
36
|
+
*/
|
|
37
|
+
export interface CompletedToolRecord {
|
|
38
|
+
readonly toolUseId: string;
|
|
39
|
+
readonly toolName: string;
|
|
40
|
+
readonly result: string;
|
|
41
|
+
readonly isError: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface RunStore {
|
|
44
|
+
/**
|
|
45
|
+
* Bind this store to a run, before any other call.
|
|
46
|
+
*
|
|
47
|
+
* Returns a location when the backend has one — the built-in disk store
|
|
48
|
+
* returns the run's directory — and `null` when it does not. A caller
|
|
49
|
+
* that renders the value must treat `null` as "this run is not on a
|
|
50
|
+
* filesystem" rather than as an error: an in-memory or object-storage
|
|
51
|
+
* backend has nothing to print, and inventing a path for it would put a
|
|
52
|
+
* directory that does not exist in front of an operator.
|
|
53
|
+
*/
|
|
54
|
+
initRun(runId: string, parentRunId?: string): Promise<string | null>;
|
|
55
|
+
/** Persist the run record: status, metadata, usage, timings. */
|
|
56
|
+
writeRunMeta(run: Run): Promise<void>;
|
|
57
|
+
/** Persist the run's full message history. */
|
|
58
|
+
writeMessages(run: Run): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Append one event to the run's durable event log.
|
|
61
|
+
*
|
|
62
|
+
* High-frequency streaming deltas are excluded before they reach here —
|
|
63
|
+
* that exclusion is a deliberate trade and belongs to the emitter, not to
|
|
64
|
+
* the backend, so a store must not re-filter.
|
|
65
|
+
*/
|
|
66
|
+
appendEvent(event: RunEvent): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Persist the run's final report. Returns a location, or `null` when the
|
|
69
|
+
* backend has none. See {@link RunStore.initRun}.
|
|
70
|
+
*/
|
|
71
|
+
writeReport(content: string): Promise<string | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Every tool call this run has already finished, keyed by `toolUseId`.
|
|
74
|
+
*
|
|
75
|
+
* A batch's results reach the message history only once the WHOLE batch
|
|
76
|
+
* settles, so a hard kill part-way through loses every result that had
|
|
77
|
+
* already come back, and the resumed run re-executes those calls. For a
|
|
78
|
+
* file write that is waste; for a payment or an email it is a second one.
|
|
79
|
+
*
|
|
80
|
+
* A backend that does not retain individual events answers with an empty
|
|
81
|
+
* map, which costs re-execution and is honest. It must not answer with a
|
|
82
|
+
* PARTIAL map: a caller reads a present entry as "this call is already
|
|
83
|
+
* answered", so a half-remembered batch is worse than a forgotten one.
|
|
84
|
+
*/
|
|
85
|
+
readCompletedTools(): Promise<Map<string, CompletedToolRecord>>;
|
|
86
|
+
/**
|
|
87
|
+
* Where this run's evidence lives, or `null` when it is not on a
|
|
88
|
+
* filesystem. Valid only after {@link RunStore.initRun}.
|
|
89
|
+
*/
|
|
90
|
+
getRunDir(): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Record the run in a browsable catalogue of runs. OPTIONAL.
|
|
93
|
+
*
|
|
94
|
+
* Optional because it is the one method here that is not evidence: it
|
|
95
|
+
* maintains a convenience listing for a human reading the directory, and
|
|
96
|
+
* a backend whose runs are already queryable has nothing to add. The
|
|
97
|
+
* programmatic answer to "which runs are there" is
|
|
98
|
+
* `CheckpointStore.listDurableRuns`, which carries attribution and
|
|
99
|
+
* includes sub-runs; this does neither.
|
|
100
|
+
*/
|
|
101
|
+
addToIndex?(run: Run): Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/types/run/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,QAAQ;IACxB;;;;;;;;;OASG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAEpE,gEAAgE;IAChE,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAErC,8CAA8C;IAC9C,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtC;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3C;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAEpD;;;;;;;;;;;;OAYG;IACH,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAA;IAE/D;;;OAGG;IACH,SAAS,IAAI,MAAM,GAAG,IAAI,CAAA;IAE1B;;;;;;;;;OASG;IACH,UAAU,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunStore — persistence contract for a run's own evidence.
|
|
3
|
+
*
|
|
4
|
+
* The checkpoint store got an injectable seam and this did not, which left
|
|
5
|
+
* the run record, its messages, its transcript and its report reachable only
|
|
6
|
+
* through a concrete filesystem class. For a kernel whose stated purpose is
|
|
7
|
+
* auditable evidence, the evidence was the one thing that could not be
|
|
8
|
+
* pointed at durable storage: on ephemeral infrastructure the transcript dies
|
|
9
|
+
* with the container, and behind a load balancer two replicas write two
|
|
10
|
+
* disjoint run trees for one tenant.
|
|
11
|
+
*
|
|
12
|
+
* The location was already injectable through a path builder — but that
|
|
13
|
+
* returns filesystem path strings, so it relocates the directory without
|
|
14
|
+
* changing the medium.
|
|
15
|
+
*
|
|
16
|
+
* ## Bound to one run, unlike {@link CheckpointStore}
|
|
17
|
+
*
|
|
18
|
+
* Every accessor here addresses the run the store was bound to by
|
|
19
|
+
* {@link RunStore.initRun}, where a `CheckpointStore` takes an explicit scope
|
|
20
|
+
* per call. That asymmetry is inherited rather than chosen: this contract is
|
|
21
|
+
* extracted from a class the runtime already constructs per run and holds for
|
|
22
|
+
* the run's lifetime, and re-keying it would change every call site in the
|
|
23
|
+
* same change that introduces the seam — two risks where one will do.
|
|
24
|
+
*
|
|
25
|
+
* A host implementing a shared backend therefore keys its rows by the
|
|
26
|
+
* attribution it was constructed with plus the bound run id. If this is later
|
|
27
|
+
* re-keyed per call, it happens once, deliberately, as its own change.
|
|
28
|
+
*/
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/types/run/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import type { ProviderErrorInfo } from '../../types/provider/index.js'
|
|
|
9
9
|
import type { CheckpointRunScope, CheckpointStore } from '../../types/run/checkpoint-store.js'
|
|
10
10
|
import type { EmergencySaveData } from '../../types/run/emergency.js'
|
|
11
11
|
import type { Run, RunPersistenceConfig, StepResult, StopReason } from '../../types/run/index.js'
|
|
12
|
+
import type { RunStore } from '../../types/run/store.js'
|
|
12
13
|
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
13
14
|
import { type ModelPricing, ZERO_COST, accumulateCost } from '../../utils/cost.js'
|
|
14
15
|
import { generateEmergencySaveId } from '../../utils/id.js'
|
|
@@ -16,7 +17,7 @@ import type { Logger } from '../../utils/logger.js'
|
|
|
16
17
|
|
|
17
18
|
export class RunPersistence {
|
|
18
19
|
private run: Run
|
|
19
|
-
private runStore:
|
|
20
|
+
private runStore: RunStore
|
|
20
21
|
private checkpointStore: CheckpointStore
|
|
21
22
|
private pricing?: ModelPricing
|
|
22
23
|
|
|
@@ -38,10 +39,17 @@ export class RunPersistence {
|
|
|
38
39
|
this._tenantId = config.tenantId
|
|
39
40
|
this._projectId = config.projectId
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// The run's own evidence goes through the same kind of injectable seam
|
|
43
|
+
// the checkpoint store already had. Without it the transcript, the run
|
|
44
|
+
// record and the report could only ever live on a local filesystem —
|
|
45
|
+
// which for a kernel whose purpose is auditable evidence made the
|
|
46
|
+
// evidence the one thing that could not be pointed at durable storage.
|
|
47
|
+
this.runStore =
|
|
48
|
+
config.runStore ??
|
|
49
|
+
new RunDiskStore({
|
|
50
|
+
baseDir: config.outputDir,
|
|
51
|
+
logger: config.log,
|
|
52
|
+
})
|
|
45
53
|
|
|
46
54
|
// Checkpoints go through the injectable seam; the disk layout under
|
|
47
55
|
// `outputDir` (same tree the runStore writes to) stays the default.
|
|
@@ -137,7 +145,7 @@ export class RunPersistence {
|
|
|
137
145
|
return this.run
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
getRunStore():
|
|
148
|
+
getRunStore(): RunStore {
|
|
141
149
|
return this.runStore
|
|
142
150
|
}
|
|
143
151
|
|
|
@@ -457,7 +465,9 @@ export class RunPersistence {
|
|
|
457
465
|
async persist(): Promise<void> {
|
|
458
466
|
await this.runStore.writeRunMeta(this.run)
|
|
459
467
|
await this.runStore.writeMessages(this.run)
|
|
460
|
-
|
|
468
|
+
// Optional on the contract: a backend whose runs are already queryable
|
|
469
|
+
// has no browsable directory to add a row to.
|
|
470
|
+
await this.runStore.addToIndex?.(this.run)
|
|
461
471
|
|
|
462
472
|
if (this.run.result) {
|
|
463
473
|
await this.runStore.writeReport(this.run.result)
|
package/src/public-runtime.ts
CHANGED
|
@@ -200,6 +200,7 @@ export {
|
|
|
200
200
|
DiskTaskStore,
|
|
201
201
|
InMemoryCheckpointStore,
|
|
202
202
|
InMemoryMemoryIndex,
|
|
203
|
+
InMemoryRunStore,
|
|
203
204
|
InMemoryMemoryStore,
|
|
204
205
|
InMemoryStore,
|
|
205
206
|
InMemoryTaskStore,
|
|
@@ -217,6 +218,11 @@ export {
|
|
|
217
218
|
paginateDurableRuns,
|
|
218
219
|
toDurableRunEntry,
|
|
219
220
|
} from './store/index.js'
|
|
221
|
+
// Cross-process possession of a run. `claimRun` REFUSES on a store that
|
|
222
|
+
// cannot arbitrate rather than proceeding unclaimed, because proceeding lets
|
|
223
|
+
// two workers restore one checkpoint, both run its tools and both write under
|
|
224
|
+
// one run id — which loses half the work and reports nothing.
|
|
225
|
+
export { claimRun, fencedOut, releaseRun, toClaimSummary } from './store/index.js'
|
|
220
226
|
|
|
221
227
|
export {
|
|
222
228
|
AgentRegistry,
|
|
@@ -11,7 +11,11 @@ import type {
|
|
|
11
11
|
PendingDecision,
|
|
12
12
|
} from '../../types/hitl/index.js'
|
|
13
13
|
import type { AssistantMessage } from '../../types/message/index.js'
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
CheckpointRunScope,
|
|
16
|
+
CheckpointStore,
|
|
17
|
+
ClaimFence,
|
|
18
|
+
} from '../../types/run/checkpoint-store.js'
|
|
15
19
|
import type { EmergencySaveData } from '../../types/run/emergency.js'
|
|
16
20
|
import type { CheckpointListEntry } from '../../types/run/replay.js'
|
|
17
21
|
import { ZERO_COST } from '../../utils/cost.js'
|
|
@@ -154,6 +158,22 @@ export class CheckpointManager {
|
|
|
154
158
|
/** See {@link setParkTtl}. */
|
|
155
159
|
private parkTtlMs?: number
|
|
156
160
|
|
|
161
|
+
/**
|
|
162
|
+
* The claim this run holds, presented on every checkpoint write.
|
|
163
|
+
*
|
|
164
|
+
* Unset means unfenced, which is correct for a single-writer deployment
|
|
165
|
+
* and is what every run did before claims existed. Set it and a write from
|
|
166
|
+
* a superseded holding is refused by the store.
|
|
167
|
+
*
|
|
168
|
+
* This existed nowhere for one release, and the omission was invisible in
|
|
169
|
+
* the worst way: the claim, the fence and the refusal were all built and
|
|
170
|
+
* tested, and no code path between a run and its store carried the number,
|
|
171
|
+
* so every checkpoint a RUN wrote went out unfenced. A capability that is
|
|
172
|
+
* complete except for the wire between its halves reads exactly like a
|
|
173
|
+
* working one.
|
|
174
|
+
*/
|
|
175
|
+
private claimFence?: ClaimFence
|
|
176
|
+
|
|
157
177
|
/**
|
|
158
178
|
* The run's attribution instant, stamped onto every checkpoint this
|
|
159
179
|
* manager writes.
|
|
@@ -234,7 +254,7 @@ export class CheckpointManager {
|
|
|
234
254
|
traceContext: this.traceSource?.(),
|
|
235
255
|
}
|
|
236
256
|
|
|
237
|
-
await this.store.writeCheckpoint(this.scope, checkpoint)
|
|
257
|
+
await this.store.writeCheckpoint(this.scope, checkpoint, this.claimFence)
|
|
238
258
|
this.lastCreatedId = checkpoint.id
|
|
239
259
|
return checkpoint
|
|
240
260
|
}
|
|
@@ -298,10 +318,27 @@ export class CheckpointManager {
|
|
|
298
318
|
...(ttl !== undefined && ttl > 0 ? { deadlineAt: parkedAt + ttl } : {}),
|
|
299
319
|
},
|
|
300
320
|
}
|
|
301
|
-
await this.store.writeCheckpoint(this.scope, parked)
|
|
321
|
+
await this.store.writeCheckpoint(this.scope, parked, this.claimFence)
|
|
302
322
|
return parked
|
|
303
323
|
}
|
|
304
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Present this claim on every subsequent write. See {@link claimFence}.
|
|
327
|
+
*
|
|
328
|
+
* A setter rather than a constructor argument because a run is claimed at
|
|
329
|
+
* a different moment than it is constructed — a worker draining a queue
|
|
330
|
+
* takes the run, then builds the pipeline around it — and because a
|
|
331
|
+
* renewal mints a NEW fence mid-run that has to replace the old one.
|
|
332
|
+
*/
|
|
333
|
+
setClaimFence(fence: ClaimFence | undefined): void {
|
|
334
|
+
this.claimFence = fence
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** The claim currently presented on writes, if any. */
|
|
338
|
+
get presentedFence(): ClaimFence | undefined {
|
|
339
|
+
return this.claimFence
|
|
340
|
+
}
|
|
341
|
+
|
|
305
342
|
/** Default time-to-live applied to every park this manager records. */
|
|
306
343
|
setParkTtl(ttlMs: number | undefined): void {
|
|
307
344
|
this.parkTtlMs = ttlMs
|
|
@@ -329,7 +366,7 @@ export class CheckpointManager {
|
|
|
329
366
|
decision: { action: 'pause', reason: 'The approval request expired without an answer.' },
|
|
330
367
|
},
|
|
331
368
|
}
|
|
332
|
-
await this.store.writeCheckpoint(this.scope, expired)
|
|
369
|
+
await this.store.writeCheckpoint(this.scope, expired, this.claimFence)
|
|
333
370
|
return expired
|
|
334
371
|
}
|
|
335
372
|
|
|
@@ -352,7 +389,7 @@ export class CheckpointManager {
|
|
|
352
389
|
...checkpoint,
|
|
353
390
|
pending: { ...checkpoint.pending, resolvedAt: Date.now(), decision },
|
|
354
391
|
}
|
|
355
|
-
await this.store.writeCheckpoint(this.scope, resolved)
|
|
392
|
+
await this.store.writeCheckpoint(this.scope, resolved, this.claimFence)
|
|
356
393
|
return resolved
|
|
357
394
|
}
|
|
358
395
|
|
|
@@ -17,6 +17,7 @@ import type { PermissionMode } from '../../types/permission/index.js'
|
|
|
17
17
|
import type { LLMProvider } from '../../types/provider/index.js'
|
|
18
18
|
import type { CheckpointStore } from '../../types/run/checkpoint-store.js'
|
|
19
19
|
import type { AgentRunConfig } from '../../types/run/index.js'
|
|
20
|
+
import type { RunStore } from '../../types/run/store.js'
|
|
20
21
|
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
21
22
|
import type { ModelPricing } from '../../utils/cost.js'
|
|
22
23
|
import { generateRunId } from '../../utils/id.js'
|
|
@@ -62,6 +63,7 @@ export interface RunContextConfig {
|
|
|
62
63
|
* output directory.
|
|
63
64
|
*/
|
|
64
65
|
checkpointStore?: CheckpointStore
|
|
66
|
+
runStore?: RunStore
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
69
|
* Optional injected migrator — tests pass a stub; production code relies
|
|
@@ -186,6 +188,7 @@ export class RunContextFactory {
|
|
|
186
188
|
parentRunId: config.parentRunId,
|
|
187
189
|
depth: config.depth,
|
|
188
190
|
checkpointStore: config.checkpointStore,
|
|
191
|
+
runStore: config.runStore,
|
|
189
192
|
})
|
|
190
193
|
|
|
191
194
|
const trackingConfig = resolveActivityTracking(permissionMode, config.enableActivityTracking)
|
|
@@ -58,7 +58,7 @@ import type { AgentPersona } from '../../types/persona/index.js'
|
|
|
58
58
|
import type { LLMProvider } from '../../types/provider/index.js'
|
|
59
59
|
import type { TaskRouterConfig } from '../../types/router/index.js'
|
|
60
60
|
import type { ReviewAnswer } from '../../types/run/answer-review.js'
|
|
61
|
-
import type { CheckpointStore } from '../../types/run/checkpoint-store.js'
|
|
61
|
+
import type { CheckpointStore, ClaimFence } from '../../types/run/checkpoint-store.js'
|
|
62
62
|
import type {
|
|
63
63
|
AgentRunConfig,
|
|
64
64
|
PrepareStepChain,
|
|
@@ -70,6 +70,7 @@ import type {
|
|
|
70
70
|
} from '../../types/run/index.js'
|
|
71
71
|
import type { PromoteMemory } from '../../types/run/memory-promotion.js'
|
|
72
72
|
import { memoryCandidateFor } from '../../types/run/memory-promotion.js'
|
|
73
|
+
import type { RunStore } from '../../types/run/store.js'
|
|
73
74
|
import type { Sandbox, SandboxProvider } from '../../types/sandbox/index.js'
|
|
74
75
|
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
75
76
|
import type { Skill } from '../../types/skills/index.js'
|
|
@@ -412,6 +413,38 @@ export interface QueryParams {
|
|
|
412
413
|
*/
|
|
413
414
|
checkpointStore?: CheckpointStore
|
|
414
415
|
|
|
416
|
+
/**
|
|
417
|
+
* The fence of the claim this worker holds on the run, from `claimRun`.
|
|
418
|
+
*
|
|
419
|
+
* Presented on every checkpoint the run writes, so a worker that stalled
|
|
420
|
+
* past its lease is refused rather than writing into a run somebody else
|
|
421
|
+
* has taken over. Omit it for single-writer deployments, which is what
|
|
422
|
+
* every run did before claims existed.
|
|
423
|
+
*
|
|
424
|
+
* This hop did not exist for a release. The claim, the fence and the
|
|
425
|
+
* store-side refusal were all built and tested, and no path between a run
|
|
426
|
+
* and its store carried the number — so every checkpoint a RUN wrote went
|
|
427
|
+
* out unfenced while the tests, which called the store directly, all
|
|
428
|
+
* passed. A capability complete except for the wire between its halves
|
|
429
|
+
* reads exactly like a working one.
|
|
430
|
+
*
|
|
431
|
+
* It fences checkpoints and nothing else. {@link QueryParams.runStore}
|
|
432
|
+
* takes no fence, so two workers that both took one run still overwrite
|
|
433
|
+
* each other's run record, transcript and report — see the changeset.
|
|
434
|
+
*/
|
|
435
|
+
claimFence?: ClaimFence
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Where this run records its own evidence — the run record, its messages,
|
|
439
|
+
* its transcript and its report. Defaults to the disk layout under the
|
|
440
|
+
* resolved output directory.
|
|
441
|
+
*
|
|
442
|
+
* The sibling of {@link QueryParams.checkpointStore}, and it should always
|
|
443
|
+
* have been one: checkpoints could be pointed at durable storage and the
|
|
444
|
+
* evidence could not.
|
|
445
|
+
*/
|
|
446
|
+
runStore?: RunStore
|
|
447
|
+
|
|
415
448
|
runId?: RunId
|
|
416
449
|
|
|
417
450
|
parentRunId?: RunId
|
|
@@ -610,6 +643,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
610
643
|
tenantId: params.tenantId,
|
|
611
644
|
pathBuilder: params.pathBuilder,
|
|
612
645
|
checkpointStore: params.checkpointStore,
|
|
646
|
+
runStore: params.runStore,
|
|
613
647
|
runId: params.runId,
|
|
614
648
|
parentRunId: params.parentRunId,
|
|
615
649
|
depth: params.depth,
|
|
@@ -1000,6 +1034,10 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
1000
1034
|
// And every park it records carries an absolute deadline, so an
|
|
1001
1035
|
// unanswered approval cannot outlive the worker that asked for it.
|
|
1002
1036
|
checkpointMgr.setParkTtl(params.runConfig.hitlParkTtlMs)
|
|
1037
|
+
// The claim this worker holds, if it took one. Without this hop the
|
|
1038
|
+
// fence exists, the refusal exists, and no checkpoint a RUN writes ever
|
|
1039
|
+
// carries a number — so a stalled worker is refused nowhere.
|
|
1040
|
+
checkpointMgr.setClaimFence(params.claimFence)
|
|
1003
1041
|
|
|
1004
1042
|
// A question raised from inside a tool becomes a real checkpoint
|
|
1005
1043
|
// here. It used to park under a synthetic id nothing ever wrote, so
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PendingDecision } from '../../types/hitl/index.js'
|
|
2
|
-
import type { CheckpointStore } from '../../types/run/checkpoint-store.js'
|
|
2
|
+
import type { CheckpointStore, ClaimFence } from '../../types/run/checkpoint-store.js'
|
|
3
3
|
import type { Run } from '../../types/run/entity.js'
|
|
4
4
|
import type { RunState } from '../../types/run/state.js'
|
|
5
5
|
import { type QueryParams, drainQuery } from './index.js'
|
|
@@ -38,6 +38,15 @@ export interface ResumeRunParams
|
|
|
38
38
|
readonly scope: RunStateScope
|
|
39
39
|
/** Required to find the checkpoint; also threaded into the resumed run. */
|
|
40
40
|
readonly checkpointStore: CheckpointStore
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The fence of the claim this worker took on the run before resuming.
|
|
44
|
+
*
|
|
45
|
+
* A resume is the one moment two workers are most likely to collide — it
|
|
46
|
+
* is what a queue reader does with a parked run — so this is the call that
|
|
47
|
+
* most needs to carry one.
|
|
48
|
+
*/
|
|
49
|
+
readonly claimFence?: ClaimFence
|
|
41
50
|
/**
|
|
42
51
|
* Resume a specific checkpoint instead of the one the store would pick.
|
|
43
52
|
* Absent means the parked checkpoint if there is one, else the newest.
|
|
@@ -67,7 +76,7 @@ export interface ResumeRunParams
|
|
|
67
76
|
* resumed past without the answer it is waiting for.
|
|
68
77
|
*/
|
|
69
78
|
export async function resumeRun(params: ResumeRunParams): Promise<ResumeOutcome> {
|
|
70
|
-
const { scope, checkpointStore, checkpointId, pendingDecision, ...rest } = params
|
|
79
|
+
const { scope, checkpointStore, checkpointId, pendingDecision, claimFence, ...rest } = params
|
|
71
80
|
|
|
72
81
|
const state = await loadRunState(checkpointStore, scope, checkpointId)
|
|
73
82
|
if (!state?.checkpointId) return { resumed: false, reason: 'no-checkpoint' }
|
|
@@ -86,6 +95,7 @@ export async function resumeRun(params: ResumeRunParams): Promise<ResumeOutcome>
|
|
|
86
95
|
runId: state.runId,
|
|
87
96
|
resumeFromCheckpoint: state.checkpointId,
|
|
88
97
|
checkpointStore,
|
|
98
|
+
...(claimFence !== undefined ? { claimFence } : {}),
|
|
89
99
|
...(pendingDecision ? { pendingDecision } : {}),
|
|
90
100
|
} as QueryParams)
|
|
91
101
|
|
package/src/store/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { InMemoryStore } from './InMemoryStore.js'
|
|
|
2
2
|
export type { Identifiable, Timestamped } from './InMemoryStore.js'
|
|
3
3
|
|
|
4
4
|
export { RunDiskStore } from './run/disk.js'
|
|
5
|
+
export { InMemoryRunStore } from './run/memory.js'
|
|
5
6
|
export { DiskCheckpointStore } from './run/checkpoint-disk.js'
|
|
6
7
|
export type { DiskCheckpointStoreAttribution } from './run/checkpoint-disk.js'
|
|
7
8
|
export { InMemoryCheckpointStore } from './run/checkpoint-memory.js'
|
|
@@ -17,8 +18,12 @@ export { InMemoryCheckpointStore } from './run/checkpoint-memory.js'
|
|
|
17
18
|
// host has no use for is surface to keep correct forever for nobody.
|
|
18
19
|
export {
|
|
19
20
|
assertContiguousListingScope,
|
|
21
|
+
claimRun,
|
|
22
|
+
fencedOut,
|
|
20
23
|
listDurableRuns,
|
|
21
24
|
paginateDurableRuns,
|
|
25
|
+
releaseRun,
|
|
26
|
+
toClaimSummary,
|
|
22
27
|
toDurableRunEntry,
|
|
23
28
|
} from './run/listing.js'
|
|
24
29
|
|
|
@@ -7,14 +7,24 @@ import type {
|
|
|
7
7
|
CheckpointListingScope,
|
|
8
8
|
CheckpointRunScope,
|
|
9
9
|
CheckpointStore,
|
|
10
|
+
ClaimFence,
|
|
11
|
+
ClaimRunOptions,
|
|
10
12
|
DurableRunEntry,
|
|
11
13
|
DurableRunPage,
|
|
12
14
|
ListDurableRunsOptions,
|
|
15
|
+
RunClaim,
|
|
13
16
|
} from '../../types/run/checkpoint-store.js'
|
|
14
17
|
import type { RunStoreConfig } from '../../types/run/index.js'
|
|
15
18
|
import type { ProjectId } from '../../types/session/ids.js'
|
|
19
|
+
import { acquireClaim, currentFence, readClaim, releaseClaim } from './claim-disk.js'
|
|
16
20
|
import { RunDiskStore, readCheckpointsIn } from './disk.js'
|
|
17
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
assertContiguousListingScope,
|
|
23
|
+
fencedOut,
|
|
24
|
+
paginateDurableRuns,
|
|
25
|
+
toClaimSummary,
|
|
26
|
+
toDurableRunEntry,
|
|
27
|
+
} from './listing.js'
|
|
18
28
|
|
|
19
29
|
/**
|
|
20
30
|
* The attribution a disk store's own layout does not record.
|
|
@@ -84,11 +94,47 @@ export class DiskCheckpointStore implements CheckpointStore {
|
|
|
84
94
|
return promise
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
async writeCheckpoint(
|
|
97
|
+
async writeCheckpoint(
|
|
98
|
+
scope: CheckpointRunScope,
|
|
99
|
+
checkpoint: IterationCheckpoint,
|
|
100
|
+
fence?: ClaimFence,
|
|
101
|
+
): Promise<void> {
|
|
88
102
|
const store = await this.bind(scope)
|
|
103
|
+
if (fence !== undefined) {
|
|
104
|
+
// Read at the moment of the write, not at the start of the run.
|
|
105
|
+
// A holder that stalled past its lease believes it still holds,
|
|
106
|
+
// and this is the only point at which it can be told otherwise.
|
|
107
|
+
// Names only, no parsing. The fence is the file name, so a corrupt
|
|
108
|
+
// body cannot make this check skip itself — which the previous shape
|
|
109
|
+
// did, at the one site whose entire job is refusing.
|
|
110
|
+
const current = await currentFence(this.runDir(scope))
|
|
111
|
+
if (fence < current) throw fencedOut(scope, fence, current)
|
|
112
|
+
}
|
|
89
113
|
await store.writeCheckpoint(checkpoint)
|
|
90
114
|
}
|
|
91
115
|
|
|
116
|
+
async claimRun(scope: CheckpointRunScope, options: ClaimRunOptions): Promise<RunClaim | null> {
|
|
117
|
+
return acquireClaim(this.runDir(scope), options)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async releaseRun(scope: CheckpointRunScope, fence: ClaimFence): Promise<void> {
|
|
121
|
+
await releaseClaim(this.runDir(scope), fence)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The run's directory, resolved the same way `RunDiskStore.initRun` does.
|
|
126
|
+
*
|
|
127
|
+
* Duplicated rather than shared because the claim path must be derivable
|
|
128
|
+
* WITHOUT binding a store — binding creates the directory, and a claim
|
|
129
|
+
* read is a read. Kept beside the layout comment on `listDurableRuns` so
|
|
130
|
+
* the two stay together if the layout ever moves.
|
|
131
|
+
*/
|
|
132
|
+
private runDir(scope: CheckpointRunScope): string {
|
|
133
|
+
return scope.parentRunId
|
|
134
|
+
? join(this.config.baseDir, scope.parentRunId, 'children', scope.runId)
|
|
135
|
+
: join(this.config.baseDir, scope.runId)
|
|
136
|
+
}
|
|
137
|
+
|
|
92
138
|
async readCheckpoint(
|
|
93
139
|
scope: CheckpointRunScope,
|
|
94
140
|
checkpointId: CheckpointId,
|
|
@@ -163,21 +209,40 @@ export class DiskCheckpointStore implements CheckpointStore {
|
|
|
163
209
|
const runDir = join(this.config.baseDir, runId)
|
|
164
210
|
|
|
165
211
|
const own = toDurableRunEntry({ ...attribution, runId }, await readCheckpointsIn(runDir), now)
|
|
166
|
-
if (own) entries.push(own)
|
|
212
|
+
if (own) entries.push(await this.withClaim(own, runDir, now))
|
|
167
213
|
|
|
168
214
|
for (const childId of await this.readRunDirs(join(runDir, 'children'))) {
|
|
215
|
+
const childDir = join(runDir, 'children', childId)
|
|
169
216
|
const child = toDurableRunEntry(
|
|
170
217
|
{ ...attribution, runId: childId, parentRunId: runId },
|
|
171
|
-
await readCheckpointsIn(
|
|
218
|
+
await readCheckpointsIn(childDir),
|
|
172
219
|
now,
|
|
173
220
|
)
|
|
174
|
-
if (child) entries.push(child)
|
|
221
|
+
if (child) entries.push(await this.withClaim(child, childDir, now))
|
|
175
222
|
}
|
|
176
223
|
}
|
|
177
224
|
|
|
178
225
|
return paginateDurableRuns(entries, options)
|
|
179
226
|
}
|
|
180
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Attach the run's claim to its listing row, judged against the page's
|
|
230
|
+
* own clock so one page cannot disagree with itself about availability.
|
|
231
|
+
*/
|
|
232
|
+
private async withClaim(
|
|
233
|
+
entry: DurableRunEntry,
|
|
234
|
+
runDir: string,
|
|
235
|
+
now: number,
|
|
236
|
+
): Promise<DurableRunEntry> {
|
|
237
|
+
const claim = await readClaim(runDir)
|
|
238
|
+
// A holding whose body could not be read still appears on the row,
|
|
239
|
+
// carrying an expiry of 0 so it reads as available. Dropping the field
|
|
240
|
+
// entirely — which is what happened before — put the run under
|
|
241
|
+
// `claimed: false` by looking unclaimed rather than by being
|
|
242
|
+
// reclaimable, so a queue reader was told a wedged run was free work.
|
|
243
|
+
return claim ? { ...entry, claim: toClaimSummary(claim, now) } : entry
|
|
244
|
+
}
|
|
245
|
+
|
|
181
246
|
/** Directory names under `dir`, or none when `dir` does not exist. */
|
|
182
247
|
private async readRunDirs(dir: string): Promise<RunId[]> {
|
|
183
248
|
try {
|