dsh-loop-engine 1.0.0-rc6 → 1.0.0-rc8
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 +43 -3
- package/README.zh.md +4 -3
- package/lib/client.js +82 -19
- package/lib/index.js +2249 -460
- package/lib/invariant.js +4 -3
- package/lib/types/client/LoopEngineBadge.d.ts +1 -1
- package/lib/types/client/LoopEngineComposerSelect.d.ts +1 -1
- package/lib/types/client/LoopEngineSection.d.ts +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/store.d.ts +2 -1
- package/lib/types/engine-claude/agent.d.ts +2 -0
- package/lib/types/engine-claude/loop.d.ts +24 -2
- package/lib/types/engine-claude/mapping.d.ts +3 -3
- package/lib/types/engine-codex/agent.d.ts +2 -0
- package/lib/types/engine-codex/appserver/mapping.d.ts +4 -4
- package/lib/types/engine-codex/loop.d.ts +24 -2
- package/lib/types/engine-kimi/acp/client.d.ts +76 -0
- package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
- package/lib/types/engine-kimi/acp/types.d.ts +95 -0
- package/lib/types/engine-kimi/agent.d.ts +123 -0
- package/lib/types/engine-kimi/commands.d.ts +40 -0
- package/lib/types/engine-kimi/loop.d.ts +108 -0
- package/lib/types/engine-kimi/mapping.d.ts +71 -0
- package/lib/types/engine-kimi/permission.d.ts +28 -0
- package/lib/types/engine-kimi/process.d.ts +61 -0
- package/lib/types/engine-kimi/skills.d.ts +57 -0
- package/lib/types/engine-kimi/types.d.ts +23 -0
- package/lib/types/engine-pi/agent.d.ts +2 -0
- package/lib/types/engine-pi/loop.d.ts +24 -2
- package/lib/types/index.d.ts +20 -2
- package/lib/types/patch-manager.d.ts +9 -0
- package/lib/types/preset.d.ts +73 -0
- package/lib/types/provider-route.d.ts +35 -0
- package/lib/types/settings.d.ts +6 -6
- package/package.json +24 -24
package/lib/index.js
CHANGED
|
@@ -46,11 +46,10 @@ 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, readFile as
|
|
50
|
-
import { randomUUID } from "node:crypto";
|
|
51
|
-
import { dirname as
|
|
52
|
-
import
|
|
53
|
-
import { installSettingsSection } from "@deepseek-ai/dsh-settings";
|
|
49
|
+
import { mkdir as mkdir2, readFile as readFile6, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
50
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
51
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
52
|
+
import z6 from "@deepseek-ai/schemastery";
|
|
54
53
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
55
54
|
|
|
56
55
|
// src/engine-claude/loop.ts
|
|
@@ -58,7 +57,7 @@ import { Service } from "@deepseek-ai/cordis";
|
|
|
58
57
|
import z from "@deepseek-ai/schemastery";
|
|
59
58
|
import { emitAgentEvent } from "@deepseek-ai/dsh-agent";
|
|
60
59
|
import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
|
|
61
|
-
import { SessionPreparation } from "@deepseek-ai/dsh-session";
|
|
60
|
+
import { interruptedTurnClosers, SessionLogOffset, SessionPreparation } from "@deepseek-ai/dsh-session";
|
|
62
61
|
|
|
63
62
|
// src/engine-claude/agent.ts
|
|
64
63
|
import { Inbox, agentEvents } from "@deepseek-ai/dsh-agent";
|
|
@@ -69,7 +68,7 @@ import { query as officialQuery } from "@anthropic-ai/claude-agent-sdk";
|
|
|
69
68
|
|
|
70
69
|
// src/engine-claude/mapping.ts
|
|
71
70
|
import {
|
|
72
|
-
|
|
71
|
+
ToolCallId,
|
|
73
72
|
createToolResultMessage
|
|
74
73
|
} from "@deepseek-ai/dsh-llm";
|
|
75
74
|
function stringifyToolInput(input) {
|
|
@@ -88,7 +87,7 @@ function mapAssistantMessage(message) {
|
|
|
88
87
|
content.push({ type: "text", text: block.text });
|
|
89
88
|
break;
|
|
90
89
|
case "tool_use": {
|
|
91
|
-
const callId =
|
|
90
|
+
const callId = ToolCallId(block.id);
|
|
92
91
|
content.push({
|
|
93
92
|
type: "tool-call",
|
|
94
93
|
id: callId,
|
|
@@ -123,7 +122,7 @@ function mapToolResults(message) {
|
|
|
123
122
|
for (const block of content) {
|
|
124
123
|
if (block.type !== "tool_result") continue;
|
|
125
124
|
results.push(createToolResultMessage({
|
|
126
|
-
callId:
|
|
125
|
+
callId: ToolCallId(block.tool_use_id),
|
|
127
126
|
content: toolResultContent(block.content),
|
|
128
127
|
isError: block.is_error === true
|
|
129
128
|
}));
|
|
@@ -166,7 +165,7 @@ function mapStreamEvent(event, toolCalls) {
|
|
|
166
165
|
return [{ type: "block-start", index: event.index, blockType: "reasoning" }];
|
|
167
166
|
}
|
|
168
167
|
if (block.type === "tool_use") {
|
|
169
|
-
toolCalls.set(event.index, { callId:
|
|
168
|
+
toolCalls.set(event.index, { callId: ToolCallId(block.id), name: block.name });
|
|
170
169
|
return [{ type: "block-start", index: event.index, blockType: "tool-call" }];
|
|
171
170
|
}
|
|
172
171
|
return [];
|
|
@@ -184,7 +183,7 @@ function mapStreamEvent(event, toolCalls) {
|
|
|
184
183
|
return [{
|
|
185
184
|
type: "tool-call-delta",
|
|
186
185
|
index: event.index,
|
|
187
|
-
id: call?.callId ??
|
|
186
|
+
id: call?.callId ?? ToolCallId(`call-${event.index}`),
|
|
188
187
|
...call === void 0 ? {} : { name: call.name },
|
|
189
188
|
argumentsDelta: delta.partial_json
|
|
190
189
|
}];
|
|
@@ -552,7 +551,7 @@ var ClaudeCodeAgent = class {
|
|
|
552
551
|
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
553
552
|
}
|
|
554
553
|
});
|
|
555
|
-
const lastTurn = session.
|
|
554
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
556
555
|
this.phase = { kind: "idle", lastTurn };
|
|
557
556
|
this.scope = createScope(loopCtx, this);
|
|
558
557
|
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
@@ -756,7 +755,7 @@ var ClaudeCodeAgent = class {
|
|
|
756
755
|
*/
|
|
757
756
|
queryPermission() {
|
|
758
757
|
if (this.config.permissionMode !== void 0) return { permissionMode: this.config.permissionMode };
|
|
759
|
-
const permission = resolveSessionPermission(this.session.
|
|
758
|
+
const permission = resolveSessionPermission(this.session.snapshotEvents());
|
|
760
759
|
if (permission.kind === "bypass") return { permissionMode: "bypassPermissions" };
|
|
761
760
|
if (permission.kind === "ask") {
|
|
762
761
|
const approval = this.loopCtx.get("approval");
|
|
@@ -1162,7 +1161,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1162
1161
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
1163
1162
|
*/
|
|
1164
1163
|
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the default agent-loop factory; depending on agent-loop is forbidden. */
|
|
1165
|
-
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
1164
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
1166
1165
|
ownerCtx.fiber.assertActive();
|
|
1167
1166
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
1168
1167
|
if (callerSignal?.aborted) {
|
|
@@ -1196,11 +1195,15 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1196
1195
|
}
|
|
1197
1196
|
} finally {
|
|
1198
1197
|
try {
|
|
1199
|
-
|
|
1200
|
-
detachSession?.();
|
|
1198
|
+
await handle?.close();
|
|
1201
1199
|
} finally {
|
|
1202
|
-
|
|
1203
|
-
|
|
1200
|
+
try {
|
|
1201
|
+
detachAgent?.();
|
|
1202
|
+
detachSession?.();
|
|
1203
|
+
} finally {
|
|
1204
|
+
untrack();
|
|
1205
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
1206
|
+
}
|
|
1204
1207
|
}
|
|
1205
1208
|
}
|
|
1206
1209
|
})();
|
|
@@ -1250,18 +1253,27 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1250
1253
|
}
|
|
1251
1254
|
}
|
|
1252
1255
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
1253
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
1256
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
1254
1257
|
var _stack = [];
|
|
1255
1258
|
try {
|
|
1256
1259
|
const ownedPreparation = __using(_stack, preparation);
|
|
1257
1260
|
const session = ownedPreparation.session;
|
|
1258
|
-
|
|
1261
|
+
let prepared;
|
|
1262
|
+
try {
|
|
1263
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
await stored?.handle.close().catch(() => {
|
|
1266
|
+
});
|
|
1267
|
+
throw error;
|
|
1268
|
+
}
|
|
1259
1269
|
try {
|
|
1260
1270
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
1261
1271
|
setupCommit?.commit();
|
|
1272
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
1262
1273
|
return prepared.publish(source);
|
|
1263
1274
|
} catch (error) {
|
|
1264
|
-
await prepared.dispose()
|
|
1275
|
+
await prepared.dispose().catch(() => {
|
|
1276
|
+
});
|
|
1265
1277
|
throw error;
|
|
1266
1278
|
}
|
|
1267
1279
|
} catch (_) {
|
|
@@ -1272,7 +1284,8 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1272
1284
|
}
|
|
1273
1285
|
/**
|
|
1274
1286
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
1275
|
-
* the accessing fiber.
|
|
1287
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
1288
|
+
* durable identity is stored before publication.
|
|
1276
1289
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
1277
1290
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
1278
1291
|
* @returns the published handle.
|
|
@@ -1280,20 +1293,73 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1280
1293
|
async createAgent(ownerCtx, options) {
|
|
1281
1294
|
const preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
1282
1295
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
1283
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
1296
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
1297
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
1284
1298
|
}));
|
|
1285
|
-
const published =
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1299
|
+
const published = (async () => {
|
|
1300
|
+
let stored;
|
|
1301
|
+
try {
|
|
1302
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
1303
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
1304
|
+
options.signal,
|
|
1305
|
+
options.sessionId,
|
|
1306
|
+
(abandoned) => {
|
|
1307
|
+
void abandoned?.handle.close().catch(() => {
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
preparation[Symbol.dispose]();
|
|
1313
|
+
throw error;
|
|
1314
|
+
}
|
|
1315
|
+
return this.setupAndPublish(
|
|
1316
|
+
ownerCtx,
|
|
1317
|
+
options.sessionId,
|
|
1318
|
+
preparation,
|
|
1319
|
+
options.agentOptions ?? {},
|
|
1320
|
+
options.setup,
|
|
1321
|
+
options.signal,
|
|
1322
|
+
"startup",
|
|
1323
|
+
stored
|
|
1324
|
+
);
|
|
1325
|
+
})();
|
|
1294
1326
|
this.ownership.trackWrapper(published);
|
|
1295
1327
|
return published;
|
|
1296
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
1331
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
1332
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
1333
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
1334
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
1335
|
+
* created again.
|
|
1336
|
+
* @param session - the unpublished session to store.
|
|
1337
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
1338
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
1339
|
+
*/
|
|
1340
|
+
async createStoredSession(session, signal) {
|
|
1341
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
1342
|
+
if (persistence === void 0) return void 0;
|
|
1343
|
+
const handle = await persistence.create(session.header, {
|
|
1344
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
1345
|
+
...signal === void 0 ? {} : { signal }
|
|
1346
|
+
});
|
|
1347
|
+
return { handle, storedCount: 0 };
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Durably store the session events appended since the last stored cursor.
|
|
1351
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
1352
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
1353
|
+
* through the handle before live events start routing into it.
|
|
1354
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
1355
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
1356
|
+
*/
|
|
1357
|
+
async appendUnstoredSuffix(stored, session) {
|
|
1358
|
+
if (stored === void 0) return;
|
|
1359
|
+
const suffix = session.snapshotEvents(SessionLogOffset(stored.storedCount));
|
|
1360
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
1361
|
+
stored.storedCount += suffix.length;
|
|
1362
|
+
}
|
|
1297
1363
|
/**
|
|
1298
1364
|
* Resume an owned agent from the configured persistence service.
|
|
1299
1365
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -1307,11 +1373,10 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1307
1373
|
}
|
|
1308
1374
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
1309
1375
|
}
|
|
1310
|
-
/** Resume through an explicit persistence
|
|
1311
|
-
|
|
1376
|
+
/** Resume through an explicit persistence service. */
|
|
1377
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
1312
1378
|
const id = options.resumeSessionId;
|
|
1313
|
-
|
|
1314
|
-
try {
|
|
1379
|
+
const published = (async () => {
|
|
1315
1380
|
const ownerAbort = new AbortController();
|
|
1316
1381
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
1317
1382
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -1321,32 +1386,56 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1321
1386
|
ownerAbort.signal,
|
|
1322
1387
|
this.ownership.signal
|
|
1323
1388
|
]);
|
|
1389
|
+
let handle;
|
|
1390
|
+
let stored;
|
|
1391
|
+
let preparation;
|
|
1324
1392
|
try {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1393
|
+
try {
|
|
1394
|
+
handle = await raceAbortCall(
|
|
1395
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
1396
|
+
fused,
|
|
1397
|
+
id,
|
|
1398
|
+
(abandoned) => {
|
|
1399
|
+
void abandoned.close();
|
|
1400
|
+
}
|
|
1401
|
+
);
|
|
1402
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
1403
|
+
fused.throwIfAborted();
|
|
1404
|
+
const closers = interruptedTurnClosers(persisted);
|
|
1405
|
+
if (closers.length > 0) await handle.append(closers);
|
|
1406
|
+
preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(id, {
|
|
1407
|
+
seed: [...persisted, ...closers],
|
|
1408
|
+
meta: structuredClone(handle.header),
|
|
1409
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
1410
|
+
seedSource: "persistence"
|
|
1411
|
+
}));
|
|
1412
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
1413
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
1414
|
+
} finally {
|
|
1415
|
+
await unfollowOwner();
|
|
1416
|
+
}
|
|
1417
|
+
ownerCtx.fiber.assertActive();
|
|
1418
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
1419
|
+
const owned = stored;
|
|
1420
|
+
handle = void 0;
|
|
1421
|
+
return await this.setupAndPublish(
|
|
1422
|
+
ownerCtx,
|
|
1328
1423
|
id,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1424
|
+
preparation,
|
|
1425
|
+
options.agentOptions ?? {},
|
|
1426
|
+
options.setup,
|
|
1427
|
+
options.signal,
|
|
1428
|
+
"resume",
|
|
1429
|
+
owned
|
|
1332
1430
|
);
|
|
1333
1431
|
} finally {
|
|
1334
|
-
|
|
1432
|
+
preparation?.[Symbol.dispose]();
|
|
1433
|
+
await handle?.close().catch(() => {
|
|
1434
|
+
});
|
|
1335
1435
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
ownerCtx,
|
|
1340
|
-
id,
|
|
1341
|
-
preparation,
|
|
1342
|
-
options.agentOptions ?? {},
|
|
1343
|
-
options.setup,
|
|
1344
|
-
options.signal,
|
|
1345
|
-
"resume"
|
|
1346
|
-
);
|
|
1347
|
-
} finally {
|
|
1348
|
-
preparation?.[Symbol.dispose]();
|
|
1349
|
-
}
|
|
1436
|
+
})();
|
|
1437
|
+
this.ownership.trackWrapper(published);
|
|
1438
|
+
return published;
|
|
1350
1439
|
}
|
|
1351
1440
|
};
|
|
1352
1441
|
|
|
@@ -1354,7 +1443,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1354
1443
|
import { Service as Service2 } from "@deepseek-ai/cordis";
|
|
1355
1444
|
import z2 from "@deepseek-ai/schemastery";
|
|
1356
1445
|
import { emitAgentEvent as emitAgentEvent2 } from "@deepseek-ai/dsh-agent";
|
|
1357
|
-
import { SessionPreparation as SessionPreparation2 } from "@deepseek-ai/dsh-session";
|
|
1446
|
+
import { interruptedTurnClosers as interruptedTurnClosers2, SessionLogOffset as SessionLogOffset2, SessionPreparation as SessionPreparation2 } from "@deepseek-ai/dsh-session";
|
|
1358
1447
|
|
|
1359
1448
|
// src/engine-codex/agent.ts
|
|
1360
1449
|
import { Inbox as Inbox2, agentEvents as agentEvents2 } from "@deepseek-ai/dsh-agent";
|
|
@@ -1441,7 +1530,7 @@ var AppServerClient = class _AppServerClient {
|
|
|
1441
1530
|
clientInfo: {
|
|
1442
1531
|
name: "dsh-loop-engine",
|
|
1443
1532
|
title: null,
|
|
1444
|
-
version: "0.
|
|
1533
|
+
version: "1.0.0-rc8"
|
|
1445
1534
|
},
|
|
1446
1535
|
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
1447
1536
|
};
|
|
@@ -1478,8 +1567,8 @@ var AppServerClient = class _AppServerClient {
|
|
|
1478
1567
|
}
|
|
1479
1568
|
const id = this.reqId++;
|
|
1480
1569
|
const msg = { jsonrpc: "2.0", id, method, params };
|
|
1481
|
-
return new Promise((
|
|
1482
|
-
this.pending.set(id, { resolve:
|
|
1570
|
+
return new Promise((resolve5, reject) => {
|
|
1571
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
1483
1572
|
this.process.stdin.write(JSON.stringify(msg) + "\n");
|
|
1484
1573
|
});
|
|
1485
1574
|
}
|
|
@@ -1530,7 +1619,7 @@ var AppServerThread = class _AppServerThread {
|
|
|
1530
1619
|
const { signal, params } = options;
|
|
1531
1620
|
const queue = [];
|
|
1532
1621
|
const earlyNotifications = [];
|
|
1533
|
-
let
|
|
1622
|
+
let resolve5;
|
|
1534
1623
|
let done = false;
|
|
1535
1624
|
let turnError;
|
|
1536
1625
|
let turnId;
|
|
@@ -1604,7 +1693,7 @@ var AppServerThread = class _AppServerThread {
|
|
|
1604
1693
|
}
|
|
1605
1694
|
if (event) {
|
|
1606
1695
|
queue.push(event);
|
|
1607
|
-
|
|
1696
|
+
resolve5?.();
|
|
1608
1697
|
}
|
|
1609
1698
|
};
|
|
1610
1699
|
this.client.onNotification(notificationHandler);
|
|
@@ -1630,7 +1719,7 @@ var AppServerThread = class _AppServerThread {
|
|
|
1630
1719
|
void this.client.turnInterrupt({ threadId: this.threadId, turnId }).catch(() => {
|
|
1631
1720
|
});
|
|
1632
1721
|
}
|
|
1633
|
-
|
|
1722
|
+
resolve5?.();
|
|
1634
1723
|
};
|
|
1635
1724
|
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1636
1725
|
try {
|
|
@@ -1640,9 +1729,9 @@ var AppServerThread = class _AppServerThread {
|
|
|
1640
1729
|
yield queue.shift();
|
|
1641
1730
|
} else {
|
|
1642
1731
|
await new Promise((r) => {
|
|
1643
|
-
|
|
1732
|
+
resolve5 = r;
|
|
1644
1733
|
});
|
|
1645
|
-
|
|
1734
|
+
resolve5 = void 0;
|
|
1646
1735
|
}
|
|
1647
1736
|
}
|
|
1648
1737
|
if (turnError) throw turnError;
|
|
@@ -1656,7 +1745,7 @@ function noopNotificationHandler() {
|
|
|
1656
1745
|
}
|
|
1657
1746
|
|
|
1658
1747
|
// src/engine-codex/appserver/mapping.ts
|
|
1659
|
-
import {
|
|
1748
|
+
import { ToolCallId as ToolCallId2, createToolResultMessage as createToolResultMessage2 } from "@deepseek-ai/dsh-llm";
|
|
1660
1749
|
function mapUsage2(usage) {
|
|
1661
1750
|
return {
|
|
1662
1751
|
inputTokens: usage.inputTokens,
|
|
@@ -1668,12 +1757,12 @@ function mapUsage2(usage) {
|
|
|
1668
1757
|
function mapCommandExecution(item) {
|
|
1669
1758
|
return {
|
|
1670
1759
|
call: {
|
|
1671
|
-
callId:
|
|
1760
|
+
callId: ToolCallId2(item.id),
|
|
1672
1761
|
name: "command_execution",
|
|
1673
1762
|
arguments: JSON.stringify({ command: item.command ?? "" })
|
|
1674
1763
|
},
|
|
1675
1764
|
result: createToolResultMessage2({
|
|
1676
|
-
callId:
|
|
1765
|
+
callId: ToolCallId2(item.id),
|
|
1677
1766
|
content: [{ type: "text", text: item.aggregatedOutput ?? "" }],
|
|
1678
1767
|
isError: (item.exitCode ?? 0) !== 0 || item.status === "failed"
|
|
1679
1768
|
})
|
|
@@ -1682,12 +1771,12 @@ function mapCommandExecution(item) {
|
|
|
1682
1771
|
function mapFileChange(item) {
|
|
1683
1772
|
return {
|
|
1684
1773
|
call: {
|
|
1685
|
-
callId:
|
|
1774
|
+
callId: ToolCallId2(item.id),
|
|
1686
1775
|
name: "apply_patch",
|
|
1687
1776
|
arguments: JSON.stringify(item.changes ?? [])
|
|
1688
1777
|
},
|
|
1689
1778
|
result: createToolResultMessage2({
|
|
1690
|
-
callId:
|
|
1779
|
+
callId: ToolCallId2(item.id),
|
|
1691
1780
|
content: [{ type: "text", text: `patch ${item.status ?? "completed"}` }],
|
|
1692
1781
|
isError: item.status === "failed"
|
|
1693
1782
|
})
|
|
@@ -1698,12 +1787,12 @@ function mapMcpToolCall(item) {
|
|
|
1698
1787
|
const isError = item.error !== void 0 && item.error !== null;
|
|
1699
1788
|
return {
|
|
1700
1789
|
call: {
|
|
1701
|
-
callId:
|
|
1790
|
+
callId: ToolCallId2(item.id),
|
|
1702
1791
|
name: name2,
|
|
1703
1792
|
arguments: JSON.stringify(item.arguments ?? {})
|
|
1704
1793
|
},
|
|
1705
1794
|
result: createToolResultMessage2({
|
|
1706
|
-
callId:
|
|
1795
|
+
callId: ToolCallId2(item.id),
|
|
1707
1796
|
content: isError ? [{ type: "text", text: item.error?.message ?? "tool call failed" }] : [{ type: "text", text: JSON.stringify(item.result?.content ?? []) }],
|
|
1708
1797
|
isError
|
|
1709
1798
|
})
|
|
@@ -1732,7 +1821,7 @@ var CodexAgent = class {
|
|
|
1732
1821
|
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
1733
1822
|
}
|
|
1734
1823
|
});
|
|
1735
|
-
const lastTurn = session.
|
|
1824
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
1736
1825
|
this.phase = { kind: "idle", lastTurn };
|
|
1737
1826
|
this.scope = createScope2(loopCtx, this);
|
|
1738
1827
|
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
@@ -1945,7 +2034,7 @@ var CodexAgent = class {
|
|
|
1945
2034
|
* @returns the permission fields of the query spec.
|
|
1946
2035
|
*/
|
|
1947
2036
|
queryPermission() {
|
|
1948
|
-
const fold = resolveSessionPermission2(this.session.
|
|
2037
|
+
const fold = resolveSessionPermission2(this.session.snapshotEvents());
|
|
1949
2038
|
return {
|
|
1950
2039
|
sandboxMode: this.config.sandboxMode ?? fold.sandboxMode,
|
|
1951
2040
|
approvalPolicy: this.config.approvalPolicy ?? fold.approvalPolicy
|
|
@@ -2298,7 +2387,7 @@ var CodexLoop = class extends Service2 {
|
|
|
2298
2387
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
2299
2388
|
*/
|
|
2300
2389
|
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
2301
|
-
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
2390
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
2302
2391
|
ownerCtx.fiber.assertActive();
|
|
2303
2392
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
2304
2393
|
if (callerSignal?.aborted) {
|
|
@@ -2332,11 +2421,15 @@ var CodexLoop = class extends Service2 {
|
|
|
2332
2421
|
}
|
|
2333
2422
|
} finally {
|
|
2334
2423
|
try {
|
|
2335
|
-
|
|
2336
|
-
detachSession?.();
|
|
2424
|
+
await handle?.close();
|
|
2337
2425
|
} finally {
|
|
2338
|
-
|
|
2339
|
-
|
|
2426
|
+
try {
|
|
2427
|
+
detachAgent?.();
|
|
2428
|
+
detachSession?.();
|
|
2429
|
+
} finally {
|
|
2430
|
+
untrack();
|
|
2431
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
2432
|
+
}
|
|
2340
2433
|
}
|
|
2341
2434
|
}
|
|
2342
2435
|
})();
|
|
@@ -2386,18 +2479,27 @@ var CodexLoop = class extends Service2 {
|
|
|
2386
2479
|
}
|
|
2387
2480
|
}
|
|
2388
2481
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
2389
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
2482
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
2390
2483
|
var _stack = [];
|
|
2391
2484
|
try {
|
|
2392
2485
|
const ownedPreparation = __using(_stack, preparation);
|
|
2393
2486
|
const session = ownedPreparation.session;
|
|
2394
|
-
|
|
2487
|
+
let prepared;
|
|
2488
|
+
try {
|
|
2489
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
await stored?.handle.close().catch(() => {
|
|
2492
|
+
});
|
|
2493
|
+
throw error;
|
|
2494
|
+
}
|
|
2395
2495
|
try {
|
|
2396
2496
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
2397
2497
|
setupCommit?.commit();
|
|
2498
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
2398
2499
|
return prepared.publish(source);
|
|
2399
2500
|
} catch (error) {
|
|
2400
|
-
await prepared.dispose()
|
|
2501
|
+
await prepared.dispose().catch(() => {
|
|
2502
|
+
});
|
|
2401
2503
|
throw error;
|
|
2402
2504
|
}
|
|
2403
2505
|
} catch (_) {
|
|
@@ -2408,7 +2510,8 @@ var CodexLoop = class extends Service2 {
|
|
|
2408
2510
|
}
|
|
2409
2511
|
/**
|
|
2410
2512
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
2411
|
-
* the accessing fiber.
|
|
2513
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
2514
|
+
* durable identity is stored before publication.
|
|
2412
2515
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
2413
2516
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
2414
2517
|
* @returns the published handle.
|
|
@@ -2416,20 +2519,73 @@ var CodexLoop = class extends Service2 {
|
|
|
2416
2519
|
async createAgent(ownerCtx, options) {
|
|
2417
2520
|
const preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
2418
2521
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
2419
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
2522
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
2523
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
2420
2524
|
}));
|
|
2421
|
-
const published =
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2525
|
+
const published = (async () => {
|
|
2526
|
+
let stored;
|
|
2527
|
+
try {
|
|
2528
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
2529
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
2530
|
+
options.signal,
|
|
2531
|
+
options.sessionId,
|
|
2532
|
+
(abandoned) => {
|
|
2533
|
+
void abandoned?.handle.close().catch(() => {
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
);
|
|
2537
|
+
} catch (error) {
|
|
2538
|
+
preparation[Symbol.dispose]();
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2541
|
+
return this.setupAndPublish(
|
|
2542
|
+
ownerCtx,
|
|
2543
|
+
options.sessionId,
|
|
2544
|
+
preparation,
|
|
2545
|
+
options.agentOptions ?? {},
|
|
2546
|
+
options.setup,
|
|
2547
|
+
options.signal,
|
|
2548
|
+
"startup",
|
|
2549
|
+
stored
|
|
2550
|
+
);
|
|
2551
|
+
})();
|
|
2430
2552
|
this.ownership.trackWrapper(published);
|
|
2431
2553
|
return published;
|
|
2432
2554
|
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
2557
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
2558
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
2559
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
2560
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
2561
|
+
* created again.
|
|
2562
|
+
* @param session - the unpublished session to store.
|
|
2563
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
2564
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
2565
|
+
*/
|
|
2566
|
+
async createStoredSession(session, signal) {
|
|
2567
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
2568
|
+
if (persistence === void 0) return void 0;
|
|
2569
|
+
const handle = await persistence.create(session.header, {
|
|
2570
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
2571
|
+
...signal === void 0 ? {} : { signal }
|
|
2572
|
+
});
|
|
2573
|
+
return { handle, storedCount: 0 };
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Durably store the session events appended since the last stored cursor.
|
|
2577
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
2578
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
2579
|
+
* through the handle before live events start routing into it.
|
|
2580
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
2581
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
2582
|
+
*/
|
|
2583
|
+
async appendUnstoredSuffix(stored, session) {
|
|
2584
|
+
if (stored === void 0) return;
|
|
2585
|
+
const suffix = session.snapshotEvents(SessionLogOffset2(stored.storedCount));
|
|
2586
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
2587
|
+
stored.storedCount += suffix.length;
|
|
2588
|
+
}
|
|
2433
2589
|
/**
|
|
2434
2590
|
* Resume an owned agent from the configured persistence service.
|
|
2435
2591
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -2443,11 +2599,10 @@ var CodexLoop = class extends Service2 {
|
|
|
2443
2599
|
}
|
|
2444
2600
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
2445
2601
|
}
|
|
2446
|
-
/** Resume through an explicit persistence
|
|
2447
|
-
|
|
2602
|
+
/** Resume through an explicit persistence service. */
|
|
2603
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
2448
2604
|
const id = options.resumeSessionId;
|
|
2449
|
-
|
|
2450
|
-
try {
|
|
2605
|
+
const published = (async () => {
|
|
2451
2606
|
const ownerAbort = new AbortController();
|
|
2452
2607
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
2453
2608
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -2457,32 +2612,56 @@ var CodexLoop = class extends Service2 {
|
|
|
2457
2612
|
ownerAbort.signal,
|
|
2458
2613
|
this.ownership.signal
|
|
2459
2614
|
]);
|
|
2615
|
+
let handle;
|
|
2616
|
+
let stored;
|
|
2617
|
+
let preparation;
|
|
2460
2618
|
try {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2619
|
+
try {
|
|
2620
|
+
handle = await raceAbortCall(
|
|
2621
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
2622
|
+
fused,
|
|
2623
|
+
id,
|
|
2624
|
+
(abandoned) => {
|
|
2625
|
+
void abandoned.close();
|
|
2626
|
+
}
|
|
2627
|
+
);
|
|
2628
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
2629
|
+
fused.throwIfAborted();
|
|
2630
|
+
const closers = interruptedTurnClosers2(persisted);
|
|
2631
|
+
if (closers.length > 0) await handle.append(closers);
|
|
2632
|
+
preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(id, {
|
|
2633
|
+
seed: [...persisted, ...closers],
|
|
2634
|
+
meta: structuredClone(handle.header),
|
|
2635
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
2636
|
+
seedSource: "persistence"
|
|
2637
|
+
}));
|
|
2638
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
2639
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
2640
|
+
} finally {
|
|
2641
|
+
await unfollowOwner();
|
|
2642
|
+
}
|
|
2643
|
+
ownerCtx.fiber.assertActive();
|
|
2644
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
2645
|
+
const owned = stored;
|
|
2646
|
+
handle = void 0;
|
|
2647
|
+
return await this.setupAndPublish(
|
|
2648
|
+
ownerCtx,
|
|
2464
2649
|
id,
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2650
|
+
preparation,
|
|
2651
|
+
options.agentOptions ?? {},
|
|
2652
|
+
options.setup,
|
|
2653
|
+
options.signal,
|
|
2654
|
+
"resume",
|
|
2655
|
+
owned
|
|
2468
2656
|
);
|
|
2469
2657
|
} finally {
|
|
2470
|
-
|
|
2658
|
+
preparation?.[Symbol.dispose]();
|
|
2659
|
+
await handle?.close().catch(() => {
|
|
2660
|
+
});
|
|
2471
2661
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
ownerCtx,
|
|
2476
|
-
id,
|
|
2477
|
-
preparation,
|
|
2478
|
-
options.agentOptions ?? {},
|
|
2479
|
-
options.setup,
|
|
2480
|
-
options.signal,
|
|
2481
|
-
"resume"
|
|
2482
|
-
);
|
|
2483
|
-
} finally {
|
|
2484
|
-
preparation?.[Symbol.dispose]();
|
|
2485
|
-
}
|
|
2662
|
+
})();
|
|
2663
|
+
this.ownership.trackWrapper(published);
|
|
2664
|
+
return published;
|
|
2486
2665
|
}
|
|
2487
2666
|
};
|
|
2488
2667
|
|
|
@@ -2493,11 +2672,11 @@ import { fileURLToPath } from "node:url";
|
|
|
2493
2672
|
import { Service as Service3 } from "@deepseek-ai/cordis";
|
|
2494
2673
|
import z3 from "@deepseek-ai/schemastery";
|
|
2495
2674
|
import { emitAgentEvent as emitAgentEvent3 } from "@deepseek-ai/dsh-agent";
|
|
2496
|
-
import { SessionPreparation as SessionPreparation3 } from "@deepseek-ai/dsh-session";
|
|
2675
|
+
import { interruptedTurnClosers as interruptedTurnClosers3, SessionLogOffset as SessionLogOffset3, SessionPreparation as SessionPreparation3 } from "@deepseek-ai/dsh-session";
|
|
2497
2676
|
|
|
2498
2677
|
// src/engine-pi/agent.ts
|
|
2499
2678
|
import { Inbox as Inbox3, agentEvents as agentEvents3 } from "@deepseek-ai/dsh-agent";
|
|
2500
|
-
import {
|
|
2679
|
+
import { ToolCallId as ToolCallId4, LlmError as LlmError3, createAssistantMessage as createAssistantMessage3, createUserMessage as createUserMessage3, errorChain as errorChain3 } from "@deepseek-ai/dsh-llm";
|
|
2501
2680
|
import { createScope as createScope3 } from "@deepseek-ai/dsh-scope";
|
|
2502
2681
|
import { canonicalHeader as canonicalHeader3 } from "@deepseek-ai/dsh-session";
|
|
2503
2682
|
|
|
@@ -2591,8 +2770,8 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2591
2770
|
* absent falls back to the plain node child spawn.
|
|
2592
2771
|
* @returns the connected client.
|
|
2593
2772
|
*/
|
|
2594
|
-
static create(spec,
|
|
2595
|
-
const process2 =
|
|
2773
|
+
static create(spec, spawn4) {
|
|
2774
|
+
const process2 = spawn4 === void 0 ? defaultSpawn(spec) : spawn4(spec);
|
|
2596
2775
|
return new _PiRpcClient(process2);
|
|
2597
2776
|
}
|
|
2598
2777
|
/** Register the event dispatch handler. */
|
|
@@ -2637,8 +2816,8 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2637
2816
|
if (this.disposed) throw new Error("pi RPC client is disposed");
|
|
2638
2817
|
const id = command.id ?? this.reqId++;
|
|
2639
2818
|
const wire = { ...command, id };
|
|
2640
|
-
return new Promise((
|
|
2641
|
-
this.pending.set(id, { resolve:
|
|
2819
|
+
return new Promise((resolve5, reject) => {
|
|
2820
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
2642
2821
|
this.process.stdin.write(`${JSON.stringify(wire)}
|
|
2643
2822
|
`);
|
|
2644
2823
|
});
|
|
@@ -2655,8 +2834,8 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2655
2834
|
continue;
|
|
2656
2835
|
}
|
|
2657
2836
|
if (this.disposed) return;
|
|
2658
|
-
await new Promise((
|
|
2659
|
-
this.eventWake =
|
|
2837
|
+
await new Promise((resolve5) => {
|
|
2838
|
+
this.eventWake = resolve5;
|
|
2660
2839
|
});
|
|
2661
2840
|
this.eventWake = void 0;
|
|
2662
2841
|
}
|
|
@@ -2717,7 +2896,7 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2717
2896
|
|
|
2718
2897
|
// src/engine-pi/rpc/mapping.ts
|
|
2719
2898
|
import {
|
|
2720
|
-
|
|
2899
|
+
ToolCallId as ToolCallId3,
|
|
2721
2900
|
createToolResultMessage as createToolResultMessage3
|
|
2722
2901
|
} from "@deepseek-ai/dsh-llm";
|
|
2723
2902
|
function mapUsage3(usage) {
|
|
@@ -2748,7 +2927,7 @@ function resultText(content) {
|
|
|
2748
2927
|
}
|
|
2749
2928
|
function mapToolResult(ev) {
|
|
2750
2929
|
return createToolResultMessage3({
|
|
2751
|
-
callId:
|
|
2930
|
+
callId: ToolCallId3(ev.toolCallId),
|
|
2752
2931
|
content: [{ type: "text", text: resultText(ev.result) || "(no content)" }],
|
|
2753
2932
|
isError: ev.isError
|
|
2754
2933
|
});
|
|
@@ -2763,13 +2942,13 @@ function specsEqual(a, b) {
|
|
|
2763
2942
|
return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
|
|
2764
2943
|
}
|
|
2765
2944
|
var PiAgent = class {
|
|
2766
|
-
constructor(loopCtx, id, options, session, config,
|
|
2945
|
+
constructor(loopCtx, id, options, session, config, spawn4, bin) {
|
|
2767
2946
|
this.loopCtx = loopCtx;
|
|
2768
2947
|
this.id = id;
|
|
2769
2948
|
this.options = options;
|
|
2770
2949
|
this.session = session;
|
|
2771
2950
|
this.config = config;
|
|
2772
|
-
this.spawn =
|
|
2951
|
+
this.spawn = spawn4;
|
|
2773
2952
|
this.bin = bin;
|
|
2774
2953
|
this.dispatch = agentEvents3(loopCtx, this);
|
|
2775
2954
|
this.inbox = new Inbox3(session, {
|
|
@@ -2783,7 +2962,7 @@ var PiAgent = class {
|
|
|
2783
2962
|
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
2784
2963
|
}
|
|
2785
2964
|
});
|
|
2786
|
-
const lastTurn = session.
|
|
2965
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
2787
2966
|
this.phase = { kind: "idle", lastTurn };
|
|
2788
2967
|
this.scope = createScope3(loopCtx, this);
|
|
2789
2968
|
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
@@ -3004,7 +3183,7 @@ var PiAgent = class {
|
|
|
3004
3183
|
* @returns the permission fields of the query spec.
|
|
3005
3184
|
*/
|
|
3006
3185
|
queryPermission() {
|
|
3007
|
-
const fold = resolveSessionPermission3(this.session.
|
|
3186
|
+
const fold = resolveSessionPermission3(this.session.snapshotEvents());
|
|
3008
3187
|
const sandboxMode = this.config.sandboxMode ?? fold.sandboxMode;
|
|
3009
3188
|
return {
|
|
3010
3189
|
sandboxMode,
|
|
@@ -3215,7 +3394,7 @@ var PiAgent = class {
|
|
|
3215
3394
|
this.session.append("tool/call", {
|
|
3216
3395
|
turn,
|
|
3217
3396
|
step,
|
|
3218
|
-
callId:
|
|
3397
|
+
callId: ToolCallId4(callId),
|
|
3219
3398
|
name: name2,
|
|
3220
3399
|
arguments: argumentsValue
|
|
3221
3400
|
});
|
|
@@ -3316,8 +3495,8 @@ var PiAgent = class {
|
|
|
3316
3495
|
held = { content: contentOf(event.message), refs: [...chunkSeqs] };
|
|
3317
3496
|
chunkSeqs.length = 0;
|
|
3318
3497
|
}
|
|
3319
|
-
for (const
|
|
3320
|
-
this.appendToolResult(turn, step,
|
|
3498
|
+
for (const toolResult2 of event.toolResults ?? []) {
|
|
3499
|
+
this.appendToolResult(turn, step, toolResult2);
|
|
3321
3500
|
}
|
|
3322
3501
|
flushHeld(lastUsage);
|
|
3323
3502
|
finished = true;
|
|
@@ -3350,15 +3529,15 @@ var PiAgent = class {
|
|
|
3350
3529
|
}
|
|
3351
3530
|
}
|
|
3352
3531
|
/** Append one Pi tool result to the durable log as a `tool/result` message. */
|
|
3353
|
-
appendToolResult(turn, step,
|
|
3354
|
-
const text = typeof
|
|
3532
|
+
appendToolResult(turn, step, toolResult2) {
|
|
3533
|
+
const text = typeof toolResult2.content === "string" ? toolResult2.content : toolResult2.content.map((block) => block.type === "text" ? block.text : "").filter((segment) => segment !== "").join("\n\n");
|
|
3355
3534
|
this.session.append("tool/result", {
|
|
3356
3535
|
turn,
|
|
3357
3536
|
step,
|
|
3358
3537
|
message: mapToolResult({
|
|
3359
|
-
toolCallId:
|
|
3538
|
+
toolCallId: toolResult2.toolCallId,
|
|
3360
3539
|
result: { content: [{ type: "text", text }] },
|
|
3361
|
-
isError:
|
|
3540
|
+
isError: toolResult2.isError === true
|
|
3362
3541
|
})
|
|
3363
3542
|
}, { surfaceOp: "append" });
|
|
3364
3543
|
}
|
|
@@ -3452,7 +3631,7 @@ var PiLoop = class extends Service3 {
|
|
|
3452
3631
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
3453
3632
|
*/
|
|
3454
3633
|
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
3455
|
-
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
3634
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
3456
3635
|
ownerCtx.fiber.assertActive();
|
|
3457
3636
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
3458
3637
|
if (callerSignal?.aborted) {
|
|
@@ -3486,11 +3665,15 @@ var PiLoop = class extends Service3 {
|
|
|
3486
3665
|
}
|
|
3487
3666
|
} finally {
|
|
3488
3667
|
try {
|
|
3489
|
-
|
|
3490
|
-
detachSession?.();
|
|
3668
|
+
await handle?.close();
|
|
3491
3669
|
} finally {
|
|
3492
|
-
|
|
3493
|
-
|
|
3670
|
+
try {
|
|
3671
|
+
detachAgent?.();
|
|
3672
|
+
detachSession?.();
|
|
3673
|
+
} finally {
|
|
3674
|
+
untrack();
|
|
3675
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
3676
|
+
}
|
|
3494
3677
|
}
|
|
3495
3678
|
}
|
|
3496
3679
|
})();
|
|
@@ -3540,18 +3723,27 @@ var PiLoop = class extends Service3 {
|
|
|
3540
3723
|
}
|
|
3541
3724
|
}
|
|
3542
3725
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
3543
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
3726
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
3544
3727
|
var _stack = [];
|
|
3545
3728
|
try {
|
|
3546
3729
|
const ownedPreparation = __using(_stack, preparation);
|
|
3547
3730
|
const session = ownedPreparation.session;
|
|
3548
|
-
|
|
3731
|
+
let prepared;
|
|
3732
|
+
try {
|
|
3733
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
3734
|
+
} catch (error) {
|
|
3735
|
+
await stored?.handle.close().catch(() => {
|
|
3736
|
+
});
|
|
3737
|
+
throw error;
|
|
3738
|
+
}
|
|
3549
3739
|
try {
|
|
3550
3740
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
3551
3741
|
setupCommit?.commit();
|
|
3742
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
3552
3743
|
return prepared.publish(source);
|
|
3553
3744
|
} catch (error) {
|
|
3554
|
-
await prepared.dispose()
|
|
3745
|
+
await prepared.dispose().catch(() => {
|
|
3746
|
+
});
|
|
3555
3747
|
throw error;
|
|
3556
3748
|
}
|
|
3557
3749
|
} catch (_) {
|
|
@@ -3562,7 +3754,8 @@ var PiLoop = class extends Service3 {
|
|
|
3562
3754
|
}
|
|
3563
3755
|
/**
|
|
3564
3756
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
3565
|
-
* the accessing fiber.
|
|
3757
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
3758
|
+
* durable identity is stored before publication.
|
|
3566
3759
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
3567
3760
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
3568
3761
|
* @returns the published handle.
|
|
@@ -3570,20 +3763,73 @@ var PiLoop = class extends Service3 {
|
|
|
3570
3763
|
async createAgent(ownerCtx, options) {
|
|
3571
3764
|
const preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
3572
3765
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
3573
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
3766
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
3767
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
3574
3768
|
}));
|
|
3575
|
-
const published =
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3769
|
+
const published = (async () => {
|
|
3770
|
+
let stored;
|
|
3771
|
+
try {
|
|
3772
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
3773
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
3774
|
+
options.signal,
|
|
3775
|
+
options.sessionId,
|
|
3776
|
+
(abandoned) => {
|
|
3777
|
+
void abandoned?.handle.close().catch(() => {
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
);
|
|
3781
|
+
} catch (error) {
|
|
3782
|
+
preparation[Symbol.dispose]();
|
|
3783
|
+
throw error;
|
|
3784
|
+
}
|
|
3785
|
+
return this.setupAndPublish(
|
|
3786
|
+
ownerCtx,
|
|
3787
|
+
options.sessionId,
|
|
3788
|
+
preparation,
|
|
3789
|
+
options.agentOptions ?? {},
|
|
3790
|
+
options.setup,
|
|
3791
|
+
options.signal,
|
|
3792
|
+
"startup",
|
|
3793
|
+
stored
|
|
3794
|
+
);
|
|
3795
|
+
})();
|
|
3584
3796
|
this.ownership.trackWrapper(published);
|
|
3585
3797
|
return published;
|
|
3586
3798
|
}
|
|
3799
|
+
/**
|
|
3800
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
3801
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
3802
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
3803
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
3804
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
3805
|
+
* created again.
|
|
3806
|
+
* @param session - the unpublished session to store.
|
|
3807
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
3808
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
3809
|
+
*/
|
|
3810
|
+
async createStoredSession(session, signal) {
|
|
3811
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
3812
|
+
if (persistence === void 0) return void 0;
|
|
3813
|
+
const handle = await persistence.create(session.header, {
|
|
3814
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
3815
|
+
...signal === void 0 ? {} : { signal }
|
|
3816
|
+
});
|
|
3817
|
+
return { handle, storedCount: 0 };
|
|
3818
|
+
}
|
|
3819
|
+
/**
|
|
3820
|
+
* Durably store the session events appended since the last stored cursor.
|
|
3821
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
3822
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
3823
|
+
* through the handle before live events start routing into it.
|
|
3824
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
3825
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
3826
|
+
*/
|
|
3827
|
+
async appendUnstoredSuffix(stored, session) {
|
|
3828
|
+
if (stored === void 0) return;
|
|
3829
|
+
const suffix = session.snapshotEvents(SessionLogOffset3(stored.storedCount));
|
|
3830
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
3831
|
+
stored.storedCount += suffix.length;
|
|
3832
|
+
}
|
|
3587
3833
|
/**
|
|
3588
3834
|
* Resume an owned agent from the configured persistence service.
|
|
3589
3835
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -3597,11 +3843,10 @@ var PiLoop = class extends Service3 {
|
|
|
3597
3843
|
}
|
|
3598
3844
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
3599
3845
|
}
|
|
3600
|
-
/** Resume through an explicit persistence
|
|
3601
|
-
|
|
3846
|
+
/** Resume through an explicit persistence service. */
|
|
3847
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
3602
3848
|
const id = options.resumeSessionId;
|
|
3603
|
-
|
|
3604
|
-
try {
|
|
3849
|
+
const published = (async () => {
|
|
3605
3850
|
const ownerAbort = new AbortController();
|
|
3606
3851
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
3607
3852
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -3611,203 +3856,1181 @@ var PiLoop = class extends Service3 {
|
|
|
3611
3856
|
ownerAbort.signal,
|
|
3612
3857
|
this.ownership.signal
|
|
3613
3858
|
]);
|
|
3859
|
+
let handle;
|
|
3860
|
+
let stored;
|
|
3861
|
+
let preparation;
|
|
3614
3862
|
try {
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3863
|
+
try {
|
|
3864
|
+
handle = await raceAbortCall(
|
|
3865
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
3866
|
+
fused,
|
|
3867
|
+
id,
|
|
3868
|
+
(abandoned) => {
|
|
3869
|
+
void abandoned.close();
|
|
3870
|
+
}
|
|
3871
|
+
);
|
|
3872
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
3873
|
+
fused.throwIfAborted();
|
|
3874
|
+
const closers = interruptedTurnClosers3(persisted);
|
|
3875
|
+
if (closers.length > 0) await handle.append(closers);
|
|
3876
|
+
preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(id, {
|
|
3877
|
+
seed: [...persisted, ...closers],
|
|
3878
|
+
meta: structuredClone(handle.header),
|
|
3879
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
3880
|
+
seedSource: "persistence"
|
|
3881
|
+
}));
|
|
3882
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
3883
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
3884
|
+
} finally {
|
|
3885
|
+
await unfollowOwner();
|
|
3886
|
+
}
|
|
3887
|
+
ownerCtx.fiber.assertActive();
|
|
3888
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
3889
|
+
const owned = stored;
|
|
3890
|
+
handle = void 0;
|
|
3891
|
+
return await this.setupAndPublish(
|
|
3892
|
+
ownerCtx,
|
|
3618
3893
|
id,
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3894
|
+
preparation,
|
|
3895
|
+
options.agentOptions ?? {},
|
|
3896
|
+
options.setup,
|
|
3897
|
+
options.signal,
|
|
3898
|
+
"resume",
|
|
3899
|
+
owned
|
|
3622
3900
|
);
|
|
3623
3901
|
} finally {
|
|
3624
|
-
|
|
3902
|
+
preparation?.[Symbol.dispose]();
|
|
3903
|
+
await handle?.close().catch(() => {
|
|
3904
|
+
});
|
|
3625
3905
|
}
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
ownerCtx,
|
|
3630
|
-
id,
|
|
3631
|
-
preparation,
|
|
3632
|
-
options.agentOptions ?? {},
|
|
3633
|
-
options.setup,
|
|
3634
|
-
options.signal,
|
|
3635
|
-
"resume"
|
|
3636
|
-
);
|
|
3637
|
-
} finally {
|
|
3638
|
-
preparation?.[Symbol.dispose]();
|
|
3639
|
-
}
|
|
3906
|
+
})();
|
|
3907
|
+
this.ownership.trackWrapper(published);
|
|
3908
|
+
return published;
|
|
3640
3909
|
}
|
|
3641
3910
|
};
|
|
3642
3911
|
|
|
3643
|
-
// src/
|
|
3912
|
+
// src/engine-kimi/loop.ts
|
|
3913
|
+
import { Service as Service4 } from "@deepseek-ai/cordis";
|
|
3644
3914
|
import z4 from "@deepseek-ai/schemastery";
|
|
3645
|
-
import {
|
|
3915
|
+
import { emitAgentEvent as emitAgentEvent4 } from "@deepseek-ai/dsh-agent";
|
|
3916
|
+
import { interruptedTurnClosers as interruptedTurnClosers4, SessionLogOffset as SessionLogOffset4, SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
|
|
3646
3917
|
|
|
3647
|
-
// src/
|
|
3648
|
-
|
|
3918
|
+
// src/engine-kimi/agent.ts
|
|
3919
|
+
import { Inbox as Inbox4, agentEvents as agentEvents4 } from "@deepseek-ai/dsh-agent";
|
|
3920
|
+
import { ToolCallId as ToolCallId6, LlmError as LlmError4, createAssistantMessage as createAssistantMessage4, createUserMessage as createUserMessage4, errorChain as errorChain4 } from "@deepseek-ai/dsh-llm";
|
|
3921
|
+
import { createScope as createScope4 } from "@deepseek-ai/dsh-scope";
|
|
3922
|
+
import { canonicalHeader as canonicalHeader4 } from "@deepseek-ai/dsh-session";
|
|
3649
3923
|
|
|
3650
|
-
// src/
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
return settingsNamespace(LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL);
|
|
3924
|
+
// src/engine-kimi/process.ts
|
|
3925
|
+
import { existsSync } from "node:fs";
|
|
3926
|
+
import { homedir } from "node:os";
|
|
3927
|
+
import { join as join3 } from "node:path";
|
|
3928
|
+
function kimiHomeDir() {
|
|
3929
|
+
const envHome = process.env.KIMI_CODE_HOME;
|
|
3930
|
+
return envHome !== void 0 && envHome !== "" ? envHome : join3(homedir(), ".kimi-code");
|
|
3658
3931
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
`;
|
|
3665
|
-
function renderManagedBlock(engine) {
|
|
3666
|
-
if (engine === "in-process") return "";
|
|
3667
|
-
return [
|
|
3668
|
-
`${MANAGED_BLOCK_BEGIN}${engine} --`,
|
|
3669
|
-
"- id: agent-loop",
|
|
3670
|
-
" disabled: true",
|
|
3671
|
-
END_MARKER_LINE
|
|
3672
|
-
].join("\n");
|
|
3932
|
+
function kimiBinResolver(configBin) {
|
|
3933
|
+
if (configBin !== void 0 && configBin !== "") return configBin;
|
|
3934
|
+
const executable = process.platform === "win32" ? "kimi.exe" : "kimi";
|
|
3935
|
+
const candidate = join3(kimiHomeDir(), "bin", executable);
|
|
3936
|
+
return existsSync(candidate) ? candidate : "kimi";
|
|
3673
3937
|
}
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
const engine = BEGIN_MARKER_RE.exec(text)?.[1];
|
|
3677
|
-
return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : "in-process";
|
|
3938
|
+
function kimiAcpArgv(bin) {
|
|
3939
|
+
return [bin, "acp"];
|
|
3678
3940
|
}
|
|
3679
|
-
function
|
|
3680
|
-
const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
|
|
3681
|
-
if (begin === -1) return { head: text, tail: "", present: false, blankBefore: false };
|
|
3682
|
-
const afterBegin = begin + MANAGED_BLOCK_BEGIN.length;
|
|
3683
|
-
const endAt = text.indexOf(MANAGED_BLOCK_END, afterBegin);
|
|
3684
|
-
const spanEnd = endAt === -1 ? text.length : endAt + END_MARKER_LINE.length;
|
|
3685
|
-
const before = text.slice(0, begin);
|
|
3686
|
-
const blankBefore = before.endsWith("\n\n");
|
|
3941
|
+
function kimiSubprocessSpec(spec, graceMs) {
|
|
3687
3942
|
return {
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3943
|
+
argv: [...spec.argv],
|
|
3944
|
+
cwd: spec.cwd,
|
|
3945
|
+
stdio: { stdin: "pipe", stdout: "pipe", stderr: "pipe" },
|
|
3946
|
+
graceMs,
|
|
3947
|
+
env: spec.env,
|
|
3948
|
+
...spec.signal === void 0 ? {} : { signal: spec.signal }
|
|
3692
3949
|
};
|
|
3693
3950
|
}
|
|
3694
|
-
function
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
function hasRootEntry(text) {
|
|
3699
|
-
return /^(?:- |\[)/m.test(text);
|
|
3700
|
-
}
|
|
3701
|
-
function dropSeedPlaceholder(text) {
|
|
3702
|
-
return text.replace(/^\[\]\n/m, "");
|
|
3703
|
-
}
|
|
3704
|
-
function seedEmptyArray(text) {
|
|
3705
|
-
const head = text.replace(/\n+$/, "");
|
|
3706
|
-
return head === "" ? "[]\n" : `${head}
|
|
3707
|
-
[]
|
|
3708
|
-
`;
|
|
3709
|
-
}
|
|
3710
|
-
function applyManagedBlock(text, engine) {
|
|
3711
|
-
const block = renderManagedBlock(engine);
|
|
3712
|
-
const span = managedSpan(text);
|
|
3713
|
-
let result;
|
|
3714
|
-
if (!span.present) {
|
|
3715
|
-
if (block === "") {
|
|
3716
|
-
result = text;
|
|
3717
|
-
} else {
|
|
3718
|
-
const base = ensureTrailingNewline(text);
|
|
3719
|
-
result = `${base}
|
|
3720
|
-
${block}`;
|
|
3721
|
-
}
|
|
3722
|
-
} else if (block === "") {
|
|
3723
|
-
result = span.tail.startsWith("\n") ? `${span.head}${span.tail.slice(1)}` : `${span.head}${span.tail}`;
|
|
3724
|
-
} else {
|
|
3725
|
-
result = `${span.head}${span.blankBefore ? "\n" : ""}${block}${span.tail}`;
|
|
3951
|
+
function fromSubprocess2(handle) {
|
|
3952
|
+
const { stdin, stdout, stderr } = handle;
|
|
3953
|
+
if (stdin === void 0 || stdout === void 0 || stderr === void 0) {
|
|
3954
|
+
throw new Error("agent-loop-kimi: spawned child must pipe stdin/stdout/stderr");
|
|
3726
3955
|
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3956
|
+
return {
|
|
3957
|
+
stdin,
|
|
3958
|
+
stdout,
|
|
3959
|
+
stderr,
|
|
3960
|
+
done: handle.done,
|
|
3961
|
+
terminate: () => handle.terminate()
|
|
3962
|
+
};
|
|
3730
3963
|
}
|
|
3731
3964
|
|
|
3732
|
-
// src/
|
|
3733
|
-
import {
|
|
3734
|
-
import {
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
return (invocation) => {
|
|
3740
|
-
invocation.agent.followup(createUserMessage4({
|
|
3741
|
-
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
3742
|
-
source: { kind: "user" }
|
|
3743
|
-
}));
|
|
3744
|
-
return { kind: "success" };
|
|
3745
|
-
};
|
|
3965
|
+
// src/engine-kimi/acp/client.ts
|
|
3966
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
3967
|
+
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
3968
|
+
|
|
3969
|
+
// src/engine-kimi/acp/types.ts
|
|
3970
|
+
function isUpdateFrame(frame2) {
|
|
3971
|
+
return frame2.method === "session/update" && typeof frame2.params === "object" && frame2.params !== null && typeof frame2.params.update === "object" && frame2.params.update !== null;
|
|
3746
3972
|
}
|
|
3747
|
-
function
|
|
3748
|
-
return
|
|
3973
|
+
function isPermissionRequestFrame(frame2) {
|
|
3974
|
+
return frame2.method === "session/request_permission" && typeof frame2.id === "number";
|
|
3749
3975
|
}
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3976
|
+
|
|
3977
|
+
// src/engine-kimi/acp/client.ts
|
|
3978
|
+
function defaultSpawn2(spec) {
|
|
3979
|
+
const child = spawn3(spec.argv[0], spec.argv.slice(1), {
|
|
3980
|
+
cwd: spec.cwd,
|
|
3981
|
+
env: spec.env,
|
|
3982
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3983
|
+
});
|
|
3984
|
+
return fromNativeChild(child);
|
|
3985
|
+
}
|
|
3986
|
+
function fromNativeChild(child) {
|
|
3987
|
+
const done = Promise.withResolvers();
|
|
3988
|
+
child.once("exit", () => done.resolve(void 0));
|
|
3989
|
+
child.once("error", done.reject);
|
|
3990
|
+
return {
|
|
3991
|
+
stdin: child.stdin,
|
|
3992
|
+
stdout: child.stdout,
|
|
3993
|
+
stderr: child.stderr,
|
|
3994
|
+
done: done.promise,
|
|
3995
|
+
terminate: () => child.kill()
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
var AcpClient = class _AcpClient {
|
|
3999
|
+
/** Mount a client over an already-spawned `kimi acp` process. */
|
|
4000
|
+
constructor(process2) {
|
|
4001
|
+
this.process = process2;
|
|
4002
|
+
this.process.stdout.on("data", (chunk) => this.feed(chunk));
|
|
4003
|
+
this.process.stderr.on("data", () => {
|
|
4004
|
+
});
|
|
4005
|
+
this.process.done.then(() => {
|
|
4006
|
+
this.sealed = true;
|
|
4007
|
+
const error = new Error("kimi acp process exited unexpectedly");
|
|
4008
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
4009
|
+
this.pending.clear();
|
|
4010
|
+
this.updateWake?.();
|
|
4011
|
+
}, () => {
|
|
4012
|
+
this.sealed = true;
|
|
4013
|
+
this.updateWake?.();
|
|
4014
|
+
});
|
|
3765
4015
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
4016
|
+
process;
|
|
4017
|
+
pending = /* @__PURE__ */ new Map();
|
|
4018
|
+
updateBuffer = [];
|
|
4019
|
+
updateWake;
|
|
4020
|
+
updateHandler;
|
|
4021
|
+
permissionHandler;
|
|
4022
|
+
sealed = false;
|
|
4023
|
+
nextId = 1;
|
|
4024
|
+
decoder = new StringDecoder2("utf8");
|
|
4025
|
+
buffer = "";
|
|
4026
|
+
/** Whether this client was sealed or its process exited. */
|
|
4027
|
+
get closed() {
|
|
4028
|
+
return this.sealed;
|
|
4029
|
+
}
|
|
4030
|
+
/**
|
|
4031
|
+
* Create a client, spawning the `kimi acp` child through the supplied
|
|
4032
|
+
* capability (or the default node spawn when none is given).
|
|
4033
|
+
* @param spec - the `kimi acp` argv/cwd/env the child should run with.
|
|
4034
|
+
* @param spawn - optional process-spawn capability (the subprocess seam).
|
|
4035
|
+
* @returns the connected client.
|
|
4036
|
+
*/
|
|
4037
|
+
static create(spec, spawn4) {
|
|
4038
|
+
const process2 = spawn4 === void 0 ? defaultSpawn2(spec) : spawn4(spec);
|
|
4039
|
+
return new _AcpClient(process2);
|
|
4040
|
+
}
|
|
4041
|
+
/** Register the event dispatch handler. */
|
|
4042
|
+
onUpdate(handler) {
|
|
4043
|
+
this.updateHandler = handler;
|
|
4044
|
+
}
|
|
4045
|
+
/** Register the permission-approval handler (reverse-RPC answers). */
|
|
4046
|
+
onPermission(handler) {
|
|
4047
|
+
this.permissionHandler = handler;
|
|
4048
|
+
}
|
|
4049
|
+
/** Send one request and await the correlated response. */
|
|
4050
|
+
request(method, params) {
|
|
4051
|
+
if (this.sealed) return Promise.reject(new Error("kimi acp client is sealed"));
|
|
4052
|
+
const id = this.nextId++;
|
|
4053
|
+
return new Promise((resolve5, reject) => {
|
|
4054
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
4055
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
4056
|
+
`);
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
/** Send a notification (no correlated response awaited). */
|
|
4060
|
+
notify(method, params) {
|
|
4061
|
+
if (this.sealed) return;
|
|
4062
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
4063
|
+
`);
|
|
4064
|
+
}
|
|
4065
|
+
/** Open the protocol handshake. */
|
|
4066
|
+
initialize() {
|
|
4067
|
+
return this.request("initialize", { protocolVersion: 1, clientCapabilities: {}, clientInfo: { name: "dsh-loop-engine", version: "1.0.0" } });
|
|
4068
|
+
}
|
|
4069
|
+
/** Start a fresh ACP session and resolve to its session id. */
|
|
4070
|
+
async newSession(cwd) {
|
|
4071
|
+
const result = await this.request("session/new", { cwd, mcpServers: [] });
|
|
4072
|
+
const sessionId = result?.sessionId;
|
|
4073
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
4074
|
+
throw new Error("kimi acp session/new returned no session id");
|
|
4075
|
+
}
|
|
4076
|
+
return sessionId;
|
|
4077
|
+
}
|
|
4078
|
+
/** Prompt the agent in a session and resolve when the turn completes. */
|
|
4079
|
+
prompt(sessionId, text) {
|
|
4080
|
+
return this.request("session/prompt", { sessionId, prompt: [{ type: "text", text }] });
|
|
4081
|
+
}
|
|
4082
|
+
/** Cancel the active turn in a session (fire-and-forget). */
|
|
4083
|
+
cancel(sessionId) {
|
|
4084
|
+
this.request("session/cancel", { sessionId }).catch(() => void 0);
|
|
4085
|
+
}
|
|
4086
|
+
/** Answer a pending `session/request_permission`. */
|
|
4087
|
+
respondPermission(id, approved) {
|
|
4088
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result: { approved } })}
|
|
4089
|
+
`);
|
|
4090
|
+
}
|
|
4091
|
+
/** Consume every buffered update as an async generator. */
|
|
4092
|
+
async *updates() {
|
|
4093
|
+
while (true) {
|
|
4094
|
+
if (this.updateBuffer.length > 0) {
|
|
4095
|
+
yield this.updateBuffer.shift();
|
|
4096
|
+
continue;
|
|
4097
|
+
}
|
|
4098
|
+
if (this.sealed) return;
|
|
4099
|
+
await new Promise((resolve5) => {
|
|
4100
|
+
this.updateWake = resolve5;
|
|
4101
|
+
});
|
|
4102
|
+
this.updateWake = void 0;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
/** Seal the client and request child termination. */
|
|
4106
|
+
dispose() {
|
|
4107
|
+
if (this.sealed) return;
|
|
4108
|
+
this.sealed = true;
|
|
4109
|
+
this.process.terminate();
|
|
4110
|
+
const error = new Error("kimi acp client is sealed");
|
|
4111
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
4112
|
+
this.pending.clear();
|
|
4113
|
+
this.updateWake?.();
|
|
4114
|
+
}
|
|
4115
|
+
feed(chunk) {
|
|
4116
|
+
if (this.sealed) return;
|
|
4117
|
+
const text = typeof chunk === "string" ? this.buffer + chunk : this.buffer + this.decoder.write(chunk);
|
|
4118
|
+
this.buffer = text;
|
|
4119
|
+
while (true) {
|
|
4120
|
+
const newline = this.buffer.indexOf("\n");
|
|
4121
|
+
if (newline === -1) break;
|
|
4122
|
+
let line = this.buffer.slice(0, newline);
|
|
4123
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
4124
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
4125
|
+
this.dispatch(line);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
/** Dispatch one parsed line: a response, an update notification, or a reverse-RPC request. */
|
|
4129
|
+
dispatch(line) {
|
|
4130
|
+
if (line.trim().length === 0) return;
|
|
4131
|
+
let frame2;
|
|
3774
4132
|
try {
|
|
3775
|
-
|
|
4133
|
+
frame2 = JSON.parse(line);
|
|
3776
4134
|
} catch {
|
|
3777
|
-
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
if (isPermissionRequestFrame(frame2)) {
|
|
4138
|
+
void this.handlePermission(frame2.id, frame2);
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
if (typeof frame2.id === "number" && frame2.method === void 0) {
|
|
4142
|
+
this.settle(frame2);
|
|
4143
|
+
return;
|
|
4144
|
+
}
|
|
4145
|
+
if (isUpdateFrame(frame2)) {
|
|
4146
|
+
const update = frame2.params.update;
|
|
4147
|
+
this.updateHandler?.(update);
|
|
4148
|
+
this.updateBuffer.push(update);
|
|
4149
|
+
this.updateWake?.();
|
|
4150
|
+
return;
|
|
4151
|
+
}
|
|
4152
|
+
if (typeof frame2.id === "number" && typeof frame2.method === "string") {
|
|
4153
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: frame2.id, error: { code: -32601, message: "Method not found" } })}
|
|
4154
|
+
`);
|
|
3778
4155
|
}
|
|
3779
|
-
const description = commandDescription(raw);
|
|
3780
|
-
if (description === void 0) continue;
|
|
3781
|
-
seen.add(name2);
|
|
3782
|
-
definitions.push({ name: name2, description, handler: forwardClaudeCodeCommand(name2) });
|
|
3783
4156
|
}
|
|
3784
|
-
|
|
4157
|
+
settle(frame2) {
|
|
4158
|
+
const id = frame2.id;
|
|
4159
|
+
const pending = this.pending.get(id);
|
|
4160
|
+
if (pending === void 0) return;
|
|
4161
|
+
this.pending.delete(id);
|
|
4162
|
+
if (frame2.error !== void 0) {
|
|
4163
|
+
pending.reject(new Error(frame2.error.message ?? "kimi acp request failed"));
|
|
4164
|
+
} else {
|
|
4165
|
+
pending.resolve(frame2.result);
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
async handlePermission(id, frame2) {
|
|
4169
|
+
const approved = this.permissionHandler === void 0 ? false : await this.permissionHandler(frame2);
|
|
4170
|
+
this.respondPermission(id, approved);
|
|
4171
|
+
}
|
|
4172
|
+
};
|
|
4173
|
+
|
|
4174
|
+
// src/engine-kimi/acp/mapping.ts
|
|
4175
|
+
import { ToolCallId as ToolCallId5, createToolResultMessage as createToolResultMessage4 } from "@deepseek-ai/dsh-llm";
|
|
4176
|
+
function isTextChunk(update) {
|
|
4177
|
+
return update.sessionUpdate === "agent_message_chunk";
|
|
3785
4178
|
}
|
|
3786
|
-
function
|
|
3787
|
-
return
|
|
4179
|
+
function isThoughtChunk(update) {
|
|
4180
|
+
return update.sessionUpdate === "agent_thought_chunk";
|
|
3788
4181
|
}
|
|
3789
|
-
function
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
4182
|
+
function isToolCall(update) {
|
|
4183
|
+
return update.sessionUpdate === "tool_call";
|
|
4184
|
+
}
|
|
4185
|
+
function isToolCallUpdate(update) {
|
|
4186
|
+
return update.sessionUpdate === "tool_call_update";
|
|
4187
|
+
}
|
|
4188
|
+
function chunkDelta(update) {
|
|
4189
|
+
const content = update.content;
|
|
4190
|
+
if (content === void 0) return "";
|
|
4191
|
+
const text = content.text;
|
|
4192
|
+
return typeof text === "string" ? text : "";
|
|
4193
|
+
}
|
|
4194
|
+
function toolCallIdOf(update) {
|
|
4195
|
+
return update.toolCallId;
|
|
4196
|
+
}
|
|
4197
|
+
function toolCallName(update) {
|
|
4198
|
+
return update.title;
|
|
4199
|
+
}
|
|
4200
|
+
function isToolSettledStatus(status) {
|
|
4201
|
+
return status !== "pending" && status !== "queued" && status !== "running" && status !== "in_progress";
|
|
4202
|
+
}
|
|
4203
|
+
function isToolErrorStatus(status) {
|
|
4204
|
+
return status === "failed" || status === "error" || status === "denied";
|
|
4205
|
+
}
|
|
4206
|
+
function toolContentText(update) {
|
|
4207
|
+
const blocks = update.content ?? [];
|
|
4208
|
+
return blocks.map((block) => block.type === "content" && block.content.type === "text" ? block.content.text : "").join("");
|
|
4209
|
+
}
|
|
4210
|
+
function toolResult(callId, text, isError) {
|
|
4211
|
+
return createToolResultMessage4({
|
|
4212
|
+
callId: ToolCallId5(callId),
|
|
4213
|
+
content: [{ type: "text", text: text.length > 0 ? text : "(no content)" }],
|
|
4214
|
+
isError
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
// src/engine-kimi/permission.ts
|
|
4219
|
+
function resolveToolApproval(events) {
|
|
4220
|
+
return sessionApprovalPolicy(events) !== "ask";
|
|
4221
|
+
}
|
|
4222
|
+
|
|
4223
|
+
// src/engine-kimi/agent.ts
|
|
4224
|
+
var PROVIDER4 = "kimi";
|
|
4225
|
+
var NATIVE_MODEL_LABEL4 = "kimi-native";
|
|
4226
|
+
var KimiAgent = class {
|
|
4227
|
+
constructor(loopCtx, id, options, session, config, spawn4, bin) {
|
|
4228
|
+
this.loopCtx = loopCtx;
|
|
4229
|
+
this.id = id;
|
|
4230
|
+
this.options = options;
|
|
4231
|
+
this.session = session;
|
|
4232
|
+
this.config = config;
|
|
4233
|
+
this.spawn = spawn4;
|
|
4234
|
+
this.bin = bin;
|
|
4235
|
+
this.dispatch = agentEvents4(loopCtx, this);
|
|
4236
|
+
this.inbox = new Inbox4(session, {
|
|
4237
|
+
inserted: (message) => {
|
|
4238
|
+
this.dispatch.emit("agent/inbox/inserted", { message });
|
|
4239
|
+
},
|
|
4240
|
+
discarded: (message) => {
|
|
4241
|
+
this.dispatch.emit("agent/inbox/discarded", { message });
|
|
4242
|
+
},
|
|
4243
|
+
claimed: (message, turn) => {
|
|
4244
|
+
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
4245
|
+
}
|
|
4246
|
+
});
|
|
4247
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
4248
|
+
this.phase = { kind: "idle", lastTurn };
|
|
4249
|
+
this.scope = createScope4(loopCtx, this);
|
|
4250
|
+
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
4251
|
+
this.scope.ctx.effect(() => () => {
|
|
4252
|
+
this.acp?.dispose();
|
|
4253
|
+
this.acp = void 0;
|
|
4254
|
+
}, "kimi.acpClient()");
|
|
4255
|
+
}
|
|
4256
|
+
loopCtx;
|
|
4257
|
+
id;
|
|
4258
|
+
options;
|
|
4259
|
+
session;
|
|
4260
|
+
config;
|
|
4261
|
+
spawn;
|
|
4262
|
+
bin;
|
|
4263
|
+
inbox;
|
|
4264
|
+
phase;
|
|
4265
|
+
activityDone = Promise.resolve();
|
|
4266
|
+
/** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
|
|
4267
|
+
scope;
|
|
4268
|
+
ctx;
|
|
4269
|
+
/** Fused dispatcher, built once in the constructor so hot-path dispatches never allocate. */
|
|
4270
|
+
dispatch;
|
|
4271
|
+
/** Whether this loop instance has appended its initial/resume request anchor. */
|
|
4272
|
+
requestHeaderLogged = false;
|
|
4273
|
+
/** Lazily created ACP client, reused across steps and released on scope teardown. */
|
|
4274
|
+
acp;
|
|
4275
|
+
/** The spawn spec the cached client was built from; a change forces a respawn. */
|
|
4276
|
+
lastSpec;
|
|
4277
|
+
get status() {
|
|
4278
|
+
return this.phase.kind === "idle" || this.phase.kind === "maintenance" ? "idle" : "running";
|
|
4279
|
+
}
|
|
4280
|
+
/** Commit a phase and publish its externally visible status transition. */
|
|
4281
|
+
setPhase(next) {
|
|
4282
|
+
const previousStatus = this.status;
|
|
4283
|
+
this.phase = next;
|
|
4284
|
+
const status = this.status;
|
|
4285
|
+
if (status !== previousStatus) {
|
|
4286
|
+
this.dispatch.emit("agent/status", { status });
|
|
3801
4287
|
}
|
|
3802
|
-
body = trimmed.slice(closing + 4);
|
|
3803
4288
|
}
|
|
3804
|
-
|
|
3805
|
-
const
|
|
3806
|
-
|
|
3807
|
-
|
|
4289
|
+
send(message, target, wakeup) {
|
|
4290
|
+
const wakingAfterAbort = wakeup && this.phase.kind !== "idle" && this.phase.abort.signal.aborted;
|
|
4291
|
+
const resolvedTarget = wakingAfterAbort ? "next-turn" : target;
|
|
4292
|
+
this.inbox.splice(resolvedTarget, Infinity, 0, [message]);
|
|
4293
|
+
if (wakeup) this.wakeDriver(wakingAfterAbort);
|
|
3808
4294
|
}
|
|
3809
|
-
|
|
3810
|
-
|
|
4295
|
+
/**
|
|
4296
|
+
* Queue a message for the next turn and wake the driver.
|
|
4297
|
+
* @param input - the user message to deliver.
|
|
4298
|
+
*/
|
|
4299
|
+
followup(input) {
|
|
4300
|
+
this.send(input, "next-turn", true);
|
|
4301
|
+
}
|
|
4302
|
+
/**
|
|
4303
|
+
* Queue a message for the running step and wake the driver.
|
|
4304
|
+
* @param input - the user message to deliver.
|
|
4305
|
+
*/
|
|
4306
|
+
steer(input) {
|
|
4307
|
+
this.send(input, "next-step", true);
|
|
4308
|
+
}
|
|
4309
|
+
/**
|
|
4310
|
+
* Queue a message for the running step without waking the driver.
|
|
4311
|
+
* @param input - the user message to deliver.
|
|
4312
|
+
*/
|
|
4313
|
+
inject(input) {
|
|
4314
|
+
this.send(input, "next-step", false);
|
|
4315
|
+
}
|
|
4316
|
+
cancel(cause, options = {}) {
|
|
4317
|
+
if (!options.keepInbox) {
|
|
4318
|
+
this.inbox.clear();
|
|
4319
|
+
if (this.phase.kind !== "idle") this.phase.wakeRequested = false;
|
|
4320
|
+
}
|
|
4321
|
+
if (this.phase.kind !== "idle") this.phase.abort.abort(cause);
|
|
4322
|
+
}
|
|
4323
|
+
/**
|
|
4324
|
+
* Run a maintenance job while the agent is idle.
|
|
4325
|
+
* @param job - the maintenance operation, receiving the phase abort signal.
|
|
4326
|
+
* @returns the maintenance result.
|
|
4327
|
+
*/
|
|
4328
|
+
runMaintenance(job) {
|
|
4329
|
+
if (this.phase.kind !== "idle") throw new Error(`agent "${this.id}" already has active work`);
|
|
4330
|
+
const done = Promise.withResolvers();
|
|
4331
|
+
const maintenance = {
|
|
4332
|
+
kind: "maintenance",
|
|
4333
|
+
abort: new AbortController(),
|
|
4334
|
+
lastTurn: this.phase.lastTurn,
|
|
4335
|
+
wakeRequested: false
|
|
4336
|
+
};
|
|
4337
|
+
this.setPhase(maintenance);
|
|
4338
|
+
this.activityDone = done.promise;
|
|
4339
|
+
return (async () => {
|
|
4340
|
+
try {
|
|
4341
|
+
return await job(maintenance.abort.signal);
|
|
4342
|
+
} finally {
|
|
4343
|
+
this.setPhase({ kind: "idle", lastTurn: maintenance.lastTurn });
|
|
4344
|
+
if (maintenance.wakeRequested && this.inbox.hasPending) this.wakeDriver();
|
|
4345
|
+
done.resolve();
|
|
4346
|
+
}
|
|
4347
|
+
})();
|
|
4348
|
+
}
|
|
4349
|
+
/**
|
|
4350
|
+
* Start one driver, or latch its wake behind maintenance or an aborted
|
|
4351
|
+
* activity. A wake sent while idle always opens its turn boundary, even
|
|
4352
|
+
* when its message was cleared; only a latched replay is suppressed when
|
|
4353
|
+
* the queue no longer holds the wake.
|
|
4354
|
+
* @param wakeAfterAbort - the {@link send} classification, captured before
|
|
4355
|
+
* the inbox insertion so a reentrant cancel cannot reclassify it.
|
|
4356
|
+
*/
|
|
4357
|
+
wakeDriver(wakeAfterAbort = false) {
|
|
4358
|
+
if (this.phase.kind !== "idle") {
|
|
4359
|
+
const reason = this.phase.abort.signal.reason;
|
|
4360
|
+
if (reason?.kind !== "disposed" && (this.phase.kind === "maintenance" || wakeAfterAbort)) {
|
|
4361
|
+
this.phase.wakeRequested = true;
|
|
4362
|
+
}
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
const driver = Promise.withResolvers();
|
|
4366
|
+
this.activityDone = driver.promise;
|
|
4367
|
+
this.setPhase({
|
|
4368
|
+
kind: "running",
|
|
4369
|
+
abort: new AbortController(),
|
|
4370
|
+
turn: this.phase.lastTurn,
|
|
4371
|
+
step: 0,
|
|
4372
|
+
wakeRequested: false
|
|
4373
|
+
});
|
|
4374
|
+
this.loopCtx.agents.withInitiator(this, () => this.kick()).then(driver.resolve, driver.reject);
|
|
4375
|
+
}
|
|
4376
|
+
async whenIdle() {
|
|
4377
|
+
let activity;
|
|
4378
|
+
do {
|
|
4379
|
+
await (activity = this.activityDone);
|
|
4380
|
+
} while (activity !== this.activityDone);
|
|
4381
|
+
}
|
|
4382
|
+
/** Report one failure at its live boundary, then preserve it for driver containment. */
|
|
4383
|
+
throwError(error) {
|
|
4384
|
+
const turn = this.phase.kind === "running" ? this.phase.turn : this.phase.lastTurn;
|
|
4385
|
+
const step = this.phase.kind === "running" ? this.phase.step : 0;
|
|
4386
|
+
this.dispatch.emit("agent/error", { turn, step, error });
|
|
4387
|
+
throw error;
|
|
4388
|
+
}
|
|
4389
|
+
async kick() {
|
|
4390
|
+
try {
|
|
4391
|
+
while (await this.turn()) {
|
|
4392
|
+
}
|
|
4393
|
+
} catch (_error) {
|
|
4394
|
+
} finally {
|
|
4395
|
+
if (this.phase.kind === "running") {
|
|
4396
|
+
const { turn, wakeRequested } = this.phase;
|
|
4397
|
+
this.setPhase({ kind: "idle", lastTurn: turn });
|
|
4398
|
+
if (wakeRequested && this.inbox.hasPending) this.wakeDriver();
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
async preStep(target, position) {
|
|
4403
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": pre-step outside running phase`);
|
|
4404
|
+
const signal = this.phase.abort.signal;
|
|
4405
|
+
const claimed = this.inbox.claim(target, position.turn);
|
|
4406
|
+
const decision = await this.dispatch.waterfall(
|
|
4407
|
+
"agent/pre-step",
|
|
4408
|
+
{ messages: claimed, ...position, signal },
|
|
4409
|
+
() => Promise.resolve({ kind: "enter", messages: claimed })
|
|
4410
|
+
);
|
|
4411
|
+
signal.throwIfAborted();
|
|
4412
|
+
if (decision.kind === "reject") return decision;
|
|
4413
|
+
const injected = await this.injectSkills(decision.messages, signal);
|
|
4414
|
+
signal.throwIfAborted();
|
|
4415
|
+
return injected !== decision.messages ? { kind: "enter", messages: [...injected] } : { ...decision };
|
|
4416
|
+
}
|
|
4417
|
+
/**
|
|
4418
|
+
* Scan the step's user messages for `/name` skill gestures, load each
|
|
4419
|
+
* matching skill, and inject the rendered skill content into the message
|
|
4420
|
+
* batch. This mirrors what dsh-tool-skill does for the in-process engine.
|
|
4421
|
+
* @param messages - the current step's message batch.
|
|
4422
|
+
* @param signal - cancellation signal (aborted loads are silently dropped).
|
|
4423
|
+
* @returns the original batch when no skill was invoked, or an extended
|
|
4424
|
+
* batch with injected skill-content messages appended.
|
|
4425
|
+
*/
|
|
4426
|
+
async injectSkills(messages, signal) {
|
|
4427
|
+
const names = invokedSkillNames(messages);
|
|
4428
|
+
if (names.length === 0) return messages;
|
|
4429
|
+
const skills = this.loopCtx.get("skills");
|
|
4430
|
+
if (skills === void 0) return messages;
|
|
4431
|
+
const cwd = this.session.header.cwd;
|
|
4432
|
+
const injections = [];
|
|
4433
|
+
for (const name2 of names) {
|
|
4434
|
+
if (!isSkillName(name2)) continue;
|
|
4435
|
+
let skill;
|
|
4436
|
+
try {
|
|
4437
|
+
skill = await skills.get(name2, { signal, scope: this, ...cwd === void 0 ? {} : { cwd } });
|
|
4438
|
+
} catch {
|
|
4439
|
+
continue;
|
|
4440
|
+
}
|
|
4441
|
+
if (skill === void 0 || !skill.invocation.userInvocable) continue;
|
|
4442
|
+
if (signal.aborted) return messages;
|
|
4443
|
+
injections.push(createUserMessage4({
|
|
4444
|
+
content: [{ type: "text", text: renderSkillContent(skill) }],
|
|
4445
|
+
source: { kind: "skill-invocation", name: name2, form: "instructions" }
|
|
4446
|
+
}));
|
|
4447
|
+
}
|
|
4448
|
+
return injections.length > 0 ? [...messages, ...injections] : messages;
|
|
4449
|
+
}
|
|
4450
|
+
/** Open one turn before claiming its first proposed step. */
|
|
4451
|
+
async turn() {
|
|
4452
|
+
if (this.phase.kind !== "running") {
|
|
4453
|
+
this.throwError(new Error(`agent "${this.id}": turn without driver reservation`));
|
|
4454
|
+
}
|
|
4455
|
+
const phase = this.phase;
|
|
4456
|
+
const { signal } = phase.abort;
|
|
4457
|
+
signal.throwIfAborted();
|
|
4458
|
+
const turn = phase.turn + 1;
|
|
4459
|
+
try {
|
|
4460
|
+
this.session.append("turn/start", { turn });
|
|
4461
|
+
} catch (error) {
|
|
4462
|
+
this.throwError(error);
|
|
4463
|
+
}
|
|
4464
|
+
phase.turn = turn;
|
|
4465
|
+
let turnEnds = null;
|
|
4466
|
+
let target = "next-turn";
|
|
4467
|
+
try {
|
|
4468
|
+
while (true) {
|
|
4469
|
+
signal.throwIfAborted();
|
|
4470
|
+
const step = phase.step + 1;
|
|
4471
|
+
const decision = await this.preStep(target, { turn, step });
|
|
4472
|
+
if (decision.kind === "reject") {
|
|
4473
|
+
turnEnds = { kind: "blocked" };
|
|
4474
|
+
return false;
|
|
4475
|
+
}
|
|
4476
|
+
if (turnEnds && decision.messages.length === 0) break;
|
|
4477
|
+
if (phase.step === 0 && decision.messages.length === 0) {
|
|
4478
|
+
turnEnds = { kind: "completed" };
|
|
4479
|
+
return false;
|
|
4480
|
+
}
|
|
4481
|
+
signal.throwIfAborted();
|
|
4482
|
+
this.session.append("step/start", { turn, step });
|
|
4483
|
+
phase.step = step;
|
|
4484
|
+
try {
|
|
4485
|
+
for (const message of decision.messages) {
|
|
4486
|
+
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
4487
|
+
}
|
|
4488
|
+
const stepEnd = await this.step();
|
|
4489
|
+
if (turnEnds === null) turnEnds = stepEnd;
|
|
4490
|
+
} finally {
|
|
4491
|
+
this.session.append("step/end", { turn, step });
|
|
4492
|
+
}
|
|
4493
|
+
signal.throwIfAborted();
|
|
4494
|
+
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
4495
|
+
await this.dispatch.serial("agent/turn-stopping", { turn, signal });
|
|
4496
|
+
signal.throwIfAborted();
|
|
4497
|
+
}
|
|
4498
|
+
if (turnEnds && this.inbox.nextStep.length === 0) break;
|
|
4499
|
+
target = "next-step";
|
|
4500
|
+
}
|
|
4501
|
+
} catch (error) {
|
|
4502
|
+
if (signal.aborted) {
|
|
4503
|
+
turnEnds = { kind: "aborted", reason: signal.reason };
|
|
4504
|
+
throw error;
|
|
4505
|
+
}
|
|
4506
|
+
turnEnds = {
|
|
4507
|
+
kind: "error",
|
|
4508
|
+
error: error instanceof LlmError4 ? error.failure : { message: errorChain4(error), code: "UNKNOWN" }
|
|
4509
|
+
};
|
|
4510
|
+
this.throwError(error);
|
|
4511
|
+
} finally {
|
|
4512
|
+
try {
|
|
4513
|
+
this.session.append("turn/end", { turn, reason: turnEnds });
|
|
4514
|
+
} catch (error) {
|
|
4515
|
+
this.throwError(error);
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
if (!this.inbox.hasPending) return false;
|
|
4519
|
+
phase.abort = new AbortController();
|
|
4520
|
+
phase.wakeRequested = false;
|
|
4521
|
+
phase.step = 0;
|
|
4522
|
+
return true;
|
|
4523
|
+
}
|
|
4524
|
+
/** Model label recorded in the request header for one lifecycle. */
|
|
4525
|
+
modelLabel() {
|
|
4526
|
+
return this.config.model ?? NATIVE_MODEL_LABEL4;
|
|
4527
|
+
}
|
|
4528
|
+
/** Append the request header snapshot once per loop instance. */
|
|
4529
|
+
assertRequestHeader() {
|
|
4530
|
+
if (this.requestHeaderLogged) return;
|
|
4531
|
+
const header = canonicalHeader4({
|
|
4532
|
+
config: { provider: PROVIDER4, model: this.modelLabel() }
|
|
4533
|
+
});
|
|
4534
|
+
const baseline = this.session.requestHeader();
|
|
4535
|
+
this.session.append("request/header", {
|
|
4536
|
+
header,
|
|
4537
|
+
reason: baseline === void 0 ? "initial" : "resume"
|
|
4538
|
+
});
|
|
4539
|
+
this.requestHeaderLogged = true;
|
|
4540
|
+
}
|
|
4541
|
+
/** Whether two spawn specs describe the same `kimi acp` child. */
|
|
4542
|
+
specsEqual(a, b) {
|
|
4543
|
+
if (a === void 0) return false;
|
|
4544
|
+
return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
|
|
4545
|
+
}
|
|
4546
|
+
/** Return the cached ACP client, respawning when the spec or process changed. */
|
|
4547
|
+
async acpClient(cwd) {
|
|
4548
|
+
const spec = this.spawnSpec(cwd);
|
|
4549
|
+
if (this.acp !== void 0 && !this.acp.closed && this.specsEqual(this.lastSpec, spec)) return this.acp;
|
|
4550
|
+
this.acp?.dispose();
|
|
4551
|
+
const client = AcpClient.create(spec, this.spawn);
|
|
4552
|
+
this.acp = client;
|
|
4553
|
+
this.lastSpec = spec;
|
|
4554
|
+
try {
|
|
4555
|
+
await client.initialize();
|
|
4556
|
+
} catch (error) {
|
|
4557
|
+
this.acp = void 0;
|
|
4558
|
+
this.lastSpec = void 0;
|
|
4559
|
+
client.dispose();
|
|
4560
|
+
throw error;
|
|
4561
|
+
}
|
|
4562
|
+
return client;
|
|
4563
|
+
}
|
|
4564
|
+
/** Build the `kimi acp` argv/cwd/env for the persistent child. */
|
|
4565
|
+
spawnSpec(cwd) {
|
|
4566
|
+
return {
|
|
4567
|
+
argv: kimiAcpArgv(this.bin),
|
|
4568
|
+
cwd,
|
|
4569
|
+
env: this.config.env
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
/** Run one `kimi acp` step for the current session history and map the streamed updates. */
|
|
4573
|
+
async step() {
|
|
4574
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
4575
|
+
const { turn, step, abort: { signal } } = this.phase;
|
|
4576
|
+
signal.throwIfAborted();
|
|
4577
|
+
this.blocks = [];
|
|
4578
|
+
this.emittedToolCalls = /* @__PURE__ */ new Set();
|
|
4579
|
+
this.toolText = /* @__PURE__ */ new Map();
|
|
4580
|
+
const cwd = this.session.header.cwd;
|
|
4581
|
+
if (cwd === void 0 || cwd.length === 0) {
|
|
4582
|
+
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
4583
|
+
}
|
|
4584
|
+
const history = this.session.deriveMessages();
|
|
4585
|
+
const prompt = serializeHistory(history);
|
|
4586
|
+
if (prompt.length === 0) {
|
|
4587
|
+
throw new Error(`agent "${this.id}": cannot derive a prompt from an empty session log`);
|
|
4588
|
+
}
|
|
4589
|
+
this.assertRequestHeader();
|
|
4590
|
+
signal.throwIfAborted();
|
|
4591
|
+
const client = await this.acpClient(cwd);
|
|
4592
|
+
signal.throwIfAborted();
|
|
4593
|
+
client.onPermission(() => resolveToolApproval(this.session.snapshotEvents()));
|
|
4594
|
+
const acpSessionId = await client.newSession(cwd);
|
|
4595
|
+
signal.throwIfAborted();
|
|
4596
|
+
client.onUpdate((update) => this.applyUpdate(turn, step, update));
|
|
4597
|
+
const cancel = () => {
|
|
4598
|
+
client.cancel(acpSessionId);
|
|
4599
|
+
};
|
|
4600
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
4601
|
+
try {
|
|
4602
|
+
await raceAbort(client.prompt(acpSessionId, prompt), signal, this.id);
|
|
4603
|
+
} finally {
|
|
4604
|
+
signal.removeEventListener("abort", cancel);
|
|
4605
|
+
}
|
|
4606
|
+
this.flushAssistant(turn, step);
|
|
4607
|
+
if (this.blocks.length === 0 && this.emittedToolCalls.size === 0) {
|
|
4608
|
+
throw new LlmError4(
|
|
4609
|
+
`agent "${this.id}": kimi query produced no assistant output`,
|
|
4610
|
+
"KIMI_NO_RESULT"
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
return { kind: "completed" };
|
|
4614
|
+
}
|
|
4615
|
+
/** Per-step accumulation state for streamed assistant blocks and tool calls. */
|
|
4616
|
+
blocks = [];
|
|
4617
|
+
emittedToolCalls = /* @__PURE__ */ new Set();
|
|
4618
|
+
toolText = /* @__PURE__ */ new Map();
|
|
4619
|
+
blockRef(type) {
|
|
4620
|
+
return this.blocks.find((block) => block.type === type);
|
|
4621
|
+
}
|
|
4622
|
+
ensureBlock(type) {
|
|
4623
|
+
const existing = this.blockRef(type);
|
|
4624
|
+
if (existing !== void 0) return existing;
|
|
4625
|
+
const index = this.blocks.length;
|
|
4626
|
+
const block = { index, type, text: "", refs: [] };
|
|
4627
|
+
this.blocks.push(block);
|
|
4628
|
+
return block;
|
|
4629
|
+
}
|
|
4630
|
+
/** Append one streamed update's durable effect for the current step. */
|
|
4631
|
+
applyUpdate(turn, step, update) {
|
|
4632
|
+
if (isThoughtChunk(update)) {
|
|
4633
|
+
const delta = chunkDelta(update);
|
|
4634
|
+
if (delta === "") return;
|
|
4635
|
+
const block = this.ensureBlock("reasoning");
|
|
4636
|
+
const started = block.refs.length === 0;
|
|
4637
|
+
if (started) block.refs.push(this.appendChunk(turn, step, { type: "block-start", index: block.index, blockType: "reasoning" }));
|
|
4638
|
+
block.refs.push(this.appendChunk(turn, step, { type: "reasoning-delta", index: block.index, text: delta }));
|
|
4639
|
+
block.text += delta;
|
|
4640
|
+
return;
|
|
4641
|
+
}
|
|
4642
|
+
if (isTextChunk(update)) {
|
|
4643
|
+
const delta = chunkDelta(update);
|
|
4644
|
+
if (delta === "") return;
|
|
4645
|
+
const block = this.ensureBlock("text");
|
|
4646
|
+
const started = block.refs.length === 0;
|
|
4647
|
+
if (started) block.refs.push(this.appendChunk(turn, step, { type: "block-start", index: block.index, blockType: "text" }));
|
|
4648
|
+
block.refs.push(this.appendChunk(turn, step, { type: "text-delta", index: block.index, text: delta }));
|
|
4649
|
+
block.text += delta;
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
if (isToolCall(update)) {
|
|
4653
|
+
const callId = toolCallIdOf(update);
|
|
4654
|
+
if (callId === "" || this.emittedToolCalls.has(callId)) return;
|
|
4655
|
+
this.emittedToolCalls.add(callId);
|
|
4656
|
+
const name2 = toolCallName(update);
|
|
4657
|
+
this.session.append("tool/call", { turn, step, callId: ToolCallId6(callId), name: name2, arguments: "{}" });
|
|
4658
|
+
this.toolText.set(callId, "");
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
if (isToolCallUpdate(update)) {
|
|
4662
|
+
const callId = toolCallIdOf(update);
|
|
4663
|
+
if (callId === "" || !this.toolText.has(callId)) return;
|
|
4664
|
+
const delta = toolContentText(update);
|
|
4665
|
+
const accumulated = `${this.toolText.get(callId)}${delta}`;
|
|
4666
|
+
this.toolText.set(callId, accumulated);
|
|
4667
|
+
const status = update.status;
|
|
4668
|
+
if (isToolSettledStatus(status)) {
|
|
4669
|
+
const message = toolResult(callId, accumulated, isToolErrorStatus(status));
|
|
4670
|
+
this.session.append("tool/result", { turn, step, message }, { surfaceOp: "append" });
|
|
4671
|
+
this.toolText.delete(callId);
|
|
4672
|
+
}
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
/** Append one live chunk and return its durable seq. */
|
|
4677
|
+
appendChunk(turn, step, chunk) {
|
|
4678
|
+
return this.session.append("assistant/chunk", { turn, step, chunk }).seq;
|
|
4679
|
+
}
|
|
4680
|
+
/** Flush the accumulated assistant blocks into one durable assistant/message. */
|
|
4681
|
+
flushAssistant(turn, step) {
|
|
4682
|
+
if (this.blocks.length === 0 && this.emittedToolCalls.size === 0) return;
|
|
4683
|
+
const content = [];
|
|
4684
|
+
const refs = [];
|
|
4685
|
+
for (const block of this.blocks) {
|
|
4686
|
+
const delta = block.text;
|
|
4687
|
+
content.push(block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta });
|
|
4688
|
+
block.refs.push(this.appendChunk(turn, step, { type: "block-end", index: block.index, block: block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta } }));
|
|
4689
|
+
refs.push(...block.refs);
|
|
4690
|
+
}
|
|
4691
|
+
this.session.append("assistant/message", {
|
|
4692
|
+
turn,
|
|
4693
|
+
step,
|
|
4694
|
+
message: createAssistantMessage4({
|
|
4695
|
+
content,
|
|
4696
|
+
source: { provider: PROVIDER4, model: this.modelLabel() }
|
|
4697
|
+
})
|
|
4698
|
+
}, {
|
|
4699
|
+
surfaceOp: "append",
|
|
4700
|
+
sourceEventSeqs: refs
|
|
4701
|
+
});
|
|
4702
|
+
}
|
|
4703
|
+
};
|
|
4704
|
+
|
|
4705
|
+
// src/engine-kimi/loop.ts
|
|
4706
|
+
var KIMI_DISPOSE_GRACE_MS = 3e3;
|
|
4707
|
+
var Config4 = z4.object({
|
|
4708
|
+
model: z4.string(),
|
|
4709
|
+
env: z4.dict(z4.string()).default({}),
|
|
4710
|
+
bin: z4.string()
|
|
4711
|
+
});
|
|
4712
|
+
function resolveConfig4(config) {
|
|
4713
|
+
return {
|
|
4714
|
+
model: config.model,
|
|
4715
|
+
env: config.env ?? {},
|
|
4716
|
+
bin: kimiBinResolver(config.bin)
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
var KimiLoop = class extends Service4 {
|
|
4720
|
+
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
4721
|
+
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
4722
|
+
/** Validated configuration owned by the loop plugin. */
|
|
4723
|
+
config;
|
|
4724
|
+
ownership;
|
|
4725
|
+
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
4726
|
+
runtime;
|
|
4727
|
+
/** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
4728
|
+
spawn;
|
|
4729
|
+
constructor(ctx, config) {
|
|
4730
|
+
super(ctx, "agentLoopKimi");
|
|
4731
|
+
this.config = resolveConfig4(config);
|
|
4732
|
+
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
4733
|
+
this.runtime = { ctx };
|
|
4734
|
+
this.spawn = (spec) => fromSubprocess2(this.runtime.ctx.subprocess.spawn(kimiSubprocessSpec(spec, KIMI_DISPOSE_GRACE_MS)));
|
|
4735
|
+
ctx.effect(() => () => this.ownership.dispose(), "agentLoopKimi.transactions()");
|
|
4736
|
+
ctx.effect(() => ctx.agents.setFactory(this), "agentLoopKimi.setFactory()");
|
|
4737
|
+
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
4738
|
+
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
4739
|
+
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
4740
|
+
}
|
|
4741
|
+
/**
|
|
4742
|
+
* Construct the driver, scope, and one memoized reverse teardown for a new
|
|
4743
|
+
* agent. The teardown is registered with the factory and the owner fiber
|
|
4744
|
+
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
4745
|
+
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
4746
|
+
*/
|
|
4747
|
+
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
4748
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
4749
|
+
ownerCtx.fiber.assertActive();
|
|
4750
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
4751
|
+
if (callerSignal?.aborted) {
|
|
4752
|
+
throw callerSignal.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal.reason });
|
|
4753
|
+
}
|
|
4754
|
+
const loopCtx = this.runtime.ctx;
|
|
4755
|
+
const abort = new AbortController();
|
|
4756
|
+
const onCallerAbort = () => {
|
|
4757
|
+
abort.abort(callerSignal?.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal?.reason }));
|
|
4758
|
+
};
|
|
4759
|
+
const onFactoryTeardown = () => {
|
|
4760
|
+
abort.abort(this.ownership.signal.reason);
|
|
4761
|
+
};
|
|
4762
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
4763
|
+
this.ownership.signal.addEventListener("abort", onFactoryTeardown, { once: true });
|
|
4764
|
+
let machine;
|
|
4765
|
+
let detachSession;
|
|
4766
|
+
let detachAgent;
|
|
4767
|
+
let disposing;
|
|
4768
|
+
const machineReady = Promise.withResolvers();
|
|
4769
|
+
const dispose = (ownerTriggered = false) => disposing ??= (async () => {
|
|
4770
|
+
abort.abort(new Error(`agent "${id}" lifecycle disposed`));
|
|
4771
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
4772
|
+
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
4773
|
+
try {
|
|
4774
|
+
if (machine === void 0) await machineReady.promise;
|
|
4775
|
+
if (machine !== void 0) {
|
|
4776
|
+
machine.cancel({ kind: "disposed" });
|
|
4777
|
+
await machine.whenIdle();
|
|
4778
|
+
await machine.scope.dispose();
|
|
4779
|
+
}
|
|
4780
|
+
} finally {
|
|
4781
|
+
try {
|
|
4782
|
+
await handle?.close();
|
|
4783
|
+
} finally {
|
|
4784
|
+
try {
|
|
4785
|
+
detachAgent?.();
|
|
4786
|
+
detachSession?.();
|
|
4787
|
+
} finally {
|
|
4788
|
+
untrack();
|
|
4789
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
})();
|
|
4794
|
+
const untrack = this.ownership.track(dispose);
|
|
4795
|
+
let unfollowOwner;
|
|
4796
|
+
try {
|
|
4797
|
+
unfollowOwner = ownerCtx.effect(() => () => {
|
|
4798
|
+
if (disposing !== void 0) return;
|
|
4799
|
+
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
4800
|
+
return dispose(true);
|
|
4801
|
+
}, `agentLoopKimi.lifecycle(${id})`);
|
|
4802
|
+
} catch (error) {
|
|
4803
|
+
untrack();
|
|
4804
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
4805
|
+
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
4806
|
+
throw error;
|
|
4807
|
+
}
|
|
4808
|
+
const assertLive = () => {
|
|
4809
|
+
if (!abort.signal.aborted) return;
|
|
4810
|
+
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
4811
|
+
};
|
|
4812
|
+
try {
|
|
4813
|
+
const agent = machine = new KimiAgent(loopCtx, id, options, session, this.config, this.spawn, this.config.bin);
|
|
4814
|
+
machineReady.resolve();
|
|
4815
|
+
assertLive();
|
|
4816
|
+
return {
|
|
4817
|
+
agent,
|
|
4818
|
+
signal: abort.signal,
|
|
4819
|
+
publish: (source) => {
|
|
4820
|
+
assertLive();
|
|
4821
|
+
detachSession = agent.ctx.sessions.enter(session);
|
|
4822
|
+
detachAgent = loopCtx.agents.enter(agent, ownerCtx.agent);
|
|
4823
|
+
agent.ctx.sessions.announce(session);
|
|
4824
|
+
assertLive();
|
|
4825
|
+
loopCtx.agents.announce(agent);
|
|
4826
|
+
assertLive();
|
|
4827
|
+
emitAgentEvent4(loopCtx, agent, "agent/session-start", { source });
|
|
4828
|
+
assertLive();
|
|
4829
|
+
return { agent, dispose };
|
|
4830
|
+
},
|
|
4831
|
+
dispose
|
|
4832
|
+
};
|
|
4833
|
+
} catch (error) {
|
|
4834
|
+
machineReady.resolve();
|
|
4835
|
+
void dispose();
|
|
4836
|
+
throw error;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
4840
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
4841
|
+
var _stack = [];
|
|
4842
|
+
try {
|
|
4843
|
+
const ownedPreparation = __using(_stack, preparation);
|
|
4844
|
+
const session = ownedPreparation.session;
|
|
4845
|
+
let prepared;
|
|
4846
|
+
try {
|
|
4847
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
4848
|
+
} catch (error) {
|
|
4849
|
+
await stored?.handle.close().catch(() => {
|
|
4850
|
+
});
|
|
4851
|
+
throw error;
|
|
4852
|
+
}
|
|
4853
|
+
try {
|
|
4854
|
+
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
4855
|
+
setupCommit?.commit();
|
|
4856
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
4857
|
+
return prepared.publish(source);
|
|
4858
|
+
} catch (error) {
|
|
4859
|
+
await prepared.dispose().catch(() => {
|
|
4860
|
+
});
|
|
4861
|
+
throw error;
|
|
4862
|
+
}
|
|
4863
|
+
} catch (_) {
|
|
4864
|
+
var _error = _, _hasError = true;
|
|
4865
|
+
} finally {
|
|
4866
|
+
__callDispose(_stack, _error, _hasError);
|
|
4867
|
+
}
|
|
4868
|
+
}
|
|
4869
|
+
/**
|
|
4870
|
+
* Create an agent and session under one caller-supplied identity, owned by
|
|
4871
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
4872
|
+
* durable identity is stored before publication.
|
|
4873
|
+
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
4874
|
+
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
4875
|
+
* @returns the published handle.
|
|
4876
|
+
*/
|
|
4877
|
+
async createAgent(ownerCtx, options) {
|
|
4878
|
+
const preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
4879
|
+
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
4880
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
4881
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
4882
|
+
}));
|
|
4883
|
+
const published = (async () => {
|
|
4884
|
+
let stored;
|
|
4885
|
+
try {
|
|
4886
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
4887
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
4888
|
+
options.signal,
|
|
4889
|
+
options.sessionId,
|
|
4890
|
+
(abandoned) => {
|
|
4891
|
+
void abandoned?.handle.close().catch(() => {
|
|
4892
|
+
});
|
|
4893
|
+
}
|
|
4894
|
+
);
|
|
4895
|
+
} catch (error) {
|
|
4896
|
+
preparation[Symbol.dispose]();
|
|
4897
|
+
throw error;
|
|
4898
|
+
}
|
|
4899
|
+
return this.setupAndPublish(
|
|
4900
|
+
ownerCtx,
|
|
4901
|
+
options.sessionId,
|
|
4902
|
+
preparation,
|
|
4903
|
+
options.agentOptions ?? {},
|
|
4904
|
+
options.setup,
|
|
4905
|
+
options.signal,
|
|
4906
|
+
"startup",
|
|
4907
|
+
stored
|
|
4908
|
+
);
|
|
4909
|
+
})();
|
|
4910
|
+
this.ownership.trackWrapper(published);
|
|
4911
|
+
return published;
|
|
4912
|
+
}
|
|
4913
|
+
/**
|
|
4914
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
4915
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
4916
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
4917
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
4918
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
4919
|
+
* created again.
|
|
4920
|
+
* @param session - the unpublished session to store.
|
|
4921
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
4922
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
4923
|
+
*/
|
|
4924
|
+
async createStoredSession(session, signal) {
|
|
4925
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
4926
|
+
if (persistence === void 0) return void 0;
|
|
4927
|
+
const handle = await persistence.create(session.header, {
|
|
4928
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
4929
|
+
...signal === void 0 ? {} : { signal }
|
|
4930
|
+
});
|
|
4931
|
+
return { handle, storedCount: 0 };
|
|
4932
|
+
}
|
|
4933
|
+
/**
|
|
4934
|
+
* Durably store the session events appended since the last stored cursor.
|
|
4935
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
4936
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
4937
|
+
* through the handle before live events start routing into it.
|
|
4938
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
4939
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
4940
|
+
*/
|
|
4941
|
+
async appendUnstoredSuffix(stored, session) {
|
|
4942
|
+
if (stored === void 0) return;
|
|
4943
|
+
const suffix = session.snapshotEvents(SessionLogOffset4(stored.storedCount));
|
|
4944
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
4945
|
+
stored.storedCount += suffix.length;
|
|
4946
|
+
}
|
|
4947
|
+
/**
|
|
4948
|
+
* Resume an owned agent from the configured persistence service.
|
|
4949
|
+
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
4950
|
+
* @param options - persisted identity, loop options, setup, and cancellation.
|
|
4951
|
+
* @returns the published handle.
|
|
4952
|
+
*/
|
|
4953
|
+
async resume(ownerCtx, options) {
|
|
4954
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
4955
|
+
if (persistence === void 0) {
|
|
4956
|
+
throw new Error("cannot resume: session persistence is not configured (load a dsh-session-persistence backend)");
|
|
4957
|
+
}
|
|
4958
|
+
return this.resumeWith(ownerCtx, persistence, options);
|
|
4959
|
+
}
|
|
4960
|
+
/** Resume through an explicit persistence service. */
|
|
4961
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
4962
|
+
const id = options.resumeSessionId;
|
|
4963
|
+
const published = (async () => {
|
|
4964
|
+
const ownerAbort = new AbortController();
|
|
4965
|
+
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
4966
|
+
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
4967
|
+
}, `agentLoopKimi.resume-load(${id})`);
|
|
4968
|
+
const fused = AbortSignal.any([
|
|
4969
|
+
...options.signal === void 0 ? [] : [options.signal],
|
|
4970
|
+
ownerAbort.signal,
|
|
4971
|
+
this.ownership.signal
|
|
4972
|
+
]);
|
|
4973
|
+
let handle;
|
|
4974
|
+
let stored;
|
|
4975
|
+
let preparation;
|
|
4976
|
+
try {
|
|
4977
|
+
try {
|
|
4978
|
+
handle = await raceAbortCall(
|
|
4979
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
4980
|
+
fused,
|
|
4981
|
+
id,
|
|
4982
|
+
(abandoned) => {
|
|
4983
|
+
void abandoned.close();
|
|
4984
|
+
}
|
|
4985
|
+
);
|
|
4986
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
4987
|
+
fused.throwIfAborted();
|
|
4988
|
+
const closers = interruptedTurnClosers4(persisted);
|
|
4989
|
+
if (closers.length > 0) await handle.append(closers);
|
|
4990
|
+
preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(id, {
|
|
4991
|
+
seed: [...persisted, ...closers],
|
|
4992
|
+
meta: structuredClone(handle.header),
|
|
4993
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
4994
|
+
seedSource: "persistence"
|
|
4995
|
+
}));
|
|
4996
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
4997
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
4998
|
+
} finally {
|
|
4999
|
+
await unfollowOwner();
|
|
5000
|
+
}
|
|
5001
|
+
ownerCtx.fiber.assertActive();
|
|
5002
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
5003
|
+
const owned = stored;
|
|
5004
|
+
handle = void 0;
|
|
5005
|
+
return await this.setupAndPublish(
|
|
5006
|
+
ownerCtx,
|
|
5007
|
+
id,
|
|
5008
|
+
preparation,
|
|
5009
|
+
options.agentOptions ?? {},
|
|
5010
|
+
options.setup,
|
|
5011
|
+
options.signal,
|
|
5012
|
+
"resume",
|
|
5013
|
+
owned
|
|
5014
|
+
);
|
|
5015
|
+
} finally {
|
|
5016
|
+
preparation?.[Symbol.dispose]();
|
|
5017
|
+
await handle?.close().catch(() => {
|
|
5018
|
+
});
|
|
5019
|
+
}
|
|
5020
|
+
})();
|
|
5021
|
+
this.ownership.trackWrapper(published);
|
|
5022
|
+
return published;
|
|
5023
|
+
}
|
|
5024
|
+
};
|
|
5025
|
+
|
|
5026
|
+
// src/engine-kimi/skills.ts
|
|
5027
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
5028
|
+
import { homedir as homedir3 } from "node:os";
|
|
5029
|
+
import { dirname as dirname3, join as join6, resolve as resolve3 } from "node:path";
|
|
5030
|
+
|
|
5031
|
+
// src/driver-core/context-files.ts
|
|
5032
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
5033
|
+
import { join as join5, resolve as resolve2 } from "node:path";
|
|
3811
5034
|
|
|
3812
5035
|
// src/skills.ts
|
|
3813
5036
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
@@ -4001,101 +5224,526 @@ function toCandidate(skill, path, rank, resourceBaseDir) {
|
|
|
4001
5224
|
resourceBase: { kind: "directory", path: resourceBaseDir }
|
|
4002
5225
|
};
|
|
4003
5226
|
}
|
|
4004
|
-
async function tryParseSkill(path) {
|
|
4005
|
-
try {
|
|
4006
|
-
const raw = await readFile(path, { encoding: "utf8" });
|
|
4007
|
-
return parseSkillFile(raw);
|
|
4008
|
-
} catch {
|
|
4009
|
-
return void 0;
|
|
4010
|
-
}
|
|
5227
|
+
async function tryParseSkill(path) {
|
|
5228
|
+
try {
|
|
5229
|
+
const raw = await readFile(path, { encoding: "utf8" });
|
|
5230
|
+
return parseSkillFile(raw);
|
|
5231
|
+
} catch {
|
|
5232
|
+
return void 0;
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
async function findProjectRoot(cwd) {
|
|
5236
|
+
let current = cwd;
|
|
5237
|
+
while (true) {
|
|
5238
|
+
try {
|
|
5239
|
+
await stat(join4(current, ".git"));
|
|
5240
|
+
return current;
|
|
5241
|
+
} catch {
|
|
5242
|
+
}
|
|
5243
|
+
const parent = resolve(current, "..");
|
|
5244
|
+
if (parent === current) return cwd;
|
|
5245
|
+
current = parent;
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
// src/driver-core/context-files.ts
|
|
5250
|
+
async function projectAncestors(cwd) {
|
|
5251
|
+
const root = await findProjectRoot(resolve2(cwd));
|
|
5252
|
+
const dirs = [];
|
|
5253
|
+
let current = resolve2(cwd);
|
|
5254
|
+
while (true) {
|
|
5255
|
+
dirs.push(current);
|
|
5256
|
+
if (current === root) return dirs;
|
|
5257
|
+
current = resolve2(current, "..");
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
async function collectProjectContextFiles(cwd, policy) {
|
|
5261
|
+
const files = [];
|
|
5262
|
+
for (const dir of await projectAncestors(cwd)) {
|
|
5263
|
+
const chosen = await dirContextFile(dir, policy);
|
|
5264
|
+
if (chosen !== void 0) files.push(chosen);
|
|
5265
|
+
}
|
|
5266
|
+
return files;
|
|
5267
|
+
}
|
|
5268
|
+
async function dirContextFile(dir, policy) {
|
|
5269
|
+
if (policy.override !== void 0) {
|
|
5270
|
+
const override = join5(dir, policy.override);
|
|
5271
|
+
if (await pathExists(override)) return override;
|
|
5272
|
+
}
|
|
5273
|
+
for (const name2 of policy.primary) {
|
|
5274
|
+
const candidate = join5(dir, name2);
|
|
5275
|
+
if (await pathExists(candidate)) return candidate;
|
|
5276
|
+
}
|
|
5277
|
+
return void 0;
|
|
5278
|
+
}
|
|
5279
|
+
async function pathExists(path) {
|
|
5280
|
+
try {
|
|
5281
|
+
await stat2(path);
|
|
5282
|
+
return true;
|
|
5283
|
+
} catch {
|
|
5284
|
+
return false;
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
async function readOptionalFile(path) {
|
|
5288
|
+
try {
|
|
5289
|
+
return await readFile2(path, { encoding: "utf8" });
|
|
5290
|
+
} catch {
|
|
5291
|
+
return void 0;
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
async function anySourceNonEmpty(paths) {
|
|
5295
|
+
for (const path of paths) {
|
|
5296
|
+
const raw = await readOptionalFile(path);
|
|
5297
|
+
if (raw !== void 0 && raw.trim().length > 0) return true;
|
|
5298
|
+
}
|
|
5299
|
+
return false;
|
|
5300
|
+
}
|
|
5301
|
+
async function fileNonEmpty(path) {
|
|
5302
|
+
const raw = await readOptionalFile(path);
|
|
5303
|
+
return raw !== void 0 && raw.trim().length > 0;
|
|
5304
|
+
}
|
|
5305
|
+
async function readSources(paths) {
|
|
5306
|
+
const parts = [];
|
|
5307
|
+
for (const path of paths) {
|
|
5308
|
+
const raw = await readOptionalFile(path);
|
|
5309
|
+
if (raw !== void 0 && raw.trim().length > 0) parts.push(raw);
|
|
5310
|
+
}
|
|
5311
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
5312
|
+
}
|
|
5313
|
+
|
|
5314
|
+
// src/engine-kimi/skills.ts
|
|
5315
|
+
var PROVIDER_NAME2 = "kimi";
|
|
5316
|
+
var KIMI_AGENTS_PROJECT_RANK = 140;
|
|
5317
|
+
var KIMI_SKILL_PROJECT_RANK = 150;
|
|
5318
|
+
var KIMI_SKILL_USER_RANK = 160;
|
|
5319
|
+
var KIMI_CONTEXT_POLICY = {
|
|
5320
|
+
primary: ["AGENTS.md"]
|
|
5321
|
+
};
|
|
5322
|
+
function kimiAgentDir() {
|
|
5323
|
+
const override = process.env.KIMI_CODE_HOME;
|
|
5324
|
+
if (override !== void 0 && override.length > 0) return resolve3(override);
|
|
5325
|
+
return join6(homedir3(), ".kimi-code");
|
|
5326
|
+
}
|
|
5327
|
+
var KimiSkillProvider = class {
|
|
5328
|
+
constructor(control) {
|
|
5329
|
+
this.control = control;
|
|
5330
|
+
}
|
|
5331
|
+
control;
|
|
5332
|
+
name = PROVIDER_NAME2;
|
|
5333
|
+
async list(options) {
|
|
5334
|
+
const candidates = [];
|
|
5335
|
+
const cwd = options.cwd;
|
|
5336
|
+
if (cwd !== void 0) {
|
|
5337
|
+
const projectDirs = await projectAncestors(cwd);
|
|
5338
|
+
const contextPaths = await collectProjectContextFiles(cwd, KIMI_CONTEXT_POLICY);
|
|
5339
|
+
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, KIMI_AGENTS_PROJECT_RANK));
|
|
5340
|
+
for (const dir of projectDirs) {
|
|
5341
|
+
await this.collectSkillsDir(join6(dir, ".kimi-code", "skills"), KIMI_SKILL_PROJECT_RANK, candidates);
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
await this.collectSkillsDir(join6(kimiAgentDir(), "skills"), KIMI_SKILL_USER_RANK, candidates);
|
|
5345
|
+
if (this.control.signal.aborted) return [];
|
|
5346
|
+
return candidates;
|
|
5347
|
+
}
|
|
5348
|
+
async get(candidate, _options) {
|
|
5349
|
+
const locator = candidate.locator;
|
|
5350
|
+
if (locator.kind === "skill-file") {
|
|
5351
|
+
const parsed = await this.tryParse(locator.path);
|
|
5352
|
+
if (parsed === void 0) return void 0;
|
|
5353
|
+
return {
|
|
5354
|
+
name: parsed.name,
|
|
5355
|
+
description: parsed.description,
|
|
5356
|
+
...parsed.whenToUse === void 0 ? {} : { whenToUse: parsed.whenToUse },
|
|
5357
|
+
invocation: parsed.invocation,
|
|
5358
|
+
source: candidate.source,
|
|
5359
|
+
provider: this.name,
|
|
5360
|
+
content: parsed.content,
|
|
5361
|
+
path: locator.path,
|
|
5362
|
+
resourceBase: { kind: "directory", path: dirname3(locator.path) }
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
5365
|
+
const content = await readSources(locator.paths);
|
|
5366
|
+
if (content === void 0) return void 0;
|
|
5367
|
+
const first = locator.paths[0];
|
|
5368
|
+
return {
|
|
5369
|
+
name: candidate.name,
|
|
5370
|
+
description: candidate.description,
|
|
5371
|
+
invocation: candidate.invocation,
|
|
5372
|
+
source: candidate.source,
|
|
5373
|
+
provider: this.name,
|
|
5374
|
+
content,
|
|
5375
|
+
path: first,
|
|
5376
|
+
resourceBase: { kind: "file", path: first }
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
/** One merged `agents-md` candidate for a ranked file set. */
|
|
5380
|
+
agentsCandidate(paths, rank) {
|
|
5381
|
+
const first = paths[0];
|
|
5382
|
+
return {
|
|
5383
|
+
name: "agents-md",
|
|
5384
|
+
description: "Kimi project instructions (AGENTS.md)",
|
|
5385
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
5386
|
+
source: "custom",
|
|
5387
|
+
provider: this.name,
|
|
5388
|
+
rank,
|
|
5389
|
+
locator: { kind: "agents-md", paths },
|
|
5390
|
+
path: first,
|
|
5391
|
+
resourceBase: { kind: "file", path: first }
|
|
5392
|
+
};
|
|
5393
|
+
}
|
|
5394
|
+
/** Collect every skill in one skills directory, both kimi layouts. */
|
|
5395
|
+
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
5396
|
+
let entries;
|
|
5397
|
+
try {
|
|
5398
|
+
entries = await readdir2(skillsDir, { withFileTypes: true, encoding: "utf8" });
|
|
5399
|
+
} catch {
|
|
5400
|
+
return;
|
|
5401
|
+
}
|
|
5402
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5403
|
+
const entryPath = join6(skillsDir, entry.name);
|
|
5404
|
+
const info = await stat3(entryPath).catch(() => void 0);
|
|
5405
|
+
if (info === void 0) continue;
|
|
5406
|
+
if (info.isDirectory()) {
|
|
5407
|
+
const path = join6(entryPath, "SKILL.md");
|
|
5408
|
+
const parsed2 = await this.tryParse(path);
|
|
5409
|
+
if (parsed2 === void 0) continue;
|
|
5410
|
+
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
5411
|
+
continue;
|
|
5412
|
+
}
|
|
5413
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
5414
|
+
const parsed = await this.tryParse(entryPath);
|
|
5415
|
+
if (parsed === void 0) continue;
|
|
5416
|
+
candidates.push(this.skillCandidate(parsed, entryPath, rank, skillsDir));
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
/** One parsed skill as a ranked candidate. */
|
|
5420
|
+
skillCandidate(skill, path, rank, resourceDir) {
|
|
5421
|
+
return {
|
|
5422
|
+
name: skill.name,
|
|
5423
|
+
description: skill.description,
|
|
5424
|
+
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
|
|
5425
|
+
invocation: skill.invocation,
|
|
5426
|
+
source: "custom",
|
|
5427
|
+
provider: this.name,
|
|
5428
|
+
rank,
|
|
5429
|
+
locator: { kind: "skill-file", path },
|
|
5430
|
+
path,
|
|
5431
|
+
resourceBase: { kind: "directory", path: resourceDir }
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5434
|
+
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
5435
|
+
async tryParse(path) {
|
|
5436
|
+
try {
|
|
5437
|
+
const raw = await readFile3(path, { encoding: "utf8" });
|
|
5438
|
+
return parseSkillFile(raw);
|
|
5439
|
+
} catch {
|
|
5440
|
+
return void 0;
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
};
|
|
5444
|
+
|
|
5445
|
+
// src/engine-kimi/commands.ts
|
|
5446
|
+
import { createUserMessage as createUserMessage5 } from "@deepseek-ai/dsh-llm";
|
|
5447
|
+
function forwardKimiCommand(name2) {
|
|
5448
|
+
return (invocation) => {
|
|
5449
|
+
invocation.agent.followup(createUserMessage5({
|
|
5450
|
+
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
5451
|
+
source: { kind: "user" }
|
|
5452
|
+
}));
|
|
5453
|
+
return { kind: "success" };
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
function builtin(name2, description) {
|
|
5457
|
+
return { name: name2, description, handler: forwardKimiCommand(name2) };
|
|
5458
|
+
}
|
|
5459
|
+
var KIMI_COMMANDS = [
|
|
5460
|
+
builtin("help", "Show available Kimi Code commands"),
|
|
5461
|
+
builtin("status", "Show the current session runtime state"),
|
|
5462
|
+
builtin("compact", "Compact the conversation context to free token usage"),
|
|
5463
|
+
builtin("clear", "Start a fresh session, discarding the current context"),
|
|
5464
|
+
builtin("plan", "Toggle plan (read-only exploration) mode"),
|
|
5465
|
+
builtin("auto", "Toggle auto permission mode"),
|
|
5466
|
+
builtin("usage", "Show token usage, context, and quota information"),
|
|
5467
|
+
builtin("version", "Display the Kimi Code CLI version number"),
|
|
5468
|
+
builtin("goal", "Start or manage an autonomous goal")
|
|
5469
|
+
];
|
|
5470
|
+
|
|
5471
|
+
// src/settings.ts
|
|
5472
|
+
import z5 from "@deepseek-ai/schemastery";
|
|
5473
|
+
|
|
5474
|
+
// src/namespace.ts
|
|
5475
|
+
var LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
|
|
5476
|
+
|
|
5477
|
+
// src/settings.ts
|
|
5478
|
+
var LOOP_ENGINE_IDS = ["in-process", "claude-code", "codex", "pi", "kimi"];
|
|
5479
|
+
var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
|
|
5480
|
+
engine: z5.union([z5.const("in-process"), z5.const("claude-code"), z5.const("codex"), z5.const("pi"), z5.const("kimi")]).default("in-process"),
|
|
5481
|
+
showInComposer: z5.boolean().default(true)
|
|
5482
|
+
});
|
|
5483
|
+
function loopEngineSettingsNamespace() {
|
|
5484
|
+
return LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL;
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
// src/patch-manager.ts
|
|
5488
|
+
var MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block: ";
|
|
5489
|
+
var MANAGED_BLOCK_END = "# -- /dsh-loop-engine managed block --";
|
|
5490
|
+
var END_MARKER_LINE = `${MANAGED_BLOCK_END}
|
|
5491
|
+
`;
|
|
5492
|
+
function renderManagedBlock(engine) {
|
|
5493
|
+
if (engine === "in-process") return "";
|
|
5494
|
+
return [
|
|
5495
|
+
`${MANAGED_BLOCK_BEGIN}${engine} --`,
|
|
5496
|
+
"- id: agent-loop",
|
|
5497
|
+
" disabled: true",
|
|
5498
|
+
"- id: command-goal",
|
|
5499
|
+
" disabled: true",
|
|
5500
|
+
END_MARKER_LINE
|
|
5501
|
+
].join("\n");
|
|
5502
|
+
}
|
|
5503
|
+
var BEGIN_MARKER_RE = /^# -- dsh-loop-engine managed block: (\S+) --$/m;
|
|
5504
|
+
function currentEngineOf(text) {
|
|
5505
|
+
const engine = BEGIN_MARKER_RE.exec(text)?.[1];
|
|
5506
|
+
return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : "in-process";
|
|
5507
|
+
}
|
|
5508
|
+
function managedSpan(text) {
|
|
5509
|
+
const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
|
|
5510
|
+
if (begin === -1) return { head: text, tail: "", present: false, blankBefore: false };
|
|
5511
|
+
const afterBegin = begin + MANAGED_BLOCK_BEGIN.length;
|
|
5512
|
+
const endAt = text.indexOf(MANAGED_BLOCK_END, afterBegin);
|
|
5513
|
+
const spanEnd = endAt === -1 ? text.length : endAt + END_MARKER_LINE.length;
|
|
5514
|
+
const before = text.slice(0, begin);
|
|
5515
|
+
const blankBefore = before.endsWith("\n\n");
|
|
5516
|
+
return {
|
|
5517
|
+
head: blankBefore ? before.slice(0, -1) : before,
|
|
5518
|
+
tail: text.slice(spanEnd),
|
|
5519
|
+
present: true,
|
|
5520
|
+
blankBefore
|
|
5521
|
+
};
|
|
5522
|
+
}
|
|
5523
|
+
function ensureTrailingNewline(text) {
|
|
5524
|
+
return text.endsWith("\n") ? text : `${text}
|
|
5525
|
+
`;
|
|
4011
5526
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
5527
|
+
function hasRootEntry(text) {
|
|
5528
|
+
return /^(?:- |\[)/m.test(text);
|
|
5529
|
+
}
|
|
5530
|
+
function dropSeedPlaceholder(text) {
|
|
5531
|
+
return text.replace(/^\[\]\n/m, "");
|
|
5532
|
+
}
|
|
5533
|
+
function seedEmptyArray(text) {
|
|
5534
|
+
const head = text.replace(/\n+$/, "");
|
|
5535
|
+
return head === "" ? "[]\n" : `${head}
|
|
5536
|
+
[]
|
|
5537
|
+
`;
|
|
5538
|
+
}
|
|
5539
|
+
function applyManagedBlock(text, engine) {
|
|
5540
|
+
const block = renderManagedBlock(engine);
|
|
5541
|
+
const span = managedSpan(text);
|
|
5542
|
+
let result;
|
|
5543
|
+
if (!span.present) {
|
|
5544
|
+
if (block === "") {
|
|
5545
|
+
result = text;
|
|
5546
|
+
} else {
|
|
5547
|
+
const base = ensureTrailingNewline(text);
|
|
5548
|
+
result = `${base}
|
|
5549
|
+
${block}`;
|
|
4019
5550
|
}
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
5551
|
+
} else if (block === "") {
|
|
5552
|
+
result = span.tail.startsWith("\n") ? `${span.head}${span.tail.slice(1)}` : `${span.head}${span.tail}`;
|
|
5553
|
+
} else {
|
|
5554
|
+
result = `${span.head}${span.blankBefore ? "\n" : ""}${block}${span.tail}`;
|
|
4023
5555
|
}
|
|
5556
|
+
if (block !== "") return dropSeedPlaceholder(result);
|
|
5557
|
+
if (text.trim() === "") return result;
|
|
5558
|
+
return hasRootEntry(result) ? result : seedEmptyArray(result);
|
|
4024
5559
|
}
|
|
4025
5560
|
|
|
4026
|
-
// src/
|
|
4027
|
-
import {
|
|
4028
|
-
import {
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
5561
|
+
// src/preset.ts
|
|
5562
|
+
import { mkdir, readFile as readFile4, rename, writeFile } from "node:fs/promises";
|
|
5563
|
+
import { randomUUID } from "node:crypto";
|
|
5564
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
5565
|
+
var HOSTED_PRESET_ID = "loop-engine";
|
|
5566
|
+
var USER_PRESET_DIR = ".agent-presets";
|
|
5567
|
+
var COMPOSITION_FILE = "agent.cordis.yml";
|
|
5568
|
+
var METADATA_FILE = "preset.yml";
|
|
5569
|
+
var SOURCE_PRESET_ID = "standard";
|
|
5570
|
+
var STRIPPED_ROWS = ["skill-filesystem", "tool-skill", "tool-goal", "planning", "compaction"];
|
|
5571
|
+
var MANAGED_HEADER = `# Managed by dsh-loop-engine: the deployment's "${SOURCE_PRESET_ID}" preset minus
|
|
5572
|
+
# the dsh-native command/skill rows a hosted loop engine replaces. Regenerated
|
|
5573
|
+
# from "${SOURCE_PRESET_ID}" on boot \u2014 hand edits are overwritten.
|
|
5574
|
+
`;
|
|
5575
|
+
var MANAGED_METADATA = "name: Hosted Engine\ndescription: Standard preset minus the dsh-native commands and skills a hosted loop engine replaces.\n";
|
|
5576
|
+
function isEntryStart(line) {
|
|
5577
|
+
return line.startsWith("- ");
|
|
4042
5578
|
}
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
for (const dir of await projectAncestors(cwd)) {
|
|
4046
|
-
const chosen = await dirContextFile(dir, policy);
|
|
4047
|
-
if (chosen !== void 0) files.push(chosen);
|
|
4048
|
-
}
|
|
4049
|
-
return files;
|
|
5579
|
+
function entryId(line) {
|
|
5580
|
+
return /^- id:\s*(\S+)\s*$/.exec(line)?.[1];
|
|
4050
5581
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
5582
|
+
function stripPresetRows(text, ids = STRIPPED_ROWS) {
|
|
5583
|
+
const lines = text.split("\n");
|
|
5584
|
+
const starts = [];
|
|
5585
|
+
for (const [index, line] of lines.entries()) {
|
|
5586
|
+
if (isEntryStart(line)) starts.push(index);
|
|
5587
|
+
}
|
|
5588
|
+
if (starts.length === 0) return text;
|
|
5589
|
+
const drop = new Set(ids);
|
|
5590
|
+
const entries = [];
|
|
5591
|
+
let heading = lines.slice(0, starts[0]);
|
|
5592
|
+
for (const [index, start] of starts.entries()) {
|
|
5593
|
+
const end = index + 1 < starts.length ? starts[index + 1] : lines.length;
|
|
5594
|
+
const span = lines.slice(start, end);
|
|
5595
|
+
let bodyEnd = span.length;
|
|
5596
|
+
while (bodyEnd > 1) {
|
|
5597
|
+
const line = span[bodyEnd - 1];
|
|
5598
|
+
if (line.trim() !== "" && !line.trimStart().startsWith("#")) break;
|
|
5599
|
+
bodyEnd -= 1;
|
|
5600
|
+
}
|
|
5601
|
+
entries.push({ id: entryId(span[0]), heading, body: span.slice(0, bodyEnd) });
|
|
5602
|
+
heading = span.slice(bodyEnd);
|
|
5603
|
+
}
|
|
5604
|
+
const out = [];
|
|
5605
|
+
out.push(...entries[0].heading);
|
|
5606
|
+
let lastKept = -1;
|
|
5607
|
+
for (const [index, entry] of entries.entries()) {
|
|
5608
|
+
if (entry.id !== void 0 && drop.has(entry.id)) continue;
|
|
5609
|
+
if (index > 0) out.push(...entry.heading);
|
|
5610
|
+
out.push(...entry.body);
|
|
5611
|
+
lastKept = index;
|
|
5612
|
+
}
|
|
5613
|
+
if (lastKept === entries.length - 1) out.push(...heading);
|
|
5614
|
+
if (out.length > 0 && out[out.length - 1] !== "") out.push("");
|
|
5615
|
+
return out.join("\n");
|
|
4061
5616
|
}
|
|
4062
|
-
async function
|
|
5617
|
+
async function writeIfDifferent(path, text) {
|
|
4063
5618
|
try {
|
|
4064
|
-
await
|
|
4065
|
-
return true;
|
|
5619
|
+
if (await readFile4(path, "utf8") === text) return false;
|
|
4066
5620
|
} catch {
|
|
4067
|
-
return false;
|
|
4068
5621
|
}
|
|
5622
|
+
await mkdir(dirname4(path), { recursive: true });
|
|
5623
|
+
const tmp = `${path}.tmp-${randomUUID()}`;
|
|
5624
|
+
await writeFile(tmp, text, "utf8");
|
|
5625
|
+
await rename(tmp, path);
|
|
5626
|
+
return true;
|
|
4069
5627
|
}
|
|
4070
|
-
async function
|
|
5628
|
+
async function ensureHostedPreset(dshHome, source) {
|
|
5629
|
+
const composition = await source.read(SOURCE_PRESET_ID);
|
|
5630
|
+
const stripped = `${MANAGED_HEADER}
|
|
5631
|
+
${stripPresetRows(composition)}`;
|
|
5632
|
+
const dir = join7(dshHome, USER_PRESET_DIR, HOSTED_PRESET_ID);
|
|
5633
|
+
const compositionChanged = await writeIfDifferent(join7(dir, COMPOSITION_FILE), stripped);
|
|
5634
|
+
const metadataChanged = await writeIfDifferent(join7(dir, METADATA_FILE), MANAGED_METADATA);
|
|
5635
|
+
return compositionChanged || metadataChanged;
|
|
5636
|
+
}
|
|
5637
|
+
|
|
5638
|
+
// src/provider-route.ts
|
|
5639
|
+
import { LlmAdapter, LlmError as LlmError5 } from "@deepseek-ai/dsh-llm";
|
|
5640
|
+
var HOSTED_PROVIDER_ROUTES = {
|
|
5641
|
+
"claude-code": PROVIDER,
|
|
5642
|
+
codex: PROVIDER2,
|
|
5643
|
+
pi: PROVIDER3,
|
|
5644
|
+
kimi: PROVIDER4
|
|
5645
|
+
};
|
|
5646
|
+
var HostedEngineRouteAdapter = class extends LlmAdapter {
|
|
5647
|
+
/**
|
|
5648
|
+
* @param label - the provider route label this placeholder serves.
|
|
5649
|
+
*/
|
|
5650
|
+
constructor(label) {
|
|
5651
|
+
super();
|
|
5652
|
+
this.label = label;
|
|
5653
|
+
}
|
|
5654
|
+
label;
|
|
5655
|
+
stream(_options) {
|
|
5656
|
+
throw new LlmError5(
|
|
5657
|
+
`provider "${this.label}" is a hosted loop engine route, not a model endpoint`,
|
|
5658
|
+
"HOSTED_ENGINE_ROUTE"
|
|
5659
|
+
);
|
|
5660
|
+
}
|
|
5661
|
+
};
|
|
5662
|
+
|
|
5663
|
+
// src/commands.ts
|
|
5664
|
+
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
5665
|
+
import { homedir as homedir4 } from "node:os";
|
|
5666
|
+
import { join as join8 } from "node:path";
|
|
5667
|
+
import { createUserMessage as createUserMessage6 } from "@deepseek-ai/dsh-llm";
|
|
5668
|
+
var COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
|
|
5669
|
+
function forwardClaudeCodeCommand(name2) {
|
|
5670
|
+
return (invocation) => {
|
|
5671
|
+
invocation.agent.followup(createUserMessage6({
|
|
5672
|
+
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
5673
|
+
source: { kind: "user" }
|
|
5674
|
+
}));
|
|
5675
|
+
return { kind: "success" };
|
|
5676
|
+
};
|
|
5677
|
+
}
|
|
5678
|
+
function builtin2(name2, description) {
|
|
5679
|
+
return { name: name2, description, handler: forwardClaudeCodeCommand(name2) };
|
|
5680
|
+
}
|
|
5681
|
+
var CLAUDE_CODE_COMMANDS = [
|
|
5682
|
+
builtin2("help", "Show help about Claude Code commands"),
|
|
5683
|
+
builtin2("compact", "Compact the conversation to reduce context usage"),
|
|
5684
|
+
builtin2("clear", "Clear the conversation and start fresh"),
|
|
5685
|
+
builtin2("review", "Review recent changes (git diff)"),
|
|
5686
|
+
builtin2("explain", "Explain the selected code"),
|
|
5687
|
+
builtin2("fix", "Fix issues in the code"),
|
|
5688
|
+
builtin2("tests", "Add tests for the selected code")
|
|
5689
|
+
];
|
|
5690
|
+
function discoverUserSlashCommands() {
|
|
5691
|
+
let entries;
|
|
4071
5692
|
try {
|
|
4072
|
-
|
|
5693
|
+
entries = readdirSync(userCommandsDir(), { encoding: "utf8" });
|
|
4073
5694
|
} catch {
|
|
4074
|
-
return
|
|
5695
|
+
return [];
|
|
4075
5696
|
}
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
for (const
|
|
4079
|
-
|
|
4080
|
-
|
|
5697
|
+
const definitions = [];
|
|
5698
|
+
const seen = new Set(CLAUDE_CODE_COMMANDS.map((command) => command.name));
|
|
5699
|
+
for (const entry of entries.sort()) {
|
|
5700
|
+
if (!entry.endsWith(".md")) continue;
|
|
5701
|
+
const name2 = entry.slice(0, -".md".length);
|
|
5702
|
+
if (!COMMAND_NAME.test(name2) || seen.has(name2)) continue;
|
|
5703
|
+
const path = join8(userCommandsDir(), entry);
|
|
5704
|
+
let raw;
|
|
5705
|
+
try {
|
|
5706
|
+
raw = readFileSync2(path, "utf8");
|
|
5707
|
+
} catch {
|
|
5708
|
+
continue;
|
|
5709
|
+
}
|
|
5710
|
+
const description = commandDescription(raw);
|
|
5711
|
+
if (description === void 0) continue;
|
|
5712
|
+
seen.add(name2);
|
|
5713
|
+
definitions.push({ name: name2, description, handler: forwardClaudeCodeCommand(name2) });
|
|
4081
5714
|
}
|
|
4082
|
-
return
|
|
5715
|
+
return definitions;
|
|
4083
5716
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
return raw !== void 0 && raw.trim().length > 0;
|
|
5717
|
+
function userCommandsDir() {
|
|
5718
|
+
return join8(homedir4(), ".claude", "commands");
|
|
4087
5719
|
}
|
|
4088
|
-
|
|
4089
|
-
const
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
5720
|
+
function commandDescription(raw) {
|
|
5721
|
+
const trimmed = raw.trim();
|
|
5722
|
+
if (trimmed.length === 0) return void 0;
|
|
5723
|
+
let body = trimmed;
|
|
5724
|
+
if (trimmed.startsWith("---\n")) {
|
|
5725
|
+
const closing = trimmed.indexOf("\n---");
|
|
5726
|
+
if (closing <= 0) return void 0;
|
|
5727
|
+
for (const line of trimmed.slice(4, closing).split("\n")) {
|
|
5728
|
+
const colon = line.indexOf(":");
|
|
5729
|
+
if (colon < 0 || line.slice(0, colon).trim() !== "description") continue;
|
|
5730
|
+
const value = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
5731
|
+
if (value.length > 0) return value;
|
|
5732
|
+
}
|
|
5733
|
+
body = trimmed.slice(closing + 4);
|
|
4093
5734
|
}
|
|
4094
|
-
|
|
5735
|
+
for (const line of body.split("\n")) {
|
|
5736
|
+
const candidate = line.trim();
|
|
5737
|
+
if (candidate.length === 0 || candidate.startsWith("#")) continue;
|
|
5738
|
+
return candidate.length > 120 ? `${candidate.slice(0, 119)}\u2026` : candidate;
|
|
5739
|
+
}
|
|
5740
|
+
return void 0;
|
|
4095
5741
|
}
|
|
4096
5742
|
|
|
4097
5743
|
// src/engine-codex/skills.ts
|
|
4098
|
-
|
|
5744
|
+
import { homedir as homedir5 } from "node:os";
|
|
5745
|
+
import { join as join9 } from "node:path";
|
|
5746
|
+
var PROVIDER_NAME3 = "codex";
|
|
4099
5747
|
var CODEX_PROJECT_RANK = 140;
|
|
4100
5748
|
var CODEX_USER_RANK = 160;
|
|
4101
5749
|
var CODEX_CONTEXT_POLICY = { primary: ["AGENTS.md"] };
|
|
@@ -4104,7 +5752,7 @@ var CodexSkillProvider = class {
|
|
|
4104
5752
|
this.control = control;
|
|
4105
5753
|
}
|
|
4106
5754
|
control;
|
|
4107
|
-
name =
|
|
5755
|
+
name = PROVIDER_NAME3;
|
|
4108
5756
|
async list(options) {
|
|
4109
5757
|
const candidates = [];
|
|
4110
5758
|
const cwd = options.cwd;
|
|
@@ -4112,7 +5760,7 @@ var CodexSkillProvider = class {
|
|
|
4112
5760
|
const paths = await collectProjectContextFiles(cwd, CODEX_CONTEXT_POLICY);
|
|
4113
5761
|
if (await anySourceNonEmpty(paths)) candidates.push(this.agentsCandidate(paths, CODEX_PROJECT_RANK));
|
|
4114
5762
|
}
|
|
4115
|
-
const userPath =
|
|
5763
|
+
const userPath = join9(homedir5(), ".codex", "AGENTS.md");
|
|
4116
5764
|
if (await fileNonEmpty(userPath)) candidates.push(this.agentsCandidate([userPath], CODEX_USER_RANK));
|
|
4117
5765
|
if (this.control.signal.aborted) return [];
|
|
4118
5766
|
return candidates;
|
|
@@ -4151,10 +5799,10 @@ var CodexSkillProvider = class {
|
|
|
4151
5799
|
};
|
|
4152
5800
|
|
|
4153
5801
|
// src/engine-pi/skills.ts
|
|
4154
|
-
import { readdir as
|
|
4155
|
-
import { homedir as
|
|
4156
|
-
import { dirname as
|
|
4157
|
-
var
|
|
5802
|
+
import { readdir as readdir3, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
5803
|
+
import { homedir as homedir6 } from "node:os";
|
|
5804
|
+
import { dirname as dirname5, join as join10, resolve as resolve4 } from "node:path";
|
|
5805
|
+
var PROVIDER_NAME4 = "pi";
|
|
4158
5806
|
var PI_AGENTS_PROJECT_RANK = 140;
|
|
4159
5807
|
var PI_SKILL_PROJECT_RANK = 150;
|
|
4160
5808
|
var PI_AGENTS_USER_RANK = 160;
|
|
@@ -4165,15 +5813,15 @@ var PI_CONTEXT_POLICY = {
|
|
|
4165
5813
|
};
|
|
4166
5814
|
function piAgentDir() {
|
|
4167
5815
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
4168
|
-
if (override !== void 0 && override.length > 0) return
|
|
4169
|
-
return
|
|
5816
|
+
if (override !== void 0 && override.length > 0) return resolve4(override);
|
|
5817
|
+
return join10(homedir6(), ".pi", "agent");
|
|
4170
5818
|
}
|
|
4171
5819
|
var PiSkillProvider = class {
|
|
4172
5820
|
constructor(control) {
|
|
4173
5821
|
this.control = control;
|
|
4174
5822
|
}
|
|
4175
5823
|
control;
|
|
4176
|
-
name =
|
|
5824
|
+
name = PROVIDER_NAME4;
|
|
4177
5825
|
async list(options) {
|
|
4178
5826
|
const candidates = [];
|
|
4179
5827
|
const cwd = options.cwd;
|
|
@@ -4182,13 +5830,13 @@ var PiSkillProvider = class {
|
|
|
4182
5830
|
const contextPaths = await collectProjectContextFiles(cwd, PI_CONTEXT_POLICY);
|
|
4183
5831
|
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, PI_AGENTS_PROJECT_RANK));
|
|
4184
5832
|
for (const dir of projectDirs) {
|
|
4185
|
-
await this.collectSkillsDir(
|
|
5833
|
+
await this.collectSkillsDir(join10(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
|
|
4186
5834
|
}
|
|
4187
5835
|
}
|
|
4188
5836
|
const userAgentDir = piAgentDir();
|
|
4189
|
-
const userContext =
|
|
5837
|
+
const userContext = join10(userAgentDir, "AGENTS.md");
|
|
4190
5838
|
if (await fileNonEmpty(userContext)) candidates.push(this.agentsCandidate([userContext], PI_AGENTS_USER_RANK));
|
|
4191
|
-
await this.collectSkillsDir(
|
|
5839
|
+
await this.collectSkillsDir(join10(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
|
|
4192
5840
|
if (this.control.signal.aborted) return [];
|
|
4193
5841
|
return candidates;
|
|
4194
5842
|
}
|
|
@@ -4206,7 +5854,7 @@ var PiSkillProvider = class {
|
|
|
4206
5854
|
provider: this.name,
|
|
4207
5855
|
content: parsed.content,
|
|
4208
5856
|
path: locator.path,
|
|
4209
|
-
resourceBase: { kind: "directory", path:
|
|
5857
|
+
resourceBase: { kind: "directory", path: dirname5(locator.path) }
|
|
4210
5858
|
};
|
|
4211
5859
|
}
|
|
4212
5860
|
const content = await readSources(locator.paths);
|
|
@@ -4242,16 +5890,16 @@ var PiSkillProvider = class {
|
|
|
4242
5890
|
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
4243
5891
|
let entries;
|
|
4244
5892
|
try {
|
|
4245
|
-
entries = await
|
|
5893
|
+
entries = await readdir3(skillsDir, { withFileTypes: true, encoding: "utf8" });
|
|
4246
5894
|
} catch {
|
|
4247
5895
|
return;
|
|
4248
5896
|
}
|
|
4249
5897
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4250
|
-
const entryPath =
|
|
4251
|
-
const info = await
|
|
5898
|
+
const entryPath = join10(skillsDir, entry.name);
|
|
5899
|
+
const info = await stat4(entryPath).catch(() => void 0);
|
|
4252
5900
|
if (info === void 0) continue;
|
|
4253
5901
|
if (info.isDirectory()) {
|
|
4254
|
-
const path =
|
|
5902
|
+
const path = join10(entryPath, "SKILL.md");
|
|
4255
5903
|
const parsed2 = await this.tryParse(path);
|
|
4256
5904
|
if (parsed2 === void 0) continue;
|
|
4257
5905
|
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
@@ -4281,7 +5929,7 @@ var PiSkillProvider = class {
|
|
|
4281
5929
|
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
4282
5930
|
async tryParse(path) {
|
|
4283
5931
|
try {
|
|
4284
|
-
const raw = await
|
|
5932
|
+
const raw = await readFile5(path, { encoding: "utf8" });
|
|
4285
5933
|
return parseSkillFile(raw);
|
|
4286
5934
|
} catch {
|
|
4287
5935
|
return void 0;
|
|
@@ -4294,23 +5942,24 @@ var name = "loop-engine";
|
|
|
4294
5942
|
var inject = [];
|
|
4295
5943
|
var MAX_MOUNT_ATTEMPTS = 40;
|
|
4296
5944
|
var MOUNT_RETRY_MS = 50;
|
|
4297
|
-
var
|
|
4298
|
-
profile:
|
|
4299
|
-
patchFilename:
|
|
4300
|
-
patchPath:
|
|
4301
|
-
permissionMode:
|
|
4302
|
-
env:
|
|
4303
|
-
model:
|
|
4304
|
-
disposeGraceMs:
|
|
4305
|
-
maxTurns:
|
|
4306
|
-
sandboxMode:
|
|
4307
|
-
approvalPolicy:
|
|
4308
|
-
piProvider:
|
|
4309
|
-
piThinking:
|
|
5945
|
+
var Config5 = z6.object({
|
|
5946
|
+
profile: z6.string(),
|
|
5947
|
+
patchFilename: z6.string(),
|
|
5948
|
+
patchPath: z6.string(),
|
|
5949
|
+
permissionMode: z6.union(CLAUDE_CODE_PERMISSION_MODES.map((mode) => z6.const(mode))),
|
|
5950
|
+
env: z6.dict(z6.string()),
|
|
5951
|
+
model: z6.string(),
|
|
5952
|
+
disposeGraceMs: z6.number(),
|
|
5953
|
+
maxTurns: z6.number(),
|
|
5954
|
+
sandboxMode: z6.union(CODEX_SANDBOX_MODES.map((mode) => z6.const(mode))),
|
|
5955
|
+
approvalPolicy: z6.union(CODEX_APPROVAL_POLICIES.map((policy) => z6.const(policy))),
|
|
5956
|
+
piProvider: z6.string(),
|
|
5957
|
+
piThinking: z6.string(),
|
|
5958
|
+
kimiBin: z6.string()
|
|
4310
5959
|
});
|
|
4311
5960
|
function resolvePatchPath(config) {
|
|
4312
5961
|
if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
|
|
4313
|
-
return
|
|
5962
|
+
return join11(
|
|
4314
5963
|
resolveDshHome(),
|
|
4315
5964
|
"profiles",
|
|
4316
5965
|
config.profile ?? "web",
|
|
@@ -4322,21 +5971,21 @@ function isMissing(error) {
|
|
|
4322
5971
|
}
|
|
4323
5972
|
async function readPatchOrUndefined(path) {
|
|
4324
5973
|
try {
|
|
4325
|
-
return await
|
|
5974
|
+
return await readFile6(path, "utf8");
|
|
4326
5975
|
} catch (error) {
|
|
4327
5976
|
if (isMissing(error)) return void 0;
|
|
4328
5977
|
throw error;
|
|
4329
5978
|
}
|
|
4330
5979
|
}
|
|
4331
5980
|
async function writePatchFile(path, text) {
|
|
4332
|
-
await
|
|
4333
|
-
const tmp = `${path}.tmp-${
|
|
4334
|
-
await
|
|
4335
|
-
await
|
|
5981
|
+
await mkdir2(dirname6(path), { recursive: true });
|
|
5982
|
+
const tmp = `${path}.tmp-${randomUUID2()}`;
|
|
5983
|
+
await writeFile2(tmp, text, "utf8");
|
|
5984
|
+
await rename2(tmp, path);
|
|
4336
5985
|
}
|
|
4337
5986
|
function writePatchFileSync(path, text) {
|
|
4338
|
-
mkdirSync(
|
|
4339
|
-
const tmp = `${path}.tmp-${
|
|
5987
|
+
mkdirSync(dirname6(path), { recursive: true });
|
|
5988
|
+
const tmp = `${path}.tmp-${randomUUID2()}`;
|
|
4340
5989
|
writeFileSync(tmp, text, "utf8");
|
|
4341
5990
|
renameSync(tmp, path);
|
|
4342
5991
|
}
|
|
@@ -4381,9 +6030,19 @@ function piConfig(config) {
|
|
|
4381
6030
|
...config.sandboxMode === void 0 ? {} : { sandboxMode: config.sandboxMode }
|
|
4382
6031
|
};
|
|
4383
6032
|
}
|
|
6033
|
+
function kimiConfig(config) {
|
|
6034
|
+
return {
|
|
6035
|
+
...config.model === void 0 ? {} : { model: config.model },
|
|
6036
|
+
...config.env === void 0 ? {} : { env: config.env },
|
|
6037
|
+
...config.kimiBin === void 0 ? {} : { bin: config.kimiBin }
|
|
6038
|
+
};
|
|
6039
|
+
}
|
|
4384
6040
|
function apply(ctx, config) {
|
|
4385
6041
|
const patchPath = resolvePatchPath(config);
|
|
4386
6042
|
let fileEngine = currentEngineOf(readPatchFileSync(patchPath));
|
|
6043
|
+
const AGENT_PRESETS_NS = "agent-presets";
|
|
6044
|
+
const PRESET_DEFAULT_ATTEMPTS = 30;
|
|
6045
|
+
const PRESET_DEFAULT_RETRY_MS = 100;
|
|
4387
6046
|
let engineFiber;
|
|
4388
6047
|
let mountedEngine;
|
|
4389
6048
|
let commandDisposers;
|
|
@@ -4396,6 +6055,105 @@ function apply(ctx, config) {
|
|
|
4396
6055
|
mountRetry = void 0;
|
|
4397
6056
|
}
|
|
4398
6057
|
};
|
|
6058
|
+
let savedPresetDefault;
|
|
6059
|
+
let presetRetry;
|
|
6060
|
+
const CLEAR_PRESET_RETRY = () => {
|
|
6061
|
+
if (presetRetry !== void 0) {
|
|
6062
|
+
clearTimeout(presetRetry);
|
|
6063
|
+
presetRetry = void 0;
|
|
6064
|
+
}
|
|
6065
|
+
};
|
|
6066
|
+
const ROUTE_ATTEMPTS = 30;
|
|
6067
|
+
const ROUTE_RETRY_MS = 100;
|
|
6068
|
+
let routeHandle;
|
|
6069
|
+
let routeEngine;
|
|
6070
|
+
let routeRetry;
|
|
6071
|
+
const CLEAR_ROUTE_RETRY = () => {
|
|
6072
|
+
if (routeRetry !== void 0) {
|
|
6073
|
+
clearTimeout(routeRetry);
|
|
6074
|
+
routeRetry = void 0;
|
|
6075
|
+
}
|
|
6076
|
+
};
|
|
6077
|
+
const releaseRoute = () => {
|
|
6078
|
+
CLEAR_ROUTE_RETRY();
|
|
6079
|
+
const handle = routeHandle;
|
|
6080
|
+
routeHandle = void 0;
|
|
6081
|
+
routeEngine = void 0;
|
|
6082
|
+
handle?.();
|
|
6083
|
+
};
|
|
6084
|
+
const mountProviderRoute = (engine, attempt = 0) => {
|
|
6085
|
+
if (engine === "in-process") return;
|
|
6086
|
+
if (routeEngine === engine && routeHandle !== void 0) return;
|
|
6087
|
+
CLEAR_ROUTE_RETRY();
|
|
6088
|
+
const label = HOSTED_PROVIDER_ROUTES[engine];
|
|
6089
|
+
const llm = ctx.get("llm");
|
|
6090
|
+
if (llm === void 0) {
|
|
6091
|
+
if (attempt < ROUTE_ATTEMPTS) {
|
|
6092
|
+
routeRetry = setTimeout(() => {
|
|
6093
|
+
mountProviderRoute(engine, attempt + 1);
|
|
6094
|
+
}, ROUTE_RETRY_MS);
|
|
6095
|
+
}
|
|
6096
|
+
return;
|
|
6097
|
+
}
|
|
6098
|
+
try {
|
|
6099
|
+
routeHandle = llm.registerAdapter([label], new HostedEngineRouteAdapter(label));
|
|
6100
|
+
routeEngine = engine;
|
|
6101
|
+
} catch (error) {
|
|
6102
|
+
if (error instanceof Error && error.message.includes("already registered")) {
|
|
6103
|
+
ctx.logger.warn(`loop-engine: provider route "${label}" is already served by another adapter`);
|
|
6104
|
+
return;
|
|
6105
|
+
}
|
|
6106
|
+
ctx.logger.error(`loop-engine: provider route "${label}" registration failed: ${String(error)}`);
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
const mutatePresetDefault = (op, attempt = 0) => {
|
|
6110
|
+
const settings = ctx.get("settings");
|
|
6111
|
+
if (settings === void 0) return;
|
|
6112
|
+
settings.mutate(AGENT_PRESETS_NS, [op]).then(() => void 0, (error) => {
|
|
6113
|
+
if (error instanceof Error && error.message.includes("not registered") && attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6114
|
+
presetRetry = setTimeout(() => {
|
|
6115
|
+
mutatePresetDefault(op, attempt + 1);
|
|
6116
|
+
}, PRESET_DEFAULT_RETRY_MS);
|
|
6117
|
+
return;
|
|
6118
|
+
}
|
|
6119
|
+
ctx.logger.error(`loop-engine: preset default switch failed: ${String(error)}`);
|
|
6120
|
+
});
|
|
6121
|
+
};
|
|
6122
|
+
const restorePresetDefault = (attempt = 0) => {
|
|
6123
|
+
const presets = ctx.get("agentPresets");
|
|
6124
|
+
if (presets === void 0) return;
|
|
6125
|
+
if (presets.defaultId === HOSTED_PRESET_ID) {
|
|
6126
|
+
const saved = savedPresetDefault;
|
|
6127
|
+
savedPresetDefault = void 0;
|
|
6128
|
+
mutatePresetDefault(saved === void 0 ? { op: "unset", path: ["default"] } : { op: "set", path: ["default"], value: saved });
|
|
6129
|
+
return;
|
|
6130
|
+
}
|
|
6131
|
+
if (attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6132
|
+
presetRetry = setTimeout(() => {
|
|
6133
|
+
restorePresetDefault(attempt + 1);
|
|
6134
|
+
}, PRESET_DEFAULT_RETRY_MS);
|
|
6135
|
+
}
|
|
6136
|
+
};
|
|
6137
|
+
const steerPresetDefault = (engine) => {
|
|
6138
|
+
const presets = ctx.get("agentPresets");
|
|
6139
|
+
if (presets === void 0) return;
|
|
6140
|
+
if (engine === "in-process") {
|
|
6141
|
+
restorePresetDefault();
|
|
6142
|
+
return;
|
|
6143
|
+
}
|
|
6144
|
+
void (async () => {
|
|
6145
|
+
try {
|
|
6146
|
+
await ensureHostedPreset(resolveDshHome(), presets);
|
|
6147
|
+
} catch (error) {
|
|
6148
|
+
ctx.logger.error(`loop-engine: hosted preset authoring failed: ${String(error)}`);
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
const current = presets.defaultId;
|
|
6152
|
+
if (current === HOSTED_PRESET_ID) return;
|
|
6153
|
+
savedPresetDefault = current;
|
|
6154
|
+
mutatePresetDefault({ op: "set", path: ["default"], value: HOSTED_PRESET_ID });
|
|
6155
|
+
})();
|
|
6156
|
+
};
|
|
4399
6157
|
const cleanupEngineRegistrations = () => {
|
|
4400
6158
|
if (commandDisposers !== void 0) {
|
|
4401
6159
|
for (const dispose of commandDisposers) dispose();
|
|
@@ -4459,15 +6217,37 @@ function apply(ctx, config) {
|
|
|
4459
6217
|
}
|
|
4460
6218
|
hostFactory("pi", () => ctx.plugin(PiLoop, piConfig(config)));
|
|
4461
6219
|
};
|
|
6220
|
+
const mountKimi = () => {
|
|
6221
|
+
const commands = ctx.get("commands");
|
|
6222
|
+
if (commands !== void 0) {
|
|
6223
|
+
const disposers = [];
|
|
6224
|
+
for (const command of KIMI_COMMANDS) {
|
|
6225
|
+
try {
|
|
6226
|
+
disposers.push(commands.register(command));
|
|
6227
|
+
} catch (error) {
|
|
6228
|
+
ctx.logger.warn(`loop-engine: skip kimi command /${command.name}: ${String(error)}`);
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
commandDisposers = disposers;
|
|
6232
|
+
}
|
|
6233
|
+
const skills = ctx.get("skills");
|
|
6234
|
+
if (skills !== void 0) {
|
|
6235
|
+
skillDisposer = skills.registerProvider((control) => new KimiSkillProvider(control));
|
|
6236
|
+
}
|
|
6237
|
+
hostFactory("kimi", () => ctx.plugin(KimiLoop, kimiConfig(config)));
|
|
6238
|
+
};
|
|
4462
6239
|
const mountEngine = (engine) => {
|
|
6240
|
+
mountProviderRoute(engine);
|
|
4463
6241
|
if (engine === "claude-code") mountClaude();
|
|
4464
6242
|
else if (engine === "codex") mountCodex();
|
|
4465
6243
|
else if (engine === "pi") mountPi();
|
|
6244
|
+
else if (engine === "kimi") mountKimi();
|
|
4466
6245
|
};
|
|
4467
6246
|
const unmountEngine = () => {
|
|
4468
6247
|
const fiber = engineFiber;
|
|
4469
6248
|
mountAttempts = 0;
|
|
4470
6249
|
CLEAR_RETRY();
|
|
6250
|
+
releaseRoute();
|
|
4471
6251
|
cleanupEngineRegistrations();
|
|
4472
6252
|
mountedEngine = void 0;
|
|
4473
6253
|
if (fiber === void 0) return;
|
|
@@ -4477,31 +6257,40 @@ function apply(ctx, config) {
|
|
|
4477
6257
|
}, () => void 0);
|
|
4478
6258
|
};
|
|
4479
6259
|
mountEngine(fileEngine);
|
|
4480
|
-
|
|
6260
|
+
steerPresetDefault(fileEngine);
|
|
6261
|
+
ctx.effect(() => () => {
|
|
6262
|
+
CLEAR_RETRY();
|
|
6263
|
+
CLEAR_PRESET_RETRY();
|
|
6264
|
+
releaseRoute();
|
|
6265
|
+
}, "loop-engine: retry cleanup");
|
|
4481
6266
|
let source;
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
6267
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
6268
|
+
settingsCtx.settings.installSection(ctx, loopEngineSettingsNamespace(), LOOP_ENGINE_SETTINGS_SCHEMA, { engine: fileEngine, showInComposer: true }, {
|
|
6269
|
+
setSource: (current) => {
|
|
6270
|
+
source = current;
|
|
6271
|
+
},
|
|
6272
|
+
onChange: () => {
|
|
6273
|
+
const next = source().engine;
|
|
6274
|
+
if (next === fileEngine) return;
|
|
6275
|
+
if (mountedEngine !== next) {
|
|
6276
|
+
unmountEngine();
|
|
6277
|
+
mountEngine(next);
|
|
6278
|
+
}
|
|
6279
|
+
try {
|
|
6280
|
+
const updated = applyManagedBlock(readPatchFileSync(patchPath), next);
|
|
6281
|
+
writePatchFileSync(patchPath, updated);
|
|
6282
|
+
fileEngine = next;
|
|
6283
|
+
} catch (error) {
|
|
6284
|
+
ctx.logger.error(`loop-engine: managed block write failed: ${String(error)}`);
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
steerPresetDefault(next);
|
|
4499
6288
|
}
|
|
4500
|
-
}
|
|
6289
|
+
});
|
|
4501
6290
|
});
|
|
4502
6291
|
}
|
|
4503
6292
|
export {
|
|
4504
|
-
|
|
6293
|
+
Config5 as Config,
|
|
4505
6294
|
apply,
|
|
4506
6295
|
inject,
|
|
4507
6296
|
name,
|