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
|
@@ -48,8 +48,8 @@ export declare function kimiBinResolver(configBin?: string): string;
|
|
|
48
48
|
/**
|
|
49
49
|
* Build the persistent `kimi acp` argv. The ACP child stays alive across steps
|
|
50
50
|
* and is spoken to over JSON-RPC on stdio — the prompt is a request body, not an
|
|
51
|
-
* argv positional — so there is no command-line length ceiling and no model flag
|
|
52
|
-
*
|
|
51
|
+
* argv positional — so there is no command-line length ceiling and no model flag:
|
|
52
|
+
* a model is selected per ACP session through `session/set_model`, not argv.
|
|
53
53
|
* @param bin - the Kimi executable.
|
|
54
54
|
* @returns the argv, `argv[0]` being the executable.
|
|
55
55
|
*/
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** Driver configuration after defaults and load-time validation. */
|
|
15
15
|
export interface ResolvedConfig {
|
|
16
|
-
/** Model
|
|
16
|
+
/** Model sent per ACP session (`session/set_model`) when the session selects none; Kimi native config owns the model when omitted. */
|
|
17
17
|
readonly model: string | undefined;
|
|
18
18
|
/** Explicit environment entries layered over the credential-scrubbed parent environment. */
|
|
19
19
|
readonly env: Record<string, string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine one session runs: the single authoritative read, shared by the
|
|
3
|
+
* router's routing decision and the plugin's own Remote.
|
|
4
|
+
*
|
|
5
|
+
* Two facts can answer it, in this order:
|
|
6
|
+
*
|
|
7
|
+
* 1. THE PLUGIN'S OWN RECORD — `$DSH_HOME/.loop-engine/engines.json` (see
|
|
8
|
+
* `./session-engine-store.ts`). It exists because a user may move a session
|
|
9
|
+
* to another engine at any time, including long after the session started,
|
|
10
|
+
* and the harness's preset channel refuses exactly that
|
|
11
|
+
* (`agent-preset/locked`), so the plugin keeps the choice itself. When the
|
|
12
|
+
* record names an engine, that IS the session's engine and nothing else is
|
|
13
|
+
* consulted — not even the durable log, which is why a session with a record
|
|
14
|
+
* still reads correctly when its persistence is unavailable.
|
|
15
|
+
* 2. THE RECORDED AGENT PRESET, for every session the plugin has no record
|
|
16
|
+
* for: a session created before per-session switching existed, one created
|
|
17
|
+
* on a preset this plugin does not own, or one in a deployment whose record
|
|
18
|
+
* file is gone. That answer comes from the durable log — the harness's
|
|
19
|
+
* `agentPreset` projection folds the session header together with every
|
|
20
|
+
* committed `agent-preset/selected`, so reading it IS reading the log — and
|
|
21
|
+
* it never comes from the session header alone (a creation fact, deep-frozen)
|
|
22
|
+
* or from a client-side listing hint (a partial cache).
|
|
23
|
+
*
|
|
24
|
+
* This module is the ONLY read: the router routes on what it returns and the
|
|
25
|
+
* plugin's own Remote reports the same value, so the engine a session is driven
|
|
26
|
+
* by and the engine it is shown as running cannot disagree.
|
|
27
|
+
*
|
|
28
|
+
* There is one refinement on top of it, and it is the whole reason
|
|
29
|
+
* {@link engineReportOfSession} exists: a session with a LIVE agent has a better
|
|
30
|
+
* answer than its record — the agent in front of it — and the two can differ
|
|
31
|
+
* (only when a release of that agent did not take, since every other switch
|
|
32
|
+
* either swaps the agent in place or leaves the session cold). The report
|
|
33
|
+
* carries both, never blending them (see {@link SessionEngineReport}).
|
|
34
|
+
*
|
|
35
|
+
* The secondary read goes through the same seam the host itself uses before
|
|
36
|
+
* choosing the composition to mount
|
|
37
|
+
* (`ctx.sessionQuery.observeSession(id, { projectionMode: 'all' })`, see
|
|
38
|
+
* `packages/api/session-controller/src/agent.ts` in the harness). The
|
|
39
|
+
* observation is a read-only lease: it is released immediately through `using`,
|
|
40
|
+
* takes no write handle, and holds no write lock.
|
|
41
|
+
*
|
|
42
|
+
* @module dsh-loop-engine/engine-of-session
|
|
43
|
+
*/
|
|
44
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
45
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
46
|
+
import { type LoopEngineId, type SessionEngine, type SessionEngineReport } from './agent-preset-ids.ts';
|
|
47
|
+
import type { EngineRecordSource } from './session-engine-store.ts';
|
|
48
|
+
/**
|
|
49
|
+
* The engine one session runs, read from the plugin's own record first and from
|
|
50
|
+
* the durable `agentPreset` projection otherwise.
|
|
51
|
+
*
|
|
52
|
+
* A deployment that composes no `sessionQuery` service has no durable log to
|
|
53
|
+
* ask, so a session with no record reads as {@link SessionEngine} `unset`: no
|
|
54
|
+
* engine is claimed for it, and the router falls back exactly as it does for a
|
|
55
|
+
* session that recorded nothing.
|
|
56
|
+
* @param ctx - the context carrying the session-query seam.
|
|
57
|
+
* @param sessionId - the persisted session to inspect.
|
|
58
|
+
* @param records - the plugin's own per-session engine record.
|
|
59
|
+
* @returns what the plugin's record, or else the durable log, says about that
|
|
60
|
+
* session's engine.
|
|
61
|
+
*/
|
|
62
|
+
export declare function engineOfSession(ctx: Context, sessionId: SessionId, records: EngineRecordSource): Promise<SessionEngine>;
|
|
63
|
+
/**
|
|
64
|
+
* Reports the engine of one session's LIVE agent, when this process has one:
|
|
65
|
+
* the loop router's own bookkeeping (`RouterLoop.live`), which is the only
|
|
66
|
+
* record of what it actually built for a session.
|
|
67
|
+
*
|
|
68
|
+
* A reader rather than the router itself, so this fold can be exercised without
|
|
69
|
+
* one — and so the router stays the only thing that owns its bookkeeping.
|
|
70
|
+
*/
|
|
71
|
+
export type LiveEngineReader = (sessionId: SessionId) => LoopEngineId | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* What one session actually runs, and — when they differ — the engine its own
|
|
74
|
+
* record names.
|
|
75
|
+
*
|
|
76
|
+
* The live agent outranks the record, because it is the answer to the question
|
|
77
|
+
* this report exists to answer honestly: what is driving this session NOW. The
|
|
78
|
+
* record is still consulted, but as the OTHER fact — the engine this session
|
|
79
|
+
* takes when it is next built. A record that equals the live agent is no news
|
|
80
|
+
* and travels as absent; a live agent with no record at all has nothing to
|
|
81
|
+
* report beside it, because a session with no record was never switched.
|
|
82
|
+
*
|
|
83
|
+
* A session with NO live agent answers exactly as {@link engineOfSession} does:
|
|
84
|
+
* nothing is running, so the record (or, with no record, the recorded preset) IS
|
|
85
|
+
* the engine that session runs — the one its next build will use, which is the
|
|
86
|
+
* state a switch that released the session's agent leaves it in. There is no
|
|
87
|
+
* second fact to report and never a pending one.
|
|
88
|
+
* @param ctx - the context carrying the session-query seam.
|
|
89
|
+
* @param sessionId - the session to report on.
|
|
90
|
+
* @param records - the plugin's own per-session engine record.
|
|
91
|
+
* @param liveEngineOf - the engine of this session's live agent, when this
|
|
92
|
+
* process has one (the router's bookkeeping).
|
|
93
|
+
* @returns the engine driving the session, plus the recorded engine it is not
|
|
94
|
+
* running when the two differ.
|
|
95
|
+
*/
|
|
96
|
+
export declare function engineReportOfSession(ctx: Context, sessionId: SessionId, records: EngineRecordSource, liveEngineOf: LiveEngineReader): Promise<SessionEngineReport>;
|
|
97
|
+
//# sourceMappingURL=engine-of-session.d.ts.map
|
|
@@ -15,11 +15,15 @@ import type { Scope } from '@deepseek-ai/dsh-scope';
|
|
|
15
15
|
import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
|
|
16
16
|
import type { Context } from '@deepseek-ai/cordis';
|
|
17
17
|
import type { ResolvedConfig } from './types.ts';
|
|
18
|
-
import type { PiModelEntry } from './probe.ts';
|
|
19
18
|
import { DriverInbox } from '../driver-core/inbox.ts';
|
|
20
19
|
import { type PiSpawnCapability } from './rpc/client.ts';
|
|
21
|
-
/**
|
|
22
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Provider route label this driver logs into request/header snapshots and
|
|
22
|
+
* message provenance — the ONE route every hosted engine shares
|
|
23
|
+
* ({@link HOSTED_ROUTE_LABEL}), so all four engines select `external/default`
|
|
24
|
+
* and the model menu carries a single group instead of one per engine.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PROVIDER = "external";
|
|
23
27
|
/** Drives one session through turn and step boundaries on Pi. */
|
|
24
28
|
export declare class PiAgent implements Agent {
|
|
25
29
|
private loopCtx;
|
|
@@ -29,7 +33,6 @@ export declare class PiAgent implements Agent {
|
|
|
29
33
|
private readonly config;
|
|
30
34
|
private readonly spawn;
|
|
31
35
|
private readonly bin;
|
|
32
|
-
private readonly catalog;
|
|
33
36
|
readonly inbox: DriverInbox;
|
|
34
37
|
private phase;
|
|
35
38
|
private activityDone;
|
|
@@ -61,9 +64,7 @@ export declare class PiAgent implements Agent {
|
|
|
61
64
|
* @param phase - the running phase carrying the open step's position.
|
|
62
65
|
*/
|
|
63
66
|
private beginSegment;
|
|
64
|
-
constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: PiSpawnCapability, bin: string
|
|
65
|
-
readonly entries: readonly PiModelEntry[];
|
|
66
|
-
});
|
|
67
|
+
constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: PiSpawnCapability, bin: string);
|
|
67
68
|
/**
|
|
68
69
|
* Open this step's RPC child. The Pi RPC process is single-session, so a step
|
|
69
70
|
* never reuses one: the step teardown disposes it and the next step respawns
|
|
@@ -135,23 +136,24 @@ export declare class PiAgent implements Agent {
|
|
|
135
136
|
/** Append the request header snapshot once per loop instance. */
|
|
136
137
|
private assertRequestHeader;
|
|
137
138
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* `
|
|
151
|
-
*
|
|
152
|
-
*
|
|
139
|
+
* Build the `pi --mode rpc` argv/cwd/env for one step's child process.
|
|
140
|
+
*
|
|
141
|
+
* The model comes from the session's own selection when it names a real dsh
|
|
142
|
+
* model, else from the deployment's pinned configuration
|
|
143
|
+
* ({@link sessionModelOverrideOf}). Pi's `--model` flag is
|
|
144
|
+
* `"provider/id"`-qualified, so a session pick travels as the composite —
|
|
145
|
+
* which also carries the provider, leaving the deployment's `--provider` out
|
|
146
|
+
* of the argv for that step. A step with neither a selection nor a pin sends
|
|
147
|
+
* no `--model` at all and lets Pi's own configuration decide. Read every step,
|
|
148
|
+
* so a model picked mid-conversation lands on the next child.
|
|
149
|
+
*
|
|
150
|
+
* When dsh also discloses that model's ENDPOINT, the child is pointed at it:
|
|
151
|
+
* `--provider` names the provider pi's `models.json` declares, `--api-key`
|
|
152
|
+
* carries the credential, and `PI_CODING_AGENT_DIR` redirects pi's agent
|
|
153
|
+
* directory to the plugin-owned one holding that `models.json`
|
|
154
|
+
* ({@link piAgentDir}) — so the child runs on dsh's endpoint instead of its
|
|
155
|
+
* own `~/.pi` configuration, which is never touched.
|
|
153
156
|
*/
|
|
154
|
-
private pickModel;
|
|
155
157
|
private spawnSpec;
|
|
156
158
|
/**
|
|
157
159
|
* Run one Pi RPC query for the current step and map its event stream into the
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pi loop engine module:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* entry. Pi has no permission system, so the entire `pi --mode rpc` child is
|
|
2
|
+
* Pi loop engine module: drives every session it is handed through the Pi CLI
|
|
3
|
+
* (`@earendil-works/pi-coding-agent`) over its JSONL RPC mode, one stateless
|
|
4
|
+
* session per dsh step, with the durable session log as the sole source of
|
|
5
|
+
* model context. The router routes a session here on the plugin's own engine
|
|
6
|
+
* record, else its agent preset; this module is a library, not a Cordis plugin entry. Pi has no permission system, so the entire `pi --mode rpc` child is
|
|
8
7
|
* spawned through the dsh subprocess seam — the only available privilege
|
|
9
8
|
* boundary — and its `--tools` are pruned to the resolved sandbox stance.
|
|
10
9
|
*
|
|
@@ -15,10 +14,9 @@ import z from '@deepseek-ai/schemastery';
|
|
|
15
14
|
import type { AgentOptions } from '@deepseek-ai/dsh-agent';
|
|
16
15
|
import type { Session, SessionId } from '@deepseek-ai/dsh-session';
|
|
17
16
|
import { PiAgent } from './agent.ts';
|
|
18
|
-
import type { PiModelEntry } from './probe.ts';
|
|
19
17
|
import type { PiProcess, PiSpawnSpec } from './rpc/client.ts';
|
|
20
18
|
import type { PiSandboxMode, ResolvedConfig } from './types.ts';
|
|
21
|
-
import {
|
|
19
|
+
import { HostedEngineRuntime } from '../driver-core/hosted-engine-runtime.ts';
|
|
22
20
|
/** Pi CLI sandbox modes a deployment may pin. */
|
|
23
21
|
export declare const PI_SANDBOX_MODES: readonly PiSandboxMode[];
|
|
24
22
|
/** Grace in milliseconds for Pi process-tree termination. */
|
|
@@ -35,40 +33,32 @@ export interface Config {
|
|
|
35
33
|
sandboxMode?: PiSandboxMode;
|
|
36
34
|
/** LLM provider for the `pi` child (`--provider`), when the deployment pins one. */
|
|
37
35
|
provider?: string;
|
|
38
|
-
/**
|
|
36
|
+
/** Fallback model for the `pi` child (`--model`), used when the session selects none; Pi native settings own the model when omitted. */
|
|
39
37
|
model?: string;
|
|
40
38
|
/** Thinking/reasoning level, appended to the `--model` pattern when pinned. */
|
|
41
39
|
thinkingLevel?: string;
|
|
42
40
|
/** Explicit environment entries passed to the `pi` child. */
|
|
43
41
|
env?: Record<string, string>;
|
|
44
|
-
/** Shared Pi model catalog holder; the loop writes its `pi --list-models` probe result here. */
|
|
45
|
-
piCatalogHolder?: {
|
|
46
|
-
entries: readonly PiModelEntry[];
|
|
47
|
-
};
|
|
48
42
|
}
|
|
49
43
|
/** Schema of the Pi loop plugin configuration. */
|
|
50
44
|
export declare const Config: z<Config>;
|
|
51
|
-
/** Host-face ctx key
|
|
45
|
+
/** Host-face ctx key this engine's runtime is registered under. */
|
|
46
|
+
export declare const PI_ENGINE_LABEL = "agentLoopPi";
|
|
52
47
|
declare module '@deepseek-ai/cordis' {
|
|
53
48
|
interface Context {
|
|
54
49
|
agentLoopPi: PiLoop;
|
|
55
50
|
}
|
|
56
51
|
}
|
|
57
52
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `agent/session-start`.
|
|
53
|
+
* Creation/resume machinery for the Pi engine. The process-wide AgentFactory
|
|
54
|
+
* slot belongs to the router, which delegates each session to the engine its
|
|
55
|
+
* preset names; this class is that engine's driver, not a plugin.
|
|
62
56
|
*/
|
|
63
|
-
export declare class PiLoop extends
|
|
64
|
-
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
65
|
-
static inject: string[];
|
|
57
|
+
export declare class PiLoop extends HostedEngineRuntime<ResolvedConfig, PiAgent> {
|
|
66
58
|
/** Process-tree spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
67
59
|
readonly spawn: (spec: PiSpawnSpec) => PiProcess;
|
|
68
60
|
/** Resolved Pi CLI entrypoint; `argv[0]` of every Pi RPC child. */
|
|
69
61
|
readonly bin: string;
|
|
70
|
-
/** Discovered Pi model catalog, forwarded to each agent so it can validate the session-selected model against what pi can actually serve. */
|
|
71
|
-
private readonly catalog;
|
|
72
62
|
constructor(ctx: Context, config: Config);
|
|
73
63
|
/** Construct the Pi RPC driver for one prepared session. */
|
|
74
64
|
protected buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): PiAgent;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi's dialect for a dsh endpoint: a self-built agent directory plus the argv
|
|
3
|
+
* flags that point the `pi --mode rpc` child at dsh's model.
|
|
4
|
+
*
|
|
5
|
+
* Pi resolves a model through its own agent directory — `models.json` under
|
|
6
|
+
* `PI_CODING_AGENT_DIR` (or `~/.pi` when unset) — and its custom-provider schema
|
|
7
|
+
* has no `apiKeyEnv`: a provider's credential is either a literal `apiKey` in
|
|
8
|
+
* that file or the CLI's own `--api-key`. There is no `PI_*` variable for a base
|
|
9
|
+
* URL either. So the only automatic way to hand pi a dsh endpoint is to give it
|
|
10
|
+
* an agent directory THIS PLUGIN owns, containing a `models.json` that declares
|
|
11
|
+
* the dsh provider, and to point `PI_CODING_AGENT_DIR` at it.
|
|
12
|
+
*
|
|
13
|
+
* The user's `~/.pi` is deliberately never written or read: the plugin owns a
|
|
14
|
+
* separate directory under the OS temp dir, one per distinct endpoint, created
|
|
15
|
+
* 0700 with a 0600 file because it carries the credential. The cost is real and
|
|
16
|
+
* documented: redirecting the agent directory means the child no longer sees the
|
|
17
|
+
* user's own `~/.pi` skills/auth/theme. That is the trade the deployment makes
|
|
18
|
+
* by selecting a real dsh model on pi.
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-loop-engine/engine-pi/model-handover
|
|
21
|
+
*/
|
|
22
|
+
import type { DshModelHandover } from '../driver-core/model-handover.ts';
|
|
23
|
+
/**
|
|
24
|
+
* The agent directory for one handover, materialized on first use.
|
|
25
|
+
*
|
|
26
|
+
* The directory name is a hash of the endpoint facts, so two sessions on the
|
|
27
|
+
* same provider/model share one directory and a changed endpoint gets its own;
|
|
28
|
+
* `models.json` declares exactly the provider pi is told to use (`--provider`)
|
|
29
|
+
* and the one model it is told to run (`--model`), keyed the way pi's own
|
|
30
|
+
* schema reads it (`baseUrl`/`api`/`apiKey`/`models`).
|
|
31
|
+
* @param handover - the resolved dsh endpoint for the session's selection.
|
|
32
|
+
* @returns the absolute path to hand the child as `PI_CODING_AGENT_DIR`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function piAgentDir(handover: DshModelHandover): string;
|
|
35
|
+
//# sourceMappingURL=model-handover.d.ts.map
|
|
@@ -15,9 +15,9 @@ export type PiSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-acces
|
|
|
15
15
|
export interface ResolvedConfig {
|
|
16
16
|
/** Pinned sandbox mode; `undefined` follows the session's dsh permission knobs per query. */
|
|
17
17
|
readonly sandboxMode: PiSandboxMode | undefined;
|
|
18
|
-
/** LLM provider the `pi` RPC process is launched with (`--provider`). */
|
|
18
|
+
/** LLM provider the `pi` RPC process is launched with (`--provider`), when the session selects no model. */
|
|
19
19
|
readonly provider: string | undefined;
|
|
20
|
-
/**
|
|
20
|
+
/** Fallback model pattern for the `pi` RPC process (`--model`), used when the session selects none. */
|
|
21
21
|
readonly model: string | undefined;
|
|
22
22
|
/** Thinking/reasoning level for the model (`--model <id>:<level>` or set at runtime). */
|
|
23
23
|
readonly thinkingLevel: string | undefined;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin's own Remote: "which engine does this session run?" and "move this
|
|
3
|
+
* session to another engine", both answered from the plugin's own per-session
|
|
4
|
+
* record, the session's live agent, and the durable log through one shared read.
|
|
5
|
+
*
|
|
6
|
+
* The browser half cannot answer the first question honestly from what the
|
|
7
|
+
* harness hands a page. The client Session list carries `projectionValues`
|
|
8
|
+
* computed by `@deepseek-ai/dsh-api-session-controller`'s `projectionsFor()`,
|
|
9
|
+
* which returns a `SessionProjectionHints` snapshot — "every currently cached
|
|
10
|
+
* wire value", and partial by its own documentation: a cell the page's cache has
|
|
11
|
+
* not folded yet is simply absent. The engine a session ACTUALLY runs is not a
|
|
12
|
+
* cache fact at all, so the plugin becomes its own authority:
|
|
13
|
+
*
|
|
14
|
+
* - node half (here): a Typert Remote service, discovered by the host Gateway
|
|
15
|
+
* through its visible `typertRemote` binding and its `@Remote` method marks
|
|
16
|
+
* (`bindTypertRemote` / `@Remote` in `@deepseek-ai/dsh-typert-protocol`, the
|
|
17
|
+
* same mechanism `@deepseek-ai/dsh-agent-presets` uses for its own roster).
|
|
18
|
+
* Both endpoints go through {@link engineReportOfSession} /
|
|
19
|
+
* {@link RouterLoop.reportEngine} / {@link RouterLoop.selectEngine} — the very
|
|
20
|
+
* reads the router routes on — so what a session is driven by and what a
|
|
21
|
+
* session is shown as running cannot disagree, because there is one answer.
|
|
22
|
+
* The report carries the ONE case in which a session has two facts (a live
|
|
23
|
+
* agent and a record that did not displace it) as two fields rather than
|
|
24
|
+
* blending them; see {@link SessionEngineReport}.
|
|
25
|
+
* - browser half (`src/client/session-engine.ts`): mounts these endpoints as
|
|
26
|
+
* `remote.loopEngine.engine({ sessionId })` and
|
|
27
|
+
* `remote.loopEngine.select({ sessionId, engine })`, and renders only what
|
|
28
|
+
* they answer.
|
|
29
|
+
*
|
|
30
|
+
* Both methods take a PLAIN object parameter rather than an identity the Gateway
|
|
31
|
+
* resolves: typert resolves a parameter named `agent`/`session` into a LIVE
|
|
32
|
+
* object through its lookup providers, and these endpoints must answer for any
|
|
33
|
+
* PERSISTED session — including one no process has loaded.
|
|
34
|
+
*
|
|
35
|
+
* Wire contract (no codegen is involved; the Gateway derives the signature from
|
|
36
|
+
* the live method):
|
|
37
|
+
*
|
|
38
|
+
* - endpoint `loopEngine/engine`;
|
|
39
|
+
* - endpoint `loopEngine/select`;
|
|
40
|
+
* - one `src-json` argument per endpoint whose wire name is the method's own
|
|
41
|
+
* parameter name (`request`) — REQUIRED for the browser half to be
|
|
42
|
+
* understood, so the parameter name is part of the contract and must not be
|
|
43
|
+
* renamed or destructured (see the Gateway's `methodParameterNames`);
|
|
44
|
+
* - `engine` results in the plain-JSON {@link SessionEngineReport} — the engine
|
|
45
|
+
* the session ACTUALLY runs, plus (only while a live agent the record did not
|
|
46
|
+
* displace keeps running) the one the record names; `select` results in the
|
|
47
|
+
* plain-JSON {@link LoopEngineSelectResult} — a refusal is a VALUE, so a
|
|
48
|
+
* predictable "no" travels as data the surface can render (a
|
|
49
|
+
* {@link LoopEngineRefusalCode} plus the host's own sentence as detail, which
|
|
50
|
+
* is what lets the browser half show localized copy instead of a raw English
|
|
51
|
+
* one-liner), while only a malformed request is a `RemoteError`, and an
|
|
52
|
+
* outcome carrying `reload` tells the browser half to reload the page, because
|
|
53
|
+
* the switch was made to land by releasing the session's agent.
|
|
54
|
+
*
|
|
55
|
+
* @module dsh-loop-engine/engine-remote
|
|
56
|
+
*/
|
|
57
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
58
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
59
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
60
|
+
import { type LoopEngineId, type SessionEngine, type SessionEngineReport, type LoopEngineSelectResult } from './agent-preset-ids.ts';
|
|
61
|
+
/** Cordis service key AND wire namespace of the plugin's own Remote. */
|
|
62
|
+
export declare const LOOP_ENGINE_REMOTE_KEY = "loopEngine";
|
|
63
|
+
/** Endpoint method reporting one session's engine. */
|
|
64
|
+
export declare const LOOP_ENGINE_REMOTE_METHOD = "engine";
|
|
65
|
+
/** Endpoint method moving one session to another engine. */
|
|
66
|
+
export declare const LOOP_ENGINE_REMOTE_SELECT_METHOD = "select";
|
|
67
|
+
/** The wire request the reporting endpoint accepts. */
|
|
68
|
+
export interface LoopEngineRequest {
|
|
69
|
+
/** The session to report on. A plain string, never a resolved live object. */
|
|
70
|
+
readonly sessionId: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The wire request the switching endpoint accepts. The engine is typed as a
|
|
74
|
+
* string because that is what the wire carries; the endpoint validates it
|
|
75
|
+
* against {@link LOOP_ENGINE_IDS} before anything acts on it.
|
|
76
|
+
*/
|
|
77
|
+
export interface LoopEngineSelectRequest {
|
|
78
|
+
/** The session to move. A plain string, never a resolved live object. */
|
|
79
|
+
readonly sessionId: string;
|
|
80
|
+
/** The engine that session should run. */
|
|
81
|
+
readonly engine: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The engine one session runs, read from the plugin's own record and the durable
|
|
85
|
+
* log (`engineOfSession`).
|
|
86
|
+
*
|
|
87
|
+
* The FALLBACK answer, not the whole one: it knows nothing about a live agent, so
|
|
88
|
+
* the endpoint uses it only while no router is mounted
|
|
89
|
+
* ({@link RouterSurfaceHolder}) — the mount window, in which no session is driven
|
|
90
|
+
* by this plugin anyway.
|
|
91
|
+
*/
|
|
92
|
+
export type SessionEngineResolver = (sessionId: SessionId) => Promise<SessionEngine>;
|
|
93
|
+
/** Move one session to another engine, refusing with a reason rather than throwing. */
|
|
94
|
+
export type SessionEngineSelector = (sessionId: SessionId, engine: LoopEngineId) => Promise<LoopEngineSelectResult>;
|
|
95
|
+
/**
|
|
96
|
+
* The mounted loop router, as these two endpoints need it: the reporting read
|
|
97
|
+
* and the move, both of which only a live router can make.
|
|
98
|
+
*
|
|
99
|
+
* Structural rather than the class itself, so this module takes no build-time
|
|
100
|
+
* dependency on `./router-loop.ts` (which drags in the harness loop and every
|
|
101
|
+
* engine driver) and a test can stand in for it.
|
|
102
|
+
*/
|
|
103
|
+
export interface RouterSurface {
|
|
104
|
+
/** Report what one session actually runs, plus the recorded engine when the two differ. */
|
|
105
|
+
reportEngine(sessionId: SessionId): Promise<SessionEngineReport>;
|
|
106
|
+
/** Move one session to another engine, or answer why it was not moved. */
|
|
107
|
+
selectEngine(sessionId: SessionId, engine: LoopEngineId): Promise<LoopEngineSelectResult>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Where the two endpoints find the mounted router.
|
|
111
|
+
*
|
|
112
|
+
* A holder rather than a fixed reference because the router is mounted
|
|
113
|
+
* asynchronously — and not at all until the harness's live patch reload drops the
|
|
114
|
+
* base bundle's `agent-loop` row (`src/index.ts` `mountRouter`). Until then both
|
|
115
|
+
* endpoints answer from what a router-less process still knows: the record and
|
|
116
|
+
* the log. A session that is not driven by this plugin has no live agent to
|
|
117
|
+
* report anyway, so the answers agree.
|
|
118
|
+
*/
|
|
119
|
+
export interface RouterSurfaceHolder {
|
|
120
|
+
/** The mounted router, or undefined while there is no router. */
|
|
121
|
+
current: RouterSurface | undefined;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `remote.loopEngine`: report one session's engine, and move it to another.
|
|
125
|
+
*
|
|
126
|
+
* Registered as a Cordis service under {@link LOOP_ENGINE_REMOTE_KEY} on the
|
|
127
|
+
* plugin's own fiber, so the binding and the method marks disappear with the
|
|
128
|
+
* plugin: a profile that mounts no Gateway never discovers it, and unmounting
|
|
129
|
+
* the plugin withdraws both.
|
|
130
|
+
*/
|
|
131
|
+
export declare class LoopEngineRemote extends TypertRemoteService {
|
|
132
|
+
private readonly resolve;
|
|
133
|
+
private readonly router;
|
|
134
|
+
private readonly warn;
|
|
135
|
+
/**
|
|
136
|
+
* @param ctx - the context the service (and its lifetime) belongs to.
|
|
137
|
+
* @param resolve - the router-less read; normally
|
|
138
|
+
* `engineOfSession(ctx, sessionId, records)`.
|
|
139
|
+
* @param router - where both endpoints find the mounted loop router.
|
|
140
|
+
* @param warn - diagnostic sink for a session that could not be read.
|
|
141
|
+
*/
|
|
142
|
+
constructor(ctx: Context, resolve: SessionEngineResolver, router: RouterSurfaceHolder, warn: (message: string) => void);
|
|
143
|
+
/**
|
|
144
|
+
* Report what one session runs — and, when its own record names a different
|
|
145
|
+
* engine than the live agent, that fact separately.
|
|
146
|
+
*
|
|
147
|
+
* The router answers when it is mounted, because the live agent outranks every
|
|
148
|
+
* record: only the router knows which engine built the agent driving this
|
|
149
|
+
* session now, and only the router can tell a record whose release did not take
|
|
150
|
+
* from one that already landed. Without a router (the mount-retry window) no
|
|
151
|
+
* session is driven by this plugin, so the record — i.e.
|
|
152
|
+
* {@link SessionEngineResolver} — is the whole answer.
|
|
153
|
+
*
|
|
154
|
+
* A session that cannot be read answers `unset` rather than throwing: a
|
|
155
|
+
* surface that asked about a session the durable log does not know must render
|
|
156
|
+
* "engine not recorded", not break the page it is rendered in. Failures are
|
|
157
|
+
* reported once on the host's log instead.
|
|
158
|
+
*
|
|
159
|
+
* A malformed REQUEST is a caller fault, not an unreadable session, and is
|
|
160
|
+
* refused the way every typert endpoint refuses one.
|
|
161
|
+
* @param request - the wire request carrying the session id.
|
|
162
|
+
* @returns the engine driving that session, plus the recorded engine it is not
|
|
163
|
+
* running when the two differ.
|
|
164
|
+
* @throws {RemoteError} with `gateway/bad-request` when no session id was sent.
|
|
165
|
+
*/
|
|
166
|
+
engine(request: LoopEngineRequest): Promise<SessionEngineReport>;
|
|
167
|
+
/**
|
|
168
|
+
* Move one session to another engine.
|
|
169
|
+
*
|
|
170
|
+
* The move owns no policy of its own: the router decides whether this session
|
|
171
|
+
* can be moved right now and answers with a reason when it cannot, because
|
|
172
|
+
* only it knows the session's live agent, its turn state, and the teardown it
|
|
173
|
+
* would have to perform. This endpoint's job is the boundary — reject a
|
|
174
|
+
* malformed request the way every typert endpoint does, and hand a well-formed
|
|
175
|
+
* one to the selector.
|
|
176
|
+
* @param request - the wire request carrying the session id and target engine.
|
|
177
|
+
* @returns the engine now recorded for the session — with `reload: true` when
|
|
178
|
+
* its agent had to be released for the change to land, which the browser half
|
|
179
|
+
* acts on by reloading the page — or, as `ok: false`, the
|
|
180
|
+
* {@link LoopEngineRefusalCode} of the branch that refused it together with the
|
|
181
|
+
* host's own sentence about this session (`router-unmounted` is this
|
|
182
|
+
* endpoint's own, `session-closed` / `turn-running` / `subagent-session` /
|
|
183
|
+
* `not-driven` / `record-failed` / `rebuild-failed` come from the router's
|
|
184
|
+
* checks). The browser half localizes from the code and keeps the sentence as
|
|
185
|
+
* detail, so the code is part of this endpoint's contract even though the
|
|
186
|
+
* shape allows it to be absent.
|
|
187
|
+
* @throws {RemoteError} with `gateway/bad-request` when the session id is not
|
|
188
|
+
* a non-empty string or the engine is not an installed engine id.
|
|
189
|
+
*/
|
|
190
|
+
select(request: LoopEngineSelectRequest): Promise<LoopEngineSelectResult>;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=engine-remote.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session command and skill surface of the hosted engines.
|
|
3
|
+
*
|
|
4
|
+
* A hosted engine owns its session's slash-command menu and skill catalog: the
|
|
5
|
+
* engine expands `/name` itself and reads its own instruction files, so the
|
|
6
|
+
* plugin bridges both into the session the engine serves and nothing more.
|
|
7
|
+
*
|
|
8
|
+
* The registrations are made through the AGENT's own context, which is what
|
|
9
|
+
* makes them session-scoped: `commands.register` and
|
|
10
|
+
* `skills.registerProvider` file a definition into the layer of the calling
|
|
11
|
+
* context's scope (`packages/core/scope/src/store.ts` `effect()` keys the layer
|
|
12
|
+
* by `scopeOf(ctx)`), and an agent's scope key is the agent itself
|
|
13
|
+
* (`createScope(loopCtx, this)` in the default loop; the hosted drivers do the
|
|
14
|
+
* same). Two sessions running different engines therefore never see each
|
|
15
|
+
* other's menus, and the whole surface disappears with the agent's scope — no
|
|
16
|
+
* plugin-side bookkeeping, no leak across an engine switch.
|
|
17
|
+
*
|
|
18
|
+
* @module dsh-loop-engine/engine-surface
|
|
19
|
+
*/
|
|
20
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
21
|
+
import type { HostedEngineId } from './settings.ts';
|
|
22
|
+
/**
|
|
23
|
+
* Bridge one hosted engine's commands and skills into the session it serves.
|
|
24
|
+
*
|
|
25
|
+
* Best-effort like the rest of the plugin's host-service use: a profile without
|
|
26
|
+
* the commands or skills registry simply has no surface to extend. A name that
|
|
27
|
+
* collides with an already-registered command in the same layer is skipped with
|
|
28
|
+
* a warning rather than failing the agent: the engine expands the raw `/name`
|
|
29
|
+
* line itself, so a shyer menu beats an agent that refuses to start.
|
|
30
|
+
*
|
|
31
|
+
* @param agent - the freshly built agent whose scope owns the registrations.
|
|
32
|
+
* @param engine - the hosted engine driving that agent.
|
|
33
|
+
* @param warn - sink for the skip diagnostics.
|
|
34
|
+
*/
|
|
35
|
+
export declare function registerEngineSurface(agent: Agent, engine: HostedEngineId, warn: (message: string) => void): void;
|
|
36
|
+
//# sourceMappingURL=engine-surface.d.ts.map
|