@dolusoft/command-console-core 1.0.0 → 1.1.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.
- package/README.md +95 -0
- package/dist/command-console-core.js +517 -28
- package/dist/command-console-core.js.map +1 -1
- package/dist/types/core/console/console.types.d.ts +28 -2
- package/dist/types/core/dispatch/commandError.d.ts +24 -0
- package/dist/types/core/dispatch/dispatch.types.d.ts +28 -1
- package/dist/types/core/dispatch/dispatch.utils.d.ts +4 -3
- package/dist/types/core/dispatch/operation.d.ts +23 -0
- package/dist/types/helpers/agentTools.d.ts +34 -0
- package/dist/types/index.d.ts +6 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -71,6 +71,10 @@ no optional method, in its type and at run time. The feature set is fixed for th
|
|
|
71
71
|
| `isSensitive(line)` | marks more lines sensitive, OR-ed with the commands' `sensitive`; a throw counts as sensitive |
|
|
72
72
|
| `recallLimit` | recall: the most lines kept; default 100 (`Infinity` keeps all, a fraction is rounded down) |
|
|
73
73
|
| `shouldRecord(line)` | recall: whether a submitted line is recorded; default every line |
|
|
74
|
+
| `gate` | sends described commands as envelopes and runs them from their echo (below); fixed for the console's life |
|
|
75
|
+
| `activeView()` | the view a typed line (or a `{ line }` request) targets, read when the line ends |
|
|
76
|
+
| `echoTimeoutMs` | how long an envelope waits for its echo, a finite number above 0 (else a `TypeError`); default 5000 |
|
|
77
|
+
| `createEnvelopeId()` | makes envelope ids; default `crypto.randomUUID()`, else a v4 UUID from `crypto.getRandomValues` (below) |
|
|
74
78
|
|
|
75
79
|
Run ids come from `createId` too, so with the default counter the first command's output is `c3` (`c1` the input entry,
|
|
76
80
|
`c2` the run). An id `createId` returns that is already present throws from the method that caused it, as a duplicate
|
|
@@ -115,6 +119,7 @@ Every console has:
|
|
|
115
119
|
empty line, a parse error, an unknown command), `false` without doing anything while a run exists.
|
|
116
120
|
- `run(line)`: replaces the live input with `line` and submits it; the same result as `submit()`.
|
|
117
121
|
- `cancel()`: interrupts the running command, else ends the live line as cancelled.
|
|
122
|
+
- `request(input)`: runs a command for an agent (below); resolves a `CommandOutcome`, never rejects.
|
|
118
123
|
- `handleKey(input: KeyInput)`: applies the fixed key map; `true` when the key was handled, so the adapter prevents the
|
|
119
124
|
default.
|
|
120
125
|
- `appendEntries(entries)` (an id already present throws), `clearEntries()`.
|
|
@@ -193,6 +198,96 @@ the sequence of frame ids differs; a new array with the same ids is not one.
|
|
|
193
198
|
- `dispose()`: ends the running command the same way, runs every feature's cleanup once (an exception in one does not
|
|
194
199
|
stop the others), clears the internal state, and turns every later method into a no-op. Two consoles share nothing.
|
|
195
200
|
|
|
201
|
+
## Commands with an input schema
|
|
202
|
+
|
|
203
|
+
A command with `input` (an `InputSchema`, checked with `checkInputSchema` when the command is registered) is typed as
|
|
204
|
+
`name --field value …` (`parseOptions`). A command with `danger: 'irreversible'`, with `input` or without, cannot be
|
|
205
|
+
run from the line (`operation-failed` with `not-allowed`, before its options are read). Options that do not match
|
|
206
|
+
append an `error` entry `invalid-args` with the first error's `path` and `reason`, and nothing runs. Otherwise the
|
|
207
|
+
run starts: `normalize(input)` (optional, pure) maps the value, which is checked again, and the command's
|
|
208
|
+
`CommandRequest` carries it as `input`, the target view as `view` (none for an empty or `null` view), and `args`,
|
|
209
|
+
`rest` and `line` of the canonical line (`formatCommandLine`). `getCommands()` copies `input` and `danger`.
|
|
210
|
+
|
|
211
|
+
## The gate
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
interface ConsoleGate {
|
|
215
|
+
tab: string
|
|
216
|
+
send(envelope: Envelope): Promise<void>
|
|
217
|
+
subscribe(listener: (raw: unknown) => void): () => void
|
|
218
|
+
schedule(ms: number, fn: () => void): () => void
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The consumer's transport implements the gate; the core never touches the network, and `schedule` is its only clock
|
|
223
|
+
(ADR 0012). With a gate, a line of a command that has `input` and `danger` and is not sensitive does not run at once:
|
|
224
|
+
|
|
225
|
+
1. `beforeRun` is asked as before; the run stays `running` and the console locked.
|
|
226
|
+
2. An envelope (`v: 1`, `kind: 'request'`, the actor, `from.tab` and `target.tab` the gate's tab, `target.view`,
|
|
227
|
+
the canonical name, the normalized input) is sent as a copy: the transport cannot change the local record. Above
|
|
228
|
+
`envelopeMaxBytes` it is not sent (`too-large`).
|
|
229
|
+
3. Its echo, delivered through `subscribe`, runs the command with the local record when `command`, `args` and `target`
|
|
230
|
+
match; a different echo ends the run with `id-conflict` and `onError`. Other traffic is ignored.
|
|
231
|
+
4. A `send` that rejects, or a `subscribe`, `schedule` or `send` that throws, ends the run with `connection-lost`;
|
|
232
|
+
no echo within `echoTimeoutMs` with `timeout`. `cancel()`, `reset()` and a context change end it as cancelled.
|
|
233
|
+
|
|
234
|
+
These ends append an `operation-failed` entry with the code. The last 500 closed ids are remembered, so a late echo
|
|
235
|
+
runs nothing. The console subscribes on its first envelope, and `dispose()` unsubscribes and cancels the timer. A
|
|
236
|
+
command without `input` or `danger`, or a sensitive one, runs locally as without a gate.
|
|
237
|
+
|
|
238
|
+
Envelope ids come from `createEnvelopeId`, else `crypto.randomUUID()`, else a version 4 UUID made from
|
|
239
|
+
`crypto.getRandomValues` (a page served over plain `http` on a LAN address has only the latter). A console with a gate
|
|
240
|
+
and none of these throws on creation.
|
|
241
|
+
|
|
242
|
+
## Requests and agent tools
|
|
243
|
+
|
|
244
|
+
`request({ command, args, view?, actor })` or `request({ line, actor })` runs a command for a caller other than the
|
|
245
|
+
user's Enter. It resolves `busy` while a run exists, `unknown-command` (for a `hidden` command too), `invalid-args`,
|
|
246
|
+
or `not-allowed` for a command without `input` or `danger`, a sensitive or `irreversible` one, and in this release
|
|
247
|
+
for every actor other than `external-agent` and every `danger` other than `read`. These leave no entry. Otherwise an
|
|
248
|
+
input entry with the canonical line and the `actor` is appended and the line takes the path above. The outcome is
|
|
249
|
+
`ok` with the response's `summary` (cut to 200 code points), `failed`, `cancelled`, `not-allowed` (`beforeRun`
|
|
250
|
+
returned `false`), the code of a `CommandError`, or the gate's code. A response with `tone: 'error'` (from the command
|
|
251
|
+
or from `beforeRun`) is drawn as usual but its outcome is `failed`, without `summary`. A line the user types carries no
|
|
252
|
+
`actor`.
|
|
253
|
+
|
|
254
|
+
A command ends its run with a code of its own by throwing (or rejecting) a `CommandError`:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { CommandError } from '@dolusoft/command-console-core'
|
|
258
|
+
|
|
259
|
+
const show: CommandDefinition = {
|
|
260
|
+
name: 'show',
|
|
261
|
+
input: showInput,
|
|
262
|
+
danger: 'read',
|
|
263
|
+
run: request => {
|
|
264
|
+
const tab = findTab(request.view)
|
|
265
|
+
if (!tab) {
|
|
266
|
+
throw new CommandError('target-not-found', 'No such tab.')
|
|
267
|
+
}
|
|
268
|
+
return tab.render(request.input)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The run fails as with any error (`onError` gets it, the entry is `formatError`'s or `command-failed`), and the
|
|
274
|
+
outcome is `{ status: 'error', code }`. A command may give `target-not-found`, `invalid-args` or `failed`; any other
|
|
275
|
+
code (`busy`, `timeout`, `id-conflict`, … belong to the core) gives `failed`, and so does a `CommandError` thrown by
|
|
276
|
+
`beforeRun`, `normalize` or `createEnvelopeId`.
|
|
277
|
+
|
|
278
|
+
`agentTools(console, { context })` returns the `context`, `help` and `run` tools as plain objects in the shape of a
|
|
279
|
+
WebMCP tool (`name`, `description`, `inputSchema`, `annotations`, `execute`). The consumer registers them:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const controller = new AbortController()
|
|
283
|
+
for (const tool of agentTools(console, { context: () => describeViews() })) {
|
|
284
|
+
document.modelContext?.registerTool(tool, { signal: controller.signal })
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`help` lists the commands that have `input` and `danger` and are neither `hidden` nor `sensitive`; `run` calls
|
|
289
|
+
`request` as `external-agent`. No `execute` rejects.
|
|
290
|
+
|
|
196
291
|
## Contract
|
|
197
292
|
|
|
198
293
|
The behavior rules are in [`contract/rules.md`](https://github.com/dolusoft/command-console/blob/main/contract/rules.md);
|