dsh-loop-engine 1.0.0-rc7 → 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/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 readFile5, rename, writeFile } from "node:fs/promises";
50
- import { randomUUID } from "node:crypto";
51
- import { dirname as dirname5, join as join10 } from "node:path";
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
- CallId,
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 = CallId(block.id);
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: CallId(block.tool_use_id),
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: CallId(block.id), name: block.name });
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 ?? CallId(`call-${event.index}`),
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.events.findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
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.events);
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
- detachAgent?.();
1200
- detachSession?.();
1198
+ await handle?.close();
1201
1199
  } finally {
1202
- untrack();
1203
- if (!ownerTriggered) await unfollowOwner();
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
- const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal);
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 = this.setupAndPublish(
1286
- ownerCtx,
1287
- options.sessionId,
1288
- preparation,
1289
- options.agentOptions ?? {},
1290
- options.setup,
1291
- options.signal,
1292
- "startup"
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 handle. */
1311
- async resumeWith(ownerCtx, persistence, options) {
1376
+ /** Resume through an explicit persistence service. */
1377
+ resumeWith(ownerCtx, persistence, options) {
1312
1378
  const id = options.resumeSessionId;
1313
- let preparation;
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
- preparation = await raceAbortCall(
1326
- () => persistence.prepare(id, fused),
1327
- fused,
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
- (abandoned) => {
1330
- abandoned[Symbol.dispose]();
1331
- }
1424
+ preparation,
1425
+ options.agentOptions ?? {},
1426
+ options.setup,
1427
+ options.signal,
1428
+ "resume",
1429
+ owned
1332
1430
  );
1333
1431
  } finally {
1334
- await unfollowOwner();
1432
+ preparation?.[Symbol.dispose]();
1433
+ await handle?.close().catch(() => {
1434
+ });
1335
1435
  }
1336
- ownerCtx.fiber.assertActive();
1337
- if (!this.ownership.isActive()) throw new Error("agent loop is not active");
1338
- return await this.setupAndPublish(
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.1.1-rc.2"
1533
+ version: "1.0.0-rc8"
1445
1534
  },
1446
1535
  capabilities: { experimentalApi: true, requestAttestation: false }
1447
1536
  };
@@ -1656,7 +1745,7 @@ function noopNotificationHandler() {
1656
1745
  }
1657
1746
 
1658
1747
  // src/engine-codex/appserver/mapping.ts
1659
- import { CallId as CallId2, createToolResultMessage as createToolResultMessage2 } from "@deepseek-ai/dsh-llm";
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: CallId2(item.id),
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: CallId2(item.id),
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: CallId2(item.id),
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: CallId2(item.id),
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: CallId2(item.id),
1790
+ callId: ToolCallId2(item.id),
1702
1791
  name: name2,
1703
1792
  arguments: JSON.stringify(item.arguments ?? {})
1704
1793
  },
1705
1794
  result: createToolResultMessage2({
1706
- callId: CallId2(item.id),
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.events.findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
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.events);
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
- detachAgent?.();
2336
- detachSession?.();
2424
+ await handle?.close();
2337
2425
  } finally {
2338
- untrack();
2339
- if (!ownerTriggered) await unfollowOwner();
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
- const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal);
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 = this.setupAndPublish(
2422
- ownerCtx,
2423
- options.sessionId,
2424
- preparation,
2425
- options.agentOptions ?? {},
2426
- options.setup,
2427
- options.signal,
2428
- "startup"
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 handle. */
2447
- async resumeWith(ownerCtx, persistence, options) {
2602
+ /** Resume through an explicit persistence service. */
2603
+ resumeWith(ownerCtx, persistence, options) {
2448
2604
  const id = options.resumeSessionId;
2449
- let preparation;
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
- preparation = await raceAbortCall(
2462
- () => persistence.prepare(id, fused),
2463
- fused,
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
- (abandoned) => {
2466
- abandoned[Symbol.dispose]();
2467
- }
2650
+ preparation,
2651
+ options.agentOptions ?? {},
2652
+ options.setup,
2653
+ options.signal,
2654
+ "resume",
2655
+ owned
2468
2656
  );
2469
2657
  } finally {
2470
- await unfollowOwner();
2658
+ preparation?.[Symbol.dispose]();
2659
+ await handle?.close().catch(() => {
2660
+ });
2471
2661
  }
2472
- ownerCtx.fiber.assertActive();
2473
- if (!this.ownership.isActive()) throw new Error("agent loop is not active");
2474
- return await this.setupAndPublish(
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 { CallId as CallId4, LlmError as LlmError3, createAssistantMessage as createAssistantMessage3, createUserMessage as createUserMessage3, errorChain as errorChain3 } from "@deepseek-ai/dsh-llm";
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
 
@@ -2717,7 +2896,7 @@ var PiRpcClient = class _PiRpcClient {
2717
2896
 
2718
2897
  // src/engine-pi/rpc/mapping.ts
2719
2898
  import {
2720
- CallId as CallId3,
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: CallId3(ev.toolCallId),
2930
+ callId: ToolCallId3(ev.toolCallId),
2752
2931
  content: [{ type: "text", text: resultText(ev.result) || "(no content)" }],
2753
2932
  isError: ev.isError
2754
2933
  });
@@ -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.events.findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
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.events);
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: CallId4(callId),
3397
+ callId: ToolCallId4(callId),
3219
3398
  name: name2,
3220
3399
  arguments: argumentsValue
3221
3400
  });
@@ -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
- detachAgent?.();
3490
- detachSession?.();
3668
+ await handle?.close();
3491
3669
  } finally {
3492
- untrack();
3493
- if (!ownerTriggered) await unfollowOwner();
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
- const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal);
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 = this.setupAndPublish(
3576
- ownerCtx,
3577
- options.sessionId,
3578
- preparation,
3579
- options.agentOptions ?? {},
3580
- options.setup,
3581
- options.signal,
3582
- "startup"
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 handle. */
3601
- async resumeWith(ownerCtx, persistence, options) {
3846
+ /** Resume through an explicit persistence service. */
3847
+ resumeWith(ownerCtx, persistence, options) {
3602
3848
  const id = options.resumeSessionId;
3603
- let preparation;
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,32 +3856,56 @@ 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
- preparation = await raceAbortCall(
3616
- () => persistence.prepare(id, fused),
3617
- fused,
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
- (abandoned) => {
3620
- abandoned[Symbol.dispose]();
3621
- }
3894
+ preparation,
3895
+ options.agentOptions ?? {},
3896
+ options.setup,
3897
+ options.signal,
3898
+ "resume",
3899
+ owned
3622
3900
  );
3623
3901
  } finally {
3624
- await unfollowOwner();
3902
+ preparation?.[Symbol.dispose]();
3903
+ await handle?.close().catch(() => {
3904
+ });
3625
3905
  }
3626
- ownerCtx.fiber.assertActive();
3627
- if (!this.ownership.isActive()) throw new Error("agent loop is not active");
3628
- return await this.setupAndPublish(
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
 
@@ -3644,11 +3913,11 @@ var PiLoop = class extends Service3 {
3644
3913
  import { Service as Service4 } from "@deepseek-ai/cordis";
3645
3914
  import z4 from "@deepseek-ai/schemastery";
3646
3915
  import { emitAgentEvent as emitAgentEvent4 } from "@deepseek-ai/dsh-agent";
3647
- import { SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
3916
+ import { interruptedTurnClosers as interruptedTurnClosers4, SessionLogOffset as SessionLogOffset4, SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
3648
3917
 
3649
3918
  // src/engine-kimi/agent.ts
3650
3919
  import { Inbox as Inbox4, agentEvents as agentEvents4 } from "@deepseek-ai/dsh-agent";
3651
- import { CallId as CallId6, LlmError as LlmError4, createAssistantMessage as createAssistantMessage4, createUserMessage as createUserMessage4, errorChain as errorChain4 } from "@deepseek-ai/dsh-llm";
3920
+ import { ToolCallId as ToolCallId6, LlmError as LlmError4, createAssistantMessage as createAssistantMessage4, createUserMessage as createUserMessage4, errorChain as errorChain4 } from "@deepseek-ai/dsh-llm";
3652
3921
  import { createScope as createScope4 } from "@deepseek-ai/dsh-scope";
3653
3922
  import { canonicalHeader as canonicalHeader4 } from "@deepseek-ai/dsh-session";
3654
3923
 
@@ -3903,7 +4172,7 @@ var AcpClient = class _AcpClient {
3903
4172
  };
3904
4173
 
3905
4174
  // src/engine-kimi/acp/mapping.ts
3906
- import { CallId as CallId5, createToolResultMessage as createToolResultMessage4 } from "@deepseek-ai/dsh-llm";
4175
+ import { ToolCallId as ToolCallId5, createToolResultMessage as createToolResultMessage4 } from "@deepseek-ai/dsh-llm";
3907
4176
  function isTextChunk(update) {
3908
4177
  return update.sessionUpdate === "agent_message_chunk";
3909
4178
  }
@@ -3940,7 +4209,7 @@ function toolContentText(update) {
3940
4209
  }
3941
4210
  function toolResult(callId, text, isError) {
3942
4211
  return createToolResultMessage4({
3943
- callId: CallId5(callId),
4212
+ callId: ToolCallId5(callId),
3944
4213
  content: [{ type: "text", text: text.length > 0 ? text : "(no content)" }],
3945
4214
  isError
3946
4215
  });
@@ -3975,7 +4244,7 @@ var KimiAgent = class {
3975
4244
  this.dispatch.emit("agent/inbox/claimed", { message, turn });
3976
4245
  }
3977
4246
  });
3978
- const lastTurn = session.events.findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
4247
+ const lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
3979
4248
  this.phase = { kind: "idle", lastTurn };
3980
4249
  this.scope = createScope4(loopCtx, this);
3981
4250
  this.ctx = this.scope.ctx.extend({ agent: this });
@@ -4321,7 +4590,7 @@ var KimiAgent = class {
4321
4590
  signal.throwIfAborted();
4322
4591
  const client = await this.acpClient(cwd);
4323
4592
  signal.throwIfAborted();
4324
- client.onPermission(() => resolveToolApproval(this.session.events));
4593
+ client.onPermission(() => resolveToolApproval(this.session.snapshotEvents()));
4325
4594
  const acpSessionId = await client.newSession(cwd);
4326
4595
  signal.throwIfAborted();
4327
4596
  client.onUpdate((update) => this.applyUpdate(turn, step, update));
@@ -4385,7 +4654,7 @@ var KimiAgent = class {
4385
4654
  if (callId === "" || this.emittedToolCalls.has(callId)) return;
4386
4655
  this.emittedToolCalls.add(callId);
4387
4656
  const name2 = toolCallName(update);
4388
- this.session.append("tool/call", { turn, step, callId: CallId6(callId), name: name2, arguments: "{}" });
4657
+ this.session.append("tool/call", { turn, step, callId: ToolCallId6(callId), name: name2, arguments: "{}" });
4389
4658
  this.toolText.set(callId, "");
4390
4659
  return;
4391
4660
  }
@@ -4476,7 +4745,7 @@ var KimiLoop = class extends Service4 {
4476
4745
  * fuses caller cancellation with lifecycle teardown for setup awaits.
4477
4746
  */
4478
4747
  /* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
4479
- prepare(ownerCtx, id, options, session, callerSignal) {
4748
+ prepare(ownerCtx, id, options, session, callerSignal, handle) {
4480
4749
  ownerCtx.fiber.assertActive();
4481
4750
  if (!this.ownership.isActive()) throw new Error("agent loop is not active");
4482
4751
  if (callerSignal?.aborted) {
@@ -4510,11 +4779,15 @@ var KimiLoop = class extends Service4 {
4510
4779
  }
4511
4780
  } finally {
4512
4781
  try {
4513
- detachAgent?.();
4514
- detachSession?.();
4782
+ await handle?.close();
4515
4783
  } finally {
4516
- untrack();
4517
- if (!ownerTriggered) await unfollowOwner();
4784
+ try {
4785
+ detachAgent?.();
4786
+ detachSession?.();
4787
+ } finally {
4788
+ untrack();
4789
+ if (!ownerTriggered) await unfollowOwner();
4790
+ }
4518
4791
  }
4519
4792
  }
4520
4793
  })();
@@ -4564,18 +4837,27 @@ var KimiLoop = class extends Service4 {
4564
4837
  }
4565
4838
  }
4566
4839
  /** Prepare one Agent around an acquired Session, run setup, and publish it. */
4567
- async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
4840
+ async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source, stored) {
4568
4841
  var _stack = [];
4569
4842
  try {
4570
4843
  const ownedPreparation = __using(_stack, preparation);
4571
4844
  const session = ownedPreparation.session;
4572
- const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal);
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
+ }
4573
4853
  try {
4574
4854
  const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
4575
4855
  setupCommit?.commit();
4856
+ await this.appendUnstoredSuffix(stored, session);
4576
4857
  return prepared.publish(source);
4577
4858
  } catch (error) {
4578
- await prepared.dispose();
4859
+ await prepared.dispose().catch(() => {
4860
+ });
4579
4861
  throw error;
4580
4862
  }
4581
4863
  } catch (_) {
@@ -4586,7 +4868,8 @@ var KimiLoop = class extends Service4 {
4586
4868
  }
4587
4869
  /**
4588
4870
  * Create an agent and session under one caller-supplied identity, owned by
4589
- * the accessing fiber.
4871
+ * the accessing fiber. When a persistence backend is mounted, the session's
4872
+ * durable identity is stored before publication.
4590
4873
  * @param ownerCtx - caller context that structurally owns the lifecycle.
4591
4874
  * @param options - identities, session seed/metadata, loop options, setup, and cancellation.
4592
4875
  * @returns the published handle.
@@ -4594,20 +4877,73 @@ var KimiLoop = class extends Service4 {
4594
4877
  async createAgent(ownerCtx, options) {
4595
4878
  const preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
4596
4879
  ...options.seed === void 0 ? {} : { seed: options.seed },
4597
- ...options.meta === void 0 ? {} : { meta: options.meta }
4880
+ ...options.meta === void 0 ? {} : { meta: options.meta },
4881
+ ...options.inheritedEventCount === void 0 ? {} : { inheritedEventCount: options.inheritedEventCount }
4598
4882
  }));
4599
- const published = this.setupAndPublish(
4600
- ownerCtx,
4601
- options.sessionId,
4602
- preparation,
4603
- options.agentOptions ?? {},
4604
- options.setup,
4605
- options.signal,
4606
- "startup"
4607
- );
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
+ })();
4608
4910
  this.ownership.trackWrapper(published);
4609
4911
  return published;
4610
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
+ }
4611
4947
  /**
4612
4948
  * Resume an owned agent from the configured persistence service.
4613
4949
  * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
@@ -4621,11 +4957,10 @@ var KimiLoop = class extends Service4 {
4621
4957
  }
4622
4958
  return this.resumeWith(ownerCtx, persistence, options);
4623
4959
  }
4624
- /** Resume through an explicit persistence handle. */
4625
- async resumeWith(ownerCtx, persistence, options) {
4960
+ /** Resume through an explicit persistence service. */
4961
+ resumeWith(ownerCtx, persistence, options) {
4626
4962
  const id = options.resumeSessionId;
4627
- let preparation;
4628
- try {
4963
+ const published = (async () => {
4629
4964
  const ownerAbort = new AbortController();
4630
4965
  const unfollowOwner = ownerCtx.effect(() => () => {
4631
4966
  ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
@@ -4635,32 +4970,56 @@ var KimiLoop = class extends Service4 {
4635
4970
  ownerAbort.signal,
4636
4971
  this.ownership.signal
4637
4972
  ]);
4973
+ let handle;
4974
+ let stored;
4975
+ let preparation;
4638
4976
  try {
4639
- preparation = await raceAbortCall(
4640
- () => persistence.prepare(id, fused),
4641
- fused,
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,
4642
5007
  id,
4643
- (abandoned) => {
4644
- abandoned[Symbol.dispose]();
4645
- }
5008
+ preparation,
5009
+ options.agentOptions ?? {},
5010
+ options.setup,
5011
+ options.signal,
5012
+ "resume",
5013
+ owned
4646
5014
  );
4647
5015
  } finally {
4648
- await unfollowOwner();
5016
+ preparation?.[Symbol.dispose]();
5017
+ await handle?.close().catch(() => {
5018
+ });
4649
5019
  }
4650
- ownerCtx.fiber.assertActive();
4651
- if (!this.ownership.isActive()) throw new Error("agent loop is not active");
4652
- return await this.setupAndPublish(
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
- }
5020
+ })();
5021
+ this.ownership.trackWrapper(published);
5022
+ return published;
4664
5023
  }
4665
5024
  };
4666
5025
 
@@ -5102,7 +5461,6 @@ var KIMI_COMMANDS = [
5102
5461
  builtin("status", "Show the current session runtime state"),
5103
5462
  builtin("compact", "Compact the conversation context to free token usage"),
5104
5463
  builtin("clear", "Start a fresh session, discarding the current context"),
5105
- builtin("model", "Switch the LLM model used in the current session"),
5106
5464
  builtin("plan", "Toggle plan (read-only exploration) mode"),
5107
5465
  builtin("auto", "Toggle auto permission mode"),
5108
5466
  builtin("usage", "Show token usage, context, and quota information"),
@@ -5112,7 +5470,6 @@ var KIMI_COMMANDS = [
5112
5470
 
5113
5471
  // src/settings.ts
5114
5472
  import z5 from "@deepseek-ai/schemastery";
5115
- import { settingsNamespace } from "@deepseek-ai/dsh-settings";
5116
5473
 
5117
5474
  // src/namespace.ts
5118
5475
  var LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
@@ -5124,7 +5481,7 @@ var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
5124
5481
  showInComposer: z5.boolean().default(true)
5125
5482
  });
5126
5483
  function loopEngineSettingsNamespace() {
5127
- return settingsNamespace(LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL);
5484
+ return LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL;
5128
5485
  }
5129
5486
 
5130
5487
  // src/patch-manager.ts
@@ -5138,6 +5495,8 @@ function renderManagedBlock(engine) {
5138
5495
  `${MANAGED_BLOCK_BEGIN}${engine} --`,
5139
5496
  "- id: agent-loop",
5140
5497
  " disabled: true",
5498
+ "- id: command-goal",
5499
+ " disabled: true",
5141
5500
  END_MARKER_LINE
5142
5501
  ].join("\n");
5143
5502
  }
@@ -5199,10 +5558,112 @@ ${block}`;
5199
5558
  return hasRootEntry(result) ? result : seedEmptyArray(result);
5200
5559
  }
5201
5560
 
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("- ");
5578
+ }
5579
+ function entryId(line) {
5580
+ return /^- id:\s*(\S+)\s*$/.exec(line)?.[1];
5581
+ }
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");
5616
+ }
5617
+ async function writeIfDifferent(path, text) {
5618
+ try {
5619
+ if (await readFile4(path, "utf8") === text) return false;
5620
+ } catch {
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;
5627
+ }
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
+
5202
5663
  // src/commands.ts
5203
5664
  import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
5204
5665
  import { homedir as homedir4 } from "node:os";
5205
- import { join as join7 } from "node:path";
5666
+ import { join as join8 } from "node:path";
5206
5667
  import { createUserMessage as createUserMessage6 } from "@deepseek-ai/dsh-llm";
5207
5668
  var COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
5208
5669
  function forwardClaudeCodeCommand(name2) {
@@ -5239,7 +5700,7 @@ function discoverUserSlashCommands() {
5239
5700
  if (!entry.endsWith(".md")) continue;
5240
5701
  const name2 = entry.slice(0, -".md".length);
5241
5702
  if (!COMMAND_NAME.test(name2) || seen.has(name2)) continue;
5242
- const path = join7(userCommandsDir(), entry);
5703
+ const path = join8(userCommandsDir(), entry);
5243
5704
  let raw;
5244
5705
  try {
5245
5706
  raw = readFileSync2(path, "utf8");
@@ -5254,7 +5715,7 @@ function discoverUserSlashCommands() {
5254
5715
  return definitions;
5255
5716
  }
5256
5717
  function userCommandsDir() {
5257
- return join7(homedir4(), ".claude", "commands");
5718
+ return join8(homedir4(), ".claude", "commands");
5258
5719
  }
5259
5720
  function commandDescription(raw) {
5260
5721
  const trimmed = raw.trim();
@@ -5281,7 +5742,7 @@ function commandDescription(raw) {
5281
5742
 
5282
5743
  // src/engine-codex/skills.ts
5283
5744
  import { homedir as homedir5 } from "node:os";
5284
- import { join as join8 } from "node:path";
5745
+ import { join as join9 } from "node:path";
5285
5746
  var PROVIDER_NAME3 = "codex";
5286
5747
  var CODEX_PROJECT_RANK = 140;
5287
5748
  var CODEX_USER_RANK = 160;
@@ -5299,7 +5760,7 @@ var CodexSkillProvider = class {
5299
5760
  const paths = await collectProjectContextFiles(cwd, CODEX_CONTEXT_POLICY);
5300
5761
  if (await anySourceNonEmpty(paths)) candidates.push(this.agentsCandidate(paths, CODEX_PROJECT_RANK));
5301
5762
  }
5302
- const userPath = join8(homedir5(), ".codex", "AGENTS.md");
5763
+ const userPath = join9(homedir5(), ".codex", "AGENTS.md");
5303
5764
  if (await fileNonEmpty(userPath)) candidates.push(this.agentsCandidate([userPath], CODEX_USER_RANK));
5304
5765
  if (this.control.signal.aborted) return [];
5305
5766
  return candidates;
@@ -5338,9 +5799,9 @@ var CodexSkillProvider = class {
5338
5799
  };
5339
5800
 
5340
5801
  // src/engine-pi/skills.ts
5341
- import { readdir as readdir3, readFile as readFile4, stat as stat4 } from "node:fs/promises";
5802
+ import { readdir as readdir3, readFile as readFile5, stat as stat4 } from "node:fs/promises";
5342
5803
  import { homedir as homedir6 } from "node:os";
5343
- import { dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
5804
+ import { dirname as dirname5, join as join10, resolve as resolve4 } from "node:path";
5344
5805
  var PROVIDER_NAME4 = "pi";
5345
5806
  var PI_AGENTS_PROJECT_RANK = 140;
5346
5807
  var PI_SKILL_PROJECT_RANK = 150;
@@ -5353,7 +5814,7 @@ var PI_CONTEXT_POLICY = {
5353
5814
  function piAgentDir() {
5354
5815
  const override = process.env.PI_CODING_AGENT_DIR;
5355
5816
  if (override !== void 0 && override.length > 0) return resolve4(override);
5356
- return join9(homedir6(), ".pi", "agent");
5817
+ return join10(homedir6(), ".pi", "agent");
5357
5818
  }
5358
5819
  var PiSkillProvider = class {
5359
5820
  constructor(control) {
@@ -5369,13 +5830,13 @@ var PiSkillProvider = class {
5369
5830
  const contextPaths = await collectProjectContextFiles(cwd, PI_CONTEXT_POLICY);
5370
5831
  if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, PI_AGENTS_PROJECT_RANK));
5371
5832
  for (const dir of projectDirs) {
5372
- await this.collectSkillsDir(join9(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
5833
+ await this.collectSkillsDir(join10(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
5373
5834
  }
5374
5835
  }
5375
5836
  const userAgentDir = piAgentDir();
5376
- const userContext = join9(userAgentDir, "AGENTS.md");
5837
+ const userContext = join10(userAgentDir, "AGENTS.md");
5377
5838
  if (await fileNonEmpty(userContext)) candidates.push(this.agentsCandidate([userContext], PI_AGENTS_USER_RANK));
5378
- await this.collectSkillsDir(join9(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
5839
+ await this.collectSkillsDir(join10(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
5379
5840
  if (this.control.signal.aborted) return [];
5380
5841
  return candidates;
5381
5842
  }
@@ -5393,7 +5854,7 @@ var PiSkillProvider = class {
5393
5854
  provider: this.name,
5394
5855
  content: parsed.content,
5395
5856
  path: locator.path,
5396
- resourceBase: { kind: "directory", path: dirname4(locator.path) }
5857
+ resourceBase: { kind: "directory", path: dirname5(locator.path) }
5397
5858
  };
5398
5859
  }
5399
5860
  const content = await readSources(locator.paths);
@@ -5434,11 +5895,11 @@ var PiSkillProvider = class {
5434
5895
  return;
5435
5896
  }
5436
5897
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
5437
- const entryPath = join9(skillsDir, entry.name);
5898
+ const entryPath = join10(skillsDir, entry.name);
5438
5899
  const info = await stat4(entryPath).catch(() => void 0);
5439
5900
  if (info === void 0) continue;
5440
5901
  if (info.isDirectory()) {
5441
- const path = join9(entryPath, "SKILL.md");
5902
+ const path = join10(entryPath, "SKILL.md");
5442
5903
  const parsed2 = await this.tryParse(path);
5443
5904
  if (parsed2 === void 0) continue;
5444
5905
  candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
@@ -5468,7 +5929,7 @@ var PiSkillProvider = class {
5468
5929
  /** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
5469
5930
  async tryParse(path) {
5470
5931
  try {
5471
- const raw = await readFile4(path, { encoding: "utf8" });
5932
+ const raw = await readFile5(path, { encoding: "utf8" });
5472
5933
  return parseSkillFile(raw);
5473
5934
  } catch {
5474
5935
  return void 0;
@@ -5498,7 +5959,7 @@ var Config5 = z6.object({
5498
5959
  });
5499
5960
  function resolvePatchPath(config) {
5500
5961
  if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
5501
- return join10(
5962
+ return join11(
5502
5963
  resolveDshHome(),
5503
5964
  "profiles",
5504
5965
  config.profile ?? "web",
@@ -5510,21 +5971,21 @@ function isMissing(error) {
5510
5971
  }
5511
5972
  async function readPatchOrUndefined(path) {
5512
5973
  try {
5513
- return await readFile5(path, "utf8");
5974
+ return await readFile6(path, "utf8");
5514
5975
  } catch (error) {
5515
5976
  if (isMissing(error)) return void 0;
5516
5977
  throw error;
5517
5978
  }
5518
5979
  }
5519
5980
  async function writePatchFile(path, text) {
5520
- await mkdir(dirname5(path), { recursive: true });
5521
- const tmp = `${path}.tmp-${randomUUID()}`;
5522
- await writeFile(tmp, text, "utf8");
5523
- await rename(tmp, path);
5981
+ await mkdir2(dirname6(path), { recursive: true });
5982
+ const tmp = `${path}.tmp-${randomUUID2()}`;
5983
+ await writeFile2(tmp, text, "utf8");
5984
+ await rename2(tmp, path);
5524
5985
  }
5525
5986
  function writePatchFileSync(path, text) {
5526
- mkdirSync(dirname5(path), { recursive: true });
5527
- const tmp = `${path}.tmp-${randomUUID()}`;
5987
+ mkdirSync(dirname6(path), { recursive: true });
5988
+ const tmp = `${path}.tmp-${randomUUID2()}`;
5528
5989
  writeFileSync(tmp, text, "utf8");
5529
5990
  renameSync(tmp, path);
5530
5991
  }
@@ -5579,6 +6040,9 @@ function kimiConfig(config) {
5579
6040
  function apply(ctx, config) {
5580
6041
  const patchPath = resolvePatchPath(config);
5581
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;
5582
6046
  let engineFiber;
5583
6047
  let mountedEngine;
5584
6048
  let commandDisposers;
@@ -5591,6 +6055,105 @@ function apply(ctx, config) {
5591
6055
  mountRetry = void 0;
5592
6056
  }
5593
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
+ };
5594
6157
  const cleanupEngineRegistrations = () => {
5595
6158
  if (commandDisposers !== void 0) {
5596
6159
  for (const dispose of commandDisposers) dispose();
@@ -5674,6 +6237,7 @@ function apply(ctx, config) {
5674
6237
  hostFactory("kimi", () => ctx.plugin(KimiLoop, kimiConfig(config)));
5675
6238
  };
5676
6239
  const mountEngine = (engine) => {
6240
+ mountProviderRoute(engine);
5677
6241
  if (engine === "claude-code") mountClaude();
5678
6242
  else if (engine === "codex") mountCodex();
5679
6243
  else if (engine === "pi") mountPi();
@@ -5683,6 +6247,7 @@ function apply(ctx, config) {
5683
6247
  const fiber = engineFiber;
5684
6248
  mountAttempts = 0;
5685
6249
  CLEAR_RETRY();
6250
+ releaseRoute();
5686
6251
  cleanupEngineRegistrations();
5687
6252
  mountedEngine = void 0;
5688
6253
  if (fiber === void 0) return;
@@ -5692,27 +6257,36 @@ function apply(ctx, config) {
5692
6257
  }, () => void 0);
5693
6258
  };
5694
6259
  mountEngine(fileEngine);
5695
- ctx.effect(() => () => CLEAR_RETRY(), "loop-engine: mount retry cleanup");
6260
+ steerPresetDefault(fileEngine);
6261
+ ctx.effect(() => () => {
6262
+ CLEAR_RETRY();
6263
+ CLEAR_PRESET_RETRY();
6264
+ releaseRoute();
6265
+ }, "loop-engine: retry cleanup");
5696
6266
  let source;
5697
- installSettingsSection(ctx, loopEngineSettingsNamespace(), LOOP_ENGINE_SETTINGS_SCHEMA, { engine: fileEngine, showInComposer: true }, {
5698
- setSource: (current) => {
5699
- source = current;
5700
- },
5701
- onChange: () => {
5702
- const next = source().engine;
5703
- if (next === fileEngine) return;
5704
- if (mountedEngine !== next) {
5705
- unmountEngine();
5706
- mountEngine(next);
5707
- }
5708
- try {
5709
- const updated = applyManagedBlock(readPatchFileSync(patchPath), next);
5710
- writePatchFileSync(patchPath, updated);
5711
- fileEngine = next;
5712
- } catch (error) {
5713
- ctx.logger.error(`loop-engine: managed block write failed: ${String(error)}`);
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);
5714
6288
  }
5715
- }
6289
+ });
5716
6290
  });
5717
6291
  }
5718
6292
  export {