dsh-loop-engine 1.0.0-rc13 → 1.0.0-rc15
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 +3 -6
- package/README.zh.md +13 -0
- package/lib/index.js +203 -94
- package/lib/types/engine-pi/agent.d.ts +14 -1
- package/lib/types/engine-pi/loop.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,13 +34,10 @@ matched — a mismatch fails loudly at boot or session resume:
|
|
|
34
34
|
|
|
35
35
|
| dsh-loop-engine | Requires harness |
|
|
36
36
|
|---|---|
|
|
37
|
-
| 1.0.0-
|
|
38
|
-
| 1.0.0-
|
|
39
|
-
| 1.0.0-rc11 | **0.1.2-rc.1** |
|
|
40
|
-
| 1.0.0-rc10 | **0.1.2-rc.1** |
|
|
41
|
-
| 1.0.0-rc7 | 0.1.1-rc.2 |
|
|
37
|
+
| 1.0.0-rc8 and later | **0.1.2-rc.1** |
|
|
38
|
+
| 1.0.0-rc7 and earlier | 0.1.1-rc.2 |
|
|
42
39
|
|
|
43
|
-
- **1.0.0-
|
|
40
|
+
- **1.0.0-rc8 and later is not compatible with harness 0.1.1-rc.2 or earlier.** It uses
|
|
44
41
|
the 0.1.2 persistence seam (`SessionPersistence.create` / `open` +
|
|
45
42
|
`SessionHandle`), the `installSection` settings API, `ToolCallId`, and
|
|
46
43
|
`Session.snapshotEvents()` — none of which exist in older harnesses.
|
package/README.zh.md
CHANGED
|
@@ -21,6 +21,19 @@ dsh plugin --profile web add dsh-loop-engine
|
|
|
21
21
|
- 使用 Pi 引擎时需要以 `pi` 要求的方式完成认证(其自身的 `~/.pi/agent/auth.json`,或提供方的 API-key 环境变量,如 `ANTHROPIC_API_KEY`)。
|
|
22
22
|
- 使用 Kimi Code 引擎时需要本机已安装并登录 `kimi` CLI(例如 `kimi login`),且在 `PATH` 上(或在组合条目里用 `kimiBin` 固定为绝对路径)。
|
|
23
23
|
|
|
24
|
+
## 版本兼容
|
|
25
|
+
|
|
26
|
+
`dsh-loop-engine` 与 harness **独立**版本(`1.0.0-rcN`),但通过 `peerDependencies` 绑定到特定 harness 版本;两者必须匹配——不匹配会在启动或会话恢复时响亮地失败:
|
|
27
|
+
|
|
28
|
+
| dsh-loop-engine | 需要 harness |
|
|
29
|
+
|---|---|
|
|
30
|
+
| 1.0.0-rc8 及以后 | **0.1.2-rc.1** |
|
|
31
|
+
| 1.0.0-rc7 及更早 | 0.1.1-rc.2 |
|
|
32
|
+
|
|
33
|
+
- **1.0.0-rc8 及以后不兼容 harness 0.1.1-rc.2 或更早版本。** 它使用 0.1.2 的持久化 seam(`SessionPersistence.create` / `open` + `SessionHandle`)、`installSection` settings API、`ToolCallId` 与 `Session.snapshotEvents()` —— 这些在更老的 harness 里都不存在。
|
|
34
|
+
- 要在更老的 harness 上使用本插件,请安装与之匹配的 loop-engine 版本(例如 harness 0.1.1-rc.2 用 `npm i dsh-loop-engine@1.0.0-rc7`)。
|
|
35
|
+
- 每个 tag 的 GitHub Release 正文会写明它针对的 harness 版本。
|
|
36
|
+
|
|
24
37
|
## 使用方法
|
|
25
38
|
|
|
26
39
|
1. 在 **Settings → Loop engine** 选择引擎——`in-process`(默认)、`claude-code`、`codex`、`pi` 或 `kimi`——然后重启 `dsh web`。
|
package/lib/index.js
CHANGED
|
@@ -1347,6 +1347,11 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1347
1347
|
const handle = await persistence.create(session.header, {
|
|
1348
1348
|
inheritedEventCount: session.inheritedEventCount,
|
|
1349
1349
|
...signal === void 0 ? {} : { signal }
|
|
1350
|
+
}).catch((error) => {
|
|
1351
|
+
if (error instanceof TypeError && error.message.includes("SessionLogOffset")) {
|
|
1352
|
+
return persistence.create(session.header, session.inheritedEventCount);
|
|
1353
|
+
}
|
|
1354
|
+
throw error;
|
|
1350
1355
|
});
|
|
1351
1356
|
return { handle, storedCount: 0 };
|
|
1352
1357
|
}
|
|
@@ -1395,26 +1400,38 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1395
1400
|
let preparation;
|
|
1396
1401
|
try {
|
|
1397
1402
|
try {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1403
|
+
if (typeof persistence.open === "function") {
|
|
1404
|
+
handle = await raceAbortCall(
|
|
1405
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
1406
|
+
fused,
|
|
1407
|
+
id,
|
|
1408
|
+
(abandoned) => {
|
|
1409
|
+
void abandoned.close();
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
1413
|
+
fused.throwIfAborted();
|
|
1414
|
+
const closers = interruptedTurnClosers(persisted);
|
|
1415
|
+
if (closers.length > 0) await handle.append(closers);
|
|
1416
|
+
preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(id, {
|
|
1417
|
+
seed: [...persisted, ...closers],
|
|
1418
|
+
meta: structuredClone(handle.header),
|
|
1419
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
1420
|
+
seedSource: "persistence"
|
|
1421
|
+
}));
|
|
1422
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
1423
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
1424
|
+
} else {
|
|
1425
|
+
const publishedApi = persistence;
|
|
1426
|
+
preparation = await raceAbortCall(
|
|
1427
|
+
() => publishedApi.prepare(id, fused),
|
|
1428
|
+
fused,
|
|
1429
|
+
id,
|
|
1430
|
+
(abandoned) => {
|
|
1431
|
+
abandoned?.[Symbol.dispose]?.();
|
|
1432
|
+
}
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1418
1435
|
} finally {
|
|
1419
1436
|
await unfollowOwner();
|
|
1420
1437
|
}
|
|
@@ -2573,6 +2590,11 @@ var CodexLoop = class extends Service2 {
|
|
|
2573
2590
|
const handle = await persistence.create(session.header, {
|
|
2574
2591
|
inheritedEventCount: session.inheritedEventCount,
|
|
2575
2592
|
...signal === void 0 ? {} : { signal }
|
|
2593
|
+
}).catch((error) => {
|
|
2594
|
+
if (error instanceof TypeError && error.message.includes("SessionLogOffset")) {
|
|
2595
|
+
return persistence.create(session.header, session.inheritedEventCount);
|
|
2596
|
+
}
|
|
2597
|
+
throw error;
|
|
2576
2598
|
});
|
|
2577
2599
|
return { handle, storedCount: 0 };
|
|
2578
2600
|
}
|
|
@@ -2621,26 +2643,38 @@ var CodexLoop = class extends Service2 {
|
|
|
2621
2643
|
let preparation;
|
|
2622
2644
|
try {
|
|
2623
2645
|
try {
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2646
|
+
if (typeof persistence.open === "function") {
|
|
2647
|
+
handle = await raceAbortCall(
|
|
2648
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
2649
|
+
fused,
|
|
2650
|
+
id,
|
|
2651
|
+
(abandoned) => {
|
|
2652
|
+
void abandoned.close();
|
|
2653
|
+
}
|
|
2654
|
+
);
|
|
2655
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
2656
|
+
fused.throwIfAborted();
|
|
2657
|
+
const closers = interruptedTurnClosers2(persisted);
|
|
2658
|
+
if (closers.length > 0) await handle.append(closers);
|
|
2659
|
+
preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(id, {
|
|
2660
|
+
seed: [...persisted, ...closers],
|
|
2661
|
+
meta: structuredClone(handle.header),
|
|
2662
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
2663
|
+
seedSource: "persistence"
|
|
2664
|
+
}));
|
|
2665
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
2666
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
2667
|
+
} else {
|
|
2668
|
+
const publishedApi = persistence;
|
|
2669
|
+
preparation = await raceAbortCall(
|
|
2670
|
+
() => publishedApi.prepare(id, fused),
|
|
2671
|
+
fused,
|
|
2672
|
+
id,
|
|
2673
|
+
(abandoned) => {
|
|
2674
|
+
abandoned?.[Symbol.dispose]?.();
|
|
2675
|
+
}
|
|
2676
|
+
);
|
|
2677
|
+
}
|
|
2644
2678
|
} finally {
|
|
2645
2679
|
await unfollowOwner();
|
|
2646
2680
|
}
|
|
@@ -2946,7 +2980,7 @@ function specsEqual(a, b) {
|
|
|
2946
2980
|
return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
|
|
2947
2981
|
}
|
|
2948
2982
|
var PiAgent = class {
|
|
2949
|
-
constructor(loopCtx, id, options, session, config, spawn4, bin) {
|
|
2983
|
+
constructor(loopCtx, id, options, session, config, spawn4, bin, catalog) {
|
|
2950
2984
|
this.loopCtx = loopCtx;
|
|
2951
2985
|
this.id = id;
|
|
2952
2986
|
this.options = options;
|
|
@@ -2954,6 +2988,7 @@ var PiAgent = class {
|
|
|
2954
2988
|
this.config = config;
|
|
2955
2989
|
this.spawn = spawn4;
|
|
2956
2990
|
this.bin = bin;
|
|
2991
|
+
this.catalog = catalog;
|
|
2957
2992
|
this.dispatch = agentEvents3(loopCtx, this);
|
|
2958
2993
|
this.inbox = new Inbox3(session, {
|
|
2959
2994
|
inserted: (message) => {
|
|
@@ -2982,6 +3017,7 @@ var PiAgent = class {
|
|
|
2982
3017
|
config;
|
|
2983
3018
|
spawn;
|
|
2984
3019
|
bin;
|
|
3020
|
+
catalog;
|
|
2985
3021
|
inbox;
|
|
2986
3022
|
phase;
|
|
2987
3023
|
activityDone = Promise.resolve();
|
|
@@ -3303,9 +3339,28 @@ var PiAgent = class {
|
|
|
3303
3339
|
return void 0;
|
|
3304
3340
|
}
|
|
3305
3341
|
/** Build the `pi --mode rpc` argv/cwd/env for one step's child process. */
|
|
3342
|
+
/**
|
|
3343
|
+
* Resolve the `--model` for the RPC child. The session-selected model (last
|
|
3344
|
+
* `model/selection` event) is honored only when it is one of pi's discovered
|
|
3345
|
+
* models; an unknown harness model (e.g. another provider's model such as
|
|
3346
|
+
* `anyai-v1`, which pi cannot serve) is dropped so the child falls back to pi's
|
|
3347
|
+
* own default instead of exiting with "Model ... not found". An empty catalog
|
|
3348
|
+
* (probe not concluded) keeps the candidate, matching prior behavior.
|
|
3349
|
+
*/
|
|
3350
|
+
pickModel() {
|
|
3351
|
+
const candidate = this.dynamicModel() ?? this.config.model;
|
|
3352
|
+
if (candidate === void 0) return void 0;
|
|
3353
|
+
if (this.catalog.entries.length > 0) {
|
|
3354
|
+
const known = this.catalog.entries.some(
|
|
3355
|
+
(entry) => entry.model === candidate || `${entry.provider}/${entry.model}` === candidate
|
|
3356
|
+
);
|
|
3357
|
+
if (!known) return void 0;
|
|
3358
|
+
}
|
|
3359
|
+
return candidate;
|
|
3360
|
+
}
|
|
3306
3361
|
spawnSpec(cwd) {
|
|
3307
3362
|
const argv = [];
|
|
3308
|
-
const model = this.
|
|
3363
|
+
const model = this.pickModel();
|
|
3309
3364
|
if (this.config.provider !== void 0) argv.push("--provider", this.config.provider);
|
|
3310
3365
|
if (model !== void 0 && this.config.thinkingLevel !== void 0) {
|
|
3311
3366
|
argv.push("--model", `${model}:${this.config.thinkingLevel}`);
|
|
@@ -3548,6 +3603,8 @@ var PiAgent = class {
|
|
|
3548
3603
|
} finally {
|
|
3549
3604
|
signal.removeEventListener("abort", cancel);
|
|
3550
3605
|
controller.abort();
|
|
3606
|
+
this.rpc?.dispose();
|
|
3607
|
+
this.rpc = void 0;
|
|
3551
3608
|
}
|
|
3552
3609
|
}
|
|
3553
3610
|
/** Append one Pi tool result to the durable log as a `tool/result` message. */
|
|
@@ -3574,24 +3631,30 @@ function waitForExit(child) {
|
|
|
3574
3631
|
);
|
|
3575
3632
|
});
|
|
3576
3633
|
}
|
|
3577
|
-
function
|
|
3634
|
+
function collectOutput(child) {
|
|
3578
3635
|
return new Promise((resolve5) => {
|
|
3579
|
-
|
|
3580
|
-
if (child.stdout.on === void 0) {
|
|
3636
|
+
if (child.stdout.on === void 0 || child.stderr.on === void 0) {
|
|
3581
3637
|
resolve5("");
|
|
3582
3638
|
return;
|
|
3583
3639
|
}
|
|
3640
|
+
let text = "";
|
|
3641
|
+
let pending = 2;
|
|
3642
|
+
const finish = () => {
|
|
3643
|
+
pending -= 1;
|
|
3644
|
+
if (pending === 0) resolve5(text);
|
|
3645
|
+
};
|
|
3584
3646
|
const out = child.stdout;
|
|
3647
|
+
const err = child.stderr;
|
|
3585
3648
|
out.setEncoding("utf8");
|
|
3586
3649
|
out.on("data", (data) => {
|
|
3587
3650
|
text += String(data);
|
|
3588
3651
|
});
|
|
3589
|
-
out.on("
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
resolve5(text);
|
|
3652
|
+
out.on("close", finish);
|
|
3653
|
+
err.setEncoding("utf8");
|
|
3654
|
+
err.on("data", (data) => {
|
|
3655
|
+
text += String(data);
|
|
3594
3656
|
});
|
|
3657
|
+
err.on("close", finish);
|
|
3595
3658
|
});
|
|
3596
3659
|
}
|
|
3597
3660
|
function parsePiModelList(output) {
|
|
@@ -3619,11 +3682,11 @@ async function probePiModels(bin, spawn4) {
|
|
|
3619
3682
|
} catch {
|
|
3620
3683
|
return [];
|
|
3621
3684
|
}
|
|
3622
|
-
const
|
|
3685
|
+
const outputPromise = collectOutput(child);
|
|
3623
3686
|
const exitCode = await waitForExit(child);
|
|
3624
3687
|
if (exitCode !== 0) return [];
|
|
3625
|
-
const
|
|
3626
|
-
return parsePiModelList(
|
|
3688
|
+
const output = await outputPromise;
|
|
3689
|
+
return parsePiModelList(output);
|
|
3627
3690
|
}
|
|
3628
3691
|
|
|
3629
3692
|
// src/engine-pi/loop.ts
|
|
@@ -3696,12 +3759,15 @@ var PiLoop = class extends Service3 {
|
|
|
3696
3759
|
spawn;
|
|
3697
3760
|
/** Resolved Pi CLI entrypoint; `argv[0]` of every Pi RPC child. */
|
|
3698
3761
|
bin;
|
|
3762
|
+
/** Discovered Pi model catalog, forwarded to each agent so it can validate the session-selected model against what pi can actually serve. */
|
|
3763
|
+
catalog;
|
|
3699
3764
|
constructor(ctx, config) {
|
|
3700
3765
|
super(ctx, "agentLoopPi");
|
|
3701
3766
|
this.config = resolveConfig3(config);
|
|
3702
3767
|
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
3703
3768
|
this.runtime = { ctx };
|
|
3704
3769
|
this.bin = piCliEntrypoint();
|
|
3770
|
+
this.catalog = config.piCatalogHolder ?? { entries: [] };
|
|
3705
3771
|
this.spawn = (spec) => fromSubprocess(this.runtime.ctx.subprocess.spawn(piSubprocessSpec(spec, PI_DISPOSE_GRACE_MS)));
|
|
3706
3772
|
const holder = config.piCatalogHolder;
|
|
3707
3773
|
if (holder !== void 0) {
|
|
@@ -3789,7 +3855,16 @@ var PiLoop = class extends Service3 {
|
|
|
3789
3855
|
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
3790
3856
|
};
|
|
3791
3857
|
try {
|
|
3792
|
-
const agent = machine = new PiAgent(
|
|
3858
|
+
const agent = machine = new PiAgent(
|
|
3859
|
+
loopCtx,
|
|
3860
|
+
id,
|
|
3861
|
+
options,
|
|
3862
|
+
session,
|
|
3863
|
+
this.config,
|
|
3864
|
+
this.spawn,
|
|
3865
|
+
this.bin,
|
|
3866
|
+
this.catalog
|
|
3867
|
+
);
|
|
3793
3868
|
machineReady.resolve();
|
|
3794
3869
|
assertLive();
|
|
3795
3870
|
return {
|
|
@@ -3906,6 +3981,11 @@ var PiLoop = class extends Service3 {
|
|
|
3906
3981
|
const handle = await persistence.create(session.header, {
|
|
3907
3982
|
inheritedEventCount: session.inheritedEventCount,
|
|
3908
3983
|
...signal === void 0 ? {} : { signal }
|
|
3984
|
+
}).catch((error) => {
|
|
3985
|
+
if (error instanceof TypeError && error.message.includes("SessionLogOffset")) {
|
|
3986
|
+
return persistence.create(session.header, session.inheritedEventCount);
|
|
3987
|
+
}
|
|
3988
|
+
throw error;
|
|
3909
3989
|
});
|
|
3910
3990
|
return { handle, storedCount: 0 };
|
|
3911
3991
|
}
|
|
@@ -3954,26 +4034,38 @@ var PiLoop = class extends Service3 {
|
|
|
3954
4034
|
let preparation;
|
|
3955
4035
|
try {
|
|
3956
4036
|
try {
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
4037
|
+
if (typeof persistence.open === "function") {
|
|
4038
|
+
handle = await raceAbortCall(
|
|
4039
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
4040
|
+
fused,
|
|
4041
|
+
id,
|
|
4042
|
+
(abandoned) => {
|
|
4043
|
+
void abandoned.close();
|
|
4044
|
+
}
|
|
4045
|
+
);
|
|
4046
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
4047
|
+
fused.throwIfAborted();
|
|
4048
|
+
const closers = interruptedTurnClosers3(persisted);
|
|
4049
|
+
if (closers.length > 0) await handle.append(closers);
|
|
4050
|
+
preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(id, {
|
|
4051
|
+
seed: [...persisted, ...closers],
|
|
4052
|
+
meta: structuredClone(handle.header),
|
|
4053
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
4054
|
+
seedSource: "persistence"
|
|
4055
|
+
}));
|
|
4056
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
4057
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
4058
|
+
} else {
|
|
4059
|
+
const publishedApi = persistence;
|
|
4060
|
+
preparation = await raceAbortCall(
|
|
4061
|
+
() => publishedApi.prepare(id, fused),
|
|
4062
|
+
fused,
|
|
4063
|
+
id,
|
|
4064
|
+
(abandoned) => {
|
|
4065
|
+
abandoned?.[Symbol.dispose]?.();
|
|
4066
|
+
}
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
3977
4069
|
} finally {
|
|
3978
4070
|
await unfollowOwner();
|
|
3979
4071
|
}
|
|
@@ -5020,6 +5112,11 @@ var KimiLoop = class extends Service4 {
|
|
|
5020
5112
|
const handle = await persistence.create(session.header, {
|
|
5021
5113
|
inheritedEventCount: session.inheritedEventCount,
|
|
5022
5114
|
...signal === void 0 ? {} : { signal }
|
|
5115
|
+
}).catch((error) => {
|
|
5116
|
+
if (error instanceof TypeError && error.message.includes("SessionLogOffset")) {
|
|
5117
|
+
return persistence.create(session.header, session.inheritedEventCount);
|
|
5118
|
+
}
|
|
5119
|
+
throw error;
|
|
5023
5120
|
});
|
|
5024
5121
|
return { handle, storedCount: 0 };
|
|
5025
5122
|
}
|
|
@@ -5068,26 +5165,38 @@ var KimiLoop = class extends Service4 {
|
|
|
5068
5165
|
let preparation;
|
|
5069
5166
|
try {
|
|
5070
5167
|
try {
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5168
|
+
if (typeof persistence.open === "function") {
|
|
5169
|
+
handle = await raceAbortCall(
|
|
5170
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
5171
|
+
fused,
|
|
5172
|
+
id,
|
|
5173
|
+
(abandoned) => {
|
|
5174
|
+
void abandoned.close();
|
|
5175
|
+
}
|
|
5176
|
+
);
|
|
5177
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
5178
|
+
fused.throwIfAborted();
|
|
5179
|
+
const closers = interruptedTurnClosers4(persisted);
|
|
5180
|
+
if (closers.length > 0) await handle.append(closers);
|
|
5181
|
+
preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(id, {
|
|
5182
|
+
seed: [...persisted, ...closers],
|
|
5183
|
+
meta: structuredClone(handle.header),
|
|
5184
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
5185
|
+
seedSource: "persistence"
|
|
5186
|
+
}));
|
|
5187
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
5188
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
5189
|
+
} else {
|
|
5190
|
+
const publishedApi = persistence;
|
|
5191
|
+
preparation = await raceAbortCall(
|
|
5192
|
+
() => publishedApi.prepare(id, fused),
|
|
5193
|
+
fused,
|
|
5194
|
+
id,
|
|
5195
|
+
(abandoned) => {
|
|
5196
|
+
abandoned?.[Symbol.dispose]?.();
|
|
5197
|
+
}
|
|
5198
|
+
);
|
|
5199
|
+
}
|
|
5091
5200
|
} finally {
|
|
5092
5201
|
await unfollowOwner();
|
|
5093
5202
|
}
|
|
@@ -16,6 +16,7 @@ import type { Scope } from '@deepseek-ai/dsh-scope';
|
|
|
16
16
|
import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
|
|
17
17
|
import type { Context } from '@deepseek-ai/cordis';
|
|
18
18
|
import type { ResolvedConfig } from './types.ts';
|
|
19
|
+
import type { PiModelEntry } from './probe.ts';
|
|
19
20
|
import { type PiSpawnCapability } from './rpc/client.ts';
|
|
20
21
|
/** Provider route label used for logged header snapshots and message provenance. */
|
|
21
22
|
export declare const PROVIDER = "pi";
|
|
@@ -28,6 +29,7 @@ export declare class PiAgent implements Agent {
|
|
|
28
29
|
private readonly config;
|
|
29
30
|
private readonly spawn;
|
|
30
31
|
private readonly bin;
|
|
32
|
+
private readonly catalog;
|
|
31
33
|
readonly inbox: Inbox;
|
|
32
34
|
private phase;
|
|
33
35
|
private activityDone;
|
|
@@ -42,7 +44,9 @@ export declare class PiAgent implements Agent {
|
|
|
42
44
|
private rpc;
|
|
43
45
|
/** The spawn spec the cached client was built from; a change forces a respawn. */
|
|
44
46
|
private lastSpec;
|
|
45
|
-
constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: PiSpawnCapability, bin: string
|
|
47
|
+
constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: PiSpawnCapability, bin: string, catalog: {
|
|
48
|
+
readonly entries: readonly PiModelEntry[];
|
|
49
|
+
});
|
|
46
50
|
/** Return the cached RPC client, respawning when the spec or process changed. */
|
|
47
51
|
private rpcClient;
|
|
48
52
|
get status(): AgentStatus;
|
|
@@ -118,6 +122,15 @@ export declare class PiAgent implements Agent {
|
|
|
118
122
|
*/
|
|
119
123
|
private dynamicModel;
|
|
120
124
|
/** Build the `pi --mode rpc` argv/cwd/env for one step's child process. */
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the `--model` for the RPC child. The session-selected model (last
|
|
127
|
+
* `model/selection` event) is honored only when it is one of pi's discovered
|
|
128
|
+
* models; an unknown harness model (e.g. another provider's model such as
|
|
129
|
+
* `anyai-v1`, which pi cannot serve) is dropped so the child falls back to pi's
|
|
130
|
+
* own default instead of exiting with "Model ... not found". An empty catalog
|
|
131
|
+
* (probe not concluded) keeps the candidate, matching prior behavior.
|
|
132
|
+
*/
|
|
133
|
+
private pickModel;
|
|
121
134
|
private spawnSpec;
|
|
122
135
|
/**
|
|
123
136
|
* Run one Pi RPC query for the current step and map its event stream into the
|
|
@@ -70,6 +70,8 @@ export declare class PiLoop extends Service implements AgentFactory {
|
|
|
70
70
|
readonly spawn: (spec: PiSpawnSpec) => PiProcess;
|
|
71
71
|
/** Resolved Pi CLI entrypoint; `argv[0]` of every Pi RPC child. */
|
|
72
72
|
readonly bin: string;
|
|
73
|
+
/** Discovered Pi model catalog, forwarded to each agent so it can validate the session-selected model against what pi can actually serve. */
|
|
74
|
+
private readonly catalog;
|
|
73
75
|
constructor(ctx: Context, config: Config);
|
|
74
76
|
/**
|
|
75
77
|
* Construct the driver, scope, and one memoized reverse teardown for a new
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-loop-engine",
|
|
3
3
|
"description": "Web-switchable agent loop engine selection for the DeepSeek Harness - out-of-tree plugin (Claude Code / Codex / Pi / Kimi Code drivers) maintained by @kuun993, zero main-repo changes",
|
|
4
|
-
"version": "1.0.0-
|
|
4
|
+
"version": "1.0.0-rc15",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|