dsh-edge 0.3.0-alpha.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority.
3
3
  # After editing either side, update both and re-record every pair with:
4
4
  # pnpm run doc-pairs -- --write
5
- README.md: 8f0179678a006d175cbb9207577f78654bc9ca69
6
- README.zh.md: f69246647c123e8633138a5032c5556724ab2d22
5
+ README.md: f341a64cd11e666f6bc70c18635b0e7212eb5a50
6
+ README.zh.md: fea9635e544b436cadb2bf33159ae1b8ca866938
package/README.md CHANGED
@@ -8,9 +8,29 @@ English | [中文](README.zh.md)
8
8
 
9
9
  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.
10
10
 
11
- The runtime 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.
11
+ The runtime keeps upstream ownership clear:
12
12
 
13
- 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. The upstream image composer, gallery, lightbox, attachment wire contract, and DeepSeek serializer are reused unchanged; the storage seam selects private R2 for new permanent deployments and bounded Durable Object storage for temporary deployments. 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.
13
+ - `ReactLoopAgent`, `AgentRegistry`, `LlmRuntime`, `ToolRuntime`, `SystemPrompt`, `SessionStore`, and `SessionPersistence` run through the upstream Cordis composition.
14
+ - Edge binds a request-scoped DeepSeek adapter and maps the native DSH `bash` tool onto Cloudflare Computer.
15
+ - Durable Object SQLite implements the upstream persistence backend contract. `PersistenceCoordinator` still owns write-behind, revisions, resume preparation, and crash recovery.
16
+ - Model history is projected from canonical events rather than persisted in a second Edge schema.
17
+
18
+ The browser also remains upstream-owned:
19
+
20
+ - A build-time assembler derives the Web roster from upstream configs, injects the standard `window.__DSH_BOOT__` graph, and emits Cloudflare static assets.
21
+ - The Durable Object implements supported upstream `ApiProxy` methods through the standard HTTP carrier and supplies both downlinks as hibernatable WebSockets.
22
+ - The image composer, gallery, lightbox, attachment wire contract, and DeepSeek serializer are reused unchanged.
23
+ - The storage seam chooses private R2 for new permanent deployments, bounded Durable Object storage for temporary deployments, and a one-time owner choice for pre-0.3 Workers.
24
+ - Client plugins whose host domains are unavailable are excluded instead of forked. Session-log export and optional local-host plugins remain unavailable.
25
+ - A small Edge-owned login shell protects the upstream UI and protocol without changing either one.
26
+
27
+ ## Find what you need
28
+
29
+ - [Install or upgrade on Cloudflare](#install-on-cloudflare)
30
+ - [Compare native, adapted, and unavailable capabilities](#cloudflare-compatibility-matrix)
31
+ - [Configure DeepSeek credentials, models, timeouts, and owner authentication](#api-key-boundary)
32
+ - [Run the release runtime locally](#run-locally)
33
+ - [Inspect routes, limits, and durability behavior](#edge-api)
14
34
 
15
35
  ## Run locally
16
36
 
@@ -70,13 +90,15 @@ curl -b /tmp/dsh-edge-cookie -N -X POST -H 'content-type: application/json' \
70
90
  http://localhost:8787/api/sessions/SESSION_ID/turn
71
91
  ```
72
92
 
73
- 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.
74
-
75
- 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.
76
-
77
- 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.
93
+ The diagnostic session APIs preserve upstream events while bounding every read:
78
94
 
79
- 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.
95
+ - A turn streams upstream `SessionEvent` values directly as SSE, including inbox splices, assistant chunks/messages, tool calls/results, and turn/step boundaries.
96
+ - A live stream queues at most 1 MiB per client. A slower reader is disconnected without cancelling the turn or its persistence.
97
+ - Session detail returns bounded metadata. Event replay defaults to 128 events, accepts at most 256, preflights stored bytes, retains at most 1 MiB of encoded SSE, and exposes continuation headers.
98
+ - Session listing defaults to 50 summaries and accepts at most 100. The Durable Object derives titles and timestamps from canonical rows without loading each log; upstream Web also receives retained blank headers.
99
+ - Browser history is capped at 50 messages and refuses, rather than truncates, windows above 8,192 events or 8 MiB. Cold paths apply the boundary in SQL; live paths locate it without copying the complete in-memory log.
100
+ - Sidebar search uses canonical current user/assistant messages without a second index or wire format. It examines at most 32 recent sessions, requires complete logs of at most 512 events and 256 KiB when cold, and returns at most 20 snippets with `hasMore` when a bound is reached.
101
+ - Model lookup and selection use header point reads. Only a turn that resumes the agent decodes canonical history.
80
102
 
81
103
  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.
82
104
 
@@ -93,13 +115,13 @@ This reference separates code that runs natively in Workers, code adapted at an
93
115
  | 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. |
94
116
  | Workspace filesystem | Local filesystem services and host paths | Adapted | Store `/workspace` in the owner's SQLite-backed Durable Object VFS. |
95
117
  | 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. |
96
- | 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. |
118
+ | 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, deployment-default model, runtime-derived upstream model catalog, per-session selection scope, limits, credential state, prompt, and tools through the upstream read-only composition viewer. Writable settings and authenticated per-user secret storage remain open. |
97
119
  | 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. |
98
120
  | 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. |
99
121
  | 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. |
100
122
  | 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. |
101
123
  | 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. |
102
- | Attachments | Local attachment storage, upstream image references, composer, gallery, lightbox, and provider conversion | Adapted at the native storage seam | Reuse upstream `AttachmentStore`, admission, protocol, authorization, UI, and DeepSeek conversion unchanged. Store immutable PNG/JPEG bytes under their SHA-256 identities in private R2 for new permanent deployments or in a 64 MiB, 512 KiB-chunked DO fallback for temporary deployments; session events retain only upstream refs. The first backend is pinned per owner instance so claiming or upgrading cannot strand existing references. |
124
+ | Attachments | Local attachment storage, upstream image references, composer, gallery, lightbox, and provider conversion | Adapted at the native storage seam | Reuse upstream `AttachmentStore`, admission, protocol, authorization, UI, and DeepSeek conversion unchanged. Store immutable PNG/JPEG bytes under their SHA-256 identities in private R2 for new permanent deployments or in a 64 MiB, 512 KiB-chunked DO backend for temporary deployments and owners who select it while upgrading a pre-attachment Worker; session events retain only upstream refs. The first backend is pinned per owner instance so claiming or upgrading cannot strand existing references. |
103
125
  | 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. |
104
126
 
105
127
  The browser request path is:
@@ -109,7 +131,7 @@ Cloudflare static assets -> upstream Web shell + client plugin graph
109
131
  -> POST /api/session.create through the upstream HTTP carrier
110
132
  -> host/workspace-changed + session/subscribed over Durable Object WebSockets
111
133
  -> POST /api/session.prompt with the client rpcId
112
- -> upstream image admission validates and stores immutable bytes in private R2
134
+ -> upstream image admission validates and stores immutable bytes in the selected R2 or DO backend
113
135
  -> canonical session events retain upstream sha256 attachment refs only
114
136
  -> AgentRegistry live lookup or resume
115
137
  -> sessionPersistence.prepare through PersistenceCoordinator on cold resume
@@ -131,23 +153,63 @@ Cloudflare static assets -> upstream Web shell + client plugin graph
131
153
  -> upstream Web runtime reconciles and renders the canonical events
132
154
  ```
133
155
 
134
- The local integration check uses an SSE stand-in and the real Wrangler, Durable Object SQLite, local R2, the default direct Computer workspace backend, static asset service, HTTP carrier, and WebSockets. Direct mode exercises the temporary DO attachment backend while Isolated mode exercises private R2. 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; an image prompt through the upstream composer/protocol/provider path; authorized attachment reads, cross-session rejection, fork reuse, and attachment persistence after restart; 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, image 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.
156
+ The local integration suite uses an SSE stand-in with real Wrangler, Durable Object SQLite, local R2, the Direct Computer workspace backend, static assets, the HTTP carrier, and WebSockets. Direct mode exercises DO attachment storage; Isolated mode exercises private R2. Together they verify:
157
+
158
+ - owner login, API/WebSocket cookie enforcement, legacy-selector rejection, and disabled Direct-shell networking;
159
+ - upstream session create/list/history/search/prompt/rename/fork and queue edit/remove/steering flows;
160
+ - image admission through the upstream composer, protocol, provider, authorization, fork reuse, and restart persistence;
161
+ - Workspace create/list/rename/delete/reorder/archive, live/reconnect baselines, and Host frames;
162
+ - real browser boot, UI-issued Workspace rename, image turn, content search, branch, archive, and expired-session login recovery;
163
+ - conversation continuity, event replay, two-step bash and Web Search tool exchanges, and Wrangler-restart restoration.
164
+
165
+ A focused failure test proves that post-enqueue durability failure blocks model use without reporting the already-woken prompt as rejected. Committed model-visible and ARIA goldens pin tool transcripts and the assembled upstream Web client. A live DeepSeek call requires the developer's key and is intentionally outside the repository test suite.
135
166
 
136
167
  ## API-key boundary
137
168
 
138
- `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.
169
+ `DEEPSEEK_API_KEY` from `.dev.vars` is the local credential source. A read-only Edge provider exposes it through upstream `ctx.credentials` for each chat or search operation without writing the value to Durable Object storage, the VFS, session events, or responses. Surrounding whitespace is removed; a blank value is unconfigured.
170
+
171
+ | Variable | Purpose and validation |
172
+ | --- | --- |
173
+ | `DEEPSEEK_BASE_URL` | Chat endpoint. Must be HTTP(S) without URL userinfo. The browser projection omits query and fragment components that may carry gateway credentials. |
174
+ | `DEEPSEEK_SEARCH_BASE_URL` | Anthropic-compatible Messages endpoint for native Web Search. Defaults to `https://api.deepseek.com/anthropic/v1`; must be HTTP(S) without userinfo, query, or fragment. Search does not follow redirects. |
175
+ | `DEEPSEEK_MODEL` | Validated deployment default; defaults to `deepseek-v4-flash`. Each session may choose another upstream catalog entry. |
176
+ | `DEEPSEEK_REASONING_EFFORT` | `off`, `low`, `high`, or `max`; defaults to `off`. |
177
+ | `DEEPSEEK_MAX_OUTPUT_TOKENS` | Optional positive safe integer overriding the 8,192-token chat default. |
178
+ | `DEEPSEEK_STREAM_IDLE_TIMEOUT_MS` | Optional positive integer up to 2,147,483,647; defaults to 120,000 ms. |
179
+ | `DSH_EDGE_DEFAULT_COMMAND_TIMEOUT_MS` | Default Computer command timeout; defaults to 120,000 ms. |
180
+ | `DSH_EDGE_MAX_COMMAND_TIMEOUT_MS` | Caller-selectable timeout ceiling; defaults to 120,000 ms and cannot be lower than the default. |
139
181
 
140
- `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.
182
+ Invalid deployment configuration fails before session lookup or SSE response creation. Edge mounts upstream `web_search` with a 30-second tool-call timeout and structured results. `web_fetch` remains disabled because the runtime has no arbitrary-URL network policy.
141
183
 
142
- `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.
184
+ ### Owner authentication
185
+
186
+ - `DSH_EDGE_ACCESS_KEY` is the single-owner boundary. It must contain 32–512 UTF-8 bytes without surrounding whitespace or control characters; generate a random value instead of reusing a human password.
187
+ - Login creates a signed 30-day HttpOnly `SameSite=Strict` cookie. HTTPS uses the host-only `__Host-dsh_edge_owner` name with `Secure`; local HTTP uses an unprefixed cookie.
188
+ - The cookie carries no user data, is never forwarded to the Durable Object, and becomes invalid when the access key rotates.
189
+ - Unauthenticated API and WebSocket requests return 401. Only an owner-authentication 401 carrying `WWW-Authenticate: DshEdgeOwner` makes the same-origin shell navigate to `/login`; provider/configuration 401 diagnostics remain visible.
190
+ - Authenticated browser API and WebSocket requests from another origin return 403 even with a same-site cookie.
191
+ - The asset policy prevents framing through `/`, `/index.html`, or an SPA fallback. `/` redirects to `/login`; `/api/health` and immutable assets remain public.
192
+
193
+ This deliberately is not an account system or multi-tenant boundary.
143
194
 
144
195
  ## Install on Cloudflare
145
196
 
146
- 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.
197
+ | Target | Cloudflare requirement | Command boundary | Health identifier |
198
+ | --- | --- | --- | --- |
199
+ | Direct (default top level) | Workers Free; no Loader binding | Hardened just-bash in the agent/VFS Durable Object, with explicit timeouts, bounded output/environment, and no network command | `just-bash-direct` |
200
+ | `env.isolated` | Workers Paid with `LOADER` | Computer Worker Shell in a separate Dynamic Worker | `just-bash-isolated` |
147
201
 
148
- 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.
202
+ Direct mode is lighter isolation than a separate Worker; do not expose the single-owner deployment to untrusted users. Workers Paid is a Workers subscription starting at $5 per month, not the Cloudflare Pro website plan. Worker names have independent Durable Object storage and secrets, so use different names when both modes should remain live.
149
203
 
150
- `wrangler.jsonc` remains the single canonical configuration for both modes. Release packaging builds one tested, minified Worker artifact per mode from the workspace sources. Direct mode replaces only Computer's unreachable Dynamic Worker shell-core module at build 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 artifact carries only its selected command runtime. The published installer generates a private mode-specific configuration that points at the selected artifact and asks Wrangler to upload it with `no_bundle`; the user's machine does not rebuild dsh-edge or resolve the upstream Harness packages into a new Worker. CI starts the Direct artifact from an installed tarball and rejects it above a 900 KiB compressed budget, leaving headroom below the 1 MiB limit enforced by Cloudflare's anonymous temporary-account upload path.
204
+ `wrangler.jsonc` remains the canonical source for both modes:
205
+
206
+ - Release packaging builds one tested, minified artifact per mode.
207
+ - Direct replaces only Computer's unreachable Dynamic Worker shell-core module; its Workspace adapter and command exports remain upstream.
208
+ - Isolated preserves that shell core and replaces the unreachable Direct backend with a fail-closed module. Each artifact therefore carries only its selected command runtime.
209
+ - The installer generates a private mode-specific config, points it at the selected artifact, and uploads with `no_bundle`. The user's machine does not rebuild dsh-edge or resolve Harness packages into a new Worker.
210
+ - CI starts the Direct artifact from an installed tarball and rejects gzip output above 900 KiB, preserving headroom below the 1 MiB anonymous temporary-account limit.
211
+
212
+ ### Install and upgrade
151
213
 
152
214
  Run the stable installer without cloning this repository:
153
215
 
@@ -155,7 +217,7 @@ Run the stable installer without cloning this repository:
155
217
  npx dsh-edge install
156
218
  ```
157
219
 
158
- This resolves through npm's `latest` channel. Use `npx dsh-edge@next install` to test the current 0.3 prerelease with upstream model selection and image prompts.
220
+ This resolves through npm's `latest` channel. Use `npx dsh-edge@next install` only to opt into a newer prerelease when one is available.
159
221
 
160
222
  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:
161
223
 
@@ -165,13 +227,32 @@ For a stable deployment, run:
165
227
  npx dsh-edge upgrade
166
228
  ```
167
229
 
168
- If the installed version is a 0.2 alpha, promote it to the stable channel once with `npx dsh-edge@latest upgrade`; prerelease deployments otherwise remain on `next`. The Edge settings page derives the channel from the installed version and copies the matching command.
169
-
170
- 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. For a new permanent account installation, the installer creates or reuses a private `<worker-name>-attachments` R2 bucket and writes only its binding to the generated private Wrangler config. It never deletes the bucket on deployment failure. R2 must be enabled for the selected account; otherwise the installer provides the activation/retry path. Temporary accounts use a 64 MiB Durable Object attachment backend and support the same upstream image UI. Claiming preserves that backend and its existing image history; automatic migration to R2 is not implemented. Each new deployment records an explicit attachment-storage marker. Before updating an existing Worker, the installer inspects every active version, preserves the marked R2-or-DO choice, initializes an unmarked pre-attachment release on private R2, and refuses a mixed rollout rather than guessing.
171
-
172
- 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. Deployment output is hidden behind one progress indicator by default; add `--verbose` to either command to inspect Wrangler diagnostics.
173
-
174
- After an accepted upload, a second progress indicator observes the public `/api/health` route for at most 45 seconds without sending either credential or following redirects. It accepts only the exact packaged version and selected runtime. A matching response produces a ready card. Cloudflare propagation, challenge, placeholder, transport, and older-release responses remain pending; expiry still exits successfully and tells the owner to wait briefly and refresh. This observation does not call DeepSeek or touch Durable Object state. The final card 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. A rejected upload is reported as not installed and, when Wrangler created a temporary account first, still prints its claim URL without presenting the unused owner key as active. If upload succeeds but output parsing, claim-URL extraction, interruption handling, activation interruption, 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.
230
+ If the installed version contains `-alpha` or `-rc`, promote it to the stable channel once with `npx dsh-edge@latest upgrade`. The Edge settings page derives its command from the installed version; without that explicit `@latest` command, an existing prerelease remains on `next`.
231
+
232
+ ### Accounts and attachment storage
233
+
234
+ - The installer asks for the runtime before the account.
235
+ - Recommended `Free — Direct Shell` works on Workers Free with a detected account, a new sign-in/registration, or a temporary account without login.
236
+ - `Isolated Dynamic Worker` requires Workers Paid and offers only a detected or newly authenticated account. Cloudflare authorizes the Loader upload; rejection becomes a choice between enabling Workers Paid and switching to Direct mode.
237
+ - New permanent installs create or reuse a private `<worker-name>-attachments` R2 bucket and place only its binding in the generated private Wrangler config. Deployment failure never deletes the bucket.
238
+ - R2 Standard has an included monthly free tier, but the account must enable its separate usage-based subscription. The installer checks R2 before collecting Worker secrets.
239
+ - Cloudflare error `10042` offers account-specific activation, retry, and cancellation. Only an unmarked pre-attachment Worker may safely switch to DO storage; a new or R2-pinned deployment cannot switch and strand references.
240
+ - Temporary accounts use the same image UI with a 64 MiB DO backend. Claiming preserves that backend and history; automatic R2 migration is not implemented.
241
+ - Every new deployment records its attachment-storage marker. Upgrades inspect every active version and preserve the marked or bound backend.
242
+ - A pre-image Worker has no marker, binding, or image references, so its first 0.3 upgrade asks once between 64 MiB DO storage and private R2, then pins the choice. Mixed active rollouts are refused rather than guessed.
243
+
244
+ ### Credential handoff and activation
245
+
246
+ - The remaining prompts select a Worker name, generate or accept the owner access key, collect the DeepSeek key through hidden input, and show a final cost summary. Temporary installs also require explicit acceptance of Cloudflare's terms and privacy policy.
247
+ - Existing Workers are never overwritten without confirmation.
248
+ - Both credentials travel through a mode-`0600` temporary secrets file. Wrangler receives only an allowlisted runtime environment and the selected Cloudflare authentication; unrelated ambient secrets and Node injection options do not reach the child.
249
+ - The secret file is removed after the command. Wrangler's structured output supplies the deployed URL. Add `--verbose` to inspect full deployment diagnostics.
250
+ - After upload, the installer observes public `/api/health` for up to 45 seconds without credentials or redirects. Only the exact package version and selected runtime produce a ready card; propagation, challenge, placeholder, transport, and older-release responses remain pending.
251
+ - Observation expiry exits successfully and asks the owner to refresh shortly. It never calls DeepSeek or touches Durable Object state.
252
+ - The final card prints the URL, active owner key, and next steps. Temporary accounts also receive a bearer claim URL that must be claimed within 60 minutes.
253
+ - A rejected upload is reported as not installed. If Wrangler created a temporary account, its claim URL is still shown without presenting the unused owner key as active.
254
+ - If upload succeeds but handoff fails, a recovery card prints the active owner key and all known URLs before the command exits unsuccessfully.
255
+ - Installation uploads directly through Wrangler; it does not create or bind a GitHub repository, Cloudflare Builds project, or source-build pipeline.
175
256
 
176
257
  Contributors working from a checkout can reproduce the two release artifacts locally with `pnpm --filter dsh-edge bundle:direct` and `pnpm --filter dsh-edge bundle:isolated`. The first command also enforces the compressed-size budget.
177
258
 
@@ -183,11 +264,27 @@ pnpm --filter dsh-edge example:install
183
264
 
184
265
  ## Edge API
185
266
 
186
- - `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.
187
- - `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.
188
- - `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.
189
- - `POST /api/commands/list` implements the upstream generated-Remote envelope with an empty catalog because the Edge preset registers no human commands.
190
- - `GET /api/health` returns the public package-and-mode release identifier and configured attachment default (`private-r2` or `temporary-do`), 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, R2, VFS, or shell. The authenticated agent-preset projection reports the actual backend pinned by the owner Durable Object and the temporary storage cap.
267
+ ### Upstream RPC carrier
268
+
269
+ - `POST /api/<upstream-method>` accepts the upstream `ClientRequest` envelope for supported `ApiProxy` methods.
270
+ - The Web client 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.
271
+ - `agentPreset.read` renders the programmatic Edge composition through the upstream read-only viewer. `credentials.describe` returns credential state without a value.
272
+ - Search projects canonical current-message surfaces and returns bounded upstream result values.
273
+ - Fork copies a completed-turn prefix through the canonical session seed format and retains parent lineage. Edge refuses seeds above 8,192 events or 8 MiB instead of materializing unbounded history.
274
+ - Queue mutations edit, remove, or promote an item through the live upstream Agent inbox. The synchronous mutation is the acceptance point; the persistence coordinator owns later write-behind and retirement retry.
275
+ - Workspace mutations persist 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.
276
+
277
+ ### Authentication and downlinks
278
+
279
+ - `GET /login` renders the owner form. `POST /api/auth/login` exchanges the configured key for a signed cookie; `GET /api/auth/session` reports validity; `POST /api/auth/logout` clears it.
280
+ - `GET /api/events.mux` and `GET /api/events.host` upgrade to upstream downlink WebSockets. The Durable Object serializes each socket's channel and verified owner-session expiry as a hibernation attachment, closes it at expiry through an alarm, and reconstructs canonical sessions plus retained blank headers from SQL.
281
+ - After each committed inbox splice, mux publishes a complete `session/queue` snapshot. Reconnecting clients receive pending live-inbox baselines.
282
+ - `POST /api/commands/list` uses the upstream generated-Remote envelope with an empty catalog because the Edge preset registers no human commands.
283
+ - `GET /api/health` returns the public release/mode identifier and configured attachment default (`private-r2` or `temporary-do`). It validates owner authentication, deployment-scoped DeepSeek credentials, model/transport choices, and command timeouts before reporting ready.
284
+ - Health does not call the provider, Durable Object, R2, VFS, or shell. The authenticated agent-preset projection reports the pinned backend, temporary cap, deployment-default model, and runtime-derived upstream catalog with session selection scope.
285
+
286
+ ### Diagnostic REST routes
287
+
191
288
  - `PUT /api/workspace/file?path=/workspace/...` writes a UTF-8 file.
192
289
  - `GET /api/workspace/file?path=/workspace/...` reads a UTF-8 file.
193
290
  - `DELETE /api/workspace/file?path=/workspace/...` removes a file.
@@ -198,6 +295,49 @@ pnpm --filter dsh-edge example:install
198
295
  - `GET /api/sessions/:sessionId/events?after=...&limit=...` replays a bounded event page and returns continuation headers.
199
296
  - `POST /api/sessions/:sessionId/cancel` aborts the active turn owned by the current Durable Object process.
200
297
 
201
- 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. Their RPC carrier accepts up to 10 MiB so a projected 7 MiB raw-image batch still fits after base64 and envelope overhead. 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.
202
-
203
- 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. Both attachment backends accept PNG and JPEG only, at most 4 images per message, 3.5 MiB per image, 7 MiB total, 40 million pixels, and 2,000 pixels per side; admission fully decodes the declared raster format before writing. 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 10 MiB, 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/`.
298
+ ### Session and Workspace behavior
299
+
300
+ - Session creation and fork return `workspace-attach-failed` with the published session and Workspace ids when publication succeeds but Workspace attachment fails. The diagnostic route returns the same code plus the complete created session.
301
+ - Prompt and queue-edit text share a 64 KiB semantic limit. Their 10 MiB RPC carrier leaves room for a 7 MiB raw-image batch after base64 and envelope overhead.
302
+ - Because Edge has no directory-flow provider, the upstream browser hides Delete on its sole Workspace and exposes it again whenever restoration remains possible.
303
+
304
+ ### Limits and request admission
305
+
306
+ | Surface | Limit |
307
+ | --- | --- |
308
+ | UTF-8 text file | 1 MiB |
309
+ | Shell command | 16 KiB |
310
+ | User message or queue-edit text | 64 KiB |
311
+ | Retained shell stdout + stderr | 64 KiB |
312
+ | Session-create JSON body | 8 KiB |
313
+ | Workspace-exec JSON body | 128 KiB |
314
+ | Message-bearing turn or queue-update RPC | 10 MiB |
315
+ | Images | PNG/JPEG; 4 per message; 3.5 MiB each; 7 MiB total; 40 million pixels; 2,000 px per side |
316
+
317
+ - Request bodies are consumed incrementally. Once a route limit is crossed, later chunks are drained without being retained and the route returns 413. File uploads also reject malformed UTF-8.
318
+ - File reads check VFS metadata, then collect the opened byte stream through the same 1 MiB cap. This closes the growth race between `stat()` and `readFile()` without retaining an unbounded value.
319
+ - Image admission fully decodes the declared raster format before writing.
320
+ - When combined shell output crosses its retention bound, the runtime requests interruption and stops accumulating later output. `cancelled` reflects an adapter-requested interruption; `timedOut` independently records deadline expiry.
321
+
322
+ ### Durability and concurrency
323
+
324
+ - Failed initial session persistence discards the retained unmaterialized batch before disposing the new upstream agent handle, so teardown cannot commit a session whose create request returned an error.
325
+ - A lazy blank session retains only its upstream header. The first canonical event removes that header in the same SQL transaction.
326
+ - Each turn owns one upstream handle and disposes it after streaming, so previously accessed conversations do not remain resident for the Durable Object lifetime.
327
+ - Deployment settings resolve before the process-local owner claim. One owner process rejects concurrent turns; cancellation uses the native agent path.
328
+ - A prompt is accepted and running state is published only after its inbox event crosses `SessionStore.flush()`. Streamed events cross the same durability barrier before WebSocket or SSE delivery.
329
+ - Queue edits, removals, and steering promotion use the synchronous live-inbox mutation as their acceptance point. `PersistenceCoordinator` owns later write-behind and retirement retries, so a later storage attempt cannot reverse an accepted mutation.
330
+ - Session rename uses the synchronous title append as its acceptance point for both active and cold sessions.
331
+ - Workspace global state and records keep upstream logical schemas under Edge-specific physical keys. DO transactions atomically pair record and registry-order changes; a process-local chain serializes Workspace mutations.
332
+ - Committed rename, delete, recreation, session reorder, attachment, and archive changes publish matching upstream Host frames. `workspace.list` restores the complete baseline after restart.
333
+ - Cold resume uses upstream interrupted-turn repair to close an open persisted tail. Canonical `session/end-seed` markers preserve lifecycle boundaries.
334
+
335
+ ### Bounded reads and canonical history
336
+
337
+ - Replay checks session absence separately, so persistence corruption and SQL failures do not collapse into 404. It reads one bounded SQL page and caps the encoded response.
338
+ - `PersistenceCoordinator.readValidatedPage()` validates identity, format, legacy shape, and event vocabulary on every page without adding Edge pagination to the public persistence service.
339
+ - If legacy normalization needs earlier messages, it rereads one prefix through the same byte-bounded loader and refuses the page when that prefix does not fit.
340
+ - Cold browser history selects its message boundary in SQL and loads only the resulting contiguous range under fixed event and stored-byte ceilings.
341
+ - Session listing queries one bounded canonical header/title summary page. Detail reads a canonical point summary or retained blank header; turn existence checks use point queries instead of projecting a complete log.
342
+ - Effective model, system prompt, adapter defaults, and tools remain standard `request/header` events. The request-scoped adapter applies validated deployment reasoning and output policies.
343
+ - Workspace paths must stay below `/workspace/`.