@snarkipus/opencode-beads 0.9.4 → 0.10.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
@@ -30,7 +30,7 @@ Add the version-pinned plugin to `~/.config/opencode/opencode.json`:
30
30
 
31
31
  ```json
32
32
  {
33
- "plugin": ["@snarkipus/opencode-beads@0.9.4"]
33
+ "plugin": ["@snarkipus/opencode-beads@0.10.0"]
34
34
  }
35
35
  ```
36
36
 
@@ -38,13 +38,13 @@ Restart OpenCode. Update the pin explicitly when upgrading; an unpinned package
38
38
 
39
39
  ### 0.9.3 recovery notice
40
40
 
41
- Version 0.9.3 contained command guidance from the withdrawn upstream Beads v1.2.1 release. This plugin did not bundle the `bd` binary or implement database migrations, but it could invoke a consumer-installed v1.2.1 binary through `bd prime`. Even one v1.2.1 execution could migrate the database. Upgrade the plugin package to 0.9.4; if v1.2.1 was installed and may have run, also follow the [upstream database recovery guidance](https://github.com/gastownhall/beads/blob/v1.2.2/docs/RECOVERY-1.2.1.md).
41
+ Version 0.9.3 contained command guidance from the withdrawn upstream Beads v1.2.1 release. This plugin did not bundle the `bd` binary or implement database migrations, but it could invoke a consumer-installed v1.2.1 binary through `bd prime`. Even one v1.2.1 execution could migrate the database. Upgrade the plugin package to 0.9.4 or later; if v1.2.1 was installed and may have run, also follow the [upstream database recovery guidance](https://github.com/gastownhall/beads/blob/v1.2.2/docs/RECOVERY-1.2.1.md).
42
42
 
43
43
  ## Context injection
44
44
 
45
45
  The plugin runs full `bd prime` when a primary-agent session first receives a message and after compaction. Prime supplies the canonical current workflow, command guidance, and persistent memories; the plugin adds only compact OpenCode-specific safety and delegation guidance.
46
46
 
47
- Known regular subagents remain excluded, including `explore` and `general`, while `beads-task-agent` remains eligible. If agent lookup fails, context injection fails open to preserve compatibility with unknown custom primary agents.
47
+ Known regular subagents remain excluded, including `explore` and `general`, while `beads-task-agent` and `beads-task-agent-luna` remain eligible. If agent lookup fails, context injection fails open to preserve compatibility with unknown custom primary agents.
48
48
 
49
49
  If `bd` is unavailable, the project is uninitialized, or prime fails or returns no content, injection is silently skipped and remains retryable. Vendored commands stay available, and the task agent retains a bounded standalone quick reference for `ready`, `show`, atomic claim, discovered follow-up, validate before closure, and close.
50
50
 
@@ -52,7 +52,7 @@ If `bd` is unavailable, the project is uninitialized, or prime fails or returns
52
52
 
53
53
  Vendored workflows are registered under `/beads:*`, including `/beads:ready`, `/beads:create`, and `/beads:show`. The plugin does not generate a command for every `bd` subcommand; use the CLI and the generated [Beads CLI reference](https://beads.gascity.com/cli-reference/index) for the complete surface.
54
54
 
55
- Explicit command and agent definitions in OpenCode configuration take precedence over plugin definitions with the same name. Exact `beads:*` or `beads-task-agent` collisions produce a grouped, rate-limited warning without preventing non-conflicting definitions from loading.
55
+ Explicit command definitions in OpenCode configuration replace plugin commands with the same name. Task-agent overrides merge field-by-field over plugin defaults, so a partial override does not discard the bounded prompt or subagent mode. Explicit fields (including `prompt`, `permission`, `tools`, `model`, `variant`, and `disable`) win; nested fields are replaced, not deep-merged. Conflicting `beads:*` commands or either named task-agent profile produce a grouped, rate-limited warning without preventing definitions from loading. An empty profile opt-in does not warn.
56
56
 
57
57
  ## Task agent
58
58
 
@@ -60,6 +60,26 @@ Explicit command and agent definitions in OpenCode configuration take precedence
60
60
 
61
61
  Its runtime prompt is a fork-owned bounded OpenCode adaptation. The vendored upstream task-agent artifact supplies metadata, provenance, and compatibility validation; it is not executed as upstream's autonomous multi-task loop.
62
62
 
63
+ ### Opt-in Luna Max profile
64
+
65
+ The default `beads-task-agent` leaves model and reasoning variant unset, preserving OpenCode's caller inheritance. To add the named Luna Max profile, merge this entry into your project `opencode.json` alongside the plugin entry:
66
+
67
+ ```json
68
+ {
69
+ "agent": {
70
+ "beads-task-agent-luna": {}
71
+ }
72
+ }
73
+ ```
74
+
75
+ The plugin fills this profile with `model: "openai/gpt-5.6-luna"` and `variant: "max"`, using exactly the same shared prompt, subagent mode, and inherited permissions/tools as the vanilla agent. Per-agent overrides remain independent: customizations to `beads-task-agent` are not copied to Luna. Both profiles get full prime context at startup and after compaction and retain the same exact-one-Bead workflow.
76
+
77
+ Choose `beads-task-agent-luna` explicitly as the task tool's `subagent_type` (or ask the primary agent to delegate to that name). Default delegation guidance still names `beads-task-agent`; enabling Luna does not reroute existing calls or change the primary agent's model/variant. No extra orchestrator or plugin-specific config file is involved.
78
+
79
+ Omit the Luna entry from the effective configuration to keep it unregistered; set `"disable": true` inside the entry to disable it using OpenCode's normal agent configuration. To undo the opt-in, remove the entry at its source and restart OpenCode; omission in a child project does not remove an ancestor/global entry. Explicit routing or prompt overrides are honored, so changing those fields opts out of the corresponding profile defaults.
80
+
81
+ Luna requires access to `openai/gpt-5.6-luna` and its `max` variant in your OpenCode provider setup. The plugin does not install models, configure authentication, or silently fall back to another model. Native Luna Max routing was verified on OpenCode 1.18.29; the plugin's general compatibility range is not a guarantee that every provider account exposes this model.
82
+
63
83
  ## Vendored content
64
84
 
65
85
  [`scripts/sync-beads.sh`](scripts/sync-beads.sh) copies the reviewed upstream command templates and task-agent artifact into `vendor/`. [`vendor/manifest.json`](vendor/manifest.json) pins the upstream tag and commit and records source paths, sorted inventory, byte lengths, and SHA-256 checksums. A deterministic adaptation layer translates reviewed host-specific instructions to OpenCode's CLI-only model.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snarkipus/opencode-beads",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "A maintained OpenCode integration for the Beads issue tracker, including context injection, vendored workflows, and a task agent.",
6
6
  "author": "Matt Jackson",
@@ -1,4 +1,5 @@
1
1
  import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { AgentConfig as AgentConfigV2 } from "@opencode-ai/sdk/v2";
2
3
  import type {
3
4
  Agent,
4
5
  SessionMessagesResponse,
@@ -11,6 +12,12 @@ import { PrimeTimeoutError } from "./prime";
11
12
  import { beadsGuidance, loadAgent, loadCommands } from "./vendor";
12
13
 
13
14
  const BEADS_TASK_AGENT = "beads-task-agent";
15
+ const BEADS_TASK_AGENT_LUNA = "beads-task-agent-luna";
16
+
17
+ /** Both named profiles use the same bounded task-agent context. */
18
+ function isBeadsTaskAgent(name: string | undefined): boolean {
19
+ return name === BEADS_TASK_AGENT || name === BEADS_TASK_AGENT_LUNA;
20
+ }
14
21
 
15
22
  type ChatMessageInput = Parameters<NonNullable<Hooks["chat.message"]>>[0];
16
23
  type SessionMessageResponse = SessionMessagesResponse[number];
@@ -99,7 +106,7 @@ function beadsContextAudience(text: string): InjectionAudience | undefined {
99
106
  }
100
107
 
101
108
  function injectionAudience(agentName: string | undefined): InjectionAudience {
102
- return agentName === BEADS_TASK_AGENT ? "task-agent" : "primary";
109
+ return isBeadsTaskAgent(agentName) ? "task-agent" : "primary";
103
110
  }
104
111
 
105
112
  /** Resolve OpenCode project scope without falling back to the process directory. */
@@ -160,7 +167,7 @@ export async function createBeadsController(
160
167
  agentName: string | undefined,
161
168
  sessionID: string
162
169
  ): Promise<boolean> {
163
- if (!agentName || agentName === BEADS_TASK_AGENT) return true;
170
+ if (!agentName || isBeadsTaskAgent(agentName)) return true;
164
171
 
165
172
  const availableAgents = await runtime.getAgents(directory).catch(async () => {
166
173
  await diagnoseSession("agents_lookup_failed", sessionID);
@@ -315,16 +322,29 @@ export async function createBeadsController(
315
322
  },
316
323
 
317
324
  async configure(config) {
325
+ // A named config entry opts in without another plugin option or config file.
326
+ const configuredAgents = { ...agents };
327
+ if (Object.hasOwn(config.agent ?? {}, BEADS_TASK_AGENT_LUNA)) {
328
+ configuredAgents[BEADS_TASK_AGENT_LUNA] = {
329
+ ...agents[BEADS_TASK_AGENT],
330
+ model: "openai/gpt-5.6-luna",
331
+ variant: "max",
332
+ } satisfies AgentConfigV2;
333
+ }
318
334
  const commandCollisions = Object.keys(config.command ?? {})
319
335
  .filter(
320
336
  (name) =>
321
337
  Object.hasOwn(commands, name) && config.command?.[name] !== commands[name]
322
338
  )
323
339
  .sort();
324
- const agentCollisions = Object.keys(config.agent ?? {})
325
- .filter(
326
- (name) => Object.hasOwn(agents, name) && config.agent?.[name] !== agents[name]
340
+ const agentCollisions = Object.entries(configuredAgents)
341
+ .filter(([name, defaults]) =>
342
+ defaults !== undefined &&
343
+ Object.entries(config.agent?.[name] ?? {}).some(
344
+ ([key, value]) => value !== defaults[key]
345
+ )
327
346
  )
347
+ .map(([name]) => name)
328
348
  .sort();
329
349
 
330
350
  await Promise.all(
@@ -341,7 +361,11 @@ export async function createBeadsController(
341
361
  );
342
362
 
343
363
  config.command = { ...commands, ...config.command };
344
- config.agent = { ...agents, ...config.agent };
364
+ const mergedAgents = { ...config.agent };
365
+ for (const [name, defaults] of Object.entries(configuredAgents)) {
366
+ mergedAgents[name] = { ...defaults, ...config.agent?.[name] };
367
+ }
368
+ config.agent = mergedAgents;
345
369
  },
346
370
  };
347
371
  }