dsh-loop-engine 0.1.7-rc1 → 0.1.7-rc3
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 +2 -1
- package/README.zh.md +2 -1
- package/lib/client.js +6 -2
- package/lib/index.js +75 -25
- package/lib/types/client/turn-status.d.ts +20 -10
- package/lib/types/driver-core/hosted-tool-vocabulary.d.ts +12 -7
- package/lib/types/engine-claude/agent.d.ts +13 -0
- package/package.json +24 -15
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ A **published** dsh needs no extra setup. Booting the harness from its **source
|
|
|
22
22
|
|
|
23
23
|
## Version compatibility
|
|
24
24
|
|
|
25
|
-
dsh-loop-engine is versioned **in lockstep with the harness it targets**: `<harness version>-rcN`. From `0.1.7-rc1` on, **one release serves two harness generations**: the 0.1.5 line (`>=0.1.5-rc.1 <0.1.6-0`, all sharing the old settings API) and
|
|
25
|
+
dsh-loop-engine is versioned **in lockstep with the harness it targets**: `<harness version>-rcN`. From `0.1.7-rc1` on, **one release serves two harness generations**: the 0.1.5 line (`>=0.1.5-rc.1 <0.1.6-0`, all sharing the old settings API) and the 0.1.7 line (`>=0.1.7-rc.1 <0.1.8-0`, whose `rc.1` and `rc.2` are both verified). It detects the running generation at load and takes the matching code path, so a single published package installs onto either. Every harness package it consumes declares that union range in `peerDependencies`; a harness outside it fails loudly at boot or session resume. Within a covered line a new `rc` needs no plugin release unless the API surfaces it depends on moved — `docs/compatibility.md` §1 lists the surfaces and the diff command that decides it. `0.1.5-rc3` … `0.1.5-rc5` target harness `0.1.5-rc.2`; `0.1.5-rc1`/`0.1.5-rc2` target `0.1.5-rc.1`; `1.0.0-rc8` … `1.0.0-rc15` target `0.1.2-rc.1`; `1.0.0-rc7` and earlier target `0.1.1-rc.2`.
|
|
26
26
|
|
|
27
27
|
### Requirements
|
|
28
28
|
|
|
@@ -65,6 +65,7 @@ dsh-loop-engine is versioned **in lockstep with the harness it targets**: `<harn
|
|
|
65
65
|
- [docs/per-session-engine.md](docs/per-session-engine.md) — the full user-visible behavior of per-session engines.
|
|
66
66
|
- [docs/source-checkout.md](docs/source-checkout.md) — the `file:` shims a source-launched harness needs.
|
|
67
67
|
- [docs/architecture.md](docs/architecture.md) — plugin core: the single factory slot, the managed block, routing, per-session engine facts, the provider route.
|
|
68
|
+
- [docs/compatibility.md](docs/compatibility.md) — **start here when the harness upgrades**: every generation-dependent code site, the four techniques for spanning them, and the checklist for adding the next generation.
|
|
68
69
|
- [docs/driver-core.md](docs/driver-core.md) — the shared driver infrastructure.
|
|
69
70
|
- [docs/engine-claude.md](docs/engine-claude.md), [docs/engine-codex.md](docs/engine-codex.md), [docs/engine-kimi.md](docs/engine-kimi.md), [docs/engine-pi.md](docs/engine-pi.md) — per-engine internals.
|
|
70
71
|
- [docs/optimization-backlog.md](docs/optimization-backlog.md) — known issues and the optimization list.
|
package/README.zh.md
CHANGED
|
@@ -22,7 +22,7 @@ dsh plugin --profile web add dsh-loop-engine
|
|
|
22
22
|
|
|
23
23
|
## 版本兼容
|
|
24
24
|
|
|
25
|
-
`dsh-loop-engine` 与它针对的 harness **同版本对齐**:`<harness version>-rcN`。自 `0.1.7-rc1` 起,**一个发布版本同时服务两代 harness**:0.1.5 线(`>=0.1.5-rc.1 <0.1.6-0`,三段 `rc` 共用同一套旧 settings API)与
|
|
25
|
+
`dsh-loop-engine` 与它针对的 harness **同版本对齐**:`<harness version>-rcN`。自 `0.1.7-rc1` 起,**一个发布版本同时服务两代 harness**:0.1.5 线(`>=0.1.5-rc.1 <0.1.6-0`,三段 `rc` 共用同一套旧 settings API)与 0.1.7 线(`>=0.1.7-rc.1 <0.1.8-0`,其中 `rc.1` 与 `rc.2` 都已核对)。它在加载期探测当前是哪一代、走对应分支,所以同一份已发布产物在两代上都能安装运行。它消费的每个 harness 包都在 `peerDependencies` 里声明这个并集范围;超出范围会在启动或会话恢复时响亮地失败。在被覆盖的那条线里,新的 `rc` **不需要**插件跟版——除非它依赖的 API 面动了;`docs/compatibility.md` §1 列出了这些面与"要不要改代码"的那条判定命令。`0.1.5-rc3` … `0.1.5-rc5` 针对 harness `0.1.5-rc.2`;`0.1.5-rc1`/`0.1.5-rc2` 针对 `0.1.5-rc.1`;`1.0.0-rc8` … `1.0.0-rc15` 针对 `0.1.2-rc.1`;`1.0.0-rc7` 及更早针对 `0.1.1-rc.2`。
|
|
26
26
|
|
|
27
27
|
### 环境要求
|
|
28
28
|
|
|
@@ -65,6 +65,7 @@ dsh plugin --profile web add dsh-loop-engine
|
|
|
65
65
|
- [docs/per-session-engine.md](docs/per-session-engine.md)——按会话选引擎的用户可见行为全解。
|
|
66
66
|
- [docs/source-checkout.md](docs/source-checkout.md)——源码启动 harness 时需要的那套 `file:` shim。
|
|
67
67
|
- [docs/architecture.md](docs/architecture.md)——插件核心:唯一 factory 槽位、受管理块、路由、逐会话引擎事实、provider 路由。
|
|
68
|
+
- [docs/compatibility.md](docs/compatibility.md)——**harness 升级时先看这篇**:全部随代际分叉的代码位置、跨代的四种手法,以及新增一代的清单。
|
|
68
69
|
- [docs/driver-core.md](docs/driver-core.md)——共享驱动基础设施。
|
|
69
70
|
- [docs/engine-claude.md](docs/engine-claude.md)、[docs/engine-codex.md](docs/engine-codex.md)、[docs/engine-kimi.md](docs/engine-kimi.md)、[docs/engine-pi.md](docs/engine-pi.md)——逐引擎内部实现。
|
|
70
71
|
- [docs/optimization-backlog.md](docs/optimization-backlog.md)——已知问题与优化清单。
|
package/lib/client.js
CHANGED
|
@@ -270,8 +270,12 @@ var PLUGIN_ID = "dsh-loop-engine";
|
|
|
270
270
|
var ROW_SELECTORS = [
|
|
271
271
|
/** 0.1.5 line: the turn-status row, whose class name ends `_turnStatus`. */
|
|
272
272
|
'[class$="_turnStatus"]',
|
|
273
|
-
/**
|
|
274
|
-
|
|
273
|
+
/**
|
|
274
|
+
* 0.1.7 line: the LIVE turn-process button — `disabled` is ui-chat's "this
|
|
275
|
+
* turn cannot be collapsed", which is true only while the turn is open — and
|
|
276
|
+
* its status text is the label span.
|
|
277
|
+
*/
|
|
278
|
+
'button[data-turn-process]:disabled [class$="_label"]'
|
|
275
279
|
];
|
|
276
280
|
var sheetFor = (ROW) => {
|
|
277
281
|
const GATE = `html[${ENGINE_ATTR}][${RUNNING_ATTR}]`;
|
package/lib/index.js
CHANGED
|
@@ -1289,6 +1289,19 @@ var ClaudeCodeAgent = class {
|
|
|
1289
1289
|
* opens the next step (see {@link beginSegment}).
|
|
1290
1290
|
*/
|
|
1291
1291
|
stepSettledTools = 0;
|
|
1292
|
+
/**
|
|
1293
|
+
* Calls already given a `tool/result` in the current step. The Claude Agent
|
|
1294
|
+
* SDK delivers the same tool result in two `user` messages ~57 ms apart, and
|
|
1295
|
+
* Session V4 keys its pending-tool map by call id alone — it deletes the call
|
|
1296
|
+
* on the first result, so a second result for the same call is refused
|
|
1297
|
+
* (`tool/result <id> has no advertised tool lifecycle`) and the whole session
|
|
1298
|
+
* log fails to load. The durable contract is exactly one result per call, so
|
|
1299
|
+
* a repeat is dropped rather than appended. Reset per step (see {@link step}),
|
|
1300
|
+
* never on an internal segment rotation: call ids are unique for the life of
|
|
1301
|
+
* the session, so retaining the marks only widens duplicate suppression and
|
|
1302
|
+
* can never hide a legitimate result.
|
|
1303
|
+
*/
|
|
1304
|
+
settledCalls = /* @__PURE__ */ new Set();
|
|
1292
1305
|
/**
|
|
1293
1306
|
* Whether the current query has rotated into a second step. The query-total
|
|
1294
1307
|
* usage record is only meaningful while one step holds the whole query.
|
|
@@ -1616,6 +1629,7 @@ var ClaudeCodeAgent = class {
|
|
|
1616
1629
|
const { abort: { signal } } = phase;
|
|
1617
1630
|
signal.throwIfAborted();
|
|
1618
1631
|
this.stepSettledTools = 0;
|
|
1632
|
+
this.settledCalls.clear();
|
|
1619
1633
|
this.rotated = false;
|
|
1620
1634
|
const cwd = this.session.header.cwd;
|
|
1621
1635
|
if (cwd === void 0 || cwd.length === 0) {
|
|
@@ -1711,6 +1725,15 @@ var ClaudeCodeAgent = class {
|
|
|
1711
1725
|
const synthesized = [...reasoningByIndex.entries()].sort((a, b) => a[0] - b[0]).map(([, text]) => ({ type: "reasoning", text }));
|
|
1712
1726
|
content = [...synthesized, ...content];
|
|
1713
1727
|
}
|
|
1728
|
+
const normalizedCalls = new Map(mapped.toolCalls.map((call) => [
|
|
1729
|
+
call.callId,
|
|
1730
|
+
normalizeHostedToolCall("claude-code", call.name, call.arguments)
|
|
1731
|
+
]));
|
|
1732
|
+
content = content.map((block) => {
|
|
1733
|
+
if (block.type !== "tool-call") return block;
|
|
1734
|
+
const normalized = normalizedCalls.get(block.id);
|
|
1735
|
+
return { ...block, name: normalized.name, arguments: normalized.arguments };
|
|
1736
|
+
});
|
|
1714
1737
|
if (content.length > 0) {
|
|
1715
1738
|
this.beginSegment(phase);
|
|
1716
1739
|
reasoningByIndex.clear();
|
|
@@ -1736,7 +1759,7 @@ var ClaudeCodeAgent = class {
|
|
|
1736
1759
|
}
|
|
1737
1760
|
}
|
|
1738
1761
|
for (const call of mapped.toolCalls) {
|
|
1739
|
-
const normalized =
|
|
1762
|
+
const normalized = normalizedCalls.get(call.callId);
|
|
1740
1763
|
this.session.append("tool/call", {
|
|
1741
1764
|
turn: phase.turn,
|
|
1742
1765
|
step: phase.step,
|
|
@@ -1751,6 +1774,9 @@ var ClaudeCodeAgent = class {
|
|
|
1751
1774
|
}
|
|
1752
1775
|
case "user": {
|
|
1753
1776
|
for (const result of mapToolResults(message.message)) {
|
|
1777
|
+
const callId = result.source.callId;
|
|
1778
|
+
if (this.settledCalls.has(callId)) continue;
|
|
1779
|
+
this.settledCalls.add(callId);
|
|
1754
1780
|
this.session.append("tool/result", { turn: phase.turn, step: phase.step, message: result }, { surfaceOp: "append" });
|
|
1755
1781
|
this.stepSettledTools += 1;
|
|
1756
1782
|
}
|
|
@@ -3493,9 +3519,9 @@ var CodexAgent = class {
|
|
|
3493
3519
|
} else if (item.type === "commandExecution") {
|
|
3494
3520
|
this.beginSegment(phase);
|
|
3495
3521
|
const activity = mapCommandExecution(item);
|
|
3496
|
-
foldToolCall(activity.call);
|
|
3497
|
-
flushHeld();
|
|
3498
3522
|
const normalizedCall = normalizeHostedToolCall("codex", activity.call.name, activity.call.arguments);
|
|
3523
|
+
foldToolCall({ ...activity.call, name: normalizedCall.name, arguments: normalizedCall.arguments });
|
|
3524
|
+
flushHeld();
|
|
3499
3525
|
this.session.append("tool/call", {
|
|
3500
3526
|
turn: phase.turn,
|
|
3501
3527
|
step: phase.step,
|
|
@@ -3508,9 +3534,9 @@ var CodexAgent = class {
|
|
|
3508
3534
|
} else if (item.type === "fileChange") {
|
|
3509
3535
|
this.beginSegment(phase);
|
|
3510
3536
|
const activity = mapFileChange(item);
|
|
3511
|
-
foldToolCall(activity.call);
|
|
3512
|
-
flushHeld();
|
|
3513
3537
|
const normalizedCall = normalizeHostedToolCall("codex", activity.call.name, activity.call.arguments);
|
|
3538
|
+
foldToolCall({ ...activity.call, name: normalizedCall.name, arguments: normalizedCall.arguments });
|
|
3539
|
+
flushHeld();
|
|
3514
3540
|
this.session.append("tool/call", {
|
|
3515
3541
|
turn: phase.turn,
|
|
3516
3542
|
step: phase.step,
|
|
@@ -3523,9 +3549,9 @@ var CodexAgent = class {
|
|
|
3523
3549
|
} else if (item.type === "mcpToolCall") {
|
|
3524
3550
|
this.beginSegment(phase);
|
|
3525
3551
|
const activity = mapMcpToolCall(item);
|
|
3526
|
-
foldToolCall(activity.call);
|
|
3527
|
-
flushHeld();
|
|
3528
3552
|
const normalizedCall = normalizeHostedToolCall("codex", activity.call.name, activity.call.arguments);
|
|
3553
|
+
foldToolCall({ ...activity.call, name: normalizedCall.name, arguments: normalizedCall.arguments });
|
|
3554
|
+
flushHeld();
|
|
3529
3555
|
this.session.append("tool/call", {
|
|
3530
3556
|
turn: phase.turn,
|
|
3531
3557
|
step: phase.step,
|
|
@@ -4401,6 +4427,7 @@ var PiAgent = class {
|
|
|
4401
4427
|
const startedReasoning = /* @__PURE__ */ new Set();
|
|
4402
4428
|
const thinkingByIndex = /* @__PURE__ */ new Map();
|
|
4403
4429
|
const emittedToolCalls = /* @__PURE__ */ new Set();
|
|
4430
|
+
const settledToolCalls = /* @__PURE__ */ new Set();
|
|
4404
4431
|
let pendingToolCalls = [];
|
|
4405
4432
|
let pendingCallLog = [];
|
|
4406
4433
|
let callsToLog = [];
|
|
@@ -4428,13 +4455,12 @@ var PiAgent = class {
|
|
|
4428
4455
|
}
|
|
4429
4456
|
held = void 0;
|
|
4430
4457
|
for (const call of callsToLog) {
|
|
4431
|
-
const normalized = normalizeHostedToolCall("pi", call.name, call.arguments);
|
|
4432
4458
|
this.session.append("tool/call", {
|
|
4433
4459
|
turn: phase.turn,
|
|
4434
4460
|
step: phase.step,
|
|
4435
4461
|
callId: ToolCallId5(call.callId),
|
|
4436
|
-
name:
|
|
4437
|
-
arguments:
|
|
4462
|
+
name: call.name,
|
|
4463
|
+
arguments: call.arguments
|
|
4438
4464
|
});
|
|
4439
4465
|
}
|
|
4440
4466
|
callsToLog = [];
|
|
@@ -4453,8 +4479,9 @@ var PiAgent = class {
|
|
|
4453
4479
|
if (emittedToolCalls.has(callId)) return;
|
|
4454
4480
|
emittedToolCalls.add(callId);
|
|
4455
4481
|
const argumentsValue = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments ?? {});
|
|
4456
|
-
|
|
4457
|
-
|
|
4482
|
+
const normalized = normalizeHostedToolCall("pi", name2, argumentsValue);
|
|
4483
|
+
pendingToolCalls.push({ type: "tool-call", id: ToolCallId5(callId), name: normalized.name, arguments: normalized.arguments });
|
|
4484
|
+
pendingCallLog.push({ callId, name: normalized.name, arguments: normalized.arguments });
|
|
4458
4485
|
};
|
|
4459
4486
|
const ensureToolCallOwner = () => {
|
|
4460
4487
|
if (pendingToolCalls.length === 0) return;
|
|
@@ -4555,12 +4582,15 @@ var PiAgent = class {
|
|
|
4555
4582
|
case "tool_execution_end":
|
|
4556
4583
|
emitToolCall(event.toolCallId, event.toolName, void 0);
|
|
4557
4584
|
ensureToolCallOwner();
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4585
|
+
if (!settledToolCalls.has(event.toolCallId)) {
|
|
4586
|
+
settledToolCalls.add(event.toolCallId);
|
|
4587
|
+
this.session.append("tool/result", {
|
|
4588
|
+
turn: phase.turn,
|
|
4589
|
+
step: phase.step,
|
|
4590
|
+
message: mapToolResult({ toolCallId: event.toolCallId, result: event.result, isError: event.isError })
|
|
4591
|
+
}, { surfaceOp: "append" });
|
|
4592
|
+
this.stepSettledTools += 1;
|
|
4593
|
+
}
|
|
4564
4594
|
break;
|
|
4565
4595
|
case "turn_end": {
|
|
4566
4596
|
if (!assistantFlushed && event.message !== void 0) {
|
|
@@ -4570,6 +4600,8 @@ var PiAgent = class {
|
|
|
4570
4600
|
flushHeld(lastUsage);
|
|
4571
4601
|
assistantFlushed = true;
|
|
4572
4602
|
for (const toolResult2 of event.toolResults ?? []) {
|
|
4603
|
+
if (settledToolCalls.has(toolResult2.toolCallId)) continue;
|
|
4604
|
+
settledToolCalls.add(toolResult2.toolCallId);
|
|
4573
4605
|
ensureToolCallOwner();
|
|
4574
4606
|
this.appendToolResult(phase.turn, phase.step, toolResult2);
|
|
4575
4607
|
this.stepSettledTools += 1;
|
|
@@ -5638,10 +5670,13 @@ var KimiAgent = class {
|
|
|
5638
5670
|
flushSegment(phase) {
|
|
5639
5671
|
const calls = this.segmentCalls;
|
|
5640
5672
|
this.segmentCalls = [];
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5673
|
+
const normalized = calls.map((call) => {
|
|
5674
|
+
const projection = normalizeHostedToolCall("kimi", call.name, call.arguments);
|
|
5675
|
+
return { callId: call.callId, name: projection.name, arguments: projection.arguments };
|
|
5676
|
+
});
|
|
5677
|
+
this.flushAssistant(phase, normalized);
|
|
5678
|
+
for (const call of normalized) {
|
|
5679
|
+
this.session.append("tool/call", { turn: phase.turn, step: phase.step, callId: ToolCallId7(call.callId), name: call.name, arguments: call.arguments });
|
|
5645
5680
|
}
|
|
5646
5681
|
}
|
|
5647
5682
|
/**
|
|
@@ -7495,6 +7530,8 @@ function apply(ctx, config) {
|
|
|
7495
7530
|
const AGENT_PRESETS_NS = "agent-presets";
|
|
7496
7531
|
const PRESET_DEFAULT_ATTEMPTS = 30;
|
|
7497
7532
|
const PRESET_DEFAULT_RETRY_MS = 100;
|
|
7533
|
+
const PRESET_SOURCE_ATTEMPTS = 20;
|
|
7534
|
+
const PRESET_SOURCE_RETRY_MS = 100;
|
|
7498
7535
|
const ROUTE_ATTEMPTS = 30;
|
|
7499
7536
|
const ROUTE_RETRY_MS = 100;
|
|
7500
7537
|
const ROUTER_ATTEMPTS = 40;
|
|
@@ -7570,11 +7607,24 @@ function apply(ctx, config) {
|
|
|
7570
7607
|
}
|
|
7571
7608
|
return void 0;
|
|
7572
7609
|
}
|
|
7573
|
-
const settled = authoring ??=
|
|
7574
|
-
|
|
7610
|
+
const settled = authoring ??= (async () => {
|
|
7611
|
+
let lastError;
|
|
7612
|
+
for (let attempt2 = 0; attempt2 <= PRESET_SOURCE_ATTEMPTS; attempt2 += 1) {
|
|
7613
|
+
try {
|
|
7614
|
+
return await ensureEnginePresets(resolveDshHome2(), presets);
|
|
7615
|
+
} catch (error) {
|
|
7616
|
+
lastError = error;
|
|
7617
|
+
if (attempt2 < PRESET_SOURCE_ATTEMPTS) {
|
|
7618
|
+
await new Promise((resolve5) => {
|
|
7619
|
+
setTimeout(resolve5, PRESET_SOURCE_RETRY_MS);
|
|
7620
|
+
});
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
ctx.logger.error(`loop-engine: engine preset authoring failed: ${String(lastError)}`);
|
|
7575
7625
|
authoring = void 0;
|
|
7576
7626
|
return false;
|
|
7577
|
-
});
|
|
7627
|
+
})();
|
|
7578
7628
|
return { presets, settled };
|
|
7579
7629
|
};
|
|
7580
7630
|
let authoring;
|
|
@@ -47,16 +47,26 @@
|
|
|
47
47
|
* paints by it, and a leftover attribute is inert on a page whose turn-status row
|
|
48
48
|
* is not rendered.
|
|
49
49
|
*
|
|
50
|
-
* The paint is
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
50
|
+
* The paint is gated TWICE, and both gates are needed on the 0.1.7 line.
|
|
51
|
+
*
|
|
52
|
+
* First, on the session being MID-TURN, through a second document-level
|
|
53
|
+
* attribute ({@link RUNNING_ATTR}, written from the same reflection). On the
|
|
54
|
+
* 0.1.5 line the row only exists while a turn is in flight, so the engine
|
|
55
|
+
* attribute alone was enough; on the 0.1.7 line that same button stays on screen
|
|
56
|
+
* after the turn ends, as the collapsed summary that reads "用时 4秒" — an
|
|
57
|
+
* engine-only gate leaves the glyph and the sweep animating on a finished turn,
|
|
58
|
+
* which is not what the live line means. The gate is a surface's own answer
|
|
59
|
+
* (`session.running`, which every session-scoped seat receives), so a surface
|
|
60
|
+
* that cannot answer passes nothing and leaves it alone rather than guessing.
|
|
61
|
+
*
|
|
62
|
+
* Second, and this is the one a session-level gate cannot express: on the
|
|
63
|
+
* 0.1.7 line EVERY turn's row stays on screen, so while turn N runs, turns
|
|
64
|
+
* 1..N-1 are still there. A session-wide gate turns the whole sheet on for all
|
|
65
|
+
* of them — the finished rows would wear the glyph again for as long as any
|
|
66
|
+
* later turn ran. The row selector therefore carries `:disabled` (see
|
|
67
|
+
* {@link ROW_SELECTORS}), which is how ui-chat marks the live row. The two
|
|
68
|
+
* gates answer different questions — "is a turn in flight for this session" and
|
|
69
|
+
* "is this row the one it belongs to" — and neither alone is enough.
|
|
60
70
|
*
|
|
61
71
|
* Three facts about the harness markup make that safe and specific:
|
|
62
72
|
* - the row has one stable handle per harness generation — an
|
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
* dsh's own tool names and argument shapes, while a hosted engine names its
|
|
10
10
|
* tools differently: Claude's `Write`/`Edit`/`Read`/`Bash`, Codex's
|
|
11
11
|
* `apply_patch`/`command_execution`, Kimi's title-cased `Bash`, Pi's
|
|
12
|
-
* `path`-keyed arguments. This module
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* `path`-keyed arguments. This module projects a hosted call onto dsh's
|
|
13
|
+
* vocabulary, and each driver feeds the projected values to BOTH writers — the
|
|
14
|
+
* durable `tool/call` event and the matching `tool-call` block in the assistant
|
|
15
|
+
* message that advertised it. Session V4 pairs the two by `id` and rejects a
|
|
16
|
+
* log whose block and event disagree on `name` or `arguments`, so the two sides
|
|
17
|
+
* must be byte-identical and both carry dsh's spelling. That spelling also lets
|
|
18
|
+
* an assistant message replayed into an in-process turn name a tool dsh's
|
|
19
|
+
* registry actually has (`bash`, not `Bash`).
|
|
16
20
|
*
|
|
17
21
|
* Only lossless projections are applied. A call whose arguments do not carry
|
|
18
22
|
* the dsh fields is passed through unchanged and simply renders as a generic
|
|
@@ -51,9 +55,10 @@ declare module '@deepseek-ai/dsh-session/types' {
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
54
|
-
* Project one hosted tool call onto dsh's `tool/call` vocabulary.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
58
|
+
* Project one hosted tool call onto dsh's `tool/call` vocabulary. A driver
|
|
59
|
+
* calls this once per call and writes the SAME name and arguments to both the
|
|
60
|
+
* assistant message's `tool-call` block and the durable `tool/call` event, so
|
|
61
|
+
* Session V4's id-and-payload pairing holds.
|
|
57
62
|
* @param engine - the hosted engine the call came from.
|
|
58
63
|
* @param name - the engine's own tool name.
|
|
59
64
|
* @param argumentsJson - the engine's arguments JSON, verbatim.
|
|
@@ -52,6 +52,19 @@ export declare class ClaudeCodeAgent implements Agent {
|
|
|
52
52
|
* opens the next step (see {@link beginSegment}).
|
|
53
53
|
*/
|
|
54
54
|
private stepSettledTools;
|
|
55
|
+
/**
|
|
56
|
+
* Calls already given a `tool/result` in the current step. The Claude Agent
|
|
57
|
+
* SDK delivers the same tool result in two `user` messages ~57 ms apart, and
|
|
58
|
+
* Session V4 keys its pending-tool map by call id alone — it deletes the call
|
|
59
|
+
* on the first result, so a second result for the same call is refused
|
|
60
|
+
* (`tool/result <id> has no advertised tool lifecycle`) and the whole session
|
|
61
|
+
* log fails to load. The durable contract is exactly one result per call, so
|
|
62
|
+
* a repeat is dropped rather than appended. Reset per step (see {@link step}),
|
|
63
|
+
* never on an internal segment rotation: call ids are unique for the life of
|
|
64
|
+
* the session, so retaining the marks only widens duplicate suppression and
|
|
65
|
+
* can never hide a legitimate result.
|
|
66
|
+
*/
|
|
67
|
+
private readonly settledCalls;
|
|
55
68
|
/**
|
|
56
69
|
* Whether the current query has rotated into a second step. The query-total
|
|
57
70
|
* usage record is only meaningful while one step holds the whole query.
|
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": "0.1.7-
|
|
4
|
+
"version": "0.1.7-rc3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -82,20 +82,29 @@
|
|
|
82
82
|
"@deepseek-ai/dsh-typert-protocol": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@deepseek-ai/dsh-agent
|
|
86
|
-
"@deepseek-ai/dsh-
|
|
87
|
-
"@deepseek-ai/dsh-
|
|
88
|
-
"@deepseek-ai/dsh-
|
|
89
|
-
"@deepseek-ai/dsh-
|
|
90
|
-
"@deepseek-ai/dsh-
|
|
91
|
-
"@deepseek-ai/dsh-
|
|
92
|
-
"@deepseek-ai/dsh-
|
|
93
|
-
"@deepseek-ai/dsh-
|
|
94
|
-
"@deepseek-ai/dsh-
|
|
95
|
-
"@deepseek-ai/dsh-
|
|
96
|
-
"@deepseek-ai/dsh-
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
85
|
+
"@deepseek-ai/dsh-agent": "0.1.7-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.7-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-invariants": "0.1.7-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-llm": "0.1.7-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-scope": "0.1.7-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-session": "0.1.7-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.7-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-settings": "0.1.7-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-subprocess": "0.1.7-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-timeout": "0.1.7-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-attachment": "0.1.7-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
|
|
101
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2",
|
|
102
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-home-paths": "0.1.7-rc.2",
|
|
104
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.7-rc.2",
|
|
105
|
+
"@deepseek-ai/dsh-subprocess-local": "0.1.7-rc.2",
|
|
106
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-rc.2",
|
|
107
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
|
|
99
108
|
"@types/node": "^22.20.0",
|
|
100
109
|
"@types/react": "^19.0.0",
|
|
101
110
|
"@vitest/coverage-v8": "^4.1.8",
|