@deepseek-insight/dsi 0.5.29 → 0.5.31

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 (91) hide show
  1. package/README.md +10 -5
  2. package/build/client/_app/immutable/assets/0.CeQisprJ.css +2 -0
  3. package/build/client/_app/immutable/assets/{2.B2JMICOx.css → 2.DUjQJ-eX.css} +1 -1
  4. package/build/client/_app/immutable/chunks/{D4yILG4p.js → 2on6iyc3.js} +1 -1
  5. package/build/client/_app/immutable/chunks/3oS6cDzS.js +1 -0
  6. package/build/client/_app/immutable/chunks/BG5jnpAS.js +1 -0
  7. package/build/client/_app/immutable/chunks/C3BHN0de.js +16 -0
  8. package/build/client/_app/immutable/chunks/{Ch00tocz.js → CgNCitGK.js} +2 -2
  9. package/build/client/_app/immutable/chunks/DIOBYJe3.js +1 -0
  10. package/build/client/_app/immutable/entry/{app.CxAgK_V4.js → app.CQmyDBAG.js} +2 -2
  11. package/build/client/_app/immutable/entry/start.CX9hkEBk.js +1 -0
  12. package/build/client/_app/immutable/nodes/{1.DJ0wIUro.js → 1.BZc7zU_C.js} +1 -1
  13. package/build/client/_app/immutable/nodes/2.Bl8zOReA.js +174 -0
  14. package/build/client/_app/version.json +1 -1
  15. package/build/handler.js +4 -4
  16. package/build/index.js +4 -4
  17. package/build/server/chunks/chunks/{app-config.svelte.js-F-U5xz0r.js → app-config.svelte.js-BBPHOX4N.js} +2 -2
  18. package/build/server/chunks/chunks/{config.js-BvW5LrfQ.js → config.js-B0w1-uCA.js} +10 -2
  19. package/build/server/chunks/chunks/{db.js-CSrMZzws.js → db.js-BTsRdpsi.js} +2 -2
  20. package/build/server/chunks/chunks/{db2.js-B9P1MuEl.js → db2.js-ou6Vlz7O.js} +2 -2
  21. package/build/server/chunks/chunks/{dsh-connection.js-DjwmUFoE.js → dsh-connection.js-DawPog_I.js} +3 -3
  22. package/build/server/chunks/chunks/{git-probe.js-BGOqAXVt.js → git-probe.js-D5q52JMr.js} +2 -2
  23. package/build/server/chunks/chunks/{insight-config.js-CmCFt2Kp.js → insight-config.js-DiF58x83.js} +22 -4
  24. package/build/server/chunks/chunks/{internal.js-D-70iBmc.js → internal.js-BtKbXGxV.js} +3 -3
  25. package/build/server/chunks/chunks/registry.js-G1hzOje_.js +545 -0
  26. package/build/server/chunks/chunks/{settings-document.js-i5MkkSFH.js → settings-document.js-BRTO59Al.js} +2 -2
  27. package/build/server/chunks/chunks/{state.js-BKUnFOB4.js → state.js-DO4TvfeY.js} +3 -3
  28. package/build/server/chunks/chunks/{watcher.js-CuX0WU5B.js → watcher.js-BJlOJaBs.js} +4 -4
  29. package/build/server/chunks/entries/endpoints/api/a2a/_id_/{_server.ts.js-BGuC49gg.js → _server.ts.js-CDJ0OmUG.js} +5 -5
  30. package/build/server/chunks/entries/endpoints/api/a2a/{_server.ts.js-BVH1ToLe.js → _server.ts.js-BVFSRjvv.js} +7 -7
  31. package/build/server/chunks/entries/endpoints/api/a2a/register/{_server.ts.js-a85F1GOf.js → _server.ts.js-Bhou8F5X.js} +7 -7
  32. package/build/server/chunks/entries/endpoints/api/config/{_server.ts.js-CBq4bRig.js → _server.ts.js-BJIzdaiA.js} +5 -5
  33. package/build/server/chunks/entries/endpoints/api/dsh/directory/{_server.ts.js-BpQX_N7Q.js → _server.ts.js-C6n_idy7.js} +5 -5
  34. package/build/server/chunks/entries/endpoints/api/dsh/pick-directory/{_server.ts.js-eYQQnSgq.js → _server.ts.js-DADH8M-H.js} +5 -5
  35. package/build/server/chunks/entries/endpoints/api/dsh/presets/{_server.ts.js-vOFZ1siq.js → _server.ts.js-bC_NLj4U.js} +5 -5
  36. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/attachment/{_server.ts.js-D2tytVhM.js → _server.ts.js-CoScceI5.js} +5 -5
  37. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/cancel/{_server.ts.js-BEn9Ta_U.js → _server.ts.js-C1FgtncS.js} +5 -5
  38. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/catalog/{_server.ts.js-ZfOtq-D3.js → _server.ts.js-PIxDn234.js} +5 -5
  39. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/command/{_server.ts.js-BmJbICbR.js → _server.ts.js-B_CRf61I.js} +5 -5
  40. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/events/{_server.ts.js-gsaVQwiM.js → _server.ts.js-DGGUjpY9.js} +5 -5
  41. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/fork/{_server.ts.js-Dn0v3kPL.js → _server.ts.js-xEVeT3pb.js} +5 -5
  42. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/goal/{_server.ts.js-3y0ufVj_.js → _server.ts.js-Df8t4hkX.js} +5 -5
  43. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/history/{_server.ts.js-CLe_Yd9T.js → _server.ts.js-CH1L9p8z.js} +5 -5
  44. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/models/{_server.ts.js-Reg3sATn.js → _server.ts.js-BvG76FMN.js} +5 -5
  45. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/path/{_server.ts.js-Der7_1Gf.js → _server.ts.js-t8Oiyikl.js} +4 -4
  46. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/permission/{_server.ts.js-B0F4vS5A.js → _server.ts.js-C7fokwxa.js} +5 -5
  47. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/prompt/{_server.ts.js-DLlAJ59P.js → _server.ts.js-Cfm2eSa-.js} +5 -5
  48. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/rename/{_server.ts.js-cxdpHzTp.js → _server.ts.js-DJ3WSBOS.js} +5 -5
  49. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/respond/{_server.ts.js-Bdymup3B.js → _server.ts.js-B8lDX-zk.js} +5 -5
  50. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/select-model/{_server.ts.js-13DIGKaK.js → _server.ts.js-DCKvoONe.js} +5 -5
  51. package/build/server/chunks/entries/endpoints/api/dsh/sessions/{_server.ts.js-ChqfgswA.js → _server.ts.js-Bl4xBKoB.js} +5 -5
  52. package/build/server/chunks/entries/endpoints/api/dsh/workspace-file/{_server.ts.js-CT8DKYFX.js → _server.ts.js--FCZv4Np.js} +5 -5
  53. package/build/server/chunks/entries/endpoints/api/dsh/workspace-file-bytes/{_server.ts.js-DY-EoPi1.js → _server.ts.js-DEUnD1Mn.js} +5 -5
  54. package/build/server/chunks/entries/endpoints/api/dsh/workspace-tree/{_server.ts.js-D4tdFfjX.js → _server.ts.js-BiHVvbW-.js} +5 -5
  55. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/{_server.ts.js-BUcBfYnX.js → _server.ts.js-CcDE1rk8.js} +5 -5
  56. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/delete/{_server.ts.js-CunmEcMs.js → _server.ts.js-C8vdE5Jr.js} +5 -5
  57. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/rename/{_server.ts.js-CmGqWlUy.js → _server.ts.js-C9cgB8t0.js} +5 -5
  58. package/build/server/chunks/entries/endpoints/api/prompts/_id_/{_server.ts.js-DMyDOvrX.js → _server.ts.js-oR6Qu-Wa.js} +5 -5
  59. package/build/server/chunks/entries/endpoints/api/prompts/{_server.ts.js-BS2wQOe7.js → _server.ts.js-j8TNOmk3.js} +5 -5
  60. package/build/server/chunks/entries/endpoints/api/prompts/use/{_server.ts.js-5-lf15sG.js → _server.ts.js-B6UZ2HZ5.js} +5 -5
  61. package/build/server/chunks/entries/endpoints/api/settings/{_server.ts.js-PDRzWcsI.js → _server.ts.js-BWE38_9X.js} +4 -4
  62. package/build/server/chunks/entries/endpoints/api/terminal/_id_/output/_server.ts.js-m6Cug5hN.js +31 -0
  63. package/build/server/chunks/entries/endpoints/api/terminal/_id_/reattach/_server.ts.js-wSsMKLiw.js +34 -0
  64. package/build/server/chunks/entries/endpoints/api/terminal/_id_/send/_server.ts.js-CTc5xP72.js +60 -0
  65. package/build/server/chunks/entries/endpoints/api/terminal/_id_/stream/_server.ts.js-C8PLMU0Y.js +116 -0
  66. package/build/server/chunks/entries/endpoints/api/terminal/_server.ts.js-CmM-ksLQ.js +73 -0
  67. package/build/server/chunks/entries/endpoints/api/workspace/file-write/{_server.ts.js-COt-HtL0.js → _server.ts.js-1pMekv7y.js} +6 -6
  68. package/build/server/chunks/entries/endpoints/api/workspace/git-events/{_server.ts.js-k7bgOO_c.js → _server.ts.js-Cwebkv8Y.js} +6 -6
  69. package/build/server/chunks/entries/endpoints/api/workspace/git-file-head/{_server.ts.js-H1HUbRif.js → _server.ts.js-4JF7UGE-.js} +6 -6
  70. package/build/server/chunks/entries/endpoints/api/workspace/git-map/{_server.ts.js-D7nqciP7.js → _server.ts.js-B5i8ziTR.js} +6 -6
  71. package/build/server/chunks/entries/endpoints/api/workspace/git-status/{_server.ts.js-D0HYWO9M.js → _server.ts.js-zYBj_jzG.js} +6 -6
  72. package/build/server/chunks/entries/endpoints/api/workspace/write-gate/{_server.ts.js-Boi1Sn8U.js → _server.ts.js-BbgR18Zo.js} +6 -6
  73. package/build/server/chunks/entries/{hooks.server.js-Bt6VP5fg.js → hooks.server.js-BSnc9oHw.js} +3 -3
  74. package/build/server/chunks/entries/pages/{_error.svelte.js-Bee5xNts.js → _error.svelte.js-BppCLlQz.js} +4 -4
  75. package/build/server/chunks/entries/pages/{_page.server.ts.js-DdzOMHcO.js → _page.server.ts.js-CNmIVlOY.js} +2 -2
  76. package/build/server/chunks/entries/pages/{_page.svelte.js-B2EA5ccE.js → _page.svelte.js-B26jgDy0.js} +443 -52
  77. package/build/server/chunks/{handler-BVZR5A-R.js → handler-H035jucs.js} +3 -3
  78. package/build/server/chunks/{index.js-Nwzp6b_k.js → index.js-BwOnKfpd.js} +2 -2
  79. package/build/server/chunks/{manifest.js-Bof9naPY.js → manifest.js-DSGtd5rA.js} +79 -44
  80. package/build/server/chunks/nodes/{0.js-iA5BIbe7.js → 0.js--kZVi_Mf.js} +3 -3
  81. package/build/server/chunks/nodes/1.js-CyCPH90h.js +9 -0
  82. package/build/server/chunks/nodes/{2.js-QPzVOzfX.js → 2.js-Bws89uBE.js} +9 -9
  83. package/package.json +4 -1
  84. package/template/.dsi/resources/skills-reff.md +1 -1
  85. package/build/client/_app/immutable/assets/0.BbBUj8Gs.css +0 -2
  86. package/build/client/_app/immutable/chunks/Zxb_bQ44.js +0 -1
  87. package/build/client/_app/immutable/chunks/muEkqSbS.js +0 -1
  88. package/build/client/_app/immutable/entry/start.CImCAUy1.js +0 -1
  89. package/build/client/_app/immutable/nodes/2.8eo5qS19.js +0 -171
  90. package/build/server/chunks/nodes/1.js-CxOuNsR_.js +0 -9
  91. /package/build/client/_app/immutable/nodes/{0.B3l2ZewD.js → 0.CioJu6OT.js} +0 -0
@@ -0,0 +1,545 @@
1
+ import { join } from 'node:path';
2
+ import { tmpdir } from 'node:os';
3
+ import { mkdir, rm, open } from 'node:fs/promises';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { EventEmitter } from 'node:events';
7
+ import * as nodePty from 'node-pty';
8
+
9
+ //#region src/lib/server/terminal/scrub.ts
10
+ /**
11
+ * Child-environment scrub for DSI's terminal sessions — a deliberate
12
+ * mechanism-copy of DSH's `scrubbedParentEnv` with one ownership-specific
13
+ * change: DSI strips its own `DSI_*` namespace too (ADR 2026-09-23 D6).
14
+ *
15
+ * Credential-shaped names and both reserved namespaces must never reach a
16
+ * terminal the operator can type into; explicitly supplied entries merge
17
+ * AFTER the scrub, so a deliberate opt-in survives. Case-insensitive
18
+ * matching because Windows environment names are case-insensitive.
19
+ * The proxy overlay re-applies after the strip so a child behind a proxied
20
+ * parent routes the same way.
21
+ * @mirrors deepseek-harness/packages/subprocess/subprocess/src/index.ts:47-80
22
+ */
23
+ /** Namespace prefixes reserved by the harness and by DSI — neither leaks
24
+ * into a child implicitly. */
25
+ var RESERVED_PREFIXES = ["DSH_", "DSI_"];
26
+ /** Credential-shaped environment names are NOT forwarded to children. */
27
+ var SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i;
28
+ function isReserved(key) {
29
+ const upper = key.toUpperCase();
30
+ return RESERVED_PREFIXES.some((prefix) => upper.startsWith(prefix));
31
+ }
32
+ /**
33
+ * The ambient parent environment minus credential-shaped names and minus
34
+ * every `DSH_*`/`DSI_*` name — the base every DSI terminal child
35
+ * starts from. `PATH`, `HOME`, locale, and proxy variables survive,
36
+ * so child shells run normally.
37
+ * @param explicit - deliberate caller entries merged after the scrub; a
38
+ * string is an opt-in that survives, `undefined` is a tombstone that
39
+ * removes an ordinary ambient entry.
40
+ * @returns a fresh environment object safe to hand to a PTY child.
41
+ */
42
+ function scrubbedChildEnv(explicit) {
43
+ const env = {};
44
+ for (const [key, value] of Object.entries(process.env)) if (value !== void 0 && !SENSITIVE_ENV_PATTERN.test(key) && !isReserved(key)) env[key] = value;
45
+ if (explicit !== void 0) for (const [key, value] of Object.entries(explicit)) if (value === void 0) Reflect.deleteProperty(env, key);
46
+ else env[key] = value;
47
+ return env;
48
+ }
49
+ //#endregion
50
+ //#region src/lib/server/terminal/session.ts
51
+ /**
52
+ * TerminalSession — one live PTY session and its owned process tree
53
+ * (spec 2026-09-24 Wave 2; ADR 2026-09-23 D4/D5/D8).
54
+ *
55
+ * Contracts honored here:
56
+ * - D4: one send in flight; a second refuses SEND_ACTIVE. Sends settle with
57
+ * exactly stdin_read / inferred_idle / timeout / session_exit — silence
58
+ * alone is inferred_idle, never completion; stdin_read requires positive
59
+ * prompt evidence at the tail.
60
+ * - D5: close is a quiescence ladder over the whole session — TERM to the
61
+ * foreground group, graceMs, KILL to survivors, reap; idempotent; rejects
62
+ * LINGERING_PIDS rather than claiming a success it cannot prove.
63
+ * - D8: bounded retained tail with spill recovery; offsets are whole-stream
64
+ * byte coordinates and reads never consume one another.
65
+ *
66
+ * Foreground-group note (W2): the PTY child is a session leader, so the
67
+ * negative-pid kill targets its process group — the foreground group for
68
+ * the sessions DSI opens. A child that creates its own group is reached by
69
+ * the ps-based survivor sweep in the close ladder.
70
+ */
71
+ /** Positive prompt evidence: the tail's last line ends with a bare shell
72
+ * prompt ($ # % >). Overridable per session — the detector is an instance
73
+ * field, never a hidden global. */
74
+ var DEFAULT_PROMPT_DETECT = /(?:^|\r?\n)[$%>#] \r?\n?$/;
75
+ /** Send refusal helper — D4's SEND_ACTIVE is a refusal code, not a settle
76
+ * reason; sessions surface it as a thrown refusal the registry maps to the
77
+ * wire. Internal sends use this typed error. */
78
+ var TerminalSendRefusal = class extends Error {
79
+ code;
80
+ constructor() {
81
+ super("a send is already in flight for this session");
82
+ this.name = "TerminalSendRefusal";
83
+ this.code = "SEND_ACTIVE";
84
+ }
85
+ };
86
+ var nextSessionId = 1;
87
+ var TerminalSession = class extends EventEmitter {
88
+ /** Opaque session id (registry scope). */
89
+ id;
90
+ pid;
91
+ /** The session leader's process group — the foreground group DSI signals. */
92
+ processGroupId;
93
+ spec;
94
+ pty;
95
+ promptDetect;
96
+ tail = "";
97
+ tailStartOffset = 0;
98
+ totalBytes = 0;
99
+ spillFile = "";
100
+ spillHandle = null;
101
+ spillDropped = false;
102
+ exited = false;
103
+ outcome = null;
104
+ exitWaiters = [];
105
+ sendInFlight = false;
106
+ passiveSettling = false;
107
+ closePromise = null;
108
+ closedAnnounced = false;
109
+ constructor(spec, options) {
110
+ super();
111
+ this.spec = spec;
112
+ this.id = (options?.idPrefix ?? "t") + "-" + nextSessionId++;
113
+ this.promptDetect = options?.promptDetect ?? ((tail) => DEFAULT_PROMPT_DETECT.test(tail));
114
+ this.pty = nodePty.spawn(spec.argv[0], spec.argv.slice(1), {
115
+ name: spec.terminalType,
116
+ cols: spec.cols,
117
+ rows: spec.rows,
118
+ cwd: spec.cwd,
119
+ env: scrubbedChildEnv({
120
+ ...spec.env,
121
+ TERM: spec.terminalType
122
+ })
123
+ });
124
+ this.pid = this.pty.pid;
125
+ this.processGroupId = this.pid;
126
+ this.pty.onData((data) => this.onOutput(data));
127
+ this.pty.onExit(({ exitCode, signal }) => this.onExit(exitCode, signal));
128
+ }
129
+ onOutput(data) {
130
+ const bytes = Buffer.byteLength(data);
131
+ this.totalBytes += bytes;
132
+ this.tail += data;
133
+ if (Buffer.byteLength(this.tail) > this.spec.tailBytes + Math.min(65536, this.spec.tailBytes)) this.trimTail();
134
+ this.emit("event", {
135
+ type: "output",
136
+ bytes: data,
137
+ nextOffset: this.totalBytes
138
+ });
139
+ }
140
+ /** Drop the head past the cap; the dropped bytes land in the spill file. */
141
+ trimTail() {
142
+ let cut = 0;
143
+ while (cut < this.tail.length && Buffer.byteLength(this.tail.slice(0, cut)) < Buffer.byteLength(this.tail) - this.spec.tailBytes) {
144
+ cut = this.tail.indexOf("\n", cut + 1);
145
+ if (cut === -1) {
146
+ cut = this.tail.length;
147
+ break;
148
+ }
149
+ }
150
+ if (cut <= 0) return;
151
+ const dropped = this.tail.slice(0, cut);
152
+ this.spillAppend(dropped);
153
+ this.tailStartOffset += Buffer.byteLength(dropped);
154
+ this.tail = this.tail.slice(cut);
155
+ }
156
+ async spillAppend(dropped) {
157
+ try {
158
+ if (!this.spillFile) {
159
+ this.spillFile = join(tmpdir(), "dsi-terminal-" + this.id + ".spill");
160
+ await mkdir(tmpdir(), { recursive: true });
161
+ await rm(this.spillFile, { force: true });
162
+ }
163
+ this.spillHandle ??= await open(this.spillFile, "a");
164
+ await this.spillHandle.writeFile(dropped);
165
+ this.spillDropped = true;
166
+ } catch {
167
+ this.spillDropped = false;
168
+ this.spillFile = "";
169
+ this.spillHandle = null;
170
+ }
171
+ }
172
+ /** Cursor-free read of the retained stream (never consumes).
173
+ * @mirrors SubprocessOutputReader.readFrom */
174
+ readFrom(fromByte) {
175
+ const end = this.totalBytes;
176
+ const tailStart = this.tailStartOffset;
177
+ if (fromByte < tailStart) return {
178
+ text: this.tail,
179
+ nextOffset: end,
180
+ lossy: true,
181
+ spillPath: this.spillDropped ? this.spillFile : void 0
182
+ };
183
+ const skip = Math.max(0, fromByte - tailStart);
184
+ return {
185
+ text: this.tail.slice(skip),
186
+ nextOffset: end,
187
+ lossy: false
188
+ };
189
+ }
190
+ onExit(exitCode, signal) {
191
+ this.exited = true;
192
+ this.outcome = {
193
+ exitCode,
194
+ signal: signal === void 0 ? null : signal
195
+ };
196
+ this.emit("event", {
197
+ type: "exit",
198
+ outcome: this.outcome
199
+ });
200
+ for (const wake of this.exitWaiters) wake(this.outcome);
201
+ this.exitWaiters = [];
202
+ }
203
+ /** Resolves when the top-level process exits. */
204
+ waitForExit() {
205
+ if (this.outcome) return Promise.resolve(this.outcome);
206
+ return new Promise((resolve) => this.exitWaiters.push(resolve));
207
+ }
208
+ sleep(ms) {
209
+ return new Promise((resolve) => setTimeout(resolve, ms));
210
+ }
211
+ /** Current retained tail (prompt detection input). */
212
+ getTail() {
213
+ return this.tail;
214
+ }
215
+ /** Whole-stream byte coordinate of the live tail (reattach resume point).
216
+ * Named past the field — a same-named method would be shadowed by the
217
+ * field initializer at runtime. */
218
+ streamBytes() {
219
+ return this.totalBytes;
220
+ }
221
+ /** Exit facts once the top-level process exited; null while live. */
222
+ exitOutcome() {
223
+ return this.outcome;
224
+ }
225
+ isExited() {
226
+ return this.exited;
227
+ }
228
+ /**
229
+ * Write text to the terminal and wait for one settle fact. Exactly one
230
+ * send may be in flight; a second refuses SEND_ACTIVE.
231
+ * @param data - text delivered verbatim (append \\n to submit a line).
232
+ * @param timeoutMs - hard bound for the whole wait.
233
+ */
234
+ async send(data, timeoutMs) {
235
+ if (this.sendInFlight) throw new TerminalSendRefusal();
236
+ if (this.exited) return "session_exit";
237
+ this.sendInFlight = true;
238
+ try {
239
+ const baseline = this.tail.length;
240
+ this.pty.write(data);
241
+ const deadline = Date.now() + timeoutMs;
242
+ let sawOutput = false;
243
+ let lastActivity = Date.now();
244
+ const onOutput = () => {
245
+ sawOutput = true;
246
+ lastActivity = Date.now();
247
+ };
248
+ this.on("event", onOutput);
249
+ try {
250
+ for (;;) {
251
+ let reason;
252
+ if (this.exited) reason = "session_exit";
253
+ else if (this.promptDetect(this.tail.slice(baseline))) reason = "stdin_read";
254
+ else if (sawOutput && Date.now() - lastActivity >= this.spec.idleMs) reason = "inferred_idle";
255
+ else if (Date.now() >= deadline) reason = "timeout";
256
+ else {
257
+ await this.sleep(25);
258
+ continue;
259
+ }
260
+ this.emit("event", {
261
+ type: "settled",
262
+ reason
263
+ });
264
+ return reason;
265
+ }
266
+ } finally {
267
+ this.off("event", onOutput);
268
+ }
269
+ } finally {
270
+ this.sendInFlight = false;
271
+ }
272
+ }
273
+ /**
274
+ * Interactive typing — plain write, never locked, never dropped (the
275
+ * xterm/input path). A passive settle observer runs when no awaited send
276
+ * is watching, so the wire still gets settle facts for the badge.
277
+ */
278
+ write(data) {
279
+ if (this.exited) return;
280
+ this.pty.write(data);
281
+ this.startPassiveSettle();
282
+ }
283
+ /** One passive settle observation; concurrent writes share the watcher. */
284
+ startPassiveSettle() {
285
+ if (this.passiveSettling || this.sendInFlight) return;
286
+ this.passiveSettling = true;
287
+ (async () => {
288
+ const deadline = Date.now() + 3e4;
289
+ let lastActivity = Date.now();
290
+ let sawOutput = false;
291
+ const onOutput = () => {
292
+ sawOutput = true;
293
+ lastActivity = Date.now();
294
+ };
295
+ this.on("event", onOutput);
296
+ try {
297
+ const baseline = this.tail.length;
298
+ for (;;) {
299
+ let reason = null;
300
+ if (this.exited) reason = "session_exit";
301
+ else if (this.promptDetect(this.tail.slice(baseline))) reason = "stdin_read";
302
+ else if (sawOutput && Date.now() - lastActivity >= this.spec.idleMs) reason = "inferred_idle";
303
+ else if (Date.now() >= deadline) reason = "timeout";
304
+ if (reason) {
305
+ this.emit("event", {
306
+ type: "settled",
307
+ reason
308
+ });
309
+ return;
310
+ }
311
+ await this.sleep(50);
312
+ }
313
+ } finally {
314
+ this.off("event", onOutput);
315
+ this.passiveSettling = false;
316
+ }
317
+ })();
318
+ }
319
+ /** Deliver one allowlisted signal to the foreground group.
320
+ * @returns the exact group id that received it. */
321
+ signalForeground(signal) {
322
+ if (this.exited) throw new Error("session exited");
323
+ process.kill(-this.processGroupId, signal);
324
+ return this.processGroupId;
325
+ }
326
+ /** pid → ppid snapshot of the live process table. */
327
+ processTable() {
328
+ const out = spawnSync("ps", [
329
+ "-ax",
330
+ "-o",
331
+ "pid=,ppid="
332
+ ], { encoding: "utf8" });
333
+ const table = /* @__PURE__ */ new Map();
334
+ if (out.status !== 0 || !out.stdout) return table;
335
+ for (const line of out.stdout.split("\n")) {
336
+ const m = line.trim().match(/^(\d+)\s+(\d+)$/);
337
+ if (m) table.set(Number(m[1]), Number(m[2]));
338
+ }
339
+ return table;
340
+ }
341
+ /** All descendants of a pid (any process group) from one snapshot —
342
+ * job-control shells move background children to their own group, so a
343
+ * pgid filter alone would orphan them. */
344
+ descendantPids(root, table) {
345
+ const children = /* @__PURE__ */ new Map();
346
+ for (const [pid, ppid] of table) {
347
+ const list = children.get(ppid) ?? [];
348
+ list.push(pid);
349
+ children.set(ppid, list);
350
+ }
351
+ const found = [];
352
+ const walk = (pid) => {
353
+ for (const child of children.get(pid) ?? []) {
354
+ found.push(child);
355
+ walk(child);
356
+ }
357
+ };
358
+ walk(root);
359
+ return found;
360
+ }
361
+ alive(pid) {
362
+ try {
363
+ process.kill(pid, 0);
364
+ return true;
365
+ } catch {
366
+ return false;
367
+ }
368
+ }
369
+ async killSurvivors(survivors, signal) {
370
+ for (const pid of survivors) try {
371
+ process.kill(pid, signal);
372
+ } catch {}
373
+ }
374
+ /**
375
+ * TERM the foreground group, wait graceMs, KILL the session tree, reap.
376
+ * Idempotent: a second call awaits the first ladder's result.
377
+ */
378
+ close() {
379
+ if (this.closePromise) return this.closePromise;
380
+ this.closePromise = this.runCloseLadder();
381
+ return this.closePromise;
382
+ }
383
+ async runCloseLadder() {
384
+ try {
385
+ const descendants = this.descendantPids(this.pid, this.processTable());
386
+ if (!this.exited) {
387
+ try {
388
+ this.signalForeground("SIGTERM");
389
+ } catch {}
390
+ if (!await Promise.race([this.waitForExit(), this.sleep(this.spec.graceMs).then(() => null)])) {
391
+ try {
392
+ process.kill(-this.processGroupId, "SIGKILL");
393
+ } catch {}
394
+ await this.waitForExit();
395
+ }
396
+ }
397
+ let survivors = descendants.filter((pid) => this.alive(pid));
398
+ if (survivors.length > 0) {
399
+ await this.killSurvivors(survivors, "SIGKILL");
400
+ await this.sleep(100);
401
+ survivors = survivors.filter((pid) => this.alive(pid));
402
+ }
403
+ if (!this.closedAnnounced) {
404
+ this.closedAnnounced = true;
405
+ this.emit("event", {
406
+ type: "closed",
407
+ quiescent: survivors.length === 0,
408
+ lingeringPids: survivors.length > 0 ? survivors : void 0
409
+ });
410
+ }
411
+ await this.dispose();
412
+ return survivors.length > 0 ? {
413
+ quiescent: false,
414
+ lingeringPids: survivors
415
+ } : { quiescent: true };
416
+ } catch (err) {
417
+ throw err instanceof Error ? err : new Error(String(err));
418
+ }
419
+ }
420
+ /** Release file handles; safe to call once the ladder settled. */
421
+ async dispose() {
422
+ if (this.spillHandle) {
423
+ await this.spillHandle.close().catch(() => void 0);
424
+ this.spillHandle = null;
425
+ }
426
+ }
427
+ };
428
+ //#endregion
429
+ //#region src/lib/server/terminal/registry.ts
430
+ /**
431
+ * Terminal registry — the server-side session table and ownership fence
432
+ * (spec 2026-09-24 Wave 2; ADR 2026-09-23 D7).
433
+ *
434
+ * DSI's terminal is operator-owned: sessions belong to the DSI server
435
+ * process, and each attached browser tab holds one server-minted token.
436
+ * Write/signal/close operations present the token; a wrong one is refused
437
+ * FOREIGN_SESSION. Re-attach re-mints the token to the asking tab and
438
+ * evicts the old one — many watchers, one writer (the one-writer invariant
439
+ * of ADR D4 stays mechanical, not conventional).
440
+ *
441
+ * This module is the WIRING POINT: transport routes import the singleton;
442
+ * nothing imports a route or a component.
443
+ */
444
+ /** Refusal with a stable wire code (mirrors dsh-terminal's codes; the
445
+ * registry is where FOREIGN_SESSION and NO_SESSION are decided). */
446
+ var TerminalRegistryRefusal = class extends Error {
447
+ code;
448
+ constructor(code, message) {
449
+ super(message);
450
+ this.name = "TerminalRegistryRefusal";
451
+ this.code = code;
452
+ }
453
+ };
454
+ var TerminalRegistry = class {
455
+ entries = /* @__PURE__ */ new Map();
456
+ /** Spawn a session from a fully specified spec (no defaults here —
457
+ * the transport resolved them from config). Returns the minted token. */
458
+ create(spec, options) {
459
+ const session = new TerminalSession(spec, { promptDetect: options?.promptDetect });
460
+ const token = randomUUID();
461
+ this.entries.set(session.id, {
462
+ session,
463
+ token,
464
+ spec
465
+ });
466
+ return {
467
+ session,
468
+ token
469
+ };
470
+ }
471
+ /** Resolve a session for a tokened operation — the fence.
472
+ * @throws TerminalRegistryRefusal NO_SESSION / FOREIGN_SESSION */
473
+ authorized(sessionId, token) {
474
+ const entry = this.entries.get(sessionId);
475
+ if (!entry) throw new TerminalRegistryRefusal("NO_SESSION", "no such terminal session");
476
+ if (entry.token !== token) throw new TerminalRegistryRefusal("FOREIGN_SESSION", "this tab does not own the terminal");
477
+ return entry.session;
478
+ }
479
+ /** Read-only view without a token: watching is open, writing is not. */
480
+ peek(sessionId) {
481
+ const entry = this.entries.get(sessionId);
482
+ if (!entry) throw new TerminalRegistryRefusal("NO_SESSION", "no such terminal session");
483
+ return {
484
+ id: entry.session.id,
485
+ exited: entry.session.isExited()
486
+ };
487
+ }
488
+ /** Re-attach: re-mint the token to the asking tab; the old token dies. */
489
+ reattach(sessionId) {
490
+ const entry = this.entries.get(sessionId);
491
+ if (!entry) throw new TerminalRegistryRefusal("NO_SESSION", "no such terminal session");
492
+ entry.token = randomUUID();
493
+ return {
494
+ token: entry.token,
495
+ fromByte: entry.session.streamBytes()
496
+ };
497
+ }
498
+ /** Tokened operations — each throws through the fence. */
499
+ send(sessionId, token, data, timeoutMs) {
500
+ return this.authorized(sessionId, token).send(data, timeoutMs);
501
+ }
502
+ /** Interactive typing — the write never locks and never drops. */
503
+ write(sessionId, token, data) {
504
+ this.authorized(sessionId, token).write(data);
505
+ }
506
+ signal(sessionId, token, signal) {
507
+ return this.authorized(sessionId, token).signalForeground(signal);
508
+ }
509
+ async close(sessionId, token) {
510
+ const result = await this.authorized(sessionId, token).close();
511
+ return result.quiescent ? { quiescent: true } : {
512
+ quiescent: false,
513
+ lingeringPids: result.lingeringPids
514
+ };
515
+ }
516
+ readFrom(sessionId, fromByte) {
517
+ const entry = this.entries.get(sessionId);
518
+ if (!entry) throw new TerminalRegistryRefusal("NO_SESSION", "no such terminal session");
519
+ return entry.session.readFrom(fromByte);
520
+ }
521
+ events(sessionId) {
522
+ return this.entries.get(sessionId)?.session;
523
+ }
524
+ exitOutcome(sessionId) {
525
+ return this.entries.get(sessionId)?.session.exitOutcome() ?? null;
526
+ }
527
+ /** Every live session id (the operator's list — one owner, no scoping). */
528
+ list() {
529
+ return [...this.entries.values()].map((e) => ({
530
+ id: e.session.id,
531
+ exited: e.session.isExited()
532
+ }));
533
+ }
534
+ /** Close every session and await quiescence (server shutdown path). */
535
+ async dispose() {
536
+ const closings = [...this.entries.values()].map((e) => e.session.close().catch(() => void 0));
537
+ await Promise.all(closings);
538
+ this.entries.clear();
539
+ }
540
+ };
541
+ /** The wiring singleton — routes import this, nothing else constructs. */
542
+ var terminalRegistry = new TerminalRegistry();
543
+
544
+ export { TerminalRegistryRefusal as T, TerminalSendRefusal as a, terminalRegistry as t };
545
+ //# sourceMappingURL=registry.js-G1hzOje_.js.map
@@ -1,4 +1,4 @@
1
- import { i as isConfigRecord } from './config.js-BvW5LrfQ.js';
1
+ import { i as isConfigRecord } from './config.js-B0w1-uCA.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-i5MkkSFH.js.map
162
+ //# sourceMappingURL=settings-document.js-BRTO59Al.js.map
@@ -1,8 +1,8 @@
1
- import { i as index_server_exports } from './internal.js-D-70iBmc.js';
1
+ import { i as index_server_exports } from './internal.js-BtKbXGxV.js';
2
2
  import './shared.js-HQ0rfhm4.js';
3
3
  import './exports.js-DH_tjtdv.js';
4
4
  import { U as snapshot, J as getContext, G as noop } from './server.js-11-pxAWV.js';
5
- import { U as UI_LOCALES } from './config.js-BvW5LrfQ.js';
5
+ import { U as UI_LOCALES } from './config.js-B0w1-uCA.js';
6
6
  import { c as getLocale } from './runtime.js-CCJi3XaY.js';
7
7
  import './utils.js-CAdNO8Tu.js';
8
8
 
@@ -303,4 +303,4 @@ var page = {
303
303
  };
304
304
 
305
305
  export { afterNavigate as a, backtoworkspace2 as b, replaceState as c, dshunreachable1 as d, currentLocale as e, page as p, reapednote1 as r, sessionnotfound2 as s, t, unexpectederror1 as u };
306
- //# sourceMappingURL=state.js-BKUnFOB4.js.map
306
+ //# sourceMappingURL=state.js-DO4TvfeY.js.map
@@ -1,6 +1,6 @@
1
- import { c as readA2aConfig } from './insight-config.js-CmCFt2Kp.js';
2
- import { i as insertWaiting, o as openA2aDb, s as settleRow, p as pruneTerminalBefore, l as listWaiting } from './db.js-CSrMZzws.js';
3
- import { g as getDshConnection } from './dsh-connection.js-DjwmUFoE.js';
1
+ import { c as readA2aConfig } from './insight-config.js-DiF58x83.js';
2
+ import { i as insertWaiting, o as openA2aDb, s as settleRow, p as pruneTerminalBefore, l as listWaiting } from './db.js-BTsRdpsi.js';
3
+ import { g as getDshConnection } from './dsh-connection.js-DawPog_I.js';
4
4
  import { a as entryForEvent, h as historyToEvents } from './dsh-events.js-DEZgATm-.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-CuX0WU5B.js.map
473
+ //# sourceMappingURL=watcher.js-BJlOJaBs.js.map
@@ -1,8 +1,8 @@
1
- import { g as getRow } from '../../../../../chunks/db.js-CSrMZzws.js';
1
+ import { g as getRow } from '../../../../../chunks/db.js-BTsRdpsi.js';
2
2
  import { j as json } from '../../../../../chunks/utils.js-CAdNO8Tu.js';
3
- import '../../../../../chunks/insight-config.js-CmCFt2Kp.js';
4
- import '../../../../../chunks/config.js-BvW5LrfQ.js';
5
- import '../../../../../chunks/settings-document.js-i5MkkSFH.js';
3
+ import '../../../../../chunks/insight-config.js-DiF58x83.js';
4
+ import '../../../../../chunks/config.js-B0w1-uCA.js';
5
+ import '../../../../../chunks/settings-document.js-BRTO59Al.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-BGuC49gg.js.map
39
+ //# sourceMappingURL=_server.ts.js-CDJ0OmUG.js.map
@@ -1,16 +1,16 @@
1
- import { a as listRecent } from '../../../../chunks/db.js-CSrMZzws.js';
2
- import { e as ensureStarted } from '../../../../chunks/watcher.js-CuX0WU5B.js';
1
+ import { a as listRecent } from '../../../../chunks/db.js-BTsRdpsi.js';
2
+ import { e as ensureStarted } from '../../../../chunks/watcher.js-BJlOJaBs.js';
3
3
  import { j as json } from '../../../../chunks/utils.js-CAdNO8Tu.js';
4
- import '../../../../chunks/insight-config.js-CmCFt2Kp.js';
5
- import '../../../../chunks/config.js-BvW5LrfQ.js';
6
- import '../../../../chunks/settings-document.js-i5MkkSFH.js';
4
+ import '../../../../chunks/insight-config.js-DiF58x83.js';
5
+ import '../../../../chunks/config.js-B0w1-uCA.js';
6
+ import '../../../../chunks/settings-document.js-BRTO59Al.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-DjwmUFoE.js';
13
+ import '../../../../chunks/dsh-connection.js-DawPog_I.js';
14
14
  import '../../../../chunks/dsh-events.js-DEZgATm-.js';
15
15
  import '../../../../chunks/shared.js-HQ0rfhm4.js';
16
16
  import '../../../../chunks/uneval.js-BJ9Qq2SF.js';
@@ -60,4 +60,4 @@ var GET = ({ url }) => {
60
60
  };
61
61
 
62
62
  export { GET };
63
- //# sourceMappingURL=_server.ts.js-BVH1ToLe.js.map
63
+ //# sourceMappingURL=_server.ts.js-BVFSRjvv.js.map
@@ -1,16 +1,16 @@
1
- import { c as readA2aConfig } from '../../../../../chunks/insight-config.js-CmCFt2Kp.js';
2
- import { g as getDshConnection } from '../../../../../chunks/dsh-connection.js-DjwmUFoE.js';
3
- import { c as configureWatcher, r as registerWatch } from '../../../../../chunks/watcher.js-CuX0WU5B.js';
1
+ import { c as readA2aConfig } from '../../../../../chunks/insight-config.js-DiF58x83.js';
2
+ import { g as getDshConnection } from '../../../../../chunks/dsh-connection.js-DawPog_I.js';
3
+ import { c as configureWatcher, r as registerWatch } from '../../../../../chunks/watcher.js-BJlOJaBs.js';
4
4
  import { m as mintA2aId } from '../../../../../chunks/a2a-protocol.js-C77mVg2Q.js';
5
5
  import { j as json } from '../../../../../chunks/utils.js-CAdNO8Tu.js';
6
- import '../../../../../chunks/config.js-BvW5LrfQ.js';
7
- import '../../../../../chunks/settings-document.js-i5MkkSFH.js';
6
+ import '../../../../../chunks/config.js-B0w1-uCA.js';
7
+ import '../../../../../chunks/settings-document.js-BRTO59Al.js';
8
8
  import 'node:fs';
9
9
  import 'node:path';
10
10
  import 'node:os';
11
11
  import 'yaml';
12
12
  import '../../../../../chunks/settings-home2.js-f75-B6RI.js';
13
- import '../../../../../chunks/db.js-CSrMZzws.js';
13
+ import '../../../../../chunks/db.js-BTsRdpsi.js';
14
14
  import 'node:sqlite';
15
15
  import '../../../../../chunks/dsh-events.js-DEZgATm-.js';
16
16
  import '../../../../../chunks/shared.js-HQ0rfhm4.js';
@@ -98,4 +98,4 @@ var POST = async ({ request }) => {
98
98
  };
99
99
 
100
100
  export { POST };
101
- //# sourceMappingURL=_server.ts.js-a85F1GOf.js.map
101
+ //# sourceMappingURL=_server.ts.js-Bhou8F5X.js.map