dsh-loop-engine 0.1.5-rc3 → 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 -187
- package/README.zh.md +45 -103
- 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Loop engine settings page copy (Chinese product copy; comments in English).
|
|
3
3
|
* @module dsh-loop-engine/client/locales
|
|
4
4
|
*/
|
|
5
|
+
import { type LoopEngineId, type LoopEngineRefusalCode, type SessionEngine } from '../agent-preset-ids.ts';
|
|
5
6
|
/** Copy keys of the loop engine settings page. */
|
|
6
7
|
export interface LoopEngineKey {
|
|
7
8
|
/** Settings navigation label. */
|
|
@@ -18,6 +19,12 @@ export interface LoopEngineKey {
|
|
|
18
19
|
enginePi: string;
|
|
19
20
|
/** Option label: the Kimi Code CLI driver. */
|
|
20
21
|
engineKimi: string;
|
|
22
|
+
/** Option label: a session on the pre-routing single preset id. */
|
|
23
|
+
engineLegacy: string;
|
|
24
|
+
/** Option label: a session this plugin holds no engine record for. */
|
|
25
|
+
engineUnrecorded: string;
|
|
26
|
+
/** Composer picker label while the session's own engine has no answer yet. */
|
|
27
|
+
engineLoading: string;
|
|
21
28
|
/** Settings toggle: show the engine picker in the chat page composer. */
|
|
22
29
|
showInComposerLabel: string;
|
|
23
30
|
/** Unavailable-state message. */
|
|
@@ -26,21 +33,140 @@ export interface LoopEngineKey {
|
|
|
26
33
|
switchNotice: string;
|
|
27
34
|
/** Saving state label. */
|
|
28
35
|
saving: string;
|
|
29
|
-
/**
|
|
36
|
+
/** Settings-section confirmation title: the picker that changes the new-session default. */
|
|
30
37
|
confirmTitle: string;
|
|
31
|
-
/**
|
|
38
|
+
/** Settings-section confirmation body: the picker that changes the new-session default. */
|
|
32
39
|
confirmBody: string;
|
|
33
|
-
/**
|
|
40
|
+
/** Session-header chip tooltip: the engine this session runs, and what decides it. */
|
|
41
|
+
sessionNotice: string;
|
|
42
|
+
/** Session-header chip tooltip when the session's engine was never recorded. */
|
|
43
|
+
legacySessionNotice: string;
|
|
44
|
+
/** Marker lead-in naming the engine a session's record holds while another one drives it. */
|
|
45
|
+
enginePendingPrefix: string;
|
|
46
|
+
/** Marker trailer saying that recorded engine is not what the session runs. */
|
|
47
|
+
enginePendingSuffix: string;
|
|
48
|
+
/** Composer menu-row suffix marking the engine a session's record holds but is not running. */
|
|
49
|
+
engineMenuPendingSuffix: string;
|
|
50
|
+
/** Session-header chip tooltip while the record names an engine the session is not running. */
|
|
51
|
+
pendingSessionNotice: string;
|
|
52
|
+
/** Composer picker tooltip while the record names an engine the session is not running. */
|
|
53
|
+
pendingComposerHint: string;
|
|
54
|
+
/** Composer picker tooltip: it chooses this session's engine, and which picks apply immediately. */
|
|
55
|
+
composerHint: string;
|
|
56
|
+
/** Title of the notice reporting a refused per-session switch. */
|
|
57
|
+
switchFailedTitle: string;
|
|
58
|
+
/** Body of a refused per-session switch: the session is not open yet. */
|
|
59
|
+
refusedSessionClosed: string;
|
|
60
|
+
/** Body of a refused per-session switch: a turn is in flight and is not interrupted. */
|
|
61
|
+
refusedTurnRunning: string;
|
|
62
|
+
/** Body of a refused per-session switch: a delegated child's engine follows its parent. */
|
|
63
|
+
refusedSubagentSession: string;
|
|
64
|
+
/** Body of a refused per-session switch while this process has no loop engine router. */
|
|
65
|
+
refusedRouterNotReady: string;
|
|
66
|
+
/** Body of a refused per-session switch whose engine record could not be written. */
|
|
67
|
+
refusedRecordFailed: string;
|
|
68
|
+
/** Body of a refused per-session switch whose successor engine could not be built. */
|
|
69
|
+
refusedRebuildFailed: string;
|
|
70
|
+
/** Title of the confirmation shown before a switch that reloads the page. */
|
|
71
|
+
switchReloadConfirmTitle: string;
|
|
72
|
+
/** Body of that confirmation: what the reload is for, and what this page loses with it. */
|
|
73
|
+
switchReloadConfirmBody: string;
|
|
74
|
+
/** Confirm action label of that confirmation. */
|
|
75
|
+
switchReloadConfirmAction: string;
|
|
76
|
+
/** Title of the notice shown while a per-session switch reloads the page. */
|
|
77
|
+
switchReloadTitle: string;
|
|
78
|
+
/** Body of that notice: what the reload is doing, and that it returns to this session. */
|
|
79
|
+
switchReloadBody: string;
|
|
80
|
+
/** Message when this page cannot reach the engine switch endpoint at all. */
|
|
81
|
+
switchUnavailable: string;
|
|
82
|
+
/** Confirmation action label of the settings section's own dialog. */
|
|
34
83
|
confirmAction: string;
|
|
35
|
-
/** Cancel action label. */
|
|
84
|
+
/** Cancel action label of the settings section's own dialog. */
|
|
36
85
|
cancelAction: string;
|
|
37
|
-
/** Accessible close-button label of the confirmation
|
|
86
|
+
/** Accessible close-button label of the settings confirmation and the switch notices. */
|
|
38
87
|
closeLabel: string;
|
|
39
|
-
/** Notice shown while
|
|
40
|
-
|
|
88
|
+
/** Notice shown while a hosted engine drives the session: the model seat is the engine's own business. */
|
|
89
|
+
hostedEngineModelNotice: string;
|
|
41
90
|
}
|
|
42
91
|
/** Simplified Chinese copy. */
|
|
43
92
|
export declare const zh: Record<keyof LoopEngineKey, string>;
|
|
44
93
|
/** English copy. */
|
|
45
94
|
export declare const en: Record<keyof LoopEngineKey, string>;
|
|
95
|
+
/**
|
|
96
|
+
* The copy key naming one engine, shared by the session header chip and the
|
|
97
|
+
* composer picker (both name a resolved engine, and neither may invent a name
|
|
98
|
+
* for one of the two non-engine session states).
|
|
99
|
+
* @param engine - a resolved engine id.
|
|
100
|
+
* @returns the key of that engine's own label.
|
|
101
|
+
*/
|
|
102
|
+
export declare function engineLabelKey(engine: LoopEngineId): keyof LoopEngineKey;
|
|
103
|
+
/**
|
|
104
|
+
* The copy key naming the engine a session RUNS, for the two surfaces that name
|
|
105
|
+
* one (the header chip and the composer picker's trigger).
|
|
106
|
+
*
|
|
107
|
+
* The three-state is folded in one place so the two surfaces cannot answer the
|
|
108
|
+
* "which engine is this?" question differently — and so neither can hand it the
|
|
109
|
+
* pending engine: {@link SessionEngine} is the ACTUAL answer by construction, and
|
|
110
|
+
* the recorded-but-not-adopted engine travels beside it
|
|
111
|
+
* (`SessionEngineReport.pending`), where {@link pendingEngineText} turns it into
|
|
112
|
+
* a marker rather than a name.
|
|
113
|
+
* @param state - what the session's engine read answered for its live engine.
|
|
114
|
+
* @returns the key of that state's label.
|
|
115
|
+
*/
|
|
116
|
+
export declare function engineStateLabelKey(state: SessionEngine): keyof LoopEngineKey;
|
|
117
|
+
/**
|
|
118
|
+
* The marker naming the engine a session's record holds while something else is
|
|
119
|
+
* driving it.
|
|
120
|
+
*
|
|
121
|
+
* It is an aside, never a name: a surface appends it to the engine the session is
|
|
122
|
+
* actually running, so the user reads "Pi CLI, and the record says X" — not "X".
|
|
123
|
+
* Both surfaces that show a session's engine render it, which is why it is
|
|
124
|
+
* composed here rather than in either of them.
|
|
125
|
+
*
|
|
126
|
+
* Its trailer deliberately makes no promise about WHEN the recorded engine takes
|
|
127
|
+
* over (`enginePendingSuffix`): a switch that has to release the session's agent
|
|
128
|
+
* lands through a page reload, and this marker never survives one — it is what
|
|
129
|
+
* the user sees when that release did not take, and picking the engine again is
|
|
130
|
+
* the action that retries it.
|
|
131
|
+
* @param t - the surface's copy function.
|
|
132
|
+
* @param engine - the engine the session's own record names.
|
|
133
|
+
* @returns the complete marker, e.g. `切到 Pi CLI · 尚未接管`.
|
|
134
|
+
*/
|
|
135
|
+
export declare function pendingEngineText(t: (key: keyof LoopEngineKey) => string, engine: LoopEngineId): string;
|
|
136
|
+
/**
|
|
137
|
+
* How one refused switch reads: which local copy says it, and whether the host's
|
|
138
|
+
* own sentence belongs under that copy as detail.
|
|
139
|
+
*
|
|
140
|
+
* The refusal arrives from the host as a code plus the host's English sentence
|
|
141
|
+
* about one session (`LoopEngineRefusal.reason`), and the sentence is what a
|
|
142
|
+
* surface used to show verbatim — which is how a user got a raw
|
|
143
|
+
* `session "…" is running; switch its engine after this turn ends` for a state
|
|
144
|
+
* this plugin can say perfectly well in the user's own language. So the code
|
|
145
|
+
* decides the message here, and the sentence is kept where it is genuinely
|
|
146
|
+
* additional information: the two failures whose copy is generic and whose text
|
|
147
|
+
* carries the underlying cause (a write error, a driver that would not start).
|
|
148
|
+
*/
|
|
149
|
+
export interface RefusalFace {
|
|
150
|
+
/**
|
|
151
|
+
* Body copy key for this refusal. Absent when no local copy can say it — an
|
|
152
|
+
* absent or unknown code, where the host's own sentence IS the message
|
|
153
|
+
* (`undefined` is also what a client older than the host normalizes an
|
|
154
|
+
* unrecognized code to, `src/client/session-engine.ts` `parseSelectResult`).
|
|
155
|
+
*/
|
|
156
|
+
readonly body?: keyof LoopEngineKey;
|
|
157
|
+
/** Whether the host's `reason` is rendered under the body as detail. */
|
|
158
|
+
readonly detail: boolean;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve what one refused switch shows.
|
|
162
|
+
*
|
|
163
|
+
* A refusal ALWAYS leaves something readable: a known code gets this plugin's own
|
|
164
|
+
* copy (and, for the two technical failures, the host's sentence under it), while
|
|
165
|
+
* an absent or unknown code falls back to that sentence as the message itself.
|
|
166
|
+
* @param code - the code the host's refusal carried, or undefined when it carried
|
|
167
|
+
* none this build knows.
|
|
168
|
+
* @returns the copy key to render, plus whether the host's own text is shown as
|
|
169
|
+
* detail.
|
|
170
|
+
*/
|
|
171
|
+
export declare function refusalFace(code: LoopEngineRefusalCode | undefined): RefusalFace;
|
|
46
172
|
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reloading the page, and coming back to the session the switch rebuilt.
|
|
3
|
+
*
|
|
4
|
+
* Switching a session onto — or off — the harness loop cannot move its agent in
|
|
5
|
+
* place, so the host RELEASES that agent and answers `reload: true`
|
|
6
|
+
* (`src/router-loop.ts` `move`): the session's record already names the engine it
|
|
7
|
+
* must run, and the host builds it again on its next resolve. Two things have to
|
|
8
|
+
* happen on this side for the user to experience that as a switch rather than as
|
|
9
|
+
* a session that broke:
|
|
10
|
+
*
|
|
11
|
+
* 1. THE PAGE MUST RELOAD. Releasing the agent publishes `session/disposed`, and
|
|
12
|
+
* the session controller's client half reads it as this session being GONE:
|
|
13
|
+
* the row leaves its list, the current selection is masked away, and the
|
|
14
|
+
* session object is marked `removed` — a flag NOTHING in that page's
|
|
15
|
+
* lifetime clears (`packages/api/session-controller/src/client/sessions/session.ts`
|
|
16
|
+
* `handleRemoved`), which is what locks the composer
|
|
17
|
+
* (`packages/client/ui-conversation/src/client/skeleton/InputBar.tsx`). A
|
|
18
|
+
* page that stayed put would show the session as unusable; a reload is the
|
|
19
|
+
* smallest action that replaces that page state with a fresh list and a
|
|
20
|
+
* fresh, unmarked session object.
|
|
21
|
+
* 2. THAT SESSION HAS TO BE OPENED AGAIN, or the user lands wherever the client
|
|
22
|
+
* falls back to. Nothing does it for us: the reload clears the session
|
|
23
|
+
* controller's own persisted selection cell once the current selection was
|
|
24
|
+
* masked away (`.../client/sessions/service.ts` `projectList`), and the
|
|
25
|
+
* workspace's startup navigation only reuses a BLANK session of the most
|
|
26
|
+
* recent workspace, creating a fresh session otherwise
|
|
27
|
+
* (`.../client/ui-workspace/src/client/navigation.ts` `watchNavigation`). So
|
|
28
|
+
* the id is stashed in TAB storage — which survives a reload and dies with
|
|
29
|
+
* the tab — and {@link restoreReloadReturn} opens it as soon as the client's
|
|
30
|
+
* session list knows it.
|
|
31
|
+
*
|
|
32
|
+
* That last step is what makes the whole flow worth having: the session is still
|
|
33
|
+
* on DISK, so the list the page re-pulls from the host contains it (the host's
|
|
34
|
+
* list unions persisted headers with live sessions), and opening it is what makes
|
|
35
|
+
* the host resolve the session at all — no live agent → `ctx.agents.resume(...)`
|
|
36
|
+
* → the router reads the plugin's own record → the harness loop builds it. The
|
|
37
|
+
* process never restarts.
|
|
38
|
+
*
|
|
39
|
+
* The page is poked only through `window.location.reload()` — nothing else: a
|
|
40
|
+
* reload is the action a user would take themselves, and it is the one this
|
|
41
|
+
* plugin can justify.
|
|
42
|
+
*
|
|
43
|
+
* @module dsh-loop-engine/client/reload
|
|
44
|
+
*/
|
|
45
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
46
|
+
/** Tab-scoped web storage, as this feature reads and writes it. */
|
|
47
|
+
export interface ReloadStash {
|
|
48
|
+
getItem(key: string): string | null;
|
|
49
|
+
setItem(key: string, value: string): void;
|
|
50
|
+
removeItem(key: string): void;
|
|
51
|
+
}
|
|
52
|
+
/** The browser this plugin's client half is running in, as this feature needs it. */
|
|
53
|
+
export interface ReloadPage {
|
|
54
|
+
/** The tab's storage, or undefined off a browser (and in node, where the specs run). */
|
|
55
|
+
readonly stash: ReloadStash | undefined;
|
|
56
|
+
/** Reload the current page. */
|
|
57
|
+
reload(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The stash key holding the session a reload has to come back to.
|
|
61
|
+
*
|
|
62
|
+
* Namespaced by package: tab storage is shared with everything else the page
|
|
63
|
+
* runs, and one key per concern is all this feature needs.
|
|
64
|
+
*/
|
|
65
|
+
export declare const RELOAD_RETURN_KEY = "dsh-loop-engine:reload-session";
|
|
66
|
+
/** The live page, read off the globals. Missing ones simply mean "no page". */
|
|
67
|
+
export declare function browserPage(): ReloadPage;
|
|
68
|
+
/**
|
|
69
|
+
* Remember the session a reload must reopen.
|
|
70
|
+
*
|
|
71
|
+
* Best-effort by design: a page whose storage refuses the write (private mode,
|
|
72
|
+
* quota) still reloads, it just cannot promise the way back — the caller reports
|
|
73
|
+
* what it could do rather than refusing the switch the host already performed.
|
|
74
|
+
* @param page - the page to stash into.
|
|
75
|
+
* @param sessionId - the session to come back to.
|
|
76
|
+
* @returns whether the id was stashed.
|
|
77
|
+
*/
|
|
78
|
+
export declare function armReloadReturn(page: ReloadPage, sessionId: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Take the session this page was reloaded for, clearing it as it is read.
|
|
81
|
+
*
|
|
82
|
+
* Reading is destructive so the return happens exactly once per reload: the id
|
|
83
|
+
* must not survive a second reload the user starts for their own reasons, and a
|
|
84
|
+
* later visit to this tab has no session to come back to.
|
|
85
|
+
* @param page - the page to read.
|
|
86
|
+
* @returns the stashed session id, or undefined when this page is not one.
|
|
87
|
+
*/
|
|
88
|
+
export declare function takeReloadReturn(page: ReloadPage): string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* The slice of the session controller's client service this module drives:
|
|
91
|
+
* the list it publishes, and the verb that opens one of its sessions.
|
|
92
|
+
*
|
|
93
|
+
* Declared structurally rather than imported: this is a third-party client
|
|
94
|
+
* plugin (no build-time dependency on the harness's client packages), and the
|
|
95
|
+
* only two members it needs are these.
|
|
96
|
+
*/
|
|
97
|
+
export interface SessionListFace {
|
|
98
|
+
readonly list: {
|
|
99
|
+
getSnapshot(): {
|
|
100
|
+
readonly phase: 'pending' | 'ready';
|
|
101
|
+
readonly ids: readonly string[];
|
|
102
|
+
};
|
|
103
|
+
subscribe(listener: () => void): () => void;
|
|
104
|
+
};
|
|
105
|
+
open(id: string): void;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Open the session this page was reloaded for, once the client knows it.
|
|
109
|
+
*
|
|
110
|
+
* The wait is real and cannot be skipped: the list arrives with the host's list
|
|
111
|
+
* RPC, asynchronously, and `open` refuses an id it does not know yet
|
|
112
|
+
* (`sessions.select: unknown session ...`). One decision is made, on the FIRST
|
|
113
|
+
* settled list — the id is either in it (open it) or the session is not listed
|
|
114
|
+
* at all, in which case opening it is impossible and waiting longer would only
|
|
115
|
+
* mean hijacking the page minutes later, after the user had moved on.
|
|
116
|
+
* @param sessions - the session controller's client service.
|
|
117
|
+
* @param page - the page whose stash is read.
|
|
118
|
+
* @param warn - diagnostic sink for the two ways this can end without opening.
|
|
119
|
+
* @returns the release, in case the plugin unloads before the list settles.
|
|
120
|
+
*/
|
|
121
|
+
export declare function restoreReloadReturn(sessions: SessionListFace, page: ReloadPage, warn: (message: string) => void): () => void;
|
|
122
|
+
/**
|
|
123
|
+
* Wire the return-to-session half into the client tree.
|
|
124
|
+
*
|
|
125
|
+
* Takes the `sessions` service through a nested injection rather than declaring
|
|
126
|
+
* it in this plugin's own `inject`: a profile that composes no session
|
|
127
|
+
* controller (the settings page's own tests, a minimal client) must keep
|
|
128
|
+
* working, and this feature degrades to "reload without the way back" there —
|
|
129
|
+
* the id is simply not stashed, because nothing could open it.
|
|
130
|
+
* @param ctx - the client root context.
|
|
131
|
+
* @param page - the page to read the stash from; the real browser by default.
|
|
132
|
+
* @param warn - diagnostic sink; the client logger by default.
|
|
133
|
+
*/
|
|
134
|
+
export declare function installReloadReturn(ctx: ClientContext, page?: ReloadPage, warn?: (message: string) => void): void;
|
|
135
|
+
//# sourceMappingURL=reload.d.ts.map
|