@artenx/dshb 0.1.18 → 0.1.20

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 (112) hide show
  1. package/lib/auth/decorate-webserver.d.ts +2 -1
  2. package/lib/auth/decorate-webserver.js +21 -10
  3. package/lib/auth/decorate-webserver.js.map +1 -1
  4. package/lib/auth/gate.d.ts +3 -1
  5. package/lib/auth/gate.js +18 -1
  6. package/lib/auth/gate.js.map +1 -1
  7. package/lib/auth/index.js +11 -3
  8. package/lib/auth/index.js.map +1 -1
  9. package/lib/auth/webserver.d.ts +3 -1
  10. package/lib/auth/webserver.js +10 -1
  11. package/lib/auth/webserver.js.map +1 -1
  12. package/lib/client.js +2157 -567
  13. package/lib/client.js.map +1 -1
  14. package/lib/core/index.js +72 -4
  15. package/lib/core/index.js.map +1 -1
  16. package/lib/core/node-registry.d.ts +2 -108
  17. package/lib/core/node-registry.js +43 -2
  18. package/lib/core/node-registry.js.map +1 -1
  19. package/lib/core/routes.d.ts +4 -3
  20. package/lib/core/routes.js +67 -23
  21. package/lib/core/routes.js.map +1 -1
  22. package/lib/core/sync/coordinator.d.ts +48 -0
  23. package/lib/core/sync/coordinator.js +247 -0
  24. package/lib/core/sync/coordinator.js.map +1 -0
  25. package/lib/core/sync/host-key.d.ts +2 -0
  26. package/lib/core/sync/host-key.js +71 -0
  27. package/lib/core/sync/host-key.js.map +1 -0
  28. package/lib/core/sync/internal-routes.d.ts +9 -0
  29. package/lib/core/sync/internal-routes.js +236 -0
  30. package/lib/core/sync/internal-routes.js.map +1 -0
  31. package/lib/core/sync/model.d.ts +2 -0
  32. package/lib/core/sync/model.js +106 -0
  33. package/lib/core/sync/model.js.map +1 -0
  34. package/lib/core/sync/node-sync.d.ts +2 -0
  35. package/lib/core/sync/node-sync.js +660 -0
  36. package/lib/core/sync/node-sync.js.map +1 -0
  37. package/lib/core/sync/offline-archive.d.ts +55 -0
  38. package/lib/core/sync/offline-archive.js +673 -0
  39. package/lib/core/sync/offline-archive.js.map +1 -0
  40. package/lib/core/sync/offline-journal.d.ts +22 -0
  41. package/lib/core/sync/offline-journal.js +74 -0
  42. package/lib/core/sync/offline-journal.js.map +1 -0
  43. package/lib/core/sync/offline-routes.d.ts +7 -0
  44. package/lib/core/sync/offline-routes.js +192 -0
  45. package/lib/core/sync/offline-routes.js.map +1 -0
  46. package/lib/core/sync/peer-manager.d.ts +56 -0
  47. package/lib/core/sync/peer-manager.js +258 -0
  48. package/lib/core/sync/peer-manager.js.map +1 -0
  49. package/lib/core/sync/protocol.d.ts +2 -0
  50. package/lib/core/sync/protocol.js +124 -0
  51. package/lib/core/sync/protocol.js.map +1 -0
  52. package/lib/core/sync/remote-helper.d.ts +5 -0
  53. package/lib/core/sync/remote-helper.js +160 -0
  54. package/lib/core/sync/remote-helper.js.map +1 -0
  55. package/lib/core/sync/revision.d.ts +2 -0
  56. package/lib/core/sync/revision.js +77 -0
  57. package/lib/core/sync/revision.js.map +1 -0
  58. package/lib/core/sync/routes.d.ts +9 -0
  59. package/lib/core/sync/routes.js +339 -0
  60. package/lib/core/sync/routes.js.map +1 -0
  61. package/lib/core/sync/scheduler.d.ts +14 -0
  62. package/lib/core/sync/scheduler.js +81 -0
  63. package/lib/core/sync/scheduler.js.map +1 -0
  64. package/lib/core/sync/session-journal.d.ts +2 -0
  65. package/lib/core/sync/session-journal.js +191 -0
  66. package/lib/core/sync/session-journal.js.map +1 -0
  67. package/lib/core/sync/session-routes.d.ts +7 -0
  68. package/lib/core/sync/session-routes.js +67 -0
  69. package/lib/core/sync/session-routes.js.map +1 -0
  70. package/lib/core/sync/session-sync.d.ts +2 -0
  71. package/lib/core/sync/session-sync.js +2 -0
  72. package/lib/core/sync/ssh-transport.d.ts +2 -0
  73. package/lib/core/sync/ssh-transport.js +408 -0
  74. package/lib/core/sync/ssh-transport.js.map +1 -0
  75. package/lib/core/sync/stores.d.ts +3 -0
  76. package/lib/core/sync/stores.js +385 -0
  77. package/lib/core/sync/stores.js.map +1 -0
  78. package/lib/core/test.d.ts +1 -1
  79. package/lib/core/workspace-bindings.d.ts +2 -28
  80. package/lib/core/workspace-bindings.js +14 -14
  81. package/lib/core/workspace-bindings.js.map +1 -1
  82. package/lib/core/workspace-instruction-context.d.ts +3 -2260
  83. package/lib/core/workspace-instruction-context.js +104 -2
  84. package/lib/core/workspace-instruction-context.js.map +1 -0
  85. package/lib/core/workspace-instruction-routes.d.ts +1 -1
  86. package/lib/core/workspace-instructions.d.ts +2 -68
  87. package/lib/core/workspace-instructions.js +13 -0
  88. package/lib/core/workspace-instructions.js.map +1 -1
  89. package/lib/core/workspaces.d.ts +4 -3
  90. package/lib/core/workspaces.js +17 -1
  91. package/lib/core/workspaces.js.map +1 -1
  92. package/lib/host-key-C5afhAUd.d.ts +30 -0
  93. package/lib/index-VF8bcBeX.d.ts +2293 -0
  94. package/lib/{workspace-instruction-context-DytAmaPY.js → lib-gdtU_WKT.js} +159 -102
  95. package/lib/lib-gdtU_WKT.js.map +1 -0
  96. package/lib/model-BP7Gw1yk.d.ts +104 -0
  97. package/lib/node-registry-BTnnM5-k.d.ts +112 -0
  98. package/lib/node-sync-3Z9QLz2n.d.ts +89 -0
  99. package/lib/protocol-CV7LGE44.d.ts +35 -0
  100. package/lib/revision-C9sOlDZ6.d.ts +25 -0
  101. package/lib/router/router-fs.d.ts +12 -3
  102. package/lib/router/router-fs.js.map +1 -1
  103. package/lib/session-journal-pgPqcWn9.d.ts +40 -0
  104. package/lib/session-sync-EdfwUON5.js +2177 -0
  105. package/lib/session-sync-EdfwUON5.js.map +1 -0
  106. package/lib/session-sync-g6qKZzjV.d.ts +1252 -0
  107. package/lib/ssh-transport-BcIiLxsX.d.ts +55 -0
  108. package/lib/stores-CKyEt0yk.d.ts +31 -0
  109. package/lib/workspace-bindings-CsB0TIJB.d.ts +28 -0
  110. package/lib/workspace-instructions-DirYYiku.d.ts +69 -0
  111. package/package.json +20 -1
  112. package/lib/workspace-instruction-context-DytAmaPY.js.map +0 -1
@@ -1,3 +1,105 @@
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-DytAmaPY.js";
1
+ import { sharedSessionInstructionSnapshotStore, sharedWorkspaceInstructionStore } from "./workspace-instructions.js";
2
+ import { t as createUserMessage } from "../lib-gdtU_WKT.js";
3
+ //#region src/core/workspace-instruction-context.ts
4
+ const SYSTEM_REMINDER_OPEN = "<system-reminder>";
5
+ const SYSTEM_REMINDER_CLOSE = "</system-reminder>";
6
+ const INSTRUCTION_INTRO = "The following project instructions are managed by the user for this DSH workspace. Follow them as persistent workspace guidance. They take precedence over repository-provided workspace guidance. System, developer, and direct user instructions take precedence over these project instructions.";
7
+ function escapeInstructionFrameBody(body) {
8
+ return body.replaceAll(SYSTEM_REMINDER_CLOSE, "<\\/system-reminder>");
9
+ }
10
+ function renderInstructionMessageText(workspaceTitle, text) {
11
+ const escaped = escapeInstructionFrameBody(text);
12
+ return `${SYSTEM_REMINDER_OPEN}\n${INSTRUCTION_INTRO}\n\nInstructions for workspace: ${workspaceTitle}\n\n${escaped}\n${SYSTEM_REMINDER_CLOSE}`;
13
+ }
14
+ function createInstructionMessage(workspaceId, workspaceTitle, snapshot) {
15
+ const text = renderInstructionMessageText(workspaceTitle, snapshot.text);
16
+ return createUserMessage({
17
+ content: [{
18
+ type: "text",
19
+ text
20
+ }],
21
+ source: {
22
+ kind: "dshb-workspace-instructions",
23
+ form: "instructions",
24
+ workspaceId,
25
+ revision: snapshot.workspaceRevision,
26
+ digest: snapshot.digest,
27
+ snapshot: true
28
+ }
29
+ });
30
+ }
31
+ function hasDirectUserMessage(messages) {
32
+ return messages.some((m) => m.role === "user" && m.source.kind === "user");
33
+ }
34
+ function hasOwnInstructionMessage(agent) {
35
+ const nodes = agent.session.surface?.nodes;
36
+ if (!nodes) return false;
37
+ for (const seq of nodes) {
38
+ const event = agent.session.eventAt?.(seq);
39
+ if (event?.type === "user/message" && event.data.source.kind === "dshb-workspace-instructions") return true;
40
+ }
41
+ return false;
42
+ }
43
+ async function resolveSnapshotForSession(agent, messages, workspaceStore = sharedWorkspaceInstructionStore(), snapshotStore = sharedSessionInstructionSnapshotStore(), registry) {
44
+ const sessionId = agent.session.id;
45
+ const existing = snapshotStore.get(sessionId);
46
+ if (existing) return existing;
47
+ if (!(hasDirectUserMessage(messages) && !hasOwnInstructionMessage(agent))) return void 0;
48
+ let workspaceId;
49
+ const cwd = agent.session.header.cwd;
50
+ if (registry && cwd) try {
51
+ const ws = await registry.resolveByPath(cwd);
52
+ if (ws) {
53
+ workspaceId = ws.id;
54
+ ws.title;
55
+ }
56
+ } catch {}
57
+ const record = workspaceId ? workspaceStore.get(workspaceId) : void 0;
58
+ if (!record || record.text.length === 0) return snapshotStore.createIfAbsent(sessionId, {
59
+ kind: "empty",
60
+ workspaceId
61
+ });
62
+ return snapshotStore.createIfAbsent(sessionId, {
63
+ kind: "workspace",
64
+ record,
65
+ workspaceId
66
+ });
67
+ }
68
+ function registerWorkspaceInstructionInjector(ctx) {
69
+ if (typeof ctx.on !== "function") return;
70
+ const workspaceStore = sharedWorkspaceInstructionStore();
71
+ const snapshotStore = sharedSessionInstructionSnapshotStore();
72
+ ctx.on("agent/pre-step", async ({ agent, messages, signal }, next) => {
73
+ const decision = await next();
74
+ if (decision.kind === "reject") return decision;
75
+ signal.throwIfAborted();
76
+ const agentLike = agent;
77
+ const messagesLike = messages;
78
+ const registry = ctx.get("workspaceRegistry");
79
+ let snapshot = snapshotStore.get(agentLike.session.id);
80
+ if (!snapshot) snapshot = await resolveSnapshotForSession(agentLike, messagesLike, workspaceStore, snapshotStore, registry);
81
+ signal.throwIfAborted();
82
+ if (!snapshot || snapshot.text.length === 0) return decision;
83
+ if (hasOwnInstructionMessage(agentLike)) return decision;
84
+ const workspaceId = snapshot.workspaceId;
85
+ if (!workspaceId) return decision;
86
+ const message = createInstructionMessage(workspaceId, (registry?.get(workspaceId))?.title ?? "Unknown", snapshot);
87
+ const lastClaimedIndex = decision.messages.findLastIndex((m) => messages.includes(m));
88
+ const entered = decision.messages.toSpliced(lastClaimedIndex + 1, 0, message);
89
+ return {
90
+ ...decision,
91
+ messages: entered
92
+ };
93
+ });
94
+ try {
95
+ ctx.on?.("session/deleted", (sessionId) => {
96
+ snapshotStore.remove(sessionId);
97
+ });
98
+ } catch {}
99
+ }
100
+ const name = "dshb-workspace-instruction-injector";
101
+ const inject = ["workspaceRegistry"];
102
+ //#endregion
3
103
  export { createInstructionMessage, escapeInstructionFrameBody, inject, name, registerWorkspaceInstructionInjector, renderInstructionMessageText, resolveSnapshotForSession };
104
+
105
+ //# sourceMappingURL=workspace-instruction-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-instruction-context.js","names":[],"sources":["../../src/core/workspace-instruction-context.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis'\nimport { createUserMessage } from '@deepseek-ai/dsh-llm'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport {\n sharedWorkspaceInstructionStore,\n sharedSessionInstructionSnapshotStore,\n type SessionInstructionSnapshot,\n type WorkspaceInstructionRecord,\n} from './workspace-instructions.js'\n\nconst SYSTEM_REMINDER_OPEN = '<system-reminder>'\nconst SYSTEM_REMINDER_CLOSE = '</system-reminder>'\nconst INSTRUCTION_INTRO =\n 'The following project instructions are managed by the user for this DSH workspace. Follow them as persistent workspace guidance. They take precedence over repository-provided workspace guidance. System, developer, and direct user instructions take precedence over these project instructions.'\n\nexport interface InstructionSnapshotResult {\n snapshot: SessionInstructionSnapshot\n message: ReturnType<typeof createUserMessage> | undefined\n}\n\nexport function escapeInstructionFrameBody(body: string): string {\n return body.replaceAll(SYSTEM_REMINDER_CLOSE, '<\\\\/system-reminder>')\n}\n\nexport function renderInstructionMessageText(workspaceTitle: string, text: string): string {\n const escaped = escapeInstructionFrameBody(text)\n return `${SYSTEM_REMINDER_OPEN}\\n${INSTRUCTION_INTRO}\\n\\nInstructions for workspace: ${workspaceTitle}\\n\\n${escaped}\\n${SYSTEM_REMINDER_CLOSE}`\n}\n\nexport function createInstructionMessage(\n workspaceId: string,\n workspaceTitle: string,\n snapshot: SessionInstructionSnapshot,\n): ReturnType<typeof createUserMessage> {\n const text = renderInstructionMessageText(workspaceTitle, snapshot.text)\n return createUserMessage({\n content: [{ type: 'text', text }],\n source: {\n kind: 'dshb-workspace-instructions',\n form: 'instructions',\n workspaceId,\n revision: snapshot.workspaceRevision,\n digest: snapshot.digest,\n snapshot: true,\n } as never,\n })\n}\n\ninterface AgentLike {\n session: {\n id: string\n header: { cwd: string }\n surface?: { nodes?: number[] }\n eventAt?: (seq: number) => { type: string; data: { source: { kind: string } } } | undefined\n }\n}\n\ninterface MessageLike {\n id: string\n role: string\n source: { kind: string }\n}\n\ninterface WorkspaceRegistryLike {\n resolveByPath(path: string): Promise<{ id: string; path: string; title: string } | undefined>\n get(id: string): { id: string; path: string; title: string } | undefined\n}\n\nfunction hasDirectUserMessage(messages: MessageLike[]): boolean {\n return messages.some((m) => m.role === 'user' && m.source.kind === 'user')\n}\n\nfunction hasOwnInstructionMessage(agent: AgentLike): boolean {\n const nodes = agent.session.surface?.nodes\n if (!nodes) return false\n for (const seq of nodes) {\n const event = agent.session.eventAt?.(seq)\n if (event?.type === 'user/message' && event.data.source.kind === 'dshb-workspace-instructions') {\n return true\n }\n }\n return false\n}\n\nexport async function resolveSnapshotForSession(\n agent: AgentLike,\n messages: MessageLike[],\n workspaceStore = sharedWorkspaceInstructionStore(),\n snapshotStore = sharedSessionInstructionSnapshotStore(),\n registry?: WorkspaceRegistryLike,\n): Promise<SessionInstructionSnapshot | undefined> {\n const sessionId = agent.session.id\n const existing = snapshotStore.get(sessionId)\n if (existing) return existing\n\n const isFirstDirect = hasDirectUserMessage(messages) && !hasOwnInstructionMessage(agent)\n if (!isFirstDirect) return undefined\n\n let workspaceId: string | undefined\n let workspaceTitle = 'Unknown'\n const cwd = agent.session.header.cwd\n if (registry && cwd) {\n try {\n const ws = await registry.resolveByPath(cwd)\n if (ws) {\n workspaceId = ws.id\n workspaceTitle = ws.title\n }\n } catch {\n // path 不可解析时创建无工作区空快照\n }\n }\n\n const record: WorkspaceInstructionRecord | undefined = workspaceId\n ? workspaceStore.get(workspaceId)\n : undefined\n\n if (!record || record.text.length === 0) {\n return snapshotStore.createIfAbsent(sessionId, {\n kind: 'empty',\n workspaceId,\n })\n }\n\n return snapshotStore.createIfAbsent(sessionId, {\n kind: 'workspace',\n record,\n workspaceId: workspaceId!,\n })\n}\n\nexport function registerWorkspaceInstructionInjector(ctx: Context): void {\n if (typeof (ctx as { on?: unknown }).on !== 'function') return\n const workspaceStore = sharedWorkspaceInstructionStore()\n const snapshotStore = sharedSessionInstructionSnapshotStore()\n\n ;(ctx as unknown as { on: (event: string, handler: (arg: { agent: AgentLike; messages: MessageLike[]; signal: AbortSignal }, next: () => Promise<{ kind: string; messages: MessageLike[] }>) => Promise<unknown>) => void }).on('agent/pre-step', async ({ agent, messages, signal }, next) => {\n const decision = await next()\n if (decision.kind === 'reject') return decision\n signal.throwIfAborted()\n\n const agentLike = agent as unknown as AgentLike\n const messagesLike = messages as unknown as MessageLike[]\n\n const registry = ctx.get('workspaceRegistry') as WorkspaceRegistryLike | undefined\n\n let snapshot = snapshotStore.get(agentLike.session.id)\n if (!snapshot) {\n snapshot = await resolveSnapshotForSession(\n agentLike,\n messagesLike,\n workspaceStore,\n snapshotStore,\n registry,\n )\n }\n signal.throwIfAborted()\n if (!snapshot || snapshot.text.length === 0) return decision\n\n if (hasOwnInstructionMessage(agentLike)) return decision\n\n const workspaceId = snapshot.workspaceId\n if (!workspaceId) return decision\n const ws = registry?.get(workspaceId)\n const workspaceTitle = ws?.title ?? 'Unknown'\n\n const message = createInstructionMessage(workspaceId, workspaceTitle, snapshot)\n const lastClaimedIndex = decision.messages.findLastIndex((m) =>\n (messages as unknown as MessageLike[]).includes(m as unknown as MessageLike),\n )\n const entered = decision.messages.toSpliced(lastClaimedIndex + 1, 0, message)\n return {\n ...decision,\n messages: entered,\n }\n })\n\n try {\n ;(ctx as { on?: (event: string, handler: (sessionId: string) => void) => void }).on?.('session/deleted', (sessionId: string) => {\n snapshotStore.remove(sessionId)\n })\n } catch {\n // session/deleted event may not exist in this DSH version\n }\n}\n\nexport const name = 'dshb-workspace-instruction-injector'\nexport const inject = ['workspaceRegistry']\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { WorkspaceInstructionStore } from "./workspace-instructions.js";
1
+ import { s as WorkspaceInstructionStore } from "../workspace-instructions-DirYYiku.js";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  //#region src/core/workspace-instruction-routes.d.ts
4
4
  declare function registerWorkspaceInstructionRoutes(ctx: Context, store: WorkspaceInstructionStore): void;
@@ -1,68 +1,2 @@
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
1
+ import { a as SessionInstructionSnapshotStore, c as sharedSessionInstructionSnapshotStore, i as SessionInstructionSnapshot, l as sharedWorkspaceInstructionStore, n as InstructionConflictError, o as WorkspaceInstructionRecord, r as InstructionSizeError, s as WorkspaceInstructionStore, t as INSTRUCTION_MAX_BYTES } from "../workspace-instructions-DirYYiku.js";
2
+ export { INSTRUCTION_MAX_BYTES, InstructionConflictError, InstructionSizeError, SessionInstructionSnapshot, SessionInstructionSnapshotStore, WorkspaceInstructionRecord, WorkspaceInstructionStore, sharedSessionInstructionSnapshotStore, sharedWorkspaceInstructionStore };
@@ -168,6 +168,19 @@ var SessionInstructionSnapshotStore = class {
168
168
  this.save();
169
169
  return { ...snapshot };
170
170
  }
171
+ restore(snapshot) {
172
+ this.load();
173
+ if (utf8Bytes(snapshot.text) > 32768) throw new InstructionSizeError(utf8Bytes(snapshot.text));
174
+ if (snapshot.digest !== void 0 && snapshot.digest !== computeDigest(snapshot.text)) throw new Error("session instruction snapshot digest mismatch");
175
+ const existing = this.data.records[snapshot.sessionId];
176
+ if (existing) {
177
+ if (JSON.stringify(existing) !== JSON.stringify(snapshot)) throw new Error("session instruction snapshot already differs");
178
+ return { ...existing };
179
+ }
180
+ this.data.records[snapshot.sessionId] = { ...snapshot };
181
+ this.save();
182
+ return { ...snapshot };
183
+ }
171
184
  remove(sessionId) {
172
185
  this.load();
173
186
  if (!this.data.records[sessionId]) return;
@@ -1 +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
+ {"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 restore(snapshot: SessionInstructionSnapshot): SessionInstructionSnapshot {\n this.load()\n if (utf8Bytes(snapshot.text) > INSTRUCTION_MAX_BYTES) throw new InstructionSizeError(utf8Bytes(snapshot.text))\n if (snapshot.digest !== undefined && snapshot.digest !== computeDigest(snapshot.text)) {\n throw new Error('session instruction snapshot digest mismatch')\n }\n const existing = this.data.records[snapshot.sessionId]\n if (existing) {\n if (JSON.stringify(existing) !== JSON.stringify(snapshot)) throw new Error('session instruction snapshot already differs')\n return { ...existing }\n }\n this.data.records[snapshot.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,8 +1,9 @@
1
- import { NodeRegistry } from "./node-registry.js";
2
- import { WorkspaceBindingsStore } from "./workspace-bindings.js";
1
+ import { o as NodeRegistry } from "../node-registry-BTnnM5-k.js";
2
+ import { r as WorkspaceBindingsStore } from "../workspace-bindings-CsB0TIJB.js";
3
+ import { s as SyncNodeService } from "../node-sync-3Z9QLz2n.js";
3
4
  import { Context } from "@deepseek-ai/cordis";
4
5
  //#region src/core/workspaces.d.ts
5
- declare function registerWorkspaceRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void;
6
+ declare function registerWorkspaceRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore, syncNodes?: SyncNodeService): void;
6
7
  declare function registerRemoteDownloadRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void;
7
8
  //#endregion
8
9
  export { registerRemoteDownloadRoutes, registerWorkspaceRoutes };
@@ -39,7 +39,7 @@ function pathSegments(req) {
39
39
  function slugify(name) {
40
40
  return name.trim().toLowerCase().replace(/[^a-z0-9一-龥]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "workspace";
41
41
  }
42
- function registerWorkspaceRoutes(ctx, registry, bindings) {
42
+ function registerWorkspaceRoutes(ctx, registry, bindings, syncNodes) {
43
43
  const webServer = ctx.webServer;
44
44
  ctx.effect(() => webServer.register({
45
45
  kind: "prefix",
@@ -64,6 +64,14 @@ function registerWorkspaceRoutes(ctx, registry, bindings) {
64
64
  });
65
65
  return;
66
66
  }
67
+ if (syncNodes?.isPendingRemoval(node.id)) {
68
+ sendJson(res, 409, {
69
+ ok: false,
70
+ code: "node/pending-removal",
71
+ error: "节点等待工作区解绑后清理"
72
+ });
73
+ return;
74
+ }
67
75
  const remotePath = String(body.remotePath);
68
76
  if (node.type === "local-host") {
69
77
  sendJson(res, 200, {
@@ -111,6 +119,14 @@ function registerWorkspaceRoutes(ctx, registry, bindings) {
111
119
  }
112
120
  await world.ensureDir(remotePath);
113
121
  }
122
+ if (syncNodes?.isPendingRemoval(node.id)) {
123
+ sendJson(res, 409, {
124
+ ok: false,
125
+ code: "node/pending-removal",
126
+ error: "节点等待工作区解绑后清理"
127
+ });
128
+ return;
129
+ }
114
130
  mkdirSync(mirrorPath, { recursive: true });
115
131
  bindings.add({
116
132
  mirrorPath,
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.js","names":[],"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import { mkdirSync } from 'node:fs'\nimport { basename, extname } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport type { NodeRegistry } from './node-registry.js'\nimport type { WorkspaceBindingsStore } from './workspace-bindings.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 WorldsLike {\n ensure(nodeId: string): Promise<{ ensureDir(path: string): Promise<void> } | undefined>\n}\n\ninterface DockerProvisionLike {\n provision(nodeId: string): Promise<{ containerId: string } | undefined>\n}\n\nfunction slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9一-龥]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'workspace'\n}\n\nexport function registerWorkspaceRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/workspaces',\n handler: async (req, res) => {\n const worlds = ctx.get('dshbWorlds') as WorldsLike | undefined\n const segs = pathSegments(req)\n const action = segs[3]\n\n if (req.method === 'POST' && action === 'bind') {\n const body = await readBody(req)\n if (!body || typeof body.nodeId !== 'string' || typeof body.remotePath !== 'string') {\n sendJson(res, 400, { ok: false, error: '需要 nodeId 与 remotePath' })\n return\n }\n const node = registry.get(body.nodeId)\n if (!node) {\n sendJson(res, 404, { ok: false, error: 'node not found' })\n return\n }\n const remotePath = String(body.remotePath)\n if (node.type === 'local-host') {\n sendJson(res, 200, { ok: true, mirrorPath: remotePath })\n return\n }\n const slug = slugify(String(body.name ?? remotePath.split('/').filter(Boolean).pop() ?? 'workspace'))\n const mirrorPath = bindings.mirrorRoot(node.id, slug)\n try {\n const isDocker = node.type === 'local-docker' || node.type === 'remote-docker'\n if (isDocker) {\n const dockerWorlds = ctx.get('dshbDockerWorldsGeneric') as WorldsLike | undefined\n if (!dockerWorlds) {\n sendJson(res, 503, { ok: false, error: 'Docker 执行世界不可用(dshb-exec-docker 未加载)' })\n return\n }\n const world = await dockerWorlds.ensure(node.id)\n if (!world) {\n sendJson(res, 503, { ok: false, error: '容器未就绪,请先拉起容器' })\n return\n }\n await world.ensureDir(remotePath)\n } else {\n if (!worlds) {\n sendJson(res, 503, { ok: false, error: '远程执行世界不可用' })\n return\n }\n const world = await worlds.ensure(node.id)\n if (!world) {\n sendJson(res, 503, { ok: false, error: '节点执行世界未就绪' })\n return\n }\n await world.ensureDir(remotePath)\n }\n mkdirSync(mirrorPath, { recursive: true })\n bindings.add({ mirrorPath, nodeId: node.id, remotePath })\n sendJson(res, 201, { ok: true, mirrorPath })\n } catch (err) {\n sendJson(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) })\n }\n return\n }\n\n if (req.method === 'GET' && action === undefined) {\n sendJson(res, 200, { ok: true, bindings: bindings.list() })\n return\n }\n\n sendJson(res, 404, { ok: false, error: 'not found' })\n },\n }),\n )\n}\n\ninterface RemoteWorldLike {\n ensure(nodeId: string): Promise<{\n fs: {\n stat(path: string): Promise<{ isFile?: boolean; isDirectory?: boolean } | undefined>\n readBytes(path: string, signal?: AbortSignal, maxBytes?: number): Promise<Uint8Array>\n }\n } | undefined>\n}\n\nfunction resolveRemoteWorld(ctx: Context, node: { type: string }): RemoteWorldLike | undefined {\n if (node.type === 'local-docker' || node.type === 'remote-docker') {\n return ctx.get('dshbDockerWorldsGeneric') as RemoteWorldLike | undefined\n }\n if (node.type === 'remote-ssh') {\n return ctx.get('dshbWorlds') as RemoteWorldLike | undefined\n }\n return undefined\n}\n\nconst REMOTE_MAX_BYTES = 512 * 1024 * 1024\n\nexport function registerRemoteDownloadRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/remote',\n handler: async (req, res) => {\n const segs = pathSegments(req)\n const action = segs[3]\n if (req.method !== 'GET' || (action !== 'bound' && action !== 'file')) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n const path = new URL(req.url ?? '/', 'http://x').searchParams.get('path') ?? ''\n if (!path) {\n sendJson(res, 400, { ok: false, error: '需要 path' })\n return\n }\n const hit = bindings.resolve(path)\n if (!hit) {\n sendJson(res, action === 'bound' ? 200 : 404, { ok: true, bound: false })\n return\n }\n const node = registry.get(hit.nodeId)\n if (!node) {\n sendJson(res, 502, { ok: false, bound: true, error: 'node not found' })\n return\n }\n const worlds = resolveRemoteWorld(ctx, node)\n if (!worlds) {\n sendJson(res, 502, { ok: false, bound: true, error: '远程执行世界不可用' })\n return\n }\n try {\n const world = await worlds.ensure(node.id)\n if (!world) {\n sendJson(res, 502, { ok: false, bound: true, error: '节点执行世界未就绪' })\n return\n }\n const info = await world.fs.stat(hit.remotePath)\n const isFile = info?.isFile ?? false\n if (action === 'bound') {\n sendJson(res, 200, { ok: true, bound: true, nodeId: node.id, remotePath: hit.remotePath, kind: isFile ? 'file' : 'dir' })\n return\n }\n if (!isFile) {\n sendJson(res, 404, { ok: false, bound: true, error: '目标不是文件' })\n return\n }\n const data = await world.fs.readBytes(hit.remotePath, undefined, REMOTE_MAX_BYTES)\n const name = basename(hit.remotePath) || 'download'\n const ext = extname(name).toLowerCase()\n const mime: Record<string, string> = {\n '.txt': 'text/plain', '.md': 'text/markdown', '.json': 'application/json', '.html': 'text/html', '.htm': 'text/html',\n '.css': 'text/css', '.js': 'text/javascript', '.mjs': 'text/javascript', '.svg': 'image/svg+xml', '.png': 'image/png',\n '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf',\n '.zip': 'application/zip', '.gz': 'application/gzip', '.tar': 'application/x-tar', '.csv': 'text/csv', '.yaml': 'text/yaml', '.yml': 'text/yaml',\n }\n let contentType = mime[ext] ?? 'application/octet-stream'\n let body = Buffer.from(data)\n const isTextual = contentType.startsWith('text/') || contentType === 'application/json' || contentType === 'application/javascript'\n if (isTextual) {\n contentType += '; charset=utf-8'\n const bom = Buffer.from([0xef, 0xbb, 0xbf])\n if (!body.subarray(0, 3).equals(bom)) body = Buffer.concat([bom, body])\n }\n const asciiFallback = name.replace(/[^\\x20-\\x7e]/g, '_')\n res.writeHead(200, {\n 'content-type': contentType,\n 'content-disposition': `attachment; filename=\"${asciiFallback}\"; filename*=UTF-8''${encodeURIComponent(name).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`,\n 'cache-control': 'no-store',\n })\n res.end(body)\n } catch (err) {\n sendJson(res, 502, { ok: false, bound: true, error: err instanceof Error ? err.message : String(err) })\n }\n },\n }),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspaces.js","names":[],"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import { mkdirSync } from 'node:fs'\nimport { basename, extname } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport type { NodeRegistry } from './node-registry.js'\nimport type { WorkspaceBindingsStore } from './workspace-bindings.js'\nimport type { SyncNodeService } from './sync/node-sync.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 WorldsLike {\n ensure(nodeId: string): Promise<{ ensureDir(path: string): Promise<void> } | undefined>\n}\n\ninterface DockerProvisionLike {\n provision(nodeId: string): Promise<{ containerId: string } | undefined>\n}\n\nfunction slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9一-龥]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'workspace'\n}\n\nexport function registerWorkspaceRoutes(\n ctx: Context,\n registry: NodeRegistry,\n bindings: WorkspaceBindingsStore,\n syncNodes?: SyncNodeService,\n): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/workspaces',\n handler: async (req, res) => {\n const worlds = ctx.get('dshbWorlds') as WorldsLike | undefined\n const segs = pathSegments(req)\n const action = segs[3]\n\n if (req.method === 'POST' && action === 'bind') {\n const body = await readBody(req)\n if (!body || typeof body.nodeId !== 'string' || typeof body.remotePath !== 'string') {\n sendJson(res, 400, { ok: false, error: '需要 nodeId 与 remotePath' })\n return\n }\n const node = registry.get(body.nodeId)\n if (!node) {\n sendJson(res, 404, { ok: false, error: 'node not found' })\n return\n }\n if (syncNodes?.isPendingRemoval(node.id)) {\n sendJson(res, 409, { ok: false, code: 'node/pending-removal', error: '节点等待工作区解绑后清理' })\n return\n }\n const remotePath = String(body.remotePath)\n if (node.type === 'local-host') {\n sendJson(res, 200, { ok: true, mirrorPath: remotePath })\n return\n }\n const slug = slugify(String(body.name ?? remotePath.split('/').filter(Boolean).pop() ?? 'workspace'))\n const mirrorPath = bindings.mirrorRoot(node.id, slug)\n try {\n const isDocker = node.type === 'local-docker' || node.type === 'remote-docker'\n if (isDocker) {\n const dockerWorlds = ctx.get('dshbDockerWorldsGeneric') as WorldsLike | undefined\n if (!dockerWorlds) {\n sendJson(res, 503, { ok: false, error: 'Docker 执行世界不可用(dshb-exec-docker 未加载)' })\n return\n }\n const world = await dockerWorlds.ensure(node.id)\n if (!world) {\n sendJson(res, 503, { ok: false, error: '容器未就绪,请先拉起容器' })\n return\n }\n await world.ensureDir(remotePath)\n } else {\n if (!worlds) {\n sendJson(res, 503, { ok: false, error: '远程执行世界不可用' })\n return\n }\n const world = await worlds.ensure(node.id)\n if (!world) {\n sendJson(res, 503, { ok: false, error: '节点执行世界未就绪' })\n return\n }\n await world.ensureDir(remotePath)\n }\n if (syncNodes?.isPendingRemoval(node.id)) {\n sendJson(res, 409, { ok: false, code: 'node/pending-removal', error: '节点等待工作区解绑后清理' })\n return\n }\n mkdirSync(mirrorPath, { recursive: true })\n bindings.add({ mirrorPath, nodeId: node.id, remotePath })\n sendJson(res, 201, { ok: true, mirrorPath })\n } catch (err) {\n sendJson(res, 502, { ok: false, error: err instanceof Error ? err.message : String(err) })\n }\n return\n }\n\n if (req.method === 'GET' && action === undefined) {\n sendJson(res, 200, { ok: true, bindings: bindings.list() })\n return\n }\n\n sendJson(res, 404, { ok: false, error: 'not found' })\n },\n }),\n )\n}\n\ninterface RemoteWorldLike {\n ensure(nodeId: string): Promise<{\n fs: {\n stat(path: string): Promise<{ isFile?: boolean; isDirectory?: boolean } | undefined>\n readBytes(path: string, signal?: AbortSignal, maxBytes?: number): Promise<Uint8Array>\n }\n } | undefined>\n}\n\nfunction resolveRemoteWorld(ctx: Context, node: { type: string }): RemoteWorldLike | undefined {\n if (node.type === 'local-docker' || node.type === 'remote-docker') {\n return ctx.get('dshbDockerWorldsGeneric') as RemoteWorldLike | undefined\n }\n if (node.type === 'remote-ssh') {\n return ctx.get('dshbWorlds') as RemoteWorldLike | undefined\n }\n return undefined\n}\n\nconst REMOTE_MAX_BYTES = 512 * 1024 * 1024\n\nexport function registerRemoteDownloadRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore): void {\n const webServer = ctx.webServer\n\n ctx.effect(() =>\n webServer.register({\n kind: 'prefix',\n path: '/api/dshb/remote',\n handler: async (req, res) => {\n const segs = pathSegments(req)\n const action = segs[3]\n if (req.method !== 'GET' || (action !== 'bound' && action !== 'file')) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n const path = new URL(req.url ?? '/', 'http://x').searchParams.get('path') ?? ''\n if (!path) {\n sendJson(res, 400, { ok: false, error: '需要 path' })\n return\n }\n const hit = bindings.resolve(path)\n if (!hit) {\n sendJson(res, action === 'bound' ? 200 : 404, { ok: true, bound: false })\n return\n }\n const node = registry.get(hit.nodeId)\n if (!node) {\n sendJson(res, 502, { ok: false, bound: true, error: 'node not found' })\n return\n }\n const worlds = resolveRemoteWorld(ctx, node)\n if (!worlds) {\n sendJson(res, 502, { ok: false, bound: true, error: '远程执行世界不可用' })\n return\n }\n try {\n const world = await worlds.ensure(node.id)\n if (!world) {\n sendJson(res, 502, { ok: false, bound: true, error: '节点执行世界未就绪' })\n return\n }\n const info = await world.fs.stat(hit.remotePath)\n const isFile = info?.isFile ?? false\n if (action === 'bound') {\n sendJson(res, 200, { ok: true, bound: true, nodeId: node.id, remotePath: hit.remotePath, kind: isFile ? 'file' : 'dir' })\n return\n }\n if (!isFile) {\n sendJson(res, 404, { ok: false, bound: true, error: '目标不是文件' })\n return\n }\n const data = await world.fs.readBytes(hit.remotePath, undefined, REMOTE_MAX_BYTES)\n const name = basename(hit.remotePath) || 'download'\n const ext = extname(name).toLowerCase()\n const mime: Record<string, string> = {\n '.txt': 'text/plain', '.md': 'text/markdown', '.json': 'application/json', '.html': 'text/html', '.htm': 'text/html',\n '.css': 'text/css', '.js': 'text/javascript', '.mjs': 'text/javascript', '.svg': 'image/svg+xml', '.png': 'image/png',\n '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.pdf': 'application/pdf',\n '.zip': 'application/zip', '.gz': 'application/gzip', '.tar': 'application/x-tar', '.csv': 'text/csv', '.yaml': 'text/yaml', '.yml': 'text/yaml',\n }\n let contentType = mime[ext] ?? 'application/octet-stream'\n let body = Buffer.from(data)\n const isTextual = contentType.startsWith('text/') || contentType === 'application/json' || contentType === 'application/javascript'\n if (isTextual) {\n contentType += '; charset=utf-8'\n const bom = Buffer.from([0xef, 0xbb, 0xbf])\n if (!body.subarray(0, 3).equals(bom)) body = Buffer.concat([bom, body])\n }\n const asciiFallback = name.replace(/[^\\x20-\\x7e]/g, '_')\n res.writeHead(200, {\n 'content-type': contentType,\n 'content-disposition': `attachment; filename=\"${asciiFallback}\"; filename*=UTF-8''${encodeURIComponent(name).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`,\n 'cache-control': 'no-store',\n })\n res.end(body)\n } catch (err) {\n sendJson(res, 502, { ok: false, bound: true, error: err instanceof Error ? err.message : String(err) })\n }\n },\n }),\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { a as SyncPeer } from "./model-BP7Gw1yk.js";
2
+ //#region src/core/sync/host-key.d.ts
3
+ type SyncHostKeyDecision = 'trusted' | 'untrusted' | 'changed';
4
+ declare class SyncHostKeyChallengeError extends Error {
5
+ readonly decision: Exclude<SyncHostKeyDecision, 'trusted'>;
6
+ readonly fingerprint: string;
7
+ readonly expected?: string | undefined;
8
+ constructor(decision: Exclude<SyncHostKeyDecision, 'trusted'>, fingerprint: string, expected?: string | undefined);
9
+ }
10
+ declare class SyncHostKeyChallengeExpiredError extends Error {
11
+ constructor();
12
+ }
13
+ declare function opensshFingerprintFromSha256Hex(value: string): string;
14
+ declare function isOpenSshFingerprint(value: string): boolean;
15
+ declare function inspectSyncHostKey(peer: SyncPeer, sha256Hex: string, challenges: SyncHostKeyChallenges): {
16
+ trusted: true;
17
+ } | {
18
+ trusted: false;
19
+ challenge: SyncHostKeyChallengeError;
20
+ };
21
+ declare class SyncHostKeyChallenges {
22
+ private readonly now;
23
+ private readonly pending;
24
+ constructor(now?: () => number);
25
+ verify(peer: SyncPeer, fingerprint: string): boolean;
26
+ acceptObserved(peer: SyncPeer): string;
27
+ }
28
+ //#endregion
29
+ export { inspectSyncHostKey as a, SyncHostKeyDecision as i, SyncHostKeyChallengeExpiredError as n, isOpenSshFingerprint as o, SyncHostKeyChallenges as r, opensshFingerprintFromSha256Hex as s, SyncHostKeyChallengeError as t };
30
+ //# sourceMappingURL=host-key-C5afhAUd.d.ts.map