dsh-loop-engine 0.1.5-rc2 → 0.1.5-rc4
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 +44 -177
- package/README.zh.md +48 -100
- package/lib/client.js +887 -266
- package/lib/index.js +2212 -914
- package/lib/invariant.js +43 -45
- package/lib/types/agent-preset-ids.d.ts +303 -0
- package/lib/types/client/LoopEngineBadge.d.ts +44 -17
- package/lib/types/client/LoopEngineComposerSelect.d.ts +79 -13
- package/lib/types/client/LoopEngineSection.d.ts +5 -4
- package/lib/types/client/locales.d.ts +133 -7
- package/lib/types/client/reload.d.ts +135 -0
- package/lib/types/client/session-engine.d.ts +474 -0
- package/lib/types/client/store.d.ts +1 -1
- package/lib/types/client/turn-status.d.ts +112 -10
- package/lib/types/client/use-session-engine.d.ts +66 -0
- package/lib/types/commands.d.ts +11 -3
- package/lib/types/driver-core/host-servers.d.ts +106 -0
- package/lib/types/driver-core/hosted-engine-runtime.d.ts +190 -0
- package/lib/types/driver-core/hosted-tool-vocabulary.d.ts +72 -0
- package/lib/types/driver-core/model-handover.d.ts +116 -0
- package/lib/types/driver-core/ownership.d.ts +6 -5
- package/lib/types/driver-core/prompt.d.ts +32 -0
- package/lib/types/driver-core/session-lifetime.d.ts +62 -0
- package/lib/types/driver-core/session-model.d.ts +82 -0
- package/lib/types/engine-claude/agent.d.ts +23 -3
- package/lib/types/engine-claude/loop.d.ts +16 -15
- package/lib/types/engine-codex/agent.d.ts +22 -3
- package/lib/types/engine-codex/appserver/client.d.ts +15 -2
- package/lib/types/engine-codex/loop.d.ts +13 -15
- package/lib/types/engine-codex/model-handover.d.ts +44 -0
- package/lib/types/engine-kimi/acp/client.d.ts +10 -0
- package/lib/types/engine-kimi/agent.d.ts +19 -2
- package/lib/types/engine-kimi/commands.d.ts +18 -14
- package/lib/types/engine-kimi/loop.d.ts +14 -16
- package/lib/types/engine-kimi/model-handover.d.ts +32 -0
- package/lib/types/engine-kimi/process.d.ts +2 -2
- package/lib/types/engine-kimi/types.d.ts +1 -1
- package/lib/types/engine-of-session.d.ts +97 -0
- package/lib/types/engine-pi/agent.d.ts +25 -23
- package/lib/types/engine-pi/loop.d.ts +13 -23
- package/lib/types/engine-pi/model-handover.d.ts +35 -0
- package/lib/types/engine-pi/types.d.ts +2 -2
- package/lib/types/engine-remote.d.ts +192 -0
- package/lib/types/engine-surface.d.ts +36 -0
- package/lib/types/index.d.ts +51 -50
- package/lib/types/invariant.d.ts +8 -5
- package/lib/types/model-selection-reset.d.ts +271 -0
- package/lib/types/patch-manager.d.ts +57 -39
- package/lib/types/preset.d.ts +39 -26
- package/lib/types/provider-route.d.ts +83 -36
- package/lib/types/router-loop.d.ts +406 -0
- package/lib/types/session-engine-store.d.ts +138 -0
- package/lib/types/settings.d.ts +12 -11
- package/package.json +109 -104
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React binding over the plugin's per-session engine cache.
|
|
3
|
+
*
|
|
4
|
+
* Separated from `./session-engine.ts` on purpose: the cache, the contribution,
|
|
5
|
+
* and the switcher are plain logic with no React import, so they are exercised
|
|
6
|
+
* in node (`tests/session-engine-cache.spec.ts`) — this file is the only part of
|
|
7
|
+
* the read path that needs a component to render.
|
|
8
|
+
*
|
|
9
|
+
* It is also the ONE driver of the chat turn-status row
|
|
10
|
+
* (`./turn-status.ts`): the row is painted through a document-level attribute,
|
|
11
|
+
* and a component is the only place that knows whether the session it renders is
|
|
12
|
+
* the one on screen. The cache cannot know that — it answers for every session a
|
|
13
|
+
* surface has watched — so a reflection made from there could be (and was) taken
|
|
14
|
+
* over by a background session. Here the chip and the composer of the session on
|
|
15
|
+
* screen declare that session as the row's subject while they are mounted, with
|
|
16
|
+
* the focus guard inside `reflectTurnStatusEngine`, and the reflection follows
|
|
17
|
+
* the cache's answer so a switch repaints the row.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-loop-engine/client/use-session-engine
|
|
20
|
+
*/
|
|
21
|
+
import { type SessionEngineReport } from '../agent-preset-ids.ts';
|
|
22
|
+
import type { SessionEngineCache } from './session-engine.ts';
|
|
23
|
+
/**
|
|
24
|
+
* Follow one session's engine report in a component, and — because a component
|
|
25
|
+
* is what knows whether this session is the one on screen — drive the chat
|
|
26
|
+
* turn-status row from it.
|
|
27
|
+
*
|
|
28
|
+
* The value is read from the cache DURING render, so a session switch can never
|
|
29
|
+
* paint the previous session's engine: only the re-render is deferred, and only
|
|
30
|
+
* until the host answers. The reflection runs in an effect rather than during
|
|
31
|
+
* render (it writes to the document), and re-runs when the ANSWER changes — not
|
|
32
|
+
* just when the session id does — so the row follows a switch: the picker's
|
|
33
|
+
* `invalidate` drops the cached answer, the cache re-asks, the hook's watcher
|
|
34
|
+
* bumps, the engine changes, and the attribute lands on the engine the session
|
|
35
|
+
* now runs.
|
|
36
|
+
*
|
|
37
|
+
* Only the ACTUAL engine reaches the row. A recorded engine that differs from it
|
|
38
|
+
* is one whose switch never took over (its release did not complete), so the row
|
|
39
|
+
* keeps painting what is really running — the same rule the chip and the composer
|
|
40
|
+
* follow when they name an engine; they merely also carry the marker
|
|
41
|
+
* ({@link SessionEngineReport}).
|
|
42
|
+
*
|
|
43
|
+
* The value is read from the cache DURING render, so a session switch can never
|
|
44
|
+
* paint the previous session's engine: only the re-render is deferred, and only
|
|
45
|
+
* until the host answers. This effect is also the trigger that re-reads the
|
|
46
|
+
* session: it runs when the session on screen CHANGES (a mount, a switch, or the
|
|
47
|
+
* same session opened again), and the subscription it installs re-reads the
|
|
48
|
+
* session on its first watch (`SessionEngineCache.watch` → `refresh`), so a
|
|
49
|
+
* session the user comes back to cannot keep rendering a report this page took
|
|
50
|
+
* before — coming back is not what moves the session (a switch is performed on
|
|
51
|
+
* the host, and the one that has to release the session reloads this page), but
|
|
52
|
+
* it is long enough for the answer to be old.
|
|
53
|
+
* Re-renders of the same session do not re-run this effect, so nothing here asks
|
|
54
|
+
* again while the session stays on screen.
|
|
55
|
+
*
|
|
56
|
+
* One effect does the whole thing, so the three steps cannot come apart: declare
|
|
57
|
+
* this session as the row's subject (`focusTurnStatusSession`), reflect its
|
|
58
|
+
* engine by that declaration (`reflectTurnStatusEngine`), and release the
|
|
59
|
+
* declaration when this surface goes away (`blurTurnStatusSession`, which
|
|
60
|
+
* withdraws it only while it is still this session's).
|
|
61
|
+
* @param cache - the cache the plugin mounted.
|
|
62
|
+
* @param sessionId - the session to follow, or undefined off a session scope.
|
|
63
|
+
* @returns the engine report, or undefined while it is unknown.
|
|
64
|
+
*/
|
|
65
|
+
export declare function useEngineOfSession(cache: SessionEngineCache, sessionId: string | undefined): SessionEngineReport | undefined;
|
|
66
|
+
//# sourceMappingURL=use-session-engine.d.ts.map
|
package/lib/types/commands.d.ts
CHANGED
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
* are cwd-dependent, and a global dsh registration would collide across
|
|
18
18
|
* projects.
|
|
19
19
|
*
|
|
20
|
+
* The built-in list is limited to commands the CLI actually recognizes in SDK
|
|
21
|
+
* mode (verified against Claude Code 2.1.220 by driving real SDK queries):
|
|
22
|
+
* unknown ones are not forwarded to the model at all — the CLI answers
|
|
23
|
+
* `Unknown command: /name` — but registering one would still advertise a
|
|
24
|
+
* command that does not exist. Note that some recognized commands report
|
|
25
|
+
* `isn't available in this environment` outside the interactive TUI; that
|
|
26
|
+
* report is the engine's own answer and travels back as an assistant message.
|
|
27
|
+
*
|
|
20
28
|
* @module dsh-loop-engine/commands
|
|
21
29
|
*/
|
|
22
30
|
import type { UserMessage } from '@deepseek-ai/dsh-session';
|
|
@@ -60,9 +68,9 @@ export declare const CLAUDE_CODE_COMMANDS: readonly CommandDefinition[];
|
|
|
60
68
|
/**
|
|
61
69
|
* Discover the user-level custom slash commands from `~/.claude/commands/*.md`
|
|
62
70
|
* and build forwarding definitions for them. The scan is synchronous so the
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
71
|
+
* agent-creation path can register the commands before the agent is published;
|
|
72
|
+
* files without a usable name or description, and names already taken by the
|
|
73
|
+
* built-ins, are skipped.
|
|
66
74
|
* @returns forwarding definitions, sorted by file name.
|
|
67
75
|
*/
|
|
68
76
|
export declare function discoverUserSlashCommands(): CommandDefinition[];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural shapes of the optional host registries the plugin
|
|
3
|
+
* extends.
|
|
4
|
+
*
|
|
5
|
+
* Declared locally rather than imported from their packages: the plugin
|
|
6
|
+
* depends on the SERVICE CONTRACTS it consumes only where the harness exposes
|
|
7
|
+
* one (`dsh-agent`, `dsh-session`, …), and the command/skill registries are
|
|
8
|
+
* read through `ctx.get` and may legitimately be absent in a minimal profile.
|
|
9
|
+
* Keeping the shapes here means one definition shared by every consumer and no
|
|
10
|
+
* peer dependency that a headless composition does not need.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-loop-engine/driver-core/host-servers
|
|
13
|
+
*/
|
|
14
|
+
import type { CommandDefinition } from '../commands.ts';
|
|
15
|
+
import type { SkillProvider, SkillProviderControl } from '../skills.ts';
|
|
16
|
+
import type { SettingsNamespace, SettingsPathOp } from '@deepseek-ai/dsh-settings';
|
|
17
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
18
|
+
import type { HostedEngineRouteAdapter } from '../provider-route.ts';
|
|
19
|
+
/** The host command registry (`ctx.commands`), as this plugin uses it. */
|
|
20
|
+
export interface CommandsService {
|
|
21
|
+
/** Register one definition in the calling context's scope layer. */
|
|
22
|
+
register(def: CommandDefinition): () => void;
|
|
23
|
+
}
|
|
24
|
+
/** The host skill registry (`ctx.skills`), as this plugin uses it. */
|
|
25
|
+
export interface SkillsService {
|
|
26
|
+
/** Register a provider in the calling context's scope layer. */
|
|
27
|
+
registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void;
|
|
28
|
+
}
|
|
29
|
+
/** The host preset roster (`ctx.agentPresets`), as this plugin uses it. */
|
|
30
|
+
export interface AgentPresetsService {
|
|
31
|
+
/** The effective default preset id. */
|
|
32
|
+
readonly defaultId: string;
|
|
33
|
+
/** Read one preset's composition text. */
|
|
34
|
+
read(id: string): Promise<string>;
|
|
35
|
+
}
|
|
36
|
+
/** The host settings service's mutation seam, as this plugin uses it. */
|
|
37
|
+
export interface SettingsMutator {
|
|
38
|
+
/** Apply ops to one namespace. */
|
|
39
|
+
mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[]): Promise<void>;
|
|
40
|
+
/** Registered namespaces, when the provider can enumerate them. */
|
|
41
|
+
describe?(): SettingsDescriptorLike[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One registered settings namespace, as the host describes it
|
|
45
|
+
* (`@deepseek-ai/dsh-settings` `SettingsDescriptor`). Only the two fields this
|
|
46
|
+
* plugin reads are declared.
|
|
47
|
+
*/
|
|
48
|
+
export interface SettingsDescriptorLike {
|
|
49
|
+
/** Namespace name. */
|
|
50
|
+
readonly ns: string;
|
|
51
|
+
/**
|
|
52
|
+
* The composition's own value for this namespace, before the user layer. It is
|
|
53
|
+
* what a saved user-layer value overrode, which is why `model-selection-reset.ts`
|
|
54
|
+
* reads it: the deployment's own configured default model is a real model it
|
|
55
|
+
* can name when the saved default does not.
|
|
56
|
+
*/
|
|
57
|
+
readonly base?: unknown;
|
|
58
|
+
}
|
|
59
|
+
/** The host llm registry (`ctx.llm`), as this plugin uses it. */
|
|
60
|
+
export interface LlmRegistry {
|
|
61
|
+
/** Register a placeholder adapter for one or more provider labels. */
|
|
62
|
+
registerAdapter(providers: string[], adapter: HostedEngineRouteAdapter): () => void;
|
|
63
|
+
}
|
|
64
|
+
/** The host session-projection registry (`ctx.sessionProjections`), as this plugin uses it. */
|
|
65
|
+
export interface SessionProjectionsService {
|
|
66
|
+
/** Folded projection state of one session, or undefined when it never advanced. */
|
|
67
|
+
stateOf(session: Session, key: 'turnBoundary'): TurnBoundaryFacts | undefined;
|
|
68
|
+
/** The same fold for the durable model selection (`model-selection-reset.ts`). */
|
|
69
|
+
stateOf(session: Session, key: 'modelSelection'): ModelSelectionFacts | undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One complete model selection as the host's `model/selection` event and its
|
|
73
|
+
* projection carry it (`packages/api/session-controller/src/types.ts`,
|
|
74
|
+
* `ModelSelection`).
|
|
75
|
+
*/
|
|
76
|
+
export interface SessionModelSelection {
|
|
77
|
+
/** Registered provider route. */
|
|
78
|
+
readonly provider: string;
|
|
79
|
+
/** Provider-owned model id. */
|
|
80
|
+
readonly model: string;
|
|
81
|
+
/** Adapter-owned reasoning effort, or provider/default behavior when absent. */
|
|
82
|
+
readonly reasoningEffort?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The slice of the host's `modelSelection` projection state this plugin reads.
|
|
86
|
+
* `lastUsed` is deliberately not declared: it is that fold's view of the newest
|
|
87
|
+
* `request/header`, which the plugin reads from the session itself
|
|
88
|
+
* (`Session.requestHeader()`) with the same result.
|
|
89
|
+
*/
|
|
90
|
+
export interface ModelSelectionFacts {
|
|
91
|
+
/** Later selection not yet consumed by a matching recorded request, or null. */
|
|
92
|
+
readonly pending: SessionModelSelection | null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The turn-boundary facts this plugin reads. Mirrors `TurnBoundaryProjection`
|
|
96
|
+
* in `@deepseek-ai/dsh-agent` without depending on that module's projection
|
|
97
|
+
* registry augmentation, which a non-plugin consumer has no other reason to
|
|
98
|
+
* pull into its program.
|
|
99
|
+
*/
|
|
100
|
+
export interface TurnBoundaryFacts {
|
|
101
|
+
/** Seq of the open turn's `turn/start`, or null between turns. */
|
|
102
|
+
readonly openTurnStartSeq: number | null;
|
|
103
|
+
/** Turn number of the latest `turn/start`; 0 before the first turn. */
|
|
104
|
+
readonly lastTurn: number;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=host-servers.d.ts.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AgentFactory transaction machinery for the hosted loop engines.
|
|
3
|
+
*
|
|
4
|
+
* All four engines (Claude Code, Codex, Pi, Kimi Code) implement the harness's
|
|
5
|
+
* AgentFactory contract the same way: prepare a driver, scope, and one memoized
|
|
6
|
+
* reverse teardown for a session; run the caller's setup under a fused abort
|
|
7
|
+
* signal; publish through both registries and announce; and on resume, own a
|
|
8
|
+
* session's write handle across the cold read, crash repair, and
|
|
9
|
+
* re-publication. None of that touches an engine protocol — the whole
|
|
10
|
+
* engine-specific surface is one call, {@link HostedEngineRuntime.buildAgent}.
|
|
11
|
+
*
|
|
12
|
+
* One move has no counterpart in the harness's own factory contract and is what
|
|
13
|
+
* makes an in-place engine swap possible: a LIVE session changes drivers without
|
|
14
|
+
* being released. {@link HostedAgentHandle.retire} stops the outgoing machine
|
|
15
|
+
* while leaving the session entered, and {@link HostedEngineRuntime.swap} builds
|
|
16
|
+
* the incoming engine's machine onto that same Session. The session's store
|
|
17
|
+
* entry and write handle travel across the handover in a
|
|
18
|
+
* {@link SessionLifetime} — the one object that owns them.
|
|
19
|
+
*
|
|
20
|
+
* This body mirrors the default in-process `agent-loop` factory. It is a plain
|
|
21
|
+
* class, not a Cordis plugin: the process-wide AgentFactory slot is owned by
|
|
22
|
+
* the router (`router-loop.ts`, which subclasses the harness `AgentLoop`), and
|
|
23
|
+
* every hosted engine is one runtime instance the router delegates to. That is
|
|
24
|
+
* what lets several engines serve different sessions concurrently — the
|
|
25
|
+
* harness admits exactly one factory, so the factory itself must dispatch.
|
|
26
|
+
*
|
|
27
|
+
* Subclasses supply:
|
|
28
|
+
* - the effect label prefix (`<label>.transactions()`,
|
|
29
|
+
* `<label>.lifecycle(id)`, `<label>.resume-load(id)`) — the lifecycle label
|
|
30
|
+
* is asserted by tests, so it must stay `<label>.lifecycle(...)`;
|
|
31
|
+
* - their own configuration resolution;
|
|
32
|
+
* - {@link HostedEngineRuntime.buildAgent}.
|
|
33
|
+
*
|
|
34
|
+
* @module dsh-loop-engine/driver-core/hosted-engine-runtime
|
|
35
|
+
*/
|
|
36
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
37
|
+
import type { Agent, AgentFactory, AgentHandle, AgentOptions, AgentSetup, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
|
|
38
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
39
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
40
|
+
import type { Scope } from '@deepseek-ai/dsh-scope';
|
|
41
|
+
import { SessionLifetime } from './session-lifetime.ts';
|
|
42
|
+
/**
|
|
43
|
+
* What the transaction machinery needs of a driver beyond the harness `Agent`
|
|
44
|
+
* contract: a teardown entry point that also unwinds the driver's own scope.
|
|
45
|
+
* The concrete engines declare these; the base only spells them out so the
|
|
46
|
+
* shared body can call them generically.
|
|
47
|
+
*/
|
|
48
|
+
export interface HostedAgent extends Agent {
|
|
49
|
+
/** Stop the machine with the given cause, without waiting for it to settle. */
|
|
50
|
+
cancel(cause: Parameters<Agent['cancel']>[0]): void;
|
|
51
|
+
/** Resolves when the machine has no work in flight. */
|
|
52
|
+
whenIdle(): Promise<void>;
|
|
53
|
+
/** The driver's own resource scope, unwound after the machine settles. */
|
|
54
|
+
readonly scope: Scope;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The plugin's own handle for one published hosted agent: the harness contract
|
|
58
|
+
* plus the two facts an in-place engine swap needs.
|
|
59
|
+
*
|
|
60
|
+
* A swap is two moves by two different runtimes: the outgoing engine retires its
|
|
61
|
+
* machine and hands the session over, and the incoming engine builds onto that
|
|
62
|
+
* session. The router is the only caller and it tracks the handle already, so
|
|
63
|
+
* the harness's own `AgentHandle` — which carries neither fact — is widened
|
|
64
|
+
* here rather than reaching back into a transaction that has ended.
|
|
65
|
+
*/
|
|
66
|
+
export interface HostedAgentHandle extends AgentHandle {
|
|
67
|
+
/** The live session's lifetime resources, owned by this agent until it is disposed. */
|
|
68
|
+
readonly lifetime: SessionLifetime;
|
|
69
|
+
/**
|
|
70
|
+
* Retire this machine for good while LEAVING the session alive: stop it,
|
|
71
|
+
* unwind its scope, and leave the agent registry — the session's entry and
|
|
72
|
+
* write handle pass to {@link SessionLifetime}'s next owner instead of being
|
|
73
|
+
* released here. Only valid while this agent still owns the session: the
|
|
74
|
+
* router calls it for the session's live, idle agent only.
|
|
75
|
+
*/
|
|
76
|
+
retire(): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
/** Options for {@link HostedEngineRuntime.swap}. */
|
|
79
|
+
export interface SwapAgentOptions {
|
|
80
|
+
/** The live session's lifetime resources, handed over by the outgoing engine. */
|
|
81
|
+
readonly lifetime: SessionLifetime;
|
|
82
|
+
/** Loop options for the successor; the router replays the outgoing agent's own. */
|
|
83
|
+
readonly agentOptions?: AgentOptions | undefined;
|
|
84
|
+
/** The composition callback the session was built with, replayed onto the successor. */
|
|
85
|
+
readonly setup?: AgentSetup | undefined;
|
|
86
|
+
/** The outgoing agent's runtime owner, for a child session's inherited ownership. */
|
|
87
|
+
readonly parentAgent?: Agent | undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Concrete creation/resume machinery for one hosted engine.
|
|
91
|
+
*
|
|
92
|
+
* Creation and resume follow the registry factory contract and the shared
|
|
93
|
+
* publication transaction: prepare, run setup, then publish through both
|
|
94
|
+
* registries, announce, and emit `agent/session-start`. {@link swap} follows the
|
|
95
|
+
* same transaction onto a session another agent entered, which is the only
|
|
96
|
+
* difference between taking a session over and owning it from birth.
|
|
97
|
+
*/
|
|
98
|
+
export declare abstract class HostedEngineRuntime<TConfig, TAgent extends HostedAgent> implements AgentFactory {
|
|
99
|
+
/** Validated configuration owned by this engine instance. */
|
|
100
|
+
readonly config: TConfig;
|
|
101
|
+
/** Effect-label prefix; also identifies the engine in diagnostics. */
|
|
102
|
+
readonly label: string;
|
|
103
|
+
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
104
|
+
protected readonly runtime: {
|
|
105
|
+
ctx: Context;
|
|
106
|
+
};
|
|
107
|
+
private readonly ownership;
|
|
108
|
+
/**
|
|
109
|
+
* @param ctx - the owning context; its fiber's unload tears every live agent down.
|
|
110
|
+
* @param label - effect-label prefix, e.g. `agentLoopKimi`.
|
|
111
|
+
* @param config - already-resolved engine configuration.
|
|
112
|
+
*/
|
|
113
|
+
constructor(ctx: Context, label: string, config: TConfig);
|
|
114
|
+
/**
|
|
115
|
+
* Construct this engine's driver for one prepared session. Called once per
|
|
116
|
+
* create, resume, or swap, after the session exists and before setup runs; the
|
|
117
|
+
* hook is the engine's entire protocol surface.
|
|
118
|
+
*/
|
|
119
|
+
protected abstract buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): TAgent;
|
|
120
|
+
/**
|
|
121
|
+
* Construct the driver, scope, and one memoized reverse teardown for a new
|
|
122
|
+
* agent. The teardown is registered with the factory and the owner fiber
|
|
123
|
+
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
124
|
+
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
125
|
+
*
|
|
126
|
+
* `lifetime` carries the session's store entry and write handle rather than
|
|
127
|
+
* this body owning them: a fresh transaction binds them when it publishes,
|
|
128
|
+
* while a swap publishes a session whose entry another machine already bound
|
|
129
|
+
* and whose write handle another machine already opened.
|
|
130
|
+
*/
|
|
131
|
+
private prepare;
|
|
132
|
+
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
133
|
+
private setupAndPublish;
|
|
134
|
+
/**
|
|
135
|
+
* Create an agent and session under one caller-supplied identity, owned by
|
|
136
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
137
|
+
* durable identity is stored before publication.
|
|
138
|
+
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
139
|
+
* @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
|
|
140
|
+
* @returns the published handle.
|
|
141
|
+
*/
|
|
142
|
+
createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<HostedAgentHandle>;
|
|
143
|
+
/**
|
|
144
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
145
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
146
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
147
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
148
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
149
|
+
* created again.
|
|
150
|
+
* @param session - the unpublished session to store.
|
|
151
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
152
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
153
|
+
*/
|
|
154
|
+
private createStoredSession;
|
|
155
|
+
/**
|
|
156
|
+
* Durably store the session events appended since the last stored cursor.
|
|
157
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
158
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
159
|
+
* through the handle before live events start routing into it.
|
|
160
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
161
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
162
|
+
*/
|
|
163
|
+
private appendUnstoredSuffix;
|
|
164
|
+
/**
|
|
165
|
+
* Resume an owned agent from the configured persistence service.
|
|
166
|
+
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
167
|
+
* @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
|
|
168
|
+
* @returns the published handle.
|
|
169
|
+
*/
|
|
170
|
+
resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<HostedAgentHandle>;
|
|
171
|
+
/**
|
|
172
|
+
* Build this engine's agent onto a LIVE session another engine's agent left,
|
|
173
|
+
* taking the session's lifetime over.
|
|
174
|
+
*
|
|
175
|
+
* The counterpart of {@link HostedAgentHandle.retire}: together they are an
|
|
176
|
+
* in-place engine swap, and the harness's own factory contract has neither.
|
|
177
|
+
* Nothing is read from persistence and nothing is created in the store — the
|
|
178
|
+
* successor drives the Session object that is already live and already
|
|
179
|
+
* entered, so a browser half attached to that session sees no lifecycle edge
|
|
180
|
+
* at all. The lifetime carries the still-open write handle, so the session
|
|
181
|
+
* keeps being stored through the same channel it was already using.
|
|
182
|
+
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
183
|
+
* @param options - the live session's lifetime, loop options, setup, and parent.
|
|
184
|
+
* @returns the published handle, which owns the session from here on.
|
|
185
|
+
*/
|
|
186
|
+
swap(ownerCtx: Context, options: SwapAgentOptions): Promise<HostedAgentHandle>;
|
|
187
|
+
/** Resume through an explicit persistence service. */
|
|
188
|
+
private resumeWith;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=hosted-engine-runtime.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection of a hosted engine's tool vocabulary onto dsh's, plus the plan
|
|
3
|
+
* snapshot a hosted plan tool carries.
|
|
4
|
+
*
|
|
5
|
+
* The durable `tool/call` event is what the Web client's tool rows
|
|
6
|
+
* (`@deepseek-ai/dsh-client-ui-chat`'s tool Definition), the produced-files row
|
|
7
|
+
* and inline file links (`@deepseek-ai/dsh-client-ui-deliverables`'
|
|
8
|
+
* `mutationPath`), and the trajectory view read. Those consumers recognize only
|
|
9
|
+
* dsh's own tool names and argument shapes, while a hosted engine names its
|
|
10
|
+
* tools differently: Claude's `Write`/`Edit`/`Read`/`Bash`, Codex's
|
|
11
|
+
* `apply_patch`/`command_execution`, Kimi's title-cased `Bash`, Pi's
|
|
12
|
+
* `path`-keyed arguments. This module normalizes the event a driver appends
|
|
13
|
+
* while leaving the durable assistant message — and therefore the next step's
|
|
14
|
+
* serialized prompt — in the engine's own vocabulary. Session pairing is by
|
|
15
|
+
* callId, so the two spellings coexist.
|
|
16
|
+
*
|
|
17
|
+
* Only lossless projections are applied. A call whose arguments do not carry
|
|
18
|
+
* the dsh fields is passed through unchanged and simply renders as a generic
|
|
19
|
+
* row, so a new engine-side tool degrades instead of mis-rendering.
|
|
20
|
+
*
|
|
21
|
+
* Plan extraction is separate: dsh's todo panel is driven by the `todo/write`
|
|
22
|
+
* session event, not by the tool row, so a driver appends that event for the
|
|
23
|
+
* plan tool it recognizes.
|
|
24
|
+
*
|
|
25
|
+
* @module dsh-loop-engine/driver-core/hosted-tool-vocabulary
|
|
26
|
+
*/
|
|
27
|
+
/** A hosted loop engine id — the settings engine union without `in-process`. */
|
|
28
|
+
export type HostedEngineId = 'claude-code' | 'codex' | 'pi' | 'kimi';
|
|
29
|
+
/** One tool call projected into dsh's vocabulary for the durable `tool/call` event. */
|
|
30
|
+
export interface NormalizedHostedToolCall {
|
|
31
|
+
/** dsh tool name when the projection recognizes the call, else the engine's own spelling. */
|
|
32
|
+
readonly name: string;
|
|
33
|
+
/** dsh-shaped arguments JSON when the projection rewrites them, else the engine's own string. */
|
|
34
|
+
readonly arguments: string;
|
|
35
|
+
}
|
|
36
|
+
/** One plan entry in the shape the `todo/write` event and the todo panel consume. */
|
|
37
|
+
export interface HostedPlanTodo {
|
|
38
|
+
/** The task line, verbatim. */
|
|
39
|
+
readonly content: string;
|
|
40
|
+
/** Lifecycle state, restricted to dsh's three-state union. */
|
|
41
|
+
readonly status: 'pending' | 'in_progress' | 'completed';
|
|
42
|
+
}
|
|
43
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
44
|
+
interface SessionEventMap {
|
|
45
|
+
'todo/write': {
|
|
46
|
+
todos: {
|
|
47
|
+
content: string;
|
|
48
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Project one hosted tool call onto dsh's `tool/call` vocabulary. The engine's
|
|
55
|
+
* own assistant-message block is untouched; only the event this returns is
|
|
56
|
+
* normalized, so the next step's serialized prompt keeps the engine's spelling.
|
|
57
|
+
* @param engine - the hosted engine the call came from.
|
|
58
|
+
* @param name - the engine's own tool name.
|
|
59
|
+
* @param argumentsJson - the engine's arguments JSON, verbatim.
|
|
60
|
+
* @returns the dsh name and (when reshaped) dsh-shaped arguments.
|
|
61
|
+
*/
|
|
62
|
+
export declare function normalizeHostedToolCall(engine: HostedEngineId, name: string, argumentsJson: string): NormalizedHostedToolCall;
|
|
63
|
+
/**
|
|
64
|
+
* Read the whole todo list a hosted plan tool wrote, in the shape the
|
|
65
|
+
* `todo/write` session event carries.
|
|
66
|
+
* @param engine - the hosted engine the call came from.
|
|
67
|
+
* @param name - the engine's own tool name.
|
|
68
|
+
* @param argumentsJson - the engine's arguments JSON, verbatim.
|
|
69
|
+
* @returns the plan entries (possibly empty) when the call is a plan write, else `undefined`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function planTodosOfHostedTool(engine: HostedEngineId, name: string, argumentsJson: string): readonly HostedPlanTodo[] | undefined;
|
|
72
|
+
//# sourceMappingURL=hosted-tool-vocabulary.d.ts.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dsh endpoint, protocol, and credential a hosted engine is handed when a
|
|
3
|
+
* session selects a REAL dsh model — the second half of the model handover
|
|
4
|
+
* whose first half (`session-model.ts`) only carries the model name.
|
|
5
|
+
*
|
|
6
|
+
* A hosted engine owns its model natively, and today it also owns the endpoint
|
|
7
|
+
* and the credential: each CLI keeps its own provider table. That is why
|
|
8
|
+
* "handing over a dsh model" used to mean "the engine can find that model name
|
|
9
|
+
* in its own configuration". This module answers the other question — what
|
|
10
|
+
* `baseURL`, wire protocol, and credential does the session's selection name in
|
|
11
|
+
* dsh's own settings — so each driver can point its engine at dsh's endpoint.
|
|
12
|
+
*
|
|
13
|
+
* The two reads are the host's own seams, deliberately:
|
|
14
|
+
*
|
|
15
|
+
* - the provider → settings address mapping comes from the llm registry's
|
|
16
|
+
* configurable-provider directory (`LlmRuntime.listConfigurableProviders`,
|
|
17
|
+
* `packages/llm/llm/src/types.ts` `LlmConfigurableProvider`). That directory
|
|
18
|
+
* is the authoritative answer to "which namespace, at which path, configures
|
|
19
|
+
* provider X": `llm-pi-ai` maps a route to `['providers', route]` under
|
|
20
|
+
* `llm-pi-ai`, `llm-deepseek` maps its one route to the whole `llm-deepseek`
|
|
21
|
+
* section. Reading the directory rather than hardcoding namespaces is what
|
|
22
|
+
* keeps this plugin from inventing a mapping the deployment did not make.
|
|
23
|
+
* - the credential is resolved through the credentials seam, because the
|
|
24
|
+
* profile stores an `apiKeyEnv` NAME and its value lives in the harness's
|
|
25
|
+
* credential store, never in the process environment
|
|
26
|
+
* (`packages/bundle/base/cordis.patch.yml`).
|
|
27
|
+
*
|
|
28
|
+
* Both reads are reads of ANOTHER plugin's private namespace, and no contract in
|
|
29
|
+
* the harness guarantees their shape. Every step is therefore defensive: a shape
|
|
30
|
+
* this module does not recognize resolves to `undefined` (the engine keeps its
|
|
31
|
+
* own configuration, exactly today's behavior) and one warning, never a throw
|
|
32
|
+
* and never a guess. The warning names the provider and the model, never a
|
|
33
|
+
* credential.
|
|
34
|
+
*
|
|
35
|
+
* @module dsh-loop-engine/driver-core/model-handover
|
|
36
|
+
*/
|
|
37
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
38
|
+
import type { SessionModelOverride } from './session-model.ts';
|
|
39
|
+
/**
|
|
40
|
+
* The endpoint facts one hosted engine needs to speak to the dsh model a session
|
|
41
|
+
* selected: the model id, the base URL, dsh's wire protocol name for it, and the
|
|
42
|
+
* resolved credential value.
|
|
43
|
+
*
|
|
44
|
+
* The protocol is carried verbatim (`llm-pi-ai` values such as
|
|
45
|
+
* `anthropic-messages`, `openai-completions`, `openai-responses`), because each
|
|
46
|
+
* engine translates it into its own vocabulary — and an engine whose protocol
|
|
47
|
+
* the endpoint does not speak is left to fail loud on its own request rather
|
|
48
|
+
* than being silently given a different endpoint.
|
|
49
|
+
*/
|
|
50
|
+
export interface DshModelHandover {
|
|
51
|
+
/** Provider route the selection named (the engine's provider key). */
|
|
52
|
+
readonly provider: string;
|
|
53
|
+
/** Provider-owned model id the selection named. */
|
|
54
|
+
readonly model: string;
|
|
55
|
+
/** Endpoint base as dsh configured it. */
|
|
56
|
+
readonly baseURL: string;
|
|
57
|
+
/** dsh's wire protocol name for this endpoint. */
|
|
58
|
+
readonly api: string;
|
|
59
|
+
/** The resolved credential value; never logged, never evented. */
|
|
60
|
+
readonly apiKey: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One entry of the host llm registry's configurable-provider directory
|
|
64
|
+
* (`LlmConfigurableProvider`), narrowed to the address fields this module reads.
|
|
65
|
+
*/
|
|
66
|
+
export interface ConfigurableProviderAddress {
|
|
67
|
+
/** Provider route key the entry activates when configured. */
|
|
68
|
+
readonly provider: string;
|
|
69
|
+
/** User-settings namespace whose section configures this provider. */
|
|
70
|
+
readonly settingsNs: string;
|
|
71
|
+
/** Path from that section root to this provider's profile; empty when the section IS the profile. */
|
|
72
|
+
readonly settingsPath: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The host llm registry (`ctx.llm`), as this module uses it.
|
|
76
|
+
*
|
|
77
|
+
* `listConfigurableProviders` is optional: a minimal profile may serve the
|
|
78
|
+
* registry without the directory, and an older harness may predate it. A
|
|
79
|
+
* missing directory is "cannot map", not an error — the engine keeps its own
|
|
80
|
+
* configuration.
|
|
81
|
+
*/
|
|
82
|
+
export interface LlmDirectoryService {
|
|
83
|
+
/** Every provider route an adapter can activate through configuration. */
|
|
84
|
+
listConfigurableProviders?(): readonly ConfigurableProviderAddress[];
|
|
85
|
+
}
|
|
86
|
+
/** The host settings service (`ctx.settings`), as this module uses it. */
|
|
87
|
+
export interface SettingsReader {
|
|
88
|
+
/** Read one registered namespace's resolved value. */
|
|
89
|
+
get?(ns: string): unknown;
|
|
90
|
+
}
|
|
91
|
+
/** The host credentials seam (`ctx.credentials`), as this module uses it. */
|
|
92
|
+
export interface CredentialsService {
|
|
93
|
+
/** Resolve one reference (an environment-variable NAME) to its current value. */
|
|
94
|
+
resolve(ref: string): Promise<{
|
|
95
|
+
readonly value: string;
|
|
96
|
+
} | undefined>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the session's own model selection into the endpoint triple a hosted
|
|
100
|
+
* engine can dial, or `undefined` to leave the engine to its own configuration.
|
|
101
|
+
*
|
|
102
|
+
* `undefined` is the answer in exactly two situations: the selection names no
|
|
103
|
+
* real dsh model ({@link SessionModelOverride} is already `undefined` for the
|
|
104
|
+
* hosted seat), or a real model whose endpoint/credential dsh does not disclose
|
|
105
|
+
* to this read. The second case warns once — it is a deployment-shape gap the
|
|
106
|
+
* operator can see and fix, not a silent downgrade.
|
|
107
|
+
*
|
|
108
|
+
* Called fresh on every step by each driver, so a model or provider picked
|
|
109
|
+
* mid-conversation reaches the engine's next request rather than being frozen
|
|
110
|
+
* when the agent was built.
|
|
111
|
+
* @param ctx - the driver's context, carrying the llm/settings/credentials seams.
|
|
112
|
+
* @param override - the session's dsh model selection, already judged by `sessionModelOverrideOf`.
|
|
113
|
+
* @returns the resolvable endpoint triple, or undefined to inject nothing.
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveModelHandover(ctx: Context, override: SessionModelOverride | undefined): Promise<DshModelHandover | undefined>;
|
|
116
|
+
//# sourceMappingURL=model-handover.d.ts.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
2
|
+
* Shared engine ownership and abort-race machinery for the hosted engines.
|
|
3
3
|
* All four loop drivers (Claude Code, Codex, Pi, Kimi Code) run the same
|
|
4
|
-
* lifecycle:
|
|
5
|
-
* agent's teardown is tracked until it settles, and setup awaits are
|
|
6
|
-
* against a fused abort signal.
|
|
4
|
+
* lifecycle: one runtime per engine owns that engine's live agents, every
|
|
5
|
+
* live agent's teardown is tracked until it settles, and setup awaits are
|
|
6
|
+
* raced against a fused abort signal. The process-wide AgentFactory slot is
|
|
7
|
+
* the router's, not any engine's; these helpers are engine-free — they only
|
|
7
8
|
* touch the fiber state, the session id type, and an AbortController — so the
|
|
8
|
-
*
|
|
9
|
+
* engine modules share them verbatim.
|
|
9
10
|
*
|
|
10
11
|
* @module dsh-loop-engine/driver-core/ownership
|
|
11
12
|
*/
|
|
@@ -5,11 +5,43 @@
|
|
|
5
5
|
* exact projection, so a later replay of the same log derives the identical
|
|
6
6
|
* prompt (Model-visible ⟺ logged bridge).
|
|
7
7
|
*
|
|
8
|
+
* A step whose live request is an engine slash command is the one exception to
|
|
9
|
+
* the transcript framing: {@link engineSlashPrompt} sends that command line
|
|
10
|
+
* verbatim, because the engines only recognize their own commands at the head
|
|
11
|
+
* of the prompt. It is still derived from the log alone, so the guarantee
|
|
12
|
+
* holds.
|
|
13
|
+
*
|
|
8
14
|
* @module dsh-loop-engine/driver-core/prompt
|
|
9
15
|
*/
|
|
10
16
|
import type { Message } from '@deepseek-ai/dsh-llm';
|
|
11
17
|
/** Model-facing stand-in for an image block that the hosted engines cannot consume as bytes. */
|
|
12
18
|
export declare const OMITTED_IMAGE_TEXT = "[image omitted: the driver does not transcribe images; read the file when a path is available]";
|
|
19
|
+
/**
|
|
20
|
+
* The engine's own slash-command line to send as this step's entire prompt, or
|
|
21
|
+
* `undefined` when the step is an ordinary conversational step.
|
|
22
|
+
*
|
|
23
|
+
* Every hosted engine expands a slash command only when the text handed to it
|
|
24
|
+
* *starts* with `/` — Kimi's ACP adapter parses the first prompt block, Claude
|
|
25
|
+
* Code's local-command dispatch and Pi's input expansion both test the leading
|
|
26
|
+
* character of the message string. The serialized transcript never satisfies
|
|
27
|
+
* that (it opens with `<user>`), so a forwarded `/status` reaches the model as
|
|
28
|
+
* prose instead of the engine's own command surface. This helper lets a driver
|
|
29
|
+
* recognize the case and send the command line verbatim, with no transcript
|
|
30
|
+
* framing and no replay history: a slash command is a control line for the
|
|
31
|
+
* engine, not conversation for the model.
|
|
32
|
+
*
|
|
33
|
+
* The live request is the last derived message, so a step whose trailing
|
|
34
|
+
* message is a bare command line is a command step. A trailing skill-injection
|
|
35
|
+
* message (the `/name` skill gesture the drivers materialize as its own user
|
|
36
|
+
* message) displaces it and keeps the step on the transcript path.
|
|
37
|
+
*
|
|
38
|
+
* The returned line is a pure function of the log prefix, so the step stays
|
|
39
|
+
* replayable: the same log derives the same prompt.
|
|
40
|
+
* @param messages - derived history, oldest first, as returned by
|
|
41
|
+
* `Session.deriveMessages()` at step time.
|
|
42
|
+
* @returns the raw command line, or `undefined` for an ordinary step.
|
|
43
|
+
*/
|
|
44
|
+
export declare function engineSlashPrompt(messages: readonly Message[]): string | undefined;
|
|
13
45
|
/**
|
|
14
46
|
* Serialize a derived conversation history into the prompt text of one hosted
|
|
15
47
|
* query. The last message is the live user request that triggered the step;
|