@insitue/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,973 @@
1
+ import {
2
+ A,
3
+ PROTOCOL_VERSION,
4
+ R,
5
+ beginPick,
6
+ buildBundle,
7
+ d,
8
+ installRuntimeCollectors,
9
+ k,
10
+ runtimeErrorCount,
11
+ y
12
+ } from "./chunk-6SMY7D6U.js";
13
+
14
+ // src/client.ts
15
+ var CompanionClient = class {
16
+ constructor(port, events) {
17
+ this.port = port;
18
+ this.events = events;
19
+ this.base = `http://127.0.0.1:${port}`;
20
+ this.wsUrl = `ws://127.0.0.1:${port}`;
21
+ }
22
+ port;
23
+ events;
24
+ ws = null;
25
+ base;
26
+ wsUrl;
27
+ pending = /* @__PURE__ */ new Map();
28
+ async connect() {
29
+ this.events.onState("connecting");
30
+ let token;
31
+ try {
32
+ const res = await fetch(`${this.base}/insitu/handshake`);
33
+ if (!res.ok) throw new Error(`handshake ${res.status}`);
34
+ token = (await res.json()).token;
35
+ } catch (e) {
36
+ this.events.onState("error", `companion unreachable (run \`npx insitue\`)`);
37
+ return;
38
+ }
39
+ await new Promise((done) => {
40
+ const ws = new WebSocket(this.wsUrl);
41
+ this.ws = ws;
42
+ ws.onopen = () => ws.send(
43
+ JSON.stringify({ t: "hello", protocolVersion: PROTOCOL_VERSION, token })
44
+ );
45
+ ws.onmessage = (ev) => {
46
+ const msg = JSON.parse(ev.data);
47
+ if (msg.t === "hello-ok") {
48
+ this.events.onState("connected", `companion ${msg.companionVersion}`);
49
+ done();
50
+ } else if (msg.t === "pong") {
51
+ const cb = this.pending.get(msg.nonce);
52
+ if (cb) {
53
+ this.pending.delete(msg.nonce);
54
+ cb(performance.now() - Number(msg.nonce.split(":")[1]));
55
+ }
56
+ } else if (msg.t === "agent-status") {
57
+ this.events.onAgentStatus?.({
58
+ ready: msg.ready,
59
+ transport: msg.transport,
60
+ warnings: msg.warnings,
61
+ blockers: msg.blockers
62
+ });
63
+ } else if (msg.t === "agent-stream") {
64
+ this.events.onAgentEvent?.(msg.event);
65
+ } else if (msg.t === "changeset-proposed") {
66
+ this.events.onChangeset?.(msg.turnId, msg.files);
67
+ } else if (msg.t === "changeset-applied") {
68
+ this.events.onApplied?.(
69
+ msg.turnId,
70
+ msg.files,
71
+ msg.checkpointRef
72
+ );
73
+ } else if (msg.t === "agent-undone") {
74
+ this.events.onUndone?.(msg.turnId, msg.restored);
75
+ } else if (msg.t === "agent-session-undone") {
76
+ this.events.onSessionUndone?.(msg.restored);
77
+ } else if (msg.t === "agent-session-committed") {
78
+ this.events.onSessionCommitted?.(msg.commit, msg.files);
79
+ } else if (msg.t === "capture-resolved") {
80
+ this.events.onResolved?.(msg.id, msg.resolved, msg.note);
81
+ } else if (msg.t === "error") {
82
+ this.events.onState("error", `${msg.code}: ${msg.message}`);
83
+ done();
84
+ }
85
+ };
86
+ ws.onerror = () => {
87
+ this.events.onState("error", "websocket error");
88
+ done();
89
+ };
90
+ ws.onclose = () => {
91
+ if (this.ws === ws) this.events.onState("idle");
92
+ };
93
+ });
94
+ }
95
+ ping() {
96
+ return new Promise((resolve, reject) => {
97
+ const ws = this.ws;
98
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
99
+ reject(new Error("not connected"));
100
+ return;
101
+ }
102
+ const nonce = `n:${performance.now()}:${Math.random()}`;
103
+ this.pending.set(nonce, resolve);
104
+ ws.send(JSON.stringify({ t: "ping", nonce }));
105
+ setTimeout(() => {
106
+ if (this.pending.delete(nonce)) reject(new Error("ping timeout"));
107
+ }, 3e3);
108
+ });
109
+ }
110
+ submitCapture(bundle) {
111
+ const ws = this.ws;
112
+ if (!ws || ws.readyState !== WebSocket.OPEN) return false;
113
+ ws.send(JSON.stringify({ t: "capture", bundle }));
114
+ return true;
115
+ }
116
+ sendTurn(turnId, bundleId, userMessage) {
117
+ const ws = this.ws;
118
+ if (!ws || ws.readyState !== WebSocket.OPEN) return false;
119
+ ws.send(
120
+ JSON.stringify({ t: "agent-turn", turnId, bundleId, userMessage })
121
+ );
122
+ return true;
123
+ }
124
+ cancelTurn(turnId) {
125
+ this.ws?.send(JSON.stringify({ t: "agent-cancel", turnId }));
126
+ }
127
+ sendDecision(turnId, decision, files, reason) {
128
+ this.ws?.send(
129
+ JSON.stringify({ t: "agent-decision", turnId, decision, files, reason })
130
+ );
131
+ }
132
+ sendUndo(turnId) {
133
+ this.ws?.send(JSON.stringify({ t: "agent-undo", turnId }));
134
+ }
135
+ sendUndoSession() {
136
+ this.ws?.send(JSON.stringify({ t: "agent-undo-session" }));
137
+ }
138
+ sendCommitSession(message) {
139
+ this.ws?.send(JSON.stringify({ t: "agent-commit-session", message }));
140
+ }
141
+ dispose() {
142
+ this.ws?.close();
143
+ this.ws = null;
144
+ }
145
+ };
146
+
147
+ // src/overlay.ts
148
+ var DOT = {
149
+ idle: "#888",
150
+ connecting: "#e0a30c",
151
+ connected: "#2fd16b",
152
+ error: "#ff6b6b"
153
+ };
154
+ var muted = "#8a8a93";
155
+ var accent = "#ff6b00";
156
+ var mono = "12px ui-monospace, SFMono-Regular, Menlo, monospace";
157
+ var btn = "font:inherit;color:#ff6b00;background:transparent;border:1px solid #2e2e3c;border-radius:4px;padding:3px 8px;cursor:pointer";
158
+ var card = "background:#0b0b0d;border:1px solid #232330;border-radius:4px";
159
+ function row(label, value) {
160
+ return k("div", { style: "display:flex;gap:8px;margin:2px 0" }, [
161
+ k("span", { style: `color:${muted};min-width:96px` }, label),
162
+ k("span", { style: "color:#ececef;word-break:break-word" }, value)
163
+ ]);
164
+ }
165
+ function diffLines(diff) {
166
+ return diff.split("\n").map((ln) => {
167
+ let color = muted;
168
+ if (ln.startsWith("@@")) color = "#5fb3e0";
169
+ else if (ln.startsWith("+++") || ln.startsWith("---")) color = muted;
170
+ else if (ln.startsWith("+")) color = "#5fd18a";
171
+ else if (ln.startsWith("-")) color = "#ff7a7a";
172
+ else color = "#bfbfc6";
173
+ return k("div", { style: `color:${color};white-space:pre` }, ln || " ");
174
+ });
175
+ }
176
+ function diffBlock(changes) {
177
+ return changes.map(
178
+ (c) => k("div", { style: "margin:6px 0" }, [
179
+ k(
180
+ "div",
181
+ { style: "color:#ececef;margin-bottom:2px" },
182
+ `${c.file} (${c.bytes}B)`
183
+ ),
184
+ k(
185
+ "div",
186
+ {
187
+ style: `overflow:auto;${card};padding:8px;max-height:200px;font-size:11px;line-height:1.45`
188
+ },
189
+ diffLines(c.diff)
190
+ )
191
+ ])
192
+ );
193
+ }
194
+ function App(props) {
195
+ const [state, setState] = d("idle");
196
+ const [detail, setDetail] = d("");
197
+ const [client, setClient] = d(null);
198
+ const [bundle, setBundle] = d(null);
199
+ const [resolved, setResolved] = d(null);
200
+ const [busy, setBusy] = d(false);
201
+ const [open, setOpen] = d(false);
202
+ const [showCtx, setShowCtx] = d(false);
203
+ const [showSettings, setShowSettings] = d(false);
204
+ const [autoApply, setAutoApply] = d(false);
205
+ const [agentReady, setAgentReady] = d(null);
206
+ const [agentNote, setAgentNote] = d("");
207
+ const [chatInput, setChatInput] = d("");
208
+ const [messages, setMessages] = d([]);
209
+ const [turnBusy, setTurnBusy] = d(false);
210
+ const [thinking, setThinking] = d("");
211
+ const [activity, setActivity] = d("");
212
+ const [elapsed, setElapsed] = d(0);
213
+ const [pulse, setPulse] = d(0);
214
+ const [changes, setChanges] = d([]);
215
+ const [changeTurnId, setChangeTurnId] = d("");
216
+ const [picked, setPicked] = d({});
217
+ const [rejectReason, setRejectReason] = d("");
218
+ const [history, setHistory] = d([]);
219
+ const [revisitId, setRevisitId] = d("");
220
+ const [commitMsg, setCommitMsg] = d("");
221
+ const [sessionNote, setSessionNote] = d("");
222
+ const [lastSel, setLastSel] = d(null);
223
+ const [activeTurn, setActiveTurn] = d(null);
224
+ const autoApplyRef = A(false);
225
+ const changesRef = A([]);
226
+ const activeTurnRef = A(null);
227
+ const threadRef = A(null);
228
+ autoApplyRef.current = autoApply;
229
+ changesRef.current = changes;
230
+ activeTurnRef.current = activeTurn;
231
+ const appendAgent = (delta) => setMessages((ms) => {
232
+ const last = ms[ms.length - 1];
233
+ if (last && last.role === "agent") {
234
+ return [...ms.slice(0, -1), { role: "agent", text: last.text + delta }];
235
+ }
236
+ return [...ms, { role: "agent", text: delta }];
237
+ });
238
+ y(() => {
239
+ installRuntimeCollectors();
240
+ const c = new CompanionClient(props.port, {
241
+ onState: (s, d2) => {
242
+ setState(s);
243
+ if (d2 !== void 0) setDetail(d2);
244
+ },
245
+ onResolved: (_id, r) => setResolved(r),
246
+ onAgentStatus: (s) => {
247
+ setAgentReady(s.ready);
248
+ setAgentNote(
249
+ s.blockers.length ? `blocked: ${s.blockers.join("; ")}` : s.warnings.join(" \xB7 ") || `agent ready (${s.transport})`
250
+ );
251
+ },
252
+ onAgentEvent: (e) => {
253
+ if (e.t === "agent-text") {
254
+ setThinking("");
255
+ setActivity("");
256
+ appendAgent(e.delta);
257
+ } else if (e.t === "agent-thinking") {
258
+ setThinking(e.note.slice(-160));
259
+ } else if (e.t === "agent-activity") {
260
+ setActivity(e.label);
261
+ } else if (e.t === "agent-turn-complete") {
262
+ setThinking("");
263
+ setActivity("");
264
+ setTurnBusy(false);
265
+ } else if (e.t === "agent-error") {
266
+ setThinking("");
267
+ setActivity("");
268
+ appendAgent(`
269
+
270
+ [agent-error] ${e.message}`);
271
+ setTurnBusy(false);
272
+ }
273
+ },
274
+ onChangeset: (turnId, files) => {
275
+ setChangeTurnId(turnId);
276
+ setChanges(files);
277
+ setPicked(Object.fromEntries(files.map((f) => [f.file, true])));
278
+ setRejectReason("");
279
+ if (autoApplyRef.current) {
280
+ appendAgent(`
281
+ [insitu] auto-apply on \u2014 writing without review`);
282
+ c.sendDecision(turnId, "approve");
283
+ }
284
+ },
285
+ onApplied: (turnId, files, ref) => {
286
+ const at = activeTurnRef.current;
287
+ const entry = {
288
+ turnId,
289
+ prompt: at && at.turnId === turnId ? at.prompt : "(applied change)",
290
+ sel: at && at.turnId === turnId ? at.sel : null,
291
+ files,
292
+ checkpointRef: ref,
293
+ diff: changesRef.current,
294
+ status: "applied",
295
+ note: "HMR reloading\u2026",
296
+ postError: false
297
+ };
298
+ setHistory((hs) => [entry, ...hs]);
299
+ setMessages((ms) => [
300
+ ...ms,
301
+ {
302
+ role: "agent",
303
+ text: `\u2713 applied ${files.length} file${files.length > 1 ? "s" : ""} \u2192 filed to Session (${ref})`
304
+ }
305
+ ]);
306
+ setChanges([]);
307
+ setChangeTurnId("");
308
+ setActiveTurn(null);
309
+ setSessionNote("");
310
+ const base = runtimeErrorCount();
311
+ let ticks = 0;
312
+ const iv = setInterval(() => {
313
+ ticks++;
314
+ const threw = runtimeErrorCount() > base;
315
+ if (threw || ticks >= 10) {
316
+ setHistory(
317
+ (hs) => hs.map(
318
+ (en) => en.turnId === turnId ? {
319
+ ...en,
320
+ note: threw ? "\u26A0 host threw after HMR" : "HMR settled clean",
321
+ postError: threw
322
+ } : en
323
+ )
324
+ );
325
+ clearInterval(iv);
326
+ }
327
+ }, 500);
328
+ },
329
+ onUndone: (turnId) => setHistory(
330
+ (hs) => hs.map(
331
+ (en) => en.turnId === turnId ? { ...en, status: "undone", note: "undone \u2014 HMR reverting" } : en
332
+ )
333
+ ),
334
+ onSessionUndone: () => setHistory(
335
+ (hs) => hs.map(
336
+ (en) => en.status === "applied" ? { ...en, status: "undone" } : en
337
+ )
338
+ ),
339
+ onSessionCommitted: (commit) => {
340
+ setHistory(
341
+ (hs) => hs.map(
342
+ (en) => en.status === "applied" ? { ...en, status: "committed" } : en
343
+ )
344
+ );
345
+ setSessionNote(`committed as ${commit} (local only \u2014 not pushed)`);
346
+ }
347
+ });
348
+ setClient(c);
349
+ void c.connect();
350
+ return () => c.dispose();
351
+ }, [props.port]);
352
+ y(() => {
353
+ if (!turnBusy) {
354
+ setElapsed(0);
355
+ setPulse(0);
356
+ return;
357
+ }
358
+ const t0 = Date.now();
359
+ const a = setInterval(
360
+ () => setElapsed(Math.floor((Date.now() - t0) / 1e3)),
361
+ 1e3
362
+ );
363
+ const b = setInterval(() => setPulse((p) => p + 1), 300);
364
+ return () => {
365
+ clearInterval(a);
366
+ clearInterval(b);
367
+ };
368
+ }, [turnBusy]);
369
+ y(() => {
370
+ const el = threadRef.current;
371
+ if (el) el.scrollTop = el.scrollHeight;
372
+ }, [messages, changes, turnBusy, activity]);
373
+ const captureSel = async (sel) => {
374
+ setLastSel(sel);
375
+ const b = await buildBundle(sel);
376
+ setBundle(b);
377
+ setResolved(null);
378
+ setMessages([]);
379
+ setChanges([]);
380
+ setRevisitId("");
381
+ setOpen(true);
382
+ setShowCtx(false);
383
+ client?.submitCapture(b);
384
+ return b;
385
+ };
386
+ const pick = async (mode) => {
387
+ if (!client || state !== "connected") return;
388
+ setBusy(true);
389
+ try {
390
+ const sel = await beginPick(mode);
391
+ if (sel) await captureSel(sel);
392
+ } finally {
393
+ setBusy(false);
394
+ }
395
+ };
396
+ const sendChat = () => {
397
+ if (!client || !bundle || !chatInput.trim() || turnBusy) return;
398
+ const turnId = bundle.id;
399
+ const text = chatInput.trim();
400
+ setActiveTurn({ turnId, prompt: text, sel: lastSel });
401
+ setMessages((ms) => [...ms, { role: "user", text }]);
402
+ setChatInput("");
403
+ setChanges([]);
404
+ setRevisitId("");
405
+ setActivity("starting");
406
+ setTurnBusy(true);
407
+ client.sendTurn(turnId, bundle.id, text);
408
+ };
409
+ const continueFrom = async (e) => {
410
+ if (!client || !e.sel || busy || turnBusy) return;
411
+ setBusy(true);
412
+ try {
413
+ await captureSel(e.sel);
414
+ setChatInput("");
415
+ } finally {
416
+ setBusy(false);
417
+ }
418
+ };
419
+ const recaptureFix = async (e) => {
420
+ if (!client || !e.sel || busy || turnBusy) return;
421
+ setBusy(true);
422
+ try {
423
+ const b = await captureSel(e.sel);
424
+ if (b.runtime.errors.length > 0) {
425
+ const turnId = b.id;
426
+ const text = "fix the runtime error from the previous change";
427
+ setActiveTurn({ turnId, prompt: text, sel: e.sel });
428
+ setMessages([{ role: "user", text }]);
429
+ setActivity("starting");
430
+ setTurnBusy(true);
431
+ client.sendTurn(
432
+ turnId,
433
+ b.id,
434
+ "Your previous edit was applied but the running app now reports the runtime errors shown in the context above. Diagnose the cause and propose a corrected edit."
435
+ );
436
+ } else {
437
+ setMessages([
438
+ { role: "agent", text: "[insitu] re-captured \u2014 no runtime errors" }
439
+ ]);
440
+ }
441
+ } finally {
442
+ setBusy(false);
443
+ }
444
+ };
445
+ const approve = () => {
446
+ const chosen = changes.map((c) => c.file).filter((f) => picked[f] !== false);
447
+ client?.sendDecision(
448
+ changeTurnId,
449
+ "approve",
450
+ chosen.length === changes.length ? void 0 : chosen
451
+ );
452
+ };
453
+ const reject = () => {
454
+ client?.sendDecision(
455
+ changeTurnId,
456
+ "reject",
457
+ void 0,
458
+ rejectReason.trim() || void 0
459
+ );
460
+ setChanges([]);
461
+ setMessages((ms) => [
462
+ ...ms,
463
+ { role: "agent", text: "[insitu] changes rejected" }
464
+ ]);
465
+ };
466
+ const t = bundle?.target;
467
+ const targetSummary = t ? `${t.componentStack[0]?.name ?? t.selector.split(">").pop()?.trim() ?? "selection"} \xB7 ${t.confidence}` : "no selection";
468
+ const appliedCount = history.filter((e) => e.status === "applied").length;
469
+ const revisit = history.find((e) => e.turnId === revisitId) || null;
470
+ const spin = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
471
+ const pill = {
472
+ position: "fixed",
473
+ bottom: "16px",
474
+ right: "16px",
475
+ zIndex: 2147483e3,
476
+ display: "flex",
477
+ alignItems: "center",
478
+ gap: "10px",
479
+ padding: "8px 12px",
480
+ font: mono,
481
+ color: "#ececef",
482
+ background: "rgba(15,15,18,0.94)",
483
+ border: "1px solid #2e2e3c",
484
+ borderRadius: "6px",
485
+ boxShadow: "0 6px 24px rgba(0,0,0,0.45)"
486
+ };
487
+ const ctx = bundle && showCtx ? k("div", { style: "margin:8px 0;color:#bfbfc6" }, [
488
+ row("confidence", t?.confidence ?? "\u2014"),
489
+ row("selector", t?.selector ?? "\u2014"),
490
+ row(
491
+ "components",
492
+ t?.componentStack.map((c) => c.name).join(" < ") || "\u2014"
493
+ ),
494
+ row("tailwind", bundle.tailwindClasses.join(" ") || "\u2014"),
495
+ row("styles", `${Object.keys(bundle.computedStyles).length} props`),
496
+ row(
497
+ "runtime",
498
+ `${bundle.runtime.console.length} log \xB7 ${bundle.runtime.network.length} net \xB7 ${bundle.runtime.errors.length} err`
499
+ ),
500
+ row(
501
+ "screenshot",
502
+ bundle.screenshot ? "captured" : bundle.screenshotUnavailable ? `unavailable \u2014 ${bundle.screenshotUnavailable}` : "\u2014"
503
+ ),
504
+ bundle.screenshot ? k("img", {
505
+ src: bundle.screenshot.dataUrl,
506
+ style: `max-width:100%;margin:8px 0;${card}`
507
+ }) : null,
508
+ resolved ? k(
509
+ "pre",
510
+ {
511
+ style: `white-space:pre;overflow:auto;${card};padding:10px;margin:6px 0 0;color:#bfbfc6;max-height:160px`
512
+ },
513
+ `${resolved.file}:${resolved.line}
514
+
515
+ ${resolved.snippet}`
516
+ ) : null
517
+ ]) : null;
518
+ const workingRow = turnBusy && k(
519
+ "div",
520
+ {
521
+ style: `display:flex;gap:8px;align-items:center;margin:6px 0 0;color:${accent}`
522
+ },
523
+ [
524
+ k("span", {}, spin[pulse % spin.length]),
525
+ k(
526
+ "span",
527
+ {
528
+ style: `color:${muted};overflow:hidden;text-overflow:ellipsis;white-space:nowrap`
529
+ },
530
+ `${activity || "working"} \xB7 ${elapsed}s`
531
+ ),
532
+ k(
533
+ "button",
534
+ {
535
+ style: `${btn};margin-left:auto`,
536
+ onClick: () => {
537
+ client?.cancelTurn("cur");
538
+ setTurnBusy(false);
539
+ }
540
+ },
541
+ "stop"
542
+ )
543
+ ]
544
+ );
545
+ const thread = k(
546
+ "div",
547
+ {
548
+ ref: threadRef,
549
+ style: `max-height:300px;overflow:auto;margin:6px 0;display:flex;flex-direction:column;gap:6px`
550
+ },
551
+ [
552
+ ...messages.map(
553
+ (m) => k(
554
+ "div",
555
+ {
556
+ style: m.role === "user" ? `align-self:flex-end;max-width:88%;${card};border-color:#2e2e3c;padding:6px 8px;color:#ececef;white-space:pre-wrap;word-break:break-word` : `align-self:flex-start;max-width:96%;padding:6px 8px;color:#ececef;white-space:pre-wrap;word-break:break-word`
557
+ },
558
+ m.text
559
+ )
560
+ ),
561
+ thinking && turnBusy ? k(
562
+ "div",
563
+ {
564
+ style: `align-self:flex-start;color:${muted};font-style:italic;white-space:pre-wrap;word-break:break-word`
565
+ },
566
+ `\u{1F4AD} ${thinking}`
567
+ ) : null
568
+ ]
569
+ );
570
+ const proposed = changes.length ? k("div", { style: "margin-top:8px" }, [
571
+ k(
572
+ "div",
573
+ {
574
+ style: `color:${accent};margin-bottom:4px;display:flex;justify-content:space-between;align-items:center`
575
+ },
576
+ [
577
+ k(
578
+ "span",
579
+ {},
580
+ `PROPOSED \xB7 ${changes.length} file${changes.length > 1 ? "s" : ""}`
581
+ ),
582
+ k(
583
+ "span",
584
+ { style: "display:flex;gap:6px;align-items:center" },
585
+ [
586
+ k(
587
+ "button",
588
+ { style: btn, onClick: approve },
589
+ "Approve & write"
590
+ ),
591
+ k(
592
+ "button",
593
+ { style: `${btn};color:${muted}`, onClick: reject },
594
+ "Reject"
595
+ )
596
+ ]
597
+ )
598
+ ]
599
+ ),
600
+ k("input", {
601
+ value: rejectReason,
602
+ placeholder: "reject reason (optional) \u2014 fed to the agent",
603
+ onInput: (ev) => setRejectReason(ev.target.value),
604
+ style: `width:100%;box-sizing:border-box;font:inherit;color:#ececef;${card};padding:5px 7px;margin:4px 0 6px`
605
+ }),
606
+ ...changes.map(
607
+ (c) => k("div", { style: "margin:6px 0" }, [
608
+ k(
609
+ "label",
610
+ {
611
+ style: "color:#ececef;margin-bottom:2px;display:flex;gap:6px;align-items:center;cursor:pointer"
612
+ },
613
+ [
614
+ k("input", {
615
+ type: "checkbox",
616
+ checked: picked[c.file] !== false,
617
+ onChange: (ev) => setPicked((p) => ({
618
+ ...p,
619
+ [c.file]: ev.target.checked
620
+ }))
621
+ }),
622
+ k("span", {}, `${c.file} (${c.bytes}B)`)
623
+ ]
624
+ ),
625
+ k(
626
+ "div",
627
+ {
628
+ style: `overflow:auto;${card};padding:8px;max-height:200px;font-size:11px;line-height:1.45`
629
+ },
630
+ diffLines(c.diff)
631
+ )
632
+ ])
633
+ )
634
+ ]) : null;
635
+ const conversation = revisit ? k(
636
+ "div",
637
+ { style: `margin-top:8px;border-top:1px solid #232330;padding-top:8px` },
638
+ [
639
+ k(
640
+ "div",
641
+ {
642
+ style: `color:${accent};margin-bottom:6px;display:flex;justify-content:space-between;align-items:center`
643
+ },
644
+ [
645
+ k("span", {}, `REVISIT \xB7 ${revisit.prompt}`.slice(0, 52)),
646
+ k(
647
+ "button",
648
+ { style: btn, onClick: () => setRevisitId("") },
649
+ "close"
650
+ )
651
+ ]
652
+ ),
653
+ k(
654
+ "div",
655
+ { style: `color:${muted};margin-bottom:6px` },
656
+ `${revisit.status} \xB7 ${revisit.files.join(", ")} \xB7 ${revisit.checkpointRef}`
657
+ ),
658
+ ...diffBlock(revisit.diff)
659
+ ]
660
+ ) : bundle ? k(
661
+ "div",
662
+ {
663
+ style: `margin-top:8px;border-top:1px solid #232330;padding-top:8px`
664
+ },
665
+ [
666
+ k(
667
+ "div",
668
+ {
669
+ style: `color:${accent};margin-bottom:6px;display:flex;justify-content:space-between`
670
+ },
671
+ [
672
+ k("span", {}, "ASK"),
673
+ autoApply ? k("span", { style: `color:${accent}` }, "auto-apply ON") : null
674
+ ]
675
+ ),
676
+ agentReady === false ? k(
677
+ "div",
678
+ { style: "color:#ff6b6b;margin-bottom:6px" },
679
+ agentNote
680
+ ) : agentNote && !messages.length ? k(
681
+ "div",
682
+ { style: `color:${muted};margin-bottom:6px` },
683
+ agentNote
684
+ ) : null,
685
+ messages.length ? thread : null,
686
+ workingRow,
687
+ proposed,
688
+ k("textarea", {
689
+ value: chatInput,
690
+ placeholder: messages.length ? "reply\u2026 (the agent remembers this thread)" : "what does this do? \xB7 make the padding bigger \xB7 fix this bug",
691
+ rows: 2,
692
+ onInput: (ev) => setChatInput(ev.target.value),
693
+ onKeyDown: (ev) => {
694
+ if ((ev.metaKey || ev.ctrlKey) && ev.key === "Enter")
695
+ sendChat();
696
+ },
697
+ style: `width:100%;box-sizing:border-box;font:inherit;color:#ececef;${card};padding:8px;margin-top:8px;resize:vertical`
698
+ }),
699
+ k(
700
+ "div",
701
+ { style: "margin-top:6px" },
702
+ k(
703
+ "button",
704
+ {
705
+ style: btn,
706
+ disabled: turnBusy || !chatInput.trim() || agentReady === false,
707
+ onClick: sendChat
708
+ },
709
+ turnBusy ? `\u2026working ${elapsed}s` : "Send (\u2318\u21B5)"
710
+ )
711
+ )
712
+ ]
713
+ ) : null;
714
+ const timeline = history.length ? k(
715
+ "div",
716
+ { style: `margin-top:8px;border-top:1px solid #232330;padding-top:8px` },
717
+ [
718
+ k(
719
+ "div",
720
+ {
721
+ style: `color:${accent};margin-bottom:6px;display:flex;justify-content:space-between;align-items:center`
722
+ },
723
+ [
724
+ k("span", {}, `SESSION \xB7 ${appliedCount} live`),
725
+ appliedCount > 0 ? k(
726
+ "button",
727
+ {
728
+ style: `${btn};color:${muted}`,
729
+ onClick: () => client?.sendUndoSession()
730
+ },
731
+ "Undo all"
732
+ ) : null
733
+ ]
734
+ ),
735
+ ...history.map((e) => {
736
+ const glyph = e.status === "applied" ? "\u2713" : e.status === "committed" ? "\u25C6" : "\u293A";
737
+ const dim = e.status !== "applied";
738
+ return k(
739
+ "div",
740
+ {
741
+ style: `${card};padding:6px 8px;margin:4px 0;${dim ? "opacity:0.6;" : ""}`
742
+ },
743
+ [
744
+ k(
745
+ "div",
746
+ {
747
+ style: "display:flex;justify-content:space-between;gap:8px;align-items:center"
748
+ },
749
+ [
750
+ k(
751
+ "span",
752
+ {
753
+ style: "color:#ececef;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
754
+ },
755
+ `${glyph} ${e.prompt}`
756
+ ),
757
+ k("span", { style: "display:flex;gap:6px;flex:none" }, [
758
+ k(
759
+ "button",
760
+ { style: btn, onClick: () => setRevisitId(e.turnId) },
761
+ "Revisit"
762
+ ),
763
+ e.sel ? k(
764
+ "button",
765
+ { style: btn, onClick: () => void continueFrom(e) },
766
+ "Continue"
767
+ ) : null,
768
+ e.status === "applied" ? k(
769
+ "button",
770
+ {
771
+ style: btn,
772
+ onClick: () => client?.sendUndo(e.turnId)
773
+ },
774
+ "Undo"
775
+ ) : null
776
+ ])
777
+ ]
778
+ ),
779
+ k(
780
+ "div",
781
+ { style: `color:${muted};margin-top:3px` },
782
+ `${e.files.join(", ")} \xB7 ${e.checkpointRef}`
783
+ ),
784
+ e.note ? k(
785
+ "div",
786
+ {
787
+ style: `margin-top:3px;color:${e.postError ? "#ff7a7a" : "#5fd18a"};display:flex;justify-content:space-between;align-items:center`
788
+ },
789
+ [
790
+ k("span", {}, e.note),
791
+ e.postError && e.sel ? k(
792
+ "button",
793
+ {
794
+ style: `${btn};color:#0f0f12;background:${accent};border-color:${accent}`,
795
+ onClick: () => void recaptureFix(e)
796
+ },
797
+ "\u26A0 Re-capture & fix"
798
+ ) : null
799
+ ]
800
+ ) : null
801
+ ]
802
+ );
803
+ }),
804
+ appliedCount > 0 ? k(
805
+ "div",
806
+ {
807
+ style: "display:flex;gap:6px;align-items:center;margin-top:6px"
808
+ },
809
+ [
810
+ k("input", {
811
+ value: commitMsg,
812
+ placeholder: "commit message (optional)",
813
+ onInput: (ev) => setCommitMsg(ev.target.value),
814
+ style: `flex:1;box-sizing:border-box;font:inherit;color:#ececef;${card};padding:5px 7px`
815
+ }),
816
+ k(
817
+ "button",
818
+ {
819
+ style: btn,
820
+ onClick: () => client?.sendCommitSession(
821
+ commitMsg.trim() || void 0
822
+ )
823
+ },
824
+ "Commit (local)"
825
+ )
826
+ ]
827
+ ) : null,
828
+ sessionNote ? k("div", { style: "color:#5fd18a;margin-top:6px" }, sessionNote) : null
829
+ ]
830
+ ) : null;
831
+ const settings = showSettings ? k("div", { style: `${card};padding:10px;margin:8px 0` }, [
832
+ k(
833
+ "label",
834
+ {
835
+ style: "display:flex;gap:8px;align-items:center;cursor:pointer;color:#ececef"
836
+ },
837
+ [
838
+ k("input", {
839
+ type: "checkbox",
840
+ checked: autoApply,
841
+ onChange: (ev) => setAutoApply(ev.target.checked)
842
+ }),
843
+ k("span", {}, "Auto-apply (skip review)")
844
+ ]
845
+ ),
846
+ k(
847
+ "div",
848
+ { style: `color:${muted};margin-top:4px` },
849
+ "Writes proposed changes immediately. Still checkpointed & undoable; no manual gate. Resets on reload."
850
+ )
851
+ ]) : null;
852
+ const panel = open ? k(
853
+ "div",
854
+ {
855
+ style: {
856
+ position: "fixed",
857
+ bottom: "64px",
858
+ right: "16px",
859
+ width: "440px",
860
+ maxHeight: "82vh",
861
+ overflow: "auto",
862
+ zIndex: 2147483e3,
863
+ font: mono,
864
+ color: "#ececef",
865
+ background: "rgba(15,15,18,0.97)",
866
+ border: "1px solid #2e2e3c",
867
+ borderRadius: "8px",
868
+ padding: "12px 14px",
869
+ boxShadow: "0 10px 36px rgba(0,0,0,0.55)"
870
+ }
871
+ },
872
+ [
873
+ k(
874
+ "div",
875
+ {
876
+ style: "display:flex;justify-content:space-between;align-items:center;gap:8px"
877
+ },
878
+ [
879
+ k(
880
+ "span",
881
+ {
882
+ style: `color:${accent};overflow:hidden;text-overflow:ellipsis;white-space:nowrap`
883
+ },
884
+ targetSummary
885
+ ),
886
+ k("span", { style: "display:flex;gap:6px;flex:none" }, [
887
+ bundle ? k(
888
+ "button",
889
+ { style: btn, onClick: () => setShowCtx((v) => !v) },
890
+ showCtx ? "details \u25BE" : "details \u25B8"
891
+ ) : null,
892
+ k(
893
+ "button",
894
+ { style: btn, onClick: () => setShowSettings((v) => !v) },
895
+ "\u2699"
896
+ ),
897
+ k(
898
+ "button",
899
+ { style: btn, onClick: () => setOpen(false) },
900
+ "close"
901
+ )
902
+ ])
903
+ ]
904
+ ),
905
+ settings,
906
+ ctx,
907
+ conversation,
908
+ timeline,
909
+ !bundle && !history.length ? k(
910
+ "div",
911
+ { style: `color:${muted};margin-top:10px` },
912
+ "Pick an element to start."
913
+ ) : null
914
+ ]
915
+ ) : null;
916
+ return k("div", {}, [
917
+ panel,
918
+ k("div", { style: pill }, [
919
+ k("span", {
920
+ style: `width:8px;height:8px;border-radius:50%;background:${DOT[state]};display:inline-block`
921
+ }),
922
+ k("strong", { style: "letter-spacing:0.08em" }, "InSitue"),
923
+ k(
924
+ "span",
925
+ {
926
+ style: `color:${muted};max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap`
927
+ },
928
+ turnBusy ? `${spin[pulse % spin.length]} ${activity || "working"}` : detail || state
929
+ ),
930
+ k(
931
+ "button",
932
+ {
933
+ disabled: state !== "connected" || busy,
934
+ onClick: () => pick("element"),
935
+ style: btn
936
+ },
937
+ busy ? "\u2026" : "Select"
938
+ ),
939
+ k(
940
+ "button",
941
+ {
942
+ disabled: state !== "connected" || busy,
943
+ onClick: () => pick("rect"),
944
+ style: btn
945
+ },
946
+ "Rect"
947
+ ),
948
+ bundle || history.length ? k(
949
+ "button",
950
+ { onClick: () => setOpen((v) => !v), style: btn },
951
+ open ? "hide" : "panel"
952
+ ) : null
953
+ ])
954
+ ]);
955
+ }
956
+ function mountInSitue(opts = {}) {
957
+ const host = document.createElement("div");
958
+ host.id = "insitu-root";
959
+ host.setAttribute("data-insitu", "");
960
+ document.body.appendChild(host);
961
+ const shadow = host.attachShadow({ mode: "open" });
962
+ const mountPoint = document.createElement("div");
963
+ shadow.appendChild(mountPoint);
964
+ R(k(App, { port: opts.port ?? 5747 }), mountPoint);
965
+ return () => {
966
+ R(null, mountPoint);
967
+ host.remove();
968
+ };
969
+ }
970
+
971
+ export {
972
+ mountInSitue
973
+ };