car-runtime 0.53.0 → 0.55.0-nightly.20260920
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 +52 -7
- package/agent-loop.d.ts +15 -0
- package/agent-loop.js +30 -16
- package/daemon-wrappers.js +6259 -0
- package/docs/CLI.md +546 -34
- package/docs/agent-ir-spec.md +34 -5
- package/index.d.ts +1219 -36
- package/index.js +45 -0
- package/install.js +16 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -21,6 +21,46 @@ Both the `.node` module **and** the `car-server` daemon binary are
|
|
|
21
21
|
included. Supported platforms: macOS (14+) arm64/x64, Linux
|
|
22
22
|
x64/arm64 glibc, Windows x64.
|
|
23
23
|
|
|
24
|
+
## Daemon/package compatibility
|
|
25
|
+
|
|
26
|
+
The npm package and the daemon use a versioned JSON-RPC wire protocol. Read
|
|
27
|
+
`carProtocolVersion` from `car-runtime/package.json` when compatibility must be
|
|
28
|
+
checked before the native module loads; `protocolVersion()` returns the same
|
|
29
|
+
number at runtime:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const pkg = require('car-runtime/package.json');
|
|
33
|
+
const { protocolVersion } = require('car-runtime');
|
|
34
|
+
|
|
35
|
+
if (protocolVersion() !== pkg.carProtocolVersion) {
|
|
36
|
+
throw new Error('car-runtime package metadata does not match its native binding');
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The daemon handshake fails closed and names both wire versions when the client
|
|
41
|
+
and daemon are incompatible. `CarHost.app` can update its bundled daemon
|
|
42
|
+
independently of project dependencies, so update this package in the project
|
|
43
|
+
that loads it; while CAR is pre-1.0, pinning the package to the daemon's CAR
|
|
44
|
+
release is the safest choice (`npm install car-runtime@<version>`). A caret
|
|
45
|
+
range does not cross a `0.x` minor release.
|
|
46
|
+
|
|
47
|
+
## Call the daemon
|
|
48
|
+
|
|
49
|
+
`CarRuntime` connects lazily. Params and results are JSON strings; generated typed
|
|
50
|
+
wrappers such as `agentsList` share the same governed connection.
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
async function daemonCallExample() {
|
|
54
|
+
// BEGIN CALL_DAEMON_FIVE_LINES
|
|
55
|
+
const { CarRuntime } = (await import('car-runtime')).default;
|
|
56
|
+
const car = new CarRuntime();
|
|
57
|
+
car.registerDaemonHandler('tools.execute', async () => JSON.stringify({ ok: true }));
|
|
58
|
+
const methods = JSON.parse(await car.daemonCall('capabilities.list', '{}'));
|
|
59
|
+
return { car, methods, agents: JSON.parse(await car.agentsList('{}')) };
|
|
60
|
+
// END CALL_DAEMON_FIVE_LINES
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
24
64
|
## Start the daemon
|
|
25
65
|
|
|
26
66
|
```bash
|
|
@@ -95,13 +135,18 @@ main({
|
|
|
95
135
|
});
|
|
96
136
|
```
|
|
97
137
|
|
|
98
|
-
`runAgent(config, goal)` is also exported for embedding and tests.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
`runAgent(config, goal)` is also exported for embedding and tests. Its
|
|
139
|
+
`AgentOutcome.run_id` is the durable key accepted by `runsGetTrace`; it is
|
|
140
|
+
`null` when an older daemon has no `runs.*` surface or did not acknowledge the
|
|
141
|
+
start. `runChatTurn` keeps returning bare text by default; pass
|
|
142
|
+
`{ includeRunId: true }` as its final options argument to receive
|
|
143
|
+
`{ text, run_id }`. `main(config)` provides `--task`, `--json`, and `--serve`.
|
|
144
|
+
The loop adds `finish`, statically verifies every proposal before execution,
|
|
145
|
+
preserves tool-call/result IDs, honors per-schema `timeoutMs`, traces task and
|
|
146
|
+
chat runs, and uses request-shaped inference with prompt-cache breakpoints. It
|
|
147
|
+
loads the native binding only when a loop runs, so
|
|
148
|
+
`require('car-runtime/agent-loop')` is safe for package discovery. Type
|
|
149
|
+
declarations ship as `agent-loop.d.ts` and shared config/outcome types in
|
|
105
150
|
`index.d.ts`.
|
|
106
151
|
|
|
107
152
|
Full API reference lives in [`index.d.ts`](./index.d.ts). The package also
|
package/agent-loop.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AgentChatTurnOptions,
|
|
3
|
+
AgentChatTurnResult,
|
|
2
4
|
AgentLoopConfig,
|
|
3
5
|
AgentLoopOptions,
|
|
4
6
|
AgentOutcome,
|
|
@@ -13,6 +15,18 @@ export function runAgent(
|
|
|
13
15
|
options?: AgentLoopOptions,
|
|
14
16
|
): Promise<AgentOutcome>;
|
|
15
17
|
|
|
18
|
+
export function runChatTurn(
|
|
19
|
+
runtime: CarRuntime,
|
|
20
|
+
config: AgentLoopConfig,
|
|
21
|
+
toolSchemas: AgentToolSchema[],
|
|
22
|
+
tools: Record<string, AgentTool>,
|
|
23
|
+
toolTimeouts: Record<string, number>,
|
|
24
|
+
sessionId: string,
|
|
25
|
+
messages: Record<string, unknown>[],
|
|
26
|
+
requestedModel: string | null | undefined,
|
|
27
|
+
options: AgentChatTurnOptions & { includeRunId: true },
|
|
28
|
+
): Promise<AgentChatTurnResult>;
|
|
29
|
+
|
|
16
30
|
export function runChatTurn(
|
|
17
31
|
runtime: CarRuntime,
|
|
18
32
|
config: AgentLoopConfig,
|
|
@@ -22,6 +36,7 @@ export function runChatTurn(
|
|
|
22
36
|
sessionId: string,
|
|
23
37
|
messages: Record<string, unknown>[],
|
|
24
38
|
requestedModel?: string | null,
|
|
39
|
+
options?: AgentChatTurnOptions & { includeRunId?: false },
|
|
25
40
|
): Promise<string>;
|
|
26
41
|
|
|
27
42
|
export function main(config: AgentLoopConfig): Promise<void>;
|
package/agent-loop.js
CHANGED
|
@@ -157,7 +157,7 @@ async function inferTurn(rt, { model, maxTokens, toolSchemas, messages, toolChoi
|
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
159
|
* Run the agent once toward `goal`. Returns an AgentOutcome-shaped object:
|
|
160
|
-
* { status, summary, evidence[], metrics{}, tools_called[] }
|
|
160
|
+
* { status, summary, evidence[], metrics{}, tools_called[], run_id }
|
|
161
161
|
* `status` is one of the six OutcomeStatus values (success|partial_success|
|
|
162
162
|
* done|give_up|timeout|failure). AgentOutcome is caller-built — CAR does not
|
|
163
163
|
* return it; we assemble it from what the loop observed.
|
|
@@ -166,7 +166,7 @@ async function inferTurn(rt, { model, maxTokens, toolSchemas, messages, toolChoi
|
|
|
166
166
|
* proposal we bracket the run open with `rt.runsStart` (daemon mints a durable
|
|
167
167
|
* `run_id`); after the terminal AgentOutcome is assembled we close it with
|
|
168
168
|
* `rt.runsComplete`. Both are best-effort and never print, so the daemon traces
|
|
169
|
-
* the run for CarHost while older daemons
|
|
169
|
+
* the run for CarHost while older daemons report `run_id: null` and still run.
|
|
170
170
|
*/
|
|
171
171
|
async function runAgent(config, goal, { maxTurns } = {}) {
|
|
172
172
|
const { CarRuntime, executeProposal } = runtimeApi();
|
|
@@ -198,7 +198,7 @@ async function runAgent(config, goal, { maxTurns } = {}) {
|
|
|
198
198
|
// CAR_AGENT_ID (the supervisor injects it) when supervised, else falls back to
|
|
199
199
|
// config.agentName for the unsupervised one-shot / run_scenarios path.
|
|
200
200
|
// Best-effort, exactly like registerAgentBasics: a daemon without runs.* (an
|
|
201
|
-
// older build) makes this throw, and the run must continue
|
|
201
|
+
// older build) makes this throw, and the run must continue with a null id. NEVER
|
|
202
202
|
// print here — the last stdout line in --json mode must stay the AgentOutcome
|
|
203
203
|
// (run_scenarios.py parses it).
|
|
204
204
|
let runId = null;
|
|
@@ -209,7 +209,9 @@ async function runAgent(config, goal, { maxTurns } = {}) {
|
|
|
209
209
|
agent_name: config.agentName,
|
|
210
210
|
outcome_description: config.targetOutcome ?? '',
|
|
211
211
|
})));
|
|
212
|
-
runId = started.run_id
|
|
212
|
+
runId = typeof started.run_id === 'string' && started.run_id.length > 0
|
|
213
|
+
? started.run_id
|
|
214
|
+
: null;
|
|
213
215
|
} catch { /* daemon lacks runs.* — degrade to untraced behavior */ }
|
|
214
216
|
|
|
215
217
|
// Local Qwen3 models default to "thinking" (they emit a long <think> block
|
|
@@ -329,13 +331,17 @@ async function runAgent(config, goal, { maxTurns } = {}) {
|
|
|
329
331
|
// before returning (the connection may close right after) so a healthy run is
|
|
330
332
|
// never raced into `Incomplete`. Best-effort + never prints, mirroring the
|
|
331
333
|
// open bracket: an older daemon without runs.* (or one that never acked the
|
|
332
|
-
// start, leaving runId null) just skips
|
|
334
|
+
// start, leaving runId null) just skips completion and returns the null id.
|
|
333
335
|
if (runId !== null) {
|
|
334
336
|
try {
|
|
335
337
|
await rt.runsComplete(JSON.stringify({ run_id: runId, outcome }));
|
|
336
338
|
} catch { /* daemon lacks runs.* — nothing to report to */ }
|
|
337
339
|
}
|
|
338
340
|
|
|
341
|
+
// `run_id` is package return metadata, not part of the daemon's car-ir
|
|
342
|
+
// AgentOutcome. Add it only after runs.complete so the wire shape stays
|
|
343
|
+
// unchanged. Null reports an older daemon or unacknowledged runs.start.
|
|
344
|
+
outcome.run_id = runId;
|
|
339
345
|
return outcome;
|
|
340
346
|
}
|
|
341
347
|
|
|
@@ -371,9 +377,15 @@ function parseArgs(argv) {
|
|
|
371
377
|
// declared policies still gate tool execution, so guardrails (draft-only, etc.)
|
|
372
378
|
// carry into the conversation.
|
|
373
379
|
|
|
374
|
-
/**
|
|
380
|
+
/**
|
|
381
|
+
* Run one chat turn against a persistent `messages` thread; stream via
|
|
382
|
+
* chatEvent. The default return stays the historical bare string. Callers that
|
|
383
|
+
* need to inspect the durable trace opt into `{ includeRunId: true }` and get
|
|
384
|
+
* `{ text, run_id }` instead.
|
|
385
|
+
*/
|
|
375
386
|
async function runChatTurn(
|
|
376
387
|
rt, config, toolSchemas, tools, toolTimeouts, sessionId, messages, requestedModel = null,
|
|
388
|
+
options = {},
|
|
377
389
|
) {
|
|
378
390
|
const { executeProposal } = runtimeApi();
|
|
379
391
|
const turnsCap = config.maxTurns ?? 8;
|
|
@@ -399,7 +411,9 @@ async function runChatTurn(
|
|
|
399
411
|
agent_name: config.agentName,
|
|
400
412
|
outcome_description: config.targetOutcome ?? '',
|
|
401
413
|
})));
|
|
402
|
-
runId = started.run_id
|
|
414
|
+
runId = typeof started.run_id === 'string' && started.run_id.length > 0
|
|
415
|
+
? started.run_id
|
|
416
|
+
: null;
|
|
403
417
|
} catch { /* daemon lacks runs.* — degrade to untraced behavior */ }
|
|
404
418
|
|
|
405
419
|
const metrics = { turns: 0, tool_calls: 0, actions_succeeded: 0, actions_failed: 0 };
|
|
@@ -462,20 +476,20 @@ async function runChatTurn(
|
|
|
462
476
|
// Run-trace bracket (close). The outcome is assembled exactly as `runAgent`
|
|
463
477
|
// assembles it, so a chat-driven run and a --task run are the same shape in
|
|
464
478
|
// the trace and a host reads one code path, not two.
|
|
479
|
+
const outcome = terminal === 'success'
|
|
480
|
+
? mkOutcome('success', finalText,
|
|
481
|
+
[{ kind: 'tool_result', description: 'finish called', data: { answer: finalText } }], metrics, toolsCalled)
|
|
482
|
+
: (terminal === 'done'
|
|
483
|
+
? mkOutcome('done', finalText || 'no further actions',
|
|
484
|
+
[{ kind: 'self_assessment', description: finalText || '', data: null }], metrics, toolsCalled)
|
|
485
|
+
: mkOutcome('timeout', `hit ${turnsCap}-turn cap without finishing`,
|
|
486
|
+
[{ kind: 'stop_reason', description: 'max turns', data: { turnsCap } }], metrics, toolsCalled));
|
|
465
487
|
if (runId !== null) {
|
|
466
|
-
const outcome = terminal === 'success'
|
|
467
|
-
? mkOutcome('success', finalText,
|
|
468
|
-
[{ kind: 'tool_result', description: 'finish called', data: { answer: finalText } }], metrics, toolsCalled)
|
|
469
|
-
: (terminal === 'done'
|
|
470
|
-
? mkOutcome('done', finalText || 'no further actions',
|
|
471
|
-
[{ kind: 'self_assessment', description: finalText || '', data: null }], metrics, toolsCalled)
|
|
472
|
-
: mkOutcome('timeout', `hit ${turnsCap}-turn cap without finishing`,
|
|
473
|
-
[{ kind: 'stop_reason', description: 'max turns', data: { turnsCap } }], metrics, toolsCalled));
|
|
474
488
|
try {
|
|
475
489
|
await rt.runsComplete(JSON.stringify({ run_id: runId, outcome }));
|
|
476
490
|
} catch { /* daemon lacks runs.* — nothing to report to */ }
|
|
477
491
|
}
|
|
478
|
-
return finalText;
|
|
492
|
+
return options?.includeRunId ? { text: finalText, run_id: runId } : finalText;
|
|
479
493
|
}
|
|
480
494
|
|
|
481
495
|
/** Set up the long-lived chat runtime: register tools/policies + the agent.chat handler. */
|