beth-copilot 1.0.15 → 1.0.16

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.
Files changed (136) hide show
  1. package/README.md +197 -77
  2. package/bin/cli.js +47 -0
  3. package/dist/core/context.d.ts +171 -0
  4. package/dist/core/context.d.ts.map +1 -0
  5. package/dist/core/context.js +353 -0
  6. package/dist/core/context.js.map +1 -0
  7. package/dist/core/context.test.d.ts +8 -0
  8. package/dist/core/context.test.d.ts.map +1 -0
  9. package/dist/core/context.test.js +253 -0
  10. package/dist/core/context.test.js.map +1 -0
  11. package/dist/core/handoffs.d.ts +151 -0
  12. package/dist/core/handoffs.d.ts.map +1 -0
  13. package/dist/core/handoffs.js +220 -0
  14. package/dist/core/handoffs.js.map +1 -0
  15. package/dist/core/handoffs.test.d.ts +8 -0
  16. package/dist/core/handoffs.test.d.ts.map +1 -0
  17. package/dist/core/handoffs.test.js +231 -0
  18. package/dist/core/handoffs.test.js.map +1 -0
  19. package/dist/core/orchestrator.d.ts +246 -0
  20. package/dist/core/orchestrator.d.ts.map +1 -0
  21. package/dist/core/orchestrator.js +514 -0
  22. package/dist/core/orchestrator.js.map +1 -0
  23. package/dist/core/orchestrator.test.d.ts +8 -0
  24. package/dist/core/orchestrator.test.d.ts.map +1 -0
  25. package/dist/core/orchestrator.test.js +517 -0
  26. package/dist/core/orchestrator.test.js.map +1 -0
  27. package/dist/core/router.d.ts +102 -0
  28. package/dist/core/router.d.ts.map +1 -0
  29. package/dist/core/router.js +178 -0
  30. package/dist/core/router.js.map +1 -0
  31. package/dist/core/router.test.d.ts +8 -0
  32. package/dist/core/router.test.d.ts.map +1 -0
  33. package/dist/core/router.test.js +215 -0
  34. package/dist/core/router.test.js.map +1 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +7 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/tools/cli/beads.d.ts +27 -0
  40. package/dist/tools/cli/beads.d.ts.map +1 -0
  41. package/dist/tools/cli/beads.js +172 -0
  42. package/dist/tools/cli/beads.js.map +1 -0
  43. package/dist/tools/cli/beads.test.d.ts +8 -0
  44. package/dist/tools/cli/beads.test.d.ts.map +1 -0
  45. package/dist/tools/cli/beads.test.js +264 -0
  46. package/dist/tools/cli/beads.test.js.map +1 -0
  47. package/dist/tools/cli/editFile.d.ts +17 -0
  48. package/dist/tools/cli/editFile.d.ts.map +1 -0
  49. package/dist/tools/cli/editFile.js +125 -0
  50. package/dist/tools/cli/editFile.js.map +1 -0
  51. package/dist/tools/cli/editFile.test.d.ts +8 -0
  52. package/dist/tools/cli/editFile.test.d.ts.map +1 -0
  53. package/dist/tools/cli/editFile.test.js +177 -0
  54. package/dist/tools/cli/editFile.test.js.map +1 -0
  55. package/dist/tools/cli/readFile.d.ts +25 -0
  56. package/dist/tools/cli/readFile.d.ts.map +1 -0
  57. package/dist/tools/cli/readFile.js +118 -0
  58. package/dist/tools/cli/readFile.js.map +1 -0
  59. package/dist/tools/cli/readFile.test.d.ts +8 -0
  60. package/dist/tools/cli/readFile.test.d.ts.map +1 -0
  61. package/dist/tools/cli/readFile.test.js +194 -0
  62. package/dist/tools/cli/readFile.test.js.map +1 -0
  63. package/dist/tools/cli/search.d.ts +16 -0
  64. package/dist/tools/cli/search.d.ts.map +1 -0
  65. package/dist/tools/cli/search.js +261 -0
  66. package/dist/tools/cli/search.js.map +1 -0
  67. package/dist/tools/cli/search.test.d.ts +8 -0
  68. package/dist/tools/cli/search.test.d.ts.map +1 -0
  69. package/dist/tools/cli/search.test.js +172 -0
  70. package/dist/tools/cli/search.test.js.map +1 -0
  71. package/dist/tools/cli/subagent.d.ts +43 -0
  72. package/dist/tools/cli/subagent.d.ts.map +1 -0
  73. package/dist/tools/cli/subagent.js +99 -0
  74. package/dist/tools/cli/subagent.js.map +1 -0
  75. package/dist/tools/cli/subagent.test.d.ts +8 -0
  76. package/dist/tools/cli/subagent.test.d.ts.map +1 -0
  77. package/dist/tools/cli/subagent.test.js +190 -0
  78. package/dist/tools/cli/subagent.test.js.map +1 -0
  79. package/dist/tools/cli/terminal.d.ts +19 -0
  80. package/dist/tools/cli/terminal.d.ts.map +1 -0
  81. package/dist/tools/cli/terminal.js +164 -0
  82. package/dist/tools/cli/terminal.js.map +1 -0
  83. package/dist/tools/cli/terminal.test.d.ts +8 -0
  84. package/dist/tools/cli/terminal.test.d.ts.map +1 -0
  85. package/dist/tools/cli/terminal.test.js +161 -0
  86. package/dist/tools/cli/terminal.test.js.map +1 -0
  87. package/dist/tools/index.d.ts +25 -0
  88. package/dist/tools/index.d.ts.map +1 -0
  89. package/dist/tools/index.js +41 -0
  90. package/dist/tools/index.js.map +1 -0
  91. package/dist/tools/interface.d.ts +64 -0
  92. package/dist/tools/interface.d.ts.map +1 -0
  93. package/dist/tools/interface.js +37 -0
  94. package/dist/tools/interface.js.map +1 -0
  95. package/dist/tools/interface.test.d.ts +7 -0
  96. package/dist/tools/interface.test.d.ts.map +1 -0
  97. package/dist/tools/interface.test.js +179 -0
  98. package/dist/tools/interface.test.js.map +1 -0
  99. package/dist/tools/mcp/bridge.d.ts +48 -0
  100. package/dist/tools/mcp/bridge.d.ts.map +1 -0
  101. package/dist/tools/mcp/bridge.js +128 -0
  102. package/dist/tools/mcp/bridge.js.map +1 -0
  103. package/dist/tools/mcp/bridge.test.d.ts +8 -0
  104. package/dist/tools/mcp/bridge.test.d.ts.map +1 -0
  105. package/dist/tools/mcp/bridge.test.js +300 -0
  106. package/dist/tools/mcp/bridge.test.js.map +1 -0
  107. package/dist/tools/mcp/client.d.ts +135 -0
  108. package/dist/tools/mcp/client.d.ts.map +1 -0
  109. package/dist/tools/mcp/client.js +263 -0
  110. package/dist/tools/mcp/client.js.map +1 -0
  111. package/dist/tools/mcp/client.test.d.ts +8 -0
  112. package/dist/tools/mcp/client.test.d.ts.map +1 -0
  113. package/dist/tools/mcp/client.test.js +390 -0
  114. package/dist/tools/mcp/client.test.js.map +1 -0
  115. package/dist/tools/registry.d.ts +82 -0
  116. package/dist/tools/registry.d.ts.map +1 -0
  117. package/dist/tools/registry.js +99 -0
  118. package/dist/tools/registry.js.map +1 -0
  119. package/dist/tools/registry.test.d.ts +7 -0
  120. package/dist/tools/registry.test.d.ts.map +1 -0
  121. package/dist/tools/registry.test.js +199 -0
  122. package/dist/tools/registry.test.js.map +1 -0
  123. package/dist/tools/suite.test.d.ts +11 -0
  124. package/dist/tools/suite.test.d.ts.map +1 -0
  125. package/dist/tools/suite.test.js +119 -0
  126. package/dist/tools/suite.test.js.map +1 -0
  127. package/dist/tools/types.d.ts +75 -0
  128. package/dist/tools/types.d.ts.map +1 -0
  129. package/dist/tools/types.js +30 -0
  130. package/dist/tools/types.js.map +1 -0
  131. package/dist/tools/types.test.d.ts +7 -0
  132. package/dist/tools/types.test.d.ts.map +1 -0
  133. package/dist/tools/types.test.js +178 -0
  134. package/dist/tools/types.test.js.map +1 -0
  135. package/package.json +2 -2
  136. package/sbom.json +209 -209
package/README.md CHANGED
@@ -14,102 +14,62 @@ They broke her wings once. They forgot she had claws.
14
14
 
15
15
  Beth is a **multi-agent AI orchestrator** with a TypeScript runtime, CLI toolchain, MCP integrations, and agent-to-agent (A2A) delegation—all driven by a ruthless coordinator who runs your development team the way Beth Dutton runs Schwartz & Meyer.
16
16
 
17
- She commands seven specialized agents, each with their own expertise, tools, and handoff chains. On top of the GitHub Copilot agent layer, Beth now ships a **TypeScript core engine** with parsed agent/skill schemas, an Azure OpenAI LLM provider, streaming tool-call support, and a CLI that validates your entire installation in one command.
17
+ She commands seven specialized agents, each with their own expertise, tools, and handoff chains. On top of the GitHub Copilot agent layer, Beth ships a **TypeScript core engine** with a full agentic loop: agent routing, conversation context management, tool calling, subagent spawning, and agent-to-agent handoffs—all backed by an Azure OpenAI LLM provider with streaming and retry.
18
18
 
19
- **The system has three execution layers:**
19
+ **The system has four execution layers:**
20
20
 
21
21
  | Layer | What It Does | Status |
22
22
  |-------|-------------|--------|
23
23
  | **Copilot Agents** | `.agent.md` definitions running in VS Code Agent Mode | Live |
24
- | **CLI Toolchain** | `beth init`, `beth doctor`, `beth quickstart` — TypeScript commands with 485 tests | Live |
24
+ | **CLI Toolchain** | `beth init`, `beth doctor`, `beth quickstart` — TypeScript commands | Live |
25
+ | **Orchestration Engine** | Fan-out routing, tool calling loop, subagent spawning, handoffs | Live |
26
+ | **Tool Abstraction** | 6 CLI tools + MCP bridge — uniform interface for all agent capabilities | Live |
25
27
  | **LLM Provider** | Azure OpenAI with Entra ID auth, streaming, retry, tool calling | Live |
26
28
 
29
+ **814 tests.** 813 pass, 1 skip, 0 fail.
30
+
27
31
  ---
28
32
 
29
33
  ## Architecture
30
34
 
31
35
  ```mermaid
32
36
  flowchart TB
33
- subgraph UI["User Interfaces"]
34
- Copilot["VS Code Copilot Chat<br/><i>Agent Mode</i>"]
35
- CLI["Beth CLI<br/><i>init · doctor · quickstart</i>"]
37
+ subgraph Input["Entry Points"]
38
+ Copilot["VS Code Copilot Chat"]
39
+ CLI["Beth CLI"]
36
40
  end
37
41
 
38
- subgraph Core["Beth Core Engine — TypeScript"]
39
- AgentLoader["Agent Loader<br/><i>Parse .agent.md frontmatter</i>"]
40
- SkillLoader["Skill Loader<br/><i>Parse SKILL.md + triggers</i>"]
41
- Types["Agent & Skill Types<br/><i>Typed schemas</i>"]
42
- PathVal["Path Validation<br/><i>Traversal/injection guard</i>"]
42
+ subgraph Engine["Orchestration Engine"]
43
+ Orch["Orchestrator<br/><i>Route LLM → Tools → Response</i>"]
43
44
  end
44
45
 
45
- subgraph Agents["Specialist Agents (A2A)"]
46
- Beth["@Beth<br/><i>Orchestrator</i>"]
46
+ subgraph Agents["Specialist Agents"]
47
+ Beth["@Beth"]
47
48
  PM["@product-manager"]
48
- Researcher["@researcher"]
49
- Designer["@ux-designer"]
50
- Developer["@developer"]
51
- Security["@security-reviewer"]
52
- Tester["@tester"]
49
+ UX["@ux-designer"]
50
+ Dev["@developer"]
51
+ Sec["@security-reviewer"]
52
+ Test["@tester"]
53
+ Res["@researcher"]
53
54
  end
54
55
 
55
- subgraph Skills["Skills — On-Demand Knowledge"]
56
- PRD["PRD Generation"]
57
- Framer["Framer Components"]
58
- React["React/Next.js<br/>Best Practices"]
59
- WebDesign["Web Design<br/>Guidelines"]
60
- Shadcn["shadcn/ui"]
61
- SecAnalysis["Security Analysis"]
62
- AzureOps["Azure Operations"]
63
- WebSearch["Web Search"]
56
+ subgraph Capabilities["Capabilities"]
57
+ Tools["Tools<br/><i>files · terminal · search · beads</i>"]
58
+ Skills["Skills<br/><i>PRD · React · shadcn · security</i>"]
59
+ MCPs["MCP Servers<br/><i>shadcn · Playwright · Azure</i>"]
64
60
  end
65
61
 
66
- subgraph MCP["MCP Servers Optional"]
67
- MCPShadcn["shadcn/ui"]
68
- MCPPlaywright["Playwright"]
69
- MCPAzure["Azure"]
70
- MCPBrave["Brave Search"]
71
- MCPDeepWiki["DeepWiki"]
72
- end
62
+ LLM["Azure OpenAI<br/><i>Entra ID · Streaming</i>"]
73
63
 
74
- subgraph Provider["LLM Provider Layer"]
75
- Interface["LLMProviderBase<br/><i>Abstract interface</i>"]
76
- Azure["AzureOpenAIProvider<br/><i>Entra ID · Streaming</i>"]
77
- Retry["Retry + Backoff<br/><i>Exponential w/ jitter</i>"]
78
- Stream["StreamAccumulator<br/><i>Tool call assembly</i>"]
79
- Config["Config Loader<br/><i>env → ~/.beth/.env</i>"]
80
- end
81
-
82
- subgraph Tracking["Work Tracking"]
83
- Beads["beads (bd CLI)<br/><i>Agent coordination</i>"]
84
- Backlog["Backlog.md<br/><i>Human changelog</i>"]
85
- end
86
-
87
- Copilot --> Beth
88
- CLI --> Core
89
- Core --> Agents
90
- Beth -->|"routes"| PM & Researcher & Designer & Developer & Security & Tester
91
-
92
- PM -.->|"loads"| PRD
93
- Designer -.->|"loads"| Framer & WebDesign
94
- Developer -.->|"loads"| React & Shadcn
95
- Security -.->|"loads"| SecAnalysis
96
- Researcher -.->|"loads"| WebSearch
97
- Developer -.->|"uses"| MCPShadcn
98
- Tester -.->|"uses"| MCPPlaywright
99
- Security -.->|"uses"| MCPAzure
100
- Researcher -.->|"uses"| MCPBrave
101
-
102
- Azure --> Interface
103
- Retry --> Azure
104
- Stream --> Azure
105
- Config --> Azure
106
-
107
- Beth -.->|"tracks"| Beads
108
- Beth -.->|"updates"| Backlog
64
+ Copilot & CLI --> Orch
65
+ Orch --> Beth
66
+ Beth -->|"delegates"| PM & UX & Dev & Sec & Test & Res
67
+ Orch <-->|"chat"| LLM
68
+ Orch --> Tools & Skills & MCPs
109
69
 
110
70
  style Beth fill:#1e3a5f,color:#fff
111
- style Core fill:#f0f4f8
112
- style Provider fill:#e8f5e9
71
+ style Engine fill:#fff3e0
72
+ style Capabilities fill:#e3f2fd
113
73
  ```
114
74
 
115
75
  ---
@@ -126,7 +86,7 @@ flowchart TB
126
86
  | **LLM Provider** | Azure OpenAI via `openai` SDK | Entra ID auth (no API keys), streaming + tool calling |
127
87
  | **Auth** | `@azure/identity` DefaultAzureCredential | az login, managed identity, VS Code creds |
128
88
  | **Frontmatter** | `gray-matter` | Parses `.agent.md` and `SKILL.md` YAML |
129
- | **Testing** | Node.js built-in test runner | 485 tests — unit, integration, E2E |
89
+ | **Testing** | Node.js built-in test runner | 814 tests — unit, integration, E2E |
130
90
  | **Task Tracking** | beads (`bd` CLI) | Dependency-aware issue tracking for agents |
131
91
  | **Package Manager** | pnpm | Lockfile committed |
132
92
 
@@ -331,6 +291,99 @@ Skills are domain-knowledge modules that agents load automatically when trigger
331
291
 
332
292
  ---
333
293
 
294
+ ## Orchestration Engine (Fan-Out Pattern)
295
+
296
+ The orchestration engine is Beth's brain — the full agentic loop that processes user messages through routing, skill injection, LLM calls, tool execution, and subagent spawning.
297
+
298
+ ```mermaid
299
+ flowchart TB
300
+ User["User Message"] --> Route["AgentRouter\n@mention · skill match · default"]
301
+ Route --> Context["ConversationContext\nBuild system prompt + history"]
302
+ Context --> Skills{"Skill triggers match?"}
303
+ Skills -->|yes| Inject["Inject skill into system prompt"]
304
+ Skills -->|no| LLM
305
+ Inject --> LLM["LLM Call\nAzure OpenAI"]
306
+ LLM --> Decision{"Response type?"}
307
+ Decision -->|text| Done["Return response"]
308
+ Decision -->|tool calls| ToolExec["Execute tools\nvia ToolRegistry"]
309
+ ToolExec --> SubCheck{"Subagent request?"}
310
+ SubCheck -->|yes| SubAgent["Spawn child loop\ndepth-limited"]
311
+ SubCheck -->|no| ToolResult["Return tool result"]
312
+ SubAgent --> ToolResult
313
+ ToolResult --> LLM
314
+ Decision -->|handoff| Handoff["HandoffManager\nContext transfer"]
315
+ Handoff --> Route
316
+
317
+ style User fill:#1e3a5f,color:#fff
318
+ style LLM fill:#e8f5e9
319
+ style ToolExec fill:#e3f2fd
320
+ style SubAgent fill:#fff3e0
321
+ ```
322
+
323
+ **Key capabilities:**
324
+ - **Agent routing** — `@mention` parsing, skill trigger matching, current-agent stickiness
325
+ - **Fan-out tool calling** — Iterative LLM → tool call → result → LLM loop (up to 25 iterations)
326
+ - **Subagent spawning** — Nested agent loops with depth limiting (default: 3 levels deep)
327
+ - **Handoff management** — Context transfer between agents with conversation summaries, ping-pong loop detection
328
+ - **Context window management** — Token-estimated truncation with tool call/result consistency repair
329
+ - **Observer callbacks** — Hook into routing decisions, LLM calls, tool executions, handoffs for logging/UI
330
+
331
+ ```typescript
332
+ // Full orchestrator usage
333
+ import { Orchestrator, createDefaultRegistry } from 'beth-copilot';
334
+
335
+ const orchestrator = new Orchestrator({
336
+ agents: loadAgents('.github/agents'),
337
+ skills: loadSkills('.github/skills'),
338
+ provider: new AzureOpenAIProvider(config),
339
+ toolRegistry: createDefaultRegistry(),
340
+ toolContext: { workingDir: process.cwd(), permissions: { ... } },
341
+ });
342
+
343
+ const result = await orchestrator.processMessage('Implement the login page');
344
+ // result.response — final text
345
+ // result.agentId — who handled it
346
+ // result.toolCallsExecuted — what tools ran
347
+ // result.subagentResults — any nested agent work
348
+ // result.injectedSkills — skills loaded for this turn
349
+ ```
350
+
351
+ ---
352
+
353
+ ## Tool Abstraction Layer
354
+
355
+ A uniform interface for all agent capabilities — file I/O, terminal, search, beads, subagent spawning, and MCP server tools. Tools expose OpenAI-compatible function calling schemas so the LLM can invoke them directly.
356
+
357
+ | Tool | What It Does | Key Features |
358
+ |------|-------------|-------------- |
359
+ | **readFile** | Read file contents | Line ranges, path validation, traversal guards |
360
+ | **editFile** | Atomic string replacement | Single-match enforcement, whitespace-safe |
361
+ | **search** | Ripgrep search | Node.js fallback, regex support, file filtering |
362
+ | **terminal** | Execute shell commands | `execFile('/bin/sh')` — no shell injection, timeouts |
363
+ | **beads** | Issue tracking | `bd create`, `bd close`, `bd list` via CLI wrapper |
364
+ | **subagent** | Spawn nested agents | Returns structured result for orchestrator to process |
365
+ | **MCP Bridge** | External tool servers | JSON-RPC 2.0 over stdio, JSONC config, namespaced tools |
366
+
367
+ ```typescript
368
+ import { createDefaultRegistry, ToolRegistry, loadAllMCPTools } from 'beth-copilot';
369
+
370
+ // Built-in tools
371
+ const registry = createDefaultRegistry();
372
+ // → readFile, editFile, search, terminal, beads, subagent
373
+
374
+ // Add MCP server tools
375
+ const { tools: mcpTools } = await loadAllMCPTools('.vscode/mcp.json');
376
+ for (const tool of mcpTools) {
377
+ registry.register(tool); // e.g., mcp_shadcn_listComponents
378
+ }
379
+
380
+ // Get OpenAI function calling definitions
381
+ const definitions = registry.getDefinitions();
382
+ // Pass to LLM as tools parameter
383
+ ```
384
+
385
+ ---
386
+
334
387
  ## LLM Provider Layer
335
388
 
336
389
  The TypeScript core includes a production-ready provider abstraction for running Beth outside VS Code.
@@ -383,7 +436,7 @@ flowchart LR
383
436
 
384
437
  ## TypeScript Core
385
438
 
386
- The engine that powers everything. Parses agent and skill definitions, validates configuration, and provides typed APIs.
439
+ The engine that powers everything. Parses agent and skill definitions, manages conversations, routes requests, executes tools, and provides typed APIs for the full agentic loop.
387
440
 
388
441
  ### Project Structure
389
442
 
@@ -392,11 +445,15 @@ beth/
392
445
  ├── bin/
393
446
  │ └── cli.js # CLI entry point (init, doctor, quickstart, help)
394
447
  ├── src/
395
- │ ├── index.ts # Barrel exports
448
+ │ ├── index.ts # Barrel exports (all public API)
396
449
  │ ├── cli/commands/
397
450
  │ │ ├── doctor.ts # System health validation
398
451
  │ │ └── quickstart.ts # Guided setup flow
399
452
  │ ├── core/
453
+ │ │ ├── orchestrator.ts # Agentic loop: route → LLM → tools → response
454
+ │ │ ├── router.ts # @mention routing, skill matching, agent lookup
455
+ │ │ ├── context.ts # Conversation state, token truncation, skill injection
456
+ │ │ ├── handoffs.ts # Agent-to-agent transfers, loop detection
400
457
  │ │ ├── agents/
401
458
  │ │ │ ├── types.ts # AgentDefinition, AgentFrontmatter, AgentHandoff
402
459
  │ │ │ └── loader.ts # Parse .agent.md → typed definitions
@@ -405,6 +462,20 @@ beth/
405
462
  │ │ └── loader.ts # Parse SKILL.md, extract triggers, match queries
406
463
  │ ├── lib/
407
464
  │ │ └── pathValidation.ts # Traversal/injection guards
465
+ │ ├── tools/
466
+ │ │ ├── interface.ts # Tool interface + toToolDefinition()
467
+ │ │ ├── types.ts # ToolError, ToolResult, ToolContext, ToolPermissions
468
+ │ │ ├── registry.ts # ToolRegistry: register, get, list, getDefinitions
469
+ │ │ ├── cli/
470
+ │ │ │ ├── readFile.ts # File reading with line ranges
471
+ │ │ │ ├── editFile.ts # Atomic string replacement
472
+ │ │ │ ├── search.ts # Ripgrep with Node.js fallback
473
+ │ │ │ ├── terminal.ts # Secure command execution
474
+ │ │ │ ├── beads.ts # Issue tracking via bd CLI
475
+ │ │ │ └── subagent.ts # Agent spawning interface
476
+ │ │ └── mcp/
477
+ │ │ ├── client.ts # JSON-RPC 2.0 over stdio
478
+ │ │ └── bridge.ts # JSONC config, tool namespacing
408
479
  │ └── providers/
409
480
  │ ├── interface.ts # LLMProviderBase abstract class
410
481
  │ ├── azure.ts # AzureOpenAIProvider (Entra ID, streaming, tools)
@@ -425,17 +496,36 @@ beth/
425
496
 
426
497
  ### Test Coverage
427
498
 
428
- **485 tests** (484 pass, 1 skip, 0 fail):
499
+ **814 tests** (813 pass, 1 skip, 0 fail):
429
500
 
430
501
  | Suite | Tests | What It Covers |
431
502
  |-------|-------|---------------|
432
- | Agent loader | 30+ | Frontmatter parsing, validation, code fence stripping, handoffs |
433
- | Skill loader | 30+ | Trigger extraction, query matching, trigger map building |
503
+ | **Orchestration** | | |
504
+ | Orchestrator | 30+ | Agentic loop, tool calling, subagent spawning, iteration limits |
505
+ | AgentRouter | 30+ | @mention routing, skill matching, agent resolution |
506
+ | ConversationContext | 30+ | Token truncation, skill injection, tool call repair |
507
+ | HandoffManager | 30+ | Context transfer, depth limits, ping-pong detection |
508
+ | **Tools** | | |
509
+ | Tool interface | 20+ | Tool → ToolDefinition conversion, schema validation |
510
+ | ToolRegistry | 20+ | Register, get, list, definitions, duplicate detection |
511
+ | readFile | 30+ | Line ranges, path validation, encoding |
512
+ | editFile | 30+ | String replacement, single-match enforcement |
513
+ | search | 30+ | Ripgrep, Node.js fallback, regex, file filtering |
514
+ | terminal | 30+ | Command execution, timeouts, output capture |
515
+ | beads | 30+ | bd CLI wrapper, create/close/list/ready |
516
+ | subagent | 30+ | Spawn interface, result marking, agent validation |
517
+ | MCP client | 30+ | JSON-RPC 2.0, protocol handshake, tool listing |
518
+ | MCP bridge | 30+ | JSONC parsing, tool namespacing, error handling |
519
+ | Tool suite | 10+ | createDefaultRegistry, integration tests |
520
+ | **Providers** | | |
434
521
  | Provider types | 40+ | LLMError codes, ChatMessage shapes, ToolDefinition schemas |
435
522
  | Provider retry | 40+ | Exponential backoff, jitter, transient error detection |
436
523
  | Provider config | 30+ | Env precedence, dotenv parsing, URL validation |
437
524
  | Provider streaming | 40+ | Chunk accumulation, tool call delta assembly |
438
525
  | Provider Azure | 30+ | Message mapping, response mapping, error wrapping |
526
+ | **Core & CLI** | | |
527
+ | Agent loader | 30+ | Frontmatter parsing, validation, code fence stripping, handoffs |
528
+ | Skill loader | 30+ | Trigger extraction, query matching, trigger map building |
439
529
  | CLI E2E | 52 | Init/doctor pipeline, MCP template validation, help output |
440
530
  | Path validation | 33 | Traversal detection, injection prevention, allowlists |
441
531
 
@@ -572,6 +662,36 @@ Is it magic? No. It's just competence with very good hair.
572
662
  - **GitHub Copilot Chat** in Agent mode
573
663
  - [**beads**](https://github.com/steveyegge/beads) for task tracking (`bd` CLI)
574
664
 
665
+ ### Installing Beads
666
+
667
+ ```bash
668
+ curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
669
+ ```
670
+
671
+ **CGO Troubleshooting (Linux/WSL):** Beads uses Dolt (a Git-for-data database) which requires CGO. If `bd init` or `bd doctor` fails with CGO-related errors:
672
+
673
+ ```bash
674
+ # Install C compiler toolchain (required for CGO)
675
+ sudo apt-get update && sudo apt-get install -y build-essential gcc
676
+
677
+ # Verify CGO is available
678
+ export CGO_ENABLED=1
679
+ go env CGO_ENABLED # should print 1
680
+
681
+ # Re-install beads
682
+ curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
683
+ ```
684
+
685
+ **Common beads issues:**
686
+ - `bd: command not found` — Add `~/.local/bin` to your PATH: `export PATH="$HOME/.local/bin:$PATH"`
687
+ - `bd doctor` warnings about metadata — Run `bd doctor --fix` to auto-repair
688
+ - Dolt migration errors — Delete `.beads/` and re-initialize with `bd init`
689
+
690
+ ```bash
691
+ # Verify beads is working
692
+ bd doctor
693
+ ```
694
+
575
695
  ### Optional: MCP Servers
576
696
 
577
697
  See [MCP Integrations](#mcp-integrations) above or [docs/MCP-SETUP.md](docs/MCP-SETUP.md) for setup.
package/bin/cli.js CHANGED
@@ -733,6 +733,48 @@ async function initializeBeads(cwd) {
733
733
  });
734
734
  }
735
735
 
736
+ /**
737
+ * Runs `bd doctor` to verify beads configuration health.
738
+ *
739
+ * SECURITY NOTE - shell:true usage:
740
+ * - bdPath is validated via getBeadsPath() (same as initializeBeads)
741
+ * - Arguments are HARDCODED ('doctor') - no user input is passed to the shell
742
+ * - Command injection risk: LOW (bdPath is validated, no user input in args)
743
+ *
744
+ * @returns {Promise<boolean>} True if bd doctor passed
745
+ */
746
+ async function runBeadsDoctor() {
747
+ log('\nRunning beads doctor to verify configuration...', COLORS.cyan);
748
+
749
+ const bdPath = getBeadsPath();
750
+ if (!bdPath) {
751
+ logWarning('Cannot run beads doctor: bd not found.');
752
+ return false;
753
+ }
754
+
755
+ return new Promise((resolve) => {
756
+ const child = spawn(bdPath, ['doctor'], {
757
+ stdio: 'inherit',
758
+ shell: true,
759
+ });
760
+
761
+ child.on('close', (code) => {
762
+ if (code === 0) {
763
+ logSuccess('beads doctor passed!');
764
+ resolve(true);
765
+ } else {
766
+ logWarning('beads doctor reported issues. Run "bd doctor" manually to investigate.');
767
+ resolve(false);
768
+ }
769
+ });
770
+
771
+ child.on('error', () => {
772
+ logWarning('Failed to run beads doctor. Run "bd doctor" manually.');
773
+ resolve(false);
774
+ });
775
+ });
776
+ }
777
+
736
778
  function showHelp() {
737
779
  showBethBannerStatic({ showQuickHelp: false });
738
780
  console.log(`${COLORS.bright}Beth${COLORS.reset} - AI Orchestrator for GitHub Copilot
@@ -1048,6 +1090,11 @@ ${COLORS.yellow}╔════════════════════
1048
1090
  logWarning('Skipped beads check (--skip-beads). Beth may not function correctly.');
1049
1091
  }
1050
1092
 
1093
+ // Run bd doctor to verify beads configuration
1094
+ if (!skipBeads && getBeadsPath() && isBeadsInitialized(cwd)) {
1095
+ await runBeadsDoctor();
1096
+ }
1097
+
1051
1098
  // Final verification
1052
1099
  console.log('');
1053
1100
  log('Verifying installation...', COLORS.cyan);
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Conversation Context Manager
3
+ *
4
+ * Manages conversation state for agent interactions:
5
+ * - Message history with role tracking
6
+ * - Context window management (token estimation + truncation)
7
+ * - System prompt construction from agent definitions + skills
8
+ * - Skill injection when trigger phrases match user input
9
+ *
10
+ * Each agent session gets its own ConversationContext. When handoffs occur,
11
+ * a summary can be extracted and injected into the new agent's context.
12
+ */
13
+ import type { ChatMessage } from '../providers/types.js';
14
+ import type { AgentDefinition } from './agents/types.js';
15
+ import type { SkillDefinition } from './skills/types.js';
16
+ /**
17
+ * Options for creating a ConversationContext.
18
+ */
19
+ export interface ConversationContextOptions {
20
+ /** Maximum tokens for the context window (default: 128000) */
21
+ maxTokens?: number;
22
+ /** Tokens reserved for the model's response (default: 4096) */
23
+ responseReserve?: number;
24
+ /** Initial conversation history to restore (e.g., from a handoff) */
25
+ initialMessages?: ChatMessage[];
26
+ }
27
+ /**
28
+ * Summary of a conversation for handoff purposes.
29
+ */
30
+ export interface ConversationSummary {
31
+ /** The agent that was running this conversation */
32
+ agentId: string;
33
+ /** Key points from the conversation */
34
+ summary: string;
35
+ /** Number of turns in the original conversation */
36
+ turnCount: number;
37
+ /** Any tool calls that were made */
38
+ toolCallSummary: string[];
39
+ }
40
+ /**
41
+ * Manages conversation state for a single agent session.
42
+ *
43
+ * Handles message accumulation, context window enforcement,
44
+ * and system prompt construction from agent definitions and skills.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const ctx = new ConversationContext(developerAgent);
49
+ * ctx.addUserMessage('Implement the login page');
50
+ *
51
+ * const messages = ctx.getMessages(); // system + user message
52
+ * // Send to LLM...
53
+ *
54
+ * ctx.addAssistantMessage('I\'ll create the login component...');
55
+ * ctx.addAssistantToolCalls([{ id: '1', type: 'function', function: { name: 'editFile', arguments: '...' } }]);
56
+ * ctx.addToolResult('1', 'File updated successfully');
57
+ * ```
58
+ */
59
+ export declare class ConversationContext {
60
+ /** The agent this context belongs to */
61
+ private readonly agent;
62
+ /** Conversation messages (excluding system prompt; it's built dynamically) */
63
+ private messages;
64
+ /** Skills injected into this context */
65
+ private injectedSkills;
66
+ /** Skill content appended to system prompt */
67
+ private skillContent;
68
+ /** Context window configuration */
69
+ private readonly maxTokens;
70
+ private readonly responseReserve;
71
+ /** Custom context additions (handoff summaries, etc.) */
72
+ private additionalContext;
73
+ constructor(agent: AgentDefinition, options?: ConversationContextOptions);
74
+ /**
75
+ * Add a user message to the conversation.
76
+ */
77
+ addUserMessage(content: string): void;
78
+ /**
79
+ * Add an assistant text response to the conversation.
80
+ */
81
+ addAssistantMessage(content: string): void;
82
+ /**
83
+ * Add an assistant message with tool calls.
84
+ */
85
+ addAssistantToolCalls(toolCalls: ChatMessage['tool_calls'], content?: string): void;
86
+ /**
87
+ * Add a tool result message.
88
+ */
89
+ addToolResult(toolCallId: string, result: string): void;
90
+ /**
91
+ * Get the full message array for sending to the LLM.
92
+ * Includes the constructed system prompt as the first message.
93
+ * Applies truncation if context window is exceeded.
94
+ */
95
+ getMessages(): ChatMessage[];
96
+ /**
97
+ * Get just the conversation messages (without system prompt).
98
+ */
99
+ getRawMessages(): ChatMessage[];
100
+ /**
101
+ * Get the number of conversation turns (user messages).
102
+ */
103
+ getTurnCount(): number;
104
+ /**
105
+ * Get estimated token count for the current context.
106
+ */
107
+ getEstimatedTokens(): number;
108
+ /**
109
+ * Clear the conversation history, keeping injected skills.
110
+ */
111
+ clearHistory(): void;
112
+ /**
113
+ * Inject a skill's content into the system prompt.
114
+ * Each skill is only injected once per context.
115
+ *
116
+ * @param skill - The skill to inject
117
+ * @returns true if the skill was injected, false if already present
118
+ */
119
+ injectSkill(skill: SkillDefinition): boolean;
120
+ /**
121
+ * Check if a skill has been injected.
122
+ */
123
+ hasSkill(skillId: string): boolean;
124
+ /**
125
+ * Get the IDs of all injected skills.
126
+ */
127
+ getInjectedSkillIds(): string[];
128
+ /**
129
+ * Add additional context (e.g., handoff summary from previous agent).
130
+ */
131
+ addContext(context: string): void;
132
+ /**
133
+ * Generate a summary of this conversation for handoff purposes.
134
+ */
135
+ getSummary(): ConversationSummary;
136
+ /**
137
+ * Get the agent definition this context belongs to.
138
+ */
139
+ getAgent(): AgentDefinition;
140
+ /**
141
+ * Build the full system prompt from the agent definition + injected content.
142
+ */
143
+ buildSystemPrompt(): string;
144
+ /**
145
+ * Estimate token count for a string.
146
+ */
147
+ private estimateTokens;
148
+ /**
149
+ * Estimate token count for a single message including structure overhead.
150
+ */
151
+ private estimateMessageTokens;
152
+ /**
153
+ * Truncate messages to fit within the token budget.
154
+ *
155
+ * Strategy: Keep the most recent messages. Drop oldest messages first,
156
+ * but never drop tool results that are paired with a tool call in the
157
+ * retained messages (to avoid orphaned tool call references).
158
+ *
159
+ * @param messages - Messages to truncate
160
+ * @param maxTokens - Maximum tokens available
161
+ * @returns Truncated message array
162
+ */
163
+ private truncateMessages;
164
+ /**
165
+ * Remove orphaned tool results (where the corresponding assistant
166
+ * tool_calls message was truncated) and orphaned tool calls
167
+ * (where the results were truncated).
168
+ */
169
+ private repairToolCallConsistency;
170
+ }
171
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAmBzD;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,qEAAqE;IACrE,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAEhB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAEhB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,mBAAmB;IAC9B,wCAAwC;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IAExC,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAqB;IAErC,wCAAwC;IACxC,OAAO,CAAC,cAAc,CAA0B;IAEhD,8CAA8C;IAC9C,OAAO,CAAC,YAAY,CAAgB;IAEpC,mCAAmC;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,yDAAyD;IACzD,OAAO,CAAC,iBAAiB,CAAgB;gBAE7B,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,0BAA0B;IAcxE;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIrC;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAQnF;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAQvD;;;;OAIG;IACH,WAAW,IAAI,WAAW,EAAE;IAY5B;;OAEG;IACH,cAAc,IAAI,WAAW,EAAE;IAI/B;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAU5B;;OAEG;IACH,YAAY,IAAI,IAAI;IAQpB;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO;IAY5C;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIlC;;OAEG;IACH,mBAAmB,IAAI,MAAM,EAAE;IAQ/B;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQjC;;OAEG;IACH,UAAU,IAAI,mBAAmB;IA4BjC;;OAEG;IACH,QAAQ,IAAI,eAAe;IAQ3B;;OAEG;IACH,iBAAiB,IAAI,MAAM;IA0B3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAyB7B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,gBAAgB;IA6CxB;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;CAgClC"}