dsh-loop-engine 0.1.5-rc1 → 0.1.5-rc2
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 +88 -5
- package/README.zh.md +74 -0
- package/lib/client.js +158 -0
- package/lib/index.js +1020 -1486
- package/lib/invariant.js +6 -3
- package/lib/types/client/turn-status.d.ts +43 -0
- package/lib/types/driver-core/agents-md-skill-provider.d.ts +72 -0
- package/lib/types/driver-core/hosted-loop-factory.d.ts +119 -0
- package/lib/types/driver-core/ownership.d.ts +6 -6
- package/lib/types/driver-core/permission-knobs.d.ts +1 -1
- package/lib/types/driver-core/prompt.d.ts +1 -1
- package/lib/types/driver-core/skill-inject.d.ts +2 -2
- package/lib/types/engine-claude/agent.d.ts +22 -0
- package/lib/types/engine-claude/loop.d.ts +7 -56
- package/lib/types/engine-claude/mapping.d.ts +28 -3
- package/lib/types/engine-codex/agent.d.ts +46 -3
- package/lib/types/engine-codex/appserver/client.d.ts +16 -0
- package/lib/types/engine-codex/loop.d.ts +7 -56
- package/lib/types/engine-codex/permission.d.ts +100 -6
- package/lib/types/engine-codex/skills.d.ts +7 -8
- package/lib/types/engine-kimi/acp/client.d.ts +33 -3
- package/lib/types/engine-kimi/acp/mapping.d.ts +36 -7
- package/lib/types/engine-kimi/acp/types.d.ts +41 -2
- package/lib/types/engine-kimi/agent.d.ts +65 -8
- package/lib/types/engine-kimi/loop.d.ts +7 -56
- package/lib/types/engine-kimi/skills.d.ts +9 -14
- package/lib/types/engine-pi/agent.d.ts +23 -4
- package/lib/types/engine-pi/loop.d.ts +7 -56
- package/lib/types/engine-pi/permission.d.ts +16 -12
- package/lib/types/engine-pi/skills.d.ts +6 -14
- package/lib/types/patch-manager.d.ts +7 -0
- package/lib/types/provider-route.d.ts +16 -7
- package/lib/types/settings.d.ts +4 -1
- package/lib/types/skills.d.ts +6 -14
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -46,22 +46,25 @@ var __callDispose = (stack, error, hasError) => {
|
|
|
46
46
|
|
|
47
47
|
// src/index.ts
|
|
48
48
|
import { mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
|
|
49
|
-
import { mkdir as mkdir2, readFile as
|
|
49
|
+
import { mkdir as mkdir2, readFile as readFile5, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
50
50
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
51
|
-
import { dirname as
|
|
51
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
52
52
|
import z6 from "@deepseek-ai/schemastery";
|
|
53
53
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
54
54
|
|
|
55
55
|
// src/engine-claude/loop.ts
|
|
56
|
-
import { Service } from "@deepseek-ai/cordis";
|
|
57
56
|
import z from "@deepseek-ai/schemastery";
|
|
58
|
-
import { emitAgentEvent } from "@deepseek-ai/dsh-agent";
|
|
59
57
|
import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
|
|
60
|
-
import { interruptedTurnClosers, SessionLogOffset, SessionPreparation } from "@deepseek-ai/dsh-session";
|
|
61
58
|
|
|
62
59
|
// src/engine-claude/agent.ts
|
|
63
60
|
import { agentEvents } from "@deepseek-ai/dsh-agent";
|
|
64
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
AssistantStreamAccumulator as AssistantStreamAccumulator2,
|
|
63
|
+
LlmError,
|
|
64
|
+
createAssistantMessage,
|
|
65
|
+
createUserMessage,
|
|
66
|
+
errorChain
|
|
67
|
+
} from "@deepseek-ai/dsh-llm";
|
|
65
68
|
import { createScope } from "@deepseek-ai/dsh-scope";
|
|
66
69
|
import { canonicalHeader } from "@deepseek-ai/dsh-session";
|
|
67
70
|
import { query as officialQuery } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -148,12 +151,16 @@ function toolResultContent(content) {
|
|
|
148
151
|
}
|
|
149
152
|
function mapUsage(usage) {
|
|
150
153
|
return {
|
|
151
|
-
inputTokens: usage.input_tokens,
|
|
152
|
-
outputTokens: usage.output_tokens,
|
|
154
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
155
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
153
156
|
...usage.cache_read_input_tokens == null ? {} : { cacheReadTokens: usage.cache_read_input_tokens },
|
|
154
157
|
...usage.cache_creation_input_tokens == null ? {} : { cacheWriteTokens: usage.cache_creation_input_tokens }
|
|
155
158
|
};
|
|
156
159
|
}
|
|
160
|
+
function meaningfulUsage(usage) {
|
|
161
|
+
if (usage === void 0) return void 0;
|
|
162
|
+
return usage.inputTokens !== 0 || usage.outputTokens !== 0 || (usage.cacheReadTokens ?? 0) !== 0 || (usage.cacheWriteTokens ?? 0) !== 0 ? usage : void 0;
|
|
163
|
+
}
|
|
157
164
|
function mapStreamEvent(event, toolCalls) {
|
|
158
165
|
switch (event.type) {
|
|
159
166
|
case "content_block_start": {
|
|
@@ -845,6 +852,17 @@ var ClaudeCodeAgent = class {
|
|
|
845
852
|
requestHeaderLogged = false;
|
|
846
853
|
/** Agent-lifecycle-local counter naming each streamed attempt. */
|
|
847
854
|
streamAttempts = 0;
|
|
855
|
+
/**
|
|
856
|
+
* Tool results logged into the currently open step. A result means the
|
|
857
|
+
* segment that requested the call is finished, so the next assistant content
|
|
858
|
+
* opens the next step (see {@link beginSegment}).
|
|
859
|
+
*/
|
|
860
|
+
stepSettledTools = 0;
|
|
861
|
+
/**
|
|
862
|
+
* Whether the current query has rotated into a second step. The query-total
|
|
863
|
+
* usage record is only meaningful while one step holds the whole query.
|
|
864
|
+
*/
|
|
865
|
+
rotated = false;
|
|
848
866
|
get status() {
|
|
849
867
|
return this.phase.kind === "idle" || this.phase.kind === "maintenance" ? "idle" : "running";
|
|
850
868
|
}
|
|
@@ -1091,7 +1109,7 @@ var ClaudeCodeAgent = class {
|
|
|
1091
1109
|
const stepEnd = await this.step();
|
|
1092
1110
|
if (turnEnds === null) turnEnds = stepEnd;
|
|
1093
1111
|
} finally {
|
|
1094
|
-
this.session.append("step/end", { turn, step });
|
|
1112
|
+
this.session.append("step/end", { turn, step: phase.step });
|
|
1095
1113
|
}
|
|
1096
1114
|
signal.throwIfAborted();
|
|
1097
1115
|
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
@@ -1124,6 +1142,24 @@ var ClaudeCodeAgent = class {
|
|
|
1124
1142
|
phase.step = 0;
|
|
1125
1143
|
return true;
|
|
1126
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Rotate to the next step when the segment that ran a tool has finished, so
|
|
1147
|
+
* each assistant segment lands in its own step.
|
|
1148
|
+
*
|
|
1149
|
+
* Called as new assistant content begins. A step holding a settled tool
|
|
1150
|
+
* result means the previous segment is complete, and the content about to be
|
|
1151
|
+
* written belongs to the next one. Rotating here (rather than when a call is
|
|
1152
|
+
* announced) keeps calls announced together — one model turn — in one step.
|
|
1153
|
+
* @param phase - the running phase carrying the open step's position.
|
|
1154
|
+
*/
|
|
1155
|
+
beginSegment(phase) {
|
|
1156
|
+
if (this.stepSettledTools === 0) return;
|
|
1157
|
+
this.session.append("step/end", { turn: phase.turn, step: phase.step });
|
|
1158
|
+
phase.step += 1;
|
|
1159
|
+
this.session.append("step/start", { turn: phase.turn, step: phase.step });
|
|
1160
|
+
this.stepSettledTools = 0;
|
|
1161
|
+
this.rotated = true;
|
|
1162
|
+
}
|
|
1127
1163
|
/** Model label recorded in the request header for one lifecycle. */
|
|
1128
1164
|
modelLabel() {
|
|
1129
1165
|
return this.config.model ?? NATIVE_MODEL_LABEL;
|
|
@@ -1144,8 +1180,11 @@ var ClaudeCodeAgent = class {
|
|
|
1144
1180
|
/** Run one Claude Code query for the current step and map its transcript into the session log. */
|
|
1145
1181
|
async step() {
|
|
1146
1182
|
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
1147
|
-
const
|
|
1183
|
+
const phase = this.phase;
|
|
1184
|
+
const { abort: { signal } } = phase;
|
|
1148
1185
|
signal.throwIfAborted();
|
|
1186
|
+
this.stepSettledTools = 0;
|
|
1187
|
+
this.rotated = false;
|
|
1149
1188
|
const cwd = this.session.header.cwd;
|
|
1150
1189
|
if (cwd === void 0 || cwd.length === 0) {
|
|
1151
1190
|
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
@@ -1186,8 +1225,8 @@ var ClaudeCodeAgent = class {
|
|
|
1186
1225
|
live = new DriverAssistantStream(
|
|
1187
1226
|
this.id,
|
|
1188
1227
|
++this.streamAttempts,
|
|
1189
|
-
turn,
|
|
1190
|
-
step,
|
|
1228
|
+
phase.turn,
|
|
1229
|
+
phase.step,
|
|
1191
1230
|
(frame2) => this.dispatch.emit("agent/assistant-stream", { frame: frame2 })
|
|
1192
1231
|
);
|
|
1193
1232
|
live.start();
|
|
@@ -1197,11 +1236,17 @@ var ClaudeCodeAgent = class {
|
|
|
1197
1236
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
1198
1237
|
const reasoningByIndex = /* @__PURE__ */ new Map();
|
|
1199
1238
|
let pendingUsage;
|
|
1239
|
+
let requestUsage;
|
|
1200
1240
|
signal.throwIfAborted();
|
|
1201
1241
|
for await (const message of query) {
|
|
1202
1242
|
signal.throwIfAborted();
|
|
1203
1243
|
switch (message.type) {
|
|
1204
1244
|
case "stream_event": {
|
|
1245
|
+
const event = message.event;
|
|
1246
|
+
if (event.type === "message_start") requestUsage = void 0;
|
|
1247
|
+
else if (event.type === "message_delta" && event.usage !== void 0) {
|
|
1248
|
+
requestUsage = meaningfulUsage(mapUsage(event.usage));
|
|
1249
|
+
}
|
|
1205
1250
|
for (const chunk of mapStreamEvent(message.event, toolCalls)) {
|
|
1206
1251
|
currentStream().push(chunk);
|
|
1207
1252
|
if (chunk.type === "reasoning-delta") {
|
|
@@ -1219,7 +1264,7 @@ var ClaudeCodeAgent = class {
|
|
|
1219
1264
|
reasoning.forEach((block, index) => {
|
|
1220
1265
|
reasoningByIndex.set(index, block.text);
|
|
1221
1266
|
});
|
|
1222
|
-
pendingUsage = mapped.usage;
|
|
1267
|
+
pendingUsage = meaningfulUsage(mapped.usage) ?? requestUsage;
|
|
1223
1268
|
break;
|
|
1224
1269
|
}
|
|
1225
1270
|
let content = mapped.content;
|
|
@@ -1228,13 +1273,14 @@ var ClaudeCodeAgent = class {
|
|
|
1228
1273
|
content = [...synthesized, ...content];
|
|
1229
1274
|
}
|
|
1230
1275
|
if (content.length > 0) {
|
|
1276
|
+
this.beginSegment(phase);
|
|
1231
1277
|
reasoningByIndex.clear();
|
|
1232
|
-
const usage = mapped.usage ?? pendingUsage;
|
|
1278
|
+
const usage = meaningfulUsage(mapped.usage) ?? requestUsage ?? pendingUsage;
|
|
1233
1279
|
pendingUsage = void 0;
|
|
1234
1280
|
const attempt = live;
|
|
1235
1281
|
const data = {
|
|
1236
|
-
turn,
|
|
1237
|
-
step,
|
|
1282
|
+
turn: phase.turn,
|
|
1283
|
+
step: phase.step,
|
|
1238
1284
|
message: createAssistantMessage({
|
|
1239
1285
|
content,
|
|
1240
1286
|
source: { provider: PROVIDER, model: mapped.model }
|
|
@@ -1252,8 +1298,8 @@ var ClaudeCodeAgent = class {
|
|
|
1252
1298
|
}
|
|
1253
1299
|
for (const call of mapped.toolCalls) {
|
|
1254
1300
|
this.session.append("tool/call", {
|
|
1255
|
-
turn,
|
|
1256
|
-
step,
|
|
1301
|
+
turn: phase.turn,
|
|
1302
|
+
step: phase.step,
|
|
1257
1303
|
callId: call.callId,
|
|
1258
1304
|
name: call.name,
|
|
1259
1305
|
arguments: call.arguments
|
|
@@ -1263,7 +1309,8 @@ var ClaudeCodeAgent = class {
|
|
|
1263
1309
|
}
|
|
1264
1310
|
case "user": {
|
|
1265
1311
|
for (const result of mapToolResults(message.message)) {
|
|
1266
|
-
this.session.append("tool/result", { turn, step, message: result }, { surfaceOp: "append" });
|
|
1312
|
+
this.session.append("tool/result", { turn: phase.turn, step: phase.step, message: result }, { surfaceOp: "append" });
|
|
1313
|
+
this.stepSettledTools += 1;
|
|
1267
1314
|
}
|
|
1268
1315
|
break;
|
|
1269
1316
|
}
|
|
@@ -1271,10 +1318,11 @@ var ClaudeCodeAgent = class {
|
|
|
1271
1318
|
if (reasoningByIndex.size > 0) {
|
|
1272
1319
|
const trailing = [...reasoningByIndex.entries()].sort((a, b) => a[0] - b[0]).map(([, text]) => ({ type: "reasoning", text }));
|
|
1273
1320
|
reasoningByIndex.clear();
|
|
1321
|
+
this.beginSegment(phase);
|
|
1274
1322
|
const attempt = live;
|
|
1275
1323
|
const data = {
|
|
1276
|
-
turn,
|
|
1277
|
-
step,
|
|
1324
|
+
turn: phase.turn,
|
|
1325
|
+
step: phase.step,
|
|
1278
1326
|
message: createAssistantMessage({
|
|
1279
1327
|
content: trailing,
|
|
1280
1328
|
source: { provider: PROVIDER, model: NATIVE_MODEL_LABEL }
|
|
@@ -1290,6 +1338,16 @@ var ClaudeCodeAgent = class {
|
|
|
1290
1338
|
}
|
|
1291
1339
|
pendingUsage = void 0;
|
|
1292
1340
|
}
|
|
1341
|
+
const stepUsage = meaningfulUsage(mapUsage(message.usage));
|
|
1342
|
+
if (stepUsage !== void 0 && !this.rotated) {
|
|
1343
|
+
const accumulator = new AssistantStreamAccumulator2();
|
|
1344
|
+
accumulator.push({ time: Date.now(), chunk: { type: "usage", usage: stepUsage } });
|
|
1345
|
+
this.session.append("assistant/attempt", {
|
|
1346
|
+
turn: phase.turn,
|
|
1347
|
+
step: phase.step,
|
|
1348
|
+
stream: [...accumulator.snapshot()]
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1293
1351
|
if (message.subtype === "success") {
|
|
1294
1352
|
finished = true;
|
|
1295
1353
|
} else {
|
|
@@ -1318,6 +1376,11 @@ var ClaudeCodeAgent = class {
|
|
|
1318
1376
|
}
|
|
1319
1377
|
};
|
|
1320
1378
|
|
|
1379
|
+
// src/driver-core/hosted-loop-factory.ts
|
|
1380
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
1381
|
+
import { emitAgentEvent } from "@deepseek-ai/dsh-agent";
|
|
1382
|
+
import { interruptedTurnClosers, SessionLogOffset, SessionPreparation } from "@deepseek-ai/dsh-session";
|
|
1383
|
+
|
|
1321
1384
|
// src/driver-core/ownership.ts
|
|
1322
1385
|
var INACTIVE_FIBER_VALUES = {
|
|
1323
1386
|
FAILED: 3,
|
|
@@ -1406,54 +1469,23 @@ async function raceAbortCall(operation, signal, id, releaseAbandoned) {
|
|
|
1406
1469
|
}
|
|
1407
1470
|
}
|
|
1408
1471
|
|
|
1409
|
-
// src/
|
|
1410
|
-
var
|
|
1411
|
-
"dontAsk",
|
|
1412
|
-
"acceptEdits",
|
|
1413
|
-
"auto",
|
|
1414
|
-
"plan",
|
|
1415
|
-
"bypassPermissions"
|
|
1416
|
-
];
|
|
1417
|
-
var Config = z.object({
|
|
1418
|
-
permissionMode: z.union([...CLAUDE_CODE_PERMISSION_MODES]),
|
|
1419
|
-
env: z.dict(z.string()).default({}),
|
|
1420
|
-
model: z.string(),
|
|
1421
|
-
disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
|
|
1422
|
-
maxTurns: z.number().step(1).min(1)
|
|
1423
|
-
});
|
|
1424
|
-
function resolveConfig(config) {
|
|
1425
|
-
const disposeGraceMs = config.disposeGraceMs ?? DEFAULT_DISPOSE_GRACE_MS;
|
|
1426
|
-
if (!Number.isFinite(disposeGraceMs) || disposeGraceMs <= 0) {
|
|
1427
|
-
throw new Error("agent-loop-claude-code: disposeGraceMs must be a positive finite number");
|
|
1428
|
-
}
|
|
1429
|
-
if (disposeGraceMs > MAX_TIMER_DELAY_MS) {
|
|
1430
|
-
throw new Error(
|
|
1431
|
-
`agent-loop-claude-code: disposeGraceMs must be no greater than ${MAX_TIMER_DELAY_MS}`
|
|
1432
|
-
);
|
|
1433
|
-
}
|
|
1434
|
-
return {
|
|
1435
|
-
permissionMode: config.permissionMode,
|
|
1436
|
-
env: config.env ?? {},
|
|
1437
|
-
model: config.model,
|
|
1438
|
-
disposeGraceMs,
|
|
1439
|
-
maxTurns: config.maxTurns
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
var ClaudeCodeLoop = class extends Service {
|
|
1443
|
-
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
1444
|
-
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
1472
|
+
// src/driver-core/hosted-loop-factory.ts
|
|
1473
|
+
var HostedLoopFactory = class extends Service {
|
|
1445
1474
|
/** Validated configuration owned by the loop plugin. */
|
|
1446
1475
|
config;
|
|
1447
1476
|
ownership;
|
|
1448
1477
|
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
1449
1478
|
runtime;
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1479
|
+
/** Cordis service name, also the prefix of every effect label. */
|
|
1480
|
+
label;
|
|
1481
|
+
constructor(ctx, label, config) {
|
|
1482
|
+
super(ctx, label);
|
|
1483
|
+
this.label = label;
|
|
1484
|
+
this.config = config;
|
|
1453
1485
|
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
1454
1486
|
this.runtime = { ctx };
|
|
1455
|
-
ctx.effect(() => () => this.ownership.dispose(),
|
|
1456
|
-
ctx.effect(() => ctx.agents.setFactory(this),
|
|
1487
|
+
ctx.effect(() => () => this.ownership.dispose(), `${label}.transactions()`);
|
|
1488
|
+
ctx.effect(() => ctx.agents.setFactory(this), `${label}.setFactory()`);
|
|
1457
1489
|
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
1458
1490
|
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
1459
1491
|
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
@@ -1518,7 +1550,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1518
1550
|
if (disposing !== void 0) return;
|
|
1519
1551
|
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
1520
1552
|
return dispose(true);
|
|
1521
|
-
},
|
|
1553
|
+
}, `${this.label}.lifecycle(${id})`);
|
|
1522
1554
|
} catch (error) {
|
|
1523
1555
|
untrack();
|
|
1524
1556
|
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
@@ -1530,7 +1562,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1530
1562
|
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
1531
1563
|
};
|
|
1532
1564
|
try {
|
|
1533
|
-
const agent = machine =
|
|
1565
|
+
const agent = machine = this.buildAgent(loopCtx, id, options, session);
|
|
1534
1566
|
machineReady.resolve();
|
|
1535
1567
|
assertLive();
|
|
1536
1568
|
return {
|
|
@@ -1685,7 +1717,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1685
1717
|
const ownerAbort = new AbortController();
|
|
1686
1718
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
1687
1719
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
1688
|
-
},
|
|
1720
|
+
}, `${this.label}.resume-load(${id})`);
|
|
1689
1721
|
const fused = AbortSignal.any([
|
|
1690
1722
|
...options.signal === void 0 ? [] : [options.signal],
|
|
1691
1723
|
ownerAbort.signal,
|
|
@@ -1746,11 +1778,53 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1746
1778
|
}
|
|
1747
1779
|
};
|
|
1748
1780
|
|
|
1781
|
+
// src/engine-claude/loop.ts
|
|
1782
|
+
var CLAUDE_CODE_PERMISSION_MODES = [
|
|
1783
|
+
"dontAsk",
|
|
1784
|
+
"acceptEdits",
|
|
1785
|
+
"auto",
|
|
1786
|
+
"plan",
|
|
1787
|
+
"bypassPermissions"
|
|
1788
|
+
];
|
|
1789
|
+
var Config = z.object({
|
|
1790
|
+
permissionMode: z.union([...CLAUDE_CODE_PERMISSION_MODES]),
|
|
1791
|
+
env: z.dict(z.string()).default({}),
|
|
1792
|
+
model: z.string(),
|
|
1793
|
+
disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
|
|
1794
|
+
maxTurns: z.number().step(1).min(1)
|
|
1795
|
+
});
|
|
1796
|
+
function resolveConfig(config) {
|
|
1797
|
+
const disposeGraceMs = config.disposeGraceMs ?? DEFAULT_DISPOSE_GRACE_MS;
|
|
1798
|
+
if (!Number.isFinite(disposeGraceMs) || disposeGraceMs <= 0) {
|
|
1799
|
+
throw new Error("agent-loop-claude-code: disposeGraceMs must be a positive finite number");
|
|
1800
|
+
}
|
|
1801
|
+
if (disposeGraceMs > MAX_TIMER_DELAY_MS) {
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
`agent-loop-claude-code: disposeGraceMs must be no greater than ${MAX_TIMER_DELAY_MS}`
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
return {
|
|
1807
|
+
permissionMode: config.permissionMode,
|
|
1808
|
+
env: config.env ?? {},
|
|
1809
|
+
model: config.model,
|
|
1810
|
+
disposeGraceMs,
|
|
1811
|
+
maxTurns: config.maxTurns
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
var ClaudeCodeLoop = class extends HostedLoopFactory {
|
|
1815
|
+
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
1816
|
+
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
1817
|
+
constructor(ctx, config) {
|
|
1818
|
+
super(ctx, "agentLoopClaudeCode", resolveConfig(config));
|
|
1819
|
+
}
|
|
1820
|
+
/** Construct the Claude Code driver for one prepared session. */
|
|
1821
|
+
buildAgent(loopCtx, id, options, session) {
|
|
1822
|
+
return new ClaudeCodeAgent(loopCtx, id, options, session, this.config);
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1749
1826
|
// src/engine-codex/loop.ts
|
|
1750
|
-
import { Service as Service2 } from "@deepseek-ai/cordis";
|
|
1751
1827
|
import z2 from "@deepseek-ai/schemastery";
|
|
1752
|
-
import { emitAgentEvent as emitAgentEvent2 } from "@deepseek-ai/dsh-agent";
|
|
1753
|
-
import { interruptedTurnClosers as interruptedTurnClosers2, SessionLogOffset as SessionLogOffset2, SessionPreparation as SessionPreparation2 } from "@deepseek-ai/dsh-session";
|
|
1754
1828
|
|
|
1755
1829
|
// src/engine-codex/agent.ts
|
|
1756
1830
|
import { agentEvents as agentEvents2 } from "@deepseek-ai/dsh-agent";
|
|
@@ -1763,6 +1837,42 @@ var DEFAULT_CODEX_PERMISSION = {
|
|
|
1763
1837
|
sandboxMode: "read-only",
|
|
1764
1838
|
approvalPolicy: "never"
|
|
1765
1839
|
};
|
|
1840
|
+
function userInputQuestions(params) {
|
|
1841
|
+
if (typeof params !== "object" || params === null) return [];
|
|
1842
|
+
const raw = params.questions;
|
|
1843
|
+
if (!Array.isArray(raw)) return [];
|
|
1844
|
+
const questions = [];
|
|
1845
|
+
for (const entry of raw) {
|
|
1846
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
1847
|
+
const question = entry;
|
|
1848
|
+
if (typeof question.id !== "string" || question.id === "") continue;
|
|
1849
|
+
if (typeof question.question !== "string" || question.question === "") continue;
|
|
1850
|
+
const options = Array.isArray(question.options) ? question.options.flatMap((option) => {
|
|
1851
|
+
if (typeof option !== "object" || option === null) return [];
|
|
1852
|
+
const { label, description } = option;
|
|
1853
|
+
if (typeof label !== "string") return [];
|
|
1854
|
+
return [{ label, ...typeof description === "string" ? { description } : {} }];
|
|
1855
|
+
}) : [];
|
|
1856
|
+
questions.push({
|
|
1857
|
+
id: question.id,
|
|
1858
|
+
question: question.question,
|
|
1859
|
+
...typeof question.header === "string" && question.header !== "" ? { header: question.header } : {},
|
|
1860
|
+
...options.length > 0 ? { options } : {}
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
return questions;
|
|
1864
|
+
}
|
|
1865
|
+
function userInputResponse(answer) {
|
|
1866
|
+
if (answer === void 0) return { answers: {} };
|
|
1867
|
+
const answers = {};
|
|
1868
|
+
for (const item of answer.answers) {
|
|
1869
|
+
answers[item.id] = { answers: [...item.selected, ...item.custom === void 0 || item.custom === "" ? [] : [item.custom]] };
|
|
1870
|
+
}
|
|
1871
|
+
return { answers };
|
|
1872
|
+
}
|
|
1873
|
+
function elicitationResponse() {
|
|
1874
|
+
return { action: "decline" };
|
|
1875
|
+
}
|
|
1766
1876
|
function resolveSessionPermission2(events) {
|
|
1767
1877
|
if (sessionSandboxMode(events) === "danger-full-access") {
|
|
1768
1878
|
return { sandboxMode: "danger-full-access", approvalPolicy: "never" };
|
|
@@ -1772,6 +1882,68 @@ function resolveSessionPermission2(events) {
|
|
|
1772
1882
|
}
|
|
1773
1883
|
return DEFAULT_CODEX_PERMISSION;
|
|
1774
1884
|
}
|
|
1885
|
+
var REASON_INPUT_CAP2 = 200;
|
|
1886
|
+
function approvalToolName(method) {
|
|
1887
|
+
switch (method) {
|
|
1888
|
+
case "item/commandExecution/requestApproval":
|
|
1889
|
+
return "command";
|
|
1890
|
+
case "item/fileChange/requestApproval":
|
|
1891
|
+
return "file-change";
|
|
1892
|
+
case "item/permissions/requestApproval":
|
|
1893
|
+
return "permissions";
|
|
1894
|
+
default:
|
|
1895
|
+
return method;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function approvalKind(method) {
|
|
1899
|
+
switch (method) {
|
|
1900
|
+
case "item/commandExecution/requestApproval":
|
|
1901
|
+
return "command execution";
|
|
1902
|
+
case "item/fileChange/requestApproval":
|
|
1903
|
+
return "file change";
|
|
1904
|
+
case "item/permissions/requestApproval":
|
|
1905
|
+
return "permission change";
|
|
1906
|
+
default:
|
|
1907
|
+
return method;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function approvalDetail(method, params) {
|
|
1911
|
+
if (typeof params !== "object" || params === null) return String(params);
|
|
1912
|
+
const record = params;
|
|
1913
|
+
if (method === "item/commandExecution/requestApproval" && typeof record.command === "string") {
|
|
1914
|
+
return record.command;
|
|
1915
|
+
}
|
|
1916
|
+
if (typeof record.reason === "string") return record.reason;
|
|
1917
|
+
if (method === "item/fileChange/requestApproval" && typeof record.grantRoot === "string") {
|
|
1918
|
+
return record.grantRoot;
|
|
1919
|
+
}
|
|
1920
|
+
return JSON.stringify(params);
|
|
1921
|
+
}
|
|
1922
|
+
function approvalReason2(method, params) {
|
|
1923
|
+
const detail = approvalDetail(method, params);
|
|
1924
|
+
const bounded = detail.length > REASON_INPUT_CAP2 ? `${detail.slice(0, REASON_INPUT_CAP2 - 3)}...` : detail;
|
|
1925
|
+
return `Codex requests permission for ${approvalKind(method)}: ${bounded}`;
|
|
1926
|
+
}
|
|
1927
|
+
function approvalDecision(outcome) {
|
|
1928
|
+
return outcome === "allowed-once" ? "accept" : "decline";
|
|
1929
|
+
}
|
|
1930
|
+
function permissionsGrant(outcome, requested) {
|
|
1931
|
+
return outcome === "allowed-once" ? { permissions: requested, scope: "turn" } : { permissions: {}, scope: "turn" };
|
|
1932
|
+
}
|
|
1933
|
+
function resolveApprovalRequest(method, params, outcome) {
|
|
1934
|
+
switch (method) {
|
|
1935
|
+
case "item/commandExecution/requestApproval":
|
|
1936
|
+
return { result: { decision: approvalDecision(outcome) } };
|
|
1937
|
+
case "item/fileChange/requestApproval":
|
|
1938
|
+
return { result: { decision: approvalDecision(outcome) } };
|
|
1939
|
+
case "item/permissions/requestApproval": {
|
|
1940
|
+
const requested = typeof params === "object" && params !== null ? params.permissions ?? {} : {};
|
|
1941
|
+
return { result: permissionsGrant(outcome, requested) };
|
|
1942
|
+
}
|
|
1943
|
+
default:
|
|
1944
|
+
return { error: { code: -32601, message: "Method not found" } };
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1775
1947
|
|
|
1776
1948
|
// src/engine-codex/appserver/client.ts
|
|
1777
1949
|
import { spawn } from "node:child_process";
|
|
@@ -1788,6 +1960,7 @@ var AppServerClient = class _AppServerClient {
|
|
|
1788
1960
|
reqId = 1;
|
|
1789
1961
|
pending = /* @__PURE__ */ new Map();
|
|
1790
1962
|
notificationHandler;
|
|
1963
|
+
requestHandler;
|
|
1791
1964
|
stderrHandler;
|
|
1792
1965
|
disposed = false;
|
|
1793
1966
|
/** Whether this client was disposed or its server process exited. */
|
|
@@ -1827,6 +2000,10 @@ var AppServerClient = class _AppServerClient {
|
|
|
1827
2000
|
onNotification(handler) {
|
|
1828
2001
|
this.notificationHandler = handler;
|
|
1829
2002
|
}
|
|
2003
|
+
/** Set the handler for server-initiated requests (e.g. approvals). */
|
|
2004
|
+
onRequest(handler) {
|
|
2005
|
+
this.requestHandler = handler;
|
|
2006
|
+
}
|
|
1830
2007
|
/** Set the stderr handler for server log lines. */
|
|
1831
2008
|
onStderr(handler) {
|
|
1832
2009
|
this.stderrHandler = handler;
|
|
@@ -1888,7 +2065,12 @@ var AppServerClient = class _AppServerClient {
|
|
|
1888
2065
|
} catch {
|
|
1889
2066
|
return;
|
|
1890
2067
|
}
|
|
2068
|
+
if (obj.id !== void 0 && obj.method !== void 0) {
|
|
2069
|
+
void this.answerRequest(obj.method, obj.params, obj.id);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
1891
2072
|
if (obj.id !== void 0) {
|
|
2073
|
+
if (typeof obj.id !== "number") return;
|
|
1892
2074
|
const pending = this.pending.get(obj.id);
|
|
1893
2075
|
if (pending) {
|
|
1894
2076
|
this.pending.delete(obj.id);
|
|
@@ -1898,11 +2080,24 @@ var AppServerClient = class _AppServerClient {
|
|
|
1898
2080
|
pending.resolve(obj.result);
|
|
1899
2081
|
}
|
|
1900
2082
|
}
|
|
2083
|
+
return;
|
|
1901
2084
|
}
|
|
1902
2085
|
if (obj.method !== void 0) {
|
|
1903
2086
|
this.notificationHandler?.(obj.method, obj.params);
|
|
1904
2087
|
}
|
|
1905
2088
|
}
|
|
2089
|
+
/** Resolve one inbound server request and write the JSON-RPC reply to stdin. */
|
|
2090
|
+
async answerRequest(method, params, id) {
|
|
2091
|
+
let outcome;
|
|
2092
|
+
try {
|
|
2093
|
+
outcome = this.requestHandler !== void 0 ? await this.requestHandler(method, params, id) : { error: { code: -32601, message: "Method not found" } };
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
outcome = { error: { code: -32603, message: error instanceof Error ? error.message : "internal error" } };
|
|
2096
|
+
}
|
|
2097
|
+
if (this.disposed) return;
|
|
2098
|
+
const reply = "result" in outcome ? { id, result: outcome.result } : { id, error: outcome.error };
|
|
2099
|
+
this.process.stdin?.write(JSON.stringify(reply) + "\n");
|
|
2100
|
+
}
|
|
1906
2101
|
};
|
|
1907
2102
|
|
|
1908
2103
|
// src/engine-codex/appserver/thread.ts
|
|
@@ -2109,6 +2304,9 @@ function mapMcpToolCall(item) {
|
|
|
2109
2304
|
// src/engine-codex/agent.ts
|
|
2110
2305
|
var PROVIDER2 = "codex";
|
|
2111
2306
|
var NATIVE_MODEL_LABEL2 = "codex-native";
|
|
2307
|
+
function nonEmptyText(parts) {
|
|
2308
|
+
return parts !== void 0 && parts.some((part) => part.length > 0) ? parts.join("\n") : void 0;
|
|
2309
|
+
}
|
|
2112
2310
|
var CodexAgent = class {
|
|
2113
2311
|
constructor(loopCtx, id, options, session, config) {
|
|
2114
2312
|
this.loopCtx = loopCtx;
|
|
@@ -2154,14 +2352,103 @@ var CodexAgent = class {
|
|
|
2154
2352
|
requestHeaderLogged = false;
|
|
2155
2353
|
/** Agent-lifecycle-local counter naming each streamed attempt. */
|
|
2156
2354
|
streamAttempts = 0;
|
|
2355
|
+
/**
|
|
2356
|
+
* Tool results logged into the currently open step. A result means the
|
|
2357
|
+
* segment that requested the call is finished, so the next assistant content
|
|
2358
|
+
* opens the next step (see {@link beginSegment}).
|
|
2359
|
+
*/
|
|
2360
|
+
stepSettledTools = 0;
|
|
2361
|
+
/**
|
|
2362
|
+
* Rotate to the next step when the segment that ran a tool has finished, so
|
|
2363
|
+
* each assistant segment lands in its own step.
|
|
2364
|
+
*
|
|
2365
|
+
* Called as new assistant content begins. A step holding a settled tool
|
|
2366
|
+
* result means the previous segment is complete, and the content about to be
|
|
2367
|
+
* written belongs to the next one. Rotating here (rather than when a call is
|
|
2368
|
+
* announced) keeps calls announced together — one model turn — in one step.
|
|
2369
|
+
* @param phase - the running phase carrying the open step's position.
|
|
2370
|
+
*/
|
|
2371
|
+
beginSegment(phase) {
|
|
2372
|
+
if (this.stepSettledTools === 0) return;
|
|
2373
|
+
this.session.append("step/end", { turn: phase.turn, step: phase.step });
|
|
2374
|
+
phase.step += 1;
|
|
2375
|
+
this.session.append("step/start", { turn: phase.turn, step: phase.step });
|
|
2376
|
+
this.stepSettledTools = 0;
|
|
2377
|
+
}
|
|
2157
2378
|
/** Lazily created app-server client, reused across steps and released on scope teardown. */
|
|
2158
2379
|
appServer;
|
|
2159
2380
|
/** Return the cached app-server client, spawning one on first use or after a dead process. */
|
|
2160
2381
|
async appServerClient() {
|
|
2161
2382
|
if (this.appServer !== void 0 && !this.appServer.closed) return this.appServer;
|
|
2162
2383
|
this.appServer = await AppServerClient.create();
|
|
2384
|
+
this.appServer.onRequest((method, params) => this.answerRequest(method, params));
|
|
2163
2385
|
return this.appServer;
|
|
2164
2386
|
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Answer one server-initiated interaction. Approvals go through the dsh
|
|
2389
|
+
* approval seam; a `request_user_input` question goes to the user-questions
|
|
2390
|
+
* seam (both fail closed when their seam is absent), and an MCP elicitation is
|
|
2391
|
+
* declined outright. Anything else is a protocol error.
|
|
2392
|
+
* @param method - the server request method.
|
|
2393
|
+
* @param params - the server request params.
|
|
2394
|
+
* @returns the JSON-RPC outcome to send back.
|
|
2395
|
+
*/
|
|
2396
|
+
async answerRequest(method, params) {
|
|
2397
|
+
if (method === "item/tool/requestUserInput") return this.answerUserInput(params);
|
|
2398
|
+
if (method === "mcpServer/elicitation/request") return { result: elicitationResponse() };
|
|
2399
|
+
return this.answerApproval(method, params);
|
|
2400
|
+
}
|
|
2401
|
+
/** Resolve one native Codex approval request through the dsh approval seam. */
|
|
2402
|
+
async answerApproval(method, params) {
|
|
2403
|
+
const outcome = await this.requestApproval(method, params);
|
|
2404
|
+
return resolveApprovalRequest(method, params, outcome);
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Put one `request_user_input` question to the human through the dsh
|
|
2408
|
+
* user-questions seam. The seam itself fails closed (`NO_PROVIDER`) when no
|
|
2409
|
+
* answerer is composed, so a refusal degrades to "no answers given" and the
|
|
2410
|
+
* turn continues; asking is never silently skipped, and the degradation is
|
|
2411
|
+
* logged rather than swallowed.
|
|
2412
|
+
* @param params - the request params carrying the questions.
|
|
2413
|
+
* @returns the response payload (or the empty answer when nobody answered).
|
|
2414
|
+
*/
|
|
2415
|
+
async answerUserInput(params) {
|
|
2416
|
+
const questions = userInputQuestions(params);
|
|
2417
|
+
const service = this.loopCtx.get("userQuestions");
|
|
2418
|
+
if (questions.length === 0) {
|
|
2419
|
+
return { result: userInputResponse(void 0) };
|
|
2420
|
+
}
|
|
2421
|
+
if (service === void 0) {
|
|
2422
|
+
this.loopCtx.logger.warn("loop-engine: codex asked for user input, but the user-questions service is not composed; answering with no answers");
|
|
2423
|
+
return { result: userInputResponse(void 0) };
|
|
2424
|
+
}
|
|
2425
|
+
const phase = this.phase;
|
|
2426
|
+
const signal = phase.kind === "running" ? phase.abort.signal : void 0;
|
|
2427
|
+
try {
|
|
2428
|
+
const answer = await service.ask({
|
|
2429
|
+
questions,
|
|
2430
|
+
agent: this,
|
|
2431
|
+
...signal === void 0 ? {} : { signal }
|
|
2432
|
+
});
|
|
2433
|
+
return { result: userInputResponse(answer) };
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
this.loopCtx.logger.warn(`loop-engine: codex user-input request went unanswered: ${String(error)}`);
|
|
2436
|
+
return { result: userInputResponse(void 0) };
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
/** Ask the dsh approval seam; fail closed to a denial when it is absent. */
|
|
2440
|
+
async requestApproval(method, params) {
|
|
2441
|
+
const approval = this.loopCtx.get("approval");
|
|
2442
|
+
if (approval === void 0) return "unavailable";
|
|
2443
|
+
const phase = this.phase;
|
|
2444
|
+
const signal = phase.kind === "running" ? phase.abort.signal : void 0;
|
|
2445
|
+
return approval.request({
|
|
2446
|
+
agent: this,
|
|
2447
|
+
toolName: approvalToolName(method),
|
|
2448
|
+
reason: approvalReason2(method, params),
|
|
2449
|
+
...signal === void 0 ? {} : { signal }
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2165
2452
|
get status() {
|
|
2166
2453
|
return this.phase.kind === "idle" || this.phase.kind === "maintenance" ? "idle" : "running";
|
|
2167
2454
|
}
|
|
@@ -2390,7 +2677,7 @@ var CodexAgent = class {
|
|
|
2390
2677
|
const stepEnd = await this.step();
|
|
2391
2678
|
if (turnEnds === null) turnEnds = stepEnd;
|
|
2392
2679
|
} finally {
|
|
2393
|
-
this.session.append("step/end", { turn, step });
|
|
2680
|
+
this.session.append("step/end", { turn, step: phase.step });
|
|
2394
2681
|
}
|
|
2395
2682
|
signal.throwIfAborted();
|
|
2396
2683
|
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
@@ -2443,8 +2730,10 @@ var CodexAgent = class {
|
|
|
2443
2730
|
/** Run one Codex thread for the current step and map its transcript into the session log. */
|
|
2444
2731
|
async step() {
|
|
2445
2732
|
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
2446
|
-
const
|
|
2733
|
+
const phase = this.phase;
|
|
2734
|
+
const { abort: { signal } } = phase;
|
|
2447
2735
|
signal.throwIfAborted();
|
|
2736
|
+
this.stepSettledTools = 0;
|
|
2448
2737
|
const cwd = this.session.header.cwd;
|
|
2449
2738
|
if (cwd === void 0 || cwd.length === 0) {
|
|
2450
2739
|
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
@@ -2485,6 +2774,7 @@ var CodexAgent = class {
|
|
|
2485
2774
|
let finished = false;
|
|
2486
2775
|
const pendingReasoning = [];
|
|
2487
2776
|
const pendingReasoningStream = [];
|
|
2777
|
+
const streamedReasoning = /* @__PURE__ */ new Map();
|
|
2488
2778
|
let held;
|
|
2489
2779
|
let reasoningBlockStarted = false;
|
|
2490
2780
|
let textBlockStarted = false;
|
|
@@ -2494,20 +2784,29 @@ var CodexAgent = class {
|
|
|
2494
2784
|
live = new DriverAssistantStream(
|
|
2495
2785
|
this.id,
|
|
2496
2786
|
++this.streamAttempts,
|
|
2497
|
-
turn,
|
|
2498
|
-
step,
|
|
2787
|
+
phase.turn,
|
|
2788
|
+
phase.step,
|
|
2499
2789
|
(frame2) => this.dispatch.emit("agent/assistant-stream", { frame: frame2 })
|
|
2500
2790
|
);
|
|
2501
2791
|
live.start();
|
|
2502
2792
|
}
|
|
2503
2793
|
return live;
|
|
2504
2794
|
};
|
|
2795
|
+
const foldReasoning = () => {
|
|
2796
|
+
if (pendingReasoning.length === 0) return;
|
|
2797
|
+
const reasoningBlocks = pendingReasoning.map((text) => ({ type: "reasoning", text }));
|
|
2798
|
+
held = held === void 0 ? { content: reasoningBlocks, stream: [...pendingReasoningStream] } : { ...held, content: [...held.content, ...reasoningBlocks], stream: [...held.stream, ...pendingReasoningStream] };
|
|
2799
|
+
pendingReasoning.length = 0;
|
|
2800
|
+
pendingReasoningStream.length = 0;
|
|
2801
|
+
reasoningBlockStarted = false;
|
|
2802
|
+
};
|
|
2505
2803
|
const flushHeld = (usage) => {
|
|
2804
|
+
foldReasoning();
|
|
2506
2805
|
if (held === void 0) return;
|
|
2507
2806
|
const attempt = live;
|
|
2508
2807
|
const data = {
|
|
2509
|
-
turn,
|
|
2510
|
-
step,
|
|
2808
|
+
turn: phase.turn,
|
|
2809
|
+
step: phase.step,
|
|
2511
2810
|
message: createAssistantMessage2({
|
|
2512
2811
|
content: held.content,
|
|
2513
2812
|
source: { provider: PROVIDER2, model: this.modelLabel() }
|
|
@@ -2524,16 +2823,10 @@ var CodexAgent = class {
|
|
|
2524
2823
|
}
|
|
2525
2824
|
held = void 0;
|
|
2526
2825
|
};
|
|
2527
|
-
const
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
held = {
|
|
2531
|
-
content: pendingReasoning.map((text) => ({ type: "reasoning", text })),
|
|
2532
|
-
stream: [...pendingReasoningStream]
|
|
2533
|
-
};
|
|
2534
|
-
pendingReasoning.length = 0;
|
|
2535
|
-
pendingReasoningStream.length = 0;
|
|
2536
|
-
flushHeld(usage);
|
|
2826
|
+
const foldToolCall = (call) => {
|
|
2827
|
+
foldReasoning();
|
|
2828
|
+
const block = { type: "tool-call", id: call.callId, name: call.name, arguments: call.arguments };
|
|
2829
|
+
held = held === void 0 ? { content: [block], stream: [] } : { ...held, content: [...held.content, block] };
|
|
2537
2830
|
};
|
|
2538
2831
|
signal.throwIfAborted();
|
|
2539
2832
|
for await (const event of events) {
|
|
@@ -2544,7 +2837,7 @@ var CodexAgent = class {
|
|
|
2544
2837
|
case "item-started": {
|
|
2545
2838
|
if (event.itemType === "agentMessage") {
|
|
2546
2839
|
textBlockStarted = false;
|
|
2547
|
-
textBlockIndex = pendingReasoning.length;
|
|
2840
|
+
textBlockIndex = (held?.content.length ?? 0) + pendingReasoning.length;
|
|
2548
2841
|
}
|
|
2549
2842
|
break;
|
|
2550
2843
|
}
|
|
@@ -2559,85 +2852,100 @@ var CodexAgent = class {
|
|
|
2559
2852
|
case "reasoning-summary-delta":
|
|
2560
2853
|
case "reasoning-text-delta":
|
|
2561
2854
|
case "plan-delta": {
|
|
2562
|
-
|
|
2855
|
+
this.beginSegment(phase);
|
|
2856
|
+
const index = (held?.content.length ?? 0) + pendingReasoning.length;
|
|
2563
2857
|
if (!reasoningBlockStarted) {
|
|
2564
2858
|
reasoningBlockStarted = true;
|
|
2565
2859
|
currentStream().push({ type: "block-start", index, blockType: "reasoning" });
|
|
2566
2860
|
}
|
|
2567
2861
|
currentStream().push({ type: "reasoning-delta", index, text: event.delta });
|
|
2862
|
+
if (event.kind !== "plan-delta") {
|
|
2863
|
+
streamedReasoning.set(event.itemId, (streamedReasoning.get(event.itemId) ?? "") + event.delta);
|
|
2864
|
+
}
|
|
2568
2865
|
break;
|
|
2569
2866
|
}
|
|
2570
2867
|
case "item-completed": {
|
|
2571
2868
|
const item = event.item;
|
|
2572
2869
|
if (item.type === "reasoning") {
|
|
2573
|
-
const
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2870
|
+
const terminal = item;
|
|
2871
|
+
const text = nonEmptyText(terminal.summary) ?? nonEmptyText(terminal.content) ?? streamedReasoning.get(terminal.id) ?? "";
|
|
2872
|
+
streamedReasoning.delete(terminal.id);
|
|
2576
2873
|
pendingReasoning.push(text);
|
|
2577
2874
|
pendingReasoningStream.push(...live?.takeStream() ?? []);
|
|
2578
2875
|
reasoningBlockStarted = false;
|
|
2876
|
+
} else if (item.type === "plan") {
|
|
2877
|
+
live?.takeStream();
|
|
2878
|
+
reasoningBlockStarted = false;
|
|
2579
2879
|
} else if (item.type === "agentMessage") {
|
|
2880
|
+
this.beginSegment(phase);
|
|
2580
2881
|
const textStream = live?.takeStream() ?? [];
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2882
|
+
const reasoningBlocks = pendingReasoning.map((text) => ({ type: "reasoning", text }));
|
|
2883
|
+
const textBlock = { type: "text", text: item.text ?? "" };
|
|
2884
|
+
if (held === void 0) {
|
|
2885
|
+
held = { content: [...reasoningBlocks, textBlock], stream: [...pendingReasoningStream, ...textStream] };
|
|
2886
|
+
} else {
|
|
2887
|
+
held = {
|
|
2888
|
+
...held,
|
|
2889
|
+
content: [...held.content, ...reasoningBlocks, textBlock],
|
|
2890
|
+
stream: [...held.stream, ...pendingReasoningStream, ...textStream]
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2589
2893
|
pendingReasoning.length = 0;
|
|
2590
2894
|
pendingReasoningStream.length = 0;
|
|
2591
2895
|
reasoningBlockStarted = false;
|
|
2592
2896
|
textBlockStarted = false;
|
|
2593
2897
|
} else if (item.type === "commandExecution") {
|
|
2594
|
-
|
|
2595
|
-
flushHeld();
|
|
2898
|
+
this.beginSegment(phase);
|
|
2596
2899
|
const activity = mapCommandExecution(item);
|
|
2900
|
+
foldToolCall(activity.call);
|
|
2901
|
+
flushHeld();
|
|
2597
2902
|
this.session.append("tool/call", {
|
|
2598
|
-
turn,
|
|
2599
|
-
step,
|
|
2903
|
+
turn: phase.turn,
|
|
2904
|
+
step: phase.step,
|
|
2600
2905
|
callId: activity.call.callId,
|
|
2601
2906
|
name: activity.call.name,
|
|
2602
2907
|
arguments: activity.call.arguments
|
|
2603
2908
|
});
|
|
2604
|
-
this.session.append("tool/result", { turn, step, message: activity.result }, { surfaceOp: "append" });
|
|
2909
|
+
this.session.append("tool/result", { turn: phase.turn, step: phase.step, message: activity.result }, { surfaceOp: "append" });
|
|
2910
|
+
this.stepSettledTools += 1;
|
|
2605
2911
|
} else if (item.type === "fileChange") {
|
|
2606
|
-
|
|
2607
|
-
flushHeld();
|
|
2912
|
+
this.beginSegment(phase);
|
|
2608
2913
|
const activity = mapFileChange(item);
|
|
2914
|
+
foldToolCall(activity.call);
|
|
2915
|
+
flushHeld();
|
|
2609
2916
|
this.session.append("tool/call", {
|
|
2610
|
-
turn,
|
|
2611
|
-
step,
|
|
2917
|
+
turn: phase.turn,
|
|
2918
|
+
step: phase.step,
|
|
2612
2919
|
callId: activity.call.callId,
|
|
2613
2920
|
name: activity.call.name,
|
|
2614
2921
|
arguments: activity.call.arguments
|
|
2615
2922
|
});
|
|
2616
|
-
this.session.append("tool/result", { turn, step, message: activity.result }, { surfaceOp: "append" });
|
|
2923
|
+
this.session.append("tool/result", { turn: phase.turn, step: phase.step, message: activity.result }, { surfaceOp: "append" });
|
|
2924
|
+
this.stepSettledTools += 1;
|
|
2617
2925
|
} else if (item.type === "mcpToolCall") {
|
|
2618
|
-
|
|
2619
|
-
flushHeld();
|
|
2926
|
+
this.beginSegment(phase);
|
|
2620
2927
|
const activity = mapMcpToolCall(item);
|
|
2928
|
+
foldToolCall(activity.call);
|
|
2929
|
+
flushHeld();
|
|
2621
2930
|
this.session.append("tool/call", {
|
|
2622
|
-
turn,
|
|
2623
|
-
step,
|
|
2931
|
+
turn: phase.turn,
|
|
2932
|
+
step: phase.step,
|
|
2624
2933
|
callId: activity.call.callId,
|
|
2625
2934
|
name: activity.call.name,
|
|
2626
2935
|
arguments: activity.call.arguments
|
|
2627
2936
|
});
|
|
2628
|
-
this.session.append("tool/result", { turn, step, message: activity.result }, { surfaceOp: "append" });
|
|
2937
|
+
this.session.append("tool/result", { turn: phase.turn, step: phase.step, message: activity.result }, { surfaceOp: "append" });
|
|
2938
|
+
this.stepSettledTools += 1;
|
|
2629
2939
|
}
|
|
2630
2940
|
break;
|
|
2631
2941
|
}
|
|
2632
2942
|
case "turn-completed": {
|
|
2633
2943
|
const usage = event.turn.usage ? mapUsage2(event.turn.usage) : void 0;
|
|
2634
|
-
|
|
2635
|
-
else flushHeld(usage);
|
|
2944
|
+
flushHeld(usage);
|
|
2636
2945
|
finished = true;
|
|
2637
2946
|
break;
|
|
2638
2947
|
}
|
|
2639
2948
|
case "error":
|
|
2640
|
-
flushReasoning();
|
|
2641
2949
|
flushHeld();
|
|
2642
2950
|
throw new LlmError2(event.error.message, "CODEX_ERROR");
|
|
2643
2951
|
/* v8 ignore next -- AppServerEvent is a closed union; no unknown kinds */
|
|
@@ -2645,7 +2953,6 @@ var CodexAgent = class {
|
|
|
2645
2953
|
break;
|
|
2646
2954
|
}
|
|
2647
2955
|
}
|
|
2648
|
-
flushReasoning();
|
|
2649
2956
|
flushHeld();
|
|
2650
2957
|
if (!finished) {
|
|
2651
2958
|
throw new LlmError2(
|
|
@@ -2688,310 +2995,15 @@ function resolveConfig2(config) {
|
|
|
2688
2995
|
model: config.model
|
|
2689
2996
|
};
|
|
2690
2997
|
}
|
|
2691
|
-
var CodexLoop = class extends
|
|
2998
|
+
var CodexLoop = class extends HostedLoopFactory {
|
|
2692
2999
|
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
2693
3000
|
static inject = ["agents", "sessions", "systemPrompt"];
|
|
2694
|
-
/** Validated configuration owned by the loop plugin. */
|
|
2695
|
-
config;
|
|
2696
|
-
ownership;
|
|
2697
|
-
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
2698
|
-
runtime;
|
|
2699
3001
|
constructor(ctx, config) {
|
|
2700
|
-
super(ctx, "agentLoopCodex");
|
|
2701
|
-
this.config = resolveConfig2(config);
|
|
2702
|
-
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
2703
|
-
this.runtime = { ctx };
|
|
2704
|
-
ctx.effect(() => () => this.ownership.dispose(), "agentLoopCodex.transactions()");
|
|
2705
|
-
ctx.effect(() => ctx.agents.setFactory(this), "agentLoopCodex.setFactory()");
|
|
2706
|
-
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
2707
|
-
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
2708
|
-
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
3002
|
+
super(ctx, "agentLoopCodex", resolveConfig2(config));
|
|
2709
3003
|
}
|
|
2710
|
-
/**
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
2714
|
-
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
2715
|
-
*/
|
|
2716
|
-
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
2717
|
-
prepare(ownerCtx, id, options, session, callerSignal, handle, parentAgent) {
|
|
2718
|
-
ownerCtx.fiber.assertActive();
|
|
2719
|
-
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
2720
|
-
if (callerSignal?.aborted) {
|
|
2721
|
-
throw callerSignal.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal.reason });
|
|
2722
|
-
}
|
|
2723
|
-
const loopCtx = this.runtime.ctx;
|
|
2724
|
-
const abort = new AbortController();
|
|
2725
|
-
const onCallerAbort = () => {
|
|
2726
|
-
abort.abort(callerSignal?.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal?.reason }));
|
|
2727
|
-
};
|
|
2728
|
-
const onFactoryTeardown = () => {
|
|
2729
|
-
abort.abort(this.ownership.signal.reason);
|
|
2730
|
-
};
|
|
2731
|
-
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
2732
|
-
this.ownership.signal.addEventListener("abort", onFactoryTeardown, { once: true });
|
|
2733
|
-
let machine;
|
|
2734
|
-
let detachSession;
|
|
2735
|
-
let detachAgent;
|
|
2736
|
-
let disposing;
|
|
2737
|
-
const machineReady = Promise.withResolvers();
|
|
2738
|
-
const dispose = (ownerTriggered = false) => disposing ??= (async () => {
|
|
2739
|
-
abort.abort(new Error(`agent "${id}" lifecycle disposed`));
|
|
2740
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
2741
|
-
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
2742
|
-
try {
|
|
2743
|
-
if (machine === void 0) await machineReady.promise;
|
|
2744
|
-
if (machine !== void 0) {
|
|
2745
|
-
machine.cancel({ kind: "disposed" });
|
|
2746
|
-
await machine.whenIdle();
|
|
2747
|
-
await machine.scope.dispose();
|
|
2748
|
-
}
|
|
2749
|
-
} finally {
|
|
2750
|
-
try {
|
|
2751
|
-
await handle?.close();
|
|
2752
|
-
} finally {
|
|
2753
|
-
try {
|
|
2754
|
-
detachAgent?.();
|
|
2755
|
-
detachSession?.();
|
|
2756
|
-
} finally {
|
|
2757
|
-
untrack();
|
|
2758
|
-
if (!ownerTriggered) await unfollowOwner();
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
})();
|
|
2763
|
-
const untrack = this.ownership.track(dispose);
|
|
2764
|
-
let unfollowOwner;
|
|
2765
|
-
try {
|
|
2766
|
-
unfollowOwner = ownerCtx.effect(() => () => {
|
|
2767
|
-
if (disposing !== void 0) return;
|
|
2768
|
-
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
2769
|
-
return dispose(true);
|
|
2770
|
-
}, `agentLoopCodex.lifecycle(${id})`);
|
|
2771
|
-
} catch (error) {
|
|
2772
|
-
untrack();
|
|
2773
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
2774
|
-
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
2775
|
-
throw error;
|
|
2776
|
-
}
|
|
2777
|
-
const assertLive = () => {
|
|
2778
|
-
if (!abort.signal.aborted) return;
|
|
2779
|
-
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
2780
|
-
};
|
|
2781
|
-
try {
|
|
2782
|
-
const agent = machine = new CodexAgent(loopCtx, id, options, session, this.config);
|
|
2783
|
-
machineReady.resolve();
|
|
2784
|
-
assertLive();
|
|
2785
|
-
return {
|
|
2786
|
-
agent,
|
|
2787
|
-
signal: abort.signal,
|
|
2788
|
-
publish: (source) => {
|
|
2789
|
-
assertLive();
|
|
2790
|
-
detachSession = agent.ctx.sessions.enter(session);
|
|
2791
|
-
detachAgent = loopCtx.agents.enter(agent, parentAgent);
|
|
2792
|
-
agent.ctx.sessions.announce(session);
|
|
2793
|
-
assertLive();
|
|
2794
|
-
loopCtx.agents.announce(agent);
|
|
2795
|
-
assertLive();
|
|
2796
|
-
emitAgentEvent2(loopCtx, agent, "agent/session-start", { source });
|
|
2797
|
-
assertLive();
|
|
2798
|
-
return { agent, dispose };
|
|
2799
|
-
},
|
|
2800
|
-
dispose
|
|
2801
|
-
};
|
|
2802
|
-
} catch (error) {
|
|
2803
|
-
machineReady.resolve();
|
|
2804
|
-
void dispose();
|
|
2805
|
-
throw error;
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
2809
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored, parentAgent) {
|
|
2810
|
-
var _stack = [];
|
|
2811
|
-
try {
|
|
2812
|
-
const ownedPreparation = __using(_stack, preparation);
|
|
2813
|
-
const session = ownedPreparation.session;
|
|
2814
|
-
let prepared;
|
|
2815
|
-
try {
|
|
2816
|
-
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle, parentAgent);
|
|
2817
|
-
} catch (error) {
|
|
2818
|
-
await stored?.handle.close().catch(() => {
|
|
2819
|
-
});
|
|
2820
|
-
throw error;
|
|
2821
|
-
}
|
|
2822
|
-
try {
|
|
2823
|
-
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx, prepared.agent), prepared.signal, id);
|
|
2824
|
-
setupCommit?.commit();
|
|
2825
|
-
await this.appendUnstoredSuffix(stored, session);
|
|
2826
|
-
return prepared.publish(source);
|
|
2827
|
-
} catch (error) {
|
|
2828
|
-
await prepared.dispose().catch(() => {
|
|
2829
|
-
});
|
|
2830
|
-
throw error;
|
|
2831
|
-
}
|
|
2832
|
-
} catch (_) {
|
|
2833
|
-
var _error = _, _hasError = true;
|
|
2834
|
-
} finally {
|
|
2835
|
-
__callDispose(_stack, _error, _hasError);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
/**
|
|
2839
|
-
* Create an agent and session under one caller-supplied identity, owned by
|
|
2840
|
-
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
2841
|
-
* durable identity is stored before publication.
|
|
2842
|
-
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
2843
|
-
* @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
|
|
2844
|
-
* @returns the published handle.
|
|
2845
|
-
*/
|
|
2846
|
-
async createAgent(ownerCtx, options) {
|
|
2847
|
-
const preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
2848
|
-
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
2849
|
-
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
2850
|
-
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
2851
|
-
}));
|
|
2852
|
-
const published = (async () => {
|
|
2853
|
-
let stored;
|
|
2854
|
-
try {
|
|
2855
|
-
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
2856
|
-
() => this.createStoredSession(preparation.session, options.signal),
|
|
2857
|
-
options.signal,
|
|
2858
|
-
options.sessionId,
|
|
2859
|
-
(abandoned) => {
|
|
2860
|
-
void abandoned?.handle.close().catch(() => {
|
|
2861
|
-
});
|
|
2862
|
-
}
|
|
2863
|
-
);
|
|
2864
|
-
} catch (error) {
|
|
2865
|
-
preparation[Symbol.dispose]();
|
|
2866
|
-
throw error;
|
|
2867
|
-
}
|
|
2868
|
-
return this.setupAndPublish(
|
|
2869
|
-
ownerCtx,
|
|
2870
|
-
options.sessionId,
|
|
2871
|
-
preparation,
|
|
2872
|
-
options.agentOptions ?? {},
|
|
2873
|
-
options.setup,
|
|
2874
|
-
options.signal,
|
|
2875
|
-
"startup",
|
|
2876
|
-
stored,
|
|
2877
|
-
options.parentAgent
|
|
2878
|
-
);
|
|
2879
|
-
})();
|
|
2880
|
-
this.ownership.trackWrapper(published);
|
|
2881
|
-
return published;
|
|
2882
|
-
}
|
|
2883
|
-
/**
|
|
2884
|
-
* Take a fresh session's write ownership when persistence is mounted.
|
|
2885
|
-
* Nothing is appended here: the constructor seed (which never re-emits
|
|
2886
|
-
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
2887
|
-
* publication commit point, so a failed or cancelled setup closes an
|
|
2888
|
-
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
2889
|
-
* created again.
|
|
2890
|
-
* @param session - the unpublished session to store.
|
|
2891
|
-
* @param signal - optional cancellation forwarded to the backend create.
|
|
2892
|
-
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
2893
|
-
*/
|
|
2894
|
-
async createStoredSession(session, signal) {
|
|
2895
|
-
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
2896
|
-
if (persistence === void 0) return void 0;
|
|
2897
|
-
const handle = await persistence.create(session.header, {
|
|
2898
|
-
inheritedEventCount: session.inheritedEventCount,
|
|
2899
|
-
...signal === void 0 ? {} : { signal }
|
|
2900
|
-
});
|
|
2901
|
-
return { handle, storedCount: 0 };
|
|
2902
|
-
}
|
|
2903
|
-
/**
|
|
2904
|
-
* Durably store the session events appended since the last stored cursor.
|
|
2905
|
-
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
2906
|
-
* never re-emit through `session/event`, so publication must flush them
|
|
2907
|
-
* through the handle before live events start routing into it.
|
|
2908
|
-
* @param stored - the session's owned handle and stored cursor, if any.
|
|
2909
|
-
* @param session - the unpublished session whose suffix is stored.
|
|
2910
|
-
*/
|
|
2911
|
-
async appendUnstoredSuffix(stored, session) {
|
|
2912
|
-
if (stored === void 0) return;
|
|
2913
|
-
const suffix = session.snapshotEvents(SessionLogOffset2(stored.storedCount));
|
|
2914
|
-
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
2915
|
-
stored.storedCount += suffix.length;
|
|
2916
|
-
}
|
|
2917
|
-
/**
|
|
2918
|
-
* Resume an owned agent from the configured persistence service.
|
|
2919
|
-
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
2920
|
-
* @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
|
|
2921
|
-
* @returns the published handle.
|
|
2922
|
-
*/
|
|
2923
|
-
async resume(ownerCtx, options) {
|
|
2924
|
-
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
2925
|
-
if (persistence === void 0) {
|
|
2926
|
-
throw new Error("cannot resume: session persistence is not configured (load a dsh-session-persistence backend)");
|
|
2927
|
-
}
|
|
2928
|
-
return this.resumeWith(ownerCtx, persistence, options);
|
|
2929
|
-
}
|
|
2930
|
-
/** Resume through an explicit persistence service. */
|
|
2931
|
-
resumeWith(ownerCtx, persistence, options) {
|
|
2932
|
-
const id = options.resumeSessionId;
|
|
2933
|
-
const published = (async () => {
|
|
2934
|
-
const ownerAbort = new AbortController();
|
|
2935
|
-
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
2936
|
-
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
2937
|
-
}, `agentLoopCodex.resume-load(${id})`);
|
|
2938
|
-
const fused = AbortSignal.any([
|
|
2939
|
-
...options.signal === void 0 ? [] : [options.signal],
|
|
2940
|
-
ownerAbort.signal,
|
|
2941
|
-
this.ownership.signal
|
|
2942
|
-
]);
|
|
2943
|
-
let handle;
|
|
2944
|
-
let stored;
|
|
2945
|
-
let preparation;
|
|
2946
|
-
try {
|
|
2947
|
-
try {
|
|
2948
|
-
handle = await raceAbortCall(
|
|
2949
|
-
() => persistence.open(id, "write", { signal: fused }),
|
|
2950
|
-
fused,
|
|
2951
|
-
id,
|
|
2952
|
-
(abandoned) => {
|
|
2953
|
-
void abandoned.close();
|
|
2954
|
-
}
|
|
2955
|
-
);
|
|
2956
|
-
const coldRead = await handle.read(0, void 0, { signal: fused });
|
|
2957
|
-
fused.throwIfAborted();
|
|
2958
|
-
const persisted = coldRead.events;
|
|
2959
|
-
const closers = interruptedTurnClosers2(persisted);
|
|
2960
|
-
if (closers.length > 0) await handle.append(closers);
|
|
2961
|
-
preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(id, {
|
|
2962
|
-
seed: [...persisted, ...closers],
|
|
2963
|
-
meta: structuredClone(handle.header),
|
|
2964
|
-
inheritedEventCount: handle.inheritedEventCount,
|
|
2965
|
-
eventState: coldRead.eventState
|
|
2966
|
-
}));
|
|
2967
|
-
stored = { handle, storedCount: persisted.length + closers.length };
|
|
2968
|
-
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
2969
|
-
} finally {
|
|
2970
|
-
await unfollowOwner();
|
|
2971
|
-
}
|
|
2972
|
-
ownerCtx.fiber.assertActive();
|
|
2973
|
-
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
2974
|
-
const owned = stored;
|
|
2975
|
-
handle = void 0;
|
|
2976
|
-
return await this.setupAndPublish(
|
|
2977
|
-
ownerCtx,
|
|
2978
|
-
id,
|
|
2979
|
-
preparation,
|
|
2980
|
-
options.agentOptions ?? {},
|
|
2981
|
-
options.setup,
|
|
2982
|
-
options.signal,
|
|
2983
|
-
"resume",
|
|
2984
|
-
owned,
|
|
2985
|
-
options.parentAgent
|
|
2986
|
-
);
|
|
2987
|
-
} finally {
|
|
2988
|
-
preparation?.[Symbol.dispose]();
|
|
2989
|
-
await handle?.close().catch(() => {
|
|
2990
|
-
});
|
|
2991
|
-
}
|
|
2992
|
-
})();
|
|
2993
|
-
this.ownership.trackWrapper(published);
|
|
2994
|
-
return published;
|
|
3004
|
+
/** Construct the Codex driver for one prepared session. */
|
|
3005
|
+
buildAgent(loopCtx, id, options, session) {
|
|
3006
|
+
return new CodexAgent(loopCtx, id, options, session, this.config);
|
|
2995
3007
|
}
|
|
2996
3008
|
};
|
|
2997
3009
|
|
|
@@ -2999,23 +3011,20 @@ var CodexLoop = class extends Service2 {
|
|
|
2999
3011
|
import { readFileSync } from "node:fs";
|
|
3000
3012
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
3001
3013
|
import { fileURLToPath } from "node:url";
|
|
3002
|
-
import { Service as Service3 } from "@deepseek-ai/cordis";
|
|
3003
3014
|
import z3 from "@deepseek-ai/schemastery";
|
|
3004
|
-
import { emitAgentEvent as emitAgentEvent3 } from "@deepseek-ai/dsh-agent";
|
|
3005
|
-
import { interruptedTurnClosers as interruptedTurnClosers3, SessionLogOffset as SessionLogOffset3, SessionPreparation as SessionPreparation3 } from "@deepseek-ai/dsh-session";
|
|
3006
3015
|
|
|
3007
3016
|
// src/engine-pi/agent.ts
|
|
3008
3017
|
import { agentEvents as agentEvents3 } from "@deepseek-ai/dsh-agent";
|
|
3009
|
-
import { ToolCallId as
|
|
3018
|
+
import { ToolCallId as ToolCallId5, LlmError as LlmError3, createAssistantMessage as createAssistantMessage3, createUserMessage as createUserMessage3, errorChain as errorChain3 } from "@deepseek-ai/dsh-llm";
|
|
3010
3019
|
import { createScope as createScope3 } from "@deepseek-ai/dsh-scope";
|
|
3011
3020
|
import { canonicalHeader as canonicalHeader3 } from "@deepseek-ai/dsh-session";
|
|
3012
3021
|
|
|
3013
3022
|
// src/engine-pi/permission.ts
|
|
3014
3023
|
var DEFAULT_PI_PERMISSION = {
|
|
3015
3024
|
sandboxMode: "read-only",
|
|
3016
|
-
tools: ["read"
|
|
3025
|
+
tools: ["read"]
|
|
3017
3026
|
};
|
|
3018
|
-
var WORKSPACE_WRITE_TOOLS = ["read", "
|
|
3027
|
+
var WORKSPACE_WRITE_TOOLS = ["read", "write", "edit"];
|
|
3019
3028
|
var FULL_ACCESS_TOOLS = [];
|
|
3020
3029
|
function toolsForSandbox(mode) {
|
|
3021
3030
|
switch (mode) {
|
|
@@ -3226,7 +3235,7 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
3226
3235
|
|
|
3227
3236
|
// src/engine-pi/rpc/mapping.ts
|
|
3228
3237
|
import {
|
|
3229
|
-
ToolCallId as
|
|
3238
|
+
ToolCallId as ToolCallId4,
|
|
3230
3239
|
createToolResultMessage as createToolResultMessage3
|
|
3231
3240
|
} from "@deepseek-ai/dsh-llm";
|
|
3232
3241
|
function mapUsage3(usage) {
|
|
@@ -3257,7 +3266,7 @@ function resultText(content) {
|
|
|
3257
3266
|
}
|
|
3258
3267
|
function mapToolResult(ev) {
|
|
3259
3268
|
return createToolResultMessage3({
|
|
3260
|
-
callId:
|
|
3269
|
+
callId: ToolCallId4(ev.toolCallId),
|
|
3261
3270
|
content: [{ type: "text", text: resultText(ev.result) || "(no content)" }],
|
|
3262
3271
|
isError: ev.isError
|
|
3263
3272
|
});
|
|
@@ -3267,10 +3276,6 @@ function mapToolResult(ev) {
|
|
|
3267
3276
|
var PROVIDER3 = "pi";
|
|
3268
3277
|
var NATIVE_MODEL_LABEL3 = "pi-native";
|
|
3269
3278
|
var TOOLS_FLAG = "--tools";
|
|
3270
|
-
function specsEqual(a, b) {
|
|
3271
|
-
if (a === void 0) return false;
|
|
3272
|
-
return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
|
|
3273
|
-
}
|
|
3274
3279
|
var PiAgent = class {
|
|
3275
3280
|
constructor(loopCtx, id, options, session, config, spawn4, bin, catalog) {
|
|
3276
3281
|
this.loopCtx = loopCtx;
|
|
@@ -3322,18 +3327,39 @@ var PiAgent = class {
|
|
|
3322
3327
|
requestHeaderLogged = false;
|
|
3323
3328
|
/** Agent-lifecycle-local counter naming each streamed attempt. */
|
|
3324
3329
|
streamAttempts = 0;
|
|
3325
|
-
/**
|
|
3330
|
+
/** This step's RPC child; released by the step teardown and the scope teardown. */
|
|
3326
3331
|
rpc;
|
|
3327
|
-
/**
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3332
|
+
/**
|
|
3333
|
+
* Tool results logged into the currently open step. A result means the
|
|
3334
|
+
* segment that requested the call is finished, so the next assistant content
|
|
3335
|
+
* opens the next step (see {@link beginSegment}).
|
|
3336
|
+
*/
|
|
3337
|
+
stepSettledTools = 0;
|
|
3338
|
+
/**
|
|
3339
|
+
* Rotate to the next step when the segment that ran a tool has finished, so
|
|
3340
|
+
* each assistant segment lands in its own step.
|
|
3341
|
+
*
|
|
3342
|
+
* Called as new assistant content begins. A step holding a settled tool
|
|
3343
|
+
* result means the previous segment is complete, and the content about to be
|
|
3344
|
+
* written belongs to the next one. Rotating at a message boundary keeps calls
|
|
3345
|
+
* announced together — one model turn — in one step.
|
|
3346
|
+
* @param phase - the running phase carrying the open step's position.
|
|
3347
|
+
*/
|
|
3348
|
+
beginSegment(phase) {
|
|
3349
|
+
if (this.stepSettledTools === 0) return;
|
|
3350
|
+
this.session.append("step/end", { turn: phase.turn, step: phase.step });
|
|
3351
|
+
phase.step += 1;
|
|
3352
|
+
this.session.append("step/start", { turn: phase.turn, step: phase.step });
|
|
3353
|
+
this.stepSettledTools = 0;
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Open this step's RPC child. The Pi RPC process is single-session, so a step
|
|
3357
|
+
* never reuses one: the step teardown disposes it and the next step respawns
|
|
3358
|
+
* a fresh child.
|
|
3359
|
+
*/
|
|
3360
|
+
rpcClient(cwd) {
|
|
3361
|
+
const client = PiRpcClient.create(this.spawnSpec(cwd), this.spawn);
|
|
3335
3362
|
this.rpc = client;
|
|
3336
|
-
this.lastSpec = spec;
|
|
3337
3363
|
return client;
|
|
3338
3364
|
}
|
|
3339
3365
|
get status() {
|
|
@@ -3565,7 +3591,7 @@ var PiAgent = class {
|
|
|
3565
3591
|
const stepEnd = await this.step();
|
|
3566
3592
|
if (turnEnds === null) turnEnds = stepEnd;
|
|
3567
3593
|
} finally {
|
|
3568
|
-
this.session.append("step/end", { turn, step });
|
|
3594
|
+
this.session.append("step/end", { turn, step: phase.step });
|
|
3569
3595
|
}
|
|
3570
3596
|
signal.throwIfAborted();
|
|
3571
3597
|
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
@@ -3688,8 +3714,10 @@ var PiAgent = class {
|
|
|
3688
3714
|
*/
|
|
3689
3715
|
async step() {
|
|
3690
3716
|
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
3691
|
-
const
|
|
3717
|
+
const phase = this.phase;
|
|
3718
|
+
const { abort: { signal } } = phase;
|
|
3692
3719
|
signal.throwIfAborted();
|
|
3720
|
+
this.stepSettledTools = 0;
|
|
3693
3721
|
const cwd = this.session.header.cwd;
|
|
3694
3722
|
if (cwd === void 0 || cwd.length === 0) {
|
|
3695
3723
|
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
@@ -3716,15 +3744,15 @@ var PiAgent = class {
|
|
|
3716
3744
|
live = new DriverAssistantStream(
|
|
3717
3745
|
this.id,
|
|
3718
3746
|
++this.streamAttempts,
|
|
3719
|
-
turn,
|
|
3720
|
-
step,
|
|
3747
|
+
phase.turn,
|
|
3748
|
+
phase.step,
|
|
3721
3749
|
(frame2) => this.dispatch.emit("agent/assistant-stream", { frame: frame2 })
|
|
3722
3750
|
);
|
|
3723
3751
|
live.start();
|
|
3724
3752
|
}
|
|
3725
3753
|
return live;
|
|
3726
3754
|
};
|
|
3727
|
-
const client =
|
|
3755
|
+
const client = this.rpcClient(cwd);
|
|
3728
3756
|
signal.throwIfAborted();
|
|
3729
3757
|
await client.newSession();
|
|
3730
3758
|
client.clearEvents();
|
|
@@ -3736,14 +3764,17 @@ var PiAgent = class {
|
|
|
3736
3764
|
const startedReasoning = /* @__PURE__ */ new Set();
|
|
3737
3765
|
const thinkingByIndex = /* @__PURE__ */ new Map();
|
|
3738
3766
|
const emittedToolCalls = /* @__PURE__ */ new Set();
|
|
3767
|
+
let pendingToolCalls = [];
|
|
3768
|
+
let pendingCallLog = [];
|
|
3769
|
+
let callsToLog = [];
|
|
3739
3770
|
let lastUsage;
|
|
3740
3771
|
let assistantFlushed = false;
|
|
3741
3772
|
const flushHeld = (usage) => {
|
|
3742
3773
|
if (held === void 0) return;
|
|
3743
3774
|
const attempt = live;
|
|
3744
3775
|
const data = {
|
|
3745
|
-
turn,
|
|
3746
|
-
step,
|
|
3776
|
+
turn: phase.turn,
|
|
3777
|
+
step: phase.step,
|
|
3747
3778
|
message: createAssistantMessage3({
|
|
3748
3779
|
content: held.content,
|
|
3749
3780
|
source: { provider: PROVIDER3, model: this.modelLabel() }
|
|
@@ -3759,6 +3790,16 @@ var PiAgent = class {
|
|
|
3759
3790
|
live = void 0;
|
|
3760
3791
|
}
|
|
3761
3792
|
held = void 0;
|
|
3793
|
+
for (const call of callsToLog) {
|
|
3794
|
+
this.session.append("tool/call", {
|
|
3795
|
+
turn: phase.turn,
|
|
3796
|
+
step: phase.step,
|
|
3797
|
+
callId: ToolCallId5(call.callId),
|
|
3798
|
+
name: call.name,
|
|
3799
|
+
arguments: call.arguments
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
callsToLog = [];
|
|
3762
3803
|
};
|
|
3763
3804
|
const ensureTextBlock = (index) => {
|
|
3764
3805
|
if (startedText.has(index)) return;
|
|
@@ -3774,13 +3815,17 @@ var PiAgent = class {
|
|
|
3774
3815
|
if (emittedToolCalls.has(callId)) return;
|
|
3775
3816
|
emittedToolCalls.add(callId);
|
|
3776
3817
|
const argumentsValue = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments ?? {});
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
}
|
|
3818
|
+
pendingToolCalls.push({ type: "tool-call", id: ToolCallId5(callId), name: name2, arguments: argumentsValue });
|
|
3819
|
+
pendingCallLog.push({ callId, name: name2, arguments: argumentsValue });
|
|
3820
|
+
};
|
|
3821
|
+
const ensureToolCallOwner = () => {
|
|
3822
|
+
if (pendingToolCalls.length === 0) return;
|
|
3823
|
+
flushHeld();
|
|
3824
|
+
held = { content: [...pendingToolCalls] };
|
|
3825
|
+
pendingToolCalls = [];
|
|
3826
|
+
callsToLog = pendingCallLog;
|
|
3827
|
+
pendingCallLog = [];
|
|
3828
|
+
flushHeld();
|
|
3784
3829
|
};
|
|
3785
3830
|
const contentOf = (message) => {
|
|
3786
3831
|
let blocks = [];
|
|
@@ -3796,6 +3841,12 @@ var PiAgent = class {
|
|
|
3796
3841
|
const folded = [...thinkingByIndex.entries()].sort((a, b) => a[0] - b[0]).map(([, text]) => ({ type: "reasoning", text }));
|
|
3797
3842
|
blocks = [...folded, ...blocks];
|
|
3798
3843
|
}
|
|
3844
|
+
if (pendingToolCalls.length > 0) {
|
|
3845
|
+
blocks = [...blocks, ...pendingToolCalls];
|
|
3846
|
+
pendingToolCalls = [];
|
|
3847
|
+
callsToLog = pendingCallLog;
|
|
3848
|
+
pendingCallLog = [];
|
|
3849
|
+
}
|
|
3799
3850
|
return blocks;
|
|
3800
3851
|
};
|
|
3801
3852
|
signal.throwIfAborted();
|
|
@@ -3813,6 +3864,7 @@ var PiAgent = class {
|
|
|
3813
3864
|
break;
|
|
3814
3865
|
case "message_start":
|
|
3815
3866
|
if (event.message.role === "assistant") {
|
|
3867
|
+
this.beginSegment(phase);
|
|
3816
3868
|
startedText.clear();
|
|
3817
3869
|
startedReasoning.clear();
|
|
3818
3870
|
thinkingByIndex.clear();
|
|
@@ -3864,21 +3916,26 @@ var PiAgent = class {
|
|
|
3864
3916
|
break;
|
|
3865
3917
|
case "tool_execution_end":
|
|
3866
3918
|
emitToolCall(event.toolCallId, event.toolName, void 0);
|
|
3919
|
+
ensureToolCallOwner();
|
|
3867
3920
|
this.session.append("tool/result", {
|
|
3868
|
-
turn,
|
|
3869
|
-
step,
|
|
3921
|
+
turn: phase.turn,
|
|
3922
|
+
step: phase.step,
|
|
3870
3923
|
message: mapToolResult({ toolCallId: event.toolCallId, result: event.result, isError: event.isError })
|
|
3871
3924
|
}, { surfaceOp: "append" });
|
|
3925
|
+
this.stepSettledTools += 1;
|
|
3872
3926
|
break;
|
|
3873
3927
|
case "turn_end": {
|
|
3874
3928
|
if (!assistantFlushed && event.message !== void 0) {
|
|
3875
3929
|
if (event.message.usage !== void 0) lastUsage = mapUsage3(event.message.usage);
|
|
3876
3930
|
held = { content: contentOf(event.message) };
|
|
3877
3931
|
}
|
|
3932
|
+
flushHeld(lastUsage);
|
|
3933
|
+
assistantFlushed = true;
|
|
3878
3934
|
for (const toolResult2 of event.toolResults ?? []) {
|
|
3879
|
-
|
|
3935
|
+
ensureToolCallOwner();
|
|
3936
|
+
this.appendToolResult(phase.turn, phase.step, toolResult2);
|
|
3937
|
+
this.stepSettledTools += 1;
|
|
3880
3938
|
}
|
|
3881
|
-
flushHeld(lastUsage);
|
|
3882
3939
|
finished = true;
|
|
3883
3940
|
break;
|
|
3884
3941
|
}
|
|
@@ -4051,14 +4108,9 @@ function fromSubprocess(handle) {
|
|
|
4051
4108
|
terminate: () => handle.terminate()
|
|
4052
4109
|
};
|
|
4053
4110
|
}
|
|
4054
|
-
var PiLoop = class extends
|
|
4111
|
+
var PiLoop = class extends HostedLoopFactory {
|
|
4055
4112
|
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
4056
4113
|
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
4057
|
-
/** Validated configuration owned by the loop plugin. */
|
|
4058
|
-
config;
|
|
4059
|
-
ownership;
|
|
4060
|
-
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
4061
|
-
runtime;
|
|
4062
4114
|
/** Process-tree spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
4063
4115
|
spawn;
|
|
4064
4116
|
/** Resolved Pi CLI entrypoint; `argv[0]` of every Pi RPC child. */
|
|
@@ -4066,10 +4118,7 @@ var PiLoop = class extends Service3 {
|
|
|
4066
4118
|
/** Discovered Pi model catalog, forwarded to each agent so it can validate the session-selected model against what pi can actually serve. */
|
|
4067
4119
|
catalog;
|
|
4068
4120
|
constructor(ctx, config) {
|
|
4069
|
-
super(ctx, "agentLoopPi");
|
|
4070
|
-
this.config = resolveConfig3(config);
|
|
4071
|
-
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
4072
|
-
this.runtime = { ctx };
|
|
4121
|
+
super(ctx, "agentLoopPi", resolveConfig3(config));
|
|
4073
4122
|
this.bin = piCliEntrypoint();
|
|
4074
4123
|
this.catalog = config.piCatalogHolder ?? { entries: [] };
|
|
4075
4124
|
this.spawn = (spec) => fromSubprocess(this.runtime.ctx.subprocess.spawn(piSubprocessSpec(spec, PI_DISPOSE_GRACE_MS)));
|
|
@@ -4081,318 +4130,28 @@ var PiLoop = class extends Service3 {
|
|
|
4081
4130
|
holder.entries = [];
|
|
4082
4131
|
});
|
|
4083
4132
|
}
|
|
4084
|
-
ctx.effect(() => () => this.ownership.dispose(), "agentLoopPi.transactions()");
|
|
4085
|
-
ctx.effect(() => ctx.agents.setFactory(this), "agentLoopPi.setFactory()");
|
|
4086
|
-
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
4087
|
-
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
4088
|
-
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
4089
4133
|
}
|
|
4090
|
-
/**
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
}
|
|
4103
|
-
const loopCtx = this.runtime.ctx;
|
|
4104
|
-
const abort = new AbortController();
|
|
4105
|
-
const onCallerAbort = () => {
|
|
4106
|
-
abort.abort(callerSignal?.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal?.reason }));
|
|
4107
|
-
};
|
|
4108
|
-
const onFactoryTeardown = () => {
|
|
4109
|
-
abort.abort(this.ownership.signal.reason);
|
|
4110
|
-
};
|
|
4111
|
-
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
4112
|
-
this.ownership.signal.addEventListener("abort", onFactoryTeardown, { once: true });
|
|
4113
|
-
let machine;
|
|
4114
|
-
let detachSession;
|
|
4115
|
-
let detachAgent;
|
|
4116
|
-
let disposing;
|
|
4117
|
-
const machineReady = Promise.withResolvers();
|
|
4118
|
-
const dispose = (ownerTriggered = false) => disposing ??= (async () => {
|
|
4119
|
-
abort.abort(new Error(`agent "${id}" lifecycle disposed`));
|
|
4120
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
4121
|
-
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
4122
|
-
try {
|
|
4123
|
-
if (machine === void 0) await machineReady.promise;
|
|
4124
|
-
if (machine !== void 0) {
|
|
4125
|
-
machine.cancel({ kind: "disposed" });
|
|
4126
|
-
await machine.whenIdle();
|
|
4127
|
-
await machine.scope.dispose();
|
|
4128
|
-
}
|
|
4129
|
-
} finally {
|
|
4130
|
-
try {
|
|
4131
|
-
await handle?.close();
|
|
4132
|
-
} finally {
|
|
4133
|
-
try {
|
|
4134
|
-
detachAgent?.();
|
|
4135
|
-
detachSession?.();
|
|
4136
|
-
} finally {
|
|
4137
|
-
untrack();
|
|
4138
|
-
if (!ownerTriggered) await unfollowOwner();
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
})();
|
|
4143
|
-
const untrack = this.ownership.track(dispose);
|
|
4144
|
-
let unfollowOwner;
|
|
4145
|
-
try {
|
|
4146
|
-
unfollowOwner = ownerCtx.effect(() => () => {
|
|
4147
|
-
if (disposing !== void 0) return;
|
|
4148
|
-
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
4149
|
-
return dispose(true);
|
|
4150
|
-
}, `agentLoopPi.lifecycle(${id})`);
|
|
4151
|
-
} catch (error) {
|
|
4152
|
-
untrack();
|
|
4153
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
4154
|
-
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
4155
|
-
throw error;
|
|
4156
|
-
}
|
|
4157
|
-
const assertLive = () => {
|
|
4158
|
-
if (!abort.signal.aborted) return;
|
|
4159
|
-
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
4160
|
-
};
|
|
4161
|
-
try {
|
|
4162
|
-
const agent = machine = new PiAgent(
|
|
4163
|
-
loopCtx,
|
|
4164
|
-
id,
|
|
4165
|
-
options,
|
|
4166
|
-
session,
|
|
4167
|
-
this.config,
|
|
4168
|
-
this.spawn,
|
|
4169
|
-
this.bin,
|
|
4170
|
-
this.catalog
|
|
4171
|
-
);
|
|
4172
|
-
machineReady.resolve();
|
|
4173
|
-
assertLive();
|
|
4174
|
-
return {
|
|
4175
|
-
agent,
|
|
4176
|
-
signal: abort.signal,
|
|
4177
|
-
publish: (source) => {
|
|
4178
|
-
assertLive();
|
|
4179
|
-
detachSession = agent.ctx.sessions.enter(session);
|
|
4180
|
-
detachAgent = loopCtx.agents.enter(agent, parentAgent);
|
|
4181
|
-
agent.ctx.sessions.announce(session);
|
|
4182
|
-
assertLive();
|
|
4183
|
-
loopCtx.agents.announce(agent);
|
|
4184
|
-
assertLive();
|
|
4185
|
-
emitAgentEvent3(loopCtx, agent, "agent/session-start", { source });
|
|
4186
|
-
assertLive();
|
|
4187
|
-
return { agent, dispose };
|
|
4188
|
-
},
|
|
4189
|
-
dispose
|
|
4190
|
-
};
|
|
4191
|
-
} catch (error) {
|
|
4192
|
-
machineReady.resolve();
|
|
4193
|
-
void dispose();
|
|
4194
|
-
throw error;
|
|
4195
|
-
}
|
|
4196
|
-
}
|
|
4197
|
-
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
4198
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored, parentAgent) {
|
|
4199
|
-
var _stack = [];
|
|
4200
|
-
try {
|
|
4201
|
-
const ownedPreparation = __using(_stack, preparation);
|
|
4202
|
-
const session = ownedPreparation.session;
|
|
4203
|
-
let prepared;
|
|
4204
|
-
try {
|
|
4205
|
-
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle, parentAgent);
|
|
4206
|
-
} catch (error) {
|
|
4207
|
-
await stored?.handle.close().catch(() => {
|
|
4208
|
-
});
|
|
4209
|
-
throw error;
|
|
4210
|
-
}
|
|
4211
|
-
try {
|
|
4212
|
-
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx, prepared.agent), prepared.signal, id);
|
|
4213
|
-
setupCommit?.commit();
|
|
4214
|
-
await this.appendUnstoredSuffix(stored, session);
|
|
4215
|
-
return prepared.publish(source);
|
|
4216
|
-
} catch (error) {
|
|
4217
|
-
await prepared.dispose().catch(() => {
|
|
4218
|
-
});
|
|
4219
|
-
throw error;
|
|
4220
|
-
}
|
|
4221
|
-
} catch (_) {
|
|
4222
|
-
var _error = _, _hasError = true;
|
|
4223
|
-
} finally {
|
|
4224
|
-
__callDispose(_stack, _error, _hasError);
|
|
4225
|
-
}
|
|
4226
|
-
}
|
|
4227
|
-
/**
|
|
4228
|
-
* Create an agent and session under one caller-supplied identity, owned by
|
|
4229
|
-
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
4230
|
-
* durable identity is stored before publication.
|
|
4231
|
-
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
4232
|
-
* @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
|
|
4233
|
-
* @returns the published handle.
|
|
4234
|
-
*/
|
|
4235
|
-
async createAgent(ownerCtx, options) {
|
|
4236
|
-
const preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
4237
|
-
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
4238
|
-
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
4239
|
-
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
4240
|
-
}));
|
|
4241
|
-
const published = (async () => {
|
|
4242
|
-
let stored;
|
|
4243
|
-
try {
|
|
4244
|
-
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
4245
|
-
() => this.createStoredSession(preparation.session, options.signal),
|
|
4246
|
-
options.signal,
|
|
4247
|
-
options.sessionId,
|
|
4248
|
-
(abandoned) => {
|
|
4249
|
-
void abandoned?.handle.close().catch(() => {
|
|
4250
|
-
});
|
|
4251
|
-
}
|
|
4252
|
-
);
|
|
4253
|
-
} catch (error) {
|
|
4254
|
-
preparation[Symbol.dispose]();
|
|
4255
|
-
throw error;
|
|
4256
|
-
}
|
|
4257
|
-
return this.setupAndPublish(
|
|
4258
|
-
ownerCtx,
|
|
4259
|
-
options.sessionId,
|
|
4260
|
-
preparation,
|
|
4261
|
-
options.agentOptions ?? {},
|
|
4262
|
-
options.setup,
|
|
4263
|
-
options.signal,
|
|
4264
|
-
"startup",
|
|
4265
|
-
stored,
|
|
4266
|
-
options.parentAgent
|
|
4267
|
-
);
|
|
4268
|
-
})();
|
|
4269
|
-
this.ownership.trackWrapper(published);
|
|
4270
|
-
return published;
|
|
4271
|
-
}
|
|
4272
|
-
/**
|
|
4273
|
-
* Take a fresh session's write ownership when persistence is mounted.
|
|
4274
|
-
* Nothing is appended here: the constructor seed (which never re-emits
|
|
4275
|
-
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
4276
|
-
* publication commit point, so a failed or cancelled setup closes an
|
|
4277
|
-
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
4278
|
-
* created again.
|
|
4279
|
-
* @param session - the unpublished session to store.
|
|
4280
|
-
* @param signal - optional cancellation forwarded to the backend create.
|
|
4281
|
-
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
4282
|
-
*/
|
|
4283
|
-
async createStoredSession(session, signal) {
|
|
4284
|
-
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
4285
|
-
if (persistence === void 0) return void 0;
|
|
4286
|
-
const handle = await persistence.create(session.header, {
|
|
4287
|
-
inheritedEventCount: session.inheritedEventCount,
|
|
4288
|
-
...signal === void 0 ? {} : { signal }
|
|
4289
|
-
});
|
|
4290
|
-
return { handle, storedCount: 0 };
|
|
4291
|
-
}
|
|
4292
|
-
/**
|
|
4293
|
-
* Durably store the session events appended since the last stored cursor.
|
|
4294
|
-
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
4295
|
-
* never re-emit through `session/event`, so publication must flush them
|
|
4296
|
-
* through the handle before live events start routing into it.
|
|
4297
|
-
* @param stored - the session's owned handle and stored cursor, if any.
|
|
4298
|
-
* @param session - the unpublished session whose suffix is stored.
|
|
4299
|
-
*/
|
|
4300
|
-
async appendUnstoredSuffix(stored, session) {
|
|
4301
|
-
if (stored === void 0) return;
|
|
4302
|
-
const suffix = session.snapshotEvents(SessionLogOffset3(stored.storedCount));
|
|
4303
|
-
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
4304
|
-
stored.storedCount += suffix.length;
|
|
4305
|
-
}
|
|
4306
|
-
/**
|
|
4307
|
-
* Resume an owned agent from the configured persistence service.
|
|
4308
|
-
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
4309
|
-
* @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
|
|
4310
|
-
* @returns the published handle.
|
|
4311
|
-
*/
|
|
4312
|
-
async resume(ownerCtx, options) {
|
|
4313
|
-
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
4314
|
-
if (persistence === void 0) {
|
|
4315
|
-
throw new Error("cannot resume: session persistence is not configured (load a dsh-session-persistence backend)");
|
|
4316
|
-
}
|
|
4317
|
-
return this.resumeWith(ownerCtx, persistence, options);
|
|
4318
|
-
}
|
|
4319
|
-
/** Resume through an explicit persistence service. */
|
|
4320
|
-
resumeWith(ownerCtx, persistence, options) {
|
|
4321
|
-
const id = options.resumeSessionId;
|
|
4322
|
-
const published = (async () => {
|
|
4323
|
-
const ownerAbort = new AbortController();
|
|
4324
|
-
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
4325
|
-
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
4326
|
-
}, `agentLoopPi.resume-load(${id})`);
|
|
4327
|
-
const fused = AbortSignal.any([
|
|
4328
|
-
...options.signal === void 0 ? [] : [options.signal],
|
|
4329
|
-
ownerAbort.signal,
|
|
4330
|
-
this.ownership.signal
|
|
4331
|
-
]);
|
|
4332
|
-
let handle;
|
|
4333
|
-
let stored;
|
|
4334
|
-
let preparation;
|
|
4335
|
-
try {
|
|
4336
|
-
try {
|
|
4337
|
-
handle = await raceAbortCall(
|
|
4338
|
-
() => persistence.open(id, "write", { signal: fused }),
|
|
4339
|
-
fused,
|
|
4340
|
-
id,
|
|
4341
|
-
(abandoned) => {
|
|
4342
|
-
void abandoned.close();
|
|
4343
|
-
}
|
|
4344
|
-
);
|
|
4345
|
-
const coldRead = await handle.read(0, void 0, { signal: fused });
|
|
4346
|
-
fused.throwIfAborted();
|
|
4347
|
-
const persisted = coldRead.events;
|
|
4348
|
-
const closers = interruptedTurnClosers3(persisted);
|
|
4349
|
-
if (closers.length > 0) await handle.append(closers);
|
|
4350
|
-
preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(id, {
|
|
4351
|
-
seed: [...persisted, ...closers],
|
|
4352
|
-
meta: structuredClone(handle.header),
|
|
4353
|
-
inheritedEventCount: handle.inheritedEventCount,
|
|
4354
|
-
eventState: coldRead.eventState
|
|
4355
|
-
}));
|
|
4356
|
-
stored = { handle, storedCount: persisted.length + closers.length };
|
|
4357
|
-
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
4358
|
-
} finally {
|
|
4359
|
-
await unfollowOwner();
|
|
4360
|
-
}
|
|
4361
|
-
ownerCtx.fiber.assertActive();
|
|
4362
|
-
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
4363
|
-
const owned = stored;
|
|
4364
|
-
handle = void 0;
|
|
4365
|
-
return await this.setupAndPublish(
|
|
4366
|
-
ownerCtx,
|
|
4367
|
-
id,
|
|
4368
|
-
preparation,
|
|
4369
|
-
options.agentOptions ?? {},
|
|
4370
|
-
options.setup,
|
|
4371
|
-
options.signal,
|
|
4372
|
-
"resume",
|
|
4373
|
-
owned,
|
|
4374
|
-
options.parentAgent
|
|
4375
|
-
);
|
|
4376
|
-
} finally {
|
|
4377
|
-
preparation?.[Symbol.dispose]();
|
|
4378
|
-
await handle?.close().catch(() => {
|
|
4379
|
-
});
|
|
4380
|
-
}
|
|
4381
|
-
})();
|
|
4382
|
-
this.ownership.trackWrapper(published);
|
|
4383
|
-
return published;
|
|
4134
|
+
/** Construct the Pi RPC driver for one prepared session. */
|
|
4135
|
+
buildAgent(loopCtx, id, options, session) {
|
|
4136
|
+
return new PiAgent(
|
|
4137
|
+
loopCtx,
|
|
4138
|
+
id,
|
|
4139
|
+
options,
|
|
4140
|
+
session,
|
|
4141
|
+
this.config,
|
|
4142
|
+
this.spawn,
|
|
4143
|
+
this.bin,
|
|
4144
|
+
this.catalog
|
|
4145
|
+
);
|
|
4384
4146
|
}
|
|
4385
4147
|
};
|
|
4386
4148
|
|
|
4387
4149
|
// src/engine-kimi/loop.ts
|
|
4388
|
-
import { Service as Service4 } from "@deepseek-ai/cordis";
|
|
4389
4150
|
import z4 from "@deepseek-ai/schemastery";
|
|
4390
|
-
import { emitAgentEvent as emitAgentEvent4 } from "@deepseek-ai/dsh-agent";
|
|
4391
|
-
import { interruptedTurnClosers as interruptedTurnClosers4, SessionLogOffset as SessionLogOffset4, SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
|
|
4392
4151
|
|
|
4393
4152
|
// src/engine-kimi/agent.ts
|
|
4394
4153
|
import { agentEvents as agentEvents4 } from "@deepseek-ai/dsh-agent";
|
|
4395
|
-
import { ToolCallId as
|
|
4154
|
+
import { ToolCallId as ToolCallId7, LlmError as LlmError4, createAssistantMessage as createAssistantMessage4, createUserMessage as createUserMessage4, errorChain as errorChain4 } from "@deepseek-ai/dsh-llm";
|
|
4396
4155
|
import { createScope as createScope4 } from "@deepseek-ai/dsh-scope";
|
|
4397
4156
|
import { canonicalHeader as canonicalHeader4 } from "@deepseek-ai/dsh-session";
|
|
4398
4157
|
|
|
@@ -4450,6 +4209,17 @@ function isPermissionRequestFrame(frame2) {
|
|
|
4450
4209
|
}
|
|
4451
4210
|
|
|
4452
4211
|
// src/engine-kimi/acp/client.ts
|
|
4212
|
+
function permissionOptionsOf(frame2) {
|
|
4213
|
+
const params = frame2.params;
|
|
4214
|
+
if (params === void 0 || !Array.isArray(params.options)) return [];
|
|
4215
|
+
return params.options.filter((option) => typeof option === "object" && option !== null && typeof option.optionId === "string");
|
|
4216
|
+
}
|
|
4217
|
+
function permissionResponse(approved, options) {
|
|
4218
|
+
const candidates = options.filter((option) => option.kind === (approved ? "allow_once" : "reject_once"));
|
|
4219
|
+
const match = candidates.length === 1 ? candidates[0] : void 0;
|
|
4220
|
+
if (match === void 0) return { outcome: { outcome: "cancelled" } };
|
|
4221
|
+
return { outcome: { outcome: "selected", optionId: match.optionId } };
|
|
4222
|
+
}
|
|
4453
4223
|
function defaultSpawn2(spec) {
|
|
4454
4224
|
const child = spawn3(spec.argv[0], spec.argv.slice(1), {
|
|
4455
4225
|
cwd: spec.cwd,
|
|
@@ -4558,9 +4328,13 @@ var AcpClient = class _AcpClient {
|
|
|
4558
4328
|
cancel(sessionId) {
|
|
4559
4329
|
this.request("session/cancel", { sessionId }).catch(() => void 0);
|
|
4560
4330
|
}
|
|
4561
|
-
/**
|
|
4562
|
-
|
|
4563
|
-
|
|
4331
|
+
/**
|
|
4332
|
+
* Answer a pending `session/request_permission`.
|
|
4333
|
+
* @param id - the reverse-RPC request id.
|
|
4334
|
+
* @param response - the ACP outcome to answer with.
|
|
4335
|
+
*/
|
|
4336
|
+
respondPermission(id, response) {
|
|
4337
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result: response })}
|
|
4564
4338
|
`);
|
|
4565
4339
|
}
|
|
4566
4340
|
/** Consume every buffered update as an async generator. */
|
|
@@ -4642,12 +4416,12 @@ var AcpClient = class _AcpClient {
|
|
|
4642
4416
|
}
|
|
4643
4417
|
async handlePermission(id, frame2) {
|
|
4644
4418
|
const approved = this.permissionHandler === void 0 ? false : await this.permissionHandler(frame2);
|
|
4645
|
-
this.respondPermission(id, approved);
|
|
4419
|
+
this.respondPermission(id, permissionResponse(approved, permissionOptionsOf(frame2)));
|
|
4646
4420
|
}
|
|
4647
4421
|
};
|
|
4648
4422
|
|
|
4649
4423
|
// src/engine-kimi/acp/mapping.ts
|
|
4650
|
-
import { ToolCallId as
|
|
4424
|
+
import { ToolCallId as ToolCallId6, createToolResultMessage as createToolResultMessage4 } from "@deepseek-ai/dsh-llm";
|
|
4651
4425
|
function isTextChunk(update) {
|
|
4652
4426
|
return update.sessionUpdate === "agent_message_chunk";
|
|
4653
4427
|
}
|
|
@@ -4672,6 +4446,11 @@ function toolCallIdOf(update) {
|
|
|
4672
4446
|
function toolCallName(update) {
|
|
4673
4447
|
return update.title;
|
|
4674
4448
|
}
|
|
4449
|
+
function toolRawInput(update) {
|
|
4450
|
+
const raw = update.rawInput;
|
|
4451
|
+
if (raw === void 0) return void 0;
|
|
4452
|
+
return typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
4453
|
+
}
|
|
4675
4454
|
function isToolSettledStatus(status) {
|
|
4676
4455
|
return status !== "pending" && status !== "queued" && status !== "running" && status !== "in_progress";
|
|
4677
4456
|
}
|
|
@@ -4679,12 +4458,13 @@ function isToolErrorStatus(status) {
|
|
|
4679
4458
|
return status === "failed" || status === "error" || status === "denied";
|
|
4680
4459
|
}
|
|
4681
4460
|
function toolContentText(update) {
|
|
4682
|
-
const blocks = update.content
|
|
4461
|
+
const blocks = update.content;
|
|
4462
|
+
if (!Array.isArray(blocks)) return void 0;
|
|
4683
4463
|
return blocks.map((block) => block.type === "content" && block.content.type === "text" ? block.content.text : "").join("");
|
|
4684
4464
|
}
|
|
4685
4465
|
function toolResult(callId, text, isError) {
|
|
4686
4466
|
return createToolResultMessage4({
|
|
4687
|
-
callId:
|
|
4467
|
+
callId: ToolCallId6(callId),
|
|
4688
4468
|
content: [{ type: "text", text: text.length > 0 ? text : "(no content)" }],
|
|
4689
4469
|
isError
|
|
4690
4470
|
});
|
|
@@ -4965,7 +4745,7 @@ var KimiAgent = class {
|
|
|
4965
4745
|
const stepEnd = await this.step();
|
|
4966
4746
|
if (turnEnds === null) turnEnds = stepEnd;
|
|
4967
4747
|
} finally {
|
|
4968
|
-
this.session.append("step/end", { turn, step });
|
|
4748
|
+
this.session.append("step/end", { turn, step: phase.step });
|
|
4969
4749
|
}
|
|
4970
4750
|
signal.throwIfAborted();
|
|
4971
4751
|
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
@@ -5028,507 +4808,313 @@ var KimiAgent = class {
|
|
|
5028
4808
|
const client = AcpClient.create(spec, this.spawn);
|
|
5029
4809
|
this.acp = client;
|
|
5030
4810
|
this.lastSpec = spec;
|
|
5031
|
-
try {
|
|
5032
|
-
await client.initialize();
|
|
5033
|
-
} catch (error) {
|
|
5034
|
-
this.acp = void 0;
|
|
5035
|
-
this.lastSpec = void 0;
|
|
5036
|
-
client.dispose();
|
|
5037
|
-
throw error;
|
|
5038
|
-
}
|
|
5039
|
-
return client;
|
|
5040
|
-
}
|
|
5041
|
-
/** Build the `kimi acp` argv/cwd/env for the persistent child. */
|
|
5042
|
-
spawnSpec(cwd) {
|
|
5043
|
-
return {
|
|
5044
|
-
argv: kimiAcpArgv(this.bin),
|
|
5045
|
-
cwd,
|
|
5046
|
-
env: this.config.env
|
|
5047
|
-
};
|
|
5048
|
-
}
|
|
5049
|
-
/** Run one `kimi acp` step for the current session history and map the streamed updates. */
|
|
5050
|
-
async step() {
|
|
5051
|
-
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
5052
|
-
const { turn, step, abort: { signal } } = this.phase;
|
|
5053
|
-
signal.throwIfAborted();
|
|
5054
|
-
this.blocks = [];
|
|
5055
|
-
this.emittedToolCalls = /* @__PURE__ */ new Set();
|
|
5056
|
-
this.toolText = /* @__PURE__ */ new Map();
|
|
5057
|
-
const cwd = this.session.header.cwd;
|
|
5058
|
-
if (cwd === void 0 || cwd.length === 0) {
|
|
5059
|
-
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
5060
|
-
}
|
|
5061
|
-
const history = this.session.deriveMessages();
|
|
5062
|
-
const prompt = serializeHistory(history);
|
|
5063
|
-
if (prompt.length === 0) {
|
|
5064
|
-
throw new Error(`agent "${this.id}": cannot derive a prompt from an empty session log`);
|
|
5065
|
-
}
|
|
5066
|
-
this.assertRequestHeader();
|
|
5067
|
-
signal.throwIfAborted();
|
|
5068
|
-
const client = await this.acpClient(cwd);
|
|
5069
|
-
signal.throwIfAborted();
|
|
5070
|
-
client.onPermission(() => resolveToolApproval(this.session.snapshotEvents()));
|
|
5071
|
-
const acpSessionId = await client.newSession(cwd);
|
|
5072
|
-
signal.throwIfAborted();
|
|
5073
|
-
let live;
|
|
5074
|
-
try {
|
|
5075
|
-
const currentStream = () => {
|
|
5076
|
-
if (live === void 0) {
|
|
5077
|
-
live = new DriverAssistantStream(
|
|
5078
|
-
this.id,
|
|
5079
|
-
++this.streamAttempts,
|
|
5080
|
-
turn,
|
|
5081
|
-
step,
|
|
5082
|
-
(frame2) => this.dispatch.emit("agent/assistant-stream", { frame: frame2 })
|
|
5083
|
-
);
|
|
5084
|
-
live.start();
|
|
5085
|
-
}
|
|
5086
|
-
return live;
|
|
5087
|
-
};
|
|
5088
|
-
client.onUpdate((update) => this.applyUpdate(turn, step, update, currentStream));
|
|
5089
|
-
const cancel = () => {
|
|
5090
|
-
client.cancel(acpSessionId);
|
|
5091
|
-
};
|
|
5092
|
-
signal.addEventListener("abort", cancel, { once: true });
|
|
5093
|
-
try {
|
|
5094
|
-
await raceAbort(client.prompt(acpSessionId, prompt), signal, this.id);
|
|
5095
|
-
} finally {
|
|
5096
|
-
signal.removeEventListener("abort", cancel);
|
|
5097
|
-
}
|
|
5098
|
-
this.flushAssistant(turn, step, currentStream);
|
|
5099
|
-
if (this.blocks.length === 0 && this.emittedToolCalls.size === 0) {
|
|
5100
|
-
throw new LlmError4(
|
|
5101
|
-
`agent "${this.id}": kimi query produced no assistant output`,
|
|
5102
|
-
"KIMI_NO_RESULT"
|
|
5103
|
-
);
|
|
5104
|
-
}
|
|
5105
|
-
return { kind: "completed" };
|
|
5106
|
-
} finally {
|
|
5107
|
-
if (live !== void 0 && !live.ended) live.abandon();
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
/** Per-step accumulation state for streamed assistant blocks and tool calls. */
|
|
5111
|
-
blocks = [];
|
|
5112
|
-
emittedToolCalls = /* @__PURE__ */ new Set();
|
|
5113
|
-
toolText = /* @__PURE__ */ new Map();
|
|
5114
|
-
blockRef(type) {
|
|
5115
|
-
return this.blocks.find((block) => block.type === type);
|
|
5116
|
-
}
|
|
5117
|
-
ensureBlock(type) {
|
|
5118
|
-
const existing = this.blockRef(type);
|
|
5119
|
-
if (existing !== void 0) return existing;
|
|
5120
|
-
const index = this.blocks.length;
|
|
5121
|
-
const block = { index, type, text: "" };
|
|
5122
|
-
this.blocks.push(block);
|
|
5123
|
-
return block;
|
|
5124
|
-
}
|
|
5125
|
-
/** Apply one streamed update to the current step's blocks and stream. */
|
|
5126
|
-
applyUpdate(turn, step, update, currentStream) {
|
|
5127
|
-
if (isThoughtChunk(update)) {
|
|
5128
|
-
const delta = chunkDelta(update);
|
|
5129
|
-
if (delta === "") return;
|
|
5130
|
-
const block = this.ensureBlock("reasoning");
|
|
5131
|
-
if (block.text === "") currentStream().push({ type: "block-start", index: block.index, blockType: "reasoning" });
|
|
5132
|
-
currentStream().push({ type: "reasoning-delta", index: block.index, text: delta });
|
|
5133
|
-
block.text += delta;
|
|
5134
|
-
return;
|
|
5135
|
-
}
|
|
5136
|
-
if (isTextChunk(update)) {
|
|
5137
|
-
const delta = chunkDelta(update);
|
|
5138
|
-
if (delta === "") return;
|
|
5139
|
-
const block = this.ensureBlock("text");
|
|
5140
|
-
if (block.text === "") currentStream().push({ type: "block-start", index: block.index, blockType: "text" });
|
|
5141
|
-
currentStream().push({ type: "text-delta", index: block.index, text: delta });
|
|
5142
|
-
block.text += delta;
|
|
5143
|
-
return;
|
|
5144
|
-
}
|
|
5145
|
-
if (isToolCall(update)) {
|
|
5146
|
-
const callId = toolCallIdOf(update);
|
|
5147
|
-
if (callId === "" || this.emittedToolCalls.has(callId)) return;
|
|
5148
|
-
this.emittedToolCalls.add(callId);
|
|
5149
|
-
const name2 = toolCallName(update);
|
|
5150
|
-
this.session.append("tool/call", { turn, step, callId: ToolCallId6(callId), name: name2, arguments: "{}" });
|
|
5151
|
-
this.toolText.set(callId, "");
|
|
5152
|
-
return;
|
|
5153
|
-
}
|
|
5154
|
-
if (isToolCallUpdate(update)) {
|
|
5155
|
-
const callId = toolCallIdOf(update);
|
|
5156
|
-
if (callId === "" || !this.toolText.has(callId)) return;
|
|
5157
|
-
const delta = toolContentText(update);
|
|
5158
|
-
const accumulated = `${this.toolText.get(callId)}${delta}`;
|
|
5159
|
-
this.toolText.set(callId, accumulated);
|
|
5160
|
-
const status = update.status;
|
|
5161
|
-
if (isToolSettledStatus(status)) {
|
|
5162
|
-
const message = toolResult(callId, accumulated, isToolErrorStatus(status));
|
|
5163
|
-
this.session.append("tool/result", { turn, step, message }, { surfaceOp: "append" });
|
|
5164
|
-
this.toolText.delete(callId);
|
|
5165
|
-
}
|
|
5166
|
-
return;
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
/**
|
|
5170
|
-
* Flush the accumulated assistant blocks into one durable assistant/message
|
|
5171
|
-
* carrying the exact stream the attempt published live.
|
|
5172
|
-
* @param turn - durable turn owning the message.
|
|
5173
|
-
* @param step - durable step owning the message.
|
|
5174
|
-
* @param currentStream - the step's live attempt accessor; a step whose
|
|
5175
|
-
* blocks streamed always has one open, while a tool-only step has none.
|
|
5176
|
-
*/
|
|
5177
|
-
flushAssistant(turn, step, currentStream) {
|
|
5178
|
-
if (this.blocks.length === 0 && this.emittedToolCalls.size === 0) return;
|
|
5179
|
-
const content = [];
|
|
5180
|
-
const attempt = this.blocks.length === 0 ? void 0 : currentStream();
|
|
5181
|
-
for (const block of this.blocks) {
|
|
5182
|
-
const delta = block.text;
|
|
5183
|
-
content.push(block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta });
|
|
5184
|
-
attempt?.push({ type: "block-end", index: block.index, block: block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta } });
|
|
5185
|
-
}
|
|
5186
|
-
const data = {
|
|
5187
|
-
turn,
|
|
5188
|
-
step,
|
|
5189
|
-
message: createAssistantMessage4({
|
|
5190
|
-
content,
|
|
5191
|
-
source: { provider: PROVIDER4, model: this.modelLabel() }
|
|
5192
|
-
}),
|
|
5193
|
-
// The attempt's exact timed stream travels with its message.
|
|
5194
|
-
stream: attempt?.stream ?? []
|
|
5195
|
-
};
|
|
5196
|
-
if (attempt === void 0) {
|
|
5197
|
-
this.session.append("assistant/message", data, { surfaceOp: "append" });
|
|
5198
|
-
} else {
|
|
5199
|
-
attempt.settle(() => this.session.append("assistant/message", data, { surfaceOp: "append" }).seq);
|
|
5200
|
-
}
|
|
5201
|
-
}
|
|
5202
|
-
};
|
|
5203
|
-
|
|
5204
|
-
// src/engine-kimi/loop.ts
|
|
5205
|
-
var KIMI_DISPOSE_GRACE_MS = 3e3;
|
|
5206
|
-
var Config4 = z4.object({
|
|
5207
|
-
model: z4.string(),
|
|
5208
|
-
env: z4.dict(z4.string()).default({}),
|
|
5209
|
-
bin: z4.string()
|
|
5210
|
-
});
|
|
5211
|
-
function resolveConfig4(config) {
|
|
5212
|
-
return {
|
|
5213
|
-
model: config.model,
|
|
5214
|
-
env: config.env ?? {},
|
|
5215
|
-
bin: kimiBinResolver(config.bin)
|
|
5216
|
-
};
|
|
5217
|
-
}
|
|
5218
|
-
var KimiLoop = class extends Service4 {
|
|
5219
|
-
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
5220
|
-
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
5221
|
-
/** Validated configuration owned by the loop plugin. */
|
|
5222
|
-
config;
|
|
5223
|
-
ownership;
|
|
5224
|
-
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
5225
|
-
runtime;
|
|
5226
|
-
/** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
5227
|
-
spawn;
|
|
5228
|
-
constructor(ctx, config) {
|
|
5229
|
-
super(ctx, "agentLoopKimi");
|
|
5230
|
-
this.config = resolveConfig4(config);
|
|
5231
|
-
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
5232
|
-
this.runtime = { ctx };
|
|
5233
|
-
this.spawn = (spec) => fromSubprocess2(this.runtime.ctx.subprocess.spawn(kimiSubprocessSpec(spec, KIMI_DISPOSE_GRACE_MS)));
|
|
5234
|
-
ctx.effect(() => () => this.ownership.dispose(), "agentLoopKimi.transactions()");
|
|
5235
|
-
ctx.effect(() => ctx.agents.setFactory(this), "agentLoopKimi.setFactory()");
|
|
5236
|
-
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
5237
|
-
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
5238
|
-
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
5239
|
-
}
|
|
5240
|
-
/**
|
|
5241
|
-
* Construct the driver, scope, and one memoized reverse teardown for a new
|
|
5242
|
-
* agent. The teardown is registered with the factory and the owner fiber
|
|
5243
|
-
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
5244
|
-
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
5245
|
-
*/
|
|
5246
|
-
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
5247
|
-
prepare(ownerCtx, id, options, session, callerSignal, handle, parentAgent) {
|
|
5248
|
-
ownerCtx.fiber.assertActive();
|
|
5249
|
-
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
5250
|
-
if (callerSignal?.aborted) {
|
|
5251
|
-
throw callerSignal.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal.reason });
|
|
5252
|
-
}
|
|
5253
|
-
const loopCtx = this.runtime.ctx;
|
|
5254
|
-
const abort = new AbortController();
|
|
5255
|
-
const onCallerAbort = () => {
|
|
5256
|
-
abort.abort(callerSignal?.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal?.reason }));
|
|
5257
|
-
};
|
|
5258
|
-
const onFactoryTeardown = () => {
|
|
5259
|
-
abort.abort(this.ownership.signal.reason);
|
|
5260
|
-
};
|
|
5261
|
-
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
5262
|
-
this.ownership.signal.addEventListener("abort", onFactoryTeardown, { once: true });
|
|
5263
|
-
let machine;
|
|
5264
|
-
let detachSession;
|
|
5265
|
-
let detachAgent;
|
|
5266
|
-
let disposing;
|
|
5267
|
-
const machineReady = Promise.withResolvers();
|
|
5268
|
-
const dispose = (ownerTriggered = false) => disposing ??= (async () => {
|
|
5269
|
-
abort.abort(new Error(`agent "${id}" lifecycle disposed`));
|
|
5270
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
5271
|
-
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
5272
|
-
try {
|
|
5273
|
-
if (machine === void 0) await machineReady.promise;
|
|
5274
|
-
if (machine !== void 0) {
|
|
5275
|
-
machine.cancel({ kind: "disposed" });
|
|
5276
|
-
await machine.whenIdle();
|
|
5277
|
-
await machine.scope.dispose();
|
|
5278
|
-
}
|
|
5279
|
-
} finally {
|
|
5280
|
-
try {
|
|
5281
|
-
await handle?.close();
|
|
5282
|
-
} finally {
|
|
5283
|
-
try {
|
|
5284
|
-
detachAgent?.();
|
|
5285
|
-
detachSession?.();
|
|
5286
|
-
} finally {
|
|
5287
|
-
untrack();
|
|
5288
|
-
if (!ownerTriggered) await unfollowOwner();
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5291
|
-
}
|
|
5292
|
-
})();
|
|
5293
|
-
const untrack = this.ownership.track(dispose);
|
|
5294
|
-
let unfollowOwner;
|
|
5295
|
-
try {
|
|
5296
|
-
unfollowOwner = ownerCtx.effect(() => () => {
|
|
5297
|
-
if (disposing !== void 0) return;
|
|
5298
|
-
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
5299
|
-
return dispose(true);
|
|
5300
|
-
}, `agentLoopKimi.lifecycle(${id})`);
|
|
5301
|
-
} catch (error) {
|
|
5302
|
-
untrack();
|
|
5303
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
5304
|
-
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
5305
|
-
throw error;
|
|
5306
|
-
}
|
|
5307
|
-
const assertLive = () => {
|
|
5308
|
-
if (!abort.signal.aborted) return;
|
|
5309
|
-
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
5310
|
-
};
|
|
5311
|
-
try {
|
|
5312
|
-
const agent = machine = new KimiAgent(loopCtx, id, options, session, this.config, this.spawn, this.config.bin);
|
|
5313
|
-
machineReady.resolve();
|
|
5314
|
-
assertLive();
|
|
5315
|
-
return {
|
|
5316
|
-
agent,
|
|
5317
|
-
signal: abort.signal,
|
|
5318
|
-
publish: (source) => {
|
|
5319
|
-
assertLive();
|
|
5320
|
-
detachSession = agent.ctx.sessions.enter(session);
|
|
5321
|
-
detachAgent = loopCtx.agents.enter(agent, parentAgent);
|
|
5322
|
-
agent.ctx.sessions.announce(session);
|
|
5323
|
-
assertLive();
|
|
5324
|
-
loopCtx.agents.announce(agent);
|
|
5325
|
-
assertLive();
|
|
5326
|
-
emitAgentEvent4(loopCtx, agent, "agent/session-start", { source });
|
|
5327
|
-
assertLive();
|
|
5328
|
-
return { agent, dispose };
|
|
5329
|
-
},
|
|
5330
|
-
dispose
|
|
5331
|
-
};
|
|
4811
|
+
try {
|
|
4812
|
+
await client.initialize();
|
|
5332
4813
|
} catch (error) {
|
|
5333
|
-
|
|
5334
|
-
void
|
|
4814
|
+
this.acp = void 0;
|
|
4815
|
+
this.lastSpec = void 0;
|
|
4816
|
+
client.dispose();
|
|
5335
4817
|
throw error;
|
|
5336
4818
|
}
|
|
4819
|
+
return client;
|
|
5337
4820
|
}
|
|
5338
|
-
/**
|
|
5339
|
-
|
|
5340
|
-
|
|
4821
|
+
/** Build the `kimi acp` argv/cwd/env for the persistent child. */
|
|
4822
|
+
spawnSpec(cwd) {
|
|
4823
|
+
return {
|
|
4824
|
+
argv: kimiAcpArgv(this.bin),
|
|
4825
|
+
cwd,
|
|
4826
|
+
env: this.config.env
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
/** Run one `kimi acp` step for the current session history and map the streamed updates. */
|
|
4830
|
+
async step() {
|
|
4831
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
4832
|
+
const phase = this.phase;
|
|
4833
|
+
const { abort: { signal } } = phase;
|
|
4834
|
+
signal.throwIfAborted();
|
|
4835
|
+
this.blocks = [];
|
|
4836
|
+
this.pendingCalls = /* @__PURE__ */ new Map();
|
|
4837
|
+
this.segmentCalls = [];
|
|
4838
|
+
this.toolContent = /* @__PURE__ */ new Map();
|
|
4839
|
+
this.producedOutput = false;
|
|
4840
|
+
this.stepSettledTools = 0;
|
|
4841
|
+
const cwd = this.session.header.cwd;
|
|
4842
|
+
if (cwd === void 0 || cwd.length === 0) {
|
|
4843
|
+
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
4844
|
+
}
|
|
4845
|
+
const history = this.session.deriveMessages();
|
|
4846
|
+
const prompt = serializeHistory(history);
|
|
4847
|
+
if (prompt.length === 0) {
|
|
4848
|
+
throw new Error(`agent "${this.id}": cannot derive a prompt from an empty session log`);
|
|
4849
|
+
}
|
|
4850
|
+
this.assertRequestHeader();
|
|
4851
|
+
signal.throwIfAborted();
|
|
4852
|
+
const client = await this.acpClient(cwd);
|
|
4853
|
+
signal.throwIfAborted();
|
|
4854
|
+
client.onPermission(() => resolveToolApproval(this.session.snapshotEvents()));
|
|
4855
|
+
const acpSessionId = await client.newSession(cwd);
|
|
4856
|
+
signal.throwIfAborted();
|
|
5341
4857
|
try {
|
|
5342
|
-
|
|
5343
|
-
const
|
|
5344
|
-
|
|
4858
|
+
client.onUpdate((update) => this.applyUpdate(phase, update));
|
|
4859
|
+
const cancel = () => {
|
|
4860
|
+
client.cancel(acpSessionId);
|
|
4861
|
+
};
|
|
4862
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
5345
4863
|
try {
|
|
5346
|
-
|
|
5347
|
-
}
|
|
5348
|
-
|
|
5349
|
-
});
|
|
5350
|
-
throw error;
|
|
4864
|
+
await raceAbort(client.prompt(acpSessionId, prompt), signal, this.id);
|
|
4865
|
+
} finally {
|
|
4866
|
+
signal.removeEventListener("abort", cancel);
|
|
5351
4867
|
}
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
setupCommit?.commit();
|
|
5355
|
-
await this.appendUnstoredSuffix(stored, session);
|
|
5356
|
-
return prepared.publish(source);
|
|
5357
|
-
} catch (error) {
|
|
5358
|
-
await prepared.dispose().catch(() => {
|
|
5359
|
-
});
|
|
5360
|
-
throw error;
|
|
4868
|
+
for (const [callId, call] of this.pendingCalls) {
|
|
4869
|
+
this.segmentCalls.push({ callId, name: call.name, arguments: "{}" });
|
|
5361
4870
|
}
|
|
5362
|
-
|
|
5363
|
-
|
|
4871
|
+
this.pendingCalls.clear();
|
|
4872
|
+
this.flushSegment(phase);
|
|
4873
|
+
if (!this.producedOutput) {
|
|
4874
|
+
throw new LlmError4(
|
|
4875
|
+
`agent "${this.id}": kimi query produced no assistant output`,
|
|
4876
|
+
"KIMI_NO_RESULT"
|
|
4877
|
+
);
|
|
4878
|
+
}
|
|
4879
|
+
return { kind: "completed" };
|
|
5364
4880
|
} finally {
|
|
5365
|
-
|
|
4881
|
+
if (this.live !== void 0 && !this.live.ended) this.live.abandon();
|
|
4882
|
+
this.live = void 0;
|
|
5366
4883
|
}
|
|
5367
4884
|
}
|
|
4885
|
+
/** Per-step accumulation state for streamed assistant blocks and tool calls. */
|
|
4886
|
+
blocks = [];
|
|
5368
4887
|
/**
|
|
5369
|
-
*
|
|
5370
|
-
*
|
|
5371
|
-
*
|
|
5372
|
-
*
|
|
5373
|
-
* @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
|
|
5374
|
-
* @returns the published handle.
|
|
4888
|
+
* Announced calls still waiting for the update that carries their input,
|
|
4889
|
+
* keyed by call id, holding the latest content snapshot seen meanwhile (a
|
|
4890
|
+
* frame can report output before it reports input). `toolContent` holds the
|
|
4891
|
+
* calls already logged, so a call is in exactly one of the two.
|
|
5375
4892
|
*/
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
5380
|
-
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
5381
|
-
}));
|
|
5382
|
-
const published = (async () => {
|
|
5383
|
-
let stored;
|
|
5384
|
-
try {
|
|
5385
|
-
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
5386
|
-
() => this.createStoredSession(preparation.session, options.signal),
|
|
5387
|
-
options.signal,
|
|
5388
|
-
options.sessionId,
|
|
5389
|
-
(abandoned) => {
|
|
5390
|
-
void abandoned?.handle.close().catch(() => {
|
|
5391
|
-
});
|
|
5392
|
-
}
|
|
5393
|
-
);
|
|
5394
|
-
} catch (error) {
|
|
5395
|
-
preparation[Symbol.dispose]();
|
|
5396
|
-
throw error;
|
|
5397
|
-
}
|
|
5398
|
-
return this.setupAndPublish(
|
|
5399
|
-
ownerCtx,
|
|
5400
|
-
options.sessionId,
|
|
5401
|
-
preparation,
|
|
5402
|
-
options.agentOptions ?? {},
|
|
5403
|
-
options.setup,
|
|
5404
|
-
options.signal,
|
|
5405
|
-
"startup",
|
|
5406
|
-
stored,
|
|
5407
|
-
options.parentAgent
|
|
5408
|
-
);
|
|
5409
|
-
})();
|
|
5410
|
-
this.ownership.trackWrapper(published);
|
|
5411
|
-
return published;
|
|
5412
|
-
}
|
|
4893
|
+
pendingCalls = /* @__PURE__ */ new Map();
|
|
4894
|
+
/** Whether the current step published any assistant message at all. */
|
|
4895
|
+
producedOutput = false;
|
|
5413
4896
|
/**
|
|
5414
|
-
*
|
|
5415
|
-
*
|
|
5416
|
-
*
|
|
5417
|
-
* publication commit point, so a failed or cancelled setup closes an
|
|
5418
|
-
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
5419
|
-
* created again.
|
|
5420
|
-
* @param session - the unpublished session to store.
|
|
5421
|
-
* @param signal - optional cancellation forwarded to the backend create.
|
|
5422
|
-
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
4897
|
+
* Tool results logged into the currently open step. A result means the
|
|
4898
|
+
* segment that requested the call is finished, so the next assistant content
|
|
4899
|
+
* opens the next step (see {@link beginSegment}).
|
|
5423
4900
|
*/
|
|
5424
|
-
|
|
5425
|
-
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
5426
|
-
if (persistence === void 0) return void 0;
|
|
5427
|
-
const handle = await persistence.create(session.header, {
|
|
5428
|
-
inheritedEventCount: session.inheritedEventCount,
|
|
5429
|
-
...signal === void 0 ? {} : { signal }
|
|
5430
|
-
});
|
|
5431
|
-
return { handle, storedCount: 0 };
|
|
5432
|
-
}
|
|
4901
|
+
stepSettledTools = 0;
|
|
5433
4902
|
/**
|
|
5434
|
-
*
|
|
5435
|
-
*
|
|
5436
|
-
*
|
|
5437
|
-
*
|
|
5438
|
-
*
|
|
5439
|
-
*
|
|
4903
|
+
* Tool calls the current segment has already received input for, awaiting the
|
|
4904
|
+
* segment's single assistant message. A model turn that announces several
|
|
4905
|
+
* calls before any result must land them all in ONE message — the chat node
|
|
4906
|
+
* keys by `${turn}:${step}` and replaces blocks on every message, so a second
|
|
4907
|
+
* message would overwrite the first one's reasoning/text (and tool-call head).
|
|
4908
|
+
* The list is flushed once, when the segment closes (its first settled result).
|
|
5440
4909
|
*/
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
4910
|
+
segmentCalls = [];
|
|
4911
|
+
/** Latest content snapshot per logged tool call (see {@link applyUpdate}). */
|
|
4912
|
+
toolContent = /* @__PURE__ */ new Map();
|
|
4913
|
+
/** The live attempt framing the assistant message being assembled right now. */
|
|
4914
|
+
live;
|
|
4915
|
+
blockRef(type) {
|
|
4916
|
+
return this.blocks.find((block) => block.type === type);
|
|
4917
|
+
}
|
|
4918
|
+
ensureBlock(type) {
|
|
4919
|
+
const existing = this.blockRef(type);
|
|
4920
|
+
if (existing !== void 0) return existing;
|
|
4921
|
+
const index = this.blocks.length;
|
|
4922
|
+
const block = { index, type, text: "" };
|
|
4923
|
+
this.blocks.push(block);
|
|
4924
|
+
return block;
|
|
5446
4925
|
}
|
|
5447
4926
|
/**
|
|
5448
|
-
*
|
|
5449
|
-
*
|
|
5450
|
-
*
|
|
5451
|
-
*
|
|
4927
|
+
* Rotate to the next step when the segment that ran a tool has finished, so
|
|
4928
|
+
* each assistant segment lands in its own step.
|
|
4929
|
+
*
|
|
4930
|
+
* Called as new assistant content begins. A step holding a settled tool
|
|
4931
|
+
* result means the previous segment is complete, and the content about to be
|
|
4932
|
+
* applied belongs to the next one. Rotating here (rather than when a call is
|
|
4933
|
+
* announced) keeps calls that were announced before any result — one model
|
|
4934
|
+
* turn — in a single step.
|
|
5452
4935
|
*/
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
4936
|
+
beginSegment(phase) {
|
|
4937
|
+
if (this.stepSettledTools === 0) return;
|
|
4938
|
+
this.session.append("step/end", { turn: phase.turn, step: phase.step });
|
|
4939
|
+
phase.step += 1;
|
|
4940
|
+
this.session.append("step/start", { turn: phase.turn, step: phase.step });
|
|
4941
|
+
this.stepSettledTools = 0;
|
|
4942
|
+
}
|
|
4943
|
+
/** Apply one streamed update to the open step's blocks and stream. */
|
|
4944
|
+
applyUpdate(phase, update) {
|
|
4945
|
+
if (isThoughtChunk(update)) {
|
|
4946
|
+
const delta = chunkDelta(update);
|
|
4947
|
+
if (delta === "") return;
|
|
4948
|
+
this.beginSegment(phase);
|
|
4949
|
+
const block = this.ensureBlock("reasoning");
|
|
4950
|
+
if (block.text === "") this.currentStream(phase).push({ type: "block-start", index: block.index, blockType: "reasoning" });
|
|
4951
|
+
this.currentStream(phase).push({ type: "reasoning-delta", index: block.index, text: delta });
|
|
4952
|
+
block.text += delta;
|
|
4953
|
+
return;
|
|
5457
4954
|
}
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
inheritedEventCount: handle.inheritedEventCount,
|
|
5495
|
-
eventState: coldRead.eventState
|
|
5496
|
-
}));
|
|
5497
|
-
stored = { handle, storedCount: persisted.length + closers.length };
|
|
5498
|
-
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
5499
|
-
} finally {
|
|
5500
|
-
await unfollowOwner();
|
|
5501
|
-
}
|
|
5502
|
-
ownerCtx.fiber.assertActive();
|
|
5503
|
-
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
5504
|
-
const owned = stored;
|
|
5505
|
-
handle = void 0;
|
|
5506
|
-
return await this.setupAndPublish(
|
|
5507
|
-
ownerCtx,
|
|
5508
|
-
id,
|
|
5509
|
-
preparation,
|
|
5510
|
-
options.agentOptions ?? {},
|
|
5511
|
-
options.setup,
|
|
5512
|
-
options.signal,
|
|
5513
|
-
"resume",
|
|
5514
|
-
owned,
|
|
5515
|
-
options.parentAgent
|
|
5516
|
-
);
|
|
5517
|
-
} finally {
|
|
5518
|
-
preparation?.[Symbol.dispose]();
|
|
5519
|
-
await handle?.close().catch(() => {
|
|
5520
|
-
});
|
|
4955
|
+
if (isTextChunk(update)) {
|
|
4956
|
+
const delta = chunkDelta(update);
|
|
4957
|
+
if (delta === "") return;
|
|
4958
|
+
this.beginSegment(phase);
|
|
4959
|
+
const block = this.ensureBlock("text");
|
|
4960
|
+
if (block.text === "") this.currentStream(phase).push({ type: "block-start", index: block.index, blockType: "text" });
|
|
4961
|
+
this.currentStream(phase).push({ type: "text-delta", index: block.index, text: delta });
|
|
4962
|
+
block.text += delta;
|
|
4963
|
+
return;
|
|
4964
|
+
}
|
|
4965
|
+
if (isToolCall(update)) {
|
|
4966
|
+
const callId = toolCallIdOf(update);
|
|
4967
|
+
if (callId === "" || this.pendingCalls.has(callId) || this.toolContent.has(callId)) return;
|
|
4968
|
+
this.beginSegment(phase);
|
|
4969
|
+
this.pendingCalls.set(callId, { name: toolCallName(update) });
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
if (isToolCallUpdate(update)) {
|
|
4973
|
+
const callId = toolCallIdOf(update);
|
|
4974
|
+
if (callId === "") return;
|
|
4975
|
+
const status = update.status;
|
|
4976
|
+
const settled = isToolSettledStatus(status);
|
|
4977
|
+
const snapshot = toolContentText(update);
|
|
4978
|
+
const pending = this.pendingCalls.get(callId);
|
|
4979
|
+
if (pending !== void 0) {
|
|
4980
|
+
if (snapshot !== void 0) pending.content = snapshot;
|
|
4981
|
+
const args = toolRawInput(update);
|
|
4982
|
+
if (args === void 0 && !settled) return;
|
|
4983
|
+
this.pendingCalls.delete(callId);
|
|
4984
|
+
this.beginSegment(phase);
|
|
4985
|
+
this.segmentCalls.push({ callId, name: pending.name, arguments: args ?? "{}" });
|
|
4986
|
+
this.toolContent.set(callId, pending.content ?? "");
|
|
4987
|
+
} else if (!this.toolContent.has(callId)) {
|
|
4988
|
+
return;
|
|
4989
|
+
} else if (snapshot !== void 0) {
|
|
4990
|
+
this.toolContent.set(callId, snapshot);
|
|
5521
4991
|
}
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
4992
|
+
if (settled) {
|
|
4993
|
+
this.flushSegment(phase);
|
|
4994
|
+
const message = toolResult(callId, this.toolContent.get(callId), isToolErrorStatus(status));
|
|
4995
|
+
this.session.append("tool/result", { turn: phase.turn, step: phase.step, message }, { surfaceOp: "append" });
|
|
4996
|
+
this.toolContent.delete(callId);
|
|
4997
|
+
this.stepSettledTools += 1;
|
|
4998
|
+
}
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
/** The live attempt framing the current segment, opened on its first chunk. */
|
|
5003
|
+
currentStream(phase) {
|
|
5004
|
+
if (this.live === void 0) {
|
|
5005
|
+
this.live = new DriverAssistantStream(
|
|
5006
|
+
this.id,
|
|
5007
|
+
++this.streamAttempts,
|
|
5008
|
+
phase.turn,
|
|
5009
|
+
phase.step,
|
|
5010
|
+
(frame2) => this.dispatch.emit("agent/assistant-stream", { frame: frame2 })
|
|
5011
|
+
);
|
|
5012
|
+
this.live.start();
|
|
5013
|
+
}
|
|
5014
|
+
return this.live;
|
|
5015
|
+
}
|
|
5016
|
+
/**
|
|
5017
|
+
* Flush the open segment's single assistant message and every `tool/call` it
|
|
5018
|
+
* accumulated, in the load-bearing order assistant/message → tool/call. One
|
|
5019
|
+
* segment produces exactly ONE message even when the model announced several
|
|
5020
|
+
* calls before any result, because the chat view keys an assistant node by
|
|
5021
|
+
* `${turn}:${step}` and replaces its blocks on every message — a second
|
|
5022
|
+
* message in the same step would overwrite the first one's reasoning/text and
|
|
5023
|
+
* leave only the last bare tool-call head visible.
|
|
5024
|
+
* @param phase - the open step the segment belongs to.
|
|
5025
|
+
*/
|
|
5026
|
+
flushSegment(phase) {
|
|
5027
|
+
const calls = this.segmentCalls;
|
|
5028
|
+
this.segmentCalls = [];
|
|
5029
|
+
this.flushAssistant(phase, calls);
|
|
5030
|
+
for (const call of calls) {
|
|
5031
|
+
this.session.append("tool/call", { turn: phase.turn, step: phase.step, callId: ToolCallId7(call.callId), name: call.name, arguments: call.arguments });
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
/**
|
|
5035
|
+
* Flush the accumulated assistant blocks into one durable assistant/message
|
|
5036
|
+
* carrying the exact stream the attempt published live, optionally closing it
|
|
5037
|
+
* with the tool-call block(s) that ended the segment.
|
|
5038
|
+
*
|
|
5039
|
+
* Every flush settles its own attempt (and so its own live `end` frame): a
|
|
5040
|
+
* step emits one message per assistant segment, and the client pairs a durable
|
|
5041
|
+
* message with the attempt whose `end` cites it, so two messages may not share
|
|
5042
|
+
* one attempt.
|
|
5043
|
+
* @param phase - the open step the message belongs to.
|
|
5044
|
+
* @param toolCalls - the calls that closed this segment, rendered as trailing
|
|
5045
|
+
* `tool-call` content blocks. A segment with no blocks of its own — a step
|
|
5046
|
+
* whose only activity was a tool call — still emits this message, so its
|
|
5047
|
+
* `tool/call` and `tool/result` events have a parent to pair with.
|
|
5048
|
+
*/
|
|
5049
|
+
flushAssistant(phase, toolCalls = []) {
|
|
5050
|
+
if (this.blocks.length === 0 && toolCalls.length === 0) return;
|
|
5051
|
+
const content = [];
|
|
5052
|
+
this.producedOutput = true;
|
|
5053
|
+
const attempt = this.blocks.length === 0 ? void 0 : this.currentStream(phase);
|
|
5054
|
+
for (const block of this.blocks) {
|
|
5055
|
+
const delta = block.text;
|
|
5056
|
+
content.push(block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta });
|
|
5057
|
+
attempt?.push({ type: "block-end", index: block.index, block: block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta } });
|
|
5058
|
+
}
|
|
5059
|
+
for (const call of toolCalls) {
|
|
5060
|
+
content.push({ type: "tool-call", id: ToolCallId7(call.callId), name: call.name, arguments: call.arguments });
|
|
5061
|
+
}
|
|
5062
|
+
const data = {
|
|
5063
|
+
turn: phase.turn,
|
|
5064
|
+
step: phase.step,
|
|
5065
|
+
message: createAssistantMessage4({
|
|
5066
|
+
content,
|
|
5067
|
+
source: { provider: PROVIDER4, model: this.modelLabel() }
|
|
5068
|
+
}),
|
|
5069
|
+
// The attempt's exact timed stream travels with its message.
|
|
5070
|
+
stream: attempt?.stream ?? []
|
|
5071
|
+
};
|
|
5072
|
+
if (attempt === void 0) {
|
|
5073
|
+
this.session.append("assistant/message", data, { surfaceOp: "append" });
|
|
5074
|
+
} else {
|
|
5075
|
+
attempt.settle(() => this.session.append("assistant/message", data, { surfaceOp: "append" }).seq);
|
|
5076
|
+
this.live = void 0;
|
|
5077
|
+
}
|
|
5078
|
+
this.blocks = [];
|
|
5079
|
+
}
|
|
5080
|
+
};
|
|
5081
|
+
|
|
5082
|
+
// src/engine-kimi/loop.ts
|
|
5083
|
+
var KIMI_DISPOSE_GRACE_MS = 3e3;
|
|
5084
|
+
var Config4 = z4.object({
|
|
5085
|
+
model: z4.string(),
|
|
5086
|
+
env: z4.dict(z4.string()).default({}),
|
|
5087
|
+
bin: z4.string()
|
|
5088
|
+
});
|
|
5089
|
+
function resolveConfig4(config) {
|
|
5090
|
+
return {
|
|
5091
|
+
model: config.model,
|
|
5092
|
+
env: config.env ?? {},
|
|
5093
|
+
bin: kimiBinResolver(config.bin)
|
|
5094
|
+
};
|
|
5095
|
+
}
|
|
5096
|
+
var KimiLoop = class extends HostedLoopFactory {
|
|
5097
|
+
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
5098
|
+
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
5099
|
+
/** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
5100
|
+
spawn;
|
|
5101
|
+
constructor(ctx, config) {
|
|
5102
|
+
super(ctx, "agentLoopKimi", resolveConfig4(config));
|
|
5103
|
+
this.spawn = (spec) => fromSubprocess2(this.runtime.ctx.subprocess.spawn(kimiSubprocessSpec(spec, KIMI_DISPOSE_GRACE_MS)));
|
|
5104
|
+
}
|
|
5105
|
+
/** Construct the Kimi ACP driver for one prepared session. */
|
|
5106
|
+
buildAgent(loopCtx, id, options, session) {
|
|
5107
|
+
return new KimiAgent(loopCtx, id, options, session, this.config, this.spawn, this.config.bin);
|
|
5525
5108
|
}
|
|
5526
5109
|
};
|
|
5527
5110
|
|
|
5528
5111
|
// src/engine-kimi/skills.ts
|
|
5529
|
-
import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
5530
5112
|
import { homedir as homedir3 } from "node:os";
|
|
5531
|
-
import {
|
|
5113
|
+
import { join as join7, resolve as resolve3 } from "node:path";
|
|
5114
|
+
|
|
5115
|
+
// src/driver-core/agents-md-skill-provider.ts
|
|
5116
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
5117
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
5532
5118
|
|
|
5533
5119
|
// src/driver-core/context-files.ts
|
|
5534
5120
|
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
@@ -5813,37 +5399,39 @@ async function readSources(paths) {
|
|
|
5813
5399
|
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
5814
5400
|
}
|
|
5815
5401
|
|
|
5816
|
-
// src/
|
|
5817
|
-
var
|
|
5818
|
-
var
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
var KIMI_CONTEXT_POLICY = {
|
|
5822
|
-
primary: ["AGENTS.md"]
|
|
5823
|
-
};
|
|
5824
|
-
function kimiAgentDir() {
|
|
5825
|
-
const override = process.env.KIMI_CODE_HOME;
|
|
5826
|
-
if (override !== void 0 && override.length > 0) return resolve3(override);
|
|
5827
|
-
return join6(homedir3(), ".kimi-code");
|
|
5828
|
-
}
|
|
5829
|
-
var KimiSkillProvider = class {
|
|
5830
|
-
constructor(control) {
|
|
5402
|
+
// src/driver-core/agents-md-skill-provider.ts
|
|
5403
|
+
var AGENTS_MD = "agents-md";
|
|
5404
|
+
var AgentsMdSkillProvider = class {
|
|
5405
|
+
constructor(spec, control) {
|
|
5406
|
+
this.spec = spec;
|
|
5831
5407
|
this.control = control;
|
|
5408
|
+
this.name = spec.name;
|
|
5832
5409
|
}
|
|
5410
|
+
spec;
|
|
5833
5411
|
control;
|
|
5834
|
-
name
|
|
5412
|
+
name;
|
|
5835
5413
|
async list(options) {
|
|
5836
5414
|
const candidates = [];
|
|
5837
5415
|
const cwd = options.cwd;
|
|
5838
5416
|
if (cwd !== void 0) {
|
|
5839
|
-
const
|
|
5840
|
-
|
|
5841
|
-
if (
|
|
5842
|
-
|
|
5843
|
-
|
|
5417
|
+
const contextPaths = await collectProjectContextFiles(cwd, this.spec.contextPolicy);
|
|
5418
|
+
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, this.spec.projectRank));
|
|
5419
|
+
if (this.spec.skills !== void 0) {
|
|
5420
|
+
const catalog = this.spec.skills;
|
|
5421
|
+
for (const dir of await projectAncestors(cwd)) {
|
|
5422
|
+
await this.collectSkillsDir(join6(dir, ...catalog.project), catalog.projectRank, candidates);
|
|
5423
|
+
}
|
|
5844
5424
|
}
|
|
5845
5425
|
}
|
|
5846
|
-
|
|
5426
|
+
const userDir = this.spec.userDir();
|
|
5427
|
+
const userContext = this.spec.userContext;
|
|
5428
|
+
if (userContext !== void 0) {
|
|
5429
|
+
const userFile = join6(userDir, userContext.file);
|
|
5430
|
+
if (await fileNonEmpty(userFile)) candidates.push(this.agentsCandidate([userFile], userContext.rank));
|
|
5431
|
+
}
|
|
5432
|
+
if (this.spec.skills !== void 0) {
|
|
5433
|
+
await this.collectSkillsDir(join6(userDir, this.spec.skills.userDir), this.spec.skills.userRank, candidates);
|
|
5434
|
+
}
|
|
5847
5435
|
if (this.control.signal.aborted) return [];
|
|
5848
5436
|
return candidates;
|
|
5849
5437
|
}
|
|
@@ -5882,8 +5470,8 @@ var KimiSkillProvider = class {
|
|
|
5882
5470
|
agentsCandidate(paths, rank) {
|
|
5883
5471
|
const first = paths[0];
|
|
5884
5472
|
return {
|
|
5885
|
-
name:
|
|
5886
|
-
description:
|
|
5473
|
+
name: AGENTS_MD,
|
|
5474
|
+
description: this.spec.agentsMdDescription,
|
|
5887
5475
|
invocation: { modelInvocable: true, userInvocable: true },
|
|
5888
5476
|
source: "custom",
|
|
5889
5477
|
provider: this.name,
|
|
@@ -5893,7 +5481,7 @@ var KimiSkillProvider = class {
|
|
|
5893
5481
|
resourceBase: { kind: "file", path: first }
|
|
5894
5482
|
};
|
|
5895
5483
|
}
|
|
5896
|
-
/** Collect every skill in one skills directory, both
|
|
5484
|
+
/** Collect every skill in one skills directory, both nesting layouts. */
|
|
5897
5485
|
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
5898
5486
|
let entries;
|
|
5899
5487
|
try {
|
|
@@ -5944,6 +5532,37 @@ var KimiSkillProvider = class {
|
|
|
5944
5532
|
}
|
|
5945
5533
|
};
|
|
5946
5534
|
|
|
5535
|
+
// src/engine-kimi/skills.ts
|
|
5536
|
+
var KIMI_AGENTS_PROJECT_RANK = 140;
|
|
5537
|
+
var KIMI_SKILL_PROJECT_RANK = 150;
|
|
5538
|
+
var KIMI_SKILL_USER_RANK = 160;
|
|
5539
|
+
var KIMI_CONTEXT_POLICY = {
|
|
5540
|
+
primary: ["AGENTS.md"]
|
|
5541
|
+
};
|
|
5542
|
+
function kimiAgentDir() {
|
|
5543
|
+
const override = process.env.KIMI_CODE_HOME;
|
|
5544
|
+
if (override !== void 0 && override.length > 0) return resolve3(override);
|
|
5545
|
+
return join7(homedir3(), ".kimi-code");
|
|
5546
|
+
}
|
|
5547
|
+
var KIMI_SPEC = {
|
|
5548
|
+
name: "kimi",
|
|
5549
|
+
agentsMdDescription: "Kimi project instructions (AGENTS.md)",
|
|
5550
|
+
contextPolicy: KIMI_CONTEXT_POLICY,
|
|
5551
|
+
userDir: kimiAgentDir,
|
|
5552
|
+
projectRank: KIMI_AGENTS_PROJECT_RANK,
|
|
5553
|
+
skills: {
|
|
5554
|
+
project: [".kimi-code", "skills"],
|
|
5555
|
+
projectRank: KIMI_SKILL_PROJECT_RANK,
|
|
5556
|
+
userDir: "skills",
|
|
5557
|
+
userRank: KIMI_SKILL_USER_RANK
|
|
5558
|
+
}
|
|
5559
|
+
};
|
|
5560
|
+
var KimiSkillProvider = class extends AgentsMdSkillProvider {
|
|
5561
|
+
constructor(control) {
|
|
5562
|
+
super(KIMI_SPEC, control);
|
|
5563
|
+
}
|
|
5564
|
+
};
|
|
5565
|
+
|
|
5947
5566
|
// src/engine-kimi/commands.ts
|
|
5948
5567
|
import { createUserMessage as createUserMessage5 } from "@deepseek-ai/dsh-llm";
|
|
5949
5568
|
function forwardKimiCommand(name2) {
|
|
@@ -5979,7 +5598,7 @@ var LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
|
|
|
5979
5598
|
// src/settings.ts
|
|
5980
5599
|
var LOOP_ENGINE_IDS = ["in-process", "claude-code", "codex", "pi", "kimi"];
|
|
5981
5600
|
var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
|
|
5982
|
-
engine: z5.union(
|
|
5601
|
+
engine: z5.union(LOOP_ENGINE_IDS.map((id) => z5.const(id))).default("in-process"),
|
|
5983
5602
|
showInComposer: z5.boolean().default(true)
|
|
5984
5603
|
});
|
|
5985
5604
|
function loopEngineSettingsNamespace() {
|
|
@@ -6002,10 +5621,16 @@ function renderManagedBlock(engine) {
|
|
|
6002
5621
|
END_MARKER_LINE
|
|
6003
5622
|
].join("\n");
|
|
6004
5623
|
}
|
|
5624
|
+
function hasManagedBlock(text) {
|
|
5625
|
+
return text.includes(MANAGED_BLOCK_BEGIN);
|
|
5626
|
+
}
|
|
6005
5627
|
var BEGIN_MARKER_RE = /^# -- dsh-loop-engine managed block: (\S+) --$/m;
|
|
6006
|
-
function
|
|
5628
|
+
function managedBlockEngineOf(text) {
|
|
6007
5629
|
const engine = BEGIN_MARKER_RE.exec(text)?.[1];
|
|
6008
|
-
return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine :
|
|
5630
|
+
return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : void 0;
|
|
5631
|
+
}
|
|
5632
|
+
function currentEngineOf(text) {
|
|
5633
|
+
return managedBlockEngineOf(text) ?? "in-process";
|
|
6009
5634
|
}
|
|
6010
5635
|
function managedSpan(text) {
|
|
6011
5636
|
const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
|
|
@@ -6063,7 +5688,7 @@ ${block}`;
|
|
|
6063
5688
|
// src/preset.ts
|
|
6064
5689
|
import { mkdir, readFile as readFile4, rename, writeFile } from "node:fs/promises";
|
|
6065
5690
|
import { randomUUID } from "node:crypto";
|
|
6066
|
-
import { dirname as dirname4, join as
|
|
5691
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
6067
5692
|
var HOSTED_PRESET_ID = "loop-engine";
|
|
6068
5693
|
var USER_PRESET_DIR = ".agent-presets";
|
|
6069
5694
|
var COMPOSITION_FILE = "agent.cordis.yml";
|
|
@@ -6131,9 +5756,9 @@ async function ensureHostedPreset(dshHome, source) {
|
|
|
6131
5756
|
const composition = await source.read(SOURCE_PRESET_ID);
|
|
6132
5757
|
const stripped = `${MANAGED_HEADER}
|
|
6133
5758
|
${stripPresetRows(composition)}`;
|
|
6134
|
-
const dir =
|
|
6135
|
-
const compositionChanged = await writeIfDifferent(
|
|
6136
|
-
const metadataChanged = await writeIfDifferent(
|
|
5759
|
+
const dir = join8(dshHome, USER_PRESET_DIR, HOSTED_PRESET_ID);
|
|
5760
|
+
const compositionChanged = await writeIfDifferent(join8(dir, COMPOSITION_FILE), stripped);
|
|
5761
|
+
const metadataChanged = await writeIfDifferent(join8(dir, METADATA_FILE), MANAGED_METADATA);
|
|
6137
5762
|
return compositionChanged || metadataChanged;
|
|
6138
5763
|
}
|
|
6139
5764
|
|
|
@@ -6157,13 +5782,20 @@ var HostedEngineRouteAdapter = class extends LlmAdapter {
|
|
|
6157
5782
|
}
|
|
6158
5783
|
label;
|
|
6159
5784
|
options;
|
|
6160
|
-
/**
|
|
5785
|
+
/**
|
|
5786
|
+
* Advertise the injected Pi models (if any) under this route's provider label.
|
|
5787
|
+
*
|
|
5788
|
+
* The model is what the picker shows at the top level, so `name` is the bare
|
|
5789
|
+
* model; the `provider/model` composite stays the submitted `id`, which is
|
|
5790
|
+
* both what the engine receives as `--model` and how the driver validates a
|
|
5791
|
+
* session-selected model against its catalog.
|
|
5792
|
+
*/
|
|
6161
5793
|
async listModels(_provider) {
|
|
6162
5794
|
const catalog = this.options.listModels?.() ?? [];
|
|
6163
5795
|
return catalog.map((entry) => ({
|
|
6164
5796
|
provider: this.label,
|
|
6165
5797
|
id: `${entry.provider}/${entry.model}`,
|
|
6166
|
-
name:
|
|
5798
|
+
name: entry.model
|
|
6167
5799
|
}));
|
|
6168
5800
|
}
|
|
6169
5801
|
stream(_options) {
|
|
@@ -6177,7 +5809,7 @@ var HostedEngineRouteAdapter = class extends LlmAdapter {
|
|
|
6177
5809
|
// src/commands.ts
|
|
6178
5810
|
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
6179
5811
|
import { homedir as homedir4 } from "node:os";
|
|
6180
|
-
import { join as
|
|
5812
|
+
import { join as join9 } from "node:path";
|
|
6181
5813
|
import { createUserMessage as createUserMessage6 } from "@deepseek-ai/dsh-llm";
|
|
6182
5814
|
var COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
|
|
6183
5815
|
function forwardClaudeCodeCommand(name2) {
|
|
@@ -6214,7 +5846,7 @@ function discoverUserSlashCommands() {
|
|
|
6214
5846
|
if (!entry.endsWith(".md")) continue;
|
|
6215
5847
|
const name2 = entry.slice(0, -".md".length);
|
|
6216
5848
|
if (!COMMAND_NAME.test(name2) || seen.has(name2)) continue;
|
|
6217
|
-
const path =
|
|
5849
|
+
const path = join9(userCommandsDir(), entry);
|
|
6218
5850
|
let raw;
|
|
6219
5851
|
try {
|
|
6220
5852
|
raw = readFileSync2(path, "utf8");
|
|
@@ -6229,7 +5861,7 @@ function discoverUserSlashCommands() {
|
|
|
6229
5861
|
return definitions;
|
|
6230
5862
|
}
|
|
6231
5863
|
function userCommandsDir() {
|
|
6232
|
-
return
|
|
5864
|
+
return join9(homedir4(), ".claude", "commands");
|
|
6233
5865
|
}
|
|
6234
5866
|
function commandDescription(raw) {
|
|
6235
5867
|
const trimmed = raw.trim();
|
|
@@ -6256,67 +5888,27 @@ function commandDescription(raw) {
|
|
|
6256
5888
|
|
|
6257
5889
|
// src/engine-codex/skills.ts
|
|
6258
5890
|
import { homedir as homedir5 } from "node:os";
|
|
6259
|
-
import { join as
|
|
6260
|
-
var PROVIDER_NAME3 = "codex";
|
|
5891
|
+
import { join as join10 } from "node:path";
|
|
6261
5892
|
var CODEX_PROJECT_RANK = 140;
|
|
6262
5893
|
var CODEX_USER_RANK = 160;
|
|
6263
5894
|
var CODEX_CONTEXT_POLICY = { primary: ["AGENTS.md"] };
|
|
6264
|
-
var
|
|
5895
|
+
var CODEX_SPEC = {
|
|
5896
|
+
name: "codex",
|
|
5897
|
+
agentsMdDescription: "Codex project/user instructions (AGENTS.md)",
|
|
5898
|
+
contextPolicy: CODEX_CONTEXT_POLICY,
|
|
5899
|
+
userDir: () => join10(homedir5(), ".codex"),
|
|
5900
|
+
projectRank: CODEX_PROJECT_RANK,
|
|
5901
|
+
userContext: { file: "AGENTS.md", rank: CODEX_USER_RANK }
|
|
5902
|
+
};
|
|
5903
|
+
var CodexSkillProvider = class extends AgentsMdSkillProvider {
|
|
6265
5904
|
constructor(control) {
|
|
6266
|
-
|
|
6267
|
-
}
|
|
6268
|
-
control;
|
|
6269
|
-
name = PROVIDER_NAME3;
|
|
6270
|
-
async list(options) {
|
|
6271
|
-
const candidates = [];
|
|
6272
|
-
const cwd = options.cwd;
|
|
6273
|
-
if (cwd !== void 0) {
|
|
6274
|
-
const paths = await collectProjectContextFiles(cwd, CODEX_CONTEXT_POLICY);
|
|
6275
|
-
if (await anySourceNonEmpty(paths)) candidates.push(this.agentsCandidate(paths, CODEX_PROJECT_RANK));
|
|
6276
|
-
}
|
|
6277
|
-
const userPath = join9(homedir5(), ".codex", "AGENTS.md");
|
|
6278
|
-
if (await fileNonEmpty(userPath)) candidates.push(this.agentsCandidate([userPath], CODEX_USER_RANK));
|
|
6279
|
-
if (this.control.signal.aborted) return [];
|
|
6280
|
-
return candidates;
|
|
6281
|
-
}
|
|
6282
|
-
async get(candidate, _options) {
|
|
6283
|
-
const locator = candidate.locator;
|
|
6284
|
-
const content = await readSources(locator.paths);
|
|
6285
|
-
if (content === void 0) return void 0;
|
|
6286
|
-
const first = locator.paths[0];
|
|
6287
|
-
return {
|
|
6288
|
-
name: candidate.name,
|
|
6289
|
-
description: candidate.description,
|
|
6290
|
-
invocation: candidate.invocation,
|
|
6291
|
-
source: candidate.source,
|
|
6292
|
-
provider: this.name,
|
|
6293
|
-
content,
|
|
6294
|
-
path: first,
|
|
6295
|
-
resourceBase: { kind: "file", path: first }
|
|
6296
|
-
};
|
|
6297
|
-
}
|
|
6298
|
-
/** One merged `agents-md` candidate for a ranked file set. */
|
|
6299
|
-
agentsCandidate(paths, rank) {
|
|
6300
|
-
const first = paths[0];
|
|
6301
|
-
return {
|
|
6302
|
-
name: "agents-md",
|
|
6303
|
-
description: "Codex project/user instructions (AGENTS.md)",
|
|
6304
|
-
invocation: { modelInvocable: true, userInvocable: true },
|
|
6305
|
-
source: "custom",
|
|
6306
|
-
provider: this.name,
|
|
6307
|
-
rank,
|
|
6308
|
-
locator: { kind: "agents-md", paths },
|
|
6309
|
-
path: first,
|
|
6310
|
-
resourceBase: { kind: "file", path: first }
|
|
6311
|
-
};
|
|
5905
|
+
super(CODEX_SPEC, control);
|
|
6312
5906
|
}
|
|
6313
5907
|
};
|
|
6314
5908
|
|
|
6315
5909
|
// src/engine-pi/skills.ts
|
|
6316
|
-
import { readdir as readdir3, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
6317
5910
|
import { homedir as homedir6 } from "node:os";
|
|
6318
|
-
import {
|
|
6319
|
-
var PROVIDER_NAME4 = "pi";
|
|
5911
|
+
import { join as join11, resolve as resolve4 } from "node:path";
|
|
6320
5912
|
var PI_AGENTS_PROJECT_RANK = 140;
|
|
6321
5913
|
var PI_SKILL_PROJECT_RANK = 150;
|
|
6322
5914
|
var PI_AGENTS_USER_RANK = 160;
|
|
@@ -6328,126 +5920,25 @@ var PI_CONTEXT_POLICY = {
|
|
|
6328
5920
|
function piAgentDir() {
|
|
6329
5921
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
6330
5922
|
if (override !== void 0 && override.length > 0) return resolve4(override);
|
|
6331
|
-
return
|
|
6332
|
-
}
|
|
6333
|
-
var
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, PI_AGENTS_PROJECT_RANK));
|
|
6346
|
-
for (const dir of projectDirs) {
|
|
6347
|
-
await this.collectSkillsDir(join10(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
const userAgentDir = piAgentDir();
|
|
6351
|
-
const userContext = join10(userAgentDir, "AGENTS.md");
|
|
6352
|
-
if (await fileNonEmpty(userContext)) candidates.push(this.agentsCandidate([userContext], PI_AGENTS_USER_RANK));
|
|
6353
|
-
await this.collectSkillsDir(join10(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
|
|
6354
|
-
if (this.control.signal.aborted) return [];
|
|
6355
|
-
return candidates;
|
|
6356
|
-
}
|
|
6357
|
-
async get(candidate, _options) {
|
|
6358
|
-
const locator = candidate.locator;
|
|
6359
|
-
if (locator.kind === "skill-file") {
|
|
6360
|
-
const parsed = await this.tryParse(locator.path);
|
|
6361
|
-
if (parsed === void 0) return void 0;
|
|
6362
|
-
return {
|
|
6363
|
-
name: parsed.name,
|
|
6364
|
-
description: parsed.description,
|
|
6365
|
-
...parsed.whenToUse === void 0 ? {} : { whenToUse: parsed.whenToUse },
|
|
6366
|
-
invocation: parsed.invocation,
|
|
6367
|
-
source: candidate.source,
|
|
6368
|
-
provider: this.name,
|
|
6369
|
-
content: parsed.content,
|
|
6370
|
-
path: locator.path,
|
|
6371
|
-
resourceBase: { kind: "directory", path: dirname5(locator.path) }
|
|
6372
|
-
};
|
|
6373
|
-
}
|
|
6374
|
-
const content = await readSources(locator.paths);
|
|
6375
|
-
if (content === void 0) return void 0;
|
|
6376
|
-
const first = locator.paths[0];
|
|
6377
|
-
return {
|
|
6378
|
-
name: candidate.name,
|
|
6379
|
-
description: candidate.description,
|
|
6380
|
-
invocation: candidate.invocation,
|
|
6381
|
-
source: candidate.source,
|
|
6382
|
-
provider: this.name,
|
|
6383
|
-
content,
|
|
6384
|
-
path: first,
|
|
6385
|
-
resourceBase: { kind: "file", path: first }
|
|
6386
|
-
};
|
|
6387
|
-
}
|
|
6388
|
-
/** One merged `agents-md` candidate for a ranked file set. */
|
|
6389
|
-
agentsCandidate(paths, rank) {
|
|
6390
|
-
const first = paths[0];
|
|
6391
|
-
return {
|
|
6392
|
-
name: "agents-md",
|
|
6393
|
-
description: "Pi project/user instructions (AGENTS.md / CLAUDE.md)",
|
|
6394
|
-
invocation: { modelInvocable: true, userInvocable: true },
|
|
6395
|
-
source: "custom",
|
|
6396
|
-
provider: this.name,
|
|
6397
|
-
rank,
|
|
6398
|
-
locator: { kind: "agents-md", paths },
|
|
6399
|
-
path: first,
|
|
6400
|
-
resourceBase: { kind: "file", path: first }
|
|
6401
|
-
};
|
|
6402
|
-
}
|
|
6403
|
-
/** Collect every skill in one skills directory, both pi layouts. */
|
|
6404
|
-
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
6405
|
-
let entries;
|
|
6406
|
-
try {
|
|
6407
|
-
entries = await readdir3(skillsDir, { withFileTypes: true, encoding: "utf8" });
|
|
6408
|
-
} catch {
|
|
6409
|
-
return;
|
|
6410
|
-
}
|
|
6411
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6412
|
-
const entryPath = join10(skillsDir, entry.name);
|
|
6413
|
-
const info = await stat4(entryPath).catch(() => void 0);
|
|
6414
|
-
if (info === void 0) continue;
|
|
6415
|
-
if (info.isDirectory()) {
|
|
6416
|
-
const path = join10(entryPath, "SKILL.md");
|
|
6417
|
-
const parsed2 = await this.tryParse(path);
|
|
6418
|
-
if (parsed2 === void 0) continue;
|
|
6419
|
-
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
6420
|
-
continue;
|
|
6421
|
-
}
|
|
6422
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
6423
|
-
const parsed = await this.tryParse(entryPath);
|
|
6424
|
-
if (parsed === void 0) continue;
|
|
6425
|
-
candidates.push(this.skillCandidate(parsed, entryPath, rank, skillsDir));
|
|
6426
|
-
}
|
|
6427
|
-
}
|
|
6428
|
-
/** One parsed skill as a ranked candidate. */
|
|
6429
|
-
skillCandidate(skill, path, rank, resourceDir) {
|
|
6430
|
-
return {
|
|
6431
|
-
name: skill.name,
|
|
6432
|
-
description: skill.description,
|
|
6433
|
-
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
|
|
6434
|
-
invocation: skill.invocation,
|
|
6435
|
-
source: "custom",
|
|
6436
|
-
provider: this.name,
|
|
6437
|
-
rank,
|
|
6438
|
-
locator: { kind: "skill-file", path },
|
|
6439
|
-
path,
|
|
6440
|
-
resourceBase: { kind: "directory", path: resourceDir }
|
|
6441
|
-
};
|
|
5923
|
+
return join11(homedir6(), ".pi", "agent");
|
|
5924
|
+
}
|
|
5925
|
+
var PI_SPEC = {
|
|
5926
|
+
name: "pi",
|
|
5927
|
+
agentsMdDescription: "Pi project/user instructions (AGENTS.md / CLAUDE.md)",
|
|
5928
|
+
contextPolicy: PI_CONTEXT_POLICY,
|
|
5929
|
+
userDir: piAgentDir,
|
|
5930
|
+
projectRank: PI_AGENTS_PROJECT_RANK,
|
|
5931
|
+
userContext: { file: "AGENTS.md", rank: PI_AGENTS_USER_RANK },
|
|
5932
|
+
skills: {
|
|
5933
|
+
project: [".pi", "skills"],
|
|
5934
|
+
projectRank: PI_SKILL_PROJECT_RANK,
|
|
5935
|
+
userDir: "skills",
|
|
5936
|
+
userRank: PI_SKILL_USER_RANK
|
|
6442
5937
|
}
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
return parseSkillFile(raw);
|
|
6448
|
-
} catch {
|
|
6449
|
-
return void 0;
|
|
6450
|
-
}
|
|
5938
|
+
};
|
|
5939
|
+
var PiSkillProvider = class extends AgentsMdSkillProvider {
|
|
5940
|
+
constructor(control) {
|
|
5941
|
+
super(PI_SPEC, control);
|
|
6451
5942
|
}
|
|
6452
5943
|
};
|
|
6453
5944
|
|
|
@@ -6473,7 +5964,7 @@ var Config5 = z6.object({
|
|
|
6473
5964
|
});
|
|
6474
5965
|
function resolvePatchPath(config) {
|
|
6475
5966
|
if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
|
|
6476
|
-
return
|
|
5967
|
+
return join12(
|
|
6477
5968
|
resolveDshHome(),
|
|
6478
5969
|
"profiles",
|
|
6479
5970
|
config.profile ?? "web",
|
|
@@ -6485,20 +5976,20 @@ function isMissing(error) {
|
|
|
6485
5976
|
}
|
|
6486
5977
|
async function readPatchOrUndefined(path) {
|
|
6487
5978
|
try {
|
|
6488
|
-
return await
|
|
5979
|
+
return await readFile5(path, "utf8");
|
|
6489
5980
|
} catch (error) {
|
|
6490
5981
|
if (isMissing(error)) return void 0;
|
|
6491
5982
|
throw error;
|
|
6492
5983
|
}
|
|
6493
5984
|
}
|
|
6494
5985
|
async function writePatchFile(path, text) {
|
|
6495
|
-
await mkdir2(
|
|
5986
|
+
await mkdir2(dirname5(path), { recursive: true });
|
|
6496
5987
|
const tmp = `${path}.tmp-${randomUUID2()}`;
|
|
6497
5988
|
await writeFile2(tmp, text, "utf8");
|
|
6498
5989
|
await rename2(tmp, path);
|
|
6499
5990
|
}
|
|
6500
5991
|
function writePatchFileSync(path, text) {
|
|
6501
|
-
mkdirSync(
|
|
5992
|
+
mkdirSync(dirname5(path), { recursive: true });
|
|
6502
5993
|
const tmp = `${path}.tmp-${randomUUID2()}`;
|
|
6503
5994
|
writeFileSync(tmp, text, "utf8");
|
|
6504
5995
|
renameSync(tmp, path);
|
|
@@ -6553,12 +6044,29 @@ function kimiConfig(config) {
|
|
|
6553
6044
|
}
|
|
6554
6045
|
function apply(ctx, config) {
|
|
6555
6046
|
const patchPath = resolvePatchPath(config);
|
|
6556
|
-
|
|
6047
|
+
const patchText = readPatchFileSync(patchPath);
|
|
6048
|
+
let fileEngine = currentEngineOf(patchText);
|
|
6049
|
+
if (hasManagedBlock(patchText) && managedBlockEngineOf(patchText) === void 0) {
|
|
6050
|
+
try {
|
|
6051
|
+
writePatchFileSync(patchPath, applyManagedBlock(patchText, "in-process"));
|
|
6052
|
+
fileEngine = "in-process";
|
|
6053
|
+
ctx.logger.error(
|
|
6054
|
+
"loop-engine: the managed block names an engine this build does not recognize; removed it so the base agent loop can own the factory slot. Restart `dsh web` to bring the base loop back."
|
|
6055
|
+
);
|
|
6056
|
+
} catch (error) {
|
|
6057
|
+
ctx.logger.error(`loop-engine: could not repair the unrecognized managed block: ${String(error)}`);
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6557
6060
|
const AGENT_PRESETS_NS = "agent-presets";
|
|
6558
6061
|
const PRESET_DEFAULT_ATTEMPTS = 30;
|
|
6559
6062
|
const PRESET_DEFAULT_RETRY_MS = 100;
|
|
6560
6063
|
let engineFiber;
|
|
6561
6064
|
let mountedEngine;
|
|
6065
|
+
let mountGeneration = 0;
|
|
6066
|
+
let disposed = false;
|
|
6067
|
+
const retryLater = (run, ms) => setTimeout(() => {
|
|
6068
|
+
if (!disposed) run();
|
|
6069
|
+
}, ms);
|
|
6562
6070
|
let commandDisposers;
|
|
6563
6071
|
let skillDisposer;
|
|
6564
6072
|
const piCatalogHolder = { entries: [] };
|
|
@@ -6604,9 +6112,7 @@ function apply(ctx, config) {
|
|
|
6604
6112
|
const llm = ctx.get("llm");
|
|
6605
6113
|
if (llm === void 0) {
|
|
6606
6114
|
if (attempt < ROUTE_ATTEMPTS) {
|
|
6607
|
-
routeRetry =
|
|
6608
|
-
mountProviderRoute(engine, attempt + 1);
|
|
6609
|
-
}, ROUTE_RETRY_MS);
|
|
6115
|
+
routeRetry = retryLater(() => mountProviderRoute(engine, attempt + 1), ROUTE_RETRY_MS);
|
|
6610
6116
|
}
|
|
6611
6117
|
return;
|
|
6612
6118
|
}
|
|
@@ -6615,7 +6121,8 @@ function apply(ctx, config) {
|
|
|
6615
6121
|
routeHandle = llm.registerAdapter([label], new HostedEngineRouteAdapter(label, options));
|
|
6616
6122
|
routeEngine = engine;
|
|
6617
6123
|
} catch (error) {
|
|
6618
|
-
|
|
6124
|
+
const duplicateAdapter = error?.code === "DUPLICATE_ADAPTER";
|
|
6125
|
+
if (error instanceof Error && (duplicateAdapter || error.message.includes("already registered"))) {
|
|
6619
6126
|
ctx.logger.warn(`loop-engine: provider route "${label}" is already served by another adapter`);
|
|
6620
6127
|
return;
|
|
6621
6128
|
}
|
|
@@ -6625,11 +6132,17 @@ function apply(ctx, config) {
|
|
|
6625
6132
|
const mutatePresetDefault = (op, attempt = 0) => {
|
|
6626
6133
|
const settings = ctx.get("settings");
|
|
6627
6134
|
if (settings === void 0) return;
|
|
6135
|
+
if (settings.describe !== void 0 && !settings.describe().some((entry) => entry.ns === AGENT_PRESETS_NS)) {
|
|
6136
|
+
if (attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6137
|
+
presetRetry = retryLater(() => mutatePresetDefault(op, attempt + 1), PRESET_DEFAULT_RETRY_MS);
|
|
6138
|
+
} else {
|
|
6139
|
+
ctx.logger.error(`loop-engine: preset default switch failed: the "${AGENT_PRESETS_NS}" settings namespace never registered`);
|
|
6140
|
+
}
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6628
6143
|
settings.mutate(AGENT_PRESETS_NS, [op]).then(() => void 0, (error) => {
|
|
6629
6144
|
if (error instanceof Error && error.message.includes("not registered") && attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6630
|
-
presetRetry =
|
|
6631
|
-
mutatePresetDefault(op, attempt + 1);
|
|
6632
|
-
}, PRESET_DEFAULT_RETRY_MS);
|
|
6145
|
+
presetRetry = retryLater(() => mutatePresetDefault(op, attempt + 1), PRESET_DEFAULT_RETRY_MS);
|
|
6633
6146
|
return;
|
|
6634
6147
|
}
|
|
6635
6148
|
ctx.logger.error(`loop-engine: preset default switch failed: ${String(error)}`);
|
|
@@ -6645,9 +6158,7 @@ function apply(ctx, config) {
|
|
|
6645
6158
|
return;
|
|
6646
6159
|
}
|
|
6647
6160
|
if (attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6648
|
-
presetRetry =
|
|
6649
|
-
restorePresetDefault(attempt + 1);
|
|
6650
|
-
}, PRESET_DEFAULT_RETRY_MS);
|
|
6161
|
+
presetRetry = retryLater(() => restorePresetDefault(attempt + 1), PRESET_DEFAULT_RETRY_MS);
|
|
6651
6162
|
}
|
|
6652
6163
|
};
|
|
6653
6164
|
const steerPresetDefault = (engine) => {
|
|
@@ -6682,21 +6193,30 @@ function apply(ctx, config) {
|
|
|
6682
6193
|
};
|
|
6683
6194
|
const hostFactory = (engine, mount) => {
|
|
6684
6195
|
if (engineFiber !== void 0) return;
|
|
6196
|
+
const generation = ++mountGeneration;
|
|
6685
6197
|
const fiber = mount();
|
|
6686
6198
|
engineFiber = fiber;
|
|
6687
6199
|
mountedEngine = engine;
|
|
6688
6200
|
void fiber.then(() => void 0, (error) => {
|
|
6201
|
+
if (generation !== mountGeneration) return;
|
|
6689
6202
|
cleanupEngineRegistrations();
|
|
6690
6203
|
engineFiber = void 0;
|
|
6691
6204
|
mountedEngine = void 0;
|
|
6692
|
-
|
|
6205
|
+
const baseLoopHoldsSlot = ctx.get("agentLoop") !== void 0;
|
|
6206
|
+
if (error instanceof Error && (baseLoopHoldsSlot || error.message.includes("an agent factory is already registered")) && mountAttempts < MAX_MOUNT_ATTEMPTS) {
|
|
6693
6207
|
mountAttempts += 1;
|
|
6694
|
-
mountRetry =
|
|
6695
|
-
|
|
6208
|
+
mountRetry = retryLater(() => {
|
|
6209
|
+
try {
|
|
6210
|
+
mountEngine(engine);
|
|
6211
|
+
} catch (retryError) {
|
|
6212
|
+
ctx.logger.error(`loop-engine: ${engine} mount retry failed: ${String(retryError)}`);
|
|
6213
|
+
}
|
|
6696
6214
|
}, MOUNT_RETRY_MS);
|
|
6697
6215
|
return;
|
|
6698
6216
|
}
|
|
6699
|
-
ctx.logger.error(
|
|
6217
|
+
ctx.logger.error(
|
|
6218
|
+
`loop-engine: ${engine} factory failed to start: ${String(error)} \u2014 restart \`dsh web\` to release the factory slot`
|
|
6219
|
+
);
|
|
6700
6220
|
});
|
|
6701
6221
|
};
|
|
6702
6222
|
const mountClaude = () => {
|
|
@@ -6764,20 +6284,29 @@ function apply(ctx, config) {
|
|
|
6764
6284
|
};
|
|
6765
6285
|
const unmountEngine = () => {
|
|
6766
6286
|
const fiber = engineFiber;
|
|
6287
|
+
const engine = mountedEngine;
|
|
6767
6288
|
mountAttempts = 0;
|
|
6289
|
+
mountGeneration += 1;
|
|
6768
6290
|
CLEAR_RETRY();
|
|
6769
6291
|
releaseRoute();
|
|
6770
6292
|
cleanupEngineRegistrations();
|
|
6771
6293
|
mountedEngine = void 0;
|
|
6772
6294
|
if (fiber === void 0) return;
|
|
6773
6295
|
engineFiber = void 0;
|
|
6774
|
-
void fiber.then(
|
|
6775
|
-
|
|
6776
|
-
|
|
6296
|
+
void fiber.then(
|
|
6297
|
+
(resolved) => {
|
|
6298
|
+
resolved.dispose().catch((error) => {
|
|
6299
|
+
ctx.logger.error(`loop-engine: ${String(engine)} factory dispose failed: ${String(error)}`);
|
|
6300
|
+
});
|
|
6301
|
+
},
|
|
6302
|
+
/* v8 ignore next -- a fiber that failed already cleared engineFiber in hostFactory's rejection handler, so this rejection arm is unreachable */
|
|
6303
|
+
() => void 0
|
|
6304
|
+
);
|
|
6777
6305
|
};
|
|
6778
6306
|
mountEngine(fileEngine);
|
|
6779
6307
|
steerPresetDefault(fileEngine);
|
|
6780
6308
|
ctx.effect(() => () => {
|
|
6309
|
+
disposed = true;
|
|
6781
6310
|
CLEAR_RETRY();
|
|
6782
6311
|
CLEAR_PRESET_RETRY();
|
|
6783
6312
|
releaseRoute();
|
|
@@ -6791,18 +6320,23 @@ function apply(ctx, config) {
|
|
|
6791
6320
|
onChange: () => {
|
|
6792
6321
|
const next = source().engine;
|
|
6793
6322
|
if (next === fileEngine) return;
|
|
6794
|
-
if (mountedEngine !== next) {
|
|
6795
|
-
unmountEngine();
|
|
6796
|
-
mountEngine(next);
|
|
6797
|
-
}
|
|
6798
6323
|
try {
|
|
6799
6324
|
const updated = applyManagedBlock(readPatchFileSync(patchPath), next);
|
|
6800
6325
|
writePatchFileSync(patchPath, updated);
|
|
6801
|
-
fileEngine = next;
|
|
6802
6326
|
} catch (error) {
|
|
6803
6327
|
ctx.logger.error(`loop-engine: managed block write failed: ${String(error)}`);
|
|
6328
|
+
setTimeout(() => {
|
|
6329
|
+
settingsCtx.settings.mutate(loopEngineSettingsNamespace(), [{ op: "set", path: ["engine"], value: fileEngine }]).catch((revertError) => {
|
|
6330
|
+
ctx.logger.error(`loop-engine: engine selection revert failed: ${String(revertError)}`);
|
|
6331
|
+
});
|
|
6332
|
+
}, 0);
|
|
6804
6333
|
return;
|
|
6805
6334
|
}
|
|
6335
|
+
fileEngine = next;
|
|
6336
|
+
if (mountedEngine !== next) {
|
|
6337
|
+
unmountEngine();
|
|
6338
|
+
mountEngine(next);
|
|
6339
|
+
}
|
|
6806
6340
|
steerPresetDefault(next);
|
|
6807
6341
|
}
|
|
6808
6342
|
});
|