@4fu/dsh-arbor-driver 0.0.0-stage → 2026.1110.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/inbox.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The durable agent inbox for arbor sessions.
3
+ *
4
+ * The projection definition mirrors
5
+ * `packages/core/agent-loop/src/inbox.ts` in deepseek-harness (key `inbox`,
6
+ * stateVersion 1): the official AgentLoop registers the same key, and
7
+ * `SessionProjectionRegistry.register` refcounts one definition per key, so
8
+ * both drivers share one projection table. The driver-side `Inbox`
9
+ * implementation mirrors `ReactLoopInbox`: splice, claim and clear record
10
+ * durable `agent/inbox/spliced` events exactly as the official loop does.
11
+ */
12
+ import type { MessageId } from '@deepseek-ai/dsh-llm';
13
+ import type { Session, UserMessage } from '@deepseek-ai/dsh-session';
14
+ import type { AgentEventDispatch, Inbox as InboxContract, InboxState, InboxTarget, InboxWireState } from '@deepseek-ai/dsh-agent';
15
+ import { z } from 'zod';
16
+ /** Wire validation for pending agent input reconstructed from durable inbox splices. */
17
+ export declare const inboxProjectionSchema: z.ZodReadonly<z.ZodObject<{
18
+ 'next-turn': z.ZodReadonly<z.ZodArray<z.ZodCustom<UserMessage, UserMessage>>>;
19
+ 'next-step': z.ZodReadonly<z.ZodArray<z.ZodCustom<UserMessage, UserMessage>>>;
20
+ }, z.core.$strip>>;
21
+ /** Standard fold that reconstructs pending input and rejects invalid durable splice history. */
22
+ export declare const inboxProjectionDefinition: {
23
+ key: "inbox";
24
+ stateSchema: z.ZodReadonly<z.ZodObject<{
25
+ 'next-turn': z.ZodReadonly<z.ZodArray<z.ZodCustom<UserMessage, UserMessage>>>;
26
+ 'next-step': z.ZodReadonly<z.ZodArray<z.ZodCustom<UserMessage, UserMessage>>>;
27
+ }, z.core.$strip>>;
28
+ init: () => InboxState;
29
+ apply(state: InboxState, event: import("@deepseek-ai/dsh-session").SessionEvent): InboxState;
30
+ wire: {
31
+ viewSchema: z.ZodType<InboxWireState>;
32
+ view: (state: InboxState) => InboxWireState;
33
+ };
34
+ stateVersion: number;
35
+ };
36
+ /** Driver-owned durable Inbox for arbor sessions; mirrors ReactLoopInbox. */
37
+ export declare class ArborInbox implements InboxContract {
38
+ private readonly readState;
39
+ private readonly session;
40
+ private readonly dispatch;
41
+ constructor(readState: () => InboxState, session: Session, dispatch: AgentEventDispatch);
42
+ get nextTurn(): readonly UserMessage[];
43
+ get nextStep(): readonly UserMessage[];
44
+ get hasPending(): boolean;
45
+ clear(): void;
46
+ claim(target: InboxTarget, turn: number): UserMessage[];
47
+ append(target: InboxTarget, message: UserMessage): void;
48
+ prepend(target: InboxTarget, message: UserMessage): void;
49
+ replace(messageId: MessageId, newMessage: UserMessage): boolean;
50
+ remove(messageId: MessageId): boolean;
51
+ splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];
52
+ private locate;
53
+ /** Commit one normalized mutation and publish its live events. */
54
+ private mutate;
55
+ }
package/inbox.js ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * The durable agent inbox for arbor sessions.
3
+ *
4
+ * The projection definition mirrors
5
+ * `packages/core/agent-loop/src/inbox.ts` in deepseek-harness (key `inbox`,
6
+ * stateVersion 1): the official AgentLoop registers the same key, and
7
+ * `SessionProjectionRegistry.register` refcounts one definition per key, so
8
+ * both drivers share one projection table. The driver-side `Inbox`
9
+ * implementation mirrors `ReactLoopInbox`: splice, claim and clear record
10
+ * durable `agent/inbox/spliced` events exactly as the official loop does.
11
+ */
12
+ import { z } from 'zod';
13
+ /** Wire validation for pending agent input reconstructed from durable inbox splices. */
14
+ export const inboxProjectionSchema = z.object({
15
+ 'next-turn': z.array(z.custom()).readonly(),
16
+ 'next-step': z.array(z.custom()).readonly(),
17
+ }).readonly();
18
+ /** Standard fold that reconstructs pending input and rejects invalid durable splice history. */
19
+ export const inboxProjectionDefinition = {
20
+ key: 'inbox',
21
+ stateSchema: inboxProjectionSchema,
22
+ init: () => ({ 'next-turn': [], 'next-step': [] }),
23
+ apply(state, event) {
24
+ if (event.type !== 'agent/inbox/spliced')
25
+ return state;
26
+ const splice = event.data;
27
+ try {
28
+ const inbox = state[splice.target];
29
+ const removedCount = splice.removedCount ?? 0;
30
+ if (!Number.isSafeInteger(splice.start) || splice.start < 0 || splice.start > inbox.length
31
+ || !Number.isSafeInteger(removedCount) || removedCount < 0 || splice.start + removedCount > inbox.length) {
32
+ throw new Error('invalid inbox splice');
33
+ }
34
+ const next = inbox.toSpliced(splice.start, removedCount, ...splice.inserted);
35
+ const ids = new Set();
36
+ for (const message of splice.target === 'next-turn'
37
+ ? [...next, ...state['next-step']]
38
+ : [...state['next-turn'], ...next]) {
39
+ if (ids.has(message.id))
40
+ throw new Error(`message "${message.id}" is already pending`);
41
+ ids.add(message.id);
42
+ }
43
+ return splice.target === 'next-turn'
44
+ ? { 'next-turn': next, 'next-step': state['next-step'] }
45
+ : { 'next-turn': state['next-turn'], 'next-step': next };
46
+ }
47
+ catch (error) {
48
+ throw new Error(`invalid persisted inbox splice at session seq ${event.seq}`, { cause: error });
49
+ }
50
+ },
51
+ wire: {
52
+ viewSchema: inboxProjectionSchema,
53
+ view: (state) => state,
54
+ },
55
+ stateVersion: 1,
56
+ };
57
+ /** Driver-owned durable Inbox for arbor sessions; mirrors ReactLoopInbox. */
58
+ export class ArborInbox {
59
+ readState;
60
+ session;
61
+ dispatch;
62
+ constructor(readState, session, dispatch) {
63
+ this.readState = readState;
64
+ this.session = session;
65
+ this.dispatch = dispatch;
66
+ }
67
+ get nextTurn() {
68
+ return this.readState()['next-turn'];
69
+ }
70
+ get nextStep() {
71
+ return this.readState()['next-step'];
72
+ }
73
+ get hasPending() {
74
+ const state = this.readState();
75
+ return state['next-turn'].length > 0 || state['next-step'].length > 0;
76
+ }
77
+ clear() {
78
+ this.splice('next-step', 0, this.nextStep.length, []);
79
+ this.splice('next-turn', 0, this.nextTurn.length, []);
80
+ }
81
+ claim(target, turn) {
82
+ const claimed = this.mutate('next-step', 0, this.nextStep.length, [], false);
83
+ if (target === 'next-turn')
84
+ claimed.push(...this.mutate('next-turn', 0, 1, [], false));
85
+ for (const message of claimed)
86
+ this.dispatch.emit('agent/inbox/claimed', { message, turn });
87
+ return claimed;
88
+ }
89
+ append(target, message) {
90
+ this.splice(target, this.readState()[target].length, 0, [message]);
91
+ }
92
+ prepend(target, message) {
93
+ this.splice(target, 0, 0, [message]);
94
+ }
95
+ replace(messageId, newMessage) {
96
+ const location = this.locate(messageId);
97
+ if (location === undefined)
98
+ return false;
99
+ this.splice(location.target, location.index, 1, [newMessage]);
100
+ return true;
101
+ }
102
+ remove(messageId) {
103
+ const location = this.locate(messageId);
104
+ if (location === undefined)
105
+ return false;
106
+ this.splice(location.target, location.index, 1, []);
107
+ return true;
108
+ }
109
+ splice(target, start, deleteCount, inserted) {
110
+ return this.mutate(target, start, deleteCount, inserted, true);
111
+ }
112
+ locate(messageId) {
113
+ const state = this.readState();
114
+ for (const target of ['next-turn', 'next-step']) {
115
+ const index = state[target].findIndex(message => message.id === messageId);
116
+ if (index >= 0)
117
+ return { target, index };
118
+ }
119
+ return undefined;
120
+ }
121
+ /** Commit one normalized mutation and publish its live events. */
122
+ mutate(target, start, deleteCount, inserted, discardRemoved) {
123
+ const state = this.readState();
124
+ const inbox = state[target];
125
+ const truncatedStart = Math.trunc(start);
126
+ const offset = Number.isNaN(truncatedStart) ? 0 : truncatedStart;
127
+ const actualStart = offset < 0 ? Math.max(inbox.length + offset, 0) : Math.min(offset, inbox.length);
128
+ const truncatedDeleteCount = Math.trunc(deleteCount);
129
+ const actualDeleteCount = Math.min(Math.max(Number.isNaN(truncatedDeleteCount) ? 0 : truncatedDeleteCount, 0), inbox.length - actualStart);
130
+ if (actualDeleteCount === 0 && inserted.length === 0)
131
+ return [];
132
+ const candidate = inbox.toSpliced(actualStart, actualDeleteCount, ...inserted);
133
+ const ids = new Set();
134
+ for (const message of target === 'next-turn'
135
+ ? [...candidate, ...state['next-step']]
136
+ : [...state['next-turn'], ...candidate]) {
137
+ if (ids.has(message.id))
138
+ throw new Error(`message "${message.id}" is already pending`);
139
+ ids.add(message.id);
140
+ }
141
+ const outcome = discardRemoved && actualDeleteCount > 0 ? 'canceled' : undefined;
142
+ const splice = {
143
+ target,
144
+ start: actualStart,
145
+ ...(actualDeleteCount === 0 ? {} : { removedCount: actualDeleteCount }),
146
+ inserted,
147
+ ...(outcome === undefined ? {} : { outcome }),
148
+ };
149
+ const removed = inbox.slice(actualStart, actualStart + actualDeleteCount);
150
+ const event = this.session.append('agent/inbox/spliced', splice);
151
+ if (discardRemoved) {
152
+ for (const message of removed)
153
+ this.dispatch.emit('agent/inbox/discarded', { message });
154
+ }
155
+ for (const message of event.data.inserted) {
156
+ this.dispatch.emit('agent/inbox/inserted', { message });
157
+ }
158
+ return removed;
159
+ }
160
+ }
package/index.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@4fu/dsh-arbor-driver` — the DSH plugin that runs arbor as the agent driver.
3
+ *
4
+ * The default export is the routing `AgentRegistry` subclass the patch row
5
+ * mounts in place of the base `agent` row; `./driver` mounts the arbor driver
6
+ * that runs arbor-preset sessions on the process's one Arbor host.
7
+ */
8
+ export { ArborAgentRegistry, ARBOR_PRESET } from './registry.js';
9
+ export type { ArborRoutingFactory } from './registry.js';
10
+ export { ArborSessionAgent } from './session-agent.js';
11
+ export type { ArborWindowBridge, ModelCallInput, ModelCallOutcome } from './session-agent.js';
12
+ export { ArborInbox, inboxProjectionDefinition } from './inbox.js';
13
+ export { ArborStreamAttempt } from './assistant-stream.js';
14
+ export { ArborDriver } from './driver.js';
15
+ export type { Config as DriverConfig, SessionArborLike } from './driver.js';
16
+ export { windowToolCalls, relayToolCalls } from './relay.js';
17
+ export type { ArborToolCall } from './relay.js';
18
+ export { default } from './registry.js';
package/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@4fu/dsh-arbor-driver` — the DSH plugin that runs arbor as the agent driver.
3
+ *
4
+ * The default export is the routing `AgentRegistry` subclass the patch row
5
+ * mounts in place of the base `agent` row; `./driver` mounts the arbor driver
6
+ * that runs arbor-preset sessions on the process's one Arbor host.
7
+ */
8
+ export { ArborAgentRegistry, ARBOR_PRESET } from "./registry.js";
9
+ export { ArborSessionAgent } from "./session-agent.js";
10
+ export { ArborInbox, inboxProjectionDefinition } from "./inbox.js";
11
+ export { ArborStreamAttempt } from "./assistant-stream.js";
12
+ export { ArborDriver } from "./driver.js";
13
+ export { windowToolCalls, relayToolCalls } from "./relay.js";
14
+ export { default } from "./registry.js";
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@4fu/dsh-arbor-driver",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2026.1110.0",
4
+ "type": "module",
5
+ "description": "The arbor agent driver: per-preset routing over the embedded official AgentLoop, the arbor AgentFactory, the session agent, and resume from the arbor journal",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./index.d.ts",
9
+ "default": "./index.js"
10
+ },
11
+ "./driver": {
12
+ "types": "./driver.d.ts",
13
+ "default": "./driver.js"
14
+ },
15
+ "./relay": {
16
+ "types": "./relay.d.ts",
17
+ "default": "./relay.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "dependencies": {
22
+ "@4fu/arbor-core": "2026.1109.1",
23
+ "@4fu/arbor-interfaces": "2026.1109.1",
24
+ "@4fu/arbor-prompt": "2026.1109.1",
25
+ "@4fu/dsh-arbor-host": "2026.1110.0",
26
+ "@4fu/dsh-arbor-llm": "2026.1110.0",
27
+ "@4fu/dsh-arbor-prompt": "2026.1110.0",
28
+ "@4fu/dsh-arbor-tools": "2026.1110.0",
29
+ "@deepseek-ai/cordis": "4.1.0",
30
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
31
+ "@deepseek-ai/dsh-brand": "0.2.2-rc.1",
32
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
33
+ "@deepseek-ai/dsh-scope": "0.2.2-rc.1",
34
+ "@deepseek-ai/dsh-session": "0.2.2-rc.1",
35
+ "@deepseek-ai/dsh-session-persistence": "0.2.2-rc.1",
36
+ "@deepseek-ai/dsh-session-projection": "0.2.2-rc.1",
37
+ "@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
38
+ "@deepseek-ai/dsh-tools": "0.2.2-rc.1",
39
+ "@deepseek-ai/dsh-working-directory": "0.2.2-rc.1",
40
+ "@deepseek-ai/schemastery": "~3.19.0",
41
+ "zod": "~4.4.3"
42
+ },
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/4fuu/dsh-arbor.git",
47
+ "directory": "packages/arbor-driver"
48
+ },
49
+ "engines": {
50
+ "node": ">=24.9.0",
51
+ "dsh": "0.2.2-rc.1"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "files": [
57
+ "assistant-stream.d.ts",
58
+ "assistant-stream.js",
59
+ "driver.d.ts",
60
+ "driver.js",
61
+ "inbox.d.ts",
62
+ "inbox.js",
63
+ "index.d.ts",
64
+ "index.js",
65
+ "registry.d.ts",
66
+ "registry.js",
67
+ "relay.d.ts",
68
+ "relay.js",
69
+ "runtime-context.d.ts",
70
+ "runtime-context.js",
71
+ "session-agent.d.ts",
72
+ "session-agent.js"
73
+ ],
74
+ "types": "./index.d.ts",
75
+ "main": "./index.js"
76
+ }
package/registry.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The routing agent registry for arbor.
3
+ *
4
+ * DSH allows exactly one AgentFactory per process: `AgentRegistry.setFactory`
5
+ * throws `'an agent factory is already registered'` on a second call
6
+ * (packages/core/agent/src/index.ts, setFactory ~L383-399), and AgentLoop
7
+ * claims that single slot in its own `[Service.init]`
8
+ * (packages/core/agent-loop/src/index.ts L336). A bundle patch also cannot
9
+ * swap which package a row mounts — `applyPatches` treats a patch's `name` as
10
+ * a check only (vendor/include/src/patch.ts) — so the base `agent` row is
11
+ * disabled and this subclass is inserted in its place.
12
+ *
13
+ * The subclass keeps TWO factories:
14
+ *
15
+ * - the official AgentLoop, captured when it registers itself through the
16
+ * overridden `setFactory` (never passed to the base class, so the base
17
+ * slot stays free and nothing throws); and
18
+ * - the arbor driver, registered through {@link useArborFactory}.
19
+ *
20
+ * `create` routes on `meta.agentPreset === 'arbor'`; `resume` asks the arbor
21
+ * driver whether the persisted session is an arbor session and routes
22
+ * accordingly. Both paths delegate exactly the way the base class does
23
+ * (`getTraceable` + `Reflect.apply` through the caller-traced receiver), so
24
+ * the embedded loop keeps creating its agents into this same registry
25
+ * instance: ordinary DSH sessions keep working, including every consumer that
26
+ * reads `ctx.agents` (session-controller routing, UI liveness, the subagent
27
+ * tool).
28
+ */
29
+ import AgentRegistry from '@deepseek-ai/dsh-agent';
30
+ import type { AgentFactory, AgentHandle, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
31
+ import type { SessionId } from '@deepseek-ai/dsh-session';
32
+ /** The agent-preset value that routes a session to arbor. */
33
+ export declare const ARBOR_PRESET = "arbor";
34
+ /** An agent factory that can also say whether a persisted session is its own. */
35
+ export interface ArborRoutingFactory extends AgentFactory {
36
+ /** Whether the persisted session with this id runs under the arbor driver. */
37
+ ownsSession?(sessionId: SessionId): Promise<boolean>;
38
+ }
39
+ /** The agent registry with per-preset routing over an embedded AgentLoop. */
40
+ export declare class ArborAgentRegistry extends AgentRegistry {
41
+ private embeddedFactory;
42
+ private arborFactory;
43
+ /**
44
+ * Capture the official loop's self-registration instead of claiming the
45
+ * base class's single factory slot. AgentLoop calls this from its
46
+ * `[Service.init]`; the returned disposer clears the capture on unload.
47
+ */
48
+ setFactory(factory: AgentFactory): () => void;
49
+ /** Register the arbor routing factory. Returns its disposer. */
50
+ useArborFactory(factory: ArborRoutingFactory): () => void;
51
+ /** The captured official AgentLoop, when one is mounted. */
52
+ get embedded(): AgentFactory | undefined;
53
+ create(options: CreateAgentOptions): Promise<AgentHandle>;
54
+ resume(options: ResumeAgentOptions): Promise<AgentHandle>;
55
+ private requireEmbedded;
56
+ }
57
+ export default ArborAgentRegistry;
package/registry.js ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The routing agent registry for arbor.
3
+ *
4
+ * DSH allows exactly one AgentFactory per process: `AgentRegistry.setFactory`
5
+ * throws `'an agent factory is already registered'` on a second call
6
+ * (packages/core/agent/src/index.ts, setFactory ~L383-399), and AgentLoop
7
+ * claims that single slot in its own `[Service.init]`
8
+ * (packages/core/agent-loop/src/index.ts L336). A bundle patch also cannot
9
+ * swap which package a row mounts — `applyPatches` treats a patch's `name` as
10
+ * a check only (vendor/include/src/patch.ts) — so the base `agent` row is
11
+ * disabled and this subclass is inserted in its place.
12
+ *
13
+ * The subclass keeps TWO factories:
14
+ *
15
+ * - the official AgentLoop, captured when it registers itself through the
16
+ * overridden `setFactory` (never passed to the base class, so the base
17
+ * slot stays free and nothing throws); and
18
+ * - the arbor driver, registered through {@link useArborFactory}.
19
+ *
20
+ * `create` routes on `meta.agentPreset === 'arbor'`; `resume` asks the arbor
21
+ * driver whether the persisted session is an arbor session and routes
22
+ * accordingly. Both paths delegate exactly the way the base class does
23
+ * (`getTraceable` + `Reflect.apply` through the caller-traced receiver), so
24
+ * the embedded loop keeps creating its agents into this same registry
25
+ * instance: ordinary DSH sessions keep working, including every consumer that
26
+ * reads `ctx.agents` (session-controller routing, UI liveness, the subagent
27
+ * tool).
28
+ */
29
+ import { getTraceable, symbols } from '@deepseek-ai/cordis';
30
+ import AgentRegistry from '@deepseek-ai/dsh-agent';
31
+ /** The agent-preset value that routes a session to arbor. */
32
+ export const ARBOR_PRESET = 'arbor';
33
+ /** Delegate to one factory exactly the way `AgentRegistry.create/resume` do. */
34
+ function delegate(ownerCtx, factory, method, options) {
35
+ const target = factory[symbols.original] ?? factory;
36
+ const receiver = getTraceable(ownerCtx, target);
37
+ return Reflect.apply(target[method], receiver, [ownerCtx, options]);
38
+ }
39
+ /** The agent registry with per-preset routing over an embedded AgentLoop. */
40
+ export class ArborAgentRegistry extends AgentRegistry {
41
+ embeddedFactory;
42
+ arborFactory;
43
+ /**
44
+ * Capture the official loop's self-registration instead of claiming the
45
+ * base class's single factory slot. AgentLoop calls this from its
46
+ * `[Service.init]`; the returned disposer clears the capture on unload.
47
+ */
48
+ setFactory(factory) {
49
+ this.embeddedFactory = factory;
50
+ return () => {
51
+ if (this.embeddedFactory === factory)
52
+ this.embeddedFactory = undefined;
53
+ };
54
+ }
55
+ /** Register the arbor routing factory. Returns its disposer. */
56
+ useArborFactory(factory) {
57
+ this.arborFactory = factory;
58
+ return () => {
59
+ if (this.arborFactory === factory)
60
+ this.arborFactory = undefined;
61
+ };
62
+ }
63
+ /** The captured official AgentLoop, when one is mounted. */
64
+ get embedded() {
65
+ return this.embeddedFactory;
66
+ }
67
+ async create(options) {
68
+ // Every session is created through the arbor driver while it is mounted: the
69
+ // driver runs arbor sessions itself and keeps the embedded loop's handle for
70
+ // the others, which is what lets a preset selection re-route a session that
71
+ // has not started a turn (see ArborDriver's `agent-preset/selected` seam).
72
+ // Without the driver every session keeps running on the embedded loop, which
73
+ // is what a deployment that loads neither row gets.
74
+ const arbor = this.arborFactory;
75
+ if (arbor !== undefined)
76
+ return delegate(this.ctx, arbor, 'createAgent', options);
77
+ return delegate(this.ctx, this.requireEmbedded(), 'createAgent', options);
78
+ }
79
+ async resume(options) {
80
+ // The driver decides per session: it owns the persisted session when the log
81
+ // records arbor, and hands everything else to the embedded loop. Both paths
82
+ // keep their handle, for the same reason `create` does.
83
+ const arbor = this.arborFactory;
84
+ if (arbor !== undefined)
85
+ return delegate(this.ctx, arbor, 'resume', options);
86
+ return delegate(this.ctx, this.requireEmbedded(), 'resume', options);
87
+ }
88
+ requireEmbedded() {
89
+ if (this.embeddedFactory === undefined) {
90
+ throw new Error('no agent factory registered (load an agent-loop plugin)');
91
+ }
92
+ return this.embeddedFactory;
93
+ }
94
+ }
95
+ export default ArborAgentRegistry;
package/relay.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The relay: arbor's journal onto the DSH session log.
3
+ *
4
+ * A window's own record lives in the arbor journal, and the user-visible one
5
+ * lives in the DSH session log. The driver mirrors the second from the first:
6
+ * while a turn runs, every tool call is recorded in the journal before it runs
7
+ * and its result after, and DSH appends the matching `tool/call` +
8
+ * `tool/result` pair to the session log. A process that stops in between
9
+ * leaves the journal holding a call the log never saw.
10
+ *
11
+ * On resume the relay reads the session's window chain out of the journal and
12
+ * appends the `tool/call` events the log is missing, so DSH's own
13
+ * `interruptedTurnClosers` can then close them with an outcome-unknown result
14
+ * instead of a not-started one — the model is told the call may have had side
15
+ * effects, which is the truth.
16
+ *
17
+ * The cursor is the DSH session log itself: the journal's calls are matched,
18
+ * in order, against the calls the log's assistant messages asked for, and
19
+ * whatever the log already carries is left alone. There is no second cursor to
20
+ * keep in step, and nothing is replayed twice.
21
+ */
22
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
23
+ import type { Journal, WindowRef } from '@4fu/arbor-interfaces';
24
+ /** One tool call the arbor window recorded, read back from the journal. */
25
+ export interface ArborToolCall {
26
+ /** The tool the window called. */
27
+ readonly name: string;
28
+ /** The arguments as the journal recorded them. */
29
+ readonly args: string;
30
+ /** Whether the window recorded this call's result as well. */
31
+ readonly settled: boolean;
32
+ }
33
+ /**
34
+ * The tool calls one window chain recorded, in journal order.
35
+ *
36
+ * The chain is every window of one session: the first, its continuations, and
37
+ * the one a restart opened for it. A `tool_result` names the `tool_call` it
38
+ * answers in its refs, which is how a call is known to be settled.
39
+ */
40
+ export declare function windowToolCalls(journal: Journal, windows: readonly WindowRef[]): Promise<ArborToolCall[]>;
41
+ /**
42
+ * The `tool/call` events the arbor journal recorded and this log lost.
43
+ *
44
+ * Arbor runs the calls it recorded, in order, so they are the prefix of the
45
+ * calls the log's assistant messages asked for; a mismatch stops the replay
46
+ * rather than guessing. Calls the log already answers, and calls an
47
+ * interrupted attempt left behind (which arbor never ran), are left out.
48
+ */
49
+ export declare function relayToolCalls(log: readonly SessionEvent[], calls: readonly ArborToolCall[]): Extract<SessionEvent, {
50
+ type: 'tool/call';
51
+ }>[];