@gobing-ai/ts-ai-runner 0.4.8 → 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,7 +25,8 @@ 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 |
@@ -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` |
@@ -456,7 +454,7 @@ resolveAgentName('cursor'); // → undefined
456
454
  **Current deprecation map:**
457
455
 
458
456
  | Id | Status | Canonical | Notes |
459
- |----|--------|-----------|-------|
457
+ | ---- | -------- | ----------- | ------- |
460
458
  | `antigravity` | alias of `antigravity-cli` | `antigravity-cli` | Old tier-2 id; both use binary `agy`. Resolving warns. |
461
459
  | `gemini` | deprecated | `gemini` (self) → replaced by `antigravity-cli` | Gemini CLI sunset 2026-06-18. Shim stays functional. |
462
460
  | `omp` | canonical | `omp` | First-class; NOT a pi alias. |
@@ -465,7 +463,6 @@ resolveAgentName('cursor'); // → undefined
465
463
 
466
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.
467
465
 
468
-
469
466
  ## Team Mode Primitives
470
467
 
471
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.
@@ -498,24 +495,38 @@ const specs = loadAgentSpecs('./agents');
498
495
 
499
496
  ### Durable messages
500
497
 
501
- `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`:
502
508
 
503
509
  ```ts
504
- import { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
505
- 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';
506
513
 
507
- 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 });
508
517
 
509
- const id = await messages.enqueue(null, 'coder', 'Review the runtime process seam');
510
- 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');
511
520
 
512
521
  for (const msg of pending) {
513
- console.log(MessageService.formatMessage(msg));
514
- await messages.deliver(msg.id);
522
+ console.log(formatMessage(msg));
523
+ await inbox.markDelivered(msg.id);
515
524
  }
516
525
  ```
517
526
 
518
- `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.
519
530
 
520
531
  ### Persistent agent processes
521
532
 
@@ -551,24 +562,27 @@ unsubscribe();
551
562
 
552
563
  ### Team orchestrator
553
564
 
554
- `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.
555
566
 
556
567
  ```ts
557
- import { InboxMessageDao } from '@gobing-ai/ts-db/inbox';
558
- 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';
559
571
 
560
- const messages = new MessageService(new InboxMessageDao(adapter));
561
- 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 });
562
576
 
563
577
  await team.startAgent('coder');
564
578
  await team.sendMessage(null, 'coder', 'Please implement task 0005');
565
579
 
566
- console.log(team.getAgentStatus('coder')); // running
580
+ console.log(await team.getAgentStatus('coder')); // running
567
581
 
568
582
  await team.stopAll();
569
583
  ```
570
584
 
571
- 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.
572
586
 
573
587
  ## Adding a New Coding Agent
574
588
 
@@ -635,7 +649,7 @@ If the agent supports an auth-status command, `getAuthCommand()` already returns
635
649
  ### Summary checklist
636
650
 
637
651
  | Step | File | What to change |
638
- |------|------|----------------|
652
+ | ------ | ------ | ---------------- |
639
653
  | Shim | `src/agents/shims.ts` | Add `AgentShim` impl, update `AgentName`, `AGENT_SHIMS`, `DISPLAY_ORDER` |
640
654
  | Tier | `src/agents/shims.ts` | Add to `TIER1_PRIORITY` or `TIER2_AGENTS` |
641
655
  | Slash | `src/slash-command.ts` | Add case if dialect differs from default |
@@ -650,5 +664,5 @@ After these changes, the new agent is automatically available to `AiRunner`, `Ag
650
664
  - It does not install agent CLIs or manage credentials.
651
665
  - It does not parse agent responses beyond process result capture.
652
666
  - It keeps subprocess launching behind `ProcessExecutor` / `PipeProcess`, so tests can stay deterministic.
653
- - 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).
654
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.
@@ -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":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAa,KAAK,MAAM,EAAc,MAAM,qBAAqB,CAAC;AACxF,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,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;gBAE/C,OAAO,GAAE,eAAoB;IAuBzC,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"}
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.8",
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.8",
51
- "@gobing-ai/ts-infra": "^0.4.8",
52
- "@gobing-ai/ts-runtime": "^0.4.8"
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/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[]> {