dsh-loop-engine 0.1.5-rc4 → 0.1.7-rc1
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +174 -76
- package/lib/index.js +136 -57
- package/lib/invariant.js +8 -0
- package/lib/types/client/index.d.ts +16 -6
- package/lib/types/client/session-engine.d.ts +40 -6
- package/lib/types/client/store.d.ts +53 -18
- package/lib/types/client/turn-status.d.ts +27 -6
- package/lib/types/client/use-session-engine.d.ts +12 -4
- package/lib/types/compat.d.ts +25 -0
- package/lib/types/driver-core/host-servers.d.ts +6 -2
- package/lib/types/driver-core/hosted-engine-runtime.d.ts +3 -3
- package/lib/types/driver-core/model-handover.d.ts +22 -4
- package/lib/types/driver-core/system-head.d.ts +43 -0
- package/lib/types/engine-codex/model-handover.d.ts +21 -9
- package/lib/types/engine-pi/model-handover.d.ts +4 -0
- package/lib/types/index.d.ts +25 -3
- package/lib/types/namespace.d.ts +20 -4
- package/lib/types/preset.d.ts +7 -3
- package/lib/types/settings.d.ts +31 -11
- package/package.json +27 -27
package/lib/index.js
CHANGED
|
@@ -395,21 +395,30 @@ function renderAssistantBlocks(blocks) {
|
|
|
395
395
|
}
|
|
396
396
|
return sections.join("\n\n");
|
|
397
397
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
switch (child.type) {
|
|
398
|
+
function renderTextBlocks(blocks) {
|
|
399
|
+
return blocks.map((block) => {
|
|
400
|
+
switch (block.type) {
|
|
402
401
|
case "text":
|
|
403
|
-
return
|
|
402
|
+
return block.text;
|
|
404
403
|
case "image":
|
|
405
404
|
return OMITTED_IMAGE_TEXT;
|
|
406
405
|
default:
|
|
407
406
|
return "";
|
|
408
407
|
}
|
|
409
408
|
}).filter((section) => section !== "").join("\n\n");
|
|
410
|
-
|
|
409
|
+
}
|
|
410
|
+
function renderToolResult(blocks, isError) {
|
|
411
|
+
const body = renderTextBlocks(blocks);
|
|
412
|
+
const tag = isError === true ? "tool-result-error" : "tool-result";
|
|
411
413
|
return frame(tag, body || "(no content)");
|
|
412
414
|
}
|
|
415
|
+
function renderModernToolResult(message) {
|
|
416
|
+
return renderToolResult(message.content, message.isError);
|
|
417
|
+
}
|
|
418
|
+
function renderLegacyToolResult(message) {
|
|
419
|
+
const block = message.content[0];
|
|
420
|
+
return renderToolResult(block?.content ?? [], block?.isError);
|
|
421
|
+
}
|
|
413
422
|
var ENGINE_SLASH_LINE = /^\/[^\s/]+(?:[ \t]+.*)?$/;
|
|
414
423
|
function engineSlashPrompt(messages) {
|
|
415
424
|
const last = messages.at(-1);
|
|
@@ -433,24 +442,18 @@ function serializeHistory(messages) {
|
|
|
433
442
|
break;
|
|
434
443
|
}
|
|
435
444
|
case "user": {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
sections.push(renderToolResult(user));
|
|
445
|
+
if (message.source.kind === "tool") {
|
|
446
|
+
sections.push(renderLegacyToolResult(message));
|
|
439
447
|
} else {
|
|
440
|
-
const body =
|
|
441
|
-
switch (block.type) {
|
|
442
|
-
case "text":
|
|
443
|
-
return block.text;
|
|
444
|
-
case "image":
|
|
445
|
-
return OMITTED_IMAGE_TEXT;
|
|
446
|
-
default:
|
|
447
|
-
return "";
|
|
448
|
-
}
|
|
449
|
-
}).filter((section) => section !== "").join("\n\n");
|
|
448
|
+
const body = renderTextBlocks(message.content);
|
|
450
449
|
sections.push(frame("user", body || "(no content)"));
|
|
451
450
|
}
|
|
452
451
|
break;
|
|
453
452
|
}
|
|
453
|
+
case "tool": {
|
|
454
|
+
sections.push(renderModernToolResult(message));
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
454
457
|
default:
|
|
455
458
|
break;
|
|
456
459
|
}
|
|
@@ -624,6 +627,29 @@ var DriverInbox = class {
|
|
|
624
627
|
}
|
|
625
628
|
};
|
|
626
629
|
|
|
630
|
+
// src/driver-core/system-head.ts
|
|
631
|
+
import { createSystemMessage } from "@deepseek-ai/dsh-llm";
|
|
632
|
+
|
|
633
|
+
// src/compat.ts
|
|
634
|
+
import * as dshSettings from "@deepseek-ai/dsh-settings";
|
|
635
|
+
var LEGACY_HARNESS = "SettingsProvider" in dshSettings;
|
|
636
|
+
|
|
637
|
+
// src/driver-core/system-head.ts
|
|
638
|
+
var SURFACE_TYPES = /* @__PURE__ */ new Set([
|
|
639
|
+
"system/message",
|
|
640
|
+
"user/message",
|
|
641
|
+
"developer/message",
|
|
642
|
+
"assistant/message",
|
|
643
|
+
"tool/result"
|
|
644
|
+
]);
|
|
645
|
+
function appendSystemHeadIfMissing(session, turn, step) {
|
|
646
|
+
if (LEGACY_HARNESS) return;
|
|
647
|
+
for (const event of session.snapshotEvents()) {
|
|
648
|
+
if (SURFACE_TYPES.has(event.type) && event.surfaceOp !== void 0) return;
|
|
649
|
+
}
|
|
650
|
+
session.append("system/message", { turn, step, message: createSystemMessage("") }, { surfaceOp: "append" });
|
|
651
|
+
}
|
|
652
|
+
|
|
627
653
|
// src/provider-route.ts
|
|
628
654
|
import { LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
|
|
629
655
|
function hostedRouteLabelOf(_engine) {
|
|
@@ -685,6 +711,9 @@ function sessionModelOverrideOf(ctx, session) {
|
|
|
685
711
|
}
|
|
686
712
|
|
|
687
713
|
// src/driver-core/model-handover.ts
|
|
714
|
+
var SHIPPED_ROUTE_APIS = {
|
|
715
|
+
"deepseek-official": "openai-completions"
|
|
716
|
+
};
|
|
688
717
|
var warned = /* @__PURE__ */ new WeakMap();
|
|
689
718
|
function warnOnce(ctx, key, message) {
|
|
690
719
|
let seen = warned.get(ctx);
|
|
@@ -711,10 +740,9 @@ function readProviderProfile(ctx, address) {
|
|
|
711
740
|
const record = node;
|
|
712
741
|
const { baseURL, api, apiKeyEnv } = record;
|
|
713
742
|
if (typeof baseURL !== "string" || baseURL.length === 0) return void 0;
|
|
714
|
-
if (typeof api !== "string" || api.length === 0) return void 0;
|
|
715
743
|
return {
|
|
716
744
|
baseURL,
|
|
717
|
-
api,
|
|
745
|
+
api: typeof api === "string" && api.length > 0 ? api : void 0,
|
|
718
746
|
apiKeyEnv: typeof apiKeyEnv === "string" && apiKeyEnv.length > 0 ? apiKeyEnv : void 0
|
|
719
747
|
};
|
|
720
748
|
}
|
|
@@ -734,7 +762,7 @@ async function resolveModelHandover(ctx, override) {
|
|
|
734
762
|
}
|
|
735
763
|
const profile = readProviderProfile(ctx, address);
|
|
736
764
|
if (profile === void 0) {
|
|
737
|
-
return refuse(ctx, override, `the "${address.settingsNs}" settings section names no baseURL
|
|
765
|
+
return refuse(ctx, override, `the "${address.settingsNs}" settings section names no baseURL for it`);
|
|
738
766
|
}
|
|
739
767
|
const apiKey = await readApiKey(ctx, profile.apiKeyEnv);
|
|
740
768
|
if (apiKey === void 0) {
|
|
@@ -744,7 +772,7 @@ async function resolveModelHandover(ctx, override) {
|
|
|
744
772
|
provider: override.provider,
|
|
745
773
|
model: override.model,
|
|
746
774
|
baseURL: profile.baseURL,
|
|
747
|
-
api: profile.api,
|
|
775
|
+
api: profile.api ?? SHIPPED_ROUTE_APIS[override.provider],
|
|
748
776
|
apiKey
|
|
749
777
|
};
|
|
750
778
|
}
|
|
@@ -1506,6 +1534,7 @@ var ClaudeCodeAgent = class {
|
|
|
1506
1534
|
this.session.append("step/start", { turn, step });
|
|
1507
1535
|
phase.step = step;
|
|
1508
1536
|
try {
|
|
1537
|
+
appendSystemHeadIfMissing(this.session, turn, step);
|
|
1509
1538
|
for (const message of decision.messages) {
|
|
1510
1539
|
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
1511
1540
|
}
|
|
@@ -2035,16 +2064,21 @@ var HostedEngineRuntime = class {
|
|
|
2035
2064
|
return {
|
|
2036
2065
|
agent,
|
|
2037
2066
|
signal: abort.signal,
|
|
2038
|
-
publish: (source) => {
|
|
2067
|
+
publish: async (source) => {
|
|
2039
2068
|
assertLive();
|
|
2040
2069
|
const joining = lifetime.entered;
|
|
2041
2070
|
if (!joining) lifetime.bind(agent.ctx.sessions.enter(session));
|
|
2042
2071
|
detachAgent = loopCtx.agents.enter(agent, parentAgent);
|
|
2043
2072
|
if (!joining) agent.ctx.sessions.announce(session);
|
|
2044
2073
|
assertLive();
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2074
|
+
if (LEGACY_HARNESS) {
|
|
2075
|
+
;
|
|
2076
|
+
loopCtx.agents.announce(agent);
|
|
2077
|
+
assertLive();
|
|
2078
|
+
emitAgentEvent(loopCtx, agent, "agent/session-start", { source });
|
|
2079
|
+
} else {
|
|
2080
|
+
await loopCtx.agents.announce(agent, source, abort.signal);
|
|
2081
|
+
}
|
|
2048
2082
|
assertLive();
|
|
2049
2083
|
return { agent, dispose, retire, lifetime };
|
|
2050
2084
|
},
|
|
@@ -2333,15 +2367,15 @@ import { canonicalHeader as canonicalHeader2 } from "@deepseek-ai/dsh-session";
|
|
|
2333
2367
|
var CODEX_DSH_PROVIDER = "dsh";
|
|
2334
2368
|
var CODEX_DSH_API_KEY_ENV = "DSH_LOOP_ENGINE_API_KEY";
|
|
2335
2369
|
var CODEX_WIRE_APIS = {
|
|
2336
|
-
"openai-responses": "responses"
|
|
2337
|
-
"openai-completions": "chat"
|
|
2370
|
+
"openai-responses": "responses"
|
|
2338
2371
|
};
|
|
2339
2372
|
function tomlString(value) {
|
|
2340
2373
|
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
2341
2374
|
}
|
|
2342
2375
|
function codexModelConfig(handover) {
|
|
2343
|
-
const wire = CODEX_WIRE_APIS[handover.api];
|
|
2376
|
+
const wire = handover.api === void 0 ? void 0 : CODEX_WIRE_APIS[handover.api];
|
|
2344
2377
|
const profile = [
|
|
2378
|
+
`name=${tomlString(CODEX_DSH_PROVIDER)}`,
|
|
2345
2379
|
`base_url=${tomlString(handover.baseURL)}`,
|
|
2346
2380
|
...wire === void 0 ? [] : [`wire_api=${tomlString(wire)}`],
|
|
2347
2381
|
`env_key=${tomlString(CODEX_DSH_API_KEY_ENV)}`
|
|
@@ -2911,6 +2945,7 @@ var CodexAgent = class {
|
|
|
2911
2945
|
this.session.append("step/end", { turn: phase.turn, step: phase.step });
|
|
2912
2946
|
phase.step += 1;
|
|
2913
2947
|
this.session.append("step/start", { turn: phase.turn, step: phase.step });
|
|
2948
|
+
appendSystemHeadIfMissing(this.session, phase.turn, phase.step);
|
|
2914
2949
|
this.stepSettledTools = 0;
|
|
2915
2950
|
}
|
|
2916
2951
|
/** Lazily created app-server client, reused across steps and released on scope teardown. */
|
|
@@ -3962,6 +3997,7 @@ var PiAgent = class {
|
|
|
3962
3997
|
this.session.append("step/end", { turn: phase.turn, step: phase.step });
|
|
3963
3998
|
phase.step += 1;
|
|
3964
3999
|
this.session.append("step/start", { turn: phase.turn, step: phase.step });
|
|
4000
|
+
appendSystemHeadIfMissing(this.session, phase.turn, phase.step);
|
|
3965
4001
|
this.stepSettledTools = 0;
|
|
3966
4002
|
}
|
|
3967
4003
|
/**
|
|
@@ -4325,10 +4361,11 @@ var PiAgent = class {
|
|
|
4325
4361
|
}
|
|
4326
4362
|
this.assertRequestHeader();
|
|
4327
4363
|
signal.throwIfAborted();
|
|
4328
|
-
const
|
|
4364
|
+
const resolved = await resolveModelHandover(
|
|
4329
4365
|
this.loopCtx,
|
|
4330
4366
|
sessionModelOverrideOf(this.loopCtx, this.session)
|
|
4331
4367
|
);
|
|
4368
|
+
const handover = resolved?.api === void 0 ? void 0 : resolved;
|
|
4332
4369
|
const controller = new AbortController();
|
|
4333
4370
|
const cancel = () => {
|
|
4334
4371
|
if (!controller.signal.aborted) {
|
|
@@ -4685,7 +4722,7 @@ var KIMI_PROVIDER_TYPES = {
|
|
|
4685
4722
|
"openai-responses": "openai"
|
|
4686
4723
|
};
|
|
4687
4724
|
function kimiModelEnv(handover) {
|
|
4688
|
-
const type = KIMI_PROVIDER_TYPES[handover.api];
|
|
4725
|
+
const type = handover.api === void 0 ? void 0 : KIMI_PROVIDER_TYPES[handover.api];
|
|
4689
4726
|
return {
|
|
4690
4727
|
KIMI_MODEL_NAME: handover.model,
|
|
4691
4728
|
KIMI_MODEL_API_KEY: handover.apiKey,
|
|
@@ -5296,6 +5333,7 @@ var KimiAgent = class {
|
|
|
5296
5333
|
this.session.append("step/start", { turn, step });
|
|
5297
5334
|
phase.step = step;
|
|
5298
5335
|
try {
|
|
5336
|
+
appendSystemHeadIfMissing(this.session, turn, step);
|
|
5299
5337
|
for (const message of decision.messages) {
|
|
5300
5338
|
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
5301
5339
|
}
|
|
@@ -5690,7 +5728,7 @@ var KimiLoop = class extends HostedEngineRuntime {
|
|
|
5690
5728
|
import z5 from "@deepseek-ai/schemastery";
|
|
5691
5729
|
|
|
5692
5730
|
// src/namespace.ts
|
|
5693
|
-
var
|
|
5731
|
+
var LEGACY_LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
|
|
5694
5732
|
|
|
5695
5733
|
// src/settings.ts
|
|
5696
5734
|
var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
|
|
@@ -5698,8 +5736,16 @@ var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
|
|
|
5698
5736
|
showInComposer: z5.boolean().default(true)
|
|
5699
5737
|
});
|
|
5700
5738
|
function loopEngineSettingsNamespace() {
|
|
5701
|
-
return
|
|
5739
|
+
return LEGACY_LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL;
|
|
5740
|
+
}
|
|
5741
|
+
function withVolatile(schema) {
|
|
5742
|
+
const volatile = schema.volatile;
|
|
5743
|
+
return volatile === void 0 ? schema : volatile.call(schema);
|
|
5702
5744
|
}
|
|
5745
|
+
var LOOP_ENGINE_ENGINE_SCHEMA = withVolatile(
|
|
5746
|
+
z5.union(LOOP_ENGINE_IDS.map((id) => z5.const(id))).default("in-process")
|
|
5747
|
+
);
|
|
5748
|
+
var LOOP_ENGINE_SHOW_IN_COMPOSER_SCHEMA = withVolatile(z5.boolean().default(true));
|
|
5703
5749
|
|
|
5704
5750
|
// src/patch-manager.ts
|
|
5705
5751
|
var MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block --";
|
|
@@ -5838,8 +5884,13 @@ async function writeIfDifferent(path, text) {
|
|
|
5838
5884
|
await rename(tmp, path);
|
|
5839
5885
|
return true;
|
|
5840
5886
|
}
|
|
5887
|
+
async function readComposition(source, id) {
|
|
5888
|
+
if (source.readDocument !== void 0) return (await source.readDocument(id)).content;
|
|
5889
|
+
if (source.read !== void 0) return source.read(id);
|
|
5890
|
+
throw new Error("loop-engine: the preset roster exposes neither readDocument() nor read()");
|
|
5891
|
+
}
|
|
5841
5892
|
async function ensureEnginePresets(dshHome, source) {
|
|
5842
|
-
const composition = await source
|
|
5893
|
+
const composition = await readComposition(source, SOURCE_PRESET_ID);
|
|
5843
5894
|
const stripped = `${MANAGED_HEADER}
|
|
5844
5895
|
${stripPresetRows(composition)}`;
|
|
5845
5896
|
let changed = false;
|
|
@@ -5853,7 +5904,7 @@ ${stripPresetRows(composition)}`;
|
|
|
5853
5904
|
}
|
|
5854
5905
|
|
|
5855
5906
|
// src/router-loop.ts
|
|
5856
|
-
import AgentLoop from "@deepseek-ai/dsh-agent-loop";
|
|
5907
|
+
import AgentLoop, { DEFAULT_MAX_PARALLEL_TOOL_CALLS } from "@deepseek-ai/dsh-agent-loop";
|
|
5857
5908
|
|
|
5858
5909
|
// src/model-selection-reset.ts
|
|
5859
5910
|
var AGENT_DEFAULT_MODEL_NS = "agent-default-model";
|
|
@@ -6754,7 +6805,10 @@ var RouterLoop = class extends AgentLoop {
|
|
|
6754
6805
|
* @param warn - diagnostic sink for a skipped engine command.
|
|
6755
6806
|
*/
|
|
6756
6807
|
constructor(ctx, build, records, warn) {
|
|
6757
|
-
super(ctx, { agents: [] }
|
|
6808
|
+
super(ctx, LEGACY_HARNESS ? { agents: [] } : {
|
|
6809
|
+
agents: [],
|
|
6810
|
+
maxParallelToolCalls: { get: () => DEFAULT_MAX_PARALLEL_TOOL_CALLS }
|
|
6811
|
+
});
|
|
6758
6812
|
this.build = build;
|
|
6759
6813
|
this.records = records;
|
|
6760
6814
|
this.warn = warn;
|
|
@@ -7338,7 +7392,13 @@ var Config5 = z6.object({
|
|
|
7338
7392
|
approvalPolicy: z6.union(CODEX_APPROVAL_POLICIES.map((policy) => z6.const(policy))),
|
|
7339
7393
|
piProvider: z6.string(),
|
|
7340
7394
|
piThinking: z6.string(),
|
|
7341
|
-
kimiBin: z6.string()
|
|
7395
|
+
kimiBin: z6.string(),
|
|
7396
|
+
// The live fields exist only on the 0.1.7 line; the 0.1.5 line carries the
|
|
7397
|
+
// selection in a settings section instead. The empty arm never runs when the
|
|
7398
|
+
// coverage job is on 0.1.7; the 0.1.5 dep set is exercised by
|
|
7399
|
+
// vitest.config.compat015.ts, which takes it.
|
|
7400
|
+
/* v8 ignore next -- legacy arm, exercised by vitest.config.compat015.ts */
|
|
7401
|
+
...LEGACY_HARNESS ? {} : { engine: LOOP_ENGINE_ENGINE_SCHEMA, showInComposer: LOOP_ENGINE_SHOW_IN_COMPOSER_SCHEMA }
|
|
7342
7402
|
});
|
|
7343
7403
|
function resolvePatchPath(config) {
|
|
7344
7404
|
if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
|
|
@@ -7538,9 +7598,7 @@ function apply(ctx, config) {
|
|
|
7538
7598
|
});
|
|
7539
7599
|
};
|
|
7540
7600
|
let savedPresetDefault;
|
|
7541
|
-
let steeredEngine;
|
|
7542
7601
|
const steerPresetDefault = (engine) => {
|
|
7543
|
-
steeredEngine = engine;
|
|
7544
7602
|
if (engine === "in-process") {
|
|
7545
7603
|
const saved = savedPresetDefault;
|
|
7546
7604
|
savedPresetDefault = void 0;
|
|
@@ -7559,7 +7617,6 @@ function apply(ctx, config) {
|
|
|
7559
7617
|
mutatePresetDefault({ op: "set", path: ["default"], value: target });
|
|
7560
7618
|
});
|
|
7561
7619
|
};
|
|
7562
|
-
const seedEngine = legacyEngine ?? "in-process";
|
|
7563
7620
|
const pluginWarn = (message) => {
|
|
7564
7621
|
ctx.logger.warn(message);
|
|
7565
7622
|
};
|
|
@@ -7620,25 +7677,47 @@ function apply(ctx, config) {
|
|
|
7620
7677
|
CLEAR_ROUTER_RETRY();
|
|
7621
7678
|
releaseRoutes();
|
|
7622
7679
|
}, "loop-engine: cleanup");
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7680
|
+
if (LEGACY_HARNESS) {
|
|
7681
|
+
let source;
|
|
7682
|
+
let steered;
|
|
7683
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
7684
|
+
const settings = settingsCtx.settings;
|
|
7685
|
+
settings.installSection(
|
|
7686
|
+
ctx,
|
|
7687
|
+
loopEngineSettingsNamespace(),
|
|
7688
|
+
LOOP_ENGINE_SETTINGS_SCHEMA,
|
|
7689
|
+
// The managed block's engine survives the upgrade as the section's seed;
|
|
7690
|
+
// a deployment with no block keeps the in-process default.
|
|
7691
|
+
{ engine: legacyEngine ?? "in-process", showInComposer: true },
|
|
7692
|
+
{
|
|
7693
|
+
setSource: (current) => {
|
|
7694
|
+
source = current;
|
|
7695
|
+
},
|
|
7696
|
+
onChange: () => {
|
|
7697
|
+
const next = source().engine;
|
|
7698
|
+
if (next === steered) return;
|
|
7699
|
+
steered = next;
|
|
7700
|
+
steerPresetDefault(next);
|
|
7701
|
+
}
|
|
7638
7702
|
}
|
|
7639
|
-
|
|
7703
|
+
);
|
|
7704
|
+
});
|
|
7705
|
+
return;
|
|
7706
|
+
}
|
|
7707
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
7708
|
+
settingsCtx.effect(
|
|
7709
|
+
() => settingsCtx.settings.configure({ auto: false }, ctx.fiber),
|
|
7710
|
+
"loop-engine: settings page policy"
|
|
7640
7711
|
);
|
|
7641
7712
|
});
|
|
7713
|
+
let observedEngine = config.engine.get();
|
|
7714
|
+
ctx.on("settings/document-updated", () => {
|
|
7715
|
+
const next = config.engine.get();
|
|
7716
|
+
if (next === observedEngine) return;
|
|
7717
|
+
observedEngine = next;
|
|
7718
|
+
steerPresetDefault(next);
|
|
7719
|
+
});
|
|
7720
|
+
steerPresetDefault(legacyEngine ?? observedEngine);
|
|
7642
7721
|
}
|
|
7643
7722
|
export {
|
|
7644
7723
|
Config5 as Config,
|
package/lib/invariant.js
CHANGED
|
@@ -12,6 +12,14 @@ var LOOP_ENGINE_SETTINGS_SCHEMA = z.object({
|
|
|
12
12
|
engine: z.union(LOOP_ENGINE_IDS.map((id) => z.const(id))).default("in-process"),
|
|
13
13
|
showInComposer: z.boolean().default(true)
|
|
14
14
|
});
|
|
15
|
+
function withVolatile(schema) {
|
|
16
|
+
const volatile = schema.volatile;
|
|
17
|
+
return volatile === void 0 ? schema : volatile.call(schema);
|
|
18
|
+
}
|
|
19
|
+
var LOOP_ENGINE_ENGINE_SCHEMA = withVolatile(
|
|
20
|
+
z.union(LOOP_ENGINE_IDS.map((id) => z.const(id))).default("in-process")
|
|
21
|
+
);
|
|
22
|
+
var LOOP_ENGINE_SHOW_IN_COMPOSER_SCHEMA = withVolatile(z.boolean().default(true));
|
|
15
23
|
|
|
16
24
|
// src/patch-manager.ts
|
|
17
25
|
var MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block --";
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Loop engine settings plugin, browser half. Registers the "Loop engine"
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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.
|
|
5
10
|
* Export discipline: packages/client/AGENTS.md.
|
|
6
11
|
* @module dsh-loop-engine/client
|
|
7
12
|
*/
|
|
@@ -17,12 +22,17 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
17
22
|
'settings.loop-engine': LoopEngineKey;
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
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
|
+
*/
|
|
22
32
|
export declare const inject: string[];
|
|
23
33
|
/**
|
|
24
34
|
* Register the Loop engine section once the `settings.section` declaration is
|
|
25
|
-
* on the ledger and bind its store to
|
|
35
|
+
* on the ledger and bind its store to whichever settings client is present.
|
|
26
36
|
* @param ctx - client root context.
|
|
27
37
|
*/
|
|
28
38
|
export declare function apply(ctx: ClientContext): void;
|
|
@@ -82,8 +82,9 @@ export type { SessionEngineReport } from '../agent-preset-ids.ts';
|
|
|
82
82
|
/**
|
|
83
83
|
* The standard seat members this half reads off a session-scope slot.
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
85
|
+
* The identity, plus the one live fact the turn-status row needs: whether the
|
|
86
|
+
* session is mid-turn. The engine itself is no longer read from a framework
|
|
87
|
+
* hook, it is asked of the host through this plugin's own Remote.
|
|
87
88
|
*/
|
|
88
89
|
export interface SessionSeat {
|
|
89
90
|
/**
|
|
@@ -92,6 +93,23 @@ export interface SessionSeat {
|
|
|
92
93
|
* (which only ever renders with a session) shows nothing.
|
|
93
94
|
*/
|
|
94
95
|
sessionId?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Selector hook over the current Session's snapshot (`SessionStandardProps` of
|
|
98
|
+
* every session-scoped seat). Optional for the same reason as the identity: a
|
|
99
|
+
* caller with no such seat — or a test rendering one directly — has none, and
|
|
100
|
+
* the turn-status gate is then left alone rather than guessed.
|
|
101
|
+
*/
|
|
102
|
+
useSession?: SnapshotSelector;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The selector-hook shape this half needs from `useSession`: a read of one field
|
|
106
|
+
* of the Session snapshot, with no equality function of its own.
|
|
107
|
+
*/
|
|
108
|
+
export type SnapshotSelector = <S>(select: (snapshot: SessionRunningState) => S) => S;
|
|
109
|
+
/** The one field of the Session snapshot the turn-status row reads. */
|
|
110
|
+
export interface SessionRunningState {
|
|
111
|
+
/** Whether a turn is in flight for this session. */
|
|
112
|
+
readonly running: boolean;
|
|
95
113
|
}
|
|
96
114
|
/** Cordis service key AND wire namespace of the plugin's own Remote. */
|
|
97
115
|
export declare const LOOP_ENGINE_REMOTE_NAMESPACE = "loopEngine";
|
|
@@ -239,14 +257,30 @@ export declare function engineSwitchReady(report: SessionEngineReport | undefine
|
|
|
239
257
|
* this page.
|
|
240
258
|
*/
|
|
241
259
|
export declare function switchNeedsReload(current: SessionEngine, target: LoopEngineId): boolean;
|
|
242
|
-
/**
|
|
260
|
+
/** One boundary schema of a wire field (a zod schema, as the Gateway holds it). */
|
|
261
|
+
interface RemoteSchema {
|
|
262
|
+
parse(value: unknown): unknown;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* A codec of one wire field (harness: `TypertCodec`, strict branch).
|
|
266
|
+
*
|
|
267
|
+
* The two harness generations read DIFFERENT members of this object and each
|
|
268
|
+
* validates only its own: the 0.1.5 line's registry and client parse through
|
|
269
|
+
* `schema`, while the 0.1.7 line's registry requires a `create()` factory and
|
|
270
|
+
* parses through `codec.create()` (`typert: … strict codec has no create()
|
|
271
|
+
* factory`). One build serves both, so the codec carries both fields.
|
|
272
|
+
*/
|
|
243
273
|
interface RemoteCodec {
|
|
244
274
|
readonly mode: 'strict';
|
|
245
275
|
/** Stable identity of the field's declared type, for diagnostics. */
|
|
246
276
|
readonly typeSymbol: string;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
277
|
+
/** The 0.1.5 line's read: the boundary schema itself. */
|
|
278
|
+
readonly schema: RemoteSchema;
|
|
279
|
+
/**
|
|
280
|
+
* The 0.1.7 line's read: a factory returning the boundary schema.
|
|
281
|
+
* @returns the same boundary schema.
|
|
282
|
+
*/
|
|
283
|
+
readonly create: () => RemoteSchema;
|
|
250
284
|
}
|
|
251
285
|
/** One ordered business parameter (harness: `InvocationParameterDescriptor`). */
|
|
252
286
|
interface RemoteParameter {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Loop engine selection store:
|
|
3
|
-
*
|
|
2
|
+
* Loop engine selection store: a settings transport is the channel, and the
|
|
3
|
+
* store publishes a render-safe snapshot plus the write path. The transport is
|
|
4
|
+
* abstracted so one store follows either generation's client service — the
|
|
5
|
+
* 0.1.7 `ConfigForm` for the plugin's own profile entry, or the 0.1.5
|
|
6
|
+
* `SettingsScope` for its `agent-loop-engine` settings section.
|
|
4
7
|
* @module dsh-loop-engine/client/store
|
|
5
8
|
*/
|
|
6
|
-
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
7
9
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
8
10
|
import type { LoopEngineId } from '../agent-preset-ids.ts';
|
|
11
|
+
import type { LoopEngineSettings } from '../settings.ts';
|
|
9
12
|
/** State rendered by the loop engine section. */
|
|
10
13
|
export interface LoopEngineState {
|
|
11
14
|
status: 'loading' | 'ready' | 'unavailable' | 'saving';
|
|
@@ -14,44 +17,76 @@ export interface LoopEngineState {
|
|
|
14
17
|
writable: boolean;
|
|
15
18
|
error: string | null;
|
|
16
19
|
}
|
|
17
|
-
/**
|
|
20
|
+
/** The render-relevant slice of a settings snapshot, shared by both generations' transports. */
|
|
21
|
+
export interface LoopEngineSettingsSnapshot {
|
|
22
|
+
status: 'loading' | 'ready' | 'unavailable';
|
|
23
|
+
value: LoopEngineSettings | undefined;
|
|
24
|
+
writable: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The settings channel the store follows. Satisfied by the 0.1.7 `ConfigForm`
|
|
28
|
+
* and by the 0.1.5 `SettingsScope` (through a thin adapter in `./index.ts`); the
|
|
29
|
+
* write's boolean is optional because the 0.1.5 scope reports acceptance only
|
|
30
|
+
* through the snapshot it leaves behind.
|
|
31
|
+
*/
|
|
32
|
+
export interface LoopEngineSettingsTransport {
|
|
33
|
+
/** @returns the current sync snapshot. */
|
|
34
|
+
getSnapshot(): LoopEngineSettingsSnapshot;
|
|
35
|
+
/**
|
|
36
|
+
* Observe snapshot replacements.
|
|
37
|
+
* @param listener - invoked after each snapshot change.
|
|
38
|
+
* @returns the disposer removing the listener.
|
|
39
|
+
*/
|
|
40
|
+
subscribe(listener: () => void): () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Persist one field.
|
|
43
|
+
* @param field - the field to write.
|
|
44
|
+
* @param value - the value to write.
|
|
45
|
+
* @returns whether the write was accepted, when the transport reports it.
|
|
46
|
+
*/
|
|
47
|
+
set(field: 'engine' | 'showInComposer', value: unknown): Promise<boolean | void>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Narrow a wire section to the stored engine id and display toggle; an invalid
|
|
51
|
+
* one reads default. Used by the 0.1.5 `settingsScope.bind` decoder, whose scope
|
|
52
|
+
* receives raw wire sections.
|
|
53
|
+
* @param section - the wire section value.
|
|
54
|
+
* @returns the decoded selection, or `undefined` when the section is unusable.
|
|
55
|
+
*/
|
|
18
56
|
export declare function decodeLoopEngine(section: unknown): {
|
|
19
57
|
engine: LoopEngineId;
|
|
20
58
|
showInComposer: boolean;
|
|
21
59
|
} | undefined;
|
|
22
60
|
/** Coordinates the settings-backed loop engine selection. */
|
|
23
61
|
export declare class LoopEngineStore {
|
|
24
|
-
private readonly
|
|
62
|
+
private readonly transport;
|
|
25
63
|
/** uSES-safe state source shared by the registered settings section. */
|
|
26
64
|
readonly store: SnapshotStore<LoopEngineState>;
|
|
27
65
|
private following;
|
|
28
66
|
private saving;
|
|
29
67
|
/**
|
|
30
|
-
* @param
|
|
68
|
+
* @param transport - the settings channel the store follows.
|
|
31
69
|
*/
|
|
32
|
-
constructor(
|
|
33
|
-
|
|
34
|
-
showInComposer: boolean;
|
|
35
|
-
}>);
|
|
36
|
-
/** Begin following the bound scope and publish its current answer. */
|
|
70
|
+
constructor(transport: LoopEngineSettingsTransport);
|
|
71
|
+
/** Begin following the transport and publish its current answer. */
|
|
37
72
|
load(): void;
|
|
38
73
|
/**
|
|
39
|
-
* Persist the selected engine. Success is
|
|
40
|
-
*
|
|
74
|
+
* Persist the selected engine. Success is the transport's accepted answer
|
|
75
|
+
* (when it reports one) checked against the snapshot the write left behind,
|
|
76
|
+
* so a refused write reports error after its recovery.
|
|
41
77
|
* @param engine - the engine to select for future Agent turns.
|
|
42
78
|
* @returns whether the write landed.
|
|
43
79
|
*/
|
|
44
80
|
setEngine(engine: LoopEngineId): Promise<boolean>;
|
|
45
81
|
/**
|
|
46
|
-
* Persist whether the composer shows the engine picker.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the page — the toggle only changes composer visibility.
|
|
82
|
+
* Persist whether the composer shows the engine picker. Unlike
|
|
83
|
+
* {@link setEngine}, landing does not reload the page — the toggle only
|
|
84
|
+
* changes composer visibility.
|
|
50
85
|
* @param show - whether the chat page composer reveals the engine picker.
|
|
51
86
|
* @returns whether the write landed.
|
|
52
87
|
*/
|
|
53
88
|
setShowInComposer(show: boolean): Promise<boolean>;
|
|
54
|
-
/** Stop following the
|
|
89
|
+
/** Stop following the transport. */
|
|
55
90
|
dispose(): void;
|
|
56
91
|
private derive;
|
|
57
92
|
}
|