@artenx/dshb 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import "./workspace-instructions.js";
2
+ import { a as registerWorkspaceInstructionInjector, i as name, n as escapeInstructionFrameBody, o as renderInstructionMessageText, r as inject, s as resolveSnapshotForSession, t as createInstructionMessage } from "../workspace-instruction-context-CxUldQkP.js";
3
+ export { createInstructionMessage, escapeInstructionFrameBody, inject, name, registerWorkspaceInstructionInjector, renderInstructionMessageText, resolveSnapshotForSession };
@@ -0,0 +1,7 @@
1
+ import { WorkspaceInstructionStore } from "./workspace-instructions.js";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/core/workspace-instruction-routes.d.ts
4
+ declare function registerWorkspaceInstructionRoutes(ctx: Context, store: WorkspaceInstructionStore): void;
5
+ //#endregion
6
+ export { registerWorkspaceInstructionRoutes };
7
+ //# sourceMappingURL=workspace-instruction-routes.d.ts.map
@@ -0,0 +1,155 @@
1
+ import { INSTRUCTION_MAX_BYTES, InstructionConflictError, InstructionSizeError } from "./workspace-instructions.js";
2
+ //#region src/core/workspace-instruction-routes.ts
3
+ const MAX_BODY = 65536;
4
+ function sendJson(res, status, body) {
5
+ res.writeHead(status, { "content-type": "application/json" });
6
+ res.end(JSON.stringify(body));
7
+ }
8
+ function readBody(req) {
9
+ return new Promise((resolve) => {
10
+ if ((req.headers["content-type"] ?? "") !== "application/json") {
11
+ resolve(null);
12
+ return;
13
+ }
14
+ const chunks = [];
15
+ let size = 0;
16
+ req.on("data", (chunk) => {
17
+ size += chunk.length;
18
+ if (size > MAX_BODY) {
19
+ resolve(null);
20
+ req.destroy();
21
+ return;
22
+ }
23
+ chunks.push(chunk);
24
+ });
25
+ req.on("end", () => {
26
+ try {
27
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
28
+ } catch {
29
+ resolve(null);
30
+ }
31
+ });
32
+ req.on("error", () => resolve(null));
33
+ });
34
+ }
35
+ function pathSegments(req) {
36
+ return new URL(req.url ?? "/", "http://x").pathname.split("/").filter(Boolean);
37
+ }
38
+ function registerWorkspaceInstructionRoutes(ctx, store) {
39
+ const webServer = ctx.webServer;
40
+ ctx.effect(() => webServer.register({
41
+ kind: "prefix",
42
+ path: "/api/dshb/instructions",
43
+ handler: async (req, res) => {
44
+ const workspaceId = pathSegments(req)[3];
45
+ if (!workspaceId) {
46
+ sendJson(res, 404, {
47
+ ok: false,
48
+ error: "not found"
49
+ });
50
+ return;
51
+ }
52
+ const registry = ctx.get("workspaceRegistry");
53
+ if (registry) {
54
+ if (!registry.get(workspaceId)) {
55
+ sendJson(res, 404, {
56
+ ok: false,
57
+ error: "workspace not found"
58
+ });
59
+ return;
60
+ }
61
+ }
62
+ if (req.method === "GET") {
63
+ const record = store.get(workspaceId);
64
+ if (!record) {
65
+ sendJson(res, 200, {
66
+ workspaceId,
67
+ text: "",
68
+ revision: 0,
69
+ updatedAt: null
70
+ });
71
+ return;
72
+ }
73
+ sendJson(res, 200, {
74
+ workspaceId: record.workspaceId,
75
+ text: record.text,
76
+ revision: record.revision,
77
+ updatedAt: record.updatedAt
78
+ });
79
+ return;
80
+ }
81
+ if (req.method === "PUT") {
82
+ const body = await readBody(req);
83
+ if (!body || typeof body.text !== "string" || typeof body.expectedRevision !== "number") {
84
+ sendJson(res, 400, {
85
+ ok: false,
86
+ error: "需要 text 和 expectedRevision"
87
+ });
88
+ return;
89
+ }
90
+ const text = body.text;
91
+ const expectedRevision = body.expectedRevision;
92
+ try {
93
+ if (text.length === 0) {
94
+ store.clear(workspaceId, expectedRevision);
95
+ sendJson(res, 200, {
96
+ workspaceId,
97
+ text: "",
98
+ revision: expectedRevision,
99
+ updatedAt: null
100
+ });
101
+ return;
102
+ }
103
+ const record = store.update({
104
+ workspaceId,
105
+ text,
106
+ expectedRevision
107
+ });
108
+ sendJson(res, 200, {
109
+ workspaceId: record.workspaceId,
110
+ text: record.text,
111
+ revision: record.revision,
112
+ updatedAt: record.updatedAt
113
+ });
114
+ return;
115
+ } catch (err) {
116
+ if (err instanceof InstructionConflictError) {
117
+ const current = store.get(workspaceId);
118
+ sendJson(res, 409, {
119
+ ok: false,
120
+ error: "revision conflict",
121
+ expected: err.expected,
122
+ actual: err.actual,
123
+ currentRevision: current?.revision ?? 0,
124
+ currentText: current?.text ?? "",
125
+ currentUpdatedAt: current?.updatedAt ?? null
126
+ });
127
+ return;
128
+ }
129
+ if (err instanceof InstructionSizeError) {
130
+ sendJson(res, 400, {
131
+ ok: false,
132
+ error: `instruction exceeds ${INSTRUCTION_MAX_BYTES} bytes`,
133
+ bytes: err.bytes,
134
+ limit: INSTRUCTION_MAX_BYTES
135
+ });
136
+ return;
137
+ }
138
+ sendJson(res, 500, {
139
+ ok: false,
140
+ error: err instanceof Error ? err.message : String(err)
141
+ });
142
+ return;
143
+ }
144
+ }
145
+ sendJson(res, 404, {
146
+ ok: false,
147
+ error: "not found"
148
+ });
149
+ }
150
+ }));
151
+ }
152
+ //#endregion
153
+ export { registerWorkspaceInstructionRoutes };
154
+
155
+ //# sourceMappingURL=workspace-instruction-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-instruction-routes.js","names":[],"sources":["../../src/core/workspace-instruction-routes.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport {\n InstructionConflictError,\n InstructionSizeError,\n INSTRUCTION_MAX_BYTES,\n type WorkspaceInstructionStore,\n} from './workspace-instructions.js'\n\nconst MAX_BODY = 64 * 1024\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n if ((req.headers['content-type'] ?? '') !== 'application/json') {\n resolve(null)\n return\n }\n const chunks: Buffer[] = []\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > MAX_BODY) {\n resolve(null)\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n try {\n resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>)\n } catch {\n resolve(null)\n }\n })\n req.on('error', () => resolve(null))\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\ninterface WorkspaceRegistryLike {\n get(workspaceId: string): { id: string; path: string; title: string } | undefined\n}\n\nexport function registerWorkspaceInstructionRoutes(ctx: Context, store: WorkspaceInstructionStore): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/instructions',\n handler: async (req, res) => {\n const segs = pathSegments(req)\n const workspaceId = segs[3]\n if (!workspaceId) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n const registry = ctx.get('workspaceRegistry') as WorkspaceRegistryLike | undefined\n if (registry) {\n const ws = registry.get(workspaceId)\n if (!ws) {\n sendJson(res, 404, { ok: false, error: 'workspace not found' })\n return\n }\n }\n\n if (req.method === 'GET') {\n const record = store.get(workspaceId)\n if (!record) {\n sendJson(res, 200, {\n workspaceId,\n text: '',\n revision: 0,\n updatedAt: null,\n })\n return\n }\n sendJson(res, 200, {\n workspaceId: record.workspaceId,\n text: record.text,\n revision: record.revision,\n updatedAt: record.updatedAt,\n })\n return\n }\n\n if (req.method === 'PUT') {\n const body = await readBody(req)\n if (!body || typeof body.text !== 'string' || typeof body.expectedRevision !== 'number') {\n sendJson(res, 400, { ok: false, error: '需要 text 和 expectedRevision' })\n return\n }\n const text = body.text as string\n const expectedRevision = body.expectedRevision as number\n try {\n if (text.length === 0) {\n store.clear(workspaceId, expectedRevision)\n sendJson(res, 200, {\n workspaceId,\n text: '',\n revision: expectedRevision,\n updatedAt: null,\n })\n return\n }\n const record = store.update({ workspaceId, text, expectedRevision })\n sendJson(res, 200, {\n workspaceId: record.workspaceId,\n text: record.text,\n revision: record.revision,\n updatedAt: record.updatedAt,\n })\n return\n } catch (err) {\n if (err instanceof InstructionConflictError) {\n const current = store.get(workspaceId)\n sendJson(res, 409, {\n ok: false,\n error: 'revision conflict',\n expected: err.expected,\n actual: err.actual,\n currentRevision: current?.revision ?? 0,\n currentText: current?.text ?? '',\n currentUpdatedAt: current?.updatedAt ?? null,\n })\n return\n }\n if (err instanceof InstructionSizeError) {\n sendJson(res, 400, {\n ok: false,\n error: `instruction exceeds ${INSTRUCTION_MAX_BYTES} bytes`,\n bytes: err.bytes,\n limit: INSTRUCTION_MAX_BYTES,\n })\n return\n }\n sendJson(res, 500, { ok: false, error: err instanceof Error ? err.message : String(err) })\n return\n }\n }\n\n sendJson(res, 404, { ok: false, error: 'not found' })\n },\n }),\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ //#region src/core/workspace-instructions.d.ts
2
+ declare const INSTRUCTION_MAX_BYTES = 32768;
3
+ interface WorkspaceInstructionRecord {
4
+ workspaceId: string;
5
+ text: string;
6
+ revision: number;
7
+ digest: string;
8
+ createdAt: string;
9
+ updatedAt: string;
10
+ }
11
+ interface SessionInstructionSnapshot {
12
+ sessionId: string;
13
+ workspaceId?: string;
14
+ workspaceRevision?: number;
15
+ digest?: string;
16
+ text: string;
17
+ capturedAt: string;
18
+ inheritedFromSessionId?: string;
19
+ }
20
+ declare class InstructionConflictError extends Error {
21
+ readonly expected: number;
22
+ readonly actual: number;
23
+ constructor(expected: number, actual: number);
24
+ }
25
+ declare class InstructionSizeError extends Error {
26
+ readonly bytes: number;
27
+ constructor(bytes: number);
28
+ }
29
+ declare class WorkspaceInstructionStore {
30
+ private data;
31
+ private loaded;
32
+ private file;
33
+ private load;
34
+ private save;
35
+ get(workspaceId: string): WorkspaceInstructionRecord | undefined;
36
+ update(input: {
37
+ workspaceId: string;
38
+ text: string;
39
+ expectedRevision: number;
40
+ }): WorkspaceInstructionRecord;
41
+ clear(workspaceId: string, expectedRevision: number): void;
42
+ removeByWorkspace(workspaceId: string): void;
43
+ }
44
+ declare class SessionInstructionSnapshotStore {
45
+ private data;
46
+ private loaded;
47
+ private file;
48
+ private load;
49
+ private save;
50
+ get(sessionId: string): SessionInstructionSnapshot | undefined;
51
+ createIfAbsent(sessionId: string, source: {
52
+ kind: 'workspace';
53
+ record: WorkspaceInstructionRecord;
54
+ workspaceId: string;
55
+ } | {
56
+ kind: 'empty';
57
+ workspaceId?: string;
58
+ } | {
59
+ kind: 'inherit';
60
+ parentSnapshot: SessionInstructionSnapshot;
61
+ }): SessionInstructionSnapshot;
62
+ remove(sessionId: string): void;
63
+ }
64
+ declare function sharedWorkspaceInstructionStore(): WorkspaceInstructionStore;
65
+ declare function sharedSessionInstructionSnapshotStore(): SessionInstructionSnapshotStore;
66
+ //#endregion
67
+ export { INSTRUCTION_MAX_BYTES, InstructionConflictError, InstructionSizeError, SessionInstructionSnapshot, SessionInstructionSnapshotStore, WorkspaceInstructionRecord, WorkspaceInstructionStore, sharedSessionInstructionSnapshotStore, sharedWorkspaceInstructionStore };
68
+ //# sourceMappingURL=workspace-instructions.d.ts.map
@@ -0,0 +1,191 @@
1
+ import { homedir } from "node:os";
2
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ //#region src/core/workspace-instructions.ts
6
+ const INSTRUCTION_MAX_BYTES = 32768;
7
+ var InstructionConflictError = class extends Error {
8
+ expected;
9
+ actual;
10
+ constructor(expected, actual) {
11
+ super(`revision conflict: expected ${expected}, current ${actual}`);
12
+ this.name = "InstructionConflictError";
13
+ this.expected = expected;
14
+ this.actual = actual;
15
+ }
16
+ };
17
+ var InstructionSizeError = class extends Error {
18
+ bytes;
19
+ constructor(bytes) {
20
+ super(`instruction exceeds ${INSTRUCTION_MAX_BYTES} bytes (got ${bytes})`);
21
+ this.name = "InstructionSizeError";
22
+ this.bytes = bytes;
23
+ }
24
+ };
25
+ function dshHome() {
26
+ return process.env.DSH_HOME ?? join(homedir(), ".dsh");
27
+ }
28
+ function utf8Bytes(text) {
29
+ return Buffer.byteLength(text, "utf8");
30
+ }
31
+ function computeDigest(text) {
32
+ return createHash("sha256").update(text, "utf8").digest("hex");
33
+ }
34
+ var WorkspaceInstructionStore = class {
35
+ data = { records: {} };
36
+ loaded = false;
37
+ file() {
38
+ return join(dshHome(), "dshb", "workspace-instructions.json");
39
+ }
40
+ load() {
41
+ if (this.loaded) return;
42
+ this.loaded = true;
43
+ try {
44
+ const raw = readFileSync(this.file(), "utf8");
45
+ this.data = JSON.parse(raw);
46
+ if (!this.data.records || typeof this.data.records !== "object") this.data = { records: {} };
47
+ } catch {
48
+ this.data = { records: {} };
49
+ }
50
+ }
51
+ save() {
52
+ this.load();
53
+ const file = this.file();
54
+ mkdirSync(join(file, ".."), {
55
+ recursive: true,
56
+ mode: 448
57
+ });
58
+ const tmp = `${file}.${process.pid}.tmp`;
59
+ writeFileSync(tmp, JSON.stringify(this.data, null, 2), { mode: 384 });
60
+ renameSync(tmp, file);
61
+ chmodSync(file, 384);
62
+ }
63
+ get(workspaceId) {
64
+ this.load();
65
+ const r = this.data.records[workspaceId];
66
+ return r ? { ...r } : void 0;
67
+ }
68
+ update(input) {
69
+ this.load();
70
+ const bytes = utf8Bytes(input.text);
71
+ if (bytes > 32768) throw new InstructionSizeError(bytes);
72
+ const existing = this.data.records[input.workspaceId];
73
+ if (existing && existing.revision !== input.expectedRevision) throw new InstructionConflictError(input.expectedRevision, existing.revision);
74
+ const now = (/* @__PURE__ */ new Date()).toISOString();
75
+ const record = {
76
+ workspaceId: input.workspaceId,
77
+ text: input.text,
78
+ revision: (existing?.revision ?? 0) + 1,
79
+ digest: computeDigest(input.text),
80
+ createdAt: existing?.createdAt ?? now,
81
+ updatedAt: now
82
+ };
83
+ this.data.records[input.workspaceId] = record;
84
+ this.save();
85
+ return { ...record };
86
+ }
87
+ clear(workspaceId, expectedRevision) {
88
+ this.load();
89
+ const existing = this.data.records[workspaceId];
90
+ if (existing && existing.revision !== expectedRevision) throw new InstructionConflictError(expectedRevision, existing.revision);
91
+ if (!existing) return;
92
+ delete this.data.records[workspaceId];
93
+ this.save();
94
+ }
95
+ removeByWorkspace(workspaceId) {
96
+ this.load();
97
+ if (!this.data.records[workspaceId]) return;
98
+ delete this.data.records[workspaceId];
99
+ this.save();
100
+ }
101
+ };
102
+ var SessionInstructionSnapshotStore = class {
103
+ data = { records: {} };
104
+ loaded = false;
105
+ file() {
106
+ return join(dshHome(), "dshb", "session-instruction-snapshots.json");
107
+ }
108
+ load() {
109
+ if (this.loaded) return;
110
+ this.loaded = true;
111
+ try {
112
+ const raw = readFileSync(this.file(), "utf8");
113
+ this.data = JSON.parse(raw);
114
+ if (!this.data.records || typeof this.data.records !== "object") this.data = { records: {} };
115
+ } catch {
116
+ this.data = { records: {} };
117
+ }
118
+ }
119
+ save() {
120
+ this.load();
121
+ const file = this.file();
122
+ mkdirSync(join(file, ".."), {
123
+ recursive: true,
124
+ mode: 448
125
+ });
126
+ const tmp = `${file}.${process.pid}.tmp`;
127
+ writeFileSync(tmp, JSON.stringify(this.data, null, 2), { mode: 384 });
128
+ renameSync(tmp, file);
129
+ chmodSync(file, 384);
130
+ }
131
+ get(sessionId) {
132
+ this.load();
133
+ const r = this.data.records[sessionId];
134
+ return r ? { ...r } : void 0;
135
+ }
136
+ createIfAbsent(sessionId, source) {
137
+ this.load();
138
+ const existing = this.data.records[sessionId];
139
+ if (existing) return { ...existing };
140
+ let snapshot;
141
+ const now = (/* @__PURE__ */ new Date()).toISOString();
142
+ if (source.kind === "workspace") snapshot = {
143
+ sessionId,
144
+ workspaceId: source.workspaceId,
145
+ workspaceRevision: source.record.revision,
146
+ digest: source.record.digest,
147
+ text: source.record.text,
148
+ capturedAt: now
149
+ };
150
+ else if (source.kind === "inherit") {
151
+ const parent = source.parentSnapshot;
152
+ snapshot = {
153
+ sessionId,
154
+ workspaceId: parent.workspaceId,
155
+ workspaceRevision: parent.workspaceRevision,
156
+ digest: parent.digest,
157
+ text: parent.text,
158
+ capturedAt: now,
159
+ inheritedFromSessionId: parent.sessionId
160
+ };
161
+ } else snapshot = {
162
+ sessionId,
163
+ workspaceId: source.workspaceId,
164
+ text: "",
165
+ capturedAt: now
166
+ };
167
+ this.data.records[sessionId] = snapshot;
168
+ this.save();
169
+ return { ...snapshot };
170
+ }
171
+ remove(sessionId) {
172
+ this.load();
173
+ if (!this.data.records[sessionId]) return;
174
+ delete this.data.records[sessionId];
175
+ this.save();
176
+ }
177
+ };
178
+ let sharedWorkspaceStore = null;
179
+ let sharedSnapshotStore = null;
180
+ function sharedWorkspaceInstructionStore() {
181
+ if (!sharedWorkspaceStore) sharedWorkspaceStore = new WorkspaceInstructionStore();
182
+ return sharedWorkspaceStore;
183
+ }
184
+ function sharedSessionInstructionSnapshotStore() {
185
+ if (!sharedSnapshotStore) sharedSnapshotStore = new SessionInstructionSnapshotStore();
186
+ return sharedSnapshotStore;
187
+ }
188
+ //#endregion
189
+ export { INSTRUCTION_MAX_BYTES, InstructionConflictError, InstructionSizeError, SessionInstructionSnapshotStore, WorkspaceInstructionStore, sharedSessionInstructionSnapshotStore, sharedWorkspaceInstructionStore };
190
+
191
+ //# sourceMappingURL=workspace-instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-instructions.js","names":[],"sources":["../../src/core/workspace-instructions.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport const INSTRUCTION_MAX_BYTES = 32768\n\nexport interface WorkspaceInstructionRecord {\n workspaceId: string\n text: string\n revision: number\n digest: string\n createdAt: string\n updatedAt: string\n}\n\nexport interface SessionInstructionSnapshot {\n sessionId: string\n workspaceId?: string\n workspaceRevision?: number\n digest?: string\n text: string\n capturedAt: string\n inheritedFromSessionId?: string\n}\n\nexport class InstructionConflictError extends Error {\n readonly expected: number\n readonly actual: number\n constructor(expected: number, actual: number) {\n super(`revision conflict: expected ${expected}, current ${actual}`)\n this.name = 'InstructionConflictError'\n this.expected = expected\n this.actual = actual\n }\n}\n\nexport class InstructionSizeError extends Error {\n readonly bytes: number\n constructor(bytes: number) {\n super(`instruction exceeds ${INSTRUCTION_MAX_BYTES} bytes (got ${bytes})`)\n this.name = 'InstructionSizeError'\n this.bytes = bytes\n }\n}\n\nfunction dshHome(): string {\n return process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction utf8Bytes(text: string): number {\n return Buffer.byteLength(text, 'utf8')\n}\n\nfunction computeDigest(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex')\n}\n\ninterface WorkspaceStoreData {\n records: Record<string, WorkspaceInstructionRecord>\n}\n\ninterface SnapshotStoreData {\n records: Record<string, SessionInstructionSnapshot>\n}\n\nexport class WorkspaceInstructionStore {\n private data: WorkspaceStoreData = { records: {} }\n private loaded = false\n\n private file(): string {\n return join(dshHome(), 'dshb', 'workspace-instructions.json')\n }\n\n private load(): void {\n if (this.loaded) return\n this.loaded = true\n try {\n const raw = readFileSync(this.file(), 'utf8')\n this.data = JSON.parse(raw) as WorkspaceStoreData\n if (!this.data.records || typeof this.data.records !== 'object') {\n this.data = { records: {} }\n }\n } catch {\n this.data = { records: {} }\n }\n }\n\n private save(): void {\n this.load()\n const file = this.file()\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(this.data, null, 2), { mode: 0o600 })\n renameSync(tmp, file)\n chmodSync(file, 0o600)\n }\n\n get(workspaceId: string): WorkspaceInstructionRecord | undefined {\n this.load()\n const r = this.data.records[workspaceId]\n return r ? { ...r } : undefined\n }\n\n update(input: {\n workspaceId: string\n text: string\n expectedRevision: number\n }): WorkspaceInstructionRecord {\n this.load()\n const bytes = utf8Bytes(input.text)\n if (bytes > INSTRUCTION_MAX_BYTES) throw new InstructionSizeError(bytes)\n const existing = this.data.records[input.workspaceId]\n if (existing && existing.revision !== input.expectedRevision) {\n throw new InstructionConflictError(input.expectedRevision, existing.revision)\n }\n const now = new Date().toISOString()\n const record: WorkspaceInstructionRecord = {\n workspaceId: input.workspaceId,\n text: input.text,\n revision: (existing?.revision ?? 0) + 1,\n digest: computeDigest(input.text),\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n }\n this.data.records[input.workspaceId] = record\n this.save()\n return { ...record }\n }\n\n clear(workspaceId: string, expectedRevision: number): void {\n this.load()\n const existing = this.data.records[workspaceId]\n if (existing && existing.revision !== expectedRevision) {\n throw new InstructionConflictError(expectedRevision, existing.revision)\n }\n if (!existing) return\n delete this.data.records[workspaceId]\n this.save()\n }\n\n removeByWorkspace(workspaceId: string): void {\n this.load()\n if (!this.data.records[workspaceId]) return\n delete this.data.records[workspaceId]\n this.save()\n }\n}\n\nexport class SessionInstructionSnapshotStore {\n private data: SnapshotStoreData = { records: {} }\n private loaded = false\n\n private file(): string {\n return join(dshHome(), 'dshb', 'session-instruction-snapshots.json')\n }\n\n private load(): void {\n if (this.loaded) return\n this.loaded = true\n try {\n const raw = readFileSync(this.file(), 'utf8')\n this.data = JSON.parse(raw) as SnapshotStoreData\n if (!this.data.records || typeof this.data.records !== 'object') {\n this.data = { records: {} }\n }\n } catch {\n this.data = { records: {} }\n }\n }\n\n private save(): void {\n this.load()\n const file = this.file()\n mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(this.data, null, 2), { mode: 0o600 })\n renameSync(tmp, file)\n chmodSync(file, 0o600)\n }\n\n get(sessionId: string): SessionInstructionSnapshot | undefined {\n this.load()\n const r = this.data.records[sessionId]\n return r ? { ...r } : undefined\n }\n\n createIfAbsent(\n sessionId: string,\n source:\n | { kind: 'workspace'; record: WorkspaceInstructionRecord; workspaceId: string }\n | { kind: 'empty'; workspaceId?: string }\n | { kind: 'inherit'; parentSnapshot: SessionInstructionSnapshot },\n ): SessionInstructionSnapshot {\n this.load()\n const existing = this.data.records[sessionId]\n if (existing) return { ...existing }\n let snapshot: SessionInstructionSnapshot\n const now = new Date().toISOString()\n if (source.kind === 'workspace') {\n snapshot = {\n sessionId,\n workspaceId: source.workspaceId,\n workspaceRevision: source.record.revision,\n digest: source.record.digest,\n text: source.record.text,\n capturedAt: now,\n }\n } else if (source.kind === 'inherit') {\n const parent = source.parentSnapshot\n snapshot = {\n sessionId,\n workspaceId: parent.workspaceId,\n workspaceRevision: parent.workspaceRevision,\n digest: parent.digest,\n text: parent.text,\n capturedAt: now,\n inheritedFromSessionId: parent.sessionId,\n }\n } else {\n snapshot = {\n sessionId,\n workspaceId: source.workspaceId,\n text: '',\n capturedAt: now,\n }\n }\n this.data.records[sessionId] = snapshot\n this.save()\n return { ...snapshot }\n }\n\n remove(sessionId: string): void {\n this.load()\n if (!this.data.records[sessionId]) return\n delete this.data.records[sessionId]\n this.save()\n }\n}\n\nlet sharedWorkspaceStore: WorkspaceInstructionStore | null = null\nlet sharedSnapshotStore: SessionInstructionSnapshotStore | null = null\n\nexport function sharedWorkspaceInstructionStore(): WorkspaceInstructionStore {\n if (!sharedWorkspaceStore) sharedWorkspaceStore = new WorkspaceInstructionStore()\n return sharedWorkspaceStore\n}\n\nexport function sharedSessionInstructionSnapshotStore(): SessionInstructionSnapshotStore {\n if (!sharedSnapshotStore) sharedSnapshotStore = new SessionInstructionSnapshotStore()\n return sharedSnapshotStore\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import SandboxedFileSystem from "@deepseek-ai/dsh-fs-sandbox";
2
1
  import { Context } from "@deepseek-ai/cordis";
2
+ import SandboxedFileSystem from "@deepseek-ai/dsh-fs-sandbox";
3
3
  import { FsDirEntry, FsEditOutcome, FsEditRequest, FsInfo, FsPathInfo, FsTarget, FsVersion, FsWriteIntent, FsWriteOutcome } from "@deepseek-ai/dsh-fs";
4
4
  import { SandboxExecutionPolicy } from "@deepseek-ai/dsh-sandbox";
5
5
  //#region src/router/router-fs.d.ts
@@ -1,5 +1,5 @@
1
- import SandboxBashExecutor from "@deepseek-ai/dsh-bash-sandbox";
2
1
  import { Context } from "@deepseek-ai/cordis";
2
+ import SandboxBashExecutor from "@deepseek-ai/dsh-bash-sandbox";
3
3
  import { ShellExecRequest, ShellExecSpec, ShellProcess, ShellRunResult } from "@deepseek-ai/dsh-shell";
4
4
  //#region src/router/router-shell.d.ts
5
5
  declare class RouterShell extends SandboxBashExecutor {
@@ -1,5 +1,5 @@
1
- import LocalSubprocessRuntime from "@deepseek-ai/dsh-subprocess-local";
2
1
  import { Context } from "@deepseek-ai/cordis";
2
+ import LocalSubprocessRuntime from "@deepseek-ai/dsh-subprocess-local";
3
3
  import { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from "@deepseek-ai/dsh-subprocess";
4
4
  //#region src/router/router-subprocess.d.ts
5
5
  declare class RouterSubprocess extends LocalSubprocessRuntime {
@@ -1,5 +1,5 @@
1
- import DirectoryPicker, { DirectoryPickerBrowseCapability } from "@deepseek-ai/dsh-host-directory-picker";
2
1
  import { Context } from "@deepseek-ai/cordis";
2
+ import DirectoryPicker, { DirectoryPickerBrowseCapability } from "@deepseek-ai/dsh-host-directory-picker";
3
3
  //#region src/ui/picker.d.ts
4
4
  declare class DshbDirectoryPicker extends DirectoryPicker {
5
5
  constructor(ctx: Context);