dsh-loop-engine-allinone 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/CONTRIBUTING.md +78 -0
  3. package/LICENSE +23 -0
  4. package/NOTICE +45 -0
  5. package/README.md +221 -2
  6. package/README.zh.md +89 -0
  7. package/SECURITY.md +39 -0
  8. package/cordis.patch.yml +3 -0
  9. package/lib/client.js +2166 -0
  10. package/lib/index.js +11621 -0
  11. package/lib/invariant.js +453 -0
  12. package/lib/types/agent-preset-ids.d.ts +276 -0
  13. package/lib/types/client/LoopEngineBadge.d.ts +71 -0
  14. package/lib/types/client/LoopEngineComposerSelect.d.ts +126 -0
  15. package/lib/types/client/LoopEngineSection.d.ts +35 -0
  16. package/lib/types/client/engine-marks.d.ts +87 -0
  17. package/lib/types/client/index.d.ts +39 -0
  18. package/lib/types/client/locales.d.ts +182 -0
  19. package/lib/types/client/reload.d.ts +135 -0
  20. package/lib/types/client/session-engine.d.ts +508 -0
  21. package/lib/types/client/store.d.ts +103 -0
  22. package/lib/types/client/turn-status.d.ts +234 -0
  23. package/lib/types/client/use-session-engine.d.ts +74 -0
  24. package/lib/types/commands.d.ts +77 -0
  25. package/lib/types/compat.d.ts +25 -0
  26. package/lib/types/driver-acp/agent.d.ts +289 -0
  27. package/lib/types/driver-acp/approval.d.ts +27 -0
  28. package/lib/types/driver-acp/capabilities.d.ts +200 -0
  29. package/lib/types/driver-acp/client.d.ts +297 -0
  30. package/lib/types/driver-acp/executable.d.ts +53 -0
  31. package/lib/types/driver-acp/mapping.d.ts +76 -0
  32. package/lib/types/driver-acp/profile.d.ts +126 -0
  33. package/lib/types/driver-acp/runtime.d.ts +45 -0
  34. package/lib/types/driver-acp/subprocess.d.ts +19 -0
  35. package/lib/types/driver-acp/terminal.d.ts +98 -0
  36. package/lib/types/driver-acp/transport.d.ts +43 -0
  37. package/lib/types/driver-acp/types.d.ts +152 -0
  38. package/lib/types/driver-cli/capability.d.ts +42 -0
  39. package/lib/types/driver-cli/cli-agent.d.ts +258 -0
  40. package/lib/types/driver-cli/cli-loop.d.ts +46 -0
  41. package/lib/types/driver-cli/flatten.d.ts +35 -0
  42. package/lib/types/driver-cli/frames.d.ts +128 -0
  43. package/lib/types/driver-cli/ndjson.d.ts +106 -0
  44. package/lib/types/driver-cli/session-sink.d.ts +333 -0
  45. package/lib/types/driver-cli/stream-json.d.ts +61 -0
  46. package/lib/types/driver-cli/text.d.ts +66 -0
  47. package/lib/types/driver-cli/transport.d.ts +84 -0
  48. package/lib/types/driver-core/agents-md-skill-provider.d.ts +72 -0
  49. package/lib/types/driver-core/assistant-stream.d.ts +68 -0
  50. package/lib/types/driver-core/context-files.d.ts +62 -0
  51. package/lib/types/driver-core/host-servers.d.ts +119 -0
  52. package/lib/types/driver-core/hosted-engine-runtime.d.ts +190 -0
  53. package/lib/types/driver-core/hosted-tool-vocabulary.d.ts +78 -0
  54. package/lib/types/driver-core/idle-child.d.ts +57 -0
  55. package/lib/types/driver-core/image-access.d.ts +25 -0
  56. package/lib/types/driver-core/inbox.d.ts +104 -0
  57. package/lib/types/driver-core/model-handover.d.ts +134 -0
  58. package/lib/types/driver-core/ownership.d.ts +41 -0
  59. package/lib/types/driver-core/permission-knobs.d.ts +26 -0
  60. package/lib/types/driver-core/prompt.d.ts +64 -0
  61. package/lib/types/driver-core/session-lifetime.d.ts +62 -0
  62. package/lib/types/driver-core/session-model.d.ts +128 -0
  63. package/lib/types/driver-core/skill-inject.d.ts +59 -0
  64. package/lib/types/driver-core/step-close.d.ts +49 -0
  65. package/lib/types/driver-core/step-images.d.ts +62 -0
  66. package/lib/types/driver-core/step-tools.d.ts +43 -0
  67. package/lib/types/driver-core/system-head.d.ts +43 -0
  68. package/lib/types/engine-agy/agent.d.ts +67 -0
  69. package/lib/types/engine-agy/frames.d.ts +90 -0
  70. package/lib/types/engine-agy/loop.d.ts +64 -0
  71. package/lib/types/engine-agy/process.d.ts +173 -0
  72. package/lib/types/engine-claude/agent.d.ts +186 -0
  73. package/lib/types/engine-claude/loop.d.ts +73 -0
  74. package/lib/types/engine-claude/mapping.d.ts +108 -0
  75. package/lib/types/engine-claude/permission.d.ts +41 -0
  76. package/lib/types/engine-claude/process.d.ts +59 -0
  77. package/lib/types/engine-claude/sdk.d.ts +67 -0
  78. package/lib/types/engine-claude/types.d.ts +28 -0
  79. package/lib/types/engine-codex/agent.d.ts +216 -0
  80. package/lib/types/engine-codex/appserver/client.d.ts +80 -0
  81. package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
  82. package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
  83. package/lib/types/engine-codex/appserver/types.d.ts +225 -0
  84. package/lib/types/engine-codex/loop.d.ts +75 -0
  85. package/lib/types/engine-codex/model-handover.d.ts +56 -0
  86. package/lib/types/engine-codex/permission.d.ts +126 -0
  87. package/lib/types/engine-codex/skills.d.ts +28 -0
  88. package/lib/types/engine-codex/types.d.ts +36 -0
  89. package/lib/types/engine-drivers.d.ts +132 -0
  90. package/lib/types/engine-grok/agent.d.ts +24 -0
  91. package/lib/types/engine-grok/loop.d.ts +61 -0
  92. package/lib/types/engine-grok/process.d.ts +46 -0
  93. package/lib/types/engine-grok/profile.d.ts +82 -0
  94. package/lib/types/engine-hermes/agent.d.ts +47 -0
  95. package/lib/types/engine-hermes/loop.d.ts +56 -0
  96. package/lib/types/engine-hermes/process.d.ts +92 -0
  97. package/lib/types/engine-kimi/acp/client.d.ts +123 -0
  98. package/lib/types/engine-kimi/acp/mapping.d.ts +73 -0
  99. package/lib/types/engine-kimi/acp/types.d.ts +143 -0
  100. package/lib/types/engine-kimi/agent.d.ts +24 -0
  101. package/lib/types/engine-kimi/commands.d.ts +44 -0
  102. package/lib/types/engine-kimi/loop.d.ts +62 -0
  103. package/lib/types/engine-kimi/model-handover.d.ts +32 -0
  104. package/lib/types/engine-kimi/permission.d.ts +28 -0
  105. package/lib/types/engine-kimi/process.d.ts +47 -0
  106. package/lib/types/engine-kimi/profile.d.ts +19 -0
  107. package/lib/types/engine-kimi/skills.d.ts +52 -0
  108. package/lib/types/engine-kimi/types.d.ts +25 -0
  109. package/lib/types/engine-minimaxcode/agent.d.ts +24 -0
  110. package/lib/types/engine-minimaxcode/loop.d.ts +83 -0
  111. package/lib/types/engine-minimaxcode/process.d.ts +53 -0
  112. package/lib/types/engine-minimaxcode/profile.d.ts +72 -0
  113. package/lib/types/engine-of-session.d.ts +97 -0
  114. package/lib/types/engine-pi/agent.d.ts +230 -0
  115. package/lib/types/engine-pi/loop.d.ts +72 -0
  116. package/lib/types/engine-pi/model-handover.d.ts +84 -0
  117. package/lib/types/engine-pi/permission.d.ts +47 -0
  118. package/lib/types/engine-pi/rpc/client.d.ts +107 -0
  119. package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
  120. package/lib/types/engine-pi/rpc/types.d.ts +249 -0
  121. package/lib/types/engine-pi/skills.d.ts +47 -0
  122. package/lib/types/engine-pi/types.d.ts +36 -0
  123. package/lib/types/engine-qoderclicn/agent.d.ts +59 -0
  124. package/lib/types/engine-qoderclicn/loop.d.ts +51 -0
  125. package/lib/types/engine-qoderclicn/process.d.ts +113 -0
  126. package/lib/types/engine-registry.d.ts +584 -0
  127. package/lib/types/engine-remote.d.ts +192 -0
  128. package/lib/types/engine-surface.d.ts +36 -0
  129. package/lib/types/engine-zcode/agent.d.ts +50 -0
  130. package/lib/types/engine-zcode/loop.d.ts +47 -0
  131. package/lib/types/engine-zcode/process.d.ts +111 -0
  132. package/lib/types/index.d.ts +205 -0
  133. package/lib/types/invariant.d.ts +26 -0
  134. package/lib/types/model-selection-reset.d.ts +271 -0
  135. package/lib/types/namespace.d.ts +25 -0
  136. package/lib/types/patch-manager.d.ts +104 -0
  137. package/lib/types/preset.d.ts +254 -0
  138. package/lib/types/provider-route.d.ts +245 -0
  139. package/lib/types/router-loop.d.ts +458 -0
  140. package/lib/types/session-engine-store.d.ts +138 -0
  141. package/lib/types/settings.d.ts +60 -0
  142. package/lib/types/skills.d.ts +85 -0
  143. package/package.json +150 -4
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Loop engine ids, the agent-preset ids that select them, and the two plain
3
+ * wire shapes both halves of the plugin share — in a module with no imports at
4
+ * all.
5
+ *
6
+ * The mapping is pure identity arithmetic (an engine to a preset id and back),
7
+ * so it belongs to neither half; it lives here for the same reason
8
+ * `./namespace.ts` does — the browser bundle needs it, and the files that
9
+ * otherwise carry it are host-side: `./settings.ts` imports `schemastery` and
10
+ * the `dsh-settings` brand type, `./preset.ts` imports `node:fs/promises`.
11
+ * Importing either from `src/client/**` would pull a host package into the
12
+ * client artifact. Both of them re-export what this module defines, so the
13
+ * existing import paths and names stay valid on the node side.
14
+ *
15
+ * The two shapes are here for that same reason: {@link SessionEngine} is what
16
+ * the browser half renders and {@link LoopEngineSelectResult} is what its switch
17
+ * comes back with, so both halves name the same types without either half's
18
+ * modules crossing the boundary. The refusal codes travel with the second one
19
+ * ({@link LoopEngineRefusalCode}) for the same reason plus one more: the browser
20
+ * half is what turns a refusal into copy, so the codes it may read have to be
21
+ * knowable without importing the host's router.
22
+ *
23
+ * @module dsh-loop-engine-allinone/agent-preset-ids
24
+ */
25
+ export { HOSTED_ENGINE_IDS, LOOP_ENGINE_IDS, isHostedEngine, isLoopEngineId, } from './engine-registry.ts';
26
+ export type { HostedEngineId, LoopEngineId } from './engine-registry.ts';
27
+ import type { HostedEngineId, LoopEngineId } from './engine-registry.ts';
28
+ /**
29
+ * The single provider route label EVERY hosted engine logs into its sessions'
30
+ * `request/header`, and the one placeholder route this plugin registers in the
31
+ * llm registry.
32
+ *
33
+ * One label for every hosted engine, because the browser model catalog is built
34
+ * for the whole Host GENERATION and is not scoped to a session
35
+ * (`packages/api/session-controller/src/catalog.ts` — "Build the browser model
36
+ * catalog without requiring a Session"; it walks `ctx.llm.listProviders()`
37
+ * once). A per-engine label would therefore surface one provider group per
38
+ * engine in every session's menu at once — four identical `default` entries —
39
+ * which is what this constant collapses away.
40
+ *
41
+ * The value is ASCII on purpose: it is a WIRE value. It is written into each
42
+ * session's `request/header`, it travels through selections, and the host
43
+ * compares it (`providerInfo().id` must equal the provider the adapter
44
+ * registered for — `packages/llm/llm/src/index.ts` `prepareRoutes`). The
45
+ * display name ({@link HOSTED_ROUTE_NAME}) is the same token, for the reason
46
+ * that constant documents.
47
+ */
48
+ export declare const HOSTED_ROUTE_LABEL = "external";
49
+ /**
50
+ * The user-visible name of the one hosted route ({@link HOSTED_ROUTE_LABEL}),
51
+ * shown as the model menu's provider group label.
52
+ *
53
+ * The same token as the wire value, and deliberately not localized: the catalog
54
+ * carries exactly one string per provider group (`ModelCatalog`'s `group.name`,
55
+ * taken from `LlmProviderInfo.name`) and the browser half has no hook to
56
+ * re-translate it, so one fixed name is what every locale sees.
57
+ */
58
+ export declare const HOSTED_ROUTE_NAME = "external";
59
+ /**
60
+ * The one model id a hosted engine's provider route advertises to the model
61
+ * menu, and the model label that engine logs into its sessions'
62
+ * `request/header` when the deployment pins none.
63
+ *
64
+ * The two are ONE string on purpose, and that is a host-side requirement rather
65
+ * than a style choice: the picker builds its selection out of a catalog entry
66
+ * (`ModelSelect.tsx` `choices` — `model: model.id`) and resolves a session's
67
+ * `(provider, model)` back to that entry by comparing the pair with the logged
68
+ * header (`selectedIndex`), so an entry whose `id` differs from the logged
69
+ * label falls through to the raw `provider/model` string — how a menu ends up
70
+ * showing a model that does not exist. The label is the engine's own word for
71
+ * "whatever it decides"; `name` is the same string because `LlmModelInfo.name`
72
+ * is rendered verbatim and has no localization hook.
73
+ */
74
+ export declare const HOSTED_DEFAULT_MODEL = "default";
75
+ /** Prefix every plugin-authored preset id carries. */
76
+ export declare const HOSTED_PRESET_PREFIX = "loop-engine-";
77
+ /** The deployment's own preset id, which selects the harness loop. */
78
+ export declare const SOURCE_PRESET_ID = "standard";
79
+ /**
80
+ * The pre-routing single preset id: older versions of this plugin authored one
81
+ * preset for every hosted engine and pinned the profile to one of them, so a
82
+ * session carrying this id DID run a hosted engine — but the id does not say
83
+ * which one. It is not a hosted preset id ({@link engineOfPreset} reads it as
84
+ * `undefined`, so the router keeps such a session on the harness loop), and it
85
+ * is not `standard` either; surfaces that name a session's engine must report it
86
+ * as "a hosted engine, unrecorded" rather than claim the in-process loop.
87
+ */
88
+ export declare const LEGACY_HOSTED_PRESET_ID = "loop-engine";
89
+ /**
90
+ * The preset id that selects one engine for a session. `in-process` names the
91
+ * deployment's own `standard` preset: the harness loop is not something this
92
+ * plugin authors a preset for.
93
+ * @param engine - the engine a session should run.
94
+ * @returns the preset id to record on the session.
95
+ */
96
+ export declare function enginePresetId(engine: LoopEngineId): string;
97
+ /**
98
+ * The engine a preset id selects, or `undefined` for a preset this plugin does
99
+ * not own (any deployment-authored preset, including `standard`).
100
+ * @param presetId - the session's preset id, when it has one.
101
+ * @returns the hosted engine it names, or undefined for the harness loop.
102
+ */
103
+ export declare function engineOfPreset(presetId: string | undefined): HostedEngineId | undefined;
104
+ /**
105
+ * What a session's recorded preset says about the engine that session runs.
106
+ *
107
+ * Not every session has an answer: the pre-routing single preset id names a
108
+ * hosted engine without recording which one, and a deployment that composes no
109
+ * presets (or a transcript whose projection is gone) records nothing at all.
110
+ * Both are reported as themselves — a surface that names a session's engine must
111
+ * never read "unknown" as the in-process loop.
112
+ */
113
+ export type SessionEngine = {
114
+ readonly kind: 'engine';
115
+ readonly engine: LoopEngineId;
116
+ }
117
+ /** The pre-routing preset: this session ran a hosted engine, but the id does not say which. */
118
+ | {
119
+ readonly kind: 'legacy';
120
+ }
121
+ /** The session records no preset at all (a deployment that composes none, or one created before presets existed). */
122
+ | {
123
+ readonly kind: 'unset';
124
+ };
125
+ /**
126
+ * What one session's engine report carries: the engine the session ACTUALLY
127
+ * runs, and — when they differ — the engine its own record names for it.
128
+ *
129
+ * The two are one answer because a surface must never render the second as if it
130
+ * were the first. A session switched onto or off the harness loop has its agent
131
+ * RELEASED (`src/router-loop.ts` `move`), so the session is simply cold
132
+ * afterwards and its record IS what its next build runs — no second fact, no
133
+ * pending. This field therefore survives for the one case in which a live agent
134
+ * still differs from the record: the release did not take (its teardown failed,
135
+ * or another process wrote the record), so the session keeps running its old
136
+ * engine while the record names another. Reporting only the live agent would
137
+ * hide that a switch was accepted at all — the "显示在撒谎" bug this report
138
+ * exists to prevent — so both travel, and {@link pending} says which is which.
139
+ */
140
+ export interface SessionEngineReport {
141
+ /** The engine driving this session NOW. */
142
+ readonly engine: SessionEngine;
143
+ /**
144
+ * Present only while the session has an engine recorded that its LIVE agent is
145
+ * not running — i.e. a recorded switch whose release did not complete. Absent
146
+ * whenever the record and the live agent agree, and always absent for a session
147
+ * with no live agent (there the record IS what the session runs).
148
+ */
149
+ readonly pending?: LoopEngineId;
150
+ }
151
+ /**
152
+ * Every way this plugin refuses to move a session to another engine, as a stable
153
+ * code.
154
+ *
155
+ * A code exists because the host's own sentence is not what a surface shows any
156
+ * more: the browser half localizes the refusal from the code (`refusalFace` in
157
+ * `./client/locales.ts`) and keeps {@link LoopEngineRefusal.reason} only as
158
+ * detail, so the codes are part of the wire contract and a value may be added but
159
+ * never quietly repurposed. The list is exactly the refusals the host can produce
160
+ * — one per branch of `RouterLoop.selectEngine`, plus the Remote's own
161
+ * "no router mounted" answer:
162
+ *
163
+ * - `session-closed`: the session has no agent in this process (nothing is open
164
+ * to move);
165
+ * - `turn-running`: a turn is in flight, and it is never interrupted;
166
+ * - `subagent-session`: a delegated child's agent belongs to its delegation;
167
+ * - `not-driven`: an agent is live but this router did not build it (the mount
168
+ * window in which the base loop still owns the factory slot);
169
+ * - `router-unmounted`: no router is mounted at all yet;
170
+ * - `record-failed`: the plugin's own per-session record could not be written,
171
+ * so the choice could not be committed;
172
+ * - `rebuild-failed`: the outgoing machine was retired, the successor could not
173
+ * be built, and the session was left cold on the recorded engine.
174
+ *
175
+ * A malformed REQUEST is deliberately NOT one of these: it travels as a
176
+ * `RemoteError` with `gateway/bad-request`, because it is the caller's fault
177
+ * rather than a state of the session.
178
+ */
179
+ export declare const LOOP_ENGINE_REFUSAL_CODES: readonly ["session-closed", "turn-running", "subagent-session", "not-driven", "router-unmounted", "record-failed", "rebuild-failed"];
180
+ /** Why one session's engine switch was refused. */
181
+ export type LoopEngineRefusalCode = (typeof LOOP_ENGINE_REFUSAL_CODES)[number];
182
+ /**
183
+ * Whether an untyped value is one of {@link LOOP_ENGINE_REFUSAL_CODES}.
184
+ *
185
+ * The boundary test the browser half needs: a code is only a code when this
186
+ * build knows it, so a value it does not recognize is normalized away and the
187
+ * host's `reason` stays readable instead (`./client/session-engine.ts`
188
+ * `parseSelectResult`).
189
+ * @param value - the value to classify.
190
+ * @returns whether `value` is a known refusal code.
191
+ */
192
+ export declare function isLoopEngineRefusalCode(value: unknown): value is LoopEngineRefusalCode;
193
+ /**
194
+ * What one attempt to move a session to another engine produced.
195
+ *
196
+ * A refusal is a VALUE rather than a thrown error: every reason below is a
197
+ * predictable state of the session (it is not open, it is mid-turn, it belongs
198
+ * to subagent routing) that the surface must show — localized by
199
+ * {@link LoopEngineRefusal.code}, with the host's own sentence kept as detail —
200
+ * and only a malformed REQUEST is the caller's fault (`gateway/bad-request`).
201
+ */
202
+ export type LoopEngineSelectResult = {
203
+ readonly ok: true;
204
+ readonly engine: LoopEngineId;
205
+ /**
206
+ * Present when the switch was made to land by RELEASING the session's live
207
+ * agent rather than by moving it: a change with the harness loop on either
208
+ * side of it cannot be applied in place, so the agent is torn down, the
209
+ * session goes cold, and the record's engine is what its next build uses.
210
+ *
211
+ * The page must reload for that next build to happen: releasing publishes
212
+ * `session/disposed`, which the browser half reads as this session being
213
+ * gone — with no way back in that page's lifetime — so a reload (and a
214
+ * re-open of the session) is what replaces the page state with a fresh list
215
+ * and a fresh build. The flag is how the surface knows to do that instead of
216
+ * leaving the user on a session that looks broken.
217
+ */
218
+ readonly reload?: true;
219
+ }
220
+ /** The switch did not happen; the branch that refused it is `code`. */
221
+ | {
222
+ readonly ok: false;
223
+ /**
224
+ * Which refusal this is, for the surface to localize
225
+ * ({@link LoopEngineRefusalCode}). Every refusal this plugin's host produces
226
+ * carries one — `refuse(code, reason)` in `./router-loop.ts` takes it as a
227
+ * required argument, so the node half cannot forget it.
228
+ *
229
+ * Optional in the SHAPE only, because the browser half is what reads it off
230
+ * the wire: a code this build does not know (a newer host, or a hand-written
231
+ * answer) is normalized to absent so that `reason` — which is always there —
232
+ * is what the user reads, instead of the whole answer failing the boundary
233
+ * check and leaving the surface with nothing to show.
234
+ */
235
+ readonly code?: LoopEngineRefusalCode;
236
+ /**
237
+ * The host's own complete sentence about this session. It is DETAIL, not the
238
+ * message any more: a surface shows its own localized copy for `code` and
239
+ * keeps this beside it (small print) or falls back to it when `code` is
240
+ * absent.
241
+ */
242
+ readonly reason: string;
243
+ };
244
+ /**
245
+ * The engine one session's recorded preset says it runs, with the harness's own
246
+ * fallback for a preset this plugin does not own: the deployment's `standard` and
247
+ * anything else a deployment authored keep the session on the harness loop.
248
+ *
249
+ * The two answers that are not an engine stay distinct: {@link LEGACY_HOSTED_PRESET_ID}
250
+ * is a hosted engine whose name was never recorded, and a missing preset is this
251
+ * session recording nothing.
252
+ *
253
+ * This is the ONE preset-id → engine judgement: the router routes on what it
254
+ * returns and the plugin's own Remote reports it, so "what the session runs" and
255
+ * "what the session is shown as running" cannot disagree
256
+ * (`src/engine-of-session.ts` supplies the preset id from the durable log;
257
+ * `src/engine-remote.ts` publishes the answer to the browser half).
258
+ * @param presetId - the session's recorded preset id, when it has one. Reads off
259
+ * an untyped projection, so anything that is not a string is as good as no
260
+ * preset at all.
261
+ * @returns what that id says about the session's engine.
262
+ */
263
+ export declare function sessionEngineOf(presetId: unknown): SessionEngine;
264
+ /**
265
+ * The hosted engine one {@link SessionEngine} names, or `undefined` for every
266
+ * answer that is not this plugin's own engine preset.
267
+ *
268
+ * This is the router's half of the judgement: the harness loop still owns
269
+ * `in-process`, the pre-routing id and an unrecorded session both run whatever
270
+ * the deployment's own composition gives them (the harness loop).
271
+ * @param session - what a session's preset says about its engine.
272
+ * @returns the hosted engine to build a driver runtime for, or undefined for the
273
+ * harness loop.
274
+ */
275
+ export declare function hostedEngineOf(session: SessionEngine): HostedEngineId | undefined;
276
+ //# sourceMappingURL=agent-preset-ids.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Session header engine chip: a read-only pill naming the engine THIS session
3
+ * runs, read from this plugin's own Remote — which answers from the plugin's own
4
+ * per-session record and, for a session with none, from the session's durable
5
+ * log: the same read the router routes on, so the chip cannot disagree with what
6
+ * the session is actually driven by.
7
+ *
8
+ * It deliberately does NOT read the client session list's `agentPreset`
9
+ * projection: that value is a cache-shaped hint, and it names the preset the
10
+ * session was CREATED with, which is the wrong answer both for a session that
11
+ * switched while it was blank and for one that switched after it started. The
12
+ * harness's own preset label reads that same hint and lags the same way; this
13
+ * chip does not.
14
+ *
15
+ * The settings section's engine is NOT what this chip shows: that value is only
16
+ * the default for sessions created later, and a session created before a default
17
+ * change keeps running the engine it has. The one settings value this chip DOES
18
+ * read is the display toggle: `showEngineBadge` off means the chip renders
19
+ * nothing at all, the way the composer picker renders nothing when
20
+ * `showInComposer` is off. The two sessions that name no engine
21
+ * are reported as themselves rather than as the in-process loop: the pre-routing
22
+ * single preset id reads as "legacy hosted engine" (it ran a hosted engine, the
23
+ * id just never recorded which), and a session whose engine is not known — no
24
+ * record, no preset, or no answer from the host — renders nothing, which is also
25
+ * what the chip does until the host's first answer arrives.
26
+ *
27
+ * "What the session runs" is the ACTUAL engine, and for a live session that is
28
+ * the agent in front of it rather than the plugin's record: a switch onto the
29
+ * harness loop makes the host RELEASE the session's agent and reload the page,
30
+ * and if that release did not take, the session keeps running the engine it had
31
+ * while its record names another. This chip never renders the record as its name;
32
+ * it appends the `切到 X · 尚未接管` marker beside the engine that really runs,
33
+ * and its tooltip says what to do about it (`pendingSessionNotice`). See
34
+ * `SessionEngineReport` in `src/agent-preset-ids.ts`.
35
+ *
36
+ * Styling is token-driven inline styles like the settings section (the
37
+ * client-module bundle is esbuild-built without a CSS loader).
38
+ * @module dsh-loop-engine-allinone/client/badge
39
+ */
40
+ import type { JSX } from 'react';
41
+ import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
42
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
43
+ import type { SessionEngineCache, SessionSeat } from './session-engine.ts';
44
+ import type { LoopEngineState } from './store.ts';
45
+ import { type en } from './locales.ts';
46
+ /** Registration-side business face for the header badge. */
47
+ export interface LoopEngineBadgeInjected {
48
+ /** The plugin's authoritative per-session engine cache. */
49
+ sessionEngines: SessionEngineCache;
50
+ hooks: {
51
+ /** Badge-visibility snapshot bound by the UI renderer as useSnapshot. */
52
+ snapshot: SnapshotStore<LoopEngineState>;
53
+ };
54
+ /** Section copy bound to the engine dictionaries. */
55
+ t: (key: keyof typeof en) => string;
56
+ }
57
+ /**
58
+ * Props delivered by the slot outlet (the renderer erases the share boundary).
59
+ * The seat members stay partial here so the registration face matches the slot's
60
+ * own props; {@link BadgeFace} asserts them the way the component reads them.
61
+ */
62
+ export type LoopEngineBadgeProps = PropsRuntime<'conversation.session.header.actions'> & Partial<SessionSeat> & Partial<InjectFace<LoopEngineBadgeInjected>>;
63
+ /**
64
+ * Render the session header's loop-engine chip for the session on screen.
65
+ * @param props - composed slot props.
66
+ * @returns the chip, or null when the seat carries no session to speak about,
67
+ * the session's engine is not known yet (or not recorded at all), or the
68
+ * settings hide the chip.
69
+ */
70
+ export declare function LoopEngineBadge(props: LoopEngineBadgeProps): JSX.Element | null;
71
+ //# sourceMappingURL=LoopEngineBadge.d.ts.map
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Composer loop-engine picker: a compact dropdown registered at the
3
+ * `conversation.input.right` seat, so it sits immediately left of the model
4
+ * select in the composer's tool row.
5
+ *
6
+ * It picks the engine of the session it is rendered in — the picker's value is
7
+ * this plugin's own authoritative read of that session's engine, and a pick
8
+ * moves the session over this plugin's own `loopEngine/select` Remote. Any
9
+ * session can be moved, at any point in its life, as long as it is open and no
10
+ * turn is in flight, and the host picks one of two ways to make the pick land:
11
+ *
12
+ * - between two hosted engines it REBUILDS that session's agent IN PLACE, on the
13
+ * session's own live Session object, so the conversation it is rendered beside
14
+ * never closes;
15
+ * - when the harness loop is on either side of the change, the host RELEASES
16
+ * that session's agent and answers `reload: true`: the session goes cold with
17
+ * its record naming the new engine, this page is reloaded (which is what
18
+ * clears the client state a `session/disposed` leaves behind), and the page
19
+ * that comes back opens the same session again — the host then builds it on
20
+ * the engine the record names (`src/client/reload.ts`). This control says so
21
+ * in a notice rather than letting the reload look like a glitch.
22
+ *
23
+ * Either way the host refuses one that is
24
+ * running, and a refusal leaves the session's engine untouched — the label goes
25
+ * back to the engine the session actually runs. The refusal is rendered from its
26
+ * CODE in the user's own language (`refusalFace`), with the host's own sentence
27
+ * kept as detail — never as the message itself, which is how a raw
28
+ * `session "…" is running; …` used to reach the user.
29
+ *
30
+ * A pick commits as soon as the host can apply it, with ONE exception: when the
31
+ * target and the engine this session ACTUALLY runs differ in being the in-process
32
+ * one, the host cannot hand the session over in place, so the switch releases its
33
+ * agent and reloads this page — and a reload costs this page's scroll position and
34
+ * any unsent draft in it. That one pick is staged behind a confirmation
35
+ * (`switchNeedsReload(报告里的实际引擎, 目标引擎)`, resolved before anything is
36
+ * sent) and only then committed; a pick between two hosted engines, which swaps
37
+ * the agent in place and reloads nothing, still commits immediately and opens no
38
+ * dialog at all. That judgement is only ever made about an engine somebody knows:
39
+ * until the host has answered what this session runs there is no engine to judge,
40
+ * and a session with no answer may reload in EITHER direction — so the control is
41
+ * DISABLED for as long as it is reading (`engineSwitchReady`), rather than
42
+ * guessing a direction. This control deliberately asks NOTHING else: whether the session
43
+ * can be moved right now is the host's own judgement — it refuses one that is not
44
+ * open, one that is mid-turn, and a subagent's own session, each with a code and a
45
+ * sentence — so a pick that cannot land costs nothing, and the control never
46
+ * reads an idle hint off a cached session list.
47
+ *
48
+ * So it opens two dialogs, with different jobs: the CONFIRMATION (two buttons,
49
+ * cancel and switch) that a reloading pick must pass before it is sent, and the
50
+ * NOTICE (one button, close) that reports what the host answered — the refusal's
51
+ * localized copy with the host's sentence as detail, or this plugin's own
52
+ * sentence for a reloaded pick.
53
+ *
54
+ * WITH a session, the settings default is never shown — not even while the first
55
+ * answer is still in flight: a session on the pre-routing single preset id reads
56
+ * "legacy hosted engine", one whose engine is not recorded (or not readable)
57
+ * reads "not recorded", and a session whose engine has not been answered yet
58
+ * reads "reading" AND is disabled while it does (see above) — a default or a stale
59
+ * hint would be a claim about a session this control has no facts for, and a pick
60
+ * would have to be judged against an engine nobody knows. Without a session (the
61
+ * seat renders only with one, so this is the defensive branch) the trigger names
62
+ * the default and a pick writes it — immediately, like every other pick here, and
63
+ * with the control usable from the start: a new-session page is waiting for
64
+ * nothing, and that pick reloads nothing. The settings section's own picker is the
65
+ * one that still stages its choice behind a confirmation.
66
+ *
67
+ * Styling is token-driven inline styles like the badge and section (the
68
+ * client-module bundle is esbuild-built without a CSS loader).
69
+ * @module dsh-loop-engine-allinone/client/composer
70
+ */
71
+ import { type JSX } from 'react';
72
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
73
+ import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
74
+ import type { LoopEngineStore, LoopEngineState } from './store.ts';
75
+ import type { LoopEngineId } from '../agent-preset-ids.ts';
76
+ import { type SessionEngineCache, type SessionEngineSwitcher, type SessionSeat } from './session-engine.ts';
77
+ import { type en } from './locales.ts';
78
+ /** Injected dependencies of {@link LoopEngineComposerSelect} (slot `inject`). */
79
+ export interface LoopEngineComposerSelectInjected {
80
+ /** The selection store (loaded on mount, refreshed by scope pushes). */
81
+ controller: LoopEngineStore;
82
+ hooks: {
83
+ /** Engine snapshot bound by the UI renderer as useSnapshot. */
84
+ snapshot: SnapshotStore<LoopEngineState>;
85
+ };
86
+ /** The plugin's authoritative per-session engine cache. */
87
+ sessionEngines: SessionEngineCache;
88
+ /** Move one session to another engine. */
89
+ switchEngine: SessionEngineSwitcher;
90
+ /** Composer copy bound to the loop engine dictionaries. */
91
+ t: (key: keyof typeof en) => string;
92
+ }
93
+ /**
94
+ * Props delivered by the slot outlet (the renderer erases the share boundary).
95
+ * The seat members stay partial here so the registration face matches the slot's
96
+ * own props; {@link ComposerFace} asserts them the way the component reads them.
97
+ */
98
+ export type LoopEngineComposerSelectProps = PropsRuntime<'conversation.input.right'> & Partial<SessionSeat> & Partial<InjectFace<LoopEngineComposerSelectInjected>>;
99
+ /**
100
+ * One engine's mark, tinted by `currentColor` so it follows the trigger and menu
101
+ * text. The composer bundle is esbuild-built without a CSS loader, so these are
102
+ * inline SVGs.
103
+ *
104
+ * The paths live in `./engine-marks.ts` as DATA, not here: they come from each
105
+ * vendor's own artwork (traced or measured — see that module), and putting them
106
+ * in a data module is what lets a test assert that every registered engine has a
107
+ * mark. That assertion matters: when four engines were added, all four fell
108
+ * through to `default` and rendered the harness's DeepSeek fish beside their
109
+ * names.
110
+ *
111
+ * `in-process` is the one session kind that really is DeepSeek's, so it is the
112
+ * one that keeps the fish.
113
+ * @param engine - the engine whose mark to draw.
114
+ * @param size - edge length in CSS pixels.
115
+ * @returns the mark, as an inline SVG.
116
+ */
117
+ export declare function engineGlyph(engine: LoopEngineId, size?: number): JSX.Element;
118
+ /**
119
+ * Render the composer's loop-engine dropdown for the session on screen. Hides
120
+ * until the settings scope settles, so the picker never flashes a provisional
121
+ * default while a session's own engine is already known.
122
+ * @param props - composed slot props.
123
+ * @returns the picker, or null while the picker is unavailable or switched off.
124
+ */
125
+ export declare function LoopEngineComposerSelect(props: LoopEngineComposerSelectProps): JSX.Element | null;
126
+ //# sourceMappingURL=LoopEngineComposerSelect.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Loop engine settings section component: one dropdown choosing the DEFAULT
3
+ * agent loop engine for new sessions, backed by the duplicated settings scope
4
+ * through the inject face. The commit is confirmed first because it changes
5
+ * what every future session runs; sessions that already exist keep the engine
6
+ * they run, so nothing is interrupted and nothing reloads.
7
+ *
8
+ * Styling is token-driven like the rest of the settings shell (`--dsw-*`
9
+ * aliases), with the picker rendered through the shared `Menu` primitive and
10
+ * the confirmation through `Modal`. The client-module bundle is esbuild-built
11
+ * without a CSS loader, so the section shell uses token-based inline styles
12
+ * instead of a CSS module.
13
+ * @module dsh-loop-engine-allinone/client
14
+ */
15
+ import { type JSX } from 'react';
16
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
17
+ import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
18
+ import type { LoopEngineStore, LoopEngineState } from './store.ts';
19
+ import type { en } from './locales.ts';
20
+ /** Injected dependencies of {@link LoopEngineSection} (slot `inject`). */
21
+ export interface LoopEngineSectionInjected {
22
+ /** The selection store (loaded on mount, refreshed by scope pushes). */
23
+ controller: LoopEngineStore;
24
+ hooks: {
25
+ /** Section snapshot bound by the UI renderer as useSnapshot. */
26
+ snapshot: SnapshotStore<LoopEngineState>;
27
+ };
28
+ /** Section copy. */
29
+ t: (key: keyof typeof en) => string;
30
+ }
31
+ /** Props delivered by the slot outlet (the renderer erases the share boundary). */
32
+ export type LoopEngineSectionProps = Partial<InjectFace<LoopEngineSectionInjected>>;
33
+ /** Render the engine dropdown plus the interrupt notice and the switch confirmation. */
34
+ export declare function LoopEngineSection(props: LoopEngineSectionProps): JSX.Element;
35
+ //# sourceMappingURL=LoopEngineSection.d.ts.map
@@ -0,0 +1,87 @@
1
+ /**
2
+ * One vector mark per hosted engine, as data.
3
+ *
4
+ * Data rather than JSX so it can be TESTED: the composer component cannot be
5
+ * imported by the node test runner (it pulls
6
+ * `@deepseek-ai/dsh-client-ui-primitives`, which resolves React through its own
7
+ * package path rather than through the test alias), while this module imports
8
+ * nothing at all. The regression it guards is concrete: every engine added to the
9
+ * registry once fell through the mark switch's `default` arm and rendered the
10
+ * harness's own DeepSeek fish beside its name.
11
+ *
12
+ * `in-process` is deliberately absent: it is the harness's own loop, it really is
13
+ * DeepSeek's, and its mark is the `FishLogo` component rather than a path here.
14
+ *
15
+ * ## Where each mark comes from
16
+ *
17
+ * The four original engines carry their vendors' published paths. The four added
18
+ * by the registry fork are traced or measured from the vendors' OWN artwork found
19
+ * on this machine (see `data/trace-icons.py`, which records the source file and
20
+ * the mask rule for each), because hand-drawing them produced shapes that did not
21
+ * resemble the real marks.
22
+ *
23
+ * Each mark keeps its own `viewBox` and `fillRule`: they come from different
24
+ * sources at different scales, and normalizing them would either distort the
25
+ * vendor's proportions or force a re-trace for no gain.
26
+ *
27
+ * @module dsh-loop-engine-allinone/client/engine-marks
28
+ */
29
+ import type { HostedEngineId } from '../engine-registry.ts';
30
+ /** One engine's mark, ready to render as a single `<path>`. */
31
+ export interface EngineMark {
32
+ /** The source artwork's own coordinate system. */
33
+ readonly viewBox: string;
34
+ /** Path data, in that coordinate system. */
35
+ readonly d: string;
36
+ /**
37
+ * `evenodd` for marks that rely on a counter (a hole); omitted for the default
38
+ * `nonzero`, which is what a single solid path needs.
39
+ */
40
+ readonly fillRule?: 'nonzero' | 'evenodd';
41
+ /**
42
+ * A second subpath drawn in its OWN colour, for a mark whose brand identity is
43
+ * two-tone (Kimi's white "K" carries a blue dot).
44
+ *
45
+ * Everything else stays `currentColor`, so the mark follows the menu's text
46
+ * colour; an accent is the deliberate exception, and it exists rather than a
47
+ * two-tone path because a single `d` can only be filled one way.
48
+ */
49
+ readonly accent?: {
50
+ readonly d: string;
51
+ readonly fill: string;
52
+ };
53
+ /**
54
+ * Draw the path as a STROKE rather than a fill, at this width in viewBox units.
55
+ *
56
+ * Some vendors publish the same mark twice: a solid app-tile version and a
57
+ * monochrome outline one. Qoder is such a case, and the outline is the variant
58
+ * that matches how the other marks read here.
59
+ */
60
+ readonly strokeWidth?: number;
61
+ }
62
+ /**
63
+ * How one mark should be painted, decided once.
64
+ *
65
+ * Both the component (JSX props) and the preview scripts (SVG attribute strings)
66
+ * read this, because they each used to decide for themselves — and the preview
67
+ * silently kept rendering a fill after Qoder's mark became a stroke, so the
68
+ * picture I was judging by was no longer the picture the picker draws.
69
+ */
70
+ export interface MarkPaint {
71
+ /** Paint the path's interior, or trace its outline. */
72
+ readonly mode: 'fill' | 'stroke';
73
+ /** The paint colour. */
74
+ readonly colour: string;
75
+ /** Stroke width in viewBox units; present only in `stroke` mode. */
76
+ readonly width?: number;
77
+ }
78
+ /**
79
+ * Resolve how to paint one mark.
80
+ * @param mark - the engine's mark.
81
+ * @param colour - the colour the surface wants (`currentColor` resolves to it).
82
+ * @returns the paint decision.
83
+ */
84
+ export declare function markPaint(mark: EngineMark, colour: string): MarkPaint;
85
+ /** Every hosted engine's mark, keyed by engine id. */
86
+ export declare const ENGINE_MARKS: Readonly<Record<HostedEngineId, EngineMark>>;
87
+ //# sourceMappingURL=engine-marks.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Loop engine settings plugin, browser half. Registers the "Loop engine" page
3
+ * under the settings section slot and binds one store to whichever settings
4
+ * client the running harness serves: the 0.1.7 `configForms` form for the
5
+ * plugin's own `loop-engine` profile entry, or the 0.1.5 `settingsScope` for its
6
+ * `agent-loop-engine` settings section. Two inject callbacks register the page,
7
+ * one per generation — whichever service exists fires, and the client bundle
8
+ * never imports a host package, so it cannot probe the generation like the node
9
+ * half does.
10
+ * Export discipline: packages/client/AGENTS.md.
11
+ * @module dsh-loop-engine-allinone/client
12
+ */
13
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
14
+ import { type LoopEngineKey } from './locales.ts';
15
+ export type { LoopEngineSectionInjected, LoopEngineSectionProps } from './LoopEngineSection.tsx';
16
+ export type { LoopEngineBadgeInjected, LoopEngineBadgeProps } from './LoopEngineBadge.tsx';
17
+ export type { LoopEngineComposerSelectInjected, LoopEngineComposerSelectProps } from './LoopEngineComposerSelect.tsx';
18
+ export type { LoopEngineState } from './store.ts';
19
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
20
+ interface LocaleNamespaceMap {
21
+ /** The Loop engine settings page copy. */
22
+ 'settings.loop-engine': LoopEngineKey;
23
+ }
24
+ }
25
+ /**
26
+ * Required services (cordis fiber inject). Only the services BOTH generations
27
+ * provide are listed statically: `configForms` (0.1.7) and `settingsScope`
28
+ * (0.1.5) are mutually exclusive, so each is injected inside its own callback
29
+ * rather than named here. The target slot is declared by ui-settings' apply;
30
+ * registration depends on it through `slots.inject()`.
31
+ */
32
+ export declare const inject: string[];
33
+ /**
34
+ * Register the Loop engine section once the `settings.section` declaration is
35
+ * on the ledger and bind its store to whichever settings client is present.
36
+ * @param ctx - client root context.
37
+ */
38
+ export declare function apply(ctx: ClientContext): void;
39
+ //# sourceMappingURL=index.d.ts.map