@gobing-ai/ts-ai-runner 0.4.7 → 0.4.9

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 CHANGED
@@ -13,7 +13,7 @@ bun add @gobing-ai/ts-ai-runner
13
13
  `ts-ai-runner` normalizes the command-line surface of common coding agents so application code can work with stable TypeScript APIs instead of hard-coded executable arguments.
14
14
 
15
15
  | Export | Purpose |
16
- |--------|---------|
16
+ | -------- | --------- |
17
17
  | `AiRunner` | Runs help, version, auth, prompt, and slash commands through a pluggable process executor; can also build a prompt command without executing it. Emits typed events via optional `EventBus<AgentEvents>`. |
18
18
  | `AgentDetector` | Probes supported agent CLIs and parses version output |
19
19
  | `DoctorRunner` | Combines installation and authentication checks into a usability report |
@@ -25,14 +25,15 @@ bun add @gobing-ai/ts-ai-runner
25
25
  | `buildIdentityPreamble()` / `getGitContext()` | Builds team-mode identity, communication context, and git metadata for prompts |
26
26
  | `loadAgentSpecs()` / `saveAgentSpec()` / `deleteAgentSpec()` | Persist agent definitions as YAML-compatible config |
27
27
  | `validateAgentId()` | Enforces agent ID format rules |
28
- | `MessageService` | Thin service wrapper around `@gobing-ai/ts-db/inbox` |
28
+ | `formatMessage()` | Renders a `DrainedMessage` into the line injected into an agent's stdin pipe |
29
+ | `MessageStore` / `DrainedMessage` | ai-runner-owned orchestration persistence port and minimal message view consumed by `TeamOrchestrator` |
29
30
  | `TeamAgentProcess` | Manages a long-running agent subprocess with pipe-mode stdin/stdout |
30
31
  | `TeamOrchestrator` | Loads specs, starts/stops agents, routes durable/live messages, and emits lifecycle events |
31
32
  | `AgentEvents` / `AiRunnerProcessEvents` | Typed event maps for agent and process-level observability |
32
33
  | `AGENT_SHIMS` / `TIER1_PRIORITY` / `TIER2_AGENTS` / `DISPLAY_ORDER` | Agent registry constants |
33
34
  | `isAgentName()` | Type guard for supported agent identifiers |
34
35
 
35
- Supported agent identifiers: `claude`, `codex`, `gemini` (deprecated), `pi`, `omp`, `opencode`, `antigravity-cli`, `openclaw`, `hermes`. The `antigravity` id is a deprecated alias of `antigravity-cli`. See [Deprecation & Aliases](#deprecation--aliases).
36
+ Supported agent identifiers: `claude`, `codex`, `gemini` (deprecated), `pi`, `omp`, `opencode`, `antigravity-cli`, `openclaw`, `hermes`, `grok`. The `antigravity` id is a deprecated alias of `antigravity-cli`. See [Deprecation & Aliases](#deprecation--aliases).
36
37
 
37
38
  ## Architecture
38
39
 
@@ -62,11 +63,13 @@ graph TB
62
63
  TeamOrchestrator["TeamOrchestrator<br/>loadSpecs / startAgent / stopAgent<br/>sendMessage / getAgentStatus / stopAll"]
63
64
  AgentSpec["AgentSpec<br/>(YAML config)<br/>load / save / delete"]
64
65
  TeamAgentProcess["TeamAgentProcess<br/>(pipe-mode subprocess)<br/>start / stop / send / subscribe"]
65
- MessageService["MessageService<br/>(InboxMessageDao wrapper)<br/>enqueue / drain / deliver / fail"]
66
+ MessageStore["<b>MessageStore</b><br/>(ai-runner-owned port)<br/>enqueue / drainPending / markDelivered / markFailed"]
67
+ InboxMessageDao["<i>InboxMessageDao<br/>(@gobing-ai/ts-db/inbox)</i><br/>structural provider"]
66
68
 
67
69
  TeamOrchestrator -->|"loads"| AgentSpec
68
70
  TeamOrchestrator -->|"creates + manages"| TeamAgentProcess
69
- TeamOrchestrator -->|"routes through"| MessageService
71
+ TeamOrchestrator -->|"writes/reads via"| MessageStore
72
+ InboxMessageDao -.->|"satisfies"| MessageStore
70
73
  TeamOrchestrator -->|"resolves command via"| AgentShim
71
74
  TeamOrchestrator -.->|"emits events"| EventBus
72
75
  TeamOrchestrator -.->|"builds preamble"| Identity
@@ -75,6 +78,7 @@ graph TB
75
78
  ```
76
79
 
77
80
  ### One-shot prompt flow
81
+
78
82
  ```mermaid
79
83
  sequenceDiagram
80
84
  participant Caller
@@ -116,7 +120,6 @@ sequenceDiagram
116
120
  participant Shim as AgentShim
117
121
  participant Identity as buildIdentityPreamble
118
122
  participant Proc as TeamAgentProcess
119
- participant Msg as MessageService
120
123
  participant DB as InboxMessageDao
121
124
  participant Agent as Coding Agent (CLI)
122
125
  participant EventBus
@@ -135,15 +138,13 @@ sequenceDiagram
135
138
  Orch->>Proc: start()
136
139
  Proc->>Agent: spawn pipe-mode subprocess
137
140
  Agent-->>Proc: stdout/stderr streams
138
- Orch->>Msg: drain("coder")
139
- Msg->>DB: drain(toId)
140
- DB-->>Msg: pending messages
141
- Msg-->>Orch: messages[]
141
+ Orch->>DB: drainPending("coder")
142
+ DB-->>Orch: pending messages
142
143
  alt pending messages exist
143
144
  loop for each message
144
145
  Orch->>Proc: send(formattedMessage)
145
146
  Proc->>Agent: write to stdin
146
- Orch->>Msg: deliver(msg.id)
147
+ Orch->>DB: markDelivered(msg.id)
147
148
  end
148
149
  end
149
150
  Orch->>EventBus: emit("agent.started")
@@ -151,18 +152,15 @@ sequenceDiagram
151
152
 
152
153
  Note over Host,EventBus: Sending a message (durable + live)
153
154
  Host->>Orch: sendMessage(null, "coder", "Implement task 0005")
154
- Orch->>Msg: enqueue(null, "coder", body)
155
- Msg->>DB: enqueue(from, to, body)
156
- DB-->>Msg: messageId
157
- Msg-->>Orch: messageId
155
+ Orch->>DB: enqueue(null, "coder", body)
156
+ DB-->>Orch: messageId
158
157
  alt agent is running
159
- Orch->>Msg: drain("coder")
160
- Msg->>DB: drain(toId)
161
- DB-->>Msg: pending messages
158
+ Orch->>DB: drainPending("coder")
159
+ DB-->>Orch: pending messages
162
160
  loop for each message
163
161
  Orch->>Proc: send(formattedMessage)
164
162
  Proc->>Agent: write to stdin
165
- Orch->>Msg: deliver(msg.id)
163
+ Orch->>DB: markDelivered(msg.id)
166
164
  end
167
165
  end
168
166
  Orch->>EventBus: emit("agent.message.sent")
@@ -182,9 +180,9 @@ Key design decisions:
182
180
  - **Shims are pure**: `AgentShim` produces `{ command, args }` without touching the filesystem or launching processes. All side effects live in `AiRunner` and `TeamAgentProcess`.
183
181
  - **ProcessExecutor is injectable**: tests inject a stub executor; production uses `NodeProcessExecutor` (or the Bun pipe-process seam for team mode).
184
182
  - **Events are opt-in**: `AiRunnerOptions.events` and `TeamOrchestratorOptions.events` accept an `EventBus<AgentEvents>` for structured observability. Without it, the runner is silent.
185
- - **Team mode is composable**: `AgentSpec`, `TeamAgentProcess`, `MessageService`, and `TeamOrchestrator` are small building blocks. Downstream apps compose them into their own orchestration layer.
183
+ - **Team mode is composable**: `AgentSpec`, `TeamAgentProcess`, and `TeamOrchestrator` are small building blocks. `TeamOrchestrator` depends on the ai-runner-owned `MessageStore` port; `InboxMessageDao` from `@gobing-ai/ts-db/inbox` is one structural provider — no adapter class — and in-memory test doubles implement the port directly. Downstream apps compose them into their own orchestration layer.
186
184
 
187
- The package depends on `@gobing-ai/ts-runtime` for process execution, `@gobing-ai/ts-db` for team-mode inbox types, and `@gobing-ai/ts-infra` for structured logging and EventBus. The target agent CLIs are not bundled; install them separately in the host environment.
185
+ The package depends on `@gobing-ai/ts-runtime` for process execution and `@gobing-ai/ts-infra` for structured logging and EventBus. `@gobing-ai/ts-db` is a development dependency only: production source under `packages/ai-runner/src` has no direct `@gobing-ai/ts-db` imports for message access, and only the DB-backed integration test resolves the concrete `InboxMessageDao`. The target agent CLIs are not bundled; install them separately in the host environment.
188
186
 
189
187
  ## Detect Installed Agents
190
188
 
@@ -312,7 +310,7 @@ const runner = new AiRunner({ events: bus });
312
310
  Available events:
313
311
 
314
312
  | Event | When |
315
- |-------|------|
313
+ | ------- | ------ |
316
314
  | `agent.invoke.start` | Immediately before an agent CLI invocation starts |
317
315
  | `agent.invoke.exit` | After an agent CLI invocation exits |
318
316
  | `agent.started` | When a long-running team agent process starts |
@@ -424,7 +422,7 @@ interface AgentShim {
424
422
  Agent-specific behavior:
425
423
 
426
424
  | Agent | CLI | Tier | Auth check | Prompt flags |
427
- |-------|-----|------|------------|--------------|
425
+ | ------- | ----- | ------ | ------------ | -------------- |
428
426
  | `claude` | `claude` | 1 | `claude auth status` | `-p`, `--continue`, `--model`, `--output-format` |
429
427
  | `codex` | `codex` | 1 | `codex login status` | `exec <prompt>`, `exec resume --last`, `-m`, `--json` |
430
428
  | `gemini` *(deprecated)* | `gemini` | 1 | env-only | `-p`, `-r latest` (resume), `-m`, `-o` |
@@ -433,6 +431,9 @@ Agent-specific behavior:
433
431
  | `opencode` | `opencode` | 1 | `opencode providers` | `run`, `-c`, `-m`, `--format json` |
434
432
  | `antigravity-cli` | `agy` | 1 | env-only | `-p`, `--continue`, `--model` |
435
433
  | `openclaw` | `openclaw` | 2 | `openclaw health` | `agent --local -m` |
434
+ | `hermes` | `hermes` | 1 | `hermes doctor` | `chat -q`, `--continue`, `-m` |
435
+ | `grok` | `grok` | 1 | env/file (`XAI_API_KEY` or `~/.grok/auth.json`) | `-p`, `-c` (resume), `-m`, `--output-format plain\|json` (maps ai-runner `text` → `plain`) |
436
+
436
437
  This is the right layer for UI previews, audit logging, and custom launchers.
437
438
 
438
439
  ## Deprecation & Aliases
@@ -453,15 +454,15 @@ resolveAgentName('cursor'); // → undefined
453
454
  **Current deprecation map:**
454
455
 
455
456
  | Id | Status | Canonical | Notes |
456
- |----|--------|-----------|-------|
457
+ | ---- | -------- | ----------- | ------- |
457
458
  | `antigravity` | alias of `antigravity-cli` | `antigravity-cli` | Old tier-2 id; both use binary `agy`. Resolving warns. |
458
459
  | `gemini` | deprecated | `gemini` (self) → replaced by `antigravity-cli` | Gemini CLI sunset 2026-06-18. Shim stays functional. |
459
460
  | `omp` | canonical | `omp` | First-class; NOT a pi alias. |
460
461
  | `hermes` | canonical | `hermes` | First-class; OpenClaw-compatible but distinct binary. |
462
+ | `grok` | canonical | `grok` | Grok Build CLI; headless via `-p`; auth is env/file only (no status verb). |
461
463
 
462
464
  `getAgentShim()` and `isAgentName()` are alias-aware: passing `'antigravity'` resolves to the `antigravity-cli` shim. `DoctorResult` and `DetectedAgent` surface `deprecated` + `replacedBy` when the resolved canonical id is marked deprecated.
463
465
 
464
-
465
466
  ## Team Mode Primitives
466
467
 
467
468
  The team-mode APIs are intentionally small building blocks. They do not implement an HTTP API, dashboard, or product workflow; downstream apps compose them into their own orchestration layer.
@@ -494,24 +495,38 @@ const specs = loadAgentSpecs('./agents');
494
495
 
495
496
  ### Durable messages
496
497
 
497
- `MessageService` wraps `InboxMessageDao` from `@gobing-ai/ts-db/inbox`. It owns no subprocess behavior; it only persists, drains, marks delivery/failure, and formats messages.
498
+ `TeamOrchestrator` depends on the ai-runner-owned `MessageStore` port a minimal interface with
499
+ `enqueue`, `drainPending`, `markDelivered`, and `markFailed` — plus the `DrainedMessage` view
500
+ containing only the fields the orchestrator consumes (`id`, `fromId`, `body`). The port is the
501
+ orchestration boundary; production source under `packages/ai-runner/src` has no direct
502
+ `@gobing-ai/ts-db` import for message access.
503
+
504
+ `InboxMessageDao` from `@gobing-ai/ts-db/inbox` is one structural provider: it satisfies
505
+ `MessageStore` without an adapter class, so no `ts-db` runtime changes are required. In-memory
506
+ test doubles implement the port directly. Consumers compose `InboxMessageDao` +
507
+ `EventBus<InboxMessageEvents>` and pass the DAO to `TeamOrchestrator`:
498
508
 
499
509
  ```ts
500
- import { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
501
- import { MessageService } from '@gobing-ai/ts-ai-runner';
510
+ import { type BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
511
+ import { InboxMessageDao, type InboxMessageEvents } from '@gobing-ai/ts-db/inbox';
512
+ import { formatMessage, type MessageStore } from '@gobing-ai/ts-ai-runner';
502
513
 
503
- const messages = new MessageService(new InboxMessageDao(adapter));
514
+ const lifecycleBus = new EventBus<BusLifecycleEvents>();
515
+ const events = new EventBus<InboxMessageEvents>({ lifecycleBus });
516
+ const inbox: MessageStore = new InboxMessageDao(adapter, { events });
504
517
 
505
- const id = await messages.enqueue(null, 'coder', 'Review the runtime process seam');
506
- const pending = await messages.drain('coder');
518
+ const id = await inbox.enqueue(null, 'coder', 'Review the runtime process seam');
519
+ const pending = await inbox.drainPending('coder');
507
520
 
508
521
  for (const msg of pending) {
509
- console.log(MessageService.formatMessage(msg));
510
- await messages.deliver(msg.id);
522
+ console.log(formatMessage(msg));
523
+ await inbox.markDelivered(msg.id);
511
524
  }
512
525
  ```
513
526
 
514
- `MessageService` also exposes `fail(msgId, error)`, `inbox(toId, limit?, offset?)`, and `countPending(toId)`.
527
+ Message lifecycle events are metadata-only and do not include the durable message body. A
528
+ `message.failed` event does include the caller-provided error string; pre-redact it when the
529
+ lifecycle bus is attached to persistent System Events observers.
515
530
 
516
531
  ### Persistent agent processes
517
532
 
@@ -547,24 +562,27 @@ unsubscribe();
547
562
 
548
563
  ### Team orchestrator
549
564
 
550
- `TeamOrchestrator` connects specs, shims, processes, and messages. On start it loads an agent spec, builds the agent command through the matching shim, starts the process, drains pending inbox messages, and injects them live. `sendMessage()` always persists first, then injects immediately when the target agent is running.
565
+ `TeamOrchestrator` connects specs, shims, processes, and the `MessageStore` port. On start it loads an agent spec, builds the agent command through the matching shim, starts the process, drains pending inbox messages, and injects them live. `sendMessage()` always persists first, then injects immediately when the target agent is running.
551
566
 
552
567
  ```ts
553
- import { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
554
- import { MessageService, TeamOrchestrator } from '@gobing-ai/ts-ai-runner';
568
+ import { type BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
569
+ import { InboxMessageDao, type InboxMessageEvents } from '@gobing-ai/ts-db/inbox';
570
+ import { TeamOrchestrator, type MessageStore } from '@gobing-ai/ts-ai-runner';
555
571
 
556
- const messages = new MessageService(new InboxMessageDao(adapter));
557
- const team = new TeamOrchestrator('./agents', messages);
572
+ const lifecycleBus = new EventBus<BusLifecycleEvents>();
573
+ const events = new EventBus<InboxMessageEvents>({ lifecycleBus });
574
+ const inbox: MessageStore = new InboxMessageDao(adapter, { events });
575
+ const team = new TeamOrchestrator('./agents', inbox, { lifecycleBus });
558
576
 
559
577
  await team.startAgent('coder');
560
578
  await team.sendMessage(null, 'coder', 'Please implement task 0005');
561
579
 
562
- console.log(team.getAgentStatus('coder')); // running
580
+ console.log(await team.getAgentStatus('coder')); // running
563
581
 
564
582
  await team.stopAll();
565
583
  ```
566
584
 
567
- The orchestrator also provides `restartAgent(id)`, `getRunningAgents()`, `getPeerSpecs(workspace, excludeId?)`, and `on(event, listener)` for event subscription.
585
+ Any object implementing `MessageStore` can be supplied to `TeamOrchestrator`; `InboxMessageDao` is one structural provider, and in-memory test doubles implement the port directly without an adapter class. The orchestrator also provides `restartAgent(id)`, `getRunningAgents()`, `getPeerSpecs(workspace, excludeId?)`, and `on(event, listener)` for event subscription.
568
586
 
569
587
  ## Adding a New Coding Agent
570
588
 
@@ -631,7 +649,7 @@ If the agent supports an auth-status command, `getAuthCommand()` already returns
631
649
  ### Summary checklist
632
650
 
633
651
  | Step | File | What to change |
634
- |------|------|----------------|
652
+ | ------ | ------ | ---------------- |
635
653
  | Shim | `src/agents/shims.ts` | Add `AgentShim` impl, update `AgentName`, `AGENT_SHIMS`, `DISPLAY_ORDER` |
636
654
  | Tier | `src/agents/shims.ts` | Add to `TIER1_PRIORITY` or `TIER2_AGENTS` |
637
655
  | Slash | `src/slash-command.ts` | Add case if dialect differs from default |
@@ -646,5 +664,5 @@ After these changes, the new agent is automatically available to `AiRunner`, `Ag
646
664
  - It does not install agent CLIs or manage credentials.
647
665
  - It does not parse agent responses beyond process result capture.
648
666
  - It keeps subprocess launching behind `ProcessExecutor` / `PipeProcess`, so tests can stay deterministic.
649
- - Team-mode persistence is delegated to `@gobing-ai/ts-db/inbox`; host apps own migrations and adapter lifecycle.
667
+ - Team-mode persistence is consumed through the ai-runner-owned `MessageStore` port; `InboxMessageDao` from `@gobing-ai/ts-db/inbox` is one structural provider. Host apps own migrations and adapter lifecycle, and `@gobing-ai/ts-db` is a development dependency of this package (only the DB-backed integration test resolves the concrete DAO).
650
668
  - Platform APIs (`node:fs`, `node:path`, `Bun.spawn`, etc.) are confined to `@gobing-ai/ts-runtime` per ADR-011. This package accesses them through the runtime's `FileSystem`, `ProcessExecutor`, and path utilities.
@@ -37,6 +37,8 @@ export interface AuthContext {
37
37
  * - **gemini** — credential-like content in `~/.gemini/settings.json`.
38
38
  * - **codex** — `codex login status` CLI output, falling back to an
39
39
  * `~/.codex/auth{.json,}` credential file.
40
+ * - **grok** — non-empty `XAI_API_KEY` env, else non-empty `~/.grok/auth.json`;
41
+ * no CLI auth-status verb (shim `getAuthCommand` is null).
40
42
  * - **pi / omp** — a non-empty `GOOGLE_API_KEY` or `ANTHROPIC_API_KEY` in env
41
43
  * (an empty export is not a usable credential), else the CLI auth probe.
42
44
  * - **others** — the shim's auth command; matched against {@link AUTH_PATTERNS}.
@@ -1 +1 @@
1
- {"version":3,"file":"auth-shims.d.ts","sourceRoot":"","sources":["../../src/agents/auth-shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAA2B,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAmC,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,KAAK,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAExE,qDAAqD;AACrD,MAAM,WAAW,WAAW;IACxB,0CAA0C;IAC1C,MAAM,EAAE,QAAQ,CAAC;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,0EAA0E;IAC1E,UAAU,EAAE,UAAU,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AA6CD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAgB5F;AA8CD,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"auth-shims.d.ts","sourceRoot":"","sources":["../../src/agents/auth-shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAA2B,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAmC,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,KAAK,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAExE,qDAAqD;AACrD,MAAM,WAAW,WAAW;IACxB,0CAA0C;IAC1C,MAAM,EAAE,QAAQ,CAAC;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,0EAA0E;IAC1E,UAAU,EAAE,UAAU,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AA6CD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAiB5F;AA+DD,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -49,6 +49,8 @@ function isNonEmpty(value) {
49
49
  * - **gemini** — credential-like content in `~/.gemini/settings.json`.
50
50
  * - **codex** — `codex login status` CLI output, falling back to an
51
51
  * `~/.codex/auth{.json,}` credential file.
52
+ * - **grok** — non-empty `XAI_API_KEY` env, else non-empty `~/.grok/auth.json`;
53
+ * no CLI auth-status verb (shim `getAuthCommand` is null).
52
54
  * - **pi / omp** — a non-empty `GOOGLE_API_KEY` or `ANTHROPIC_API_KEY` in env
53
55
  * (an empty export is not a usable credential), else the CLI auth probe.
54
56
  * - **others** — the shim's auth command; matched against {@link AUTH_PATTERNS}.
@@ -63,6 +65,8 @@ export async function isAuthenticated(agent, ctx) {
63
65
  return geminiSettingsContainCredentials(fs, home);
64
66
  if (agent === 'codex')
65
67
  return checkCodexAuth(ctx.runner, fs, home, timeout);
68
+ if (agent === 'grok')
69
+ return checkGrokAuth(fs, home, env);
66
70
  // pi and omp read provider keys from the environment; require a non-empty
67
71
  // value rather than mere presence (an empty export is not a usable credential).
68
72
  if ((agent === 'pi' || agent === 'omp') && (isNonEmpty(env.GOOGLE_API_KEY) || isNonEmpty(env.ANTHROPIC_API_KEY))) {
@@ -78,6 +82,20 @@ async function checkCodexAuth(runner, fs, home, timeout) {
78
82
  (await hasNonEmptyFile(fs, joinPath(home, '.codex', 'auth')));
79
83
  return hasFile ? 'authenticated' : 'unknown';
80
84
  }
85
+ /**
86
+ * Grok has no auth-status CLI verb. Credential sources (never false-negative
87
+ * to `unauthenticated` when missing):
88
+ * 1. non-empty `XAI_API_KEY`
89
+ * 2. non-empty `~/.grok/auth.json`
90
+ * Else `unknown`.
91
+ */
92
+ async function checkGrokAuth(fs, home, env) {
93
+ if (isNonEmpty(env.XAI_API_KEY))
94
+ return 'authenticated';
95
+ if (await hasNonEmptyFile(fs, joinPath(home, '.grok', 'auth.json')))
96
+ return 'authenticated';
97
+ return 'unknown';
98
+ }
81
99
  async function geminiSettingsContainCredentials(fs, home) {
82
100
  try {
83
101
  const content = await fs.readFile(joinPath(home, '.gemini', 'settings.json'));
@@ -1,5 +1,5 @@
1
1
  /** Identifier for one supported coding agent (canonical id). */
2
- export type AgentName = 'claude' | 'codex' | 'gemini' | 'pi' | 'opencode' | 'antigravity-cli' | 'openclaw' | 'hermes' | 'omp';
2
+ export type AgentName = 'claude' | 'codex' | 'gemini' | 'pi' | 'opencode' | 'antigravity-cli' | 'openclaw' | 'hermes' | 'omp' | 'grok';
3
3
  /** Output mode for prompt invocations. */
4
4
  export type OutputMode = 'text' | 'json';
5
5
  /** Concrete executable and argv pair returned by an agent shim. */
@@ -1 +1 @@
1
- {"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"AAEA,gEAAgE;AAChE,MAAM,MAAM,SAAS,GACf,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,IAAI,GACJ,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,QAAQ,GACR,KAAK,CAAC;AAEZ,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC,mEAAmE;AACnE,MAAM,WAAW,WAAW;IACxB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;CACnC;AAED,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACtB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,wFAAwF;IACxF,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IACvC,oCAAoC;IACpC,cAAc,IAAI,WAAW,CAAC;IAC9B,yCAAyC;IACzC,iBAAiB,IAAI,WAAW,CAAC;IACjC,yCAAyC;IACzC,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,CAAC;IACtD,8DAA8D;IAC9D,cAAc,IAAI,WAAW,GAAG,IAAI,CAAC;CACxC;AA+JD,6DAA6D;AAC7D,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAU9D,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,cAAc,EAAE,SAAS,SAAS,EAQ9C,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,aAAa,EAAE,SAAS,SAAS,EAU7C,CAAC;AAEF,6CAA6C;AAC7C,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,SAAS,CAAyB,CAAC;AAU1E;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAQrE;AAED;mEACmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAMxD"}
1
+ {"version":3,"file":"shims.d.ts","sourceRoot":"","sources":["../../src/agents/shims.ts"],"names":[],"mappings":"AAEA,gEAAgE;AAChE,MAAM,MAAM,SAAS,GACf,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,IAAI,GACJ,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,QAAQ,GACR,KAAK,GACL,MAAM,CAAC;AAEb,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC,mEAAmE;AACnE,MAAM,WAAW,WAAW;IACxB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;CACnC;AAED,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACtB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,wFAAwF;IACxF,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IACvC,oCAAoC;IACpC,cAAc,IAAI,WAAW,CAAC;IAC9B,yCAAyC;IACzC,iBAAiB,IAAI,WAAW,CAAC;IACjC,yCAAyC;IACzC,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,CAAC;IACtD,8DAA8D;IAC9D,cAAc,IAAI,WAAW,GAAG,IAAI,CAAC;CACxC;AAuLD,6DAA6D;AAC7D,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAW9D,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,cAAc,EAAE,SAAS,SAAS,EAS9C,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,aAAa,EAAE,SAAS,SAAS,EAW7C,CAAC;AAEF,6CAA6C;AAC7C,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,SAAS,CAAyB,CAAC;AAU1E;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAQrE;AAED;mEACmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAMxD"}
@@ -166,6 +166,31 @@ const ompShim = {
166
166
  },
167
167
  getAuthCommand: () => ({ command: 'omp', args: ['--list-models'] }),
168
168
  };
169
+ /**
170
+ * Grok Build CLI (`grok`) — xAI coding agent. Headless one-shot via `-p`/`--single`;
171
+ * continue with `-c`; model via `-m`. Output formats are `plain`/`json`/`streaming-json`
172
+ * (map ai-runner `text` → `plain`). No auth-status verb — `getAuthCommand` is null;
173
+ * credential probing lives in auth-shims (env / `~/.grok/auth.json`).
174
+ */
175
+ const grokShim = {
176
+ name: 'grok',
177
+ command: 'grok',
178
+ tier: 1,
179
+ getHelpCommand: () => ({ command: 'grok', args: ['--help'] }),
180
+ getVersionCommand: () => ({ command: 'grok', args: ['--version'] }),
181
+ getPromptCommand: (options) => {
182
+ const args = ['-p', options.input ?? ''];
183
+ if (options.continue === true)
184
+ args.push('-c');
185
+ if (options.model !== undefined)
186
+ args.push('-m', options.model);
187
+ // Grok has no `text` format; map ai-runner OutputMode `text` → `plain`.
188
+ const format = (options.mode ?? 'text') === 'json' ? 'json' : 'plain';
189
+ args.push('--output-format', format);
190
+ return { command: 'grok', args };
191
+ },
192
+ getAuthCommand: () => null,
193
+ };
169
194
  /** All bundled agent shims keyed by canonical agent name. */
170
195
  export const AGENT_SHIMS = {
171
196
  claude: claudeShim,
@@ -177,6 +202,7 @@ export const AGENT_SHIMS = {
177
202
  openclaw: openclawShim,
178
203
  hermes: hermesShim,
179
204
  omp: ompShim,
205
+ grok: grokShim,
180
206
  };
181
207
  /** Tier-1 auto-selection priority. Deprecated ids are excluded. */
182
208
  export const TIER1_PRIORITY = [
@@ -187,6 +213,7 @@ export const TIER1_PRIORITY = [
187
213
  'claude',
188
214
  'hermes',
189
215
  'opencode',
216
+ 'grok',
190
217
  ];
191
218
  /** Display order for doctor and list commands. */
192
219
  export const DISPLAY_ORDER = [
@@ -199,6 +226,7 @@ export const DISPLAY_ORDER = [
199
226
  'antigravity-cli',
200
227
  'openclaw',
201
228
  'hermes',
229
+ 'grok',
202
230
  ];
203
231
  /** Set of gateway/TUI-constrained agents. */
204
232
  export const TIER2_AGENTS = new Set(['openclaw']);
@@ -1,4 +1,4 @@
1
- import { type EventBus, type Logger } from '@gobing-ai/ts-infra';
1
+ import { type BusLifecycleEvents, EventBus, type Logger } from '@gobing-ai/ts-infra';
2
2
  import { type ProcessExecutor, type TracerPort } from '@gobing-ai/ts-runtime';
3
3
  import { type AgentName, type PromptOptions, type ShimCommand } from './agents/shims';
4
4
  import type { AgentEvents, AiRunnerProcessEvents } from './events';
@@ -38,6 +38,12 @@ export interface AiRunnerOptions {
38
38
  processEvents?: EventBus<AiRunnerProcessEvents>;
39
39
  /** Event bus receiving agent-level invocation observability. */
40
40
  events?: EventBus<AgentEvents>;
41
+ /**
42
+ * Optional lifecycle bus to bridge `agent.*` and `process.*` events into
43
+ * the application System Events stream (R4). When `events` / `processEvents`
44
+ * are omitted the runner constructs internal buses parented to this bus.
45
+ */
46
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
41
47
  /** Tracer adapter for the default executor. Defaults to `ts-infra` traceAsync. */
42
48
  tracer?: TracerPort;
43
49
  }
@@ -48,6 +54,8 @@ export declare class AiRunner {
48
54
  private readonly defaultTimeout;
49
55
  private readonly logger;
50
56
  private readonly events;
57
+ /** Internal process-level observability bus, parented to `lifecycleBus` when auto-constructed. Exposed for introspection/testing. */
58
+ readonly processEvents: EventBus<AiRunnerProcessEvents> | undefined;
51
59
  constructor(options?: AiRunnerOptions);
52
60
  /** Run an agent help command. */
53
61
  runHelpCommand(agent: AgentName, options?: AgentRunOptions): Promise<AgentRunResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"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"}
package/dist/ai-runner.js CHANGED
@@ -1,5 +1,5 @@
1
- import { getLogger, traceAsync } from '@gobing-ai/ts-infra';
2
- import { getProcessCwd, NodeProcessExecutor, } from '@gobing-ai/ts-runtime';
1
+ import { EventBus, getLogger, traceAsync } from '@gobing-ai/ts-infra';
2
+ import { getProcessCwd, nodeBunFactory, } from '@gobing-ai/ts-runtime';
3
3
  import { getAgentShim } from './agents/shims.js';
4
4
  import { buildIdentityPreamble } from './identity.js';
5
5
  import { translateSlashCommand } from './slash-command.js';
@@ -10,15 +10,23 @@ export class AiRunner {
10
10
  defaultTimeout;
11
11
  logger;
12
12
  events;
13
+ /** Internal process-level observability bus, parented to `lifecycleBus` when auto-constructed. Exposed for introspection/testing. */
14
+ processEvents;
13
15
  constructor(options = {}) {
16
+ const processEvents = options.processEvents ??
17
+ (options.lifecycleBus
18
+ ? new EventBus({ lifecycleBus: options.lifecycleBus })
19
+ : undefined);
20
+ const events = options.events ??
21
+ (options.lifecycleBus ? new EventBus({ lifecycleBus: options.lifecycleBus }) : undefined);
14
22
  this.processExecutor =
15
23
  options.processExecutor ??
16
- new NodeProcessExecutor({
17
- ...(options.processEvents !== undefined
24
+ nodeBunFactory.createProcessExecutor({
25
+ ...(processEvents !== undefined
18
26
  ? {
19
27
  events: {
20
28
  emit: (event, detail) => {
21
- void options.processEvents?.emit(event, detail);
29
+ void processEvents?.emit(event, detail);
22
30
  },
23
31
  },
24
32
  }
@@ -30,7 +38,8 @@ export class AiRunner {
30
38
  this.defaultCwd = options.defaultCwd;
31
39
  this.defaultTimeout = options.defaultTimeout;
32
40
  this.logger = options.logger ?? getLogger('ai-runner');
33
- this.events = options.events;
41
+ this.events = events;
42
+ this.processEvents = processEvents;
34
43
  }
35
44
  /** Run an agent help command. */
36
45
  runHelpCommand(agent, options = {}) {
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './ai-runner';
6
6
  export * from './doctor-runner';
7
7
  export * from './events';
8
8
  export * from './identity';
9
+ export * from './message-store';
9
10
  export * from './messages';
10
11
  export * from './model-health-probe';
11
12
  export * from './slash-command';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export * from './ai-runner.js';
6
6
  export * from './doctor-runner.js';
7
7
  export * from './events.js';
8
8
  export * from './identity.js';
9
+ export * from './message-store.js';
9
10
  export * from './messages.js';
10
11
  export * from './model-health-probe.js';
11
12
  export * from './slash-command.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ai-runner-owned orchestration persistence boundary (ADR-023 follow-up A4).
3
+ *
4
+ * `TeamOrchestrator` depends only on this port. `InboxMessageDao` from
5
+ * `@gobing-ai/ts-db/inbox` satisfies it structurally — no adapter class — and
6
+ * in-memory test doubles implement it directly.
7
+ */
8
+ /**
9
+ * Minimal read-only view of a drained message containing only the fields
10
+ * `ts-ai-runner` consumes. It deliberately does not mirror the full
11
+ * `InboxMessage` persistence model.
12
+ */
13
+ export interface DrainedMessage {
14
+ readonly id: string;
15
+ readonly fromId: string | null;
16
+ readonly body: string;
17
+ }
18
+ /**
19
+ * Message store operations consumed by `TeamOrchestrator`. Mirrors the subset
20
+ * of `InboxMessageDao` operations the orchestrator uses: enqueue a message,
21
+ * drain pending messages for a recipient, mark a message delivered, and mark
22
+ * a message failed.
23
+ */
24
+ export interface MessageStore {
25
+ enqueue(fromId: string | null, toId: string, body: string, inReplyTo?: string): Promise<string>;
26
+ drainPending(toId: string): Promise<DrainedMessage[]>;
27
+ markDelivered(msgId: string): Promise<void>;
28
+ markFailed(msgId: string, error: string): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=message-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-store.d.ts","sourceRoot":"","sources":["../src/message-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChG,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACtD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ai-runner-owned orchestration persistence boundary (ADR-023 follow-up A4).
3
+ *
4
+ * `TeamOrchestrator` depends only on this port. `InboxMessageDao` from
5
+ * `@gobing-ai/ts-db/inbox` satisfies it structurally — no adapter class — and
6
+ * in-memory test doubles implement it directly.
7
+ */
@@ -1,4 +1,4 @@
1
- import type { InboxMessage } from '@gobing-ai/ts-db/inbox';
2
- /** Renders an inbox message into the line injected into an agent's stdin. */
3
- export declare function formatMessage(msg: InboxMessage): string;
1
+ import type { DrainedMessage } from './message-store';
2
+ /** Renders a drained message into the line injected into an agent's stdin. */
3
+ export declare function formatMessage(msg: DrainedMessage): string;
4
4
  //# sourceMappingURL=messages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAEvD"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAEzD"}
package/dist/messages.js CHANGED
@@ -1,4 +1,4 @@
1
- /** Renders an inbox message into the line injected into an agent's stdin. */
1
+ /** Renders a drained message into the line injected into an agent's stdin. */
2
2
  export function formatMessage(msg) {
3
3
  return `[task from=${msg.fromId ?? 'operator'} id=${msg.id}] ${msg.body}`;
4
4
  }
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { type Logger } from '@gobing-ai/ts-infra';
3
- import { ProcessExecutor } from '@gobing-ai/ts-runtime';
3
+ import { type ProcessExecutor } from '@gobing-ai/ts-runtime';
4
4
  import type { AgentSpec } from './agent-spec';
5
5
  /** Options for spawning a team agent subprocess. */
6
6
  export interface AgentProcessOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"team-agent-process.d.ts","sourceRoot":"","sources":["../src/team-agent-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAoB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;gBAErD,OAAO,EAAE,mBAAmB;IASlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAerD,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAOvD,SAAS,IAAI,aAAa;IAI1B,MAAM,IAAI,MAAM,GAAG,IAAI;IAIvB,WAAW,IAAI,MAAM,GAAG,IAAI;YAId,IAAI;IAiBlB,OAAO,CAAC,IAAI;CAOf"}
1
+ {"version":3,"file":"team-agent-process.d.ts","sourceRoot":"","sources":["../src/team-agent-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEvD;;;GAGG;AACH,qBAAa,gBAAgB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;gBAErD,OAAO,EAAE,mBAAmB;IASlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAerD,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAOvD,SAAS,IAAI,aAAa;IAI1B,MAAM,IAAI,MAAM,GAAG,IAAI;IAIvB,WAAW,IAAI,MAAM,GAAG,IAAI;YAId,IAAI;IAiBlB,OAAO,CAAC,IAAI;CAOf"}
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { getLogger } from '@gobing-ai/ts-infra';
3
- import { ProcessExecutor } from '@gobing-ai/ts-runtime';
3
+ import { nodeBunFactory } from '@gobing-ai/ts-runtime';
4
4
  /**
5
5
  * Manages the lifecycle of a single agent subprocess — start, stop, message send, and stdout/stderr subscription.
6
6
  * The identity preamble is built by `TeamOrchestrator` and baked into `command` before the process is constructed.
@@ -21,7 +21,7 @@ export class TeamAgentProcess {
21
21
  this.command = options.command;
22
22
  this.env = options.env;
23
23
  this.cwd = options.cwd ?? options.spec.workspace;
24
- this.processExecutor = options.processExecutor ?? new ProcessExecutor();
24
+ this.processExecutor = options.processExecutor ?? nodeBunFactory.createProcessExecutor();
25
25
  this.logger = options.logger ?? getLogger('team-agent');
26
26
  }
27
27
  async start() {
@@ -1,13 +1,19 @@
1
- import type { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
2
- import { EventBus } from '@gobing-ai/ts-infra';
1
+ import { type BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
3
2
  import type { AgentSpec } from './agent-spec';
4
3
  import type { AgentEvents } from './events';
4
+ import type { MessageStore } from './message-store';
5
5
  import { TeamAgentProcess } from './team-agent-process';
6
6
  type AgentProcessFactory = (options: ConstructorParameters<typeof TeamAgentProcess>[0]) => TeamAgentProcess;
7
7
  /** Configuration options for `TeamOrchestrator`. */
8
8
  export interface TeamOrchestratorOptions {
9
9
  processFactory?: AgentProcessFactory;
10
10
  events?: EventBus<AgentEvents>;
11
+ /**
12
+ * Optional lifecycle bus to bridge `agent.*` events into the application
13
+ * System Events stream (R4). When `events` is omitted the orchestrator
14
+ * constructs an internal `EventBus<AgentEvents>` parented to this bus.
15
+ */
16
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
11
17
  }
12
18
  /**
13
19
  * Orchestrates a team of AI agents — loads specs, starts/stops agent processes, routes messages between them,
@@ -20,7 +26,7 @@ export declare class TeamOrchestrator {
20
26
  private readonly running;
21
27
  private readonly processFactory;
22
28
  private readonly events;
23
- constructor(configDir: string, inbox: InboxMessageDao, options?: TeamOrchestratorOptions);
29
+ constructor(configDir: string, inbox: MessageStore, options?: TeamOrchestratorOptions);
24
30
  loadSpecs(): Promise<AgentSpec[]>;
25
31
  getSpec(id: string): Promise<AgentSpec | undefined>;
26
32
  startAgent(id: string): Promise<TeamAgentProcess>;
@@ -1 +1 @@
1
- {"version":3,"file":"team-orchestrator.d.ts","sourceRoot":"","sources":["../src/team-orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,KAAK,mBAAmB,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,gBAAgB,CAAC;AAE5G,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACpC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAOrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAP1B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG1B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,eAAe,EACvC,OAAO,GAAE,uBAA4B;IAMnC,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAKjC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAKnD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiCjD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAKnD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQzG,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAI3C,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAMlF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAKzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;YAKjE,WAAW;IAMzB,OAAO,CAAC,gBAAgB;YAMV,qBAAqB;YAIrB,UAAU;CAa3B"}
1
+ {"version":3,"file":"team-orchestrator.d.ts","sourceRoot":"","sources":["../src/team-orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,KAAK,mBAAmB,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,gBAAgB,CAAC;AAE5G,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACpC,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/B;;;;OAIG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAOrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAP1B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG1B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,EACpC,OAAO,GAAE,uBAA4B;IAUnC,SAAS,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAKjC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAKnD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiCjD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAKnD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQzG,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAI3C,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAMlF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAKzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;YAKjE,WAAW;IAMzB,OAAO,CAAC,gBAAgB;YAMV,qBAAqB;YAIrB,UAAU;CAa3B"}
@@ -19,7 +19,11 @@ export class TeamOrchestrator {
19
19
  this.configDir = configDir;
20
20
  this.inbox = inbox;
21
21
  this.processFactory = options.processFactory ?? ((processOptions) => new TeamAgentProcess(processOptions));
22
- this.events = options.events ?? new EventBus();
22
+ this.events =
23
+ options.events ??
24
+ (options.lifecycleBus
25
+ ? new EventBus({ lifecycleBus: options.lifecycleBus })
26
+ : new EventBus());
23
27
  }
24
28
  async loadSpecs() {
25
29
  this.specs = await loadAgentSpecs(this.configDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-ai-runner",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "@gobing-ai/ts-ai-runner — Coding-agent shims, detection, doctor checks, and prompt execution.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,12 +47,12 @@
47
47
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-ai-runner-v<version> && git push --tags' && exit 1"
48
48
  },
49
49
  "dependencies": {
50
- "@gobing-ai/ts-db": "^0.4.7",
51
- "@gobing-ai/ts-infra": "^0.4.7",
52
- "@gobing-ai/ts-runtime": "^0.4.7"
50
+ "@gobing-ai/ts-infra": "^0.4.9",
51
+ "@gobing-ai/ts-runtime": "^0.4.9"
53
52
  },
54
53
  "devDependencies": {
55
- "@types/bun": "1.3.14"
54
+ "@types/bun": "1.3.14",
55
+ "@gobing-ai/ts-db": "^0.4.9"
56
56
  },
57
57
  "publishConfig": {
58
58
  "access": "public"
@@ -83,6 +83,8 @@ function isNonEmpty(value: string | undefined): boolean {
83
83
  * - **gemini** — credential-like content in `~/.gemini/settings.json`.
84
84
  * - **codex** — `codex login status` CLI output, falling back to an
85
85
  * `~/.codex/auth{.json,}` credential file.
86
+ * - **grok** — non-empty `XAI_API_KEY` env, else non-empty `~/.grok/auth.json`;
87
+ * no CLI auth-status verb (shim `getAuthCommand` is null).
86
88
  * - **pi / omp** — a non-empty `GOOGLE_API_KEY` or `ANTHROPIC_API_KEY` in env
87
89
  * (an empty export is not a usable credential), else the CLI auth probe.
88
90
  * - **others** — the shim's auth command; matched against {@link AUTH_PATTERNS}.
@@ -96,6 +98,7 @@ export async function isAuthenticated(agent: AgentName, ctx: AuthContext): Promi
96
98
 
97
99
  if (agent === 'gemini') return geminiSettingsContainCredentials(fs, home);
98
100
  if (agent === 'codex') return checkCodexAuth(ctx.runner, fs, home, timeout);
101
+ if (agent === 'grok') return checkGrokAuth(fs, home, env);
99
102
 
100
103
  // pi and omp read provider keys from the environment; require a non-empty
101
104
  // value rather than mere presence (an empty export is not a usable credential).
@@ -115,6 +118,23 @@ async function checkCodexAuth(runner: AiRunner, fs: FileSystem, home: string, ti
115
118
  return hasFile ? 'authenticated' : 'unknown';
116
119
  }
117
120
 
121
+ /**
122
+ * Grok has no auth-status CLI verb. Credential sources (never false-negative
123
+ * to `unauthenticated` when missing):
124
+ * 1. non-empty `XAI_API_KEY`
125
+ * 2. non-empty `~/.grok/auth.json`
126
+ * Else `unknown`.
127
+ */
128
+ async function checkGrokAuth(
129
+ fs: FileSystem,
130
+ home: string,
131
+ env: Record<string, string | undefined>,
132
+ ): Promise<AuthState> {
133
+ if (isNonEmpty(env.XAI_API_KEY)) return 'authenticated';
134
+ if (await hasNonEmptyFile(fs, joinPath(home, '.grok', 'auth.json'))) return 'authenticated';
135
+ return 'unknown';
136
+ }
137
+
118
138
  async function geminiSettingsContainCredentials(fs: FileSystem, home: string): Promise<AuthState> {
119
139
  try {
120
140
  const content = await fs.readFile(joinPath(home, '.gemini', 'settings.json'));
@@ -10,7 +10,8 @@ export type AgentName =
10
10
  | 'antigravity-cli'
11
11
  | 'openclaw'
12
12
  | 'hermes'
13
- | 'omp';
13
+ | 'omp'
14
+ | 'grok';
14
15
 
15
16
  /** Output mode for prompt invocations. */
16
17
  export type OutputMode = 'text' | 'json';
@@ -232,6 +233,30 @@ const ompShim: AgentShim = {
232
233
  getAuthCommand: () => ({ command: 'omp', args: ['--list-models'] }),
233
234
  };
234
235
 
236
+ /**
237
+ * Grok Build CLI (`grok`) — xAI coding agent. Headless one-shot via `-p`/`--single`;
238
+ * continue with `-c`; model via `-m`. Output formats are `plain`/`json`/`streaming-json`
239
+ * (map ai-runner `text` → `plain`). No auth-status verb — `getAuthCommand` is null;
240
+ * credential probing lives in auth-shims (env / `~/.grok/auth.json`).
241
+ */
242
+ const grokShim: AgentShim = {
243
+ name: 'grok',
244
+ command: 'grok',
245
+ tier: 1,
246
+ getHelpCommand: () => ({ command: 'grok', args: ['--help'] }),
247
+ getVersionCommand: () => ({ command: 'grok', args: ['--version'] }),
248
+ getPromptCommand: (options) => {
249
+ const args = ['-p', options.input ?? ''];
250
+ if (options.continue === true) args.push('-c');
251
+ if (options.model !== undefined) args.push('-m', options.model);
252
+ // Grok has no `text` format; map ai-runner OutputMode `text` → `plain`.
253
+ const format = (options.mode ?? 'text') === 'json' ? 'json' : 'plain';
254
+ args.push('--output-format', format);
255
+ return { command: 'grok', args };
256
+ },
257
+ getAuthCommand: () => null,
258
+ };
259
+
235
260
  /** All bundled agent shims keyed by canonical agent name. */
236
261
  export const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>> = {
237
262
  claude: claudeShim,
@@ -243,6 +268,7 @@ export const AGENT_SHIMS: Readonly<Record<AgentName, AgentShim>> = {
243
268
  openclaw: openclawShim,
244
269
  hermes: hermesShim,
245
270
  omp: ompShim,
271
+ grok: grokShim,
246
272
  };
247
273
 
248
274
  /** Tier-1 auto-selection priority. Deprecated ids are excluded. */
@@ -254,6 +280,7 @@ export const TIER1_PRIORITY: readonly AgentName[] = [
254
280
  'claude',
255
281
  'hermes',
256
282
  'opencode',
283
+ 'grok',
257
284
  ];
258
285
 
259
286
  /** Display order for doctor and list commands. */
@@ -267,6 +294,7 @@ export const DISPLAY_ORDER: readonly AgentName[] = [
267
294
  'antigravity-cli',
268
295
  'openclaw',
269
296
  'hermes',
297
+ 'grok',
270
298
  ];
271
299
 
272
300
  /** Set of gateway/TUI-constrained agents. */
package/src/ai-runner.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type EventBus, getLogger, type Logger, traceAsync } from '@gobing-ai/ts-infra';
1
+ import { type BusLifecycleEvents, EventBus, getLogger, type Logger, traceAsync } from '@gobing-ai/ts-infra';
2
2
  import {
3
3
  getProcessCwd,
4
- NodeProcessExecutor,
4
+ nodeBunFactory,
5
5
  type ProcessExecutor,
6
6
  type ProcessResult,
7
7
  type TracerPort,
@@ -49,6 +49,12 @@ export interface AiRunnerOptions {
49
49
  processEvents?: EventBus<AiRunnerProcessEvents>;
50
50
  /** Event bus receiving agent-level invocation observability. */
51
51
  events?: EventBus<AgentEvents>;
52
+ /**
53
+ * Optional lifecycle bus to bridge `agent.*` and `process.*` events into
54
+ * the application System Events stream (R4). When `events` / `processEvents`
55
+ * are omitted the runner constructs internal buses parented to this bus.
56
+ */
57
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
52
58
  /** Tracer adapter for the default executor. Defaults to `ts-infra` traceAsync. */
53
59
  tracer?: TracerPort;
54
60
  }
@@ -60,16 +66,26 @@ export class AiRunner {
60
66
  private readonly defaultTimeout: number | undefined;
61
67
  private readonly logger: Logger;
62
68
  private readonly events: EventBus<AgentEvents> | undefined;
69
+ /** Internal process-level observability bus, parented to `lifecycleBus` when auto-constructed. Exposed for introspection/testing. */
70
+ readonly processEvents: EventBus<AiRunnerProcessEvents> | undefined;
63
71
 
64
72
  constructor(options: AiRunnerOptions = {}) {
73
+ const processEvents =
74
+ options.processEvents ??
75
+ (options.lifecycleBus
76
+ ? new EventBus<AiRunnerProcessEvents>({ lifecycleBus: options.lifecycleBus })
77
+ : undefined);
78
+ const events =
79
+ options.events ??
80
+ (options.lifecycleBus ? new EventBus<AgentEvents>({ lifecycleBus: options.lifecycleBus }) : undefined);
65
81
  this.processExecutor =
66
82
  options.processExecutor ??
67
- new NodeProcessExecutor({
68
- ...(options.processEvents !== undefined
83
+ nodeBunFactory.createProcessExecutor({
84
+ ...(processEvents !== undefined
69
85
  ? {
70
86
  events: {
71
87
  emit: (event, detail) => {
72
- void options.processEvents?.emit(event, detail);
88
+ void processEvents?.emit(event, detail);
73
89
  },
74
90
  },
75
91
  }
@@ -81,7 +97,8 @@ export class AiRunner {
81
97
  this.defaultCwd = options.defaultCwd;
82
98
  this.defaultTimeout = options.defaultTimeout;
83
99
  this.logger = options.logger ?? getLogger('ai-runner');
84
- this.events = options.events;
100
+ this.events = events;
101
+ this.processEvents = processEvents;
85
102
  }
86
103
 
87
104
  /** Run an agent help command. */
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './ai-runner';
6
6
  export * from './doctor-runner';
7
7
  export * from './events';
8
8
  export * from './identity';
9
+ export * from './message-store';
9
10
  export * from './messages';
10
11
  export * from './model-health-probe';
11
12
  export * from './slash-command';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ai-runner-owned orchestration persistence boundary (ADR-023 follow-up A4).
3
+ *
4
+ * `TeamOrchestrator` depends only on this port. `InboxMessageDao` from
5
+ * `@gobing-ai/ts-db/inbox` satisfies it structurally — no adapter class — and
6
+ * in-memory test doubles implement it directly.
7
+ */
8
+
9
+ /**
10
+ * Minimal read-only view of a drained message containing only the fields
11
+ * `ts-ai-runner` consumes. It deliberately does not mirror the full
12
+ * `InboxMessage` persistence model.
13
+ */
14
+ export interface DrainedMessage {
15
+ readonly id: string;
16
+ readonly fromId: string | null;
17
+ readonly body: string;
18
+ }
19
+
20
+ /**
21
+ * Message store operations consumed by `TeamOrchestrator`. Mirrors the subset
22
+ * of `InboxMessageDao` operations the orchestrator uses: enqueue a message,
23
+ * drain pending messages for a recipient, mark a message delivered, and mark
24
+ * a message failed.
25
+ */
26
+ export interface MessageStore {
27
+ enqueue(fromId: string | null, toId: string, body: string, inReplyTo?: string): Promise<string>;
28
+ drainPending(toId: string): Promise<DrainedMessage[]>;
29
+ markDelivered(msgId: string): Promise<void>;
30
+ markFailed(msgId: string, error: string): Promise<void>;
31
+ }
package/src/messages.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { InboxMessage } from '@gobing-ai/ts-db/inbox';
1
+ import type { DrainedMessage } from './message-store';
2
2
 
3
- /** Renders an inbox message into the line injected into an agent's stdin. */
4
- export function formatMessage(msg: InboxMessage): string {
3
+ /** Renders a drained message into the line injected into an agent's stdin. */
4
+ export function formatMessage(msg: DrainedMessage): string {
5
5
  return `[task from=${msg.fromId ?? 'operator'} id=${msg.id}] ${msg.body}`;
6
6
  }
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { getLogger, type Logger } from '@gobing-ai/ts-infra';
3
- import { type PipeProcess, ProcessExecutor } from '@gobing-ai/ts-runtime';
3
+ import { nodeBunFactory, type PipeProcess, type ProcessExecutor } from '@gobing-ai/ts-runtime';
4
4
  import type { AgentSpec } from './agent-spec';
5
5
 
6
6
  /** Options for spawning a team agent subprocess. */
@@ -36,7 +36,7 @@ export class TeamAgentProcess {
36
36
  this.command = options.command;
37
37
  this.env = options.env;
38
38
  this.cwd = options.cwd ?? options.spec.workspace;
39
- this.processExecutor = options.processExecutor ?? new ProcessExecutor();
39
+ this.processExecutor = options.processExecutor ?? nodeBunFactory.createProcessExecutor();
40
40
  this.logger = options.logger ?? getLogger('team-agent');
41
41
  }
42
42
 
@@ -1,10 +1,10 @@
1
- import type { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
2
- import { EventBus } from '@gobing-ai/ts-infra';
1
+ import { type BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
3
2
  import type { AgentSpec } from './agent-spec';
4
3
  import { loadAgentSpecs } from './agent-spec';
5
4
  import { type AgentName, resolveAgentName } from './agents/shims';
6
5
  import { buildAgentCommand } from './ai-runner';
7
6
  import type { AgentEvents } from './events';
7
+ import type { MessageStore } from './message-store';
8
8
  import { formatMessage } from './messages';
9
9
  import { TeamAgentProcess } from './team-agent-process';
10
10
 
@@ -14,6 +14,12 @@ type AgentProcessFactory = (options: ConstructorParameters<typeof TeamAgentProce
14
14
  export interface TeamOrchestratorOptions {
15
15
  processFactory?: AgentProcessFactory;
16
16
  events?: EventBus<AgentEvents>;
17
+ /**
18
+ * Optional lifecycle bus to bridge `agent.*` events into the application
19
+ * System Events stream (R4). When `events` is omitted the orchestrator
20
+ * constructs an internal `EventBus<AgentEvents>` parented to this bus.
21
+ */
22
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
17
23
  }
18
24
 
19
25
  /**
@@ -28,11 +34,15 @@ export class TeamOrchestrator {
28
34
 
29
35
  constructor(
30
36
  private readonly configDir: string,
31
- private readonly inbox: InboxMessageDao,
37
+ private readonly inbox: MessageStore,
32
38
  options: TeamOrchestratorOptions = {},
33
39
  ) {
34
40
  this.processFactory = options.processFactory ?? ((processOptions) => new TeamAgentProcess(processOptions));
35
- this.events = options.events ?? new EventBus<AgentEvents>();
41
+ this.events =
42
+ options.events ??
43
+ (options.lifecycleBus
44
+ ? new EventBus<AgentEvents>({ lifecycleBus: options.lifecycleBus })
45
+ : new EventBus<AgentEvents>());
36
46
  }
37
47
 
38
48
  async loadSpecs(): Promise<AgentSpec[]> {