@alook/daemon 0.1.15 → 0.1.16
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 +119 -130
- package/dist/cli/index.js +7774 -3743
- package/dist/index.js +6812 -2950
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
# @alook/daemon
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
surface). It also injects an `ALOOK_*` env contract. The default
|
|
16
|
-
`MOCK_CLI_CONFIG` wires no `targetCommand` (the wrapper errors if invoked); a
|
|
17
|
-
real deployment passes its own `CliTransportConfig` (CLI name, env prefix,
|
|
18
|
-
`targetCommand`) and its own system-prompt communication guide. The abstraction
|
|
19
|
-
is what's reusable — plug in whatever host you like.
|
|
3
|
+
Alook's host daemon: control-plane connectivity, agent scheduling, credentials,
|
|
4
|
+
diagnostics, and lifecycle orchestration.
|
|
5
|
+
|
|
6
|
+
Runtime execution lives in the independently buildable `@alook/agent-driver`
|
|
7
|
+
package under `agent-driver/`. That package adapts Claude Code, Codex, Cursor,
|
|
8
|
+
OpenCode, and Pi behind one public `AgentSession` contract. The daemon consumes
|
|
9
|
+
that contract and does not speak vendor process or SDK protocols.
|
|
10
|
+
|
|
11
|
+
The driver package remains host-neutral. Its CLI transport creates a per-launch
|
|
12
|
+
state directory, exposes a stable `alook` command through `PATH`, and composes
|
|
13
|
+
explicit environment layers supplied by the host. Vendor adapters never import
|
|
14
|
+
daemon credentials or control-plane code.
|
|
20
15
|
|
|
21
16
|
> **Provenance.** The shapes, protocols, flag strings, and control flow here were
|
|
22
17
|
> derived by studying how production agent-runtime daemons drive these CLIs, then
|
|
@@ -61,67 +56,60 @@ into logs, bug reports, or chat.
|
|
|
61
56
|
|
|
62
57
|
## The big idea
|
|
63
58
|
|
|
64
|
-
The daemon
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
1. **`spawn`** (or `createSession`) — launch the runtime,
|
|
68
|
-
2. **`encodeStdinMessage`** — encode an outgoing user message for the runtime's
|
|
69
|
-
input channel,
|
|
70
|
-
3. **`parseLine`** — normalize the runtime's output into a tiny shared event
|
|
71
|
-
vocabulary (`ParsedEvent`: `session_init`, `thinking`, `text`, `tool_call`,
|
|
72
|
-
`tool_output`, `compaction_*`, `turn_end`, `error`, `telemetry`).
|
|
73
|
-
|
|
74
|
-
A generic **session host** then runs the process and fans `ParsedEvent`s out to
|
|
75
|
-
the daemon:
|
|
59
|
+
The daemon creates an `AgentDriverSdk`, probes or opens a selected backend, and
|
|
60
|
+
then works only with an `AgentSession`:
|
|
76
61
|
|
|
77
|
-
- `
|
|
78
|
-
- `
|
|
79
|
-
|
|
62
|
+
- `start(message)` admits the first command.
|
|
63
|
+
- `send(message)` accepts, queues, or rejects a later command with a typed receipt.
|
|
64
|
+
- `interrupt(...)` and `stop(...)` provide bounded lifecycle control.
|
|
65
|
+
- `snapshot()` exposes logical state without leaking process/SDK objects.
|
|
66
|
+
- `events` is an async stream of normalized `AgentEvent` values.
|
|
67
|
+
- `closed` settles exactly once with terminal and host-cleanup facts.
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
Inside `@alook/agent-driver`, a backend adapter declares its execution model and
|
|
70
|
+
normalizes vendor output. `LogicalAgentSession` owns admission, FIFO queueing,
|
|
71
|
+
safe-boundary delivery, terminal ordering, and cleanup. Internal `ProcessLane`
|
|
72
|
+
and `SdkLane` hosts make child-process and in-process SDK transports look alike;
|
|
73
|
+
they are deliberately not public daemon primitives.
|
|
83
74
|
|
|
84
75
|
---
|
|
85
76
|
|
|
86
|
-
##
|
|
77
|
+
## Persistent delivery models (the heart of it)
|
|
87
78
|
|
|
88
79
|
When a message arrives, what happens depends on the runtime's lifecycle:
|
|
89
80
|
|
|
90
|
-
###
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
could declare `lifecycle.stdin: "gated"` yet `busyDeliveryMode: "direct"`).
|
|
98
|
-
`persistent+direct → direct`, `persistent+gated → gated`, `per_turn → none`.
|
|
99
|
-
|
|
100
|
-
- **`direct`** (kimi, pi): write immediately; the runtime tolerates injection
|
|
101
|
-
any time.
|
|
102
|
-
- **`gated`** (claude, codex): a raw write mid-stream could collide with an active
|
|
103
|
-
signed thinking block, so writes are **held until a safe boundary**,
|
|
104
|
-
implemented by:
|
|
105
|
-
- `apmStateMachine` (`src/runtime/apmStateMachine.ts`) — the policy reducers
|
|
106
|
-
that decide *when* to flush queued inbox notices and emit the concrete
|
|
107
|
-
`notify_stdin` / `deliver_stdin` effects (clause `SMR-002`). Flushing is
|
|
108
|
-
blocked while `outstanding_tool_uses > 0`, while `compacting`, while
|
|
109
|
-
`reviewing` (Codex review mode), or when `tool_boundary_flush_disabled`.
|
|
110
|
-
Compaction *and* review exit are treated as safe flush boundaries.
|
|
81
|
+
### Safe-boundary runtimes (Claude and Codex)
|
|
82
|
+
|
|
83
|
+
One child process spans many turns. Both adapters declare `lifetime: "session"`;
|
|
84
|
+
their public capability is `midTurnDelivery: "safe_boundary_queue"`. A message arriving during tool use,
|
|
85
|
+
compaction, or review is queued by `LogicalAgentSession` and receives a later
|
|
86
|
+
`command_accepted` or `command_failed` event. The daemon does not implement that
|
|
87
|
+
protocol or queue.
|
|
111
88
|
|
|
112
89
|
For Claude specifically, the input channel is **stream-json**: the process is
|
|
113
90
|
launched with `--input-format stream-json --output-format stream-json
|
|
114
91
|
--include-partial-messages`, and each message is one NDJSON line
|
|
115
92
|
`{"type":"user","message":{"role":"user","content":[{"type":"text","text":…}]}}`.
|
|
116
93
|
|
|
117
|
-
###
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
94
|
+
### In-process SDK runtime (Pi)
|
|
95
|
+
|
|
96
|
+
Pi declares the `in_process_sdk` transport and
|
|
97
|
+
`midTurnDelivery: "steer"`. Its lane delegates prompt, steer, abort, and dispose
|
|
98
|
+
to the SDK while preserving the same receipts, events, and terminal contract.
|
|
99
|
+
|
|
100
|
+
### Persistent queued runtime (Cursor)
|
|
101
|
+
|
|
102
|
+
Cursor keeps one `cursor-agent acp` process and ACP session for the logical
|
|
103
|
+
session. Each idle command is a `session/prompt` request; while one is active,
|
|
104
|
+
later commands remain in the logical next-turn FIFO until its correlated
|
|
105
|
+
response arrives. Interrupt sends `session/cancel` without killing the process.
|
|
106
|
+
|
|
107
|
+
### Persistent service runtime (OpenCode)
|
|
108
|
+
|
|
109
|
+
OpenCode starts one authenticated, loopback-only v2 service per logical session.
|
|
110
|
+
Root prompts and busy steers share the same service and vendor session. Durable
|
|
111
|
+
session SSE is replayed by event id and sequence across reconnects; a separate
|
|
112
|
+
live stream handles permissions.
|
|
125
113
|
|
|
126
114
|
---
|
|
127
115
|
|
|
@@ -129,67 +117,48 @@ message and terminates the process on turn end.
|
|
|
129
117
|
|
|
130
118
|
| Runtime | Lifecycle | Transport / protocol | Steering | Initial input | Output format |
|
|
131
119
|
|---|---|---|---|---|---|
|
|
132
|
-
| **claude** | persistent |
|
|
133
|
-
| **codex** | persistent |
|
|
134
|
-
| **
|
|
135
|
-
| **
|
|
136
|
-
| **
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| **opencode** | per-turn (defer-spawn, terminate-on-end) | child process, JSON | none | prompt as `-- <arg>` | JSON events |
|
|
140
|
-
| **antigravity** | per-turn | child process, **plain text** | none | prompt on stdin, then close | plain text lines |
|
|
141
|
-
|
|
142
|
-
(Each driver's exact launch flags live in its file under `src/drivers/`.)
|
|
120
|
+
| **claude** | persistent session | stream-json NDJSON | `safe_boundary_queue` | stdin user-message line | stream-json |
|
|
121
|
+
| **codex** | persistent session | JSON-RPC 2.0 (`app-server --listen stdio://`) | `safe_boundary_queue` | `initialize` → `thread/start`/`resume` | JSON-RPC notifications |
|
|
122
|
+
| **pi** | persistent session | `@earendil-works/pi-coding-agent` | `steer` | `session.prompt()` | SDK callback |
|
|
123
|
+
| **cursor** | persistent session | ACP JSON-RPC 2.0 (`cursor-agent acp`) | `next_turn_queue` | `session/prompt` | `session/update` + correlated prompt response |
|
|
124
|
+
| **opencode** | persistent session | authenticated HTTP + SSE (`opencode serve --pure`) | `steer` | v2 session prompt API | durable + live SSE |
|
|
125
|
+
|
|
126
|
+
(Exact launch flags live in `agent-driver/src/adapters/<backend>/`.)
|
|
143
127
|
|
|
144
128
|
---
|
|
145
129
|
|
|
146
130
|
## Layout
|
|
147
131
|
|
|
148
132
|
```
|
|
133
|
+
agent-driver/
|
|
134
|
+
src/
|
|
135
|
+
contract.ts # public SDK, session, receipt, event, and result types
|
|
136
|
+
sdk.ts # createAgentDriverSdk
|
|
137
|
+
registry.ts # built-in ids and capabilities
|
|
138
|
+
controller/
|
|
139
|
+
logical-session.ts # admission, queueing, turns, stop, cleanup
|
|
140
|
+
process-host.ts # internal child-process lane
|
|
141
|
+
sdk-host.ts # internal in-process SDK lane
|
|
142
|
+
adapters/<backend>/ # vendor launch + normalization
|
|
143
|
+
host/default-host.ts # standalone host implementation
|
|
144
|
+
internal/ # adapter-only transport/config/process helpers
|
|
145
|
+
testing/ # public conformance fixtures
|
|
149
146
|
src/
|
|
150
|
-
|
|
151
|
-
index.ts
|
|
152
|
-
logger.ts #
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
index.ts # getDriver(runtimeId) registry ← start here
|
|
156
|
-
cliTransport.ts # shared: state dir, token, decoupling cliName wrapper -> targetCommand, env
|
|
157
|
-
systemPrompt.ts # shared: standing-prompt assembly
|
|
158
|
-
probe.ts # CLI/binary detection + version
|
|
159
|
-
claude.ts # Claude Code driver
|
|
160
|
-
claudeLaunch.ts # args / command / spawn-spec / prompt-file
|
|
161
|
-
claudeProviderIsolation.ts # custom-provider HOME/config isolation
|
|
162
|
-
claudeEventNormalizer.ts # stream-json → ParsedEvent
|
|
163
|
-
codex.ts / codexEventNormalizer.ts / codexTelemetrySidecar.ts
|
|
164
|
-
gemini.ts copilot.ts cursor.ts opencode.ts antigravity.ts
|
|
165
|
-
kimi.ts # child-process Kimi (JSON-RPC wire)
|
|
166
|
-
pi.ts # in-process Pi SDK (multi-provider)
|
|
167
|
-
runtime/
|
|
168
|
-
runtimeSession.ts # ChildProcessRuntimeSession + descriptor
|
|
169
|
-
sdkRuntimeSession.ts # SdkRuntimeSession (in-process)
|
|
170
|
-
apmStateMachine.ts # gated-delivery policy reducers (SMR-002)
|
|
171
|
-
progressState.ts # liveness / stall detection
|
|
172
|
-
notificationState.ts # inbox-notice de-dup + batching
|
|
173
|
-
errorDiagnostics.ts # runtime-error classification + scrubbing
|
|
174
|
-
inbox/
|
|
175
|
-
projection.ts # bucket pending messages by target → notice snapshots
|
|
176
|
-
stateMachine.ts # pre-action freshness guard (forward / hold / bypass)
|
|
147
|
+
index.ts # daemon exports; re-exports the driver public API
|
|
148
|
+
drivers/index.ts # daemon runtime lookup/probe facade only
|
|
149
|
+
logger.ts # structured daemon logging
|
|
150
|
+
runtime/ # manager liveness, notification, error helpers
|
|
151
|
+
inbox/ # unread projection and freshness policy
|
|
177
152
|
manager/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
createDaemon.ts # runtime-agnostic daemon factory (driver/sessionFactory/runtimes INJECTED; no test code)
|
|
186
|
-
cli/
|
|
187
|
-
index.ts # the `alook` agent CLI skeleton (message send / inbox pull)
|
|
188
|
-
proxyServerApi.ts # agent data-plane client: voucher → proxy → server (identity from the voucher)
|
|
189
|
-
credentials/
|
|
190
|
-
credentialProxy.ts # CredentialBroker (mint/revoke/check per-voucher vouchers) + local key-swapping proxy
|
|
153
|
+
agentDriverHost.ts # host resource/env/CLI preparation
|
|
154
|
+
managerPolicy.ts # pure scheduling reducer
|
|
155
|
+
managerRuntime.ts # applies effects through AgentSession
|
|
156
|
+
server/ # data/control-plane contracts and WebSockets
|
|
157
|
+
daemon/createDaemon.ts # runtime-agnostic daemon composition
|
|
158
|
+
cli/ # lifecycle CLI and proxy data-plane client
|
|
159
|
+
credentials/ # voucher broker and key-swapping proxy
|
|
191
160
|
scripts/
|
|
192
|
-
daemon.ts # local
|
|
161
|
+
daemon.ts # local daemon entry
|
|
193
162
|
```
|
|
194
163
|
|
|
195
164
|
## Host orchestration (manager + server)
|
|
@@ -361,22 +330,42 @@ posting a message in a channel it's a member of — the real wake-producer path
|
|
|
361
330
|
(`src/web` → `src/wake-worker` → `src/ws-do`) delivers `agent:wake` over the
|
|
362
331
|
daemon's real `WsControlChannel`.
|
|
363
332
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
> (tsx / ts-node / vite / esbuild / webpack).
|
|
333
|
+
`pnpm --filter @alook/daemon build` emits a self-contained daemon bundle. The
|
|
334
|
+
published tarball includes the runtime-driver implementation, while
|
|
335
|
+
`@alook/agent-driver` is also independently buildable and publishable for hosts
|
|
336
|
+
that want only the logical-session SDK.
|
|
369
337
|
|
|
370
338
|
## Usage sketch
|
|
371
339
|
|
|
372
340
|
```ts
|
|
373
|
-
import {
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
341
|
+
import { createAgentDriverSdk } from "@alook/agent-driver";
|
|
342
|
+
|
|
343
|
+
const sdk = createAgentDriverSdk();
|
|
344
|
+
const opened = await sdk.open({
|
|
345
|
+
backend: "codex",
|
|
346
|
+
launch: {
|
|
347
|
+
workingDirectory: ".",
|
|
348
|
+
instructions: { format: "markdown", content: "Be concise." },
|
|
349
|
+
launchId: "launch-example",
|
|
350
|
+
},
|
|
351
|
+
config: {
|
|
352
|
+
model: { kind: "default" },
|
|
353
|
+
mode: "default",
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
if (!opened.ok) throw new Error(opened.error.message);
|
|
357
|
+
|
|
358
|
+
const { session } = opened;
|
|
359
|
+
const eventsDone = (async () => {
|
|
360
|
+
for await (const event of session.events) {
|
|
361
|
+
if (event.type === "text_delta") process.stdout.write(event.text);
|
|
362
|
+
}
|
|
363
|
+
})();
|
|
364
|
+
|
|
365
|
+
await session.start({ id: "command-1", kind: "user", text: "Inspect this repository." });
|
|
366
|
+
await session.send({ id: "command-2", kind: "user", text: "Now summarize the risks." });
|
|
367
|
+
await session.stop({ reason: "owner_request", forceAfterMs: 5_000 });
|
|
368
|
+
const result = await session.closed;
|
|
369
|
+
await eventsDone;
|
|
370
|
+
void result;
|
|
382
371
|
```
|