craftdriver 1.6.0 → 1.8.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.
Files changed (229) hide show
  1. package/CHANGELOG.md +35 -6
  2. package/README.md +17 -6
  3. package/dist/cli/agentSession.d.ts +62 -0
  4. package/dist/cli/agentSession.d.ts.map +1 -0
  5. package/dist/cli/agentSession.js +100 -0
  6. package/dist/cli/agentSession.js.map +1 -0
  7. package/dist/cli/artifactPaths.d.ts +42 -0
  8. package/dist/cli/artifactPaths.d.ts.map +1 -0
  9. package/dist/cli/artifactPaths.js +144 -0
  10. package/dist/cli/artifactPaths.js.map +1 -0
  11. package/dist/cli/bounds.d.ts +88 -0
  12. package/dist/cli/bounds.d.ts.map +1 -0
  13. package/dist/cli/bounds.js +128 -0
  14. package/dist/cli/bounds.js.map +1 -0
  15. package/dist/cli/client.d.ts +3 -0
  16. package/dist/cli/client.d.ts.map +1 -1
  17. package/dist/cli/client.js +24 -8
  18. package/dist/cli/client.js.map +1 -1
  19. package/dist/cli/daemon.d.ts +3 -0
  20. package/dist/cli/daemon.d.ts.map +1 -1
  21. package/dist/cli/daemon.js +152 -39
  22. package/dist/cli/daemon.js.map +1 -1
  23. package/dist/cli/defaults.d.ts +23 -3
  24. package/dist/cli/defaults.d.ts.map +1 -1
  25. package/dist/cli/defaults.js +50 -5
  26. package/dist/cli/defaults.js.map +1 -1
  27. package/dist/cli/dispatcher.d.ts +54 -11
  28. package/dist/cli/dispatcher.d.ts.map +1 -1
  29. package/dist/cli/dispatcher.js +851 -54
  30. package/dist/cli/dispatcher.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +305 -56
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/init.d.ts +14 -14
  35. package/dist/cli/init.d.ts.map +1 -1
  36. package/dist/cli/init.js +328 -180
  37. package/dist/cli/init.js.map +1 -1
  38. package/dist/cli/journal.d.ts +153 -0
  39. package/dist/cli/journal.d.ts.map +1 -0
  40. package/dist/cli/journal.js +252 -0
  41. package/dist/cli/journal.js.map +1 -0
  42. package/dist/cli/lineReader.d.ts +33 -0
  43. package/dist/cli/lineReader.d.ts.map +1 -0
  44. package/dist/cli/lineReader.js +66 -0
  45. package/dist/cli/lineReader.js.map +1 -0
  46. package/dist/cli/locatorCandidates.d.ts +44 -0
  47. package/dist/cli/locatorCandidates.d.ts.map +1 -0
  48. package/dist/cli/locatorCandidates.js +190 -0
  49. package/dist/cli/locatorCandidates.js.map +1 -0
  50. package/dist/cli/mcp/artifacts.d.ts +39 -1
  51. package/dist/cli/mcp/artifacts.d.ts.map +1 -1
  52. package/dist/cli/mcp/artifacts.js +104 -9
  53. package/dist/cli/mcp/artifacts.js.map +1 -1
  54. package/dist/cli/mcp/bounds.d.ts +65 -0
  55. package/dist/cli/mcp/bounds.d.ts.map +1 -0
  56. package/dist/cli/mcp/bounds.js +124 -0
  57. package/dist/cli/mcp/bounds.js.map +1 -0
  58. package/dist/cli/mcp/params.d.ts +38 -0
  59. package/dist/cli/mcp/params.d.ts.map +1 -0
  60. package/dist/cli/mcp/params.js +174 -0
  61. package/dist/cli/mcp/params.js.map +1 -0
  62. package/dist/cli/mcp/server.d.ts +94 -16
  63. package/dist/cli/mcp/server.d.ts.map +1 -1
  64. package/dist/cli/mcp/server.js +304 -150
  65. package/dist/cli/mcp/server.js.map +1 -1
  66. package/dist/cli/mcp/tools.d.ts +62 -17
  67. package/dist/cli/mcp/tools.d.ts.map +1 -1
  68. package/dist/cli/mcp/tools.js +373 -158
  69. package/dist/cli/mcp/tools.js.map +1 -1
  70. package/dist/cli/pageSemantics.d.ts +42 -0
  71. package/dist/cli/pageSemantics.d.ts.map +1 -0
  72. package/dist/cli/pageSemantics.js +194 -0
  73. package/dist/cli/pageSemantics.js.map +1 -0
  74. package/dist/cli/parseArgs.d.ts +6 -18
  75. package/dist/cli/parseArgs.d.ts.map +1 -1
  76. package/dist/cli/parseArgs.js +689 -45
  77. package/dist/cli/parseArgs.js.map +1 -1
  78. package/dist/cli/protocol.d.ts +8 -0
  79. package/dist/cli/protocol.d.ts.map +1 -1
  80. package/dist/cli/sessionRegistry.d.ts +42 -0
  81. package/dist/cli/sessionRegistry.d.ts.map +1 -0
  82. package/dist/cli/sessionRegistry.js +99 -0
  83. package/dist/cli/sessionRegistry.js.map +1 -0
  84. package/dist/cli/snapshot.d.ts +100 -18
  85. package/dist/cli/snapshot.d.ts.map +1 -1
  86. package/dist/cli/snapshot.js +248 -102
  87. package/dist/cli/snapshot.js.map +1 -1
  88. package/dist/cli/stateStore.d.ts +50 -0
  89. package/dist/cli/stateStore.d.ts.map +1 -0
  90. package/dist/cli/stateStore.js +122 -0
  91. package/dist/cli/stateStore.js.map +1 -0
  92. package/dist/index.d.ts +2 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +2 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/lib/bidi/index.d.ts +1 -1
  97. package/dist/lib/bidi/index.d.ts.map +1 -1
  98. package/dist/lib/bidi/index.js +2 -2
  99. package/dist/lib/bidi/index.js.map +1 -1
  100. package/dist/lib/bidi/logs.d.ts +3 -0
  101. package/dist/lib/bidi/logs.d.ts.map +1 -1
  102. package/dist/lib/bidi/logs.js +7 -0
  103. package/dist/lib/bidi/logs.js.map +1 -1
  104. package/dist/lib/bidi/network.d.ts +14 -1
  105. package/dist/lib/bidi/network.d.ts.map +1 -1
  106. package/dist/lib/bidi/network.js +45 -7
  107. package/dist/lib/bidi/network.js.map +1 -1
  108. package/dist/lib/bidi/storage.d.ts +30 -6
  109. package/dist/lib/bidi/storage.d.ts.map +1 -1
  110. package/dist/lib/bidi/storage.js +190 -45
  111. package/dist/lib/bidi/storage.js.map +1 -1
  112. package/dist/lib/bidi/types.d.ts +7 -2
  113. package/dist/lib/bidi/types.d.ts.map +1 -1
  114. package/dist/lib/browser.d.ts +38 -3
  115. package/dist/lib/browser.d.ts.map +1 -1
  116. package/dist/lib/browser.js +196 -30
  117. package/dist/lib/browser.js.map +1 -1
  118. package/dist/lib/browserContext.d.ts +54 -18
  119. package/dist/lib/browserContext.d.ts.map +1 -1
  120. package/dist/lib/browserContext.js +295 -92
  121. package/dist/lib/browserContext.js.map +1 -1
  122. package/dist/lib/by.d.ts +3 -3
  123. package/dist/lib/by.d.ts.map +1 -1
  124. package/dist/lib/by.js +7 -3
  125. package/dist/lib/by.js.map +1 -1
  126. package/dist/lib/driver.d.ts +7 -0
  127. package/dist/lib/driver.d.ts.map +1 -1
  128. package/dist/lib/driver.js +12 -0
  129. package/dist/lib/driver.js.map +1 -1
  130. package/dist/lib/errors.d.ts +15 -0
  131. package/dist/lib/errors.d.ts.map +1 -1
  132. package/dist/lib/errors.js +15 -0
  133. package/dist/lib/errors.js.map +1 -1
  134. package/dist/lib/initScript.d.ts +9 -0
  135. package/dist/lib/initScript.d.ts.map +1 -0
  136. package/dist/lib/initScript.js +14 -0
  137. package/dist/lib/initScript.js.map +1 -0
  138. package/dist/lib/page.d.ts +22 -0
  139. package/dist/lib/page.d.ts.map +1 -1
  140. package/dist/lib/page.js +48 -0
  141. package/dist/lib/page.js.map +1 -1
  142. package/dist/lib/secureFile.d.ts +8 -0
  143. package/dist/lib/secureFile.d.ts.map +1 -0
  144. package/dist/lib/secureFile.js +67 -0
  145. package/dist/lib/secureFile.js.map +1 -0
  146. package/dist/lib/sessionStateValidation.d.ts +35 -0
  147. package/dist/lib/sessionStateValidation.d.ts.map +1 -0
  148. package/dist/lib/sessionStateValidation.js +223 -0
  149. package/dist/lib/sessionStateValidation.js.map +1 -0
  150. package/dist/lib/timing.d.ts +8 -0
  151. package/dist/lib/timing.d.ts.map +1 -1
  152. package/dist/lib/timing.js +8 -0
  153. package/dist/lib/timing.js.map +1 -1
  154. package/dist/lib/tracing.d.ts +2 -1
  155. package/dist/lib/tracing.d.ts.map +1 -1
  156. package/dist/lib/tracing.js +12 -1
  157. package/dist/lib/tracing.js.map +1 -1
  158. package/dist/lib/visual/antialias.d.ts +41 -0
  159. package/dist/lib/visual/antialias.d.ts.map +1 -0
  160. package/dist/lib/visual/antialias.js +126 -0
  161. package/dist/lib/visual/antialias.js.map +1 -0
  162. package/dist/lib/visual/codec.d.ts +24 -0
  163. package/dist/lib/visual/codec.d.ts.map +1 -0
  164. package/dist/lib/visual/codec.js +11 -0
  165. package/dist/lib/visual/codec.js.map +1 -0
  166. package/dist/lib/visual/compare.d.ts +38 -0
  167. package/dist/lib/visual/compare.d.ts.map +1 -0
  168. package/dist/lib/visual/compare.js +209 -0
  169. package/dist/lib/visual/compare.js.map +1 -0
  170. package/dist/lib/visual/diff.d.ts +18 -0
  171. package/dist/lib/visual/diff.d.ts.map +1 -0
  172. package/dist/lib/visual/diff.js +23 -0
  173. package/dist/lib/visual/diff.js.map +1 -0
  174. package/dist/lib/visual/expectScreenshot.d.ts +65 -0
  175. package/dist/lib/visual/expectScreenshot.d.ts.map +1 -0
  176. package/dist/lib/visual/expectScreenshot.js +287 -0
  177. package/dist/lib/visual/expectScreenshot.js.map +1 -0
  178. package/dist/lib/visual/index.d.ts +10 -0
  179. package/dist/lib/visual/index.d.ts.map +1 -0
  180. package/dist/lib/visual/index.js +9 -0
  181. package/dist/lib/visual/index.js.map +1 -0
  182. package/dist/lib/visual/pixelMath.d.ts +32 -0
  183. package/dist/lib/visual/pixelMath.d.ts.map +1 -0
  184. package/dist/lib/visual/pixelMath.js +70 -0
  185. package/dist/lib/visual/pixelMath.js.map +1 -0
  186. package/dist/lib/visual/pngHeader.d.ts +27 -0
  187. package/dist/lib/visual/pngHeader.d.ts.map +1 -0
  188. package/dist/lib/visual/pngHeader.js +63 -0
  189. package/dist/lib/visual/pngHeader.js.map +1 -0
  190. package/dist/lib/visual/pngjsCodec.d.ts +22 -0
  191. package/dist/lib/visual/pngjsCodec.d.ts.map +1 -0
  192. package/dist/lib/visual/pngjsCodec.js +70 -0
  193. package/dist/lib/visual/pngjsCodec.js.map +1 -0
  194. package/dist/lib/visual/types.d.ts +89 -0
  195. package/dist/lib/visual/types.d.ts.map +1 -0
  196. package/dist/lib/visual/types.js +20 -0
  197. package/dist/lib/visual/types.js.map +1 -0
  198. package/dist/lib/visual/visualError.d.ts +32 -0
  199. package/dist/lib/visual/visualError.d.ts.map +1 -0
  200. package/dist/lib/visual/visualError.js +59 -0
  201. package/dist/lib/visual/visualError.js.map +1 -0
  202. package/dist/lib/wait.d.ts.map +1 -1
  203. package/dist/lib/wait.js +7 -1
  204. package/dist/lib/wait.js.map +1 -1
  205. package/docs/agents.md +56 -61
  206. package/docs/api-reference.md +16 -1
  207. package/docs/browser-api.md +24 -4
  208. package/docs/browser-context.md +18 -12
  209. package/docs/cli.md +346 -26
  210. package/docs/error-codes.md +6 -4
  211. package/docs/getting-started.md +6 -1
  212. package/docs/index.md +1 -1
  213. package/docs/mcp.md +129 -62
  214. package/docs/public/examples/agent-actions.html +48 -0
  215. package/docs/public/examples/agent-debug.html +86 -0
  216. package/docs/public/examples/agent-late-mutation.html +18 -0
  217. package/docs/public/examples/agent-ref-shift.html +33 -0
  218. package/docs/recipes/login-once-reuse-session.md +32 -4
  219. package/docs/screenshots.md +15 -6
  220. package/docs/session-management.md +64 -3
  221. package/docs/visual-testing.md +254 -0
  222. package/docs/why-craftdriver.md +4 -3
  223. package/package.json +5 -1
  224. package/skills/craftdriver/SKILL.md +59 -75
  225. package/skills/craftdriver/agents/openai.yaml +4 -0
  226. package/skills/craftdriver/cheatsheet.md +18 -4
  227. package/skills/craftdriver/cli.md +194 -68
  228. package/skills/craftdriver/patterns.md +14 -8
  229. package/skills/craftdriver/workflow.md +149 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,38 @@
1
+ # [1.8.0](https://github.com/dtopuzov/craftdriver/compare/v1.7.0...v1.8.0) (2026-07-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * **agent:** add AI tooling and reliable auth-state reuse ([2a0f079](https://github.com/dtopuzov/craftdriver/commit/2a0f079b327ffd1545fa0f1919e06045e89422dc))
7
+
8
+ ## Unreleased
9
+
10
+ ### Features
11
+
12
+ * **agent CLI:** add a persistent browser exploration workflow with semantic snapshots, safe temporary refs, live-validated durable locators, manual QA actions, console/network evidence, mocks, traces, and reusable login state
13
+ * **auth state:** restore cookies and multi-origin localStorage before the first navigation on BiDi launch and contexts, with path/object inputs and one-time hydration that preserves later application writes
14
+ * **sessions:** isolate browsers, cookies, pages, refs, and artifacts in named sessions under a project-scoped daemon
15
+ * **MCP:** add a validated stdio tool server with automatic post-action snapshot diffs, bounded responses, and quota-controlled file artifacts for token-efficient context
16
+ * **skill:** add a safe project-local CraftDriver skill installer that teaches coding agents to explore first and turn runtime evidence into maintainable tests
17
+
18
+ ### Bug Fixes
19
+
20
+ * **agent reliability:** fail closed on malformed CLI input, recover failed browser launches, reset browser-owned state on quit, and bound large results, errors, screenshots, and artifact storage
21
+ * **auth state:** validate every restore before mutation, quarantine internal hydration pages from public APIs and telemetry, serialize per-context overlays, reject partial Classic launch restores, and make direct state saves atomic and owner-restricted
22
+ * **pages:** preserve the active page when closing background tabs and cover page activation/closing in BiDi-enabled non-default contexts
23
+
24
+ # [1.7.0](https://github.com/dtopuzov/craftdriver/compare/v1.6.0...v1.7.0) (2026-07-18)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * evaluate wait conditions at zero timeout ([68ec2f9](https://github.com/dtopuzov/craftdriver/commit/68ec2f95148b93ba81f29ecc9ba293eef2265f44))
30
+
31
+
32
+ ### Features
33
+
34
+ * add visual testing ([fd6ce7d](https://github.com/dtopuzov/craftdriver/commit/fd6ce7db8fde26c5ffa80bead9514479547c2a62))
35
+
1
36
  # [1.6.0](https://github.com/dtopuzov/craftdriver/compare/v1.5.0...v1.6.0) (2026-07-16)
2
37
 
3
38
 
@@ -26,12 +61,6 @@
26
61
 
27
62
  * make traces Playwright and Vibium compatible ([c6c0746](https://github.com/dtopuzov/craftdriver/commit/c6c0746dcecc671d1d6d6cd26badf6a8432a7f37))
28
63
 
29
- ## Unreleased
30
-
31
- ### Features
32
-
33
- * **tracing:** export Vibium Player compatible zips from the library and MCP server
34
-
35
64
  # [1.2.0](https://github.com/dtopuzov/craftdriver/compare/v1.1.1...v1.2.0) (2026-07-10)
36
65
 
37
66
 
package/README.md CHANGED
@@ -22,11 +22,12 @@ CraftDriver is for writing boringly reliable automation against real browsers, w
22
22
  - 📡 **Network control** - mock, block, intercept, and wait for browser requests and responses.
23
23
  - 🔐 **Reusable sessions** - save cookies and localStorage, then launch already signed in.
24
24
  - ⏱️ **Virtual clock** - freeze or fast-forward `Date`, timers, and time-sensitive UI.
25
- - ♿ **Accessibility audits** - run axe-core checks on pages, elements, and locators.
25
+ - ♿ **Accessibility audits** - find WCAG 2.2 and Section 508 violations with built-in axe-core checks.
26
+ - 🖼️ **Visual testing** - catch visual regressions against baselines, without fighting anti-aliasing flakiness.
26
27
  - 🧾 **Trace evidence** - capture actions, console output, errors, network events, and screenshots.
27
- - ⚛️ **Electron apps** - drive packaged Electron desktop app renderers, matched to the app's bundled Chromium.
28
28
  - ☁️ **Remote WebDriver** - use the Browser API with a self-hosted Selenium Grid or a cloud provider like [BrowserStack](https://www.browserstack.com/).
29
- - 🤖 **Agent-friendly** - CLI, MCP, and assistant rules when coding agents need the browser.
29
+ - ⚛️ **Electron apps** - drive packaged Electron desktop apps and mock native OS dialogs (open/save, message boxes).
30
+ - 🤖 **Agent-friendly** - CLI, a project-local skill, and optional MCP when coding agents need the browser.
30
31
 
31
32
  ## Choose Your Path
32
33
 
@@ -59,13 +60,17 @@ No separate chromedriver or geckodriver setup for normal use. CraftDriver resolv
59
60
 
60
61
  ## AI Agent Bonus
61
62
 
62
- If your coding agent can use a shell or MCP, CraftDriver can give it a real browser too. Same selectors, same behavior, same error codes as the library API.
63
+ If your coding agent can use a shell, CraftDriver can give it a real browser
64
+ for exploring the app before it writes tests. Install the project-local skill;
65
+ it never changes repository instruction files.
63
66
 
64
67
  ```bash
65
- npx craftdriver init agents
66
- claude mcp add craftdriver -- npx -y craftdriver mcp
68
+ npx craftdriver init codex
67
69
  ```
68
70
 
71
+ MCP is optional. `npx craftdriver init codex --mcp` prints a project-pinned
72
+ manual configuration snippet without reading or changing Codex config.
73
+
69
74
  See the [AI agent guide](https://dtopuzov.github.io/craftdriver/agents) when you need the full setup.
70
75
 
71
76
  ## What It Can Do
@@ -95,6 +100,11 @@ await Browser.launch({
95
100
  await browser.a11y.check();
96
101
  ```
97
102
 
103
+ Launch-time state restores cookies plus multi-origin localStorage on supported
104
+ WebDriver BiDi sessions (Chrome/Chromium and Firefox). WebDriver Classic has an
105
+ explicit single-active-origin fallback after navigation; see
106
+ [Session management](https://dtopuzov.github.io/craftdriver/session-management#browser-and-transport-support).
107
+
98
108
  ## Feature Guide
99
109
 
100
110
  | Area | What you get | Learn more |
@@ -111,6 +121,7 @@ await browser.a11y.check();
111
121
  | Network mocking | Mock, block, intercept, and wait for browser requests and responses | [Network mocking](https://dtopuzov.github.io/craftdriver/network) |
112
122
  | Console and errors | Capture console output and fail tests on JavaScript errors | [Console logs](https://dtopuzov.github.io/craftdriver/browser-logs) |
113
123
  | Screenshots | Page and element screenshots for tests and debugging | [Screenshots](https://dtopuzov.github.io/craftdriver/screenshots) |
124
+ | Visual testing | Compare screenshots to baselines with tolerances, retries, and diff artifacts | [Visual testing](https://dtopuzov.github.io/craftdriver/visual-testing) |
114
125
  | Mobile and emulation | Device presets, viewport, locale, timezone, offline, reduced motion | [Mobile emulation](https://dtopuzov.github.io/craftdriver/mobile-emulation), [Emulation](https://dtopuzov.github.io/craftdriver/emulation) |
115
126
  | Browser contexts | Isolated profiles for multi-user and multi-session testing | [Browser contexts](https://dtopuzov.github.io/craftdriver/browser-context) |
116
127
  | Tracing | Crash-resilient NDJSON plus Vibium Player compatible trace zips | [Tracing](https://dtopuzov.github.io/craftdriver/tracing) |
@@ -0,0 +1,62 @@
1
+ import { Browser, type LaunchOptions } from '../lib/browser.js';
2
+ import { type BrowserHandle, type DispatchContext } from './dispatcher.js';
3
+ import { type SnapshotShape } from './snapshot.js';
4
+ export interface AgentCommand {
5
+ cmd: string;
6
+ args?: Record<string, unknown>;
7
+ }
8
+ export type AgentResult = unknown;
9
+ /**
10
+ * A command's result plus the one post-action snapshot it earned.
11
+ *
12
+ * Transports that show the agent what changed (MCP, the CLI's pretty
13
+ * mode) read `delta`; everything else uses {@link AgentSession.run} and
14
+ * ignores it.
15
+ */
16
+ export interface AgentDetailedResult {
17
+ value: AgentResult;
18
+ /** Rendered diff (or full snapshot on a document change). */
19
+ delta?: string;
20
+ snapshot?: SnapshotShape | null;
21
+ }
22
+ export type AgentDispatcher = (ctx: DispatchContext, cmd: string, args?: Record<string, unknown>) => Promise<AgentResult>;
23
+ export interface AgentSessionRunner {
24
+ run(command: AgentCommand): Promise<AgentResult>;
25
+ runDetailed(command: AgentCommand): Promise<AgentDetailedResult>;
26
+ close(): Promise<void>;
27
+ }
28
+ interface AgentSessionOptions {
29
+ launchOptions: LaunchOptions;
30
+ /** Safe name for session-owned artifacts such as the implicit screenshot. */
31
+ artifactName?: string;
32
+ launch?: () => Promise<Browser>;
33
+ dispatcher?: AgentDispatcher;
34
+ /** Internal lifecycle seam used by transport/session tests. */
35
+ handle?: BrowserHandle;
36
+ /** Set false to suppress automatic post-action snapshots. */
37
+ autoSnapshot?: boolean;
38
+ }
39
+ /** A lazy browser session whose accepted commands execute in FIFO order. */
40
+ export declare class AgentSession implements AgentSessionRunner {
41
+ private readonly ctx;
42
+ private readonly journal;
43
+ private readonly dispatcher;
44
+ private readonly autoSnapshot;
45
+ private tail;
46
+ private state;
47
+ private closePromise;
48
+ constructor(options: AgentSessionOptions);
49
+ run(command: AgentCommand): Promise<AgentResult>;
50
+ /**
51
+ * Execute a command and, for a successful mutation, capture the one
52
+ * post-action snapshot in the *same* queue slot.
53
+ *
54
+ * Action and snapshot are deliberately one operation: if they were two,
55
+ * a concurrent command could land between them and the agent would be
56
+ * shown a diff attributed to the wrong action.
57
+ */
58
+ runDetailed(command: AgentCommand): Promise<AgentDetailedResult>;
59
+ close(): Promise<void>;
60
+ }
61
+ export {};
62
+ //# sourceMappingURL=agentSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentSession.d.ts","sourceRoot":"","sources":["../../src/cli/agentSession.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9F,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,WAAW,CAAC;IACnB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,UAAU,mBAAmB;IAC3B,aAAa,EAAE,aAAa,CAAC;IAC7B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,4EAA4E;AAC5E,qBAAa,YAAa,YAAW,kBAAkB;IACrD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,IAAI,CAAoC;IAChD,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,YAAY,CAA8B;gBAEtC,OAAO,EAAE,mBAAmB;IAwBxC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAIhD;;;;;;;OAOG;IACH,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAyChE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBvB"}
@@ -0,0 +1,100 @@
1
+ import { Browser } from '../lib/browser.js';
2
+ import { CraftdriverError, ErrorCode } from '../lib/errors.js';
3
+ import { createBrowserHandle, dispatch, isMutating, resetBrowserOwnedState, } from './dispatcher.js';
4
+ import { takeSnapshot, peekDialog, SnapshotTracker } from './snapshot.js';
5
+ import { SessionJournal } from './journal.js';
6
+ /** A lazy browser session whose accepted commands execute in FIFO order. */
7
+ export class AgentSession {
8
+ ctx;
9
+ journal;
10
+ dispatcher;
11
+ autoSnapshot;
12
+ tail = Promise.resolve();
13
+ state = 'open';
14
+ closePromise = null;
15
+ constructor(options) {
16
+ const baseLaunch = options.launch ?? (() => Browser.launch(options.launchOptions));
17
+ const journal = new SessionJournal();
18
+ // Attach inside the launch step, not after the first command: the handle
19
+ // must never hand out a browser whose console and network events are
20
+ // already going unrecorded. Events during the first navigation are the
21
+ // ones an agent most often asks about.
22
+ const launch = async () => {
23
+ const browser = await baseLaunch();
24
+ journal.attach(browser);
25
+ return browser;
26
+ };
27
+ this.journal = journal;
28
+ this.ctx = {
29
+ handle: options.handle ?? createBrowserHandle(launch),
30
+ launchOptions: options.launchOptions,
31
+ artifactName: options.artifactName,
32
+ tracker: new SnapshotTracker(),
33
+ journal,
34
+ };
35
+ this.dispatcher = options.dispatcher ?? dispatch;
36
+ this.autoSnapshot = options.autoSnapshot !== false;
37
+ }
38
+ run(command) {
39
+ return this.runDetailed(command).then((detailed) => detailed.value);
40
+ }
41
+ /**
42
+ * Execute a command and, for a successful mutation, capture the one
43
+ * post-action snapshot in the *same* queue slot.
44
+ *
45
+ * Action and snapshot are deliberately one operation: if they were two,
46
+ * a concurrent command could land between them and the agent would be
47
+ * shown a diff attributed to the wrong action.
48
+ */
49
+ runDetailed(command) {
50
+ if (this.state !== 'open') {
51
+ return Promise.reject(new CraftdriverError(ErrorCode.STATE_INVALID, 'AgentSession is closing or closed; it cannot accept new commands'));
52
+ }
53
+ const result = this.tail.then(async () => {
54
+ const value = await this.dispatcher(this.ctx, command.cmd, command.args ?? {});
55
+ if (!this.autoSnapshot || !isMutating(command.cmd))
56
+ return { value };
57
+ // Only if a browser is actually up: a fake dispatcher (or a command
58
+ // that never needed a browser) must not cause a launch here.
59
+ const browser = this.ctx.handle.peek();
60
+ if (!browser)
61
+ return { value };
62
+ // A modal dialog blocks script execution, so snapshotting behind one
63
+ // stalls for the full WebDriver script timeout (measured: 60s) and
64
+ // then fails anyway. Probing costs ~2ms and tells the agent the one
65
+ // thing it actually needs to know here.
66
+ const dialog = await peekDialog(browser);
67
+ if (dialog !== null) {
68
+ return {
69
+ value,
70
+ delta: `dialog open: ${dialog}\n(snapshot skipped — accept or dismiss it first)`,
71
+ };
72
+ }
73
+ const snap = await takeSnapshot(browser, this.ctx.tracker.minRef);
74
+ // A failed command threw above, so the baseline only ever advances
75
+ // on success.
76
+ const delta = this.ctx.tracker.advance(snap);
77
+ return { value, ...(delta ? { delta } : {}), snapshot: snap };
78
+ });
79
+ this.tail = result.then(() => undefined, () => undefined);
80
+ return result;
81
+ }
82
+ close() {
83
+ if (this.closePromise)
84
+ return this.closePromise;
85
+ this.state = 'closing';
86
+ this.closePromise = this.tail.then(() => this.ctx.handle.close()).then(() => {
87
+ this.state = 'closed';
88
+ // Same teardown `quit` performs: releases journal listeners and any
89
+ // caller blocked in a wait, which would otherwise sit out its full
90
+ // timeout against a dead browser, and drops trace and mock metadata
91
+ // that described the browser just closed.
92
+ resetBrowserOwnedState(this.ctx);
93
+ }, (error) => {
94
+ this.closePromise = null;
95
+ throw error;
96
+ });
97
+ return this.closePromise;
98
+ }
99
+ }
100
+ //# sourceMappingURL=agentSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentSession.js","sourceRoot":"","sources":["../../src/cli/agentSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ export declare const MAX_ARTIFACT_NAME = 64;
2
+ export declare function validateArtifactName(raw: unknown, kind: string, root: string): string;
3
+ /**
4
+ * Full path for `<name><suffix>` inside `root`, proven to stay there.
5
+ *
6
+ * Creates the root (mode 0700) as a side effect so readers and writers agree
7
+ * on where it is even on a first run.
8
+ */
9
+ export declare function resolveArtifactPath(opts: {
10
+ root: string;
11
+ name: string;
12
+ /** `.json` for state; empty for a trace directory. */
13
+ suffix?: string;
14
+ /** Used in error messages: `state`, `trace`. */
15
+ kind: string;
16
+ }): Promise<string>;
17
+ /**
18
+ * Prepare a temp path next to `target`, pre-created with owner-only
19
+ * permissions.
20
+ *
21
+ * Opened `wx` (`O_EXCL`), which is what makes the mode guarantee real: plain
22
+ * `w` follows an existing symlink and ignores the mode argument, so a
23
+ * leftover or planted entry would silently redirect the write and leave the
24
+ * mode unset. The random suffix keeps concurrent writers from colliding.
25
+ */
26
+ export declare function prepareTempPath(target: string): Promise<string>;
27
+ /** Move a fully written temp file into place. */
28
+ export declare function commitFile(tmp: string, target: string): Promise<void>;
29
+ /** Best-effort cleanup for a failed write. */
30
+ export declare function discardTempFile(tmp: string): Promise<void>;
31
+ /**
32
+ * Root for a CLI-owned artifact kind, overridable by env.
33
+ *
34
+ * Anchored to the project root rather than `process.cwd()`. The daemon is a
35
+ * long-lived process with its own working directory, so resolving from `cwd`
36
+ * meant saved state and traces landed wherever the daemon happened to be
37
+ * started — and, before the socket was project-scoped, in a different project
38
+ * entirely. Anchoring also makes a run from a subdirectory address the same
39
+ * artifacts as one from the repository root.
40
+ */
41
+ export declare function artifactRoot(kind: 'state' | 'traces' | 'screenshots', envVar: string, env?: NodeJS.ProcessEnv): string;
42
+ //# sourceMappingURL=artifactPaths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactPaths.d.ts","sourceRoot":"","sources":["../../src/cli/artifactPaths.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6BrF;AA4BD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrE;AAED,iDAAiD;AACjD,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ3E;AAED,8CAA8C;AAC9C,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAKnI"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Shared path safety for CLI-owned artifact directories.
3
+ *
4
+ * Saved login state and traces both write files a name chooses, under a root
5
+ * craftdriver owns. The rules are identical and security-relevant — a name is
6
+ * never a path, and a symlink must not widen the root — so they live here once
7
+ * rather than being reimplemented per artifact kind. The one place this
8
+ * codebase already copied helpers between modules (`locatorCandidates` from
9
+ * `snapshot`) is recorded as a mistake because the copies then diverged.
10
+ *
11
+ * Callers supply the root and the kind label; the rules do not vary.
12
+ */
13
+ import fs from 'node:fs/promises';
14
+ import path from 'node:path';
15
+ import { randomBytes } from 'node:crypto';
16
+ import { CraftdriverError, ErrorCode } from '../lib/errors.js';
17
+ import { projectRoot } from './defaults.js';
18
+ /**
19
+ * Accepted artifact names.
20
+ *
21
+ * Deliberately narrower than "a valid filename": with no `.`, `/` or `\` a
22
+ * name cannot traverse or hide, and a required leading alphanumeric means it
23
+ * can never be read as a flag.
24
+ */
25
+ const ARTIFACT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
26
+ export const MAX_ARTIFACT_NAME = 64;
27
+ export function validateArtifactName(raw, kind, root) {
28
+ if (typeof raw !== 'string' || raw.length === 0) {
29
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `${kind}: a name is required`, {
30
+ hint: `pass a bare name, e.g. \`${kind} save alice\``,
31
+ });
32
+ }
33
+ // Reported apart from the charset rule: a caller passing a path is making a
34
+ // different mistake than one passing "my file", and the fix differs.
35
+ if (raw.includes('/') || raw.includes('\\') || raw.includes('.')) {
36
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `${kind}: ${JSON.stringify(raw)} is a name, not a path`, {
37
+ detail: { root },
38
+ hint: `pass a bare name like \`alice\`; files are kept under the ${kind} root`,
39
+ });
40
+ }
41
+ if (raw.length > MAX_ARTIFACT_NAME || !ARTIFACT_NAME.test(raw)) {
42
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `${kind}: invalid name ${JSON.stringify(raw)}`, {
43
+ detail: { maxLength: MAX_ARTIFACT_NAME },
44
+ hint: `must start with a letter or digit and use only letters, digits, "-" and "_" (max ${MAX_ARTIFACT_NAME} chars)`,
45
+ });
46
+ }
47
+ return raw;
48
+ }
49
+ function isInside(root, target) {
50
+ const rel = path.relative(root, target);
51
+ return rel.length > 0 && !rel.startsWith('..') && !path.isAbsolute(rel);
52
+ }
53
+ /**
54
+ * Resolve the real path of the deepest ancestor of `target` that exists.
55
+ *
56
+ * Checking `realpath(target)` alone is not enough: the target usually does not
57
+ * exist yet when writing, and a symlinked *directory* in the middle of the
58
+ * path is exactly the escape this guards against.
59
+ */
60
+ async function realpathOfExistingAncestor(target) {
61
+ let current = target;
62
+ for (;;) {
63
+ try {
64
+ return await fs.realpath(current);
65
+ }
66
+ catch {
67
+ const parent = path.dirname(current);
68
+ // Reached the filesystem root without finding anything that exists.
69
+ if (parent === current)
70
+ return current;
71
+ current = parent;
72
+ }
73
+ }
74
+ }
75
+ /**
76
+ * Full path for `<name><suffix>` inside `root`, proven to stay there.
77
+ *
78
+ * Creates the root (mode 0700) as a side effect so readers and writers agree
79
+ * on where it is even on a first run.
80
+ */
81
+ export async function resolveArtifactPath(opts) {
82
+ const { root, kind } = opts;
83
+ const validated = validateArtifactName(opts.name, kind, root);
84
+ const target = path.join(root, `${validated}${opts.suffix ?? ''}`);
85
+ await fs.mkdir(root, { recursive: true, mode: 0o700 });
86
+ const realRoot = await fs.realpath(root);
87
+ const realAncestor = await realpathOfExistingAncestor(target);
88
+ // Either the root itself (target not created yet) or the target, which must
89
+ // resolve to something inside the root.
90
+ if (realAncestor !== realRoot && !isInside(realRoot, realAncestor)) {
91
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `${kind}: ${JSON.stringify(opts.name)} resolves outside the ${kind} root`, {
92
+ detail: { root: realRoot },
93
+ hint: `a symlink in the ${kind} directory points elsewhere; remove it`,
94
+ });
95
+ }
96
+ return target;
97
+ }
98
+ /**
99
+ * Prepare a temp path next to `target`, pre-created with owner-only
100
+ * permissions.
101
+ *
102
+ * Opened `wx` (`O_EXCL`), which is what makes the mode guarantee real: plain
103
+ * `w` follows an existing symlink and ignores the mode argument, so a
104
+ * leftover or planted entry would silently redirect the write and leave the
105
+ * mode unset. The random suffix keeps concurrent writers from colliding.
106
+ */
107
+ export async function prepareTempPath(target) {
108
+ const tmp = `${target}.tmp-${process.pid}-${randomBytes(6).toString('hex')}`;
109
+ const handle = await fs.open(tmp, 'wx', 0o600);
110
+ await handle.close();
111
+ return tmp;
112
+ }
113
+ /** Move a fully written temp file into place. */
114
+ export async function commitFile(tmp, target) {
115
+ try {
116
+ await fs.chmod(tmp, 0o600);
117
+ await fs.rename(tmp, target);
118
+ }
119
+ catch (err) {
120
+ await fs.rm(tmp, { force: true }).catch(() => { });
121
+ throw err;
122
+ }
123
+ }
124
+ /** Best-effort cleanup for a failed write. */
125
+ export async function discardTempFile(tmp) {
126
+ await fs.rm(tmp, { force: true }).catch(() => { });
127
+ }
128
+ /**
129
+ * Root for a CLI-owned artifact kind, overridable by env.
130
+ *
131
+ * Anchored to the project root rather than `process.cwd()`. The daemon is a
132
+ * long-lived process with its own working directory, so resolving from `cwd`
133
+ * meant saved state and traces landed wherever the daemon happened to be
134
+ * started — and, before the socket was project-scoped, in a different project
135
+ * entirely. Anchoring also makes a run from a subdirectory address the same
136
+ * artifacts as one from the repository root.
137
+ */
138
+ export function artifactRoot(kind, envVar, env = process.env) {
139
+ const override = env[envVar];
140
+ return override && override.length > 0
141
+ ? path.resolve(override)
142
+ : path.resolve(projectRoot(), '.craftdriver', kind);
143
+ }
144
+ //# sourceMappingURL=artifactPaths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactPaths.js","sourceRoot":"","sources":["../../src/cli/artifactPaths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,6BAA6B,CAAC;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,IAAY,EAAE,IAAY;IAC3E,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,gBAAgB,CAAC,SAAS,CAAC,gBAAgB,EAAE,GAAG,IAAI,sBAAsB,EAAE;YACpF,IAAI,EAAE,4BAA4B,IAAI,eAAe;SACtD,CAAC,CAAC;IACL,CAAC;IACD,4EAA4E;IAC5E,qEAAqE;IACrE,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,gBAAgB,CACxB,SAAS,CAAC,gBAAgB,EAC1B,GAAG,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,wBAAwB,EACvD;YACE,MAAM,EAAE,EAAE,IAAI,EAAE;YAChB,IAAI,EAAE,6DAA6D,IAAI,OAAO;SAC/E,CACF,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,GAAG,iBAAiB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,gBAAgB,CACxB,SAAS,CAAC,gBAAgB,EAC1B,GAAG,IAAI,kBAAkB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAC9C;YACE,MAAM,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE;YACxC,IAAI,EAAE,oFAAoF,iBAAiB,SAAS;SACrH,CACF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,0BAA0B,CAAC,MAAc;IACtD,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACrC,oEAAoE;YACpE,IAAI,MAAM,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC;YACvC,OAAO,GAAG,MAAM,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAOzC;IACC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAC5B,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;IAEnE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEvD,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,YAAY,GAAG,MAAM,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAC9D,4EAA4E;IAC5E,wCAAwC;IACxC,IAAI,YAAY,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,gBAAgB,CACxB,SAAS,CAAC,gBAAgB,EAC1B,GAAG,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,IAAI,OAAO,EACzE;YACE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC1B,IAAI,EAAE,oBAAoB,IAAI,wCAAwC;SACvE,CACF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc;IAClD,MAAM,GAAG,GAAG,GAAG,MAAM,QAAQ,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iDAAiD;AACjD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,MAAc;IAC1D,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,8CAA8C;AAC9C,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAwC,EAAE,MAAc,EAAE,MAAyB,OAAO,CAAC,GAAG;IACzH,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QACpC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACxB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Transport-neutral output bounds for the agent surface.
3
+ *
4
+ * Bounded, token-efficient output is a product contract, not an MCP feature.
5
+ * It was only ever enforced on the MCP envelope, so the same `eval` that
6
+ * produced a 32 KB-capped MCP result produced a ~1 MB daemon response, read
7
+ * into an unbounded client buffer, printed in full. The bound belongs where
8
+ * the value is produced, so every transport inherits it.
9
+ *
10
+ * Truncation is always visible: a shortened value carries `truncated` and the
11
+ * byte counts, so an agent can tell a short answer from a shortened one.
12
+ * Cuts land on code-point boundaries — splitting a multi-byte sequence would
13
+ * emit a replacement character, or an unparseable frame.
14
+ */
15
+ /** Cap on a single command result. */
16
+ export declare const DEFAULT_MAX_RESULT_BYTES: number;
17
+ /** Cap on `--ephemeral` stdin, which is read whole into memory. */
18
+ export declare const MAX_STDIN_BYTES: number;
19
+ /** Largest `--limit` a list-returning command will honour. */
20
+ export declare const MAX_LIST_LIMIT = 1000;
21
+ /**
22
+ * Largest `--limit` a *character*-limited command will honour.
23
+ *
24
+ * Separate from the list cap because they count different things: `text`
25
+ * takes a character budget whose default is already 2000, so clamping it to
26
+ * the row cap silently halved it. The result is still bounded by
27
+ * `resolveMaxResultBytes` regardless of what is asked for here.
28
+ */
29
+ export declare const MAX_TEXT_CHARS = 100000;
30
+ export declare function resolveMaxResultBytes(env?: NodeJS.ProcessEnv): number;
31
+ export declare function utf8Bytes(text: string): number;
32
+ /**
33
+ * Truncate to at most `maxBytes` of UTF-8, never splitting a character.
34
+ *
35
+ * Iterates code points (so a surrogate pair is kept whole) and stops before
36
+ * the budget is exceeded. A string already within budget is returned as-is.
37
+ */
38
+ export declare function truncateUtf8(text: string, maxBytes: number): string;
39
+ export interface BoundedValue {
40
+ value: unknown;
41
+ truncated: true;
42
+ /** Bytes the full value would have occupied once serialized. */
43
+ totalBytes: number;
44
+ /** Bytes actually retained. */
45
+ retainedBytes: number;
46
+ }
47
+ /**
48
+ * Bound one command result value.
49
+ *
50
+ * A string is truncated in place, which keeps the common case (`eval`
51
+ * returning a long string, `text` on a large element) readable. Anything else
52
+ * is serialized and replaced by a bounded preview, because trimming an
53
+ * arbitrary object structurally would produce something that still looks like
54
+ * a complete answer.
55
+ */
56
+ export declare function boundValue(value: unknown, maxBytes?: number): unknown | BoundedValue;
57
+ /**
58
+ * Clamp a caller-supplied list limit.
59
+ *
60
+ * A negative limit silently returned nothing and a huge one defeated the
61
+ * paging that keeps list output bounded, so both are pulled into range rather
62
+ * than rejected — the agent asked for a list, and one it can read is a better
63
+ * answer than an error.
64
+ */
65
+ export declare function clampLimit(raw: number, fallback: number, max?: number): number;
66
+ /**
67
+ * One bounded string result.
68
+ *
69
+ * `text`, `attr` and `value` all return a string that the page controls, and
70
+ * all three reported it differently once bounding was added — a bare string
71
+ * under budget, a nested object over it. This keeps the shape identical
72
+ * either way, so a caller reads `.value` and checks `.truncated` without
73
+ * having to type-test first.
74
+ */
75
+ export interface BoundedString {
76
+ value: string | null;
77
+ truncated: boolean;
78
+ /** Length in characters of the untruncated string. */
79
+ total: number;
80
+ }
81
+ export interface BoundStringOptions {
82
+ /** Transport-neutral byte ceiling. */
83
+ maxBytes?: number;
84
+ /** Optional caller-visible character limit, such as `text --limit`. */
85
+ maxChars?: number;
86
+ }
87
+ export declare function boundString(raw: string | null, options?: BoundStringOptions): BoundedString;
88
+ //# sourceMappingURL=bounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounds.d.ts","sourceRoot":"","sources":["../../src/cli/bounds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,sCAAsC;AACtC,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAElD,mEAAmE;AACnE,eAAO,MAAM,eAAe,QAAc,CAAC;AAE3C,8DAA8D;AAC9D,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,SAAU,CAAC;AAEtC,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAOlF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAanE;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAA0B,GAAG,OAAO,GAAG,YAAY,CA4BrG;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAiB,GAAG,MAAM,CAKtF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE,kBAAuB,GAAG,aAAa,CAO/F"}