@bastani/atomic 0.9.19-alpha.5 → 0.9.19-alpha.7
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 +32 -0
- package/README.md +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/broker/client.ts +19 -1
- package/dist/builtin/intercom/index.bundle.mjs +8 -1
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/README.md +3 -1
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +7 -7
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +3 -1
- package/dist/builtin/subagents/src/extension/index.bundle.mjs +2 -1
- package/dist/builtin/web-access/CHANGELOG.md +13 -0
- package/dist/builtin/web-access/README.md +31 -24
- package/dist/builtin/web-access/index.bundle.mjs +16121 -80
- package/dist/builtin/web-access/package.json +2 -1
- package/dist/builtin/workflows/CHANGELOG.md +15 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/builtin/adversarial-verification.js +2 -2
- package/dist/builtin/workflows/builtin/{chunk-vhmnzdj9.js → chunk-1jth021m.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-p8jjwfg6.js → chunk-2377r57g.js} +4 -2
- package/dist/builtin/workflows/builtin/{chunk-hbnwv56f.js → chunk-31j2gs7h.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-n8zysf1t.js → chunk-ngkqkzej.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-1nbq96fh.js → chunk-nkrafh9s.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-1kx4aggw.js → chunk-wf741bv3.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-t6x2d78s.js → chunk-z29qbehr.js} +1 -1
- package/dist/builtin/workflows/builtin/generate-and-filter.js +2 -2
- package/dist/builtin/workflows/builtin/goal.js +3 -3
- package/dist/builtin/workflows/builtin/index.js +7 -7
- package/dist/builtin/workflows/builtin/ralph.js +3 -3
- package/dist/builtin/workflows/builtin/tournament.js +2 -2
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +339 -31
- package/dist/builtin/workflows/src/index.js +67 -19
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -0
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +10 -0
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/tools/bash.d.ts +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -2
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +3 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +91 -12
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-coordinator.d.ts +255 -0
- package/dist/core/tools/file-mutation-coordinator.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-coordinator.js +300 -0
- package/dist/core/tools/file-mutation-coordinator.js.map +1 -0
- package/dist/core/tools/file-mutation-queue.d.ts +14 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +15 -3
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/hashline.d.ts +9 -0
- package/dist/core/tools/hashline.d.ts.map +1 -1
- package/dist/core/tools/hashline.js +12 -1
- package/dist/core/tools/hashline.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +67 -12
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/non-minting-snapshot-store.d.ts +49 -0
- package/dist/core/tools/non-minting-snapshot-store.d.ts.map +1 -0
- package/dist/core/tools/non-minting-snapshot-store.js +64 -0
- package/dist/core/tools/non-minting-snapshot-store.js.map +1 -0
- package/dist/core/tools/powershell.d.ts +2 -2
- package/dist/core/tools/powershell.d.ts.map +1 -1
- package/dist/core/tools/powershell.js +2 -5
- package/dist/core/tools/powershell.js.map +1 -1
- package/dist/core/tools/read-document-extract.js.map +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts +30 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +97 -8
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/task-live-transcript.d.ts +1 -1
- package/dist/modes/interactive/components/task-live-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-live-transcript.js +19 -11
- package/dist/modes/interactive/components/task-live-transcript.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +3 -1
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +28 -4
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +3 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive-engine/activity-watchdog.d.ts +2 -2
- package/dist/modes/interactive-engine/activity-watchdog.d.ts.map +1 -1
- package/dist/modes/interactive-engine/activity-watchdog.js.map +1 -1
- package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts +3 -1
- package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-diagnostic-view.js +11 -0
- package/dist/modes/interactive-engine/engine-diagnostic-view.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +2 -1
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/rpc/rpc-client-process.d.ts +2 -0
- package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-process.js +23 -1
- package/dist/modes/rpc/rpc-client-process.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +20 -3
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/utils/markit.d.ts +2 -0
- package/dist/utils/markit.d.ts.map +1 -1
- package/dist/utils/markit.js +65 -5
- package/dist/utils/markit.js.map +1 -1
- package/docs/background-tasks.md +15 -11
- package/docs/build.md +51 -0
- package/docs/changelog.mdx +8 -8
- package/docs/compaction/reference.md +267 -0
- package/docs/compaction.md +36 -219
- package/docs/computer-use.md +35 -0
- package/docs/containerization.md +9 -5
- package/docs/custom-provider/api-reference.md +145 -0
- package/docs/custom-provider/oauth.md +131 -0
- package/docs/custom-provider/override.md +34 -0
- package/docs/custom-provider/registration.md +163 -0
- package/docs/custom-provider/streaming.md +214 -0
- package/docs/custom-provider.md +43 -607
- package/docs/development.md +87 -0
- package/docs/docs.json +199 -76
- package/docs/environment-variables.md +1 -1
- package/docs/extensions/api-reference.md +1001 -0
- package/docs/extensions/authoring.md +676 -0
- package/docs/extensions/events.md +793 -0
- package/docs/extensions/examples.md +94 -0
- package/docs/extensions/ui.md +470 -0
- package/docs/extensions.md +270 -2768
- package/docs/getting-started/authentication.md +55 -0
- package/docs/getting-started/first-session.md +172 -0
- package/docs/getting-started/installation.md +154 -0
- package/docs/getting-started/project-instructions.md +43 -0
- package/docs/guides/configuration.md +65 -0
- package/docs/guides/intercom.md +24 -0
- package/docs/guides/non-interactive.md +33 -0
- package/docs/guides/subagents.md +24 -0
- package/docs/guides/workflows.md +27 -0
- package/docs/guides.md +41 -0
- package/docs/index.md +13 -45
- package/docs/intercom/operations.md +178 -0
- package/docs/intercom/reference.md +146 -0
- package/docs/intercom.md +54 -259
- package/docs/json.md +17 -0
- package/docs/llama-cpp.md +5 -0
- package/docs/models/artificial-analysis-index.md +89 -0
- package/docs/models/evals.md +2 -2
- package/docs/models/model-selection.md +3 -3
- package/docs/models/pareto-efficiency.md +2 -2
- package/docs/models/reference.md +601 -0
- package/docs/models.md +45 -550
- package/docs/packages/authoring.md +88 -0
- package/docs/packages/reference.md +43 -0
- package/docs/packages.md +28 -107
- package/docs/programmatic.md +29 -0
- package/docs/prompt-templates.md +11 -0
- package/docs/providers/reference.md +23 -0
- package/docs/providers.md +10 -15
- package/docs/quickstart.md +44 -238
- package/docs/reference/cli.md +252 -0
- package/docs/reference.md +52 -0
- package/docs/rpc/examples.md +64 -0
- package/docs/rpc/extension-ui.md +198 -0
- package/docs/rpc/protocol.md +1341 -0
- package/docs/rpc.md +109 -1346
- package/docs/sdk/reference.md +1090 -0
- package/docs/sdk.md +56 -1002
- package/docs/session-format.md +2 -0
- package/docs/sessions.md +6 -10
- package/docs/settings.md +6 -45
- package/docs/skills/authoring.md +95 -0
- package/docs/skills/reference.md +60 -0
- package/docs/skills.md +20 -122
- package/docs/subagents/authoring.md +37 -0
- package/docs/subagents/reference.md +60 -0
- package/docs/subagents.md +42 -96
- package/docs/terminal-setup.md +1 -1
- package/docs/themes/reference.md +176 -0
- package/docs/themes.md +21 -135
- package/docs/tmux.md +5 -0
- package/docs/tools/edit.md +5 -0
- package/docs/tools.md +31 -0
- package/docs/tui/reference.md +343 -0
- package/docs/tui.md +23 -286
- package/docs/usage.md +26 -209
- package/docs/web-access.md +40 -0
- package/docs/windows.md +2 -1
- package/docs/workflows/api-reference.md +11 -4
- package/docs/workflows/authoring.md +4 -2
- package/docs/workflows/builtins.md +2 -2
- package/docs/workflows/operations.md +11 -7
- package/docs/workflows/reliable-design.md +21 -3
- package/docs/workflows/verification.md +8 -0
- package/examples/extensions/gondolin/index.ts +116 -148
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/ssh.ts +56 -34
- package/npm-shrinkwrap.json +33 -33
- package/package.json +4 -4
|
@@ -100,14 +100,14 @@ Workflow-authored `ctx.ui` gates remain supported. `workflow answer` relays an a
|
|
|
100
100
|
|
|
101
101
|
### Stage model and thinking-level assignment
|
|
102
102
|
|
|
103
|
-
Before launching an authored workflow, assign every model stage a **role**, **failure cost**, **primary model**, **thinking level**, and **fallback policy**. Read [Model Selection](/models/model-selection) for the role defaults and [Evals](/models/evals) for the measured per-evaluation scores — its task-type picker maps each stage type (terminal debugging, knowledge-work planning, tool-calling loops, document research, code-reading review) to the eval that measures it and the models that lead it — but treat thinking levels in benchmark rows as measurement configurations, not production defaults.
|
|
103
|
+
Before launching an authored workflow, assign every model stage a **role**, **failure cost**, **primary model**, **thinking level**, and **fallback policy**. Read [Model Selection](/models/model-selection) for the role defaults and [Evals](/models/evals) for the measured per-evaluation scores — its task-type picker maps each stage type (terminal debugging, knowledge-work planning, tool-calling loops, document research, code-reading review) to the eval that measures it and the models that lead it — but treat thinking levels in benchmark rows as measurement configurations, not production defaults. Use `low` or `medium` for implementation and routine fixes, and `high` or `xhigh` for code review, test design, failure analysis, and approval decisions when supported. Use `high` for demanding mapping, lifecycle analysis, compatibility, planning, synthesis, and triage, and `medium` for user-impact review and final reporting. `max` is an exception justified by task-specific evidence or an explicit user request, not a role default. Keep deterministic checks as tool nodes with no model call.
|
|
104
104
|
|
|
105
105
|
Print this compact assignment before launch, with a short cost/quality rationale for each model stage:
|
|
106
106
|
|
|
107
107
|
```text
|
|
108
108
|
Stage | Model | Thinking | Role
|
|
109
109
|
map | <catalog fullId> | high | codebase mapping
|
|
110
|
-
approve | <catalog fullId> |
|
|
110
|
+
approve | <catalog fullId> | high | final approval
|
|
111
111
|
report | <catalog fullId> | medium | final reporting
|
|
112
112
|
tests | — | — | deterministic check (tool node)
|
|
113
113
|
```
|
|
@@ -410,7 +410,7 @@ Humans can steer the shape directly:
|
|
|
410
410
|
- **State the loop.** "Iterate until tests pass" or "review and fix until approved" defines a hard workflow stop condition.
|
|
411
411
|
- **State the evidence.** A QA video, test output, generated artifact, or reviewer sign-off tells the graph which gates it needs.
|
|
412
412
|
- **State the boundary.** "Work in a separate worktree", "do not create a PR", or "stop after implementation" separates implementation from final actions.
|
|
413
|
-
- **State the queue policy.** Say how to split, order, isolate, and bound queued items; otherwise Atomic runs the [dependency-triage and bounded-dispatch playbook](#task-queues-and-software-factories) before implementation. Ordinary list order and per-item "create a PR after" wording do not create a cross-item dependency.
|
|
413
|
+
- **State the queue policy.** Say how to split, order, isolate, and bound queued items; otherwise Atomic runs the [dependency-triage and bounded-dispatch playbook](/workflows/reliable-design#task-queues-and-software-factories) before implementation. Ordinary list order and per-item "create a PR after" wording do not create a cross-item dependency.
|
|
414
414
|
|
|
415
415
|
Absent these controls, Atomic applies the self-prompt and rubric above; a prompt that names none of them delegates the shape decision rather than avoiding it.
|
|
416
416
|
|
|
@@ -1348,6 +1348,8 @@ A failed test becomes the next objective.
|
|
|
1348
1348
|
Validation failed on `[command]`. Treat that as the source of truth. Fix the root cause only, rerun the failing check, then report the result.
|
|
1349
1349
|
```
|
|
1350
1350
|
|
|
1351
|
+
<a id="8-interrupt-stale-or-wrong-work" />
|
|
1352
|
+
|
|
1351
1353
|
#### 8. Pause stale or wrong work
|
|
1352
1354
|
|
|
1353
1355
|
If a run is solving the wrong problem, based on outdated assumptions, or duplicating another run, stop it. Continuing usually creates more cleanup.
|
|
@@ -1395,6 +1397,8 @@ Constructive quorum relies on existing Intercom mechanics: every workflow invoca
|
|
|
1395
1397
|
|
|
1396
1398
|
#### Pattern diagrams
|
|
1397
1399
|
|
|
1400
|
+
<a id="1-classify-and-act"></a>
|
|
1401
|
+
|
|
1398
1402
|
##### 1. Classify-and-act
|
|
1399
1403
|
|
|
1400
1404
|
Builtin definition and contracts: [Six composable pattern builtins](/workflows/builtins#six-composable-pattern-builtins).
|
|
@@ -1420,6 +1424,8 @@ Best practices:
|
|
|
1420
1424
|
- Keep each action branch isolated with the minimum tools and context it needs.
|
|
1421
1425
|
- Add a fallback or human-input branch for low-confidence classifications.
|
|
1422
1426
|
|
|
1427
|
+
<a id="2-fan-out-and-synthesize"></a>
|
|
1428
|
+
|
|
1423
1429
|
##### 2. Fan-out-and-synthesize
|
|
1424
1430
|
|
|
1425
1431
|
Builtin definition and contracts: [Six composable pattern builtins](/workflows/builtins#six-composable-pattern-builtins).
|
|
@@ -1448,6 +1454,8 @@ Best practices:
|
|
|
1448
1454
|
- Save each branch to a separate artifact and pass paths with `reads` instead of inlining all branch output.
|
|
1449
1455
|
- Treat synthesis as a barrier: it waits for every branch, deduplicates, resolves conflicts, and cites evidence.
|
|
1450
1456
|
|
|
1457
|
+
<a id="3-adversarial-verification" />
|
|
1458
|
+
|
|
1451
1459
|
##### 3. Adversarial verification
|
|
1452
1460
|
|
|
1453
1461
|
Builtin definition and contracts: [Six composable pattern builtins](/workflows/builtins#six-composable-pattern-builtins).
|
|
@@ -1486,6 +1494,8 @@ Best practices:
|
|
|
1486
1494
|
- Invalid criterion reports are written as invalid artifacts and re-asked in bounded waves up to `reask_limit`; an invalid or missing report is counted in `invalidCount` only and is never converted into a fail vote or included in the mean. If the required quorum is still missing after the re-asks, the round is `indeterminate` rather than silently narrowing the decision.
|
|
1487
1495
|
- `score_table_path` names the durable `verification-summary-<round>.json` for the final round. Its object contains `scores` (`criterion_id`, integer `score`, `evidence`, and `findings` with `finding` plus `severity`), `mean`, `invalidCount`, the `decision` (`accept`, `repair`, or `indeterminate` with its corresponding mean/findings or missing count), and folded `usage`; `review_report_path` carries repair guidance or quorum evidence.
|
|
1488
1496
|
|
|
1497
|
+
<a id="4-generate-and-filter"></a>
|
|
1498
|
+
|
|
1489
1499
|
##### 4. Generate-and-filter
|
|
1490
1500
|
|
|
1491
1501
|
Builtin definition and contracts: [Six composable pattern builtins](/workflows/builtins#six-composable-pattern-builtins).
|
|
@@ -1514,6 +1524,8 @@ Best practices:
|
|
|
1514
1524
|
- When the filter ranks candidates rather than applying a threshold, use the same judge guidance as Tournament: graded per-criterion integer scores rather than binary keep/drop, a Bradley–Terry preference from the score gap so near-ties stay near-ties, and K repeats with candidates swapped between the A and B slots. See [Verification scaling](#verification-scaling).
|
|
1515
1525
|
- For a custom ranking filter, reuse the shared `verification-criteria` module and its `criteria.md` parser rather than inventing a binary keep/drop rubric; stable criterion ids let the judge select the same criteria in each comparison. See [Adversarial verification](#3-adversarial-verification) for the accepted shapes and score decision.
|
|
1516
1526
|
|
|
1527
|
+
<a id="5-tournament" />
|
|
1528
|
+
|
|
1517
1529
|
##### 5. Tournament
|
|
1518
1530
|
|
|
1519
1531
|
Builtin definition and contracts: [Six composable pattern builtins](/workflows/builtins#six-composable-pattern-builtins).
|
|
@@ -1547,6 +1559,8 @@ Best practices:
|
|
|
1547
1559
|
- The shipped tournament inputs use `num_attempts=4` and `max_concurrency=4`; `n_evaluations=2` repeats each criterion/directed pair, `pivots=1` selects the second comparison phase's pivot candidates, and `seed=0` drives the deterministic schedule. `criteria` is optional and accepts a markdown rubric, a string-to-description record, a string list, or a `CriterionInput` list; omission uses the shipped three-criterion Correctness, Completeness, and Evidence and task fit rubric. Optional ordered `models` ids are assigned round-robin to attempt slots.
|
|
1548
1560
|
- `comparisons_path` points to `comparisons.json`, whose ledger records the task and seed, `params` (`n`, `pivots`, `n_evaluations`, and normalized `criteria`), per-job `comparisons` rows (`a`, `b`, phase, criterion id, repeat, slot-swap flag, scores or an `invalid` marker, preference, and judge artifact path), aggregate `pairs`, weights/counts, the complete `ranking`, and optional model assignment. Its `budget` records planned versus executed judge stages, including re-asks; invalid reports remain auditable rows and an all-invalid pair remains marked invalid rather than becoming a score.
|
|
1549
1561
|
|
|
1562
|
+
<a id="6-loop-until-done" />
|
|
1563
|
+
|
|
1550
1564
|
##### 6. Loop until done
|
|
1551
1565
|
|
|
1552
1566
|
Builtin definition and contracts: [Six composable pattern builtins](/workflows/builtins#six-composable-pattern-builtins).
|
|
@@ -1577,6 +1591,8 @@ Best practices:
|
|
|
1577
1591
|
- Progress scores use the anchored 1–20 scale and average valid repeat scores per checkpoint. `classify_trend` uses `window=3`, `riseDelta=1.5`, and `fallDelta=-1.5`; it compares equal leading/trailing halves of the trailing two windows, drops an odd middle sample, and classifies inclusive threshold crossings as `rising`, `flat`, or `regressing`. A short series is `flat` evidence.
|
|
1578
1592
|
- The trend is monitoring and escalation evidence only: it never kills, terminates, or approves a loop, and the explicit evaluator stop condition remains authoritative. `progress_curve`, `final_trend`, and `progress_disclaimer` are advisory outputs, not alternate closure signals.
|
|
1579
1593
|
|
|
1594
|
+
<a id="7-constructive-quorum"></a>
|
|
1595
|
+
|
|
1580
1596
|
##### 7. Constructive quorum
|
|
1581
1597
|
|
|
1582
1598
|
This prompt-level reviewer pattern is used by the `goal` and `ralph` builtins; it does not add a reducer or quorum mechanism.
|
|
@@ -1608,6 +1624,8 @@ Best practices:
|
|
|
1608
1624
|
- Change a verdict only through evidence, never deference. Each reviewer emits its own final structured verdict and records whether deliberation changed it and which evidence caused the change.
|
|
1609
1625
|
- Let the existing deterministic reducer count the final votes; deliberation shapes votes but does not replace quorum counts or the `stop_review_loop` contract.
|
|
1610
1626
|
|
|
1627
|
+
<a id="stacked-implementation-slices-starter-pattern" />
|
|
1628
|
+
|
|
1611
1629
|
##### Stacked implementation slices starter pattern
|
|
1612
1630
|
|
|
1613
1631
|
Use this authoring pattern when one implementation objective should land as a stack of small, independently verified changes. It is not a queue dispatcher: the slices belong to one dependency chain, so slice N+1 starts only after slice N is verified.
|
|
@@ -4,6 +4,8 @@ Verify the behavior that changed, then give the reviewer enough evidence to unde
|
|
|
4
4
|
|
|
5
5
|
For tool installation, automation techniques, platform permissions, and general work in applications, read [Computer use](/computer-use). That guide covers Herdr for terminals, playwright-cli for browsers, and PyAutoGUI with uv for desktop CUA on macOS, Linux, and Windows. It also covers native accessibility and application tools when they are easier or more reliable.
|
|
6
6
|
|
|
7
|
+
<a id="select-the-verification-environment" />
|
|
8
|
+
|
|
7
9
|
## Choose checks that answer the question
|
|
8
10
|
|
|
9
11
|
Start with the project's existing tests, build, typecheck, and lint commands. Add an interactive scenario when the change affects what a user sees or does. Do not replace required checks with a recording, or create a UI solely to demonstrate a non-UI change.
|
|
@@ -54,6 +56,8 @@ playwright-cli -s=pr-check video-stop
|
|
|
54
56
|
|
|
55
57
|
Open the saved artifact and confirm it includes the final state. Keep recordings short and focused. Inspect traces, authentication state, and network output for secrets before sharing. See [browser setup and best practices](/computer-use#browser-automation-with-playwright-cli).
|
|
56
58
|
|
|
59
|
+
<a id="terminal-contracts" />
|
|
60
|
+
|
|
57
61
|
### Terminal changes
|
|
58
62
|
|
|
59
63
|
Use Herdr to send real interactive input and inspect the resulting pane, or use tmux/psmux when Herdr cannot be used. Keep the terminal dimensions and shell relevant to the change. Test narrow layouts or modified keys when those are affected.
|
|
@@ -62,6 +66,10 @@ Retain screen captures or transcripts that show the input and result. For cursor
|
|
|
62
66
|
|
|
63
67
|
See [terminal setup and capture commands](/computer-use#terminal-automation-with-herdr). A browser rendering of terminal text is not proof that the real TUI accepted input. WSL tmux does not establish native Windows terminal coverage.
|
|
64
68
|
|
|
69
|
+
<a id="reproduce-stage-skill-terminal-evidence" />
|
|
70
|
+
|
|
71
|
+
<a id="desktop-safety" />
|
|
72
|
+
|
|
65
73
|
### Desktop, simulator, and emulator changes
|
|
66
74
|
|
|
67
75
|
Run PyAutoGUI through uv in a dedicated graphical session, using [the desktop guide](/computer-use#desktop-automation-with-pyautogui-and-uv). Native accessibility tools or application APIs can give more reliable assertions than pixel matching. For example, check a saved document through the app API after exercising the visible Save flow.
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gondolin Tool Routing Example
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* directory is mounted at /workspace
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Routes read, write, edit, bash, find, ls and user ! commands into a local
|
|
5
|
+
* Gondolin micro-VM. The host working directory is mounted at /workspace;
|
|
6
|
+
* changes there write through to the host. Other guest changes stay in the VM.
|
|
7
|
+
* Search and other unoverridden tools still run on the host. For guest-only
|
|
8
|
+
* content searches use the routed shell; this is not whole-session isolation.
|
|
8
9
|
*
|
|
9
10
|
* Setup:
|
|
10
11
|
* cd packages/coding-agent/examples/extensions/gondolin
|
|
11
|
-
* npm
|
|
12
|
+
* npm ci --ignore-scripts
|
|
13
|
+
* npm run check # from this repository: full Gondolin + SSH adapter typecheck
|
|
12
14
|
*
|
|
13
15
|
* Usage:
|
|
14
16
|
* cd /path/to/project
|
|
@@ -24,33 +26,22 @@ import type { ExtensionAPI, ExtensionContext } from "@bastani/atomic";
|
|
|
24
26
|
import {
|
|
25
27
|
type BashOperations,
|
|
26
28
|
createBashTool,
|
|
29
|
+
createCodingTools,
|
|
27
30
|
createEditTool,
|
|
28
31
|
createFindTool,
|
|
29
|
-
createGrepTool,
|
|
30
32
|
createLsTool,
|
|
31
33
|
createReadTool,
|
|
32
34
|
createWriteTool,
|
|
33
|
-
DEFAULT_MAX_BYTES,
|
|
34
35
|
type EditOperations,
|
|
35
36
|
type FindOperations,
|
|
36
|
-
formatSize,
|
|
37
|
-
type GrepToolDetails,
|
|
38
|
-
type GrepToolInput,
|
|
39
37
|
type LsOperations,
|
|
40
38
|
type ReadOperations,
|
|
41
|
-
truncateHead,
|
|
42
|
-
truncateLine,
|
|
43
39
|
type WriteOperations,
|
|
44
40
|
} from "@bastani/atomic";
|
|
45
41
|
import { RealFSProvider, VM } from "@earendil-works/gondolin";
|
|
46
42
|
|
|
47
43
|
const GUEST_WORKSPACE = "/workspace";
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
type TextToolResult<TDetails> = {
|
|
51
|
-
content: Array<{ type: "text"; text: string }>;
|
|
52
|
-
details: TDetails | undefined;
|
|
53
|
-
};
|
|
44
|
+
const GUEST_FUSE_MOUNT = "/data";
|
|
54
45
|
|
|
55
46
|
function stripAtPrefix(value: string): string {
|
|
56
47
|
return value.startsWith("@") ? value.slice(1) : value;
|
|
@@ -98,20 +89,99 @@ function createGondolinReadOps(vm: VM, localCwd: string): ReadOperations {
|
|
|
98
89
|
};
|
|
99
90
|
}
|
|
100
91
|
|
|
101
|
-
function createGondolinWriteOps(vm: VM, localCwd: string): WriteOperations {
|
|
92
|
+
function createGondolinWriteOps(vm: VM, localCwd: string, workspace: RealFSProvider): WriteOperations {
|
|
93
|
+
const workspaceRelativePath = (target: string) => {
|
|
94
|
+
for (const mount of [GUEST_WORKSPACE, `${GUEST_FUSE_MOUNT}${GUEST_WORKSPACE}`]) {
|
|
95
|
+
if (target === mount || target.startsWith(`${mount}/`)) return target.slice(mount.length) || "/";
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
};
|
|
102
99
|
return {
|
|
103
|
-
writeFile: async (filePath, content) => {
|
|
104
|
-
|
|
100
|
+
writeFile: async (filePath, content, options) => {
|
|
101
|
+
const target = toGuestPath(localCwd, filePath);
|
|
102
|
+
if (!options?.exclusive) {
|
|
103
|
+
await vm.fs.writeFile(target, content, { encoding: "utf8" });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Both fs.writeFile and the 0.12.0 FUSE bridge discard exclusive flags.
|
|
107
|
+
// Open through the exact provider mounted at /workspace, not unrelated host IO.
|
|
108
|
+
let relativePath = workspaceRelativePath(target);
|
|
109
|
+
if (relativePath === undefined) {
|
|
110
|
+
// A guest symlink can route an otherwise native-looking parent onto FUSE.
|
|
111
|
+
const parent = await vm.exec(["/bin/sh", "-c", 'cd "$1" && pwd -P', "sh", path.posix.dirname(target)]);
|
|
112
|
+
if (parent.exitCode !== 0) throw new Error(parent.stderr);
|
|
113
|
+
relativePath = workspaceRelativePath(
|
|
114
|
+
path.posix.join(parent.stdout.replace(/\n$/, ""), path.posix.basename(target)),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (relativePath !== undefined) {
|
|
118
|
+
const mountedPath = relativePath;
|
|
119
|
+
const handle = await workspace.open(mountedPath, "wx").catch(async (error: NodeJS.ErrnoException) => {
|
|
120
|
+
// The provider resolves links before open: a dangling collision becomes ENOENT.
|
|
121
|
+
// Check occupancy in that same mounted namespace, without following the link.
|
|
122
|
+
if (
|
|
123
|
+
error.code === "ENOENT" &&
|
|
124
|
+
(await workspace.lstat(mountedPath).catch(() => undefined))?.isSymbolicLink()
|
|
125
|
+
) {
|
|
126
|
+
throw Object.assign(new Error(`EEXIST: file already exists, open '${target}'`), { code: "EEXIST" });
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
await handle.writeFile(content, { encoding: "utf8" });
|
|
132
|
+
} finally {
|
|
133
|
+
await handle.close();
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Native guest paths do not cross FUSE. Noclobber is enforced by the guest OS.
|
|
138
|
+
const result = await vm.exec(
|
|
139
|
+
[
|
|
140
|
+
"/bin/sh",
|
|
141
|
+
"-c",
|
|
142
|
+
'(set -C; cat > "$1") || { if [ -e "$1" ] || [ -L "$1" ]; then exit 17; else exit 1; fi; }',
|
|
143
|
+
"sh",
|
|
144
|
+
target,
|
|
145
|
+
],
|
|
146
|
+
{ stdin: content },
|
|
147
|
+
);
|
|
148
|
+
if (result.exitCode !== 0) {
|
|
149
|
+
const error = new Error(`Failed to create guest file '${target}': ${result.stderr}`);
|
|
150
|
+
if (result.exitCode === 17) throw Object.assign(error, { code: "EEXIST" });
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
105
153
|
},
|
|
106
154
|
mkdir: async (dirPath) => {
|
|
107
155
|
await vm.fs.mkdir(toGuestPath(localCwd, dirPath), { recursive: true });
|
|
108
156
|
},
|
|
157
|
+
// Reads inside the guest, so the checks `write` runs before overwriting see the same
|
|
158
|
+
// filesystem the write lands on rather than the host's. Absence is `undefined`; a path
|
|
159
|
+
// that exists but cannot be read keeps throwing, since it is not a free path.
|
|
160
|
+
readFile: async (filePath) => {
|
|
161
|
+
const target = toGuestPath(localCwd, filePath);
|
|
162
|
+
try {
|
|
163
|
+
return await vm.fs.readFile(target, { encoding: "utf8" });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Gondolin 0.12.0 wraps filesystem errors without preserving errno. Probe
|
|
166
|
+
// the guest namespace rather than parsing localized error messages.
|
|
167
|
+
const probe = await vm.exec([
|
|
168
|
+
"/bin/sh",
|
|
169
|
+
"-c",
|
|
170
|
+
'(cd "$2") && { if [ -e "$1" ] || [ -L "$1" ]; then exit 0; else exit 44; fi; }',
|
|
171
|
+
"sh",
|
|
172
|
+
target,
|
|
173
|
+
path.posix.dirname(target),
|
|
174
|
+
]);
|
|
175
|
+
if (probe.exitCode === 44) return undefined;
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
109
179
|
};
|
|
110
180
|
}
|
|
111
181
|
|
|
112
|
-
function createGondolinEditOps(vm: VM, localCwd: string): EditOperations {
|
|
182
|
+
function createGondolinEditOps(vm: VM, localCwd: string, workspace: RealFSProvider): EditOperations {
|
|
113
183
|
const readOps = createGondolinReadOps(vm, localCwd);
|
|
114
|
-
const writeOps = createGondolinWriteOps(vm, localCwd);
|
|
184
|
+
const writeOps = createGondolinWriteOps(vm, localCwd, workspace);
|
|
115
185
|
return {
|
|
116
186
|
readFile: readOps.readFile,
|
|
117
187
|
writeFile: writeOps.writeFile,
|
|
@@ -203,110 +273,6 @@ function createGondolinFindOps(vm: VM, localCwd: string): FindOperations {
|
|
|
203
273
|
};
|
|
204
274
|
}
|
|
205
275
|
|
|
206
|
-
function createLineMatcher(pattern: string, literal: boolean | undefined, ignoreCase: boolean | undefined) {
|
|
207
|
-
if (literal) {
|
|
208
|
-
const needle = ignoreCase ? pattern.toLowerCase() : pattern;
|
|
209
|
-
return (line: string) => (ignoreCase ? line.toLowerCase() : line).includes(needle);
|
|
210
|
-
}
|
|
211
|
-
const regex = new RegExp(pattern, ignoreCase ? "i" : undefined);
|
|
212
|
-
return (line: string) => regex.test(line);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function appendGrepBlock(params: {
|
|
216
|
-
outputLines: string[];
|
|
217
|
-
lines: string[];
|
|
218
|
-
relativePath: string;
|
|
219
|
-
lineIndex: number;
|
|
220
|
-
contextLines: number;
|
|
221
|
-
}): boolean {
|
|
222
|
-
let linesTruncated = false;
|
|
223
|
-
const start = params.contextLines > 0 ? Math.max(0, params.lineIndex - params.contextLines) : params.lineIndex;
|
|
224
|
-
const end =
|
|
225
|
-
params.contextLines > 0
|
|
226
|
-
? Math.min(params.lines.length - 1, params.lineIndex + params.contextLines)
|
|
227
|
-
: params.lineIndex;
|
|
228
|
-
|
|
229
|
-
for (let index = start; index <= end; index++) {
|
|
230
|
-
const rawLine = params.lines[index] ?? "";
|
|
231
|
-
const { text, wasTruncated } = truncateLine(rawLine.replace(/\r/g, ""));
|
|
232
|
-
if (wasTruncated) linesTruncated = true;
|
|
233
|
-
const separator = index === params.lineIndex ? ":" : "-";
|
|
234
|
-
params.outputLines.push(`${params.relativePath}${separator}${index + 1}${separator} ${text}`);
|
|
235
|
-
}
|
|
236
|
-
return linesTruncated;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async function executeGondolinGrep(
|
|
240
|
-
vm: VM,
|
|
241
|
-
localCwd: string,
|
|
242
|
-
params: GrepToolInput,
|
|
243
|
-
signal?: AbortSignal,
|
|
244
|
-
): Promise<TextToolResult<GrepToolDetails>> {
|
|
245
|
-
const root = toGuestPath(localCwd, params.path ?? ".");
|
|
246
|
-
const rootStat = await vm.fs.stat(root, { signal });
|
|
247
|
-
const rootIsDirectory = rootStat.isDirectory();
|
|
248
|
-
const matcher = createLineMatcher(params.pattern, params.literal, params.ignoreCase);
|
|
249
|
-
const contextLines = params.context && params.context > 0 ? params.context : 0;
|
|
250
|
-
const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
|
|
251
|
-
const outputLines: string[] = [];
|
|
252
|
-
const details: GrepToolDetails = {};
|
|
253
|
-
let matchCount = 0;
|
|
254
|
-
let matchLimitReached = false;
|
|
255
|
-
let linesTruncated = false;
|
|
256
|
-
|
|
257
|
-
await walkGuestFiles(
|
|
258
|
-
vm,
|
|
259
|
-
root,
|
|
260
|
-
async (guestPath, relativePath) => {
|
|
261
|
-
if (matchCount >= effectiveLimit) return false;
|
|
262
|
-
if (params.glob && !matchesToolGlob(relativePath, params.glob)) return true;
|
|
263
|
-
let content: string;
|
|
264
|
-
try {
|
|
265
|
-
content = await vm.fs.readFile(guestPath, { encoding: "utf8", signal });
|
|
266
|
-
} catch {
|
|
267
|
-
return true;
|
|
268
|
-
}
|
|
269
|
-
const lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
270
|
-
const displayPath = rootIsDirectory ? relativePath : path.posix.basename(guestPath);
|
|
271
|
-
for (let index = 0; index < lines.length; index++) {
|
|
272
|
-
if (signal?.aborted) throw new Error("Operation aborted");
|
|
273
|
-
if (!matcher(lines[index] ?? "")) continue;
|
|
274
|
-
matchCount++;
|
|
275
|
-
if (appendGrepBlock({ outputLines, lines, relativePath: displayPath, lineIndex: index, contextLines })) {
|
|
276
|
-
linesTruncated = true;
|
|
277
|
-
}
|
|
278
|
-
if (matchCount >= effectiveLimit) {
|
|
279
|
-
matchLimitReached = true;
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return true;
|
|
284
|
-
},
|
|
285
|
-
signal,
|
|
286
|
-
);
|
|
287
|
-
if (matchCount === 0) return { content: [{ type: "text", text: "No matches found" }], details: undefined };
|
|
288
|
-
const rawOutput = outputLines.join("\n");
|
|
289
|
-
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
290
|
-
const notices: string[] = [];
|
|
291
|
-
let output = truncation.content;
|
|
292
|
-
if (matchLimitReached) {
|
|
293
|
-
details.matchLimitReached = effectiveLimit;
|
|
294
|
-
notices.push(`${effectiveLimit} matches limit reached`);
|
|
295
|
-
}
|
|
296
|
-
if (linesTruncated) {
|
|
297
|
-
details.linesTruncated = true;
|
|
298
|
-
notices.push("long lines truncated");
|
|
299
|
-
}
|
|
300
|
-
if (truncation.truncated) {
|
|
301
|
-
details.truncation = truncation;
|
|
302
|
-
notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
|
|
303
|
-
}
|
|
304
|
-
if (notices.length > 0) output += `\n\n[${notices.join(". ")}]`;
|
|
305
|
-
return {
|
|
306
|
-
content: [{ type: "text", text: output }],
|
|
307
|
-
details: Object.keys(details).length > 0 ? details : undefined,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
276
|
function createGondolinBashOps(vm: VM, localCwd: string, shellPath: string): BashOperations {
|
|
311
277
|
return {
|
|
312
278
|
exec: async (command, cwd, { onData, signal, timeout }) => {
|
|
@@ -348,23 +314,33 @@ function createGondolinBashOps(vm: VM, localCwd: string, shellPath: string): Bas
|
|
|
348
314
|
}
|
|
349
315
|
export default function (pi: ExtensionAPI) {
|
|
350
316
|
const localCwd = process.cwd();
|
|
317
|
+
const workspace = new RealFSProvider(localCwd);
|
|
351
318
|
const localRead = createReadTool(localCwd);
|
|
352
319
|
const localWrite = createWriteTool(localCwd);
|
|
353
320
|
const localEdit = createEditTool(localCwd);
|
|
354
321
|
const localBash = createBashTool(localCwd);
|
|
355
|
-
const localGrep = createGrepTool(localCwd);
|
|
356
322
|
const localFind = createFindTool(localCwd);
|
|
357
323
|
const localLs = createLsTool(localCwd);
|
|
358
324
|
let vm: VM | undefined;
|
|
359
325
|
let vmStarting: Promise<VM> | undefined;
|
|
360
326
|
let shellPath = "/bin/sh";
|
|
327
|
+
let fileTools: ReturnType<typeof createCodingTools> | undefined;
|
|
328
|
+
function getFileTools(activeVm: VM) {
|
|
329
|
+
fileTools ??= createCodingTools(GUEST_WORKSPACE, {
|
|
330
|
+
read: { operations: createGondolinReadOps(activeVm, localCwd) },
|
|
331
|
+
write: { operations: createGondolinWriteOps(activeVm, localCwd, workspace) },
|
|
332
|
+
edit: { operations: createGondolinEditOps(activeVm, localCwd, workspace) },
|
|
333
|
+
});
|
|
334
|
+
return fileTools;
|
|
335
|
+
}
|
|
361
336
|
async function startVm(ctx?: ExtensionContext): Promise<VM> {
|
|
362
337
|
ctx?.ui.setStatus("gondolin", ctx.ui.theme.fg("accent", `Gondolin: starting ${GUEST_WORKSPACE}`));
|
|
363
338
|
const created = await VM.create({
|
|
364
339
|
sessionLabel: `atomic ${path.basename(localCwd)}`,
|
|
365
340
|
vfs: {
|
|
341
|
+
fuseMount: GUEST_FUSE_MOUNT,
|
|
366
342
|
mounts: {
|
|
367
|
-
[GUEST_WORKSPACE]:
|
|
343
|
+
[GUEST_WORKSPACE]: workspace,
|
|
368
344
|
},
|
|
369
345
|
},
|
|
370
346
|
});
|
|
@@ -388,9 +364,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
388
364
|
return vmStarting;
|
|
389
365
|
}
|
|
390
366
|
pi.on("session_start", async (_event, ctx) => {
|
|
367
|
+
fileTools = undefined;
|
|
391
368
|
await ensureVm(ctx);
|
|
392
369
|
});
|
|
393
370
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
371
|
+
fileTools = undefined;
|
|
394
372
|
const activeVm = vm;
|
|
395
373
|
vm = undefined;
|
|
396
374
|
vmStarting = undefined;
|
|
@@ -421,30 +399,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
421
399
|
...localRead,
|
|
422
400
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
423
401
|
const activeVm = await ensureVm(ctx);
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return tool.execute(id, params, signal, onUpdate);
|
|
402
|
+
return getFileTools(activeVm)
|
|
403
|
+
.find((tool) => tool.name === "read")!
|
|
404
|
+
.execute(id, params, signal, onUpdate);
|
|
428
405
|
},
|
|
429
406
|
});
|
|
430
407
|
pi.registerTool({
|
|
431
408
|
...localWrite,
|
|
432
409
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
433
410
|
const activeVm = await ensureVm(ctx);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return tool.execute(id, params, signal, onUpdate);
|
|
411
|
+
return getFileTools(activeVm)
|
|
412
|
+
.find((tool) => tool.name === "write")!
|
|
413
|
+
.execute(id, params, signal, onUpdate);
|
|
438
414
|
},
|
|
439
415
|
});
|
|
440
416
|
pi.registerTool({
|
|
441
417
|
...localEdit,
|
|
442
418
|
async execute(id, params, signal, onUpdate, ctx) {
|
|
443
419
|
const activeVm = await ensureVm(ctx);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return tool.execute(id, params, signal, onUpdate);
|
|
420
|
+
return getFileTools(activeVm)
|
|
421
|
+
.find((tool) => tool.name === "edit")!
|
|
422
|
+
.execute(id, params, signal, onUpdate);
|
|
448
423
|
},
|
|
449
424
|
});
|
|
450
425
|
pi.registerTool({
|
|
@@ -477,13 +452,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
477
452
|
return tool.execute(id, params, signal, onUpdate);
|
|
478
453
|
},
|
|
479
454
|
});
|
|
480
|
-
pi.registerTool({
|
|
481
|
-
...localGrep,
|
|
482
|
-
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
483
|
-
const activeVm = await ensureVm(ctx);
|
|
484
|
-
return executeGondolinGrep(activeVm, localCwd, params, signal);
|
|
485
|
-
},
|
|
486
|
-
});
|
|
487
455
|
pi.on("user_bash", async (_event, ctx) => {
|
|
488
456
|
const activeVm = await ensureVm(ctx);
|
|
489
457
|
return { operations: createGondolinBashOps(activeVm, localCwd, shellPath) };
|