@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 +61 -43
- package/dist/agents/auth-shims.d.ts +2 -0
- package/dist/agents/auth-shims.d.ts.map +1 -1
- package/dist/agents/auth-shims.js +18 -0
- package/dist/agents/shims.d.ts +1 -1
- package/dist/agents/shims.d.ts.map +1 -1
- package/dist/agents/shims.js +28 -0
- package/dist/ai-runner.d.ts +9 -1
- package/dist/ai-runner.d.ts.map +1 -1
- package/dist/ai-runner.js +15 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/message-store.d.ts +30 -0
- package/dist/message-store.d.ts.map +1 -0
- package/dist/message-store.js +7 -0
- package/dist/messages.d.ts +3 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +1 -1
- package/dist/team-agent-process.d.ts +1 -1
- package/dist/team-agent-process.d.ts.map +1 -1
- package/dist/team-agent-process.js +2 -2
- package/dist/team-orchestrator.d.ts +9 -3
- package/dist/team-orchestrator.d.ts.map +1 -1
- package/dist/team-orchestrator.js +5 -1
- package/package.json +5 -5
- package/src/agents/auth-shims.ts +20 -0
- package/src/agents/shims.ts +29 -1
- package/src/ai-runner.ts +23 -6
- package/src/index.ts +1 -0
- package/src/message-store.ts +31 -0
- package/src/messages.ts +3 -3
- package/src/team-agent-process.ts +2 -2
- package/src/team-orchestrator.ts +14 -4
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
|
-
| `
|
|
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
|
-
|
|
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 -->|"
|
|
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->>
|
|
139
|
-
|
|
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->>
|
|
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->>
|
|
155
|
-
|
|
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->>
|
|
160
|
-
|
|
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->>
|
|
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`,
|
|
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
|
|
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
|
-
`
|
|
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 {
|
|
501
|
-
import {
|
|
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
|
|
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
|
|
506
|
-
const pending = await
|
|
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(
|
|
510
|
-
await
|
|
522
|
+
console.log(formatMessage(msg));
|
|
523
|
+
await inbox.markDelivered(msg.id);
|
|
511
524
|
}
|
|
512
525
|
```
|
|
513
526
|
|
|
514
|
-
|
|
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
|
|
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 {
|
|
554
|
-
import {
|
|
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
|
|
557
|
-
const
|
|
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
|
|
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
|
|
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'));
|
package/dist/agents/shims.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/agents/shims.js
CHANGED
|
@@ -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']);
|
package/dist/ai-runner.d.ts
CHANGED
|
@@ -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>;
|
package/dist/ai-runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAa,KAAK,MAAM,EAAc,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-runner.d.ts","sourceRoot":"","sources":["../src/ai-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,QAAQ,EAAa,KAAK,MAAM,EAAc,MAAM,qBAAqB,CAAC;AAC5G,OAAO,EAGH,KAAK,eAAe,EAEpB,KAAK,UAAU,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,SAAS,EAAgB,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACpG,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAInE,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC3B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC5B,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC5B,4DAA4D;IAC5D,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,aAAa,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAChD,gEAAgE;IAChE,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/B;;;;OAIG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5C,kFAAkF;IAClF,MAAM,CAAC,EAAE,UAAU,CAAC;CACvB;AAED,uEAAuE;AACvE,qBAAa,QAAQ;IACjB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,qIAAqI;IACrI,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,SAAS,CAAC;gBAExD,OAAO,GAAE,eAAoB;IAgCzC,iCAAiC;IACjC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAIxF,oCAAoC;IACpC,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IAI3F,mCAAmC;IACnC,gBAAgB,CACZ,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,eAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;IAI1B,6EAA6E;IAC7E,eAAe,CACX,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,eAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;IAI1B,0DAA0D;IAC1D,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,eAAoB,GAAG,WAAW;IAM9G,4EAA4E;IAC5E,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;YAKjF,MAAM;CA2CvB;AAWD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC7B,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/B,WAAW,CAEb"}
|
package/dist/ai-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getLogger, traceAsync } from '@gobing-ai/ts-infra';
|
|
2
|
-
import { getProcessCwd,
|
|
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
|
-
|
|
17
|
-
...(
|
|
24
|
+
nodeBunFactory.createProcessExecutor({
|
|
25
|
+
...(processEvents !== undefined
|
|
18
26
|
? {
|
|
19
27
|
events: {
|
|
20
28
|
emit: (event, detail) => {
|
|
21
|
-
void
|
|
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 =
|
|
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
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
+
*/
|
package/dist/messages.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/** Renders
|
|
3
|
-
export declare function formatMessage(msg:
|
|
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
|
package/dist/messages.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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
|
|
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,
|
|
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 {
|
|
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 ??
|
|
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
|
|
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:
|
|
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,
|
|
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 =
|
|
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.
|
|
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-
|
|
51
|
-
"@gobing-ai/ts-
|
|
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"
|
package/src/agents/auth-shims.ts
CHANGED
|
@@ -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'));
|
package/src/agents/shims.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
68
|
-
...(
|
|
83
|
+
nodeBunFactory.createProcessExecutor({
|
|
84
|
+
...(processEvents !== undefined
|
|
69
85
|
? {
|
|
70
86
|
events: {
|
|
71
87
|
emit: (event, detail) => {
|
|
72
|
-
void
|
|
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 =
|
|
100
|
+
this.events = events;
|
|
101
|
+
this.processEvents = processEvents;
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
/** Run an agent help command. */
|
package/src/index.ts
CHANGED
|
@@ -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 {
|
|
1
|
+
import type { DrainedMessage } from './message-store';
|
|
2
2
|
|
|
3
|
-
/** Renders
|
|
4
|
-
export function formatMessage(msg:
|
|
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 ??
|
|
39
|
+
this.processExecutor = options.processExecutor ?? nodeBunFactory.createProcessExecutor();
|
|
40
40
|
this.logger = options.logger ?? getLogger('team-agent');
|
|
41
41
|
}
|
|
42
42
|
|
package/src/team-orchestrator.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
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:
|
|
37
|
+
private readonly inbox: MessageStore,
|
|
32
38
|
options: TeamOrchestratorOptions = {},
|
|
33
39
|
) {
|
|
34
40
|
this.processFactory = options.processFactory ?? ((processOptions) => new TeamAgentProcess(processOptions));
|
|
35
|
-
this.events =
|
|
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[]> {
|