car-runtime 0.52.0 → 0.53.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/index.d.ts CHANGED
@@ -32,6 +32,61 @@
32
32
  * `ws://127.0.0.1:9100`).
33
33
  */
34
34
 
35
+ /** Agent-loop tool declaration. `timeoutMs` becomes the action budget. */
36
+ export interface AgentToolSchema {
37
+ name: string;
38
+ description: string;
39
+ parameters: Record<string, unknown>;
40
+ timeoutMs?: number;
41
+ }
42
+
43
+ export interface AgentToolContext {
44
+ signal: AbortSignal;
45
+ timeoutMs?: number;
46
+ }
47
+
48
+ export type AgentTool = (
49
+ params: Record<string, unknown>,
50
+ context: AgentToolContext,
51
+ ) => unknown | Promise<unknown>;
52
+
53
+ export type AgentOutcomeStatus =
54
+ | 'success' | 'partial_success' | 'done' | 'give_up' | 'timeout' | 'failure';
55
+
56
+ export interface AgentOutcome {
57
+ status: AgentOutcomeStatus;
58
+ summary: string;
59
+ evidence: Array<{ kind: string; description: string; data: unknown }>;
60
+ metrics: {
61
+ turns: number;
62
+ tool_calls: number;
63
+ actions_succeeded: number;
64
+ actions_failed: number;
65
+ };
66
+ tools_called: string[];
67
+ timestamp: string;
68
+ }
69
+
70
+ /** Declarative input consumed by `car-runtime/agent-loop`. */
71
+ export interface AgentLoopConfig {
72
+ agentId?: string;
73
+ agentName: string;
74
+ identity: string;
75
+ toolSchemas?: AgentToolSchema[];
76
+ tools?: Record<string, AgentTool>;
77
+ policies?: Array<[string, string, string?, string?, string?, string?]>;
78
+ defaultModel?: string | null;
79
+ maxTokens?: number;
80
+ maxTurns?: number;
81
+ targetOutcome?: string;
82
+ standingGoal?: string | null;
83
+ intervalSecs?: number;
84
+ }
85
+
86
+ export interface AgentLoopOptions {
87
+ maxTurns?: number;
88
+ }
89
+
35
90
  /** Persistent runtime instance with state, memory, tools, and policies. */
36
91
  /**
37
92
  * Optional settings for `coderStart`. Every field is independently omittable;
@@ -58,6 +113,24 @@ export interface CoderStartOptions {
58
113
  * hypothesis, the other buys a retry.
59
114
  */
60
115
  transientRetries?: number | undefined | null;
116
+ /**
117
+ * Farm a **foreman** session's subtasks across every reachable CAR instance
118
+ * that can serve this repository, instead of this machine alone. The
119
+ * merge-verify gate and delivery stay on the orchestrating host — a peer
120
+ * returns a patch and this host gates it — so a distributed run still
121
+ * produces a gated pull request.
122
+ *
123
+ * Only the foreman engine decomposes a goal into subtasks, so any other
124
+ * engine runs locally and says so. Off by default: it spends agent quota on
125
+ * other people's machines.
126
+ */
127
+ distributed?: boolean | undefined | null;
128
+ /**
129
+ * Restrict placement to these instances by name. Empty or omitted means
130
+ * every instance that reports it can serve the repository. Ignored unless
131
+ * `distributed` is set.
132
+ */
133
+ workers?: Array<string> | undefined | null;
61
134
  /**
62
135
  * A `coder.discuss` conversation this run was distilled from. Its agreed
63
136
  * constraints ride into contract derivation, so a rule stated once in the
@@ -68,9 +141,41 @@ export interface CoderStartOptions {
68
141
  discussionId?: string | undefined | null;
69
142
  }
70
143
 
144
+ export interface DaemonRpcError extends Error {
145
+ /** Numeric JSON-RPC error code returned by the daemon. */
146
+ code: number;
147
+ /** Daemon-provided diagnostic text. */
148
+ message: string;
149
+ /** Optional JSON-RPC error data returned by the daemon. */
150
+ data?: unknown;
151
+ }
152
+
71
153
  export class CarRuntime {
72
154
  constructor();
73
155
 
156
+ /**
157
+ * Invoke any daemon JSON-RPC method with a JSON-encoded params value.
158
+ * `daemonCall` is the call-by-name escape hatch; use the typed wrappers as the primary API.
159
+ * The result is returned as JSON. Daemon rejections are `DaemonRpcError`;
160
+ * transport failures reject without a synthetic numeric code.
161
+ */
162
+ daemonCall(method: string, paramsJson: string): Promise<string>;
163
+
164
+ /** Host-management-token twin of `daemonCall`; the method allowlist remains enforced. */
165
+ daemonCallHostManagement(method: string, paramsJson: string): Promise<string>;
166
+
167
+ /** Register a server-initiated JSON-RPC request handler. */
168
+ registerDaemonHandler(
169
+ method: string,
170
+ handler: (paramsJson: string) => Promise<string>,
171
+ ): void;
172
+
173
+ /** Register a server-initiated JSON-RPC notification handler. */
174
+ registerDaemonNotificationHandler(
175
+ method: string,
176
+ handler: (paramsJson: string) => void,
177
+ ): void;
178
+
74
179
  // --- Memory persistence ---
75
180
 
76
181
  /**
@@ -142,7 +247,54 @@ export class CarRuntime {
142
247
  adapter?: string,
143
248
  verifyCommand?: Array<string>,
144
249
  unionVerifyCommand?: Array<string>,
145
- maxAttempts?: number
250
+ maxAttempts?: number,
251
+ distributed?: boolean,
252
+ workers?: Array<string>
253
+ ): Promise<string>;
254
+
255
+ // --- Fleet ---
256
+
257
+ /**
258
+ * This instance's agents, capabilities, and models — one `InstanceInventory`
259
+ * JSON object. The same report peers receive over A2A, plus this session's
260
+ * own registered tools and learned skills.
261
+ */
262
+ fleetInventory(): Promise<string>;
263
+
264
+ /**
265
+ * Every agent, capability, and model across this daemon and every reachable
266
+ * CAR instance, folded so one row names every instance that offers it.
267
+ * `includeRemote` defaults to true. `timeoutMs` bounds each peer
268
+ * individually: a sleeping machine appears as an unreachable row carrying the
269
+ * reason, never a missing one. Returns `FleetComposite` JSON.
270
+ */
271
+ fleetComposite(includeRemote?: boolean, timeoutMs?: number): Promise<string>;
272
+
273
+ /** Whether this instance takes farmed-out coding work. `{ config, profile }` JSON. */
274
+ fleetWorkerGet(): Promise<string>;
275
+
276
+ /**
277
+ * Enroll (or withdraw) this instance as a fleet worker. **Operator-only, and
278
+ * a real grant**: enrolling lets a trusted peer run a coding CLI against the
279
+ * checkouts named in `repos`. Only the fields supplied change.
280
+ *
281
+ * The limits belong to this machine, not the caller: `dispatchesPerHour`
282
+ * budgets one peer's spend (concurrency is not a spend bound),
283
+ * `maxSubtaskSecs` caps the timeout a sender asks for, and `allowedTools` is
284
+ * intersected with whatever the dispatch requests. `fetchMissingBase` makes
285
+ * this machine a **runner**: rather than decline a base commit it lacks, it
286
+ * fetches from `fetchRemote` (its own, default `origin`).
287
+ */
288
+ fleetWorkerSet(
289
+ acceptsWork?: boolean,
290
+ repos?: Array<string>,
291
+ maxParallel?: number,
292
+ localParallel?: number,
293
+ dispatchesPerHour?: number,
294
+ maxSubtaskSecs?: number,
295
+ allowedTools?: Array<string>,
296
+ fetchMissingBase?: boolean,
297
+ fetchRemote?: string
146
298
  ): Promise<string>;
147
299
 
148
300
  // --- Tools & policies ---
@@ -152,7 +304,8 @@ export class CarRuntime {
152
304
 
153
305
  /**
154
306
  * The tools currently registered on this runtime, as a JSON array of full
155
- * `ToolSchema` objects sorted by name.
307
+ * `ToolSchema` objects sorted by name. Every schema includes its runtime-
308
+ * assigned `source` (`builtin|user_defined|subprocess|mcp`).
156
309
  *
157
310
  * Counterpart to `registerTool` / `registerToolSchema`, which had none: a
158
311
  * caller could add tools but never ask what was actually in effect, so a
@@ -296,7 +449,8 @@ export class CarRuntime {
296
449
  // --- Memory / Facts (graph-backed) ---
297
450
 
298
451
  /**
299
- * Add a fact. `kind` is typically "pattern" or "constraint".
452
+ * Add a fact. `kind` is typically "pattern" or "constraint". Optional
453
+ * `factId`, ordered `tags`, and `source` are preserved by the daemon.
300
454
  *
301
455
  * In Daemon mode, rejects with the daemon-unreachable error
302
456
  * instead of silently returning 0 (#146).
@@ -306,9 +460,16 @@ export class CarRuntime {
306
460
  body: string,
307
461
  kind: string,
308
462
  confidence?: number | null,
463
+ factId?: string | null,
464
+ tags?: string[] | null,
465
+ source?: string | null,
309
466
  ): Promise<number>;
310
467
 
311
- /** Query facts via graph spreading activation. Returns a JSON array. */
468
+ /**
469
+ * Query facts via graph spreading activation. Returns a JSON array whose
470
+ * rows include `fact_id` (null for graph nodes without one), `subject`,
471
+ * `body`, `kind`, `confidence`, `tags`, and `source`.
472
+ */
312
473
  queryFacts(query: string, k?: number | null): string;
313
474
 
314
475
  /**
@@ -462,7 +623,37 @@ export class CarRuntime {
462
623
  syncStatus(requestJson: string): Promise<string>;
463
624
  /** `sync.append` — record an op on any surface: `{ surface, payload, scope? }` (B6). */
464
625
  syncAppend(requestJson: string): Promise<string>;
465
- /** `agents.peers` — the agents this runtime can message, from the daemon's live connection table. */
626
+ /** `host.agents` — current host agent registry snapshot. */
627
+ hostAgents(): Promise<string>;
628
+ /** `host.events` — recent host events, newest last; omit `limit` for the daemon default. */
629
+ hostEvents(limit?: number): Promise<string>;
630
+ /** `host.approvals` — pending host approvals. */
631
+ hostApprovals(): Promise<string>;
632
+ /** `host.register_agent` — register an agent on this connection. */
633
+ hostRegisterAgent(requestJson: string): Promise<string>;
634
+ /** `host.unregister_agent` — unregister an agent owned by this connection. */
635
+ hostUnregisterAgent(requestJson: string): Promise<string>;
636
+ /** `host.set_status` — publish status for an agent owned by this connection. */
637
+ hostSetStatus(requestJson: string): Promise<string>;
638
+ /** `host.register_device` — register a device on this connection. */
639
+ hostRegisterDevice(requestJson: string): Promise<string>;
640
+ /** `host.update_device` — update a device owned by this connection. */
641
+ hostUpdateDevice(requestJson: string): Promise<string>;
642
+ /** `host.devices` — current host device registry snapshot. */
643
+ hostDevices(): Promise<string>;
644
+ /** `host.notify` — emit a user-facing host notification. */
645
+ hostNotify(requestJson: string): Promise<string>;
646
+ /** `host.request_approval` — request approval for a gated action. */
647
+ hostRequestApproval(requestJson: string): Promise<string>;
648
+ /** `host.resolve_approval` — resolve one pending host approval. */
649
+ hostResolveApproval(requestJson: string): Promise<string>;
650
+ // `host.subscribe` event delivery is deferred to the callback-aware
651
+ // daemon-session API; subscribing without a consumer would drop the stream.
652
+ /**
653
+ * `agents.peers` — visible peers as JSON. Each row distinguishes the
654
+ * kind-level `can_receive` capability from the current `reachable` delivery
655
+ * preflight; the send remains authoritative.
656
+ */
466
657
  agentsPeers(requestJson: string): Promise<string>;
467
658
  /** `agents.message` — send text to one peer: `{ to, body, summary? }`. The sender is derived server-side. */
468
659
  agentsMessage(requestJson: string): Promise<string>;
@@ -795,6 +986,21 @@ export class CarRuntime {
795
986
  * of silently serving a different model (Parslee-ai/car#888). Absent on
796
987
  * the common path.
797
988
  *
989
+ * `fallback_from` is an ARRAY of every candidate the chain moved past,
990
+ * in the order it tried them: `[{ candidate, reason }, ...]`, where
991
+ * `reason` is one of `"credential_rejected"`, `"credential_absent"`,
992
+ * `"rate_limited"`, `"quota_exhausted"`, `"timed_out"` or `"failed"`.
993
+ * Absent when the first candidate served. Before this, a run whose
994
+ * backbone changed because of a rate limit or a timeout recorded no
995
+ * cause anywhere, so a surprising result got attributed to the code
996
+ * rather than to the model swap (Parslee-ai/car#1351).
997
+ *
998
+ * `reason` is classified from the runtime's typed error, not from error
999
+ * prose. `"credential_rejected"` is deliberately BROADER than
1000
+ * `auth_fallback_from`: it covers a provider refusing an API key, whose
1001
+ * remedy is to fix the key, not to sign in. Do not derive one field
1002
+ * from the other.
1003
+ *
798
1004
  * **Note:** intent is not exposed on the tracked path until the
799
1005
  * positional argument list is converted to an options object —
800
1006
  * this method already takes 9 positional parameters and adding
@@ -1462,7 +1668,10 @@ export class CarRuntime {
1462
1668
 
1463
1669
  /** Structured audit query over the event log (G2). `queryJson` is an
1464
1670
  * EventQuery object (kinds/actionId/proposalId/since/until/dataMatches/limit);
1465
- * returns `{count, events}` as a JSON string, most-recent-first. */
1671
+ * returns `{count, events}` as a JSON string, most-recent-first.
1672
+ * `ActionFailed.data` includes `params_digest`, `expected_effects`, and
1673
+ * `error_class` (`timeout|rejected_by_policy|tool_error|validation|unknown`),
1674
+ * never raw parameters. `ActionSucceeded.data` includes the first two. */
1466
1675
  eventQuery(queryJson: string): Promise<string>;
1467
1676
 
1468
1677
  /** Get/set the event-log retention policy (G2). Pass a
@@ -1498,6 +1707,41 @@ export class CarRuntime {
1498
1707
  * `cost_overage` alert. */
1499
1708
  metricsAlerts(thresholdsJson?: string): Promise<string>;
1500
1709
 
1710
+ /** Self-healing repair loop status: enabled/why-not, cadence, targets,
1711
+ * rejected targets, review panel, engine. */
1712
+ healStatus(): Promise<string>;
1713
+ /** Run one self-healing repair sweep now. May open a pull request; never merges. */
1714
+ healRun(): Promise<string>;
1715
+ /** Self-heal status as JSON: cadence, `auto_fix_enabled`, `max_concurrent`,
1716
+ * `max_per_day`, `max_rounds_per_key`, optional `auto_fix_refusal_reason`,
1717
+ * last tick, source route/refusal,
1718
+ * detector counts, and `filing_mode` (`watch-only` or `pr-only`). */
1719
+
1720
+ selfhealStatus(): Promise<string>;
1721
+
1722
+ /** List active (not dismissed) self-heal detections as JSON. Each includes
1723
+ * `route` and an optional `local_issue_path`. Recurring tool failures add
1724
+ * `eligible`, a secret-safe `reconstructed_call` (`tool` plus exact `params`),
1725
+ * optional owner-private `reconstructed_call_path`, `auto_fix_attempts`,
1726
+ * `auto_fix_exhausted`, `auto_fix_in_progress`, and
1727
+ * `last_auto_fix_attempt` (including `exit_code` and `failure_class`). Remote
1728
+ * deduplication adds `auto_fix_awaiting_review`, `auto_fix_parked`,
1729
+ * `remote_pr_number`, and `remote_pr_url`.
1730
+ * `queryJson` carries optional `kind`, `severity`,
1731
+ * `since`, `offset`, and `limit` (max 500). */
1732
+ selfhealDetections(queryJson?: string): Promise<string>;
1733
+
1734
+ /** Append a dismissal marker for a stable detection dedup key without
1735
+ * deleting history. */
1736
+ selfhealDismiss(dedupKey: string): Promise<string>;
1737
+
1738
+ /** Start one bounded template-owned coder round for an eligible recurring
1739
+ * tool failure. Returns the durable attempt result as JSON. */
1740
+ selfhealFix(dedupKey: string): Promise<string>;
1741
+
1742
+ /** Run one non-overlapping detection tick and default-on auto-fix hook. */
1743
+ selfhealRun(): Promise<string>;
1744
+
1501
1745
  /** Execution log counts and approximate retained native bytes. Returns JSON. */
1502
1746
  eventLogStats(): Promise<string>;
1503
1747
 
@@ -1594,9 +1838,10 @@ export class CarRuntime {
1594
1838
  * automatically.
1595
1839
  *
1596
1840
  * Tools registered via the schemaless `registerTool(name)` bypass type
1597
- * validation; this is the opt-in upgrade path.
1841
+ * validation; this is the opt-in upgrade path. The daemon assigns
1842
+ * `source = "user_defined"`; callers cannot claim another origin.
1598
1843
  *
1599
- * `schemaJson` matches:
1844
+ * `schemaJson` carries the caller-settable fields:
1600
1845
  * ```json
1601
1846
  * {
1602
1847
  * "name": "read_file",
@@ -2579,10 +2824,21 @@ export function reapStaleAgents(
2579
2824
  * register an `agent.chat` handler to serve conversational turns, and/or
2580
2825
  * a `registerToolHandler` for explicit tool `data` parts.
2581
2826
  *
2827
+ * `allow_non_loopback_bind` (boolean, default `false`) is required to bind
2828
+ * anything but loopback. This listener serves NO authentication — there
2829
+ * is no auth parameter, and its router is `NoAuth` — and without
2830
+ * `share_session_runtime` its runtime registers the agent-basics
2831
+ * filesystem tools, so a reachable bind publishes `write_file` /
2832
+ * `edit_file` to anyone who can route to the port. A wildcard bind
2833
+ * (`0.0.0.0:...`) is refused too. To be reachable by other CAR daemons
2834
+ * you want the peer-authenticated, messaging-only listener `car-server`
2835
+ * already runs by default, not this.
2836
+ *
2582
2837
  * Returns `'{"bound":"127.0.0.1:8731"}'` on success. Errors if a
2583
- * server is already running, the bind fails, `share_session_runtime`
2584
- * is set but no session runtime is available (e.g. invoked from a
2585
- * non-WS path), or `paramsJson` is malformed.
2838
+ * server is already running, the bind fails, the bind is non-loopback
2839
+ * without `allow_non_loopback_bind`, `share_session_runtime` is set but no
2840
+ * session runtime is available (e.g. invoked from a non-WS path), or
2841
+ * `paramsJson` is malformed.
2586
2842
  */
2587
2843
  export function startA2AServer(rt: CarRuntime, paramsJson: string): Promise<string>;
2588
2844
 
package/package.json CHANGED
@@ -1,9 +1,23 @@
1
1
  {
2
2
  "name": "car-runtime",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "Common Agent Runtime — a deterministic execution layer for AI agents",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "require": "./index.js",
11
+ "default": "./index.js"
12
+ },
13
+ "./agent-loop": {
14
+ "types": "./agent-loop.d.ts",
15
+ "import": "./agent-loop.mjs",
16
+ "require": "./agent-loop.js",
17
+ "default": "./agent-loop.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
7
21
  "bin": {
8
22
  "car-server": "bin/car-server"
9
23
  },
@@ -36,6 +50,9 @@
36
50
  "files": [
37
51
  "index.js",
38
52
  "index.d.ts",
53
+ "agent-loop.js",
54
+ "agent-loop.mjs",
55
+ "agent-loop.d.ts",
39
56
  "install.js",
40
57
  "assets.json",
41
58
  "bin/car-server",
@@ -45,7 +62,8 @@
45
62
  ],
46
63
  "scripts": {
47
64
  "install": "node install.js",
48
- "prepack": "node sync-docs.js"
65
+ "prepack": "node sync-docs.js",
66
+ "test": "node --test test/install.test.js"
49
67
  },
50
68
  "license": "SEE LICENSE IN LICENSE",
51
69
  "private": false