@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +2 -2
  3. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  4. package/dist/builtin/intercom/broker/client.ts +19 -1
  5. package/dist/builtin/intercom/index.bundle.mjs +8 -1
  6. package/dist/builtin/intercom/package.json +1 -1
  7. package/dist/builtin/mcp/package.json +1 -1
  8. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  9. package/dist/builtin/subagents/README.md +3 -1
  10. package/dist/builtin/subagents/agents/codebase-online-researcher.md +7 -7
  11. package/dist/builtin/subagents/package.json +1 -1
  12. package/dist/builtin/subagents/skills/subagent/SKILL.md +3 -1
  13. package/dist/builtin/subagents/src/extension/index.bundle.mjs +2 -1
  14. package/dist/builtin/web-access/CHANGELOG.md +13 -0
  15. package/dist/builtin/web-access/README.md +31 -24
  16. package/dist/builtin/web-access/index.bundle.mjs +16121 -80
  17. package/dist/builtin/web-access/package.json +2 -1
  18. package/dist/builtin/workflows/CHANGELOG.md +15 -0
  19. package/dist/builtin/workflows/README.md +1 -1
  20. package/dist/builtin/workflows/builtin/adversarial-verification.js +2 -2
  21. package/dist/builtin/workflows/builtin/{chunk-vhmnzdj9.js → chunk-1jth021m.js} +1 -1
  22. package/dist/builtin/workflows/builtin/{chunk-p8jjwfg6.js → chunk-2377r57g.js} +4 -2
  23. package/dist/builtin/workflows/builtin/{chunk-hbnwv56f.js → chunk-31j2gs7h.js} +2 -2
  24. package/dist/builtin/workflows/builtin/{chunk-n8zysf1t.js → chunk-ngkqkzej.js} +1 -1
  25. package/dist/builtin/workflows/builtin/{chunk-1nbq96fh.js → chunk-nkrafh9s.js} +1 -1
  26. package/dist/builtin/workflows/builtin/{chunk-1kx4aggw.js → chunk-wf741bv3.js} +2 -2
  27. package/dist/builtin/workflows/builtin/{chunk-t6x2d78s.js → chunk-z29qbehr.js} +1 -1
  28. package/dist/builtin/workflows/builtin/generate-and-filter.js +2 -2
  29. package/dist/builtin/workflows/builtin/goal.js +3 -3
  30. package/dist/builtin/workflows/builtin/index.js +7 -7
  31. package/dist/builtin/workflows/builtin/ralph.js +3 -3
  32. package/dist/builtin/workflows/builtin/tournament.js +2 -2
  33. package/dist/builtin/workflows/package.json +1 -1
  34. package/dist/builtin/workflows/src/extension/index.bundle.mjs +339 -31
  35. package/dist/builtin/workflows/src/index.js +67 -19
  36. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -0
  37. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  38. package/dist/core/agent-session-tool-registry.js +10 -0
  39. package/dist/core/agent-session-tool-registry.js.map +1 -1
  40. package/dist/core/bash-executor.js.map +1 -1
  41. package/dist/core/tools/bash.d.ts +1 -1
  42. package/dist/core/tools/bash.d.ts.map +1 -1
  43. package/dist/core/tools/bash.js +3 -2
  44. package/dist/core/tools/bash.js.map +1 -1
  45. package/dist/core/tools/edit.d.ts +3 -0
  46. package/dist/core/tools/edit.d.ts.map +1 -1
  47. package/dist/core/tools/edit.js +91 -12
  48. package/dist/core/tools/edit.js.map +1 -1
  49. package/dist/core/tools/file-mutation-coordinator.d.ts +255 -0
  50. package/dist/core/tools/file-mutation-coordinator.d.ts.map +1 -0
  51. package/dist/core/tools/file-mutation-coordinator.js +300 -0
  52. package/dist/core/tools/file-mutation-coordinator.js.map +1 -0
  53. package/dist/core/tools/file-mutation-queue.d.ts +14 -1
  54. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  55. package/dist/core/tools/file-mutation-queue.js +15 -3
  56. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  57. package/dist/core/tools/hashline.d.ts +9 -0
  58. package/dist/core/tools/hashline.d.ts.map +1 -1
  59. package/dist/core/tools/hashline.js +12 -1
  60. package/dist/core/tools/hashline.js.map +1 -1
  61. package/dist/core/tools/index.d.ts +9 -1
  62. package/dist/core/tools/index.d.ts.map +1 -1
  63. package/dist/core/tools/index.js +67 -12
  64. package/dist/core/tools/index.js.map +1 -1
  65. package/dist/core/tools/non-minting-snapshot-store.d.ts +49 -0
  66. package/dist/core/tools/non-minting-snapshot-store.d.ts.map +1 -0
  67. package/dist/core/tools/non-minting-snapshot-store.js +64 -0
  68. package/dist/core/tools/non-minting-snapshot-store.js.map +1 -0
  69. package/dist/core/tools/powershell.d.ts +2 -2
  70. package/dist/core/tools/powershell.d.ts.map +1 -1
  71. package/dist/core/tools/powershell.js +2 -5
  72. package/dist/core/tools/powershell.js.map +1 -1
  73. package/dist/core/tools/read-document-extract.js.map +1 -1
  74. package/dist/core/tools/render-utils.js.map +1 -1
  75. package/dist/core/tools/write.d.ts +30 -1
  76. package/dist/core/tools/write.d.ts.map +1 -1
  77. package/dist/core/tools/write.js +97 -8
  78. package/dist/core/tools/write.js.map +1 -1
  79. package/dist/index.d.ts +1 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  83. package/dist/modes/interactive/components/task-live-transcript.d.ts +1 -1
  84. package/dist/modes/interactive/components/task-live-transcript.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/task-live-transcript.js +19 -11
  86. package/dist/modes/interactive/components/task-live-transcript.js.map +1 -1
  87. package/dist/modes/interactive/interactive-mode-base.d.ts +1 -0
  88. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode-base.js +3 -1
  90. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  91. package/dist/modes/interactive/interactive-mode-surface.d.ts +1 -1
  92. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  93. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  94. package/dist/modes/interactive/interactive-render-chat.js +28 -4
  95. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  96. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  97. package/dist/modes/interactive/interactive-startup.js +3 -0
  98. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  99. package/dist/modes/interactive-engine/activity-watchdog.d.ts +2 -2
  100. package/dist/modes/interactive-engine/activity-watchdog.d.ts.map +1 -1
  101. package/dist/modes/interactive-engine/activity-watchdog.js.map +1 -1
  102. package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts +3 -1
  103. package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts.map +1 -1
  104. package/dist/modes/interactive-engine/engine-diagnostic-view.js +11 -0
  105. package/dist/modes/interactive-engine/engine-diagnostic-view.js.map +1 -1
  106. package/dist/modes/interactive-engine/isolated-runtime.js +2 -1
  107. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  108. package/dist/modes/rpc/rpc-client-process.d.ts +2 -0
  109. package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-client-process.js +23 -1
  111. package/dist/modes/rpc/rpc-client-process.js.map +1 -1
  112. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  113. package/dist/modes/rpc/rpc-client.js +20 -3
  114. package/dist/modes/rpc/rpc-client.js.map +1 -1
  115. package/dist/utils/markit.d.ts +2 -0
  116. package/dist/utils/markit.d.ts.map +1 -1
  117. package/dist/utils/markit.js +65 -5
  118. package/dist/utils/markit.js.map +1 -1
  119. package/docs/background-tasks.md +15 -11
  120. package/docs/build.md +51 -0
  121. package/docs/changelog.mdx +8 -8
  122. package/docs/compaction/reference.md +267 -0
  123. package/docs/compaction.md +36 -219
  124. package/docs/computer-use.md +35 -0
  125. package/docs/containerization.md +9 -5
  126. package/docs/custom-provider/api-reference.md +145 -0
  127. package/docs/custom-provider/oauth.md +131 -0
  128. package/docs/custom-provider/override.md +34 -0
  129. package/docs/custom-provider/registration.md +163 -0
  130. package/docs/custom-provider/streaming.md +214 -0
  131. package/docs/custom-provider.md +43 -607
  132. package/docs/development.md +87 -0
  133. package/docs/docs.json +199 -76
  134. package/docs/environment-variables.md +1 -1
  135. package/docs/extensions/api-reference.md +1001 -0
  136. package/docs/extensions/authoring.md +676 -0
  137. package/docs/extensions/events.md +793 -0
  138. package/docs/extensions/examples.md +94 -0
  139. package/docs/extensions/ui.md +470 -0
  140. package/docs/extensions.md +270 -2768
  141. package/docs/getting-started/authentication.md +55 -0
  142. package/docs/getting-started/first-session.md +172 -0
  143. package/docs/getting-started/installation.md +154 -0
  144. package/docs/getting-started/project-instructions.md +43 -0
  145. package/docs/guides/configuration.md +65 -0
  146. package/docs/guides/intercom.md +24 -0
  147. package/docs/guides/non-interactive.md +33 -0
  148. package/docs/guides/subagents.md +24 -0
  149. package/docs/guides/workflows.md +27 -0
  150. package/docs/guides.md +41 -0
  151. package/docs/index.md +13 -45
  152. package/docs/intercom/operations.md +178 -0
  153. package/docs/intercom/reference.md +146 -0
  154. package/docs/intercom.md +54 -259
  155. package/docs/json.md +17 -0
  156. package/docs/llama-cpp.md +5 -0
  157. package/docs/models/artificial-analysis-index.md +89 -0
  158. package/docs/models/evals.md +2 -2
  159. package/docs/models/model-selection.md +3 -3
  160. package/docs/models/pareto-efficiency.md +2 -2
  161. package/docs/models/reference.md +601 -0
  162. package/docs/models.md +45 -550
  163. package/docs/packages/authoring.md +88 -0
  164. package/docs/packages/reference.md +43 -0
  165. package/docs/packages.md +28 -107
  166. package/docs/programmatic.md +29 -0
  167. package/docs/prompt-templates.md +11 -0
  168. package/docs/providers/reference.md +23 -0
  169. package/docs/providers.md +10 -15
  170. package/docs/quickstart.md +44 -238
  171. package/docs/reference/cli.md +252 -0
  172. package/docs/reference.md +52 -0
  173. package/docs/rpc/examples.md +64 -0
  174. package/docs/rpc/extension-ui.md +198 -0
  175. package/docs/rpc/protocol.md +1341 -0
  176. package/docs/rpc.md +109 -1346
  177. package/docs/sdk/reference.md +1090 -0
  178. package/docs/sdk.md +56 -1002
  179. package/docs/session-format.md +2 -0
  180. package/docs/sessions.md +6 -10
  181. package/docs/settings.md +6 -45
  182. package/docs/skills/authoring.md +95 -0
  183. package/docs/skills/reference.md +60 -0
  184. package/docs/skills.md +20 -122
  185. package/docs/subagents/authoring.md +37 -0
  186. package/docs/subagents/reference.md +60 -0
  187. package/docs/subagents.md +42 -96
  188. package/docs/terminal-setup.md +1 -1
  189. package/docs/themes/reference.md +176 -0
  190. package/docs/themes.md +21 -135
  191. package/docs/tmux.md +5 -0
  192. package/docs/tools/edit.md +5 -0
  193. package/docs/tools.md +31 -0
  194. package/docs/tui/reference.md +343 -0
  195. package/docs/tui.md +23 -286
  196. package/docs/usage.md +26 -209
  197. package/docs/web-access.md +40 -0
  198. package/docs/windows.md +2 -1
  199. package/docs/workflows/api-reference.md +11 -4
  200. package/docs/workflows/authoring.md +4 -2
  201. package/docs/workflows/builtins.md +2 -2
  202. package/docs/workflows/operations.md +11 -7
  203. package/docs/workflows/reliable-design.md +21 -3
  204. package/docs/workflows/verification.md +8 -0
  205. package/examples/extensions/gondolin/index.ts +116 -148
  206. package/examples/extensions/gondolin/package.json +1 -1
  207. package/examples/extensions/ssh.ts +56 -34
  208. package/npm-shrinkwrap.json +33 -33
  209. 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. Reserve `max` for high-cost-of-error roles or an explicit user request; use `high` for demanding mapping, lifecycle analysis, compatibility, planning, synthesis, triage, and repair; use `medium` for user-impact review and final reporting; and keep deterministic checks as tool nodes with no model call.
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> | max | final approval
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
- * Runs Atomic's built-in tools inside a local Gondolin micro-VM. The host working
5
- * directory is mounted at /workspace in the guest. File changes under
6
- * /workspace write through to the host; other guest filesystem changes are
7
- * isolated to the VM.
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 install --ignore-scripts
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 DEFAULT_GREP_LIMIT = 100;
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
- await vm.fs.writeFile(toGuestPath(localCwd, filePath), content, { encoding: "utf8" });
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]: new RealFSProvider(localCwd),
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
- const tool = createReadTool(GUEST_WORKSPACE, {
425
- operations: createGondolinReadOps(activeVm, localCwd),
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
- const tool = createWriteTool(GUEST_WORKSPACE, {
435
- operations: createGondolinWriteOps(activeVm, localCwd),
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
- const tool = createEditTool(GUEST_WORKSPACE, {
445
- operations: createGondolinEditOps(activeVm, localCwd),
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) };
@@ -6,7 +6,7 @@
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
8
8
  "build": "echo 'nothing to build'",
9
- "check": "echo 'nothing to check'"
9
+ "check": "tsgo -p ../../../test/tsconfig.remote-examples.json"
10
10
  },
11
11
  "dependencies": {
12
12
  "@earendil-works/gondolin": "0.12.0"