cowork-harness 0.21.0 → 0.22.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/.claude/skills/cowork-harness/SKILL.md +14 -10
- package/.claude/skills/cowork-harness/references/ci-recipe.md +61 -7
- package/.claude/skills/cowork-harness/references/fidelity-and-answers.md +1 -1
- package/.claude/skills/cowork-harness/references/scenario-schema.md +23 -11
- package/.claude/skills/cowork-harness/scripts/assertion-keys.json +7 -0
- package/.claude/skills/cowork-harness/scripts/scenario.py +9 -1
- package/CHANGELOG.md +180 -0
- package/DESIGN.md +3 -1
- package/README.md +55 -7
- package/SPEC.md +28 -4
- package/baselines/desktop-1.11847.5.json +2 -0
- package/baselines/desktop-1.12603.1.json +2 -0
- package/baselines/desktop-1.13576.1.json +3 -1
- package/baselines/desktop-1.14271.0.json +3 -1
- package/baselines/desktop-1.15200.0.json +3 -1
- package/baselines/desktop-1.15962.0.json +3 -1
- package/baselines/desktop-1.15962.1.json +3 -1
- package/baselines/desktop-1.17377.1.json +4 -1
- package/baselines/desktop-1.17377.2.json +5 -1
- package/baselines/desktop-1.18286.0.json +250 -0
- package/baselines/prompts/desktop-1.18286.0/system-prompt-append.md +214 -0
- package/dist/agent/session.js +7 -2
- package/dist/assert.js +88 -0
- package/dist/baseline.js +45 -3
- package/dist/cli.js +917 -38
- package/dist/prompt.js +31 -8
- package/dist/run/cassette.js +78 -4
- package/dist/run/chat.js +30 -3
- package/dist/run/computer-links.js +198 -0
- package/dist/run/diff.js +178 -0
- package/dist/run/display-translate.js +47 -0
- package/dist/run/doctor.js +17 -2
- package/dist/run/envelope.js +32 -9
- package/dist/run/execute.js +61 -4
- package/dist/run/matrix.js +131 -0
- package/dist/run/renderer.js +22 -7
- package/dist/run/repeat.js +89 -0
- package/dist/run/run-index.js +253 -0
- package/dist/run/run.js +15 -2
- package/dist/run/trace-view.js +59 -7
- package/dist/session.js +36 -0
- package/dist/sync/baseline-diff.js +138 -0
- package/dist/sync/cowork-sync.js +24 -2
- package/dist/types.js +49 -0
- package/dist/vm-paths.js +341 -0
- package/docs/README.md +3 -0
- package/docs/cassette.md +21 -11
- package/docs/debugging.md +19 -0
- package/docs/fidelity-gaps.md +15 -0
- package/docs/maintenance.md +25 -0
- package/docs/protocol.md +136 -0
- package/docs/scenario.md +95 -6
- package/docs/stats.md +74 -0
- package/examples/README.md +1 -0
- package/examples/replays/README.md +10 -1
- package/examples/replays/example-multiselect-gate.cassette.json +1 -1
- package/examples/replays/example-pdf-skill-ci-selftest-failing.yaml +30 -0
- package/examples/replays/example-pdf-skill.cassette.json +1 -1
- package/fixtures/protocol/v1/allow-response.json +13 -0
- package/fixtures/protocol/v1/deny-response.json +11 -0
- package/fixtures/protocol/v1/hook-callback.json +27 -0
- package/fixtures/protocol/v1/initialize.json +18 -0
- package/fixtures/protocol/v1/mcp-message.json +42 -0
- package/fixtures/protocol/v1/permission-request.json +13 -0
- package/fixtures/protocol/v1/question-answer-response.json +36 -0
- package/fixtures/protocol/v1/question-request.json +33 -0
- package/llms.txt +3 -0
- package/package.json +2 -2
- package/schema/protocol.v1.json +261 -0
- package/schema/run-result.json +24 -2
- package/schema/scenario.schema.json +36 -0
- package/schema/session.schema.json +3 -0
|
@@ -3,8 +3,8 @@ name: cowork-harness
|
|
|
3
3
|
description: Test or debug a Claude Code skill/plugin under Claude Cowork's runtime — sandboxed agent, default-deny egress, the can_use_tool permission/question protocol — using the cowork-harness CLI. Use when validating or regression-testing a skill, authoring or debugging a scenario YAML (prompt + scripted answers + assert:), choosing a fidelity tier, scripting AskUserQuestion / tool-permission answers, or asserting artifacts, egress, or sub-agent dispatch. Especially when a harness run no-ops an assertion, fails on an unanswered gate, false-greens, a steered answer never reaches the model, or a web_fetch is unexpectedly denied or gated. NOT for generic unit testing (pytest/vitest of your own scripts) or non-Cowork CI. Covers the skill / run / chat / record / replay / trace / decide / assertions / scaffold commands and the session-vs-scenario split.
|
|
4
4
|
metadata:
|
|
5
5
|
author: cowork-harness
|
|
6
|
-
version: 0.
|
|
7
|
-
tracks-harness: cowork-harness 0.
|
|
6
|
+
version: 0.22.0
|
|
7
|
+
tracks-harness: cowork-harness 0.22.0 (baseline desktop-1.18286.0)
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# cowork-harness
|
|
@@ -22,8 +22,8 @@ flagged with a loud `::warning::`, not silent — auto-answer a gate, observe an
|
|
|
22
22
|
allowlist). This skill exists mostly to keep you out of those traps — the Gotchas section below is
|
|
23
23
|
the highest-value part. Read it.
|
|
24
24
|
|
|
25
|
-
> **Version note:** the facts and `file:line` pointers here track `cowork-harness 0.
|
|
26
|
-
> `desktop-1.
|
|
25
|
+
> **Version note:** the facts and `file:line` pointers here track `cowork-harness 0.22.0` (baseline
|
|
26
|
+
> `desktop-1.18286.0`). If your checkout is newer, prefer the live `--help`, `SPEC.md`, and
|
|
27
27
|
> `docs/*.md` over this snapshot, and re-run the bundled linter.
|
|
28
28
|
|
|
29
29
|
## 0. Preflight — make sure the harness can actually run
|
|
@@ -38,7 +38,7 @@ cowork-harness skill ./my-skill "do X" # run the skill once against the sta
|
|
|
38
38
|
Before the first command, confirm the CLI is reachable and **fail loud** (never fake a pass) when a tier's dependencies are missing:
|
|
39
39
|
|
|
40
40
|
- **One-shot check.** Run `cowork-harness doctor [--tier <tier>]` first — a read-only prerequisite check that inspects Docker, the staged agent, the token, and the baseline in one pass. The bullets below explain each thing it checks (and how to fix it).
|
|
41
|
-
- **CLI on PATH, recent enough?** Run `cowork-harness --version` — this skill needs **≥ 0.
|
|
41
|
+
- **CLI on PATH, recent enough?** Run `cowork-harness --version` — this skill needs **≥ 0.22.0**. If it's missing or older, prefix every command with the version floor `npx cowork-harness@>=0.22.0 <cmd>` (Node ≥ 20), or install once with `npm i -g cowork-harness@>=0.22.0`. **Pin `@>=0.22.0`, never `@latest`** — `@latest` can silently fetch an older CLI and the new commands fail as "unknown command", whereas the floor **fails loud** if no compatible version is published. (≥ 0.22.0 is what gates the commands/assertions this skill teaches: `assertions --list`, `scaffold <run-id>`, `trace --view dispatches`, `artifact_json` incl. the `in:` operator, `verify-cassettes`, batch `record <dir>`/`--rerecord-stale`, `record --concurrency <N>`, record-time redaction, multiSelect/`answer:`, `verify-run` answer-coverage, `record --max-artifact-bytes`, live record-time deciders, `verify-cassettes --allow-domain`/`--allow-email`/`--allow-path`/`--allow-file` (`path` — local absolute filesystem paths — is the scanner's 4th class, new in 0.21.0), scenario `skills:` staleness scoping with `COWORK_HARNESS_AGENT_SCOPE=skill`, `chat --plugin`, `/help` in the REPL, `hostloop`'s native host/VM process split with its `allow_host_writes:` consent field, and `computer_links_resolve` (new in 0.22.0).)
|
|
42
42
|
- **Agent binary (sandboxed live tiers — `container`/`microvm`/`hostloop`/`cowork`).** The staged Claude Code agent is **bind-mounted** from a local Claude Desktop install, or point `COWORK_AGENT_BINARY` at a `claude-code-vm/<ver>/claude` ELF. Nothing is bundled. `protocol` (L0) and `replay` need no staged agent; for the sandboxed tiers, no agent → no run; report that, don't skip silently.
|
|
43
43
|
- **Docker / Lima.** Only `--fidelity protocol` (L0) runs without them. `container` / `microvm` / `hostloop` / `cowork` need Docker (Lima for L2). If they're absent, drop to `--fidelity protocol` and **say so** — a green that never exercised the sandbox is not a sandbox pass.
|
|
44
44
|
- **Auth.** `CLAUDE_CODE_OAUTH_TOKEN` (preferred) or `ANTHROPIC_API_KEY`, via env or `.env`. Minting an OAuth token needs the **`claude` CLI** (`npm i -g @anthropic-ai/claude-code`, then `claude setup-token`).
|
|
@@ -54,8 +54,8 @@ Before the first command, confirm the CLI is reachable and **fail loud** (never
|
|
|
54
54
|
debugging map).
|
|
55
55
|
|
|
56
56
|
Full command set: `skill · run · chat · record · replay · verify-cassettes · rehash · prune · lint ·
|
|
57
|
-
verify-run · trace · inspect · decide · gates · answer · scaffold · assertions --list · sync ·
|
|
58
|
-
boundary-check · vm <init|status|delete|prune> · doctor`. Always check `cowork-harness <cmd> --help`.
|
|
57
|
+
verify-run · trace · inspect · diff · stats · decide · gates · answer · scaffold · assertions --list · sync ·
|
|
58
|
+
list · boundary-check · vm <init|status|delete|prune> · doctor`. Always check `cowork-harness <cmd> --help`.
|
|
59
59
|
|
|
60
60
|
## 2. Two files: session vs scenario
|
|
61
61
|
|
|
@@ -288,7 +288,9 @@ keys). Exit code is non-zero on errors (CI-friendly). `scaffold` auto-upgrades t
|
|
|
288
288
|
for egress on `protocol`, so it never emits a scenario `lint` would reject.
|
|
289
289
|
|
|
290
290
|
CI placement: a **token-free `replay` PR gate** (content/structure only) + a **nightly live `run`**
|
|
291
|
-
(filesystem/egress).
|
|
291
|
+
(filesystem/egress). Fastest setup: `uses: yaniv-golan/cowork-harness@v1` (a packaged GitHub Action with a
|
|
292
|
+
PR job-summary reporter). See `references/ci-recipe.md` for the Action, the manual step-by-step form, and
|
|
293
|
+
the four-stage pipeline.
|
|
292
294
|
|
|
293
295
|
## 10. Debugging with `chat`
|
|
294
296
|
|
|
@@ -326,7 +328,8 @@ are the ones that bite hardest.
|
|
|
326
328
|
`replay` skips filesystem/egress keys (`file_exists`, `user_visible_artifact`, `artifact_json`,
|
|
327
329
|
`egress_*`, `no_delete_in_outputs`, `self_heal_ran`, `transcript_no_host_path`); a *mixed* item like
|
|
328
330
|
`{result, egress_denied}` greens on `result` while its `egress_denied` half is dropped. (`record`
|
|
329
|
-
snapshots an `artifacts` manifest, which makes
|
|
331
|
+
snapshots an `artifacts` manifest, which makes
|
|
332
|
+
`file_exists`/`user_visible_artifact`/`artifact_json`/`computer_links_resolve`
|
|
330
333
|
replay-checkable — but the live-only egress keys stay skipped.) *Fix:* put egress/live-only checks on
|
|
331
334
|
a live gate; keep one concern per `assert:` item; run the linter. The harness warns loudly on skip.
|
|
332
335
|
|
|
@@ -444,7 +447,8 @@ and the CI recipe, read the files in `references/`.
|
|
|
444
447
|
- `references/scenario-schema.md` — scenario/session YAML schema, full assertion catalog (with each
|
|
445
448
|
key's replay class), the web_fetch model, and the complete gotcha list.
|
|
446
449
|
- `references/fidelity-and-answers.md` — fidelity tiers, answer paths, the determinism contract.
|
|
447
|
-
- `references/ci-recipe.md` — replay-vs-live lane split and the four-stage
|
|
450
|
+
- `references/ci-recipe.md` — the packaged GitHub Action, replay-vs-live lane split, and the four-stage
|
|
451
|
+
GitHub Actions pipeline.
|
|
448
452
|
- `scripts/scenario.py` — `scaffold` a valid scenario skeleton and `lint` scenarios for the
|
|
449
453
|
no-silent-false-green invariants (both usable as CI steps).
|
|
450
454
|
- `../../../docs/run-status.md` — checking a background run's status without `ps aux`.
|
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
# CI recipe — replay vs live lanes
|
|
2
2
|
|
|
3
|
-
Self-contained reference. Tracks `cowork-harness 0.
|
|
3
|
+
Self-contained reference. Tracks `cowork-harness 0.22.0` (baseline `desktop-1.18286.0`).
|
|
4
4
|
|
|
5
|
-
**
|
|
6
|
-
|
|
5
|
+
**Fastest path: the packaged Action.** One step gets you `replay`/`lint`/`verify-cassettes` plus a PR
|
|
6
|
+
job-summary reporter (verdict table, staleness findings, cost/turns when available):
|
|
7
7
|
|
|
8
8
|
```yaml
|
|
9
|
-
-
|
|
9
|
+
- uses: yaniv-golan/cowork-harness@v1
|
|
10
|
+
with:
|
|
11
|
+
command: replay
|
|
12
|
+
path: cassettes/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Reach for the manual multi-step form below only when you need per-step control the Action's inputs don't
|
|
16
|
+
cover (a custom flag combination, a different runner matrix per step, or `lint`/`verify-cassettes` gated
|
|
17
|
+
independently instead of as one action run per command).
|
|
18
|
+
|
|
19
|
+
**Live lane (`command: run`) with the packaged Action — self-hosted runner, you stage the binary.** The
|
|
20
|
+
Action never downloads or provisions the agent ELF itself — by design, not an oversight (see "the
|
|
21
|
+
realistic CI shape" below for why). Stage it yourself as an explicit step in *your own* workflow, then
|
|
22
|
+
point the Action at it:
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
jobs:
|
|
26
|
+
live:
|
|
27
|
+
runs-on: [self-hosted, linux, arm64] # needs Docker + this staged ELF; not a stock GitHub-hosted runner
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- name: Stage the agent binary (official channel, sha256-verified — see docs/maintenance.md)
|
|
31
|
+
run: |
|
|
32
|
+
V=2.1.197 # match your scenario's pinned baseline's agentVersion
|
|
33
|
+
curl -fSL "https://downloads.claude.ai/claude-code-releases/$V/linux-arm64/claude" -o "$RUNNER_TEMP/claude-$V"
|
|
34
|
+
chmod +x "$RUNNER_TEMP/claude-$V"
|
|
35
|
+
# verify against the committed baseline's sha256 (baselines/desktop-*.json → agentBinary.sha256)
|
|
36
|
+
# before trusting it — see docs/maintenance.md's "Agent-binary provenance" section.
|
|
37
|
+
echo "COWORK_AGENT_BINARY=$RUNNER_TEMP/claude-$V" >> "$GITHUB_ENV"
|
|
38
|
+
- uses: yaniv-golan/cowork-harness@v1
|
|
39
|
+
with:
|
|
40
|
+
command: run
|
|
41
|
+
path: scenarios/
|
|
42
|
+
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Why this is a step *you* write, not an Action input the harness provides for you: pulling Anthropic's
|
|
46
|
+
binary is a call about your own relationship with their distribution terms — keeping it in your own
|
|
47
|
+
version-controlled workflow keeps that decision and its execution yours, auditable, and outside any
|
|
48
|
+
third-party action's code. See the [agent-binary provenance runbook](../../../../docs/maintenance.md) for
|
|
49
|
+
the full recovery/verification story (including why `COWORK_AGENT_BINARY` substitutions are
|
|
50
|
+
sha256-*checked* but not hard-blocking on mismatch — it's advisory for an intentional substitution).
|
|
51
|
+
|
|
52
|
+
**Minimal token-free PR gate (manual form)** — the smallest thing worth committing; runs on stock
|
|
53
|
+
GitHub-hosted runners, no token/Docker/agent:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
- run: npm i -g cowork-harness@>=0.22.0
|
|
10
57
|
- run: cowork-harness lint scenarios/*.yaml # no silent false-greens
|
|
11
58
|
- run: cowork-harness verify-cassettes cassettes/ # privacy + staleness
|
|
12
59
|
- run: cowork-harness replay cassettes/ # token-free content/structure
|
|
@@ -22,7 +69,8 @@ The split is not just about tokens — it decides **where each lane can run**:
|
|
|
22
69
|
- **`replay` / `verify-cassettes` (token-free, agent-free).** Replays a recorded cassette
|
|
23
70
|
(`events.jsonl` + `control-out.jsonl`) and lints the committed cassettes. **No model tokens, no
|
|
24
71
|
Docker, no agent binary** — runs on a stock GitHub Actions runner. Evaluates **content** assertions
|
|
25
|
-
only (`transcript_*`, `tool_*`, `subagent_*`, `dispatch_count_max`, `
|
|
72
|
+
only (`transcript_*`, `tool_*`, `subagent_*`, `dispatch_count_max`, `skill_triggered`,
|
|
73
|
+
`no_skill_triggered`, `max_cost_usd`, `max_tokens`, `tool_calls_max`, `result`, and the verdict
|
|
26
74
|
modifiers `allow_permissive_auto_allow` / `allow_missing_capability` / `allow_l0_plugin_divergence` /
|
|
27
75
|
`allow_stall` (no-op passes); plus the gate keys `question_asked` / `questions_count_max` /
|
|
28
76
|
`gate_answers_delivered` **if** the cassette has `controlOut`). Filesystem/egress assertions are
|
|
@@ -110,6 +158,12 @@ A typical skill repo runs four stages, fastest/cheapest first:
|
|
|
110
158
|
|
|
111
159
|
## GitHub Actions sketch
|
|
112
160
|
|
|
161
|
+
The PR gate below is the manual, step-by-step version of what `uses: yaniv-golan/cowork-harness@v1` does
|
|
162
|
+
in one step (see the top of this doc) — reach for this form when you need independent per-command
|
|
163
|
+
gating/annotations rather than one action run per command. The nightly live job has no packaged-Action
|
|
164
|
+
equivalent yet (the Action's `command: run` mode needs a self-hosted runner with Docker + the agent binary
|
|
165
|
+
already provisioned, same as the manual form below).
|
|
166
|
+
|
|
113
167
|
PR gate (token-free — runs on every push):
|
|
114
168
|
|
|
115
169
|
```yaml
|
|
@@ -124,7 +178,7 @@ jobs:
|
|
|
124
178
|
with: { node-version: '20' }
|
|
125
179
|
- uses: actions/setup-python@v5
|
|
126
180
|
with: { python-version: '3.x' } # python3 only — PyYAML is bundled with the linter
|
|
127
|
-
- run: npm i -g cowork-harness@>=0.
|
|
181
|
+
- run: npm i -g cowork-harness@>=0.22.0
|
|
128
182
|
- run: cowork-harness lint scenarios/*.yaml # no-silent-false-green (needs python3; PyYAML bundled)
|
|
129
183
|
- run: cowork-harness verify-cassettes cassettes/ --output-format json # privacy + staleness gate
|
|
130
184
|
- run: cowork-harness replay cassettes/ --output-format json # token-free content/structure
|
|
@@ -153,7 +207,7 @@ jobs:
|
|
|
153
207
|
echo "live=true" >> "$GITHUB_OUTPUT"
|
|
154
208
|
fi
|
|
155
209
|
- if: steps.guard.outputs.live == 'true'
|
|
156
|
-
run: npm i -g cowork-harness@>=0.
|
|
210
|
+
run: npm i -g cowork-harness@>=0.22.0
|
|
157
211
|
- if: steps.guard.outputs.live == 'true'
|
|
158
212
|
run: cowork-harness run scenarios/ --output-format json
|
|
159
213
|
env:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Fidelity tiers & answer paths
|
|
2
2
|
|
|
3
|
-
Self-contained reference. Tracks `cowork-harness 0.
|
|
3
|
+
Self-contained reference. Tracks `cowork-harness 0.22.0` (baseline `desktop-1.18286.0`).
|
|
4
4
|
|
|
5
5
|
## Fidelity tiers (`fidelity:` in the scenario)
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Scenario & session schema, assertion catalog, web_fetch, full gotchas
|
|
2
2
|
|
|
3
|
-
Self-contained reference for authoring `cowork-harness` scenarios. Tracks `cowork-harness 0.
|
|
4
|
-
(baseline `desktop-1.
|
|
3
|
+
Self-contained reference for authoring `cowork-harness` scenarios. Tracks `cowork-harness 0.22.0`
|
|
4
|
+
(baseline `desktop-1.18286.0`). If your checkout is newer, prefer the live `docs/scenario.md`,
|
|
5
5
|
`docs/session.md`, and `SPEC.md`.
|
|
6
6
|
|
|
7
7
|
**Minimal scenario** — `prompt` is the only required field:
|
|
@@ -220,6 +220,12 @@ passes only if every key passes. Keep one concern per item unless you mean conju
|
|
|
220
220
|
| `subagent_dispatched: <regex>` | a sub-agent whose `agentType` **or dispatch description** matches |
|
|
221
221
|
| `subagent_declared_but_unused: <Tool>` | a sub-agent declared the tool but never used **that** tool (even if it used others) |
|
|
222
222
|
| `dispatch_count_max: <N>` | at most N sub-agents dispatched (records only — does NOT enforce a cap) |
|
|
223
|
+
| `skill_triggered: <regex>` | a skill matching the regex (invoked id, e.g. `"plugin:skill"`) was invoked via the `Skill` tool — evidence-unavailable (not a normal fail) if the agent's init tools have no `Skill` tool |
|
|
224
|
+
| `no_skill_triggered: <regex>` | no invoked skill id matched — the negative-control / description-collision catcher; evidence-unavailable (never a vacuous pass) if invocation data is absent or the `Skill` tool is unobservable |
|
|
225
|
+
| `max_cost_usd: <N>` | the run's SDK-reported cost is ≤ N USD — evidence-unavailable if cost telemetry is absent. **Replay asserts the frozen recording's cost, not fresh spend** — a real regression needs a live `run` |
|
|
226
|
+
| `max_tokens: <N>` | `usage.input_tokens + usage.output_tokens` ≤ N (cache tokens excluded) — same replay caveat as `max_cost_usd` |
|
|
227
|
+
| `tool_calls_max: <N>` | total top-level tool calls (sum of `toolCounts`) ≤ N — meaningfully replay-checkable (re-drive recomputes `toolCounts` deterministically) |
|
|
228
|
+
| `max_turns: <N>` | the SDK-reported (or fallback-counted) turn count ≤ N — meaningfully replay-checkable (re-drive recounts turns deterministically, same as `tool_calls_max`) |
|
|
223
229
|
| `question_asked: <regex>` | the agent asked an AskUserQuestion whose text matches |
|
|
224
230
|
| `questions_count_max: <N>` | the agent asked at most N questions |
|
|
225
231
|
| `gate_answers_delivered: true` | every answered gate's answer reached the model (observed `tool_result`; unobserved = fail) |
|
|
@@ -232,6 +238,7 @@ passes only if every key passes. Keep one concern per item unless you mean conju
|
|
|
232
238
|
| `egress_denied: <host>` | the host was blocked by the egress proxy |
|
|
233
239
|
| `egress_allowed: <host>` | the host was allowed through |
|
|
234
240
|
| `artifact_json: {artifact, path, …}` | assert a JSON artifact's contents — `equals`/`gt`/`in`/`exists`/`absent`/`is_null` over a dotted `path` (`in` = membership in a list, for a stochastic/LLM value; `absent` ≠ `is_null`; an unresolved intermediate fails loud) |
|
|
241
|
+
| `computer_links_resolve: true` | every `computer://` link in the model-visible transcript resolves to an artifact that exists in the run's collected outputs/mounts — a dangling link fails, naming which target was checked (a live host path, the collected work tree, or the replay manifest). Zero links in the transcript **passes** (presence-gated separately — pair with `transcript_contains` if you also need a link to show up). **Only `true` is valid** (`false` is rejected by the schema) |
|
|
235
242
|
|
|
236
243
|
`expect_denied: [host, …]` adds one `egress_denied` per host. Run `cowork-harness assertions --list` for this
|
|
237
244
|
table from the live schema. Example: `artifact_json: { artifact: outputs/cap.json, path: me.run_id, equals: "r1" }`.
|
|
@@ -270,21 +277,26 @@ sourcing ≠ evaluation (replay warns when you edit one). `verify-run` is the on
|
|
|
270
277
|
*run dir*; `--assert-from` is the equivalent for a *cassette*.
|
|
271
278
|
|
|
272
279
|
**Evaluated on replay (content):** `transcript_*`, `tool_*`, `subagent_*`, `dispatch_count_max`,
|
|
273
|
-
`
|
|
274
|
-
`
|
|
280
|
+
`skill_triggered`, `no_skill_triggered`, `max_cost_usd`, `max_tokens`, `tool_calls_max`, `max_turns`, `result`
|
|
281
|
+
(`max_cost_usd`/`max_tokens` assert the frozen recording's spend on replay, not fresh spend). The verdict
|
|
282
|
+
modifiers `allow_permissive_auto_allow` / `allow_missing_capability` / `allow_l0_plugin_divergence` /
|
|
283
|
+
`allow_stall` are also kept on replay, evaluated as no-op passes.
|
|
275
284
|
|
|
276
285
|
**Gate keys — replay only with a `controlOut` cassette:** `question_asked`, `questions_count_max`,
|
|
277
286
|
`gate_answers_delivered`. With `controlOut` present they evaluate; on an old cassette without it, a
|
|
278
287
|
**loud warning** fires and they are **excluded** (not vacuously passed). Re-record to enable them.
|
|
279
288
|
|
|
280
289
|
**Filesystem — replay-checkable WITH an artifact manifest:** `file_exists`, `user_visible_artifact`,
|
|
281
|
-
`artifact_json` run on replay when the cassette carries an `artifacts` snapshot
|
|
282
|
-
`outputs/` + connected folders; `replay` materializes it). `artifact_json` needs the
|
|
283
|
-
inlined; a hash-only entry still satisfies `file_exists`.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
290
|
+
`artifact_json`, `computer_links_resolve` run on replay when the cassette carries an `artifacts` snapshot
|
|
291
|
+
(`record` captures `outputs/` + connected folders; `replay` materializes it). `artifact_json` needs the
|
|
292
|
+
small-file JSON `body` inlined; a hash-only entry still satisfies `file_exists`. `computer_links_resolve`
|
|
293
|
+
resolves a `/sessions/…/mnt/…`-shaped link directly against the manifest, and a host-shaped (hostloop) link
|
|
294
|
+
by first normalizing it to a mount-relative path (recorded connected-folder prefixes + the outputs/uploads
|
|
295
|
+
mounts) — replay has no live filesystem to check a host path against directly (that only happens on a live
|
|
296
|
+
`run`/`verify-run`). Without a manifest (older cassettes) all four are skipped. A green replay re-confirms
|
|
297
|
+
*record-time* artifacts, not that the current skill still produces them — `replay --strict` fails when the
|
|
298
|
+
staleness `fingerprint` shows ANY skill/baseline drift, or `replay --fail-on-skill-drift` only on
|
|
299
|
+
skill-source drift; every replay result also reports it class-tagged in `staleness[]` for a JSON gate.
|
|
288
300
|
|
|
289
301
|
**Egress + other filesystem — still skipped on replay (live-only):** `no_delete_in_outputs`,
|
|
290
302
|
`self_heal_ran`, `transcript_no_host_path`, `egress_*` / `expect_denied`. These run only on a live `run`/`record`.
|
|
@@ -6,22 +6,29 @@
|
|
|
6
6
|
"allow_permissive_auto_allow",
|
|
7
7
|
"allow_stall",
|
|
8
8
|
"artifact_json",
|
|
9
|
+
"computer_links_resolve",
|
|
9
10
|
"dispatch_count_max",
|
|
10
11
|
"egress_allowed",
|
|
11
12
|
"egress_denied",
|
|
12
13
|
"file_exists",
|
|
13
14
|
"gate_answers_delivered",
|
|
15
|
+
"max_cost_usd",
|
|
16
|
+
"max_tokens",
|
|
17
|
+
"max_turns",
|
|
14
18
|
"no_delete_in_outputs",
|
|
19
|
+
"no_skill_triggered",
|
|
15
20
|
"question_asked",
|
|
16
21
|
"questions_count_max",
|
|
17
22
|
"replay_protocol_fidelity",
|
|
18
23
|
"result",
|
|
19
24
|
"self_heal_ran",
|
|
25
|
+
"skill_triggered",
|
|
20
26
|
"subagent_declared_but_unused",
|
|
21
27
|
"subagent_dispatched",
|
|
22
28
|
"subagent_tool_absent",
|
|
23
29
|
"subagent_tool_used",
|
|
24
30
|
"tool_called",
|
|
31
|
+
"tool_calls_max",
|
|
25
32
|
"tool_not_called",
|
|
26
33
|
"tool_result_contains",
|
|
27
34
|
"tool_result_not_contains",
|
|
@@ -59,12 +59,20 @@ CONTENT_KEYS = {
|
|
|
59
59
|
"subagent_dispatched",
|
|
60
60
|
"subagent_declared_but_unused",
|
|
61
61
|
"dispatch_count_max",
|
|
62
|
+
"skill_triggered",
|
|
63
|
+
"no_skill_triggered",
|
|
64
|
+
"max_cost_usd",
|
|
65
|
+
"max_tokens",
|
|
66
|
+
"tool_calls_max",
|
|
67
|
+
"max_turns",
|
|
62
68
|
}
|
|
63
69
|
# content keys, but only evaluated on replay when the cassette carries controlOut
|
|
64
70
|
GATE_KEYS = {"question_asked", "questions_count_max", "gate_answers_delivered"}
|
|
65
71
|
# manifest-backed: replay-checkable when the cassette carries an `artifacts` manifest (record snapshots one);
|
|
66
72
|
# a manifest-less cassette skips them. Since the 0.3.0 artifact-manifest these are NOT always live-only.
|
|
67
|
-
|
|
73
|
+
# computer_links_resolve joins this bucket (not CONTENT_KEYS): resolving a non-empty link set needs
|
|
74
|
+
# either a live filesystem or the cassette's artifacts manifest — see cassette.ts's manifestKeys comment.
|
|
75
|
+
MANIFEST_KEYS = {"file_exists", "user_visible_artifact", "artifact_json", "computer_links_resolve"}
|
|
68
76
|
# live-only: ALWAYS skipped on replay, with a loud warning (no filesystem, no network on the token-free lane)
|
|
69
77
|
LIVE_ONLY_KEYS = {
|
|
70
78
|
"egress_denied",
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,186 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.22.0] — 2026-07-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`computer://` link modeling — the prompt now instructs file links exactly as production does.**
|
|
14
|
+
Four pieces landed together (design + binary research in
|
|
15
|
+
`docs/internal/2026-07-03-computer-link-scheme-research-and-plan.md`):
|
|
16
|
+
- `src/vm-paths.ts` — a faithful port of Desktop's display-side VM→host path transform
|
|
17
|
+
(`deepTranslateVMPaths` / `mapVMPathToHostPath` / `encodeComputerUrlsForHostLoop`): markdown-link,
|
|
18
|
+
backtick, bare-token, and prose rewrite positions; per-segment percent-encoding; traversal
|
|
19
|
+
rejection; dormant `.host-home` / `.auto-memory` mount branches.
|
|
20
|
+
- **Hostloop display translation** (`src/run/display-translate.ts`): at hostloop fidelity the
|
|
21
|
+
`run`/`chat` renderer shows production-identical host paths in assistant text and tool lines.
|
|
22
|
+
Hostloop-only by design (container staging paths would be less faithful than VM paths), identity on
|
|
23
|
+
replay (no live ctx), suppressed in `--compact`/`--demo` (the shareable no-host-paths contract).
|
|
24
|
+
Hostloop prompt tokens now render HOST paths (`{{cwd}}` / `{{workspaceFolder}}` / `{{skillsDir}}` +
|
|
25
|
+
the dedicated `{{cwd}}/mnt/uploads` pre-replacement), matching the Desktop builder's own host-loop
|
|
26
|
+
substitution recipe.
|
|
27
|
+
- **New assertion `computer_links_resolve: true`** — every `computer://` link in the transcript must
|
|
28
|
+
resolve to an artifact that exists (live/verify-run: filesystem; replay: the cassette's artifact
|
|
29
|
+
manifest, with host-shaped links normalized via the recorded session folders). Zero links pass;
|
|
30
|
+
dangling links report which target was checked. Assert links with this key, not literal link text.
|
|
31
|
+
- The `sharing_files` prompt section now instructs `[View your X](computer://{{workspaceFolder}}/x.ext)`
|
|
32
|
+
links faithfully — the prompt-reconstruction divergence for links is retired (docs/fidelity-gaps.md
|
|
33
|
+
updated).
|
|
34
|
+
- **Dark-feature gate sentinels.** Four newly discovered GrowthBook gates pinned in the baseline
|
|
35
|
+
(host-fs skeleton `2614807392`, standard-session auto-memory `123929380`, memory-guidelines env
|
|
36
|
+
`1696890383`, memory extra-guidelines `2860753854`) — all dark or inert-default for standard cowork
|
|
37
|
+
sessions today; pinned so a production flip surfaces as a `sync --diff` delta. Absent-from-fcache
|
|
38
|
+
dark gates record an explicit `source:"absent"` marker (the fcache re-key guard's semantics are
|
|
39
|
+
preserved).
|
|
40
|
+
|
|
41
|
+
- **`stats` command + cross-run result index.** Every `run`/`skill` invocation (and `record`'s live
|
|
42
|
+
execution) now appends one JSON line to `<runsRoot>/index.jsonl` at the same moment it writes
|
|
43
|
+
`result.json` — a durable, queryable history independent of whether the run dir itself survives a later
|
|
44
|
+
`prune`. `cowork-harness stats [<scenario>]` reads it back: run count, pass rate, cost/duration/token/turn
|
|
45
|
+
p50/p95, and the last-green timestamp, filterable by `--since`/`--baseline`/`--branch` and windowable by
|
|
46
|
+
`--last <n>` (per-scenario, not globally). `--reindex` rebuilds the index from the physical run-dir tree
|
|
47
|
+
— the migration path for runs that predate the index. `trace`/`inspect`/`scaffold`/`status`'s existing
|
|
48
|
+
run-id/fragment resolution now checks the index FIRST (faster, and the source of truth going forward),
|
|
49
|
+
falling through to the pre-index filesystem walk automatically and unchanged for any run that predates
|
|
50
|
+
the index or was never indexed — same commands, same output, same ambiguity-handling behavior either
|
|
51
|
+
way. See [docs/stats.md](./docs/stats.md).
|
|
52
|
+
- **`run --matrix` matrix runner.** Runs one scenario across the cross-product of baseline/model/skill_dir
|
|
53
|
+
axes declared in a `matrix.yaml` file (any axis optional; an absent axis contributes one unmodified
|
|
54
|
+
cell) and reports one row per cell instead of a single pass/fail. `--max-cells` caps the cross-product
|
|
55
|
+
(default 16, warns and truncates rather than silently dropping cells); `--concurrency` (default 1, max 8)
|
|
56
|
+
runs cells N at a time via the same bounded pool `record --concurrency` uses. Exit is non-zero if ANY
|
|
57
|
+
cell fails — a real assertion failure or a cell-level infrastructure error (e.g. the pinned baseline's
|
|
58
|
+
agent binary isn't staged), rendered as a distinct `cell error: …` line rather than a fake assertion
|
|
59
|
+
failure. The `skill_dirs` axis substitutes the session's single `plugins.local_plugins` entry; candidates
|
|
60
|
+
must share that entry's directory basename (the mount name derives from it, with no author-chosen
|
|
61
|
+
override anywhere in the harness) — a mismatch is a loud, explicit usage error. `--concurrency > 1`
|
|
62
|
+
cannot combine with `--decider-dir`/`--decider-cmd` (the external decider channel is one shared object
|
|
63
|
+
across every cell, and every channel implementation is strictly serial over shared mutable state — not
|
|
64
|
+
safe for concurrent gate answers; `--concurrency 1`, the default, is genuinely serial and fine). The
|
|
65
|
+
JSON envelope gains an additive `matrix: {cells[]}` field; `ok`/the exit code are `!matrix.anyFail` for
|
|
66
|
+
this mode.
|
|
67
|
+
- **`--matrix` composes with `--repeat`.** Each cell now runs as its own repeat batch (N iterations of that
|
|
68
|
+
cell's axes-overridden scenario) through the same `runRepeatBatch` helper standalone `--repeat` uses —
|
|
69
|
+
same unanswered-gate, error, and budget-cap handling — with `MatrixCellRepeatResult`/`MatrixRepeatRollup`
|
|
70
|
+
carrying each cell's full `RepeatRollup` (pass rate, per-assertion attribution, signal histogram,
|
|
71
|
+
stoppedEarly) rather than a single pass/fail. The matrix verdict judges each cell's rollup against
|
|
72
|
+
`--min-pass-rate`; the JSON envelope gains an additive `matrixRepeat: {cells[]}` field, checked before
|
|
73
|
+
`matrix`/`rollups` when present. Also closes the previously-ungated `--repeat` + `--decider-cmd`
|
|
74
|
+
combination (rejected for the same live-decider reasoning as `--decider-dir`).
|
|
75
|
+
- **Packaged GitHub Action** (`uses: yaniv-golan/cowork-harness@v1`, [`action.yml`](./action.yml)) wrapping
|
|
76
|
+
`replay`/`lint`/`verify-cassettes`/`run` with a PR job-summary reporter (verdict table, staleness
|
|
77
|
+
findings, the skipped-live-only-assertions honesty line, cost/turns when available). Token-free lane runs
|
|
78
|
+
on any `ubuntu-latest` runner; `run` (live lane) needs a self-hosted runner with Docker + the agent binary
|
|
79
|
+
already provisioned — the action does not stage either, by design (staging Anthropic's binary is a call
|
|
80
|
+
about your own distribution-terms relationship, so it stays a step in your own workflow, not something a
|
|
81
|
+
third-party action automates for you). README and the companion skill's `ci-recipe.md` both carry a
|
|
82
|
+
worked self-hosted-runner example for the live lane. Self-tested in CI (`uses: ./` against a packed
|
|
83
|
+
tarball of the current commit, a passing case, a usage-error case, and a genuine assertion-failure case).
|
|
84
|
+
A `publish-image.yml` workflow pushes `ghcr.io/yaniv-golan/cowork-agent-base:2`/`cowork-agent-full:2` on
|
|
85
|
+
release tags for consumers (and this repo's own CI) to `docker pull` instead of building from scratch.
|
|
86
|
+
- **`skill_triggered` / `no_skill_triggered` assertions.** Skill invocation (the top-level `Skill` tool_use)
|
|
87
|
+
is now a first-class assertable event, recorded as `RunResult.skillsInvoked[]` and evaluated as a regex
|
|
88
|
+
match, matching the `subagent_dispatched` convention. Fails as evidence-unavailable (never a vacuous pass)
|
|
89
|
+
when the agent's init tool list has no `Skill` tool (agent-version drift) or, for the negative form, when
|
|
90
|
+
invocation data itself is absent (an old run predating this key). Replay-checkable (content key).
|
|
91
|
+
- **`max_cost_usd` / `max_tokens` / `tool_calls_max` / `max_turns` budget assertions**, built on Wave 0's
|
|
92
|
+
cost/turns seam. Each fails as evidence-unavailable (never a vacuous pass) when the underlying telemetry
|
|
93
|
+
is absent. `max_cost_usd`/`max_tokens` are honest about the replay lane: they assert the *frozen
|
|
94
|
+
recording's* spend, not fresh spend — a live `run` is where a real budget regression is caught.
|
|
95
|
+
`tool_calls_max`/`max_turns` are meaningfully replay-checkable (the re-drive recomputes `toolCounts`/turn
|
|
96
|
+
count deterministically).
|
|
97
|
+
- **`diff <a> <b>` command.** Compares two committed platform baselines (`--changelog` renders known-field
|
|
98
|
+
prose — agent/Desktop version bumps, egress allowlist changes, gate flips — from a proper recursive
|
|
99
|
+
structural differ, replacing the old one-level diff that dumped a whole subtree on any nested change;
|
|
100
|
+
`sync --diff` now uses the same differ), two runs, two cassettes, or a run and a cassette (kind
|
|
101
|
+
auto-detected by CONTENT, not filename — a cassette-shaped file not literally named `*.cassette.json`
|
|
102
|
+
still detects correctly). Run/cassette mode has four views (`tools`/`transcript`/`artifacts`/`meta`, or
|
|
103
|
+
`all`) with normalization masking per-run noise (tool-use ids, UUIDs, session-dir markers, timestamps,
|
|
104
|
+
host paths) so two runs of the *same* scenario diff as identical despite that noise; `--no-normalize`
|
|
105
|
+
compares raw values for forensics. Comparing runs of two *different* scenarios is allowed (useful for
|
|
106
|
+
skill-variant comparison) but warns on stderr — added/removed rows may then reflect scenario
|
|
107
|
+
differences, not drift. Token-free — no live Desktop install or Docker needed either way.
|
|
108
|
+
- **`run --repeat N` variance rollup.** Runs each resolved scenario N times (2-100) and aggregates a
|
|
109
|
+
rollup (pass rate, per-assertion pass/fail attribution, a verdict-signal histogram, cost/token totals,
|
|
110
|
+
non-deterministic-run count) instead of a single pass/fail. `--min-pass-rate` sets the batch threshold
|
|
111
|
+
(default 1.0 — no flakiness tolerance); `--stop-on-diverge` stops the loop as soon as both a pass and a
|
|
112
|
+
fail are observed (that batch always fails — divergence IS the failure being measured for);
|
|
113
|
+
`--max-budget-usd` stops the loop once cumulative cost would exceed it (an incomplete-but-clean stop is a
|
|
114
|
+
warning, not a failure by itself). `--repeat` rejects `--decider-dir`/`--decider-cmd` (an interactive
|
|
115
|
+
driving agent × N runs is not a measurement). The JSON envelope gains an optional `rollups[]` array;
|
|
116
|
+
`ok`/the exit code are
|
|
117
|
+
redefined for this mode from the rollups, not from `results.every(pass)` — `results[]` still holds every
|
|
118
|
+
raw run.
|
|
119
|
+
- **E9: a hand-authored draft-07 JSON Schema for the harness's own control-channel wire protocol**
|
|
120
|
+
(`schema/protocol.v1.json`) — the `initialize` handshake, `can_use_tool` permission/question gates
|
|
121
|
+
(incl. AskUserQuestion's `questions[]`), `hook_callback`/`mcp_message` round-trips, and the nested
|
|
122
|
+
`control_response` envelope + the `answers` wire-shape — formalizing the prose in DESIGN.md §6/SPEC.md
|
|
123
|
+
§4-5. Deliberately does NOT schema the Claude Agent SDK's own event stream (Anthropic's surface).
|
|
124
|
+
Ships with a golden vector pack (`fixtures/protocol/v1/*.json` — real cassette-extracted where
|
|
125
|
+
possible, synthetic-via-the-real-`session.ts`-envelope-builders otherwise) and conformance tests
|
|
126
|
+
(`test/protocol-schema.test.ts`) that validate every committed cassette's control-channel lines plus
|
|
127
|
+
the real envelope-builder functions' actual output, and guard the schema/vector-pack lockstep. See
|
|
128
|
+
[docs/protocol.md](./docs/protocol.md) for the versioning policy and the explicit
|
|
129
|
+
descriptive-not-normative scope statement.
|
|
130
|
+
|
|
131
|
+
### Parity
|
|
132
|
+
|
|
133
|
+
- **Synced the platform baseline to Claude Desktop 1.18286.0** (`baselines/desktop-1.18286.0.json`).
|
|
134
|
+
The staged agent ELF is unchanged (`2.1.197`; measured sha256 matches the official release manifest).
|
|
135
|
+
`sync` re-derived egress/gates/mount/web_fetch facts — no unknown deltas; the egress allowlist (15
|
|
136
|
+
domains) and all 6 pinned GrowthBook gates held. `asarFingerprint` moved (`0b2f2fb6 → edff6926`);
|
|
137
|
+
the host-loop `## Shell access` generator and the subagent append were re-verified against the new
|
|
138
|
+
asar (unconditional fragments still byte-faithful).
|
|
139
|
+
- **Re-authored the system-prompt append reconstruction for 1.18286.0**
|
|
140
|
+
(`baselines/prompts/desktop-1.18286.0/system-prompt-append.md`). The real append was RESTRUCTURED
|
|
141
|
+
at this release (constant `aui`, 37.9KB): a new `<claude_behavior>` wrapper plus new
|
|
142
|
+
behavior-driving sections — AskUserQuestion-before-work, task-list/verification, citation,
|
|
143
|
+
file-creation/computer-use guidance, web-fetch no-fallback restrictions, sharing/package/examples,
|
|
144
|
+
an `<env>` block — and the skills/file-handling sections moved inside `<computer_use>`. The
|
|
145
|
+
reconstruction (paraphrased per the no-bundling rule) now carries these; generic refusal/safety
|
|
146
|
+
policy stays elided, and the deliberate divergences (artifacts renderer catalog trimmed,
|
|
147
|
+
`computer://` links described-not-instructed) are logged in the asset header. New `<env>` tokens
|
|
148
|
+
`{{currentDateTime}}` / `{{currentTimezone}}` / `{{accountName}}` (session `account_name`,
|
|
149
|
+
default `"User"`) render in `src/prompt.ts`. New tests guard baseline→asset references, token
|
|
150
|
+
hygiene, and the `/sessions/` link-leak trade.
|
|
151
|
+
- **Annotated `desktop-1.17377.2` as append-unverified** (`$comment_prompts_unverified`): its own
|
|
152
|
+
asar was never prompt-spot-checked and is no longer obtainable locally, so whether the 1.18286.0
|
|
153
|
+
restructure landed there or later is unverifiable; the pin carries the last-verified 1.15200.0
|
|
154
|
+
reconstruction.
|
|
155
|
+
|
|
156
|
+
### Added
|
|
157
|
+
|
|
158
|
+
- **Agent-binary provenance in baselines.** Each baseline's `agentBinary` now records the Linux/arm64 ELF's
|
|
159
|
+
`sha256` plus `shaProvenance` — `measured-local` (hashed from the staged binary at `sync` and cross-checked
|
|
160
|
+
against the official release manifest) or `official-manifest` (copied from the manifest for a version not
|
|
161
|
+
staged on the syncing machine; staging-identity unverified) — and, on measured rows, `manifestChecksumMatch`.
|
|
162
|
+
`sync` computes these and stays offline-capable (an unreachable manifest records `"unknown"` and never fails
|
|
163
|
+
the sync). All committed baselines back-filled. (No `nativeSha256`: the signed native Mach-O never equals a
|
|
164
|
+
manifest hash.)
|
|
165
|
+
- **Default-on agent-ELF integrity check.** The resolved ELF is verified against the recorded `sha256` at run
|
|
166
|
+
time **by default** (opt out with `COWORK_HARNESS_VERIFY_AGENT_SHA=0`; ELF only). Hard-fails only on a
|
|
167
|
+
`measured-local` mismatch at the baseline's own staged path; intentional substitutions (`COWORK_AGENT_BINARY`
|
|
168
|
+
/ newest-sibling fallback) and `official-manifest` hashes advisory-warn. `doctor` now shows a
|
|
169
|
+
`[sha256 ✓ vs baseline, …]` provenance line. Old agent versions are re-downloadable and verifiable — recovery
|
|
170
|
+
runbook in `docs/maintenance.md`.
|
|
171
|
+
|
|
172
|
+
### Changed
|
|
173
|
+
|
|
174
|
+
- **`RunResult.cost`/`.usage` retyped** from opaque `Record<string, unknown>` to structured shapes.
|
|
175
|
+
`cost` is now `{ usd?, raw? }`: `usd` is the SDK result message's `total_cost_usd`, newly extracted (was
|
|
176
|
+
previously dropped on the floor); `raw` is the pre-existing `api_metrics` payload, now nested under `raw`
|
|
177
|
+
instead of being the whole `cost` object. `usage` gains a `turns?: number` field, from the SDK's
|
|
178
|
+
`num_turns` (also newly extracted). Breaking shape change for anything reading `result.json`'s `cost`
|
|
179
|
+
field directly — see SPEC.md's `RunResult` reference for the new shape. No cassette-format bump (derived
|
|
180
|
+
reporting, not a stored format change).
|
|
181
|
+
|
|
182
|
+
### Fixed
|
|
183
|
+
|
|
184
|
+
- **Replay now surfaces `usage`/`cost` in `result.json`.** `replayCassette` previously omitted both fields
|
|
185
|
+
entirely from every replayed `RunResult`, regardless of what the cassette recorded — a replay-lane blind
|
|
186
|
+
spot, not a live-only limitation. Both are now re-derived from the cassette's re-driven record, same as
|
|
187
|
+
the live/partial-run lanes.
|
|
188
|
+
|
|
9
189
|
## [0.21.0] — 2026-07-03
|
|
10
190
|
|
|
11
191
|
### Added
|
package/DESIGN.md
CHANGED
|
@@ -38,7 +38,7 @@ flowchart TB
|
|
|
38
38
|
A Cowork session is the Desktop app driving an agent that runs **inside an Apple Virtualization.framework microVM**:
|
|
39
39
|
|
|
40
40
|
- VM bundle: `~/Library/Application Support/Claude/vm_bundles/claudevm.bundle/` (`rootfs.img`, `sessiondata.img`, `efivars.fd`, `machineIdentifier`, `gvisorMacAddress`, `vmIP`); a warm pool at `vm_bundles/warm/<sha>/`.
|
|
41
|
-
- In-VM agent: `~/Library/Application Support/Claude/claude-code-vm/<ver>/claude` (currently **2.1.197**, an **ELF aarch64** binary), spawned by the host **in cowork mode via the `CLAUDE_CODE_IS_COWORK=1` env var** — *not* a `--cowork` flag (that flag is plugin-scope and the staged agent rejects it; see the control-protocol note below).
|
|
41
|
+
- In-VM agent: `~/Library/Application Support/Claude/claude-code-vm/<ver>/claude` (currently **2.1.197**, an **ELF aarch64** binary), spawned by the host **in cowork mode via the `CLAUDE_CODE_IS_COWORK=1` env var** — *not* a `--cowork` flag (that flag is plugin-scope and the staged agent rejects it; see the control-protocol note below). Each baseline records this ELF's `sha256` (`agentBinary.sha256`/`shaProvenance`), and the resolver integrity-checks the binary it's about to run against that hash by default (opt out `COWORK_HARNESS_VERIFY_AGENT_SHA=0`), so "the same pinned agent" is enforced, not just asserted. Old versions are re-downloadable + verifiable from the official release channel (see `docs/maintenance.md`).
|
|
42
42
|
- Network: `vm_network_mode: "gvisor"`, egress through a userspace netstack with a **compiled domain allowlist**; off-list partners rejected (`partner rejected: entry not on compiled allowlist`).
|
|
43
43
|
- Control plane: Electron renderer→main typed IPC on channels named `$eipc_message$_<per-build-UUID>_$_claude.web_$_<Class>_$_<method>`, every handler validating `event.senderFrame.url` against a trusted-origin allowlist. The session manager is `LocalAgentModeSessions` (80 methods: `start`, `sendMessage`, `setDraftSessionFolders`, `onToolPermissionRequest`, `respondToToolPermission`, `getTranscript`, `onEvent`, …), bridged to the renderer as `window.cowork`.
|
|
44
44
|
|
|
@@ -163,6 +163,8 @@ L1 reproduces this as a **default-deny forward proxy**: the agent's `HTTP(S)_PRO
|
|
|
163
163
|
|
|
164
164
|
The policy that produces those `allow`/`deny` responses is the **Decider** seam (see the architecture diagram); to smoke-test a decider against a sample question without a full run, use `cowork-harness decide`.
|
|
165
165
|
|
|
166
|
+
> **Machine-readable form:** the five shapes below are schema'd as `schema/protocol.v1.json`, with a golden vector pack at `fixtures/protocol/v1/` — see [docs/protocol.md](./docs/protocol.md) for scope, versioning, and how to conformance-test against them.
|
|
167
|
+
|
|
166
168
|
### Control protocol — VERIFIED end-to-end against the live host CLI (macOS build 2.1.177+; the staged in-VM agent that L1/L2 run is 2.1.197, baseline `desktop-1.17377.1`)
|
|
167
169
|
|
|
168
170
|
> The staged agent ELF is unchanged (2.1.181) across the 1.14271.0→1.15200.0 asar bump, and 2.1.187 across the 1.15200.0→1.15962.0 bump. The live scenario suite (`protocol` + `container` tiers) was re-run against the 1.15200.0 baseline; the 1.15962.0 bump was verified via asar analysis (content byte-identical: host-loop generator, system prompt, identity string, gates, and egress domains all unchanged) plus a full local test suite pass. The 1.15962.1→1.17377.1 bump moved the staged agent to **2.1.197** and added `api.claude.ai` to the egress allowlist; re-verified via `sync` (no unknown deltas) plus a manual asar spot-check of the reconstructed prompt content (substantively unchanged — see the Parity entry in CHANGELOG.md) and a full live scenario-suite pass (`protocol` + `container` tiers).
|