dsh-openwrite 0.2.21 → 0.2.23

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.
@@ -7,6 +7,16 @@ import { ConnectionRpcHandler } from "@deepseek-ai/dsh-client-connection";
7
7
  import { SessionId } from "@deepseek-ai/dsh-session";
8
8
  import { JobRegistry } from "@deepseek-ai/dsh-jobs";
9
9
  //#region src/debug-contract.d.ts
10
+ /**
11
+ * Dedicated debugger channel. It is served by this plugin directly on the Web
12
+ * carrier (see serveDogDebugRpc): Connection's generic channel registry mounts
13
+ * its HTTP route through the Connection plugin's own context, which only sees
14
+ * `webServer` when a bundle patch injects it into the shared connection row —
15
+ * and per-key patch semantics make any such patch clobber other bundles
16
+ * (OpenWrite issue #81), while the shared `/api` channel already belongs to
17
+ * the host's Typert gateway. The wire envelope mirrors Connection's, so the
18
+ * browser side keeps using its stock `connection.rpc.call`.
19
+ */
10
20
  declare const DOG_DEBUG_RPC_CHANNEL = "/dog-rpc";
11
21
  declare const DOG_DEBUG_SNAPSHOT_ENDPOINT = "snapshot";
12
22
  declare const DOG_RUNTIME_TRACE_ENDPOINT = "goal-runtime";
@@ -278,6 +278,16 @@ function defineTool(options) {
278
278
  }
279
279
  //#endregion
280
280
  //#region src/debug-contract.ts
281
+ /**
282
+ * Dedicated debugger channel. It is served by this plugin directly on the Web
283
+ * carrier (see serveDogDebugRpc): Connection's generic channel registry mounts
284
+ * its HTTP route through the Connection plugin's own context, which only sees
285
+ * `webServer` when a bundle patch injects it into the shared connection row —
286
+ * and per-key patch semantics make any such patch clobber other bundles
287
+ * (OpenWrite issue #81), while the shared `/api` channel already belongs to
288
+ * the host's Typert gateway. The wire envelope mirrors Connection's, so the
289
+ * browser side keeps using its stock `connection.rpc.call`.
290
+ */
281
291
  const DOG_DEBUG_RPC_CHANNEL = "/dog-rpc";
282
292
  const DOG_DEBUG_SNAPSHOT_ENDPOINT = "snapshot";
283
293
  const DOG_RUNTIME_TRACE_ENDPOINT = "goal-runtime";
@@ -357,6 +367,86 @@ function createDogDebugRpcHandler(repository) {
357
367
  }
358
368
  };
359
369
  }
370
+ const DOG_DEBUG_BODY_LIMIT = 8388608;
371
+ const ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/;
372
+ /**
373
+ * Serve the debugger RPC channel directly on the Web carrier. Connection's
374
+ * generic channel registry (`rpc.handle`) mounts its HTTP route through the
375
+ * Connection plugin's own context, which only sees `webServer` when a bundle
376
+ * patch injects it into the shared connection row — and per-key patch
377
+ * semantics make any such patch clobber other bundles (OpenWrite issue #81);
378
+ * the shared `/api` channel is already claimed by the host's Typert gateway.
379
+ * The wire envelope mirrors Connection's (`client-request`/`server-response`)
380
+ * so the browser side keeps using its stock `connection.rpc.call`; admission
381
+ * at the route reuses Connection's trusted-host fence and browser
382
+ * authentication, so the channel stays exactly as protected as a native one.
383
+ */
384
+ async function serveDogDebugRpc(req, res, handler) {
385
+ const respond = (status, body) => {
386
+ res.writeHead(status, { "content-type": "application/json" });
387
+ res.end(typeof body === "string" ? body : JSON.stringify(body));
388
+ };
389
+ const endpoint = dogDebugEndpointOf(req.url ?? "");
390
+ if (endpoint === void 0 || req.method !== "POST") {
391
+ respond(404, "not found");
392
+ return;
393
+ }
394
+ if (((req.headers["content-type"] ?? "").split(";", 1)[0] ?? "").trim().toLowerCase() !== "application/json") {
395
+ respond(415, "content type must be application/json");
396
+ return;
397
+ }
398
+ let raw = "";
399
+ let size = 0;
400
+ for await (const chunk of req) {
401
+ size += chunk.length;
402
+ if (size > DOG_DEBUG_BODY_LIMIT) {
403
+ respond(413, "request too large");
404
+ return;
405
+ }
406
+ raw += chunk;
407
+ }
408
+ let body;
409
+ try {
410
+ body = JSON.parse(raw);
411
+ } catch {
412
+ respond(400, "body is not JSON");
413
+ return;
414
+ }
415
+ const rpcId = typeof body?.rpcId === "string" ? body.rpcId : "invalid-request";
416
+ if (body?.type !== "client-request" || body.method !== endpoint) {
417
+ respond(200, {
418
+ type: "server-response",
419
+ rpcId,
420
+ result: {
421
+ ok: false,
422
+ error: {
423
+ code: "gateway/bad-request",
424
+ message: "invalid client-request message",
425
+ details: {}
426
+ }
427
+ }
428
+ });
429
+ return;
430
+ }
431
+ const controller = new AbortController();
432
+ req.on("close", () => controller.abort());
433
+ try {
434
+ respond(200, {
435
+ type: "server-response",
436
+ rpcId,
437
+ result: await handler(endpoint, body.payload, controller.signal)
438
+ });
439
+ } catch (error) {
440
+ respond(500, `handler failure: ${String(error)}`);
441
+ }
442
+ }
443
+ function dogDebugEndpointOf(url) {
444
+ const pathname = url.split("?", 1)[0] ?? "";
445
+ if (!pathname.startsWith(`/dog-rpc/`)) return void 0;
446
+ const endpoint = pathname.slice(9);
447
+ if (endpoint.split("/").some((segment) => segment === "" || segment === "." || segment === ".." || !ENDPOINT_SEGMENT.test(segment))) return void 0;
448
+ return endpoint;
449
+ }
360
450
  function compareRevisions(left, right) {
361
451
  if (left.current !== right.current) return left.current ? -1 : 1;
362
452
  const leftUpdated = left.runs[0]?.updatedAt ?? "";
@@ -1094,8 +1184,20 @@ async function apply(ctx, config) {
1094
1184
  if (event.data.reason?.kind !== "interrupted") return;
1095
1185
  captureInterruptedTurn(repository, session, event).catch(() => void 0);
1096
1186
  });
1097
- ctx.inject(["connection", "webServer"], (connectionCtx) => {
1098
- connectionCtx.effect(() => connectionCtx.connection.rpc.handle(DOG_DEBUG_RPC_CHANNEL, debugHandler), "dog: trusted debugger RPC");
1187
+ ctx.inject(["connection", "webServer"], (c) => {
1188
+ c.effect(() => c.webServer.register({
1189
+ kind: "prefix",
1190
+ path: DOG_DEBUG_RPC_CHANNEL,
1191
+ handler: async (req, res) => {
1192
+ const rejection = c.connection.requestRejection({ headers: req.headers });
1193
+ if (rejection !== void 0) {
1194
+ res.writeHead(rejection);
1195
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
1196
+ return;
1197
+ }
1198
+ await serveDogDebugRpc(req, res, debugHandler);
1199
+ }
1200
+ }), "dog: trusted debugger RPC");
1099
1201
  });
1100
1202
  }
1101
1203
  async function captureInterruptedTurn(repository, session, event) {
@@ -46,25 +46,26 @@
46
46
  "platform": "web"
47
47
  },
48
48
  "compatibility": {
49
- "dsh": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
49
+ "dsh": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
50
50
  "dshReleases": {
51
51
  "0.1.2-rc.1": "compatible",
52
52
  "0.1.5-alpha.1": "compatible",
53
- "0.2.0-rc.2": "compatible"
53
+ "0.2.0-rc.2": "compatible",
54
+ "0.2.1-rc.2": "compatible"
54
55
  }
55
56
  }
56
57
  },
57
58
  "peerDependencies": {
58
59
  "@deepseek-ai/cordis": "4.0.2 || ~4.0.4",
59
- "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
60
- "@deepseek-ai/dsh-client-ui-layout": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
61
- "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
62
- "@deepseek-ai/dsh-home-paths": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
63
- "@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
64
- "@deepseek-ai/dsh-tools": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2",
60
+ "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
61
+ "@deepseek-ai/dsh-client-ui-layout": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
62
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
63
+ "@deepseek-ai/dsh-home-paths": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
64
+ "@deepseek-ai/dsh-host-webserver": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
65
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2",
65
66
  "@deepseek-ai/schemastery": "3.18.2",
66
67
  "react": "^18.3.1",
67
- "@deepseek-ai/dsh-client-ui-workspace": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2"
68
+ "@deepseek-ai/dsh-client-ui-workspace": "0.1.2-rc.1 || 0.1.5-alpha.1 || 0.2.0-rc.2 || 0.2.1-rc.2"
68
69
  },
69
70
  "devDependencies": {
70
71
  "@deepseek-ai/cordis": "4.0.2",