triss-coworker 0.22.4 → 0.24.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 +78 -0
- package/README.md +63 -9
- package/bin/triss.js +10 -3
- package/docs/coder-agent-plan.md +311 -0
- package/docs/configuration.md +47 -4
- package/docs/crush-issues.md +270 -0
- package/docs/crush-restrict-issues.md +170 -0
- package/docs/glm-clients.md +358 -0
- package/docs/mcp.md +31 -15
- package/docs/opencode-zen.md +256 -0
- package/package.json +1 -1
- package/src/coder-engines/crush.js +398 -0
- package/src/commands/coder.js +1382 -71
- package/src/commands/completion.js +4 -1
- package/src/commands/config.js +25 -5
- package/src/commands/status.js +42 -15
- package/src/integrations/_contract.js +49 -0
- package/src/integrations/confluence/commands.js +2 -2
- package/src/mcp/handlers.js +37 -11
- package/src/mcp/tools.js +20 -9
- package/src/secrets.js +3 -1
- package/templates/claude-full.md +38 -5
- package/templates/claude.md +8 -2
- package/templates/codex-full.md +34 -9
- package/templates/codex.md +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,84 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.24.0] — 2026-07-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **OpenCode Zen provider for `triss coder`.** The default `opencode` engine
|
|
15
|
+
is no longer limited to Z.AI GLM — it can run any model served by
|
|
16
|
+
[OpenCode Zen](https://opencode.ai/docs/zen/) (`opencode/<id>`, e.g. the free
|
|
17
|
+
`opencode/hy3-free` / Tencent Hunyuan 3) by authenticating with a new
|
|
18
|
+
`OPENCODE_API_KEY`. Credentials are provider-scoped: a run forwards **only**
|
|
19
|
+
the key its model needs, so a Zen-only machine needs no `ZHIPU_API_KEY`. Z.AI
|
|
20
|
+
GLM stays the default provider; `crush` remains Z.AI-only. (#12)
|
|
21
|
+
- `triss coder init --provider opencode-zen` — guided Zen setup: prompts for
|
|
22
|
+
`OPENCODE_API_KEY`, resolves the model against the **live Zen catalogue**
|
|
23
|
+
(`GET /zen/v1/models`, first-available from a free-model priority list), writes
|
|
24
|
+
`opencode.json` (deny-first bash policy + agent templates), and pins the chosen
|
|
25
|
+
model into `TRISS_CODER_MODEL` so a bare `triss coder run` works. Provider is
|
|
26
|
+
auto-inferred from a preset/single credential when the flag is omitted. (#12)
|
|
27
|
+
- `OPENCODE_API_KEY` is a first-class, optional, masked secret on the coder
|
|
28
|
+
manifest — surfaced in `triss status`, the `config` wizard, and the MCP coder
|
|
29
|
+
tools (which now light up on `ZHIPU_API_KEY` **or** `OPENCODE_API_KEY`). (#12)
|
|
30
|
+
- `--allow-unsafe-bash` on `triss coder init` — explicit opt-in to proceed when
|
|
31
|
+
an existing `opencode.json` has no deny-first bash policy. (#12)
|
|
32
|
+
- New `docs/opencode-zen.md` deep-dive (auth, live catalogue, precedence, setup
|
|
33
|
+
gates, privacy, MCP, verification). (#12)
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `triss coder init` / `config wizard coder` now **fail (non-zero)** instead of
|
|
38
|
+
reporting a misleading success on an unrunnable setup: a missing provider key,
|
|
39
|
+
a pin shadowed by a shell export or higher-precedence `.env`, or an
|
|
40
|
+
existing/cross-scope `opencode.json` that is unsafe (no deny-first policy) or
|
|
41
|
+
unusable (a `small_model` that is cross-provider, cross-plan, or no longer in
|
|
42
|
+
the live catalogue). Config and templates are still written, so fixing the
|
|
43
|
+
cause and re-running is a clean idempotent completion. (#12)
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
- Resolved 8 CodeQL code-scanning alerts (input validation and safe handling in
|
|
48
|
+
`completion.js`, `_contract.js`, `handlers.js`, `secrets.js`, and hardened the
|
|
49
|
+
`publish` / `test` GitHub workflows).
|
|
50
|
+
- Rewrote `stripHtml` as a state machine and removed HTML-entity decoding to
|
|
51
|
+
eliminate a double-unescape flagged by CodeQL.
|
|
52
|
+
|
|
53
|
+
## [0.23.0] — 2026-07-07
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- **Second GLM coding engine: `crush`** (`@phpcraftdream/crush`, pinned
|
|
58
|
+
`0.1.3`), selectable per run with `triss coder run --engine crush` or
|
|
59
|
+
globally with `TRISS_CODER_ENGINE=crush`; set up via
|
|
60
|
+
`triss coder init --engine crush`. Compared to the default opencode
|
|
61
|
+
engine it emits one JSON envelope (no ndjson fold), uses native
|
|
62
|
+
get-or-create session ids (no `.triss/sessions.json` map), reports a real
|
|
63
|
+
per-call `delta_cost_usd`, and exposes `--role smart|fast`. Both engines
|
|
64
|
+
share the single `ZHIPU_API_KEY` (crush reads it natively on ≥0.1.1, with
|
|
65
|
+
a `ZAI_API_KEY` compatibility alias). (#10)
|
|
66
|
+
- `--restrict` / `--no-restrict` on `triss coder run`, plus the
|
|
67
|
+
`TRISS_CODER_CRUSH_RESTRICT` env var, to opt into a crush command
|
|
68
|
+
allowlist. When on, triss emits the read-only allowlist as CLI flags
|
|
69
|
+
(`--allow-bash` / `--allow-tool`); it is **opt-in** (default off) because
|
|
70
|
+
a denied bash command currently deadlocks crush until the timeout. (#10)
|
|
71
|
+
- `TRISS_CODER_CRUSH_VERSION` to pin a specific `@phpcraftdream/crush`
|
|
72
|
+
npm version. (#10)
|
|
73
|
+
- New `docs/glm-clients.md` — a single reference for how Triss talks to
|
|
74
|
+
GLM (both engines, key/endpoint routing, model selection, usage modes,
|
|
75
|
+
and the safety model), and `docs/crush-issues.md` /
|
|
76
|
+
`docs/crush-restrict-issues.md` capturing the crush maintainer bug
|
|
77
|
+
reports. (#10)
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- `triss coder run --engine crush` isolates by default (a disposable
|
|
82
|
+
`.triss/wt/<slug>` worktree); pass `--no-isolate` to opt out. crush's
|
|
83
|
+
own `crush.json` `permissions.run` config is not yet honored upstream and
|
|
84
|
+
a denied command deadlocks, so the worktree is crush's reliable safety
|
|
85
|
+
layer. opencode is unchanged (isolate off; its `opencode.json` bash
|
|
86
|
+
allowlist is enforced). (#10)
|
|
87
|
+
|
|
10
88
|
## [0.22.4] — 2026-07-04
|
|
11
89
|
|
|
12
90
|
### Fixed
|
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 `ZHIPU_API_KEY` is set
|
|
240
|
-
(setup: `triss coder init`). `triss_coder_run`
|
|
240
|
+
(setup: `triss coder init`). `triss_coder_run` takes an optional `engine`
|
|
241
|
+
(`opencode` default, or `crush`); its timeout defaults to
|
|
241
242
|
1500s (25 min) over MCP, above the CLI's 900s, since GLM runs are
|
|
242
243
|
expected to be long; override per call via the `timeout` arg. For
|
|
243
244
|
runs that may exceed it, use `triss coder run` on the CLI instead.
|
|
@@ -268,8 +269,8 @@ and writing**.
|
|
|
268
269
|
| `triss review` | Code review on current branch or a PR (diff + linked ticket) | The agent reading the whole diff |
|
|
269
270
|
| `triss commit-msg` | Generates a commit message from staged diff | Hand-writing or copy-pasting from web LLMs |
|
|
270
271
|
| `triss usage` | Cumulative cost / token usage with per-project breakdown | Squinting at stderr after each call |
|
|
271
|
-
| `triss coder init` | Sets up a
|
|
272
|
-
| `triss coder run` | Spawns the GLM coding agent and prints one JSON envelope (`--isolate` for a disposable worktree). **POSIX only** (macOS/Linux) — refuses to run on Windows. | Manually driving `opencode run` and parsing its ndjson stream |
|
|
272
|
+
| `triss coder init` | Sets up a coding agent (default `opencode` engine; `--engine crush` for crush): provider key (Z.AI GLM, or `--provider opencode-zen` for free OpenCode Zen models like `hy3`), `opencode.json` / crush models + `permissions.run` policy, 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 |
|
|
273
|
+
| `triss coder run` | Spawns the GLM coding agent 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 |
|
|
273
274
|
| `triss coder clean` | Removes finished `.triss/wt` isolation worktrees (`--all` forces all) | Manually finding and deleting stale git worktrees |
|
|
274
275
|
| `triss init` | Drops a tiny (~15 line) delegation block into `CLAUDE.md` / `AGENTS.md` | Hand-writing routing rules |
|
|
275
276
|
| `triss agent-help` | Prints the full delegation cookbook on demand (the nano block points here) | A 200-line CLAUDE.md that always loads |
|
|
@@ -430,27 +431,80 @@ default timeout, configurable via `--timeout <ms>`.
|
|
|
430
431
|
### `triss coder`
|
|
431
432
|
|
|
432
433
|
Delegates an implementation task to a GLM coding agent (the `opencode`
|
|
433
|
-
engine
|
|
434
|
+
engine by default; `crush` is an alternative — see **Engines** below)
|
|
435
|
+
instead of the primary model writing the code itself.
|
|
434
436
|
|
|
435
437
|
```bash
|
|
436
438
|
triss coder init # once: key, opencode.json, agent templates
|
|
437
439
|
triss coder run "add input validation to /signup" --isolate
|
|
440
|
+
triss coder run "..." --engine crush # crush engine (isolates by default; restrict is opt-in)
|
|
441
|
+
triss coder run "..." --engine crush --restrict # crush + CLI allowlist on top of the worktree
|
|
438
442
|
triss coder clean # remove finished isolation worktrees
|
|
439
443
|
```
|
|
440
444
|
|
|
441
|
-
`triss coder init`
|
|
442
|
-
(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
+
`triss coder init` first asks which provider to configure — **Z.AI GLM**
|
|
446
|
+
(default) or **OpenCode Zen** (`--provider opencode-zen`, free models incl.
|
|
447
|
+
Hunyuan `hy3`). For Z.AI it probes which plan your key works with (subscription
|
|
448
|
+
vs. pay-as-you-go) and writes the matching model prefix; for Zen it saves an
|
|
449
|
+
`OPENCODE_API_KEY` and writes an `opencode/<id>` model. Either way it lets you
|
|
450
|
+
pick the model interactively. See the **Providers** section below and
|
|
451
|
+
[docs/opencode-zen.md](docs/opencode-zen.md) for the OpenCode Zen deep‑dive.
|
|
445
452
|
|
|
446
453
|
Prints one JSON envelope to stdout — `files_changed`, `diff_stat`, and
|
|
447
454
|
`worktree` tell you what to review; `--isolate` runs the agent in a
|
|
448
455
|
disposable `.triss/wt/<slug>` git worktree so it never touches your
|
|
449
456
|
working tree directly. `--session <slug>` continues the same opencode
|
|
450
457
|
conversation across calls. **POSIX only** (macOS/Linux) for now. See
|
|
451
|
-
`docs/
|
|
458
|
+
`docs/glm-clients.md` for the full picture of how Triss talks to GLM
|
|
459
|
+
(both engines, key/endpoint routing, models, and every usage mode),
|
|
460
|
+
`docs/configuration.md` for the coder env vars, and `docs/mcp.md` for the
|
|
452
461
|
MCP tool equivalents.
|
|
453
462
|
|
|
463
|
+
**Engines** — `opencode` (default) enforces a deny-first bash allowlist
|
|
464
|
+
(`opencode.json`) that actually works — prefer it when you want that safety
|
|
465
|
+
layer baked in. `crush` (`--engine crush` / `TRISS_CODER_ENGINE=crush`, npm
|
|
466
|
+
`@phpcraftdream/crush` ≥0.1.3) has a **weaker, interim** safety story: live
|
|
467
|
+
testing proved crush 0.1.3 **ignores** its `permissions.run` config block and
|
|
468
|
+
a denied bash command **deadlocks to the timeout**. So triss ships crush with
|
|
469
|
+
isolate-**ON** by default (the disposable worktree is the reliable safety
|
|
470
|
+
layer) and makes restrict **opt-in** (default OFF). `triss coder init` still
|
|
471
|
+
seeds a `permissions.run` block into crush.json as forward-compat (harmless,
|
|
472
|
+
correct once upstream honors it), but the working allowlist today is the CLI
|
|
473
|
+
flags: when you pass `--restrict` (or `TRISS_CODER_CRUSH_RESTRICT=1`), `triss
|
|
474
|
+
coder run` emits `--restrict-run` plus `--allow-bash`/`--allow-tool` flags for
|
|
475
|
+
each entry. Override per-run with `--restrict` / `--no-restrict` (resolution:
|
|
476
|
+
CLI flag > env > crush.json `permissions.run.restrict` > default OFF). For
|
|
477
|
+
Z.AI GLM, both engines share the single `ZHIPU_API_KEY` (crush ≥0.1.1 reads it
|
|
478
|
+
natively; triss also forwards it as `ZAI_API_KEY` for older binaries; see
|
|
479
|
+
**Providers** below for the opencode-only OpenCode Zen alternative). See
|
|
480
|
+
`docs/crush-restrict-issues.md` for the live-verified bug facts.
|
|
481
|
+
|
|
482
|
+
**Providers** — the `opencode` engine isn't limited to Z.AI. The required
|
|
483
|
+
API key follows the model's `<provider>/` prefix: `zai-coding-plan/*` and
|
|
484
|
+
`zai/*` (GLM) use `ZHIPU_API_KEY`, while `opencode/*` models — served free by
|
|
485
|
+
[OpenCode Zen](https://opencode.ai/docs/zen/), e.g. **`opencode/hy3-free`**
|
|
486
|
+
(Tencent Hunyuan 3) — use `OPENCODE_API_KEY`. To point coder at a Zen model:
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
triss coder init --provider opencode-zen # guided: key + opencode.json
|
|
490
|
+
# …or by hand:
|
|
491
|
+
triss config set OPENCODE_API_KEY <key> # or add it to .triss.env
|
|
492
|
+
export TRISS_CODER_MODEL=opencode/hy3-free # default it for every run
|
|
493
|
+
export TRISS_CODER_SMALL_MODEL=opencode/hy3-free
|
|
494
|
+
triss coder run "add input validation to /signup"
|
|
495
|
+
# …or per-run, without changing the default:
|
|
496
|
+
triss coder run "..." --model opencode/hy3-free
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`triss coder run` passes the resolved model to opencode with `--model` and
|
|
500
|
+
forwards only the key that model needs, so a Zen run works with
|
|
501
|
+
`OPENCODE_API_KEY` alone — no Z.AI key required. The deny-first `opencode.json`
|
|
502
|
+
bash policy applies to Zen runs too. Any Zen model id works via
|
|
503
|
+
`TRISS_CODER_MODEL=opencode/<id>` / `--model`, not just the free ones the
|
|
504
|
+
picker lists. (The `crush` engine speaks Z.AI only.) Full details, the model
|
|
505
|
+
catalogue, and every configuration path are in
|
|
506
|
+
[docs/opencode-zen.md](docs/opencode-zen.md).
|
|
507
|
+
|
|
454
508
|
If your Z.AI plan hits its usage limit, `triss coder run` fails fast with
|
|
455
509
|
the reset time converted to your local timezone (Z.AI reports it in
|
|
456
510
|
Beijing time), instead of hanging until `--timeout`. Under the hood
|
package/bin/triss.js
CHANGED
|
@@ -223,23 +223,30 @@ config
|
|
|
223
223
|
|
|
224
224
|
const coder = program
|
|
225
225
|
.command('coder')
|
|
226
|
-
.description('Run a GLM coding agent (opencode engine)');
|
|
226
|
+
.description('Run a GLM coding agent (opencode or crush engine)');
|
|
227
227
|
|
|
228
228
|
coder
|
|
229
229
|
.command('init')
|
|
230
|
-
.description('Install/configure
|
|
230
|
+
.description('Install/configure a coding engine (opencode default, or crush), provider key, permission policy, and agent templates')
|
|
231
231
|
.option('-g, --global', 'save to the global scope (~/.config/triss/.env, ~/.config/opencode/)')
|
|
232
232
|
.option('-l, --local', 'save to the project scope (./.triss.env, ./opencode.json)')
|
|
233
|
+
.option('--engine <name>', 'coding engine to configure: opencode (default) or crush')
|
|
234
|
+
.option('--provider <name>', 'opencode engine model provider: zai (Z.AI GLM, default) or opencode-zen (free models incl. hy3)')
|
|
235
|
+
.option('--allow-unsafe-bash', 'proceed even if an existing opencode.json has no deny-first bash policy (the agent runs with --auto)')
|
|
233
236
|
.action(wrap(runCoderInit));
|
|
234
237
|
|
|
235
238
|
coder
|
|
236
239
|
.command('run [prompt]')
|
|
237
|
-
.description('Spawn a GLM coding agent (opencode engine) and print a JSON envelope to stdout')
|
|
240
|
+
.description('Spawn a GLM coding agent (opencode default, or --engine crush) and print a JSON envelope to stdout')
|
|
241
|
+
.option('--engine <name>', 'coding engine: opencode (default) or crush')
|
|
238
242
|
.option('--session <id>', 'triss-side session slug, mapped to a real opencode session id in .triss/sessions.json')
|
|
239
243
|
.option('--continue', 'continue the most recent opencode session (maps to opencode --continue)')
|
|
240
244
|
.option('--agent <name>', 'opencode agent template to use', 'coder')
|
|
241
245
|
.option('--model <p/m>', 'override the model for this run only')
|
|
242
246
|
.option('--isolate', 'run in a disposable git worktree under .triss/wt/<slug>')
|
|
247
|
+
.option('--no-isolate', 'disable worktree isolation (opencode defaults to OFF; crush defaults to ON)')
|
|
248
|
+
.option('--restrict', 'crush only: enforce the allowlist via CLI --allow-bash/--allow-tool flags (--restrict-run). Opt-in (default OFF)')
|
|
249
|
+
.option('--no-restrict', 'crush only: disable restrict (crush auto-approves every tool — the default)')
|
|
243
250
|
.option('--cwd <path>', 'working directory (ignored with --isolate)')
|
|
244
251
|
.option('--timeout <sec>', 'kill the engine after this many seconds', (v) => parseInt(v, 10), 900)
|
|
245
252
|
.option('--stdin', 'read the prompt from piped stdin instead of the [prompt] argument')
|
package/docs/coder-agent-plan.md
CHANGED
|
@@ -616,3 +616,314 @@ reuse, 2 safety-layer denial checks, 2 `--dir`/global-config checks, plus
|
|
|
616
616
|
retries) against the `zai-coding-plan` endpoint, all subscription-covered.
|
|
617
617
|
One `zai` (non-coding-plan) call was attempted and failed before any
|
|
618
618
|
tokens were billed (`isRetryable` error prior to a completion).
|
|
619
|
+
|
|
620
|
+
## Phase 6 recon (crush fork — 2026-07-05)
|
|
621
|
+
|
|
622
|
+
The prereq is unblocked: the fork publishes **`@phpcraftdream/crush`** on
|
|
623
|
+
npm (`0.1.0`, FSL-1.1-MIT, bin `crush`, node ≥18, Go-free prebuilt binary
|
|
624
|
+
via per-platform optionalDependencies). Live recon of that binary
|
|
625
|
+
(`v0.0.0-20260704…+dirty`) in a scratch dir against the existing
|
|
626
|
+
`ZHIPU_API_KEY` — the fork's `crush run` **diverges substantially from
|
|
627
|
+
this plan's Phase 6 assumptions**. Corrections, all verified live:
|
|
628
|
+
|
|
629
|
+
- **Provider / key var mismatch.** crush ships Z.AI as a built-in Catwalk
|
|
630
|
+
provider `zai` (`type: openai-compat`, endpoint
|
|
631
|
+
`https://api.z.ai/api/coding/paas/v4` — same coding-plan endpoint as
|
|
632
|
+
opencode), models `glm-5.2 / glm-5.1 / glm-5-turbo / glm-4.7 / …`. But
|
|
633
|
+
it reads **`ZAI_API_KEY`**, NOT `ZHIPU_API_KEY`. Adapter must bridge
|
|
634
|
+
this: simplest is to map the value into `ZAI_API_KEY` inside crush's
|
|
635
|
+
spawn-env allowlist so triss keeps a single user-facing `ZHIPU_API_KEY`.
|
|
636
|
+
Confirmed working: `crush ping` → `zai / glm-5.2 … status: ok`; a
|
|
637
|
+
`crush run --role smart --json` returned a clean `end_turn` envelope.
|
|
638
|
+
- **Single JSON envelope (no ndjson fold).** `crush run --json` prints ONE
|
|
639
|
+
object to stdout at end-of-run:
|
|
640
|
+
`{session_id, exit_reason, final_text, assistant_notes?, tool_calls,
|
|
641
|
+
usage:{delta_tokens, delta_cost_usd}, duration_ms, error}`. `exit_reason`
|
|
642
|
+
vocabulary: `end_turn | done | canceled | timeout | max_cost |
|
|
643
|
+
max_tokens | error`. Tool-call heartbeat still goes to stderr as
|
|
644
|
+
`▶ <toolName>`. So the crush adapter's `foldOutput` is a trivial
|
|
645
|
+
parse-last-line, not a streaming fold. **cost is real here**
|
|
646
|
+
(`delta_cost_usd: 0.000048` observed) — unlike opencode's coding-plan
|
|
647
|
+
cost:0. Usage is a combined `delta_tokens`, NOT split prompt/completion.
|
|
648
|
+
- **Native sessions — no slug→id map needed.** `crush run --session <id>`
|
|
649
|
+
is genuine get-or-create with a **caller-supplied arbitrary id** (docs:
|
|
650
|
+
"an arbitrary new id to start a fresh session with that exact id — handy
|
|
651
|
+
for CI"). So for crush, pass triss's own slug straight through; the
|
|
652
|
+
`.triss/sessions.json` mapping (an opencode-only workaround) is
|
|
653
|
+
unnecessary. Adapter flag e.g. `needsSessionMap: false`.
|
|
654
|
+
- **`--role` is REQUIRED.** Every `crush run` must declare `--role
|
|
655
|
+
smart|large` (big model) or `--role fast|small` (cheap). `--model
|
|
656
|
+
<provider/model[@level]>` overrides per invocation. Default large model
|
|
657
|
+
resolved from `crush models use <large> <small>` (atoms `glm5_2`,
|
|
658
|
+
`glm5_turbo`) written to `crush.json` (global
|
|
659
|
+
`~/.local/share/crush/crush.json`, local `./.crush/crush.json`).
|
|
660
|
+
- **SAFETY MODEL IS WEAKER — the headline caveat.** This confirms Phase 6
|
|
661
|
+
note 3, but stronger: `crush run` is non-interactive and
|
|
662
|
+
**auto-approves EVERY permission request; the agent gets the full tool
|
|
663
|
+
set with no prompting** and there is **no bash command-pattern
|
|
664
|
+
allowlist** like opencode's deny-first `opencode.json`. crush's own
|
|
665
|
+
`run --help` warns runs are "fast but irreversible — only run in a
|
|
666
|
+
workspace you can afford to lose." Mitigations available, all must be
|
|
667
|
+
applied by triss for crush: (a) force/strongly-default `--isolate`
|
|
668
|
+
(disposable git worktree) for crush; (b) `CRUSH_FORBID_WRITES=<paths>`
|
|
669
|
+
env blocks write/edit tools from named paths; (c) `--agents single`
|
|
670
|
+
(default) disables sub-agent fan-out/recursion; (d) `--max-cost` /
|
|
671
|
+
`--max-tokens` runaway caps. There is NO way to reproduce opencode's
|
|
672
|
+
per-command bash allowlist — document this trade-off prominently
|
|
673
|
+
(agent can't be given a curated safe-command list; it's all-or-nothing
|
|
674
|
+
inside the sandbox).
|
|
675
|
+
- **stdin IS accepted** (unlike opencode): prompt = `<stdin>\n\n<args>`.
|
|
676
|
+
- **Extra levers worth surfacing in the adapter/docs:** `--timeout`
|
|
677
|
+
(accepts `900`/`900s`/`15m`; preserves partial answer in envelope),
|
|
678
|
+
`--on-finish "cmd"` (sets `CRUSH_SESSION_ID`/`CRUSH_EXIT_REASON`/…),
|
|
679
|
+
`crush sessions cancel <id>` for external cancellation, `--format json`
|
|
680
|
+
post-processing that strips code fences from `final_text`.
|
|
681
|
+
|
|
682
|
+
Net: the crush adapter is in most respects SIMPLER than opencode
|
|
683
|
+
(single-envelope parse, native sessions, no template-shape guessing) but
|
|
684
|
+
its **safety story is the one place it is worse** and must not be papered
|
|
685
|
+
over. The engine-abstraction shape in Phase 6 step 1 still holds; the
|
|
686
|
+
`configTemplate` member becomes a `crush.json` + `crush models use`
|
|
687
|
+
writer, and a new adapter member is needed for the spawn-env key bridge
|
|
688
|
+
(`ZHIPU_API_KEY` → `ZAI_API_KEY`) and the crush-only safety env
|
|
689
|
+
(`CRUSH_FORBID_WRITES`).
|
|
690
|
+
|
|
691
|
+
## Phase 6 re-eval (crush 0.1.3 — 2026-07-06)
|
|
692
|
+
|
|
693
|
+
The fork maintainer shipped three releases (0.1.1 → 0.1.3, all 2026-07-05)
|
|
694
|
+
in direct response to `docs/crush-issues.md`. Re-verified every one of the
|
|
695
|
+
seven reported issues live on `0.1.3` (real `ZHIPU_API_KEY`, live Z.AI
|
|
696
|
+
coding-plan endpoint). Scorecard:
|
|
697
|
+
|
|
698
|
+
| # | Sev | Issue | Status | Evidence (0.1.3) |
|
|
699
|
+
|---|-----|-------|--------|------------------|
|
|
700
|
+
| 1 | High | `--version` = `v0.0.0…+dirty` | ✅ fixed | reports clean `v0.1.3` |
|
|
701
|
+
| 2 | High | `zai` reads only `ZAI_API_KEY` | ✅ fixed | with only `ZHIPU_API_KEY` set (`ZAI_API_KEY` unset), `zai` hit the network and returned 401 → key read natively |
|
|
702
|
+
| 3 | Med | `--role fast` (glm-5-turbo) hung to timeout | ✅ fixed | original repro (`crush run --role fast --model zai/glm-5-turbo`) → `exit_reason:end_turn`, `final_text:"PONG"` in 47s |
|
|
703
|
+
| 4 | Med | no allowlist, everything auto-approved | ✅ fixed | `--restrict-run` + `--allow-bash` (forms: prefix / `exact:` / `glob:` / `regex:`, chaining-guarded) + `--allow-tool` + config `permissions.run.{restrict,allow_bash,allow_tools}`; CLI merges with config |
|
|
704
|
+
| 5 | Med | `models list` does network + disk writes | ✅ fixed | default reads cache/embedded, no network, no writes; `--refresh` is opt-in |
|
|
705
|
+
| 6 | Low | `ping` rejects `--role` | ✅ fixed | `ping --role smart\|fast` + dedicated `ping-fast` |
|
|
706
|
+
| 7 | Low | startup WARN noise on stderr | 🟡 mostly | old git-repo / Apple-Terminal warnings gone; stderr is clean once models are configured; one WARN remains only on misconfig (large-as-small fallback for `local-cli`) |
|
|
707
|
+
|
|
708
|
+
**6/7 fully fixed, 1 (low) mostly. Every High and Medium is resolved.**
|
|
709
|
+
Issue #4 was closed exactly as suggested (command-pattern allowlist honored
|
|
710
|
+
by `crush run`), and better — CLI flags merge with `crush.json`.
|
|
711
|
+
|
|
712
|
+
Bonus levers added (not requested), worth surfacing later: `--effort
|
|
713
|
+
low|medium|high`, `--on-finish` hook (`CRUSH_SESSION_ID/EXIT_REASON/COST_USD/
|
|
714
|
+
TOKENS/DURATION_SEC`), watchdog `--timeout-extends-on-progress` +
|
|
715
|
+
`--timeout-hard-cap`, `--small-model`, `--format json-schema:<file>`,
|
|
716
|
+
`--aggregation attach|concat` for sub-agent fan-out + reduction-loss
|
|
717
|
+
warning, per-session budget persistence, default `--timeout` now 60m.
|
|
718
|
+
|
|
719
|
+
The `exit_reason` vocabulary is unchanged (`done | canceled | timeout |
|
|
720
|
+
max_cost | max_tokens | error`, plus `end_turn`) → `mapCrushExitReason`
|
|
721
|
+
already covers all of them; **no mapper change needed.**
|
|
722
|
+
|
|
723
|
+
### What to do now (adapter work — implement via GLM, Claude reviews)
|
|
724
|
+
|
|
725
|
+
Assignee: GLM (`triss coder run --engine crush`). Reviewer: Claude.
|
|
726
|
+
All changes are in `src/coder-engines/crush.js` + tests + docs. Ship on
|
|
727
|
+
`feat/coder-crush-engine`.
|
|
728
|
+
|
|
729
|
+
1. **Bump the pin + enforce the version.**
|
|
730
|
+
- `CRUSH_PIN_DEFAULT`: `0.1.0` → `0.1.3`.
|
|
731
|
+
- `detectCrush()` is presence-only today with a TODO "enforce pin once
|
|
732
|
+
crush --version reports clean semver." That precondition now holds
|
|
733
|
+
(`v0.1.3`). Parse the `vX.Y.Z` out of `crush --version` and return a
|
|
734
|
+
structured `{found, version, satisfiesPin}`; the caller warns (dim
|
|
735
|
+
stderr, non-fatal) on mismatch. Keep it non-fatal — a newer crush must
|
|
736
|
+
still run. Drop the two `TODO: enforce pin` comments.
|
|
737
|
+
- Test: feed a fake `sh` returning `crush version v0.1.3` and assert
|
|
738
|
+
parsed `version === '0.1.3'` / `satisfiesPin === true`; also a
|
|
739
|
+
`v0.2.0` (newer, still `found:true`) and a garbage string.
|
|
740
|
+
|
|
741
|
+
2. **Retire / re-annotate the ZHIPU→ZAI env bridge.**
|
|
742
|
+
- crush now reads `ZHIPU_API_KEY` natively (issue #2), so the
|
|
743
|
+
`ZHIPU_API_KEY → ZAI_API_KEY` copy in `buildCrushSpawnEnv` is no longer
|
|
744
|
+
required. Decision: **keep it as belt-and-suspenders** (harmless, helps
|
|
745
|
+
anyone still on 0.1.0) BUT rewrite the "silently unconfigured zai
|
|
746
|
+
provider" comment — it is now false for 0.1.1+. New comment: crush
|
|
747
|
+
≥0.1.1 reads `ZHIPU_API_KEY` directly; we still forward `ZAI_API_KEY`
|
|
748
|
+
as a compatibility alias for older binaries. Also forward
|
|
749
|
+
`ZHIPU_API_KEY` itself into the env allowlist so the native path works.
|
|
750
|
+
- Test: assert the spawn env contains BOTH `ZHIPU_API_KEY` and
|
|
751
|
+
`ZAI_API_KEY` when the base env has `ZHIPU_API_KEY`.
|
|
752
|
+
|
|
753
|
+
3. **Adopt real restricted-run safety — DECISION: Variant A (parity with
|
|
754
|
+
opencode), restrict ON by default.** This closes the one gap we flagged.
|
|
755
|
+
The design mirrors opencode's deny-first `opencode.json`: persist the
|
|
756
|
+
policy in `crush.json`, do NOT inject the allowlist as per-run CLI flags.
|
|
757
|
+
Rationale: crush merges `permissions.run.allow_bash` from config with CLI
|
|
758
|
+
flags and honors `permissions.run.restrict:true` — so a config-seeded
|
|
759
|
+
policy travels into isolated worktrees and lets users add/remove/replace
|
|
760
|
+
commands by editing ONE file, exactly like `opencode.json`. Injecting via
|
|
761
|
+
CLI flags would let users only ADD, not remove our defaults.
|
|
762
|
+
- **Seed `permissions.run` into `crush.json` at init**
|
|
763
|
+
(`configureCrushModels` / the crush init path). `crush models use`
|
|
764
|
+
already writes `.crush/crush.json` (local) / `~/.local/share/crush/
|
|
765
|
+
crush.json` (global) with a `models` block and NO permissions — there
|
|
766
|
+
is no `crush config` CLI, so read-modify-write that JSON, merging the
|
|
767
|
+
`permissions.run` block into the existing object (never clobber the
|
|
768
|
+
`models` block). Write:
|
|
769
|
+
```json
|
|
770
|
+
"permissions": { "run": {
|
|
771
|
+
"restrict": true,
|
|
772
|
+
"allow_bash": [ …shared read-only set… ],
|
|
773
|
+
"allow_tools": ["view"]
|
|
774
|
+
}}
|
|
775
|
+
```
|
|
776
|
+
- **Shared allowlist constant.** Mirror the opencode adapter's trusted
|
|
777
|
+
set verbatim so both engines behave identically. opencode's is
|
|
778
|
+
`opencode.json` `permission.bash` (see coder.js ~L453): `git status`,
|
|
779
|
+
`git diff*`, `git log*`, `ls*`, `node --test*`, `npm test*`,
|
|
780
|
+
`npm run test*`, with `webfetch/websearch: deny`. Translate to crush
|
|
781
|
+
forms (`'git diff'` prefix, `'glob:ls *'`, `'glob:npm test *'`,
|
|
782
|
+
`'glob:node --test *'`, etc.) in ONE named constant so a bump is one
|
|
783
|
+
edit. crush's chaining-guard on prefix patterns is stricter than
|
|
784
|
+
opencode's glob — fine, keep it.
|
|
785
|
+
- **No-clobber, like opencode.** If the user already has a
|
|
786
|
+
`permissions.run` block in crush.json, do NOT overwrite it; if an
|
|
787
|
+
existing crush.json has no restrict policy, warn on stderr (dim), same
|
|
788
|
+
as the opencode "existing config lacks deny policy" warning path
|
|
789
|
+
(coder.js ~L503).
|
|
790
|
+
- **Flip crush's isolate default to OFF** to complete the parity. Today
|
|
791
|
+
`coder.js:1697` defaults crush isolate-ON because it had no allowlist;
|
|
792
|
+
under Variant A the config-seeded deny-first policy is the safety layer,
|
|
793
|
+
so crush should default isolate-OFF like opencode. Update that line AND
|
|
794
|
+
its comment. `--isolate` / `--no-isolate` tristate still wins.
|
|
795
|
+
|
|
796
|
+
**User override surface (precedence high → low) — document all four:**
|
|
797
|
+
1. Per-run CLI: add `--no-restrict` / `--restrict` tristate on `coder
|
|
798
|
+
run` in `bin/triss.js` (mirror the existing `--isolate` tristate — no
|
|
799
|
+
Commander default, undefined = "use env/config default"). `--no-restrict`
|
|
800
|
+
drops to auto-approve for one run; `--isolate` brings back the
|
|
801
|
+
disposable worktree for one run.
|
|
802
|
+
2. Env: `TRISS_CODER_CRUSH_RESTRICT=0` disables restrict by default
|
|
803
|
+
without touching config or flags. (Add to README + .env.example.)
|
|
804
|
+
3. Persistent: edit `crush.json` `permissions.run` — set
|
|
805
|
+
`restrict:false`, or add/remove/replace `allow_bash`/`allow_tools`.
|
|
806
|
+
This is the recommended, opencode-equivalent override.
|
|
807
|
+
4. Fallback default when none of the above is set: restrict ON.
|
|
808
|
+
|
|
809
|
+
Resolution order in code: CLI flag (if defined) > `TRISS_CODER_CRUSH_
|
|
810
|
+
RESTRICT` env (if set) > `crush.json` `permissions.run.restrict` (if the
|
|
811
|
+
user hand-set it) > built-in default `true`. When restrict resolves ON
|
|
812
|
+
and we control the config, `buildCrushRunArgv` passes `--restrict-run`
|
|
813
|
+
(belt-and-suspenders even though config also has `restrict:true`); when
|
|
814
|
+
it resolves OFF, pass nothing (crush's auto-approve default).
|
|
815
|
+
- Tests: (a) init seeds `permissions.run.restrict:true` + the shared
|
|
816
|
+
allow_bash into a fresh crush.json WITHOUT dropping the `models` block;
|
|
817
|
+
(b) init does NOT clobber a user's existing `permissions.run`;
|
|
818
|
+
(c) `buildCrushRunArgv` with restrict ON contains `--restrict-run`,
|
|
819
|
+
with restrict OFF contains neither `--restrict-run` nor allow flags;
|
|
820
|
+
(d) resolution order: CLI `--no-restrict` beats env=1 beats config.
|
|
821
|
+
|
|
822
|
+
4. **Docs lockstep (per CLAUDE.md "user-visible change" rule).**
|
|
823
|
+
- `docs/crush-issues.md`: add a "Resolved in 0.1.3 (2026-07-06)" header
|
|
824
|
+
block at the top mapping each issue → fixed/mostly, so the report reads
|
|
825
|
+
as closed, not open. Do not delete the original findings (they are the
|
|
826
|
+
provenance).
|
|
827
|
+
- `README.md` + `.env.example`: document any new env var introduced in
|
|
828
|
+
step 3 (`TRISS_CODER_CRUSH_RESTRICT` / allowlist) and the
|
|
829
|
+
`TRISS_CODER_CRUSH_VERSION` pin now defaulting to `0.1.3`.
|
|
830
|
+
- `docs/mcp.md` only if a new gating env var is added.
|
|
831
|
+
|
|
832
|
+
5. **Do NOT (out of scope for this pass, note for later):** wiring
|
|
833
|
+
`--effort`, `--on-finish`, or the progress-watchdog flags. They are real
|
|
834
|
+
upside but belong in a follow-up; keep this PR to "close the 7 issues +
|
|
835
|
+
safety parity."
|
|
836
|
+
|
|
837
|
+
Review focus for Claude: (a) version parse handles `+dirty`/garbage/newer
|
|
838
|
+
without throwing; (b) the env bridge keeps BOTH keys and the comment no
|
|
839
|
+
longer claims the provider is "silently unconfigured"; (c) Variant A parity
|
|
840
|
+
is correct — init seeds `permissions.run` WITHOUT clobbering the `models`
|
|
841
|
+
block or an existing user policy, the shared allowlist actually matches the
|
|
842
|
+
opencode set, crush isolate default is flipped to OFF, and the override
|
|
843
|
+
resolution order (CLI > env > config > default-ON) is exactly as specified;
|
|
844
|
+
(d) `spawnSync` stays argv-array (never `shell:true`); (e) all three doc
|
|
845
|
+
files moved in lockstep, and the `--no-restrict` flag + `TRISS_CODER_CRUSH_
|
|
846
|
+
RESTRICT` env are both documented in README + .env.example.
|
|
847
|
+
|
|
848
|
+
## Phase 6 fix — restrict enforcement is CLI-only (2026-07-06, live-verified)
|
|
849
|
+
|
|
850
|
+
Post-merge review (Fable + live crush runs) found that the committed Variant A
|
|
851
|
+
(commit `2ad14dc`) **does not actually restrict crush**, and by flipping crush
|
|
852
|
+
to isolate-OFF it shipped a net safety *regression*. All facts below were
|
|
853
|
+
verified live against `@phpcraftdream/crush@0.1.3` with a real Z.AI key; see
|
|
854
|
+
`docs/crush-restrict-issues.md` for the maintainer bug report.
|
|
855
|
+
|
|
856
|
+
**What's broken (verified live):**
|
|
857
|
+
- **Config `permissions.run` is inert.** `crush run --restrict-run` with an
|
|
858
|
+
`allow_bash` policy seeded into `crush.json` (tried `./crush.json`,
|
|
859
|
+
`./.crush/crush.json`, and both) still ran a non-allowlisted `echo` to
|
|
860
|
+
completion. Our `seedCrushPermissions` writes a policy crush ignores.
|
|
861
|
+
- **`--restrict-run` with no CLI allow flags == unrestricted** (auto-approves
|
|
862
|
+
everything), not deny-all.
|
|
863
|
+
- **CLI `--allow-bash` / `--allow-tool` DO enforce.** Only the command-line
|
|
864
|
+
flags take effect. Verified tool taxonomy: file tools are `view`, `edit`,
|
|
865
|
+
`write`, `ls` (accepts `name` or `tool:action`, e.g. `edit:write`); a coder
|
|
866
|
+
successfully created a file via `write` under `--restrict-run` + those
|
|
867
|
+
allow-tools with no deadlock.
|
|
868
|
+
- **A denied *bash* command deadlocks to timeout** (`Context deadline
|
|
869
|
+
exceeded`, no envelope) instead of denying cleanly. File-tool denials were
|
|
870
|
+
not observed to deadlock; only bash.
|
|
871
|
+
|
|
872
|
+
**Consequence for the default.** A coding agent routinely runs bash outside a
|
|
873
|
+
read-only allowlist (`npm run build`, `tsc`, `npm run lint`, …). With the
|
|
874
|
+
deadlock bug, every such call dead-ends the whole run at the timeout. So
|
|
875
|
+
**Variant-A "restrict ON by default" is NOT viable for the coder use case
|
|
876
|
+
until the maintainer fixes the deadlock** — it would make crush runs
|
|
877
|
+
routinely dead-end. This supersedes the "restrict ON by default" decision
|
|
878
|
+
above: that decision assumed clean deny (as the crush `--help` text promises),
|
|
879
|
+
which live testing disproved.
|
|
880
|
+
|
|
881
|
+
**Revised interim stance (until the two crush bugs are fixed upstream):**
|
|
882
|
+
- **crush isolate default → back to ON** (revert the flip in `2ad14dc`). The
|
|
883
|
+
disposable worktree is the reliable, deadlock-free safety layer — the same
|
|
884
|
+
posture crush shipped with originally.
|
|
885
|
+
- **crush restrict default → OFF**, but **opt-in restrict actually works**:
|
|
886
|
+
when the user passes `--restrict` / `TRISS_CODER_CRUSH_RESTRICT=1`,
|
|
887
|
+
`buildCrushRunArgv` emits the allowlist as **CLI flags** (not config).
|
|
888
|
+
- Net interim posture: *worktree containment (default) + opt-in CLI allowlist
|
|
889
|
+
for defense-in-depth*. Once the maintainer fixes deadlock + config, revisit
|
|
890
|
+
flipping restrict back ON by default for true opencode parity.
|
|
891
|
+
|
|
892
|
+
### Tasks (implement, then live-verify — enforcement can't be unit-tested)
|
|
893
|
+
|
|
894
|
+
1. **`buildCrushRunArgv` — emit CLI allow flags when restrict is ON.**
|
|
895
|
+
For each pattern in `CRUSH_ALLOW_BASH_PATTERNS` push `--allow-bash <p>`,
|
|
896
|
+
and for each tool in a new `CRUSH_ALLOW_TOOLS = ['view','edit','write','ls']`
|
|
897
|
+
constant push `--allow-tool <t>`, alongside the existing `--restrict-run`.
|
|
898
|
+
When restrict is OFF, emit none of them (unchanged). Keep argv an array.
|
|
899
|
+
2. **Revert the isolate default for crush to ON.** In `src/commands/coder.js`
|
|
900
|
+
change `opts.isolate === undefined ? false` back to `? engine === 'crush'`
|
|
901
|
+
(opencode stays OFF). Update the comment to cite the deadlock/inert-config
|
|
902
|
+
reason, not the old "policy is the safety layer" reason.
|
|
903
|
+
3. **Change `CRUSH_RESTRICT_DEFAULT` to `false`.** `resolveCrushRestrict`
|
|
904
|
+
precedence is unchanged (CLI > env > crush.json > default); only the final
|
|
905
|
+
default flips. `--restrict` / `TRISS_CODER_CRUSH_RESTRICT=1` still turn it on.
|
|
906
|
+
4. **Keep `seedCrushPermissions` but label it forward-compat.** The config is
|
|
907
|
+
inert today; keep seeding it (harmless, and correct once the maintainer
|
|
908
|
+
honors it) BUT update the comment + any user-facing message to say the
|
|
909
|
+
allowlist is currently enforced via CLI flags, not this config block.
|
|
910
|
+
5. **Fable low fixes:**
|
|
911
|
+
- `seedCrushPermissions`: a valid-JSON-but-non-object crush.json (e.g. `[]`)
|
|
912
|
+
must route into the same warn-and-skip branch as a parse error — do NOT
|
|
913
|
+
silently overwrite it.
|
|
914
|
+
- `detectCrush`/pin: when the pin string itself doesn't parse to semver
|
|
915
|
+
(e.g. `TRISS_CODER_CRUSH_VERSION=latest`), skip the comparison (treat as
|
|
916
|
+
satisfied / emit a dim "pin unparseable" note) instead of a perpetual
|
|
917
|
+
`satisfiesPin:false` warning.
|
|
918
|
+
6. **Docs lockstep + reconcile the now-false parity claims:** `glm-clients.md`
|
|
919
|
+
§2 table (Safety/Isolation rows) + §8, `crush-issues.md` row #4, README
|
|
920
|
+
Engines section, and `.env.example` currently say crush has working
|
|
921
|
+
`permissions.run` parity and isolate-OFF. Correct all to the interim stance:
|
|
922
|
+
config inert / CLI-flag enforcement / isolate-ON / restrict opt-in.
|
|
923
|
+
|
|
924
|
+
**Live-verify after implementing (mandatory — this is the whole point):**
|
|
925
|
+
`triss coder run --engine crush --restrict "<edit a file + run an allowed
|
|
926
|
+
test>"` actually edits and runs; a run that tries a disallowed bash command
|
|
927
|
+
fails safe (times out) rather than executing it; default (no `--restrict`)
|
|
928
|
+
runs isolated. Passing unit tests are necessary but NOT sufficient — the
|
|
929
|
+
`2ad14dc` regression passed 431 tests.
|
package/docs/configuration.md
CHANGED
|
@@ -122,7 +122,7 @@ Removes a variable.
|
|
|
122
122
|
| `worker` | `TRISS_WORKER_API_KEY`, `TRISS_WORKER_BASE_URL`, `TRISS_WORKER_FLASH_MODEL`, `TRISS_WORKER_PRO_MODEL` | only `TRISS_WORKER_API_KEY` is required |
|
|
123
123
|
| `jira` | `ATLASSIAN_BASE_URL`, `ATLASSIAN_EMAIL`, `ATLASSIAN_API_TOKEN` | all three |
|
|
124
124
|
| `linear` | `LINEAR_API_KEY`, `LINEAR_API_URL` | only `LINEAR_API_KEY` is required |
|
|
125
|
-
| `coder` | `ZHIPU_API_KEY` (setup: `triss coder init` — engine, `opencode.json`, agent templates) | `ZHIPU_API_KEY`
|
|
125
|
+
| `coder` | `ZHIPU_API_KEY`, `OPENCODE_API_KEY` (setup: `triss coder init` — engine, `opencode.json`, agent templates) | `ZHIPU_API_KEY` (Z.AI GLM); `OPENCODE_API_KEY` optional (OpenCode Zen, e.g. `opencode/hy3-free`) |
|
|
126
126
|
|
|
127
127
|
When you add a new integration (see [extending.md](extending.md)), its
|
|
128
128
|
`envVars` declaration is automatically picked up — no wizard changes needed.
|
|
@@ -241,14 +241,18 @@ self-hosted endpoints).
|
|
|
241
241
|
| `TRISS_WORKER_PRO_MODEL` | no | `deepseek-v4-pro` | Override the `pro` preset |
|
|
242
242
|
| `TRISS_DEFAULT_MODEL` | no | `flash` | Which preset is used when no `--model`|
|
|
243
243
|
|
|
244
|
-
### Coder (GLM coding agent
|
|
244
|
+
### Coder (GLM coding agent)
|
|
245
245
|
|
|
246
246
|
| Variable | Required | Default | Notes |
|
|
247
247
|
| -------------------------------- | -------- | ------------------ | ------------------------------------------ |
|
|
248
|
-
| `ZHIPU_API_KEY` | yes
|
|
249
|
-
| `
|
|
248
|
+
| `ZHIPU_API_KEY` | yes¹ | — | Z.AI API key for GLM models — <https://z.ai/manage-apikey/apikey-list> |
|
|
249
|
+
| `OPENCODE_API_KEY` | no¹ | — | OpenCode Zen key (opencode engine only) — unlocks `opencode/*` models like `opencode/hy3-free` — <https://opencode.ai/docs/zen/> |
|
|
250
|
+
| `TRISS_CODER_MODEL` | no | `zai-coding-plan/glm-5.2` | Resolved model, passed to opencode via `--model` (and written to `opencode.json` by `init`). Use `opencode/hy3-free` for OpenCode Zen |
|
|
250
251
|
| `TRISS_CODER_SMALL_MODEL` | no | `zai-coding-plan/glm-5-turbo` | Small/fast model written to `opencode.json`|
|
|
251
252
|
| `TRISS_CODER_OPENCODE_VERSION` | no | `1.17.13` | Pin override for the `opencode-ai` npm install |
|
|
253
|
+
| `TRISS_CODER_ENGINE` | no | `opencode` | Coding engine: `opencode` (default) or `crush` |
|
|
254
|
+
| `TRISS_CODER_CRUSH_VERSION` | no | `0.1.3` | Pin override for the `@phpcraftdream/crush` npm install (crush engine) |
|
|
255
|
+
| `TRISS_CODER_CRUSH_RESTRICT` | no | unset (`0`) | crush only — `1` opts INTO the CLI allowlist (`--restrict-run` + `--allow-bash`/`--allow-tool`, the only enforcement path that works today — crush 0.1.3 ignores the `permissions.run` config). Unset leaves crush unrestricted; crush then defaults to isolate-ON. CLI `--restrict`/`--no-restrict` overrides; crush.json `permissions.run.restrict` is the next fallback (forward-compat — currently inert) |
|
|
252
256
|
|
|
253
257
|
`triss coder init` auto-detects which Z.AI endpoint `ZHIPU_API_KEY`
|
|
254
258
|
actually authenticates against — the `zai-coding-plan` (subscription)
|
|
@@ -261,6 +265,45 @@ file) when the existing `model` prefix doesn't match what the key
|
|
|
261
265
|
verified against — that mismatch is what makes opencode retry a model
|
|
262
266
|
call it can never complete.
|
|
263
267
|
|
|
268
|
+
¹ **Credentials are provider-specific.** A run needs the one key its
|
|
269
|
+
resolved model requires: `zai-coding-plan/*` and `zai/*` (GLM) need
|
|
270
|
+
`ZHIPU_API_KEY`; `opencode/*` OpenCode Zen models (e.g. the free
|
|
271
|
+
`opencode/hy3-free`) need `OPENCODE_API_KEY`. So `ZHIPU_API_KEY` is
|
|
272
|
+
"required" only in the sense that it is the default provider's key — a
|
|
273
|
+
zen-only setup runs on `OPENCODE_API_KEY` alone. `triss coder run` forwards
|
|
274
|
+
whichever key the model needs to the engine subprocess and gates on it
|
|
275
|
+
before spawning. The `crush` engine speaks Z.AI only and always needs
|
|
276
|
+
`ZHIPU_API_KEY`. `triss status` / `triss_coder_status` show both keys, and
|
|
277
|
+
the coder MCP tools surface once **either** is set. Run
|
|
278
|
+
`triss coder init --provider opencode-zen` to set up a Zen model interactively
|
|
279
|
+
(key + `opencode.json`); full details are in
|
|
280
|
+
[opencode-zen.md](opencode-zen.md).
|
|
281
|
+
|
|
282
|
+
**Engines.** `opencode` (default) enforces a deny-first per-command bash
|
|
283
|
+
allowlist via `opencode.json` (curated safe commands only) that actually
|
|
284
|
+
works. `crush` (`--engine crush` or `TRISS_CODER_ENGINE=crush`; npm
|
|
285
|
+
`@phpcraftdream/crush` ≥0.1.3, bin `crush`) has a **weaker, interim** safety
|
|
286
|
+
story: live testing proved crush 0.1.3 **ignores** its `permissions.run`
|
|
287
|
+
config block and a denied bash command **deadlocks to timeout**, so triss
|
|
288
|
+
ships crush isolate-**ON** by default (the disposable worktree is the reliable
|
|
289
|
+
safety layer) and makes restrict **opt-in** (default OFF). `triss coder init`
|
|
290
|
+
still seeds a `permissions.run` block into crush.json as forward-compat
|
|
291
|
+
(harmless, correct once upstream honors it), but the working allowlist today
|
|
292
|
+
is the CLI flags: `--restrict` (or `TRISS_CODER_CRUSH_RESTRICT=1`) makes
|
|
293
|
+
`triss coder run` emit `--restrict-run` plus `--allow-bash`/`--allow-tool` for
|
|
294
|
+
each entry. Override per-run with `--restrict` / `--no-restrict` (resolution:
|
|
295
|
+
CLI flag > env > crush.json `permissions.run.restrict` > default OFF). Prefer
|
|
296
|
+
opencode when you want the bash-policy safety layer baked into the project;
|
|
297
|
+
crush is simpler (one JSON envelope, native session ids) but pair it with its
|
|
298
|
+
default isolation, or opt into `--restrict` for a CLI allowlist on top — don't
|
|
299
|
+
combine `--no-restrict` with `--no-isolate` in a workspace you can't afford to
|
|
300
|
+
lose. For Z.AI GLM, both engines share the single `ZHIPU_API_KEY` — crush
|
|
301
|
+
≥0.1.1 reads it natively; triss also forwards it as `ZAI_API_KEY` for older
|
|
302
|
+
binaries. (opencode can alternatively run OpenCode Zen `opencode/*` models on
|
|
303
|
+
`OPENCODE_API_KEY` — see the coder env-var table above.)
|
|
304
|
+
See `docs/crush-restrict-issues.md` for the live-verified bug facts and
|
|
305
|
+
`docs/crush-issues.md` for the fuller list of crush caveats.
|
|
306
|
+
|
|
264
307
|
`triss coder run` is **POSIX only** (macOS/Linux) — its engine env
|
|
265
308
|
allowlist and `--timeout` kill both rely on POSIX process-group
|
|
266
309
|
semantics. It refuses to run on Windows with a clear error rather than
|