triss-coworker 0.28.0 → 0.30.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/CHANGELOG.md CHANGED
@@ -7,6 +7,98 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.30.0] — 2026-08-06
11
+
12
+ ### Added
13
+
14
+ - **OpenCode coder runs can switch provider and the complete main/small pair
15
+ for one invocation.** `triss coder run --provider <name> --model <p/m>
16
+ [--small-model <p/m>]` uses an in-memory OpenCode overlay and never rewrites
17
+ `.env` or `opencode.json`; omitted `--small-model` defaults to the one-shot
18
+ main. CLI help, MCP (`provider` / `model` / `small_model`), README, config
19
+ reference, and generated agent instructions document the same contract.
20
+ - Added docs-first implementation and process-lifecycle contracts with focused
21
+ RED/GREEN coverage for GLM ↔ Triss worker switching.
22
+
23
+ ### Changed
24
+
25
+ - The MCP server now advertises the package version instead of a stale
26
+ hard-coded protocol version.
27
+ - `--model` without `--provider` retains its existing main-only semantics.
28
+ An explicit provider requires a fully qualified model, both transient roles
29
+ must share its exact prefix, and Crush remains Z.AI-only. Worker credentials
30
+ and the managed provider can coexist with GLM while either pair is selected
31
+ per run.
32
+
33
+ ### Fixed
34
+
35
+ - `coder run` no longer resolves when the immediate OpenCode CLI exits but a
36
+ same-process-group tool descendant remains alive. Triss now terminates and
37
+ waits for residual processes before computing the final envelope, preventing
38
+ late edits and lingering database/WAL contention.
39
+ - MCP request cancellation/timeout signals now reach `runCoderRun()` and the
40
+ selected engine's detached process group instead of leaving OpenCode or
41
+ Crush running after the client regains control. Timeout, host SIGINT/SIGTERM,
42
+ caller cancellation, and normal-close cleanup preserve SIGKILL escalation
43
+ for descendants that survive SIGTERM.
44
+ - A successful child exit now immediately disarms its execution timeout and
45
+ host/AbortSignal listeners while bounded residual cleanup and stdio close
46
+ continue. A delayed `close` can no longer relabel a completed OpenCode or
47
+ Crush run as `timeout` or signal a recycled numeric process group.
48
+
49
+ ### Security
50
+
51
+ - Detached OpenCode and Crush group signalling now rejects missing,
52
+ non-integer, zero, and `1` child PIDs before negation. This prevents a failed
53
+ or test-double spawn from issuing POSIX `kill(-1, SIGTERM)` to every process
54
+ the current user can signal, or `kill(0, SIGTERM)` to Triss's own group.
55
+ Custom spawn seams also receive no real group-signalling authority unless
56
+ they explicitly inject the matching process-group owner.
57
+ - One-shot runs overlay only model fields and never assume that overlay has
58
+ final precedence. Before any real selected credential reaches OpenCode,
59
+ Triss audits every file-backed source loaded by pinned OpenCode 1.18.7:
60
+ global `config.json`/`opencode.json(c)`, `~/.opencode/opencode.json(c)`, and
61
+ runtime-directory ancestors through the Git root or `/` outside Git. It then
62
+ resolves the final merged config with `debug config --pure` under the exact
63
+ sanitized child environment and a random canary instead of the real key.
64
+ Final main/small models and the selected provider must match, catching later
65
+ account/org, managed-directory, and macOS MDM overrides. The actual run also
66
+ uses `--pure`; unreadable, JSONC, unknown, or unverified config fails closed.
67
+ Concurrent same-user mutation between preflight and spawn remains outside
68
+ the guard's threat model.
69
+
70
+ ## [0.29.0] — 2026-08-06
71
+
72
+ ### Added
73
+
74
+ - **Existing OpenAI-compatible worker profiles are now available in coder
75
+ mode.** `triss coder init --provider worker` registers an env-backed
76
+ `triss-worker` provider through `@ai-sdk/openai-compatible`, reusing
77
+ `TRISS_WORKER_API_KEY`, `TRISS_WORKER_BASE_URL`, and the configured
78
+ flash/pro model ids instead of introducing another credential. Init, run,
79
+ model inspection/switching, status, MCP, wizard/help output, and generated
80
+ agent instructions all understand `triss-worker/<model-id>`. V1 supports one
81
+ custom profile and the Chat Completions protocol; Crush remains Z.AI-only.
82
+
83
+ ### Changed
84
+
85
+ - Worker setup resolves credentials, endpoint, and model ids from the selected
86
+ local/global scope while preserving genuine parent-shell overrides. Worker
87
+ readiness now has one shared definition across `triss status` and MCP, and
88
+ the MCP coder-status description names `TRISS_WORKER_API_KEY` explicitly.
89
+ - OpenCode init, persistent model changes, and rollback now coordinate through
90
+ the same PID/token `(engine, scope)` filesystem lock with dead-PID recovery.
91
+
92
+ ### Security
93
+
94
+ - Worker init and run fail closed before forwarding credentials when the
95
+ effective project/global `provider["triss-worker"]`, endpoint, env-backed key
96
+ binding, package, or complete flash/pro model allowlist is missing, stale, or
97
+ conflicting. A global init cannot persist project-local endpoint settings or
98
+ report success over a higher-precedence unsafe project provider.
99
+ - Jira issue output and its model-summarization corpus no longer include the
100
+ presence or absence of the worker credential.
101
+
10
102
  ## [0.28.0] — 2026-08-06
11
103
 
12
104
  ### Added
@@ -841,7 +933,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
841
933
 
842
934
  - Initial release of `triss-coworker`.
843
935
 
844
- [Unreleased]: https://github.com/ayleen/triss-coworker/compare/v0.27.0...HEAD
936
+ [Unreleased]: https://github.com/ayleen/triss-coworker/compare/v0.30.0...HEAD
937
+ [0.30.0]: https://github.com/ayleen/triss-coworker/compare/v0.29.0...v0.30.0
938
+ [0.29.0]: https://github.com/ayleen/triss-coworker/compare/v0.28.0...v0.29.0
939
+ [0.28.0]: https://github.com/ayleen/triss-coworker/compare/v0.27.1...v0.28.0
940
+ [0.27.1]: https://github.com/ayleen/triss-coworker/compare/v0.27.0...v0.27.1
845
941
  [0.27.0]: https://github.com/ayleen/triss-coworker/compare/v0.26.0...v0.27.0
846
942
  [0.26.0]: https://github.com/ayleen/triss-coworker/compare/v0.25.0...v0.26.0
847
943
  [0.25.0]: https://github.com/ayleen/triss-coworker/compare/v0.24.2...v0.25.0
package/README.md CHANGED
@@ -237,7 +237,8 @@ The exposed tool set is **filtered by configured credentials**:
237
237
  - **`triss_github_*`** when `GITHUB_TOKEN` is set (or `gh` CLI logged in).
238
238
  - **`triss_gitlab_*`** when `GITLAB_TOKEN` is set.
239
239
  - **`triss_coder_run` + `triss_coder_status`** when any coder provider key is
240
- set — `ZHIPU_API_KEY` (Z.AI GLM), `OPENCODE_API_KEY` (OpenCode Zen or Go),
240
+ set — `TRISS_WORKER_API_KEY` (the existing OpenAI-compatible worker),
241
+ `ZHIPU_API_KEY` (Z.AI GLM), `OPENCODE_API_KEY` (OpenCode Zen or Go),
241
242
  `MOONSHOT_API_KEY` (Moonshot Kimi), or `KIMI_API_KEY` (Kimi for Coding)
242
243
  (setup: `triss coder init`). `triss_coder_run` takes an optional `engine`
243
244
  (`opencode` default, or `crush`); its timeout defaults to
@@ -271,8 +272,8 @@ and writing**.
271
272
  | `triss review` | Code review on current branch or a PR (diff + linked ticket) | The agent reading the whole diff |
272
273
  | `triss commit-msg` | Generates a commit message from staged diff | Hand-writing or copy-pasting from web LLMs |
273
274
  | `triss usage` | Cumulative cost / token usage with per-project breakdown | Squinting at stderr after each call |
274
- | `triss coder init` | Sets up a coding agent (default `opencode` engine; `--engine crush` for crush): provider key (Z.AI GLM default; `--provider opencode-zen` for free rotating Zen models; `--provider opencode-go` for the paid OpenCode Go subscription; `--provider moonshot` / `kimi-for-coding` for Kimi), config, permission policy, and agent templates. Blocks (non-zero) on an unsafe existing `opencode.json` — missing deny-first bash policy (override with `--allow-unsafe-bash`) or a stale/cross-provider `small_model`. | Manually installing/configuring opencode |
275
- | `triss coder run` | Spawns the coding agent (GLM, Kimi, OpenCode Zen, or OpenCode Go) and prints one JSON envelope (`--engine opencode\|crush`; `--isolate` for a disposable worktree — opencode defaults to isolate-OFF, crush defaults to isolate-ON; crush adds opt-in `--restrict`/`--no-restrict` for its CLI allowlist). **POSIX only** (macOS/Linux) — refuses to run on Windows. | Manually driving `opencode run` and parsing its ndjson stream |
275
+ | `triss coder init` | Sets up a coding agent (default `opencode` engine; `--engine crush` for crush): provider key/profile (`--provider worker` reuses `TRISS_WORKER_*`; Z.AI GLM default; Zen, Go, and Kimi are also supported), config, permission policy, and agent templates. Blocks (non-zero) on an unsafe existing `opencode.json` — missing deny-first bash policy (override with `--allow-unsafe-bash`) or a stale/cross-provider `small_model`. | Manually installing/configuring opencode |
276
+ | `triss coder run` | Spawns the coding agent (the OpenAI-compatible Triss worker, GLM, Kimi, OpenCode Zen, or OpenCode Go) and prints one JSON envelope (`--engine opencode\|crush`; `--isolate` for a disposable worktree — opencode defaults to isolate-OFF, crush defaults to isolate-ON; crush adds opt-in `--restrict`/`--no-restrict` for its CLI allowlist). **POSIX only** (macOS/Linux) — refuses to run on Windows. | Manually driving `opencode run` and parsing its ndjson stream |
276
277
  | `triss coder clean` | Removes finished `.triss/wt` isolation worktrees (`--all` forces all) | Manually finding and deleting stale git worktrees |
277
278
  | `triss init` | Drops a tiny (~15 line) delegation block into `CLAUDE.md` / `AGENTS.md` | Hand-writing routing rules |
278
279
  | `triss agent-help` | Prints the full delegation cookbook on demand (the nano block points here) | A 200-line CLAUDE.md that always loads |
@@ -453,7 +454,8 @@ default timeout, configurable via `--timeout <ms>`.
453
454
 
454
455
  ### `triss coder`
455
456
 
456
- Delegates an implementation task to a cheap coding agent — GLM, Kimi,
457
+ Delegates an implementation task to a cheap coding agent — the existing
458
+ OpenAI-compatible Triss worker, GLM, Kimi,
457
459
  OpenCode Zen, or OpenCode Go models (the `opencode` engine by default; `crush` is an
458
460
  alternative — see **Engines** below) instead of the primary model writing
459
461
  the code itself.
@@ -467,7 +469,8 @@ triss coder clean # remove finished isolation wo
467
469
  ```
468
470
 
469
471
  `triss coder init` first asks which provider to configure — **Z.AI GLM**
470
- (default), **OpenCode Zen** (`--provider opencode-zen`, free rotating
472
+ (default), the existing **Triss worker** (`--provider worker`, aliases
473
+ `openai` / `openai-compatible`), **OpenCode Zen** (`--provider opencode-zen`, free rotating
471
474
  models), **OpenCode Go** (`--provider opencode-go`, paid subscription),
472
475
  **Moonshot Kimi** (`--provider moonshot`, pay-as-you-go
473
476
  `moonshotai/*` models like `kimi-k2.7-code`), or **Kimi for Coding**
@@ -486,7 +489,10 @@ endpoint. Either way it lets you pick the model interactively. See the
486
489
  Prints one JSON envelope to stdout — `files_changed`, `diff_stat`, and
487
490
  `worktree` tell you what to review; `--isolate` runs the agent in a
488
491
  disposable `.triss/wt/<slug>` git worktree so it never touches your
489
- working tree directly. `--session <slug>` continues the same opencode
492
+ working tree directly. Before returning the envelope, Triss terminates and
493
+ waits for any residual process in the selected engine's process group, so tests or DB
494
+ clients cannot keep locks or write files after completion. MCP cancellation is
495
+ forwarded to the same cleanup path. `--session <slug>` continues the same opencode
490
496
  conversation across calls. **POSIX only** (macOS/Linux) for now. See
491
497
  `docs/glm-clients.md` for the full picture of how Triss talks to GLM
492
498
  (both engines, key/endpoint routing, models, and every usage mode),
@@ -513,7 +519,9 @@ natively; triss also forwards it as `ZAI_API_KEY` for older binaries; see
513
519
  `docs/crush-restrict-issues.md` for the live-verified bug facts.
514
520
 
515
521
  **Providers** — the `opencode` engine isn't limited to Z.AI. The required
516
- API key follows the model's `<provider>/` prefix: `zai-coding-plan/*` and
522
+ API key follows the model's `<provider>/` prefix: `triss-worker/*` reuses
523
+ the existing `TRISS_WORKER_API_KEY` + `TRISS_WORKER_BASE_URL` profile;
524
+ `zai-coding-plan/*` and
517
525
  `zai/*` (GLM) use `ZHIPU_API_KEY`; `opencode/*` models — served free by
518
526
  [OpenCode Zen](https://opencode.ai/docs/zen/) — and paid `opencode-go/*`
519
527
  models use the shared `OPENCODE_API_KEY`;
@@ -523,6 +531,55 @@ models use the shared `OPENCODE_API_KEY`;
523
531
  **`kimi-for-coding/k3`** (Kimi K3) — use `KIMI_API_KEY`. Zen ids rotate
524
532
  freely, so discover the current id and pass it rather than hard-coding one:
525
533
 
534
+ ```bash
535
+ triss coder init --provider worker
536
+ triss coder run "mechanical task" \
537
+ --provider worker --model triss-worker/deepseek-v4-flash
538
+ ```
539
+
540
+ The worker route uses Chat Completions through `@ai-sdk/openai-compatible`.
541
+ It creates no second API key and is supported by the OpenCode engine only.
542
+ After changing the worker base URL or flash/pro model ids, rerun
543
+ `triss coder init --provider worker`; `coder model set --provider worker`
544
+ fails closed until the matching env-backed provider block is present. Global
545
+ init reads only global worker settings (except genuine shell exports) and
546
+ rejects a conflicting higher-precedence project provider. Every worker run
547
+ revalidates the registered global/project endpoint and model allowlist before
548
+ forwarding the key, and prints an exact scope-specific init command when the
549
+ saved provider is missing or stale. The broader runtime-tree snapshot described
550
+ below applies to explicit one-shot `--provider worker` runs.
551
+
552
+ Worker init is a one-time provider registration, not an exclusive provider
553
+ choice: GLM and worker credentials/configuration can coexist. After the worker
554
+ provider has been registered, switch the complete main/small pair for one task
555
+ without changing the persistent default:
556
+
557
+ ```bash
558
+ # Worker for this task; small defaults to the same model.
559
+ triss coder run "mechanical task" \
560
+ --provider worker --model triss-worker/deepseek-v4-flash
561
+
562
+ # GLM for this task, with an explicit fast model.
563
+ triss coder run "hard task" \
564
+ --provider zai --model zai-coding-plan/glm-5.2 \
565
+ --small-model zai-coding-plan/glm-5-turbo
566
+ ```
567
+
568
+ `--provider` is OpenCode-only, requires a provider-qualified `--model`, and
569
+ never rewrites `.env` or `opencode.json`. `--small-model` is available only
570
+ with `--provider`; when omitted it equals the one-shot main model. The worker
571
+ run still validates the previously registered env-backed provider before the
572
+ key is forwarded. During a one-shot provider run, Triss first audits every
573
+ file-backed source loaded by the pinned OpenCode version: global `config.json`
574
+ and `opencode.json(c)`, `~/.opencode/opencode.json(c)`, and runtime-directory
575
+ ancestors up to the Git root (or `/` outside Git). It then asks OpenCode for the
576
+ final merged config under the exact sanitized child environment, with a random
577
+ canary instead of the real credential, and verifies the final main/small models
578
+ and selected provider. Both the preflight and actual run use `--pure` to disable
579
+ external plugins. Unreadable/JSONC/unknown layers fail closed. Concurrent
580
+ same-user config mutation between preflight and spawn remains outside this
581
+ guard's threat model.
582
+
526
583
  ```bash
527
584
  triss coder init --provider opencode-zen # guided: key + opencode.json
528
585
  triss coder models --provider opencode-zen # see live Zen ids
@@ -563,8 +620,10 @@ MAIN model only (it sits in the OpenCode-main precedence chain: one-run `--model
563
620
  default), so it changes the main model for every run until unset.
564
621
  `TRISS_CODER_SMALL_MODEL` is setup intent consumed by `triss coder init` /
565
622
  `triss coder model` — it does not swap the small model mid-run (opencode reads
566
- `small_model` from `opencode.json`); use `--model` for a one-run main-model
567
- override. The deny-first `opencode.json` bash policy applies to every provider.
623
+ `small_model` from `opencode.json`). Use `--model` alone for a one-run main-only
624
+ override within the current provider; use `--provider` + `--model` and optional
625
+ `--small-model` to switch the complete pair for one run. The deny-first
626
+ `opencode.json` bash policy applies to every provider.
568
627
  Full details, the model catalogue, and every configuration path are in
569
628
  [docs/opencode-zen.md](docs/opencode-zen.md) and [docs/opencode-go.md](docs/opencode-go.md).
570
629
 
@@ -581,26 +640,29 @@ confusion is conflating them:
581
640
  - **Engine** = *how* the agent is launched: `opencode` (default) or
582
641
  `crush`. Set at `triss coder init --engine …` or per run with
583
642
  `triss coder run --engine …`.
584
- - **Provider** = *which* API serves the model: Z.AI GLM, OpenCode Zen, OpenCode Go,
585
- Moonshot Kimi, Kimi for Coding. Set at `triss coder init --provider …`.
643
+ - **Provider** = *which* API serves the model: Triss worker, Z.AI GLM, OpenCode Zen, OpenCode Go,
644
+ Moonshot Kimi, Kimi for Coding. Register/set the persistent default with
645
+ `triss coder init --provider …`, or select a complete one-shot pair with
646
+ `triss coder run --provider … --model … [--small-model …]`.
586
647
 
587
648
  Not every engine speaks every provider:
588
649
 
589
650
  | Engine | Providers served |
590
651
  | ---------- | ------------------------------------------------- |
591
- | `opencode` | Z.AI GLM, OpenCode Zen, OpenCode Go, Moonshot, Kimi for Coding |
652
+ | `opencode` | Triss worker, Z.AI GLM, OpenCode Zen, OpenCode Go, Moonshot, Kimi for Coding |
592
653
  | `crush` | Z.AI GLM (coding-plan only) |
593
654
 
594
655
  `triss coder init` drives setup in that order — **engine, then provider,
595
656
  then it asks for only that provider's credential** and writes the matching
596
- model prefix (`opencode/<id>`, `opencode-go/<id>`, `zai-coding-plan/*`, `moonshotai/*`, or
657
+ model prefix (`triss-worker/<id>`, `opencode/<id>`, `opencode-go/<id>`, `zai-coding-plan/*`, `moonshotai/*`, or
597
658
  `kimi-for-coding/*`).
598
659
 
599
- **Choosing a model.** `--model` on `triss coder run` is a
600
- **single-run override of the main model only** — it does not persist
601
- and does not change the small/summarization model. To persist a
602
- choice, use `triss coder model set` — positional main plus `--small` —
603
- which requires you to name the engine and the scope explicitly:
660
+ **Choosing a model.** `--model` by itself on `triss coder run` remains a
661
+ **single-run override of the main model only**. For a non-persistent provider
662
+ switch, pass `--provider` with a provider-qualified `--model`; add
663
+ `--small-model` when the small role should differ, otherwise it defaults to the
664
+ one-shot main. To persist a choice, use `triss coder model set` — positional
665
+ main plus `--small` — which requires the engine and scope explicitly:
604
666
 
605
667
  ```bash
606
668
  # global, opencode engine: positional main + --small; --yes for noninteractive use
package/bin/triss.js CHANGED
@@ -183,7 +183,7 @@ config
183
183
  .option('--standard', 'API key + one model only — skip the standard/advanced prompt')
184
184
  .option('--advanced', 'full wizard with presets, base URL, integrations — skip the prompt')
185
185
  .option('--coder-engine <name>', 'coder target only: coding engine to configure (opencode default, or crush). `coder init` uses --engine')
186
- .option('--coder-provider <name>', 'coder target only: opencode engine provider (zai, opencode-zen, opencode-go, moonshot, kimi-for-coding). `coder init` uses --provider')
186
+ .option('--coder-provider <name>', 'coder target only: opencode engine provider (zai, worker, opencode-zen, opencode-go, moonshot, kimi-for-coding). `coder init` uses --provider')
187
187
  .action(wrap(runWizard));
188
188
 
189
189
  config
@@ -238,19 +238,21 @@ coder
238
238
  .option('-g, --global', 'save to the global scope (~/.config/triss/.env, ~/.config/opencode/)')
239
239
  .option('-l, --local', 'save to the project scope (./.triss.env, ./opencode.json)')
240
240
  .option('--engine <name>', 'coding engine to configure: opencode (default) or crush')
241
- .option('--provider <name>', 'opencode engine model provider: zai (Z.AI GLM, default), opencode-zen (free rotating models), opencode-go (paid Go subscription), moonshot (Kimi pay-as-you-go), or kimi-for-coding (subscription)')
241
+ .option('--provider <name>', 'opencode engine model provider: zai, worker (existing OpenAI-compatible TRISS_WORKER_* profile), opencode-zen, opencode-go, moonshot, or kimi-for-coding')
242
242
  .option('--allow-unverified', 'requires explicit --provider opencode-go (alias: go): allow the built-in fallback only after a temporary network or HTTP 408/429/500/502/503/504 catalogue failure (never bypasses 401/403, empty, or invalid responses)')
243
243
  .option('--allow-unsafe-bash', 'proceed even if an existing opencode.json has no deny-first bash policy (the agent runs with --auto)')
244
244
  .action(wrap(runCoderInit));
245
245
 
246
246
  coder
247
247
  .command('run [prompt]')
248
- .description('Spawn a coding agent — GLM, Kimi, OpenCode Zen, or OpenCode Go models (opencode default, or --engine crush) — and print a JSON envelope to stdout')
248
+ .description('Spawn a coding agent — GLM, the OpenAI-compatible Triss worker, Kimi, OpenCode Zen, or OpenCode Go (opencode default, or --engine crush) — and print a JSON envelope to stdout')
249
249
  .option('--engine <name>', 'coding engine: opencode (default) or crush')
250
250
  .option('--session <id>', 'triss-side session slug, mapped to a real opencode session id in .triss/sessions.json')
251
251
  .option('--continue', 'continue the most recent opencode session (maps to opencode --continue)')
252
252
  .option('--agent <name>', 'opencode agent template to use', 'coder')
253
+ .option('--provider <name>', 'OpenCode only: select a provider for one run; requires --model and does not modify .env or opencode.json')
253
254
  .option('--model <p/m>', 'override the MAIN model for this one run only (does not change small_model or repair persistent config; use `triss coder model set` for that)')
255
+ .option('--small-model <p/m>', 'with --provider, override small_model for one run (defaults to the one-shot main model)')
254
256
  .option('--isolate', 'run in a disposable git worktree under .triss/wt/<slug>')
255
257
  .option('--no-isolate', 'disable worktree isolation (opencode defaults to OFF; crush defaults to ON)')
256
258
  .option('--restrict', 'crush only: enforce the allowlist via CLI --allow-bash/--allow-tool flags (--restrict-run). Opt-in (default OFF)')
@@ -274,7 +276,7 @@ coder
274
276
  ' • opencode: project opencode.json (local) or ~/.config/opencode/opencode.json (global)\n' +
275
277
  ' • crush: ./.crush/crush.json (local) or ~/.local/share/crush/crush.json (global)')
276
278
  .option('--engine <name>', 'coding engine: opencode (default) or crush')
277
- .option('--provider <name>', 'provider kind: zai, opencode-zen, opencode-go, moonshot, or kimi-for-coding')
279
+ .option('--provider <name>', 'provider kind: zai, worker, opencode-zen, opencode-go, moonshot, or kimi-for-coding')
278
280
  .option('--json', 'print the stable machine-readable state object (no secrets)')
279
281
  .action(wrap(runCoderModels));
280
282
 
@@ -296,7 +298,7 @@ coderModel
296
298
  )
297
299
  .option('--small <model>', 'small/fast model id (omit to keep the current compatible value)')
298
300
  .option('--engine <name>', 'coding engine: opencode (default) or crush')
299
- .option('--provider <name>', 'provider kind: zai, opencode-zen, opencode-go, moonshot, or kimi-for-coding')
301
+ .option('--provider <name>', 'provider kind: zai, worker, opencode-zen, opencode-go, moonshot, or kimi-for-coding')
300
302
  .option('-g, --global', 'write to the global scope (OpenCode: ~/.config/opencode/opencode.json + global .env; Crush: ~/.local/share/crush/crush.json)')
301
303
  .option('-l, --local', 'write to the project scope (OpenCode: ./opencode.json + ./.triss.env; Crush: ./.crush/crush.json)')
302
304
  .option('--allow-unverified', 'with explicit main + --small, bypass only a provider-defined transient catalogue failure (Go: transport or HTTP 408/429/500/502/503/504; Zen: timeout/http-error/parse-error; never auth or authoritative failure)')
@@ -30,7 +30,7 @@ management and recovery workflow.
30
30
 
31
31
  | Engine | Supported providers | Persistent model store |
32
32
  | --- | --- | --- |
33
- | `opencode` | Z.AI GLM, OpenCode Zen, Moonshot Kimi, Kimi for Coding | `opencode.json` plus `TRISS_CODER_MODEL` / `TRISS_CODER_SMALL_MODEL` |
33
+ | `opencode` | Triss worker (OpenAI-compatible), Z.AI GLM, OpenCode Zen, OpenCode Go, Moonshot Kimi, Kimi for Coding | `opencode.json` plus `TRISS_CODER_MODEL` / `TRISS_CODER_SMALL_MODEL` |
34
34
  | `crush` | Z.AI GLM only | crush large/fast model roles in `crush.json` |
35
35
 
36
36
  OpenCode Zen is not an engine and `hy3-free` was not a GLM model. It was one
@@ -648,7 +648,7 @@ verification contract as OpenCode applies (Corrective Blocker A):
648
648
  released on every exit (success, failure, rollback-failed).
649
649
  - `deps.lock` is an OVERRIDE seam for deterministic unit tests ONLY. Absence of
650
650
  `deps.lock` MUST NEVER mean unlocked: when `deps.lock` is not supplied the
651
- operation MUST use the built-in filesystem lock via `acquireDefaultLock`.
651
+ operation MUST use the shared filesystem lock via `acquireCoderMutationLock`.
652
652
  - The lock seam is `deps.lock(engine, scope)` returning `{ release() }`. When
653
653
  `deps.lock` is injected, the apply MUST call it; tests assert acquire happens
654
654
  before the config read and release happens after verification completes.
@@ -0,0 +1,126 @@
1
+ # One-shot coder provider selection
2
+
3
+ Status: implementation contract for v0.30.0.
4
+
5
+ ## Problem
6
+
7
+ `triss coder run --model` currently overrides only OpenCode's main model. The
8
+ persisted `small_model` remains active, so changing from GLM to the Triss worker
9
+ (or back) for one run can create a cross-provider pair and is rejected. Users
10
+ must persistently switch both roles even when they only want another provider
11
+ for one task.
12
+
13
+ ## User contract
14
+
15
+ The OpenCode engine accepts an explicit one-shot provider pair:
16
+
17
+ ```bash
18
+ triss coder run "mechanical task" \
19
+ --provider worker \
20
+ --model triss-worker/deepseek-v4-flash
21
+
22
+ triss coder run "hard task" \
23
+ --provider zai \
24
+ --model zai-coding-plan/glm-5.2 \
25
+ --small-model zai-coding-plan/glm-5-turbo
26
+ ```
27
+
28
+ - `--provider` is one-run only and never rewrites the project `.triss.env`,
29
+ the global Triss `.env`, or any `opencode.json`.
30
+ - `--provider` requires a provider-qualified `--model`. This keeps Z.AI
31
+ coding-plan versus pay-as-you-go selection explicit and avoids guessing a
32
+ model from whichever credentials happen to be present.
33
+ - `--small-model` is valid only with `--provider`. When omitted, the one-shot
34
+ small model equals the explicit main model.
35
+ - Main and small must resolve to the selected provider and must use the same
36
+ raw provider prefix. A regional/plan mix is rejected before credentials are
37
+ forwarded.
38
+ - The feature is OpenCode-only. Crush remains Z.AI-only and rejects
39
+ `--provider`/`--small-model` before spawning.
40
+ - Existing `--model` without `--provider` keeps its v0.29 main-only semantics.
41
+
42
+ The MCP `triss_coder_run` tool exposes the same operation through `provider`,
43
+ `model`, and `small_model` fields.
44
+
45
+ ## Runtime design and security invariants
46
+
47
+ For an explicit provider run, Triss supplies an in-memory
48
+ `OPENCODE_CONFIG_CONTENT` overlay containing only the selected `model` and
49
+ `small_model`. No temporary or persistent config file is published. The
50
+ overlay is not assumed to have final precedence: OpenCode 1.18.7 can merge
51
+ account/organization, managed-directory, and macOS MDM configuration after it.
52
+
53
+ The overlay must not define or replace providers. OpenCode deep-merges provider
54
+ objects, which could retain untrusted lower-precedence options such as custom
55
+ headers. Before forwarding any selected credential, Triss audits the pinned
56
+ OpenCode version's global `config.json` and `opencode.json(c)`, the legacy
57
+ `~/.opencode/opencode.json(c)` source, and direct config ancestors from the
58
+ actual runtime directory to its Git root (or filesystem root outside Git).
59
+ Built-in providers reject any persistent block for the selected provider id;
60
+ managed worker blocks must match the complete expected definition in every
61
+ layer. JSONC is rejected because Triss cannot prove that commented config
62
+ contains no hidden endpoint/header override.
63
+
64
+ After this file audit, Triss runs `opencode debug config --pure` under the exact
65
+ sanitized child environment. The real selected credential is absent; a random
66
+ canary is supplied instead so the managed worker's env binding can still be
67
+ verified. The final merged main model, small model, and selected-provider block
68
+ must match. The actual one-shot `opencode run` also receives `--pure`, disabling
69
+ external plugins in both phases. Any probe failure or unknown final shape is
70
+ fail-closed. This remains a pre-spawn check; concurrent same-user mutation
71
+ between preflight and spawn is outside the guard's threat model. Unverified
72
+ OpenCode versions fail closed so changed discovery rules cannot silently bypass
73
+ the audit. The runtime directory is explicit `--cwd`, inherited `process.cwd()`,
74
+ or the created/reused isolation worktree. The Triss worker
75
+ therefore retains its existing setup prerequisite:
76
+ `triss coder init --engine opencode --provider worker --global|--local`
77
+ registers the env-backed provider once. Every worker run then verifies the
78
+ effective provider package, endpoint, credential binding, and complete model
79
+ allowlist before forwarding `TRISS_WORKER_API_KEY`. In one-shot mode the audit
80
+ checks the selected transient pair rather than requiring the persisted
81
+ main/small pair to be worker models.
82
+
83
+ Only the credential required by the explicit main model is forwarded. The
84
+ selected provider must equal the provider implied by both model prefixes, so a
85
+ flag cannot relabel a model to obtain a different credential.
86
+
87
+ ## Failure contract
88
+
89
+ All usage failures happen before isolation. Provider-config auditing happens
90
+ after an isolation worktree is resolved but before engine spawn or credential
91
+ forwarding; a newly created clean worktree is removed if that audit fails, while
92
+ a reused worktree is preserved for inspection. Failure cases are:
93
+
94
+ - missing `--model` with `--provider`;
95
+ - `--small-model` without `--provider`;
96
+ - unknown provider or model/provider mismatch;
97
+ - empty or whitespace-containing provider/model id;
98
+ - different main/small raw prefixes;
99
+ - non-Z.AI provider flags on Crush;
100
+ - missing selected-provider credential;
101
+ - missing, stale, or conflicting managed worker provider;
102
+ - selected-provider overrides or unauditable JSONC in effective config layers;
103
+ - an installed OpenCode version other than the audited pin.
104
+
105
+ Errors name the invalid flags and include the exact worker init recovery
106
+ command when applicable. No failure writes model pins or OpenCode config.
107
+
108
+ ## TDD and acceptance
109
+
110
+ RED tests must establish:
111
+
112
+ 1. Commander help exposes `--provider` and `--small-model` with one-shot and
113
+ non-persistent wording.
114
+ 2. GLM-persisted config can run a worker one-shot pair, and worker-persisted
115
+ config can run a GLM one-shot pair.
116
+ 3. Spawn receives a matching in-memory main/small overlay and only the selected
117
+ provider credential.
118
+ 4. The persistent env/config bytes are unchanged after success and failure.
119
+ 5. Every failure case above occurs before spawn.
120
+ 6. Legacy main-only `--model` behavior remains unchanged.
121
+ 7. MCP forwards `provider` and `small_model` without changing isolation
122
+ defaults.
123
+
124
+ Acceptance requires focused tests, the full Node test/lint suite, package
125
+ smoke, a live synthetic worker run from a GLM-persisted temporary project, and
126
+ sequential GLM 5.2 plus independent agent reviews.
@@ -0,0 +1,62 @@
1
+ # Coder process-group lifecycle
2
+
3
+ Status: remediation contract for v0.30.0.
4
+
5
+ ## User report
6
+
7
+ An OpenCode Go user observed that Triss returned control while several
8
+ OpenCode-related processes remained alive. Repository edits sometimes appeared
9
+ after the JSON completion envelope, and lingering database users caused WAL
10
+ contention.
11
+
12
+ ## Reproduction and root cause
13
+
14
+ A minimal live `opencode-go/deepseek-v4-flash` completion on OpenCode 1.18.7
15
+ left no process behind. A live synthetic `npm test` child was also cleaned up
16
+ by OpenCode itself. The report is nevertheless valid at Triss's boundary:
17
+
18
+ 1. A deterministic process-group test demonstrated that the immediate
19
+ OpenCode CLI can close its stdio and exit while a same-group tool descendant
20
+ remains alive. `spawnEngine()` resolved on the immediate child's `close`
21
+ event and never checked the rest of the detached process group.
22
+ 2. The MCP SDK supplies an `AbortSignal` when the client cancels or times out a
23
+ tool request. The Triss MCP server discarded that signal, so a client could
24
+ return control while `runCoderRun()` and OpenCode continued in the server.
25
+ 3. On timeout, `close` cleared the pending SIGKILL escalation, which could also
26
+ leave a descendant that survived SIGTERM.
27
+
28
+ ## Contract
29
+
30
+ - The coder completion envelope is emitted only after the selected engine's
31
+ detached process group can no longer execute or write files.
32
+ - On immediate-child close, Triss probes the group, sends SIGTERM to residual
33
+ members, waits a short grace period, escalates to SIGKILL, and waits for group
34
+ disappearance before resolving.
35
+ - Signal permission failures and a group that remains observable after SIGKILL
36
+ fail the coder run closed; Triss must not emit a successful completion
37
+ envelope for either condition.
38
+ - MCP request cancellation is forwarded server → tool handler →
39
+ `runCoderRun()` → `spawnEngine()` / `spawnCrush()`, which terminates the same
40
+ process group.
41
+ - Timeout, rate-limit, host-signal, normal completion, spawn error, and MCP
42
+ cancellation use the same cleanup boundary.
43
+ - Triss never kills processes outside the detached group it created for the
44
+ current engine call.
45
+ - A missing, non-integer, zero, or `1` child pid is never negated or passed to
46
+ `process.kill`; in particular, Triss can never issue POSIX `kill(-1, ...)`
47
+ against every signalable process in the user's login session.
48
+ - A custom/injected spawn function cannot signal an arbitrary real process
49
+ group unless its caller also injects the matching group-signalling owner.
50
+
51
+ ## Verification
52
+
53
+ - A real-process regression spawns a fake OpenCode group leader that emits a
54
+ valid fixture, leaves a 30-second same-group descendant, and exits. The test
55
+ proves the descendant is gone before `runCoderRun()` returns.
56
+ - An abort regression proves cancellation sends SIGTERM to the negative process
57
+ group and produces a killed envelope when partial events exist.
58
+ - MCP unit tests prove the SDK signal reaches the coder lifecycle.
59
+ - Crush regressions prove cancellation, residual-child cleanup, and the
60
+ degenerate-pid guard use the same lifecycle boundary.
61
+ - Live OpenCode Go smoke is repeated after the fix, followed by a process-list
62
+ and delayed-file check.