dsh-loop-engine 1.0.0-rc7 → 1.0.0-rc9
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 +39 -0
- package/lib/client.js +6 -2
- package/lib/index.js +953 -269
- package/lib/invariant.js +2 -1
- 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 +2 -0
- package/lib/types/client/store.d.ts +2 -1
- package/lib/types/driver-core/ownership.d.ts +1 -2
- 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/agent.d.ts +2 -0
- package/lib/types/engine-kimi/commands.d.ts +7 -0
- package/lib/types/engine-kimi/loop.d.ts +24 -2
- package/lib/types/engine-pi/agent.d.ts +10 -0
- package/lib/types/engine-pi/loop.d.ts +29 -2
- package/lib/types/engine-pi/probe.d.ts +23 -0
- package/lib/types/index.d.ts +16 -0
- package/lib/types/patch-manager.d.ts +9 -0
- package/lib/types/preset.d.ts +73 -0
- package/lib/types/provider-route.d.ts +49 -0
- package/lib/types/settings.d.ts +5 -5
- package/package.json +23 -23
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
|
|
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
52
|
import z6 from "@deepseek-ai/schemastery";
|
|
53
|
-
import { installSettingsSection } from "@deepseek-ai/dsh-settings";
|
|
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");
|
|
@@ -1020,11 +1019,15 @@ var ClaudeCodeAgent = class {
|
|
|
1020
1019
|
};
|
|
1021
1020
|
|
|
1022
1021
|
// src/driver-core/ownership.ts
|
|
1023
|
-
|
|
1022
|
+
var INACTIVE_FIBER_VALUES = {
|
|
1023
|
+
FAILED: 3,
|
|
1024
|
+
DISPOSED: 4,
|
|
1025
|
+
UNLOADING: 5
|
|
1026
|
+
};
|
|
1024
1027
|
var INACTIVE_STATES = /* @__PURE__ */ new Set([
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
+
INACTIVE_FIBER_VALUES.UNLOADING,
|
|
1029
|
+
INACTIVE_FIBER_VALUES.DISPOSED,
|
|
1030
|
+
INACTIVE_FIBER_VALUES.FAILED
|
|
1028
1031
|
]);
|
|
1029
1032
|
var FactoryOwnership = class {
|
|
1030
1033
|
constructor(fiber) {
|
|
@@ -1162,7 +1165,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1162
1165
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
1163
1166
|
*/
|
|
1164
1167
|
/* 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) {
|
|
1168
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
1166
1169
|
ownerCtx.fiber.assertActive();
|
|
1167
1170
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
1168
1171
|
if (callerSignal?.aborted) {
|
|
@@ -1196,11 +1199,15 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1196
1199
|
}
|
|
1197
1200
|
} finally {
|
|
1198
1201
|
try {
|
|
1199
|
-
|
|
1200
|
-
detachSession?.();
|
|
1202
|
+
await handle?.close();
|
|
1201
1203
|
} finally {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
+
try {
|
|
1205
|
+
detachAgent?.();
|
|
1206
|
+
detachSession?.();
|
|
1207
|
+
} finally {
|
|
1208
|
+
untrack();
|
|
1209
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
1210
|
+
}
|
|
1204
1211
|
}
|
|
1205
1212
|
}
|
|
1206
1213
|
})();
|
|
@@ -1250,18 +1257,27 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1250
1257
|
}
|
|
1251
1258
|
}
|
|
1252
1259
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
1253
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
1260
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
1254
1261
|
var _stack = [];
|
|
1255
1262
|
try {
|
|
1256
1263
|
const ownedPreparation = __using(_stack, preparation);
|
|
1257
1264
|
const session = ownedPreparation.session;
|
|
1258
|
-
|
|
1265
|
+
let prepared;
|
|
1266
|
+
try {
|
|
1267
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
await stored?.handle.close().catch(() => {
|
|
1270
|
+
});
|
|
1271
|
+
throw error;
|
|
1272
|
+
}
|
|
1259
1273
|
try {
|
|
1260
1274
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
1261
1275
|
setupCommit?.commit();
|
|
1276
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
1262
1277
|
return prepared.publish(source);
|
|
1263
1278
|
} catch (error) {
|
|
1264
|
-
await prepared.dispose()
|
|
1279
|
+
await prepared.dispose().catch(() => {
|
|
1280
|
+
});
|
|
1265
1281
|
throw error;
|
|
1266
1282
|
}
|
|
1267
1283
|
} catch (_) {
|
|
@@ -1272,7 +1288,8 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1272
1288
|
}
|
|
1273
1289
|
/**
|
|
1274
1290
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
1275
|
-
* the accessing fiber.
|
|
1291
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
1292
|
+
* durable identity is stored before publication.
|
|
1276
1293
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
1277
1294
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
1278
1295
|
* @returns the published handle.
|
|
@@ -1280,20 +1297,73 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1280
1297
|
async createAgent(ownerCtx, options) {
|
|
1281
1298
|
const preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
1282
1299
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
1283
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
1300
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
1301
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
1284
1302
|
}));
|
|
1285
|
-
const published =
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1303
|
+
const published = (async () => {
|
|
1304
|
+
let stored;
|
|
1305
|
+
try {
|
|
1306
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
1307
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
1308
|
+
options.signal,
|
|
1309
|
+
options.sessionId,
|
|
1310
|
+
(abandoned) => {
|
|
1311
|
+
void abandoned?.handle.close().catch(() => {
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
);
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
preparation[Symbol.dispose]();
|
|
1317
|
+
throw error;
|
|
1318
|
+
}
|
|
1319
|
+
return this.setupAndPublish(
|
|
1320
|
+
ownerCtx,
|
|
1321
|
+
options.sessionId,
|
|
1322
|
+
preparation,
|
|
1323
|
+
options.agentOptions ?? {},
|
|
1324
|
+
options.setup,
|
|
1325
|
+
options.signal,
|
|
1326
|
+
"startup",
|
|
1327
|
+
stored
|
|
1328
|
+
);
|
|
1329
|
+
})();
|
|
1294
1330
|
this.ownership.trackWrapper(published);
|
|
1295
1331
|
return published;
|
|
1296
1332
|
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
1335
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
1336
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
1337
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
1338
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
1339
|
+
* created again.
|
|
1340
|
+
* @param session - the unpublished session to store.
|
|
1341
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
1342
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
1343
|
+
*/
|
|
1344
|
+
async createStoredSession(session, signal) {
|
|
1345
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
1346
|
+
if (persistence === void 0) return void 0;
|
|
1347
|
+
const handle = await persistence.create(session.header, {
|
|
1348
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
1349
|
+
...signal === void 0 ? {} : { signal }
|
|
1350
|
+
});
|
|
1351
|
+
return { handle, storedCount: 0 };
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Durably store the session events appended since the last stored cursor.
|
|
1355
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
1356
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
1357
|
+
* through the handle before live events start routing into it.
|
|
1358
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
1359
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
1360
|
+
*/
|
|
1361
|
+
async appendUnstoredSuffix(stored, session) {
|
|
1362
|
+
if (stored === void 0) return;
|
|
1363
|
+
const suffix = session.snapshotEvents(SessionLogOffset(stored.storedCount));
|
|
1364
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
1365
|
+
stored.storedCount += suffix.length;
|
|
1366
|
+
}
|
|
1297
1367
|
/**
|
|
1298
1368
|
* Resume an owned agent from the configured persistence service.
|
|
1299
1369
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -1307,11 +1377,10 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1307
1377
|
}
|
|
1308
1378
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
1309
1379
|
}
|
|
1310
|
-
/** Resume through an explicit persistence
|
|
1311
|
-
|
|
1380
|
+
/** Resume through an explicit persistence service. */
|
|
1381
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
1312
1382
|
const id = options.resumeSessionId;
|
|
1313
|
-
|
|
1314
|
-
try {
|
|
1383
|
+
const published = (async () => {
|
|
1315
1384
|
const ownerAbort = new AbortController();
|
|
1316
1385
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
1317
1386
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -1321,32 +1390,56 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1321
1390
|
ownerAbort.signal,
|
|
1322
1391
|
this.ownership.signal
|
|
1323
1392
|
]);
|
|
1393
|
+
let handle;
|
|
1394
|
+
let stored;
|
|
1395
|
+
let preparation;
|
|
1324
1396
|
try {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1397
|
+
try {
|
|
1398
|
+
handle = await raceAbortCall(
|
|
1399
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
1400
|
+
fused,
|
|
1401
|
+
id,
|
|
1402
|
+
(abandoned) => {
|
|
1403
|
+
void abandoned.close();
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
1407
|
+
fused.throwIfAborted();
|
|
1408
|
+
const closers = interruptedTurnClosers(persisted);
|
|
1409
|
+
if (closers.length > 0) await handle.append(closers);
|
|
1410
|
+
preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(id, {
|
|
1411
|
+
seed: [...persisted, ...closers],
|
|
1412
|
+
meta: structuredClone(handle.header),
|
|
1413
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
1414
|
+
seedSource: "persistence"
|
|
1415
|
+
}));
|
|
1416
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
1417
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
1418
|
+
} finally {
|
|
1419
|
+
await unfollowOwner();
|
|
1420
|
+
}
|
|
1421
|
+
ownerCtx.fiber.assertActive();
|
|
1422
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
1423
|
+
const owned = stored;
|
|
1424
|
+
handle = void 0;
|
|
1425
|
+
return await this.setupAndPublish(
|
|
1426
|
+
ownerCtx,
|
|
1328
1427
|
id,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1428
|
+
preparation,
|
|
1429
|
+
options.agentOptions ?? {},
|
|
1430
|
+
options.setup,
|
|
1431
|
+
options.signal,
|
|
1432
|
+
"resume",
|
|
1433
|
+
owned
|
|
1332
1434
|
);
|
|
1333
1435
|
} finally {
|
|
1334
|
-
|
|
1436
|
+
preparation?.[Symbol.dispose]();
|
|
1437
|
+
await handle?.close().catch(() => {
|
|
1438
|
+
});
|
|
1335
1439
|
}
|
|
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
|
-
}
|
|
1440
|
+
})();
|
|
1441
|
+
this.ownership.trackWrapper(published);
|
|
1442
|
+
return published;
|
|
1350
1443
|
}
|
|
1351
1444
|
};
|
|
1352
1445
|
|
|
@@ -1354,7 +1447,7 @@ var ClaudeCodeLoop = class extends Service {
|
|
|
1354
1447
|
import { Service as Service2 } from "@deepseek-ai/cordis";
|
|
1355
1448
|
import z2 from "@deepseek-ai/schemastery";
|
|
1356
1449
|
import { emitAgentEvent as emitAgentEvent2 } from "@deepseek-ai/dsh-agent";
|
|
1357
|
-
import { SessionPreparation as SessionPreparation2 } from "@deepseek-ai/dsh-session";
|
|
1450
|
+
import { interruptedTurnClosers as interruptedTurnClosers2, SessionLogOffset as SessionLogOffset2, SessionPreparation as SessionPreparation2 } from "@deepseek-ai/dsh-session";
|
|
1358
1451
|
|
|
1359
1452
|
// src/engine-codex/agent.ts
|
|
1360
1453
|
import { Inbox as Inbox2, agentEvents as agentEvents2 } from "@deepseek-ai/dsh-agent";
|
|
@@ -1441,7 +1534,7 @@ var AppServerClient = class _AppServerClient {
|
|
|
1441
1534
|
clientInfo: {
|
|
1442
1535
|
name: "dsh-loop-engine",
|
|
1443
1536
|
title: null,
|
|
1444
|
-
version: "0.
|
|
1537
|
+
version: "1.0.0-rc9"
|
|
1445
1538
|
},
|
|
1446
1539
|
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
1447
1540
|
};
|
|
@@ -1656,7 +1749,7 @@ function noopNotificationHandler() {
|
|
|
1656
1749
|
}
|
|
1657
1750
|
|
|
1658
1751
|
// src/engine-codex/appserver/mapping.ts
|
|
1659
|
-
import {
|
|
1752
|
+
import { ToolCallId as ToolCallId2, createToolResultMessage as createToolResultMessage2 } from "@deepseek-ai/dsh-llm";
|
|
1660
1753
|
function mapUsage2(usage) {
|
|
1661
1754
|
return {
|
|
1662
1755
|
inputTokens: usage.inputTokens,
|
|
@@ -1668,12 +1761,12 @@ function mapUsage2(usage) {
|
|
|
1668
1761
|
function mapCommandExecution(item) {
|
|
1669
1762
|
return {
|
|
1670
1763
|
call: {
|
|
1671
|
-
callId:
|
|
1764
|
+
callId: ToolCallId2(item.id),
|
|
1672
1765
|
name: "command_execution",
|
|
1673
1766
|
arguments: JSON.stringify({ command: item.command ?? "" })
|
|
1674
1767
|
},
|
|
1675
1768
|
result: createToolResultMessage2({
|
|
1676
|
-
callId:
|
|
1769
|
+
callId: ToolCallId2(item.id),
|
|
1677
1770
|
content: [{ type: "text", text: item.aggregatedOutput ?? "" }],
|
|
1678
1771
|
isError: (item.exitCode ?? 0) !== 0 || item.status === "failed"
|
|
1679
1772
|
})
|
|
@@ -1682,12 +1775,12 @@ function mapCommandExecution(item) {
|
|
|
1682
1775
|
function mapFileChange(item) {
|
|
1683
1776
|
return {
|
|
1684
1777
|
call: {
|
|
1685
|
-
callId:
|
|
1778
|
+
callId: ToolCallId2(item.id),
|
|
1686
1779
|
name: "apply_patch",
|
|
1687
1780
|
arguments: JSON.stringify(item.changes ?? [])
|
|
1688
1781
|
},
|
|
1689
1782
|
result: createToolResultMessage2({
|
|
1690
|
-
callId:
|
|
1783
|
+
callId: ToolCallId2(item.id),
|
|
1691
1784
|
content: [{ type: "text", text: `patch ${item.status ?? "completed"}` }],
|
|
1692
1785
|
isError: item.status === "failed"
|
|
1693
1786
|
})
|
|
@@ -1698,12 +1791,12 @@ function mapMcpToolCall(item) {
|
|
|
1698
1791
|
const isError = item.error !== void 0 && item.error !== null;
|
|
1699
1792
|
return {
|
|
1700
1793
|
call: {
|
|
1701
|
-
callId:
|
|
1794
|
+
callId: ToolCallId2(item.id),
|
|
1702
1795
|
name: name2,
|
|
1703
1796
|
arguments: JSON.stringify(item.arguments ?? {})
|
|
1704
1797
|
},
|
|
1705
1798
|
result: createToolResultMessage2({
|
|
1706
|
-
callId:
|
|
1799
|
+
callId: ToolCallId2(item.id),
|
|
1707
1800
|
content: isError ? [{ type: "text", text: item.error?.message ?? "tool call failed" }] : [{ type: "text", text: JSON.stringify(item.result?.content ?? []) }],
|
|
1708
1801
|
isError
|
|
1709
1802
|
})
|
|
@@ -1732,7 +1825,7 @@ var CodexAgent = class {
|
|
|
1732
1825
|
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
1733
1826
|
}
|
|
1734
1827
|
});
|
|
1735
|
-
const lastTurn = session.
|
|
1828
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
1736
1829
|
this.phase = { kind: "idle", lastTurn };
|
|
1737
1830
|
this.scope = createScope2(loopCtx, this);
|
|
1738
1831
|
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
@@ -1945,7 +2038,7 @@ var CodexAgent = class {
|
|
|
1945
2038
|
* @returns the permission fields of the query spec.
|
|
1946
2039
|
*/
|
|
1947
2040
|
queryPermission() {
|
|
1948
|
-
const fold = resolveSessionPermission2(this.session.
|
|
2041
|
+
const fold = resolveSessionPermission2(this.session.snapshotEvents());
|
|
1949
2042
|
return {
|
|
1950
2043
|
sandboxMode: this.config.sandboxMode ?? fold.sandboxMode,
|
|
1951
2044
|
approvalPolicy: this.config.approvalPolicy ?? fold.approvalPolicy
|
|
@@ -2298,7 +2391,7 @@ var CodexLoop = class extends Service2 {
|
|
|
2298
2391
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
2299
2392
|
*/
|
|
2300
2393
|
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
2301
|
-
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
2394
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
2302
2395
|
ownerCtx.fiber.assertActive();
|
|
2303
2396
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
2304
2397
|
if (callerSignal?.aborted) {
|
|
@@ -2332,11 +2425,15 @@ var CodexLoop = class extends Service2 {
|
|
|
2332
2425
|
}
|
|
2333
2426
|
} finally {
|
|
2334
2427
|
try {
|
|
2335
|
-
|
|
2336
|
-
detachSession?.();
|
|
2428
|
+
await handle?.close();
|
|
2337
2429
|
} finally {
|
|
2338
|
-
|
|
2339
|
-
|
|
2430
|
+
try {
|
|
2431
|
+
detachAgent?.();
|
|
2432
|
+
detachSession?.();
|
|
2433
|
+
} finally {
|
|
2434
|
+
untrack();
|
|
2435
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
2436
|
+
}
|
|
2340
2437
|
}
|
|
2341
2438
|
}
|
|
2342
2439
|
})();
|
|
@@ -2386,18 +2483,27 @@ var CodexLoop = class extends Service2 {
|
|
|
2386
2483
|
}
|
|
2387
2484
|
}
|
|
2388
2485
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
2389
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
2486
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
2390
2487
|
var _stack = [];
|
|
2391
2488
|
try {
|
|
2392
2489
|
const ownedPreparation = __using(_stack, preparation);
|
|
2393
2490
|
const session = ownedPreparation.session;
|
|
2394
|
-
|
|
2491
|
+
let prepared;
|
|
2492
|
+
try {
|
|
2493
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
2494
|
+
} catch (error) {
|
|
2495
|
+
await stored?.handle.close().catch(() => {
|
|
2496
|
+
});
|
|
2497
|
+
throw error;
|
|
2498
|
+
}
|
|
2395
2499
|
try {
|
|
2396
2500
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
2397
2501
|
setupCommit?.commit();
|
|
2502
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
2398
2503
|
return prepared.publish(source);
|
|
2399
2504
|
} catch (error) {
|
|
2400
|
-
await prepared.dispose()
|
|
2505
|
+
await prepared.dispose().catch(() => {
|
|
2506
|
+
});
|
|
2401
2507
|
throw error;
|
|
2402
2508
|
}
|
|
2403
2509
|
} catch (_) {
|
|
@@ -2408,7 +2514,8 @@ var CodexLoop = class extends Service2 {
|
|
|
2408
2514
|
}
|
|
2409
2515
|
/**
|
|
2410
2516
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
2411
|
-
* the accessing fiber.
|
|
2517
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
2518
|
+
* durable identity is stored before publication.
|
|
2412
2519
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
2413
2520
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
2414
2521
|
* @returns the published handle.
|
|
@@ -2416,20 +2523,73 @@ var CodexLoop = class extends Service2 {
|
|
|
2416
2523
|
async createAgent(ownerCtx, options) {
|
|
2417
2524
|
const preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
2418
2525
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
2419
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
2526
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
2527
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
2420
2528
|
}));
|
|
2421
|
-
const published =
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2529
|
+
const published = (async () => {
|
|
2530
|
+
let stored;
|
|
2531
|
+
try {
|
|
2532
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
2533
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
2534
|
+
options.signal,
|
|
2535
|
+
options.sessionId,
|
|
2536
|
+
(abandoned) => {
|
|
2537
|
+
void abandoned?.handle.close().catch(() => {
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
);
|
|
2541
|
+
} catch (error) {
|
|
2542
|
+
preparation[Symbol.dispose]();
|
|
2543
|
+
throw error;
|
|
2544
|
+
}
|
|
2545
|
+
return this.setupAndPublish(
|
|
2546
|
+
ownerCtx,
|
|
2547
|
+
options.sessionId,
|
|
2548
|
+
preparation,
|
|
2549
|
+
options.agentOptions ?? {},
|
|
2550
|
+
options.setup,
|
|
2551
|
+
options.signal,
|
|
2552
|
+
"startup",
|
|
2553
|
+
stored
|
|
2554
|
+
);
|
|
2555
|
+
})();
|
|
2430
2556
|
this.ownership.trackWrapper(published);
|
|
2431
2557
|
return published;
|
|
2432
2558
|
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
2561
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
2562
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
2563
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
2564
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
2565
|
+
* created again.
|
|
2566
|
+
* @param session - the unpublished session to store.
|
|
2567
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
2568
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
2569
|
+
*/
|
|
2570
|
+
async createStoredSession(session, signal) {
|
|
2571
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
2572
|
+
if (persistence === void 0) return void 0;
|
|
2573
|
+
const handle = await persistence.create(session.header, {
|
|
2574
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
2575
|
+
...signal === void 0 ? {} : { signal }
|
|
2576
|
+
});
|
|
2577
|
+
return { handle, storedCount: 0 };
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* Durably store the session events appended since the last stored cursor.
|
|
2581
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
2582
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
2583
|
+
* through the handle before live events start routing into it.
|
|
2584
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
2585
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
2586
|
+
*/
|
|
2587
|
+
async appendUnstoredSuffix(stored, session) {
|
|
2588
|
+
if (stored === void 0) return;
|
|
2589
|
+
const suffix = session.snapshotEvents(SessionLogOffset2(stored.storedCount));
|
|
2590
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
2591
|
+
stored.storedCount += suffix.length;
|
|
2592
|
+
}
|
|
2433
2593
|
/**
|
|
2434
2594
|
* Resume an owned agent from the configured persistence service.
|
|
2435
2595
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -2443,11 +2603,10 @@ var CodexLoop = class extends Service2 {
|
|
|
2443
2603
|
}
|
|
2444
2604
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
2445
2605
|
}
|
|
2446
|
-
/** Resume through an explicit persistence
|
|
2447
|
-
|
|
2606
|
+
/** Resume through an explicit persistence service. */
|
|
2607
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
2448
2608
|
const id = options.resumeSessionId;
|
|
2449
|
-
|
|
2450
|
-
try {
|
|
2609
|
+
const published = (async () => {
|
|
2451
2610
|
const ownerAbort = new AbortController();
|
|
2452
2611
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
2453
2612
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -2457,32 +2616,56 @@ var CodexLoop = class extends Service2 {
|
|
|
2457
2616
|
ownerAbort.signal,
|
|
2458
2617
|
this.ownership.signal
|
|
2459
2618
|
]);
|
|
2619
|
+
let handle;
|
|
2620
|
+
let stored;
|
|
2621
|
+
let preparation;
|
|
2460
2622
|
try {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2623
|
+
try {
|
|
2624
|
+
handle = await raceAbortCall(
|
|
2625
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
2626
|
+
fused,
|
|
2627
|
+
id,
|
|
2628
|
+
(abandoned) => {
|
|
2629
|
+
void abandoned.close();
|
|
2630
|
+
}
|
|
2631
|
+
);
|
|
2632
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
2633
|
+
fused.throwIfAborted();
|
|
2634
|
+
const closers = interruptedTurnClosers2(persisted);
|
|
2635
|
+
if (closers.length > 0) await handle.append(closers);
|
|
2636
|
+
preparation = SessionPreparation2.create(this.runtime.ctx.sessions.prepare(id, {
|
|
2637
|
+
seed: [...persisted, ...closers],
|
|
2638
|
+
meta: structuredClone(handle.header),
|
|
2639
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
2640
|
+
seedSource: "persistence"
|
|
2641
|
+
}));
|
|
2642
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
2643
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
2644
|
+
} finally {
|
|
2645
|
+
await unfollowOwner();
|
|
2646
|
+
}
|
|
2647
|
+
ownerCtx.fiber.assertActive();
|
|
2648
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
2649
|
+
const owned = stored;
|
|
2650
|
+
handle = void 0;
|
|
2651
|
+
return await this.setupAndPublish(
|
|
2652
|
+
ownerCtx,
|
|
2464
2653
|
id,
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2654
|
+
preparation,
|
|
2655
|
+
options.agentOptions ?? {},
|
|
2656
|
+
options.setup,
|
|
2657
|
+
options.signal,
|
|
2658
|
+
"resume",
|
|
2659
|
+
owned
|
|
2468
2660
|
);
|
|
2469
2661
|
} finally {
|
|
2470
|
-
|
|
2662
|
+
preparation?.[Symbol.dispose]();
|
|
2663
|
+
await handle?.close().catch(() => {
|
|
2664
|
+
});
|
|
2471
2665
|
}
|
|
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
|
-
}
|
|
2666
|
+
})();
|
|
2667
|
+
this.ownership.trackWrapper(published);
|
|
2668
|
+
return published;
|
|
2486
2669
|
}
|
|
2487
2670
|
};
|
|
2488
2671
|
|
|
@@ -2493,11 +2676,11 @@ import { fileURLToPath } from "node:url";
|
|
|
2493
2676
|
import { Service as Service3 } from "@deepseek-ai/cordis";
|
|
2494
2677
|
import z3 from "@deepseek-ai/schemastery";
|
|
2495
2678
|
import { emitAgentEvent as emitAgentEvent3 } from "@deepseek-ai/dsh-agent";
|
|
2496
|
-
import { SessionPreparation as SessionPreparation3 } from "@deepseek-ai/dsh-session";
|
|
2679
|
+
import { interruptedTurnClosers as interruptedTurnClosers3, SessionLogOffset as SessionLogOffset3, SessionPreparation as SessionPreparation3 } from "@deepseek-ai/dsh-session";
|
|
2497
2680
|
|
|
2498
2681
|
// src/engine-pi/agent.ts
|
|
2499
2682
|
import { Inbox as Inbox3, agentEvents as agentEvents3 } from "@deepseek-ai/dsh-agent";
|
|
2500
|
-
import {
|
|
2683
|
+
import { ToolCallId as ToolCallId4, LlmError as LlmError3, createAssistantMessage as createAssistantMessage3, createUserMessage as createUserMessage3, errorChain as errorChain3 } from "@deepseek-ai/dsh-llm";
|
|
2501
2684
|
import { createScope as createScope3 } from "@deepseek-ai/dsh-scope";
|
|
2502
2685
|
import { canonicalHeader as canonicalHeader3 } from "@deepseek-ai/dsh-session";
|
|
2503
2686
|
|
|
@@ -2717,7 +2900,7 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2717
2900
|
|
|
2718
2901
|
// src/engine-pi/rpc/mapping.ts
|
|
2719
2902
|
import {
|
|
2720
|
-
|
|
2903
|
+
ToolCallId as ToolCallId3,
|
|
2721
2904
|
createToolResultMessage as createToolResultMessage3
|
|
2722
2905
|
} from "@deepseek-ai/dsh-llm";
|
|
2723
2906
|
function mapUsage3(usage) {
|
|
@@ -2748,7 +2931,7 @@ function resultText(content) {
|
|
|
2748
2931
|
}
|
|
2749
2932
|
function mapToolResult(ev) {
|
|
2750
2933
|
return createToolResultMessage3({
|
|
2751
|
-
callId:
|
|
2934
|
+
callId: ToolCallId3(ev.toolCallId),
|
|
2752
2935
|
content: [{ type: "text", text: resultText(ev.result) || "(no content)" }],
|
|
2753
2936
|
isError: ev.isError
|
|
2754
2937
|
});
|
|
@@ -2783,7 +2966,7 @@ var PiAgent = class {
|
|
|
2783
2966
|
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
2784
2967
|
}
|
|
2785
2968
|
});
|
|
2786
|
-
const lastTurn = session.
|
|
2969
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
2787
2970
|
this.phase = { kind: "idle", lastTurn };
|
|
2788
2971
|
this.scope = createScope3(loopCtx, this);
|
|
2789
2972
|
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
@@ -3004,7 +3187,7 @@ var PiAgent = class {
|
|
|
3004
3187
|
* @returns the permission fields of the query spec.
|
|
3005
3188
|
*/
|
|
3006
3189
|
queryPermission() {
|
|
3007
|
-
const fold = resolveSessionPermission3(this.session.
|
|
3190
|
+
const fold = resolveSessionPermission3(this.session.snapshotEvents());
|
|
3008
3191
|
const sandboxMode = this.config.sandboxMode ?? fold.sandboxMode;
|
|
3009
3192
|
return {
|
|
3010
3193
|
sandboxMode,
|
|
@@ -3102,14 +3285,32 @@ var PiAgent = class {
|
|
|
3102
3285
|
});
|
|
3103
3286
|
this.requestHeaderLogged = true;
|
|
3104
3287
|
}
|
|
3288
|
+
/**
|
|
3289
|
+
* The harness Session's web-side model selection, if any was stored. The
|
|
3290
|
+
* durable `model/selection` event carries `{ provider, model, ... }`; when a
|
|
3291
|
+
* user picked a model via `/model`, this is the newest pick, and it overrides
|
|
3292
|
+
* the deployment config (which stays the fallback). Returns `undefined` when
|
|
3293
|
+
* no selection was stored, so the deployment config governs.
|
|
3294
|
+
*/
|
|
3295
|
+
dynamicModel() {
|
|
3296
|
+
for (const event of [...this.session.snapshotEvents()].reverse()) {
|
|
3297
|
+
const type = event.type;
|
|
3298
|
+
if (type !== "model/selection") continue;
|
|
3299
|
+
const data = event.data;
|
|
3300
|
+
const model = data?.model;
|
|
3301
|
+
if (typeof model === "string" && model.length > 0) return model;
|
|
3302
|
+
}
|
|
3303
|
+
return void 0;
|
|
3304
|
+
}
|
|
3105
3305
|
/** Build the `pi --mode rpc` argv/cwd/env for one step's child process. */
|
|
3106
3306
|
spawnSpec(cwd) {
|
|
3107
3307
|
const argv = [];
|
|
3308
|
+
const model = this.dynamicModel() ?? this.config.model;
|
|
3108
3309
|
if (this.config.provider !== void 0) argv.push("--provider", this.config.provider);
|
|
3109
|
-
if (
|
|
3110
|
-
argv.push("--model", `${
|
|
3111
|
-
} else if (
|
|
3112
|
-
argv.push("--model",
|
|
3310
|
+
if (model !== void 0 && this.config.thinkingLevel !== void 0) {
|
|
3311
|
+
argv.push("--model", `${model}:${this.config.thinkingLevel}`);
|
|
3312
|
+
} else if (model !== void 0) {
|
|
3313
|
+
argv.push("--model", model);
|
|
3113
3314
|
} else if (this.config.thinkingLevel !== void 0) {
|
|
3114
3315
|
argv.push("--model", `:${this.config.thinkingLevel}`);
|
|
3115
3316
|
}
|
|
@@ -3215,7 +3416,7 @@ var PiAgent = class {
|
|
|
3215
3416
|
this.session.append("tool/call", {
|
|
3216
3417
|
turn,
|
|
3217
3418
|
step,
|
|
3218
|
-
callId:
|
|
3419
|
+
callId: ToolCallId4(callId),
|
|
3219
3420
|
name: name2,
|
|
3220
3421
|
arguments: argumentsValue
|
|
3221
3422
|
});
|
|
@@ -3364,6 +3565,67 @@ var PiAgent = class {
|
|
|
3364
3565
|
}
|
|
3365
3566
|
};
|
|
3366
3567
|
|
|
3568
|
+
// src/engine-pi/probe.ts
|
|
3569
|
+
function waitForExit(child) {
|
|
3570
|
+
return new Promise((resolve5) => {
|
|
3571
|
+
;
|
|
3572
|
+
child.onExit(
|
|
3573
|
+
(code) => resolve5(code ?? 0)
|
|
3574
|
+
);
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
function collectStdout(child) {
|
|
3578
|
+
return new Promise((resolve5) => {
|
|
3579
|
+
let text = "";
|
|
3580
|
+
if (child.stdout.on === void 0) {
|
|
3581
|
+
resolve5("");
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
const out = child.stdout;
|
|
3585
|
+
out.setEncoding("utf8");
|
|
3586
|
+
out.on("data", (data) => {
|
|
3587
|
+
text += String(data);
|
|
3588
|
+
});
|
|
3589
|
+
out.on("end", () => {
|
|
3590
|
+
resolve5(text);
|
|
3591
|
+
});
|
|
3592
|
+
out.on("close", () => {
|
|
3593
|
+
resolve5(text);
|
|
3594
|
+
});
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
function parsePiModelList(output) {
|
|
3598
|
+
const entries = [];
|
|
3599
|
+
for (const line of output.split("\n")) {
|
|
3600
|
+
const trimmed = line.trim();
|
|
3601
|
+
if (trimmed.length === 0) continue;
|
|
3602
|
+
const match = /^(\S+)\s{2,}(\S+)/.exec(trimmed);
|
|
3603
|
+
if (match === null) continue;
|
|
3604
|
+
const provider = match[1];
|
|
3605
|
+
if (provider === "provider") continue;
|
|
3606
|
+
entries.push({ provider, model: match[2] });
|
|
3607
|
+
}
|
|
3608
|
+
return entries;
|
|
3609
|
+
}
|
|
3610
|
+
async function probePiModels(bin, spawn4) {
|
|
3611
|
+
const spec = {
|
|
3612
|
+
argv: [bin, "--mode", "rpc", "--list-models"],
|
|
3613
|
+
cwd: process.cwd(),
|
|
3614
|
+
env: {}
|
|
3615
|
+
};
|
|
3616
|
+
let child;
|
|
3617
|
+
try {
|
|
3618
|
+
child = spawn4(spec);
|
|
3619
|
+
} catch {
|
|
3620
|
+
return [];
|
|
3621
|
+
}
|
|
3622
|
+
const stdoutPromise = collectStdout(child);
|
|
3623
|
+
const exitCode = await waitForExit(child);
|
|
3624
|
+
if (exitCode !== 0) return [];
|
|
3625
|
+
const stdout = await stdoutPromise;
|
|
3626
|
+
return parsePiModelList(stdout);
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3367
3629
|
// src/engine-pi/loop.ts
|
|
3368
3630
|
var PI_SANDBOX_MODES = [
|
|
3369
3631
|
"read-only",
|
|
@@ -3376,7 +3638,8 @@ var Config3 = z3.object({
|
|
|
3376
3638
|
provider: z3.string(),
|
|
3377
3639
|
model: z3.string(),
|
|
3378
3640
|
thinkingLevel: z3.string(),
|
|
3379
|
-
env: z3.dict(z3.string()).default({})
|
|
3641
|
+
env: z3.dict(z3.string()).default({}),
|
|
3642
|
+
piCatalogHolder: z3.any()
|
|
3380
3643
|
});
|
|
3381
3644
|
function resolveConfig3(config) {
|
|
3382
3645
|
return {
|
|
@@ -3415,7 +3678,8 @@ function fromSubprocess(handle) {
|
|
|
3415
3678
|
stdout,
|
|
3416
3679
|
stderr,
|
|
3417
3680
|
onExit: (handler) => {
|
|
3418
|
-
|
|
3681
|
+
const onExit = handler;
|
|
3682
|
+
void handle.done.then((outcome) => onExit(outcome.exitCode), handler);
|
|
3419
3683
|
},
|
|
3420
3684
|
terminate: () => handle.terminate()
|
|
3421
3685
|
};
|
|
@@ -3439,6 +3703,14 @@ var PiLoop = class extends Service3 {
|
|
|
3439
3703
|
this.runtime = { ctx };
|
|
3440
3704
|
this.bin = piCliEntrypoint();
|
|
3441
3705
|
this.spawn = (spec) => fromSubprocess(this.runtime.ctx.subprocess.spawn(piSubprocessSpec(spec, PI_DISPOSE_GRACE_MS)));
|
|
3706
|
+
const holder = config.piCatalogHolder;
|
|
3707
|
+
if (holder !== void 0) {
|
|
3708
|
+
void probePiModels(this.bin, (spec) => this.spawn(spec)).then((models) => {
|
|
3709
|
+
holder.entries = [...models];
|
|
3710
|
+
}).catch(() => {
|
|
3711
|
+
holder.entries = [];
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3442
3714
|
ctx.effect(() => () => this.ownership.dispose(), "agentLoopPi.transactions()");
|
|
3443
3715
|
ctx.effect(() => ctx.agents.setFactory(this), "agentLoopPi.setFactory()");
|
|
3444
3716
|
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
@@ -3452,7 +3724,7 @@ var PiLoop = class extends Service3 {
|
|
|
3452
3724
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
3453
3725
|
*/
|
|
3454
3726
|
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
3455
|
-
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
3727
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
3456
3728
|
ownerCtx.fiber.assertActive();
|
|
3457
3729
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
3458
3730
|
if (callerSignal?.aborted) {
|
|
@@ -3486,11 +3758,15 @@ var PiLoop = class extends Service3 {
|
|
|
3486
3758
|
}
|
|
3487
3759
|
} finally {
|
|
3488
3760
|
try {
|
|
3489
|
-
|
|
3490
|
-
detachSession?.();
|
|
3761
|
+
await handle?.close();
|
|
3491
3762
|
} finally {
|
|
3492
|
-
|
|
3493
|
-
|
|
3763
|
+
try {
|
|
3764
|
+
detachAgent?.();
|
|
3765
|
+
detachSession?.();
|
|
3766
|
+
} finally {
|
|
3767
|
+
untrack();
|
|
3768
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
3769
|
+
}
|
|
3494
3770
|
}
|
|
3495
3771
|
}
|
|
3496
3772
|
})();
|
|
@@ -3540,18 +3816,27 @@ var PiLoop = class extends Service3 {
|
|
|
3540
3816
|
}
|
|
3541
3817
|
}
|
|
3542
3818
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
3543
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
3819
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
3544
3820
|
var _stack = [];
|
|
3545
3821
|
try {
|
|
3546
3822
|
const ownedPreparation = __using(_stack, preparation);
|
|
3547
3823
|
const session = ownedPreparation.session;
|
|
3548
|
-
|
|
3824
|
+
let prepared;
|
|
3825
|
+
try {
|
|
3826
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
3827
|
+
} catch (error) {
|
|
3828
|
+
await stored?.handle.close().catch(() => {
|
|
3829
|
+
});
|
|
3830
|
+
throw error;
|
|
3831
|
+
}
|
|
3549
3832
|
try {
|
|
3550
3833
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
3551
3834
|
setupCommit?.commit();
|
|
3835
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
3552
3836
|
return prepared.publish(source);
|
|
3553
3837
|
} catch (error) {
|
|
3554
|
-
await prepared.dispose()
|
|
3838
|
+
await prepared.dispose().catch(() => {
|
|
3839
|
+
});
|
|
3555
3840
|
throw error;
|
|
3556
3841
|
}
|
|
3557
3842
|
} catch (_) {
|
|
@@ -3562,7 +3847,8 @@ var PiLoop = class extends Service3 {
|
|
|
3562
3847
|
}
|
|
3563
3848
|
/**
|
|
3564
3849
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
3565
|
-
* the accessing fiber.
|
|
3850
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
3851
|
+
* durable identity is stored before publication.
|
|
3566
3852
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
3567
3853
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
3568
3854
|
* @returns the published handle.
|
|
@@ -3570,20 +3856,73 @@ var PiLoop = class extends Service3 {
|
|
|
3570
3856
|
async createAgent(ownerCtx, options) {
|
|
3571
3857
|
const preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
3572
3858
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
3573
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
3859
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
3860
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
3574
3861
|
}));
|
|
3575
|
-
const published =
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3862
|
+
const published = (async () => {
|
|
3863
|
+
let stored;
|
|
3864
|
+
try {
|
|
3865
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
3866
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
3867
|
+
options.signal,
|
|
3868
|
+
options.sessionId,
|
|
3869
|
+
(abandoned) => {
|
|
3870
|
+
void abandoned?.handle.close().catch(() => {
|
|
3871
|
+
});
|
|
3872
|
+
}
|
|
3873
|
+
);
|
|
3874
|
+
} catch (error) {
|
|
3875
|
+
preparation[Symbol.dispose]();
|
|
3876
|
+
throw error;
|
|
3877
|
+
}
|
|
3878
|
+
return this.setupAndPublish(
|
|
3879
|
+
ownerCtx,
|
|
3880
|
+
options.sessionId,
|
|
3881
|
+
preparation,
|
|
3882
|
+
options.agentOptions ?? {},
|
|
3883
|
+
options.setup,
|
|
3884
|
+
options.signal,
|
|
3885
|
+
"startup",
|
|
3886
|
+
stored
|
|
3887
|
+
);
|
|
3888
|
+
})();
|
|
3584
3889
|
this.ownership.trackWrapper(published);
|
|
3585
3890
|
return published;
|
|
3586
3891
|
}
|
|
3892
|
+
/**
|
|
3893
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
3894
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
3895
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
3896
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
3897
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
3898
|
+
* created again.
|
|
3899
|
+
* @param session - the unpublished session to store.
|
|
3900
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
3901
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
3902
|
+
*/
|
|
3903
|
+
async createStoredSession(session, signal) {
|
|
3904
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
3905
|
+
if (persistence === void 0) return void 0;
|
|
3906
|
+
const handle = await persistence.create(session.header, {
|
|
3907
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
3908
|
+
...signal === void 0 ? {} : { signal }
|
|
3909
|
+
});
|
|
3910
|
+
return { handle, storedCount: 0 };
|
|
3911
|
+
}
|
|
3912
|
+
/**
|
|
3913
|
+
* Durably store the session events appended since the last stored cursor.
|
|
3914
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
3915
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
3916
|
+
* through the handle before live events start routing into it.
|
|
3917
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
3918
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
3919
|
+
*/
|
|
3920
|
+
async appendUnstoredSuffix(stored, session) {
|
|
3921
|
+
if (stored === void 0) return;
|
|
3922
|
+
const suffix = session.snapshotEvents(SessionLogOffset3(stored.storedCount));
|
|
3923
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
3924
|
+
stored.storedCount += suffix.length;
|
|
3925
|
+
}
|
|
3587
3926
|
/**
|
|
3588
3927
|
* Resume an owned agent from the configured persistence service.
|
|
3589
3928
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -3597,11 +3936,10 @@ var PiLoop = class extends Service3 {
|
|
|
3597
3936
|
}
|
|
3598
3937
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
3599
3938
|
}
|
|
3600
|
-
/** Resume through an explicit persistence
|
|
3601
|
-
|
|
3939
|
+
/** Resume through an explicit persistence service. */
|
|
3940
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
3602
3941
|
const id = options.resumeSessionId;
|
|
3603
|
-
|
|
3604
|
-
try {
|
|
3942
|
+
const published = (async () => {
|
|
3605
3943
|
const ownerAbort = new AbortController();
|
|
3606
3944
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
3607
3945
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -3611,32 +3949,56 @@ var PiLoop = class extends Service3 {
|
|
|
3611
3949
|
ownerAbort.signal,
|
|
3612
3950
|
this.ownership.signal
|
|
3613
3951
|
]);
|
|
3952
|
+
let handle;
|
|
3953
|
+
let stored;
|
|
3954
|
+
let preparation;
|
|
3614
3955
|
try {
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3956
|
+
try {
|
|
3957
|
+
handle = await raceAbortCall(
|
|
3958
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
3959
|
+
fused,
|
|
3960
|
+
id,
|
|
3961
|
+
(abandoned) => {
|
|
3962
|
+
void abandoned.close();
|
|
3963
|
+
}
|
|
3964
|
+
);
|
|
3965
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
3966
|
+
fused.throwIfAborted();
|
|
3967
|
+
const closers = interruptedTurnClosers3(persisted);
|
|
3968
|
+
if (closers.length > 0) await handle.append(closers);
|
|
3969
|
+
preparation = SessionPreparation3.create(this.runtime.ctx.sessions.prepare(id, {
|
|
3970
|
+
seed: [...persisted, ...closers],
|
|
3971
|
+
meta: structuredClone(handle.header),
|
|
3972
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
3973
|
+
seedSource: "persistence"
|
|
3974
|
+
}));
|
|
3975
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
3976
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
3977
|
+
} finally {
|
|
3978
|
+
await unfollowOwner();
|
|
3979
|
+
}
|
|
3980
|
+
ownerCtx.fiber.assertActive();
|
|
3981
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
3982
|
+
const owned = stored;
|
|
3983
|
+
handle = void 0;
|
|
3984
|
+
return await this.setupAndPublish(
|
|
3985
|
+
ownerCtx,
|
|
3618
3986
|
id,
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3987
|
+
preparation,
|
|
3988
|
+
options.agentOptions ?? {},
|
|
3989
|
+
options.setup,
|
|
3990
|
+
options.signal,
|
|
3991
|
+
"resume",
|
|
3992
|
+
owned
|
|
3622
3993
|
);
|
|
3623
3994
|
} finally {
|
|
3624
|
-
|
|
3995
|
+
preparation?.[Symbol.dispose]();
|
|
3996
|
+
await handle?.close().catch(() => {
|
|
3997
|
+
});
|
|
3625
3998
|
}
|
|
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
|
-
}
|
|
3999
|
+
})();
|
|
4000
|
+
this.ownership.trackWrapper(published);
|
|
4001
|
+
return published;
|
|
3640
4002
|
}
|
|
3641
4003
|
};
|
|
3642
4004
|
|
|
@@ -3644,11 +4006,11 @@ var PiLoop = class extends Service3 {
|
|
|
3644
4006
|
import { Service as Service4 } from "@deepseek-ai/cordis";
|
|
3645
4007
|
import z4 from "@deepseek-ai/schemastery";
|
|
3646
4008
|
import { emitAgentEvent as emitAgentEvent4 } from "@deepseek-ai/dsh-agent";
|
|
3647
|
-
import { SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
|
|
4009
|
+
import { interruptedTurnClosers as interruptedTurnClosers4, SessionLogOffset as SessionLogOffset4, SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
|
|
3648
4010
|
|
|
3649
4011
|
// src/engine-kimi/agent.ts
|
|
3650
4012
|
import { Inbox as Inbox4, agentEvents as agentEvents4 } from "@deepseek-ai/dsh-agent";
|
|
3651
|
-
import {
|
|
4013
|
+
import { ToolCallId as ToolCallId6, LlmError as LlmError4, createAssistantMessage as createAssistantMessage4, createUserMessage as createUserMessage4, errorChain as errorChain4 } from "@deepseek-ai/dsh-llm";
|
|
3652
4014
|
import { createScope as createScope4 } from "@deepseek-ai/dsh-scope";
|
|
3653
4015
|
import { canonicalHeader as canonicalHeader4 } from "@deepseek-ai/dsh-session";
|
|
3654
4016
|
|
|
@@ -3903,7 +4265,7 @@ var AcpClient = class _AcpClient {
|
|
|
3903
4265
|
};
|
|
3904
4266
|
|
|
3905
4267
|
// src/engine-kimi/acp/mapping.ts
|
|
3906
|
-
import {
|
|
4268
|
+
import { ToolCallId as ToolCallId5, createToolResultMessage as createToolResultMessage4 } from "@deepseek-ai/dsh-llm";
|
|
3907
4269
|
function isTextChunk(update) {
|
|
3908
4270
|
return update.sessionUpdate === "agent_message_chunk";
|
|
3909
4271
|
}
|
|
@@ -3940,7 +4302,7 @@ function toolContentText(update) {
|
|
|
3940
4302
|
}
|
|
3941
4303
|
function toolResult(callId, text, isError) {
|
|
3942
4304
|
return createToolResultMessage4({
|
|
3943
|
-
callId:
|
|
4305
|
+
callId: ToolCallId5(callId),
|
|
3944
4306
|
content: [{ type: "text", text: text.length > 0 ? text : "(no content)" }],
|
|
3945
4307
|
isError
|
|
3946
4308
|
});
|
|
@@ -3975,7 +4337,7 @@ var KimiAgent = class {
|
|
|
3975
4337
|
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
3976
4338
|
}
|
|
3977
4339
|
});
|
|
3978
|
-
const lastTurn = session.
|
|
4340
|
+
const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
3979
4341
|
this.phase = { kind: "idle", lastTurn };
|
|
3980
4342
|
this.scope = createScope4(loopCtx, this);
|
|
3981
4343
|
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
@@ -4321,7 +4683,7 @@ var KimiAgent = class {
|
|
|
4321
4683
|
signal.throwIfAborted();
|
|
4322
4684
|
const client = await this.acpClient(cwd);
|
|
4323
4685
|
signal.throwIfAborted();
|
|
4324
|
-
client.onPermission(() => resolveToolApproval(this.session.
|
|
4686
|
+
client.onPermission(() => resolveToolApproval(this.session.snapshotEvents()));
|
|
4325
4687
|
const acpSessionId = await client.newSession(cwd);
|
|
4326
4688
|
signal.throwIfAborted();
|
|
4327
4689
|
client.onUpdate((update) => this.applyUpdate(turn, step, update));
|
|
@@ -4385,7 +4747,7 @@ var KimiAgent = class {
|
|
|
4385
4747
|
if (callId === "" || this.emittedToolCalls.has(callId)) return;
|
|
4386
4748
|
this.emittedToolCalls.add(callId);
|
|
4387
4749
|
const name2 = toolCallName(update);
|
|
4388
|
-
this.session.append("tool/call", { turn, step, callId:
|
|
4750
|
+
this.session.append("tool/call", { turn, step, callId: ToolCallId6(callId), name: name2, arguments: "{}" });
|
|
4389
4751
|
this.toolText.set(callId, "");
|
|
4390
4752
|
return;
|
|
4391
4753
|
}
|
|
@@ -4476,7 +4838,7 @@ var KimiLoop = class extends Service4 {
|
|
|
4476
4838
|
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
4477
4839
|
*/
|
|
4478
4840
|
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
4479
|
-
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
4841
|
+
prepare(ownerCtx, id, options, session, callerSignal, handle) {
|
|
4480
4842
|
ownerCtx.fiber.assertActive();
|
|
4481
4843
|
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
4482
4844
|
if (callerSignal?.aborted) {
|
|
@@ -4510,11 +4872,15 @@ var KimiLoop = class extends Service4 {
|
|
|
4510
4872
|
}
|
|
4511
4873
|
} finally {
|
|
4512
4874
|
try {
|
|
4513
|
-
|
|
4514
|
-
detachSession?.();
|
|
4875
|
+
await handle?.close();
|
|
4515
4876
|
} finally {
|
|
4516
|
-
|
|
4517
|
-
|
|
4877
|
+
try {
|
|
4878
|
+
detachAgent?.();
|
|
4879
|
+
detachSession?.();
|
|
4880
|
+
} finally {
|
|
4881
|
+
untrack();
|
|
4882
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
4883
|
+
}
|
|
4518
4884
|
}
|
|
4519
4885
|
}
|
|
4520
4886
|
})();
|
|
@@ -4564,18 +4930,27 @@ var KimiLoop = class extends Service4 {
|
|
|
4564
4930
|
}
|
|
4565
4931
|
}
|
|
4566
4932
|
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
4567
|
-
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
4933
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
|
|
4568
4934
|
var _stack = [];
|
|
4569
4935
|
try {
|
|
4570
4936
|
const ownedPreparation = __using(_stack, preparation);
|
|
4571
4937
|
const session = ownedPreparation.session;
|
|
4572
|
-
|
|
4938
|
+
let prepared;
|
|
4939
|
+
try {
|
|
4940
|
+
prepared = this.prepare(ownerCtx, id, agentOptions, session, signal, stored?.handle);
|
|
4941
|
+
} catch (error) {
|
|
4942
|
+
await stored?.handle.close().catch(() => {
|
|
4943
|
+
});
|
|
4944
|
+
throw error;
|
|
4945
|
+
}
|
|
4573
4946
|
try {
|
|
4574
4947
|
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
4575
4948
|
setupCommit?.commit();
|
|
4949
|
+
await this.appendUnstoredSuffix(stored, session);
|
|
4576
4950
|
return prepared.publish(source);
|
|
4577
4951
|
} catch (error) {
|
|
4578
|
-
await prepared.dispose()
|
|
4952
|
+
await prepared.dispose().catch(() => {
|
|
4953
|
+
});
|
|
4579
4954
|
throw error;
|
|
4580
4955
|
}
|
|
4581
4956
|
} catch (_) {
|
|
@@ -4586,7 +4961,8 @@ var KimiLoop = class extends Service4 {
|
|
|
4586
4961
|
}
|
|
4587
4962
|
/**
|
|
4588
4963
|
* Create an agent and session under one caller-supplied identity, owned by
|
|
4589
|
-
* the accessing fiber.
|
|
4964
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
4965
|
+
* durable identity is stored before publication.
|
|
4590
4966
|
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
4591
4967
|
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
4592
4968
|
* @returns the published handle.
|
|
@@ -4594,20 +4970,73 @@ var KimiLoop = class extends Service4 {
|
|
|
4594
4970
|
async createAgent(ownerCtx, options) {
|
|
4595
4971
|
const preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
4596
4972
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
4597
|
-
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
4973
|
+
...options.meta === void 0 ? {} : { meta: options.meta },
|
|
4974
|
+
...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
|
|
4598
4975
|
}));
|
|
4599
|
-
const published =
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4976
|
+
const published = (async () => {
|
|
4977
|
+
let stored;
|
|
4978
|
+
try {
|
|
4979
|
+
stored = options.signal === void 0 ? await this.createStoredSession(preparation.session) : await raceAbortCall(
|
|
4980
|
+
() => this.createStoredSession(preparation.session, options.signal),
|
|
4981
|
+
options.signal,
|
|
4982
|
+
options.sessionId,
|
|
4983
|
+
(abandoned) => {
|
|
4984
|
+
void abandoned?.handle.close().catch(() => {
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
);
|
|
4988
|
+
} catch (error) {
|
|
4989
|
+
preparation[Symbol.dispose]();
|
|
4990
|
+
throw error;
|
|
4991
|
+
}
|
|
4992
|
+
return this.setupAndPublish(
|
|
4993
|
+
ownerCtx,
|
|
4994
|
+
options.sessionId,
|
|
4995
|
+
preparation,
|
|
4996
|
+
options.agentOptions ?? {},
|
|
4997
|
+
options.setup,
|
|
4998
|
+
options.signal,
|
|
4999
|
+
"startup",
|
|
5000
|
+
stored
|
|
5001
|
+
);
|
|
5002
|
+
})();
|
|
4608
5003
|
this.ownership.trackWrapper(published);
|
|
4609
5004
|
return published;
|
|
4610
5005
|
}
|
|
5006
|
+
/**
|
|
5007
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
5008
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
5009
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
5010
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
5011
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
5012
|
+
* created again.
|
|
5013
|
+
* @param session - the unpublished session to store.
|
|
5014
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
5015
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
5016
|
+
*/
|
|
5017
|
+
async createStoredSession(session, signal) {
|
|
5018
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
5019
|
+
if (persistence === void 0) return void 0;
|
|
5020
|
+
const handle = await persistence.create(session.header, {
|
|
5021
|
+
inheritedEventCount: session.inheritedEventCount,
|
|
5022
|
+
...signal === void 0 ? {} : { signal }
|
|
5023
|
+
});
|
|
5024
|
+
return { handle, storedCount: 0 };
|
|
5025
|
+
}
|
|
5026
|
+
/**
|
|
5027
|
+
* Durably store the session events appended since the last stored cursor.
|
|
5028
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
5029
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
5030
|
+
* through the handle before live events start routing into it.
|
|
5031
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
5032
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
5033
|
+
*/
|
|
5034
|
+
async appendUnstoredSuffix(stored, session) {
|
|
5035
|
+
if (stored === void 0) return;
|
|
5036
|
+
const suffix = session.snapshotEvents(SessionLogOffset4(stored.storedCount));
|
|
5037
|
+
if (suffix.length > 0) await stored.handle.append(suffix);
|
|
5038
|
+
stored.storedCount += suffix.length;
|
|
5039
|
+
}
|
|
4611
5040
|
/**
|
|
4612
5041
|
* Resume an owned agent from the configured persistence service.
|
|
4613
5042
|
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
@@ -4621,11 +5050,10 @@ var KimiLoop = class extends Service4 {
|
|
|
4621
5050
|
}
|
|
4622
5051
|
return this.resumeWith(ownerCtx, persistence, options);
|
|
4623
5052
|
}
|
|
4624
|
-
/** Resume through an explicit persistence
|
|
4625
|
-
|
|
5053
|
+
/** Resume through an explicit persistence service. */
|
|
5054
|
+
resumeWith(ownerCtx, persistence, options) {
|
|
4626
5055
|
const id = options.resumeSessionId;
|
|
4627
|
-
|
|
4628
|
-
try {
|
|
5056
|
+
const published = (async () => {
|
|
4629
5057
|
const ownerAbort = new AbortController();
|
|
4630
5058
|
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
4631
5059
|
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
@@ -4635,32 +5063,56 @@ var KimiLoop = class extends Service4 {
|
|
|
4635
5063
|
ownerAbort.signal,
|
|
4636
5064
|
this.ownership.signal
|
|
4637
5065
|
]);
|
|
5066
|
+
let handle;
|
|
5067
|
+
let stored;
|
|
5068
|
+
let preparation;
|
|
4638
5069
|
try {
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
5070
|
+
try {
|
|
5071
|
+
handle = await raceAbortCall(
|
|
5072
|
+
() => persistence.open(id, "write", { signal: fused }),
|
|
5073
|
+
fused,
|
|
5074
|
+
id,
|
|
5075
|
+
(abandoned) => {
|
|
5076
|
+
void abandoned.close();
|
|
5077
|
+
}
|
|
5078
|
+
);
|
|
5079
|
+
const persisted = await handle.read(0, void 0, { signal: fused });
|
|
5080
|
+
fused.throwIfAborted();
|
|
5081
|
+
const closers = interruptedTurnClosers4(persisted);
|
|
5082
|
+
if (closers.length > 0) await handle.append(closers);
|
|
5083
|
+
preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(id, {
|
|
5084
|
+
seed: [...persisted, ...closers],
|
|
5085
|
+
meta: structuredClone(handle.header),
|
|
5086
|
+
inheritedEventCount: handle.inheritedEventCount,
|
|
5087
|
+
seedSource: "persistence"
|
|
5088
|
+
}));
|
|
5089
|
+
stored = { handle, storedCount: persisted.length + closers.length };
|
|
5090
|
+
await this.appendUnstoredSuffix(stored, preparation.session);
|
|
5091
|
+
} finally {
|
|
5092
|
+
await unfollowOwner();
|
|
5093
|
+
}
|
|
5094
|
+
ownerCtx.fiber.assertActive();
|
|
5095
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
5096
|
+
const owned = stored;
|
|
5097
|
+
handle = void 0;
|
|
5098
|
+
return await this.setupAndPublish(
|
|
5099
|
+
ownerCtx,
|
|
4642
5100
|
id,
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
5101
|
+
preparation,
|
|
5102
|
+
options.agentOptions ?? {},
|
|
5103
|
+
options.setup,
|
|
5104
|
+
options.signal,
|
|
5105
|
+
"resume",
|
|
5106
|
+
owned
|
|
4646
5107
|
);
|
|
4647
5108
|
} finally {
|
|
4648
|
-
|
|
5109
|
+
preparation?.[Symbol.dispose]();
|
|
5110
|
+
await handle?.close().catch(() => {
|
|
5111
|
+
});
|
|
4649
5112
|
}
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
ownerCtx,
|
|
4654
|
-
id,
|
|
4655
|
-
preparation,
|
|
4656
|
-
options.agentOptions ?? {},
|
|
4657
|
-
options.setup,
|
|
4658
|
-
options.signal,
|
|
4659
|
-
"resume"
|
|
4660
|
-
);
|
|
4661
|
-
} finally {
|
|
4662
|
-
preparation?.[Symbol.dispose]();
|
|
4663
|
-
}
|
|
5113
|
+
})();
|
|
5114
|
+
this.ownership.trackWrapper(published);
|
|
5115
|
+
return published;
|
|
4664
5116
|
}
|
|
4665
5117
|
};
|
|
4666
5118
|
|
|
@@ -5102,7 +5554,6 @@ var KIMI_COMMANDS = [
|
|
|
5102
5554
|
builtin("status", "Show the current session runtime state"),
|
|
5103
5555
|
builtin("compact", "Compact the conversation context to free token usage"),
|
|
5104
5556
|
builtin("clear", "Start a fresh session, discarding the current context"),
|
|
5105
|
-
builtin("model", "Switch the LLM model used in the current session"),
|
|
5106
5557
|
builtin("plan", "Toggle plan (read-only exploration) mode"),
|
|
5107
5558
|
builtin("auto", "Toggle auto permission mode"),
|
|
5108
5559
|
builtin("usage", "Show token usage, context, and quota information"),
|
|
@@ -5112,7 +5563,6 @@ var KIMI_COMMANDS = [
|
|
|
5112
5563
|
|
|
5113
5564
|
// src/settings.ts
|
|
5114
5565
|
import z5 from "@deepseek-ai/schemastery";
|
|
5115
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
5116
5566
|
|
|
5117
5567
|
// src/namespace.ts
|
|
5118
5568
|
var LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
|
|
@@ -5124,7 +5574,7 @@ var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
|
|
|
5124
5574
|
showInComposer: z5.boolean().default(true)
|
|
5125
5575
|
});
|
|
5126
5576
|
function loopEngineSettingsNamespace() {
|
|
5127
|
-
return
|
|
5577
|
+
return LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL;
|
|
5128
5578
|
}
|
|
5129
5579
|
|
|
5130
5580
|
// src/patch-manager.ts
|
|
@@ -5138,6 +5588,8 @@ function renderManagedBlock(engine) {
|
|
|
5138
5588
|
`${MANAGED_BLOCK_BEGIN}${engine} --`,
|
|
5139
5589
|
"- id: agent-loop",
|
|
5140
5590
|
" disabled: true",
|
|
5591
|
+
"- id: command-goal",
|
|
5592
|
+
" disabled: true",
|
|
5141
5593
|
END_MARKER_LINE
|
|
5142
5594
|
].join("\n");
|
|
5143
5595
|
}
|
|
@@ -5199,10 +5651,124 @@ ${block}`;
|
|
|
5199
5651
|
return hasRootEntry(result) ? result : seedEmptyArray(result);
|
|
5200
5652
|
}
|
|
5201
5653
|
|
|
5654
|
+
// src/preset.ts
|
|
5655
|
+
import { mkdir, readFile as readFile4, rename, writeFile } from "node:fs/promises";
|
|
5656
|
+
import { randomUUID } from "node:crypto";
|
|
5657
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
5658
|
+
var HOSTED_PRESET_ID = "loop-engine";
|
|
5659
|
+
var USER_PRESET_DIR = ".agent-presets";
|
|
5660
|
+
var COMPOSITION_FILE = "agent.cordis.yml";
|
|
5661
|
+
var METADATA_FILE = "preset.yml";
|
|
5662
|
+
var SOURCE_PRESET_ID = "standard";
|
|
5663
|
+
var STRIPPED_ROWS = ["skill-filesystem", "tool-skill", "tool-goal", "planning", "compaction"];
|
|
5664
|
+
var MANAGED_HEADER = `# Managed by dsh-loop-engine: the deployment's "${SOURCE_PRESET_ID}" preset minus
|
|
5665
|
+
# the dsh-native command/skill rows a hosted loop engine replaces. Regenerated
|
|
5666
|
+
# from "${SOURCE_PRESET_ID}" on boot \u2014 hand edits are overwritten.
|
|
5667
|
+
`;
|
|
5668
|
+
var MANAGED_METADATA = "name: Hosted Engine\ndescription: Standard preset minus the dsh-native commands and skills a hosted loop engine replaces.\n";
|
|
5669
|
+
function isEntryStart(line) {
|
|
5670
|
+
return line.startsWith("- ");
|
|
5671
|
+
}
|
|
5672
|
+
function entryId(line) {
|
|
5673
|
+
return /^- id:\s*(\S+)\s*$/.exec(line)?.[1];
|
|
5674
|
+
}
|
|
5675
|
+
function stripPresetRows(text, ids = STRIPPED_ROWS) {
|
|
5676
|
+
const lines = text.split("\n");
|
|
5677
|
+
const starts = [];
|
|
5678
|
+
for (const [index, line] of lines.entries()) {
|
|
5679
|
+
if (isEntryStart(line)) starts.push(index);
|
|
5680
|
+
}
|
|
5681
|
+
if (starts.length === 0) return text;
|
|
5682
|
+
const drop = new Set(ids);
|
|
5683
|
+
const entries = [];
|
|
5684
|
+
let heading = lines.slice(0, starts[0]);
|
|
5685
|
+
for (const [index, start] of starts.entries()) {
|
|
5686
|
+
const end = index + 1 < starts.length ? starts[index + 1] : lines.length;
|
|
5687
|
+
const span = lines.slice(start, end);
|
|
5688
|
+
let bodyEnd = span.length;
|
|
5689
|
+
while (bodyEnd > 1) {
|
|
5690
|
+
const line = span[bodyEnd - 1];
|
|
5691
|
+
if (line.trim() !== "" && !line.trimStart().startsWith("#")) break;
|
|
5692
|
+
bodyEnd -= 1;
|
|
5693
|
+
}
|
|
5694
|
+
entries.push({ id: entryId(span[0]), heading, body: span.slice(0, bodyEnd) });
|
|
5695
|
+
heading = span.slice(bodyEnd);
|
|
5696
|
+
}
|
|
5697
|
+
const out = [];
|
|
5698
|
+
out.push(...entries[0].heading);
|
|
5699
|
+
let lastKept = -1;
|
|
5700
|
+
for (const [index, entry] of entries.entries()) {
|
|
5701
|
+
if (entry.id !== void 0 && drop.has(entry.id)) continue;
|
|
5702
|
+
if (index > 0) out.push(...entry.heading);
|
|
5703
|
+
out.push(...entry.body);
|
|
5704
|
+
lastKept = index;
|
|
5705
|
+
}
|
|
5706
|
+
if (lastKept === entries.length - 1) out.push(...heading);
|
|
5707
|
+
if (out.length > 0 && out[out.length - 1] !== "") out.push("");
|
|
5708
|
+
return out.join("\n");
|
|
5709
|
+
}
|
|
5710
|
+
async function writeIfDifferent(path, text) {
|
|
5711
|
+
try {
|
|
5712
|
+
if (await readFile4(path, "utf8") === text) return false;
|
|
5713
|
+
} catch {
|
|
5714
|
+
}
|
|
5715
|
+
await mkdir(dirname4(path), { recursive: true });
|
|
5716
|
+
const tmp = `${path}.tmp-${randomUUID()}`;
|
|
5717
|
+
await writeFile(tmp, text, "utf8");
|
|
5718
|
+
await rename(tmp, path);
|
|
5719
|
+
return true;
|
|
5720
|
+
}
|
|
5721
|
+
async function ensureHostedPreset(dshHome, source) {
|
|
5722
|
+
const composition = await source.read(SOURCE_PRESET_ID);
|
|
5723
|
+
const stripped = `${MANAGED_HEADER}
|
|
5724
|
+
${stripPresetRows(composition)}`;
|
|
5725
|
+
const dir = join7(dshHome, USER_PRESET_DIR, HOSTED_PRESET_ID);
|
|
5726
|
+
const compositionChanged = await writeIfDifferent(join7(dir, COMPOSITION_FILE), stripped);
|
|
5727
|
+
const metadataChanged = await writeIfDifferent(join7(dir, METADATA_FILE), MANAGED_METADATA);
|
|
5728
|
+
return compositionChanged || metadataChanged;
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5731
|
+
// src/provider-route.ts
|
|
5732
|
+
import { LlmAdapter, LlmError as LlmError5 } from "@deepseek-ai/dsh-llm";
|
|
5733
|
+
var HOSTED_PROVIDER_ROUTES = {
|
|
5734
|
+
"claude-code": PROVIDER,
|
|
5735
|
+
codex: PROVIDER2,
|
|
5736
|
+
pi: PROVIDER3,
|
|
5737
|
+
kimi: PROVIDER4
|
|
5738
|
+
};
|
|
5739
|
+
var HostedEngineRouteAdapter = class extends LlmAdapter {
|
|
5740
|
+
/**
|
|
5741
|
+
* @param label - the provider route label this placeholder serves.
|
|
5742
|
+
* @param options - optional catalog source; omit for an empty catalog.
|
|
5743
|
+
*/
|
|
5744
|
+
constructor(label, options = {}) {
|
|
5745
|
+
super();
|
|
5746
|
+
this.label = label;
|
|
5747
|
+
this.options = options;
|
|
5748
|
+
}
|
|
5749
|
+
label;
|
|
5750
|
+
options;
|
|
5751
|
+
/** Advertise the injected Pi models (if any) under this route's provider label. */
|
|
5752
|
+
async listModels(_provider) {
|
|
5753
|
+
const catalog = this.options.listModels?.() ?? [];
|
|
5754
|
+
return catalog.map((entry) => ({
|
|
5755
|
+
provider: this.label,
|
|
5756
|
+
id: `${entry.provider}/${entry.model}`,
|
|
5757
|
+
name: `${entry.provider}/${entry.model}`
|
|
5758
|
+
}));
|
|
5759
|
+
}
|
|
5760
|
+
stream(_options) {
|
|
5761
|
+
throw new LlmError5(
|
|
5762
|
+
`provider "${this.label}" is a hosted loop engine route, not a model endpoint`,
|
|
5763
|
+
"HOSTED_ENGINE_ROUTE"
|
|
5764
|
+
);
|
|
5765
|
+
}
|
|
5766
|
+
};
|
|
5767
|
+
|
|
5202
5768
|
// src/commands.ts
|
|
5203
5769
|
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
5204
5770
|
import { homedir as homedir4 } from "node:os";
|
|
5205
|
-
import { join as
|
|
5771
|
+
import { join as join8 } from "node:path";
|
|
5206
5772
|
import { createUserMessage as createUserMessage6 } from "@deepseek-ai/dsh-llm";
|
|
5207
5773
|
var COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
|
|
5208
5774
|
function forwardClaudeCodeCommand(name2) {
|
|
@@ -5239,7 +5805,7 @@ function discoverUserSlashCommands() {
|
|
|
5239
5805
|
if (!entry.endsWith(".md")) continue;
|
|
5240
5806
|
const name2 = entry.slice(0, -".md".length);
|
|
5241
5807
|
if (!COMMAND_NAME.test(name2) || seen.has(name2)) continue;
|
|
5242
|
-
const path =
|
|
5808
|
+
const path = join8(userCommandsDir(), entry);
|
|
5243
5809
|
let raw;
|
|
5244
5810
|
try {
|
|
5245
5811
|
raw = readFileSync2(path, "utf8");
|
|
@@ -5254,7 +5820,7 @@ function discoverUserSlashCommands() {
|
|
|
5254
5820
|
return definitions;
|
|
5255
5821
|
}
|
|
5256
5822
|
function userCommandsDir() {
|
|
5257
|
-
return
|
|
5823
|
+
return join8(homedir4(), ".claude", "commands");
|
|
5258
5824
|
}
|
|
5259
5825
|
function commandDescription(raw) {
|
|
5260
5826
|
const trimmed = raw.trim();
|
|
@@ -5281,7 +5847,7 @@ function commandDescription(raw) {
|
|
|
5281
5847
|
|
|
5282
5848
|
// src/engine-codex/skills.ts
|
|
5283
5849
|
import { homedir as homedir5 } from "node:os";
|
|
5284
|
-
import { join as
|
|
5850
|
+
import { join as join9 } from "node:path";
|
|
5285
5851
|
var PROVIDER_NAME3 = "codex";
|
|
5286
5852
|
var CODEX_PROJECT_RANK = 140;
|
|
5287
5853
|
var CODEX_USER_RANK = 160;
|
|
@@ -5299,7 +5865,7 @@ var CodexSkillProvider = class {
|
|
|
5299
5865
|
const paths = await collectProjectContextFiles(cwd, CODEX_CONTEXT_POLICY);
|
|
5300
5866
|
if (await anySourceNonEmpty(paths)) candidates.push(this.agentsCandidate(paths, CODEX_PROJECT_RANK));
|
|
5301
5867
|
}
|
|
5302
|
-
const userPath =
|
|
5868
|
+
const userPath = join9(homedir5(), ".codex", "AGENTS.md");
|
|
5303
5869
|
if (await fileNonEmpty(userPath)) candidates.push(this.agentsCandidate([userPath], CODEX_USER_RANK));
|
|
5304
5870
|
if (this.control.signal.aborted) return [];
|
|
5305
5871
|
return candidates;
|
|
@@ -5338,9 +5904,9 @@ var CodexSkillProvider = class {
|
|
|
5338
5904
|
};
|
|
5339
5905
|
|
|
5340
5906
|
// src/engine-pi/skills.ts
|
|
5341
|
-
import { readdir as readdir3, readFile as
|
|
5907
|
+
import { readdir as readdir3, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
5342
5908
|
import { homedir as homedir6 } from "node:os";
|
|
5343
|
-
import { dirname as
|
|
5909
|
+
import { dirname as dirname5, join as join10, resolve as resolve4 } from "node:path";
|
|
5344
5910
|
var PROVIDER_NAME4 = "pi";
|
|
5345
5911
|
var PI_AGENTS_PROJECT_RANK = 140;
|
|
5346
5912
|
var PI_SKILL_PROJECT_RANK = 150;
|
|
@@ -5353,7 +5919,7 @@ var PI_CONTEXT_POLICY = {
|
|
|
5353
5919
|
function piAgentDir() {
|
|
5354
5920
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
5355
5921
|
if (override !== void 0 && override.length > 0) return resolve4(override);
|
|
5356
|
-
return
|
|
5922
|
+
return join10(homedir6(), ".pi", "agent");
|
|
5357
5923
|
}
|
|
5358
5924
|
var PiSkillProvider = class {
|
|
5359
5925
|
constructor(control) {
|
|
@@ -5369,13 +5935,13 @@ var PiSkillProvider = class {
|
|
|
5369
5935
|
const contextPaths = await collectProjectContextFiles(cwd, PI_CONTEXT_POLICY);
|
|
5370
5936
|
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, PI_AGENTS_PROJECT_RANK));
|
|
5371
5937
|
for (const dir of projectDirs) {
|
|
5372
|
-
await this.collectSkillsDir(
|
|
5938
|
+
await this.collectSkillsDir(join10(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
|
|
5373
5939
|
}
|
|
5374
5940
|
}
|
|
5375
5941
|
const userAgentDir = piAgentDir();
|
|
5376
|
-
const userContext =
|
|
5942
|
+
const userContext = join10(userAgentDir, "AGENTS.md");
|
|
5377
5943
|
if (await fileNonEmpty(userContext)) candidates.push(this.agentsCandidate([userContext], PI_AGENTS_USER_RANK));
|
|
5378
|
-
await this.collectSkillsDir(
|
|
5944
|
+
await this.collectSkillsDir(join10(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
|
|
5379
5945
|
if (this.control.signal.aborted) return [];
|
|
5380
5946
|
return candidates;
|
|
5381
5947
|
}
|
|
@@ -5393,7 +5959,7 @@ var PiSkillProvider = class {
|
|
|
5393
5959
|
provider: this.name,
|
|
5394
5960
|
content: parsed.content,
|
|
5395
5961
|
path: locator.path,
|
|
5396
|
-
resourceBase: { kind: "directory", path:
|
|
5962
|
+
resourceBase: { kind: "directory", path: dirname5(locator.path) }
|
|
5397
5963
|
};
|
|
5398
5964
|
}
|
|
5399
5965
|
const content = await readSources(locator.paths);
|
|
@@ -5434,11 +6000,11 @@ var PiSkillProvider = class {
|
|
|
5434
6000
|
return;
|
|
5435
6001
|
}
|
|
5436
6002
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5437
|
-
const entryPath =
|
|
6003
|
+
const entryPath = join10(skillsDir, entry.name);
|
|
5438
6004
|
const info = await stat4(entryPath).catch(() => void 0);
|
|
5439
6005
|
if (info === void 0) continue;
|
|
5440
6006
|
if (info.isDirectory()) {
|
|
5441
|
-
const path =
|
|
6007
|
+
const path = join10(entryPath, "SKILL.md");
|
|
5442
6008
|
const parsed2 = await this.tryParse(path);
|
|
5443
6009
|
if (parsed2 === void 0) continue;
|
|
5444
6010
|
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
@@ -5468,7 +6034,7 @@ var PiSkillProvider = class {
|
|
|
5468
6034
|
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
5469
6035
|
async tryParse(path) {
|
|
5470
6036
|
try {
|
|
5471
|
-
const raw = await
|
|
6037
|
+
const raw = await readFile5(path, { encoding: "utf8" });
|
|
5472
6038
|
return parseSkillFile(raw);
|
|
5473
6039
|
} catch {
|
|
5474
6040
|
return void 0;
|
|
@@ -5498,7 +6064,7 @@ var Config5 = z6.object({
|
|
|
5498
6064
|
});
|
|
5499
6065
|
function resolvePatchPath(config) {
|
|
5500
6066
|
if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
|
|
5501
|
-
return
|
|
6067
|
+
return join11(
|
|
5502
6068
|
resolveDshHome(),
|
|
5503
6069
|
"profiles",
|
|
5504
6070
|
config.profile ?? "web",
|
|
@@ -5510,21 +6076,21 @@ function isMissing(error) {
|
|
|
5510
6076
|
}
|
|
5511
6077
|
async function readPatchOrUndefined(path) {
|
|
5512
6078
|
try {
|
|
5513
|
-
return await
|
|
6079
|
+
return await readFile6(path, "utf8");
|
|
5514
6080
|
} catch (error) {
|
|
5515
6081
|
if (isMissing(error)) return void 0;
|
|
5516
6082
|
throw error;
|
|
5517
6083
|
}
|
|
5518
6084
|
}
|
|
5519
6085
|
async function writePatchFile(path, text) {
|
|
5520
|
-
await
|
|
5521
|
-
const tmp = `${path}.tmp-${
|
|
5522
|
-
await
|
|
5523
|
-
await
|
|
6086
|
+
await mkdir2(dirname6(path), { recursive: true });
|
|
6087
|
+
const tmp = `${path}.tmp-${randomUUID2()}`;
|
|
6088
|
+
await writeFile2(tmp, text, "utf8");
|
|
6089
|
+
await rename2(tmp, path);
|
|
5524
6090
|
}
|
|
5525
6091
|
function writePatchFileSync(path, text) {
|
|
5526
|
-
mkdirSync(
|
|
5527
|
-
const tmp = `${path}.tmp-${
|
|
6092
|
+
mkdirSync(dirname6(path), { recursive: true });
|
|
6093
|
+
const tmp = `${path}.tmp-${randomUUID2()}`;
|
|
5528
6094
|
writeFileSync(tmp, text, "utf8");
|
|
5529
6095
|
renameSync(tmp, path);
|
|
5530
6096
|
}
|
|
@@ -5579,10 +6145,14 @@ function kimiConfig(config) {
|
|
|
5579
6145
|
function apply(ctx, config) {
|
|
5580
6146
|
const patchPath = resolvePatchPath(config);
|
|
5581
6147
|
let fileEngine = currentEngineOf(readPatchFileSync(patchPath));
|
|
6148
|
+
const AGENT_PRESETS_NS = "agent-presets";
|
|
6149
|
+
const PRESET_DEFAULT_ATTEMPTS = 30;
|
|
6150
|
+
const PRESET_DEFAULT_RETRY_MS = 100;
|
|
5582
6151
|
let engineFiber;
|
|
5583
6152
|
let mountedEngine;
|
|
5584
6153
|
let commandDisposers;
|
|
5585
6154
|
let skillDisposer;
|
|
6155
|
+
const piCatalogHolder = { entries: [] };
|
|
5586
6156
|
let mountAttempts = 0;
|
|
5587
6157
|
let mountRetry;
|
|
5588
6158
|
const CLEAR_RETRY = () => {
|
|
@@ -5591,6 +6161,106 @@ function apply(ctx, config) {
|
|
|
5591
6161
|
mountRetry = void 0;
|
|
5592
6162
|
}
|
|
5593
6163
|
};
|
|
6164
|
+
let savedPresetDefault;
|
|
6165
|
+
let presetRetry;
|
|
6166
|
+
const CLEAR_PRESET_RETRY = () => {
|
|
6167
|
+
if (presetRetry !== void 0) {
|
|
6168
|
+
clearTimeout(presetRetry);
|
|
6169
|
+
presetRetry = void 0;
|
|
6170
|
+
}
|
|
6171
|
+
};
|
|
6172
|
+
const ROUTE_ATTEMPTS = 30;
|
|
6173
|
+
const ROUTE_RETRY_MS = 100;
|
|
6174
|
+
let routeHandle;
|
|
6175
|
+
let routeEngine;
|
|
6176
|
+
let routeRetry;
|
|
6177
|
+
const CLEAR_ROUTE_RETRY = () => {
|
|
6178
|
+
if (routeRetry !== void 0) {
|
|
6179
|
+
clearTimeout(routeRetry);
|
|
6180
|
+
routeRetry = void 0;
|
|
6181
|
+
}
|
|
6182
|
+
};
|
|
6183
|
+
const releaseRoute = () => {
|
|
6184
|
+
CLEAR_ROUTE_RETRY();
|
|
6185
|
+
const handle = routeHandle;
|
|
6186
|
+
routeHandle = void 0;
|
|
6187
|
+
routeEngine = void 0;
|
|
6188
|
+
handle?.();
|
|
6189
|
+
};
|
|
6190
|
+
const mountProviderRoute = (engine, attempt = 0) => {
|
|
6191
|
+
if (engine === "in-process") return;
|
|
6192
|
+
if (routeEngine === engine && routeHandle !== void 0) return;
|
|
6193
|
+
CLEAR_ROUTE_RETRY();
|
|
6194
|
+
const label = HOSTED_PROVIDER_ROUTES[engine];
|
|
6195
|
+
const llm = ctx.get("llm");
|
|
6196
|
+
if (llm === void 0) {
|
|
6197
|
+
if (attempt < ROUTE_ATTEMPTS) {
|
|
6198
|
+
routeRetry = setTimeout(() => {
|
|
6199
|
+
mountProviderRoute(engine, attempt + 1);
|
|
6200
|
+
}, ROUTE_RETRY_MS);
|
|
6201
|
+
}
|
|
6202
|
+
return;
|
|
6203
|
+
}
|
|
6204
|
+
try {
|
|
6205
|
+
const options = engine === "pi" ? { listModels: () => piCatalogHolder.entries } : void 0;
|
|
6206
|
+
routeHandle = llm.registerAdapter([label], new HostedEngineRouteAdapter(label, options));
|
|
6207
|
+
routeEngine = engine;
|
|
6208
|
+
} catch (error) {
|
|
6209
|
+
if (error instanceof Error && error.message.includes("already registered")) {
|
|
6210
|
+
ctx.logger.warn(`loop-engine: provider route "${label}" is already served by another adapter`);
|
|
6211
|
+
return;
|
|
6212
|
+
}
|
|
6213
|
+
ctx.logger.error(`loop-engine: provider route "${label}" registration failed: ${String(error)}`);
|
|
6214
|
+
}
|
|
6215
|
+
};
|
|
6216
|
+
const mutatePresetDefault = (op, attempt = 0) => {
|
|
6217
|
+
const settings = ctx.get("settings");
|
|
6218
|
+
if (settings === void 0) return;
|
|
6219
|
+
settings.mutate(AGENT_PRESETS_NS, [op]).then(() => void 0, (error) => {
|
|
6220
|
+
if (error instanceof Error && error.message.includes("not registered") && attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6221
|
+
presetRetry = setTimeout(() => {
|
|
6222
|
+
mutatePresetDefault(op, attempt + 1);
|
|
6223
|
+
}, PRESET_DEFAULT_RETRY_MS);
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
ctx.logger.error(`loop-engine: preset default switch failed: ${String(error)}`);
|
|
6227
|
+
});
|
|
6228
|
+
};
|
|
6229
|
+
const restorePresetDefault = (attempt = 0) => {
|
|
6230
|
+
const presets = ctx.get("agentPresets");
|
|
6231
|
+
if (presets === void 0) return;
|
|
6232
|
+
if (presets.defaultId === HOSTED_PRESET_ID) {
|
|
6233
|
+
const saved = savedPresetDefault;
|
|
6234
|
+
savedPresetDefault = void 0;
|
|
6235
|
+
mutatePresetDefault(saved === void 0 ? { op: "unset", path: ["default"] } : { op: "set", path: ["default"], value: saved });
|
|
6236
|
+
return;
|
|
6237
|
+
}
|
|
6238
|
+
if (attempt < PRESET_DEFAULT_ATTEMPTS) {
|
|
6239
|
+
presetRetry = setTimeout(() => {
|
|
6240
|
+
restorePresetDefault(attempt + 1);
|
|
6241
|
+
}, PRESET_DEFAULT_RETRY_MS);
|
|
6242
|
+
}
|
|
6243
|
+
};
|
|
6244
|
+
const steerPresetDefault = (engine) => {
|
|
6245
|
+
const presets = ctx.get("agentPresets");
|
|
6246
|
+
if (presets === void 0) return;
|
|
6247
|
+
if (engine === "in-process") {
|
|
6248
|
+
restorePresetDefault();
|
|
6249
|
+
return;
|
|
6250
|
+
}
|
|
6251
|
+
void (async () => {
|
|
6252
|
+
try {
|
|
6253
|
+
await ensureHostedPreset(resolveDshHome(), presets);
|
|
6254
|
+
} catch (error) {
|
|
6255
|
+
ctx.logger.error(`loop-engine: hosted preset authoring failed: ${String(error)}`);
|
|
6256
|
+
return;
|
|
6257
|
+
}
|
|
6258
|
+
const current = presets.defaultId;
|
|
6259
|
+
if (current === HOSTED_PRESET_ID) return;
|
|
6260
|
+
savedPresetDefault = current;
|
|
6261
|
+
mutatePresetDefault({ op: "set", path: ["default"], value: HOSTED_PRESET_ID });
|
|
6262
|
+
})();
|
|
6263
|
+
};
|
|
5594
6264
|
const cleanupEngineRegistrations = () => {
|
|
5595
6265
|
if (commandDisposers !== void 0) {
|
|
5596
6266
|
for (const dispose of commandDisposers) dispose();
|
|
@@ -5652,7 +6322,10 @@ function apply(ctx, config) {
|
|
|
5652
6322
|
if (skills !== void 0) {
|
|
5653
6323
|
skillDisposer = skills.registerProvider((control) => new PiSkillProvider(control));
|
|
5654
6324
|
}
|
|
5655
|
-
hostFactory("pi", () => ctx.plugin(PiLoop,
|
|
6325
|
+
hostFactory("pi", () => ctx.plugin(PiLoop, {
|
|
6326
|
+
...piConfig(config),
|
|
6327
|
+
piCatalogHolder
|
|
6328
|
+
}));
|
|
5656
6329
|
};
|
|
5657
6330
|
const mountKimi = () => {
|
|
5658
6331
|
const commands = ctx.get("commands");
|
|
@@ -5674,6 +6347,7 @@ function apply(ctx, config) {
|
|
|
5674
6347
|
hostFactory("kimi", () => ctx.plugin(KimiLoop, kimiConfig(config)));
|
|
5675
6348
|
};
|
|
5676
6349
|
const mountEngine = (engine) => {
|
|
6350
|
+
mountProviderRoute(engine);
|
|
5677
6351
|
if (engine === "claude-code") mountClaude();
|
|
5678
6352
|
else if (engine === "codex") mountCodex();
|
|
5679
6353
|
else if (engine === "pi") mountPi();
|
|
@@ -5683,6 +6357,7 @@ function apply(ctx, config) {
|
|
|
5683
6357
|
const fiber = engineFiber;
|
|
5684
6358
|
mountAttempts = 0;
|
|
5685
6359
|
CLEAR_RETRY();
|
|
6360
|
+
releaseRoute();
|
|
5686
6361
|
cleanupEngineRegistrations();
|
|
5687
6362
|
mountedEngine = void 0;
|
|
5688
6363
|
if (fiber === void 0) return;
|
|
@@ -5692,27 +6367,36 @@ function apply(ctx, config) {
|
|
|
5692
6367
|
}, () => void 0);
|
|
5693
6368
|
};
|
|
5694
6369
|
mountEngine(fileEngine);
|
|
5695
|
-
|
|
6370
|
+
steerPresetDefault(fileEngine);
|
|
6371
|
+
ctx.effect(() => () => {
|
|
6372
|
+
CLEAR_RETRY();
|
|
6373
|
+
CLEAR_PRESET_RETRY();
|
|
6374
|
+
releaseRoute();
|
|
6375
|
+
}, "loop-engine: retry cleanup");
|
|
5696
6376
|
let source;
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
6377
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
6378
|
+
settingsCtx.settings.installSection(ctx, loopEngineSettingsNamespace(), LOOP_ENGINE_SETTINGS_SCHEMA, { engine: fileEngine, showInComposer: true }, {
|
|
6379
|
+
setSource: (current) => {
|
|
6380
|
+
source = current;
|
|
6381
|
+
},
|
|
6382
|
+
onChange: () => {
|
|
6383
|
+
const next = source().engine;
|
|
6384
|
+
if (next === fileEngine) return;
|
|
6385
|
+
if (mountedEngine !== next) {
|
|
6386
|
+
unmountEngine();
|
|
6387
|
+
mountEngine(next);
|
|
6388
|
+
}
|
|
6389
|
+
try {
|
|
6390
|
+
const updated = applyManagedBlock(readPatchFileSync(patchPath), next);
|
|
6391
|
+
writePatchFileSync(patchPath, updated);
|
|
6392
|
+
fileEngine = next;
|
|
6393
|
+
} catch (error) {
|
|
6394
|
+
ctx.logger.error(`loop-engine: managed block write failed: ${String(error)}`);
|
|
6395
|
+
return;
|
|
6396
|
+
}
|
|
6397
|
+
steerPresetDefault(next);
|
|
5714
6398
|
}
|
|
5715
|
-
}
|
|
6399
|
+
});
|
|
5716
6400
|
});
|
|
5717
6401
|
}
|
|
5718
6402
|
export {
|