@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 +788 -0
- package/config.json +52 -0
- package/dist/cli.js +3130 -0
- package/dist/cli.js.map +7 -0
- package/package.json +54 -0
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
|
+
```
|