@radiiplus/qlyx 1.9.6

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/README.md ADDED
@@ -0,0 +1,788 @@
1
+ # Qlyx
2
+
3
+ Qlyx is a workspace-aware local development bridge for AI chat agents. One
4
+ user-level daemon serves every registered project, while each project keeps a
5
+ compact provider-neutral state under `.agent/`. Every direct command writes a
6
+ correlated JSON response. Errors are JSON on standard error with a nonzero exit
7
+ code.
8
+
9
+ It uses the host platform's native path implementation. Absolute and relative
10
+ paths therefore work with Linux, macOS, Windows Command Prompt, and PowerShell.
11
+ Quote paths that contain spaces according to the shell being used.
12
+
13
+ ## Workspace daemon
14
+
15
+ After the package is published, initialize a project from its root:
16
+
17
+ ```bash
18
+ npx @radiiplus/qlyx init
19
+ ```
20
+
21
+ `init` creates `.agent/state.json`, initializes the project-owned agent files and
22
+ `qlyx.prompts.json`, registers the canonical root in the user-level Qlyx registry,
23
+ and starts the background daemon when it is offline. If the daemon is already
24
+ running, the new workspace is attached to that process. A second daemon is not
25
+ started.
26
+
27
+ ```bash
28
+ qlyx init [path] [--name name] [--no-start]
29
+ qlyx list [--json]
30
+ qlyx guide [path] [--json]
31
+ qlyx status [--json]
32
+ qlyx use <id|name>
33
+ qlyx start [path]
34
+ qlyx stop [path]
35
+ qlyx remove [path]
36
+ qlyx logs
37
+ qlyx doctor
38
+ qlyx daemon start|stop
39
+ ```
40
+
41
+ `qlyx stop` deactivates only the selected workspace and terminates its active
42
+ jobs; the shared daemon and other workspaces remain available. `qlyx remove`
43
+ unregisters the workspace without deleting project files. Use `qlyx daemon stop`
44
+ to stop the process itself.
45
+
46
+ The registry, daemon metadata, and daemon log live under the platform's per-user
47
+ state directory (`$XDG_STATE_HOME/qlyx` or `~/.local/state/qlyx` on Linux). Set
48
+ `QLYX_STATE_DIR` to relocate the parent state directory. The daemon listens on the
49
+ configured fixed endpoint, so the extension discovers all active workspaces with
50
+ one connection.
51
+
52
+ The npm artifact is built by `npm run build`; `npm pack --dry-run` verifies the
53
+ published `qlyx` executable and included files. Publishing remains an explicit
54
+ release action:
55
+
56
+ ```bash
57
+ npm login
58
+ npm publish
59
+ ```
60
+
61
+ ## Configuration
62
+
63
+ Defaults, response limits, and command names are stored in `config.json`:
64
+
65
+ ```json
66
+ {
67
+ "lines": {
68
+ "size": 200,
69
+ "limit": 500
70
+ },
71
+ "items": {
72
+ "size": 200,
73
+ "limit": 500
74
+ },
75
+ "exec": {
76
+ "timeout": 30000,
77
+ "limit": 120000,
78
+ "bytes": 1048576,
79
+ "store": 16777216,
80
+ "shell": false
81
+ },
82
+ "edit": {
83
+ "bytes": 10485760
84
+ },
85
+ "create": {
86
+ "bytes": 10485760
87
+ },
88
+ "engine": {
89
+ "limit": 8,
90
+ "wait": 30000,
91
+ "batch": 20
92
+ },
93
+ "server": {
94
+ "host": "127.0.0.1",
95
+ "port": 14783,
96
+ "path": "/socket",
97
+ "bytes": 1048576,
98
+ "token": ""
99
+ },
100
+ "commands": {
101
+ "batch": "batch",
102
+ "autonomyState": "autonomy_state",
103
+ "autonomyUpdate": "autonomy_update",
104
+ "autonomyEvent": "autonomy_event",
105
+ "list": "list",
106
+ "read": "read",
107
+ "exec": "exec",
108
+ "create": "create",
109
+ "edit": "edit",
110
+ "delete": "delete",
111
+ "status": "status",
112
+ "session": "session",
113
+ "serve": "serve",
114
+ "help": "help"
115
+ }
116
+ }
117
+ ```
118
+
119
+ `size` is the default page size and `limit` is the largest value accepted from a
120
+ command. `exec.timeout` is the normal command timeout, `exec.limit` is the largest
121
+ timeout accepted from the CLI. `exec.bytes` is the amount of stdout and stderr
122
+ returned per snapshot, while `exec.store` is the amount retained per stream for
123
+ later pages. `edit.bytes` is the largest file accepted for region replacement.
124
+ `create.bytes` is the largest initial file content accepted by creation.
125
+ `engine.limit` is the number of operations that can run concurrently,
126
+ `engine.wait` is the largest non-terminating progress wait, and `engine.batch`
127
+ is the largest accepted operation group. The edit limit applies
128
+ to both the current file and the fully patched result. Command values can be
129
+ changed without editing the TypeScript source. For example, changing
130
+ `commands.read` to `view` makes `view` the file command.
131
+
132
+ Use a different configuration file for one invocation:
133
+
134
+ ```bash
135
+ npx tsx src/tool.ts view "/path/to/file" --config "/path/to/config.json"
136
+ ```
137
+
138
+ ## Requirements
139
+
140
+ - Node.js 22.18 or newer
141
+
142
+ The included `tsx` runner executes the TypeScript source without a build step.
143
+ Run `npm install` once before using the commands.
144
+
145
+ ## Operations
146
+
147
+ Every operation has an `id` and `action`. A direct CLI invocation generates an ID
148
+ unless `--id` supplies one:
149
+
150
+ ```bash
151
+ npx tsx src/tool.ts read "/path/to/file" --id source-1
152
+ ```
153
+
154
+ Success and failure use the same correlation envelope:
155
+
156
+ ```json
157
+ {
158
+ "id": "source-1",
159
+ "action": "read",
160
+ "ok": true,
161
+ "data": {}
162
+ }
163
+ ```
164
+
165
+ ```json
166
+ {
167
+ "id": "source-1",
168
+ "action": "read",
169
+ "ok": false,
170
+ "error": {
171
+ "code": "MISSING",
172
+ "message": "Path does not exist"
173
+ }
174
+ }
175
+ ```
176
+
177
+ Operation-specific results described below are contained in `data`.
178
+
179
+ ### Concurrent mode
180
+
181
+ Start the persistent engine:
182
+
183
+ ```bash
184
+ npx tsx src/tool.ts serve
185
+ ```
186
+
187
+ Write one JSON request per line to standard input. The engine emits one compact
188
+ JSON response per line as soon as each operation responds:
189
+
190
+ ```json
191
+ {"id":"tree","action":"list","path":"/path/to/project"}
192
+ {"id":"tests","action":"exec","words":["npm","test"],"cwd":"/path/to/project"}
193
+ {"id":"source","action":"read","path":"/path/to/file","size":100}
194
+ ```
195
+
196
+ Responses may arrive in a different order because operations run concurrently.
197
+ Use `id`, not line order, to correlate them. IDs identify one immutable request for
198
+ the lifetime of the engine session. Repeating the same ID and payload returns the
199
+ recorded reply without executing again; reusing an ID for a different payload is
200
+ rejected. Once `engine.limit` operations are active, additional work is registered
201
+ as `queued` before it waits for a slot. Errors are emitted on standard output in
202
+ this mode so the NDJSON response stream remains complete.
203
+
204
+ ### Batched operations
205
+
206
+ Streaming transports accept one batch envelope containing up to `engine.batch`
207
+ child operations:
208
+
209
+ ```json
210
+ {"id":"inspect","action":"batch","operations":[{"id":"tree","action":"list","path":"src"},{"id":"tests","action":"exec","words":["npm","test"],"cwd":"."}]}
211
+ ```
212
+
213
+ Each child has its own unique ID and normal action fields. Qlyx routes each child
214
+ from its own action, so one batch may mix independent `autonomy_state`,
215
+ `autonomy_update`, `autonomy_event`, filesystem, and exec work without switching
216
+ top-level actions. Durable-state and filesystem children run in listed order so
217
+ mutations cannot race each other. Exec children use the normal bounded-concurrency
218
+ scheduler. Nested batches, session requests, status polls, and cancellation
219
+ requests are rejected.
220
+
221
+ The transport can emit more than one reply with the batch ID. Every reply groups
222
+ the children that have finished at that moment:
223
+
224
+ ```json
225
+ {"id":"inspect","action":"batch","ok":true,"data":{"total":2,"completed":1,"pending":1,"failed":0,"complete":false,"results":[{"id":"tree","action":"list","ok":true,"data":{}}]}}
226
+ ```
227
+
228
+ The final update has `complete=true`. Every update also includes `succeeded` and a
229
+ cumulative `errors` array containing the failed child IDs, actions, codes, and
230
+ messages. A successful batch envelope means the batch was accepted; clients must
231
+ still inspect each result's `ok` field. Repeating an identical batch ID returns one
232
+ cumulative snapshot and never runs its children twice.
233
+
234
+ ### Autonomous lifecycle
235
+
236
+ Autonomous work uses a durable, provider-neutral checkpoint rather than relying
237
+ on one chat transcript. Start or replace a run with `reset=true`:
238
+
239
+ ```json
240
+ {"id":"run-start","action":"autonomy_update","reset":true,"status":"running","phase":"observe","objective":"Repair authentication expiry handling","iteration":1,"plan":[{"id":"inspect","text":"Inspect the authentication path","status":"active"}],"hypotheses":[],"evidence":[],"decisions":[],"next":"Read the middleware and focused tests."}
241
+ ```
242
+
243
+ The supported phases are `observe`, `reason`, `act`, `verify`, `persist`, and
244
+ `continue`. Arrays on `autonomy_update` replace their compact current snapshots;
245
+ IDs must be unique. Record material lifecycle facts independently:
246
+
247
+ ```json
248
+ {"id":"verification-event","action":"autonomy_event","event":"verification.passed","summary":"Focused authentication tests passed.","detail":"3 tests passed with no failures.","refs":["auth-test"]}
249
+ ```
250
+
251
+ Read the current checkpoint and up to 100 paged event records with
252
+ `autonomy_state`. State mutations are serialized, schema-validated, atomically
253
+ written, and limited to 512 KiB. The append-only event history remains separate
254
+ from the general operation history.
255
+
256
+ ### WebSocket mode
257
+
258
+ The workspace CLI normally owns the Fastify daemon lifecycle:
259
+
260
+ ```bash
261
+ qlyx daemon start
262
+ ```
263
+
264
+ `QLYX_HOST` and `QLYX_PORT` override the configured address for one run:
265
+
266
+ ```bash
267
+ QLYX_PORT=14784 qlyx daemon start
268
+ ```
269
+
270
+ The defaults expose:
271
+
272
+ ```text
273
+ WebSocket: ws://127.0.0.1:14783/socket
274
+ Health: http://127.0.0.1:14783/health
275
+ ```
276
+
277
+ Each WebSocket message contains one JSON request. Each response contains the same
278
+ operation envelope used by direct and NDJSON modes:
279
+
280
+ ```typescript
281
+ import WebSocket from 'ws';
282
+
283
+ const socket = new WebSocket('ws://127.0.0.1:14783/socket');
284
+
285
+ socket.on('open', () => {
286
+ socket.send(JSON.stringify({
287
+ id: 'source-1',
288
+ action: 'read',
289
+ workspace: 'workspace-id',
290
+ path: '/path/to/file',
291
+ size: 100,
292
+ }));
293
+ });
294
+
295
+ socket.on('message', (data) => {
296
+ const reply = JSON.parse(data.toString());
297
+ console.log(reply.id, reply.ok, reply.data);
298
+ });
299
+ ```
300
+
301
+ WebSocket clients can keep a connection active without consuming an operation ID:
302
+
303
+ ```json
304
+ {"kind":"ping"}
305
+ ```
306
+
307
+ The server answers with `kind: "pong"`, the default workspace ID, and the current
308
+ workspace catalog. This control frame does not enter an engine queue or job
309
+ registry. `workspace.list`, `workspace.use`, `workspace.stop`,
310
+ `workspace.remove`, and `daemon.stop` are correlated control messages.
311
+
312
+ Operations on one connection run concurrently and may respond out of order. Every
313
+ connection has an independent engine, ID namespace, job registry, and queue for
314
+ each workspace it uses. When a socket disconnects, its still-running command trees
315
+ are terminated. Connections share each workspace's durable `.agent` store without
316
+ sharing state across workspace roots.
317
+
318
+ `server.bytes` limits each incoming WebSocket message. The server binds only to
319
+ loopback by default. A non-loopback `server.host` is rejected unless a token is set
320
+ in `server.token` or `QLYX_TOKEN`. Authenticated clients provide it during the
321
+ upgrade request:
322
+
323
+ ```typescript
324
+ const socket = new WebSocket('ws://host:14783/socket', {
325
+ headers: { authorization: `Bearer ${process.env.QLYX_TOKEN}` },
326
+ });
327
+ ```
328
+
329
+ ## Session Persistence
330
+
331
+ Running `qlyx init`, the CLI, or the WebSocket server initializes the core files
332
+ in this structure; `evidence/last-patch.json` appears after the first successful
333
+ edit:
334
+
335
+ ```text
336
+ qlyx.prompts.json
337
+ .agent/
338
+ context.md
339
+ state.json
340
+ objectives.md
341
+ hypotheses.md
342
+ decisions.md
343
+ guide.md
344
+ events.log
345
+ evidence/
346
+ index.md
347
+ last-patch.json
348
+ ```
349
+
350
+ `context.md` is the AI-maintained working summary. The desktop app creates a
351
+ structured initial document but does not summarize conversations or rewrite its
352
+ contents. It represents what is true now, not a conversation history or changelog.
353
+ Agents update persistent state only after significant discoveries, decisions,
354
+ modifications, failures, or changes in direction; routine reads, searches, polls,
355
+ and ordinary turns do not create durable records.
356
+
357
+ `state.json` is daemon-owned and atomically stores the workspace identity, durable
358
+ chat session metadata, and the compact machine-readable checkpoint for one active
359
+ run. The focused Markdown files project the active objectives, hypotheses, and
360
+ decisions for recovery without requiring a model to parse the full event history.
361
+ `events.log` is append-only and records only significant lifecycle events. Routine
362
+ operations do not rewrite `state.json` or append action/result audit noise.
363
+ `guide.md` is a stable recovery reference for an agent that loses track of the
364
+ task, protocol, or next step. Run `qlyx guide` from the workspace to print it.
365
+
366
+ Every successful `edit` atomically replaces `evidence/last-patch.json` with the
367
+ latest patch description and an embedded base64 snapshot of the file immediately
368
+ before that edit. This rolling record and the sibling `<file>.bak` are bounded, so
369
+ edits do not accumulate snapshots. Qlyx stages the record before changing the
370
+ target and rolls the edit back if the record cannot be committed. File operations
371
+ may update the current-state Markdown files and evidence contents, but cannot
372
+ mutate daemon-owned state or event history.
373
+
374
+ `qlyx.prompts.json` is the validated, workspace-editable prompt bundle. Qlyx
375
+ creates it from the built-in defaults when missing and migrates the former
376
+ `.agent/prompts.json` location on first open. It contains `base`, `protocol`, the
377
+ optional `autonomy` and `browser` modules, and the available prompt `scenarios`.
378
+ Edit it outside the Qlyx agent bridge, then restart the daemon to load changes.
379
+ The bundle must include at least one uniquely identified scenario. Invalid JSON,
380
+ duplicate IDs, empty scenario lists, and missing required text return a `PROMPTS`
381
+ failure.
382
+
383
+ Request the entry prompt alone through the CLI:
384
+
385
+ ```bash
386
+ npx tsx src/tool.ts session --mode setup --model ChatGPT
387
+ ```
388
+
389
+ Request the saved-context continuation prompt through the CLI:
390
+
391
+ ```bash
392
+ npx tsx src/tool.ts session --mode continue --model ChatGPT
393
+ ```
394
+
395
+ The same modes are available through WebSocket:
396
+
397
+ ```json
398
+ {"id":"setup-1","action":"session","mode":"setup","model":"ChatGPT"}
399
+ {"id":"continue-1","action":"session","mode":"continue","model":"ChatGPT","personal":"Keep public APIs stable."}
400
+ ```
401
+
402
+ Setup mode composes `base + protocol + optional autonomy + current autonomous
403
+ state and recent events + optional browser + all working modes + optional personal
404
+ context`. Continue mode prepends the exact current `context.md` to that
405
+ composition. Every response also includes the scenario menu as
406
+ `{id,name,description}` records for clients. The built-in scenarios are
407
+ `planning`, `exploratory`, `autonomous`, and `coding`. The prompt teaches the agent
408
+ to choose, combine, and switch these modes as work changes; there is no manual mode
409
+ selection. The legacy `scenario` request field is accepted but ignored. Personal
410
+ context is trimmed and appended under `## Task Context`.
411
+
412
+ The protocol permits at most one single or batched raw Qlyx command block per
413
+ response and documents the daemon's filesystem, process, and autonomy commands
414
+ plus the extension's `browser_*` and `agent_*` actions. Browser tabs are exposed
415
+ only through conversation-scoped logical `page` handles; raw browser tab IDs stay
416
+ inside the extension. The control surface covers open/close/list/focus,
417
+ navigate/back/forward, bounded inspect/expand, click/type/scroll, and exact
418
+ content or attribute extraction. `browser_start` launches a named asynchronous job
419
+ whose operation IDs complete independently; same-page work is ordered while
420
+ different logical pages navigate and extract concurrently. `browser_status`
421
+ returns bounded metadata and event history, and `browser_cancel` cancels a whole
422
+ job or one operation.
423
+
424
+ Browser page maps stay extension-side and expose only bounded semantic branches.
425
+ Their primary locator is a structural semantic path such as
426
+ `Main/Contract/Source Code`; missing and uncertain paths return `NOT_FOUND` or
427
+ `AMBIGUOUS_PATH` instead of selecting an approximate element. Inspection,
428
+ interaction, and extraction observations are ephemeral and omitted from the
429
+ extension's durable activity data. Asynchronous job state likewise retains only
430
+ requests, status, timestamps, bounded errors, and lifecycle events; completion
431
+ payloads are transient `browser_event` replies. A document-wide HTML read
432
+ automatically becomes a bounded collapsed page map. The agent expands one branch
433
+ at a time and reads HTML only from an identified subtree.
434
+ Only an explicit `browser_evidence` request, or the legacy `browser_dump` archive
435
+ request, moves an exact selection directly to the bound workspace through the
436
+ existing correlated `create` operation.
437
+ The protocol requires parsing, validation, execution, and delivery failures to be
438
+ reported in the next assistant response.
439
+ Search, git inspection, builds, and tests use `exec`; queued and running work uses
440
+ `status` and `cancel`. Reopening the app
441
+ or switching providers retains the same session ID and context. No conversation
442
+ transcript, automatic summary, or provider-specific state is transferred.
443
+
444
+ The AI never receives a filesystem handle, terminal, process object, registry,
445
+ or daemon credential. It can only request configured Qlyx actions and consume
446
+ their bounded correlated results. Provider-to-provider messages are likewise
447
+ untrusted claims until independently verified through those explicit actions.
448
+
449
+ ## Directories
450
+
451
+ List the current directory:
452
+
453
+ ```bash
454
+ npx tsx src/tool.ts list
455
+ ```
456
+
457
+ Start directly at an absolute directory:
458
+
459
+ ```bash
460
+ npx tsx src/tool.ts list "/home/user/project/src"
461
+ ```
462
+
463
+ ```powershell
464
+ npx tsx src/tool.ts list "C:\Users\user\project\src"
465
+ ```
466
+
467
+ Only one level is returned. Each item includes a canonical absolute `path`; pass
468
+ that value to another `list` call to expand a directory. Directory results use
469
+ `page.next` for directories containing more than the requested limit:
470
+
471
+ ```bash
472
+ npx tsx src/tool.ts list "/path/to/project/node_modules" --limit 200 --offset 200
473
+ ```
474
+
475
+ High-volume directories such as `node_modules`, `.git`, `dist`, and `vendor` are
476
+ marked with `volume: true`, but they are not hidden, skipped, or made inaccessible.
477
+ They use the same one-level listing and pagination contract as every other
478
+ directory, so dependencies can be inspected without returning the entire tree.
479
+
480
+ ## Files
481
+
482
+ Read the configured default number of lines:
483
+
484
+ ```bash
485
+ npx tsx src/tool.ts read "/path/to/project/src/tool.ts"
486
+ ```
487
+
488
+ Read a specific inclusive range:
489
+
490
+ ```bash
491
+ npx tsx src/tool.ts read "/path/to/project/src/tool.ts" --start 120 --end 219
492
+ ```
493
+
494
+ Every result includes the total and remaining line counts:
495
+
496
+ ```json
497
+ {
498
+ "range": {
499
+ "start": 120,
500
+ "end": 219,
501
+ "total": 900,
502
+ "remain": 681
503
+ },
504
+ "next": {
505
+ "start": 220,
506
+ "end": 319,
507
+ "cursor": "continuation token"
508
+ }
509
+ }
510
+ ```
511
+
512
+ Continue without resending the path or calculating a new range:
513
+
514
+ ```bash
515
+ npx tsx src/tool.ts read --cursor "continuation token"
516
+ ```
517
+
518
+ Repeat with each returned `next.cursor` until `next` is `null`. This supports a
519
+ whole-file workflow while keeping every tool response bounded. A cursor is
520
+ rejected if the file changes before the next read.
521
+
522
+ Use `--size` to choose an automatic page size up to `lines.limit`. An explicit
523
+ `--start` and `--end` range is constrained by the same configured limit.
524
+
525
+ ## Execution
526
+
527
+ Run an executable directly with its arguments after `--`:
528
+
529
+ ```bash
530
+ npx tsx src/tool.ts exec -- git status --short
531
+ npx tsx src/tool.ts exec --cwd "/path/to/project" -- npm test
532
+ ```
533
+
534
+ The executable is resolved through the operating system's `PATH`. An absolute
535
+ executable path also works. The result is JSON containing `code`, `signal`,
536
+ `output`, `error`, `timed`, `duration`, `state`, and output truncation state in
537
+ `cut`.
538
+
539
+ Use the platform shell for built-ins, pipes, expansion, or redirection. The entire
540
+ shell expression must be passed as one quoted argument:
541
+
542
+ ```bash
543
+ npx tsx src/tool.ts exec --shell -- "printf 'hello' | tr a-z A-Z"
544
+ ```
545
+
546
+ ```powershell
547
+ npx tsx src/tool.ts exec --shell -- "dir /b | findstr .ts"
548
+ ```
549
+
550
+ Optional controls are placed before `--`:
551
+
552
+ ```bash
553
+ npx tsx src/tool.ts exec --timeout 60000 --input "answer" -- command argument
554
+ ```
555
+
556
+ Execution is intentionally unrestricted. Commands inherit the Reader process's
557
+ environment and operating-system permissions, can use working directories outside
558
+ the current workspace, and are not run in a sandbox.
559
+
560
+ ### Hard timeout
561
+
562
+ `timeout` is the terminating limit. If the command is still running when it
563
+ expires, its process tree is stopped and a final response returns `state: "done"`
564
+ with `timed: true`:
565
+
566
+ ```json
567
+ {"id":"build","action":"exec","words":["npm","run","build"],"timeout":60000}
568
+ ```
569
+
570
+ ### Progress wait
571
+
572
+ `wait` is non-terminating and is available through `serve` or the imported
573
+ `Engine`. If the command has not completed when `wait` expires, the response
574
+ contains its output so far with `state: "running"`:
575
+
576
+ ```json
577
+ {"id":"build","action":"exec","words":["npm","run","build"],"timeout":120000,"wait":1000}
578
+ ```
579
+
580
+ Poll that execution using a new operation ID and the execution ID as `target`:
581
+
582
+ ```json
583
+ {"id":"build-check-1","action":"status","target":"build"}
584
+ {"id":"build-check-2","action":"status","target":"build","wait":1000}
585
+ ```
586
+
587
+ A status request without `wait` returns immediately. With `wait`, it returns when
588
+ the command completes or the progress interval expires. Continue until `state` is
589
+ `done`. The hard execution `timeout` remains active during every progress wait.
590
+ Progress mode is rejected by the one-shot CLI because that process cannot retain a
591
+ job for later polling.
592
+
593
+ An execution waiting for `engine.limit` capacity reports `state: "queued"` rather
594
+ than an unknown-job error. Status also accepts a batch ID. Batch status returns a
595
+ cumulative snapshot with `total`, `completed`, `succeeded`, `pending`, `failed`,
596
+ `complete`, `results`, and `errors`; continue until its state is `done`.
597
+
598
+ Cancel a queued or running execution, or all unfinished work in a batch:
599
+
600
+ ```json
601
+ {"id":"cancel-build","action":"cancel","target":"build"}
602
+ ```
603
+
604
+ Queued exec requests are prevented from starting. Running exec requests terminate
605
+ their process group with the same graceful-then-forced shutdown used by timeouts.
606
+ Batch cancellation stops active exec children and reports unstarted children as
607
+ cancelled. It does not roll back filesystem work that already completed.
608
+
609
+ ### Snapshot pages
610
+
611
+ Stdout and stderr are paginated independently. Every execution or status response
612
+ contains one page from each stream:
613
+
614
+ ```json
615
+ {
616
+ "output": "current stdout page",
617
+ "error": "current stderr page",
618
+ "page": {
619
+ "output": {
620
+ "start": 0,
621
+ "end": 1048576,
622
+ "total": 2400000,
623
+ "stored": 2400000,
624
+ "remain": 1351424,
625
+ "lost": 0,
626
+ "next": 1048576
627
+ },
628
+ "error": {
629
+ "start": 0,
630
+ "end": 0,
631
+ "total": 0,
632
+ "stored": 0,
633
+ "remain": 0,
634
+ "lost": 0,
635
+ "next": null
636
+ }
637
+ }
638
+ }
639
+ ```
640
+
641
+ Use the returned `next` offsets in a new status operation:
642
+
643
+ ```json
644
+ {"id":"build-page-2","action":"status","target":"build","out":1048576,"err":0}
645
+ ```
646
+
647
+ `end` is exclusive. `remain` is the retrievable content after the current page,
648
+ and `lost` reports bytes discarded after `exec.store` was exhausted. While an
649
+ execution is running, `next` can equal `end` even when `remain` is zero; polling
650
+ that offset later retrieves newly produced output. Once a completed stream is
651
+ fully consumed, `next` is `null`.
652
+
653
+ ## Editing
654
+
655
+ Replace one exact text region without supplying line numbers or the full file:
656
+
657
+ ```bash
658
+ npx tsx src/tool.ts edit "/path/to/file.ts" \
659
+ --before "const mode = 'old';" \
660
+ --after "const mode = 'new';"
661
+ ```
662
+
663
+ Matching is literal, including whitespace and line endings. No regular expression
664
+ syntax or replacement expansion is applied. If `before` is absent, the file is not
665
+ changed. If it occurs more than once, the edit is rejected as ambiguous. Select a
666
+ specific one-based occurrence when duplication is intentional:
667
+
668
+ ```bash
669
+ npx tsx src/tool.ts edit "/path/to/file.ts" \
670
+ --before "return null;" \
671
+ --after "return value;" \
672
+ --index 2
673
+ ```
674
+
675
+ For functions or other multiline regions, use a JSON specification. The `path` is
676
+ absolute or relative to the command's working directory:
677
+
678
+ ```json
679
+ {
680
+ "path": "/path/to/file.ts",
681
+ "before": "function old() {\n return false;\n}",
682
+ "after": "function current() {\n return true;\n}",
683
+ "index": 1
684
+ }
685
+ ```
686
+
687
+ ```bash
688
+ npx tsx src/tool.ts edit --spec "/path/to/change.json"
689
+ ```
690
+
691
+ Before committing, the tool stages and byte-validates both the patched file and a
692
+ full rollback snapshot. It then atomically replaces `<file>.bak` with the current
693
+ file and atomically replaces the target with the validated patch. The single
694
+ `.bak` is bounded: each successful edit overwrites it with the immediately
695
+ preceding file state instead of appending history. A rejected patch does not alter
696
+ the target or its prior backup. The result includes `backup.path` and
697
+ `backup.bytes`; a no-op edit returns `backup: null`.
698
+
699
+ When session persistence is active, the same validated source and applied
700
+ replacement are stored together in the rolling
701
+ `.agent/evidence/last-patch.json` record.
702
+
703
+ The tool preserves all content outside the selected region. It rejects binary
704
+ files, oversized current or patched files, ambiguous matches, and edits where the
705
+ file changes while the replacement is being prepared.
706
+
707
+ ## Creating
708
+
709
+ Create a directory, optionally including missing parents:
710
+
711
+ ```bash
712
+ npx tsx src/tool.ts create "/path/to/new/folder" --type directory --parents
713
+ ```
714
+
715
+ Create a file with initial content:
716
+
717
+ ```bash
718
+ npx tsx src/tool.ts create "/path/to/new/file.txt" --type file --content "hello"
719
+ ```
720
+
721
+ WebSocket and `serve` requests use the same fields:
722
+
723
+ ```json
724
+ {"id":"folder","action":"create","path":"work/nested","type":"directory","parents":true}
725
+ {"id":"file","action":"create","path":"work/nested/file.txt","type":"file","content":"hello"}
726
+ ```
727
+
728
+ Creation never overwrites an existing path. Use exact-region editing for existing
729
+ files. Initial content is limited by `create.bytes`.
730
+
731
+ ## Deleting
732
+
733
+ Delete a file or symbolic link:
734
+
735
+ ```bash
736
+ npx tsx src/tool.ts delete "/path/to/file.ts"
737
+ ```
738
+
739
+ Directories are rejected. Deleting a symbolic link removes only the link. Deletion
740
+ is permanent and does not move the file to an operating-system trash directory.
741
+
742
+ ## Library
743
+
744
+ ```typescript
745
+ import { Engine, setting, Tool } from './src/tool.ts';
746
+
747
+ const config = setting('/path/to/config.json');
748
+ const tool = new Tool(process.cwd(), config);
749
+ const tree = await tool.list('/path/to/project');
750
+ const page = await tool.read({ path: '/path/to/file', size: 200 });
751
+ const next = page.next
752
+ ? await tool.read({ cursor: page.next.cursor })
753
+ : null;
754
+ const result = await tool.exec({ words: ['git', 'status', '--short'] });
755
+ const created = await tool.create({
756
+ path: '/path/to/file',
757
+ type: 'file',
758
+ content: 'hello',
759
+ });
760
+ const change = await tool.edit({
761
+ path: '/path/to/file',
762
+ before: 'old text',
763
+ after: 'new text',
764
+ });
765
+ const removed = await tool.remove('/path/to/file');
766
+
767
+ const engine = new Engine(tool, config);
768
+ const running = await engine.run({
769
+ id: 'build',
770
+ action: config.commands.exec,
771
+ words: ['npm', 'run', 'build'],
772
+ timeout: 120000,
773
+ wait: 1000,
774
+ });
775
+ const progress = await engine.run({
776
+ id: 'build-check-1',
777
+ action: config.commands.status,
778
+ target: 'build',
779
+ });
780
+ ```
781
+
782
+ ## Commands
783
+
784
+ ```bash
785
+ npx tsx src/tool.ts help
786
+ npm test
787
+ npm run check
788
+ ```