@takboo/dsh-session-bin 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - 删除前展示确认和阻止原因,删除后显示逐项结果;中断或断线后可查询结果并明确继续。
13
13
  - 中英文界面,使用宿主控件、浅色和深色主题。
14
14
 
15
- 首发版本为 `0.1.0`,包名为 `@takboo/dsh-session-bin`。GitHub Release 提供预编译 tarball;npm 发布由维护者完成,dsh-market 收录另行提交。核心功能已完成跨平台验收;运行证据见 [Host 验证](docs/verification/host-lifecycle.md#跨平台生产删除资格晋级验证)和[客户端验证](docs/verification/client-interface.md#跨平台生产删除客户端验证)。
15
+ 当前版本为 `0.1.1`,包名为 `@takboo/dsh-session-bin`。GitHub Release 提供预编译 tarball;npm 通过 trusted publishing 自动发布同一份产物,dsh-market 收录另行提交。核心功能已完成跨平台验收;运行证据见 [Host 验证](docs/verification/host-lifecycle.md#跨平台生产删除资格晋级验证)和[客户端验证](docs/verification/client-interface.md#跨平台生产删除客户端验证)。
16
16
 
17
17
  ## 界面预览
18
18
 
@@ -38,7 +38,7 @@
38
38
 
39
39
  ```sh
40
40
  mise exec -- pnpm exec npm pack --ignore-scripts
41
- dsh plugin --profile web add ./takboo-dsh-session-bin-0.1.0.tgz
41
+ dsh plugin --profile web add ./takboo-dsh-session-bin-0.1.1.tgz
42
42
  ```
43
43
 
44
44
  以上命令使用已安装的 DSH `0.2.0-rc.2`,将完整插件 bundle 安装到 `web` profile;该路径已通过真实 CLI/GUI 验证。启动或重新启动该 profile 后,从侧栏打开“会话回收站”(Session Bin)。tarball 包含预编译 Host 和 Client,使用者安装时无需编译 TypeScript。
@@ -49,7 +49,7 @@ dsh plugin --profile web add ./takboo-dsh-session-bin-0.1.0.tgz
49
49
  dsh plugin --profile web remove @takboo/dsh-session-bin
50
50
  ```
51
51
 
52
- 也可下载 [v0.1.0 Release](https://github.com/takboo/dsh-session-bin/releases/tag/v0.1.0) 中的 `dsh-session-bin.tgz`,通过同一个 `dsh plugin --profile web add` 命令安装。npm 发布后可使用 `dsh plugin --profile web add @takboo/dsh-session-bin@0.1.0`。
52
+ 也可下载 [v0.1.1 Release](https://github.com/takboo/dsh-session-bin/releases/tag/v0.1.1) 中的 `dsh-session-bin.tgz`,通过同一个 `dsh plugin --profile web add` 命令安装。也可从 npm 安装:`dsh plugin --profile web add @takboo/dsh-session-bin@0.1.1`。
53
53
 
54
54
  如安装过早期未加 scope 的开发包,先从同一 profile 卸载 `dsh-session-bin`,再安装 scoped 包,避免重复注册;原生归档和已有操作记录保留。分发契约见[发布文档](docs/release.md)。
55
55
 
@@ -68,7 +68,7 @@ dsh plugin --profile web remove @takboo/dsh-session-bin
68
68
 
69
69
  **“清空全部归档”包含点击时的全部归档,搜索和工作区筛选不会缩小其范围。** 批量删除固定开始时的对象,后来新增的归档不会加入。
70
70
 
71
- 永久删除不可撤销,仅允许已归档、无活动且无未释放读写引用的冷会话;宿主仍加载的会话会被阻止。清除范围是目标会话日志及已声明的会话元数据、索引;共享附件、外部副本和独立 fork 会保留。插件不提供共享附件垃圾回收或安全擦除保证,也不支持自动定期清空、跨设备同步或多 Host 协调。可通过 Host 配置 `permanentDeletion: false` 关闭永久删除,配置说明见 [Host 生命周期](docs/host-lifecycle.md)。
71
+ 永久删除不可撤销,仅允许已归档、无活动且无未释放读写引用的冷会话;删除准备会释放由 API Session 激活且确认空闲的目标 Agent,再复核冷会话条件;活动任务、其他所有者的 Agent 和未释放的读写引用仍会阻止删除。清除范围是目标会话日志及已声明的会话元数据、索引;共享附件、外部副本和独立 fork 会保留。插件不提供共享附件垃圾回收或安全擦除保证,也不支持自动定期清空、跨设备同步或多 Host 协调。可通过 Host 配置 `permanentDeletion: false` 关闭永久删除,配置说明见 [Host 生命周期](docs/host-lifecycle.md)。
72
72
 
73
73
  ## 开发入门
74
74
 
@@ -64,6 +64,9 @@ export declare class NativeMetadataAdapter {
64
64
  private readonly targets;
65
65
  private readonly retained;
66
66
  private readonly followers;
67
+ private readonly followerStops;
68
+ private readonly activationTarget;
69
+ private readonly apiHandles;
67
70
  private readonly operations;
68
71
  private readonly queueTarget;
69
72
  private readonly receivers;
@@ -78,6 +81,7 @@ export declare class NativeMetadataAdapter {
78
81
  private readonly queryProvider;
79
82
  private readonly cacheTable;
80
83
  private readonly cacheDirectory;
84
+ private readonly agents;
81
85
  private moveCacheDocument;
82
86
  private clearCacheStage;
83
87
  private removeCacheStage;
@@ -90,6 +94,9 @@ export declare class NativeMetadataAdapter {
90
94
  private refuse;
91
95
  private assertInstances;
92
96
  private assertCold;
97
+ /** Explicit deletion preparation may unload an idle API-owned lifecycle.
98
+ * No bare registry Agent, foreign handle, or unknown reader is disposed. */
99
+ releaseIdle(id: string): Promise<void>;
93
100
  private cacheView;
94
101
  private openCacheDocument;
95
102
  private pathAbsent;
package/dist/index.js CHANGED
@@ -1834,6 +1834,20 @@ async function nativeRetirementSdkFingerprint() {
1834
1834
  if (createHash3("sha256").update(await readFile(join2(dirname(path), file))).digest("hex") !== digest2) return null;
1835
1835
  }
1836
1836
  }
1837
+ const runtime = createRequire(base.resolve("@deepseek-ai/dsh-base/package.json"));
1838
+ for (const [name2, expected] of Object.entries({
1839
+ "dsh-agent": "7b9aa39474de83115cf890dd7f8904cf0d59df0811cb335ab85565a04f1aac93",
1840
+ "dsh-agent-loop": "00c4814d63f3d1e2754eb09144832d19b30499d6ea444730473f740b587948c2"
1841
+ })) {
1842
+ let path;
1843
+ try {
1844
+ path = runtime.resolve(`@deepseek-ai/${name2}/package.json`);
1845
+ } catch (error) {
1846
+ if (error.code === "MODULE_NOT_FOUND") return null;
1847
+ throw error;
1848
+ }
1849
+ if (JSON.parse(await readFile(path, "utf8")).version !== "0.2.0-rc.2" || createHash3("sha256").update(await readFile(join2(dirname(path), "lib/index.js"))).digest("hex") !== expected) return null;
1850
+ }
1837
1851
  return createHash3("sha256").update(JSON.stringify(nativeRetirementSdkSources)).digest("hex");
1838
1852
  }
1839
1853
 
@@ -2448,10 +2462,14 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2448
2462
  this.queryProvider = !query ? "none" : query.config.openAt === "never" ? "sqlite-memory-disabled-0.2.0-rc.2" : "sqlite-0.2.0-rc.2";
2449
2463
  this.cacheTable = cache?.requireTable();
2450
2464
  this.cacheDirectory = this.cacheTable?.host.unit.dir;
2465
+ this.agents = controller ? _NativeMetadataAdapter.get(ctx, "agents") : void 0;
2451
2466
  }
2452
2467
  targets = /* @__PURE__ */ new Set();
2453
2468
  retained = /* @__PURE__ */ new Map();
2454
2469
  followers = /* @__PURE__ */ new Map();
2470
+ followerStops = /* @__PURE__ */ new Map();
2471
+ activationTarget = new AsyncLocalStorage();
2472
+ apiHandles = /* @__PURE__ */ new Map();
2455
2473
  operations = /* @__PURE__ */ new Map();
2456
2474
  queueTarget = new AsyncLocalStorage();
2457
2475
  receivers = new AsyncLocalStorage();
@@ -2466,6 +2484,7 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2466
2484
  queryProvider;
2467
2485
  cacheTable;
2468
2486
  cacheDirectory;
2487
+ agents;
2469
2488
  moveCacheDocument = renameWindowsWriteThrough;
2470
2489
  clearCacheStage = async (handle) => {
2471
2490
  await handle.truncate(0);
@@ -2494,7 +2513,7 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2494
2513
  const disabledMemory = query.config?.openAt === "never" && query.config.path === ":memory:" && query._db === void 0 && query._ready === void 0;
2495
2514
  if (query.constructor.name !== "SqliteSessionQueryEngine" || !enabled && !disabledMemory || typeof query.config?.path !== "string" || !methods(query, ["searchSessions", "listSessions", "observeSession", "_serialized"]) || !(query._observations?.cache instanceof Map) || !methods(query._observations, ["read", "store"])) return void 0;
2496
2515
  }
2497
- if (controller && (controller.constructor.name !== "SessionController" || !(controller.history?.closeFollowers instanceof Set) || !methods(controller.history, ["page", "follow", "sourceFor"]))) return void 0;
2516
+ if (controller && (controller.constructor.name !== "SessionController" || !(controller.history?.closeFollowers instanceof Set) || !methods(controller.history, ["page", "follow", "sourceFor"]) || controller.agents?.constructor.name !== "ApiSessionAgentController" || !methods(controller.agents, ["resumeObserved", "createOrAdopt"]) || !methods(controller.listState ?? {}, ["summarizeCold"]) || !methods(this.get(ctx, "agents") ?? {}, ["create", "resume", "get"]))) return void 0;
2498
2517
  return new _NativeMetadataAdapter(ctx, admission, workspace, sessions, cache, query, controller);
2499
2518
  }
2500
2519
  assert(id) {
@@ -2533,7 +2552,7 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2533
2552
  if (this.guardedMethods.some(({ port, name: name2, replacement }) => port[name2] !== replacement)) {
2534
2553
  this.refuse("native/guard-replaced", "A native metadata admission guard was replaced.");
2535
2554
  }
2536
- if (_NativeMetadataAdapter.get(this.ctx, "workspaceRegistry") !== this.workspace || _NativeMetadataAdapter.get(this.ctx, "sessions") !== this.sessions || _NativeMetadataAdapter.get(this.ctx, "sessionProjectionCache") !== this.cache || _NativeMetadataAdapter.get(this.ctx, "sessionQuery") !== this.query || _NativeMetadataAdapter.get(this.ctx, "sessionController") !== this.controller || this.cache && (this.cache.requireTable() !== this.cacheTable || this.cacheTable.host.unit.dir !== this.cacheDirectory) || this.query && (this.query.config.path !== this.queryConfiguration.path || this.query.config.openAt !== this.queryConfiguration.openAt) || this.queryProvider === "sqlite-memory-disabled-0.2.0-rc.2" && (this.query._db !== void 0 || this.query._ready !== void 0)) {
2555
+ if (_NativeMetadataAdapter.get(this.ctx, "workspaceRegistry") !== this.workspace || _NativeMetadataAdapter.get(this.ctx, "sessions") !== this.sessions || _NativeMetadataAdapter.get(this.ctx, "sessionProjectionCache") !== this.cache || _NativeMetadataAdapter.get(this.ctx, "sessionQuery") !== this.query || _NativeMetadataAdapter.get(this.ctx, "sessionController") !== this.controller || this.controller && _NativeMetadataAdapter.get(this.ctx, "agents") !== this.agents || this.cache && (this.cache.requireTable() !== this.cacheTable || this.cacheTable.host.unit.dir !== this.cacheDirectory) || this.query && (this.query.config.path !== this.queryConfiguration.path || this.query.config.openAt !== this.queryConfiguration.openAt) || this.queryProvider === "sqlite-memory-disabled-0.2.0-rc.2" && (this.query._db !== void 0 || this.query._ready !== void 0)) {
2537
2556
  this.refuse("native/composition-changed", "Native metadata service composition changed.");
2538
2557
  }
2539
2558
  }
@@ -2548,6 +2567,34 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2548
2567
  if ((this.controller?.history.closeFollowers.size ?? 0) > knownFollowers) this.refuse("native/unknown-follow", "A history follower predates native admission tracking.");
2549
2568
  if (this.cache && [...this.cache.dirty.keys()].some((session) => session.id === id)) this.refuse("native/cache-dirty", "Target cache write-behind is not quiescent.");
2550
2569
  }
2570
+ /** Explicit deletion preparation may unload an idle API-owned lifecycle.
2571
+ * No bare registry Agent, foreign handle, or unknown reader is disposed. */
2572
+ async releaseIdle(id) {
2573
+ this.assertInstances();
2574
+ const handle = this.apiHandles.get(id);
2575
+ if (!handle) return;
2576
+ const agent = handle.agent;
2577
+ const registry = this.agents;
2578
+ if (registry.get(id) !== agent || this.sessions.get(id) !== agent.session) return;
2579
+ const activity = await this.admission.withoutBypass(() => this.ctx.waterfall(
2580
+ "workspace/session-activity",
2581
+ { sessionId: id },
2582
+ async () => []
2583
+ ));
2584
+ if (activity.length || agent.constructor.name !== "ReactLoopAgent" || agent.phase?.kind !== "idle" || agent.status !== "idle" || !Array.isArray(agent.inbox?.nextTurn) || !Array.isArray(agent.inbox?.nextStep) || agent.inbox.nextTurn.length || agent.inbox.nextStep.length) {
2585
+ this.refuse("native/session-active", "Native deletion refuses active or queued work.");
2586
+ }
2587
+ if (!this.workspace.archivedSessionIds.includes(id)) this.refuse("native/not-archived", "Only an archived target may be unloaded.");
2588
+ if (registry.get(id) !== agent || this.sessions.get(id) !== agent.session) this.refuse("native/session-live", "The API lifecycle changed.");
2589
+ await this.admission.bypass(id, async () => {
2590
+ await handle.dispose();
2591
+ for (const stop of [...this.followerStops.get(id) ?? []]) await stop();
2592
+ if (this.cache) await this.cache.requireTable().host.enqueue(async () => void 0);
2593
+ const summary = this.controller.listState.summarizeCold(agent.session.header);
2594
+ this.admission.withoutBypass(() => this.ctx.emit("api-session/added", summary));
2595
+ });
2596
+ this.apiHandles.delete(id);
2597
+ }
2551
2598
  async cacheView(table, id, acceptedStage) {
2552
2599
  table.host.assertReadable();
2553
2600
  if (!/^[a-zA-Z0-9_-]+$/.test(id)) this.refuse("native/cache-key-unsupported", "The pinned cache backend cannot own this record key.");
@@ -2912,19 +2959,54 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2912
2959
  }
2913
2960
  if (this.controller) {
2914
2961
  const adapter = this;
2962
+ for (const name2 of ["resumeObserved", "createOrAdopt"]) {
2963
+ this.wrap(this.controller.agents, name2, (original) => (id, ...args) => this.tracked(id, () => this.activationTarget.run(id, async () => original(id, ...args))));
2964
+ }
2965
+ const agents = this.agents;
2966
+ for (const name2 of ["create", "resume"]) {
2967
+ this.wrap(agents, name2, (original) => (options, ...args) => {
2968
+ const id = String(name2 === "create" ? options.sessionId : options.resumeSessionId);
2969
+ return this.tracked(id, async () => {
2970
+ const handle = await original(options, ...args);
2971
+ if (this.activationTarget.getStore() === id && options.parentAgent === void 0 && handle.agent.id === id && handle.agent.session.header.origin !== "subagent" && typeof handle.dispose === "function") this.apiHandles.set(id, handle);
2972
+ return handle;
2973
+ });
2974
+ });
2975
+ }
2976
+ this.restores.push(this.ctx.on("agent/disposed", ({ agent }) => {
2977
+ if (this.apiHandles.get(agent.id)?.agent === agent) this.apiHandles.delete(agent.id);
2978
+ }, { global: true }));
2915
2979
  this.wrap(this.controller.history, "follow", (original) => function(request, signal) {
2916
2980
  const id = String(request.address.sessionId);
2917
2981
  adapter.assert(id);
2918
2982
  return (async function* () {
2919
2983
  adapter.assert(id);
2920
2984
  adapter.increment(adapter.followers, id, 1);
2985
+ const cancellation = new AbortController();
2986
+ const iterator = original(request, AbortSignal.any([signal, cancellation.signal]));
2987
+ const stops = adapter.followerStops.get(id) ?? /* @__PURE__ */ new Set();
2988
+ adapter.followerStops.set(id, stops);
2989
+ let finished = false;
2990
+ const finish = () => {
2991
+ if (finished) return;
2992
+ finished = true;
2993
+ adapter.increment(adapter.followers, id, -1);
2994
+ stops.delete(stop);
2995
+ if (!stops.size) adapter.followerStops.delete(id);
2996
+ };
2997
+ const stop = async () => {
2998
+ cancellation.abort();
2999
+ await iterator.return();
3000
+ finish();
3001
+ };
3002
+ stops.add(stop);
2921
3003
  try {
2922
- for await (const frame of original(request, signal)) {
3004
+ for await (const frame of iterator) {
2923
3005
  adapter.assert(id);
2924
3006
  yield frame;
2925
3007
  }
2926
3008
  } finally {
2927
- adapter.increment(adapter.followers, id, -1);
3009
+ finish();
2928
3010
  }
2929
3011
  })();
2930
3012
  });
@@ -2939,6 +3021,7 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2939
3021
  return () => closing ??= (async () => {
2940
3022
  this.fallbackRetired = new Set([...this.targets].filter((id) => this.admission.isRetired(id)));
2941
3023
  this.disposed = true;
3024
+ this.apiHandles.clear();
2942
3025
  if (this.fallbackRetired.size === 0) {
2943
3026
  for (const restore of [...this.restores].reverse()) restore();
2944
3027
  this.restores.length = 0;
@@ -2957,6 +3040,7 @@ var NativeMetadataAdapter = class _NativeMetadataAdapter {
2957
3040
  this.restores.length = 0;
2958
3041
  this.guardedMethods.length = 0;
2959
3042
  this.fallbackRetired.clear();
3043
+ this.apiHandles.clear();
2960
3044
  }
2961
3045
  async converge(input, expected, request) {
2962
3046
  const snapshot = nativeMetadataSnapshotSchema.parse(input);
@@ -4088,16 +4172,19 @@ var NativeRetirementOwner = class _NativeRetirementOwner {
4088
4172
  async eligibilityInner(id) {
4089
4173
  if (nativeServiceInstance(this.ctx.sessionPersistence) !== this.originalProvider) return "native/composition-changed";
4090
4174
  if (this.admission.isRetired(id)) return "native/session-retired";
4091
- if (this.provider.busy(id)) return "native/persistence-retained";
4092
4175
  try {
4093
4176
  const scope = this.scopes.get(id);
4094
4177
  if (scope) {
4178
+ if (this.provider.busy(id)) return "native/persistence-retained";
4095
4179
  if (scope.blocked) return scope.blocked;
4096
4180
  await this.metadata.capture(id);
4097
4181
  return null;
4098
4182
  }
4099
4183
  const release = await this.admission.acquire(id);
4100
4184
  try {
4185
+ await this.metadata.releaseIdle(id);
4186
+ await this.admission.drain(id);
4187
+ if (this.provider.busy(id)) return "native/persistence-retained";
4101
4188
  await this.metadata.capture(id);
4102
4189
  const snapshot = await this.provider.stat(id);
4103
4190
  if (!snapshot) return "session-not-found";