@bojackduy/opencode-loopd 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -1,137 +1,233 @@
1
1
  # opencode-loopd
2
2
 
3
- **Background goal engine for OpenCode**run long-running tasks as autonomous child sessions while the main chat stays interactive.
3
+ **Codex-inspired background goal engine for OpenCode — autonomous subagents, engine-driven loop, and modal TUI dashboard. Like Claude Code's loop, for OpenCode.**
4
+
5
+ > Run long-running tasks as **autonomous child sessions** (subagents) while the main chat stays interactive. Create a goal with `/goal`, watch it loop in the background, and monitor everything from a keyboard-driven TUI — without blocking the parent conversation.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@bojackduy/opencode-loopd?style=flat-square&color=blue)](https://www.npmjs.com/package/@bojackduy/opencode-loopd)
8
+ [![license](https://img.shields.io/badge/license-AGPL--3.0--only-blue?style=flat-square)](LICENSE)
9
+ [![opencode plugin](https://img.shields.io/badge/opencode-plugin-9cf?style=flat-square)](https://opencode.ai)
10
+ [![TUI](https://img.shields.io/badge/TUI-OpenTUI-purple?style=flat-square)](https://github.com/anomalyco/opentui)
11
+
12
+ ![Loop Dashboard — modal TUI with goals, running indicator, and insert mode](assets/demo.png)
13
+
14
+ *Modal dashboard (`/loop` / `Ctrl+L`): zero chat pollution — keys are trapped inside the dialog, NORMAL vs INSERT modes, vivid per-status coloring.*
15
+
16
+ ## Why opencode-loopd
17
+
18
+ - **Background agents, not blocked chats** — an OpenCode plugin that spawns a dedicated **worker (child) session** per goal. The parent stays free to keep chatting.
19
+ - **Codex-style engine-driven loop** — idle detection → continuation steering with accumulated context (progress history, transcript tail, inbox). No prompt spam in the parent.
20
+ - **Parent ↔ child visibility** — `list/inspect/read_transcript/send_input` give the parent full observability. Bidirectional inbox lets you steer mid-run.
21
+ - **Safe by default** — per-goal artifact isolation (`.opencode/loopd/goals/<id>/`), `maxTurns`/`maxFailures`/`maxNoProgress`, force-finish → semantic `complete_goal` summary → parent notification via wake-up injection.
22
+ - **Modal TUI dashboard** — `Ctrl+L` or `/loop` opens a focused dialog (no leak to chat prompt). Vim-style navigation, live running indicator, per-status borders.
23
+
24
+ Keywords: `opencode` `opencode-plugin` `background-agent` `autonomous` `subagent` `loop` `goal` `tui` `codex` `claude-code` `worker`
4
25
 
5
26
  ## What it is
6
27
 
7
- opencode-loopd is an OpenCode plugin that creates background goals with dedicated worker sessions. The parent chat stays interactive while work happens behind the scenes. You can observe, steer, pause, or stop goals at any time.
28
+ opencode-loopd is an **OpenCode plugin** (server + TUI) that adds **background goals** to OpenCode. Each goal owns a **worker (child) subagent** that loops autonomously; the **loop engine** drives continuations; the **dashboard** and **owner tools** keep the parent in control. Think *Codex goals* or *Claude Code loop*, but native to OpenCode's session model.
8
29
 
9
30
  ## Install
10
31
 
11
- ### Manual
32
+ ### Plugin — simple (no installer, just the plugin)
12
33
 
13
- Add to `~/.config/opencode/opencode.jsonc`:
34
+ Add the package to **both** configs.
14
35
 
15
- ```json
36
+ **`~/.config/opencode/opencode.jsonc`** — server engine:
37
+
38
+ ```jsonc
16
39
  {
17
- "plugin": [
18
- "/Users/you/Code/opencode-loopd"
19
- ]
40
+ "plugin": ["@bojackduy/opencode-loopd"]
20
41
  }
21
42
  ```
22
43
 
23
- ### npx (recommended)
44
+ **`~/.config/opencode/tui.json`** dashboard (modal TUI):
45
+
46
+ ```jsonc
47
+ {
48
+ "plugin": ["@bojackduy/opencode-loopd"]
49
+ }
50
+ ```
51
+
52
+ Then restart OpenCode. Verify with `/loop` (palette → Loop Dashboard) or `Ctrl+L`.
53
+
54
+ For a local checkout:
55
+
56
+ ```jsonc
57
+ // opencode.jsonc
58
+ { "plugin": ["./path/to/opencode-loopd"] }
59
+ // tui.json
60
+ { "plugin": ["./path/to/opencode-loopd"] }
61
+ ```
62
+
63
+ ### Installer — also installs `/goal` + skill
24
64
 
25
65
  ```bash
26
66
  npx -y @bojackduy/opencode-loopd@latest
27
67
  ```
28
68
 
29
- Run the same command again to update. Then restart OpenCode.
69
+ Registers the plugin, installs the `/goal` slash command and the `loopd` skill into `~/.config/opencode/`. Run again to update. Then **restart OpenCode**.
30
70
 
31
- ### npm global
71
+ Uninstall:
72
+
73
+ ```bash
74
+ npx -y @bojackduy/opencode-loopd@latest --uninstall
75
+ ```
76
+
77
+ Or via global npm:
32
78
 
33
79
  ```bash
34
80
  npm install -g @bojackduy/opencode-loopd@latest
35
- opencode-loopd
81
+ opencode-loopd # install
82
+ opencode-loopd --uninstall # remove
36
83
  ```
37
84
 
38
85
  Then restart OpenCode.
39
86
 
87
+ ### Manual (local dev)
88
+
89
+ Add to `~/.config/opencode/opencode.jsonc`:
90
+
91
+ ```json
92
+ {
93
+ "plugin": ["@bojackduy/opencode-loopd"]
94
+ }
95
+ ```
96
+
97
+ Or for local checkout:
98
+
99
+ ```json
100
+ {
101
+ "plugin": ["/Users/you/Code/opencode-loopd"]
102
+ }
103
+ ```
104
+
105
+ `opencode-loopd` exposes:
106
+ - `opencode-loopd/server` → engine plugin
107
+ - `opencode-loopd/tui` → dashboard plugin
108
+
40
109
  ## Usage
41
110
 
42
- ### Create a goal
111
+ ### 1. Create a goal — `/goal`
43
112
 
44
113
  From any session, tell the agent:
45
114
 
46
115
  ```
47
- Create a background goal to fetch the latest AI news and save 10 items to ai-news.md
116
+ /goal fetch the latest AI news and save 10 items to ai-news.md
117
+ ```
118
+
119
+ Or be explicit:
120
+
121
+ ```
122
+ Create a background goal to find all .ts files in src/, count lines, and write the summary to line-counts.md — verify with grep -c.
48
123
  ```
49
124
 
50
- The agent will use `loopd_create_goal` to start the goal.
125
+ The agent will clarify (what/where/how to verify) and then call `loopd_create_goal`. The worker starts immediately in a hidden child session.
51
126
 
52
- ### Monitor with dashboard
127
+ ### 2. Monitor with dashboard — `/loop`
53
128
 
54
- Press `Ctrl+L` or open the command palette → "Loop Dashboard".
129
+ Press **`Ctrl+L`** or open the command palette → **"Loop Dashboard"** (also `/loop`).
130
+
131
+ Dashboard (NORMAL / INSERT `:`):
55
132
 
56
- Dashboard shortcuts:
57
133
  - `j/k` — move selection
58
- - `g/G` — jump to top/bottom
59
- - `o` — open child session (full transcript)
134
+ - `g/G` — top / bottom
135
+ - `o` — open child session (full transcript, native OpenCode view)
60
136
  - `p/r/R/x` — pause / resume / retry / clear selected goal
61
- - `?`toggle help
62
- - `:`enter insert mode (send message, control commands)
63
- - `Ctrl+N` — return to normal mode
137
+ - `:`insert mode → `:send <message>` to steer, `:force` / `:block` to finish manually
138
+ - `?`toggle help help stays open while you type
139
+ - `Ctrl+N` — back to NORMAL
64
140
  - `q` — close
65
141
 
66
- ### Inspect from main agent
142
+ > The dashboard traps all keys — even when open via the palette, the chat input cursor won't blink underneath.
143
+
144
+ ### 3. Inspect from the main agent (owner tools)
67
145
 
68
- The main agent can use these tools:
146
+ The parent never needs the dashboard — it can ask:
69
147
 
70
148
  ```
71
149
  list_background_goals() — status snapshot
72
- inspect_background_goal() — full detail
73
- read_goal_transcript() — child session transcript
74
- send_goal_input(goalID, msg) — send instruction to child
75
- pause_goal(goalID) pause
76
- resume_goal(goalID) — resume
77
- clear_goal(goalID) — stop and remove
150
+ inspect_background_goal() — full detail: objective, progress, blocker, runtime
151
+ read_goal_transcript() — last N worker messages (what it's doing)
152
+ send_goal_input(goalID, msg) — steer: "skip appendix PDFs"
153
+ pause_goal(goalID) / resume / clear
78
154
  ```
79
155
 
80
- ### Worker tools (child session)
156
+ ### 4. Worker tools (child session)
81
157
 
82
- The child worker has these tools:
158
+ The child sees:
83
159
 
84
- - `get_goal` — read objective, state, criteria
85
- - `report_goal_progress` — report what was done
86
- - `complete_goal` — mark done (must pass checks)
87
- - `block_goal` — mark blocked (needs user)
88
- - `question` (OpenCode builtin) — ask the user; appears in the TUI footer
160
+ - `get_goal` — read objective, contract, progress, failures
161
+ - `report_goal_progress` — after durable writes
162
+ - `complete_goal` — only when checks pass, with evidence
163
+ - `block_goal` — real blocker needing user
164
+ - `question` (OpenCode builtin) — clarification TUI blocker tab
165
+
166
+ Completion is semantic: the child writes the summary; the plugin forwards it to the parent via wake-up injection. If limits are hit, the engine first force-asks the child to `complete_goal`; only if ignored does it auto-block with a generic message.
89
167
 
90
168
  ## Architecture
91
169
 
92
170
  ```
93
171
  Main session (parent)
94
- owner tools: inspect, steer, pause
95
- Loopd engine
96
- continuation steering + accumulated context
97
- Child worker session
98
- goal tools: progress, complete, block, ask
172
+ owner tools: inspect, steer, pause — via inbox
173
+ Loopd engine (plugin server)
174
+ continuation steering + accumulated context
175
+ ↕ parent notification on complete/block (wake-up inject)
176
+ Child worker session (subagent)
177
+ ↕ goal tools: progress, complete, block, question
178
+ • artifacts isolated: .opencode/loopd/goals/<goal-id>/
99
179
  ```
100
180
 
101
- - **Engine-driven loop**: child worksidle detectedengine re-prompts with accumulated context
102
- - **Parent visibility**: owner tools read child transcript, progress history, and current state
103
- - **Bidirectional messaging**: inbox system for user↔child instructions
104
- - **Safety defaults**: maxTurns=50, maxFailures=3
181
+ - **Engine-driven loop** (like Codex): `session.idle` lease + retry + polling → re-prompt child. Not a parent-driven re-prompt machine.
182
+ - **Accumulated context**: each continuation includes progress history, transcript tail, inbox messages, and artifact dir (unless objective names another dir).
183
+ - **Parent wake-up**: when the child calls `complete_goal`/`block_goal`, `tool.execute.after` injects the child's semantic summary into the owner's session — you see "Loop goal … completed: …" without polling.
105
184
 
106
- ## Example
185
+ ## Artifacts
107
186
 
108
- ```
109
- # Create a goal
110
- goal: "Find all .ts files in src/, count lines, write summary to line-counts.md"
187
+ Every goal writes under its own directory (`.opencode/loopd/goals/<goal-id>/`); `progressFile` defaults there. The dashboard and transcript never pollute the project root. If your objective says `save to ./reports/`, that wins.
111
188
 
112
- # Engine loops automatically
113
- Turn 1: child counts files → reports progress
114
- Turn 2: child sees "batch 1 done" → continues
115
- Turn 3: child finishes → calls complete_goal
189
+ ## Example multi-turn with force-finish
116
190
 
117
- # Parent inspects at any time
118
- inspect_background_goal() → shows turn count, progress, status
119
- read_goal_transcript() → shows child's work
191
+ ```
192
+ # Parent: /goal
193
+ goal: "Generate 5 files file1..5.md under artifacts/, 2 lines each — verify with glob."
194
+ maxTurns: 1
195
+
196
+ # Turn 1: child writes file1..3, reports progress
197
+ # → engine hits maxTurns, injects FINAL REPORT REQUIRED
198
+ # Turn 2 (forced): child wraps up, calls complete_goal(summary="created 5 files…", evidence="glob confirmed…")
199
+ # → engine forwards to parent: "Loop goal … completed: created 5 files…"
200
+ # → Artifacts: .opencode/loopd/goals/<id>/file*.md
120
201
  ```
121
202
 
203
+ See `assets/demo.png` above for the dashboard at rest (0 active, 7 done) with vivid status colors, running spinner, and `INSERT` badge.
204
+
122
205
  ## Configuration
123
206
 
124
- Goals accept these config options:
207
+ Goals accept:
125
208
 
126
209
  | Option | Default | Description |
127
210
  |---|---|---|
128
- | `maxTurns` | 50 | Max continuation turns |
129
- | `maxFailures` | 3 | Max consecutive failures before block |
130
- | `maxNoProgress` | 3 | Turns without progress before block |
131
- | `timeoutMs` | 300000 | Per-turn timeout (5 min) |
132
- | `compactEvery` | — | Compact child session every N turns |
133
- | `checks` | [] | Shell commands that must pass for completion |
134
- | `progressFile` | | Markdown file for transaction state |
211
+ | `maxTurns` | `50` | Max continuation turns before force-finish |
212
+ | `maxFailures` | `3` | Consecutive failures before block |
213
+ | `maxNoProgress` | `3` | Turns without progress before force-finish |
214
+ | `timeoutMs` | `300000` | Per-turn lease (5 min) |
215
+ | `compactEvery` | — | Compact child every N turns |
216
+ | `checks` | `[]` | Shell commands that must pass for `complete_goal` |
217
+ | `progressFile` | `<artifactDir>/progress.md` | Transaction state file |
218
+ | `artifactDir` | `goals/<id>/` | Auto — override only if objective names another dir |
219
+
220
+ Artifacts, logs, and state live under `.opencode/loopd/` (project-local). Locks live in `/tmp/loopd-locks/<project-hash>/` to avoid snapshot noise.
221
+
222
+ ## Dashboard tips
223
+
224
+ - Press `:` then type bare text → sent as `send_goal_input` to the selected goal.
225
+ - Bare `:force my summary --evidence "glob ok"` bypasses verification when the child is stuck.
226
+ - `o` jumps to the native OpenCode child session — full transcript, diffs, tool calls.
227
+
228
+ ## Related / SEO
229
+
230
+ OpenCode plugin for **background agents**, **autonomous subagents**, **loop**, **goal**, **TUI dashboard**, **Codex**-style continuation, **Claude Code** loop alternative, **opencode-tui**, **worker sessions**. Pairs well with `opencode-telescope` (search) and `lazyjira`/`lazyconfluence` (ATUI) from the same author.
135
231
 
136
232
  ## License
137
233
 
Binary file
package/dist/server.js CHANGED
@@ -18,22 +18,22 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
18
  // src/infrastructure/state-repository.ts
19
19
  var exports_state_repository = {};
20
20
  __export(exports_state_repository, {
21
- writeState: () => writeState,
22
- writeControlResponse: () => writeControlResponse,
23
- writeControlRequest: () => writeControlRequest,
24
- recoverStaleProcessing: () => recoverStaleProcessing,
25
- readState: () => readState,
26
- readEvents: () => readEvents,
27
- readControlResponse: () => readControlResponse,
28
- readControlRequest: () => readControlRequest,
29
- mutateState: () => mutateState,
30
- listPendingRequests: () => listPendingRequests,
31
- goalArtifactDir: () => goalArtifactDir,
32
- ensureGoalArtifactDir: () => ensureGoalArtifactDir,
33
- drainGoalInbox: () => drainGoalInbox,
34
- claimControlRequest: () => claimControlRequest,
21
+ appendEvent: () => appendEvent,
35
22
  appendGoalInbox: () => appendGoalInbox,
36
- appendEvent: () => appendEvent
23
+ claimControlRequest: () => claimControlRequest,
24
+ drainGoalInbox: () => drainGoalInbox,
25
+ ensureGoalArtifactDir: () => ensureGoalArtifactDir,
26
+ goalArtifactDir: () => goalArtifactDir,
27
+ listPendingRequests: () => listPendingRequests,
28
+ mutateState: () => mutateState,
29
+ readControlRequest: () => readControlRequest,
30
+ readControlResponse: () => readControlResponse,
31
+ readEvents: () => readEvents,
32
+ readState: () => readState,
33
+ recoverStaleProcessing: () => recoverStaleProcessing,
34
+ writeControlRequest: () => writeControlRequest,
35
+ writeControlResponse: () => writeControlResponse,
36
+ writeState: () => writeState
37
37
  });
38
38
  import { promises as fs } from "fs";
39
39
  import path from "path";
package/dist/tui.js CHANGED
@@ -833,7 +833,7 @@ function LoopDashboard(props) {
833
833
  fg: "yellow",
834
834
  bold: true
835
835
  });
836
- _$insert(_el$26, _$createComponent(For, {
836
+ _$insert(_el$27, _$createComponent(For, {
837
837
  get each() {
838
838
  return commandHelp().split(`
839
839
  `);
@@ -841,16 +841,16 @@ function LoopDashboard(props) {
841
841
  children: (line) => {
842
842
  const isHeader = line.startsWith("Modes:") || line.startsWith("Nav:") || line.startsWith("Commands");
843
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", {
844
+ return [`
845
+ `, (() => {
846
+ var _el$41 = _$createElement("span");
847
+ _$insert(_el$41, line);
848
+ _$effect((_$p) => _$setProp(_el$41, "style", {
849
849
  fg: isHeader ? theme().primary : isCmd ? theme().warning : theme().text,
850
850
  bold: isHeader
851
851
  }, _$p));
852
852
  return _el$41;
853
- })();
853
+ })()];
854
854
  }
855
855
  }), null);
856
856
  _$effect((_$p) => _$setProp(_el$26, "backgroundColor", theme().background, _$p));
@@ -868,31 +868,31 @@ function LoopDashboard(props) {
868
868
  },
869
869
  get fallback() {
870
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");
871
+ var _el$42 = _$createElement("box"), _el$43 = _$createElement("text"), _el$44 = _$createElement("span"), _el$46 = _$createElement("span"), _el$48 = _$createElement("span"), _el$50 = _$createElement("text"), _el$51 = _$createElement("span"), _el$53 = _$createElement("span"), _el$55 = _$createElement("span"), _el$57 = _$createElement("span"), _el$59 = _$createElement("span"), _el$61 = _$createElement("span"), _el$63 = _$createElement("span");
872
+ _$insertNode(_el$42, _el$43);
873
+ _$insertNode(_el$42, _el$50);
874
+ _$setProp(_el$42, "flexDirection", "column");
875
+ _$setProp(_el$42, "gap", 1);
872
876
  _$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.`));
877
+ _$insertNode(_el$43, _el$46);
878
+ _$insertNode(_el$43, _el$48);
879
+ _$insertNode(_el$44, _$createTextNode(`No active goals.`));
880
+ _$insertNode(_el$46, _$createTextNode(` /goal`));
881
+ _$insertNode(_el$48, _$createTextNode(` in parent chat to create one.`));
882
+ _$insertNode(_el$50, _el$51);
883
+ _$insertNode(_el$50, _el$53);
884
+ _$insertNode(_el$50, _el$55);
885
+ _$insertNode(_el$50, _el$57);
886
+ _$insertNode(_el$50, _el$59);
887
+ _$insertNode(_el$50, _el$61);
888
+ _$insertNode(_el$50, _el$63);
889
+ _$insertNode(_el$51, _$createTextNode(`Tip: `));
890
+ _$insertNode(_el$53, _$createTextNode(`:send`));
891
+ _$insertNode(_el$55, _$createTextNode(` to steer the worker \xB7 `));
892
+ _$insertNode(_el$57, _$createTextNode(`o`));
893
+ _$insertNode(_el$59, _$createTextNode(` to open child \xB7 `));
894
+ _$insertNode(_el$61, _$createTextNode(`:force`));
895
+ _$insertNode(_el$63, _$createTextNode(` to complete manually.`));
896
896
  _$effect((_p$) => {
897
897
  var _v$21 = {
898
898
  fg: theme().textMuted
@@ -915,16 +915,16 @@ function LoopDashboard(props) {
915
915
  }, _v$30 = {
916
916
  fg: theme().textMuted
917
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));
918
+ _v$21 !== _p$.e && (_p$.e = _$setProp(_el$44, "style", _v$21, _p$.e));
919
+ _v$22 !== _p$.t && (_p$.t = _$setProp(_el$46, "style", _v$22, _p$.t));
920
+ _v$23 !== _p$.a && (_p$.a = _$setProp(_el$48, "style", _v$23, _p$.a));
921
+ _v$24 !== _p$.o && (_p$.o = _$setProp(_el$51, "style", _v$24, _p$.o));
922
+ _v$25 !== _p$.i && (_p$.i = _$setProp(_el$53, "style", _v$25, _p$.i));
923
+ _v$26 !== _p$.n && (_p$.n = _$setProp(_el$55, "style", _v$26, _p$.n));
924
+ _v$27 !== _p$.s && (_p$.s = _$setProp(_el$57, "style", _v$27, _p$.s));
925
+ _v$28 !== _p$.h && (_p$.h = _$setProp(_el$59, "style", _v$28, _p$.h));
926
+ _v$29 !== _p$.r && (_p$.r = _$setProp(_el$61, "style", _v$29, _p$.r));
927
+ _v$30 !== _p$.d && (_p$.d = _$setProp(_el$63, "style", _v$30, _p$.d));
928
928
  return _p$;
929
929
  }, {
930
930
  e: undefined,
@@ -938,7 +938,7 @@ function LoopDashboard(props) {
938
938
  r: undefined,
939
939
  d: undefined
940
940
  });
941
- return _el$43;
941
+ return _el$42;
942
942
  })();
943
943
  },
944
944
  get children() {
@@ -961,86 +961,86 @@ function LoopDashboard(props) {
961
961
  return phaseColor(runtime().phase || "idle", theme());
962
962
  };
963
963
  return (() => {
964
- var _el$66 = _$createElement("box"), _el$67 = _$createElement("text"), _el$68 = _$createElement("span"), _el$69 = _$createElement("span"), _el$71 = _$createElement("span");
964
+ var _el$65 = _$createElement("box"), _el$66 = _$createElement("text"), _el$67 = _$createElement("span"), _el$68 = _$createElement("span"), _el$70 = _$createElement("span");
965
+ _$insertNode(_el$65, _el$66);
966
+ _$setProp(_el$65, "flexDirection", "row");
967
+ _$setProp(_el$65, "paddingLeft", 1);
968
+ _$setProp(_el$65, "paddingRight", 1);
965
969
  _$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, (() => {
970
+ _$insertNode(_el$66, _el$68);
971
+ _$insertNode(_el$66, _el$70);
972
+ _$insert(_el$67, (() => {
973
973
  var _c$2 = _$memo(() => !!isActive());
974
974
  return () => _c$2() ? `\u25B6 ${statusIcon(goal.status)} ${goal.name}` : ` ${statusIcon(goal.status)} ${goal.name}`;
975
975
  })());
976
- _$insertNode(_el$69, _$createTextNode(` \u2502 `));
977
- _$insert(_el$71, () => goal.status.toUpperCase());
978
- _$insert(_el$67, (() => {
976
+ _$insertNode(_el$68, _$createTextNode(` \u2502 `));
977
+ _$insert(_el$70, () => goal.status.toUpperCase());
978
+ _$insert(_el$66, (() => {
979
979
  var _c$3 = _$memo(() => !!runtime());
980
980
  return () => _c$3() && [(() => {
981
- var _el$72 = _$createElement("span");
982
- _$insertNode(_el$72, _$createTextNode(` \u2502 `));
983
- _$effect((_$p) => _$setProp(_el$72, "style", {
981
+ var _el$71 = _$createElement("span");
982
+ _$insertNode(_el$71, _$createTextNode(` \u2502 `));
983
+ _$effect((_$p) => _$setProp(_el$71, "style", {
984
984
  fg: theme().textMuted
985
985
  }, _$p));
986
- return _el$72;
986
+ return _el$71;
987
987
  })(), (() => {
988
- var _el$74 = _$createElement("span"), _el$75 = _$createTextNode(` `);
989
- _$insertNode(_el$74, _el$75);
990
- _$insert(_el$74, (() => {
988
+ var _el$73 = _$createElement("span"), _el$74 = _$createTextNode(` `);
989
+ _$insertNode(_el$73, _el$74);
990
+ _$insert(_el$73, (() => {
991
991
  var _c$6 = _$memo(() => runtime().phase === "running");
992
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", {
993
+ })(), _el$74);
994
+ _$insert(_el$73, () => runtime().phase.toUpperCase(), null);
995
+ _$effect((_$p) => _$setProp(_el$73, "style", {
996
996
  fg: turnColor(),
997
997
  bold: runtime().phase === "running"
998
998
  }, _$p));
999
- return _el$74;
999
+ return _el$73;
1000
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", {
1001
+ var _el$75 = _$createElement("span"), _el$76 = _$createTextNode(` `);
1002
+ _$insertNode(_el$75, _el$76);
1003
+ _$insert(_el$75, () => runtime().turnCount, null);
1004
+ _$insert(_el$75, maxTurns ? `/${maxTurns}` : "", null);
1005
+ _$effect((_$p) => _$setProp(_el$75, "style", {
1006
1006
  fg: turnColor()
1007
1007
  }, _$p));
1008
- return _el$76;
1008
+ return _el$75;
1009
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", {
1010
+ var _el$77 = _$createElement("span"), _el$78 = _$createTextNode(` `);
1011
+ _$insertNode(_el$77, _el$78);
1012
+ _$insert(_el$77, () => ageLabel(runtime().lastProgressAt || runtime().lastRunAt, clock()), null);
1013
+ _$effect((_$p) => _$setProp(_el$77, "style", {
1014
1014
  fg: theme().textMuted
1015
1015
  }, _$p));
1016
- return _el$78;
1016
+ return _el$77;
1017
1017
  })()];
1018
1018
  })(), null);
1019
- _$insert(_el$67, (() => {
1019
+ _$insert(_el$66, (() => {
1020
1020
  var _c$4 = _$memo(() => !!(runtime() && runtime().consecutiveFailures > 0));
1021
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", {
1022
+ var _el$79 = _$createElement("span"), _el$80 = _$createTextNode(` \u2502 \u26A0 `), _el$81 = _$createTextNode(` fail`);
1023
+ _$insertNode(_el$79, _el$80);
1024
+ _$insertNode(_el$79, _el$81);
1025
+ _$insert(_el$79, () => runtime().consecutiveFailures, _el$81);
1026
+ _$effect((_$p) => _$setProp(_el$79, "style", {
1027
1027
  fg: theme().error,
1028
1028
  bold: true
1029
1029
  }, _$p));
1030
- return _el$80;
1030
+ return _el$79;
1031
1031
  })();
1032
1032
  })(), null);
1033
- _$insert(_el$67, (() => {
1033
+ _$insert(_el$66, (() => {
1034
1034
  var _c$5 = _$memo(() => !!(runtime() && (runtime().noProgressCount || 0) > 0));
1035
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", {
1036
+ var _el$82 = _$createElement("span"), _el$83 = _$createTextNode(` \u2502 `), _el$84 = _$createTextNode(` no-progress`);
1037
+ _$insertNode(_el$82, _el$83);
1038
+ _$insertNode(_el$82, _el$84);
1039
+ _$insert(_el$82, () => runtime().noProgressCount, _el$84);
1040
+ _$effect((_$p) => _$setProp(_el$82, "style", {
1041
1041
  fg: theme().warning
1042
1042
  }, _$p));
1043
- return _el$83;
1043
+ return _el$82;
1044
1044
  })();
1045
1045
  })(), null);
1046
1046
  _$effect((_p$) => {
@@ -1053,10 +1053,10 @@ function LoopDashboard(props) {
1053
1053
  fg: statusColor(goal.status, theme()),
1054
1054
  bold: true
1055
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));
1056
+ _v$31 !== _p$.e && (_p$.e = _$setProp(_el$65, "backgroundColor", _v$31, _p$.e));
1057
+ _v$32 !== _p$.t && (_p$.t = _$setProp(_el$67, "style", _v$32, _p$.t));
1058
+ _v$33 !== _p$.a && (_p$.a = _$setProp(_el$68, "style", _v$33, _p$.a));
1059
+ _v$34 !== _p$.o && (_p$.o = _$setProp(_el$70, "style", _v$34, _p$.o));
1060
1060
  return _p$;
1061
1061
  }, {
1062
1062
  e: undefined,
@@ -1064,7 +1064,7 @@ function LoopDashboard(props) {
1064
1064
  a: undefined,
1065
1065
  o: undefined
1066
1066
  });
1067
- return _el$66;
1067
+ return _el$65;
1068
1068
  })();
1069
1069
  }
1070
1070
  });
@@ -1077,144 +1077,144 @@ function LoopDashboard(props) {
1077
1077
  children: (goal) => {
1078
1078
  const rt = () => state()?.runtimes.find((r) => r.goalID === goal().id);
1079
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");
1080
+ var _el$85 = _$createElement("box"), _el$86 = _$createElement("text"), _el$87 = _$createElement("span"), _el$88 = _$createTextNode(` `), _el$89 = _$createElement("span"), _el$90 = _$createTextNode(` `), _el$91 = _$createTextNode(`
1081
+ `), _el$92 = _$createElement("span");
1082
+ _$insertNode(_el$85, _el$86);
1083
+ _$setProp(_el$85, "flexDirection", "column");
1084
+ _$setProp(_el$85, "border", true);
1085
+ _$setProp(_el$85, "padding", 1);
1086
+ _$setProp(_el$85, "flexShrink", 0);
1087
+ _$setProp(_el$85, "maxHeight", 10);
1082
1088
  _$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);
1089
+ _$insertNode(_el$86, _el$89);
1090
+ _$insertNode(_el$86, _el$91);
1091
+ _$insertNode(_el$86, _el$92);
1088
1092
  _$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, (() => {
1093
+ _$insert(_el$87, () => statusIcon(goal().status), _el$88);
1094
+ _$insert(_el$87, () => goal().name, null);
1095
+ _$insertNode(_el$89, _el$90);
1096
+ _$insert(_el$89, () => goal().status.toUpperCase(), null);
1097
+ _$insert(_el$86, (() => {
1098
1098
  var _c$7 = _$memo(() => !!rt());
1099
1099
  return () => _c$7() && [(() => {
1100
- var _el$94 = _$createElement("span");
1101
- _$insertNode(_el$94, _$createTextNode(` \u2502 `));
1102
- _$effect((_$p) => _$setProp(_el$94, "style", {
1100
+ var _el$93 = _$createElement("span");
1101
+ _$insertNode(_el$93, _$createTextNode(` \u2502 `));
1102
+ _$effect((_$p) => _$setProp(_el$93, "style", {
1103
1103
  fg: theme().textMuted
1104
1104
  }, _$p));
1105
- return _el$94;
1105
+ return _el$93;
1106
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", {
1107
+ var _el$95 = _$createElement("span"), _el$96 = _$createTextNode(` `);
1108
+ _$insertNode(_el$95, _el$96);
1109
+ _$insert(_el$95, () => phaseIcon(rt().phase), _el$96);
1110
+ _$insert(_el$95, () => rt().phase, null);
1111
+ _$effect((_$p) => _$setProp(_el$95, "style", {
1112
1112
  fg: phaseColor(rt().phase, theme()),
1113
1113
  bold: true
1114
1114
  }, _$p));
1115
- return _el$96;
1115
+ return _el$95;
1116
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", {
1117
+ var _el$97 = _$createElement("span"), _el$98 = _$createTextNode(` turn `);
1118
+ _$insertNode(_el$97, _el$98);
1119
+ _$insert(_el$97, () => rt().turnCount, null);
1120
+ _$effect((_$p) => _$setProp(_el$97, "style", {
1121
1121
  fg: theme().textMuted
1122
1122
  }, _$p));
1123
- return _el$98;
1123
+ return _el$97;
1124
1124
  })()];
1125
- })(), _el$92);
1126
- _$insert(_el$93, () => goal().objective.slice(0, 160));
1127
- _$insert(_el$87, (() => {
1125
+ })(), _el$91);
1126
+ _$insert(_el$92, () => goal().objective.slice(0, 160));
1127
+ _$insert(_el$86, (() => {
1128
1128
  var _c$8 = _$memo(() => !!goal().lastProgress);
1129
1129
  return () => _c$8() && [(() => {
1130
- var _el$100 = _$createElement("span"), _el$101 = _$createTextNode(`
1130
+ var _el$99 = _$createElement("span"), _el$100 = _$createTextNode(`
1131
1131
  \u2714 `);
1132
- _$insertNode(_el$100, _el$101);
1133
- _$effect((_$p) => _$setProp(_el$100, "style", {
1132
+ _$insertNode(_el$99, _el$100);
1133
+ _$effect((_$p) => _$setProp(_el$99, "style", {
1134
1134
  fg: theme().success
1135
1135
  }, _$p));
1136
- return _el$100;
1136
+ return _el$99;
1137
1137
  })(), (() => {
1138
- var _el$103 = _$createElement("span");
1139
- _$insert(_el$103, () => goal().lastProgress.summary.slice(0, 100));
1140
- _$effect((_$p) => _$setProp(_el$103, "style", {
1138
+ var _el$102 = _$createElement("span");
1139
+ _$insert(_el$102, () => goal().lastProgress.summary.slice(0, 100));
1140
+ _$effect((_$p) => _$setProp(_el$102, "style", {
1141
1141
  fg: theme().text
1142
1142
  }, _$p));
1143
- return _el$103;
1143
+ return _el$102;
1144
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", {
1145
+ var _el$103 = _$createElement("span"), _el$104 = _$createTextNode(` \u2192 `);
1146
+ _$insertNode(_el$103, _el$104);
1147
+ _$insert(_el$103, () => goal().lastProgress.next?.slice(0, 60) || "", null);
1148
+ _$effect((_$p) => _$setProp(_el$103, "style", {
1149
1149
  fg: theme().textMuted
1150
1150
  }, _$p));
1151
- return _el$104;
1151
+ return _el$103;
1152
1152
  })()];
1153
1153
  })(), null);
1154
- _$insert(_el$87, (() => {
1154
+ _$insert(_el$86, (() => {
1155
1155
  var _c$9 = _$memo(() => !!goal().blocker);
1156
1156
  return () => _c$9() && [(() => {
1157
- var _el$106 = _$createElement("span"), _el$107 = _$createTextNode(`
1157
+ var _el$105 = _$createElement("span"), _el$106 = _$createTextNode(`
1158
1158
  \u2716 blocked: `);
1159
- _$insertNode(_el$106, _el$107);
1160
- _$effect((_$p) => _$setProp(_el$106, "style", {
1159
+ _$insertNode(_el$105, _el$106);
1160
+ _$effect((_$p) => _$setProp(_el$105, "style", {
1161
1161
  fg: theme().error,
1162
1162
  bold: true
1163
1163
  }, _$p));
1164
- return _el$106;
1164
+ return _el$105;
1165
1165
  })(), (() => {
1166
- var _el$109 = _$createElement("span");
1167
- _$insert(_el$109, () => goal().blocker.reason.slice(0, 140));
1168
- _$effect((_$p) => _$setProp(_el$109, "style", {
1166
+ var _el$108 = _$createElement("span");
1167
+ _$insert(_el$108, () => goal().blocker.reason.slice(0, 140));
1168
+ _$effect((_$p) => _$setProp(_el$108, "style", {
1169
1169
  fg: theme().error
1170
1170
  }, _$p));
1171
- return _el$109;
1171
+ return _el$108;
1172
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", {
1173
+ var _el$109 = _$createElement("span"), _el$110 = _$createTextNode(` \u2014 `);
1174
+ _$insertNode(_el$109, _el$110);
1175
+ _$insert(_el$109, () => goal().blocker.needed.slice(0, 60), null);
1176
+ _$effect((_$p) => _$setProp(_el$109, "style", {
1177
1177
  fg: theme().textMuted
1178
1178
  }, _$p));
1179
- return _el$110;
1179
+ return _el$109;
1180
1180
  })()];
1181
1181
  })(), null);
1182
- _$insert(_el$87, (() => {
1182
+ _$insert(_el$86, (() => {
1183
1183
  var _c$0 = _$memo(() => !!goal().config.artifactDir);
1184
1184
  return () => _c$0() && [(() => {
1185
- var _el$112 = _$createElement("span"), _el$113 = _$createTextNode(`
1185
+ var _el$111 = _$createElement("span"), _el$112 = _$createTextNode(`
1186
1186
  \uD83D\uDCC1 `);
1187
- _$insertNode(_el$112, _el$113);
1188
- _$effect((_$p) => _$setProp(_el$112, "style", {
1187
+ _$insertNode(_el$111, _el$112);
1188
+ _$effect((_$p) => _$setProp(_el$111, "style", {
1189
1189
  fg: theme().accent
1190
1190
  }, _$p));
1191
- return _el$112;
1191
+ return _el$111;
1192
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", {
1193
+ var _el$114 = _$createElement("span");
1194
+ _$insert(_el$114, () => String(goal().config.artifactDir).replace(String(props.directory), "."));
1195
+ _$effect((_$p) => _$setProp(_el$114, "style", {
1196
1196
  fg: theme().textMuted
1197
1197
  }, _$p));
1198
- return _el$115;
1198
+ return _el$114;
1199
1199
  })()];
1200
1200
  })(), null);
1201
- _$insert(_el$87, (() => {
1201
+ _$insert(_el$86, (() => {
1202
1202
  var _c$1 = _$memo(() => !!rt()?.lastError);
1203
1203
  return () => _c$1() && [(() => {
1204
- var _el$116 = _$createElement("span"), _el$117 = _$createTextNode(`
1204
+ var _el$115 = _$createElement("span"), _el$116 = _$createTextNode(`
1205
1205
  \u26A0 `);
1206
- _$insertNode(_el$116, _el$117);
1207
- _$effect((_$p) => _$setProp(_el$116, "style", {
1206
+ _$insertNode(_el$115, _el$116);
1207
+ _$effect((_$p) => _$setProp(_el$115, "style", {
1208
1208
  fg: theme().error
1209
1209
  }, _$p));
1210
- return _el$116;
1210
+ return _el$115;
1211
1211
  })(), (() => {
1212
- var _el$119 = _$createElement("span");
1213
- _$insert(_el$119, () => rt().lastError.slice(0, 120));
1214
- _$effect((_$p) => _$setProp(_el$119, "style", {
1212
+ var _el$118 = _$createElement("span");
1213
+ _$insert(_el$118, () => rt().lastError.slice(0, 120));
1214
+ _$effect((_$p) => _$setProp(_el$118, "style", {
1215
1215
  fg: theme().error
1216
1216
  }, _$p));
1217
- return _el$119;
1217
+ return _el$118;
1218
1218
  })()];
1219
1219
  })(), null);
1220
1220
  _$effect((_p$) => {
@@ -1226,10 +1226,10 @@ function LoopDashboard(props) {
1226
1226
  }, _v$38 = {
1227
1227
  fg: theme().text
1228
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));
1229
+ _v$35 !== _p$.e && (_p$.e = _$setProp(_el$85, "borderColor", _v$35, _p$.e));
1230
+ _v$36 !== _p$.t && (_p$.t = _$setProp(_el$87, "style", _v$36, _p$.t));
1231
+ _v$37 !== _p$.a && (_p$.a = _$setProp(_el$89, "style", _v$37, _p$.a));
1232
+ _v$38 !== _p$.o && (_p$.o = _$setProp(_el$92, "style", _v$38, _p$.o));
1233
1233
  return _p$;
1234
1234
  }, {
1235
1235
  e: undefined,
@@ -1237,7 +1237,7 @@ function LoopDashboard(props) {
1237
1237
  a: undefined,
1238
1238
  o: undefined
1239
1239
  });
1240
- return _el$86;
1240
+ return _el$85;
1241
1241
  })();
1242
1242
  }
1243
1243
  }), null);
@@ -1263,22 +1263,22 @@ function LoopDashboard(props) {
1263
1263
  return events().slice(-10);
1264
1264
  },
1265
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, (() => {
1266
+ var _el$119 = _$createElement("text"), _el$120 = _$createElement("span"), _el$121 = _$createElement("span"), _el$122 = _$createTextNode(` `);
1267
+ _$insertNode(_el$119, _el$120);
1268
+ _$insertNode(_el$119, _el$121);
1269
+ _$insert(_el$120, () => String(ev.type));
1270
+ _$insertNode(_el$121, _el$122);
1271
+ _$insert(_el$121, () => ev.goalID?.slice(0, 8), null);
1272
+ _$insert(_el$119, (() => {
1273
1273
  var _c$10 = _$memo(() => !!ev.summary);
1274
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", {
1275
+ var _el$123 = _$createElement("span"), _el$124 = _$createTextNode(` \u2014 `);
1276
+ _$insertNode(_el$123, _el$124);
1277
+ _$insert(_el$123, () => String(ev.summary).slice(0, 60), null);
1278
+ _$effect((_$p) => _$setProp(_el$123, "style", {
1279
1279
  fg: theme().text
1280
1280
  }, _$p));
1281
- return _el$124;
1281
+ return _el$123;
1282
1282
  })();
1283
1283
  })(), null);
1284
1284
  _$effect((_p$) => {
@@ -1288,14 +1288,14 @@ function LoopDashboard(props) {
1288
1288
  }, _v$40 = {
1289
1289
  fg: theme().textMuted
1290
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));
1291
+ _v$39 !== _p$.e && (_p$.e = _$setProp(_el$120, "style", _v$39, _p$.e));
1292
+ _v$40 !== _p$.t && (_p$.t = _$setProp(_el$121, "style", _v$40, _p$.t));
1293
1293
  return _p$;
1294
1294
  }, {
1295
1295
  e: undefined,
1296
1296
  t: undefined
1297
1297
  });
1298
- return _el$120;
1298
+ return _el$119;
1299
1299
  })()
1300
1300
  }), null);
1301
1301
  _$effect((_p$) => {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@bojackduy/opencode-loopd",
4
- "version": "1.0.0",
5
- "description": "Codex-inspired background goal engine for OpenCode — autonomous child sessions, engine-driven loop, and modal TUI dashboard",
4
+ "version": "1.0.1",
5
+ "description": "Codex-inspired background goal engine for OpenCode — autonomous subagents, engine-driven loop, child worker sessions and modal TUI dashboard. Like Claude Code loop for OpenCode.",
6
6
  "type": "module",
7
7
  "license": "AGPL-3.0-only",
8
8
  "private": false,
@@ -21,9 +21,13 @@
21
21
  "loop",
22
22
  "goal",
23
23
  "background",
24
+ "background-agent",
24
25
  "automation",
25
- "worker",
26
+ "autonomous",
27
+ "agent",
26
28
  "subagent",
29
+ "codex",
30
+ "claude-code",
27
31
  "opencode-loopd"
28
32
  ],
29
33
  "bin": {
@@ -34,6 +38,7 @@
34
38
  "scripts",
35
39
  "skills",
36
40
  "commands",
41
+ "assets",
37
42
  "README.md",
38
43
  "LICENSE",
39
44
  "CHANGELOG.md"
@@ -45,6 +50,9 @@
45
50
  "bun": ">=1.1.0"
46
51
  },
47
52
  "exports": {
53
+ ".": {
54
+ "import": "./dist/server.js"
55
+ },
48
56
  "./server": {
49
57
  "import": "./dist/server.js"
50
58
  },