@xandreed/sdk 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/README.md +1 -1
- package/dist/agent.adapter.d.ts +66 -34
- package/dist/agent.adapter.js +48 -37
- package/dist/conformance/index.d.ts +2 -0
- package/dist/conformance/index.js +1 -0
- package/dist/inbox-runner.adapter.d.ts +18 -0
- package/dist/inbox-runner.adapter.js +8 -0
- package/dist/index.d.ts +35 -2
- package/dist/index.js +59 -2
- package/dist/task-executor.adapter.d.ts +32 -0
- package/dist/task-executor.adapter.js +10 -0
- package/dist/task-inbox-runner.adapter.d.ts +11 -0
- package/dist/task-inbox-runner.adapter.js +22 -0
- package/dist/turn/memory-checkpoint.adapter.d.ts +17 -0
- package/dist/turn/memory-checkpoint.adapter.js +62 -0
- package/dist/turn/model-call-recorder.adapter.d.ts +9 -0
- package/dist/turn/model-call-recorder.adapter.js +93 -0
- package/dist/turn/model-call-recorder.entity.d.ts +145 -0
- package/dist/turn/model-call-recorder.entity.functions.d.ts +5 -0
- package/dist/turn/model-call-recorder.entity.functions.js +4 -0
- package/dist/turn/model-call-recorder.entity.js +8 -0
- package/dist/turn/tool-registry.conformance.d.ts +61 -0
- package/dist/turn/tool-registry.conformance.js +547 -0
- package/dist/turn/turn-after-reply.entity.functions.d.ts +9 -0
- package/dist/turn/turn-after-reply.entity.functions.js +4 -0
- package/dist/turn/turn-lifecycle.usecase.functions.d.ts +135 -0
- package/dist/turn/turn-lifecycle.usecase.functions.js +131 -0
- package/dist/turn/turn-observation.entity.d.ts +28 -0
- package/dist/turn/turn-observation.entity.functions.d.ts +15 -0
- package/dist/turn/turn-observation.entity.functions.js +17 -0
- package/dist/turn/turn-observation.entity.js +12 -0
- package/dist/turn/turn-overflow.entity.d.ts +7 -0
- package/dist/turn/turn-overflow.entity.functions.d.ts +11 -0
- package/dist/turn/turn-overflow.entity.functions.js +12 -0
- package/dist/turn/turn-overflow.entity.js +5 -0
- package/dist/turn/turn-phase.entity.d.ts +462 -0
- package/dist/turn/turn-phase.entity.functions.d.ts +44 -0
- package/dist/turn/turn-phase.entity.functions.js +49 -0
- package/dist/turn/turn-phase.entity.js +13 -0
- package/dist/turn/turn-run.usecase.functions.d.ts +72 -0
- package/dist/turn/turn-run.usecase.functions.js +329 -0
- package/dist/turn/turn.adapter.d.ts +26 -0
- package/dist/turn/turn.adapter.js +225 -0
- package/package.json +10 -4
- package/dist/activity.test.d.ts +0 -1
- package/dist/agent.adapter.test.d.ts +0 -1
- package/dist/agent.backends.test.d.ts +0 -1
- package/dist/agent.golden.test.d.ts +0 -231
- package/dist/agent.trace.test.d.ts +0 -1
- package/dist/events-guide.test.d.ts +0 -207
- package/dist/journal-compat.test.d.ts +0 -1
- package/dist/memory.conformance.test.d.ts +0 -1
- package/dist/models-plugin.test.d.ts +0 -1
- package/dist/plugin-stack.test.d.ts +0 -1
- package/dist/resume.journey.test.d.ts +0 -1
- package/dist/testing.port.d.ts +0 -16
- package/dist/testing.port.js +0 -7
- package/dist/turn-composition.test.d.ts +0 -1
package/README.md
CHANGED
package/dist/agent.adapter.d.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { Context, Effect } from "effect";
|
|
2
2
|
import type { Scope } from "effect";
|
|
3
3
|
import { HarnessError } from "@xandreed/core";
|
|
4
|
-
import type { Capability, LoopLimits, Plugin, ProcessActivity, Turn, TurnInput, TurnOutcome, TurnResult, TurnServices } from "@xandreed/core";
|
|
5
|
-
/**
|
|
4
|
+
import type { Capability, LoopLimits, Plugin, ProcessActivity, SessionLogError, ShutdownOptions, ShutdownReport, Turn, TurnBeginError, TurnError, TurnInput, TurnOutcome, TurnResult, TurnServices } from "@xandreed/core";
|
|
5
|
+
/**
|
|
6
|
+
* One plugin instance of an agent: the definition, its options and (optionally) an instance id.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
6
10
|
export interface AgentPluginEntry {
|
|
7
11
|
readonly plugin: Plugin;
|
|
8
12
|
/** Defaults to the plugin id. */
|
|
9
13
|
readonly id?: string;
|
|
10
14
|
readonly options?: Readonly<Record<string, unknown>>;
|
|
11
15
|
}
|
|
12
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Everything an agent is made of. Swapping a strategy is swapping one entry.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
13
21
|
export interface AgentConfig {
|
|
14
22
|
/** Memory log and strategy, tool registry, step loop, and any capability plugins. */
|
|
15
23
|
readonly plugins: ReadonlyArray<Plugin | AgentPluginEntry>;
|
|
@@ -32,7 +40,11 @@ export interface AgentConfig {
|
|
|
32
40
|
/** The prompt-cache key is `<prefix>:<conversation>`; none is sent without a prefix. */
|
|
33
41
|
readonly cacheKeyPrefix?: string;
|
|
34
42
|
readonly workspace?: string;
|
|
35
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Defaults for every run; a turn's policy overrides them. Its loop
|
|
45
|
+
* protection (`degenerate`, `malformedLimit`) is decoded here
|
|
46
|
+
* (`config.limits`), and a run's merged limits again (`turn.limits`).
|
|
47
|
+
*/
|
|
36
48
|
readonly limits?: Partial<LoopLimits>;
|
|
37
49
|
/**
|
|
38
50
|
* Every run's token budget unless its policy gives one; 64 000 by default.
|
|
@@ -43,9 +55,11 @@ export interface AgentConfig {
|
|
|
43
55
|
* prompt and tool schemas leave of it (`context.budget` when they leave
|
|
44
56
|
* nothing). The window strategy compacts, spills and drops older turns to
|
|
45
57
|
* fit the budget it is given, and fails with `context.budget` when the
|
|
46
|
-
* current turn alone does not; the summary strategy summarizes
|
|
47
|
-
*
|
|
48
|
-
*
|
|
58
|
+
* current turn alone does not; the summary strategy summarizes after the
|
|
59
|
+
* turn's sealed reply (and, as a safety net, at a run's start) when
|
|
60
|
+
* messages pass its `triggerRatio`; the digester ignores it. A model whose `ModelCatalog` window (less its output cap) is
|
|
61
|
+
* smaller lowers a step's ceiling to it. Each step's `context.built`
|
|
62
|
+
* records it.
|
|
49
63
|
*/
|
|
50
64
|
readonly budgetTokens?: number;
|
|
51
65
|
/** How deep event reactions may nest before the bus fails the publisher. */
|
|
@@ -66,6 +80,13 @@ export interface AgentConfig {
|
|
|
66
80
|
* (see `TurnTraceOptions`).
|
|
67
81
|
*/
|
|
68
82
|
readonly spanMeta?: ReadonlyArray<string>;
|
|
83
|
+
/**
|
|
84
|
+
* A graceful shutdown when the scope `Agent.define` ran in closes: before
|
|
85
|
+
* the agent's plugins close (its sessions plugin and its log with them),
|
|
86
|
+
* `Agent.shutdown` runs with these options and waits for it. Without it,
|
|
87
|
+
* closing that scope stops no turn first.
|
|
88
|
+
*/
|
|
89
|
+
readonly shutdown?: ShutdownOptions;
|
|
69
90
|
}
|
|
70
91
|
/** A defined agent: its graph is built; each turn is composed by the host. */
|
|
71
92
|
export interface Agent {
|
|
@@ -75,15 +96,36 @@ export interface Agent {
|
|
|
75
96
|
* runtime services see it: the `TurnActivity` of a runtime-scoped
|
|
76
97
|
* sessions plugin in its graph (or one in `services`), and the
|
|
77
98
|
* `TaskActivity` of a task runner in `services` (`InProcessTaskRunnerLive`
|
|
78
|
-
* built into them)
|
|
99
|
+
* built into them), owner-observed `WaitingInput`, and active `DrainActivity`:
|
|
100
|
+
* an inbox drainer (`InboxDrainLive`) runs the agent, so it is built beside
|
|
101
|
+
* it and never in its runtime, and the `DrainActivity` provided where this
|
|
102
|
+
* is read counts instead (so does `shutdown`'s).
|
|
103
|
+
* Queue storage failures remain SessionLogError. A missing probe counts nothing: a Sessions
|
|
79
104
|
* the host passes only in a turn's services is not seen here (read
|
|
80
105
|
* `processActivity` with the host's own services instead).
|
|
81
106
|
*/
|
|
82
|
-
readonly activity: Effect.Effect<ProcessActivity>;
|
|
107
|
+
readonly activity: Effect.Effect<ProcessActivity, SessionLogError>;
|
|
108
|
+
/**
|
|
109
|
+
* Stop this agent's turns within a deadline, while its services are open
|
|
110
|
+
* (`shutdownGracefully` over the same runtime services `activity` reads):
|
|
111
|
+
* refuse every new begin (`TurnRefused`, reason `shutdown`), wait for the
|
|
112
|
+
* turns and tasks running, then close the turns still running at the
|
|
113
|
+
* deadline less its reserve as interrupted with `turn.shutdown`
|
|
114
|
+
* (resumable) and wait out the reserve. `options` default to
|
|
115
|
+
* `AgentConfig.shutdown`, else a deadline of `DEFAULT_SHUTDOWN_DEADLINE`.
|
|
116
|
+
* Begins stay refused afterwards: call it when the process is about to
|
|
117
|
+
* stop (a SIGTERM), then stop the host's request fibers and close the
|
|
118
|
+
* agent's scope.
|
|
119
|
+
*/
|
|
120
|
+
readonly shutdown: (options?: ShutdownOptions) => Effect.Effect<ShutdownReport>;
|
|
83
121
|
/**
|
|
84
122
|
* Run one turn: one the host began (its `TurnWriter`; the host ends it),
|
|
85
123
|
* or one begun and ended here through the `Sessions` in the turn's
|
|
86
|
-
* services.
|
|
124
|
+
* services. A turn to begin here that cannot begin fails with the refusal
|
|
125
|
+
* itself (`TurnBeginError`: `SessionBusy`, `TurnDuplicate`,
|
|
126
|
+
* `NotResumable`…; match it with `Effect.catchTag`, `harnessCodeOf` gives
|
|
127
|
+
* its code), before anything is opened, run or counted.
|
|
128
|
+
* The turn is scoped: its subscriptions and tasks end with it.
|
|
87
129
|
* On success, tasks and background subscriptions settle as the scopes
|
|
88
130
|
* close in dependency order, before the reply is recorded exactly once.
|
|
89
131
|
* Failures and interruptions record a failed reply and cancel remaining
|
|
@@ -91,36 +133,26 @@ export interface Agent {
|
|
|
91
133
|
* begun here ends with `turnEndingOf` its exit: its outcome, or failed
|
|
92
134
|
* with the error's code (`turn.failed` for an error without one,
|
|
93
135
|
* `turn.defect`), or interrupted (`turn.interrupted`); on success, a
|
|
94
|
-
* failure to store that end fails the turn
|
|
95
|
-
*
|
|
136
|
+
* failure to store that end fails the turn, and on failure one is logged.
|
|
137
|
+
* A turn closed
|
|
138
|
+
* elsewhere (cancelled, reaped, its session removed) fails with that
|
|
139
|
+
* `TurnClosed` (stored as `turn.closed`). `use` runs in
|
|
96
140
|
* the turn's scope with the turn's services (RunContext, TurnEvents,
|
|
97
141
|
* TurnTasks and the input's `layer`) provided: the same instances the
|
|
98
142
|
* tools, policy and subscriptions see. It returns `use`'s outcome with
|
|
99
|
-
* the turn's usage (every run's provider calls, summed)
|
|
143
|
+
* the turn's usage (every run's provider calls, summed) and, for a turn
|
|
144
|
+
* begun here, `pending`: the inbox items its end left waiting (results
|
|
145
|
+
* delivered while it ran), which the caller drains (`Sessions.drain`).
|
|
146
|
+
* From the moment
|
|
100
147
|
* the turn is admitted it runs under its `agent.turn` span (`traceTurn`),
|
|
101
|
-
*
|
|
102
|
-
*
|
|
148
|
+
* its logs annotated with the turn's ids (`turnAttributes`), and is
|
|
149
|
+
* counted in the `efferent.turns` and `efferent.turn.duration` metrics by
|
|
150
|
+
* its `efferent.outcome`. A turn begun here begins under its own
|
|
151
|
+
* `agent.turn.begin` span beside it (`traceTurnBegin`), which ends with
|
|
152
|
+
* the refusal's code when the turn cannot begin.
|
|
103
153
|
*/
|
|
104
|
-
readonly turn: <A = never, E = never, R = never>(input: TurnInput<A, E>, use: (turn: Turn) => Effect.Effect<TurnOutcome,
|
|
154
|
+
readonly turn: <A = never, E = never, R = never>(input: TurnInput<A, E>, use: (turn: Turn) => Effect.Effect<TurnOutcome, TurnError, R>) => Effect.Effect<TurnResult, TurnBeginError | TurnError | E, Exclude<R, A | TurnServices | Scope.Scope>>;
|
|
105
155
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Why a turn could not begin (`Sessions.begin` failed, or, for
|
|
108
|
-
* `AttachmentRejected`, the attachments' check before it), as the code of
|
|
109
|
-
* the `HarnessError` `Agent.turn` fails with: keyed by the begin error's
|
|
110
|
-
* tag. An `AttachmentRejected`'s `reason` is in the message only; a host
|
|
111
|
-
* that branches on it runs `normalizeUserMessage` itself before the turn.
|
|
112
|
-
*/
|
|
113
|
-
export declare const turnBeginCodes: {
|
|
114
|
-
readonly AttachmentRejected: "turn.attachment-rejected";
|
|
115
|
-
readonly SessionBusy: "session.busy";
|
|
116
|
-
readonly TurnDuplicate: "turn.duplicate";
|
|
117
|
-
readonly KeyConflict: "turn.key-conflict";
|
|
118
|
-
readonly NothingPending: "session.nothing-pending";
|
|
119
|
-
readonly NotResumable: "turn.not-resumable";
|
|
120
|
-
readonly TurnRefused: "turn.refused";
|
|
121
|
-
readonly SessionMissing: "session.missing";
|
|
122
|
-
readonly SessionLogError: "session.log";
|
|
123
|
-
};
|
|
124
156
|
export declare const Agent: {
|
|
125
157
|
define: (config: AgentConfig) => Effect.Effect<Agent, HarnessError, Scope.Scope>;
|
|
126
158
|
};
|
package/dist/agent.adapter.js
CHANGED
|
@@ -1,38 +1,43 @@
|
|
|
1
|
-
import { Clock, Context, Duration, Effect, Exit, Metric, Option, Schema } from "effect";
|
|
1
|
+
import { Cause, Clock, Context, Duration, Effect, Exit, Metric, Option, Schema } from "effect";
|
|
2
2
|
import {
|
|
3
|
-
cacheKeyOf,
|
|
4
3
|
CurrentTraceContent,
|
|
4
|
+
DrainActivity,
|
|
5
|
+
WaitingInput,
|
|
5
6
|
CapabilitiesLive,
|
|
7
|
+
DEFAULT_SHUTDOWN_DEADLINE,
|
|
6
8
|
Capabilities,
|
|
7
9
|
ConversationMemory,
|
|
8
10
|
definePlugin,
|
|
9
|
-
guardTurn,
|
|
10
11
|
harnessDefectsAsFailures,
|
|
11
12
|
HarnessError,
|
|
13
|
+
LoopProtection,
|
|
12
14
|
isBudgetTokens,
|
|
13
15
|
normalizeUserMessage,
|
|
14
|
-
|
|
16
|
+
PluginInstanceId,
|
|
15
17
|
processActivity,
|
|
18
|
+
reasonOf,
|
|
16
19
|
replayableContext,
|
|
17
20
|
SessionEnvironment,
|
|
18
21
|
Sessions,
|
|
22
|
+
shutdownGracefully,
|
|
19
23
|
StepLoop,
|
|
20
24
|
TaskActivity,
|
|
21
25
|
ToolRegistry,
|
|
22
26
|
RunContext,
|
|
23
|
-
settleTurn,
|
|
24
27
|
TurnActivity,
|
|
25
28
|
TurnEvents,
|
|
29
|
+
TurnShutdown,
|
|
26
30
|
TurnTasks,
|
|
27
|
-
TurnLive,
|
|
28
31
|
TurnMemory,
|
|
29
32
|
TurnPrompt,
|
|
30
33
|
TurnToolbox,
|
|
31
|
-
|
|
32
|
-
turnEndingOf
|
|
33
|
-
turnOf
|
|
34
|
+
turnAttributes,
|
|
35
|
+
turnEndingOf
|
|
34
36
|
} from "@xandreed/core";
|
|
35
37
|
import { activateGraph, graphFingerprint, resolveGraph } from "@xandreed/runtime";
|
|
38
|
+
import { guardTurn, openTurnTools, settleTurn, traceTurn, traceTurnBegin, turnOf } from "./turn/turn-lifecycle.usecase.functions.js";
|
|
39
|
+
import { cacheKeyOf } from "./turn/turn-run.usecase.functions.js";
|
|
40
|
+
import { TurnLive } from "./turn/turn.adapter.js";
|
|
36
41
|
const failure = (code, message) => new HarnessError({ code, message }), missing = (key) => failure("service.missing", `The agent's plugins do not provide ${key}`), turnCount = Metric.counter("efferent.turns", {
|
|
37
42
|
description: "turns run by Agent.turn, by outcome",
|
|
38
43
|
incremental: !0
|
|
@@ -42,19 +47,7 @@ const failure = (code, message) => new HarnessError({ code, message }), missing
|
|
|
42
47
|
const outcome = { "efferent.outcome": turnEndingOf(exit).reason }, ended = yield* Clock.currentTimeMillis;
|
|
43
48
|
yield* Metric.update(Metric.withAttributes(turnCount, outcome), 1);
|
|
44
49
|
yield* Metric.update(Metric.withAttributes(turnDuration, outcome), Duration.millis(ended - started));
|
|
45
|
-
}))))), entryOf = (item) => ("plugin" in item) ? item : { plugin: item }, isWriter = (turn) => ("admitted" in turn)
|
|
46
|
-
export const turnBeginCodes = {
|
|
47
|
-
AttachmentRejected: "turn.attachment-rejected",
|
|
48
|
-
SessionBusy: "session.busy",
|
|
49
|
-
TurnDuplicate: "turn.duplicate",
|
|
50
|
-
KeyConflict: "turn.key-conflict",
|
|
51
|
-
NothingPending: "session.nothing-pending",
|
|
52
|
-
NotResumable: "turn.not-resumable",
|
|
53
|
-
TurnRefused: "turn.refused",
|
|
54
|
-
SessionMissing: "session.missing",
|
|
55
|
-
SessionLogError: "session.log"
|
|
56
|
-
};
|
|
57
|
-
const keyOf = (turn) => turn.key ?? (turn.resumes === void 0 ? turn.runId : `resume:${turn.resumes}`), beginTurn = (context, turn) => Effect.gen(function* () {
|
|
50
|
+
}))))), entryOf = (item) => ("plugin" in item) ? item : { plugin: item }, isWriter = (turn) => ("admitted" in turn), keyOf = (turn) => turn.key ?? (turn.resumes === void 0 ? turn.runId : `resume:${turn.resumes}`), beginTurn = (context, turn) => Effect.gen(function* () {
|
|
58
51
|
const sessions = yield* required(context, Sessions), userMessage = yield* normalizeUserMessage(turn.userMessage);
|
|
59
52
|
return yield* sessions.begin(turn.session, {
|
|
60
53
|
_tag: "User",
|
|
@@ -63,9 +56,11 @@ const keyOf = (turn) => turn.key ?? (turn.resumes === void 0 ? turn.runId : `res
|
|
|
63
56
|
key: keyOf(turn),
|
|
64
57
|
command: turn.command ?? {},
|
|
65
58
|
...turn.meta === void 0 ? {} : { meta: turn.meta },
|
|
66
|
-
...turn.resumes === void 0 ? {} : { resumes: turn.resumes }
|
|
59
|
+
...turn.resumes === void 0 ? {} : { resumes: turn.resumes },
|
|
60
|
+
...turn.takeOver === !0 ? { takeOver: !0 } : {},
|
|
61
|
+
...turn.timeLimitMs === void 0 ? {} : { timeLimitMs: turn.timeLimitMs }
|
|
67
62
|
});
|
|
68
|
-
})
|
|
63
|
+
}), untilClosed = (writer) => (body) => Effect.raceFirst(body, writer.closed.pipe(Effect.flatMap((closed) => Effect.fail(closed)))), required = (context, tag) => Option.match(Context.getOption(context, tag), { onNone: () => Effect.fail(missing(tag.key)), onSome: Effect.succeed }), provideHostLayer = (layer) => (effect) => Option.match(layer, { onNone: () => effect, onSome: (hostLayer) => effect.pipe(Effect.provide(hostLayer, { local: !0 })) }), hostCapabilities = (capabilities) => definePlugin({
|
|
69
64
|
id: "@xandreed/sdk/host-contributions",
|
|
70
65
|
version: "1",
|
|
71
66
|
scope: "runtime",
|
|
@@ -74,27 +69,34 @@ const keyOf = (turn) => turn.key ?? (turn.resumes === void 0 ? turn.runId : `res
|
|
|
74
69
|
provides: [],
|
|
75
70
|
contributes: [Capabilities],
|
|
76
71
|
layer: () => CapabilitiesLive(...capabilities)
|
|
77
|
-
}), builtInTurnKeys = [RunContext, TurnEvents, TurnTasks, TurnMemory, TurnPrompt, TurnToolbox].map((tag) => tag.key), turnDependentNodes = (graph) => graph.nodes.reduce((late, node) => {
|
|
72
|
+
}), builtInTurnKeys = [RunContext, TurnEvents, TurnTasks, TurnMemory, TurnPrompt, TurnToolbox].map((tag) => tag.key), turnPorts = [ConversationMemory, ToolRegistry, StepLoop].map((tag) => tag.key), turnDependentNodes = (graph) => graph.nodes.reduce((late, node) => {
|
|
78
73
|
return [...node.plugin.requires, ...node.plugin.optional ?? []].some((key) => builtInTurnKeys.includes(key) || late.some((source) => source.plugin.provides.includes(key) || (source.plugin.contributes ?? []).includes(key))) ? [...late, node] : late;
|
|
79
74
|
}, []), makeAgent = (config) => Effect.gen(function* () {
|
|
80
75
|
if (config.budgetTokens !== void 0 && !isBudgetTokens(config.budgetTokens))
|
|
81
76
|
return yield* Effect.fail(failure("config.budget", `budgetTokens must be an integer of at least 1, not ${config.budgetTokens}`));
|
|
77
|
+
yield* Schema.decodeUnknownEffect(LoopProtection)(config.limits ?? {}).pipe(Effect.mapError((cause) => new HarnessError({ code: "config.limits", message: "the agent's limits carry a degenerate policy or malformedLimit that does not decode", cause })));
|
|
82
78
|
const parent = yield* Effect.scope, entries = config.plugins.map(entryOf), hosted = config.capabilities ?? [], host = hostCapabilities(hosted), withHost = hosted.length > 0, plugins = [...withHost ? [host] : [], ...entries.map((entry) => entry.plugin)], harness = {
|
|
83
79
|
version: 1,
|
|
84
80
|
plugins: [
|
|
85
|
-
...withHost ? [{ id: host.id, use: host.id }] : [],
|
|
86
|
-
...entries.map((entry) => ({ id: entry.id ?? entry.plugin.id, use: entry.plugin.id, options: { ...entry.options } }))
|
|
81
|
+
...withHost ? [{ id: PluginInstanceId.make(host.id), use: host.id }] : [],
|
|
82
|
+
...entries.map((entry) => ({ id: PluginInstanceId.make(entry.id ?? entry.plugin.id), use: entry.plugin.id, options: { ...entry.options } }))
|
|
87
83
|
],
|
|
88
84
|
...config.bindings === void 0 ? {} : { bindings: { ...config.bindings } },
|
|
89
85
|
system: config.system ?? ""
|
|
90
|
-
}, services = config.services ?? Context.empty(), turnKeys = (config.turnServices ?? []).map((tag) => tag.key), external = [SessionEnvironment.key, ...services.mapUnsafe.keys(), ...turnKeys, ...builtInTurnKeys], graph = yield* resolveGraph(harness, plugins, external), late = turnDependentNodes(graph), invalidLate = late.find((node) => node.plugin.scope === "runtime" || node.plugin.provides.some((key) =>
|
|
86
|
+
}, services = config.services ?? Context.empty(), turnKeys = (config.turnServices ?? []).map((tag) => tag.key), external = [SessionEnvironment.key, ...services.mapUnsafe.keys(), ...turnKeys, ...builtInTurnKeys], graph = yield* resolveGraph(harness, plugins, external), late = turnDependentNodes(graph), invalidLate = late.find((node) => node.plugin.scope === "runtime" || node.plugin.provides.some((key) => turnPorts.includes(key)));
|
|
91
87
|
if (invalidLate !== void 0)
|
|
92
88
|
return yield* Effect.fail(failure("config.graph", `${invalidLate.entry.id}: a plugin requiring turn services must be session-scoped and cannot supply the turn's memory, registry or loop`));
|
|
89
|
+
const unprovided = turnPorts.filter((key) => graph.providers[key] === void 0 && !services.mapUnsafe.has(key) && !turnKeys.includes(key));
|
|
90
|
+
if (unprovided.length > 0)
|
|
91
|
+
return yield* Effect.fail(failure("config.graph", `the agent has no ${unprovided.join(", ")}: no plugin provides it, and neither services nor turnServices carry it`));
|
|
93
92
|
const beforeTurn = { ...graph, nodes: graph.nodes.filter((node) => !late.includes(node)) }, afterTurn = { ...graph, nodes: late }, workspace = config.workspace ?? ".", seed = Context.add(services, SessionEnvironment, { workspace }), runtime = yield* activateGraph(beforeTurn, "runtime", Context.makeUnsafe(seed.mapUnsafe), parent), perTurn = beforeTurn.nodes.some((node) => node.plugin.scope === "session"), turn = (input, use) => harnessDefectsAsFailures(Effect.scoped(Effect.gen(function* () {
|
|
94
93
|
const scope = yield* Effect.scope, absent = turnKeys.filter((key) => !input.services.mapUnsafe.has(key));
|
|
95
94
|
if (absent.length > 0)
|
|
96
95
|
return yield* Effect.fail(failure("service.missing", `The turn does not provide ${absent.join(", ")}`));
|
|
97
|
-
const merged = Context.merge(replayableContext(Context.merge(runtime, input.services)), Context.pick(CurrentTraceContent)(input.services)),
|
|
96
|
+
const merged = Context.merge(replayableContext(Context.merge(runtime, input.services)), Context.pick(CurrentTraceContent)(input.services)), open = (begin) => Effect.gen(function* () {
|
|
97
|
+
const context = perTurn ? yield* activateGraph(beforeTurn, "session", merged, scope) : merged, memory = yield* required(context, ConversationMemory), registry = yield* required(context, ToolRegistry), loop = yield* required(context, StepLoop), writer = yield* begin(context);
|
|
98
|
+
return { admitted: writer.admitted, writer, context, memory, registry, loop };
|
|
99
|
+
}), requested = input.turn, own = !isWriter(requested), { writer, context, memory, registry, loop } = yield* isWriter(requested) ? open(() => Effect.succeed(requested)) : open((context) => beginTurn(context, requested)).pipe(traceTurnBegin({ session: requested.session.id, runId: requested.runId, origin: "user" })), system = input.system ?? config.system, live = {
|
|
98
100
|
turn: writer,
|
|
99
101
|
...system === void 0 ? {} : { system },
|
|
100
102
|
...config.maxEventDepth === void 0 ? {} : { maxEventDepth: config.maxEventDepth }
|
|
@@ -103,6 +105,7 @@ const keyOf = (turn) => turn.key ?? (turn.resumes === void 0 ? turn.runId : `res
|
|
|
103
105
|
...config.budgetTokens === void 0 ? {} : { budgetTokens: config.budgetTokens },
|
|
104
106
|
cacheKey: Option.orElse(Option.fromNullishOr(input.cacheKey), () => cacheKeyOf(config.cacheKeyPrefix ?? "", writer.admitted.session.id)),
|
|
105
107
|
...input.steering === void 0 ? {} : { steering: input.steering },
|
|
108
|
+
...input.waiting === void 0 ? {} : { waiting: input.waiting },
|
|
106
109
|
...config.captureTraceContent === void 0 ? {} : { captureTraceContent: config.captureTraceContent }
|
|
107
110
|
}, body = Effect.gen(function* () {
|
|
108
111
|
yield* (yield* TurnMemory).persistMessage;
|
|
@@ -117,13 +120,21 @@ const keyOf = (turn) => turn.key ?? (turn.resumes === void 0 ? turn.runId : `res
|
|
|
117
120
|
});
|
|
118
121
|
return yield* Effect.gen(function* () {
|
|
119
122
|
const exit = yield* body.pipe(Effect.tap(() => settleTurn), Effect.scoped, Effect.tap(() => settleTurn), provideHostLayer(Option.fromNullishOr(input.layer)), Effect.tap(() => settleTurn), provideTurnPlugins, guardTurn, Effect.provide(TurnLive(live), { local: !0 }), Effect.provideService(ConversationMemory, memory), Effect.provideService(ToolRegistry, registry), Effect.provideService(StepLoop, loop), Effect.provide(context), untilClosed(writer), harnessDefectsAsFailures, Effect.exit);
|
|
120
|
-
if (own
|
|
121
|
-
yield*
|
|
122
|
-
if (
|
|
123
|
-
yield* writer.end(turnEndingOf(exit)).pipe(Effect.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
if (!own)
|
|
124
|
+
return yield* exit;
|
|
125
|
+
if (Exit.isFailure(exit)) {
|
|
126
|
+
yield* writer.end(turnEndingOf(exit)).pipe(Effect.catchTag("TurnClosed", (closed) => Effect.logDebug(`the failed turn's end was not stored: the turn was ${closed.reason}`)), Effect.catch((error) => Effect.logWarning(`the failed turn's end was not stored: ${reasonOf(Cause.fail(error))}`)));
|
|
127
|
+
return yield* Effect.failCause(exit.cause);
|
|
128
|
+
}
|
|
129
|
+
const { pending } = yield* writer.end(turnEndingOf(exit));
|
|
130
|
+
return { ...exit.value, pending };
|
|
131
|
+
}).pipe(Effect.annotateLogs(turnAttributes(writer.admitted)), measureTurn, traceTurn(writer, { spanMeta: config.spanMeta ?? [] }));
|
|
132
|
+
}))), runtimeProbes = (effect) => effect.pipe(Effect.updateContext((current) => {
|
|
133
|
+
const probes = Context.merge(Context.omit(TurnActivity, TaskActivity, TurnShutdown, WaitingInput, DrainActivity)(current), Context.pick(TurnActivity, TaskActivity, TurnShutdown, WaitingInput, DrainActivity)(runtime)), drains = Option.orElse(Context.getOption(runtime, DrainActivity), () => Context.getOption(current, DrainActivity));
|
|
134
|
+
return Option.match(drains, { onNone: () => probes, onSome: (probe) => Context.add(probes, DrainActivity, probe) });
|
|
135
|
+
})), activity = runtimeProbes(processActivity), shutdown = (options) => runtimeProbes(shutdownGracefully(options ?? config.shutdown ?? { deadline: DEFAULT_SHUTDOWN_DEADLINE })).pipe(Effect.tap((report) => report.left.inFlight ? Effect.logWarning(`the agent stopped with ${report.left.openTurns.length + report.left.beginningTurns} turns and ${report.left.runningTasks} tasks still in flight`) : Effect.void)), configured = config.shutdown;
|
|
136
|
+
if (configured !== void 0)
|
|
137
|
+
yield* Effect.addFinalizer(() => shutdown(configured).pipe(Effect.asVoid));
|
|
138
|
+
return { fingerprint: graphFingerprint(graph), activity, shutdown, turn };
|
|
128
139
|
});
|
|
129
140
|
export const Agent = { define: makeAgent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toolRegistryConformance } from "../turn/tool-registry.conformance.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import type { Scope } from "effect";
|
|
3
|
+
import { InboxRunner, Sessions } from "@xandreed/core";
|
|
4
|
+
import type { Turn, TurnError, TurnOutcome, TurnRunner, TurnServices } from "@xandreed/core";
|
|
5
|
+
/**
|
|
6
|
+
* Execute a prebegun inbox or resumed turn through the agent. Build beside
|
|
7
|
+
* the agent and provide the same Sessions that the drainer uses. Its captured
|
|
8
|
+
* Sessions takes precedence over per-turn services. The caller owns the
|
|
9
|
+
* writer's scope and end; this bridge only executes it.
|
|
10
|
+
*
|
|
11
|
+
* By default select skills for the admitted message, run the loop once, and
|
|
12
|
+
* return its outcome and text. A custom use has Agent.turn's turn services.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export declare const agentInboxRunner: (agent: TurnRunner, options: {
|
|
16
|
+
readonly services: Context.Context<never>;
|
|
17
|
+
readonly use?: (turn: Turn) => Effect.Effect<TurnOutcome, TurnError, TurnServices | Scope.Scope>;
|
|
18
|
+
}) => Layer.Layer<InboxRunner, never, Sessions>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
2
|
+
import { InboxRunner, isTurnError, Sessions } from "@xandreed/core";
|
|
3
|
+
export const agentInboxRunner = (agent, options) => Layer.effect(InboxRunner, Effect.gen(function* () {
|
|
4
|
+
const sessions = yield* Sessions, services = Context.add(options.services, Sessions, sessions), use = options.use ?? ((turn) => turn.tools.select(turn.userMessage).pipe(Effect.andThen(turn.run({})), Effect.map((result) => ({ outcome: result.outcome, reply: result.outcome === "waiting" ? Option.none() : Option.some(result.text) }))));
|
|
5
|
+
return InboxRunner.of({
|
|
6
|
+
run: Effect.fn("inbox_runner.run")((writer) => agent.turn({ turn: writer, services }, use).pipe(Effect.catch((error) => isTurnError(error) ? Effect.fail(error) : Effect.die(error)), Effect.map((result) => ({ outcome: result.outcome, reply: result.reply }))))
|
|
7
|
+
});
|
|
8
|
+
}));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { Agent, turnBeginCodes } from "./agent.adapter.js";
|
|
1
|
+
export { Agent } from "./agent.adapter.js";
|
|
3
2
|
export type { AgentConfig, AgentPluginEntry } from "./agent.adapter.js";
|
|
3
|
+
export { agentInboxRunner } from "./inbox-runner.adapter.js";
|
|
4
|
+
export { taskInboxRunner } from "./task-inbox-runner.adapter.js";
|
|
5
|
+
export { agentTaskExecutor } from "./task-executor.adapter.js";
|
|
6
|
+
export { TurnLive } from "./turn/turn.adapter.js";
|
|
7
|
+
export { cacheKeyOf, runTurnLoop, stepRequestOf } from "./turn/turn-run.usecase.functions.js";
|
|
8
|
+
export type { TurnRunServices } from "./turn/turn-run.usecase.functions.js";
|
|
9
|
+
export { finishTurn, guardTurn, openTurnTools, settleTurn, traceTurn, traceTurnBegin, turnOf, turnTools, } from "./turn/turn-lifecycle.usecase.functions.js";
|
|
10
|
+
export type { TurnBeginTrace, TurnTraceOptions } from "./turn/turn-lifecycle.usecase.functions.js";
|
|
11
|
+
export { ConversationId, mediaFromBase64, mediaFromBytes, RunId, SessionAddress, UserMessage } from "@xandreed/core";
|
|
12
|
+
export type { /** @public */ NewTurn, /** @public */ TurnInput } from "@xandreed/core";
|
|
13
|
+
export type { Turn, TurnOutcome, /** @public */ TurnPolicy, /** @public */ TurnResult } from "@xandreed/core";
|
|
14
|
+
export { SessionLog, SessionLogMemoryLive, Sessions, /** @public */ TurnAdmission, TurnAdmissionOpen, /** @public */ TurnRefused } from "@xandreed/core";
|
|
15
|
+
export {
|
|
16
|
+
/** @public */ defineCapability, definePlugin, definePromptSection,
|
|
17
|
+
/** @public */ defineSkill, defineTool, Failure, Media,
|
|
18
|
+
/** @public */ publishToolProgress, SessionEnvironment,
|
|
19
|
+
/** @public */ ActionPolicy,
|
|
20
|
+
/** @public */ PermissionGrants, } from "@xandreed/core";
|
|
21
|
+
export type { Capability } from "@xandreed/core";
|
|
22
|
+
export { AssistantMessage, defineHostEvent, onEvent, onTool, subscribeAll, TurnEndedData, turnEndedOf, TurnEvent, TurnEvents, } from "@xandreed/core";
|
|
23
|
+
export type { TurnEventOf, TurnEventsService } from "@xandreed/core";
|
|
24
|
+
export { harnessCodeOf,
|
|
25
|
+
/** @public */ HarnessError,
|
|
26
|
+
/** @public */ isHarnessError,
|
|
27
|
+
/** @public */ isTurnError, TurnBeginError, turnBeginCodes, turnEndingOf, } from "@xandreed/core";
|
|
28
|
+
export type { TurnError } from "@xandreed/core";
|
|
29
|
+
export {
|
|
30
|
+
/** @public */ awaitIdle,
|
|
31
|
+
/** @public */ DEFAULT_SHUTDOWN_DEADLINE,
|
|
32
|
+
/** @public */ isShutdownRefusal,
|
|
33
|
+
/** @public */ processActivity,
|
|
34
|
+
/** @public */ shutdownGracefully,
|
|
35
|
+
/** @public */ TurnShutdown, } from "@xandreed/core";
|
|
36
|
+
export type { ShutdownOptions, /** @public */ ShutdownReport } from "@xandreed/core";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,59 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
1
|
+
export { Agent } from "./agent.adapter.js";
|
|
2
|
+
export { agentInboxRunner } from "./inbox-runner.adapter.js";
|
|
3
|
+
export { taskInboxRunner } from "./task-inbox-runner.adapter.js";
|
|
4
|
+
export { agentTaskExecutor } from "./task-executor.adapter.js";
|
|
5
|
+
export { TurnLive } from "./turn/turn.adapter.js";
|
|
6
|
+
export { cacheKeyOf, runTurnLoop, stepRequestOf } from "./turn/turn-run.usecase.functions.js";
|
|
7
|
+
export {
|
|
8
|
+
finishTurn,
|
|
9
|
+
guardTurn,
|
|
10
|
+
openTurnTools,
|
|
11
|
+
settleTurn,
|
|
12
|
+
traceTurn,
|
|
13
|
+
traceTurnBegin,
|
|
14
|
+
turnOf,
|
|
15
|
+
turnTools
|
|
16
|
+
} from "./turn/turn-lifecycle.usecase.functions.js";
|
|
17
|
+
export { ConversationId, mediaFromBase64, mediaFromBytes, RunId, SessionAddress, UserMessage } from "@xandreed/core";
|
|
18
|
+
export { SessionLog, SessionLogMemoryLive, Sessions, TurnAdmission, TurnAdmissionOpen, TurnRefused } from "@xandreed/core";
|
|
19
|
+
export {
|
|
20
|
+
defineCapability,
|
|
21
|
+
definePlugin,
|
|
22
|
+
definePromptSection,
|
|
23
|
+
defineSkill,
|
|
24
|
+
defineTool,
|
|
25
|
+
Failure,
|
|
26
|
+
Media,
|
|
27
|
+
publishToolProgress,
|
|
28
|
+
SessionEnvironment,
|
|
29
|
+
ActionPolicy,
|
|
30
|
+
PermissionGrants
|
|
31
|
+
} from "@xandreed/core";
|
|
32
|
+
export {
|
|
33
|
+
AssistantMessage,
|
|
34
|
+
defineHostEvent,
|
|
35
|
+
onEvent,
|
|
36
|
+
onTool,
|
|
37
|
+
subscribeAll,
|
|
38
|
+
TurnEndedData,
|
|
39
|
+
turnEndedOf,
|
|
40
|
+
TurnEvent,
|
|
41
|
+
TurnEvents
|
|
42
|
+
} from "@xandreed/core";
|
|
43
|
+
export {
|
|
44
|
+
harnessCodeOf,
|
|
45
|
+
HarnessError,
|
|
46
|
+
isHarnessError,
|
|
47
|
+
isTurnError,
|
|
48
|
+
TurnBeginError,
|
|
49
|
+
turnBeginCodes,
|
|
50
|
+
turnEndingOf
|
|
51
|
+
} from "@xandreed/core";
|
|
52
|
+
export {
|
|
53
|
+
awaitIdle,
|
|
54
|
+
DEFAULT_SHUTDOWN_DEADLINE,
|
|
55
|
+
isShutdownRefusal,
|
|
56
|
+
processActivity,
|
|
57
|
+
shutdownGracefully,
|
|
58
|
+
TurnShutdown
|
|
59
|
+
} from "@xandreed/core";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import type { Scope } from "effect";
|
|
3
|
+
import { TaskExecutor } from "@xandreed/core";
|
|
4
|
+
import type { TaskView, Turn, TurnError, TurnInput, TurnOutcome, TurnRunner, TurnServices } from "@xandreed/core";
|
|
5
|
+
/** What a task's turn runs once it is composed: a `use` of `Agent.turn`, with the turn's services. */
|
|
6
|
+
type TurnUse = (turn: Turn) => Effect.Effect<TurnOutcome, TurnError, TurnServices | Scope.Scope>;
|
|
7
|
+
/**
|
|
8
|
+
* The `TaskExecutor` that runs a task's turns through an agent (`Agent.turn`,
|
|
9
|
+
* or any `TurnRunner`) with the turn's `services` (its model, the host's
|
|
10
|
+
* per-turn ports) and the `Tasks` it is called with (the tasks plugin's
|
|
11
|
+
* own), so a turn's `start_task` and `task_status` work: the child's one
|
|
12
|
+
* turn with `child` and the parent's
|
|
13
|
+
* reaction to a result with `react`, each by default the turn's skills
|
|
14
|
+
* selected for its message, then one run of the step loop, whose text is the
|
|
15
|
+
* reply. The tasks plugin begins and ends those turns; this only runs them.
|
|
16
|
+
* Build `Tasks` beside the agent, not inside it:
|
|
17
|
+
* `TasksLive(config).pipe(Layer.provide(agentTaskExecutor(agent, { services })))`
|
|
18
|
+
* (a `tasksPlugin` among the agent's own plugins would need the executor
|
|
19
|
+
* before the agent exists).
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export declare const agentTaskExecutor: (agent: TurnRunner, options: {
|
|
24
|
+
readonly services: Context.Context<never>;
|
|
25
|
+
/** Optional in-memory steering for both turns; durable input uses their writers' queued steers. */
|
|
26
|
+
readonly steering?: TurnInput["steering"];
|
|
27
|
+
/** The child's turn, given the task it runs. */
|
|
28
|
+
readonly child?: (turn: Turn, task: TaskView) => Effect.Effect<TurnOutcome, TurnError, TurnServices | Scope.Scope>;
|
|
29
|
+
/** The parent's turn that answers the results waiting in its inbox. */
|
|
30
|
+
readonly react?: TurnUse;
|
|
31
|
+
}) => Layer.Layer<TaskExecutor>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
2
|
+
import { isTurnError, TaskExecutor, Tasks } from "@xandreed/core";
|
|
3
|
+
const oneRun = (turn) => turn.tools.select(turn.userMessage).pipe(Effect.andThen(turn.run({})), Effect.map((result) => ({ outcome: result.outcome, reply: result.outcome === "waiting" ? Option.none() : Option.some(result.text) }))), runOn = (agent, services, writer, use, steering, waiting) => Tasks.pipe(Effect.flatMap((tasks) => agent.turn({ turn: writer, services: Context.add(services, Tasks, tasks), ...steering === void 0 ? {} : { steering }, ...waiting === void 0 ? {} : { waiting } }, use)), Effect.catch((error) => isTurnError(error) ? Effect.fail(error) : Effect.die(error)), Effect.map((result) => ({ outcome: result.outcome, reply: result.reply })));
|
|
4
|
+
export const agentTaskExecutor = (agent, options) => {
|
|
5
|
+
const child = options.child ?? oneRun, react = options.react ?? oneRun;
|
|
6
|
+
return Layer.succeed(TaskExecutor, TaskExecutor.of({
|
|
7
|
+
turn: (writer, task, controls) => runOn(agent, options.services, writer, (turn) => child(turn, task), options.steering, controls?.waiting),
|
|
8
|
+
react: (writer) => runOn(agent, options.services, writer, react, options.steering).pipe(Effect.asVoid)
|
|
9
|
+
}));
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context, Layer } from "effect";
|
|
2
|
+
import { InboxRunner, Sessions, Tasks } from "@xandreed/core";
|
|
3
|
+
/**
|
|
4
|
+
* Route a task child's queued follow-ups through Tasks.execute on the supplied
|
|
5
|
+
* borrowed writer. Other sessions use base. After an acknowledged end, repair
|
|
6
|
+
* task delivery and hand parent notices to the existing task runner. The hook
|
|
7
|
+
* can be skipped on process death; startup Tasks.reconcile repairs the outbox.
|
|
8
|
+
* Build beside the agent with the drainer's own Sessions and Tasks.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export declare const taskInboxRunner: (base: Context.Service.Shape<typeof InboxRunner>) => Layer.Layer<InboxRunner, never, Sessions | Tasks>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
2
|
+
import { harnessError, InboxRunner, Sessions, Tasks } from "@xandreed/core";
|
|
3
|
+
export const taskInboxRunner = (base) => Layer.effect(InboxRunner, Effect.gen(function* () {
|
|
4
|
+
const sessions = yield* Sessions, tasks = yield* Tasks, sessionOf = (writer) => sessions.get(writer.admitted.session).pipe(Effect.catchTag("SessionMissing", (cause) => Effect.fail(harnessError("task.turn", "The inbox session is unavailable.", cause))));
|
|
5
|
+
return InboxRunner.of({
|
|
6
|
+
run: Effect.fn("task_inbox_runner.run")(function* (writer) {
|
|
7
|
+
return yield* (yield* sessionOf(writer)).header.origin === "task" ? tasks.execute(writer) : base.run(writer);
|
|
8
|
+
}),
|
|
9
|
+
afterEnd: Effect.fn("task_inbox_runner.after_end")(function* (writer, ending) {
|
|
10
|
+
const view = yield* sessionOf(writer);
|
|
11
|
+
if (view.header.origin === "task") {
|
|
12
|
+
const parent = yield* Option.match(view.header.parent, {
|
|
13
|
+
onNone: () => Effect.fail(harnessError("task.turn", "The task session has no parent.")),
|
|
14
|
+
onSome: (lineage) => Effect.succeed({ id: lineage.id, owner: writer.admitted.session.owner })
|
|
15
|
+
});
|
|
16
|
+
yield* tasks.reconcile(parent, { react: !0 }).pipe(Effect.catchTag("SessionMissing", (cause) => Effect.fail(harnessError("task.turn", "The inbox session is unavailable.", cause))));
|
|
17
|
+
}
|
|
18
|
+
if (base.afterEnd !== void 0)
|
|
19
|
+
yield* base.afterEnd(writer, ending);
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
}));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { MemorySnapshotCandidate } from "@xandreed/core";
|
|
3
|
+
import type { TurnError, TurnWriter } from "@xandreed/core";
|
|
4
|
+
/** The journal-aware bridge is initialized before a custom memory opener can call it. */
|
|
5
|
+
export declare const makeMemoryCheckpoint: (writer: TurnWriter, history: Effect.Effect<readonly {
|
|
6
|
+
readonly session: string & import("effect/Brand").Brand<"ConversationId">;
|
|
7
|
+
readonly seq: number;
|
|
8
|
+
readonly turn: Option.Option<number>;
|
|
9
|
+
readonly kind: string;
|
|
10
|
+
readonly at: number;
|
|
11
|
+
readonly data: {
|
|
12
|
+
readonly [x: string]: Schema.Json;
|
|
13
|
+
};
|
|
14
|
+
}[], TurnError, never>) => Effect.Effect<{
|
|
15
|
+
readonly load: Effect.Effect<Option.Option<MemorySnapshotCandidate>, TurnError>;
|
|
16
|
+
readonly save: (candidate: MemorySnapshotCandidate) => Effect.Effect<void, TurnError>;
|
|
17
|
+
}, never, never>;
|