@phnx-labs/agents-cli 1.20.59 → 1.20.61

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.20.61
6
+
7
+ - **Detect OpenCode sign-in state so `agents view` stops mislabeling a logged-in install as "not signed in."** `getAccountInfo` had no `opencode` case, so it fell through to `signedIn: false` and every row printed "(not signed in — run opencode to log in)" even with a live login. It now reads OpenCode's `auth.json` (`$XDG_DATA_HOME/opencode/auth.json`, defaulting to `~/.local/share/opencode/auth.json` on every platform — `xdg-basedir` does not special-case macOS), validates each provider entry against its `oauth`/`api`/`wellknown` credential shape, and reports the account as signed in with the non-secret provider ids surfaced as the account label (e.g. `id:muse-spark`). Credential secrets (`access`/`refresh`/`key`/`token`) are only inspected for presence — never read into any display or JSON output. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/agents.test.ts`.
8
+ - **Wire Antigravity workflows support (RUSH-1580).** `agents` now syncs workflows to Antigravity as markdown files with the required `description` frontmatter plus an `agents_workflow` ownership marker, invocable as `/<name>` slash commands. Antigravity workflows are the one non-version-isolated target: `agy` scans a single shared, HOME-global `~/.gemini/config/global_workflows/` at startup (a real home directory, never symlinked per version — verified via strace of `agy`), so the writer and detector both resolve that shared dir for every installed version instead of a per-version home. Gated at `>= 1.0.6`. The ownership marker prevents overwriting or removing user-authored workflows of the same name. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/staleness/detectors/workflows.ts`.
9
+
10
+ ## 1.20.60
11
+ - **Fix Goose skill sync status for its native central-storage path.** `agents skills list goose@<version>` now reports skills under `~/.agents/skills/` as installed instead of falsely requiring a per-version `.config/goose/skills/` copy that Goose never reads. Source: `apps/cli/src/lib/skills.ts`.
12
+ - **Correct the documented `auto` and ACP `skip` semantics.** The README and bundled `run` skill now distinguish Kimi's interactive `--auto` from its already-auto-approved headless `-p` path, document Droid's native `--auto high`, and explain that ACP `skip` prefers `allow_always` but falls back to the first permission option offered by the server. Documentation only; runtime behavior is unchanged. Source: `README.md`, `skills/run/SKILL.md`.
13
+ - **Wire Antigravity subagents and Kimi workflow sync (RUSH-1548, RUSH-1581).** Antigravity now receives subagents as custom-agent Markdown under `~/.gemini/config/agents/<name>/agent.md` with the `>= 1.0.16` version gate enforced during sync. Kimi receives workflows as managed `type: flow` skills under `.kimi-code/skills/<name>/SKILL.md`, using the canonical slug as the flow name and an `agents_workflow` marker so native user-owned flows are not overwritten or removed. Antigravity workflows are wired separately in RUSH-1580 (they target a shared HOME-global dir, not a version home). Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/workflows.ts`.
14
+ - **Release retries rebuild the exact merged, CI-tested release tree even after `main` advances.** A registry/auth failure after the release PR merged previously stranded that version because the catch-up guard required the release merge to remain current `main`. The local release script now validates the original PR head and full green matrix, verifies the SHA-pinned keychain helper, rebuilds the unpinned menu-bar helper from historical source in a detached temporary worktree, rejects mismatched remote tags, and tags/publishes that exact merge without including later commits. Source: `apps/cli/scripts/release.sh`.
15
+ - **Wire Gemini plugins/subagents and Goose workflows/allowlists.** Gemini now syncs plugin bundles as Gemini extensions (`.gemini/extensions/<name>/gemini-extension.json`) from CLI 0.8.0+ and subagents as `.gemini/agents/*.md` from CLI 0.36.0+. Goose now syncs workflows as recipe/subrecipe YAML under `.config/goose/recipes/` and permission groups into `.config/goose/permission.yaml`. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/plugins.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/permissions.ts`. (RUSH-1568, RUSH-1569, RUSH-1574, RUSH-1582)
16
+ - **Wire Gemini permissions/allowlist support (RUSH-1567).** Gemini permission groups now sync Bash allow/deny rules into `.gemini/settings.json` as `tools.core` / `tools.exclude` entries with per-command `ShellTool(...)` patterns; non-Bash canonical permissions remain unsupported by Gemini's native tool grammar and are skipped. Flip `allowlist: true`, register the permission writer/detector through the capability table, and replace the dormant legacy `tools.allowed` serializer. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`, `apps/cli/src/lib/resources/permissions.ts`, `apps/cli/src/lib/staleness/detectors/permissions.ts`.
17
+ - **Fix: `agents run <agent>@<version> --host <host>` now forwards the version pin and most run flags to the remote host.** Previously the `--host` branch stripped `@version` and ignored `--strategy`, `--effort`, `--add-dir`, `--json`, `--verbose`, `--timeout`, `--yes`, and `--acp`, so the remote host applied its own defaults. The local CLI now parses `agent@version` verbatim, normalizes `--strategy`/`--balanced`, makes `--add-dir` paths remote-portable, and forwards all of these flags to the remote `agents run` invocation. `--add-dir` portability uses the same `~`/`$HOME` re-rooting that `--cwd` already uses, so a Linux remote resolves home paths against its own `/home/<user>`. Source: `apps/cli/src/lib/hosts/dispatch.ts`, `apps/cli/src/commands/exec.ts`, `apps/cli/src/lib/hosts/dispatch.test.ts`.
18
+ - **Fix: routine edits no longer rewrite the whole YAML file, so `~/.agents` stays clean and `agents repo pull` can sync routines across the fleet.** `writeJob` previously re-emitted the entire document via `yaml.stringify` on every mutation (pause/resume, `routines devices --set`, add), restyling untouched scalars — unquoting `schedule`, re-wrapping the folded `prompt` block — which left the git-backed user repo perpetually dirty. That made cross-device `agents repo pull` refuse ("uncommitted changes"), so a `devices:` pin set on one machine never reached the others and `Devices: all` routines kept firing on every box. A new `serializeJob` edits only the changed keys via the YAML Document API, preserving byte-for-byte formatting of untouched nodes; new/unparseable/non-mapping files fall back to canonical stringify. Source: `apps/cli/src/lib/routines.ts`, `apps/cli/src/lib/__tests__/routines.serialize.test.ts`.
19
+
5
20
  ## 1.20.59
6
21
 
7
22
  - **Fix: remote secrets now choose the Windows PowerShell wrapper from the original `--host` name, not the resolved `user@ip` SSH target.** Inline enrolled Windows hosts resolve to address-based SSH targets, but the OS registry is keyed by the host name; `agents secrets view/list/exec --host <windows>`, `agents run --secrets bundle@<windows>`, and remote secrets unlock/export paths now pass that original name into command construction so Windows hosts no longer fall back to `bash -lc`. Source: `apps/cli/src/lib/secrets/remote.ts`, `apps/cli/src/commands/secrets.ts`, `apps/cli/src/commands/exec.ts`. (RUSH-1431)
@@ -12,6 +27,8 @@
12
27
  - **OpenCode permissions write to the loaded config path (RUSH-1623).** Global config is `~/.config/opencode/opencode.jsonc` (not `~/.opencode/`); project config is `opencode.jsonc` at the project root. Source: `apps/cli/src/lib/permissions.ts`, `apps/cli/src/lib/agents.ts`.
13
28
  - **Catch-up publishes now require the exact CI-tested release tree and its full green matrix.** A package version already present on `main` no longer counts as release validation by itself: `release.sh` resolves the merged `release/v<version>` PR, requires its merge commit to be current `main`, fetches the PR head that ran CI, requires that head tree to equal current `main`, and rechecks every expected CI context before publishing. This closes the path that let 1.20.58 reach npm before its tag-triggered Windows matrix exposed failures. Source: `apps/cli/scripts/release.sh`.
14
29
  - **Windows release validation now matches portable path behavior.** Home-relative project paths normalize native separators to `/` before they are stored as `~/…`, and the systemd-manifest and remote-shell assertions now compare the escaped/quoted forms that the runtime deliberately emits. This restores the Windows Node 22/24 release matrix without weakening command escaping. Source: `apps/cli/src/lib/project-root.ts`, `apps/cli/src/lib/{project-root,daemon}.test.ts`, `apps/cli/src/lib/hosts/dispatch.test.ts`.
30
+ - **Wire subagents support for Kiro CLI.** Kiro custom agents are JSON files under `~/.kiro/agents/*.json` (introduced in kiro-cli v1.23.0). Flip Kiro's `subagents: { since: '1.23.0' }`, add `transformSubagentForKiro`, and wire the subagents writer, detector, install/remove, and orphan-detection paths. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`. (RUSH-1393)
31
+
15
32
  ## 1.20.58
16
33
 
17
34
  - **Self-updating agent CLIs are represented as one live installation.** `agents view` no longer invents version-home rows for single-binary installers such as Droid, Grok, Cursor, Kiro, Goose, and Hermes; it reports the version returned by the installed binary and folds away stale per-version directories. `agents add <agent>@<version>` now installs or keeps that agent's current release instead of rejecting an unsupported pinned install. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/versions.ts`, `apps/cli/src/commands/{versions,view}.ts`. (RUSH-1321)
@@ -19,7 +36,6 @@
19
36
  - **Wire allowlist support for Cursor CLI.** Cursor agent CLI stores allow/deny in `~/.cursor/cli-config.json` (`permissions.allow`/`deny` with Shell/Read/Write/WebFetch/Mcp). Flip `allowlist: true`, add `convertToCursorFormat` (Bash→Shell), and write via `applyPermissionsToVersion` + detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`. (RUSH-1387)
20
37
  - **GitHub Copilot CLI subagents now sync (RUSH-1390).** Installed subagents flatten into GitHub Copilot custom-agent profiles at `~/.copilot/agents/<name>.agent.md` (the Droid custom-droid format), gated to Copilot CLI ≥ 0.0.353. `agents subagents list/view` now surfaces synced Copilot agents and `agents subagents remove` soft-deletes their `.agent.md` files to trash — both previously skipped `copilot` entirely. Source: `apps/cli/src/lib/subagents.ts` (`listSubagentsForAgent`, `removeSubagentFromVersion`, `transformSubagentForCopilot`), `apps/cli/src/lib/staleness/writers/subagents.ts`, `apps/cli/src/lib/staleness/detectors/subagents.ts`, `apps/cli/src/lib/agents.ts`.
21
38
  - **Menu-bar Quick Dispatch preserves typed drafts when focus is stolen (RUSH-1592).** If another app activates while the `Cmd-Shift-O` capture panel is open, the panel can hide without destroying the note; the next summon restores the draft text plus selected screenshots, action, and agents. Return submits and clears the draft; Escape clears without dispatching. Source: `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`, `apps/cli/docs/menubar.md`.
22
- - **Wire subagents support for Kiro CLI.** Kiro custom agents are JSON files under `~/.kiro/agents/*.json` (introduced in kiro-cli v1.23.0). Flip Kiro's `subagents: { since: '1.23.0' }`, add `transformSubagentForKiro`, and wire the subagents writer, detector, install/remove, and orphan-detection paths. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`. (RUSH-1393)
23
39
  - **Menu-bar ticket agents now carry every selected screenshot into the Linear issue (RUSH-1668).** `Cmd-Shift-O` already passed selected file paths to the ticket agent, but the prompt only asked it to inspect them, so agents could create text-only issues and stop. The brief now identifies every selected path as user-provided ticket material, requires each file to be uploaded, supplies the existing `linear update <id> --proof <path>` path as a reliable default, and leaves description/comment/other placement to the agent's judgment. Source: `apps/cli/menubar/Sources/MenubarHelper/{AgentsCLI,IssueSelfTest}.swift`, `apps/cli/docs/menubar.md`.
24
40
  - **`agents sessions --active --json` now carries session attachment metadata for Factory previews (RUSH-1524).** Claude and Droid prompt image/document blocks that reference local files are preserved as `{ path, name, mediaType, sizeBytes }`, and the active-session state dedupes them into `attachments` so consumers can render screenshot thumbnails and open the original files instead of only seeing an attachment count. Source: `apps/cli/src/lib/session/parse.ts`, `apps/cli/src/lib/session/state.ts`, `apps/cli/src/lib/session/active.ts`.
25
41
  - **Retired the standalone `com.phnx-labs.agents-secrets-agent` launchd service — the always-on daemon is now the sole broker host (#416, step 2).** `ensureAgentRunning()` no longer installs a separate launchd service: it retires any leftover plist via the new `retireLegacySecretsAgentService()` and relies on the daemon (Path 0), with a one-off detached broker as the only fallback. The upgrade migration (`scripts/postinstall.js` → `healLongRunningProcesses`) now `launchctl bootout`s the legacy service **first**, then (re)starts the daemon so it takes over the broker socket, instead of kickstarting the old service onto new code. `agents secrets start` is now a thin alias that brings the daemon up (and waits for the broker to answer); `agents secrets stop` locks all bundles and retires any leftover legacy service while leaving the always-on daemon running; `agents secrets status` reports broker reachability (daemon-hosted vs standalone) rather than "service installed". The stale broker teardown (version-skew self-heal) retires the legacy service instead of kickstarting it. Source: `apps/cli/src/lib/secrets/agent.ts` (`retireLegacySecretsAgentService`, `ensureAgentRunning`, `teardownStaleBroker`, `uninstallSecretsAgentService`; removed `installSecretsAgentService`/`kickstartSecretsAgentService`/`generateServicePlist`), `apps/cli/scripts/postinstall.js` (`healLongRunningProcesses`), `apps/cli/src/commands/secrets.ts` (`start`/`stop`/`status`).
package/README.md CHANGED
@@ -161,9 +161,11 @@ Supports plan (read-only), edit, auto, and skip modes, effort levels, JSON outpu
161
161
 
162
162
  Treat `skip` as a last-resort escape hatch. In direct-exec runs (without `--acp`),
163
163
  agents-cli forwards the harness's native no-prompt flag; it does not add another
164
- safety layer. Prefer `auto` where the harness has a smart classifier (Claude Code and
165
- GitHub Copilot), or `edit` everywhere else. Harnesses without a native bypass flag
166
- reject direct-exec `skip`.
164
+ safety layer. Prefer `auto` where it adds a safer automatic policy (smart classifier
165
+ on Claude/Copilot, native high-auto mode on Droid, or interactive Kimi), or `edit`
166
+ everywhere else. For headless Kimi, `edit`, `auto`, and `skip` all use the same
167
+ already-auto-approved `-p` behavior, so prefer `edit` rather than signaling a blanket
168
+ bypass. Harnesses without a native bypass flag reject direct-exec `skip`.
167
169
 
168
170
  | Harness | Direct-exec `--mode skip` becomes |
169
171
  |---|---|
@@ -179,8 +181,9 @@ reject direct-exec `skip`.
179
181
  | Droid | `--skip-permissions-unsafe` |
180
182
 
181
183
  With `--acp`, these native flags are not used. agents-cli instead grants `skip`
182
- permission requests at the ACP protocol layer with `allow_always`; the same
183
- last-resort warning applies.
184
+ permission requests at the ACP protocol layer: it selects `allow_always` when offered,
185
+ otherwise the first permission option offered by the server. The same last-resort
186
+ warning applies.
184
187
 
185
188
  Codex has no native smart-classifier mode, so `agents run codex --mode auto` resolves
186
189
  to sandboxed `edit` and can still prompt. `agents run codex --mode skip` is different:
@@ -852,7 +855,7 @@ Which DotAgents resources each agent CLI can load. Source of truth: [src/lib/age
852
855
  | Grok Build | yes | yes | yes | yes | yes | skills ($name) | yes | no | `AGENTS.md` | no |
853
856
  | OpenClaw | yes | yes | yes | no | yes | gateway | yes | yes | `workspace/AGENTS.md` | no |
854
857
  | Cursor | yes | no | yes | no | yes | yes | no | no | `.cursorrules` | no |
855
- | OpenCode | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
858
+ | OpenCode | yes | no | yes | >= 1.1.1 | yes | yes | no | no | `AGENTS.md` | no |
856
859
  | Copilot | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
857
860
  | Amp | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
858
861
  | Kiro | yes | no | yes | >= 2.8.0 | yes | yes | no | >= 1.23.0 | `AGENTS.md` | no |
package/dist/bin/agents CHANGED
Binary file
@@ -482,6 +482,7 @@ export function registerRunCommand(program) {
482
482
  const { resolveHost, resolveHostByCap } = await import('../lib/hosts/registry.js');
483
483
  const { dispatchToHost, runInteractiveOnHost } = await import('../lib/hosts/dispatch.js');
484
484
  const { registerHostSession, registerInteractiveHostSession } = await import('../lib/hosts/session-index.js');
485
+ const { normalizeRunStrategy, RUN_STRATEGIES } = await import('../lib/rotate.js');
485
486
  // A password-auth device throws DeviceOffloadUnsupportedError here; it's
486
487
  // printed cleanly by the top-level catch in index.ts (covers every
487
488
  // resolveHost caller), so it never falls through to capability routing.
@@ -506,12 +507,30 @@ export function registerRunCommand(program) {
506
507
  process.exit(1);
507
508
  }
508
509
  try {
509
- const runAgent = agentSpec.split('@')[0];
510
+ const [runAgent, rawRunVersion] = agentSpec.split('@');
511
+ // Forward the explicit @version pin verbatim. Resolving aliases like
512
+ // @latest locally would check local installs, but the remote host may
513
+ // have versions the laptop does not. The remote agents CLI resolves
514
+ // aliases against its own installed versions.
515
+ const runVersion = rawRunVersion || undefined;
516
+ // Normalize the effective strategy exactly like the local path so we
517
+ // fail fast on invalid input and can forward --balanced/--strategy.
518
+ const explicitStrategy = options.strategy ? normalizeRunStrategy(options.strategy) : null;
519
+ if (options.strategy && !explicitStrategy) {
520
+ console.error(chalk.red(`Invalid strategy: ${options.strategy}. Use ${RUN_STRATEGIES.join(', ')}.`));
521
+ process.exit(1);
522
+ }
523
+ if (options.balanced && explicitStrategy && explicitStrategy !== 'balanced') {
524
+ console.error(chalk.red('--balanced conflicts with --strategy. Use one strategy override.'));
525
+ process.exit(1);
526
+ }
527
+ const runStrategy = options.balanced ? 'balanced' : explicitStrategy ?? undefined;
510
528
  // Working directory on the host: an explicit --remote-cwd is used
511
529
  // verbatim; --cwd/--project are made portable (a local-home absolute
512
530
  // becomes `~/…` so the remote shell re-roots it at ITS home).
513
531
  const { toRemotePortable } = await import('../lib/project-root.js');
514
532
  const hostCwd = options.remoteCwd ?? (options.cwd ? toRemotePortable(options.cwd) : undefined);
533
+ const hostAddDirs = options.addDir.length > 0 ? options.addDir.map(toRemotePortable) : undefined;
515
534
  // `--resume [id]`: commander yields the string id, or `true` when the
516
535
  // flag is passed bare. A bare resume needs the interactive picker,
517
536
  // which can't run over a detached remote dispatch — only forward a
@@ -549,9 +568,18 @@ export function registerRunCommand(program) {
549
568
  }
550
569
  const exitCode = await runInteractiveOnHost(host, {
551
570
  agent: runAgent,
571
+ version: resumeId ? undefined : runVersion,
572
+ strategy: resumeId ? undefined : runStrategy,
552
573
  prompt,
553
574
  mode: options.mode,
554
575
  model: options.model,
576
+ effort: options.effort,
577
+ addDir: hostAddDirs,
578
+ json: options.json,
579
+ verbose: options.verbose,
580
+ timeout: options.timeout,
581
+ yes: options.yes,
582
+ acp: options.acp,
555
583
  remoteCwd: hostCwd,
556
584
  sessionId: hostSessionId,
557
585
  name: options.name,
@@ -571,9 +599,18 @@ export function registerRunCommand(program) {
571
599
  const hostSessionId = runAgent === 'claude' && !resumeId ? randomUUID() : undefined;
572
600
  const { task, exitCode } = await dispatchToHost(host, {
573
601
  agent: runAgent,
602
+ version: resumeId ? undefined : runVersion,
603
+ strategy: resumeId ? undefined : runStrategy,
574
604
  prompt,
575
605
  mode: options.mode,
576
606
  model: options.model,
607
+ effort: options.effort,
608
+ addDir: hostAddDirs,
609
+ json: options.json,
610
+ verbose: options.verbose,
611
+ timeout: options.timeout,
612
+ yes: options.yes,
613
+ acp: options.acp,
577
614
  remoteCwd: hostCwd,
578
615
  sessionId: hostSessionId,
579
616
  name: options.name,
@@ -347,6 +347,7 @@ export function registerRoutinesCommands(program) {
347
347
  .option('--at <time>', 'One-shot mode: run once at this time (e.g., "14:30" or "2026-02-24 09:00"), then disable')
348
348
  .option('--end-at <iso>', 'Stop firing on or after this ISO 8601 timestamp (e.g., "2026-12-31T23:59:00Z"); routine auto-disables.')
349
349
  .option('--disabled', 'Create the routine but keep it paused (enable later with resume)')
350
+ .option('--resume <sessionId>', 'At fire time, resume this existing session id (via `agents run <agent> --resume`) instead of starting fresh — the actual session reopens with full context and the prompt becomes its next turn. Powers self-scheduled wake-ups (e.g. /hibernate). Requires --agent claude or codex; runs un-sandboxed (the session store lives in the real home, not the job overlay).')
350
351
  .action(async (nameOrPath, options) => {
351
352
  // Check if inline mode (has flags) or file mode
352
353
  const hasInlineFlags = options.schedule || options.agent || options.workflow || options.prompt || options.at;
@@ -406,6 +407,7 @@ export function registerRoutinesCommands(program) {
406
407
  ...(devices ? { devices } : {}),
407
408
  ...(runOnce ? { runOnce: true } : {}),
408
409
  ...(options.endAt ? { endAt: options.endAt } : {}),
410
+ ...(options.resume ? { resume: options.resume } : {}),
409
411
  };
410
412
  const errors = validateJob(config);
411
413
  if (errors.length > 0) {
@@ -265,7 +265,8 @@ export const AGENTS = {
265
265
  url: 'https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/',
266
266
  },
267
267
  // gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
268
- capabilities: { hooks: { since: '0.26.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'GEMINI.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
268
+ // extensions: gemini-extension.json bundles shipped in v0.8.0; custom subagents in v0.36.0.
269
+ capabilities: { hooks: { since: '0.26.0' }, mcp: true, mcpHttp: true, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: { since: '0.8.0' }, subagents: { since: '0.36.0' }, rules: { file: 'GEMINI.md' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
269
270
  },
270
271
  cursor: {
271
272
  id: 'cursor',
@@ -419,7 +420,8 @@ export const AGENTS = {
419
420
  supportsHooks: true,
420
421
  // Plugins: Open Plugins under ~/.agents/plugins/<name>/ (same layout as
421
422
  // agents-cli source). Version isolation copies into versionHome/.agents/plugins/.
422
- capabilities: { hooks: { since: '1.34.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: false, skills: { since: '1.25.0' }, commands: false, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['edit'] },
423
+ // Workflows sync as Goose recipe YAML; permissions sync to permission.yaml.
424
+ capabilities: { hooks: { since: '1.34.0' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: { since: '1.25.0' }, commands: false, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: true, memory: false, modes: ['edit'] },
423
425
  },
424
426
  // Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
425
427
  // configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
@@ -448,7 +450,7 @@ export const AGENTS = {
448
450
  variableSyntax: '{{args}}',
449
451
  supportsHooks: true,
450
452
  cloudProvider: 'antigravity',
451
- capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['edit', 'skip'], rulesImports: false },
453
+ capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '1.0.16' }, rules: { file: 'AGENTS.md' }, workflows: { since: '1.0.6' }, memory: false, modes: ['edit', 'skip'], rulesImports: false }, // workflows: markdown files in the shared, HOME-global ~/.gemini/config/global_workflows/ (agy scans it at startup; not version-isolated — see workflows.ts), invoked as /<name> slash commands
452
454
  },
453
455
  // xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
454
456
  // first launch, or XAI_API_KEY env var for headless. MCP servers configured inline
@@ -522,7 +524,7 @@ export const AGENTS = {
522
524
  plugins: true,
523
525
  subagents: true, // YAML agent files under ~/.kimi-code/agents/ (see transformSubagentForKimi)
524
526
  rules: { file: 'AGENTS.md' },
525
- workflows: false,
527
+ workflows: true,
526
528
  memory: false,
527
529
  modes: ['plan', 'edit', 'auto', 'skip'],
528
530
  rulesImports: false,
@@ -1022,6 +1024,64 @@ async function antigravityKeychainSignedIn() {
1022
1024
  }
1023
1025
  return cachedAgyKeychainSignedIn;
1024
1026
  }
1027
+ /**
1028
+ * OpenCode (sst/opencode) stores provider credentials in a single JSON file at
1029
+ * `$XDG_DATA_HOME/opencode/auth.json`, defaulting to
1030
+ * `~/.local/share/opencode/auth.json` on EVERY platform — its `xdg-basedir`
1031
+ * dependency does not special-case macOS, so there is no
1032
+ * `~/Library/Application Support` variant. The path is account-global (not
1033
+ * per-version), matching how `session/discover.ts` already resolves
1034
+ * `~/.local/share/opencode/opencode.db`.
1035
+ *
1036
+ * Resolution order, first existing wins:
1037
+ * 1. `<base>/.local/share/opencode/auth.json` — the passed per-version home.
1038
+ * This is primarily a test hook (suites write a hermetic auth file under a
1039
+ * temp home) but also covers any relocated install.
1040
+ * 2. `$XDG_DATA_HOME/opencode/auth.json` — an explicit XDG override, exactly
1041
+ * what OpenCode itself honours.
1042
+ * 3. `<realHome>/.local/share/opencode/auth.json` — the active default, under
1043
+ * `AGENTS_REAL_HOME` or `os.homedir()`, so every installed version reflects
1044
+ * the one account-global login (same fallback shape as
1045
+ * resolveAccountCredentialPath).
1046
+ * Returns the first existing path, or null. Never throws.
1047
+ */
1048
+ function resolveOpenCodeAuthPath(base) {
1049
+ const candidates = [path.join(base, '.local', 'share', 'opencode', 'auth.json')];
1050
+ const xdgData = process.env.XDG_DATA_HOME;
1051
+ if (xdgData)
1052
+ candidates.push(path.join(xdgData, 'opencode', 'auth.json'));
1053
+ const realHome = process.env.AGENTS_REAL_HOME || os.homedir();
1054
+ candidates.push(path.join(realHome, '.local', 'share', 'opencode', 'auth.json'));
1055
+ for (const candidate of candidates) {
1056
+ try {
1057
+ if (fs.existsSync(candidate))
1058
+ return candidate;
1059
+ }
1060
+ catch { /* unreadable */ }
1061
+ }
1062
+ return null;
1063
+ }
1064
+ /**
1065
+ * Validate one OpenCode auth.json entry against its discriminated union
1066
+ * (`type: 'oauth' | 'api' | 'wellknown'`) and confirm the credential actually
1067
+ * carries its required secret field(s) non-empty. This guards against a
1068
+ * corrupt/half-written entry reading as signed-in — the same "must have a real
1069
+ * credential" floor grok/antigravity apply. We only INSPECT the shape here; the
1070
+ * secret values (`access`/`refresh`/`key`/`token`) are never read out or
1071
+ * surfaced anywhere.
1072
+ */
1073
+ function isValidOpenCodeCredential(value) {
1074
+ if (!value || typeof value !== 'object')
1075
+ return false;
1076
+ const cred = value;
1077
+ const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
1078
+ switch (cred.type) {
1079
+ case 'oauth': return nonEmpty(cred.access) || nonEmpty(cred.refresh);
1080
+ case 'api': return nonEmpty(cred.key);
1081
+ case 'wellknown': return nonEmpty(cred.key) && nonEmpty(cred.token);
1082
+ default: return false;
1083
+ }
1084
+ }
1025
1085
  export async function getAccountInfo(agentId, home) {
1026
1086
  const base = home || os.homedir();
1027
1087
  const empty = {
@@ -1251,6 +1311,34 @@ export async function getAccountInfo(agentId, home) {
1251
1311
  return { ...empty, lastActive };
1252
1312
  return { ...empty, signedIn: true, lastActive };
1253
1313
  }
1314
+ case 'opencode': {
1315
+ // OpenCode's auth.json is a record keyed by provider id ->
1316
+ // { type: 'oauth'|'api'|'wellknown', ...secret fields }. There is no
1317
+ // email/identity claim to surface, so — like antigravity/kimi — we
1318
+ // report signed-in state plus the NON-SECRET provider metadata (which
1319
+ // provider ids hold a valid credential) and never read the tokens/keys
1320
+ // themselves. The user's complaint was the row read "not signed in"
1321
+ // despite a live login; a valid provider entry now shows e.g.
1322
+ // "id:muse-spark" so they can see exactly which provider is configured.
1323
+ const authPath = resolveOpenCodeAuthPath(base);
1324
+ if (!authPath)
1325
+ return { ...empty, lastActive };
1326
+ const data = JSON.parse(await fs.promises.readFile(authPath, 'utf-8'));
1327
+ if (!data || typeof data !== 'object')
1328
+ return { ...empty, lastActive };
1329
+ const providers = Object.entries(data)
1330
+ .filter(([, cred]) => isValidOpenCodeCredential(cred))
1331
+ .map(([id]) => id)
1332
+ .sort();
1333
+ if (providers.length === 0)
1334
+ return { ...empty, lastActive };
1335
+ // Provider ids are config keys (e.g. "anthropic", "muse-spark"), not
1336
+ // secrets. Join them into a stable, human-readable account label +
1337
+ // identity key for usage dedup.
1338
+ const accountId = providers.join('+');
1339
+ const accountKey = buildIdentityKey(agentId, [['providers', accountId]]);
1340
+ return { ...empty, signedIn: true, accountId, accountKey, lastActive };
1341
+ }
1254
1342
  default:
1255
1343
  return { ...empty, lastActive };
1256
1344
  }
@@ -39,8 +39,26 @@ export declare function terminateDispatchedTask(task: HostTask): void;
39
39
  export interface DispatchOptions {
40
40
  agent: string;
41
41
  prompt: string;
42
+ /** Explicit agent version pin (e.g. "2.1.207") to forward as `agent@version`. */
43
+ version?: string;
44
+ /** Explicit run strategy (e.g. "balanced") to forward as `--strategy <strategy>`. */
45
+ strategy?: string;
42
46
  mode?: string;
43
47
  model?: string;
48
+ /** Reasoning effort to forward as `--effort <effort>`. */
49
+ effort?: string;
50
+ /** Additional directories to grant, already made remote-portable. */
51
+ addDir?: string[];
52
+ /** Stream events as JSON lines. */
53
+ json?: boolean;
54
+ /** Show detailed execution logs. */
55
+ verbose?: boolean;
56
+ /** Kill the agent after this duration, forwarded as `--timeout <duration>`. */
57
+ timeout?: string;
58
+ /** Skip the interactive budget-confirm prompt. */
59
+ yes?: boolean;
60
+ /** Route through the Agent Client Protocol. */
61
+ acp?: boolean;
44
62
  remoteCwd?: string;
45
63
  /**
46
64
  * Force the remote run's NEW session to use this exact id (Claude only, via
@@ -68,10 +86,28 @@ export interface DispatchOptions {
68
86
  export declare function buildRunForwardedArgs(opts: DispatchOptions): string[];
69
87
  export interface InteractiveDispatchOptions {
70
88
  agent: string;
89
+ /** Explicit agent version pin (e.g. "2.1.207") to forward as `agent@version`. */
90
+ version?: string;
91
+ /** Explicit run strategy (e.g. "balanced") to forward as `--strategy <strategy>`. */
92
+ strategy?: string;
71
93
  /** Optional prompt — forwarded only when the caller explicitly forced interactive mode. */
72
94
  prompt?: string;
73
95
  mode?: string;
74
96
  model?: string;
97
+ /** Reasoning effort to forward as `--effort <effort>`. */
98
+ effort?: string;
99
+ /** Additional directories to grant, already made remote-portable. */
100
+ addDir?: string[];
101
+ /** Stream events as JSON lines. */
102
+ json?: boolean;
103
+ /** Show detailed execution logs. */
104
+ verbose?: boolean;
105
+ /** Kill the agent after this duration, forwarded as `--timeout <duration>`. */
106
+ timeout?: string;
107
+ /** Skip the interactive budget-confirm prompt. */
108
+ yes?: boolean;
109
+ /** Route through the Agent Client Protocol. */
110
+ acp?: boolean;
75
111
  remoteCwd?: string;
76
112
  sessionId?: string;
77
113
  name?: string;
@@ -175,11 +175,30 @@ async function launchDetached(host, target, opts) {
175
175
  * resume wins when — defensively — both are set.
176
176
  */
177
177
  export function buildRunForwardedArgs(opts) {
178
- const args = ['run', opts.agent, opts.prompt, '--quiet'];
178
+ const agentArg = opts.version ? `${opts.agent}@${opts.version}` : opts.agent;
179
+ const args = ['run', agentArg, opts.prompt, '--quiet'];
180
+ if (opts.strategy)
181
+ args.push('--strategy', opts.strategy);
179
182
  if (opts.mode)
180
183
  args.push('--mode', opts.mode);
181
184
  if (opts.model)
182
185
  args.push('--model', opts.model);
186
+ if (opts.effort)
187
+ args.push('--effort', opts.effort);
188
+ if (opts.addDir) {
189
+ for (const dir of opts.addDir)
190
+ args.push('--add-dir', dir);
191
+ }
192
+ if (opts.json)
193
+ args.push('--json');
194
+ if (opts.verbose)
195
+ args.push('--verbose');
196
+ if (opts.timeout)
197
+ args.push('--timeout', opts.timeout);
198
+ if (opts.yes)
199
+ args.push('--yes');
200
+ if (opts.acp)
201
+ args.push('--acp');
183
202
  if (opts.name)
184
203
  args.push('--name', opts.name);
185
204
  if (opts.resume)
@@ -196,15 +215,34 @@ export function buildRunForwardedArgs(opts) {
196
215
  * infer headless from the prompt).
197
216
  */
198
217
  export function buildInteractiveRunForwardedArgs(opts) {
199
- const args = ['run', opts.agent];
218
+ const agentArg = opts.version ? `${opts.agent}@${opts.version}` : opts.agent;
219
+ const args = ['run', agentArg];
200
220
  if (opts.prompt && opts.forceInteractive)
201
221
  args.push(opts.prompt);
202
222
  if (opts.forceInteractive)
203
223
  args.push('--interactive');
224
+ if (opts.strategy)
225
+ args.push('--strategy', opts.strategy);
204
226
  if (opts.mode)
205
227
  args.push('--mode', opts.mode);
206
228
  if (opts.model)
207
229
  args.push('--model', opts.model);
230
+ if (opts.effort)
231
+ args.push('--effort', opts.effort);
232
+ if (opts.addDir) {
233
+ for (const dir of opts.addDir)
234
+ args.push('--add-dir', dir);
235
+ }
236
+ if (opts.json)
237
+ args.push('--json');
238
+ if (opts.verbose)
239
+ args.push('--verbose');
240
+ if (opts.timeout)
241
+ args.push('--timeout', opts.timeout);
242
+ if (opts.yes)
243
+ args.push('--yes');
244
+ if (opts.acp)
245
+ args.push('--acp');
208
246
  if (opts.name)
209
247
  args.push('--name', opts.name);
210
248
  if (opts.resume)
@@ -106,15 +106,15 @@ export declare function convertToClaudeFormat(set: PermissionSet): ClaudePermiss
106
106
  export declare function convertToCursorFormat(set: PermissionSet): CursorPermissions;
107
107
  /**
108
108
  * Convert canonical permission set to Gemini format.
109
- * Gemini reads tool allow-lists from settings.json under `tools.allowed`.
110
- * Bash permissions map to run_shell_command(prefix) — the prefix is extracted
111
- * from the canonical "Bash(cmd:*)" pattern by stripping the trailing ":*".
112
- * Blanket Bash grants map to bare "run_shell_command" (no prefix filter).
109
+ * Gemini reads tool allow/deny lists from settings.json under
110
+ * `tools.core` / `tools.exclude`. Bash permissions map to ShellTool(pattern).
111
+ * Blanket Bash grants map to bare "ShellTool" (no command filter).
113
112
  * Non-Bash tool patterns are skipped; Gemini uses different tool names.
114
113
  */
115
114
  export declare function convertToGeminiFormat(set: PermissionSet): {
116
115
  tools: {
117
- allowed: string[];
116
+ core: string[];
117
+ exclude?: string[];
118
118
  };
119
119
  };
120
120
  /** Convert canonical Bash rules into Droid's command arrays. */
@@ -140,6 +140,15 @@ export declare function convertToAntigravityFormat(set: PermissionSet): {
140
140
  deny?: string[];
141
141
  };
142
142
  };
143
+ export interface GoosePermissionConfig {
144
+ user: {
145
+ always_allow: string[];
146
+ ask_before: string[];
147
+ never_allow: string[];
148
+ };
149
+ }
150
+ /** Convert canonical permissions to Goose's per-tool permission.yaml shape. */
151
+ export declare function convertToGooseFormat(set: PermissionSet): GoosePermissionConfig;
143
152
  /**
144
153
  * Convert canonical permission set to Grok format.
145
154
  * Grok reads ~/.grok/config.toml with