dsh-delete-chat 0.1.0
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/LICENSE +21 -0
- package/README.md +76 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +350 -0
- package/lib/index.js +179 -0
- package/lib/typert.host.js +133 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 youqu68
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# dsh-delete-chat
|
|
2
|
+
|
|
3
|
+
A DeepSeek Harness session manager: a settings page that lists every session and lets you archive (hide), unarchive (restore), and permanently delete sessions.
|
|
4
|
+
|
|
5
|
+
一个 DeepSeek Harness 会话管理插件:在设置页中列出全部会话,并支持归档(隐藏)、取消归档(恢复显示)与永久删除会话。
|
|
6
|
+
|
|
7
|
+
## What it does / 功能
|
|
8
|
+
|
|
9
|
+
- **List** every session with its title, working directory, and creation time, split into *archived* and *active* groups.
|
|
10
|
+
- **Archive** a session to hide it from the sidebar. The log is kept, so archiving is fully reversible.
|
|
11
|
+
- **Unarchive** a session to make it visible again.
|
|
12
|
+
- **Delete** a session permanently: removes its persistence log file, then detaches it from workspace accounting and from the archive set.
|
|
13
|
+
|
|
14
|
+
## Install / 安装
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
dsh plugin --profile web add dsh-delete-chat
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The package declares `dsh.bundle.patch`, so the command installs it **and** reconciles it into `dsh.profile.bundles` — no profile file editing. Restart `dsh web` to mount it.
|
|
21
|
+
|
|
22
|
+
## Usage / 使用
|
|
23
|
+
|
|
24
|
+
Open **Settings → 会话管理** (Session Manager).
|
|
25
|
+
|
|
26
|
+
## Safety / 安全边界
|
|
27
|
+
|
|
28
|
+
- **Archive is not delete.** Archiving only hides a session; its log stays on disk and `取消归档` restores it.
|
|
29
|
+
- **Live sessions cannot be deleted.** A running session's delete button is disabled, and the host refuses the call even if it is reached directly.
|
|
30
|
+
- **Delete is permanent and two-step.** The first click shows the exact file path that will be removed; only a second click deletes anything.
|
|
31
|
+
- **Only the session's own artifact is touched.** The host resolves the path through `sessionPersistence.locate()`, refuses anything whose filename is not `session.jsonl*`, and refuses backends that expose no per-session artifact (for example a SQLite store).
|
|
32
|
+
- **Sandbox-aware.** Session files live under `$DSH_HOME/sessions`, outside the workspace. If the host's shell sandbox denies the removal, the plugin surfaces that denial instead of forcing it, and the file can be deleted by hand.
|
|
33
|
+
- **Search index.** A deleted session disappears from the session list immediately; a full-text search index may keep a stale row until the next DSH restart, which is the documented reconciliation point.
|
|
34
|
+
|
|
35
|
+
## Architecture / 结构
|
|
36
|
+
|
|
37
|
+
One dual-face package:
|
|
38
|
+
|
|
39
|
+
| Piece | File | Role |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| Host plugin | `lib/index.js` | `apply(ctx)` provides the `delChat` service |
|
|
42
|
+
| Host manifest | `lib/typert.host.js` | the `./typert` Typert contribution that exposes `delChat` over the gateway |
|
|
43
|
+
| Browser plugin | `lib/client.js` | the `settings.section` page, mounted through `exports["./client"]` |
|
|
44
|
+
| Mount | `cordis.patch.yml` | one `insert` row: `del-chat → dsh-delete-chat` |
|
|
45
|
+
|
|
46
|
+
The browser half reaches the host through `remote.delChat.*`. The host half imports no `@deepseek-ai/*` runtime package — it reads the host process's own services with `ctx.get(...)` and binds itself to the gateway with a hand-written `typertRemote` binding, so no code-generation step is needed to build this plugin.
|
|
47
|
+
|
|
48
|
+
Exposed Remote methods:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
delChat.list() → { sessions: [...] }
|
|
52
|
+
delChat.archive({ sessionId }) → { archivedSessionIds: [...] }
|
|
53
|
+
delChat.unarchive({ sessionId }) → { archivedSessionIds: [...] }
|
|
54
|
+
delChat.inspectDelete({ sessionId })→ { deletable, reason, path, dir }
|
|
55
|
+
delChat.deleteSession({ sessionId })→ { path }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development / 开发
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npm install
|
|
62
|
+
npm run build # esbuild → lib/index.js, lib/typert.host.js, lib/client.js
|
|
63
|
+
npm test # structural validation of both halves
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`npm run build` writes the published artifacts:
|
|
67
|
+
|
|
68
|
+
- `lib/index.js` — the host plugin, bundled to ESM.
|
|
69
|
+
- `lib/typert.host.js` — copied verbatim from `src/typert.host.js`.
|
|
70
|
+
- `lib/client.js` — the browser bundle wrapped in the `window.__ModuleLoader__.load({ id, factory })` envelope the DSH client module system executes, with `react` left external for the shell's static module table.
|
|
71
|
+
|
|
72
|
+
`npm test` imports the built host half against a mock Cordis context (exports, provided service, `typertRemote` binding, manifest shape) and executes the built browser bundle against a fake `window.__ModuleLoader__` plus a react shim, asserting the registration id, the exports, the Remote descriptors, and the `settings.section` registration.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# dsh-delete-chat bundle patch: one dual-face row over the web profile.
|
|
2
|
+
#
|
|
3
|
+
# The row name resolves from the profile's own node_modules, where
|
|
4
|
+
# `dsh plugin --profile web add dsh-delete-chat` installed this package. The same
|
|
5
|
+
# package carries the host half (the `delChat` Remote service) and the browser
|
|
6
|
+
# half (exports["./client"], discovered through the dsh.client declaration), so
|
|
7
|
+
# a single insert is the whole mount.
|
|
8
|
+
- insert:
|
|
9
|
+
- id: del-chat
|
|
10
|
+
name: dsh-delete-chat
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// Generated by scripts/build.mjs — do not edit directly.
|
|
2
|
+
window.__ModuleLoader__.load({
|
|
3
|
+
id: "dsh-delete-chat",
|
|
4
|
+
factory: function (require) {
|
|
5
|
+
var module = { exports: {} };
|
|
6
|
+
var exports = module.exports;
|
|
7
|
+
(function (module, exports, require) {
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
27
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
28
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
29
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
30
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
31
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
32
|
+
mod
|
|
33
|
+
));
|
|
34
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
|
+
|
|
36
|
+
// src/client.js
|
|
37
|
+
var client_exports = {};
|
|
38
|
+
__export(client_exports, {
|
|
39
|
+
apply: () => apply,
|
|
40
|
+
inject: () => inject
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(client_exports);
|
|
43
|
+
var import_react = __toESM(require("react"), 1);
|
|
44
|
+
var inject = ["remote"];
|
|
45
|
+
var PACKAGE = "dsh-delete-chat";
|
|
46
|
+
var NAMESPACE = "delChat";
|
|
47
|
+
var SECTION_ID = "del-chat";
|
|
48
|
+
var CSS_TAG = "dsh-delete-chat/client.css";
|
|
49
|
+
var e = import_react.default.createElement;
|
|
50
|
+
function codecOf(parse) {
|
|
51
|
+
return { parse };
|
|
52
|
+
}
|
|
53
|
+
var anyCodec = codecOf((value) => value);
|
|
54
|
+
var codec = (typeName) => ({
|
|
55
|
+
mode: "strict",
|
|
56
|
+
typeSymbol: `${PACKAGE}#${typeName}`,
|
|
57
|
+
schema: anyCodec
|
|
58
|
+
});
|
|
59
|
+
var REMOTE_CONTRIBUTION = {
|
|
60
|
+
package: PACKAGE,
|
|
61
|
+
descriptors: [
|
|
62
|
+
{
|
|
63
|
+
id: `${PACKAGE}#${NAMESPACE}/list`,
|
|
64
|
+
service: NAMESPACE,
|
|
65
|
+
namespace: NAMESPACE,
|
|
66
|
+
method: "list",
|
|
67
|
+
invocation: { kind: "direct" },
|
|
68
|
+
parameters: [{ name: "request", wire: "request", source: "json", codec: codec("ListRequest") }],
|
|
69
|
+
result: codec("ListResult")
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: `${PACKAGE}#${NAMESPACE}/archive`,
|
|
73
|
+
service: NAMESPACE,
|
|
74
|
+
namespace: NAMESPACE,
|
|
75
|
+
method: "archive",
|
|
76
|
+
invocation: { kind: "direct" },
|
|
77
|
+
parameters: [{ name: "request", wire: "request", source: "json", codec: codec("SessionRequest") }],
|
|
78
|
+
result: codec("ArchivedIdsResult")
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: `${PACKAGE}#${NAMESPACE}/unarchive`,
|
|
82
|
+
service: NAMESPACE,
|
|
83
|
+
namespace: NAMESPACE,
|
|
84
|
+
method: "unarchive",
|
|
85
|
+
invocation: { kind: "direct" },
|
|
86
|
+
parameters: [{ name: "request", wire: "request", source: "json", codec: codec("SessionRequest") }],
|
|
87
|
+
result: codec("ArchivedIdsResult")
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: `${PACKAGE}#${NAMESPACE}/inspectDelete`,
|
|
91
|
+
service: NAMESPACE,
|
|
92
|
+
namespace: NAMESPACE,
|
|
93
|
+
method: "inspectDelete",
|
|
94
|
+
invocation: { kind: "direct" },
|
|
95
|
+
parameters: [{ name: "request", wire: "request", source: "json", codec: codec("SessionRequest") }],
|
|
96
|
+
result: codec("InspectDeleteResult")
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: `${PACKAGE}#${NAMESPACE}/deleteSession`,
|
|
100
|
+
service: NAMESPACE,
|
|
101
|
+
namespace: NAMESPACE,
|
|
102
|
+
method: "deleteSession",
|
|
103
|
+
invocation: { kind: "direct" },
|
|
104
|
+
parameters: [{ name: "request", wire: "request", source: "json", codec: codec("SessionRequest") }],
|
|
105
|
+
result: codec("DeleteResult")
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
var CSS = [
|
|
110
|
+
".dc-root{display:flex;flex-direction:column;gap:16px;font-size:13px}",
|
|
111
|
+
".dc-head{display:flex;align-items:center;justify-content:space-between}",
|
|
112
|
+
".dc-head-title{font-weight:600;font-size:15px}",
|
|
113
|
+
".dc-section{border:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.35));border-radius:10px;padding:10px 12px}",
|
|
114
|
+
".dc-section-title{font-weight:600;margin-bottom:8px;opacity:.85}",
|
|
115
|
+
".dc-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;border-top:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.18))}",
|
|
116
|
+
".dc-row:first-of-type{border-top:0}",
|
|
117
|
+
".dc-row-main{min-width:0;flex:1}",
|
|
118
|
+
".dc-row-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
119
|
+
".dc-row-sub{opacity:.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}",
|
|
120
|
+
".dc-empty{opacity:.5;padding:4px 0}",
|
|
121
|
+
".dc-live{color:var(--dsw-alias-state-info-primary,#4aa3e0);font-size:11px;margin-top:2px}",
|
|
122
|
+
".dc-warn{color:var(--dsw-alias-state-warn-primary,#e0a060);font-size:11px;margin-top:4px;word-break:break-all}",
|
|
123
|
+
".dc-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}",
|
|
124
|
+
".dc-btn{font:inherit;font-size:12px;color:inherit;background:var(--dsw-alias-button-elevated-fill,transparent);border:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.4));border-radius:7px;padding:4px 10px;cursor:pointer;white-space:nowrap}",
|
|
125
|
+
".dc-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15))}",
|
|
126
|
+
".dc-btn:disabled{opacity:.4;cursor:default}",
|
|
127
|
+
".dc-btn-restore{border-color:var(--dsw-alias-state-ok-primary,rgba(80,160,120,.6))}",
|
|
128
|
+
".dc-btn-delete{color:var(--dsw-alias-state-error-primary,#d06c6c);border-color:var(--dsw-alias-state-error-primary,rgba(208,108,108,.5))}",
|
|
129
|
+
".dc-btn-danger{background:var(--dsw-alias-state-error-primary,#b5433f);border-color:transparent;color:var(--dsw-alias-label-primary-inverted,#fff)}",
|
|
130
|
+
".dc-error{color:var(--dsw-alias-state-error-primary,#e06c6c);font-size:12px;white-space:pre-wrap}",
|
|
131
|
+
".dc-loading{opacity:.6}"
|
|
132
|
+
].join("");
|
|
133
|
+
function insertCss() {
|
|
134
|
+
if (typeof document === "undefined") return () => {
|
|
135
|
+
};
|
|
136
|
+
if (document.querySelector(`style[data-plugin-css="${CSS_TAG}"]`) !== null) return () => {
|
|
137
|
+
};
|
|
138
|
+
const style = document.createElement("style");
|
|
139
|
+
style.dataset.plugin = PACKAGE;
|
|
140
|
+
style.dataset.pluginCss = CSS_TAG;
|
|
141
|
+
style.textContent = CSS;
|
|
142
|
+
document.head.appendChild(style);
|
|
143
|
+
return () => {
|
|
144
|
+
if (style.parentNode !== null) style.parentNode.removeChild(style);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function call(remote, method, args) {
|
|
148
|
+
const carried = await remote[method](...args ?? []);
|
|
149
|
+
if (carried === null || typeof carried !== "object" || carried.ok !== true) {
|
|
150
|
+
throw new Error(carried && carried.error && carried.error.message || `${method} \u8C03\u7528\u5931\u8D25`);
|
|
151
|
+
}
|
|
152
|
+
return carried.value;
|
|
153
|
+
}
|
|
154
|
+
function formatTime(ms) {
|
|
155
|
+
try {
|
|
156
|
+
return new Date(ms).toLocaleString();
|
|
157
|
+
} catch {
|
|
158
|
+
return String(ms);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function SessionManagerPage(props) {
|
|
162
|
+
const api = props.api;
|
|
163
|
+
const [view, setView] = import_react.default.useState({
|
|
164
|
+
loading: true,
|
|
165
|
+
sessions: [],
|
|
166
|
+
error: null,
|
|
167
|
+
busyId: null,
|
|
168
|
+
confirmId: null,
|
|
169
|
+
confirmPath: null
|
|
170
|
+
});
|
|
171
|
+
const load = async () => {
|
|
172
|
+
setView((s) => ({ ...s, loading: true }));
|
|
173
|
+
try {
|
|
174
|
+
const value = await call(api, "list", [{}]);
|
|
175
|
+
setView({
|
|
176
|
+
loading: false,
|
|
177
|
+
sessions: value && value.sessions || [],
|
|
178
|
+
error: null,
|
|
179
|
+
busyId: null,
|
|
180
|
+
confirmId: null,
|
|
181
|
+
confirmPath: null
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
setView({
|
|
185
|
+
loading: false,
|
|
186
|
+
sessions: [],
|
|
187
|
+
error: String(error && error.message || error),
|
|
188
|
+
busyId: null,
|
|
189
|
+
confirmId: null,
|
|
190
|
+
confirmPath: null
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
import_react.default.useEffect(() => {
|
|
195
|
+
load();
|
|
196
|
+
}, []);
|
|
197
|
+
const toggleArchive = async (sessionId, archived2) => {
|
|
198
|
+
setView((s) => ({ ...s, busyId: sessionId, error: null }));
|
|
199
|
+
try {
|
|
200
|
+
await call(api, archived2 ? "unarchive" : "archive", [{ sessionId }]);
|
|
201
|
+
await load();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
setView((s) => ({ ...s, busyId: null, error: String(error && error.message || error) }));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const askDelete = async (sessionId) => {
|
|
207
|
+
setView((s) => ({ ...s, busyId: sessionId, error: null, confirmPath: null }));
|
|
208
|
+
try {
|
|
209
|
+
const info = await call(api, "inspectDelete", [{ sessionId }]);
|
|
210
|
+
if (info && info.deletable === true) {
|
|
211
|
+
setView((s) => ({ ...s, busyId: null, confirmId: sessionId, confirmPath: info.path }));
|
|
212
|
+
} else {
|
|
213
|
+
setView((s) => ({
|
|
214
|
+
...s,
|
|
215
|
+
busyId: null,
|
|
216
|
+
confirmId: null,
|
|
217
|
+
confirmPath: null,
|
|
218
|
+
error: info && info.reason || "\u65E0\u6CD5\u5220\u9664"
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
setView((s) => ({
|
|
223
|
+
...s,
|
|
224
|
+
busyId: null,
|
|
225
|
+
confirmId: null,
|
|
226
|
+
confirmPath: null,
|
|
227
|
+
error: String(error && error.message || error)
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const doDelete = async (sessionId) => {
|
|
232
|
+
setView((s) => ({ ...s, busyId: sessionId, error: null }));
|
|
233
|
+
try {
|
|
234
|
+
await call(api, "deleteSession", [{ sessionId }]);
|
|
235
|
+
await load();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
setView((s) => ({
|
|
238
|
+
...s,
|
|
239
|
+
busyId: null,
|
|
240
|
+
confirmId: null,
|
|
241
|
+
confirmPath: null,
|
|
242
|
+
error: String(error && error.message || error)
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const renderRow = (session) => {
|
|
247
|
+
const confirming = view.confirmId === session.id;
|
|
248
|
+
const busy = view.busyId === session.id;
|
|
249
|
+
return e(
|
|
250
|
+
"div",
|
|
251
|
+
{ key: session.id, className: "dc-row" },
|
|
252
|
+
e(
|
|
253
|
+
"div",
|
|
254
|
+
{ className: "dc-row-main" },
|
|
255
|
+
e("div", { className: "dc-row-title" }, session.title || "(\u672A\u547D\u540D\u4F1A\u8BDD)"),
|
|
256
|
+
e(
|
|
257
|
+
"div",
|
|
258
|
+
{ className: "dc-row-sub" },
|
|
259
|
+
(session.cwd || "(\u65E0\u5DE5\u4F5C\u76EE\u5F55)") + " \xB7 " + formatTime(session.createdAt)
|
|
260
|
+
),
|
|
261
|
+
session.live ? e("div", { className: "dc-live" }, "\u25CF \u8FD0\u884C\u4E2D\uFF08\u4E0D\u53EF\u5220\u9664\uFF09") : null,
|
|
262
|
+
confirming ? e(
|
|
263
|
+
"div",
|
|
264
|
+
{ className: "dc-warn" },
|
|
265
|
+
"\u5C06\u6C38\u4E45\u5220\u9664\u8BE5\u4F1A\u8BDD\u65E5\u5FD7\u6587\u4EF6\uFF0C\u4E0D\u53EF\u64A4\u9500\uFF1A" + (view.confirmPath || "")
|
|
266
|
+
) : null
|
|
267
|
+
),
|
|
268
|
+
e(
|
|
269
|
+
"div",
|
|
270
|
+
{ className: "dc-actions" },
|
|
271
|
+
e(
|
|
272
|
+
"button",
|
|
273
|
+
{
|
|
274
|
+
className: "dc-btn" + (session.archived ? " dc-btn-restore" : ""),
|
|
275
|
+
disabled: busy,
|
|
276
|
+
onClick: () => toggleArchive(session.id, session.archived)
|
|
277
|
+
},
|
|
278
|
+
session.archived ? "\u53D6\u6D88\u5F52\u6863" : "\u5F52\u6863"
|
|
279
|
+
),
|
|
280
|
+
confirming ? e(
|
|
281
|
+
"button",
|
|
282
|
+
{ className: "dc-btn dc-btn-danger", disabled: busy, onClick: () => doDelete(session.id) },
|
|
283
|
+
"\u786E\u8BA4\u5220\u9664"
|
|
284
|
+
) : e(
|
|
285
|
+
"button",
|
|
286
|
+
{
|
|
287
|
+
className: "dc-btn dc-btn-delete",
|
|
288
|
+
disabled: session.live || busy,
|
|
289
|
+
onClick: () => askDelete(session.id)
|
|
290
|
+
},
|
|
291
|
+
"\u5220\u9664"
|
|
292
|
+
),
|
|
293
|
+
confirming ? e(
|
|
294
|
+
"button",
|
|
295
|
+
{
|
|
296
|
+
className: "dc-btn",
|
|
297
|
+
onClick: () => setView((s) => ({ ...s, confirmId: null, confirmPath: null }))
|
|
298
|
+
},
|
|
299
|
+
"\u53D6\u6D88"
|
|
300
|
+
) : null
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
const renderSection = (title, sessions) => e(
|
|
305
|
+
"div",
|
|
306
|
+
{ className: "dc-section" },
|
|
307
|
+
e("div", { className: "dc-section-title" }, title + "\uFF08" + sessions.length + "\uFF09"),
|
|
308
|
+
sessions.length === 0 ? e("div", { className: "dc-empty" }, "\u65E0") : sessions.map(renderRow)
|
|
309
|
+
);
|
|
310
|
+
const archived = view.sessions.filter((s) => s.archived);
|
|
311
|
+
const active = view.sessions.filter((s) => !s.archived);
|
|
312
|
+
return e(
|
|
313
|
+
"div",
|
|
314
|
+
{ className: "dc-root" },
|
|
315
|
+
e(
|
|
316
|
+
"div",
|
|
317
|
+
{ className: "dc-head" },
|
|
318
|
+
e("div", { className: "dc-head-title" }, "\u4F1A\u8BDD\u7BA1\u7406"),
|
|
319
|
+
e("button", { className: "dc-btn", onClick: load }, "\u5237\u65B0")
|
|
320
|
+
),
|
|
321
|
+
view.error ? e("div", { className: "dc-error" }, "\u9519\u8BEF\uFF1A" + view.error) : null,
|
|
322
|
+
view.loading ? e("div", { className: "dc-loading" }, "\u52A0\u8F7D\u4E2D\u2026") : e("div", null, renderSection("\u5DF2\u5F52\u6863\uFF08\u53EF\u6062\u590D\u663E\u793A\uFF09", archived), renderSection("\u6B63\u5E38\u4F1A\u8BDD", active))
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
async function apply(ctx) {
|
|
326
|
+
const remote = ctx.remote;
|
|
327
|
+
if (remote === void 0 || typeof remote.$mount !== "function") return;
|
|
328
|
+
const unmount = await remote.$mount(REMOTE_CONTRIBUTION);
|
|
329
|
+
ctx.effect(() => () => {
|
|
330
|
+
unmount();
|
|
331
|
+
}, "del-chat: remote contribution");
|
|
332
|
+
const api = ctx.get("remote." + NAMESPACE);
|
|
333
|
+
if (api === void 0) return;
|
|
334
|
+
const removeCss = insertCss();
|
|
335
|
+
ctx.effect(() => () => removeCss(), "del-chat: stylesheet");
|
|
336
|
+
const slots = ctx.get("slots");
|
|
337
|
+
if (slots === void 0) return;
|
|
338
|
+
slots.inject(
|
|
339
|
+
"settings.section",
|
|
340
|
+
() => slots.register(
|
|
341
|
+
{ name: "settings.section", id: SECTION_ID, order: 50, label: "\u4F1A\u8BDD\u7BA1\u7406" },
|
|
342
|
+
(props) => e(SessionManagerPage, { ...props, api })
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
})(module, exports, require);
|
|
348
|
+
return module.exports;
|
|
349
|
+
},
|
|
350
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Generated by scripts/build.mjs — do not edit directly.
|
|
2
|
+
|
|
3
|
+
// src/index.js
|
|
4
|
+
var name = "del-chat";
|
|
5
|
+
function archivedIdsOf(registry) {
|
|
6
|
+
if (registry === void 0) return [];
|
|
7
|
+
try {
|
|
8
|
+
return registry.archivedSessionIds.map((id) => String(id));
|
|
9
|
+
} catch {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function parentDir(p) {
|
|
14
|
+
const i = Math.max(p.lastIndexOf("\\"), p.lastIndexOf("/"));
|
|
15
|
+
return i > 0 ? p.slice(0, i) : p;
|
|
16
|
+
}
|
|
17
|
+
async function inspectDeletable(ctx, sessionId) {
|
|
18
|
+
const sessions = ctx.get("sessions");
|
|
19
|
+
if (sessions !== void 0 && sessions.get(sessionId) !== void 0) {
|
|
20
|
+
return { deletable: false, reason: "\u8BE5\u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\uFF0C\u65E0\u6CD5\u5220\u9664", path: null, dir: null };
|
|
21
|
+
}
|
|
22
|
+
const persistence = ctx.get("sessionPersistence");
|
|
23
|
+
if (persistence === void 0) {
|
|
24
|
+
return { deletable: false, reason: "\u5B58\u50A8\u670D\u52A1\u4E0D\u53EF\u7528", path: null, dir: null };
|
|
25
|
+
}
|
|
26
|
+
let header;
|
|
27
|
+
try {
|
|
28
|
+
const list = await persistence.list();
|
|
29
|
+
header = list.find((h) => String(h.id) === sessionId);
|
|
30
|
+
} catch {
|
|
31
|
+
header = void 0;
|
|
32
|
+
}
|
|
33
|
+
if (header === void 0) {
|
|
34
|
+
return { deletable: false, reason: "\u4F1A\u8BDD\u4E0D\u5B58\u5728", path: null, dir: null };
|
|
35
|
+
}
|
|
36
|
+
if (persistence.supportsRawArtifacts !== true) {
|
|
37
|
+
return { deletable: false, reason: "\u5F53\u524D\u5B58\u50A8\u540E\u7AEF\u4E0D\u66B4\u9732\u5355\u4F1A\u8BDD\u6587\u4EF6\uFF0C\u65E0\u6CD5\u5B89\u5168\u5220\u9664", path: null, dir: null };
|
|
38
|
+
}
|
|
39
|
+
const loc = persistence.locate(header);
|
|
40
|
+
if (loc === void 0 || loc.kind !== "jsonl") {
|
|
41
|
+
return { deletable: false, reason: "\u65E0\u6CD5\u89E3\u6790\u4F1A\u8BDD\u6587\u4EF6\u8DEF\u5F84", path: null, dir: null };
|
|
42
|
+
}
|
|
43
|
+
const base = String(loc.path).split(/[\\/]/).pop() ?? "";
|
|
44
|
+
if (!base.startsWith("session.jsonl")) {
|
|
45
|
+
return { deletable: false, reason: "\u4EA7\u7269\u6587\u4EF6\u540D\u5F02\u5E38\uFF0C\u5DF2\u62D2\u7EDD\u5220\u9664", path: null, dir: null };
|
|
46
|
+
}
|
|
47
|
+
return { deletable: true, reason: null, path: String(loc.path), dir: parentDir(String(loc.path)) };
|
|
48
|
+
}
|
|
49
|
+
function createService(ctx) {
|
|
50
|
+
const service = {
|
|
51
|
+
/** Every session, newest first inside each archive group. */
|
|
52
|
+
async list() {
|
|
53
|
+
const query = ctx.get("sessionQuery");
|
|
54
|
+
const registry = ctx.get("workspaceRegistry");
|
|
55
|
+
const sessions = ctx.get("sessions");
|
|
56
|
+
const archivedIds = archivedIdsOf(registry);
|
|
57
|
+
const archivedSet = new Set(archivedIds);
|
|
58
|
+
if (query === void 0) return { sessions: [] };
|
|
59
|
+
const records = await query.listSessions();
|
|
60
|
+
const titles = /* @__PURE__ */ new Map();
|
|
61
|
+
try {
|
|
62
|
+
const ids = records.map((r) => String(r.header.id));
|
|
63
|
+
const results = await query.readTitleSnapshots(ids);
|
|
64
|
+
for (const item of results) {
|
|
65
|
+
if (item.status === "fulfilled" && item.value && item.value.title && item.value.title.title) {
|
|
66
|
+
titles.set(String(item.sessionId), String(item.value.title.title));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
const liveSet = /* @__PURE__ */ new Set();
|
|
72
|
+
if (sessions !== void 0) {
|
|
73
|
+
for (const s of sessions.list()) liveSet.add(String(s.id));
|
|
74
|
+
}
|
|
75
|
+
const out = records.map((r) => {
|
|
76
|
+
const id = String(r.header.id);
|
|
77
|
+
const title = titles.get(id);
|
|
78
|
+
const cwd = r.header.cwd;
|
|
79
|
+
return {
|
|
80
|
+
id,
|
|
81
|
+
title: typeof title === "string" && title.length > 0 ? title : null,
|
|
82
|
+
cwd: typeof cwd === "string" && cwd.length > 0 ? cwd : null,
|
|
83
|
+
createdAt: Number(r.header.createdAt) || 0,
|
|
84
|
+
live: liveSet.has(id) || r.live === true,
|
|
85
|
+
archived: archivedSet.has(id)
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
out.sort((a, b) => a.archived === b.archived ? b.createdAt - a.createdAt : a.archived ? -1 : 1);
|
|
89
|
+
return { sessions: out };
|
|
90
|
+
},
|
|
91
|
+
/** Archive (hide) one session durably. */
|
|
92
|
+
async archive(request) {
|
|
93
|
+
const registry = ctx.get("workspaceRegistry");
|
|
94
|
+
if (registry === void 0) throw new Error("workspace registry unavailable");
|
|
95
|
+
await registry.archiveSession(String(request.sessionId));
|
|
96
|
+
return { archivedSessionIds: archivedIdsOf(registry) };
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* Unarchive (restore) one session by removing it from the registry-global
|
|
100
|
+
* archive set. Goes through the registry's own state setter so its
|
|
101
|
+
* in-memory cache stays consistent with the durable domain.
|
|
102
|
+
*/
|
|
103
|
+
async unarchive(request) {
|
|
104
|
+
const registry = ctx.get("workspaceRegistry");
|
|
105
|
+
const storageDomain = ctx.get("storageDomain");
|
|
106
|
+
if (registry === void 0 || storageDomain === void 0) {
|
|
107
|
+
throw new Error("workspace registry unavailable");
|
|
108
|
+
}
|
|
109
|
+
const domain = storageDomain.get("workspace");
|
|
110
|
+
if (domain === void 0) throw new Error("workspace domain unavailable");
|
|
111
|
+
const state = domain.global.get();
|
|
112
|
+
const archived = state.archivedSessionIds ?? [];
|
|
113
|
+
const next = archived.filter((id) => String(id) !== String(request.sessionId));
|
|
114
|
+
if (next.length === archived.length) {
|
|
115
|
+
return { archivedSessionIds: archived.map((id) => String(id)) };
|
|
116
|
+
}
|
|
117
|
+
const nextState = { ...state, archivedSessionIds: next };
|
|
118
|
+
if (typeof registry.setState === "function") {
|
|
119
|
+
await registry.setState(nextState);
|
|
120
|
+
} else {
|
|
121
|
+
await domain.global.set(nextState);
|
|
122
|
+
registry.state = nextState;
|
|
123
|
+
}
|
|
124
|
+
return { archivedSessionIds: next.map((id) => String(id)) };
|
|
125
|
+
},
|
|
126
|
+
/** Whether one session's log file can be deleted, and where it is. */
|
|
127
|
+
async inspectDelete(request) {
|
|
128
|
+
return await inspectDeletable(ctx, String(request.sessionId));
|
|
129
|
+
},
|
|
130
|
+
/**
|
|
131
|
+
* Permanently delete one non-live session's persistence log file, then
|
|
132
|
+
* detach it from workspace accounting and from the archive set.
|
|
133
|
+
*/
|
|
134
|
+
async deleteSession(request) {
|
|
135
|
+
const sessionId = String(request.sessionId);
|
|
136
|
+
const info = await inspectDeletable(ctx, sessionId);
|
|
137
|
+
if (!info.deletable) throw new Error(info.reason ?? "session is not deletable");
|
|
138
|
+
const shell = ctx.get("shell");
|
|
139
|
+
if (shell === void 0) throw new Error("shell service unavailable");
|
|
140
|
+
const quoted = info.path.replace(/'/g, "''");
|
|
141
|
+
const spec = shell.resolve({ command: `Remove-Item -LiteralPath '${quoted}' -Force -ErrorAction Stop` });
|
|
142
|
+
const result = await shell.run(spec);
|
|
143
|
+
if (result.sandbox && result.sandbox.denied) {
|
|
144
|
+
throw new Error("\u6C99\u7BB1\u62D2\u7EDD\u4E86\u5220\u9664\uFF08\u4F1A\u8BDD\u6587\u4EF6\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916\uFF09");
|
|
145
|
+
}
|
|
146
|
+
if (result.exitCode !== 0) {
|
|
147
|
+
throw new Error(`\u5220\u9664\u547D\u4EE4\u5931\u8D25\uFF08exit ${result.exitCode}\uFF09`);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
await service.unarchive({ sessionId });
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
const registry = ctx.get("workspaceRegistry");
|
|
154
|
+
if (registry !== void 0) {
|
|
155
|
+
try {
|
|
156
|
+
for (const workspace of registry.list()) {
|
|
157
|
+
await workspace.detachSession(sessionId);
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { path: info.path };
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
Object.defineProperty(service, "typertRemote", {
|
|
166
|
+
configurable: false,
|
|
167
|
+
enumerable: false,
|
|
168
|
+
writable: false,
|
|
169
|
+
value: { service, serviceKey: "delChat", namespace: "delChat" }
|
|
170
|
+
});
|
|
171
|
+
return service;
|
|
172
|
+
}
|
|
173
|
+
function apply(ctx) {
|
|
174
|
+
ctx.provide("delChat", createService(ctx));
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
apply,
|
|
178
|
+
name
|
|
179
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-delete-chat — Host-face Typert manifest.
|
|
3
|
+
*
|
|
4
|
+
* Structurally identical to a `@deepseek-ai/dsh-typert-generator` artifact:
|
|
5
|
+
* `dsh-typert-loader` imports this module through the package's `./typert`
|
|
6
|
+
* export, validates it, and registers every invocation with the Typert
|
|
7
|
+
* gateway, which is what makes `remote.delChat.*` callable from the browser.
|
|
8
|
+
*
|
|
9
|
+
* Every codec must be `{ mode: 'strict', typeSymbol, schema }` with a zod v4
|
|
10
|
+
* schema — a bare zod schema is rejected at registration.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod'
|
|
13
|
+
|
|
14
|
+
const nonEmpty = z.string().min(1)
|
|
15
|
+
|
|
16
|
+
/** One session row rendered by the settings page. */
|
|
17
|
+
const sessionSchema = z.object({
|
|
18
|
+
id: nonEmpty,
|
|
19
|
+
title: z.union([z.string(), z.null()]),
|
|
20
|
+
cwd: z.union([z.string(), z.null()]),
|
|
21
|
+
createdAt: z.number(),
|
|
22
|
+
live: z.boolean(),
|
|
23
|
+
archived: z.boolean(),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const listRequestSchema = z.object({}).strict()
|
|
27
|
+
const listResultSchema = z.object({ sessions: z.array(sessionSchema) })
|
|
28
|
+
|
|
29
|
+
const sessionRequestSchema = z.object({ sessionId: nonEmpty })
|
|
30
|
+
|
|
31
|
+
const archivedIdsResultSchema = z.object({ archivedSessionIds: z.array(z.string()) })
|
|
32
|
+
|
|
33
|
+
const inspectDeleteResultSchema = z.object({
|
|
34
|
+
deletable: z.boolean(),
|
|
35
|
+
reason: z.union([z.string(), z.null()]),
|
|
36
|
+
path: z.union([z.string(), z.null()]),
|
|
37
|
+
dir: z.union([z.string(), z.null()]),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const deleteResultSchema = z.object({ path: z.string() })
|
|
41
|
+
|
|
42
|
+
const listRequest$codec = {
|
|
43
|
+
mode: 'strict',
|
|
44
|
+
typeSymbol: 'dsh-delete-chat#ListRequest',
|
|
45
|
+
schema: listRequestSchema,
|
|
46
|
+
}
|
|
47
|
+
const list$codec = {
|
|
48
|
+
mode: 'strict',
|
|
49
|
+
typeSymbol: 'dsh-delete-chat#ListResult',
|
|
50
|
+
schema: listResultSchema,
|
|
51
|
+
}
|
|
52
|
+
const sessionRequest$codec = {
|
|
53
|
+
mode: 'strict',
|
|
54
|
+
typeSymbol: 'dsh-delete-chat#SessionRequest',
|
|
55
|
+
schema: sessionRequestSchema,
|
|
56
|
+
}
|
|
57
|
+
const archivedIds$codec = {
|
|
58
|
+
mode: 'strict',
|
|
59
|
+
typeSymbol: 'dsh-delete-chat#ArchivedIdsResult',
|
|
60
|
+
schema: archivedIdsResultSchema,
|
|
61
|
+
}
|
|
62
|
+
const inspectDelete$codec = {
|
|
63
|
+
mode: 'strict',
|
|
64
|
+
typeSymbol: 'dsh-delete-chat#InspectDeleteResult',
|
|
65
|
+
schema: inspectDeleteResultSchema,
|
|
66
|
+
}
|
|
67
|
+
const delete$codec = {
|
|
68
|
+
mode: 'strict',
|
|
69
|
+
typeSymbol: 'dsh-delete-chat#DeleteResult',
|
|
70
|
+
schema: deleteResultSchema,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const sessionSchemaExport = sessionSchema
|
|
74
|
+
|
|
75
|
+
export const TYPERT = {
|
|
76
|
+
package: 'dsh-delete-chat',
|
|
77
|
+
face: 'host',
|
|
78
|
+
schemas: [],
|
|
79
|
+
invocations: [
|
|
80
|
+
{
|
|
81
|
+
id: 'dsh-delete-chat#delChat/list',
|
|
82
|
+
service: 'delChat',
|
|
83
|
+
namespace: 'delChat',
|
|
84
|
+
method: 'list',
|
|
85
|
+
invocation: { kind: 'direct' },
|
|
86
|
+
parameters: [{ name: 'request', wire: 'request', source: 'json', codec: listRequest$codec }],
|
|
87
|
+
result: list$codec,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'dsh-delete-chat#delChat/archive',
|
|
91
|
+
service: 'delChat',
|
|
92
|
+
namespace: 'delChat',
|
|
93
|
+
method: 'archive',
|
|
94
|
+
invocation: { kind: 'direct' },
|
|
95
|
+
parameters: [{ name: 'request', wire: 'request', source: 'json', codec: sessionRequest$codec }],
|
|
96
|
+
result: archivedIds$codec,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'dsh-delete-chat#delChat/unarchive',
|
|
100
|
+
service: 'delChat',
|
|
101
|
+
namespace: 'delChat',
|
|
102
|
+
method: 'unarchive',
|
|
103
|
+
invocation: { kind: 'direct' },
|
|
104
|
+
parameters: [{ name: 'request', wire: 'request', source: 'json', codec: sessionRequest$codec }],
|
|
105
|
+
result: archivedIds$codec,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'dsh-delete-chat#delChat/inspectDelete',
|
|
109
|
+
service: 'delChat',
|
|
110
|
+
namespace: 'delChat',
|
|
111
|
+
method: 'inspectDelete',
|
|
112
|
+
invocation: { kind: 'direct' },
|
|
113
|
+
parameters: [{ name: 'request', wire: 'request', source: 'json', codec: sessionRequest$codec }],
|
|
114
|
+
result: inspectDelete$codec,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'dsh-delete-chat#delChat/deleteSession',
|
|
118
|
+
service: 'delChat',
|
|
119
|
+
namespace: 'delChat',
|
|
120
|
+
method: 'deleteSession',
|
|
121
|
+
invocation: { kind: 'direct' },
|
|
122
|
+
parameters: [{ name: 'request', wire: 'request', source: 'json', codec: sessionRequest$codec }],
|
|
123
|
+
result: delete$codec,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
model: {
|
|
127
|
+
services: [],
|
|
128
|
+
events: [],
|
|
129
|
+
objects: [],
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default TYPERT
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-delete-chat",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Session manager for DeepSeek Harness: list, archive, unarchive and permanently delete sessions from a settings page.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"default": "./lib/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./client": {
|
|
12
|
+
"default": "./lib/client.js"
|
|
13
|
+
},
|
|
14
|
+
"./typert": {
|
|
15
|
+
"default": "./lib/typert.host.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"dsh": {
|
|
20
|
+
"bundle": {
|
|
21
|
+
"patch": "./cordis.patch.yml"
|
|
22
|
+
},
|
|
23
|
+
"client": {
|
|
24
|
+
"platform": "web"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib",
|
|
29
|
+
"cordis.patch.yml",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "node scripts/build.mjs",
|
|
35
|
+
"test": "node test/validate-host.mjs && node test/validate-client.mjs",
|
|
36
|
+
"prepublishOnly": "node scripts/build.mjs"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"zod": "^4.5.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"esbuild": "^0.25.9"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"deepseek-harness",
|
|
52
|
+
"dsh",
|
|
53
|
+
"dsh-plugin",
|
|
54
|
+
"session",
|
|
55
|
+
"archive",
|
|
56
|
+
"delete"
|
|
57
|
+
],
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/youqu68/dsh-delete-chat.git"
|
|
61
|
+
},
|
|
62
|
+
"license": "MIT"
|
|
63
|
+
}
|