tardie 0.14.0 → 0.15.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/package.json +2 -2
- package/src/agent/components/budget-authority.ts +2 -2
- package/src/agent/components/budget.ts +7 -7
- package/src/agent/components/compaction.ts +1 -1
- package/src/agent/components/permission-authority.ts +2 -2
- package/src/agent/index.ts +2 -2
- package/src/agent/log/events.ts +1 -1
- package/src/agent/packages/agents.ts +66 -42
- package/src/agent/runtime/resume.ts +6 -6
- package/src/bun/host.ts +357 -327
- package/src/channels/providers/slack.ts +2 -2
- package/src/channels/providers/telegram.ts +2 -2
- package/src/cli/celld.ts +4 -1
- package/src/cli/commands.ts +16 -10
- package/src/cli/config.ts +3 -3
- package/src/cli/init.ts +8 -5
- package/src/client/client.ts +42 -30
- package/src/client/contract.ts +49 -18
- package/src/client/index.ts +2 -0
- package/src/client/stream.ts +4 -2
- package/src/cloudflare/host.ts +71 -126
- package/src/cloudflare/index.ts +17 -1
- package/src/cloudflare/storage.ts +165 -51
- package/src/cloudflare/worker.ts +660 -188
- package/src/code/execution/errors.ts +1 -1
- package/src/code/execution/events.ts +3 -3
- package/src/code/execution/ids.ts +2 -2
- package/src/code/execution/projections.ts +6 -6
- package/src/code/execution/reactor.ts +4 -4
- package/src/code/package/workspace.ts +2 -2
- package/src/code/sandbox/defaults.ts +2 -2
- package/src/code/storage/store.ts +1 -1
- package/src/core/actor/method/outgoing.ts +3 -3
- package/src/core/actor/method/response.ts +12 -12
- package/src/core/actor/reference.ts +4 -3
- package/src/core/communication/endpoint.ts +44 -23
- package/src/core/communication/envelope.ts +6 -6
- package/src/core/log/service.ts +25 -0
- package/src/core/reconciliation/reconciler.ts +3 -3
- package/src/core/thread/lineage.ts +80 -16
- package/src/host/communication/channel.ts +2 -2
- package/src/host/communication/ingress.ts +2 -2
- package/src/host/deadlock.ts +22 -22
- package/src/host/driver.ts +40 -40
- package/src/host/host.ts +74 -100
- package/src/server/actor.ts +2 -2
- package/src/server/api.ts +55 -29
- package/src/server/config.ts +12 -12
- package/src/server/driver-gauge.ts +1 -1
- package/src/server/host.ts +186 -67
- package/src/server/http.ts +3 -1
- package/src/server/projections.ts +17 -12
- package/ui/assets/{index-H4ExQl4h.js → index-BN-2ETY9.js} +3 -3
- package/ui/index.html +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tardie",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Clavia, Inc.",
|
|
6
6
|
"description": "A durable agent harness. State is a pure function of the log.",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"tardigrade": {
|
|
19
|
-
"sourceTree": "
|
|
19
|
+
"sourceTree": "9d578e4ded2cd6f98b5b20d18d9c68df4a7622fa"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src",
|
|
@@ -3,7 +3,7 @@ import { Schema } from "effect"
|
|
|
3
3
|
import type { KeyFragment } from "tardie/core/log"
|
|
4
4
|
import { intent, type Transition } from "tardie/core/reconciliation"
|
|
5
5
|
import { handles, type Component } from "tardie/core/actor"
|
|
6
|
-
import {
|
|
6
|
+
import { formatThreadAddress, isThreadAddress } from "tardie/core/communication/endpoint"
|
|
7
7
|
import { BudgetDecision } from "../actor/budget"
|
|
8
8
|
import { budgetRequestDecided, budgetRequestFailed } from "../log/events"
|
|
9
9
|
import { requestBudgetMethod } from "../actor/budget"
|
|
@@ -70,7 +70,7 @@ const authorityTransition = (
|
|
|
70
70
|
turn: String(received.turn ?? ""),
|
|
71
71
|
reason: String(received.reason ?? ""),
|
|
72
72
|
amount,
|
|
73
|
-
...(
|
|
73
|
+
...(isThreadAddress(received.link?.source) ? { from: formatThreadAddress(received.link.source) } : {}),
|
|
74
74
|
grant: (granted = amount) => ({ granted }),
|
|
75
75
|
deny: (reason) => ({ denied: true, ...(reason === undefined ? {} : { reason }) })
|
|
76
76
|
}
|
|
@@ -13,7 +13,7 @@ import { turnHead, turnView } from "tardie/code/execution/turns"
|
|
|
13
13
|
import { AGENT_VIEW_ALGEBRA, type AgentComponent, type AgentTool, type AgentView } from "../runtime/composition"
|
|
14
14
|
import type { ToolSpec } from "../inference/request"
|
|
15
15
|
import { Router } from "tardie/core/communication/router"
|
|
16
|
-
import {
|
|
16
|
+
import { formatThreadAddress, isThreadAddress, type ThreadAddress } from "tardie/core/communication/endpoint"
|
|
17
17
|
import type { Link } from "tardie/core/communication/link"
|
|
18
18
|
import { threadCreatedOf } from "tardie/core/thread"
|
|
19
19
|
import { requestBudgetMethod } from "../actor/budget"
|
|
@@ -194,8 +194,8 @@ const requestBudgetTool: AgentTool = {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
const requestCallId = (child:
|
|
198
|
-
`budget/${
|
|
197
|
+
const requestCallId = (child: ThreadAddress, turn: string, request: string): string =>
|
|
198
|
+
`budget/${formatThreadAddress(child)}/${turn}/${request}`
|
|
199
199
|
|
|
200
200
|
const authorityFor = (
|
|
201
201
|
log: ReadonlyArray<Event>,
|
|
@@ -206,17 +206,17 @@ const authorityFor = (
|
|
|
206
206
|
if ("address" in authority) return authority
|
|
207
207
|
const head = log.find((event) =>
|
|
208
208
|
event.type === "MessageReceived" && String((event as { readonly id?: unknown }).id) === turn
|
|
209
|
-
) as { readonly link?: Link<unknown,
|
|
210
|
-
return
|
|
209
|
+
) as { readonly link?: Link<unknown, ThreadAddress> } | undefined
|
|
210
|
+
return isThreadAddress(head?.link?.source)
|
|
211
211
|
? { address: head.link.source, methods: { requestBudget: requestBudgetMethod } }
|
|
212
212
|
: undefined
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
const sourceFor = (log: ReadonlyArray<Event>, turn: string):
|
|
215
|
+
const sourceFor = (log: ReadonlyArray<Event>, turn: string): ThreadAddress | undefined => {
|
|
216
216
|
const head = log.find((event) =>
|
|
217
217
|
event.type === "MessageReceived" && String((event as { readonly id?: unknown }).id) === turn
|
|
218
218
|
) as { readonly link?: Link<unknown, unknown> } | undefined
|
|
219
|
-
if (
|
|
219
|
+
if (isThreadAddress(head?.link?.target)) return head.link.target
|
|
220
220
|
return threadCreatedOf(log)?.address
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -167,7 +167,7 @@ export const resolvedContextPolicyOf = (policy: Partial<ContextPolicy> = {}): Co
|
|
|
167
167
|
|
|
168
168
|
// renderedChars counts the characters a render sends for one event: capped where the render
|
|
169
169
|
// caps, zero for an event the render skips. The guard must measure the request the model sees;
|
|
170
|
-
// a measure over raw event JSON counts tool results the render truncates and
|
|
170
|
+
// a measure over raw event JSON counts tool results the render truncates and threads the render
|
|
171
171
|
// never shows, and fires against a size no request ever reaches.
|
|
172
172
|
const renderedChars = (e: Event, policy: ContextPolicy): number => {
|
|
173
173
|
const v = e as Record<string, unknown>
|
|
@@ -3,7 +3,7 @@ import { Schema } from "effect"
|
|
|
3
3
|
import type { KeyFragment } from "tardie/core/log"
|
|
4
4
|
import { intent, type Transition } from "tardie/core/reconciliation"
|
|
5
5
|
import { externallyHandled, handles, type Component } from "tardie/core/actor"
|
|
6
|
-
import {
|
|
6
|
+
import { formatThreadAddress, isThreadAddress } from "tardie/core/communication/endpoint"
|
|
7
7
|
import { PermissionDecision, requestPermissionMethod } from "../actor/permission"
|
|
8
8
|
import { permissionRequestDecided, permissionRequestFailed } from "../log/events"
|
|
9
9
|
|
|
@@ -71,7 +71,7 @@ const authorityTransition = (
|
|
|
71
71
|
action: String(received.action ?? ""),
|
|
72
72
|
...(typeof received.resource === "string" ? { resource: received.resource } : {}),
|
|
73
73
|
reason: String(received.reason ?? ""),
|
|
74
|
-
...(
|
|
74
|
+
...(isThreadAddress(received.link?.source) ? { from: formatThreadAddress(received.link.source) } : {}),
|
|
75
75
|
grant: () => ({ granted: true }),
|
|
76
76
|
deny: (reason) => ({ denied: true, ...(reason === undefined ? {} : { reason }) })
|
|
77
77
|
}
|
package/src/agent/index.ts
CHANGED
|
@@ -140,8 +140,8 @@ export {
|
|
|
140
140
|
// boundary is a projection of the log rather than a value the driver returns (boundary.ts).
|
|
141
141
|
export { boundaryOf, outputOf, type Boundary } from "./output/boundary"
|
|
142
142
|
|
|
143
|
-
// The spawn package: a value with no
|
|
144
|
-
// that binds Router and Self per
|
|
143
|
+
// The spawn package: a value with no thread in it, so the assembly that mounts it and the host
|
|
144
|
+
// that binds Router and Self per thread cannot disagree about placement (packages/agents.ts).
|
|
145
145
|
export {
|
|
146
146
|
agentsPackage,
|
|
147
147
|
INLINE_OUTPUT_NAME,
|
package/src/agent/log/events.ts
CHANGED
|
@@ -377,7 +377,7 @@ export type Action =
|
|
|
377
377
|
}
|
|
378
378
|
} & Served)
|
|
379
379
|
|
|
380
|
-
// agentKeys is the agent
|
|
380
|
+
// agentKeys is the agent thread's dedup fragment, owned beside its alphabet. tr names the tool call's recorded
|
|
381
381
|
// pair; bdec names the budget request a local decision answers, so a grant and denial for one request
|
|
382
382
|
// cannot both commit. A grant is summed into the ceiling, so a redelivery must also absorb. A
|
|
383
383
|
// decision that carries no callId predates the stamp and lands unkeyed; the fold tolerates it.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, Effect } from "effect"
|
|
1
|
+
import { Clock, Effect, Schema } from "effect"
|
|
2
2
|
import { Router } from "tardie/core/communication/router"
|
|
3
3
|
import { Self } from "tardie/core/reconciliation"
|
|
4
4
|
import { EventLog } from "tardie/core/log"
|
|
@@ -8,11 +8,19 @@ import { Park } from "tardie/code/execution/errors"
|
|
|
8
8
|
import { boundaryId } from "tardie/core/communication/message"
|
|
9
9
|
import { linkOf } from "tardie/core/communication/link"
|
|
10
10
|
import { methodEnvelopeOf } from "tardie/core/communication/envelope"
|
|
11
|
-
import { childLineageOf, threadCreatedOf } from "tardie/core/thread"
|
|
12
11
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
ChildCreated,
|
|
13
|
+
childCreated,
|
|
14
|
+
childLineageOf,
|
|
15
|
+
ChildPlacement,
|
|
16
|
+
threadCreatedOf,
|
|
17
|
+
type ThreadCreated,
|
|
18
|
+
type ThreadLineage
|
|
19
|
+
} from "tardie/core/thread"
|
|
20
|
+
import {
|
|
21
|
+
threadAddressOf,
|
|
22
|
+
formatThreadAddress,
|
|
23
|
+
type ThreadAddress
|
|
16
24
|
} from "tardie/core/communication/endpoint"
|
|
17
25
|
import { decodeOutput, outputFrom, type OutputContract } from "../output/contract"
|
|
18
26
|
import { modelRefOf } from "../inference/reference"
|
|
@@ -40,14 +48,11 @@ import {
|
|
|
40
48
|
// message id, so a replayed dispatch reaches the same child and is absorbed as a duplicate.
|
|
41
49
|
//
|
|
42
50
|
// The package is a value any consumer mounts: its host privileges are services, not constructor
|
|
43
|
-
// arguments. `Router` sends, `Self` names the calling
|
|
51
|
+
// arguments. `Router` sends, `Self` names the calling thread, and `EventLog` supplies its durable
|
|
44
52
|
// calls, responses, and creation record.
|
|
45
53
|
//
|
|
46
|
-
// place selects the child's actor address from the call id and parent address. The host resolves
|
|
47
|
-
// that stable identity to current placement when it interprets the resulting link.
|
|
48
|
-
//
|
|
49
54
|
// A plain foreground run parks, the same mechanism `tasks.fire` (`src/packages/tasks.ts`) uses:
|
|
50
|
-
// deliver the brief through a link from this
|
|
55
|
+
// deliver the brief through a link from this thread, then await the reply row on this thread, host-side
|
|
51
56
|
// `Park` when it has not landed yet (`src/code/execute.ts`'s proxy is what turns that into a promise
|
|
52
57
|
// that never settles for the code body). It never holds a call open.
|
|
53
58
|
//
|
|
@@ -246,19 +251,46 @@ const catalogQueryOf = (args: unknown): AgentCatalogQuery => {
|
|
|
246
251
|
}
|
|
247
252
|
}
|
|
248
253
|
|
|
249
|
-
// sibling is the default
|
|
250
|
-
const sibling = (callId: string, self:
|
|
251
|
-
|
|
254
|
+
// sibling is the default address: the child inherits the parent's actor instance and uses the name `ag.<callId>`. Thread placement remains a separate host decision (spawn.test.ts, "the default address is the host's own sibling"; tla/runtime/Thread.tla, CreationFirst).
|
|
255
|
+
const sibling = (callId: string, self: ThreadAddress): ThreadAddress =>
|
|
256
|
+
threadAddressOf(self.actor, self.instance, `ag.${callId}`)
|
|
257
|
+
|
|
258
|
+
const childClaimOf = (
|
|
259
|
+
placement: unknown,
|
|
260
|
+
events: ReadonlyArray<{ readonly type: string }>,
|
|
261
|
+
parent: ThreadCreated,
|
|
262
|
+
callId: string,
|
|
263
|
+
source: ThreadAddress
|
|
264
|
+
) => {
|
|
265
|
+
if (placement !== undefined && !Schema.is(ChildPlacement)(placement)) {
|
|
266
|
+
return { error: "agents.run placement must be colocated or independent" }
|
|
267
|
+
}
|
|
268
|
+
const recorded = events.find(
|
|
269
|
+
(event) => event.type === "ChildCreated" && (event as { readonly callId?: unknown }).callId === callId
|
|
270
|
+
)
|
|
271
|
+
if (recorded !== undefined && !Schema.is(ChildCreated)(recorded)) {
|
|
272
|
+
throw new Error(`child ${callId} has an invalid creation record`)
|
|
273
|
+
}
|
|
274
|
+
const lineage: ThreadLineage = recorded === undefined
|
|
275
|
+
? childLineageOf(parent, placement as ChildPlacement | undefined)
|
|
276
|
+
: {
|
|
277
|
+
parent: parent.address,
|
|
278
|
+
depth: recorded.depth,
|
|
279
|
+
...(recorded.placement === undefined ? {} : { placement: recorded.placement })
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
recorded,
|
|
283
|
+
target: recorded?.address ?? sibling(callId, source),
|
|
284
|
+
lineage
|
|
285
|
+
}
|
|
286
|
+
}
|
|
252
287
|
|
|
253
288
|
const inheritedModelsOf = (events: ReadonlyArray<{ readonly type: string }>): ModelPolicy => {
|
|
254
289
|
const resolved = [...events].reverse().find((event) => event.type === "ModelResolved") as { readonly models?: unknown } | undefined
|
|
255
290
|
return resolved?.models === undefined ? DEFAULT_MODEL_POLICY : modelPolicyOf(resolved.models)
|
|
256
291
|
}
|
|
257
292
|
|
|
258
|
-
export const agentsPackage = (
|
|
259
|
-
options: SpawnOptions & { readonly place?: (callId: string, self: ActorId) => ActorId } = {}
|
|
260
|
-
): Package<Router | Self | EventLog> => {
|
|
261
|
-
const place = options.place ?? sibling
|
|
293
|
+
export const agentsPackage = (options: SpawnOptions = {}): Package<Router | Self | EventLog> => {
|
|
262
294
|
const actorNameOf = options.actorNameOf ?? (() => undefined)
|
|
263
295
|
const reserve = options.reserve ?? (async (_callId: string, want: number) => want)
|
|
264
296
|
const shadowOf = options.shadowOf ?? (() => false)
|
|
@@ -331,6 +363,7 @@ export const agentsPackage = (
|
|
|
331
363
|
additionalProperties: false
|
|
332
364
|
},
|
|
333
365
|
budget: { type: "integer", description: "max tool calls before the agent must answer, a whole number of calls; keeps a research agent bounded" },
|
|
366
|
+
placement: { type: "string", enum: ["colocated", "independent"], description: "place the child relative to this thread's host" },
|
|
334
367
|
escalatable: { type: "boolean", description: "true: at its budget the child may ask its parent's budget authority for more before answering" }
|
|
335
368
|
},
|
|
336
369
|
required: ["text"]
|
|
@@ -394,7 +427,7 @@ export const agentsPackage = (
|
|
|
394
427
|
}),
|
|
395
428
|
run: (args, ctx) =>
|
|
396
429
|
Effect.gen(function* () {
|
|
397
|
-
// The three cross-
|
|
430
|
+
// The three cross-thread privileges, read where the work happens: send, identity,
|
|
398
431
|
// observe. A host that binds them serves this method; nothing here closes over one.
|
|
399
432
|
const router = yield* Router
|
|
400
433
|
const source = yield* Self
|
|
@@ -402,15 +435,17 @@ export const agentsPackage = (
|
|
|
402
435
|
const events = yield* log.read
|
|
403
436
|
const created = threadCreatedOf(events)
|
|
404
437
|
if (created === undefined) {
|
|
405
|
-
return yield* Effect.die(new Error(`thread ${
|
|
438
|
+
return yield* Effect.die(new Error(`thread ${formatThreadAddress(source)} cannot spawn without ThreadCreated`))
|
|
406
439
|
}
|
|
407
|
-
const
|
|
408
|
-
const self = formatActorId(source)
|
|
440
|
+
const self = formatThreadAddress(source)
|
|
409
441
|
const a = args as
|
|
410
|
-
| { text?: unknown; background?: unknown; output?: unknown; outputSchema?: unknown; model?: unknown; budget?: unknown; escalatable?: unknown }
|
|
442
|
+
| { text?: unknown; background?: unknown; output?: unknown; outputSchema?: unknown; model?: unknown; budget?: unknown; escalatable?: unknown; placement?: unknown }
|
|
411
443
|
| undefined
|
|
412
444
|
const text = String(a?.text ?? "")
|
|
413
445
|
if (text === "") return { error: "agents.run needs { text }" }
|
|
446
|
+
const child = childClaimOf(a?.placement, events, created, ctx.callId, source)
|
|
447
|
+
if ("error" in child) return child
|
|
448
|
+
const { lineage, recorded: recordedChild, target } = child
|
|
414
449
|
// The contract parameter is `output`, and a near-miss spelling fails silently: no
|
|
415
450
|
// contract means a prose answer, so the caller's field reads come back undefined and
|
|
416
451
|
// the run returns something plausible and wrong. Say so instead.
|
|
@@ -454,11 +489,8 @@ export const agentsPackage = (
|
|
|
454
489
|
// the same way, when the fire named an explicit shared one.
|
|
455
490
|
const shadow = shadowOf()
|
|
456
491
|
const world = worldOf()
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
// A background run uses the same actor protocol. Its budget request can be served while
|
|
460
|
-
// no code body awaits it, and its terminal is collected later by agents.result.
|
|
461
|
-
const at = yield* Clock.currentTimeMillis
|
|
492
|
+
const dispatch = (at: number) => Effect.gen(function* () {
|
|
493
|
+
if (recordedChild === undefined) yield* log.append([childCreated(ctx.callId, target, lineage, at)])
|
|
462
494
|
yield* router.send(methodEnvelopeOf(linkOf(source, target), { method: "message", id: ctx.callId }, {
|
|
463
495
|
type: "MessageReceived",
|
|
464
496
|
id: ctx.callId,
|
|
@@ -474,6 +506,12 @@ export const agentsPackage = (
|
|
|
474
506
|
from: self,
|
|
475
507
|
at
|
|
476
508
|
}, lineage))
|
|
509
|
+
})
|
|
510
|
+
if (a?.background === true) {
|
|
511
|
+
// A background run uses the same actor protocol. Its budget request can be served while
|
|
512
|
+
// no code body awaits it, and its terminal is collected later by agents.result.
|
|
513
|
+
const at = yield* Clock.currentTimeMillis
|
|
514
|
+
yield* dispatch(at)
|
|
477
515
|
return { dispatched: true, callId: ctx.callId }
|
|
478
516
|
}
|
|
479
517
|
// Foreground runs park on their terminal response. A replay reads that response
|
|
@@ -481,21 +519,7 @@ export const agentsPackage = (
|
|
|
481
519
|
const already = yield* awaitedBoundary(ctx.callId)
|
|
482
520
|
if (already !== undefined) return shape(answerOf(already), ctx.callId, output)
|
|
483
521
|
const at = yield* Clock.currentTimeMillis
|
|
484
|
-
yield*
|
|
485
|
-
type: "MessageReceived",
|
|
486
|
-
id: ctx.callId,
|
|
487
|
-
text,
|
|
488
|
-
...(outputDeclaration === undefined ? {} : { output: outputDeclaration }),
|
|
489
|
-
...(selectedModel === undefined ? {} : { model: selectedModel }),
|
|
490
|
-
models,
|
|
491
|
-
budget,
|
|
492
|
-
...(a?.escalatable === true ? { escalatable: true } : {}),
|
|
493
|
-
...(actor === undefined ? {} : { actor }),
|
|
494
|
-
...(shadow ? { shadow: true } : {}),
|
|
495
|
-
...(world === undefined ? {} : { world }),
|
|
496
|
-
from: self,
|
|
497
|
-
at
|
|
498
|
-
}, lineage))
|
|
522
|
+
yield* dispatch(at)
|
|
499
523
|
return yield* new Park({ callId: ctx.callId, awaiting: boundaryId(ctx.callId, 0) })
|
|
500
524
|
}),
|
|
501
525
|
// Await a run already fired in the background: no delivery, the same reply-or-park read the
|
|
@@ -7,10 +7,10 @@ type Awaitable<T> = T | Promise<T>
|
|
|
7
7
|
|
|
8
8
|
// TurnDriver is the common control surface of the memory host and the durable Bun host.
|
|
9
9
|
export interface TurnDriver {
|
|
10
|
-
readonly read: (
|
|
10
|
+
readonly read: (thread: string) => Awaitable<ReadonlyArray<Event>>
|
|
11
11
|
readonly commitRoot: (address: string, event: Event) => Awaitable<void>
|
|
12
12
|
readonly drive: () => Promise<void>
|
|
13
|
-
readonly self: (
|
|
13
|
+
readonly self: (thread: string) => string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface ResumeTurnOptions {
|
|
@@ -20,20 +20,20 @@ export interface ResumeTurnOptions {
|
|
|
20
20
|
// resumeTurn starts a new execution epoch from a failed turn's committed history.
|
|
21
21
|
export const resumeTurn = async (
|
|
22
22
|
host: TurnDriver,
|
|
23
|
-
|
|
23
|
+
thread: string,
|
|
24
24
|
turn: string,
|
|
25
25
|
options: ResumeTurnOptions = {}
|
|
26
26
|
): Promise<Boundary | undefined> => {
|
|
27
|
-
const before = await host.read(
|
|
27
|
+
const before = await host.read(thread)
|
|
28
28
|
const boundary = boundaryOf(before, turn)
|
|
29
29
|
if (boundary?.kind !== "failed") {
|
|
30
30
|
throw new Error(`turn "${turn}" cannot resume because its active epoch is not failed`)
|
|
31
31
|
}
|
|
32
32
|
const failedEpoch = turnEpochOf(before, turn)
|
|
33
33
|
await host.commitRoot(
|
|
34
|
-
host.self(
|
|
34
|
+
host.self(thread),
|
|
35
35
|
turnResumed({ turn, failedEpoch, epoch: failedEpoch + 1, at: options.at ?? Date.now() })
|
|
36
36
|
)
|
|
37
37
|
await host.drive()
|
|
38
|
-
return boundaryOf(await host.read(
|
|
38
|
+
return boundaryOf(await host.read(thread), turn)
|
|
39
39
|
}
|