dsh-edge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +183 -0
  4. package/README.zh.md +183 -0
  5. package/THIRD_PARTY_NOTICES.md +199 -0
  6. package/dist/_headers +3 -0
  7. package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  8. package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  9. package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  10. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  11. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  12. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  13. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  14. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  15. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  16. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  17. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  18. package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  19. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  20. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  21. package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  22. package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  23. package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  24. package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  25. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  26. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  27. package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  28. package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  29. package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  30. package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  31. package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  32. package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  33. package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  34. package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  35. package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  36. package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  37. package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  38. package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  39. package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  40. package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  41. package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  42. package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  43. package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  44. package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  45. package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  46. package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  47. package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  48. package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  49. package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  50. package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  51. package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  52. package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  53. package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  54. package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  55. package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  56. package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  57. package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  58. package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  59. package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  60. package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  61. package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  62. package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  63. package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  64. package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  65. package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  66. package/dist/assets/index-C-1AiF3k.js +112 -0
  67. package/dist/assets/index-CSGf6Qzd.css +1 -0
  68. package/dist/assets/langs/c-BIGW1oBm.js +2 -0
  69. package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
  70. package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
  71. package/dist/assets/langs/css-CLj8gQPS.js +2 -0
  72. package/dist/assets/langs/go-C27-OAKa.js +2 -0
  73. package/dist/assets/langs/html-CfGypltT.js +2 -0
  74. package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
  75. package/dist/assets/langs/java-CylS5w8V.js +2 -0
  76. package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
  77. package/dist/assets/langs/less-B1dDrJ26.js +2 -0
  78. package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
  79. package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
  80. package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
  81. package/dist/assets/langs/php-D-uCvoST.js +2 -0
  82. package/dist/assets/langs/python-B6aJPvgy.js +2 -0
  83. package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
  84. package/dist/assets/langs/rust-B1yitclQ.js +2 -0
  85. package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
  86. package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
  87. package/dist/assets/langs/swift-D82vCrfD.js +2 -0
  88. package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
  89. package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
  90. package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
  91. package/dist/assets/vendor-Cjbwl5VI.js +411 -0
  92. package/dist/assets/vendor-CjyC-hUb.css +1 -0
  93. package/dist/favicon.svg +8 -0
  94. package/dist/index.html +35 -0
  95. package/dist/manifest.webmanifest +16 -0
  96. package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
  97. package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
  98. package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
  99. package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
  100. package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
  101. package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
  102. package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
  103. package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
  104. package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
  105. package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
  106. package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
  107. package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
  108. package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
  109. package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
  110. package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
  111. package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
  112. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
  113. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
  114. package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
  115. package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
  116. package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
  117. package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
  118. package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
  119. package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
  120. package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
  121. package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
  122. package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
  123. package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
  124. package/package.json +97 -0
  125. package/scripts/assemble-web.mjs +230 -0
  126. package/scripts/bundle-size.d.mts +2 -0
  127. package/scripts/bundle-size.mjs +26 -0
  128. package/scripts/bundle.mjs +53 -0
  129. package/scripts/cli.d.mts +39 -0
  130. package/scripts/cli.mjs +496 -0
  131. package/scripts/install.d.mts +149 -0
  132. package/scripts/install.mjs +989 -0
  133. package/scripts/legal-files.mjs +24 -0
  134. package/scripts/verify-packed.mjs +32 -0
  135. package/scripts/wrangler-config.d.mts +18 -0
  136. package/scripts/wrangler-config.mjs +84 -0
  137. package/src/agent.ts +119 -0
  138. package/src/auth.ts +352 -0
  139. package/src/deepseek.ts +231 -0
  140. package/src/deployment.ts +147 -0
  141. package/src/direct-shell-core-empty.ts +10 -0
  142. package/src/direct-shell-protocol.ts +5 -0
  143. package/src/direct-shell.ts +341 -0
  144. package/src/do-session-persistence.ts +1064 -0
  145. package/src/edge-api.ts +796 -0
  146. package/src/edge-credentials.ts +56 -0
  147. package/src/edge-remotes.ts +41 -0
  148. package/src/edge-workspace-store.ts +339 -0
  149. package/src/http.ts +212 -0
  150. package/src/index.ts +196 -0
  151. package/src/instance.ts +1007 -0
  152. package/src/isolated-direct-shell-unavailable.ts +11 -0
  153. package/src/protocol.ts +39 -0
  154. package/src/release.ts +9 -0
  155. package/src/session-store.ts +1110 -0
  156. package/src/sse.ts +86 -0
  157. package/src/web-search.ts +38 -0
  158. package/src/workspace.ts +270 -0
  159. package/wrangler.jsonc +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write apps/dsh-edge/README.md
5
+ README.md: 3a40454ba493c5c295ca6d35498fa8a4807508e5
6
+ README.zh.md: 79450c1c682bb53c11f4af0c43af3a859ebdb5ea
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # dsh-edge
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ `dsh-edge` is the Cloudflare runtime prototype for DeepSeek Harness. One deployment maps its authenticated owner to one Durable Object whose SQLite-backed virtual filesystem survives requests. By default, an in-process just-bash backend runs commands against that same filesystem without a Linux container or Dynamic Worker.
6
+
7
+ The checked-in Wrangler configuration exposes two deployment targets from the same application graph. The default target is direct mode for Workers Free and has no Worker Loader binding. The named `isolated` target adds the `LOADER` binding and requires Workers Paid, but does not fork the DSH protocol, storage, UI, or tool implementation.
8
+
9
+ The prototype runs persistent conversations through the upstream Cordis-composed `ReactLoopAgent`, `AgentRegistry`, `LlmRuntime`, `ToolRuntime`, `SystemPrompt`, `SessionStore`, and `SessionPersistence`. Edge code only binds a request-scoped DeepSeek adapter and maps one native DSH `bash` tool definition onto Cloudflare Computer. Durable Object SQLite implements the upstream persistence backend contract; `PersistenceCoordinator` still owns write-behind, revisions, resume preparation, and crash recovery. Model history is projected from canonical events rather than persisted separately.
10
+
11
+ The browser is the upstream Web shell and upstream client-plugin bundles. A build-time assembler derives the browser roster from the upstream base and Web bundle configs, injects the standard `window.__DSH_BOOT__` graph, and publishes the result as Cloudflare static assets. The Durable Object implements the supported upstream `ApiProxy` methods through the standard HTTP carrier and supplies the two upstream downlinks as hibernatable WebSockets. Edge excludes client plugins whose host domains are absent instead of forking their UI code; this includes session-log export until its server endpoint exists. A small Edge-owned login shell protects the upstream UI and protocol without changing either one. Optional local-host plugins remain unavailable.
12
+
13
+ ## Run locally
14
+
15
+ Use Node.js 22.19 or newer and install the repository dependencies from the repository root. To call DeepSeek, create an ignored `apps/dsh-edge/.dev.vars` file:
16
+
17
+ ```dotenv
18
+ DSH_EDGE_ACCESS_KEY=replace-with-at-least-32-random-bytes
19
+ DEEPSEEK_API_KEY=replace-with-your-key
20
+ DEEPSEEK_MAX_OUTPUT_TOKENS=8192
21
+ DEEPSEEK_MODEL=deepseek-v4-flash
22
+ DEEPSEEK_REASONING_EFFORT=off
23
+ DEEPSEEK_STREAM_IDLE_TIMEOUT_MS=120000
24
+ DSH_EDGE_DEFAULT_COMMAND_TIMEOUT_MS=120000
25
+ DSH_EDGE_MAX_COMMAND_TIMEOUT_MS=120000
26
+ ```
27
+
28
+ Then start the Worker:
29
+
30
+ ```sh
31
+ pnpm --filter dsh-edge dev
32
+ ```
33
+
34
+ The command generates the upstream Host-to-Client Remote declarations, builds the upstream Web assets, and then starts Wrangler. Open the printed URL, normally `http://localhost:8787`, enter the owner access key, choose **Workspace**, and send a message. The Web UI creates a lazy blank session and streams the turn through authenticated Durable Object WebSockets.
35
+
36
+ The diagnostic API uses the same owner cookie. Log in once to a temporary cookie jar, then verify the persistent filesystem and shell:
37
+
38
+ ```sh
39
+ curl -c /tmp/dsh-edge-cookie -X POST \
40
+ -H 'content-type: application/x-www-form-urlencoded' \
41
+ --data-urlencode 'accessKey=replace-with-your-random-key' \
42
+ http://localhost:8787/api/auth/login
43
+
44
+ curl -b /tmp/dsh-edge-cookie -X PUT --data 'hello from the edge' \
45
+ 'http://localhost:8787/api/workspace/file?path=/workspace/hello.txt'
46
+
47
+ curl -b /tmp/dsh-edge-cookie -X POST -H 'content-type: application/json' \
48
+ --data '{"command":"cat /workspace/hello.txt"}' \
49
+ http://localhost:8787/api/workspace/exec
50
+ ```
51
+
52
+ For a persistent conversation, create a session and send turns to its returned id:
53
+
54
+ ```sh
55
+ curl -b /tmp/dsh-edge-cookie -X POST -H 'content-type: application/json' \
56
+ --data '{"title":"Edge session"}' \
57
+ http://localhost:8787/api/sessions
58
+
59
+ curl -b /tmp/dsh-edge-cookie -N -X POST -H 'content-type: application/json' \
60
+ --data '{"message":"Read /workspace/hello.txt and remember the result."}' \
61
+ http://localhost:8787/api/sessions/SESSION_ID/turn
62
+ ```
63
+
64
+ The session turn sends upstream `SessionEvent` values directly as SSE data, including `agent/inbox/spliced`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, and turn/step boundaries. The live stream queues at most 1 MiB for its client; a slower reader is disconnected without cancelling the turn or its persistence. `GET /api/sessions/SESSION_ID` returns bounded session metadata only; clients obtain history from `GET /api/sessions/SESSION_ID/events?after=SEQ&limit=COUNT`, which replays a bounded page by upstream `seq`. Replay defaults to 128 events, accepts at most 256, preflights stored payload bytes before loading rows, and retains at most 1 MiB of encoded SSE; `x-dsh-edge-has-more` and `x-dsh-edge-next-after` drive the next request.
65
+
66
+ Session listing is also bounded: `GET /api/sessions?after=SESSION_ID&limit=COUNT` defaults to 50 summaries, accepts at most 100, and returns `hasMore` plus `nextAfter` in the JSON body. The Durable Object derives titles and latest timestamps from canonical rows without loading each session log. The upstream Web session list additionally includes retained blank headers that have no canonical event yet.
67
+
68
+ The upstream `session.history` browser RPC uses one Edge admission budget before live and cold paths diverge: every request is capped at the browser's 50-message page size. Cold logs apply that boundary in Durable Object SQL before decoding payloads and validate the selected contiguous window under 8,192-event and 8 MiB stored-payload ceilings. Live logs locate the same boundary without first copying the complete in-memory window, then enforce the same event ceiling and an 8 MiB encoded-response ceiling. An over-budget window is refused instead of truncated. Model-directory, model-selection, and turn-admission existence checks use header point reads; only a turn that must resume the agent decodes canonical history.
69
+
70
+ The upstream sidebar's `session.search` RPC scans canonical current user and assistant messages without a second Edge index or wire format. One request examines at most the 32 sessions with the most recent human activity and searches only complete logs of at most 512 events; a cold log must also fit within 256 KiB of stored payload. It returns the upstream maximum of 20 bounded snippets; `hasMore` is true when a result or work bound prevents an exhaustive answer.
71
+
72
+ Every authenticated request uses the deployment's fixed `owner` Durable Object. The legacy `x-dsh-edge-instance` header and `instance` query parameter are rejected rather than treated as identities. `/api/sessions/SESSION_ID/turn` continues the stored canonical history.
73
+
74
+ ## Cloudflare compatibility matrix
75
+
76
+ This reference separates code that runs natively in Workers, code adapted at an existing DSH capability, and code that still assumes the local Node.js host. “Current” describes `apps/dsh-edge`; it is not a claim about all future Cloudflare work.
77
+
78
+ | Capability | Upstream implementation | Current edge status | Edge decision |
79
+ | --- | --- | --- | --- |
80
+ | DeepSeek transport | Fetch, SSE parsing, wire translation, retry metadata | Reused | Construct the upstream `DeepSeekAdapter` per request. `nodejs_compat` supplies its compatible Node APIs. |
81
+ | Provider attribution | Package version loaded with Node `createRequire` | Reused after portability fix | Import package metadata statically so bundlers preserve the same version source without requiring `import.meta.url` at runtime. |
82
+ | LLM protocol | DSH messages, content blocks, stream chunks, tool calls, usage, and finish reasons | Reused | Let upstream `LlmRuntime` and `ReactLoopAgent` assemble, stream, and log the model exchange. |
83
+ | Agent loop | Cordis-composed `ReactLoopAgent` with hooks, guards, sessions, and tools | Reused | Create and cold-resume agents through `AgentRegistry`; keep optional Node-oriented plugins such as local compaction outside the edge composition until adapted. |
84
+ | Bash tool | Node subprocess, sandbox, terminal, and job services | Adapted at the native tool seam | Register an upstream `ToolDefinition`, but execute its body through the configured Computer workspace backend and just-bash. The default direct backend runs inside the owner Durable Object with hardened interpreter limits and no network command; adding a `LOADER` binding selects Computer's isolated Worker Shell backend. Native tool cancellation sends `SIGINT` through the Computer execution handle. Deployment configuration supplies an explicit default timeout and caller-selectable ceiling, while `timedOut` reports the deadline independently from exit and cancellation status. Native binaries, background processes, PTYs, and arbitrary Linux behavior are unavailable. |
85
+ | Workspace filesystem | Local filesystem services and host paths | Adapted | Store `/workspace` in the owner's SQLite-backed Durable Object VFS. |
86
+ | Session persistence | `SessionPersistence` service, `PersistenceCoordinator`, and local JSONL/SQLite backends | Native backend adaptation | Reuse the upstream service and coordinator ownership. Implement storage primitives over Durable Object SQL with the upstream header/event mapping. One Edge-only table retains empty session headers across transparent hibernation and is removed when canonical rows materialize; no Edge turn or message schema exists. Internal coordinator helpers validate the bounded replay loader and abandon a failed unmaterialized creation before disposal. |
87
+ | Settings and credentials | File-backed settings, launch environment, and credential services | Read-only edge projection | Resolve the Worker secret per operation; never persist or return the literal key. Blank secrets are unconfigured, while surrounding whitespace is removed before use. `credentials.describe` reports only whether `DEEPSEEK_API_KEY` is configured and that its read-only source is `worker-secret`. The built-in `dsh-edge` preset projects its effective release, shell/VFS, model, limits, credential state, prompt, and tools through the upstream read-only composition viewer. Writable settings and authenticated per-user secret storage remain open. |
88
+ | Host boot and plugins | Node command line, Cordis profile loading, package resolution, and HMR | Explicit Edge composition | Keep the local boot profile out of Workerd. Build immutable client bundles ahead of deployment; exclude HMR and host domains that the Edge `ApiProxy` does not expose. |
89
+ | DSH transport | Typed HTTP RPC plus mux and host WebSocket downlinks | Reused with an Edge server implementation | Use the upstream fetch carrier for unary methods and preserve its envelopes, schemas, projections, lazy blank-session behavior, bounded content search, prompt and queue mutations, workspace mutations, queue snapshots, and event frames. Durable Object WebSocket hibernation owns both downlinks; mux reconnects replay pending live inbox state, while REST/SSE routes remain a diagnostic compatibility path. |
90
+ | Workspace registry | Storage-domain global state plus `WorkspaceRecord` rows | Native backend adaptation | Keep the upstream global and record value shapes, including manual session order and archive membership, but map their physical keys and atomic writes to Durable Object storage. Edge constrains the registry to the one native `/workspace` VFS; rename, delete, recreation, and session reordering retain the upstream RPC and Host-frame semantics. |
91
+ | Existing Web UI | Runtime-loaded shell and `dsh.client` plugin graph | Reused with generic composition fallbacks | Assemble the upstream shell and supported upstream client bundles as Worker assets. Shared slot-occupancy rules hide actions whose provider is absent; Cloudflare serves ordinary assets directly, while `/`, `/login`, and `/api/*` enter the Worker for owner access control. The assembled asset policy prevents every direct or SPA-fallback shell alias from being framed. |
92
+ | Other tools | Web Search, filesystem editor tools, MCP, skills, workflows, jobs, and subagents | Search ported; others not ported | Reuse upstream DeepSeek Web Search with its 30-second tool-call timeout. Add the remaining tools individually against Worker-compatible capabilities; do not advertise unavailable host behavior. |
93
+ | Attachments | Local attachment storage and image references | Not ported | The absent `imageLimits` capability keeps paste and drop out of the upstream composer draft. Choose Durable Object or object-storage ownership and signed delivery before enabling images. |
94
+ | Authentication and tenancy | Local trusted-user boundary | Single-owner adaptation | Require one high-entropy Worker secret, exchange it for a signed 30-day HttpOnly `SameSite=Strict` cookie, and route every accepted request to one fixed owner object. This intentionally provides no registration, user database, roles, or multi-tenant routing. |
95
+
96
+ The browser request path is:
97
+
98
+ ```text
99
+ Cloudflare static assets -> upstream Web shell + client plugin graph
100
+ -> POST /api/session.create through the upstream HTTP carrier
101
+ -> host/workspace-changed + session/subscribed over Durable Object WebSockets
102
+ -> POST /api/session.prompt with the client rpcId
103
+ -> AgentRegistry live lookup or resume
104
+ -> sessionPersistence.prepare through PersistenceCoordinator on cold resume
105
+ -> ReactLoopAgent.followup(queue) or ReactLoopAgent.steer(steer)
106
+ -> pre-step admission gate waits for the sessions.flush durability barrier
107
+ -> session/queue snapshots publish live and replay on mux reconnect
108
+ -> turn-scoped DeepSeekAdapter configuration selected by sessionId
109
+ -> upstream LlmRuntime + ReactLoopAgent stream/event pipeline
110
+ -> upstream ToolRuntime native bash or web_search call
111
+ -> upstream WebRuntime + DeepSeek native search provider for web_search
112
+ -> direct just-bash backend in the owner Durable Object
113
+ (or optional Computer Worker Shell when LOADER is bound)
114
+ -> Durable Object /workspace VFS
115
+ -> upstream tool/result and next model step
116
+ -> ReactLoopAgent appends canonical inbox, chunk, message, tool and boundary events
117
+ -> sessions.flush durable barrier -> Durable Object SQLite backend
118
+ -> session/event, projection, and status frames over Durable Object WebSockets
119
+ -> upstream Web runtime reconciles and renders the canonical events
120
+ ```
121
+
122
+ The local integration check uses an SSE stand-in and the real Wrangler, Durable Object SQLite, the default direct Computer workspace backend, static asset service, HTTP carrier, and WebSockets. It verifies owner login, API and WebSocket cookie enforcement, rejection of legacy instance selectors, disabled direct-shell networking, the upstream session create/list/history/search/prompt/rename/fork flow; queue edit, removal, and promotion to steering; workspace create/list/rename/delete/session reorder/archive; the corresponding live and reconnect baselines and Host frames; real browser boot and UI-issued workspace rename, turn, content search, branch, and archive actions; automatic return to login when the browser session expires; conversation continuity, event replay, two-step bash and Web Search tool exchanges, and restoration after a Wrangler restart. A focused failure test proves that a post-enqueue durability failure blocks model use without reporting the already-woken prompt as rejected. Committed model-visible and ARIA goldens pin the tool transcripts and the assembled upstream Web client through the Edge HTTP/WebSocket protocol. A live DeepSeek call requires the developer's own key and is intentionally not part of the repository test suite.
123
+
124
+ ## API-key boundary
125
+
126
+ `DEEPSEEK_API_KEY` from `.dev.vars` is the local credential source. A read-only Edge provider exposes that Worker secret through the upstream `ctx.credentials` service for each chat or search operation without writing it to Durable Object storage, the VFS, session events, or responses. It removes surrounding whitespace and treats a blank value as unconfigured. `DEEPSEEK_BASE_URL` controls chat and must be an HTTP(S) URL without URL userinfo; its read-only browser projection omits query and fragment components that may carry gateway credentials. `DEEPSEEK_SEARCH_BASE_URL` independently controls the Anthropic-compatible Messages endpoint used by DeepSeek native search, defaults to `https://api.deepseek.com/anthropic/v1`, and must be an HTTP(S) URL without userinfo, query, or fragment. Edge mounts the upstream `web_search` tool, its 30-second tool-call timeout policy, and structured Web result presentation; `web_fetch` remains disabled because the runtime has no arbitrary-URL network policy. Search requests do not follow redirects. `DEEPSEEK_MODEL` selects a validated chat model id and defaults to `deepseek-v4-flash`. `DEEPSEEK_REASONING_EFFORT` accepts `off`, `low`, `high`, or `max` and defaults to `off`. `DEEPSEEK_MAX_OUTPUT_TOKENS` optionally overrides the 8,192-token chat default and must be a positive safe integer. `DEEPSEEK_STREAM_IDLE_TIMEOUT_MS` optionally overrides the 120,000 ms chat default and must be a positive integer no greater than 2,147,483,647. Invalid deployment configuration fails before session lookup or the SSE response opens.
127
+
128
+ `DSH_EDGE_DEFAULT_COMMAND_TIMEOUT_MS` applies to every Computer command that omits a caller timeout, and `DSH_EDGE_MAX_COMMAND_TIMEOUT_MS` limits caller-selected values. Both default to 120,000 ms, must be positive integers no greater than 2,147,483,647, and the default cannot exceed the maximum.
129
+
130
+ `DSH_EDGE_ACCESS_KEY` is the deployment's single-owner boundary. It must contain 32–512 UTF-8 bytes without surrounding whitespace or control characters; generate a random value rather than reusing a human password. A successful form login creates a signed 30-day HttpOnly `SameSite=Strict` cookie. HTTPS deployments use the host-only `__Host-dsh_edge_owner` name and `Secure`; local HTTP development uses an unprefixed cookie because browsers reject `__Host-` cookies without HTTPS. The cookie carries no user data, is not forwarded to the Durable Object, and becomes invalid when the access key rotates. Unauthenticated API and WebSocket requests return 401. Owner-authentication API failures also carry `WWW-Authenticate: DshEdgeOwner`; only that exact same-origin 401 makes the Edge-assembled shell navigate to `/login`, so provider or configuration 401 diagnostics remain visible while an expired browser session still escapes the upstream reconnect loop. Authenticated browser API and WebSocket requests from a different origin return 403 even when they carry a same-site cookie. The Cloudflare asset policy prevents the shell from being embedded in a frame whether it is reached through `/`, `/index.html`, or an SPA fallback alias. `/` redirects to `/login`; `/api/health` and immutable asset files remain public. This deliberately is not an account system or a multi-tenant boundary.
131
+
132
+ ## Install on Cloudflare
133
+
134
+ The top level of the committed `wrangler.jsonc` is the default direct target and does not require a Worker Loader. Direct shell code executes in the same Durable Object isolate as the agent and VFS, so just-bash's hardened execution limits, explicit command timeout, bounded output, explicit environment, and disabled network command are the primary command boundary. This is a lighter isolation model than a separate Worker; do not expose the single-owner deployment to untrusted users.
135
+
136
+ The same file also defines `env.isolated`, a complete Workers Paid target with the `LOADER` binding. The application code sees `LOADER` and chooses Computer's Worker Shell backend, so `/api/health` reports `just-bash-isolated` instead of `just-bash-direct`. Workers Paid is a Workers subscription starting at $5 per month, not the Cloudflare Pro website plan. Each Worker name has independent Durable Object storage and secrets, so install both modes under different names when both should remain live.
137
+
138
+ `wrangler.jsonc` remains the single canonical configuration for both modes. The installer generates a private, mode-specific Wrangler configuration for each upload and uses absolute entrypoint and asset paths so it can live outside the checkout. Direct mode replaces only Computer's unreachable Dynamic Worker shell-core module at bundle time; the Computer workspace adapter and command exports remain the upstream implementations. Isolated mode preserves that shell core but replaces the unreachable Direct backend with a fail-closed module, so each upload carries only its selected command runtime. Both outputs are minified. CI builds through the same renderer and rejects a Direct artifact above a 900 KiB compressed budget, leaving headroom below the 1 MiB limit enforced by Cloudflare's anonymous temporary-account upload path.
139
+
140
+ Run the guided installer without cloning this repository:
141
+
142
+ ```sh
143
+ pnpm dlx dsh-edge@latest install
144
+ ```
145
+
146
+ Upgrade an existing named Worker with the same runtime choice. The deployment keeps its Durable Object data; because Cloudflare secrets are write-only, the upgrade asks for the owner access key and DeepSeek API key again and replaces their active values:
147
+
148
+ ```sh
149
+ pnpm dlx dsh-edge@latest upgrade
150
+ ```
151
+
152
+ The installer asks for the runtime before the account. The recommended `Free — Direct Shell` mode works on Workers Free and can use a detected Cloudflare account, open Cloudflare sign-in or registration, or create a temporary account without login. `Isolated — Dynamic Worker` requires Workers Paid and therefore offers only a detected or newly authenticated account. Cloudflare does not expose a reliable local entitlement check for Worker Loader, so an isolated install lets Cloudflare authorize the upload and turns a rejection into a choice between enabling Workers Paid and using direct mode.
153
+
154
+ The remaining prompts select a Worker name, generate or accept the owner access key, collect the DeepSeek API key through hidden input, and show a final cost summary. A temporary-account install also asks the user to accept Cloudflare's Terms of Service and Privacy Policy explicitly. An existing Worker is never overwritten without confirmation. The installer passes both credentials through a mode-`0600` temporary secrets file and gives Wrangler only an allowlisted runtime environment plus the Cloudflare authentication selected for that command; unrelated ambient keys, tokens, passwords, secrets, and Node injection options do not reach the child. It removes the secret file after the command and discovers the resulting URL from Wrangler's structured output. It does not probe the public Worker after upload. Instead, it prints the URL, owner access key, and concrete next steps; a temporary account also receives a bearer claim URL that must be claimed within 60 minutes to retain the Worker and its data. If upload succeeds but output parsing, claim-URL extraction, interruption handling, or local cleanup prevents a normal handoff, a recovery card still prints the active owner key and any known URLs before the command exits unsuccessfully. The installation uploads directly through Wrangler and does not create or bind a GitHub repository, Cloudflare Builds project, or source-build pipeline.
155
+
156
+ Contributors working from a checkout can reproduce the two deployment bundles locally with `pnpm --filter dsh-edge bundle:direct` and `pnpm --filter dsh-edge bundle:isolated`. The first command also enforces the compressed-size budget.
157
+
158
+ Contributors can replay the complete Free temporary-account journey without a key or network call. This example runs the shipped bin, real prompts, Wrangler subprocess, structured deployment-output parsing, and final handoff while replacing only the external Cloudflare command:
159
+
160
+ ```sh
161
+ pnpm --filter dsh-edge example:install
162
+ ```
163
+
164
+ ## Prototype API
165
+
166
+ - `POST /api/<upstream-method>` accepts the upstream `ClientRequest` envelope for the supported `ApiProxy` methods. The Web client currently uses session list/search/create/history/models/select/prompt/updateQueue/rename/fork/cancel, host description, workspace list/create/rename/delete/reorder/archive, skills, agent presets, settings and credential descriptions, and LLM catalogs. `agentPreset.read` renders the programmatic Edge composition through the upstream read-only viewer, and `credentials.describe` returns credential state without a value. Search projects canonical current-message surfaces and returns only bounded upstream result values. Fork copies a completed-turn prefix through the canonical session seed format and retains parent lineage; Edge refuses a seed above 8,192 events or 8 MiB rather than materializing an unbounded Durable Object history. Queue mutations edit, remove, or promote an item through the live upstream Agent inbox; the synchronous inbox mutation is the upstream acceptance point, while the persistence coordinator owns later write-behind and retirement retry. Workspace mutations persist the upstream workspace-domain global and record shapes through the Durable Object backend. Archive preserves the session log and workspace slot; unary responses and Host frames carry the same full snapshots as upstream.
167
+ - `GET /login` renders the Edge-owned owner form; `POST /api/auth/login` exchanges the configured access key for a signed cookie, `GET /api/auth/session` reports cookie validity, and `POST /api/auth/logout` clears it.
168
+ - `GET /api/events.mux` and `GET /api/events.host` upgrade to the upstream downlink WebSockets. The Durable Object serializes each socket's channel and verified owner-session expiry as its hibernation attachment, closes it at that expiry through an alarm, and reconstructs canonical sessions plus retained blank headers from Durable Object SQL. The mux stream publishes a complete `session/queue` snapshot after each committed inbox splice and sends pending live inbox baselines when a client reconnects.
169
+ - `POST /api/commands/list` implements the upstream generated-Remote envelope with an empty catalog because the Edge preset registers no human commands.
170
+ - `GET /api/health` returns the public per-deploy release identifier and validates owner authentication, the deployment-scoped DeepSeek credential, model and transport choices, and the command-timeout policy before reporting the runtime components as ready. It does not call the provider, Durable Object, VFS, or shell.
171
+ - `PUT /api/workspace/file?path=/workspace/...` writes a UTF-8 file.
172
+ - `GET /api/workspace/file?path=/workspace/...` reads a UTF-8 file.
173
+ - `DELETE /api/workspace/file?path=/workspace/...` removes a file.
174
+ - `POST /api/workspace/exec` accepts `{ "command": "...", "cwd": "/workspace/..." }`; `cwd` defaults to `/workspace`, every execution receives the deployment default timeout, `timedOut` reports whether that deadline elapsed, and `outputTruncated` reports whether the retention bound was crossed.
175
+ - `POST /api/sessions` creates a persistent session with a required `title`, recorded as a standard user-sourced `session/title` event before the API returns. If the session is durable but its Workspace attachment fails, the 500 response uses `workspace-attach-failed` and includes the complete created `session`, so callers can recover its id instead of creating a duplicate.
176
+ - `GET /api/sessions?after=...&limit=...` lists one bounded summary page; `GET /api/sessions/:sessionId` reads one. Session deletion is not exposed because the upstream persistence service does not define destructive deletion.
177
+ - `POST /api/sessions/:sessionId/turn` accepts `{ "message": "..." }` and streams persisted SSE events.
178
+ - `GET /api/sessions/:sessionId/events?after=...&limit=...` replays a bounded event page and returns continuation headers.
179
+ - `POST /api/sessions/:sessionId/cancel` aborts the active turn owned by the current Durable Object process.
180
+
181
+ Upstream session creation and fork return `workspace-attach-failed` with the published session and Workspace ids when publication succeeds but Workspace attachment fails; the diagnostic creation route returns the same code plus its complete created session. Prompt and queue-edit text share the same 64 KiB semantic limit even though their RPC carrier accepts up to 512 KiB. Because the Edge composition has no directory-flow provider, the upstream browser hides Delete on its sole Workspace and exposes it again whenever restoration remains possible.
182
+
183
+ The API limits text files to 1 MiB, commands to 16 KiB, user messages to 64 KiB, and retained shell stdout plus stderr to 64 KiB; these are UTF-8 byte limits. Request bodies are consumed incrementally before parsing or forwarding: session creation accepts at most 8 KiB of JSON, workspace execution 128 KiB, and message-bearing turn or queue-update RPCs 512 KiB, while file uploads enforce their 1 MiB bound during consumption and reject malformed UTF-8. Once a body exceeds its route limit, later chunks are drained without being retained and the route returns 413. File reads first check VFS metadata, then collect the opened raw byte stream through the same 1 MiB cap, closing the growth race between `stat()` and `readFile()` without retaining an unbounded value. The runtime requests interruption when combined shell output crosses the retention bound and does not accumulate later output. Command status reports cancellation only when the adapter requested interruption, independently of the shell exit code; `timedOut` separately records deadline expiry. Failed initial session persistence discards the retained unmaterialized batch before disposing the newly published upstream agent handle, so teardown cannot later commit a session whose create request returned an error. A lazy blank session retains only its upstream header until the first canonical event; that materialization removes the retained header in the same SQL transaction. Each turn owns one upstream handle and disposes it after the stream completes, so previously accessed conversations do not remain resident for the Durable Object lifetime. Deployment settings resolve before the process-local owner claim. An upstream protocol prompt returns accepted and publishes running state only after its inbox event crosses `SessionStore.flush()`; later streamed events cross the same barrier before WebSocket or SSE delivery. Queue edits, removals, and steering promotion use the synchronous live-inbox mutation as their acceptance point; `PersistenceCoordinator` owns subsequent write-behind or retirement retry, so a later storage attempt cannot turn an accepted mutation into a rejected response. Session rename follows the same upstream metadata contract: the synchronous title append is its acceptance point for both active and cold sessions. Workspace global state and records use the upstream logical schemas under Edge-specific physical keys; DO transactions atomically pair record and registry-order changes, while a process-local chain serializes workspace mutations. Committed rename, delete, recreation, session reorder, attachment, and archive changes publish the matching upstream Host frames, and `workspace.list` restores their complete baseline after restart. A process-local owner rejects a concurrent turn, and cancel calls the native agent cancellation path. On the next cold resume, upstream interrupted-turn repair closes an open persisted tail and canonical `session/end-seed` markers preserve lifecycle boundaries. Replay checks session absence separately so persistence corruption or SQL failures are not collapsed into 404, reads only one bounded SQL page rather than the full suffix, and caps the encoded response. `PersistenceCoordinator.readValidatedPage()` performs identity, format, legacy-shape, and event-vocabulary validation on every page without adding Edge pagination to the public persistence service. If legacy normalization needs earlier messages, it rereads only one prefix through the same byte-bounded loader and refuses the page when that required prefix does not fit. Cold browser history selects its message boundary in SQL and loads only the resulting contiguous range under fixed event and stored-byte ceilings. Session listing queries one bounded canonical header/title summary page; detail reads its durable canonical point summary or retained blank header, while turn existence checks use point queries instead of listing headers or projecting a complete log. Effective model, system prompt, adapter defaults, and tools are recorded in standard `request/header` events. The request-scoped adapter uses the validated deployment reasoning and output policies. Workspace paths must stay below `/workspace/`.
package/README.zh.md ADDED
@@ -0,0 +1,183 @@
1
+ # dsh-edge
2
+
3
+ [English](README.md) | 中文
4
+
5
+ `dsh-edge` 是 DeepSeek Harness 的 Cloudflare 运行时原型。每次部署把通过认证的 owner 固定映射到一个 Durable Object,其基于 SQLite 的虚拟文件系统可跨请求持久保存。默认情况下,进程内 just-bash 后端直接在同一文件系统上执行命令,不依赖 Linux 容器或 Dynamic Worker。
6
+
7
+ 仓库提交的 Wrangler 配置从同一套应用 graph 暴露两个部署目标。默认目标是面向 Workers Free 的 direct 模式,不包含 Worker Loader binding。命名的 `isolated` 目标会添加 `LOADER` binding,并且需要 Workers Paid,但不会 fork DSH protocol、storage、UI 或 tool implementation。
8
+
9
+ 该原型通过上游 Cordis 组合的 `ReactLoopAgent`、`AgentRegistry`、`LlmRuntime`、`ToolRuntime`、`SystemPrompt`、`SessionStore` 和 `SessionPersistence` 运行持久对话。Edge 代码只绑定请求作用域的 DeepSeek 适配器,并把一个原生 DSH `bash` 工具定义映射到 Cloudflare Computer。Durable Object SQLite 实现上游持久化后端约定,write-behind、revision、恢复准备和崩溃恢复仍由 `PersistenceCoordinator` 负责。模型历史从 canonical 事件投影,不再单独持久化。
10
+
11
+ 浏览器直接使用上游 Web shell 和上游客户端插件包。构建期 assembler 根据上游 base 与 Web 组合包配置推导浏览器 roster,注入标准 `window.__DSH_BOOT__` graph,并把结果发布为 Cloudflare 静态资源。Durable Object 通过标准 HTTP carrier 实现受支持的上游 `ApiProxy` 方法,并以支持休眠的 WebSocket 提供两条上游 downlink。Edge 会排除缺少对应 host domain 的客户端插件,而不会 fork 其 UI 代码;在服务端 endpoint 可用前,session log export 也属于排除项。一个很小的 Edge 登录外壳会保护上游 UI 与协议,不修改两者本身。可选的本地 host 插件仍不可用。
12
+
13
+ ## 本地运行
14
+
15
+ 使用 Node.js 22.19 或更高版本,并在仓库根目录安装依赖。调用 DeepSeek 前,创建 一个不会提交到 Git 的 `apps/dsh-edge/.dev.vars` 文件:
16
+
17
+ ```dotenv
18
+ DSH_EDGE_ACCESS_KEY=replace-with-at-least-32-random-bytes
19
+ DEEPSEEK_API_KEY=replace-with-your-key
20
+ DEEPSEEK_MAX_OUTPUT_TOKENS=8192
21
+ DEEPSEEK_MODEL=deepseek-v4-flash
22
+ DEEPSEEK_REASONING_EFFORT=off
23
+ DEEPSEEK_STREAM_IDLE_TIMEOUT_MS=120000
24
+ DSH_EDGE_DEFAULT_COMMAND_TIMEOUT_MS=120000
25
+ DSH_EDGE_MAX_COMMAND_TIMEOUT_MS=120000
26
+ ```
27
+
28
+ 然后启动 Worker:
29
+
30
+ ```sh
31
+ pnpm --filter dsh-edge dev
32
+ ```
33
+
34
+ 该命令会先生成上游 Host-to-Client Remote 声明并构建上游 Web 资源,再启动 Wrangler。打开输出的地址(通常为 `http://localhost:8787`),输入 owner access key,选择 **Workspace** 并发送消息。Web UI 会创建 lazy blank session,并通过已认证的 Durable Object WebSocket 流式接收该轮次。
35
+
36
+ 诊断 API 使用相同的 owner cookie。先登录一次并把 cookie 写入临时 cookie jar,再验证持久文件系统和 shell:
37
+
38
+ ```sh
39
+ curl -c /tmp/dsh-edge-cookie -X POST \
40
+ -H 'content-type: application/x-www-form-urlencoded' \
41
+ --data-urlencode 'accessKey=replace-with-your-random-key' \
42
+ http://localhost:8787/api/auth/login
43
+
44
+ curl -b /tmp/dsh-edge-cookie -X PUT --data 'hello from the edge' \
45
+ 'http://localhost:8787/api/workspace/file?path=/workspace/hello.txt'
46
+
47
+ curl -b /tmp/dsh-edge-cookie -X POST -H 'content-type: application/json' \
48
+ --data '{"command":"cat /workspace/hello.txt"}' \
49
+ http://localhost:8787/api/workspace/exec
50
+ ```
51
+
52
+ 如需持久对话,先创建 session,再把返回的 id 用于后续 turn:
53
+
54
+ ```sh
55
+ curl -b /tmp/dsh-edge-cookie -X POST -H 'content-type: application/json' \
56
+ --data '{"title":"Edge session"}' \
57
+ http://localhost:8787/api/sessions
58
+
59
+ curl -b /tmp/dsh-edge-cookie -N -X POST -H 'content-type: application/json' \
60
+ --data '{"message":"Read /workspace/hello.txt and remember the result."}' \
61
+ http://localhost:8787/api/sessions/SESSION_ID/turn
62
+ ```
63
+
64
+ Session turn 直接把上游 `SessionEvent` 作为 SSE data 返回,包括 `agent/inbox/spliced`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result` 及 turn/step 边界。Live stream 最多为客户端排队 1 MiB;读取更慢的客户端会断线,但 turn 及其持久化不会取消。`GET /api/sessions/SESSION_ID` 只返回有界的 session metadata;客户端通过 `GET /api/sessions/SESSION_ID/events?after=SEQ&limit=COUNT` 获取历史,该接口按上游 `seq` 重放一个有界 page。Replay 默认为 128 个 events,最多接受 256 个;它会先检查持久 payload 的字节数再加载 rows,并最多保留 1 MiB 编码后的 SSE。后续请求由 `x-dsh-edge-has-more` 与 `x-dsh-edge-next-after` 驱动。
65
+
66
+ Session listing 同样有界:`GET /api/sessions?after=SESSION_ID&limit=COUNT` 默认返回 50 个 summaries,最多接受 100 个,并在 JSON body 中返回 `hasMore` 与 `nextAfter`。Durable Object 直接从 canonical rows 推导标题和最近时间,不会加载每个 session log。上游 Web session list 还会包含尚无 canonical event 的 retained blank header。
67
+
68
+ 上游浏览器的 `session.history` RPC 会在 live/cold 路径分流前统一执行 Edge 准入预算:每次请求最多使用浏览器的 50 条消息 page size。Cold log 会先在 Durable Object SQL 中应用该边界,再解码 payload,并在 8,192 个事件和 8 MiB 存储 payload 的上限内校验选中的连续窗口。Live log 不会先复制完整内存 window,而是先定位同一边界,再执行相同的事件上限与 8 MiB 编码响应上限。超出预算的窗口会被拒绝,而不会被截断。模型目录、模型选择与 turn admission 的 session 存在性检查只读取 header point query;只有真正需要恢复 agent 的 turn 才会解码 canonical history。
69
+
70
+ 上游侧边栏的 `session.search` RPC 直接扫描 canonical current user/assistant message,不引入第二套 Edge 索引或 wire format。每个请求最多检查最近发生过人工活动的 32 个 session,并且只搜索事件数不超过 512 的完整 session log;cold log 还必须能放入 256 KiB 的 stored-payload 上限。响应沿用上游最多 20 条、带长度限制的 snippet;当结果上限或工作预算使答案无法穷尽时,`hasMore` 为 true。
71
+
72
+ 每个经过认证的请求都会使用该部署固定的 `owner` Durable Object。旧的 `x-dsh-edge-instance` 请求头与 `instance` 查询参数会被拒绝,不会被当作 identity。`/api/sessions/SESSION_ID/turn` 会延续已保存的 canonical history。
73
+
74
+ ## Cloudflare 兼容矩阵
75
+
76
+ 该参考区分三类代码:可在 Workers 原生运行、需要在现有 DSH capability 上适配, 以及仍然依赖本地 Node.js host。表中的“当前”专指 `apps/dsh-edge`,不代表未来所有 Cloudflare 工作的最终状态。
77
+
78
+ | 能力 | 上游实现 | 当前 edge 状态 | Edge 决策 |
79
+ | --- | --- | --- | --- |
80
+ | DeepSeek transport | Fetch、SSE 解析、wire translation 和 retry metadata | 复用 | 每个请求构造一个上游 `DeepSeekAdapter`;其兼容的 Node API 由 `nodejs_compat` 提供。 |
81
+ | Provider attribution | 用 Node `createRequire` 加载 package version | 完成可移植修复后复用 | 静态导入 package metadata,让 bundler 保留同一个版本来源,运行时不再依赖 `import.meta.url`。 |
82
+ | LLM protocol | DSH messages、content blocks、stream chunks、tool calls、usage 和 finish reasons | 复用 | 由上游 `LlmRuntime` 和 `ReactLoopAgent` 组装、流式处理并记录 model exchange。 |
83
+ | Agent loop | 由 Cordis 组合、带 hooks、guards、sessions 和 tools 的 `ReactLoopAgent` | 复用 | 通过 `AgentRegistry` 创建和冷恢复 agent;local compaction 等可选 Node-oriented plugins 在完成适配前不加入 edge composition。 |
84
+ | Bash tool | Node subprocess、sandbox、terminal 和 job services | 在原生 tool seam 上适配 | 注册上游 `ToolDefinition`,但通过配置的 Computer workspace backend 和 just-bash 执行其 body。默认 direct backend 在 owner Durable Object 内运行,启用 hardened interpreter limits 且不提供网络命令;添加 `LOADER` binding 后会选择 Computer 的 isolated Worker Shell backend。原生 tool cancellation 会通过 Computer execution handle 发送 `SIGINT`。部署配置提供明确的默认 timeout 与调用方可选值上限,`timedOut` 则独立于 exit 与 cancellation status 报告 deadline。不支持原生二进制、后台进程、PTY 和任意 Linux 行为。 |
85
+ | Workspace filesystem | 本地 filesystem services 和 host paths | 适配 | 在 owner 基于 SQLite 的 Durable Object VFS 中保存 `/workspace`。 |
86
+ | Session persistence | `SessionPersistence` service、`PersistenceCoordinator` 及本地 JSONL/SQLite backends | 原生 backend 适配 | 复用上游 service 及 coordinator 的职责划分,在 Durable Object SQL 上实现存储原语,并使用上游 header/event 映射。一个 Edge 独有表会在透明休眠期间保留 empty session header,并在 canonical rows 物化时删除;Edge 不定义 turn 或 message schema。内部 coordinator helper 负责校验有界 replay loader,并在 disposal 前放弃失败且尚未物化的创建。 |
87
+ | Settings and credentials | 基于文件的 settings、launch environment 和 credential services | Edge 只读投影 | 为每次操作解析 Worker secret,绝不持久化或返回 literal key。空白 secret 会被视为未配置,使用前会移除首尾空白。`credentials.describe` 只报告 `DEEPSEEK_API_KEY` 是否已配置,以及其只读来源为 `worker-secret`。内置 `dsh-edge` preset 会通过上游只读 composition viewer 投影实际 release、shell/VFS、model、limits、credential state、prompt 与 tools。可写 settings 和经过身份认证的用户级 secret storage 尚未实现。 |
88
+ | Host boot and plugins | Node 命令行、Cordis profile loading、package resolution 和 HMR | 显式 Edge composition | 不在 Workerd 中运行本地 boot profile。部署前构建 immutable 客户端包,并排除 HMR 及 Edge `ApiProxy` 未暴露的 host domain。 |
89
+ | DSH transport | Typed HTTP RPC 加 mux/host WebSocket downlink | 复用并提供 Edge 服务端实现 | 对 unary method 使用上游 fetch carrier,并保留其 envelope、schema、projection、lazy blank-session 行为、有界内容搜索、prompt 与 queue mutation、workspace mutation、queue snapshot 和 event frame。两条 downlink 都由 Durable Object WebSocket 休眠机制持有;mux 重连会重放 live inbox 的待处理状态,REST/SSE 路由则保留为诊断兼容路径。 |
90
+ | Workspace registry | Storage-domain global state 加 `WorkspaceRecord` rows | 原生 backend 适配 | 保持上游 global 和 record value shape,包括手动 session 顺序与 archive membership;仅把物理 key 和原子写入映射到 Durable Object storage。Edge 把 registry 限制为一个原生 `/workspace` VFS;rename、delete、recreate 与 session reorder 保持上游 RPC 和 Host-frame 语义。 |
91
+ | Existing Web UI | 运行时加载的 shell 和 `dsh.client` 插件 graph | 复用并采用通用 composition fallback | 把上游 shell 和受支持的上游客户端包组装成 Worker 静态资源;共享的 slot occupancy 规则会隐藏缺少 provider 的 action。Cloudflare 直接提供普通资源,`/`、`/login` 与 `/api/*` 则进入 Worker 执行 owner access control。组装后的 asset policy 会阻止所有直接或 SPA-fallback shell alias 被嵌入 frame。 |
92
+ | Other tools | Web Search、filesystem editor tools、MCP、skills、workflows、jobs 和 subagents | Search 已移植;其他未移植 | 复用上游 DeepSeek Web Search 及其 30 秒 tool-call timeout。逐个针对 Worker-compatible capabilities 增加其余工具,不宣称不可用的 host 行为。 |
93
+ | Attachments | 本地 attachment storage 和 image references | 未移植 | `imageLimits` 能力缺席时,上游 composer 不会把粘贴或拖放的图片加入草稿。启用图片前,先确定由 Durable Object 还是 object storage 持有数据,并设计 signed delivery。 |
94
+ | Authentication and tenancy | 本地 trusted-user boundary | 单 owner 适配 | 要求一个高熵 Worker secret,把它交换为带签名、有效期 30 天的 HttpOnly `SameSite=Strict` cookie,并把所有已接纳请求路由到一个固定 owner object。这里刻意不提供注册、用户数据库、角色或多租户路由。 |
95
+
96
+ 浏览器请求路径是:
97
+
98
+ ```text
99
+ Cloudflare static assets -> upstream Web shell + client plugin graph
100
+ -> POST /api/session.create through the upstream HTTP carrier
101
+ -> host/workspace-changed + session/subscribed over Durable Object WebSockets
102
+ -> POST /api/session.prompt with the client rpcId
103
+ -> AgentRegistry live lookup or resume
104
+ -> sessionPersistence.prepare through PersistenceCoordinator on cold resume
105
+ -> ReactLoopAgent.followup(queue) or ReactLoopAgent.steer(steer)
106
+ -> pre-step admission gate waits for the sessions.flush durability barrier
107
+ -> session/queue snapshots publish live and replay on mux reconnect
108
+ -> turn-scoped DeepSeekAdapter configuration selected by sessionId
109
+ -> upstream LlmRuntime + ReactLoopAgent stream/event pipeline
110
+ -> upstream ToolRuntime native bash or web_search call
111
+ -> upstream WebRuntime + DeepSeek native search provider for web_search
112
+ -> direct just-bash backend in the owner Durable Object
113
+ (or optional Computer Worker Shell when LOADER is bound)
114
+ -> Durable Object /workspace VFS
115
+ -> upstream tool/result and next model step
116
+ -> ReactLoopAgent appends canonical inbox, chunk, message, tool and boundary events
117
+ -> sessions.flush durable barrier -> Durable Object SQLite backend
118
+ -> session/event, projection, and status frames over Durable Object WebSockets
119
+ -> upstream Web runtime reconciles and renders the canonical events
120
+ ```
121
+
122
+ 本地集成检查使用 SSE stand-in 以及真实的 Wrangler、Durable Object SQLite、默认 direct Computer workspace backend、静态资源服务、HTTP carrier 和 WebSocket。它验证 owner 登录、API 与 WebSocket cookie enforcement、拒绝旧 instance selector、direct shell 网络被禁用、上游 session create/list/history/search/prompt/rename/fork;queue edit、remove 与提升为 steering;workspace create/list/rename/delete/session reorder/archive;对应的实时与重连 baseline 和 Host frame;真实浏览器启动及由 UI 发起的 workspace rename、完整 turn、内容搜索、branch 与 archive 操作;浏览器 session 过期后自动返回登录页;跨 turn 对话连续性、event 重放、两步 bash 与 Web Search tool 交互,以及 Wrangler 重启后的恢复。一项聚焦的故障测试证明,入队后的持久化失败会阻止模型调用,同时不会把已经唤醒 Agent 的 prompt 报告为拒绝。提交到仓库的 model-visible 与 ARIA golden 会固定 tool transcript 以及组装后的上游 Web client 通过 Edge HTTP/WebSocket 协议呈现的结果。真实 DeepSeek 调用需要开发者自己的 key,因此不会纳入仓库测试套件。
123
+
124
+ ## API key 边界
125
+
126
+ `.dev.vars` 中的 `DEEPSEEK_API_KEY` 是本地 credential source。只读 Edge provider 会通过上游 `ctx.credentials` service 为每次 chat 或 search 操作提供该 Worker secret,但不会将它写入 Durable Object storage、VFS、session event 或 response。Provider 会移除首尾空白,并把空白值视为未配置。`DEEPSEEK_BASE_URL` 控制 chat,必须是不含 URL userinfo 的 HTTP(S) URL;它的只读 browser 投影会省略可能携带 gateway credential 的 query 与 fragment。`DEEPSEEK_SEARCH_BASE_URL` 独立控制 DeepSeek native search 使用的 Anthropic-compatible Messages endpoint,默认为 `https://api.deepseek.com/anthropic/v1`,且必须是不含 userinfo、query 与 fragment 的 HTTP(S) URL。Edge 会挂载上游 `web_search` tool、它的 30 秒 tool-call timeout policy 与结构化 Web result presentation;由于 runtime 尚无 arbitrary-URL network policy,`web_fetch` 保持禁用。Search request 不会跟随 redirect。`DEEPSEEK_MODEL` 选择经过校验的 chat model id,默认为 `deepseek-v4-flash`。`DEEPSEEK_REASONING_EFFORT` 接受 `off`、`low`、`high` 或 `max`,默认为 `off`。`DEEPSEEK_MAX_OUTPUT_TOKENS` 可以覆盖默认的 8,192-token chat 上限,且必须是正安全整数。`DEEPSEEK_STREAM_IDLE_TIMEOUT_MS` 可以覆盖默认的 120,000 ms chat 超时,且必须是小于等于 2,147,483,647 的正整数。部署配置无效时,会在查询 session 或打开 SSE response 前失败。
127
+
128
+ `DSH_EDGE_DEFAULT_COMMAND_TIMEOUT_MS` 会应用到每个未指定调用方 timeout 的 Computer 命令,`DSH_EDGE_MAX_COMMAND_TIMEOUT_MS` 则限制调用方选择的值。两者都默认为 120,000 ms,必须是小于等于 2,147,483,647 的正整数,且默认值不能超过最大值。
129
+
130
+ `DSH_EDGE_ACCESS_KEY` 是部署的单 owner 边界。它必须包含 32–512 个 UTF-8 字节,不得带首尾空白或控制字符;应生成随机值,而不是复用人工密码。Form 登录成功后会创建一个带签名、有效期 30 天的 HttpOnly `SameSite=Strict` cookie。HTTPS 部署使用仅限当前 host 的 `__Host-dsh_edge_owner` 名称并带 `Secure`;本地 HTTP 开发使用不带前缀的 cookie,因为浏览器会拒绝没有 HTTPS 的 `__Host-` cookie。Cookie 不包含用户数据,不会转发给 Durable Object,并会在 access key 轮换后失效。未认证的 API 与 WebSocket 请求返回 401。Owner authentication API failure 还会携带 `WWW-Authenticate: DshEdgeOwner`;只有这一精确的同 origin 401 才会让 Edge 组装的 shell 导航到 `/login`,因此 provider 或配置产生的 401 诊断仍然可见,而已过期的浏览器 session 仍能退出上游重连循环。来自不同 origin 的已认证浏览器 API 与 WebSocket 请求即使携带 same-site cookie 也会返回 403。Cloudflare asset policy 会阻止通过 `/`、`/index.html` 或 SPA fallback alias 到达的 shell 被嵌入 frame。`/` 重定向到 `/login`,`/api/health` 与 immutable asset 文件保持公开。它刻意不是 account system 或多租户边界。
131
+
132
+ ## 安装到 Cloudflare
133
+
134
+ 仓库提交的 `wrangler.jsonc` 顶层是默认 direct 目标,不要求 Worker Loader。Direct shell 代码与 agent、VFS 运行在同一个 Durable Object isolate 内,因此 just-bash 的 hardened execution limits、明确 command timeout、有界输出、显式环境变量和禁用网络命令共同构成主要命令边界。这比独立 Worker 的隔离更轻;不要把这个单 owner 部署暴露给不受信任的用户。
135
+
136
+ 同一文件还定义了 `env.isolated`,这是包含 `LOADER` binding 的完整 Workers Paid 目标。应用代码会发现 `LOADER` 并选择 Computer 的 Worker Shell backend,因此 `/api/health` 会报告 `just-bash-isolated`,而不是 `just-bash-direct`。Workers Paid 是每月 5 美元起的 Workers 订阅,并非 Cloudflare Pro 网站套餐。不同 Worker 名称分别拥有独立的 Durable Object storage 与 secret;如果需要让两种模式同时在线,请使用不同名称分别安装。
137
+
138
+ `wrangler.jsonc` 仍然是两种模式唯一的 canonical configuration。安装器会为每次上传生成私有的 mode-specific Wrangler configuration,并使用绝对 entrypoint 与 asset path,使其可以安全地放在 checkout 之外。Direct 模式只在打包时替换 Computer 中不可达的 Dynamic Worker shell-core module;Computer workspace adapter 与 command export 仍使用上游实现。Isolated 模式保留该 shell core,但把不可达的 Direct backend 替换成 fail-closed module,因此每个上传产物都只携带所选 command runtime。两个输出都会 minify。CI 使用同一个 renderer 构建,并拒绝压缩后超过 900 KiB 的 Direct 产物,从而在 Cloudflare 匿名临时账户上传路径强制执行的 1 MiB 上限下保留余量。
139
+
140
+ 无需克隆仓库即可运行引导式安装器:
141
+
142
+ ```sh
143
+ pnpm dlx dsh-edge@latest install
144
+ ```
145
+
146
+ 选择相同 runtime 并输入现有 Worker 名称即可升级。部署会保留 Durable Object 数据;由于 Cloudflare secret 只能写入而不能读取,升级会再次要求 owner access key 与 DeepSeek API key,并用输入值替换当前生效值:
147
+
148
+ ```sh
149
+ pnpm dlx dsh-edge@latest upgrade
150
+ ```
151
+
152
+ 安装器会先询问运行时,再询问账户。推荐的 `Free — Direct Shell` 模式可在 Workers Free 上运行,并可使用检测到的 Cloudflare 账户、打开 Cloudflare 登录或注册,也可在不登录的情况下创建临时账户。`Isolated — Dynamic Worker` 需要 Workers Paid,因此只提供已检测到或新认证的账户。Cloudflare 没有提供可靠的本地 Worker Loader entitlement 检查;isolated 安装会由 Cloudflare 对上传进行授权,并在被拒绝时提示启用 Workers Paid 或改用 direct 模式。
153
+
154
+ 后续提示会选择 Worker 名称、生成或接收 owner access key、通过隐藏输入收集 DeepSeek API key,并显示最终费用摘要。临时账户安装还会要求用户明确接受 Cloudflare 服务条款与隐私政策。安装器绝不会在未经确认时覆盖现有 Worker。两项 credential 会通过权限模式为 `0600` 的临时 secret 文件传给 Wrangler;Wrangler 子进程只会收到 allowlist 内的运行时环境变量和当前命令选中的 Cloudflare authentication,其他 ambient key、token、password、secret 与 Node 注入选项不会进入子进程。命令结束后临时 secret 文件会被删除,安装器从 Wrangler 结构化输出中取得最终 URL。上传后它不会探测公开 Worker,而是直接输出 URL、owner access key 与明确的下一步;临时账户还会收到一个 bearer claim URL,必须在 60 分钟内认领才能保留 Worker 及其数据。如果上传成功,但输出解析、claim URL 提取、中断处理或本地清理导致正常交接无法完成,命令仍会在按失败退出前通过恢复卡片输出已生效的 owner key 与当时已知的 URL。安装过程直接通过 Wrangler 上传,不会创建或绑定 GitHub 仓库、Cloudflare Builds 项目或源码构建流水线。
155
+
156
+ 从 checkout 开发的贡献者可以用 `pnpm --filter dsh-edge bundle:direct` 和 `pnpm --filter dsh-edge bundle:isolated` 在本地复现两种部署 bundle。第一条命令还会执行压缩体积预算检查。
157
+
158
+ 贡献者可以在没有 key 且不发起网络请求的情况下重放完整的 Free 临时账户流程。这个 example 会运行实际交付的 bin、真实 prompt、Wrangler 子进程、结构化部署输出解析与最终交接,只替换外部 Cloudflare command:
159
+
160
+ ```sh
161
+ pnpm --filter dsh-edge example:install
162
+ ```
163
+
164
+ ## 原型 API
165
+
166
+ - `POST /api/<upstream-method>` 接受受支持 `ApiProxy` 方法的上游 `ClientRequest` envelope。Web client 当前使用 session list/search/create/history/models/select/prompt/updateQueue/rename/fork/cancel、host description、workspace list/create/rename/delete/reorder/archive、skills、agent presets、settings 与 credential description,以及 LLM catalog。`agentPreset.read` 会通过上游只读 viewer 渲染程序化 Edge composition,`credentials.describe` 则返回不含 value 的 credential state。Search 会投影 canonical current-message surface,并且只返回有界的上游 result value。Fork 会通过 canonical session seed format 复制 completed-turn prefix,并保留 parent lineage;超过 8,192 个事件或 8 MiB 的 seed 会被 Edge 拒绝,而不会在 Durable Object 中物化无界 history。Queue mutation 通过 live upstream Agent inbox 编辑、移除或把一项提升为 steering;同步 inbox mutation 是上游接纳点,后续 write-behind 与 retirement retry 由 persistence coordinator 负责。Workspace mutation 通过 Durable Object backend 持久化上游 workspace-domain global 与 record shape。Archive 保留 session log 与 workspace slot;unary response 与 Host frame 携带和上游一致的完整 snapshot。
167
+ - `GET /login` 渲染 Edge 持有的 owner form;`POST /api/auth/login` 用已配置的 access key 换取 signed cookie,`GET /api/auth/session` 报告 cookie 是否有效,`POST /api/auth/logout` 清除 cookie。
168
+ - `GET /api/events.mux` 和 `GET /api/events.host` 会升级为上游 downlink WebSocket。Durable Object 会把每个 socket 的 channel 与已验证 owner session 过期时间序列化为 hibernation attachment,通过 alarm 在该时间关闭连接,并从 Durable Object SQL 重建 canonical session 与 retained blank header。每次 inbox splice 提交后,mux stream 都会发布完整的 `session/queue` snapshot;客户端重连时还会发送 live inbox 的待处理 baseline。
169
+ - `POST /api/commands/list` 使用上游 generated-Remote envelope 返回空 catalog,因为 Edge preset 没有注册 human command。
170
+ - `GET /api/health` 会返回每次 deploy 生成的公开 release identifier,并先验证 owner authentication、部署级 DeepSeek 凭据、模型与传输配置,以及命令超时策略,再报告运行时组件已就绪。它不会调用提供方、Durable Object、VFS 或 shell。
171
+ - `PUT /api/workspace/file?path=/workspace/...` 写入 UTF-8 文件。
172
+ - `GET /api/workspace/file?path=/workspace/...` 读取 UTF-8 文件。
173
+ - `DELETE /api/workspace/file?path=/workspace/...` 删除文件。
174
+ - `POST /api/workspace/exec` 接受 `{ "command": "...", "cwd": "/workspace/..." }`,`cwd` 默认为 `/workspace`;每次执行都会收到部署级默认 timeout,`timedOut` 会报告该 deadline 是否已过,输出超过保留边界时,`outputTruncated` 会报告这一状态。
175
+ - `POST /api/sessions` 使用必填 `title` 创建持久 session;API 返回前,标题会写成标准、用户来源的 `session/title` 事件。如果 session 已持久化但 Workspace attachment 失败,500 响应会使用 `workspace-attach-failed` 并携带完整的已创建 `session`,调用方可以恢复其 id,而不会创建重复 session。
176
+ - `GET /api/sessions?after=...&limit=...` 列出一个有界 summary page;`GET /api/sessions/:sessionId` 读取一个 session。上游 persistence service 没有定义破坏性删除,因此这里不暴露 session deletion。
177
+ - `POST /api/sessions/:sessionId/turn` 接受 `{ "message": "..." }` 并流式返回持久 SSE events。
178
+ - `GET /api/sessions/:sessionId/events?after=...&limit=...` 重放一个有界 event page,并返回 continuation headers。
179
+ - `POST /api/sessions/:sessionId/cancel` 终止当前 Durable Object 进程持有的 active turn。
180
+
181
+ 上游 Session 创建与 fork 在发布成功但 Workspace attachment 失败时返回 `workspace-attach-failed`,并携带已发布的 session 与 Workspace id;诊断创建路由会返回相同 code 及完整的已创建 session。Prompt 和 queue-edit 文本共用 64 KiB 语义上限,即使其 RPC 载体最多接受 512 KiB。Edge 组合没有目录流 provider,因此上游浏览器会在仅剩一个 Workspace 时隐藏 Delete,并在仍有恢复路径时重新显示。
182
+
183
+ API 将文本文件限制为 1 MiB、命令限制为 16 KiB、用户消息限制为 64 KiB,并将保留的 shell stdout 与 stderr 总量限制为 64 KiB;这些均为 UTF-8 字节限制。Request body 会在解析或转发前增量消费:创建 session 的 JSON 上限为 8 KiB、workspace execution 为 128 KiB、承载消息的 turn 或 queue-update RPC 为 512 KiB,文件上传也会在消费过程中执行其 1 MiB 上限,并拒绝非法 UTF-8。Body 一旦越过路由上限,后续 chunk 只会排空而不会继续保留,路由最终返回 413。读取文件时会先检查 VFS metadata,再通过相同的 1 MiB 上限收集实际打开的原始 byte stream,从而关闭 `stat()` 与 `readFile()` 之间的增长竞态,且不会保留无界值。组合输出越界时,runtime 会请求中断 shell execution,并停止累积后续输出。只有 adapter 实际请求过中断时,命令状态才会报告 cancellation;这一状态与 shell exit code 独立,`timedOut` 则单独记录 deadline expiry。首次 session 持久化失败时,会先丢弃保留但尚未物化的 batch,再 dispose 新发布的上游 agent handle,因此 teardown 无法在创建请求返回错误后又提交该 session。Lazy blank session 在首个 canonical event 前只保留其上游 header;物化该 event 的同一个 SQL transaction 会删除 retained header。每个 turn 持有一个上游 handle,并在 stream 完成后 dispose,避免曾访问的对话在 Durable Object 整个生命周期内常驻内存。部署 settings 会在声明进程内 owner 前解析。上游 protocol prompt 只有在其 inbox event 跨过 `SessionStore.flush()` 后才返回 accepted 并发布 running state;后续 streamed event 也会先跨过同一个 barrier,再通过 WebSocket 或 SSE 发送。Queue edit、remove 与 steering promotion 以同步 live-inbox mutation 作为接纳点;后续 write-behind 或 retirement retry 由 `PersistenceCoordinator` 负责,因此后续存储尝试不能把已接纳的 mutation 变成被拒绝的响应。Session rename 遵循相同的上游 metadata contract:对于 active 与 cold session,同步追加 title 即为接纳点。Workspace global state 与 record 在 Edge-specific physical key 下使用上游逻辑 schema;DO transaction 原子组合 record 与 registry order 变更,进程内 chain 则串行化 workspace mutation。已提交的 rename、delete、recreate、session reorder、attachment 和 archive 变更会发布对应的上游 Host frame,`workspace.list` 会在重启后恢复完整 baseline。进程内 owner 会拒绝并发 turn,cancel 调用原生 agent cancellation path。下次冷恢复时,上游 interrupted-turn repair 会关闭开放的持久 event tail,canonical `session/end-seed` marker 会保留生命周期边界。Replay 会独立确认 session 是否不存在,因此 persistence corruption 或 SQL failure 不会被折叠成 404;它只读取一个有界 SQL page,而不是完整 suffix,并限制编码后的 response。`PersistenceCoordinator.readValidatedPage()` 会对每个 page 执行 identity、format、legacy-shape 和 event-vocabulary 校验,而不把 Edge pagination 加入公共 persistence service。如果 legacy normalization 需要更早的 message,它只会通过同一个受字节约束的 loader 重读一个 prefix;必要 prefix 无法装入预算时会拒绝该 page。冷浏览器 history 会在 SQL 中选择消息边界,并仅在固定事件数与存储字节上限内加载所得连续区间。Session listing 查询一个有界的 canonical header/title summary page;detail 读取持久化的 canonical point summary 或 retained blank header,turn existence check 则使用 point query,不会列出全部 headers 或投影完整 log。实际 model、system prompt、adapter defaults 和 tools 写入标准 `request/header` 事件。Request-scoped adapter 使用已校验的部署级 reasoning 与输出策略。workspace 路径必须位于 `/workspace/` 下。