dsh-more 0.0.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.
Files changed (57) hide show
  1. package/CONTRIBUTING.md +349 -0
  2. package/LICENSE +21 -0
  3. package/README.md +232 -0
  4. package/README.zh-CN.md +232 -0
  5. package/cordis.patch.yml +4 -0
  6. package/dist/client.js +954 -0
  7. package/dist/index.js +937 -0
  8. package/dist/types/client/index.d.ts +3 -0
  9. package/dist/types/generated/client-registry.d.ts +3 -0
  10. package/dist/types/generated/host-registry.d.ts +2 -0
  11. package/dist/types/generated/host-settings-schema.d.ts +10 -0
  12. package/dist/types/generated/patch-catalog.d.ts +30 -0
  13. package/dist/types/index.d.ts +4 -0
  14. package/dist/types/kernel/client/activation.d.ts +19 -0
  15. package/dist/types/kernel/client/config-card.d.ts +11 -0
  16. package/dist/types/kernel/client/message-actions.d.ts +14 -0
  17. package/dist/types/kernel/client/patch.d.ts +16 -0
  18. package/dist/types/kernel/host/activation.d.ts +14 -0
  19. package/dist/types/kernel/host/patch.d.ts +12 -0
  20. package/dist/types/kernel/host/settings.d.ts +5 -0
  21. package/dist/types/kernel/message-visibility.d.ts +6 -0
  22. package/dist/types/patches/message-delete/client/index.d.ts +5 -0
  23. package/dist/types/patches/message-delete/host/index.d.ts +2 -0
  24. package/dist/types/patches/message-delete/host/message-selection.d.ts +7 -0
  25. package/dist/types/patches/message-delete/host/projection.d.ts +8 -0
  26. package/dist/types/patches/message-delete/host/rebuild.d.ts +9 -0
  27. package/dist/types/patches/message-delete/shared.d.ts +6 -0
  28. package/dist/types/patches/message-edit/client/index.d.ts +5 -0
  29. package/dist/types/patches/message-edit/host/edit-continuation.d.ts +15 -0
  30. package/dist/types/patches/message-edit/host/index.d.ts +2 -0
  31. package/dist/types/patches/message-edit/shared.d.ts +7 -0
  32. package/dist/types/patches/session-delete/client/index.d.ts +4 -0
  33. package/dist/types/patches/session-delete/host/index.d.ts +2 -0
  34. package/dist/types/patches/session-delete/host/live-session-handles.d.ts +7 -0
  35. package/dist/types/patches/session-delete/host/session-deletion.d.ts +6 -0
  36. package/dist/types/patches/session-delete/shared.d.ts +1 -0
  37. package/dist/types/platform/dsh/client/api.d.ts +6 -0
  38. package/dist/types/platform/dsh/client/message-content.d.ts +2 -0
  39. package/dist/types/platform/dsh/client/message-targets.d.ts +2 -0
  40. package/dist/types/platform/dsh/client/runtime.d.ts +7 -0
  41. package/dist/types/platform/dsh/client/session-sync.d.ts +18 -0
  42. package/dist/types/platform/dsh/client/styles.d.ts +1 -0
  43. package/dist/types/platform/dsh/host/agent-session.d.ts +6 -0
  44. package/dist/types/platform/dsh/host/api-router.d.ts +3 -0
  45. package/dist/types/platform/dsh/host/confirmation.d.ts +14 -0
  46. package/dist/types/platform/dsh/host/error.d.ts +6 -0
  47. package/dist/types/platform/dsh/host/runtime-context.d.ts +4 -0
  48. package/dist/types/platform/dsh/host/session-history.d.ts +8 -0
  49. package/dist/types/platform/dsh/host/trust.d.ts +3 -0
  50. package/dist/types/platform/dsh/host/wire.d.ts +6 -0
  51. package/dist/types/platform/dsh/identity.d.ts +4 -0
  52. package/docs/assets/dsh-more-architecture.jpg +0 -0
  53. package/docs/assets/dsh-more-architecture.zh-CN.jpg +0 -0
  54. package/docs/assets/dsh-more-safe-mutation-flow.jpg +0 -0
  55. package/docs/assets/dsh-more-safe-mutation-flow.zh-CN.jpg +0 -0
  56. package/dsh.plugin.json +12 -0
  57. package/package.json +140 -0
package/dist/index.js ADDED
@@ -0,0 +1,937 @@
1
+ // src/index.ts
2
+ import { randomBytes } from "node:crypto";
3
+
4
+ // src/patches/message-edit/host/index.ts
5
+ import { randomUUID as randomUUID2 } from "node:crypto";
6
+ import { SessionId as SessionId3 } from "@deepseek-ai/dsh-session";
7
+
8
+ // src/platform/dsh/host/agent-session.ts
9
+ import { SessionId } from "@deepseek-ai/dsh-session";
10
+
11
+ // src/platform/dsh/host/error.ts
12
+ var DshMoreError = class extends Error {
13
+ constructor(code, message, status = 400) {
14
+ super(message);
15
+ this.code = code;
16
+ this.status = status;
17
+ this.name = "DshMoreError";
18
+ }
19
+ };
20
+
21
+ // src/platform/dsh/host/agent-session.ts
22
+ function requireLiveSession(ctx, sessionId) {
23
+ const session = ctx.sessions.get(SessionId(sessionId));
24
+ if (session === void 0) throw new DshMoreError("session-not-live", "\u8BF7\u5148\u6253\u5F00\u8FD9\u6761\u4F1A\u8BDD\u518D\u64CD\u4F5C\u3002", 409);
25
+ return session;
26
+ }
27
+ function requireIdleAgent(ctx, sessionId, session) {
28
+ const agent = ctx.agents.get(SessionId(sessionId));
29
+ if (agent === void 0 || agent.session !== session) {
30
+ throw new DshMoreError("session-not-live", "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u53EF\u7EF4\u62A4\u7684 Agent\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u540E\u518D\u8BD5\u3002", 409);
31
+ }
32
+ if (agent.status !== "idle") throw new DshMoreError("session-busy", "\u4F1A\u8BDD\u4ECD\u5728\u751F\u6210\u6216\u6267\u884C\u5DE5\u5177\uFF0C\u8BF7\u7B49\u5F85\u5B83\u7A7A\u95F2\u540E\u518D\u64CD\u4F5C\u3002", 409);
33
+ return agent;
34
+ }
35
+ function runAgentMaintenance(agent, task) {
36
+ try {
37
+ return agent.runMaintenance(task);
38
+ } catch {
39
+ throw new DshMoreError("session-busy", "\u4F1A\u8BDD\u521A\u521A\u5F00\u59CB\u4E86\u65B0\u4EFB\u52A1\uFF0C\u8BF7\u7B49\u5F85\u5B83\u7A7A\u95F2\u540E\u91CD\u8BD5\u3002", 409);
40
+ }
41
+ }
42
+
43
+ // src/platform/dsh/host/confirmation.ts
44
+ import { createHmac, timingSafeEqual } from "node:crypto";
45
+ function encode(payload) {
46
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
47
+ }
48
+ function signature(secret, encoded) {
49
+ return createHmac("sha256", secret).update(encoded).digest();
50
+ }
51
+ function createConfirmationToken(secret, facts3, now = Date.now(), ttlMs = 5 * 6e4) {
52
+ const encoded = encode({ ...facts3, expiresAt: now + ttlMs });
53
+ return `${encoded}.${signature(secret, encoded).toString("base64url")}`;
54
+ }
55
+ function verifyConfirmationToken(secret, token, expected, now = Date.now()) {
56
+ const [encoded, receivedText, extra] = token.split(".");
57
+ if (encoded === void 0 || receivedText === void 0 || extra !== void 0) {
58
+ throw new DshMoreError("stale-preview", "\u786E\u8BA4\u4FE1\u606F\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u3002", 409);
59
+ }
60
+ const received = Buffer.from(receivedText, "base64url");
61
+ const wanted = signature(secret, encoded);
62
+ if (received.length !== wanted.length || !timingSafeEqual(received, wanted)) {
63
+ throw new DshMoreError("stale-preview", "\u786E\u8BA4\u4FE1\u606F\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u3002", 409);
64
+ }
65
+ let payload;
66
+ try {
67
+ payload = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
68
+ } catch {
69
+ throw new DshMoreError("stale-preview", "\u786E\u8BA4\u4FE1\u606F\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u3002", 409);
70
+ }
71
+ const same = payload.sessionId === expected.sessionId && payload.startTurn === expected.startTurn && payload.endTurn === expected.endTurn && payload.logRevision === expected.logRevision && payload.replaceGeneration === expected.replaceGeneration && payload.operation === expected.operation && payload.targetSeq === expected.targetSeq && payload.contentDigest === expected.contentDigest && payload.continuationSessionId === expected.continuationSessionId && payload.shadowedSeqs.length === expected.shadowedSeqs.length && payload.shadowedSeqs.every((seq, index) => seq === expected.shadowedSeqs[index]);
72
+ if (!same || !Number.isFinite(payload.expiresAt) || payload.expiresAt < now) {
73
+ throw new DshMoreError("stale-preview", "\u4F1A\u8BDD\u5DF2\u53D8\u5316\u6216\u786E\u8BA4\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u3002", 409);
74
+ }
75
+ }
76
+
77
+ // src/platform/dsh/host/wire.ts
78
+ var MAX_BODY_BYTES = 64 * 1024;
79
+ async function readJson(req) {
80
+ const chunks = [];
81
+ let size = 0;
82
+ for await (const chunk of req) {
83
+ const bytes = Buffer.from(chunk);
84
+ size += bytes.length;
85
+ if (size > MAX_BODY_BYTES) throw new DshMoreError("bad-request", "\u8BF7\u6C42\u5185\u5BB9\u8FC7\u5927\u3002");
86
+ chunks.push(bytes);
87
+ }
88
+ const text = Buffer.concat(chunks).toString("utf8");
89
+ if (text.trim() === "") return {};
90
+ try {
91
+ return JSON.parse(text);
92
+ } catch {
93
+ throw new DshMoreError("bad-request", "\u8BF7\u6C42\u4E0D\u662F\u6709\u6548\u7684 JSON\u3002");
94
+ }
95
+ }
96
+ function requireString(value, key) {
97
+ const found = value?.[key];
98
+ if (typeof found !== "string" || found === "") throw new DshMoreError("bad-request", `\u7F3A\u5C11 ${key}\u3002`);
99
+ return found;
100
+ }
101
+ function requireInteger(value, key) {
102
+ const found = value?.[key];
103
+ if (!Number.isSafeInteger(found)) throw new DshMoreError("bad-request", `${key} \u5FC5\u987B\u662F\u6574\u6570\u3002`);
104
+ return found;
105
+ }
106
+ function write(res, status, body) {
107
+ res.writeHead(status, {
108
+ "content-type": "application/json; charset=utf-8",
109
+ "cache-control": "no-store",
110
+ "x-content-type-options": "nosniff"
111
+ });
112
+ res.end(JSON.stringify(body));
113
+ }
114
+ function writeOk(res, value) {
115
+ write(res, 200, { ok: true, value });
116
+ }
117
+ function writeError(res, error) {
118
+ if (error instanceof DshMoreError) {
119
+ write(res, error.status, { ok: false, error: { code: error.code, message: error.message } });
120
+ return;
121
+ }
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ write(res, 500, { ok: false, error: { code: "internal", message } });
124
+ }
125
+
126
+ // src/patches/message-edit/shared.ts
127
+ var MESSAGE_EDIT_PATCH_ID = "message-edit";
128
+
129
+ // src/patches/message-edit/host/edit-continuation.ts
130
+ import { createHash, randomUUID } from "node:crypto";
131
+ import { resolveSessionPreset } from "@deepseek-ai/dsh-agent-presets";
132
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
133
+ import { SessionId as SessionId2 } from "@deepseek-ai/dsh-session";
134
+
135
+ // src/platform/dsh/host/session-history.ts
136
+ function completedTurns(events) {
137
+ const starts = /* @__PURE__ */ new Map();
138
+ const turns = [];
139
+ for (const event of events) {
140
+ if (event.type === "turn/start") {
141
+ starts.set(event.data.turn, event.seq);
142
+ continue;
143
+ }
144
+ if (event.type !== "turn/end") continue;
145
+ const startSeq = starts.get(event.data.turn);
146
+ if (startSeq !== void 0) turns.push({ turn: event.data.turn, startSeq, endSeq: event.seq });
147
+ }
148
+ return turns.sort((left, right) => left.startSeq - right.startSeq);
149
+ }
150
+
151
+ // src/platform/dsh/host/runtime-context.ts
152
+ var RUNTIME_CONTEXT_PLUGIN = "@deepseek-ai/dsh-system-prompt";
153
+ var RUNTIME_CONTEXT_PREFIX = "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\n";
154
+ function retainedRuntimeContexts(seed) {
155
+ for (let index = seed.length - 1; index >= 0; index -= 1) {
156
+ const event = seed[index];
157
+ if (event?.type !== "user/message") continue;
158
+ const source = event.data.source;
159
+ if (source.plugin !== RUNTIME_CONTEXT_PLUGIN) continue;
160
+ if (source.form === "snapshot" && Array.isArray(source.sections)) {
161
+ const sections = source.sections.filter((section) => {
162
+ const value = section;
163
+ return typeof value.name === "string" && typeof value.text === "string";
164
+ });
165
+ if (sections.length === source.sections.length) return sections.map((section) => ({ ...section }));
166
+ }
167
+ const [block] = event.data.content;
168
+ if (event.data.content.length === 1 && block?.type === "text" && block.text.startsWith(RUNTIME_CONTEXT_PREFIX)) {
169
+ return [{ name: "dsh-more:retained-runtime-context", text: block.text.slice(RUNTIME_CONTEXT_PREFIX.length) }];
170
+ }
171
+ return [];
172
+ }
173
+ return [];
174
+ }
175
+ function replaySeedRuntimeContext(agentCtx, seed) {
176
+ const retained = retainedRuntimeContexts(seed);
177
+ return agentCtx.on("system-prompt/assemble", async (_assembly, _context, next) => ({
178
+ ...await next(),
179
+ contexts: retained.map((context) => ({ ...context }))
180
+ }));
181
+ }
182
+
183
+ // src/patches/message-edit/host/edit-continuation.ts
184
+ function textDigest(text) {
185
+ return createHash("sha256").update(text).digest("hex");
186
+ }
187
+ function editedContent(original, text) {
188
+ const kept = original.filter((block) => block.type !== "text");
189
+ return [{ type: "text", text }, ...kept];
190
+ }
191
+ function inspectEditCut(session, targetSeq, editedText) {
192
+ const text = editedText.trim();
193
+ if (text === "") throw new DshMoreError("bad-request", "\u7F16\u8F91\u540E\u7684\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A\u3002");
194
+ if (text.length > 1e5) throw new DshMoreError("bad-request", "\u7F16\u8F91\u540E\u7684\u6D88\u606F\u8FC7\u957F\u3002");
195
+ const event = session.events[targetSeq];
196
+ if (event?.type !== "user/message" || event.data.source.kind !== "user" || event.surfaceOp !== "append") {
197
+ throw new DshMoreError("bad-request", "\u53EA\u80FD\u7F16\u8F91\u666E\u901A\u7528\u6237\u6D88\u606F\u3002");
198
+ }
199
+ const turns = completedTurns(session.events);
200
+ const owning = turns.find((turn) => targetSeq >= turn.startSeq && targetSeq <= turn.endSeq);
201
+ if (owning === void 0) throw new DshMoreError("invalid-turn-range", "\u8FD9\u6761\u6D88\u606F\u6240\u5C5E\u8F6E\u6B21\u5C1A\u672A\u5B8C\u6210\u3002", 409);
202
+ return {
203
+ targetSeq,
204
+ turn: owning.turn,
205
+ turnStartSeq: owning.startSeq,
206
+ laterTurnCount: turns.filter((turn) => turn.turn >= owning.turn).length,
207
+ contentDigest: textDigest(text)
208
+ };
209
+ }
210
+ function sourceMessage(session, targetSeq) {
211
+ const event = session.events[targetSeq];
212
+ if (event?.type !== "user/message") throw new DshMoreError("bad-request", "\u7528\u6237\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
213
+ return event;
214
+ }
215
+ async function createEditedContinuation(ctx, sourceAgent, cut, editedText, childId = SessionId2(`session-${randomUUID()}`)) {
216
+ const source = sourceAgent.session;
217
+ const original = sourceMessage(source, cut.targetSeq);
218
+ const presetId = resolveSessionPreset(source);
219
+ const roster = ctx.get("agentPresets");
220
+ const request = source.requestHeader()?.config;
221
+ const seed = source.events.slice(0, cut.turnStartSeq);
222
+ let releaseRuntimeContextReplay;
223
+ const releaseReplay = () => {
224
+ const release = releaseRuntimeContextReplay;
225
+ releaseRuntimeContextReplay = void 0;
226
+ release?.();
227
+ };
228
+ const child = await ctx.agents.create({
229
+ sessionId: childId,
230
+ seed,
231
+ meta: {
232
+ ...source.header.cwd === void 0 ? {} : { cwd: source.header.cwd },
233
+ parentSession: source.id,
234
+ seedLength: cut.turnStartSeq,
235
+ ...presetId === void 0 ? {} : { agentPreset: presetId }
236
+ },
237
+ agentOptions: {
238
+ ...(request?.provider ?? sourceAgent.options.provider) === void 0 ? {} : { provider: request?.provider ?? sourceAgent.options.provider },
239
+ ...(request?.model ?? sourceAgent.options.model) === void 0 ? {} : { model: request?.model ?? sourceAgent.options.model },
240
+ ...sourceAgent.options.maxTokens === void 0 ? {} : { maxTokens: sourceAgent.options.maxTokens }
241
+ },
242
+ setup: (agentCtx) => {
243
+ roster?.composeFrom(agentCtx, sourceAgent.ctx);
244
+ releaseRuntimeContextReplay = replaySeedRuntimeContext(agentCtx, seed);
245
+ }
246
+ });
247
+ try {
248
+ const workspace = ctx.workspaceRegistry.list().find((candidate) => candidate.sessionIds.includes(source.id));
249
+ if (workspace !== void 0) await workspace.attachSession(childId);
250
+ await ctx.workspaceRegistry.archiveSession(source.id);
251
+ child.agent.followup(createUserMessage({
252
+ content: editedContent(original.data.content, editedText.trim()),
253
+ source: { kind: "user" }
254
+ }));
255
+ void child.agent.whenIdle().then(releaseReplay, releaseReplay);
256
+ return { sessionId: childId };
257
+ } catch (error) {
258
+ releaseReplay();
259
+ await child.dispose().catch(() => void 0);
260
+ throw error;
261
+ }
262
+ }
263
+
264
+ // src/patches/message-edit/host/index.ts
265
+ function facts(sessionId, session, cut, continuationSessionId) {
266
+ return {
267
+ sessionId,
268
+ startTurn: cut.turn,
269
+ endTurn: cut.turn,
270
+ logRevision: session.seq,
271
+ replaceGeneration: session.surface.replaceGeneration,
272
+ shadowedSeqs: [cut.turnStartSeq, Math.max(cut.turnStartSeq, session.seq - 1)],
273
+ operation: "edit-message",
274
+ targetSeq: cut.targetSeq,
275
+ contentDigest: cut.contentDigest,
276
+ continuationSessionId
277
+ };
278
+ }
279
+ function preview(ctx, secret, payload) {
280
+ const sessionId = requireString(payload, "sessionId");
281
+ const session = requireLiveSession(ctx, sessionId);
282
+ const cut = inspectEditCut(session, requireInteger(payload, "targetSeq"), requireString(payload, "text"));
283
+ const continuationSessionId = `session-${randomUUID2()}`;
284
+ return {
285
+ turn: cut.turn,
286
+ laterTurnCount: cut.laterTurnCount,
287
+ continuationSessionId,
288
+ confirmToken: createConfirmationToken(secret, facts(sessionId, session, cut, continuationSessionId))
289
+ };
290
+ }
291
+ async function commit(ctx, secret, payload) {
292
+ const sessionId = requireString(payload, "sessionId");
293
+ const session = requireLiveSession(ctx, sessionId);
294
+ const agent = requireIdleAgent(ctx, sessionId, session);
295
+ return runAgentMaintenance(agent, async (signal) => {
296
+ signal.throwIfAborted();
297
+ const text = requireString(payload, "text");
298
+ const cut = inspectEditCut(session, requireInteger(payload, "targetSeq"), text);
299
+ const continuationSessionId = requireString(payload, "continuationSessionId");
300
+ verifyConfirmationToken(secret, requireString(payload, "confirmToken"), facts(sessionId, session, cut, continuationSessionId));
301
+ const result = await createEditedContinuation(ctx, agent, cut, text, SessionId3(continuationSessionId));
302
+ signal.throwIfAborted();
303
+ return result;
304
+ });
305
+ }
306
+ var hostPatch = {
307
+ id: MESSAGE_EDIT_PATCH_ID,
308
+ routes: ({ ctx, confirmationSecret }) => ({
309
+ preview: (payload) => preview(ctx, confirmationSecret, payload),
310
+ commit: (payload) => commit(ctx, confirmationSecret, payload)
311
+ })
312
+ };
313
+
314
+ // src/patches/message-delete/host/index.ts
315
+ import { randomUUID as randomUUID4 } from "node:crypto";
316
+ import { SessionId as SessionId5 } from "@deepseek-ai/dsh-session";
317
+
318
+ // src/patches/message-delete/shared.ts
319
+ var MESSAGE_DELETE_PATCH_ID = "message-delete";
320
+
321
+ // src/patches/message-delete/host/message-selection.ts
322
+ import { toolPairingBalancedAfter, toolPairingBalancedBefore } from "@deepseek-ai/dsh-compaction";
323
+ import { isAppendSurfaceEvent } from "@deepseek-ai/dsh-session";
324
+ function assertDeletableTarget(session, seq) {
325
+ const event = session.events[seq];
326
+ if (event?.type === "user/message" && isAppendSurfaceEvent(event) && event.data.source.kind === "user") return;
327
+ if (event?.type === "assistant/message" && isAppendSurfaceEvent(event)) return;
328
+ throw new DshMoreError("bad-request", "\u53EA\u80FD\u5220\u9664\u4ECD\u5728\u5F53\u524D\u4E0A\u4E0B\u6587\u4E2D\u7684\u7528\u6237\u6D88\u606F\u6216\u52A9\u624B\u6D88\u606F\u3002");
329
+ }
330
+ function selectMessageDeletion(session, targetSeq) {
331
+ if (!Number.isSafeInteger(targetSeq) || targetSeq < 0) throw new DshMoreError("bad-request", "\u6D88\u606F\u5E8F\u53F7\u65E0\u6548\u3002");
332
+ const nodes = [...session.surface.nodes];
333
+ const targetIndex = nodes.indexOf(targetSeq);
334
+ if (targetIndex < 0) throw new DshMoreError("selection-empty", "\u8FD9\u6761\u6D88\u606F\u5DF2\u7ECF\u4E0D\u5728\u5F53\u524D\u6A21\u578B\u4E0A\u4E0B\u6587\u4E2D\u3002");
335
+ assertDeletableTarget(session, targetSeq);
336
+ let startIndex = targetIndex;
337
+ let endIndex = targetIndex;
338
+ while (!toolPairingBalancedBefore(session, nodes[startIndex])) {
339
+ startIndex -= 1;
340
+ if (startIndex < 0) throw new DshMoreError("invalid-turn-range", "\u65E0\u6CD5\u627E\u5230\u5B89\u5168\u7684\u5DE5\u5177\u8C03\u7528\u8D77\u70B9\u3002");
341
+ }
342
+ while (!toolPairingBalancedAfter(session, nodes[endIndex])) {
343
+ endIndex += 1;
344
+ if (endIndex >= nodes.length) throw new DshMoreError("invalid-turn-range", "\u5DE5\u5177\u8C03\u7528\u4ECD\u672A\u7ED3\u675F\uFF0C\u6682\u65F6\u4E0D\u80FD\u5220\u9664\u8FD9\u6761\u6D88\u606F\u3002");
345
+ }
346
+ const shadowedSeqs = nodes.slice(startIndex, endIndex + 1);
347
+ if (shadowedSeqs.length === 0) throw new DshMoreError("selection-empty", "\u6CA1\u6709\u53EF\u5220\u9664\u7684\u4E0A\u4E0B\u6587\u8282\u70B9\u3002");
348
+ return { targetSeq, shadowedSeqs };
349
+ }
350
+
351
+ // src/patches/message-delete/host/projection.ts
352
+ import { isReplacementSurfaceEvent } from "@deepseek-ai/dsh-session";
353
+ import { z } from "zod";
354
+
355
+ // src/kernel/message-visibility.ts
356
+ var MESSAGE_VISIBILITY_PROJECTION_KEY = "dshMoreMessageDelete";
357
+
358
+ // src/platform/dsh/identity.ts
359
+ var PLUGIN_NAME = "dsh-more";
360
+ var PLUGIN_API_PREFIX = `/${PLUGIN_NAME}/api`;
361
+ var PLUGIN_MUTATION_HEADER = "x-dsh-more";
362
+ var PATCH_SETTINGS_NAMESPACE = PLUGIN_NAME;
363
+
364
+ // src/patches/message-delete/host/projection.ts
365
+ var schema = z.object({
366
+ deletedSeqs: z.array(z.number().int().nonnegative()),
367
+ hiddenTrajectoryKeys: z.array(z.string())
368
+ });
369
+ function deletionUpdate(event) {
370
+ if (!isReplacementSurfaceEvent(event)) return null;
371
+ let metadata;
372
+ if (event.type === "user/message") {
373
+ const source = event.data.source;
374
+ if (source.kind === "plugin" && source.plugin === PLUGIN_NAME) metadata = source;
375
+ } else if (event.type === "assistant/message") {
376
+ const replayState = event.data.message.source.replayState;
377
+ metadata = replayState?.dshMoreMessageDelete;
378
+ }
379
+ if (metadata?.operation !== "delete-message") return null;
380
+ if (!Array.isArray(metadata.deletedSeqs) || !metadata.deletedSeqs.every((seq) => Number.isSafeInteger(seq) && seq >= 0)) return null;
381
+ return {
382
+ seqs: [...metadata.deletedSeqs, event.seq],
383
+ trajectoryKeys: event.type === "assistant/message" ? [`assistant\0${String(event.data.turn)}\0${String(event.data.step)}`] : []
384
+ };
385
+ }
386
+ var messageDeleteProjection = {
387
+ key: MESSAGE_VISIBILITY_PROJECTION_KEY,
388
+ schema,
389
+ stateVersion: 3,
390
+ init: () => ({ deletedSeqs: [], hiddenTrajectoryKeys: [] }),
391
+ apply: (state, event) => {
392
+ const added = deletionUpdate(event);
393
+ if (added === null) return state;
394
+ const nextSeqs = [.../* @__PURE__ */ new Set([...state.deletedSeqs, ...added.seqs])].sort((a, b) => a - b);
395
+ const nextKeys = [.../* @__PURE__ */ new Set([...state.hiddenTrajectoryKeys, ...added.trajectoryKeys])];
396
+ if (nextSeqs.length === state.deletedSeqs.length && nextKeys.length === state.hiddenTrajectoryKeys.length) return state;
397
+ return { deletedSeqs: nextSeqs, hiddenTrajectoryKeys: nextKeys };
398
+ },
399
+ view: (state) => state
400
+ };
401
+
402
+ // src/patches/message-delete/host/rebuild.ts
403
+ import { randomUUID as randomUUID3 } from "node:crypto";
404
+ import { resolveSessionPreset as resolveSessionPreset2 } from "@deepseek-ai/dsh-agent-presets";
405
+ import { CallId } from "@deepseek-ai/dsh-llm";
406
+ import { Session, SessionId as SessionId4 } from "@deepseek-ai/dsh-session";
407
+ function recordedDeletionSeqs(events) {
408
+ const deleted = /* @__PURE__ */ new Set();
409
+ for (const event of events) {
410
+ let values;
411
+ if (event.type === "user/message") {
412
+ const source = event.data.source;
413
+ if (source.plugin === PLUGIN_NAME && source.operation === "delete-message") values = source.deletedSeqs;
414
+ } else if (event.type === "assistant/message") {
415
+ const replay = event.data.message.source.replayState;
416
+ const metadata = replay?.dshMoreMessageDelete;
417
+ if (metadata?.operation === "delete-message") values = metadata.deletedSeqs;
418
+ }
419
+ if (Array.isArray(values)) {
420
+ for (const seq of values) if (Number.isSafeInteger(seq) && seq >= 0) deleted.add(seq);
421
+ }
422
+ if (values !== void 0) deleted.add(event.seq);
423
+ }
424
+ return deleted;
425
+ }
426
+ function toolCalls(message) {
427
+ const calls = [];
428
+ for (const block of message.content) {
429
+ const value = block;
430
+ if (value.type !== "tool-call" || typeof value.name !== "string") continue;
431
+ const callId = typeof value.id === "string" ? value.id : typeof value.callId === "string" ? value.callId : void 0;
432
+ if (callId === void 0) continue;
433
+ calls.push({ callId, name: value.name, arguments: typeof value.arguments === "string" ? value.arguments : "{}" });
434
+ }
435
+ return calls;
436
+ }
437
+ function buildCleanSeed(source, selection) {
438
+ const deleted = recordedDeletionSeqs(source.events);
439
+ for (const seq of selection.shadowedSeqs) deleted.add(seq);
440
+ const survivors = source.surface.nodes.filter((seq) => !deleted.has(seq)).map((seq) => source.events[seq]).filter((event) => event !== void 0 && source.deriveEventMessage(event) !== null);
441
+ const rebuilt = Session.create(SessionId4(`session-rebuild-${randomUUID3()}`));
442
+ let turn = 0;
443
+ let step = 0;
444
+ let turnOpen = false;
445
+ let stepOpen = false;
446
+ let assistantSeen = false;
447
+ const loggedCalls = /* @__PURE__ */ new Set();
448
+ const closeStep = () => {
449
+ if (!stepOpen) return;
450
+ rebuilt.append("step/end", { turn, step });
451
+ stepOpen = false;
452
+ assistantSeen = false;
453
+ loggedCalls.clear();
454
+ };
455
+ const closeTurn = () => {
456
+ if (!turnOpen) return;
457
+ closeStep();
458
+ rebuilt.append("turn/end", { turn, reason: { kind: "completed" } });
459
+ turnOpen = false;
460
+ };
461
+ const openStep = () => {
462
+ if (!turnOpen) {
463
+ turn += 1;
464
+ step = 0;
465
+ rebuilt.append("turn/start", { turn });
466
+ turnOpen = true;
467
+ }
468
+ if (!stepOpen) {
469
+ step += 1;
470
+ rebuilt.append("step/start", { turn, step });
471
+ stepOpen = true;
472
+ }
473
+ };
474
+ for (const event of survivors) {
475
+ if (event.type === "user/message" && event.data.source.kind === "user" && turnOpen) closeTurn();
476
+ if (event.type === "assistant/message" && assistantSeen) closeStep();
477
+ openStep();
478
+ if (event.type === "user/message") {
479
+ rebuilt.append("user/message", event.data, { surfaceOp: "append" });
480
+ continue;
481
+ }
482
+ if (event.type === "assistant/message") {
483
+ rebuilt.append("assistant/message", {
484
+ turn,
485
+ step,
486
+ message: event.data.message,
487
+ ...event.data.usage === void 0 ? {} : { usage: event.data.usage }
488
+ }, { surfaceOp: "append" });
489
+ assistantSeen = true;
490
+ for (const call of toolCalls(event.data.message)) {
491
+ rebuilt.append("tool/call", { turn, step, callId: CallId(call.callId), name: call.name, arguments: call.arguments });
492
+ loggedCalls.add(call.callId);
493
+ }
494
+ continue;
495
+ }
496
+ if (event.type === "tool/result") {
497
+ const callId = String(event.data.message.source.callId);
498
+ if (!loggedCalls.has(callId)) {
499
+ rebuilt.append("tool/call", { turn, step, callId: event.data.message.source.callId, name: "restored-tool", arguments: "{}" });
500
+ loggedCalls.add(callId);
501
+ }
502
+ rebuilt.append("tool/result", { turn, step, message: event.data.message, ...event.data.error === void 0 ? {} : { error: event.data.error }, ...event.data.meta === void 0 ? {} : { meta: event.data.meta } }, { surfaceOp: "append" });
503
+ }
504
+ }
505
+ closeTurn();
506
+ return rebuilt.events;
507
+ }
508
+ async function createDeletedContinuation(ctx, sourceAgent, selection, childId = SessionId4(`session-${randomUUID3()}`)) {
509
+ const source = sourceAgent.session;
510
+ const presetId = resolveSessionPreset2(source);
511
+ const roster = ctx.get("agentPresets");
512
+ const request = source.requestHeader()?.config;
513
+ const provider = request?.provider ?? sourceAgent.options.provider;
514
+ const model = request?.model ?? sourceAgent.options.model;
515
+ const seed = buildCleanSeed(source, selection);
516
+ let firstTurnStarted = false;
517
+ let releaseRuntimeContextReplay;
518
+ let releaseStatusWatch;
519
+ const releaseReplay = () => {
520
+ const releaseContext = releaseRuntimeContextReplay;
521
+ const releaseStatus = releaseStatusWatch;
522
+ releaseRuntimeContextReplay = void 0;
523
+ releaseStatusWatch = void 0;
524
+ releaseContext?.();
525
+ releaseStatus?.();
526
+ };
527
+ const child = await ctx.agents.create({
528
+ sessionId: childId,
529
+ seed,
530
+ meta: {
531
+ ...source.header.cwd === void 0 ? {} : { cwd: source.header.cwd },
532
+ parentSession: source.id,
533
+ seedLength: seed.length,
534
+ ...presetId === void 0 ? {} : { agentPreset: presetId }
535
+ },
536
+ agentOptions: {
537
+ ...provider === void 0 ? {} : { provider },
538
+ ...model === void 0 ? {} : { model },
539
+ ...sourceAgent.options.maxTokens === void 0 ? {} : { maxTokens: sourceAgent.options.maxTokens }
540
+ },
541
+ setup: (agentCtx) => {
542
+ roster?.composeFrom(agentCtx, sourceAgent.ctx);
543
+ releaseRuntimeContextReplay = replaySeedRuntimeContext(agentCtx, seed);
544
+ releaseStatusWatch = agentCtx.on("agent/status", ({ status }) => {
545
+ if (status === "running") firstTurnStarted = true;
546
+ else if (firstTurnStarted) releaseReplay();
547
+ });
548
+ }
549
+ });
550
+ try {
551
+ const workspace = ctx.workspaceRegistry.list().find((candidate) => candidate.sessionIds.includes(source.id));
552
+ if (workspace !== void 0) await workspace.attachSession(childId);
553
+ await ctx.workspaceRegistry.archiveSession(source.id);
554
+ return { sessionId: childId };
555
+ } catch (error) {
556
+ releaseReplay();
557
+ await child.dispose().catch(() => void 0);
558
+ throw error;
559
+ }
560
+ }
561
+
562
+ // src/patches/message-delete/host/index.ts
563
+ function targetTurn(session, seq) {
564
+ return completedTurns(session.events).find((turn) => seq >= turn.startSeq && seq <= turn.endSeq)?.turn ?? 0;
565
+ }
566
+ function facts2(sessionId, session, selection, continuationSessionId) {
567
+ const turn = targetTurn(session, selection.targetSeq);
568
+ return {
569
+ sessionId,
570
+ startTurn: turn,
571
+ endTurn: turn,
572
+ logRevision: session.seq,
573
+ replaceGeneration: session.surface.replaceGeneration,
574
+ shadowedSeqs: selection.shadowedSeqs,
575
+ operation: "delete-message",
576
+ targetSeq: selection.targetSeq,
577
+ continuationSessionId
578
+ };
579
+ }
580
+ function preview2(ctx, secret, payload) {
581
+ const sessionId = requireString(payload, "sessionId");
582
+ const session = requireLiveSession(ctx, sessionId);
583
+ const selection = selectMessageDeletion(session, requireInteger(payload, "targetSeq"));
584
+ const continuationSessionId = `session-${randomUUID4()}`;
585
+ return {
586
+ affectedNodeCount: selection.shadowedSeqs.length,
587
+ continuationSessionId,
588
+ confirmToken: createConfirmationToken(secret, facts2(sessionId, session, selection, continuationSessionId))
589
+ };
590
+ }
591
+ async function commit2(ctx, secret, payload) {
592
+ const sessionId = requireString(payload, "sessionId");
593
+ const session = requireLiveSession(ctx, sessionId);
594
+ const agent = requireIdleAgent(ctx, sessionId, session);
595
+ return runAgentMaintenance(agent, async (signal) => {
596
+ signal.throwIfAborted();
597
+ const selection = selectMessageDeletion(session, requireInteger(payload, "targetSeq"));
598
+ const continuationSessionId = requireString(payload, "continuationSessionId");
599
+ verifyConfirmationToken(secret, requireString(payload, "confirmToken"), facts2(sessionId, session, selection, continuationSessionId));
600
+ const child = await createDeletedContinuation(ctx, agent, selection, SessionId5(continuationSessionId));
601
+ signal.throwIfAborted();
602
+ return { sessionId: child.sessionId, deletedSeqs: selection.shadowedSeqs };
603
+ });
604
+ }
605
+ var hostPatch2 = {
606
+ id: MESSAGE_DELETE_PATCH_ID,
607
+ setup: (ctx) => ctx.effect(
608
+ () => ctx.sessionProjections.register(messageDeleteProjection),
609
+ `${MESSAGE_DELETE_PATCH_ID}: projection`
610
+ ),
611
+ routes: ({ ctx, confirmationSecret }) => ({
612
+ preview: (payload) => preview2(ctx, confirmationSecret, payload),
613
+ commit: (payload) => commit2(ctx, confirmationSecret, payload)
614
+ })
615
+ };
616
+
617
+ // src/patches/session-delete/shared.ts
618
+ var SESSION_DELETE_PATCH_ID = "session-delete";
619
+
620
+ // src/patches/session-delete/host/live-session-handles.ts
621
+ var TRACKER = Symbol.for("dsh-more.session-delete.live-session-handles");
622
+ function trackedHandle(tracker, handle) {
623
+ let disposed = false;
624
+ const wrapped = {
625
+ agent: handle.agent,
626
+ dispose: async () => {
627
+ if (disposed) return;
628
+ disposed = true;
629
+ if (tracker.handles.get(handle.agent.id) === wrapped) tracker.handles.delete(handle.agent.id);
630
+ await handle.dispose();
631
+ }
632
+ };
633
+ tracker.handles.set(handle.agent.id, wrapped);
634
+ return wrapped;
635
+ }
636
+ function installLiveSessionHandleTracker(ctx) {
637
+ const registry = ctx.agents;
638
+ const existing = registry[TRACKER];
639
+ if (existing !== void 0) {
640
+ existing.references += 1;
641
+ return () => {
642
+ existing.references -= 1;
643
+ };
644
+ }
645
+ const originalCreate = registry.create;
646
+ const originalResume = registry.resume;
647
+ const tracker = { handles: /* @__PURE__ */ new Map() };
648
+ tracker.originalCreate = originalCreate;
649
+ tracker.originalResume = originalResume;
650
+ tracker.wrappedCreate = async (options) => trackedHandle(tracker, await originalCreate.call(registry, options));
651
+ tracker.wrappedResume = async (options) => trackedHandle(tracker, await originalResume.call(registry, options));
652
+ tracker.references = 1;
653
+ registry[TRACKER] = tracker;
654
+ registry.create = tracker.wrappedCreate;
655
+ registry.resume = tracker.wrappedResume;
656
+ return () => {
657
+ tracker.references -= 1;
658
+ if (tracker.references > 0 || registry[TRACKER] !== tracker) return;
659
+ if (registry.create === tracker.wrappedCreate) registry.create = tracker.originalCreate;
660
+ if (registry.resume === tracker.wrappedResume) registry.resume = tracker.originalResume;
661
+ delete registry[TRACKER];
662
+ };
663
+ }
664
+ function getLiveSessionHandle(ctx, sessionId) {
665
+ return ctx.agents[TRACKER]?.handles.get(sessionId);
666
+ }
667
+
668
+ // src/patches/session-delete/host/session-deletion.ts
669
+ import { rm, stat } from "node:fs/promises";
670
+ import { dirname, isAbsolute, relative } from "node:path";
671
+ import { SessionId as SessionId6 } from "@deepseek-ai/dsh-session";
672
+ function detachInternal(registry, sessionId, label) {
673
+ const internal = registry;
674
+ const entry = internal.store?.get(sessionId);
675
+ if (entry === void 0) return;
676
+ if (typeof internal.detachEntered !== "function") {
677
+ throw new DshMoreError("internal", `\u5F53\u524D DSH \u7248\u672C\u65E0\u6CD5\u5378\u8F7D\u5DF2\u52A0\u8F7D\u7684${label}\u3002`, 500);
678
+ }
679
+ internal.detachEntered(entry);
680
+ }
681
+ async function unloadLiveSession(ctx, sessionId) {
682
+ const agent = ctx.agents.get(sessionId);
683
+ const session = ctx.sessions.get(sessionId);
684
+ if (agent !== void 0) {
685
+ agent.cancel({ kind: "disposed" });
686
+ await agent.whenIdle();
687
+ }
688
+ if (session !== void 0) await ctx.sessions.flush(session);
689
+ const handle = getLiveSessionHandle(ctx, sessionId);
690
+ if (handle !== void 0) {
691
+ await handle.dispose();
692
+ } else {
693
+ if (agent !== void 0) {
694
+ const scope = agent.scope;
695
+ if (scope === void 0 || typeof scope.dispose !== "function") {
696
+ throw new DshMoreError("internal", "\u5F53\u524D DSH \u7248\u672C\u65E0\u6CD5\u5378\u8F7D\u5DF2\u52A0\u8F7D\u7684 Agent\u3002", 500);
697
+ }
698
+ await scope.dispose();
699
+ detachInternal(ctx.agents, sessionId, " Agent");
700
+ }
701
+ if (session !== void 0) detachInternal(ctx.sessions, sessionId, "\u4F1A\u8BDD");
702
+ }
703
+ if (ctx.agents.get(sessionId) !== void 0 || ctx.sessions.get(sessionId) !== void 0) {
704
+ throw new DshMoreError("internal", "\u4F1A\u8BDD\u505C\u6B62\u540E\u4ECD\u5904\u4E8E\u52A0\u8F7D\u72B6\u6001\uFF0C\u672A\u6267\u884C\u78C1\u76D8\u5220\u9664\u3002", 500);
705
+ }
706
+ }
707
+ function safeSessionDirectory(locationPath) {
708
+ if (!isAbsolute(locationPath)) {
709
+ throw new DshMoreError("internal", "\u5F53\u524D\u6301\u4E45\u5316\u540E\u7AEF\u6CA1\u6709\u8FD4\u56DE\u7EDD\u5BF9\u4F1A\u8BDD\u8DEF\u5F84\u3002", 409);
710
+ }
711
+ const sessionDir = dirname(locationPath);
712
+ const sessionsRoot = dirname(dirname(sessionDir));
713
+ const withinRoot = relative(sessionsRoot, sessionDir);
714
+ if (withinRoot === "" || withinRoot.startsWith("..") || isAbsolute(withinRoot)) {
715
+ throw new DshMoreError("internal", "\u6301\u4E45\u5316\u540E\u7AEF\u8FD4\u56DE\u4E86\u4E0D\u5B89\u5168\u7684\u4F1A\u8BDD\u8DEF\u5F84\u3002", 500);
716
+ }
717
+ return sessionDir;
718
+ }
719
+ async function deleteSessionPermanently(ctx, rawSessionId) {
720
+ const sessionId = SessionId6(rawSessionId);
721
+ const live = ctx.sessions.get(sessionId);
722
+ const header2 = (await ctx.sessionPersistence.list()).find((candidate) => candidate.id === sessionId) ?? live?.header;
723
+ if (header2 === void 0) throw new DshMoreError("not-found", "\u4F1A\u8BDD\u8BB0\u5F55\u4E0D\u5B58\u5728\u3002", 404);
724
+ const location = ctx.sessionPersistence.locate(header2);
725
+ if (location === void 0) {
726
+ throw new DshMoreError("internal", "\u5F53\u524D\u6301\u4E45\u5316\u540E\u7AEF\u4E0D\u652F\u6301\u9010\u4F1A\u8BDD\u7269\u7406\u5220\u9664\u3002", 409);
727
+ }
728
+ if (location.kind !== "jsonl") {
729
+ throw new DshMoreError("internal", `\u6301\u4E45\u5316\u540E\u7AEF ${location.kind} \u4E0D\u652F\u6301\u5B89\u5168\u7684\u9010\u76EE\u5F55\u5220\u9664\u3002`, 409);
730
+ }
731
+ const sessionDir = safeSessionDirectory(location.path);
732
+ await unloadLiveSession(ctx, sessionId);
733
+ const info = await stat(sessionDir).catch(() => void 0);
734
+ if (info !== void 0) {
735
+ if (!info.isDirectory()) throw new DshMoreError("internal", "\u4F1A\u8BDD\u6301\u4E45\u5316\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\u3002", 500);
736
+ await rm(sessionDir, { recursive: true, force: false });
737
+ }
738
+ const workspaces = ctx.workspaceRegistry.list().filter((candidate) => candidate.sessionIds.includes(sessionId));
739
+ await Promise.all(workspaces.map(async (workspace) => workspace.detachSession(sessionId)));
740
+ return { sessionId, deletedPath: sessionDir };
741
+ }
742
+
743
+ // src/patches/session-delete/host/index.ts
744
+ var hostPatch3 = {
745
+ id: SESSION_DELETE_PATCH_ID,
746
+ setup: installLiveSessionHandleTracker,
747
+ routes: ({ ctx }) => ({
748
+ commit: async (payload) => {
749
+ const sessionId = requireString(payload, "sessionId");
750
+ if (requireString(payload, "confirmSessionId") !== sessionId) {
751
+ throw new DshMoreError("bad-request", "\u4F1A\u8BDD\u5220\u9664\u786E\u8BA4\u4E0D\u5339\u914D\u3002");
752
+ }
753
+ return deleteSessionPermanently(ctx, sessionId);
754
+ }
755
+ })
756
+ };
757
+
758
+ // src/generated/host-registry.ts
759
+ var HOST_PATCHES = [hostPatch, hostPatch2, hostPatch3];
760
+
761
+ // src/generated/patch-catalog.ts
762
+ var DEFAULT_PATCH_SETTINGS = {
763
+ "message-edit": true,
764
+ "message-delete": true,
765
+ "session-delete": true
766
+ };
767
+
768
+ // src/kernel/host/activation.ts
769
+ var NOOP = () => void 0;
770
+ var HostPatchActivation = class {
771
+ constructor(ctx, patches) {
772
+ this.ctx = ctx;
773
+ this.byId = new Map(patches.map((patch) => [patch.id, patch]));
774
+ }
775
+ byId;
776
+ disposers = /* @__PURE__ */ new Map();
777
+ settings = { ...DEFAULT_PATCH_SETTINGS };
778
+ apply(next) {
779
+ for (const [id, patch] of this.byId) {
780
+ const enabled = next[id] === true;
781
+ const active = this.disposers.has(id);
782
+ if (enabled && !active) this.disposers.set(id, patch.setup?.(this.ctx) ?? NOOP);
783
+ else if (!enabled && active) {
784
+ const dispose = this.disposers.get(id);
785
+ this.disposers.delete(id);
786
+ dispose?.();
787
+ }
788
+ }
789
+ this.settings = { ...next };
790
+ }
791
+ isEnabled(id) {
792
+ return this.settings[id] === true && this.byId.has(id);
793
+ }
794
+ dispose() {
795
+ for (const dispose of [...this.disposers.values()].reverse()) dispose();
796
+ this.disposers.clear();
797
+ }
798
+ };
799
+
800
+ // src/kernel/host/settings.ts
801
+ import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
802
+
803
+ // src/generated/host-settings-schema.ts
804
+ import z2 from "@deepseek-ai/schemastery";
805
+ var PatchSettingsSchema = z2.object({
806
+ "message-edit": z2.boolean().default(true),
807
+ "message-delete": z2.boolean().default(true),
808
+ "session-delete": z2.boolean().default(true)
809
+ });
810
+
811
+ // src/kernel/host/settings.ts
812
+ var PATCH_SETTINGS_NAMESPACE2 = settingsNamespace(PATCH_SETTINGS_NAMESPACE);
813
+ function installPatchSettings(ctx, activation) {
814
+ let source = () => DEFAULT_PATCH_SETTINGS;
815
+ activation.apply(source());
816
+ installSettingsSection(ctx, PATCH_SETTINGS_NAMESPACE2, PatchSettingsSchema, DEFAULT_PATCH_SETTINGS, {
817
+ setSource: (next) => {
818
+ source = next;
819
+ },
820
+ onChange: () => {
821
+ activation.apply(source());
822
+ }
823
+ });
824
+ }
825
+
826
+ // src/platform/dsh/host/trust.ts
827
+ function header(req, name2) {
828
+ const value = req.headers[name2];
829
+ return typeof value === "string" ? value : void 0;
830
+ }
831
+ function parseAuthority(authority) {
832
+ try {
833
+ return new URL(`http://${authority}`);
834
+ } catch {
835
+ return void 0;
836
+ }
837
+ }
838
+ function isLoopback(hostname) {
839
+ if (hostname === "localhost" || hostname === "[::1]") return true;
840
+ const parts = hostname.split(".");
841
+ return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
842
+ }
843
+ function trustedAuthority(host, entries) {
844
+ return entries.some((entry) => {
845
+ const parsed = parseAuthority(entry);
846
+ if (parsed === void 0) return false;
847
+ return parsed.port === "" ? parsed.hostname === host.hostname : parsed.host === host.host;
848
+ });
849
+ }
850
+ function isTrustedMutationRequest(req, trustedHosts) {
851
+ if (header(req, PLUGIN_MUTATION_HEADER) !== "1") return false;
852
+ const hostText = header(req, "host");
853
+ if (hostText === void 0) return false;
854
+ const host = parseAuthority(hostText);
855
+ if (host === void 0 || !isLoopback(host.hostname) && !trustedAuthority(host, trustedHosts)) return false;
856
+ if (header(req, "sec-fetch-site") === "cross-site") return false;
857
+ const origin = header(req, "origin");
858
+ if (origin === void 0) return true;
859
+ try {
860
+ return new URL(origin).host === host.host;
861
+ } catch {
862
+ return false;
863
+ }
864
+ }
865
+
866
+ // src/platform/dsh/host/api-router.ts
867
+ function collectRoutes(patches, runtime) {
868
+ const collected = /* @__PURE__ */ new Map();
869
+ for (const patch of patches) {
870
+ for (const [action, handler] of Object.entries(patch.routes(runtime))) {
871
+ const route = `${patch.id}/${action}`;
872
+ if (collected.has(route)) throw new Error(`Duplicate patch API route: ${route}`);
873
+ collected.set(route, { patchId: patch.id, handler });
874
+ }
875
+ }
876
+ return collected;
877
+ }
878
+ function registerPatchApi(ctx, patches, confirmationSecret, isEnabled) {
879
+ const host = ctx;
880
+ const routes = collectRoutes(patches, { ctx, confirmationSecret });
881
+ ctx.effect(() => ctx.webServer.register({
882
+ kind: "prefix",
883
+ path: PLUGIN_API_PREFIX,
884
+ handler: async (req, res) => {
885
+ if (!isTrustedMutationRequest(req, host.webRuntime.trustedHosts)) {
886
+ writeError(res, new DshMoreError("forbidden", "\u8BF7\u6C42\u6765\u6E90\u4E0D\u53D7\u4FE1\u4EFB\u3002", 403));
887
+ return;
888
+ }
889
+ if (req.method !== "POST") {
890
+ writeError(res, new DshMoreError("method-not-allowed", "\u53EA\u63A5\u53D7 POST \u8BF7\u6C42\u3002", 405));
891
+ return;
892
+ }
893
+ const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
894
+ const routePrefix = `${PLUGIN_API_PREFIX}/`;
895
+ const route = pathname.startsWith(routePrefix) ? pathname.slice(routePrefix.length) : "";
896
+ const registered = routes.get(route);
897
+ if (registered === void 0) {
898
+ writeError(res, new DshMoreError("not-found", "\u672A\u77E5\u63A5\u53E3\u3002", 404));
899
+ return;
900
+ }
901
+ if (!isEnabled(registered.patchId)) {
902
+ writeError(res, new DshMoreError("patch-disabled", "\u8FD9\u4E2A\u8865\u4E01\u5F53\u524D\u5DF2\u5173\u95ED\u3002", 409));
903
+ return;
904
+ }
905
+ try {
906
+ writeOk(res, await registered.handler(await readJson(req)));
907
+ } catch (error) {
908
+ writeError(res, error);
909
+ }
910
+ }
911
+ }), `${PLUGIN_NAME}: patch API`);
912
+ }
913
+
914
+ // src/index.ts
915
+ var name = PLUGIN_NAME;
916
+ var inject = [
917
+ "webServer",
918
+ "webRuntime",
919
+ "agents",
920
+ "sessions",
921
+ "sessionPersistence",
922
+ "sessionProjections",
923
+ "workspaceRegistry",
924
+ "agentPresets",
925
+ "settings"
926
+ ];
927
+ function apply(ctx) {
928
+ const activation = new HostPatchActivation(ctx, HOST_PATCHES);
929
+ ctx.effect(() => () => activation.dispose(), `${PLUGIN_NAME}: patch activation`);
930
+ installPatchSettings(ctx, activation);
931
+ registerPatchApi(ctx, HOST_PATCHES, randomBytes(32), (patchId) => activation.isEnabled(patchId));
932
+ }
933
+ export {
934
+ apply,
935
+ inject,
936
+ name
937
+ };