@purista/harness 1.2.5 → 1.5.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/README.md +6 -0
- package/dist/agents/index.d.ts +7 -1
- package/dist/agents/index.js +59 -39
- package/dist/errors/catalog.d.ts +18 -2
- package/dist/errors/catalog.js +10 -0
- package/dist/eval/index.d.ts +3 -3
- package/dist/eval/index.js +15 -1
- package/dist/harness/defineHarness.d.ts +91 -1
- package/dist/harness/defineHarness.js +110 -1
- package/dist/index.d.ts +37 -17
- package/dist/index.js +30 -16
- package/dist/local/index.d.ts +36 -0
- package/dist/local/index.js +24 -0
- package/dist/local/local-sandbox.d.ts +25 -0
- package/dist/local/local-sandbox.js +368 -0
- package/dist/local/local-workspace.d.ts +56 -0
- package/dist/local/local-workspace.js +496 -0
- package/dist/local/ref-hash.d.ts +6 -0
- package/dist/local/ref-hash.js +9 -0
- package/dist/local/sqlite-storage.d.ts +106 -0
- package/dist/local/sqlite-storage.js +680 -0
- package/dist/models/adapter-utils.d.ts +52 -0
- package/dist/models/adapter-utils.js +81 -0
- package/dist/models/registry.d.ts +2 -1
- package/dist/models/registry.js +28 -37
- package/dist/models/stream-pump.d.ts +16 -0
- package/dist/models/stream-pump.js +77 -0
- package/dist/ports/base-model-provider.d.ts +7 -1
- package/dist/ports/base-model-provider.js +384 -87
- package/dist/ports/capabilities.d.ts +16 -2
- package/dist/ports/context-checkpoints.d.ts +63 -0
- package/dist/ports/context-checkpoints.js +33 -0
- package/dist/ports/index.d.ts +1 -0
- package/dist/ports/index.js +1 -0
- package/dist/ports/model-provider.d.ts +110 -0
- package/dist/runtime/durable.d.ts +11 -0
- package/dist/runtime/durable.js +15 -2
- package/dist/runtime/sessionDurable.js +47 -21
- package/dist/sessions/index.d.ts +17 -6
- package/dist/sessions/index.js +337 -81
- package/dist/skills/index.d.ts +0 -2
- package/dist/skills/index.js +0 -8
- package/dist/state/in-memory.js +6 -6
- package/dist/telemetry/shim.js +2 -6
- package/dist/telemetry/span-attrs.d.ts +9 -0
- package/dist/telemetry/span-attrs.js +27 -0
- package/dist/testing/durableWorkspaceStoreContract.js +69 -0
- package/dist/testing/fakeLogger.d.ts +29 -0
- package/dist/testing/fakeLogger.js +47 -0
- package/dist/testing/fakeSandbox.d.ts +27 -0
- package/dist/testing/fakeSandbox.js +153 -0
- package/dist/testing/fakeStateStore.d.ts +36 -0
- package/dist/testing/fakeStateStore.js +66 -0
- package/dist/testing/index.d.ts +10 -4
- package/dist/testing/index.js +14 -4
- package/dist/testing/loggerContract.d.ts +9 -0
- package/dist/testing/loggerContract.js +62 -0
- package/dist/testing/modelProviderContract.d.ts +12 -0
- package/dist/testing/modelProviderContract.js +222 -0
- package/dist/testing/recordEvents.d.ts +3 -0
- package/dist/testing/recordEvents.js +8 -0
- package/dist/testing/stateStoreContract.js +27 -0
- package/dist/tools/index.js +26 -1
- package/dist/tools/mcp/http.d.ts +2 -0
- package/dist/tools/mcp/http.js +34 -21
- package/dist/tools/mcp/runner.d.ts +4 -0
- package/dist/tools/mcp/runner.js +75 -21
- package/dist/tools/mcp/stdio.d.ts +7 -1
- package/dist/tools/mcp/stdio.js +102 -23
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace/in-memory.d.ts +1 -0
- package/dist/workspace/in-memory.js +47 -12
- package/package.json +2 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HarnessConfigError } from '../errors/catalog.js';
|
|
2
|
+
const adapterIdPattern = /^[a-z][a-z0-9_.-]{1,63}$/;
|
|
3
|
+
/** Validates the context checkpoint adapter descriptor at harness build time. */
|
|
4
|
+
export function validateContextCheckpointStore(adapter) {
|
|
5
|
+
if (!adapterIdPattern.test(adapter.info.id)) {
|
|
6
|
+
throw new HarnessConfigError('Context checkpoint store id is invalid.', {
|
|
7
|
+
reason: 'invalid_context_checkpoint_store',
|
|
8
|
+
path: 'checkpoints.info.id',
|
|
9
|
+
id: adapter.info.id
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
if (!adapter.info.packageName.trim()) {
|
|
13
|
+
throw new HarnessConfigError('Context checkpoint store packageName is required.', {
|
|
14
|
+
reason: 'invalid_context_checkpoint_store',
|
|
15
|
+
path: 'checkpoints.info.packageName',
|
|
16
|
+
id: adapter.info.id
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (!adapter.info.capabilities.includes('context_checkpoint.write')) {
|
|
20
|
+
throw new HarnessConfigError('Context checkpoint store must support context_checkpoint.write.', {
|
|
21
|
+
reason: 'invalid_context_checkpoint_store',
|
|
22
|
+
path: 'checkpoints.info.capabilities',
|
|
23
|
+
id: adapter.info.id
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (adapter.info.capabilities.some((capability) => !adapter.capabilities.includes(capability))) {
|
|
27
|
+
throw new HarnessConfigError('Context checkpoint store capabilities must include info.capabilities.', {
|
|
28
|
+
reason: 'invalid_context_checkpoint_store',
|
|
29
|
+
path: 'checkpoints.capabilities',
|
|
30
|
+
id: adapter.info.id
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/ports/index.d.ts
CHANGED
package/dist/ports/index.js
CHANGED
|
@@ -23,6 +23,80 @@ export type ModelCapability =
|
|
|
23
23
|
| 'embeddings'
|
|
24
24
|
/** Document reranking. */
|
|
25
25
|
| 'rerank';
|
|
26
|
+
/** Provider-neutral retry setting used by model aliases and per-call overrides. */
|
|
27
|
+
export type ModelRetrySetting = boolean | ModelRetryPolicy;
|
|
28
|
+
/** Transient failure classes that can be retried by the harness. */
|
|
29
|
+
export interface ModelRetryOnPolicy {
|
|
30
|
+
/** Retry transport-level/network failures. Default: `true`. */
|
|
31
|
+
network?: boolean;
|
|
32
|
+
/** Retry model call timeouts. Default: `true`. */
|
|
33
|
+
timeout?: boolean;
|
|
34
|
+
/** Retry HTTP 429/rate-limit failures. Default: `true`. */
|
|
35
|
+
rateLimit?: boolean;
|
|
36
|
+
/** Retry HTTP 5xx/provider-unavailable failures. Default: `true`. */
|
|
37
|
+
serverError?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Provider-neutral retry policy.
|
|
41
|
+
*
|
|
42
|
+
* The harness actively retries only inside `maxActiveElapsedMs` and
|
|
43
|
+
* `maxActiveDelayMs`. Longer provider retry instructions fail fast with
|
|
44
|
+
* `retryKind: 'none'` by default; `longRetry: 'defer'` classifies them as
|
|
45
|
+
* deferred retry errors carrying the provider-supplied `retryAfterMs`.
|
|
46
|
+
*/
|
|
47
|
+
export interface ModelRetryPolicy {
|
|
48
|
+
/** Total active attempts including the first call. Default: `3`. */
|
|
49
|
+
maxAttempts?: number;
|
|
50
|
+
/** Maximum wall-clock time spent in active retries. Default: `60_000`. */
|
|
51
|
+
maxActiveElapsedMs?: number;
|
|
52
|
+
/** Maximum single active sleep. Default: `20_000`. */
|
|
53
|
+
maxActiveDelayMs?: number;
|
|
54
|
+
/** Optional cap for deferred retry classification with `longRetry: 'defer'`. Default: unlimited. */
|
|
55
|
+
maxDeferredDelayMs?: number;
|
|
56
|
+
/** Honor provider Retry-After/reset headers when present. Default: `true`. */
|
|
57
|
+
respectRetryAfter?: boolean;
|
|
58
|
+
/** Base delay for exponential jitter when no provider delay exists. Default: `500`. */
|
|
59
|
+
minDelayMs?: number;
|
|
60
|
+
/** Maximum computed backoff delay. Default: `8_000`. */
|
|
61
|
+
maxDelayMs?: number;
|
|
62
|
+
/** Retryable failure classes. Omitted fields default to `true`. */
|
|
63
|
+
retryOn?: ModelRetryOnPolicy;
|
|
64
|
+
/**
|
|
65
|
+
* Handling for provider-instructed delays beyond `maxActiveDelayMs`:
|
|
66
|
+
* `'error'` fails immediately with `retryKind: 'none'`; `'defer'` fails with
|
|
67
|
+
* `retryKind: 'deferred'` plus the provider-supplied `retryAfterMs` so a
|
|
68
|
+
* queue/scheduler can retry later. Default: `'error'`.
|
|
69
|
+
*/
|
|
70
|
+
longRetry?: 'error' | 'defer';
|
|
71
|
+
}
|
|
72
|
+
/** Normalized retry classification for provider failures. */
|
|
73
|
+
export type ModelRetryKind = 'none' | 'active' | 'deferred';
|
|
74
|
+
/** Structured provider outcome metadata preserved across adapters. */
|
|
75
|
+
export interface ModelOutcome {
|
|
76
|
+
/** Normalized finish reason. */
|
|
77
|
+
finishReason: FinishReason;
|
|
78
|
+
/** Raw provider finish/stop/status reason when available. */
|
|
79
|
+
providerFinishReason?: string;
|
|
80
|
+
/** Raw provider status when it carries outcome semantics. */
|
|
81
|
+
providerStatus?: string;
|
|
82
|
+
/** Whether the outcome is eligible for retry under policy. */
|
|
83
|
+
retryable?: boolean;
|
|
84
|
+
/** Active/deferred retry classification when relevant. */
|
|
85
|
+
retryKind?: ModelRetryKind;
|
|
86
|
+
/** Provider-suggested or computed retry delay. */
|
|
87
|
+
retryAfterMs?: number;
|
|
88
|
+
/** Parsed provider rate-limit metadata. */
|
|
89
|
+
rateLimit?: ModelRateLimitInfo;
|
|
90
|
+
/** Extra provider-specific structured outcome details. */
|
|
91
|
+
details?: Record<string, JsonValue>;
|
|
92
|
+
}
|
|
93
|
+
/** Parsed, provider-neutral rate-limit metadata. */
|
|
94
|
+
export interface ModelRateLimitInfo {
|
|
95
|
+
scope?: 'requests' | 'input_tokens' | 'output_tokens' | 'tokens' | 'unknown';
|
|
96
|
+
limit?: number;
|
|
97
|
+
remaining?: number;
|
|
98
|
+
resetAt?: string;
|
|
99
|
+
}
|
|
26
100
|
/** Default generation parameters applied per alias. */
|
|
27
101
|
export interface ModelDefaults {
|
|
28
102
|
temperature?: number;
|
|
@@ -31,6 +105,8 @@ export interface ModelDefaults {
|
|
|
31
105
|
stopSequences?: string[];
|
|
32
106
|
/** Whether providers should allow the model to emit multiple independent tool calls in one turn. */
|
|
33
107
|
parallelToolCalls?: boolean;
|
|
108
|
+
/** Alias-level retry behavior inherited by model calls. Default: `true`. */
|
|
109
|
+
retry?: ModelRetrySetting;
|
|
34
110
|
providerOptions?: Record<string, unknown>;
|
|
35
111
|
}
|
|
36
112
|
/** Per-call generation overrides. */
|
|
@@ -41,6 +117,8 @@ export interface ModelCallOptions {
|
|
|
41
117
|
stopSequences?: string[];
|
|
42
118
|
/** Overrides whether providers should allow multiple tool calls in one model turn. */
|
|
43
119
|
parallelToolCalls?: boolean;
|
|
120
|
+
/** Per-call retry override. Default: alias retry setting, then `true`. */
|
|
121
|
+
retry?: ModelRetrySetting;
|
|
44
122
|
providerOptions?: Record<string, unknown>;
|
|
45
123
|
}
|
|
46
124
|
/** Tool call envelope emitted by model adapters. */
|
|
@@ -49,6 +127,17 @@ export interface ToolCallSpec {
|
|
|
49
127
|
name: string;
|
|
50
128
|
arguments: JsonValue;
|
|
51
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Opaque provider wire items returned with a tool-call response and replayed
|
|
132
|
+
* verbatim on the follow-up request of the same turn (e.g. OpenAI Responses
|
|
133
|
+
* API reasoning items). Adapters only replay items tagged with their own
|
|
134
|
+
* provider id; foreign or empty items are ignored and the assistant turn is
|
|
135
|
+
* reconstructed provider-neutrally from `content`/`toolCalls`.
|
|
136
|
+
*/
|
|
137
|
+
export interface ProviderItems {
|
|
138
|
+
providerId: string;
|
|
139
|
+
items: JsonValue[];
|
|
140
|
+
}
|
|
52
141
|
/** Multimodal message content part. */
|
|
53
142
|
export type ContentPart =
|
|
54
143
|
/** Plain text input content. */
|
|
@@ -119,6 +208,7 @@ export type ModelMessage = {
|
|
|
119
208
|
role: 'assistant';
|
|
120
209
|
content: string | ContentPart[];
|
|
121
210
|
toolCalls?: ToolCallSpec[];
|
|
211
|
+
providerItems?: ProviderItems;
|
|
122
212
|
} | {
|
|
123
213
|
role: 'tool';
|
|
124
214
|
toolCallId: string;
|
|
@@ -145,10 +235,20 @@ export type FinishReason =
|
|
|
145
235
|
'stop'
|
|
146
236
|
/** Token budget reached. */
|
|
147
237
|
| 'length'
|
|
238
|
+
/** Context window reached before a valid answer could be produced. */
|
|
239
|
+
| 'context_limit'
|
|
148
240
|
/** Model requested tool calls. */
|
|
149
241
|
| 'tool_calls'
|
|
150
242
|
/** Provider content filter interrupted generation. */
|
|
151
243
|
| 'content_filter'
|
|
244
|
+
/** Provider/model refused the requested output. */
|
|
245
|
+
| 'refusal'
|
|
246
|
+
/** Provider asked the caller to resume/continue later. */
|
|
247
|
+
| 'pause'
|
|
248
|
+
/** Provider produced malformed output or malformed tool use. */
|
|
249
|
+
| 'malformed'
|
|
250
|
+
/** Cooperative cancellation interrupted generation. */
|
|
251
|
+
| 'cancelled'
|
|
152
252
|
/** Provider or adapter error fallback. */
|
|
153
253
|
| 'error';
|
|
154
254
|
/** Tool declaration exposed to model adapters. */
|
|
@@ -165,8 +265,10 @@ export interface TextRequest extends BaseRequest {
|
|
|
165
265
|
export interface TextResponse {
|
|
166
266
|
content: string;
|
|
167
267
|
toolCalls?: ToolCallSpec[];
|
|
268
|
+
providerItems?: ProviderItems;
|
|
168
269
|
usage: TokenUsage;
|
|
169
270
|
finishReason: FinishReason;
|
|
271
|
+
outcome?: ModelOutcome;
|
|
170
272
|
raw?: unknown;
|
|
171
273
|
}
|
|
172
274
|
/** Stream chunk from text-stream generation. */
|
|
@@ -180,6 +282,8 @@ export type TextStreamChunk = {
|
|
|
180
282
|
kind: 'finish';
|
|
181
283
|
usage: TokenUsage;
|
|
182
284
|
finishReason: FinishReason;
|
|
285
|
+
outcome?: ModelOutcome;
|
|
286
|
+
providerItems?: ProviderItems;
|
|
183
287
|
};
|
|
184
288
|
/** Request for object/object-stream model methods. */
|
|
185
289
|
export interface ObjectRequest<T extends JsonValue = JsonValue> extends BaseRequest {
|
|
@@ -191,8 +295,10 @@ export interface ObjectRequest<T extends JsonValue = JsonValue> extends BaseRequ
|
|
|
191
295
|
export interface ObjectResponse<T extends JsonValue = JsonValue> {
|
|
192
296
|
object: T;
|
|
193
297
|
toolCalls?: ToolCallSpec[];
|
|
298
|
+
providerItems?: ProviderItems;
|
|
194
299
|
usage: TokenUsage;
|
|
195
300
|
finishReason: FinishReason;
|
|
301
|
+
outcome?: ModelOutcome;
|
|
196
302
|
raw?: unknown;
|
|
197
303
|
}
|
|
198
304
|
/** Stream chunk from structured object streaming. */
|
|
@@ -211,6 +317,8 @@ export type ObjectStreamChunk<T extends JsonValue = JsonValue> = {
|
|
|
211
317
|
object: T;
|
|
212
318
|
usage: TokenUsage;
|
|
213
319
|
finishReason: FinishReason;
|
|
320
|
+
outcome?: ModelOutcome;
|
|
321
|
+
providerItems?: ProviderItems;
|
|
214
322
|
};
|
|
215
323
|
/** Request for embedding generation. */
|
|
216
324
|
export interface EmbeddingRequest {
|
|
@@ -280,5 +388,7 @@ export interface ModelAlias {
|
|
|
280
388
|
model: string;
|
|
281
389
|
capabilities: readonly ModelCapability[];
|
|
282
390
|
defaults?: ModelDefaults;
|
|
391
|
+
/** Alias-level retry behavior. Default: `true`. */
|
|
392
|
+
retry?: ModelRetrySetting;
|
|
283
393
|
providerOptions?: Record<string, unknown>;
|
|
284
394
|
}
|
|
@@ -121,6 +121,17 @@ export declare class DurableRunLeaseError extends Error {
|
|
|
121
121
|
}
|
|
122
122
|
/** Returns true when a durable run status is terminal. */
|
|
123
123
|
export declare function isTerminalRunStatus(status: DurableRunStatus): status is DurableTerminalRunStatus;
|
|
124
|
+
/**
|
|
125
|
+
* Returns true when a durable run status blocks resume. A `failed` run is
|
|
126
|
+
* terminal for reporting but stays resumable by a retry with the same run id
|
|
127
|
+
* (spec 22 §3); only `succeeded` and `cancelled` reject `startRun`.
|
|
128
|
+
*/
|
|
129
|
+
export declare function isResumeBlockingRunStatus(status: DurableRunStatus): boolean;
|
|
130
|
+
/** Internal in-process FIFO mutex shared by durable runtime implementations. */
|
|
131
|
+
export declare class AsyncMutex {
|
|
132
|
+
private current;
|
|
133
|
+
lock<T>(fn: () => Promise<T>): Promise<T>;
|
|
134
|
+
}
|
|
124
135
|
/**
|
|
125
136
|
* Creates a self-contained in-memory durable runtime for tests and prototypes.
|
|
126
137
|
*
|
package/dist/runtime/durable.js
CHANGED
|
@@ -16,7 +16,16 @@ export class DurableRunLeaseError extends Error {
|
|
|
16
16
|
export function isTerminalRunStatus(status) {
|
|
17
17
|
return status === 'succeeded' || status === 'failed' || status === 'cancelled';
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Returns true when a durable run status blocks resume. A `failed` run is
|
|
21
|
+
* terminal for reporting but stays resumable by a retry with the same run id
|
|
22
|
+
* (spec 22 §3); only `succeeded` and `cancelled` reject `startRun`.
|
|
23
|
+
*/
|
|
24
|
+
export function isResumeBlockingRunStatus(status) {
|
|
25
|
+
return status === 'succeeded' || status === 'cancelled';
|
|
26
|
+
}
|
|
27
|
+
/** Internal in-process FIFO mutex shared by durable runtime implementations. */
|
|
28
|
+
export class AsyncMutex {
|
|
20
29
|
current = Promise.resolve();
|
|
21
30
|
async lock(fn) {
|
|
22
31
|
const prev = this.current;
|
|
@@ -54,7 +63,9 @@ class InMemoryDurableRuntime {
|
|
|
54
63
|
async startRun(record) {
|
|
55
64
|
return this.withSessionLock(record.sessionId, async () => {
|
|
56
65
|
const current = this.runs.get(record.runId);
|
|
57
|
-
|
|
66
|
+
// Only succeeded/cancelled block resume; a failed run is recorded
|
|
67
|
+
// terminal but stays resumable for a retry with the same run id.
|
|
68
|
+
if (current && isResumeBlockingRunStatus(current.status)) {
|
|
58
69
|
throw new DurableTerminalRunError(record.runId, current.status);
|
|
59
70
|
}
|
|
60
71
|
this.assertNoConflictingLease(record);
|
|
@@ -66,6 +77,8 @@ class InMemoryDurableRuntime {
|
|
|
66
77
|
};
|
|
67
78
|
if (current) {
|
|
68
79
|
state.attempt += 1;
|
|
80
|
+
state.status = 'running';
|
|
81
|
+
delete state.finished;
|
|
69
82
|
}
|
|
70
83
|
this.runs.set(record.runId, state);
|
|
71
84
|
const lease = {
|
|
@@ -30,28 +30,47 @@ export async function beginDurableWorkflow(args) {
|
|
|
30
30
|
});
|
|
31
31
|
let handle;
|
|
32
32
|
if (workspaceStore) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
try {
|
|
34
|
+
const priorReplay = lease.checkpoint?.replay;
|
|
35
|
+
if (lease.resumed && priorReplay?.workspaceRef) {
|
|
36
|
+
handle = await workspaceStore.resumeWorkspace({
|
|
37
|
+
workspaceRef: priorReplay.workspaceRef,
|
|
38
|
+
...(priorReplay.checkpointRef ? { checkpointRef: priorReplay.checkpointRef } : {}),
|
|
39
|
+
runId: lease.runId,
|
|
40
|
+
sessionId,
|
|
41
|
+
attempt: lease.attempt,
|
|
42
|
+
idempotencyKey: `${lease.runId}:${lease.attempt}:resume`,
|
|
43
|
+
signal
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
handle = await workspaceStore.startWorkspace({
|
|
48
|
+
runId: lease.runId,
|
|
49
|
+
sessionId,
|
|
50
|
+
workflowId,
|
|
51
|
+
workerId,
|
|
52
|
+
attempt: lease.attempt,
|
|
53
|
+
idempotencyKey: `${lease.runId}:start`,
|
|
54
|
+
signal
|
|
55
|
+
});
|
|
56
|
+
}
|
|
44
57
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
catch (workspaceError) {
|
|
59
|
+
// The caller never receives the binding when the workspace phase fails,
|
|
60
|
+
// so release the acquired lease here or it stays locked for the TTL.
|
|
61
|
+
try {
|
|
62
|
+
await lease.release();
|
|
63
|
+
}
|
|
64
|
+
catch (releaseError) {
|
|
65
|
+
logger.warn('Failed to release durable lease after workspace failure.', {
|
|
66
|
+
harness: harnessName,
|
|
67
|
+
session_id: sessionId,
|
|
68
|
+
run_id: lease.runId,
|
|
69
|
+
workflow_id: workflowId,
|
|
70
|
+
error: serializeError(releaseError)
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
throw workspaceError;
|
|
55
74
|
}
|
|
56
75
|
}
|
|
57
76
|
const activeHandle = handle;
|
|
@@ -86,6 +105,12 @@ export async function beginDurableWorkflow(args) {
|
|
|
86
105
|
const ctx = createDurableWorkflowContext(runtime, lease, onStepCommit ? { onStepCommit } : {});
|
|
87
106
|
const autoCleanup = workspaceStore?.info.policy.retention?.cleanupMode === 'adapter_automatic';
|
|
88
107
|
let settled = false;
|
|
108
|
+
// Stores that bind run sandboxes to active workspaces (localDirectoryWorkspaceStore)
|
|
109
|
+
// expose an unbind hook so the binding never outlives the durable run.
|
|
110
|
+
const releaseRunBinding = () => {
|
|
111
|
+
const candidate = workspaceStore;
|
|
112
|
+
candidate?.releaseRunBinding?.(lease.runId, sessionId);
|
|
113
|
+
};
|
|
89
114
|
return {
|
|
90
115
|
runId: lease.runId,
|
|
91
116
|
attempt: lease.attempt,
|
|
@@ -116,6 +141,7 @@ export async function beginDurableWorkflow(args) {
|
|
|
116
141
|
}
|
|
117
142
|
},
|
|
118
143
|
async dispose() {
|
|
144
|
+
releaseRunBinding();
|
|
119
145
|
if (settled)
|
|
120
146
|
return;
|
|
121
147
|
try {
|
package/dist/sessions/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { RunEvent, Harness, HarnessDefaults, BuilderState, TelemetryOptions
|
|
|
3
3
|
import type { MemoryAdapter } from '../ports/memory.js';
|
|
4
4
|
import type { DurableRuntimeAdapter, HarnessInspection } from '../ports/capabilities.js';
|
|
5
5
|
import type { DurableWorkspaceStore } from '../ports/workspace.js';
|
|
6
|
+
import type { ContextCheckpointStore } from '../ports/context-checkpoints.js';
|
|
6
7
|
import type { Sandbox } from '../sandbox/index.js';
|
|
7
8
|
import type { StateStore } from '../ports/state.js';
|
|
8
9
|
import { type TelemetryShim } from '../telemetry/index.js';
|
|
@@ -16,6 +17,7 @@ type HarnessDefinition<S extends BuilderState> = {
|
|
|
16
17
|
memory: MemoryAdapter;
|
|
17
18
|
runtime?: DurableRuntimeAdapter;
|
|
18
19
|
workspaceStore?: DurableWorkspaceStore;
|
|
20
|
+
checkpoints?: ContextCheckpointStore;
|
|
19
21
|
defaults: HarnessDefaults;
|
|
20
22
|
models: NonNullable<S['models']>;
|
|
21
23
|
tools: NonNullable<S['tools']>;
|
|
@@ -27,12 +29,21 @@ type HarnessDefinition<S extends BuilderState> = {
|
|
|
27
29
|
/**
|
|
28
30
|
* Relay run events from an in-process run to a stream consumer.
|
|
29
31
|
*
|
|
30
|
-
* The unread events live in a bounded queue:
|
|
31
|
-
* growing cursor over a shared array), and on
|
|
32
|
-
* unread event is dropped and counted, so a slow
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* The unread events live in a bounded queue (cap: STREAM_MAX_BUFFERED_EVENTS):
|
|
33
|
+
* consumed events are removed (no growing cursor over a shared array), and on
|
|
34
|
+
* overflow the oldest droppable unread event is dropped and counted, so a slow
|
|
35
|
+
* consumer never silently skips an event without an accompanying
|
|
36
|
+
* `stream.overflow` notice. Only `run.finished` is undroppable; all other
|
|
37
|
+
* event types — including `agent.finished` — may be evicted under pressure.
|
|
38
|
+
* If no droppable event exists when the queue is full, the incoming event is
|
|
39
|
+
* discarded (counted) rather than growing the queue past the cap. Delivery is
|
|
40
|
+
* promise-notified rather than time-polled, so there is no fixed per-event
|
|
41
|
+
* latency or periodic timer.
|
|
42
|
+
*
|
|
43
|
+
* Abandoning the stream (`break` / `iterator.return()`) aborts `relaySignal`,
|
|
44
|
+
* so a run wired to it is cancelled promptly instead of blocking the consumer
|
|
45
|
+
* until the run finishes on its own.
|
|
35
46
|
*/
|
|
36
|
-
export declare function relayRunEvents(run: (onEvent: (event: RunEvent) => Promise<void
|
|
47
|
+
export declare function relayRunEvents(run: (onEvent: (event: RunEvent) => Promise<void>, relaySignal: AbortSignal) => Promise<unknown>): AsyncIterable<RunEvent>;
|
|
37
48
|
export declare function createSessionHarness<S extends BuilderState>(definition: HarnessDefinition<S>): Harness<S>;
|
|
38
49
|
export {};
|