@deepseek-insight/dsi 0.6.21 → 0.8.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 (110) hide show
  1. package/bin/dsi.mjs +16 -8
  2. package/bin/lib/dev-mode.mjs +6 -2
  3. package/build/client/_app/immutable/assets/0.c2dRYi3B.css +2 -0
  4. package/build/client/_app/immutable/assets/2.DBblYO_a.css +1 -0
  5. package/build/client/_app/immutable/chunks/C0CuJuob.js +1 -0
  6. package/build/client/_app/immutable/chunks/{DygY_IXG.js → CvvYpRG9.js} +1 -1
  7. package/build/client/_app/immutable/chunks/D-Rolhzs.js +1 -0
  8. package/build/client/_app/immutable/chunks/{BtiOY3LQ.js → DpSjzsDs.js} +2 -2
  9. package/build/client/_app/immutable/chunks/GMyoOYdi.js +6 -0
  10. package/build/client/_app/immutable/chunks/J_8-FdbR.js +1 -0
  11. package/build/client/_app/immutable/entry/{app.a14BvSUo.js → app.GsYnI6xk.js} +2 -2
  12. package/build/client/_app/immutable/entry/start.keNjq0dA.js +1 -0
  13. package/build/client/_app/immutable/nodes/{0.BE97Xr19.js → 0.BJVfjaPJ.js} +1 -1
  14. package/build/client/_app/immutable/nodes/{1.BKoLqRXA.js → 1.rhLd5ssJ.js} +1 -1
  15. package/build/client/_app/immutable/nodes/2.DdzHBwFT.js +340 -0
  16. package/build/client/_app/version.json +1 -1
  17. package/build/handler.js +4 -4
  18. package/build/index.js +4 -4
  19. package/build/server/chunks/chunks/{app-config.svelte.js-D7wkLUkK.js → app-config.svelte.js-BM4WbMFB.js} +5 -3
  20. package/build/server/chunks/chunks/{config.js-B0w1-uCA.js → config.js-vThvQeMn.js} +12 -3
  21. package/build/server/chunks/chunks/{db.js-DRhjT6xK.js → db.js-B8BUpxGM.js} +2 -2
  22. package/build/server/chunks/chunks/{db2.js-DUta2B-n.js → db2.js-CNe3I_g2.js} +2 -2
  23. package/build/server/chunks/chunks/{dsh-connection.js-DL5qjlq2.js → dsh-connection.js-xltcocWy.js} +181 -4
  24. package/build/server/chunks/chunks/{git-probe.js-6ARRicD2.js → git-probe.js-CUsTmhmV.js} +2 -2
  25. package/build/server/chunks/chunks/{insight-config.js-CA-iT3lB.js → insight-config.js-CaHHJpFK.js} +41 -5
  26. package/build/server/chunks/chunks/{internal2.js-Bcxjr5x2.js → internal2.js-BtL7AFjQ.js} +3 -3
  27. package/build/server/chunks/chunks/registry.js-BTiJ1Kaj.js +577 -0
  28. package/build/server/chunks/chunks/{registry.js-G1hzOje_.js → registry2.js-D3Pr1Rv9.js} +2 -2
  29. package/build/server/chunks/chunks/{settings-document.js-BRTO59Al.js → settings-document.js-Gw1puaik.js} +2 -2
  30. package/build/server/chunks/chunks/{state.js-ez_uUx9I.js → state.js-D3ob-Gqa.js} +3 -3
  31. package/build/server/chunks/chunks/{watcher.js-Dogr6xsl.js → watcher.js-DcT3cS4v.js} +4 -4
  32. package/build/server/chunks/entries/endpoints/api/a2a/_id_/{_server.ts.js-CpeC2QLb.js → _server.ts.js-19pFOjH8.js} +5 -5
  33. package/build/server/chunks/entries/endpoints/api/a2a/{_server.ts.js-BkLuKDZr.js → _server.ts.js-BcUn6axe.js} +7 -7
  34. package/build/server/chunks/entries/endpoints/api/a2a/register/{_server.ts.js-BOyrp9zt.js → _server.ts.js-oMpzLZoy.js} +7 -7
  35. package/build/server/chunks/entries/endpoints/api/browser/_id_/close/_server.ts.js-DC3qPMQS.js +32 -0
  36. package/build/server/chunks/entries/endpoints/api/browser/_id_/input/_server.ts.js-CHNcK7BH.js +78 -0
  37. package/build/server/chunks/entries/endpoints/api/browser/_id_/mcp/_server.ts.js-CYkOJL2O.js +50 -0
  38. package/build/server/chunks/entries/endpoints/api/browser/_id_/navigate/_server.ts.js-DHTDi8KR.js +62 -0
  39. package/build/server/chunks/entries/endpoints/api/browser/_id_/page/_server.ts.js-Y5khEC2l.js +33 -0
  40. package/build/server/chunks/entries/endpoints/api/browser/_id_/stream/_server.ts.js-BmjgfZD2.js +74 -0
  41. package/build/server/chunks/entries/endpoints/api/browser/_id_/viewport/_server.ts.js-IxS-goi1.js +42 -0
  42. package/build/server/chunks/entries/endpoints/api/browser/_server.ts.js-6STmJ7FD.js +37 -0
  43. package/build/server/chunks/entries/endpoints/api/config/{_server.ts.js-BG78X__J.js → _server.ts.js-BVyx8gPc.js} +6 -5
  44. package/build/server/chunks/entries/endpoints/api/dsh/directory/{_server.ts.js-Cae4x1c7.js → _server.ts.js-CNJbv6CC.js} +5 -5
  45. package/build/server/chunks/entries/endpoints/api/dsh/jobs/{_server.ts.js-hGmoSsDv.js → _server.ts.js-CDWmXDJX.js} +37 -5
  46. package/build/server/chunks/entries/endpoints/api/dsh/jobs/follow/_server.ts.js-BDXQcQSL.js +56 -0
  47. package/build/server/chunks/entries/endpoints/api/dsh/pick-directory/{_server.ts.js-BLb8o1cY.js → _server.ts.js-CgedtzOg.js} +5 -5
  48. package/build/server/chunks/entries/endpoints/api/dsh/presets/{_server.ts.js-BFeDKrPz.js → _server.ts.js-ut2pK15B.js} +5 -5
  49. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/attachment/{_server.ts.js-DmSTTfI5.js → _server.ts.js-Ct2W7g4c.js} +5 -5
  50. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/cancel/{_server.ts.js-Cus9XrC9.js → _server.ts.js-Rke3V_qh.js} +5 -5
  51. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/catalog/{_server.ts.js-DcrNU0_y.js → _server.ts.js-BYHm5WBA.js} +5 -5
  52. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/changes/{_server.ts.js-CpuEcp6p.js → _server.ts.js-B_bEUKkt.js} +5 -5
  53. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/command/{_server.ts.js-CU9o8Tho.js → _server.ts.js-D78fGUQk.js} +5 -5
  54. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/events/{_server.ts.js-LWw-YpVI.js → _server.ts.js-CgB_c7zG.js} +5 -5
  55. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/fork/{_server.ts.js-BMMZlvr7.js → _server.ts.js-dVaW22_K.js} +5 -5
  56. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/goal/{_server.ts.js-DZ6qFrfm.js → _server.ts.js-Dl8SBXdH.js} +5 -5
  57. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/history/{_server.ts.js-CYt4aYoc.js → _server.ts.js-BKDiwMIE.js} +5 -5
  58. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/models/{_server.ts.js-CPZuvN-_.js → _server.ts.js-D3FY2Wxt.js} +5 -5
  59. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/path/{_server.ts.js-_SIiC8sn.js → _server.ts.js-BWuIEdjl.js} +4 -4
  60. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/permission/{_server.ts.js-Dcfp7gSm.js → _server.ts.js-CeiQFOGP.js} +5 -5
  61. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/prompt/{_server.ts.js-CmxqUwLT.js → _server.ts.js-lMofZOns.js} +5 -5
  62. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/rename/{_server.ts.js-DCNKXRwy.js → _server.ts.js-CNalVHrK.js} +5 -5
  63. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/respond/{_server.ts.js-AURyR2ll.js → _server.ts.js-I2s8qvcV.js} +5 -5
  64. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/select-model/{_server.ts.js-BVF36vTO.js → _server.ts.js-DTKELD7v.js} +5 -5
  65. package/build/server/chunks/entries/endpoints/api/dsh/sessions/{_server.ts.js-LX3JZbRr.js → _server.ts.js-BXSD__Gg.js} +5 -5
  66. package/build/server/chunks/entries/endpoints/api/dsh/workspace-file/{_server.ts.js-BJ0d199o.js → _server.ts.js-C9IZl2Qh.js} +5 -5
  67. package/build/server/chunks/entries/endpoints/api/dsh/workspace-tree/{_server.ts.js-BDy9jQXv.js → _server.ts.js-BRXu3UtL.js} +5 -5
  68. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/{_server.ts.js-DoTpSXIc.js → _server.ts.js-DmnpiaD2.js} +5 -5
  69. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/delete/{_server.ts.js-BnfIobo7.js → _server.ts.js-C5qCdsdm.js} +5 -5
  70. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/rename/{_server.ts.js-KRyjIODB.js → _server.ts.js-BVUn49yf.js} +5 -5
  71. package/build/server/chunks/entries/endpoints/api/prompts/_id_/{_server.ts.js-bRLZ5TWI.js → _server.ts.js-DuCOsoF0.js} +5 -5
  72. package/build/server/chunks/entries/endpoints/api/prompts/{_server.ts.js-ChzWZoFk.js → _server.ts.js-2mcRY7IP.js} +5 -5
  73. package/build/server/chunks/entries/endpoints/api/prompts/use/{_server.ts.js-DdH5QmPS.js → _server.ts.js-DZjaHZxU.js} +5 -5
  74. package/build/server/chunks/entries/endpoints/api/settings/{_server.ts.js-DwGQj520.js → _server.ts.js-DTVmsohJ.js} +5 -5
  75. package/build/server/chunks/entries/endpoints/api/terminal/_id_/output/{_server.ts.js-m6Cug5hN.js → _server.ts.js-BNBxebgG.js} +3 -3
  76. package/build/server/chunks/entries/endpoints/api/terminal/_id_/reattach/{_server.ts.js-wSsMKLiw.js → _server.ts.js-DDRJBGIr.js} +3 -3
  77. package/build/server/chunks/entries/endpoints/api/terminal/_id_/send/{_server.ts.js-CTc5xP72.js → _server.ts.js-CA0vc_Cm.js} +3 -3
  78. package/build/server/chunks/entries/endpoints/api/terminal/_id_/stream/{_server.ts.js-C8PLMU0Y.js → _server.ts.js-D6L8Gd_f.js} +3 -3
  79. package/build/server/chunks/entries/endpoints/api/terminal/{_server.ts.js-CwXC6Stk.js → _server.ts.js-DZXTArj7.js} +6 -6
  80. package/build/server/chunks/entries/endpoints/api/workspace/file-write/{_server.ts.js-CWWi0aa8.js → _server.ts.js-LSc5EKH7.js} +6 -6
  81. package/build/server/chunks/entries/endpoints/api/workspace/git-events/{_server.ts.js-Dc0g7HPl.js → _server.ts.js-DNjiScBe.js} +6 -6
  82. package/build/server/chunks/entries/endpoints/api/workspace/git-file-head/{_server.ts.js-CvPoP9yH.js → _server.ts.js-CHMbLoSn.js} +6 -6
  83. package/build/server/chunks/entries/endpoints/api/workspace/git-map/{_server.ts.js-f5Gk_lb6.js → _server.ts.js-BgM4JEAm.js} +6 -6
  84. package/build/server/chunks/entries/endpoints/api/workspace/git-status/{_server.ts.js-BMkKlCgS.js → _server.ts.js-BH0cLS8o.js} +6 -6
  85. package/build/server/chunks/entries/endpoints/api/workspace/write-gate/{_server.ts.js-DUL-2q1R.js → _server.ts.js-BzlV3cVT.js} +6 -6
  86. package/build/server/chunks/entries/{hooks.server.js-BSnc9oHw.js → hooks.server.js-B-e4_ekh.js} +3 -3
  87. package/build/server/chunks/entries/pages/{_error.svelte.js-DDQEX5e3.js → _error.svelte.js-9cKDDMkE.js} +4 -4
  88. package/build/server/chunks/entries/pages/{_page.server.ts.js-DFRnK-I2.js → _page.server.ts.js-DttLUwdf.js} +2 -2
  89. package/build/server/chunks/entries/pages/{_page.svelte.js-DrMNc6ih.js → _page.svelte.js-hjZYZCe1.js} +10507 -9470
  90. package/build/server/chunks/{handler-Cqw6zPgE.js → handler-B_azsHTz.js} +3 -3
  91. package/build/server/chunks/{index.js-CZ6hvqti.js → index.js-3VY3QWe-.js} +2 -2
  92. package/build/server/chunks/{manifest.js-SFcYF8ua.js → manifest.js-hHcXduh_.js} +113 -50
  93. package/build/server/chunks/nodes/{0.js-BqYWZcQy.js → 0.js-B_LVBEe8.js} +3 -3
  94. package/build/server/chunks/nodes/1.js-BRybp38b.js +9 -0
  95. package/build/server/chunks/nodes/{2.js-CnybOb74.js → 2.js-bVRsbAhW.js} +9 -9
  96. package/package.json +4 -1
  97. package/skills/dsi-pattern-thin/SKILL.md +116 -0
  98. package/skills/dsi-pattern-thin/references/case-study-2026-10-21-conversation-feed-host.md +64 -0
  99. package/skills/dsi-pattern-thin/references/case-study-2026-10-21-tool-run-row-host.md +66 -0
  100. package/template/.dsi/prompts.sqlite-shm +0 -0
  101. package/template/.dsi/prompts.sqlite-wal +0 -0
  102. package/build/client/_app/immutable/assets/0.BQzh2ktU.css +0 -2
  103. package/build/client/_app/immutable/assets/2.ByYVysCj.css +0 -1
  104. package/build/client/_app/immutable/chunks/6lkFuaQJ.js +0 -1
  105. package/build/client/_app/immutable/chunks/Ct20Yiq1.js +0 -1
  106. package/build/client/_app/immutable/chunks/D6mTYwDH.js +0 -6
  107. package/build/client/_app/immutable/chunks/DlfWhx6l.js +0 -1
  108. package/build/client/_app/immutable/entry/start.DwHZM5ia.js +0 -1
  109. package/build/client/_app/immutable/nodes/2.CSRBDmmw.js +0 -337
  110. package/build/server/chunks/nodes/1.js-CoGgQITW.js +0 -9
@@ -0,0 +1,577 @@
1
+ import { d as readBrowserConfig } from './insight-config.js-CaHHJpFK.js';
2
+ import { mkdirSync, rmSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { spawn, spawnSync } from 'node:child_process';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { chromium } from 'playwright-core';
7
+
8
+ //#region src/lib/server/browser/relay.ts
9
+ var RelayClosedError = class extends Error {
10
+ constructor() {
11
+ super("relay is closed");
12
+ this.name = "RelayClosedError";
13
+ }
14
+ };
15
+ var BrowserRelay = class {
16
+ quality;
17
+ cdp;
18
+ subscribers = /* @__PURE__ */ new Set();
19
+ seq = 0;
20
+ latest = null;
21
+ chain = Promise.resolve();
22
+ started = false;
23
+ /** JPEG quality 0..100 — from BrowserConfig.screencastQuality
24
+ * (default 90; yaml `browser.screencast:` override, amendment 3). */
25
+ constructor(cdp, quality = 90) {
26
+ this.quality = quality;
27
+ this.cdp = cdp;
28
+ }
29
+ async start() {
30
+ if (!this.cdp || this.started) throw new RelayClosedError();
31
+ this.cdp.on("Page.screencastFrame", (ev) => {
32
+ this.seq += 1;
33
+ this.latest = {
34
+ seq: this.seq,
35
+ jpeg: ev.data,
36
+ metadata: ev.metadata
37
+ };
38
+ for (const sub of this.subscribers) sub(this.latest);
39
+ this.cdp?.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => void 0);
40
+ });
41
+ await this.cdp.send("Page.startScreencast", {
42
+ format: "jpeg",
43
+ quality: this.quality,
44
+ everyNthFrame: 1
45
+ });
46
+ this.started = true;
47
+ }
48
+ latestFrame() {
49
+ if (!this.cdp) throw new RelayClosedError();
50
+ return this.latest;
51
+ }
52
+ subscribe(fn) {
53
+ if (!this.cdp) throw new RelayClosedError();
54
+ this.subscribers.add(fn);
55
+ return () => this.subscribers.delete(fn);
56
+ }
57
+ /** Serialized dispatch — every input waits for the previous one. */
58
+ dispatch(input) {
59
+ if (!this.cdp) return Promise.reject(new RelayClosedError());
60
+ const run = async () => {
61
+ if (!this.cdp) throw new RelayClosedError();
62
+ if (input.kind === "mouse") await this.cdp.send("Input.dispatchMouseEvent", {
63
+ type: input.type,
64
+ x: input.x,
65
+ y: input.y,
66
+ button: input.button ?? "none",
67
+ clickCount: input.clickCount,
68
+ deltaX: input.deltaX,
69
+ deltaY: input.deltaY,
70
+ pointerType: "mouse"
71
+ });
72
+ else if (input.kind === "key") await this.cdp.send("Input.dispatchKeyEvent", {
73
+ type: input.type,
74
+ key: input.key,
75
+ code: input.code,
76
+ windowsVirtualKeyCode: input.windowsVirtualKeyCode,
77
+ text: input.text
78
+ });
79
+ else await this.cdp.send("Input.insertText", { text: input.text });
80
+ };
81
+ const next = this.chain.then(run, run);
82
+ this.chain = next.catch(() => void 0);
83
+ return next;
84
+ }
85
+ stop() {
86
+ const cdp = this.cdp;
87
+ this.cdp = null;
88
+ this.subscribers.clear();
89
+ this.latest = null;
90
+ if (cdp && this.started) cdp.send("Page.stopScreencast").catch(() => void 0);
91
+ }
92
+ };
93
+ Number(process.env.DSI_MCP_PORT ?? 9515);
94
+ /** The reaper's ONLY test — pure: a session is reaper-eligible when nobody
95
+ * watches its stream AND its last MCP tool traffic is older than idleMs.
96
+ * An active agent (traffic younger than idleMs) is NEVER eligible (D2). */
97
+ function reaperEligible(e, idleMs, now) {
98
+ if (e.streamSubscribers > 0) return false;
99
+ return now - e.lastMcpActivity >= idleMs;
100
+ }
101
+ var BrowserRegistryRefusal = class extends Error {
102
+ code;
103
+ constructor(code, message) {
104
+ super(message);
105
+ this.name = "BrowserRegistryRefusal";
106
+ this.code = code;
107
+ }
108
+ };
109
+ function state() {
110
+ const p = process;
111
+ if (!p.__DSI_BROWSER_STATE__) {
112
+ p.__DSI_BROWSER_STATE__ = {
113
+ entries: /* @__PURE__ */ new Map(),
114
+ closed: /* @__PURE__ */ new Set()
115
+ };
116
+ sweepOrphanProfiles(new Set([...p.__DSI_BROWSER_STATE__.entries.keys()].map((id) => id.slice(0, 8))));
117
+ }
118
+ return p.__DSI_BROWSER_STATE__;
119
+ }
120
+ /** Boot-time sweep of profile dirs left by sessions that died without a
121
+ * close (crash, kill -9, reaper miss). keep = prefixes of LIVE entries —
122
+ * a module hot reload re-runs state() init against a process-global map
123
+ * that may hold live sessions, and this sweep must never delete one. */
124
+ function sweepOrphanProfiles(keep) {
125
+ try {
126
+ const root = readBrowserConfig().profileRoot;
127
+ for (const name of readdirSync(root)) if (!keep.has(name)) rmSync(join(root, name), {
128
+ recursive: true,
129
+ force: true
130
+ });
131
+ } catch {}
132
+ }
133
+ /** The in-process brain (D7): createConnection attached to the SAME headed
134
+ * browser via cdpEndpoint, served over a SIBLING http listener (SSE +
135
+ * messages endpoints) so a streamable-http MCP client (the DSH agent) can
136
+ * reach it without a child process. Port = session port + 100 (9501...):
137
+ * unique per session, out of the debug-port range. */
138
+ async function startInProcessMcp(browserPort, brainPort, onTraffic) {
139
+ const [{ createConnection }, { StreamableHTTPServerTransport }, http] = await Promise.all([
140
+ import('@playwright/mcp'),
141
+ import('@modelcontextprotocol/sdk/server/streamableHttp.js'),
142
+ import('node:http')
143
+ ]);
144
+ const connection = await createConnection({ browser: { cdpEndpoint: "http://127.0.0.1:" + browserPort } });
145
+ const transport = new StreamableHTTPServerTransport({
146
+ sessionIdGenerator: void 0,
147
+ enableJsonResponse: true
148
+ });
149
+ await connection.connect(transport);
150
+ const server = http.createServer(async (req, res) => {
151
+ if (new URL(req.url ?? "/", "http://127.0.0.1").pathname !== "/sse") {
152
+ res.statusCode = 404;
153
+ res.end();
154
+ return;
155
+ }
156
+ onTraffic?.();
157
+ try {
158
+ await transport.handleRequest(req, res);
159
+ } catch {
160
+ if (!res.writableEnded) res.end();
161
+ }
162
+ });
163
+ let port;
164
+ try {
165
+ port = await listen(server, brainPort);
166
+ } catch {
167
+ let lastErr = null;
168
+ port = 0;
169
+ for (let p = brainPort + 1; p <= brainPort + 60; p++) try {
170
+ port = await listen(server, p);
171
+ break;
172
+ } catch (e) {
173
+ lastErr = e;
174
+ }
175
+ if (port === 0) throw lastErr ?? /* @__PURE__ */ new Error("no free brain port in " + brainPort + "+ range");
176
+ }
177
+ return {
178
+ connection,
179
+ httpServer: server,
180
+ port
181
+ };
182
+ }
183
+ var listen = (server, port) => new Promise((res, rej) => {
184
+ const onError = () => {
185
+ server.off("listening", onListen);
186
+ rej(/* @__PURE__ */ new Error("EADDRINUSE " + port));
187
+ };
188
+ const onListen = () => {
189
+ server.off("error", onError);
190
+ const a = server.address();
191
+ res(typeof a === "object" && a ? a.port : port);
192
+ };
193
+ server.once("error", onError);
194
+ server.once("listening", onListen);
195
+ server.listen(port, "127.0.0.1");
196
+ });
197
+ function pidOfPort(port) {
198
+ const p = spawnSync("lsof", [
199
+ "-ti",
200
+ "tcp:" + port,
201
+ "-sTCP:LISTEN"
202
+ ], { stdio: "pipe" });
203
+ return p.status === 0 ? Number(p.stdout.toString().trim().split("\n")[0]) : null;
204
+ }
205
+ function portFree(port) {
206
+ return spawnSync("lsof", [
207
+ "-ti",
208
+ "tcp:" + port,
209
+ "-sTCP:LISTEN"
210
+ ], { stdio: "pipe" }).status !== 0;
211
+ }
212
+ function freePort(preferred) {
213
+ if (preferred && portFree(preferred)) return preferred;
214
+ for (let p = 9400; p < 9500; p++) if (portFree(p)) return p;
215
+ throw new Error("no free debug port in 9400..9499");
216
+ }
217
+ var BrowserRegistry = class {
218
+ list() {
219
+ return [...state().entries.values()].map((e) => ({
220
+ id: e.id,
221
+ port: e.port,
222
+ pid: e.pid,
223
+ profileDir: e.profileDir,
224
+ createdAt: e.createdAt,
225
+ mcp: e.mcpChild ? "child" : e.mcpInProcess ? "in-process" : "none",
226
+ mcpHealth: e.mcpHealth,
227
+ mcpError: e.mcpError
228
+ }));
229
+ }
230
+ peek(id) {
231
+ const e = state().entries.get(id);
232
+ if (!e) throw new BrowserRegistryRefusal("NO_SESSION", "no such browser session");
233
+ return e;
234
+ }
235
+ /** The brain's live SSE URL — the ACTUAL bound port (stable 9515 in the
236
+ * common case, session-relative in the rare fallback). */
237
+ mcpBrainUrl(id) {
238
+ const e = this.peek(id);
239
+ return "http://127.0.0.1:" + (e.mcpInProcess?.port ?? e.brainPort) + "/sse";
240
+ }
241
+ /** True when the id was closed (tombstoned) — routes use it to answer
242
+ * 409 CLOSED instead of a misleading 404. */
243
+ isClosed(id) {
244
+ return state().closed.has(id);
245
+ }
246
+ /** Re-bind lookup (ADR-0030 D1): does this persisted id still own a live
247
+ * session? Pure read — never creates, never throws. */
248
+ findLive(id) {
249
+ if (!id) return false;
250
+ return state().entries.has(id) && !state().closed.has(id);
251
+ }
252
+ /** Reaper inputs — the routes call these so the registry never imports a
253
+ * route (the wiring-point rule stays one-directional). */
254
+ noteStreamOpen(id) {
255
+ const e = state().entries.get(id);
256
+ if (e) e.streamSubscribers += 1;
257
+ }
258
+ noteStreamClose(id) {
259
+ const e = state().entries.get(id);
260
+ if (e) e.streamSubscribers = Math.max(0, e.streamSubscribers - 1);
261
+ }
262
+ noteMcpTraffic(id) {
263
+ const e = state().entries.get(id);
264
+ if (e) e.lastMcpActivity = Date.now();
265
+ }
266
+ /** The idle reaper (ADR-0030 D2): a SAFETY NET, never the owner. A
267
+ * session with zero stream subscribers and no MCP tool traffic for
268
+ * `browser.idleMs` is closed through the SAME idempotent close(id) the
269
+ * panel-close click uses. Headless-default makes this a correctness
270
+ * requirement: an invisible browser must not outlive its last driver. */
271
+ ensureReaper(idleMs) {
272
+ const s = state();
273
+ if (s.reaperTimer) return;
274
+ s.reaperTimer = setInterval(() => {
275
+ const now = Date.now();
276
+ for (const e of state().entries.values()) {
277
+ if (!reaperEligible(e, idleMs, now)) continue;
278
+ this.close(e.id).catch(() => void 0);
279
+ }
280
+ }, Math.min(idleMs / 2, 3e4));
281
+ s.reaperTimer.unref?.();
282
+ }
283
+ relay(id) {
284
+ return this.peek(id).relay;
285
+ }
286
+ async open(options = {}) {
287
+ const cfg = readBrowserConfig();
288
+ const id = randomUUID();
289
+ const port = freePort(options.port);
290
+ const profileDir = join(cfg.profileRoot, id.slice(0, 8));
291
+ mkdirSync(profileDir, { recursive: true });
292
+ const ignoreDefaultArgs = ["--enable-automation", "--mute-audio"];
293
+ const launch = (useChrome) => chromium.launchPersistentContext(profileDir, {
294
+ ...useChrome ? { channel: "chrome" } : {},
295
+ headless: options.headless ?? cfg.headless,
296
+ ignoreDefaultArgs,
297
+ args: [
298
+ "--remote-debugging-port=" + port,
299
+ "--no-first-run",
300
+ "--disable-backgrounding-occluded-windows",
301
+ "--disable-renderer-backgrounding",
302
+ "--disable-background-timer-throttling",
303
+ "--disable-blink-features=AutomationControlled",
304
+ "--autoplay-policy=no-user-gesture-required",
305
+ "--window-position=-32000,-32000"
306
+ ],
307
+ viewport: null
308
+ });
309
+ let context;
310
+ try {
311
+ context = await launch(true);
312
+ } catch (chromeErr) {
313
+ context = await launch(false);
314
+ }
315
+ context.browser();
316
+ const pid = pidOfPort(port);
317
+ const cdp = await context.newCDPSession(context.pages()[0] ?? await context.newPage());
318
+ const relay = new BrowserRelay(cdp, cfg.screencastQuality);
319
+ await relay.start();
320
+ const start = options.url ?? "data:text/html,<title>Glass Browser</title><body style=\"font-family:system-ui;color:gray;display:grid;place-items:center;height:100vh;margin:0\">Glass Browser — waiting for the agent</body>";
321
+ await cdp.send("Page.enable").catch(() => void 0);
322
+ await cdp.send("Page.navigate", { url: start }).catch(() => void 0);
323
+ await new Promise((r) => setTimeout(r, 600));
324
+ const brainPort = Number(process.env.DSI_MCP_PORT ?? cfg.brainPort);
325
+ if (cfg.enabled) this.ensureReaper(cfg.idleMs);
326
+ const entry = {
327
+ id,
328
+ context,
329
+ cdp,
330
+ relay,
331
+ port,
332
+ pid,
333
+ profileDir,
334
+ brainPort,
335
+ streamSubscribers: 0,
336
+ lastMcpActivity: Date.now(),
337
+ mcpChild: null,
338
+ mcpInProcess: null,
339
+ mcpHealth: "up",
340
+ mcpError: null,
341
+ closed: false,
342
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
343
+ };
344
+ state().entries.set(id, entry);
345
+ if (options.spawnMcp ?? true) {
346
+ if (process.env.DSI_MCP_IN_PROCESS === "1") try {
347
+ entry.mcpInProcess = await startInProcessMcp(entry.port, brainPort, () => {
348
+ entry.lastMcpActivity = Date.now();
349
+ });
350
+ } catch (e) {
351
+ entry.mcpHealth = "down";
352
+ entry.mcpError = e.message;
353
+ }
354
+ else this.spawnMcpChild(entry, cfg.mcpPin);
355
+ }
356
+ return {
357
+ id,
358
+ port,
359
+ pid,
360
+ profileDir,
361
+ createdAt: entry.createdAt,
362
+ mcp: entry.mcpChild ? "child" : entry.mcpInProcess ? "in-process" : "none",
363
+ mcpHealth: entry.mcpHealth,
364
+ mcpError: entry.mcpError
365
+ };
366
+ }
367
+ /** Wave 3 (3.1) owns the real arg matrix; Wave 1 wires the spawn so close
368
+ * ordering (mcp child → cdp → browser) is pinned from day one. */
369
+ spawnMcpChild(entry, mcpPin) {
370
+ const child = spawn("npx", [
371
+ "@playwright/mcp@" + mcpPin,
372
+ "--cdp-endpoint",
373
+ "http://127.0.0.1:" + entry.port
374
+ ], {
375
+ stdio: "ignore",
376
+ detached: false
377
+ });
378
+ entry.mcpChild = child;
379
+ child.on("error", (e) => {
380
+ entry.mcpHealth = "down";
381
+ entry.mcpError = String(e);
382
+ });
383
+ child.on("exit", (code) => {
384
+ if (entry.mcpChild === child) {
385
+ entry.mcpHealth = "down";
386
+ entry.mcpError = "mcp child exited code " + code;
387
+ }
388
+ });
389
+ }
390
+ /** D8 owner-release. Idempotent: closing a closed id resolves cleanly. */
391
+ async close(id) {
392
+ const entry = state().entries.get(id);
393
+ if (!entry) return {
394
+ ok: true,
395
+ alreadyClosed: true
396
+ };
397
+ if (entry.closed) {
398
+ state().entries.delete(id);
399
+ return {
400
+ ok: true,
401
+ alreadyClosed: true
402
+ };
403
+ }
404
+ entry.closed = true;
405
+ state().closed.add(id);
406
+ if (state().closed.size > 256) state().closed.delete(state().closed.values().next().value);
407
+ const errs = [];
408
+ if (entry.mcpInProcess) try {
409
+ const brain = entry.mcpInProcess;
410
+ await new Promise((res) => {
411
+ brain.httpServer?.closeAllConnections?.();
412
+ brain.httpServer?.close?.(() => res());
413
+ setTimeout(res, 2e3);
414
+ });
415
+ await brain.connection?.close?.();
416
+ entry.mcpInProcess = null;
417
+ } catch (e) {
418
+ errs.push(e);
419
+ }
420
+ if (entry.mcpChild?.pid) try {
421
+ entry.mcpChild.kill("SIGTERM");
422
+ } catch (e) {
423
+ errs.push(e);
424
+ }
425
+ try {
426
+ entry.relay.stop();
427
+ } catch (e) {
428
+ errs.push(e);
429
+ }
430
+ try {
431
+ await entry.context.close();
432
+ } catch (e) {
433
+ errs.push(e);
434
+ }
435
+ try {
436
+ rmSync(entry.profileDir, {
437
+ recursive: true,
438
+ force: true
439
+ });
440
+ } catch (e) {
441
+ errs.push(e);
442
+ }
443
+ state().entries.delete(id);
444
+ return {
445
+ ok: errs.length === 0,
446
+ alreadyClosed: false
447
+ };
448
+ }
449
+ /** Wait for the page's next load event, capped — the navigate idiom
450
+ * (a send races the context teardown otherwise) shared by reload and
451
+ * history travel (ADR-0031 D3/D4). */
452
+ async waitLoad(e, capMs = 3e3) {
453
+ await new Promise((res) => {
454
+ const on = (p) => {
455
+ e.cdp.off("Page.loadEventFired", on);
456
+ res();
457
+ };
458
+ e.cdp.on("Page.loadEventFired", on);
459
+ setTimeout(res, capMs);
460
+ });
461
+ }
462
+ /** Navigate the session's active page — public because causing page
463
+ * damage is the only reliable way to make screencast emit a frame. */
464
+ async navigate(id, url, opts) {
465
+ const e = this.peek(id);
466
+ await e.cdp.send("Page.enable").catch(() => void 0);
467
+ if (opts?.waitLoad) {
468
+ const loaded = this.waitLoad(e);
469
+ await e.cdp.send("Page.navigate", { url });
470
+ await loaded;
471
+ return;
472
+ }
473
+ await e.cdp.send("Page.navigate", { url });
474
+ }
475
+ /** Match the page viewport to the panel's glass box (ADR-0031
476
+ * amendment 2): the screencast frame then equals the panel size, so
477
+ * the mirror shows content at native scale instead of zooming a
478
+ * fixed default viewport. CDP-side clamps keep hostile sizes out. */
479
+ async setViewport(id, width, height) {
480
+ const w = Math.max(200, Math.min(8192, Math.round(width)));
481
+ const h = Math.max(120, Math.min(8192, Math.round(height)));
482
+ const e = this.peek(id);
483
+ await e.cdp.send("Page.enable").catch(() => void 0);
484
+ await e.cdp.send("Emulation.setDeviceMetricsOverride", {
485
+ width: w,
486
+ height: h,
487
+ deviceScaleFactor: 0,
488
+ mobile: false
489
+ });
490
+ }
491
+ /** The history position (ADR-0031): every proxy-verb answer carries it
492
+ * so the header's back/forward buttons stay honest at the ends. */
493
+ async navInfo(id) {
494
+ const e = this.peek(id);
495
+ await e.cdp.send("Page.enable").catch(() => void 0);
496
+ const hist = await e.cdp.send("Page.getNavigationHistory", {});
497
+ return {
498
+ currentIndex: hist.currentIndex,
499
+ entryCount: hist.entries.length
500
+ };
501
+ }
502
+ /** Reload the session's active page (ADR-0031 D4): Page.reload keeps
503
+ * the history entry, unlike re-navigating the current URL. */
504
+ async reload(id) {
505
+ const e = this.peek(id);
506
+ await e.cdp.send("Page.enable").catch(() => void 0);
507
+ const loaded = this.waitLoad(e);
508
+ await e.cdp.send("Page.reload", {});
509
+ await loaded;
510
+ return this.pageInfo(id);
511
+ }
512
+ /** Travel the browser's OWN back/forward list (ADR-0031 D3):
513
+ * Page.getNavigationHistory picks the entry, Page.navigateToHistoryEntry
514
+ * is the browser's travel verb — never Runtime.evaluate('history.back()'),
515
+ * which races the teardown and lies about the browser-level list.
516
+ * Bounds refusals keep the header buttons honest at the list ends. */
517
+ async pageHistory(id, dir) {
518
+ const e = this.peek(id);
519
+ await e.cdp.send("Page.enable").catch(() => void 0);
520
+ const hist = await e.cdp.send("Page.getNavigationHistory", {});
521
+ const target = dir === "back" ? hist.currentIndex - 1 : hist.currentIndex + 1;
522
+ if (target < 0 || target >= hist.entries.length) throw new BrowserRegistryRefusal("CLOSED", "HISTORY_END — cannot go " + dir + " from index " + hist.currentIndex + " of " + hist.entries.length);
523
+ const loaded = this.waitLoad(e);
524
+ await e.cdp.send("Page.navigateToHistoryEntry", { entryId: hist.entries[target].id });
525
+ await loaded;
526
+ return {
527
+ ...await this.pageInfo(id),
528
+ currentIndex: target,
529
+ entryCount: hist.entries.length
530
+ };
531
+ }
532
+ /** Race an evaluate against a cap — a hung evaluate must never wedge a
533
+ * route (the navigate-hang finding, spec W2 2.3 debugging). */
534
+ async evaluateCapped(cdp, expr, capMs = 3e3) {
535
+ let timer;
536
+ const cap = new Promise((res) => {
537
+ timer = setTimeout(() => res(null), capMs);
538
+ });
539
+ try {
540
+ const res = await Promise.race([cdp.send("Runtime.evaluate", {
541
+ expression: expr,
542
+ returnByValue: true
543
+ }), cap]);
544
+ return res ? String(res.result?.value ?? "") : null;
545
+ } finally {
546
+ if (timer) clearTimeout(timer);
547
+ }
548
+ }
549
+ /** The active page's url + title + inner viewport — the status strip's
550
+ * location read, the e2e's DOM-truth channel (the glass proves clicks
551
+ * by effects the page itself reports), and the viewport-follow witness
552
+ * (innerWidth/innerHeight prove the emulation landed, amend 2). */
553
+ async pageInfo(id) {
554
+ const e = this.peek(id);
555
+ const raw = await this.evaluateCapped(e.cdp, "JSON.stringify({ url: location.href, title: document.title, dpr: window.devicePixelRatio, innerWidth: window.innerWidth, innerHeight: window.innerHeight, playerError: (document.querySelector(\".ytp-error-content-wrap-reason, .ytp-error-content, [role=alert]\")?.textContent || \"\").trim() || null, mediaErrorCode: (document.querySelector(\"video\")?.error?.code) ?? null })", 3e3);
556
+ if (raw === null) return {
557
+ url: "(busy)",
558
+ title: "(busy)",
559
+ dpr: 1,
560
+ innerWidth: 0,
561
+ innerHeight: 0,
562
+ playerError: null,
563
+ mediaErrorCode: null
564
+ };
565
+ return JSON.parse(raw);
566
+ }
567
+ dispatch(id, input) {
568
+ if (!state().entries.has(id) && state().closed.has(id)) throw new BrowserRegistryRefusal("CLOSED", "session was closed");
569
+ const e = this.peek(id);
570
+ if (e.closed) throw new BrowserRegistryRefusal("CLOSED", "session is closing");
571
+ return e.relay.dispatch(input);
572
+ }
573
+ };
574
+ var browserRegistry = new BrowserRegistry();
575
+
576
+ export { BrowserRegistryRefusal as B, browserRegistry as b };
577
+ //# sourceMappingURL=registry.js-BTiJ1Kaj.js.map
@@ -1,8 +1,8 @@
1
1
  import { join } from 'node:path';
2
2
  import { tmpdir } from 'node:os';
3
- import { mkdir, rm, open } from 'node:fs/promises';
4
3
  import { spawnSync } from 'node:child_process';
5
4
  import { randomUUID } from 'node:crypto';
5
+ import { mkdir, rm, open } from 'node:fs/promises';
6
6
  import { EventEmitter } from 'node:events';
7
7
  import * as nodePty from 'node-pty';
8
8
 
@@ -542,4 +542,4 @@ var TerminalRegistry = class {
542
542
  var terminalRegistry = new TerminalRegistry();
543
543
 
544
544
  export { TerminalRegistryRefusal as T, TerminalSendRefusal as a, terminalRegistry as t };
545
- //# sourceMappingURL=registry.js-G1hzOje_.js.map
545
+ //# sourceMappingURL=registry2.js-D3Pr1Rv9.js.map
@@ -1,4 +1,4 @@
1
- import { i as isConfigRecord } from './config.js-B0w1-uCA.js';
1
+ import { i as isConfigRecord } from './config.js-vThvQeMn.js';
2
2
  import { existsSync, readFileSync, cpSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { homedir } from 'node:os';
@@ -159,4 +159,4 @@ function parseYamlSettings(text) {
159
159
  }
160
160
 
161
161
  export { SettingsParseError as S, ensureDsiHome as e, migrateLegacyDsiConfig as m, parseYamlSettings as p, readSettingsDocument as r, saveSettingsDocument as s };
162
- //# sourceMappingURL=settings-document.js-BRTO59Al.js.map
162
+ //# sourceMappingURL=settings-document.js-Gw1puaik.js.map
@@ -1,9 +1,9 @@
1
1
  import { i as index_server_exports } from './index-server.js-CV2lq-ha.js';
2
2
  import './shared.js-HQ0rfhm4.js';
3
- import './internal2.js-Bcxjr5x2.js';
3
+ import './internal2.js-BtL7AFjQ.js';
4
4
  import './exports.js-DH_tjtdv.js';
5
5
  import { U as snapshot, g as getContext, n as noop } from './server.js-DqHGsQU2.js';
6
- import { U as UI_LOCALES } from './config.js-B0w1-uCA.js';
6
+ import { U as UI_LOCALES } from './config.js-vThvQeMn.js';
7
7
  import { c as getLocale } from './runtime.js-CCJi3XaY.js';
8
8
  import './utils.js-CAdNO8Tu.js';
9
9
 
@@ -304,4 +304,4 @@ var page = {
304
304
  };
305
305
 
306
306
  export { afterNavigate as a, backtoworkspace2 as b, currentLocale as c, dshunreachable1 as d, replaceState as e, page as p, reapednote1 as r, sessionnotfound2 as s, t, unexpectederror1 as u };
307
- //# sourceMappingURL=state.js-ez_uUx9I.js.map
307
+ //# sourceMappingURL=state.js-D3ob-Gqa.js.map
@@ -1,6 +1,6 @@
1
- import { c as readA2aConfig } from './insight-config.js-CA-iT3lB.js';
2
- import { i as insertWaiting, o as openA2aDb, s as settleRow, p as pruneTerminalBefore, l as listWaiting } from './db.js-DRhjT6xK.js';
3
- import { g as getDshConnection } from './dsh-connection.js-DL5qjlq2.js';
1
+ import { c as readA2aConfig } from './insight-config.js-CaHHJpFK.js';
2
+ import { i as insertWaiting, o as openA2aDb, s as settleRow, p as pruneTerminalBefore, l as listWaiting } from './db.js-B8BUpxGM.js';
3
+ import { g as getDshConnection } from './dsh-connection.js-xltcocWy.js';
4
4
  import { a as entryForEvent, h as historyToEvents } from './dsh-events.js-CGuF_had.js';
5
5
 
6
6
  //#region src/lib/server/a2a/matcher.ts
@@ -470,4 +470,4 @@ function registerWatch(row) {
470
470
  }
471
471
 
472
472
  export { configureWatcher as c, ensureStarted as e, registerWatch as r };
473
- //# sourceMappingURL=watcher.js-Dogr6xsl.js.map
473
+ //# sourceMappingURL=watcher.js-DcT3cS4v.js.map
@@ -1,8 +1,8 @@
1
- import { g as getRow } from '../../../../../chunks/db.js-DRhjT6xK.js';
1
+ import { g as getRow } from '../../../../../chunks/db.js-B8BUpxGM.js';
2
2
  import { j as json } from '../../../../../chunks/utils.js-CAdNO8Tu.js';
3
- import '../../../../../chunks/insight-config.js-CA-iT3lB.js';
4
- import '../../../../../chunks/config.js-B0w1-uCA.js';
5
- import '../../../../../chunks/settings-document.js-BRTO59Al.js';
3
+ import '../../../../../chunks/insight-config.js-CaHHJpFK.js';
4
+ import '../../../../../chunks/config.js-vThvQeMn.js';
5
+ import '../../../../../chunks/settings-document.js-Gw1puaik.js';
6
6
  import 'node:fs';
7
7
  import 'node:path';
8
8
  import 'node:os';
@@ -36,4 +36,4 @@ var GET = ({ params }) => {
36
36
  };
37
37
 
38
38
  export { GET };
39
- //# sourceMappingURL=_server.ts.js-CpeC2QLb.js.map
39
+ //# sourceMappingURL=_server.ts.js-19pFOjH8.js.map
@@ -1,16 +1,16 @@
1
- import { a as listRecent } from '../../../../chunks/db.js-DRhjT6xK.js';
2
- import { e as ensureStarted } from '../../../../chunks/watcher.js-Dogr6xsl.js';
1
+ import { a as listRecent } from '../../../../chunks/db.js-B8BUpxGM.js';
2
+ import { e as ensureStarted } from '../../../../chunks/watcher.js-DcT3cS4v.js';
3
3
  import { j as json } from '../../../../chunks/utils.js-CAdNO8Tu.js';
4
- import '../../../../chunks/insight-config.js-CA-iT3lB.js';
5
- import '../../../../chunks/config.js-B0w1-uCA.js';
6
- import '../../../../chunks/settings-document.js-BRTO59Al.js';
4
+ import '../../../../chunks/insight-config.js-CaHHJpFK.js';
5
+ import '../../../../chunks/config.js-vThvQeMn.js';
6
+ import '../../../../chunks/settings-document.js-Gw1puaik.js';
7
7
  import 'node:fs';
8
8
  import 'node:path';
9
9
  import 'node:os';
10
10
  import 'yaml';
11
11
  import '../../../../chunks/settings-home2.js-f75-B6RI.js';
12
12
  import 'node:sqlite';
13
- import '../../../../chunks/dsh-connection.js-DL5qjlq2.js';
13
+ import '../../../../chunks/dsh-connection.js-xltcocWy.js';
14
14
  import '../../../../chunks/dsh-events.js-CGuF_had.js';
15
15
  import '../../../../chunks/merge.js-yMwW440S.js';
16
16
  import '../../../../chunks/shared.js-HQ0rfhm4.js';
@@ -61,4 +61,4 @@ var GET = ({ url }) => {
61
61
  };
62
62
 
63
63
  export { GET };
64
- //# sourceMappingURL=_server.ts.js-BkLuKDZr.js.map
64
+ //# sourceMappingURL=_server.ts.js-BcUn6axe.js.map