@khalilgharbaoui/opencode-claude-code-plugin 0.54.2 → 0.55.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/index.d.ts +38 -10
- package/dist/index.js +433 -64
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +63 -6
package/package.json
CHANGED
|
@@ -112,8 +112,8 @@ Defaults below describe normal headless opencode use when the key is absent.
|
|
|
112
112
|
|---|---|---|---|
|
|
113
113
|
| `cliPath` | string | `"claude"` | Executable, not a shell command with flags. Use an absolute path for a non-PATH install. The opencode config hook supplies this default; only direct `createClaudeCode()` use falls back to `CLAUDE_CLI_PATH`. Account providers wrap it; never select a generated wrapper yourself. |
|
|
114
114
|
| `accounts` | string[] | unset | Unset keeps provider `claude-code`. Any array, including `[]`, expands to `claude-code-default` plus normalized, deduplicated names. Non-default accounts use `~/.claude-<name>`; default uses the CLI's normal environment/auth. |
|
|
115
|
-
| `accountFailover` | `"ask"` / `"off"` | `"off"` | **Opt-in: only an explicit `"ask"` opens the switch form**, so unset and `"off"` behave identically. `"ask"` ends a usage-limited turn on opencode's native `question` form listing the other configured accounts, and continues the task on the pick inside the same opencode turn. Only ever fires with more than one account configured. The pick is sticky for the LIMITED account until the limit's reset time (or until opencode restarts when the CLI reported none), so it covers every session on that account and subagents follow their parent; child sessions are never shown the form. Leaving it unanswered waits and costs nothing. `stop`, a dismissal, or text that is not one of the offered accounts ends the turn the way a limited turn ends without the form. Triggered only by a rejected `rate_limit_event`, one of the two known account-limit error texts, or one of the five account-level failure kinds the CLI names on its own error reply (`authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `verification_required`, `billing_error`); never by a generic failure. Never on compaction turns; both transports (the TUI's limit reply carries the same `error: "rate_limit"` kind). A switch carries the Claude conversation to the target account's config dir and resumes it there (`crossAccountResume`, default on) and only replays the thread as text when that copy is refused; either way, MCP servers configured only in the limited account's Claude profile are gone. With the default `"off"`, a limited turn ends on one `▌ **usage limit:**` note instead (see "When an account runs out of usage"). |
|
|
116
|
-
| `accountGroups` | object | – | **Off unless set**, and unset means every account behaves as one group, so nothing changes on an install that does not set it. A map from an account name in `accounts` to a group name, e.g. `{"work": "work"}`; every account not named, `default` included, is in one implicit group. With it set the plugin only moves a conversation between accounts in the SAME group, which covers
|
|
115
|
+
| `accountFailover` | `"ask"` / `"auto"` / `"off"` | `"off"` | **Opt-in: only an explicit `"ask"` opens the switch form**, so unset and `"off"` behave identically. `"auto"`, also opt-in, makes the same switch without a form, moving the same request to the next eligible account in configured order inside the same turn (see "Automatic failover" below). `"ask"` ends a usage-limited turn on opencode's native `question` form listing the other configured accounts, and continues the task on the pick inside the same opencode turn. Only ever fires with more than one account configured. The pick is sticky for the LIMITED account until the limit's reset time (or until opencode restarts when the CLI reported none), so it covers every session on that account and subagents follow their parent; child sessions are never shown the form. Leaving it unanswered waits and costs nothing. `stop`, a dismissal, or text that is not one of the offered accounts ends the turn the way a limited turn ends without the form. Triggered only by a rejected `rate_limit_event`, one of the two known account-limit error texts, or one of the five account-level failure kinds the CLI names on its own error reply (`authentication_failed`, `oauth_org_not_allowed`, `account_on_hold`, `verification_required`, `billing_error`); never by a generic failure. Never on compaction turns; both transports (the TUI's limit reply carries the same `error: "rate_limit"` kind). A switch carries the Claude conversation to the target account's config dir and resumes it there (`crossAccountResume`, default on) and only replays the thread as text when that copy is refused; either way, MCP servers configured only in the limited account's Claude profile are gone. With the default `"off"`, a limited turn ends on one `▌ **usage limit:**` note instead (see "When an account runs out of usage"). |
|
|
116
|
+
| `accountGroups` | object | – | **Off unless set**, and unset means every account behaves as one group, so nothing changes on an install that does not set it. A map from an account name in `accounts` to a group name, e.g. `{"work": "work"}`; every account not named, `default` included, is in one implicit group. With it set the plugin only moves a conversation between accounts in the SAME group, which covers every way one can move: the by-hand switch (picking a model under another account's provider in the same opencode session), the `accountFailover` form, the `"auto"` switch, the override either one sets, and `crossAccountResume`. A switch ACROSS groups sends the other account nothing at all, neither the Claude transcript nor a text replay of the thread (the replay is the same history by another route), starts fresh with only the current user message, and writes one `▌ **account group:**` note naming both accounts and both groups. The source account keeps the conversation and its transcript, so switching back continues it. The switch form offers only same-group accounts, the `"auto"` switch moves only to one, and with none left either behaves as a single-account install; `describeOtherAccounts` in the usage-limit and account-block notes is filtered the same way. An account name that is not configured is ignored with one WARN, as is a non-object value or a non-string group. Compaction is exempt and a child session follows its parent (under `"auto"` a child can move itself, which moves the parent with it, within the group). Shown resolved (never echoed) in the startup block and `/claude-code-doctor` as `accountGroups`, names only. Log: `starting fresh on this account: it is in another account group` (NOTICE, reason `another-account-group`). It guards what the plugin moves on its own; it is not a permission system. |
|
|
117
117
|
| `failoverAccounts` | string[] | unset/derived | Account expansion supplies the resolved account list so a limited account can offer the others. Do not hand-wire it; set `accounts` instead. |
|
|
118
118
|
| `baseCliPath` | string | unset/derived | The `cliPath` before the per-account wrapper substitution, so a failover can build another account's wrapper on the same binary. Supplied by the config hook. Do not hand-wire it. |
|
|
119
119
|
| `accountInProcess` | boolean | unset/derived | Set by the config hook on Windows, where there is no per-account wrapper script: the spawn exports `CLAUDE_CONFIG_DIR` itself and strips the `@<account>` marker off `--model`. Do not hand-wire it. |
|
|
@@ -495,6 +495,60 @@ Tell the user what a pick actually does before recommending one:
|
|
|
495
495
|
|
|
496
496
|
`{ "accountFailover": "off" }`, which is also the default, keeps the note.
|
|
497
497
|
|
|
498
|
+
### Automatic failover
|
|
499
|
+
|
|
500
|
+
**Opt-in.** `{ "accountFailover": "auto" }` and more than one account configured makes
|
|
501
|
+
the switch a pick would make, without the form: the failed attempt is dropped and the
|
|
502
|
+
**same request** runs on the next eligible account **inside the same opencode turn**.
|
|
503
|
+
The user sees one turn with the other account's answer under one note:
|
|
504
|
+
|
|
505
|
+
```text
|
|
506
|
+
▌ **account failover:** "work" is out of usage, so this turn moved to "personal"
|
|
507
|
+
automatically and stays there until 2026-09-20T18:00:00.000Z. The conversation
|
|
508
|
+
continues where it stopped.
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
What to tell a user who asks for it, or asks why billing moved:
|
|
512
|
+
|
|
513
|
+
- **Triggers** are exactly the form's, and only on a turn that **failed**
|
|
514
|
+
(`is_error: true`): a usage limit (rejected `rate_limit_event`, the CLI's
|
|
515
|
+
`rate_limit` reply, the two known limit texts) or one of the five account-block kinds.
|
|
516
|
+
A served turn with a rejected event, a generic error, a server error or a timeout
|
|
517
|
+
never switches, and healthy turns never rotate between accounts.
|
|
518
|
+
- **Order** is configured order: `default` first, then `accounts` as written. It skips
|
|
519
|
+
the account that just failed, every account already tried for this request, every
|
|
520
|
+
account outside the limited account's `accountGroups` group, and every account known
|
|
521
|
+
to be out: one with an unexpired override moving its own work elsewhere, or one whose
|
|
522
|
+
remembered limit has a reset time still ahead. Both expire on their own. The choice
|
|
523
|
+
is made when the failure arrives, so a limit another session learned meanwhile
|
|
524
|
+
counts. An account whose runtime cannot be prepared (config dir or wrapper not
|
|
525
|
+
writable) is skipped and the walk moves on; an account already tried is never run
|
|
526
|
+
again.
|
|
527
|
+
- **Bounded.** Each account is tried at most once per request. With none left the turn
|
|
528
|
+
finishes as an error: the `▌ **usage limit:**` note naming the last account tried and
|
|
529
|
+
offering no other (or the `▌ **claude account:**` note for a block), below one
|
|
530
|
+
`▌ **account failover:**` line per switch made on the way. A `doGenerate` caller gets
|
|
531
|
+
the error finish (`usage_limit` or the block's kind), never an answer, and no
|
|
532
|
+
usage-limit or failover line; its text is at most the CLI's own error text plus,
|
|
533
|
+
for a block, the `▌ **claude account:**` note with the login command.
|
|
534
|
+
- **Sticky** like a pick: the override is keyed on the account the conversation was on,
|
|
535
|
+
so every session on it, subagents included, follows until the limit's reset time, or
|
|
536
|
+
until opencode restarts when none was reported (always the case for a block).
|
|
537
|
+
- **Billing** moves to the target account for the rest of the turn and every later turn
|
|
538
|
+
on the limited account until the override expires. Opting in is the consent.
|
|
539
|
+
- **The conversation** is carried (`crossAccountResume`) and resumed, or replayed into a
|
|
540
|
+
fresh session when the carry is refused, exactly as for a pick; the note's last
|
|
541
|
+
sentence says which. Work the failed account did in that turn is not re-run by the
|
|
542
|
+
plugin, but a replay only has what opencode has.
|
|
543
|
+
- **Scope.** Both transports, `doGenerate` callers (no failover note in the returned text), and
|
|
544
|
+
child sessions, which switch themselves because nothing is asked; the parent follows
|
|
545
|
+
through the same override. Never compaction (it fails outright) and never a title
|
|
546
|
+
request (no spawn). An available account beats the `fallbackModels` chain, as the form
|
|
547
|
+
does. Stopping the turn, or cancelling the stream it returned, stops the attempt in
|
|
548
|
+
flight and starts no further account.
|
|
549
|
+
- Verified with a scripted `claude` only; the switch path itself is the one measured
|
|
550
|
+
live for the form.
|
|
551
|
+
|
|
498
552
|
### Switching account by hand, and keeping two accounts apart
|
|
499
553
|
|
|
500
554
|
Picking a model under another account's provider in the **same** opencode session
|
|
@@ -665,10 +719,10 @@ Per-agent replaces provider-level; it never merges. Unset means no chain, which
|
|
|
665
719
|
the default. Two triggers only, never a generic error: the CLI refusing the model
|
|
666
720
|
(assistant `error: "model_not_found"`, or a failed result whose text is *"There's an
|
|
667
721
|
issue with the selected model"*, measured on CLI 2.1.280, where the result `subtype`
|
|
668
|
-
is misleadingly `success`), and a usage limit **only when
|
|
669
|
-
|
|
670
|
-
offer. With `"ask"` and another account configured the switch
|
|
671
|
-
stays out of it: model is a capability choice, account is a billing choice.
|
|
722
|
+
is misleadingly `success`), and a usage limit **only when no `accountFailover` switch is
|
|
723
|
+
taking the turn**, meaning it is `"off"` (its default) or has no other account to
|
|
724
|
+
offer. With `"ask"` or `"auto"` and another eligible account configured the switch wins
|
|
725
|
+
and the chain stays out of it: model is a capability choice, account is a billing choice.
|
|
672
726
|
An expired login, a billing hold and every other error kind are excluded because they
|
|
673
727
|
fail the same way on the next model.
|
|
674
728
|
|
|
@@ -1284,6 +1338,9 @@ Prefer the doctor: it needs no logging change and no restart.
|
|
|
1284
1338
|
| Claude "forgot" the earlier part of a long conversation | Claude Code compacted its own context | Look for the `▌ **context compacted:**` note in the transcript |
|
|
1285
1339
|
| On a turn that replayed the thread as text, Claude knows what tools the previous reply ran but not what it concluded | Plugin 0.49.0 or older kept each replayed message's first 2,000 characters, and Claude Code runs its own tools without ending a step, so a reply's tool calls and their output (one result alone is capped at 10,000 characters) sit in the same message ahead of the prose and the answer was cut | Upgrade. Each replayed message is clipped in the middle now, keeping the answer it ends on, and the newest reply gets a larger budget. Better still, avoid the replay: it only happens when the Claude session cannot be resumed, and the `▌` note or the plugin log names the reason |
|
|
1286
1340
|
| Claude forgot the whole conversation at once | Claude Code cleared it (`/clear` sent as a message, or a plan-mode exit that clears context) | Look for the `▌ **claude code reset:**` note. The plugin does not replay history there on purpose; a new opencode session gets a clean slate |
|
|
1341
|
+
| After switching opencode agents (plan to build, build to plan) Claude ignores what was said under the other agent or answers an earlier message | Plugin older than 0.55.0: each agent runs its own `claude` process, and a reused one was sent only the newest message | Upgrade. The reused (or `--resume`d) process is now told what it missed, replayed as text ahead of the current message under `<missed_conversation>`; the log says `replaying the messages this claude session missed`. The processes stay separate on purpose (a Claude session keeps the system prompt of its first request) |
|
|
1342
|
+
| `prompt is too long` on the first turn after a restart or after a lost Claude session, and `/compact` fails the same way | Plugin older than 0.55.0: the text replay of the thread had per-message clips but no total | Upgrade. The replay is capped at 180,000 characters, newest first, with a `[N earlier messages omitted ...]` line when it had to drop |
|
|
1343
|
+
| opencode 2: every turn fails with `ENOENT ... posix_spawn '<claude>'` though the binary exists, only in a subdirectory of a project | Plugin older than 0.55.0 appended the session's project-relative `subpath` to its own absolute directory | Upgrade; drop a `cwd` set only to work around it |
|
|
1287
1344
|
| Wanting the per-turn cost in the chat | Not shown by default | Set `turnStats: true` and restart opencode |
|
|
1288
1345
|
| On the interactive transport, every turn ends with a `▌ **claude code error:**` note naming `end_turn` (or `stop_sequence` / `max_tokens`), and `turnStats` never prints | Plugin older than 0.33.0 put the stop reason in the synthesized `result`'s `subtype`, and any non-`success` subtype finishes the turn as an error, which also suppresses the stats footer | Upgrade and relaunch. A turn that reaches a terminal stop reason now synthesizes the shape a headless turn emits (`subtype: "success"`, the stop reason in a top-level `stop_reason`), so it finishes as an ordinary reply. A turn that reaches NO terminal stop reason is still an error on purpose, so truncation stays visible |
|
|
1289
1346
|
| On the interactive transport with a working directory under `/tmp` (or any symlinked path), the turn hangs until the 30-minute turn timeout and then reports no terminal stop reason | Plugin older than 0.33.0 named the transcript directory from `path.resolve`, which does not follow symlinks, so it tailed a file Claude Code never writes. On macOS `/tmp` is a symlink to `/private/tmp` | Upgrade and relaunch. The directory is now named from the cwd's resolved real path, which is what the CLI uses (`/tmp/scratch` is `~/.claude/projects/-private-tmp-scratch`). Check the `jsonlPath` in the `prepared interactive claude session` log line against the directory that actually exists under `<CLAUDE_CONFIG_DIR>/projects/` |
|