@insitue/sdk 0.1.14 → 0.3.1

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