@chatcode/chatcode-cli 3.0.4-rc.1 → 3.0.4-rc.3

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.
Files changed (136) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +2 -0
  3. package/README.zh.md +2 -0
  4. package/lib/{command-errors-BkCHTQHj.js → command-errors-BJ_ZWzUU.js} +12 -12
  5. package/lib/{command-trees-5HFyC5sI.js → command-trees-C9b7-bci.js} +2 -2
  6. package/lib/{component-identity-De91VHwW.js → component-identity-Jb1DYjke.js} +3 -3
  7. package/lib/{dist-CsDWUvQ4.js → dist-w_sIm4VD.js} +231 -231
  8. package/lib/{extension-events-CdYCRP0o.js → extension-events-DKX4LXOd.js} +26 -26
  9. package/lib/{frames-DOX-Jgk8.js → frames-B89znn_0.js} +4 -4
  10. package/lib/{host-access-Bk4xps5E.js → host-access-CMg6rsWt.js} +1 -1
  11. package/lib/{keymap-CJ1kvLR2.js → keymap-CrC4pSNH.js} +2 -2
  12. package/lib/{plugin-XR7ncbnW.js → plugin-CmyMCGfb.js} +1651 -1365
  13. package/lib/{scenes-CAmWncZD.js → scenes-Cil9RnwU.js} +3 -3
  14. package/lib/{settings-sections-CakcvBv9.js → settings-sections-jVCqXwdH.js} +3 -3
  15. package/lib/{statusBarSettings-BPPS_Usm.js → statusBarSettings-DNoBH3Cq.js} +3 -3
  16. package/lib/{supports-color-DyChQwlI.js → supports-color-CcW-FGjr.js} +2 -2
  17. package/lib/{themes-OjcUpZzw.js → themes-BdgeY3It.js} +12 -12
  18. package/lib/tui-compat-command-trees.js +1 -1
  19. package/lib/tui-compat-extensions.js +3 -3
  20. package/lib/tui-compat-plugin-host.js +5 -5
  21. package/lib/tui-compat-scenes.js +1 -1
  22. package/lib/tui-compat-settings-sections.js +1 -1
  23. package/lib/tui-compat-working-activity.js +4 -4
  24. package/lib/tui-compat-workspaces.js +1 -1
  25. package/lib/tui-compat.js +5 -5
  26. package/lib/{workspaces-XY7UdfXp.js → workspaces-DOatJRYr.js} +2 -2
  27. package/package.json +30 -29
  28. package/runtime/manifest.json +2 -2
  29. package/runtime/node_modules/@deepseek-ai/dsh-agent-default-model/lib/index.js +118 -22
  30. package/runtime/node_modules/@deepseek-ai/dsh-agent-default-model/lib/types/index.d.ts +20 -3
  31. package/runtime/node_modules/@deepseek-ai/dsh-api-remotes/lib/client.js +113 -39
  32. package/runtime/node_modules/@deepseek-ai/dsh-api-remotes/lib/index.js +4 -0
  33. package/runtime/node_modules/@deepseek-ai/dsh-api-remotes/lib/types/remote-events.d.ts +3 -0
  34. package/runtime/node_modules/@deepseek-ai/dsh-api-remotes/lib/types/remote-events.js +1 -0
  35. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/index.js +173 -34
  36. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/typert.host.js +48 -28
  37. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/typert.remote-client.js +24 -19
  38. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/agent.d.ts +9 -1
  39. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/agent.js +36 -6
  40. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/archived-session-gate.js +1 -1
  41. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/catalog.js +4 -1
  42. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/client/sessions/service.d.ts +1 -1
  43. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/commands.d.ts +2 -2
  44. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/commands.js +24 -8
  45. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/index.d.ts +4 -3
  46. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/index.js +31 -14
  47. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/list.d.ts +6 -0
  48. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/list.js +89 -4
  49. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/remote-events.d.ts +1 -1
  50. package/runtime/node_modules/@deepseek-ai/dsh-api-session-controller/lib/types/types.d.ts +18 -1
  51. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/client.js +30 -0
  52. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/index.js +83 -2
  53. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/typert.host.js +104 -11
  54. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/typert.remote-client.d.ts +5 -1
  55. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/typert.remote-client.js +78 -11
  56. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/client/model.d.ts +14 -1
  57. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/client/model.js +17 -0
  58. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/client/service.d.ts +15 -0
  59. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/client/service.js +12 -0
  60. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/commands.d.ts +13 -1
  61. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/commands.js +44 -2
  62. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/index.d.ts +13 -1
  63. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/index.js +22 -0
  64. package/runtime/node_modules/@deepseek-ai/dsh-api-workspace-controller/lib/types/types.d.ts +17 -0
  65. package/runtime/node_modules/@deepseek-ai/dsh-app-boot/lib/index.js +2 -1
  66. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/index.js +167 -44
  67. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/types/index.d.ts +14 -6
  68. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/types/linux-lock.d.ts +18 -0
  69. package/runtime/node_modules/@deepseek-ai/dsh-atomic-write/lib/types/windows-lock.d.ts +7 -0
  70. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/lib/client.js +931 -7
  71. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/lib/index.js +57 -19
  72. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/lib/types/client/index.d.ts +5 -2
  73. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/lib/types/config.d.ts +12 -0
  74. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/lib/types/events.d.ts +4 -4
  75. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/lib/types/index.d.ts +3 -8
  76. package/runtime/node_modules/@deepseek-ai/dsh-client-hmr/package.json +6 -2
  77. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-commands/lib/client.js +10 -0
  78. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/types/client/present-open.d.ts +1 -1
  79. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/client.js +8 -4
  80. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-account/lib/client.js +2 -2
  81. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +1 -1
  82. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +1 -1
  83. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/client.js +1917 -206
  84. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/ArchivedSessions.d.ts +13 -0
  85. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/contract/slots.d.ts +4 -0
  86. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/locales.d.ts +40 -0
  87. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/recent.d.ts +11 -0
  88. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/rows/RecentList.d.ts +15 -0
  89. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/rows/Rows.d.ts +6 -1
  90. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/session-actions/DeleteSession.d.ts +24 -0
  91. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/stores.d.ts +2 -2
  92. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/types/client/tree.d.ts +15 -13
  93. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-workspace/package.json +3 -1
  94. package/runtime/node_modules/@deepseek-ai/dsh-commands/lib/index.js +16 -0
  95. package/runtime/node_modules/@deepseek-ai/dsh-commands/lib/typert.host.js +7 -3
  96. package/runtime/node_modules/@deepseek-ai/dsh-commands/lib/typert.remote-client.js +6 -2
  97. package/runtime/node_modules/@deepseek-ai/dsh-commands/lib/types/index.js +20 -0
  98. package/runtime/node_modules/@deepseek-ai/dsh-commands/lib/types/types.d.ts +5 -0
  99. package/runtime/node_modules/@deepseek-ai/dsh-cordis-client-runner/lib/client.js +5 -2
  100. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/adapter.d.ts +1 -1
  101. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek/lib/types/config.d.ts +3 -3
  102. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek-account/lib/types/config.d.ts +2 -2
  103. package/runtime/node_modules/@deepseek-ai/dsh-llm-deepseek-api-key/lib/types/config.d.ts +2 -2
  104. package/runtime/node_modules/@deepseek-ai/dsh-schedule/lib/index.js +4 -0
  105. package/runtime/node_modules/@deepseek-ai/dsh-schedule/lib/typert.host.js +5 -5
  106. package/runtime/node_modules/@deepseek-ai/dsh-schedule/lib/typert.remote-client.js +5 -5
  107. package/runtime/node_modules/@deepseek-ai/dsh-schedule/lib/types/index.js +5 -0
  108. package/runtime/node_modules/@deepseek-ai/dsh-session/lib/types/index.d.ts +21 -0
  109. package/runtime/node_modules/@deepseek-ai/dsh-session-persistence/lib/index.js +12 -0
  110. package/runtime/node_modules/@deepseek-ai/dsh-session-persistence/lib/types/index.d.ts +15 -0
  111. package/runtime/node_modules/@deepseek-ai/dsh-session-persistence-jsonl/lib/index.js +95 -2
  112. package/runtime/node_modules/@deepseek-ai/dsh-session-persistence-jsonl/lib/types/index.d.ts +3 -1
  113. package/runtime/node_modules/@deepseek-ai/dsh-session-projection-cache/lib/index.js +25 -1
  114. package/runtime/node_modules/@deepseek-ai/dsh-session-projection-cache/lib/types/index.d.ts +1 -0
  115. package/runtime/node_modules/@deepseek-ai/dsh-session-projection-cache/package.json +2 -1
  116. package/runtime/node_modules/@deepseek-ai/dsh-session-query/lib/index.js +13 -0
  117. package/runtime/node_modules/@deepseek-ai/dsh-session-query/lib/types/observation.d.ts +5 -0
  118. package/runtime/node_modules/@deepseek-ai/dsh-session-query-sqlite/lib/index.js +26 -0
  119. package/runtime/node_modules/@deepseek-ai/dsh-session-query-sqlite/package.json +2 -1
  120. package/runtime/node_modules/@deepseek-ai/dsh-storage-domain/lib/index.js +5 -1
  121. package/runtime/node_modules/@deepseek-ai/dsh-storage-domain/lib/types/domain.d.ts +2 -2
  122. package/runtime/node_modules/@deepseek-ai/dsh-subagent/lib/types/control.d.ts +1 -1
  123. package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/index.js +106 -56
  124. package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/config.d.ts +2 -2
  125. package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/index.d.ts +2 -1
  126. package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/services/tianti/config.d.ts +4 -2
  127. package/runtime/node_modules/@deepseek-ai/dsh-tianti/lib/types/tools.d.ts +2 -2
  128. package/runtime/node_modules/@deepseek-ai/dsh-tianti/package.json +2 -0
  129. package/runtime/node_modules/@deepseek-ai/dsh-tool-cordis/lib/types/api-catalog.js +104 -13
  130. package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/index.js +215 -4
  131. package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/invariant.js +6 -1
  132. package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/index.d.ts +31 -0
  133. package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/index.js +219 -2
  134. package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/spec.d.ts +4 -0
  135. package/runtime/node_modules/@deepseek-ai/dsh-workspace/lib/types/spec.js +4 -0
  136. package/scripts/launch.mjs +8 -0
@@ -202,7 +202,12 @@ let ScheduleService = (() => {
202
202
  const stop = ctx.on('workspace/session-stop', async ({ sessionId }) => {
203
203
  await this.stopSessionTasks(sessionId);
204
204
  });
205
+ const deleted = ctx.on('session/deleting', async ({ sessionIds }) => {
206
+ for (const sessionId of sessionIds)
207
+ await this.stopSessionTasks(sessionId);
208
+ });
205
209
  return () => {
210
+ deleted();
206
211
  stop();
207
212
  activity();
208
213
  };
@@ -50,6 +50,27 @@ declare module '@deepseek-ai/cordis' {
50
50
  * @mode emit
51
51
  */
52
52
  'session/disposed'(this: Scoped<Session>, session: Session): void;
53
+ /** Ask capability owners to release idle, archived Sessions before writer reservation.
54
+ * Owners release only handles they own; foreign or running Sessions remain protected.
55
+ * @param request - confirmed root and owned subagent identities.
56
+ * @mode parallel
57
+ */
58
+ 'session/delete-release'(request: {
59
+ readonly sessionIds: readonly SessionId[];
60
+ }): Promise<void> | void;
61
+ /** Permanently remove secondary Session records after stored payload deletion.
62
+ * Listeners are idempotent; failures leave the deletion journal pending for recovery.
63
+ * @param request - confirmed root and owned subagent identities.
64
+ * @mode parallel
65
+ */
66
+ 'session/deleting'(request: {
67
+ readonly sessionIds: readonly SessionId[];
68
+ }): Promise<void> | void;
69
+ /** Permanent deletion observed locally or from shared storage.
70
+ * @param sessionIds - removed identities, including owned descendants.
71
+ * @mode emit
72
+ */
73
+ 'session/deleted'(sessionIds: readonly SessionId[]): void;
53
74
  /**
54
75
  * Post-commit, fire-and-forget append feed. The listener snapshot resolves
55
76
  * before the log push, but callbacks run after it; observer failures are
@@ -264,6 +264,18 @@ var SessionPersistence = class extends Service {
264
264
  constructor(ctx) {
265
265
  super(ctx, "sessionPersistence");
266
266
  }
267
+ /**
268
+ * Reserve every target's write ownership before any deletion is committed.
269
+ * The caller persists its recovery journal while this reservation is held,
270
+ * calls commit, and always disposes it to release ownership. Backends must
271
+ * retain deletion identities so old generations cannot reappear or be recreated.
272
+ * @param sessionIds - complete deletion scope, including owned descendants.
273
+ * @returns an exclusive deletion reservation; missing targets are idempotent.
274
+ * @throws when a writer holds any target, or the backend does not support deletion.
275
+ */
276
+ prepareDeletion(sessionIds) {
277
+ return Promise.reject(/* @__PURE__ */ new Error(`Session persistence does not support permanent deletion (${sessionIds.length} targets)`));
278
+ }
267
279
  };
268
280
  //#endregion
269
281
  export { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionOwnershipLostError, SessionPersistence, SessionPersistence as default, SessionPersistenceCorruptionError, SessionPersistenceNotFoundError, SessionPersistenceRevision, SessionReadOnlyError, assertContiguous, assertStoredId, assertVersion, materializeAppendBatch, materializeCreateHeader, sessionFormatVersionRefusal, validateStoredEvents };
@@ -71,6 +71,11 @@ export interface SessionPersistenceListOptions {
71
71
  /** Optional cancellation for backend listing work. */
72
72
  readonly signal?: AbortSignal;
73
73
  }
74
+ /** Exclusive reservation for an idempotent, recoverable deletion of stored Sessions. */
75
+ export interface SessionDeletion extends AsyncDisposable {
76
+ /** Permanently remove owned payloads; already deleted identities remain deleted. */
77
+ commit(): Promise<void>;
78
+ }
74
79
  declare module '@deepseek-ai/cordis' {
75
80
  interface Context {
76
81
  sessionPersistence: SessionPersistence;
@@ -155,6 +160,16 @@ export declare abstract class SessionPersistence extends Service {
155
160
  * @returns one snapshot per stored session.
156
161
  */
157
162
  abstract list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>;
163
+ /**
164
+ * Reserve every target's write ownership before any deletion is committed.
165
+ * The caller persists its recovery journal while this reservation is held,
166
+ * calls commit, and always disposes it to release ownership. Backends must
167
+ * retain deletion identities so old generations cannot reappear or be recreated.
168
+ * @param sessionIds - complete deletion scope, including owned descendants.
169
+ * @returns an exclusive deletion reservation; missing targets are idempotent.
170
+ * @throws when a writer holds any target, or the backend does not support deletion.
171
+ */
172
+ prepareDeletion(sessionIds: readonly SessionId[]): Promise<SessionDeletion>;
158
173
  }
159
174
  export default SessionPersistence;
160
175
  //# sourceMappingURL=index.d.ts.map
@@ -2,7 +2,7 @@ import z from "@deepseek-ai/schemastery";
2
2
  import { SessionFormatUnsupportedMigrationError, createSessionFormatCatalogWithChildren, historicalSessionFormatCatalog, sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
3
3
  import { readdirSync } from "node:fs";
4
4
  import { link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rm, stat, truncate } from "node:fs/promises";
5
- import { basename, dirname, isAbsolute, join, parse, resolve, toNamespacedPath } from "node:path";
5
+ import { basename, dirname, isAbsolute, join, parse, resolve, sep, toNamespacedPath } from "node:path";
6
6
  import { performance } from "node:perf_hooks";
7
7
  import { scheduler } from "node:timers/promises";
8
8
  import { createHash, randomBytes } from "node:crypto";
@@ -2282,6 +2282,8 @@ async function prepareCatalogFacts(parentId, sources, compression, signal) {
2282
2282
  * log, so the memo only needs the sessions in flight between those steps.
2283
2283
  */
2284
2284
  const COLD_LOG_MEMO_MAX_ENTRIES = 2;
2285
+ /** Retained with the stable lock inode after payload deletion. Contains no conversation data. */
2286
+ const DELETED_FILENAME = ".deleted";
2285
2287
  const DEFAULT_COMPRESSION = "zstd";
2286
2288
  /**
2287
2289
  * Internal scheduling constant, not deployment configuration: balance
@@ -2434,6 +2436,7 @@ var JsonlSessionPersistence = class extends SessionPersistence {
2434
2436
  toHeaderLine(snapshot, options?.inheritedEventCount);
2435
2437
  const inheritedEventCount = SessionLogOffset(options?.inheritedEventCount ?? 0);
2436
2438
  await this.ensureRootEncoding();
2439
+ if (await this.wasDeleted(header.id)) throw new SessionAlreadyExistsError(header.id);
2437
2440
  options?.signal?.throwIfAborted();
2438
2441
  if (this.tracker.hasPending(snapshot.id) || await this.findLog(snapshot.id, options?.signal) !== void 0) throw new SessionAlreadyExistsError(snapshot.id);
2439
2442
  options?.signal?.throwIfAborted();
@@ -2596,6 +2599,88 @@ var JsonlSessionPersistence = class extends SessionPersistence {
2596
2599
  signal?.throwIfAborted();
2597
2600
  return snapshots;
2598
2601
  }
2602
+ async prepareDeletion(sessionIds) {
2603
+ const claimed = [];
2604
+ const held = [];
2605
+ let closed = false;
2606
+ const release = async () => {
2607
+ if (closed) return;
2608
+ closed = true;
2609
+ try {
2610
+ const failures = (await Promise.allSettled(held.map((item) => item.lease.release()))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
2611
+ if (failures.length > 0) throw new AggregateError(failures, "Session deletion lock release failed");
2612
+ } finally {
2613
+ for (const id of claimed) this.tracker.releaseClaim(id);
2614
+ }
2615
+ };
2616
+ try {
2617
+ await this.ensureRootEncoding();
2618
+ const root = await realpath(this.root).catch((error) => {
2619
+ if (isENOENT(error)) return this.root;
2620
+ throw error;
2621
+ });
2622
+ for (const id of [...new Set(sessionIds)].sort()) {
2623
+ this.tracker.claimWrite(id);
2624
+ claimed.push(id);
2625
+ await this.stat(id);
2626
+ for (const project of await this.listProjectDirs()) {
2627
+ const dir = join(project, encodeSegment(id));
2628
+ const info = await lstat(dir).catch((error) => {
2629
+ if (isENOENT(error)) return void 0;
2630
+ throw error;
2631
+ });
2632
+ if (info === void 0) continue;
2633
+ if (!info.isDirectory()) throw new Error("Session deletion requires an owned directory");
2634
+ const resolved = await realpath(dir).catch((error) => {
2635
+ if (isENOENT(error)) return void 0;
2636
+ throw error;
2637
+ });
2638
+ if (resolved === void 0) continue;
2639
+ if (!resolved.startsWith(root + sep)) throw new Error("Session deletion path escapes its storage root");
2640
+ held.push({
2641
+ dir,
2642
+ lease: await SessionWriteLease.acquire(dir, id)
2643
+ });
2644
+ }
2645
+ }
2646
+ return {
2647
+ commit: async () => {
2648
+ if (closed) throw new Error("Session deletion reservation is closed");
2649
+ for (const { dir } of held) {
2650
+ const marker = await open(join(dir, DELETED_FILENAME), "a", 384);
2651
+ try {
2652
+ await marker.sync();
2653
+ } finally {
2654
+ await marker.close();
2655
+ }
2656
+ await this.syncDirPosix(dir);
2657
+ for (const entry of await readdir(dir)) if (entry !== "session.lock" && entry !== DELETED_FILENAME) await rm(join(dir, entry), {
2658
+ recursive: true,
2659
+ force: true
2660
+ });
2661
+ await this.syncDirPosix(dir);
2662
+ }
2663
+ for (const id of claimed) {
2664
+ this.coldLogMemo.delete(id);
2665
+ this.migrationPreparations.delete(id);
2666
+ }
2667
+ },
2668
+ [Symbol.asyncDispose]: release
2669
+ };
2670
+ } catch (error) {
2671
+ await release();
2672
+ throw error;
2673
+ }
2674
+ }
2675
+ async wasDeleted(id) {
2676
+ for (const project of await this.listProjectDirs()) try {
2677
+ await stat(join(project, encodeSegment(id), DELETED_FILENAME));
2678
+ return true;
2679
+ } catch (error) {
2680
+ if (!isENOENT(error)) throw error;
2681
+ }
2682
+ return false;
2683
+ }
2599
2684
  /** Resolve and read one stored log, refusing loudly when the artifact is absent. */
2600
2685
  async requireStoredLog(id, signal) {
2601
2686
  const selected = await this.findLog(id, signal);
@@ -2931,7 +3016,14 @@ var JsonlSessionPersistence = class extends SessionPersistence {
2931
3016
  */
2932
3017
  async acquireWriteLease(header) {
2933
3018
  await this.rejectOppositeArtifact(header.cwd, header.id);
2934
- return this.acquireLease(header.id, header.cwd);
3019
+ const lease = await this.acquireLease(header.id, header.cwd);
3020
+ try {
3021
+ if (await this.wasDeleted(header.id)) throw new SessionAlreadyExistsError(header.id);
3022
+ return lease;
3023
+ } catch (error) {
3024
+ await lease.release();
3025
+ throw error;
3026
+ }
2935
3027
  }
2936
3028
  /** Decode complete frames and retain complete JSONL records from a torn final frame. */
2937
3029
  async readZstdPrefix(buffer, signal) {
@@ -3334,6 +3426,7 @@ var JsonlSessionPersistence = class extends SessionPersistence {
3334
3426
  throw error;
3335
3427
  }
3336
3428
  signal?.throwIfAborted();
3429
+ if (entries.some((entry) => entry.name === DELETED_FILENAME)) return void 0;
3337
3430
  const generations = [];
3338
3431
  const opposite = [];
3339
3432
  for (const entry of entries) {
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { Context } from '@deepseek-ai/cordis';
9
9
  import z from '@deepseek-ai/schemastery';
10
- import { SessionPersistence, type SessionAccess, type SessionHandle, type SessionHandleReadResult, type SessionPersistenceCreateOptions, type SessionPersistenceListOptions, type SessionPersistenceOpenOptions, type SessionPersistenceSnapshot, type SessionPersistenceStatOptions, type SessionPersistenceRevision as PersistenceRevision } from '@deepseek-ai/dsh-session-persistence';
10
+ import { SessionPersistence, type SessionAccess, type SessionHandle, type SessionDeletion, type SessionHandleReadResult, type SessionPersistenceCreateOptions, type SessionPersistenceListOptions, type SessionPersistenceOpenOptions, type SessionPersistenceSnapshot, type SessionPersistenceStatOptions, type SessionPersistenceRevision as PersistenceRevision } from '@deepseek-ai/dsh-session-persistence';
11
11
  import { JsonlSessionHandle } from './storage.ts';
12
12
  import { SessionWriteLease } from './lease.ts';
13
13
  import type { SessionEvent, SessionId, SessionHeader, SessionLogOffset as SessionLogOffsetType } from '@deepseek-ai/dsh-session';
@@ -118,6 +118,8 @@ declare class JsonlSessionPersistence extends SessionPersistence {
118
118
  * @returns one snapshot per session, in no promised order.
119
119
  */
120
120
  list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>;
121
+ prepareDeletion(sessionIds: readonly SessionId[]): Promise<SessionDeletion>;
122
+ private wasDeleted;
121
123
  /** Resolve and read one stored log, refusing loudly when the artifact is absent. */
122
124
  private requireStoredLog;
123
125
  /** Probe the memo and otherwise decode one historical generation under backend cancellation. */
@@ -155,6 +155,7 @@ var SessionProjectionCache = class extends Service {
155
155
  listingTable;
156
156
  writes = Promise.resolve();
157
157
  closed = false;
158
+ deleted = /* @__PURE__ */ new Set();
158
159
  dirty = /* @__PURE__ */ new Map();
159
160
  constructor(ctx, config) {
160
161
  super(ctx, "sessionProjectionCache");
@@ -173,6 +174,24 @@ var SessionProjectionCache = class extends Service {
173
174
  await this.writes;
174
175
  }, "sessionProjectionCache.writeDrain");
175
176
  this.installWritePath();
177
+ const cleanup = (ids) => {
178
+ for (const id of ids) this.deleted.add(id);
179
+ return this.enqueueWrite(async () => {
180
+ for (const id of ids) {
181
+ await this.requireTable().delete(id);
182
+ await this.requireListingTable().delete(id);
183
+ }
184
+ });
185
+ };
186
+ this.ctx.on("session/deleting", ({ sessionIds }) => cleanup(sessionIds));
187
+ this.ctx.on("session/deleted", (ids) => {
188
+ cleanup(ids).catch((error) => {
189
+ this.ctx.logger.warn(`session projection cache: deletion cleanup failed: ${String(error)}`);
190
+ });
191
+ });
192
+ this.ctx.inject(["workspaceRegistry"], async (ctx) => {
193
+ await cleanup(ctx.workspaceRegistry.deletedSessionIds);
194
+ });
176
195
  }
177
196
  /**
178
197
  * The stored record for one session, accepted only when its bound log
@@ -185,6 +204,7 @@ var SessionProjectionCache = class extends Service {
185
204
  * @returns the identity-matching record, or `undefined` (absent or unrelated).
186
205
  */
187
206
  async recordFor(id, expected) {
207
+ if (this.deleted.has(id)) return void 0;
188
208
  const record = await this.requireTable().read(id);
189
209
  if (record === void 0) return void 0;
190
210
  return identityMatches(record.identity, expected) ? record : void 0;
@@ -240,14 +260,17 @@ var SessionProjectionCache = class extends Service {
240
260
  /** Read a small listing record, deriving it once from an older full checkpoint when needed. */
241
261
  async listingRecord(id) {
242
262
  await this.writes;
263
+ if (this.deleted.has(id)) return void 0;
243
264
  const signature = this.ctx.sessionProjections.checkpointViewSignature();
244
265
  const stored = await this.requireListingTable().read(id);
245
266
  if (stored?.signature === signature) return stored.checkpoint;
267
+ if (this.deleted.has(id)) return void 0;
246
268
  const record = await this.requireTable().read(id);
247
269
  if (record === void 0) return void 0;
248
270
  const listing = this.listingOf(record, signature);
249
271
  await this.enqueueWrite(async () => {
250
- if (await this.requireTable().read(id) === record) await this.requireListingTable().put(id, listing);
272
+ const current = await this.requireTable().read(id);
273
+ if (!this.deleted.has(id) && current === record) await this.requireListingTable().put(id, listing);
251
274
  }).catch((error) => {
252
275
  this.ctx.logger.warn(`session projection cache: listing write-back for "${id}" failed: ${String(error)}`);
253
276
  });
@@ -402,6 +425,7 @@ var SessionProjectionCache = class extends Service {
402
425
  rows: detached
403
426
  };
404
427
  await this.enqueueWrite(async () => {
428
+ if (this.deleted.has(id)) return;
405
429
  await this.requireTable().put(id, record);
406
430
  const listing = this.listingOf(record, this.ctx.sessionProjections.checkpointViewSignature());
407
431
  await this.requireListingTable().put(id, listing);
@@ -57,6 +57,7 @@ export declare class SessionProjectionCache extends Service {
57
57
  private listingTable?;
58
58
  private writes;
59
59
  private closed;
60
+ private readonly deleted;
60
61
  private readonly dirty;
61
62
  constructor(ctx: Context, config: Config);
62
63
  /** Open both on-demand domains and install the write-behind listeners. */
@@ -45,6 +45,7 @@
45
45
  "@deepseek-ai/dsh-session-projection": "workspace:*",
46
46
  "@deepseek-ai/dsh-storage": "workspace:*",
47
47
  "@deepseek-ai/dsh-storage-domain": "workspace:*",
48
- "@deepseek-ai/dsh-storage-json": "workspace:*"
48
+ "@deepseek-ai/dsh-storage-json": "workspace:*",
49
+ "@deepseek-ai/dsh-workspace": "workspace:*"
49
50
  }
50
51
  }
@@ -331,6 +331,13 @@ var SessionObservationReader = class {
331
331
  cacheCapacity;
332
332
  cache = /* @__PURE__ */ new Map();
333
333
  /**
334
+ * Drop deleted identities while existing observation leases settle independently.
335
+ * @param ids - permanently deleted Session identities.
336
+ */
337
+ forget(ids) {
338
+ for (const id of ids) this.cache.delete(id);
339
+ }
340
+ /**
334
341
  * @param ctx - context carrying Session and optional persistence/projection services.
335
342
  * @param cacheCapacity - maximum unpinned cold observations retained for reuse.
336
343
  */
@@ -1055,6 +1062,12 @@ var SessionQueryEngine = class extends Service {
1055
1062
  if (!Number.isSafeInteger(preparedSessionCacheSize) || preparedSessionCacheSize < 1) throw new SessionQueryError("session-query: preparedSessionCacheSize must be a positive safe integer", "SESSION_QUERY_INVALID_CONFIG");
1056
1063
  this._corpus = new SessionCorpus(ctx, persistedReadConcurrency);
1057
1064
  this._observations = new SessionObservationReader(ctx, preparedSessionCacheSize);
1065
+ ctx.on("session/deleting", ({ sessionIds }) => {
1066
+ this._observations.forget(sessionIds);
1067
+ });
1068
+ ctx.on("session/deleted", (ids) => {
1069
+ this._observations.forget(ids);
1070
+ });
1058
1071
  }
1059
1072
  /**
1060
1073
  * Observe one exact live or prepared Session without a persistence listing preflight.
@@ -50,6 +50,11 @@ export declare class SessionObservationReader {
50
50
  private readonly ctx;
51
51
  private readonly cacheCapacity;
52
52
  private readonly cache;
53
+ /**
54
+ * Drop deleted identities while existing observation leases settle independently.
55
+ * @param ids - permanently deleted Session identities.
56
+ */
57
+ forget(ids: readonly SessionId[]): void;
53
58
  /**
54
59
  * @param ctx - context carrying Session and optional persistence/projection services.
55
60
  * @param cacheCapacity - maximum unpinned cold observations retained for reuse.
@@ -529,6 +529,32 @@ var SqliteSessionQueryEngine = class extends SessionQueryEngine {
529
529
  return () => this._optionalPersistenceFiber.dispose();
530
530
  }, "sessionQuerySqlite.optionalPersistence");
531
531
  ctx.effect(() => async () => this.close(), "sessionQuerySqlite.close");
532
+ const cleanup = (ids) => this._serialized(void 0, async () => {
533
+ if (ids.length === 0 || this.config.openAt === "never") return;
534
+ await this._ensureReady(void 0);
535
+ const db = this._requireDb();
536
+ db.exec("BEGIN IMMEDIATE");
537
+ try {
538
+ for (const id of ids) {
539
+ this._deleteSession("persisted", id);
540
+ this._deleteSession("live", id);
541
+ }
542
+ db.exec("COMMIT");
543
+ } catch (error) {
544
+ db.exec("ROLLBACK");
545
+ throw error;
546
+ }
547
+ this._globalGeneration += 1;
548
+ });
549
+ ctx.on("session/deleting", ({ sessionIds }) => cleanup(sessionIds));
550
+ ctx.on("session/deleted", (ids) => {
551
+ cleanup(ids).catch((error) => {
552
+ ctx.logger.warn(`session query: deletion cleanup failed: ${String(error)}`);
553
+ });
554
+ });
555
+ ctx.inject(["workspaceRegistry"], async (ctx) => {
556
+ await cleanup(ctx.workspaceRegistry.deletedSessionIds);
557
+ });
532
558
  }
533
559
  /** Open eagerly only when activation owns the configured readiness boundary. */
534
560
  async [Service.init]() {
@@ -47,6 +47,7 @@
47
47
  "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*",
48
48
  "@deepseek-ai/dsh-session-query": "workspace:*",
49
49
  "@deepseek-ai/cordis": "workspace:~",
50
- "@deepseek-ai/dsh-session-projection": "workspace:*"
50
+ "@deepseek-ai/dsh-session-projection": "workspace:*",
51
+ "@deepseek-ai/dsh-workspace": "workspace:*"
51
52
  }
52
53
  }
@@ -337,9 +337,13 @@ var KvTableImpl = class {
337
337
  }
338
338
  delete(key) {
339
339
  return this.host.enqueue(async () => {
340
- if (!this.records.has(key)) return false;
340
+ if (!(this.records.has(key) || this.host.readStored !== void 0 && await this.host.unit.readRecord(this.tableName, key) !== void 0)) {
341
+ this.loaded.add(key);
342
+ return false;
343
+ }
341
344
  await this.host.unit.deleteRecord(this.tableName, key);
342
345
  this.records.delete(key);
346
+ this.loaded.add(key);
343
347
  this.host.emitChanged({
344
348
  domain: this.host.domainName,
345
349
  table: this.tableName,
@@ -115,8 +115,8 @@ export interface Domain<S extends DomainSpec> {
115
115
  */
116
116
  close(): Promise<void>;
117
117
  }
118
- /** Keyed reads and complete replacements without whole-table enumeration. */
119
- export type RecordTable<K extends string, V> = Pick<KvTable<K, V>, 'read' | 'put'>;
118
+ /** Keyed reads, complete replacements, and removals without whole-table enumeration. */
119
+ export type RecordTable<K extends string, V> = Pick<KvTable<K, V>, 'read' | 'put' | 'delete'>;
120
120
  /** Per-record domain opened without loading its records. No global slot is exposed. */
121
121
  export interface RecordDomain<S extends DomainSpec> {
122
122
  /** Domain name from the spec. */
@@ -12,8 +12,8 @@ declare const CONTROL_ID_SCHEMAS: {
12
12
  childSessionId: z.ZodString;
13
13
  mode: z.ZodLiteral<"continuable">;
14
14
  delivery: z.ZodEnum<{
15
- steer: "steer";
16
15
  queue: "queue";
16
+ steer: "steer";
17
17
  }>;
18
18
  }, z.core.$strip>;
19
19
  readonly 'subagent.interrupt': z.ZodObject<{