@bojackduy/opencode-loopd 1.0.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.
package/dist/tui.js ADDED
@@ -0,0 +1,1480 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // src/tui/plugin.tsx
5
+ import { createComponent as _$createComponent2 } from "@opentui/solid";
6
+
7
+ // src/tui/dashboard.tsx
8
+ import { use as _$use } from "@opentui/solid";
9
+ import { effect as _$effect } from "@opentui/solid";
10
+ import { createComponent as _$createComponent } from "@opentui/solid";
11
+ import { memo as _$memo } from "@opentui/solid";
12
+ import { insert as _$insert } from "@opentui/solid";
13
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
14
+ import { insertNode as _$insertNode } from "@opentui/solid";
15
+ import { setProp as _$setProp } from "@opentui/solid";
16
+ import { createElement as _$createElement } from "@opentui/solid";
17
+ import { createSignal, For, Show, onCleanup, onMount, createEffect } from "solid-js";
18
+ import { useKeyboard } from "@opentui/solid";
19
+
20
+ // src/infrastructure/state-repository.ts
21
+ import { promises as fs } from "fs";
22
+ import path from "path";
23
+ var CURRENT_VERSION = 2;
24
+ function emptyState() {
25
+ return { version: CURRENT_VERSION, revision: 0, goals: [], runtimes: [], commandLedger: [] };
26
+ }
27
+ function loopDir(directory) {
28
+ return path.join(directory, ".opencode", "loopd");
29
+ }
30
+ function stateFile(directory) {
31
+ return path.join(loopDir(directory), "state.json");
32
+ }
33
+ function eventsFile(directory) {
34
+ return path.join(loopDir(directory), "events.ndjson");
35
+ }
36
+ async function readState(directory) {
37
+ const target = stateFile(directory);
38
+ const attempts = 5;
39
+ for (let attempt = 0;attempt < attempts; attempt++) {
40
+ try {
41
+ const raw = await fs.readFile(target, "utf8");
42
+ const parsed = JSON.parse(raw);
43
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.goals)) {
44
+ return migrate(parsed);
45
+ }
46
+ return emptyState();
47
+ } catch (error) {
48
+ if (error?.code === "ENOENT")
49
+ return emptyState();
50
+ const transient = error instanceof SyntaxError || error?.code === "EPERM" || error?.code === "EACCES" || error?.code === "EBUSY";
51
+ if (!transient || attempt === attempts - 1)
52
+ break;
53
+ await delay(25 * (attempt + 1));
54
+ }
55
+ }
56
+ return emptyState();
57
+ }
58
+ function migrate(state) {
59
+ if (state.version === CURRENT_VERSION)
60
+ return state;
61
+ let result = { ...state };
62
+ if (result.version < 2) {
63
+ result.version = 2;
64
+ if (!result.commandLedger)
65
+ result.commandLedger = [];
66
+ result.runtimes = result.runtimes.map((rt) => ({
67
+ ...rt,
68
+ progressDuringTurn: rt.progressDuringTurn ?? false
69
+ }));
70
+ result.goals = result.goals.map((g) => ({
71
+ ...g,
72
+ lastProgress: g.lastProgress ?? undefined,
73
+ completionEvidence: g.completionEvidence ?? undefined,
74
+ blocker: g.blocker ?? undefined
75
+ }));
76
+ }
77
+ return result;
78
+ }
79
+ async function writeAtomic(target, contents) {
80
+ const dir = path.dirname(target);
81
+ await fs.mkdir(dir, { recursive: true });
82
+ const temp = path.join(dir, `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);
83
+ await fs.writeFile(temp, contents, "utf8");
84
+ try {
85
+ for (let attempt = 0;attempt < 5; attempt++) {
86
+ try {
87
+ await fs.rename(temp, target);
88
+ return;
89
+ } catch (error) {
90
+ if (error?.code === "EXDEV")
91
+ break;
92
+ if (error?.code !== "EPERM" && error?.code !== "EACCES" && error?.code !== "EBUSY" && error?.code !== "EEXIST" && error?.code !== "EAGAIN")
93
+ throw error;
94
+ if (attempt < 4)
95
+ await delay(25 * (attempt + 1));
96
+ }
97
+ }
98
+ await fs.copyFile(temp, target);
99
+ } finally {
100
+ try {
101
+ await fs.rm(temp, { force: true });
102
+ } catch {}
103
+ }
104
+ }
105
+ async function readEvents(directory, limit = 50) {
106
+ try {
107
+ const raw = await fs.readFile(eventsFile(directory), "utf8");
108
+ const lines = raw.trim().split(`
109
+ `).filter(Boolean);
110
+ return lines.slice(-limit).map((l) => JSON.parse(l));
111
+ } catch {
112
+ return [];
113
+ }
114
+ }
115
+ function controlDir(directory) {
116
+ return path.join(loopDir(directory), "control");
117
+ }
118
+ function requestFile(directory, requestID) {
119
+ return path.join(controlDir(directory), "requests", `${requestID}.json`);
120
+ }
121
+ function responseFile(directory, requestID) {
122
+ return path.join(controlDir(directory), "responses", `${requestID}.json`);
123
+ }
124
+ async function writeControlRequest(directory, request) {
125
+ const dir = path.join(controlDir(directory), "requests");
126
+ await fs.mkdir(dir, { recursive: true });
127
+ await writeAtomic(requestFile(directory, request.requestID), JSON.stringify(request, null, 2));
128
+ }
129
+ async function readControlResponse(directory, requestID) {
130
+ try {
131
+ const raw = await fs.readFile(responseFile(directory, requestID), "utf8");
132
+ return JSON.parse(raw);
133
+ } catch {
134
+ return;
135
+ }
136
+ }
137
+ function delay(ms) {
138
+ return new Promise((resolve) => setTimeout(resolve, ms));
139
+ }
140
+
141
+ // src/infrastructure/control-client.ts
142
+ function createControlClient(directory) {
143
+ async function execute(command, timeoutMs = 30000) {
144
+ const request = {
145
+ requestID: command.requestID,
146
+ command: command.command,
147
+ goalID: command.goalID,
148
+ args: "args" in command ? command.args : undefined,
149
+ requestedAt: command.requestedAt
150
+ };
151
+ await writeControlRequest(directory, request);
152
+ const deadline = Date.now() + timeoutMs;
153
+ while (Date.now() < deadline) {
154
+ const response = await readControlResponse(directory, request.requestID);
155
+ if (response)
156
+ return response;
157
+ await delay2(100);
158
+ }
159
+ return {
160
+ requestID: request.requestID,
161
+ ok: false,
162
+ message: "timeout waiting for response",
163
+ errorCode: "timeout",
164
+ completedAt: new Date().toISOString()
165
+ };
166
+ }
167
+ async function getState() {
168
+ return readState(directory);
169
+ }
170
+ async function getEvents(limit) {
171
+ return readEvents(directory, limit);
172
+ }
173
+ return { execute, getState, getEvents };
174
+ }
175
+ function delay2(ms) {
176
+ return new Promise((resolve) => setTimeout(resolve, ms));
177
+ }
178
+
179
+ // src/tui/command-parser.ts
180
+ function parseCommand(input) {
181
+ const trimmed = input.trim();
182
+ if (!trimmed)
183
+ return null;
184
+ const tokens = tokenize(trimmed);
185
+ if (tokens.length === 0)
186
+ return null;
187
+ const command = tokens[0];
188
+ const args = {};
189
+ const positional = [];
190
+ for (let i = 1;i < tokens.length; i++) {
191
+ const token = tokens[i];
192
+ if (token.startsWith("--")) {
193
+ const eqIdx = token.indexOf("=");
194
+ if (eqIdx > 0) {
195
+ args[token.slice(2, eqIdx)] = token.slice(eqIdx + 1);
196
+ } else if (i + 1 < tokens.length && !tokens[i + 1].startsWith("--")) {
197
+ args[token.slice(2)] = tokens[++i];
198
+ } else {
199
+ args[token.slice(2)] = "true";
200
+ }
201
+ } else {
202
+ positional.push(token);
203
+ }
204
+ }
205
+ return { command, args, positional, raw: trimmed };
206
+ }
207
+ function tokenize(input) {
208
+ const tokens = [];
209
+ let current = "";
210
+ let inQuote = null;
211
+ let escape = false;
212
+ for (const char of input) {
213
+ if (escape) {
214
+ current += char;
215
+ escape = false;
216
+ continue;
217
+ }
218
+ if (char === "\\") {
219
+ escape = true;
220
+ continue;
221
+ }
222
+ if (inQuote) {
223
+ if (char === inQuote) {
224
+ inQuote = null;
225
+ } else {
226
+ current += char;
227
+ }
228
+ continue;
229
+ }
230
+ if (char === '"' || char === "'") {
231
+ inQuote = char;
232
+ continue;
233
+ }
234
+ if (char === " " || char === "\t") {
235
+ if (current) {
236
+ tokens.push(current);
237
+ current = "";
238
+ }
239
+ continue;
240
+ }
241
+ current += char;
242
+ }
243
+ if (current)
244
+ tokens.push(current);
245
+ return tokens;
246
+ }
247
+ function commandHelp() {
248
+ return [
249
+ "Modes: : insert \u2192 send/commands, Ctrl+N \u2192 normal, ? toggle help",
250
+ "Nav: j/k move \u2502 g/G top/bottom \u2502 o open child \u2502 p/r/R/x pause/resume/retry/clear \u2502 L logs \u2502 q close",
251
+ "Commands (insert mode, : prefix):",
252
+ " :send <message> Send instruction to selected goal",
253
+ " :open Open child session (same as o)",
254
+ " :force <summary> --evidence <text> Force-complete (bypass checks)",
255
+ " :block <reason> --needed <text> Force-block the selected goal",
256
+ " :pause / :resume / :retry / :clear Quick controls (also p/r/R/x)",
257
+ " :logs / :help / :q Toggle logs / help / close",
258
+ " Tip: create goals via /goal in the parent chat (agent clarifies first)."
259
+ ].join(`
260
+ `);
261
+ }
262
+
263
+ // src/tui/dashboard.tsx
264
+ import { randomUUID } from "crypto";
265
+ var LOG_FILE = "/tmp/loopd-tui.log";
266
+ function debugLog(...args) {
267
+ try {
268
+ const {
269
+ appendFileSync
270
+ } = __require("fs");
271
+ appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
272
+ `);
273
+ } catch {}
274
+ }
275
+ function prevent(evt) {
276
+ const e = evt;
277
+ e.preventDefault?.();
278
+ e.stopPropagation?.();
279
+ }
280
+ function statusColor(status, theme) {
281
+ switch (status) {
282
+ case "active":
283
+ return theme.success;
284
+ case "paused":
285
+ return theme.warning;
286
+ case "blocked":
287
+ return theme.error;
288
+ case "complete":
289
+ return theme.info;
290
+ case "budget_limited":
291
+ return theme.accent;
292
+ case "usage_limited":
293
+ return theme.accent;
294
+ default:
295
+ return theme.text;
296
+ }
297
+ }
298
+ function phaseColor(phase, theme) {
299
+ switch (phase) {
300
+ case "running":
301
+ return theme.success;
302
+ case "compacting":
303
+ return theme.warning;
304
+ case "waiting_retry":
305
+ return theme.accent;
306
+ case "stopping":
307
+ return theme.error;
308
+ default:
309
+ return theme.textMuted;
310
+ }
311
+ }
312
+ function borderColorForStatus(status, theme) {
313
+ switch (status) {
314
+ case "active":
315
+ return theme.success;
316
+ case "paused":
317
+ return theme.warning;
318
+ case "blocked":
319
+ return theme.error;
320
+ case "budget_limited":
321
+ case "usage_limited":
322
+ return theme.accent;
323
+ default:
324
+ return "gray";
325
+ }
326
+ }
327
+ function eventColor(type, theme) {
328
+ if (type === "goal.completed")
329
+ return theme.info;
330
+ if (type === "goal.blocked" || type === "run.failed")
331
+ return theme.error;
332
+ if (type === "goal.created" || type === "goal.progress")
333
+ return theme.success;
334
+ if (type === "run.started" || type === "compaction.started")
335
+ return theme.warning;
336
+ return theme.textMuted;
337
+ }
338
+ function phaseIcon(phase) {
339
+ switch (phase) {
340
+ case "running":
341
+ return "\u25B6";
342
+ case "compacting":
343
+ return "\u23F3";
344
+ case "waiting_retry":
345
+ return "\uD83D\uDD04";
346
+ case "stopping":
347
+ return "\u23F9";
348
+ case "queued":
349
+ return "\u25F7";
350
+ case "idle":
351
+ return "\u25CB";
352
+ default:
353
+ return "\u25CB";
354
+ }
355
+ }
356
+ function statusIcon(status) {
357
+ switch (status) {
358
+ case "active":
359
+ return "\u25CF";
360
+ case "paused":
361
+ return "\u275A\u275A";
362
+ case "blocked":
363
+ return "\u2716";
364
+ case "complete":
365
+ return "\u2713";
366
+ case "budget_limited":
367
+ return "\u2B22";
368
+ case "usage_limited":
369
+ return "\u23F0";
370
+ default:
371
+ return "\u25CB";
372
+ }
373
+ }
374
+ function ageLabel(timestamp, now) {
375
+ if (!timestamp)
376
+ return "never";
377
+ const seconds = Math.max(0, Math.floor((now - Date.parse(timestamp)) / 1000));
378
+ if (seconds < 60)
379
+ return `${seconds}s ago`;
380
+ const minutes = Math.floor(seconds / 60);
381
+ if (minutes < 60)
382
+ return `${minutes}m ago`;
383
+ return `${Math.floor(minutes / 60)}h ago`;
384
+ }
385
+ function LoopDashboard(props) {
386
+ const theme = () => props.api.theme.current;
387
+ const [mode, setMode] = createSignal("normal");
388
+ const [selected, setSelected] = createSignal(0);
389
+ const [commandInput, setCommandInput] = createSignal("");
390
+ const [statusText, setStatusText] = createSignal("Press : to send/command, ? help, o open, q close");
391
+ const [state, setState] = createSignal(null);
392
+ const [events, setEvents] = createSignal([]);
393
+ const [selectedGoal, setSelectedGoal] = createSignal(null);
394
+ const [showLogs, setShowLogs] = createSignal(false);
395
+ const [showHelp, setShowHelp] = createSignal(false);
396
+ const [clock, setClock] = createSignal(Date.now());
397
+ let inputEl;
398
+ let focusTimer;
399
+ const client = createControlClient(props.directory);
400
+ const popMode = props.api.mode.push("loopd.dashboard");
401
+ function focusInput() {
402
+ if (focusTimer)
403
+ clearTimeout(focusTimer);
404
+ focusTimer = setTimeout(() => {
405
+ const current = props.api.renderer.currentFocusedRenderable;
406
+ if (current && current !== inputEl)
407
+ current.blur();
408
+ inputEl?.focus();
409
+ }, 10);
410
+ }
411
+ async function refresh() {
412
+ try {
413
+ const s = await client.getState();
414
+ setState(s);
415
+ const goals2 = s.goals.filter((g) => g.status !== "complete");
416
+ if (goals2.length > 0 && selected() >= goals2.length)
417
+ setSelected(goals2.length - 1);
418
+ setSelectedGoal(goals2[selected()] || null);
419
+ setEvents(await client.getEvents(20));
420
+ } catch (e) {
421
+ setStatusText(`Error: ${e instanceof Error ? e.message : String(e)}`);
422
+ }
423
+ }
424
+ refresh();
425
+ const unsubs = [props.api.event.on("session.idle", () => refresh()), props.api.event.on("session.status", () => refresh()), props.api.event.on("session.error", () => refresh()), props.api.event.on("session.compacted", () => refresh()), setInterval(refresh, 1e4), setInterval(() => setClock(Date.now()), 500)];
426
+ onCleanup(() => {
427
+ popMode();
428
+ if (focusTimer)
429
+ clearTimeout(focusTimer);
430
+ for (const u of unsubs)
431
+ typeof u === "function" ? u() : clearInterval(u);
432
+ });
433
+ onMount(() => {
434
+ try {
435
+ const {
436
+ writeFileSync
437
+ } = __require("fs");
438
+ writeFileSync(LOG_FILE, `[${new Date().toISOString()}] dashboard mounted dir=${props.directory} mode=${mode()} dialogOpen=${props.api.ui.dialog.open}
439
+ `);
440
+ } catch {}
441
+ debugLog("mounted", "dialogOpen", props.api.ui.dialog.open, "directory", props.directory);
442
+ focusInput();
443
+ });
444
+ createEffect(() => {
445
+ const m = mode();
446
+ debugLog("mode ->", m);
447
+ focusInput();
448
+ });
449
+ function enterInsertMode() {
450
+ setCommandInput("");
451
+ if (inputEl)
452
+ inputEl.value = "";
453
+ setMode("insert");
454
+ focusInput();
455
+ }
456
+ function returnToNormalMode() {
457
+ setCommandInput("");
458
+ if (inputEl)
459
+ inputEl.value = "";
460
+ setMode("normal");
461
+ focusInput();
462
+ }
463
+ useKeyboard((evt) => {
464
+ const name = evt.name || "";
465
+ const seq = evt.sequence || "";
466
+ const raw = evt.raw || "";
467
+ debugLog("useKeyboard", `name=${name} seq=${JSON.stringify(seq)} raw=${JSON.stringify(raw)} shift=${evt.shift} ctrl=${evt.ctrl} mode=${mode()} dialogOpen=${props.api.ui.dialog.open}`);
468
+ if (!props.api.ui.dialog.open)
469
+ return;
470
+ const isColon = name === ":" || seq === ":" || raw === ":" || seq.includes(":") || raw.includes(":") || name === ";" || name === "colon";
471
+ const isQuestion = name === "?" || seq === "?" || raw === "?" || seq.includes("?") || raw.includes("?");
472
+ debugLog("isColon", isColon, "isQuestion", isQuestion, "modeBefore", mode());
473
+ if (mode() === "insert") {
474
+ if (evt.ctrl && name.toLowerCase() === "n") {
475
+ prevent(evt);
476
+ returnToNormalMode();
477
+ debugLog("insert -> normal via ctrl+n");
478
+ }
479
+ return;
480
+ }
481
+ if (isColon) {
482
+ prevent(evt);
483
+ enterInsertMode();
484
+ debugLog("normal -> insert");
485
+ return;
486
+ }
487
+ if (isQuestion) {
488
+ prevent(evt);
489
+ setShowHelp((value) => !value);
490
+ debugLog("toggle help");
491
+ return;
492
+ }
493
+ const key = raw || seq || name;
494
+ const currentGoals = state()?.goals.filter((goal) => goal.status !== "complete") || [];
495
+ if (name === "down" || key === "j") {
496
+ prevent(evt);
497
+ setSelected((index) => Math.min(currentGoals.length - 1, index + 1));
498
+ return;
499
+ }
500
+ if (name === "up" || key === "k") {
501
+ prevent(evt);
502
+ setSelected((index) => Math.max(0, index - 1));
503
+ return;
504
+ }
505
+ if (key === "g") {
506
+ prevent(evt);
507
+ setSelected(0);
508
+ return;
509
+ }
510
+ if (key === "G") {
511
+ prevent(evt);
512
+ setSelected(Math.max(0, currentGoals.length - 1));
513
+ return;
514
+ }
515
+ if (key === "p") {
516
+ prevent(evt);
517
+ executeCommand("pause");
518
+ return;
519
+ }
520
+ if (key === "r") {
521
+ prevent(evt);
522
+ executeCommand("resume");
523
+ return;
524
+ }
525
+ if (key === "R") {
526
+ prevent(evt);
527
+ executeCommand("retry");
528
+ return;
529
+ }
530
+ if (key === "x") {
531
+ prevent(evt);
532
+ executeCommand("clear");
533
+ return;
534
+ }
535
+ if (key === "L") {
536
+ prevent(evt);
537
+ setShowLogs((value) => !value);
538
+ return;
539
+ }
540
+ if (key === "o") {
541
+ prevent(evt);
542
+ const goal = selectedGoal();
543
+ if (goal?.workerSessionID) {
544
+ props.api.route.navigate("session", {
545
+ sessionID: goal.workerSessionID
546
+ });
547
+ props.api.ui.dialog.clear();
548
+ }
549
+ return;
550
+ }
551
+ if (key === "q") {
552
+ prevent(evt);
553
+ props.api.ui.dialog.clear();
554
+ return;
555
+ }
556
+ });
557
+ const goals = () => state()?.goals.filter((g) => g.status !== "complete") || [];
558
+ async function executeCommand(cmd) {
559
+ debugLog("executeCommand raw=", JSON.stringify(cmd));
560
+ const parsed = parseCommand(cmd);
561
+ debugLog("parsed", parsed);
562
+ if (!parsed) {
563
+ setStatusText("Empty command");
564
+ debugLog("empty command");
565
+ return;
566
+ }
567
+ try {
568
+ switch (parsed.command) {
569
+ case "send": {
570
+ if (!selectedGoal()) {
571
+ setStatusText("No goal selected");
572
+ break;
573
+ }
574
+ const message = parsed.positional.join(" ") || parsed.args.message || "";
575
+ if (!message) {
576
+ setStatusText("Usage: :send <message>");
577
+ break;
578
+ }
579
+ const r = await client.execute({
580
+ version: 1,
581
+ requestID: randomUUID(),
582
+ requestedAt: new Date().toISOString(),
583
+ command: "send",
584
+ goalID: selectedGoal().id,
585
+ args: {
586
+ message
587
+ }
588
+ });
589
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
590
+ if (r.ok)
591
+ await refresh();
592
+ break;
593
+ }
594
+ case "open": {
595
+ const goal = selectedGoal();
596
+ if (!goal?.workerSessionID) {
597
+ setStatusText("No worker session");
598
+ break;
599
+ }
600
+ props.api.route.navigate("session", {
601
+ sessionID: goal.workerSessionID
602
+ });
603
+ props.api.ui.dialog.clear();
604
+ return;
605
+ }
606
+ case "force": {
607
+ if (!selectedGoal()) {
608
+ setStatusText("No goal");
609
+ break;
610
+ }
611
+ const summary = parsed.positional.join(" ") || parsed.args.summary || "Force-completed from dashboard.";
612
+ const evidence = parsed.args.evidence || "Manual override \u2014 no verification checks run.";
613
+ const r = await client.execute({
614
+ version: 1,
615
+ requestID: randomUUID(),
616
+ requestedAt: new Date().toISOString(),
617
+ command: "force_complete",
618
+ goalID: selectedGoal().id,
619
+ args: {
620
+ summary,
621
+ evidence
622
+ }
623
+ });
624
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
625
+ if (r.ok)
626
+ await refresh();
627
+ break;
628
+ }
629
+ case "block": {
630
+ if (!selectedGoal()) {
631
+ setStatusText("No goal");
632
+ break;
633
+ }
634
+ const reason = parsed.positional.join(" ") || parsed.args.reason || "Blocked from dashboard.";
635
+ const needed = parsed.args.needed || "User intervention required.";
636
+ const r = await client.execute({
637
+ version: 1,
638
+ requestID: randomUUID(),
639
+ requestedAt: new Date().toISOString(),
640
+ command: "block",
641
+ goalID: selectedGoal().id,
642
+ args: {
643
+ reason,
644
+ needed
645
+ }
646
+ });
647
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
648
+ if (r.ok)
649
+ await refresh();
650
+ break;
651
+ }
652
+ case "pause": {
653
+ if (!selectedGoal()) {
654
+ setStatusText("No goal");
655
+ break;
656
+ }
657
+ const r = await client.execute({
658
+ version: 1,
659
+ requestID: randomUUID(),
660
+ requestedAt: new Date().toISOString(),
661
+ command: "pause",
662
+ goalID: selectedGoal().id
663
+ });
664
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
665
+ if (r.ok)
666
+ await refresh();
667
+ break;
668
+ }
669
+ case "resume": {
670
+ if (!selectedGoal()) {
671
+ setStatusText("No goal");
672
+ break;
673
+ }
674
+ const r = await client.execute({
675
+ version: 1,
676
+ requestID: randomUUID(),
677
+ requestedAt: new Date().toISOString(),
678
+ command: "resume",
679
+ goalID: selectedGoal().id
680
+ });
681
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
682
+ if (r.ok)
683
+ await refresh();
684
+ break;
685
+ }
686
+ case "retry": {
687
+ if (!selectedGoal()) {
688
+ setStatusText("No goal");
689
+ break;
690
+ }
691
+ const r = await client.execute({
692
+ version: 1,
693
+ requestID: randomUUID(),
694
+ requestedAt: new Date().toISOString(),
695
+ command: "retry",
696
+ goalID: selectedGoal().id
697
+ });
698
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
699
+ if (r.ok)
700
+ await refresh();
701
+ break;
702
+ }
703
+ case "clear": {
704
+ if (!selectedGoal()) {
705
+ setStatusText("No goal");
706
+ break;
707
+ }
708
+ const r = await client.execute({
709
+ version: 1,
710
+ requestID: randomUUID(),
711
+ requestedAt: new Date().toISOString(),
712
+ command: "clear",
713
+ goalID: selectedGoal().id
714
+ });
715
+ setStatusText(r.ok ? r.message : `Error: ${r.message}`);
716
+ if (r.ok)
717
+ await refresh();
718
+ break;
719
+ }
720
+ case "goal": {
721
+ setStatusText("Create goals via /goal in the parent chat (agent clarifies first). Dashboard: :send to steer the worker.");
722
+ break;
723
+ }
724
+ case "logs":
725
+ setShowLogs(!showLogs());
726
+ break;
727
+ case "help":
728
+ setShowHelp(true);
729
+ break;
730
+ case "q":
731
+ case "close":
732
+ props.api.ui.dialog.clear();
733
+ return;
734
+ default: {
735
+ if (parsed.command && selectedGoal()) {
736
+ const message = parsed.raw;
737
+ const r = await client.execute({
738
+ version: 1,
739
+ requestID: randomUUID(),
740
+ requestedAt: new Date().toISOString(),
741
+ command: "send",
742
+ goalID: selectedGoal().id,
743
+ args: {
744
+ message
745
+ }
746
+ });
747
+ setStatusText(r.ok ? `sent: ${message.slice(0, 80)}` : `Error: ${r.message}`);
748
+ if (r.ok)
749
+ await refresh();
750
+ } else
751
+ setStatusText(`Unknown: ${parsed.command}. ? for help`);
752
+ }
753
+ }
754
+ } catch (e) {
755
+ setStatusText(`Error: ${e instanceof Error ? e.message : String(e)}`);
756
+ }
757
+ returnToNormalMode();
758
+ }
759
+ const activeGoals = () => state()?.goals.filter((g) => g.status !== "complete") || [];
760
+ const runningCount = () => state()?.runtimes.filter((runtime) => runtime.phase === "running").length || 0;
761
+ const runningFrame = () => ["|", "/", "-", "\\"][Math.floor(clock() / 500) % 4];
762
+ createEffect(() => setSelectedGoal(activeGoals()[selected()] || null));
763
+ return (() => {
764
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("box"), _el$4 = _$createElement("text"), _el$5 = _$createElement("span"), _el$7 = _$createElement("span"), _el$9 = _$createElement("span"), _el$0 = _$createTextNode(` `), _el$1 = _$createTextNode(` `), _el$10 = _$createElement("span"), _el$12 = _$createElement("span"), _el$13 = _$createElement("span"), _el$15 = _$createElement("span"), _el$17 = _$createElement("span"), _el$18 = _$createTextNode(` `), _el$19 = _$createTextNode(` RUNNING`), _el$20 = _$createElement("span"), _el$22 = _$createElement("span"), _el$23 = _$createElement("span"), _el$25 = _$createElement("box"), _el$30 = _$createElement("box"), _el$37 = _$createElement("box"), _el$38 = _$createElement("text"), _el$39 = _$createElement("span"), _el$40 = _$createElement("input");
765
+ _$insertNode(_el$, _el$2);
766
+ _$setProp(_el$, "flexDirection", "column");
767
+ _$setProp(_el$, "width", "100%");
768
+ _$setProp(_el$, "alignItems", "center");
769
+ _$setProp(_el$, "padding", 1);
770
+ _$insertNode(_el$2, _el$3);
771
+ _$insertNode(_el$2, _el$25);
772
+ _$insertNode(_el$2, _el$37);
773
+ _$setProp(_el$2, "flexDirection", "column");
774
+ _$setProp(_el$2, "width", "90%");
775
+ _$setProp(_el$2, "border", true);
776
+ _$setProp(_el$2, "padding", 1);
777
+ _$insertNode(_el$3, _el$4);
778
+ _$setProp(_el$3, "flexDirection", "row");
779
+ _$setProp(_el$3, "padding", 0);
780
+ _$setProp(_el$3, "flexShrink", 0);
781
+ _$insertNode(_el$4, _el$5);
782
+ _$insertNode(_el$4, _el$7);
783
+ _$insertNode(_el$4, _el$9);
784
+ _$insertNode(_el$4, _el$10);
785
+ _$insertNode(_el$4, _el$12);
786
+ _$insertNode(_el$4, _el$13);
787
+ _$insertNode(_el$4, _el$15);
788
+ _$insertNode(_el$4, _el$17);
789
+ _$insertNode(_el$4, _el$20);
790
+ _$insertNode(_el$4, _el$22);
791
+ _$insertNode(_el$4, _el$23);
792
+ _$insertNode(_el$5, _$createTextNode(`\u2B22 Loop Dashboard`));
793
+ _$insertNode(_el$7, _$createTextNode(` \u2502 `));
794
+ _$insertNode(_el$9, _el$0);
795
+ _$insertNode(_el$9, _el$1);
796
+ _$insert(_el$9, () => mode().toUpperCase(), _el$1);
797
+ _$insertNode(_el$10, _$createTextNode(` \u2502 `));
798
+ _$insert(_el$12, () => activeGoals().length);
799
+ _$insertNode(_el$13, _$createTextNode(` goals`));
800
+ _$insertNode(_el$15, _$createTextNode(` \u2502 `));
801
+ _$insertNode(_el$17, _el$18);
802
+ _$insertNode(_el$17, _el$19);
803
+ _$insert(_el$17, (() => {
804
+ var _c$ = _$memo(() => runningCount() > 0);
805
+ return () => _c$() ? runningFrame() : "\u25CB";
806
+ })(), _el$18);
807
+ _$insert(_el$17, runningCount, _el$19);
808
+ _$insertNode(_el$20, _$createTextNode(` \u2502 `));
809
+ _$insert(_el$22, () => state()?.goals.filter((g) => g.status === "complete").length || 0);
810
+ _$insertNode(_el$23, _$createTextNode(` done`));
811
+ _$insertNode(_el$25, _el$30);
812
+ _$setProp(_el$25, "flexDirection", "column");
813
+ _$setProp(_el$25, "flexGrow", 1);
814
+ _$setProp(_el$25, "minHeight", 0);
815
+ _$setProp(_el$25, "overflow", "hidden");
816
+ _$insert(_el$25, _$createComponent(Show, {
817
+ get when() {
818
+ return showHelp();
819
+ },
820
+ get children() {
821
+ var _el$26 = _$createElement("box"), _el$27 = _$createElement("text"), _el$28 = _$createElement("span");
822
+ _$insertNode(_el$26, _el$27);
823
+ _$setProp(_el$26, "flexDirection", "column");
824
+ _$setProp(_el$26, "padding", 1);
825
+ _$setProp(_el$26, "border", true);
826
+ _$setProp(_el$26, "borderColor", "yellow");
827
+ _$setProp(_el$26, "flexShrink", 0);
828
+ _$setProp(_el$26, "maxHeight", 14);
829
+ _$setProp(_el$26, "overflow", "hidden");
830
+ _$insertNode(_el$27, _el$28);
831
+ _$insertNode(_el$28, _$createTextNode(`\u2501\u2501\u2501 Keys: ? toggle : insert Ctrl+N normal o open q close \u2501\u2501\u2501`));
832
+ _$setProp(_el$28, "style", {
833
+ fg: "yellow",
834
+ bold: true
835
+ });
836
+ _$insert(_el$26, _$createComponent(For, {
837
+ get each() {
838
+ return commandHelp().split(`
839
+ `);
840
+ },
841
+ children: (line) => {
842
+ const isHeader = line.startsWith("Modes:") || line.startsWith("Nav:") || line.startsWith("Commands");
843
+ const isCmd = line.trim().startsWith(":");
844
+ return (() => {
845
+ var _el$41 = _$createElement("text"), _el$42 = _$createElement("span");
846
+ _$insertNode(_el$41, _el$42);
847
+ _$insert(_el$42, line);
848
+ _$effect((_$p) => _$setProp(_el$42, "style", {
849
+ fg: isHeader ? theme().primary : isCmd ? theme().warning : theme().text,
850
+ bold: isHeader
851
+ }, _$p));
852
+ return _el$41;
853
+ })();
854
+ }
855
+ }), null);
856
+ _$effect((_$p) => _$setProp(_el$26, "backgroundColor", theme().background, _$p));
857
+ return _el$26;
858
+ }
859
+ }), _el$30);
860
+ _$setProp(_el$30, "flexDirection", "column");
861
+ _$setProp(_el$30, "flexGrow", 1);
862
+ _$setProp(_el$30, "padding", 1);
863
+ _$setProp(_el$30, "minHeight", 0);
864
+ _$setProp(_el$30, "overflow", "hidden");
865
+ _$insert(_el$30, _$createComponent(Show, {
866
+ get when() {
867
+ return activeGoals().length > 0;
868
+ },
869
+ get fallback() {
870
+ return (() => {
871
+ var _el$43 = _$createElement("box"), _el$44 = _$createElement("text"), _el$45 = _$createElement("span"), _el$47 = _$createElement("span"), _el$49 = _$createElement("span"), _el$51 = _$createElement("text"), _el$52 = _$createElement("span"), _el$54 = _$createElement("span"), _el$56 = _$createElement("span"), _el$58 = _$createElement("span"), _el$60 = _$createElement("span"), _el$62 = _$createElement("span"), _el$64 = _$createElement("span");
872
+ _$insertNode(_el$43, _el$44);
873
+ _$insertNode(_el$43, _el$51);
874
+ _$setProp(_el$43, "flexDirection", "column");
875
+ _$setProp(_el$43, "gap", 1);
876
+ _$insertNode(_el$44, _el$45);
877
+ _$insertNode(_el$44, _el$47);
878
+ _$insertNode(_el$44, _el$49);
879
+ _$insertNode(_el$45, _$createTextNode(`No active goals.`));
880
+ _$insertNode(_el$47, _$createTextNode(` /goal`));
881
+ _$insertNode(_el$49, _$createTextNode(` in parent chat to create one.`));
882
+ _$insertNode(_el$51, _el$52);
883
+ _$insertNode(_el$51, _el$54);
884
+ _$insertNode(_el$51, _el$56);
885
+ _$insertNode(_el$51, _el$58);
886
+ _$insertNode(_el$51, _el$60);
887
+ _$insertNode(_el$51, _el$62);
888
+ _$insertNode(_el$51, _el$64);
889
+ _$insertNode(_el$52, _$createTextNode(`Tip: `));
890
+ _$insertNode(_el$54, _$createTextNode(`:send`));
891
+ _$insertNode(_el$56, _$createTextNode(` to steer the worker \xB7 `));
892
+ _$insertNode(_el$58, _$createTextNode(`o`));
893
+ _$insertNode(_el$60, _$createTextNode(` to open child \xB7 `));
894
+ _$insertNode(_el$62, _$createTextNode(`:force`));
895
+ _$insertNode(_el$64, _$createTextNode(` to complete manually.`));
896
+ _$effect((_p$) => {
897
+ var _v$21 = {
898
+ fg: theme().textMuted
899
+ }, _v$22 = {
900
+ fg: theme().accent
901
+ }, _v$23 = {
902
+ fg: theme().textMuted
903
+ }, _v$24 = {
904
+ fg: theme().textMuted
905
+ }, _v$25 = {
906
+ fg: theme().warning
907
+ }, _v$26 = {
908
+ fg: theme().textMuted
909
+ }, _v$27 = {
910
+ fg: theme().warning
911
+ }, _v$28 = {
912
+ fg: theme().textMuted
913
+ }, _v$29 = {
914
+ fg: theme().warning
915
+ }, _v$30 = {
916
+ fg: theme().textMuted
917
+ };
918
+ _v$21 !== _p$.e && (_p$.e = _$setProp(_el$45, "style", _v$21, _p$.e));
919
+ _v$22 !== _p$.t && (_p$.t = _$setProp(_el$47, "style", _v$22, _p$.t));
920
+ _v$23 !== _p$.a && (_p$.a = _$setProp(_el$49, "style", _v$23, _p$.a));
921
+ _v$24 !== _p$.o && (_p$.o = _$setProp(_el$52, "style", _v$24, _p$.o));
922
+ _v$25 !== _p$.i && (_p$.i = _$setProp(_el$54, "style", _v$25, _p$.i));
923
+ _v$26 !== _p$.n && (_p$.n = _$setProp(_el$56, "style", _v$26, _p$.n));
924
+ _v$27 !== _p$.s && (_p$.s = _$setProp(_el$58, "style", _v$27, _p$.s));
925
+ _v$28 !== _p$.h && (_p$.h = _$setProp(_el$60, "style", _v$28, _p$.h));
926
+ _v$29 !== _p$.r && (_p$.r = _$setProp(_el$62, "style", _v$29, _p$.r));
927
+ _v$30 !== _p$.d && (_p$.d = _$setProp(_el$64, "style", _v$30, _p$.d));
928
+ return _p$;
929
+ }, {
930
+ e: undefined,
931
+ t: undefined,
932
+ a: undefined,
933
+ o: undefined,
934
+ i: undefined,
935
+ n: undefined,
936
+ s: undefined,
937
+ h: undefined,
938
+ r: undefined,
939
+ d: undefined
940
+ });
941
+ return _el$43;
942
+ })();
943
+ },
944
+ get children() {
945
+ return _$createComponent(For, {
946
+ get each() {
947
+ return activeGoals();
948
+ },
949
+ children: (goal, i) => {
950
+ const runtime = () => state()?.runtimes.find((r) => r.goalID === goal.id);
951
+ const isActive = () => i() === selected();
952
+ const maxTurns = goal.config?.maxTurns;
953
+ const turnColor = () => {
954
+ if (!runtime() || !maxTurns)
955
+ return phaseColor(runtime()?.phase || "idle", theme());
956
+ const ratio = runtime().turnCount / maxTurns;
957
+ if (ratio >= 1)
958
+ return theme().error;
959
+ if (ratio >= 0.8)
960
+ return theme().warning;
961
+ return phaseColor(runtime().phase || "idle", theme());
962
+ };
963
+ return (() => {
964
+ var _el$66 = _$createElement("box"), _el$67 = _$createElement("text"), _el$68 = _$createElement("span"), _el$69 = _$createElement("span"), _el$71 = _$createElement("span");
965
+ _$insertNode(_el$66, _el$67);
966
+ _$setProp(_el$66, "flexDirection", "row");
967
+ _$setProp(_el$66, "paddingLeft", 1);
968
+ _$setProp(_el$66, "paddingRight", 1);
969
+ _$insertNode(_el$67, _el$68);
970
+ _$insertNode(_el$67, _el$69);
971
+ _$insertNode(_el$67, _el$71);
972
+ _$insert(_el$68, (() => {
973
+ var _c$2 = _$memo(() => !!isActive());
974
+ return () => _c$2() ? `\u25B6 ${statusIcon(goal.status)} ${goal.name}` : ` ${statusIcon(goal.status)} ${goal.name}`;
975
+ })());
976
+ _$insertNode(_el$69, _$createTextNode(` \u2502 `));
977
+ _$insert(_el$71, () => goal.status.toUpperCase());
978
+ _$insert(_el$67, (() => {
979
+ var _c$3 = _$memo(() => !!runtime());
980
+ return () => _c$3() && [(() => {
981
+ var _el$72 = _$createElement("span");
982
+ _$insertNode(_el$72, _$createTextNode(` \u2502 `));
983
+ _$effect((_$p) => _$setProp(_el$72, "style", {
984
+ fg: theme().textMuted
985
+ }, _$p));
986
+ return _el$72;
987
+ })(), (() => {
988
+ var _el$74 = _$createElement("span"), _el$75 = _$createTextNode(` `);
989
+ _$insertNode(_el$74, _el$75);
990
+ _$insert(_el$74, (() => {
991
+ var _c$6 = _$memo(() => runtime().phase === "running");
992
+ return () => _c$6() ? runningFrame() : phaseIcon(runtime().phase);
993
+ })(), _el$75);
994
+ _$insert(_el$74, () => runtime().phase.toUpperCase(), null);
995
+ _$effect((_$p) => _$setProp(_el$74, "style", {
996
+ fg: turnColor(),
997
+ bold: runtime().phase === "running"
998
+ }, _$p));
999
+ return _el$74;
1000
+ })(), (() => {
1001
+ var _el$76 = _$createElement("span"), _el$77 = _$createTextNode(` `);
1002
+ _$insertNode(_el$76, _el$77);
1003
+ _$insert(_el$76, () => runtime().turnCount, null);
1004
+ _$insert(_el$76, maxTurns ? `/${maxTurns}` : "", null);
1005
+ _$effect((_$p) => _$setProp(_el$76, "style", {
1006
+ fg: turnColor()
1007
+ }, _$p));
1008
+ return _el$76;
1009
+ })(), (() => {
1010
+ var _el$78 = _$createElement("span"), _el$79 = _$createTextNode(` `);
1011
+ _$insertNode(_el$78, _el$79);
1012
+ _$insert(_el$78, () => ageLabel(runtime().lastProgressAt || runtime().lastRunAt, clock()), null);
1013
+ _$effect((_$p) => _$setProp(_el$78, "style", {
1014
+ fg: theme().textMuted
1015
+ }, _$p));
1016
+ return _el$78;
1017
+ })()];
1018
+ })(), null);
1019
+ _$insert(_el$67, (() => {
1020
+ var _c$4 = _$memo(() => !!(runtime() && runtime().consecutiveFailures > 0));
1021
+ return () => _c$4() && (() => {
1022
+ var _el$80 = _$createElement("span"), _el$81 = _$createTextNode(` \u2502 \u26A0 `), _el$82 = _$createTextNode(` fail`);
1023
+ _$insertNode(_el$80, _el$81);
1024
+ _$insertNode(_el$80, _el$82);
1025
+ _$insert(_el$80, () => runtime().consecutiveFailures, _el$82);
1026
+ _$effect((_$p) => _$setProp(_el$80, "style", {
1027
+ fg: theme().error,
1028
+ bold: true
1029
+ }, _$p));
1030
+ return _el$80;
1031
+ })();
1032
+ })(), null);
1033
+ _$insert(_el$67, (() => {
1034
+ var _c$5 = _$memo(() => !!(runtime() && (runtime().noProgressCount || 0) > 0));
1035
+ return () => _c$5() && (() => {
1036
+ var _el$83 = _$createElement("span"), _el$84 = _$createTextNode(` \u2502 `), _el$85 = _$createTextNode(` no-progress`);
1037
+ _$insertNode(_el$83, _el$84);
1038
+ _$insertNode(_el$83, _el$85);
1039
+ _$insert(_el$83, () => runtime().noProgressCount, _el$85);
1040
+ _$effect((_$p) => _$setProp(_el$83, "style", {
1041
+ fg: theme().warning
1042
+ }, _$p));
1043
+ return _el$83;
1044
+ })();
1045
+ })(), null);
1046
+ _$effect((_p$) => {
1047
+ var _v$31 = isActive() ? theme().backgroundElement : undefined, _v$32 = {
1048
+ fg: statusColor(goal.status, theme()),
1049
+ bold: isActive()
1050
+ }, _v$33 = {
1051
+ fg: theme().textMuted
1052
+ }, _v$34 = {
1053
+ fg: statusColor(goal.status, theme()),
1054
+ bold: true
1055
+ };
1056
+ _v$31 !== _p$.e && (_p$.e = _$setProp(_el$66, "backgroundColor", _v$31, _p$.e));
1057
+ _v$32 !== _p$.t && (_p$.t = _$setProp(_el$68, "style", _v$32, _p$.t));
1058
+ _v$33 !== _p$.a && (_p$.a = _$setProp(_el$69, "style", _v$33, _p$.a));
1059
+ _v$34 !== _p$.o && (_p$.o = _$setProp(_el$71, "style", _v$34, _p$.o));
1060
+ return _p$;
1061
+ }, {
1062
+ e: undefined,
1063
+ t: undefined,
1064
+ a: undefined,
1065
+ o: undefined
1066
+ });
1067
+ return _el$66;
1068
+ })();
1069
+ }
1070
+ });
1071
+ }
1072
+ }));
1073
+ _$insert(_el$25, _$createComponent(Show, {
1074
+ get when() {
1075
+ return selectedGoal();
1076
+ },
1077
+ children: (goal) => {
1078
+ const rt = () => state()?.runtimes.find((r) => r.goalID === goal().id);
1079
+ return (() => {
1080
+ var _el$86 = _$createElement("box"), _el$87 = _$createElement("text"), _el$88 = _$createElement("span"), _el$89 = _$createTextNode(` `), _el$90 = _$createElement("span"), _el$91 = _$createTextNode(` `), _el$92 = _$createTextNode(`
1081
+ `), _el$93 = _$createElement("span");
1082
+ _$insertNode(_el$86, _el$87);
1083
+ _$setProp(_el$86, "flexDirection", "column");
1084
+ _$setProp(_el$86, "border", true);
1085
+ _$setProp(_el$86, "padding", 1);
1086
+ _$setProp(_el$86, "flexShrink", 0);
1087
+ _$setProp(_el$86, "maxHeight", 10);
1088
+ _$insertNode(_el$87, _el$88);
1089
+ _$insertNode(_el$87, _el$90);
1090
+ _$insertNode(_el$87, _el$92);
1091
+ _$insertNode(_el$87, _el$93);
1092
+ _$insertNode(_el$88, _el$89);
1093
+ _$insert(_el$88, () => statusIcon(goal().status), _el$89);
1094
+ _$insert(_el$88, () => goal().name, null);
1095
+ _$insertNode(_el$90, _el$91);
1096
+ _$insert(_el$90, () => goal().status.toUpperCase(), null);
1097
+ _$insert(_el$87, (() => {
1098
+ var _c$7 = _$memo(() => !!rt());
1099
+ return () => _c$7() && [(() => {
1100
+ var _el$94 = _$createElement("span");
1101
+ _$insertNode(_el$94, _$createTextNode(` \u2502 `));
1102
+ _$effect((_$p) => _$setProp(_el$94, "style", {
1103
+ fg: theme().textMuted
1104
+ }, _$p));
1105
+ return _el$94;
1106
+ })(), (() => {
1107
+ var _el$96 = _$createElement("span"), _el$97 = _$createTextNode(` `);
1108
+ _$insertNode(_el$96, _el$97);
1109
+ _$insert(_el$96, () => phaseIcon(rt().phase), _el$97);
1110
+ _$insert(_el$96, () => rt().phase, null);
1111
+ _$effect((_$p) => _$setProp(_el$96, "style", {
1112
+ fg: phaseColor(rt().phase, theme()),
1113
+ bold: true
1114
+ }, _$p));
1115
+ return _el$96;
1116
+ })(), (() => {
1117
+ var _el$98 = _$createElement("span"), _el$99 = _$createTextNode(` turn `);
1118
+ _$insertNode(_el$98, _el$99);
1119
+ _$insert(_el$98, () => rt().turnCount, null);
1120
+ _$effect((_$p) => _$setProp(_el$98, "style", {
1121
+ fg: theme().textMuted
1122
+ }, _$p));
1123
+ return _el$98;
1124
+ })()];
1125
+ })(), _el$92);
1126
+ _$insert(_el$93, () => goal().objective.slice(0, 160));
1127
+ _$insert(_el$87, (() => {
1128
+ var _c$8 = _$memo(() => !!goal().lastProgress);
1129
+ return () => _c$8() && [(() => {
1130
+ var _el$100 = _$createElement("span"), _el$101 = _$createTextNode(`
1131
+ \u2714 `);
1132
+ _$insertNode(_el$100, _el$101);
1133
+ _$effect((_$p) => _$setProp(_el$100, "style", {
1134
+ fg: theme().success
1135
+ }, _$p));
1136
+ return _el$100;
1137
+ })(), (() => {
1138
+ var _el$103 = _$createElement("span");
1139
+ _$insert(_el$103, () => goal().lastProgress.summary.slice(0, 100));
1140
+ _$effect((_$p) => _$setProp(_el$103, "style", {
1141
+ fg: theme().text
1142
+ }, _$p));
1143
+ return _el$103;
1144
+ })(), (() => {
1145
+ var _el$104 = _$createElement("span"), _el$105 = _$createTextNode(` \u2192 `);
1146
+ _$insertNode(_el$104, _el$105);
1147
+ _$insert(_el$104, () => goal().lastProgress.next?.slice(0, 60) || "", null);
1148
+ _$effect((_$p) => _$setProp(_el$104, "style", {
1149
+ fg: theme().textMuted
1150
+ }, _$p));
1151
+ return _el$104;
1152
+ })()];
1153
+ })(), null);
1154
+ _$insert(_el$87, (() => {
1155
+ var _c$9 = _$memo(() => !!goal().blocker);
1156
+ return () => _c$9() && [(() => {
1157
+ var _el$106 = _$createElement("span"), _el$107 = _$createTextNode(`
1158
+ \u2716 blocked: `);
1159
+ _$insertNode(_el$106, _el$107);
1160
+ _$effect((_$p) => _$setProp(_el$106, "style", {
1161
+ fg: theme().error,
1162
+ bold: true
1163
+ }, _$p));
1164
+ return _el$106;
1165
+ })(), (() => {
1166
+ var _el$109 = _$createElement("span");
1167
+ _$insert(_el$109, () => goal().blocker.reason.slice(0, 140));
1168
+ _$effect((_$p) => _$setProp(_el$109, "style", {
1169
+ fg: theme().error
1170
+ }, _$p));
1171
+ return _el$109;
1172
+ })(), (() => {
1173
+ var _el$110 = _$createElement("span"), _el$111 = _$createTextNode(` \u2014 `);
1174
+ _$insertNode(_el$110, _el$111);
1175
+ _$insert(_el$110, () => goal().blocker.needed.slice(0, 60), null);
1176
+ _$effect((_$p) => _$setProp(_el$110, "style", {
1177
+ fg: theme().textMuted
1178
+ }, _$p));
1179
+ return _el$110;
1180
+ })()];
1181
+ })(), null);
1182
+ _$insert(_el$87, (() => {
1183
+ var _c$0 = _$memo(() => !!goal().config.artifactDir);
1184
+ return () => _c$0() && [(() => {
1185
+ var _el$112 = _$createElement("span"), _el$113 = _$createTextNode(`
1186
+ \uD83D\uDCC1 `);
1187
+ _$insertNode(_el$112, _el$113);
1188
+ _$effect((_$p) => _$setProp(_el$112, "style", {
1189
+ fg: theme().accent
1190
+ }, _$p));
1191
+ return _el$112;
1192
+ })(), (() => {
1193
+ var _el$115 = _$createElement("span");
1194
+ _$insert(_el$115, () => String(goal().config.artifactDir).replace(String(props.directory), "."));
1195
+ _$effect((_$p) => _$setProp(_el$115, "style", {
1196
+ fg: theme().textMuted
1197
+ }, _$p));
1198
+ return _el$115;
1199
+ })()];
1200
+ })(), null);
1201
+ _$insert(_el$87, (() => {
1202
+ var _c$1 = _$memo(() => !!rt()?.lastError);
1203
+ return () => _c$1() && [(() => {
1204
+ var _el$116 = _$createElement("span"), _el$117 = _$createTextNode(`
1205
+ \u26A0 `);
1206
+ _$insertNode(_el$116, _el$117);
1207
+ _$effect((_$p) => _$setProp(_el$116, "style", {
1208
+ fg: theme().error
1209
+ }, _$p));
1210
+ return _el$116;
1211
+ })(), (() => {
1212
+ var _el$119 = _$createElement("span");
1213
+ _$insert(_el$119, () => rt().lastError.slice(0, 120));
1214
+ _$effect((_$p) => _$setProp(_el$119, "style", {
1215
+ fg: theme().error
1216
+ }, _$p));
1217
+ return _el$119;
1218
+ })()];
1219
+ })(), null);
1220
+ _$effect((_p$) => {
1221
+ var _v$35 = borderColorForStatus(goal().status, theme()), _v$36 = {
1222
+ fg: statusColor(goal().status, theme()),
1223
+ bold: true
1224
+ }, _v$37 = {
1225
+ fg: statusColor(goal().status, theme())
1226
+ }, _v$38 = {
1227
+ fg: theme().text
1228
+ };
1229
+ _v$35 !== _p$.e && (_p$.e = _$setProp(_el$86, "borderColor", _v$35, _p$.e));
1230
+ _v$36 !== _p$.t && (_p$.t = _$setProp(_el$88, "style", _v$36, _p$.t));
1231
+ _v$37 !== _p$.a && (_p$.a = _$setProp(_el$90, "style", _v$37, _p$.a));
1232
+ _v$38 !== _p$.o && (_p$.o = _$setProp(_el$93, "style", _v$38, _p$.o));
1233
+ return _p$;
1234
+ }, {
1235
+ e: undefined,
1236
+ t: undefined,
1237
+ a: undefined,
1238
+ o: undefined
1239
+ });
1240
+ return _el$86;
1241
+ })();
1242
+ }
1243
+ }), null);
1244
+ _$insert(_el$25, _$createComponent(Show, {
1245
+ get when() {
1246
+ return _$memo(() => !!showLogs())() && events().length > 0;
1247
+ },
1248
+ get children() {
1249
+ var _el$31 = _$createElement("box"), _el$32 = _$createElement("text"), _el$33 = _$createElement("span"), _el$35 = _$createElement("span");
1250
+ _$insertNode(_el$31, _el$32);
1251
+ _$setProp(_el$31, "flexDirection", "column");
1252
+ _$setProp(_el$31, "border", true);
1253
+ _$setProp(_el$31, "padding", 1);
1254
+ _$setProp(_el$31, "maxHeight", 7);
1255
+ _$setProp(_el$31, "flexShrink", 0);
1256
+ _$setProp(_el$31, "overflow", "hidden");
1257
+ _$insertNode(_el$32, _el$33);
1258
+ _$insertNode(_el$32, _el$35);
1259
+ _$insertNode(_el$33, _$createTextNode(`\u25C8 Recent Events`));
1260
+ _$insertNode(_el$35, _$createTextNode(` \u2014 :logs to hide`));
1261
+ _$insert(_el$31, _$createComponent(For, {
1262
+ get each() {
1263
+ return events().slice(-10);
1264
+ },
1265
+ children: (ev) => (() => {
1266
+ var _el$120 = _$createElement("text"), _el$121 = _$createElement("span"), _el$122 = _$createElement("span"), _el$123 = _$createTextNode(` `);
1267
+ _$insertNode(_el$120, _el$121);
1268
+ _$insertNode(_el$120, _el$122);
1269
+ _$insert(_el$121, () => String(ev.type));
1270
+ _$insertNode(_el$122, _el$123);
1271
+ _$insert(_el$122, () => ev.goalID?.slice(0, 8), null);
1272
+ _$insert(_el$120, (() => {
1273
+ var _c$10 = _$memo(() => !!ev.summary);
1274
+ return () => _c$10() && (() => {
1275
+ var _el$124 = _$createElement("span"), _el$125 = _$createTextNode(` \u2014 `);
1276
+ _$insertNode(_el$124, _el$125);
1277
+ _$insert(_el$124, () => String(ev.summary).slice(0, 60), null);
1278
+ _$effect((_$p) => _$setProp(_el$124, "style", {
1279
+ fg: theme().text
1280
+ }, _$p));
1281
+ return _el$124;
1282
+ })();
1283
+ })(), null);
1284
+ _$effect((_p$) => {
1285
+ var _v$39 = {
1286
+ fg: eventColor(String(ev.type), theme()),
1287
+ bold: true
1288
+ }, _v$40 = {
1289
+ fg: theme().textMuted
1290
+ };
1291
+ _v$39 !== _p$.e && (_p$.e = _$setProp(_el$121, "style", _v$39, _p$.e));
1292
+ _v$40 !== _p$.t && (_p$.t = _$setProp(_el$122, "style", _v$40, _p$.t));
1293
+ return _p$;
1294
+ }, {
1295
+ e: undefined,
1296
+ t: undefined
1297
+ });
1298
+ return _el$120;
1299
+ })()
1300
+ }), null);
1301
+ _$effect((_p$) => {
1302
+ var _v$ = theme().border, _v$2 = {
1303
+ fg: theme().accent,
1304
+ bold: true
1305
+ }, _v$3 = {
1306
+ fg: theme().textMuted
1307
+ };
1308
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$31, "borderColor", _v$, _p$.e));
1309
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$33, "style", _v$2, _p$.t));
1310
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$35, "style", _v$3, _p$.a));
1311
+ return _p$;
1312
+ }, {
1313
+ e: undefined,
1314
+ t: undefined,
1315
+ a: undefined
1316
+ });
1317
+ return _el$31;
1318
+ }
1319
+ }), null);
1320
+ _$insertNode(_el$37, _el$38);
1321
+ _$insertNode(_el$37, _el$40);
1322
+ _$setProp(_el$37, "flexDirection", "row");
1323
+ _$setProp(_el$37, "border", true);
1324
+ _$setProp(_el$37, "paddingLeft", 1);
1325
+ _$setProp(_el$37, "paddingRight", 1);
1326
+ _$setProp(_el$37, "flexShrink", 0);
1327
+ _$setProp(_el$37, "height", 3);
1328
+ _$setProp(_el$37, "gap", 1);
1329
+ _$insertNode(_el$38, _el$39);
1330
+ _$insert(_el$39, () => mode() === "insert" ? " INSERT \uE0B1" : " NORMAL ");
1331
+ _$use((el) => {
1332
+ inputEl = el;
1333
+ focusInput();
1334
+ }, _el$40);
1335
+ _$setProp(_el$40, "flexGrow", 1);
1336
+ _$setProp(_el$40, "onInput", (v) => {
1337
+ debugLog("onInput", JSON.stringify(v), "mode", mode());
1338
+ if (mode() === "insert")
1339
+ setCommandInput(v);
1340
+ else if (inputEl?.value)
1341
+ inputEl.value = "";
1342
+ });
1343
+ _$setProp(_el$40, "onKeyDown", (evt) => {
1344
+ const name = evt.name || "";
1345
+ const seq = evt.sequence || "";
1346
+ debugLog("input onKeyDown", `name=${name} seq=${JSON.stringify(seq)} mode=${mode()} value=${JSON.stringify(commandInput())}`);
1347
+ if (mode() !== "insert") {
1348
+ if ((evt.name || "").length === 1)
1349
+ prevent(evt);
1350
+ return;
1351
+ }
1352
+ if (name === "return" || name === "enter") {
1353
+ prevent(evt);
1354
+ debugLog("input enter -> execute");
1355
+ executeCommand(commandInput());
1356
+ return;
1357
+ }
1358
+ if (evt.ctrl && name.toLowerCase() === "n") {
1359
+ prevent(evt);
1360
+ debugLog("input ctrl+n -> normal");
1361
+ returnToNormalMode();
1362
+ return;
1363
+ }
1364
+ });
1365
+ _$effect((_p$) => {
1366
+ var _v$4 = theme().border, _v$5 = {
1367
+ fg: theme().primary,
1368
+ bold: true
1369
+ }, _v$6 = {
1370
+ fg: theme().textMuted
1371
+ }, _v$7 = {
1372
+ fg: mode() === "normal" ? theme().success : theme().warning,
1373
+ bold: true,
1374
+ bg: mode() === "insert" ? theme().backgroundElement : undefined
1375
+ }, _v$8 = {
1376
+ fg: theme().textMuted
1377
+ }, _v$9 = {
1378
+ fg: theme().accent,
1379
+ bold: true
1380
+ }, _v$0 = {
1381
+ fg: theme().textMuted
1382
+ }, _v$1 = {
1383
+ fg: theme().textMuted
1384
+ }, _v$10 = {
1385
+ fg: runningCount() > 0 ? theme().success : theme().textMuted,
1386
+ bold: runningCount() > 0
1387
+ }, _v$11 = {
1388
+ fg: theme().textMuted
1389
+ }, _v$12 = {
1390
+ fg: theme().info,
1391
+ bold: true
1392
+ }, _v$13 = {
1393
+ fg: theme().textMuted
1394
+ }, _v$14 = mode() === "insert" ? theme().warning : theme().border, _v$15 = {
1395
+ fg: mode() === "insert" ? theme().warning : theme().success,
1396
+ bold: true,
1397
+ bg: mode() === "insert" ? theme().backgroundElement : undefined
1398
+ }, _v$16 = mode() === "insert" ? ":send hello or :force done --evidence proof or :open (Ctrl+N: normal)" : statusText() || "Press : to send/command \xB7 ? help \xB7 o open child \xB7 q close", _v$17 = theme().textMuted, _v$18 = theme().primary, _v$19 = theme().text, _v$20 = theme().background;
1399
+ _v$4 !== _p$.e && (_p$.e = _$setProp(_el$2, "borderColor", _v$4, _p$.e));
1400
+ _v$5 !== _p$.t && (_p$.t = _$setProp(_el$5, "style", _v$5, _p$.t));
1401
+ _v$6 !== _p$.a && (_p$.a = _$setProp(_el$7, "style", _v$6, _p$.a));
1402
+ _v$7 !== _p$.o && (_p$.o = _$setProp(_el$9, "style", _v$7, _p$.o));
1403
+ _v$8 !== _p$.i && (_p$.i = _$setProp(_el$10, "style", _v$8, _p$.i));
1404
+ _v$9 !== _p$.n && (_p$.n = _$setProp(_el$12, "style", _v$9, _p$.n));
1405
+ _v$0 !== _p$.s && (_p$.s = _$setProp(_el$13, "style", _v$0, _p$.s));
1406
+ _v$1 !== _p$.h && (_p$.h = _$setProp(_el$15, "style", _v$1, _p$.h));
1407
+ _v$10 !== _p$.r && (_p$.r = _$setProp(_el$17, "style", _v$10, _p$.r));
1408
+ _v$11 !== _p$.d && (_p$.d = _$setProp(_el$20, "style", _v$11, _p$.d));
1409
+ _v$12 !== _p$.l && (_p$.l = _$setProp(_el$22, "style", _v$12, _p$.l));
1410
+ _v$13 !== _p$.u && (_p$.u = _$setProp(_el$23, "style", _v$13, _p$.u));
1411
+ _v$14 !== _p$.c && (_p$.c = _$setProp(_el$37, "borderColor", _v$14, _p$.c));
1412
+ _v$15 !== _p$.w && (_p$.w = _$setProp(_el$39, "style", _v$15, _p$.w));
1413
+ _v$16 !== _p$.m && (_p$.m = _$setProp(_el$40, "placeholder", _v$16, _p$.m));
1414
+ _v$17 !== _p$.f && (_p$.f = _$setProp(_el$40, "placeholderColor", _v$17, _p$.f));
1415
+ _v$18 !== _p$.y && (_p$.y = _$setProp(_el$40, "cursorColor", _v$18, _p$.y));
1416
+ _v$19 !== _p$.g && (_p$.g = _$setProp(_el$40, "focusedTextColor", _v$19, _p$.g));
1417
+ _v$20 !== _p$.p && (_p$.p = _$setProp(_el$40, "focusedBackgroundColor", _v$20, _p$.p));
1418
+ return _p$;
1419
+ }, {
1420
+ e: undefined,
1421
+ t: undefined,
1422
+ a: undefined,
1423
+ o: undefined,
1424
+ i: undefined,
1425
+ n: undefined,
1426
+ s: undefined,
1427
+ h: undefined,
1428
+ r: undefined,
1429
+ d: undefined,
1430
+ l: undefined,
1431
+ u: undefined,
1432
+ c: undefined,
1433
+ w: undefined,
1434
+ m: undefined,
1435
+ f: undefined,
1436
+ y: undefined,
1437
+ g: undefined,
1438
+ p: undefined
1439
+ });
1440
+ return _el$;
1441
+ })();
1442
+ }
1443
+
1444
+ // src/tui/plugin.tsx
1445
+ var PLUGIN_ID = "opencode-loopd.tui";
1446
+ var tui = async (api) => {
1447
+ const directory = api.state.path.directory;
1448
+ const open = () => {
1449
+ const previousFocus = api.renderer.currentFocusedRenderable;
1450
+ api.ui.dialog.replace(() => _$createComponent2(LoopDashboard, {
1451
+ api,
1452
+ directory
1453
+ }));
1454
+ api.ui.dialog.setSize("xlarge");
1455
+ previousFocus?.blur();
1456
+ };
1457
+ api.keymap.registerLayer({
1458
+ commands: [{
1459
+ name: "opencode.loopd.dashboard",
1460
+ title: "Loop Dashboard",
1461
+ category: "Loop",
1462
+ namespace: "palette",
1463
+ slashName: "loop",
1464
+ run: open
1465
+ }],
1466
+ bindings: [{
1467
+ key: "ctrl+l",
1468
+ cmd: "opencode.loopd.dashboard",
1469
+ desc: "Open loop dashboard"
1470
+ }]
1471
+ });
1472
+ api.lifecycle.onDispose(() => {});
1473
+ };
1474
+ var plugin_default = {
1475
+ id: PLUGIN_ID,
1476
+ tui
1477
+ };
1478
+ export {
1479
+ plugin_default as default
1480
+ };