@bojackduy/opencode-loopd 1.0.1 → 1.2.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/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ![Loop Dashboard — modal TUI with goals, running indicator, and insert mode](assets/demo.png)
13
13
 
14
- *Modal dashboard (`/loop` / `Ctrl+L`): zero chat pollution — keys are trapped inside the dialog, NORMAL vs INSERT modes, vivid per-status coloring.*
14
+ *Modal dashboard (`/loop` / `<leader>d`): zero chat pollution — keys are trapped inside the dialog, NORMAL vs INSERT modes, vivid per-status coloring.*
15
15
 
16
16
  ## Why opencode-loopd
17
17
 
@@ -19,7 +19,7 @@
19
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
20
  - **Parent ↔ child visibility** — `list/inspect/read_transcript/send_input` give the parent full observability. Bidirectional inbox lets you steer mid-run.
21
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.
22
+ - **Modal TUI dashboard** — `<leader>d` or `/loop` opens a focused dialog (no leak to chat prompt). Vim-style navigation, live running indicator, per-status borders.
23
23
 
24
24
  Keywords: `opencode` `opencode-plugin` `background-agent` `autonomous` `subagent` `loop` `goal` `tui` `codex` `claude-code` `worker`
25
25
 
@@ -49,7 +49,7 @@ Add the package to **both** configs.
49
49
  }
50
50
  ```
51
51
 
52
- Then restart OpenCode. Verify with `/loop` (palette → Loop Dashboard) or `Ctrl+L`.
52
+ Then restart OpenCode. Verify with `/loop` (palette → Loop Dashboard) or `<leader>d`.
53
53
 
54
54
  For a local checkout:
55
55
 
@@ -126,7 +126,7 @@ The agent will clarify (what/where/how to verify) and then call `loopd_create_go
126
126
 
127
127
  ### 2. Monitor with dashboard — `/loop`
128
128
 
129
- Press **`Ctrl+L`** or open the command palette → **"Loop Dashboard"** (also `/loop`).
129
+ Press **`<leader>d`** or open the command palette → **"Loop Dashboard"** (also `/loop`).
130
130
 
131
131
  Dashboard (NORMAL / INSERT `:`):
132
132
 
@@ -168,14 +168,31 @@ Completion is semantic: the child writes the summary; the plugin forwards it to
168
168
  ## Architecture
169
169
 
170
170
  ```
171
- Main session (parent)
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>/
171
+ ┌─────────────────────────┐
172
+ Main session (parent) │
173
+ │ owner tools │
174
+ inspect / steer / pause│
175
+ └────────────┬────────────┘
176
+ │ inbox child
177
+ wake-up engine
178
+ ┌───────────────────────┼───────────────────────┐
179
+ ▼ ▼ ▼
180
+ ┌─────────┐ ┌──────────┐ ┌─────────┐
181
+ │ Goal A │ │ Goal B │ │ Goal C │
182
+ │ active │ │ blocked │ │ paused │
183
+ └────┬────┘ └────┬─────┘ └────┬────┘
184
+ │ │ │
185
+ └─────────────────────┼───────────────────────┘
186
+
187
+ ┌─────────────┴─────────────┐
188
+ ▼ ▼
189
+ Loopd engine ──────────► Child worker (subagent)
190
+ (plugin server) steering get_goal / report / complete
191
+ │ lease / retry / polling │ block / question
192
+ │ force-finish / notify ▼
193
+ │ .opencode/loopd/goals/<id>/
194
+ │ progress.md / artifacts
195
+ └─────────────────────────────────┘
179
196
  ```
180
197
 
181
198
  - **Engine-driven loop** (like Codex): `session.idle` → lease + retry + polling → re-prompt child. Not a parent-driven re-prompt machine.
package/commands/goal.md CHANGED
@@ -15,6 +15,6 @@ When you have enough to write a concrete objective:
15
15
  - `checks` — optional shell commands that must pass before the goal can be marked complete (e.g. `["npm test"]`)
16
16
  - `progressFile` — optional path to a markdown progress file
17
17
  - limits — optional `maxTurns`, `maxNoProgress`, `maxFailures`, `compactEvery`, `timeoutMs`
18
- 2. After it returns, tell the user the goal is running in the background and they can monitor it with `/loop` (or Ctrl+L).
18
+ 2. After it returns, tell the user the goal is running in the background and they can monitor it with `/loop` (or <leader>d).
19
19
 
20
20
  Important: the worker session runs autonomously — do not try to do the goal's work in this chat. This chat only creates the goal.
package/dist/server.js CHANGED
@@ -1733,7 +1733,7 @@ function goalTools(dir, goalService, hostSessionID) {
1733
1733
  workerSessionID: worker.workerSessionID,
1734
1734
  artifactDir: goal.config.artifactDir,
1735
1735
  name: args.name,
1736
- message: `Goal "${args.name}" created and started in the background. Artifacts: ${goal.config.artifactDir}. Monitor with /loop (Ctrl+L).`
1736
+ message: `Goal "${args.name}" created and started in the background. Artifacts: ${goal.config.artifactDir}. Monitor with /loop (<leader>d).`
1737
1737
  })
1738
1738
  };
1739
1739
  } catch (error) {
package/dist/tui.js CHANGED
@@ -246,7 +246,7 @@ function tokenize(input) {
246
246
  }
247
247
  function commandHelp() {
248
248
  return [
249
- "Modes: : insert \u2192 send/commands, Ctrl+N \u2192 normal, ? toggle help",
249
+ "Modes: : insert \u2192 send/commands, Ctrl+N \u2192 normal, ? toggle help, Shift+B bug report",
250
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
251
  "Commands (insert mode, : prefix):",
252
252
  " :send <message> Send instruction to selected goal",
@@ -254,12 +254,87 @@ function commandHelp() {
254
254
  " :force <summary> --evidence <text> Force-complete (bypass checks)",
255
255
  " :block <reason> --needed <text> Force-block the selected goal",
256
256
  " :pause / :resume / :retry / :clear Quick controls (also p/r/R/x)",
257
+ " :bug / :report Open prefilled GitHub bug report",
257
258
  " :logs / :help / :q Toggle logs / help / close",
258
259
  " Tip: create goals via /goal in the parent chat (agent clarifies first)."
259
260
  ].join(`
260
261
  `);
261
262
  }
262
263
 
264
+ // src/browser.ts
265
+ import { spawn } from "child_process";
266
+ var LOOPD_ISSUES_URL = "https://github.com/bojackduy/opencode-loopd/issues/new";
267
+ function bugReportUrl(context = {}) {
268
+ const url = new URL(LOOPD_ISSUES_URL);
269
+ url.searchParams.set("title", "bug: ");
270
+ url.searchParams.set("body", [
271
+ "## What happened?",
272
+ "",
273
+ "Describe the unexpected behavior.",
274
+ "",
275
+ "## What did you expect?",
276
+ "",
277
+ "Describe the expected behavior.",
278
+ "",
279
+ "## Steps to reproduce",
280
+ "",
281
+ "1. ",
282
+ "2. ",
283
+ "3. ",
284
+ "",
285
+ "## Environment",
286
+ "",
287
+ `- opencode-loopd version: ${context.runtimeLabel ?? ""}`,
288
+ `- OS: ${process.platform}`,
289
+ "- Terminal:",
290
+ "- Installation: npm / source",
291
+ context.extra ? `
292
+ ## Goal context
293
+
294
+ ${context.extra}
295
+ ` : "",
296
+ "Do not include secrets, API tokens, or .opencode/loopd contents."
297
+ ].join(`
298
+ `));
299
+ return url.toString();
300
+ }
301
+ function openBrowserUrl(value, options = {}) {
302
+ try {
303
+ const url = normalizeBrowserUrl(value);
304
+ const command = browserCommandForUrl(url, options.platform);
305
+ const launch = options.launch ?? launchBrowserCommand;
306
+ launch(command.command, command.args);
307
+ return { status: "opened", url };
308
+ } catch (error) {
309
+ return { status: "blocked", reason: error instanceof Error ? error.message : "Could not open the browser." };
310
+ }
311
+ }
312
+ function browserCommandForUrl(value, platform = process.platform) {
313
+ const url = normalizeBrowserUrl(value);
314
+ if (platform === "darwin")
315
+ return { command: "open", args: [url] };
316
+ if (platform === "linux")
317
+ return { command: "xdg-open", args: [url] };
318
+ if (platform === "win32")
319
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", `start "" "${url}"`] };
320
+ throw new Error(`Opening a browser is not supported on ${platform}.`);
321
+ }
322
+ function normalizeBrowserUrl(value) {
323
+ let url;
324
+ try {
325
+ url = new URL(value);
326
+ } catch {
327
+ throw new Error("The selected page does not have a valid browser URL.");
328
+ }
329
+ if (url.protocol !== "http:" && url.protocol !== "https:")
330
+ throw new Error("Only http and https page URLs can be opened in a browser.");
331
+ return url.toString();
332
+ }
333
+ function launchBrowserCommand(command, args) {
334
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
335
+ child.unref();
336
+ }
337
+
263
338
  // src/tui/dashboard.tsx
264
339
  import { randomUUID } from "crypto";
265
340
  var LOG_FILE = "/tmp/loopd-tui.log";
@@ -548,6 +623,18 @@ function LoopDashboard(props) {
548
623
  }
549
624
  return;
550
625
  }
626
+ if (key === "B") {
627
+ prevent(evt);
628
+ const goal = selectedGoal();
629
+ const extra = goal ? `Goal: ${goal.name} (${goal.id.slice(0, 8)}) status=${goal.status} objective=${goal.objective.slice(0, 120)}` : "No goal selected";
630
+ const url = bugReportUrl({
631
+ runtimeLabel: `opencode-loopd dashboard`,
632
+ extra
633
+ });
634
+ const res = openBrowserUrl(url);
635
+ setStatusText(res.status === "opened" ? "Opening bug report in browser\u2026" : `Could not open browser: ${res.reason} \u2014 ${url}`);
636
+ return;
637
+ }
551
638
  if (key === "q") {
552
639
  prevent(evt);
553
640
  props.api.ui.dialog.clear();
@@ -721,6 +808,18 @@ function LoopDashboard(props) {
721
808
  setStatusText("Create goals via /goal in the parent chat (agent clarifies first). Dashboard: :send to steer the worker.");
722
809
  break;
723
810
  }
811
+ case "bug":
812
+ case "report": {
813
+ const goal = selectedGoal();
814
+ const extra = goal ? `Goal: ${goal.name} (${goal.id.slice(0, 8)}) status=${goal.status} objective=${goal.objective.slice(0, 120)}` : "No goal selected";
815
+ const url = bugReportUrl({
816
+ runtimeLabel: `opencode-loopd dashboard`,
817
+ extra
818
+ });
819
+ const res = openBrowserUrl(url);
820
+ setStatusText(res.status === "opened" ? "Opening bug report in browser\u2026" : `Could not open browser: ${res.reason} \u2014 ${url}`);
821
+ break;
822
+ }
724
823
  case "logs":
725
824
  setShowLogs(!showLogs());
726
825
  break;
@@ -828,7 +927,7 @@ function LoopDashboard(props) {
828
927
  _$setProp(_el$26, "maxHeight", 14);
829
928
  _$setProp(_el$26, "overflow", "hidden");
830
929
  _$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`));
930
+ _$insertNode(_el$28, _$createTextNode(`\u2501\u2501\u2501 Keys: ? toggle : insert Ctrl+N normal o open B bug q close \u2501\u2501\u2501`));
832
931
  _$setProp(_el$28, "style", {
833
932
  fg: "yellow",
834
933
  bold: true
@@ -1464,7 +1563,7 @@ var tui = async (api) => {
1464
1563
  run: open
1465
1564
  }],
1466
1565
  bindings: [{
1467
- key: "ctrl+l",
1566
+ key: "<leader>d",
1468
1567
  cmd: "opencode.loopd.dashboard",
1469
1568
  desc: "Open loop dashboard"
1470
1569
  }]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@bojackduy/opencode-loopd",
4
- "version": "1.0.1",
4
+ "version": "1.2.0",
5
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",
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/bojackduy/opencode-loopd.git"
12
12
  },
13
- "homepage": "https://github.com/bojackduy/opencode-loopd#readme",
13
+ "homepage": "https://bojackduy.github.io/opencode-loopd/",
14
14
  "bugs": {
15
15
  "url": "https://github.com/bojackduy/opencode-loopd/issues"
16
16
  },
@@ -51,7 +51,7 @@ loopd_create_goal({
51
51
  })
52
52
  ```
53
53
 
54
- The goal starts immediately. The user can monitor it via `/loop` (Ctrl+L).
54
+ The goal starts immediately. The user can monitor it via `/loop` (<leader>d).
55
55
 
56
56
  ## Worker Tools (Running Inside the Goal)
57
57
 
@@ -131,7 +131,7 @@ Sends a message to the worker's next turn. See table above.
131
131
 
132
132
  ## Dashboard Commands
133
133
 
134
- Open the dashboard with `/loop` or Ctrl+L.
134
+ Open the dashboard with `/loop` or <leader>d.
135
135
 
136
136
  ### Keyboard Shortcuts (Normal Mode)
137
137