@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 +4 -4
- package/dist/host/native-retirement-metadata.d.ts +7 -0
- package/dist/index.js +92 -5
- package/dist/index.js.map +2 -2
- package/docs/host-lifecycle.md +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- 删除前展示确认和阻止原因,删除后显示逐项结果;中断或断线后可查询结果并明确继续。
|
|
13
13
|
- 中英文界面,使用宿主控件、浅色和深色主题。
|
|
14
14
|
|
|
15
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
3004
|
+
for await (const frame of iterator) {
|
|
2923
3005
|
adapter.assert(id);
|
|
2924
3006
|
yield frame;
|
|
2925
3007
|
}
|
|
2926
3008
|
} finally {
|
|
2927
|
-
|
|
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";
|