@kodrunhq/opencode-autopilot 1.17.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +95 -13
  2. package/assets/commands/oc-doctor.md +17 -0
  3. package/assets/commands/oc-update-docs.md +1 -1
  4. package/bin/configure-tui.ts +1 -1
  5. package/package.json +1 -1
  6. package/src/agents/index.ts +0 -12
  7. package/src/agents/pipeline/index.ts +0 -4
  8. package/src/autonomy/completion.ts +52 -0
  9. package/src/autonomy/controller.ts +144 -0
  10. package/src/autonomy/index.ts +25 -0
  11. package/src/autonomy/injector.ts +49 -0
  12. package/src/autonomy/state.ts +91 -0
  13. package/src/autonomy/types.ts +30 -0
  14. package/src/autonomy/verification.ts +86 -0
  15. package/src/background/database.ts +170 -0
  16. package/src/background/executor.ts +174 -0
  17. package/src/background/index.ts +8 -0
  18. package/src/background/manager.ts +232 -0
  19. package/src/background/repository.ts +174 -0
  20. package/src/background/schema.ts +24 -0
  21. package/src/background/sdk-runner.ts +40 -0
  22. package/src/background/slot-manager.ts +41 -0
  23. package/src/background/state-machine.ts +19 -0
  24. package/src/config/v7.ts +3 -3
  25. package/src/config.ts +105 -21
  26. package/src/context/budget.ts +45 -0
  27. package/src/context/compaction-handler.ts +58 -0
  28. package/src/context/discovery.ts +94 -0
  29. package/src/context/index.ts +14 -0
  30. package/src/context/injector.ts +119 -0
  31. package/src/context/types.ts +24 -0
  32. package/src/health/checks.ts +214 -3
  33. package/src/health/index.ts +7 -1
  34. package/src/health/runner.ts +14 -2
  35. package/src/index.ts +113 -6
  36. package/src/installer.ts +13 -0
  37. package/src/kernel/index.ts +6 -0
  38. package/src/kernel/migrations.ts +50 -0
  39. package/src/kernel/retry.ts +49 -0
  40. package/src/kernel/schema.ts +9 -1
  41. package/src/kernel/transaction.ts +40 -12
  42. package/src/logging/forensic-writer.ts +6 -2
  43. package/src/logging/index.ts +2 -0
  44. package/src/mcp/index.ts +34 -0
  45. package/src/mcp/manager.ts +206 -0
  46. package/src/mcp/scope-filter.ts +44 -0
  47. package/src/mcp/types.ts +38 -0
  48. package/src/orchestrator/arena.ts +7 -1
  49. package/src/orchestrator/fallback/event-handler.ts +12 -1
  50. package/src/orchestrator/handlers/challenge.ts +8 -1
  51. package/src/orchestrator/handlers/plan.ts +8 -1
  52. package/src/orchestrator/handlers/recon.ts +8 -1
  53. package/src/orchestrator/handlers/types.ts +2 -2
  54. package/src/orchestrator/lesson-memory.ts +6 -1
  55. package/src/orchestrator/orchestration-logger.ts +15 -3
  56. package/src/orchestrator/skill-injection.ts +7 -1
  57. package/src/orchestrator/state.ts +6 -1
  58. package/src/recovery/classifier.ts +127 -0
  59. package/src/recovery/event-handler.ts +263 -0
  60. package/src/recovery/index.ts +20 -0
  61. package/src/recovery/orchestrator.ts +180 -0
  62. package/src/recovery/persistence.ts +87 -0
  63. package/src/recovery/strategies.ts +107 -0
  64. package/src/recovery/types.ts +31 -0
  65. package/src/registry/model-groups.ts +2 -19
  66. package/src/registry/resolver.ts +38 -9
  67. package/src/review/agent-catalog.ts +83 -251
  68. package/src/review/agents/architecture-verifier.ts +41 -0
  69. package/src/review/agents/code-hygiene-auditor.ts +40 -0
  70. package/src/review/agents/correctness-auditor.ts +41 -0
  71. package/src/review/agents/frontend-auditor.ts +39 -0
  72. package/src/review/agents/index.ts +15 -42
  73. package/src/review/agents/language-idioms-auditor.ts +39 -0
  74. package/src/review/agents/security-auditor.ts +12 -8
  75. package/src/review/stack-gate.ts +2 -6
  76. package/src/routing/categories.ts +111 -0
  77. package/src/routing/classifier.ts +152 -0
  78. package/src/routing/engine.ts +89 -0
  79. package/src/routing/index.ts +4 -0
  80. package/src/routing/types.ts +14 -0
  81. package/src/skills/adaptive-injector.ts +34 -3
  82. package/src/skills/loader.ts +4 -0
  83. package/src/tools/background.ts +196 -0
  84. package/src/tools/configure.ts +1 -1
  85. package/src/tools/delegate.ts +205 -0
  86. package/src/tools/loop.ts +94 -0
  87. package/src/tools/recover.ts +172 -0
  88. package/src/types/background.ts +51 -0
  89. package/src/types/mcp.ts +27 -0
  90. package/src/types/recovery.ts +49 -0
  91. package/src/types/routing.ts +39 -0
  92. package/src/ux/context-warnings.ts +81 -0
  93. package/src/ux/error-hints.ts +38 -0
  94. package/src/ux/index.ts +7 -0
  95. package/src/ux/notifications.ts +67 -0
  96. package/src/ux/progress.ts +77 -0
  97. package/src/ux/session-summary.ts +67 -0
  98. package/src/ux/task-status.ts +109 -0
  99. package/src/ux/types.ts +24 -0
  100. package/src/agents/db-specialist.ts +0 -295
  101. package/src/agents/devops.ts +0 -352
  102. package/src/agents/documenter.ts +0 -44
  103. package/src/agents/frontend-engineer.ts +0 -541
  104. package/src/agents/pipeline/oc-explorer.ts +0 -46
  105. package/src/agents/pipeline/oc-retrospector.ts +0 -42
  106. package/src/review/agents/auth-flow-verifier.ts +0 -47
  107. package/src/review/agents/concurrency-checker.ts +0 -47
  108. package/src/review/agents/dead-code-scanner.ts +0 -47
  109. package/src/review/agents/go-idioms-auditor.ts +0 -46
  110. package/src/review/agents/python-django-auditor.ts +0 -46
  111. package/src/review/agents/react-patterns-auditor.ts +0 -46
  112. package/src/review/agents/rust-safety-auditor.ts +0 -46
  113. package/src/review/agents/scope-intent-verifier.ts +0 -45
  114. package/src/review/agents/silent-failure-hunter.ts +0 -45
  115. package/src/review/agents/spec-checker.ts +0 -45
  116. package/src/review/agents/state-mgmt-auditor.ts +0 -46
  117. package/src/review/agents/type-soundness.ts +0 -46
  118. package/src/review/agents/wiring-inspector.ts +0 -46
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  <p align="center">
16
16
  <strong>Autonomous AI coding pipeline for OpenCode.</strong><br/>
17
- Idea to shipped code &bull; 21-agent code review &bull; Adversarial model diversity &bull; Guided setup
17
+ Idea to shipped code &bull; 13-agent code review &bull; Adversarial model diversity &bull; Background task management &bull; Session recovery
18
18
  </p>
19
19
 
20
20
  ---
@@ -93,7 +93,7 @@ Agents are organized into 8 groups by the type of thinking they do. Each group g
93
93
  | **Architects** | oc-architect, oc-planner, autopilot | System design, planning, orchestration | Most powerful available |
94
94
  | **Challengers** | oc-critic, oc-challenger | Challenge architecture, find design flaws | Strong model, **different family from Architects** |
95
95
  | **Builders** | oc-implementer | Write production code | Strong coding model |
96
- | **Reviewers** | oc-reviewer + 19 review agents | Find bugs, security issues, logic errors | Strong model, **different family from Builders** |
96
+ | **Reviewers** | oc-reviewer + 11 review agents | Find bugs, security issues, logic errors | Strong model, **different family from Builders** |
97
97
  | **Red Team** | red-team, product-thinker | Final adversarial pass, hunt exploits | **Different family from both Builders and Reviewers** |
98
98
  | **Researchers** | oc-researcher, researcher | Domain research, feasibility analysis | Good comprehension, any family |
99
99
  | **Communicators** | oc-shipper, documenter, oc-retrospector | Docs, changelogs, lesson extraction | Mid-tier model |
@@ -113,7 +113,7 @@ The installer warns when adversarial pairs share a model family:
113
113
 
114
114
  ```json
115
115
  {
116
- "version": 4,
116
+ "version": 7,
117
117
  "configured": true,
118
118
  "groups": {
119
119
  "architects": { "primary": "anthropic/claude-opus-4-6", "fallbacks": ["openai/gpt-5.4"] },
@@ -125,7 +125,22 @@ The installer warns when adversarial pairs share a model family:
125
125
  "communicators": { "primary": "anthropic/claude-sonnet-4-6", "fallbacks": ["anthropic/claude-haiku-4-5"] },
126
126
  "utilities": { "primary": "anthropic/claude-haiku-4-5", "fallbacks": ["google/gemini-3-flash"] }
127
127
  },
128
- "overrides": {}
128
+ "overrides": {},
129
+ "background": {
130
+ "maxSlots": 5,
131
+ "defaultTimeout": 300000
132
+ },
133
+ "routing": {
134
+ "defaultCategory": "unspecified"
135
+ },
136
+ "recovery": {
137
+ "maxRetries": 3,
138
+ "strategies": ["retry", "fallback", "checkpoint"]
139
+ },
140
+ "mcp": {
141
+ "enabled": true,
142
+ "timeout": 30000
143
+ }
129
144
  }
130
145
  ```
131
146
 
@@ -190,12 +205,12 @@ The `oc_review` tool provides a 4-stage multi-agent review pipeline:
190
205
 
191
206
  **Stage 4 -- Report or fix cycle:** CRITICAL findings with actionable fixes trigger an automatic fix cycle; everything else lands in the final report.
192
207
 
193
- ### 21 Review Agents
208
+ ### 13 Review Agents
194
209
 
195
210
  | Category | Agents | When selected |
196
211
  |----------|--------|--------------|
197
- | **Universal** (always run) | logic-auditor, security-auditor, code-quality-auditor, test-interrogator, silent-failure-hunter, contract-verifier | Every review |
198
- | **Stack-aware** (auto-selected) | type-soundness, react-patterns-auditor, go-idioms-auditor, python-django-auditor, rust-safety-auditor, database-auditor, auth-flow-verifier, state-mgmt-auditor, concurrency-checker, scope-intent-verifier, wiring-inspector, dead-code-scanner, spec-checker | Based on changed file types |
212
+ | **Universal** (always run) | logic-auditor, security-auditor, code-quality-auditor, test-interrogator, code-hygiene-auditor, contract-verifier | Every review |
213
+ | **Stack-aware** (auto-selected) | architecture-verifier, database-auditor, correctness-auditor, frontend-auditor, language-idioms-auditor | Based on changed file types |
199
214
  | **Sequenced** (run last) | red-team, product-thinker | After all findings collected |
200
215
 
201
216
  Review memory persists per project -- false positives are tracked and suppressed in future reviews (auto-pruned after 30 days).
@@ -242,12 +257,19 @@ Config lives at `~/.config/opencode/opencode-autopilot.json`. Run `bunx @kodrunh
242
257
  | `confidence.thresholds.proceed` | `HIGH`, `MEDIUM`, `LOW` | `MEDIUM` |
243
258
  | `confidence.thresholds.abort` | `HIGH`, `MEDIUM`, `LOW` | `LOW` |
244
259
  | `fallback.enabled` | `true` / `false` | `true` |
260
+ | `background.maxSlots` | `1`-`10` | `5` |
261
+ | `background.defaultTimeout` | milliseconds | `300000` |
262
+ | `routing.defaultCategory` | category string | `"unspecified"` |
263
+ | `recovery.maxRetries` | `1`-`10` | `3` |
264
+ | `recovery.strategies` | array of strategy names | `["retry", "fallback", "checkpoint"]` |
265
+ | `mcp.enabled` | `true` / `false` | `true` |
266
+ | `mcp.timeout` | milliseconds | `30000` |
245
267
 
246
- Config auto-migrates across schema versions (v1 -> v2 -> v3 -> v4).
268
+ Config auto-migrates across schema versions (v1 -> v2 -> v3 -> v4 -> v5 -> v6 -> v7).
247
269
 
248
270
  ## Tools
249
271
 
250
- The plugin registers 11 tools, all prefixed with `oc_` to avoid conflicts with OpenCode built-ins:
272
+ The plugin registers 25 tools, all prefixed with `oc_` to avoid conflicts with OpenCode built-ins:
251
273
 
252
274
  | Tool | Purpose |
253
275
  |------|---------|
@@ -262,13 +284,29 @@ The plugin registers 11 tools, all prefixed with `oc_` to avoid conflicts with O
262
284
  | `oc_create_agent` | Create custom agents in-session |
263
285
  | `oc_create_skill` | Create custom skills in-session |
264
286
  | `oc_create_command` | Create custom commands in-session |
287
+ | `oc_background` | Manage background tasks (spawn, monitor, cancel) |
288
+ | `oc_loop` | Start/stop autonomy loop with verification checkpoints |
289
+ | `oc_delegate` | Category-based task routing with skill injection |
290
+ | `oc_recover` | Session recovery with strategy selection |
291
+ | `oc_doctor` | Run plugin health diagnostics |
292
+ | `oc_quick` | Quick-mode pipeline bypass for trivial tasks |
293
+ | `oc_hashline_edit` | Hash-anchored line edits with FNV-1a verification |
294
+ | `oc_logs` | Query structured session logs |
295
+ | `oc_session_stats` | Session statistics and token usage |
296
+ | `oc_pipeline_report` | Generate pipeline execution report |
297
+ | `oc_summary` | Session summary generation |
298
+ | `oc_mock_fallback` | Fallback chain testing with mock providers |
299
+ | `oc_stocktake` | Audit installed assets (agents, skills, commands) |
300
+ | `oc_update_docs` | Detect docs affected by code changes |
301
+ | `oc_memory_status` | Memory system status and statistics |
302
+ | `oc_memory_preferences` | Manage user preference observations |
265
303
 
266
304
  ## Architecture
267
305
 
268
306
  ```
269
307
  src/
270
308
  +-- index.ts Plugin entry -- registers tools, hooks, fallback handlers
271
- +-- config.ts Zod-validated config with v1->v2->v3->v4 migration
309
+ +-- config.ts Zod-validated config with v1->v7 migration chain
272
310
  +-- installer.ts Self-healing asset copier (COPYFILE_EXCL, never overwrites)
273
311
  +-- registry/
274
312
  | +-- types.ts GroupId, AgentEntry, GroupDefinition, DiversityRule, ...
@@ -278,13 +316,57 @@ src/
278
316
  | +-- doctor.ts Shared diagnosis logic (CLI + tool)
279
317
  +-- tools/ Tool definitions (thin wrappers calling *Core functions)
280
318
  +-- templates/ Pure functions: input -> markdown string
281
- +-- review/ 21-agent review engine, stack gate, memory, severity
319
+ +-- review/ 13-agent review engine, stack gate, memory, severity
282
320
  +-- orchestrator/
283
321
  | +-- handlers/ Per-phase state machine handlers
284
322
  | +-- fallback/ Model fallback: classifier, manager, state, chain resolver
285
323
  | +-- artifacts.ts Phase artifact path management
286
324
  | +-- lesson-memory.ts Cross-run lesson persistence
287
325
  | +-- schemas.ts Pipeline state Zod schemas
326
+ +-- background/ Background task management with slot-based concurrency
327
+ | +-- database.ts SQLite persistence for task state
328
+ | +-- state-machine.ts Task lifecycle (queued -> running -> completed/failed)
329
+ | +-- slot-manager.ts Concurrent slot allocation and limits
330
+ | +-- executor.ts Task execution with timeout handling
331
+ | +-- manager.ts High-level API combining all background components
332
+ +-- autonomy/ Autonomy loop with verification checkpoints
333
+ | +-- state.ts Loop state tracking (iterations, context accumulation)
334
+ | +-- completion.ts Completion detection via positive/negative signals
335
+ | +-- verification.ts Post-iteration verification (tests, lint, artifacts)
336
+ | +-- controller.ts Loop lifecycle management (start, iterate, stop)
337
+ +-- routing/ Category-based task routing
338
+ | +-- categories.ts Category definitions with model and skill mappings
339
+ | +-- classifier.ts Intent classification from task descriptions
340
+ | +-- engine.ts Routing engine combining classification + delegation
341
+ +-- recovery/ Session recovery and failure resilience
342
+ | +-- classifier.ts Failure classification (transient, permanent, partial)
343
+ | +-- strategies.ts Recovery strategies (retry, fallback, checkpoint)
344
+ | +-- orchestrator.ts Strategy selection and execution
345
+ | +-- persistence.ts Checkpoint save/restore via SQLite
346
+ +-- context/ Context window management and injection
347
+ | +-- discovery.ts Active context discovery from session state
348
+ | +-- budget.ts Token budget allocation across injection sources
349
+ | +-- injector.ts System prompt injection orchestrator
350
+ | +-- compaction-handler.ts Context compaction when approaching limits
351
+ +-- ux/ User experience surfaces
352
+ | +-- notifications.ts Toast and inline notification system
353
+ | +-- progress.ts Progress tracking for multi-step operations
354
+ | +-- task-status.ts Task status formatting and display
355
+ | +-- context-warnings.ts Context usage warnings and suggestions
356
+ | +-- error-hints.ts Actionable error hints with fix suggestions
357
+ | +-- session-summary.ts End-of-session summary generation
358
+ +-- mcp/ MCP (Model Context Protocol) skill integration
359
+ | +-- types.ts MCP server and tool type definitions
360
+ | +-- manager.ts MCP server lifecycle management
361
+ | +-- scope-filter.ts Scope-based MCP server filtering
362
+ +-- kernel/ Database primitives and concurrency
363
+ | +-- transaction.ts SQLite transactions with retry on SQLITE_BUSY
364
+ | +-- retry.ts Exponential backoff retry for busy errors
365
+ +-- logging/ Structured logging with sinks and rotation
366
+ +-- memory/ Smart dual-scope memory (project patterns + user preferences)
367
+ +-- observability/ Session observability and structured event logging
368
+ +-- skills/ Adaptive skill loading and injection
369
+ +-- health/ Plugin self-diagnostics
288
370
  +-- utils/ Validators, paths, fs-helpers, gitignore management
289
371
 
290
372
  bin/
@@ -292,7 +374,7 @@ bin/
292
374
  ```
293
375
 
294
376
  **Dependency flow** (strictly top-down, no cycles):
295
- `index.ts` -> `tools/*` -> `registry/*` + `templates/*` + `utils/*` -> Node built-ins + `yaml`
377
+ `index.ts` -> `tools/*` -> `registry/*` + `templates/*` + `utils/*` + `kernel/*` -> Node built-ins + `yaml`
296
378
 
297
379
  **Key patterns:**
298
380
  - **Declarative registry** -- adding an agent = one line in AGENT_REGISTRY, everything derives from it
@@ -310,7 +392,7 @@ bun install
310
392
  bun test && bun run lint
311
393
  ```
312
394
 
313
- 834 tests across 64 files. No build step -- Bun runs TypeScript natively.
395
+ 1790+ tests across 190 files. No build step -- Bun runs TypeScript natively.
314
396
 
315
397
  ## License
316
398
 
@@ -0,0 +1,17 @@
1
+ ---
2
+ # opencode-autopilot
3
+ description: Run plugin health diagnostics — config validity, agent injection, native suppression, assets, memory, commands, and v7 config fields (background, routing, recovery, mcp)
4
+ ---
5
+
6
+ Invoke the `oc_doctor` tool to run a full health check on the opencode-autopilot plugin.
7
+
8
+ `oc_doctor` runs the current built-in health checks and reports the results by diagnostic area. These checks cover:
9
+
10
+ - **Config health** — The plugin config exists, parses correctly, and required modern config sections such as `background`, `routing`, `recovery`, and `mcp` are present or reported as needing migration.
11
+ - **Agent setup** — Expected autopilot agents are present in the OpenCode config, and native `plan`/`build` agents are suppressed as subagents.
12
+ - **Assets and installation paths** — Bundled asset directories and the global `~/.config/opencode/` target are accessible.
13
+ - **Skill loading** — Skills load correctly and are filtered against the detected project stack.
14
+ - **Memory storage** — The memory SQLite database is available, readable, or cleanly reported as not yet initialized on first install.
15
+ - **Command files** — Expected slash command files exist and have valid YAML frontmatter.
16
+
17
+ Each failing check includes a **Fix** suggestion. Run this after installation, after upgrades, or whenever something feels off.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  # opencode-autopilot
3
3
  description: Detect documentation affected by recent code changes and suggest updates
4
- agent: documenter
4
+ agent: coder
5
5
  ---
6
6
 
7
7
  Invoke the `oc_update_docs` tool to analyze recent code changes and identify documentation that may need updating.
@@ -307,7 +307,7 @@ export async function runConfigure(configPath: string = CONFIG_PATH): Promise<vo
307
307
 
308
308
  const newConfig = {
309
309
  ...baseConfig,
310
- version: 6 as const,
310
+ version: 7 as const,
311
311
  configured: true,
312
312
  groups: groupsRecord,
313
313
  overrides: baseConfig.overrides ?? {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodrunhq/opencode-autopilot",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "Curated agents, skills, and commands for the OpenCode AI coding CLI — autonomous orchestrator, multi-agent code review, model fallback, and in-session asset creation tools.",
5
5
  "main": "src/index.ts",
6
6
  "keywords": [
@@ -4,11 +4,7 @@ import { resolveModelForAgent } from "../registry/resolver";
4
4
  import type { AgentOverride, GroupModelAssignment } from "../registry/types";
5
5
  import { autopilotAgent } from "./autopilot";
6
6
  import { coderAgent } from "./coder";
7
- import { dbSpecialistAgent } from "./db-specialist";
8
7
  import { debuggerAgent } from "./debugger";
9
- import { devopsAgent } from "./devops";
10
- import { documenterAgent } from "./documenter";
11
- import { frontendEngineerAgent } from "./frontend-engineer";
12
8
  import { metaprompterAgent } from "./metaprompter";
13
9
  import { pipelineAgents } from "./pipeline/index";
14
10
  import { plannerAgent } from "./planner";
@@ -25,11 +21,7 @@ interface AgentConfig {
25
21
  export const agents = {
26
22
  autopilot: autopilotAgent,
27
23
  coder: coderAgent,
28
- "db-specialist": dbSpecialistAgent,
29
24
  debugger: debuggerAgent,
30
- devops: devopsAgent,
31
- documenter: documenterAgent,
32
- "frontend-engineer": frontendEngineerAgent,
33
25
  metaprompter: metaprompterAgent,
34
26
  planner: plannerAgent,
35
27
  "pr-reviewer": prReviewerAgent,
@@ -157,11 +149,7 @@ export async function configHook(config: Config, configPath?: string): Promise<v
157
149
 
158
150
  export { autopilotAgent } from "./autopilot";
159
151
  export { coderAgent } from "./coder";
160
- export { dbSpecialistAgent } from "./db-specialist";
161
152
  export { debuggerAgent } from "./debugger";
162
- export { devopsAgent } from "./devops";
163
- export { documenterAgent } from "./documenter";
164
- export { frontendEngineerAgent } from "./frontend-engineer";
165
153
  export { metaprompterAgent } from "./metaprompter";
166
154
  export { plannerAgent } from "./planner";
167
155
  export { prReviewerAgent } from "./pr-reviewer";
@@ -3,11 +3,9 @@ import { AGENT_NAMES } from "../../orchestrator/handlers/types";
3
3
  import { ocArchitectAgent } from "./oc-architect";
4
4
  import { ocChallengerAgent } from "./oc-challenger";
5
5
  import { ocCriticAgent } from "./oc-critic";
6
- import { ocExplorerAgent } from "./oc-explorer";
7
6
  import { ocImplementerAgent } from "./oc-implementer";
8
7
  import { ocPlannerAgent } from "./oc-planner";
9
8
  import { ocResearcherAgent } from "./oc-researcher";
10
- import { ocRetrospectorAgent } from "./oc-retrospector";
11
9
  import { ocReviewerAgent } from "./oc-reviewer";
12
10
  import { ocShipperAgent } from "./oc-shipper";
13
11
 
@@ -16,10 +14,8 @@ export const pipelineAgents: Readonly<Record<string, Readonly<AgentConfig>>> = O
16
14
  [AGENT_NAMES.CHALLENGE]: ocChallengerAgent,
17
15
  [AGENT_NAMES.ARCHITECT]: ocArchitectAgent,
18
16
  [AGENT_NAMES.CRITIC]: ocCriticAgent,
19
- [AGENT_NAMES.EXPLORE]: ocExplorerAgent,
20
17
  [AGENT_NAMES.PLAN]: ocPlannerAgent,
21
18
  [AGENT_NAMES.BUILD]: ocImplementerAgent,
22
19
  [AGENT_NAMES.REVIEW]: ocReviewerAgent,
23
20
  [AGENT_NAMES.SHIP]: ocShipperAgent,
24
- [AGENT_NAMES.RETROSPECTIVE]: ocRetrospectorAgent,
25
21
  } as const);
@@ -0,0 +1,52 @@
1
+ const EXPLICIT_SIGNALS = Object.freeze(["all tasks completed", "complete", "finished", "done"]);
2
+ const TODO_SIGNALS = Object.freeze(["all todos completed", "no remaining tasks"]);
3
+ const NEGATIVE_SIGNALS = Object.freeze(["still working", "in progress", "next step"]);
4
+
5
+ export interface CompletionDetectionResult {
6
+ readonly isComplete: boolean;
7
+ readonly confidence: number;
8
+ readonly signals: readonly string[];
9
+ }
10
+
11
+ function countMatches(content: string, phrases: readonly string[]): readonly string[] {
12
+ return phrases.filter((phrase) => new RegExp(`\\b${phrase}\\b`, "i").test(content));
13
+ }
14
+
15
+ function clampConfidence(value: number): number {
16
+ return Math.max(0, Math.min(1, value));
17
+ }
18
+
19
+ export function detectCompletion(transcript: readonly string[]): CompletionDetectionResult {
20
+ const normalizedTranscript = transcript.join("\n").toLowerCase();
21
+ const explicitMatches = countMatches(normalizedTranscript, EXPLICIT_SIGNALS);
22
+ const todoMatches = countMatches(normalizedTranscript, TODO_SIGNALS);
23
+ const negativeMatches = countMatches(normalizedTranscript, NEGATIVE_SIGNALS);
24
+ const signals = Object.freeze([...explicitMatches, ...todoMatches, ...negativeMatches]);
25
+
26
+ if (negativeMatches.length > 0) {
27
+ return Object.freeze({
28
+ isComplete: false,
29
+ confidence: clampConfidence(0.2 + negativeMatches.length * 0.05),
30
+ signals,
31
+ });
32
+ }
33
+
34
+ if (explicitMatches.length === 0 && todoMatches.length === 0) {
35
+ return Object.freeze({
36
+ isComplete: false,
37
+ confidence: 0,
38
+ signals,
39
+ });
40
+ }
41
+
42
+ const explicitConfidence =
43
+ explicitMatches.length > 0 ? 0.75 + (explicitMatches.length - 1) * 0.1 : 0;
44
+ const todoConfidence = todoMatches.length > 0 ? 0.65 + (todoMatches.length - 1) * 0.1 : 0;
45
+ const combinedBonus = explicitMatches.length > 0 && todoMatches.length > 0 ? 0.1 : 0;
46
+
47
+ return Object.freeze({
48
+ isComplete: true,
49
+ confidence: clampConfidence(Math.max(explicitConfidence, todoConfidence) + combinedBonus),
50
+ signals,
51
+ });
52
+ }
@@ -0,0 +1,144 @@
1
+ import { getLogger } from "../logging/domains";
2
+ import type { Logger } from "../logging/types";
3
+ import { detectCompletion } from "./completion";
4
+ import { LoopStateMachine } from "./state";
5
+ import type { LoopContext, LoopOptions } from "./types";
6
+ import { VerificationHandler } from "./verification";
7
+
8
+ export interface LoopControllerConfig {
9
+ readonly maxIterations?: number;
10
+ readonly verifyOnComplete?: boolean;
11
+ readonly cooldownMs?: number;
12
+ readonly logger?: Logger;
13
+ readonly verificationHandler?: VerificationHandler;
14
+ }
15
+
16
+ function delay(ms: number): Promise<void> {
17
+ return new Promise((resolve) => setTimeout(resolve, ms));
18
+ }
19
+
20
+ function summarizeFailedChecks(context: LoopContext): string {
21
+ const latestResult = context.verificationResults[context.verificationResults.length - 1];
22
+ if (!latestResult) {
23
+ return "Verification failed.";
24
+ }
25
+
26
+ const failedChecks = latestResult.checks
27
+ .filter((check) => !check.passed)
28
+ .map((check) => `${check.name}: ${check.message}`);
29
+
30
+ return failedChecks.length > 0
31
+ ? `Verification failed: ${failedChecks.join("; ")}`
32
+ : "Verification failed.";
33
+ }
34
+
35
+ export class LoopController {
36
+ private machine: LoopStateMachine;
37
+ private paused = false;
38
+ private maxIterations: number;
39
+ private verifyOnComplete: boolean;
40
+ private cooldownMs: number;
41
+ private readonly logger: Logger;
42
+ private readonly verificationHandler: VerificationHandler;
43
+
44
+ constructor(config: LoopControllerConfig = {}) {
45
+ this.maxIterations = config.maxIterations ?? 10;
46
+ this.verifyOnComplete = config.verifyOnComplete ?? true;
47
+ this.cooldownMs = config.cooldownMs ?? 0;
48
+ this.logger = config.logger ?? getLogger("autonomy", "controller");
49
+ this.verificationHandler = config.verificationHandler ?? new VerificationHandler();
50
+ this.machine = new LoopStateMachine(this.maxIterations);
51
+ }
52
+
53
+ start(taskDescription: string, options: LoopOptions = {}): LoopContext {
54
+ this.maxIterations = options.maxIterations ?? this.maxIterations;
55
+ this.verifyOnComplete = options.verifyOnComplete ?? this.verifyOnComplete;
56
+ this.cooldownMs = options.cooldownMs ?? this.cooldownMs;
57
+ this.paused = false;
58
+ this.machine = new LoopStateMachine(this.maxIterations, taskDescription);
59
+ this.machine.transition("running");
60
+ this.logger.info("Autonomy loop started", {
61
+ operation: "start",
62
+ taskDescription,
63
+ maxIterations: this.machine.getContext().maxIterations,
64
+ });
65
+ return this.machine.getContext();
66
+ }
67
+
68
+ async iterate(iterationResult: string): Promise<LoopContext> {
69
+ const status = this.machine.getContext();
70
+ if (this.paused || status.state !== "running") {
71
+ return status;
72
+ }
73
+
74
+ this.machine.addContext(iterationResult);
75
+ const exceeded = this.machine.incrementIteration();
76
+ if (exceeded) {
77
+ this.machine.transition("max_iterations");
78
+ this.logger.warn("Autonomy loop hit max iterations", {
79
+ operation: "iterate",
80
+ currentIteration: this.machine.getContext().currentIteration,
81
+ });
82
+ return this.machine.getContext();
83
+ }
84
+
85
+ const completion = detectCompletion(this.machine.getContext().accumulatedContext);
86
+ if (!completion.isComplete) {
87
+ await this.applyCooldown();
88
+ return this.machine.getContext();
89
+ }
90
+
91
+ if (!this.verifyOnComplete) {
92
+ this.machine.transition("complete");
93
+ return this.machine.getContext();
94
+ }
95
+
96
+ this.machine.transition("verifying");
97
+ const verificationResult = await this.verificationHandler.verify(this.machine.getContext());
98
+ this.machine.addVerificationResult(verificationResult);
99
+
100
+ if (verificationResult.passed) {
101
+ this.machine.transition("complete");
102
+ return this.machine.getContext();
103
+ }
104
+
105
+ this.machine.transition("running");
106
+ this.machine.addContext(summarizeFailedChecks(this.machine.getContext()));
107
+ await this.applyCooldown();
108
+ return this.machine.getContext();
109
+ }
110
+
111
+ pause(): LoopContext {
112
+ this.paused = true;
113
+ return this.machine.getContext();
114
+ }
115
+
116
+ resume(): LoopContext {
117
+ this.paused = false;
118
+ return this.machine.getContext();
119
+ }
120
+
121
+ abort(): LoopContext {
122
+ const status = this.machine.getContext();
123
+ if (status.state === "running" || status.state === "verifying") {
124
+ this.machine.transition("failed");
125
+ this.machine.addContext("Loop aborted by operator.");
126
+ this.logger.warn("Autonomy loop aborted", { operation: "abort" });
127
+ }
128
+ return this.machine.getContext();
129
+ }
130
+
131
+ getStatus(): LoopContext {
132
+ return this.machine.getContext();
133
+ }
134
+
135
+ isComplete(): boolean {
136
+ return this.machine.getContext().state === "complete";
137
+ }
138
+
139
+ private async applyCooldown(): Promise<void> {
140
+ if (this.cooldownMs > 0) {
141
+ await delay(this.cooldownMs);
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,25 @@
1
+ import { getLogger } from "../logging/domains";
2
+ import { LoopController } from "./controller";
3
+
4
+ let globalLoopController: LoopController | null = null;
5
+
6
+ export function getLoopController(): LoopController {
7
+ if (!globalLoopController) {
8
+ globalLoopController = new LoopController({
9
+ logger: getLogger("autonomy", "controller"),
10
+ });
11
+ }
12
+
13
+ return globalLoopController;
14
+ }
15
+
16
+ export function setLoopControllerForTests(controller: LoopController | null): void {
17
+ globalLoopController = controller;
18
+ }
19
+
20
+ export * from "./completion";
21
+ export * from "./controller";
22
+ export * from "./injector";
23
+ export * from "./state";
24
+ export * from "./types";
25
+ export * from "./verification";
@@ -0,0 +1,49 @@
1
+ import type { LoopController } from "./controller";
2
+
3
+ const LOOP_CONTEXT_CHAR_BUDGET = 500;
4
+
5
+ interface LoopInjectorInput {
6
+ readonly sessionID?: string;
7
+ }
8
+
9
+ interface LoopInjectorOutput {
10
+ system: string[];
11
+ }
12
+
13
+ function truncate(value: string, maxLength: number): string {
14
+ if (value.length <= maxLength) {
15
+ return value;
16
+ }
17
+
18
+ return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
19
+ }
20
+
21
+ function buildLoopContext(controller: LoopController): string {
22
+ const status = controller.getStatus();
23
+ const remainingIterations = Math.max(0, status.maxIterations - status.currentIteration);
24
+ const lastContext = status.accumulatedContext[status.accumulatedContext.length - 1] ?? "None";
25
+ const baseContext = [
26
+ "[Autonomy Loop]",
27
+ `State: ${status.state}`,
28
+ `Current iteration: ${status.currentIteration} of ${status.maxIterations}`,
29
+ `Remaining iterations: ${remainingIterations}`,
30
+ `Last context: ${lastContext}`,
31
+ ].join("\n");
32
+
33
+ return truncate(baseContext, LOOP_CONTEXT_CHAR_BUDGET);
34
+ }
35
+
36
+ export function createLoopInjector(controller: LoopController) {
37
+ return async (_input: LoopInjectorInput, output: LoopInjectorOutput): Promise<void> => {
38
+ if (controller.getStatus().state === "idle") {
39
+ return;
40
+ }
41
+
42
+ const loopContext = buildLoopContext(controller);
43
+ output.system.push(loopContext);
44
+ };
45
+ }
46
+
47
+ export const loopInjectorConstants = Object.freeze({
48
+ LOOP_CONTEXT_CHAR_BUDGET,
49
+ });
@@ -0,0 +1,91 @@
1
+ import type { LoopContext, LoopState, VerificationResult } from "./types";
2
+
3
+ const DEFAULT_MAX_ITERATIONS = 10;
4
+ const HARD_MAX_ITERATIONS = 50;
5
+
6
+ const VALID_TRANSITIONS = Object.freeze({
7
+ idle: Object.freeze(["running"] as const),
8
+ running: Object.freeze(["verifying", "complete", "failed", "max_iterations"] as const),
9
+ verifying: Object.freeze(["running", "complete", "failed"] as const),
10
+ complete: Object.freeze([] as const),
11
+ failed: Object.freeze([] as const),
12
+ max_iterations: Object.freeze([] as const),
13
+ }) satisfies Readonly<Record<LoopState, readonly LoopState[]>>;
14
+
15
+ function clampMaxIterations(maxIterations: number): number {
16
+ return Math.max(1, Math.min(maxIterations, HARD_MAX_ITERATIONS));
17
+ }
18
+
19
+ function createInitialContext(maxIterations: number): LoopContext {
20
+ return Object.freeze({
21
+ taskDescription: "",
22
+ maxIterations: clampMaxIterations(maxIterations),
23
+ currentIteration: 0,
24
+ state: "idle",
25
+ startedAt: new Date().toISOString(),
26
+ lastIterationAt: null,
27
+ accumulatedContext: Object.freeze([]),
28
+ verificationResults: Object.freeze([]),
29
+ });
30
+ }
31
+
32
+ export class LoopStateMachine {
33
+ private context: LoopContext;
34
+
35
+ constructor(maxIterations = DEFAULT_MAX_ITERATIONS, taskDescription = "") {
36
+ this.context = Object.freeze({
37
+ ...createInitialContext(maxIterations),
38
+ taskDescription,
39
+ });
40
+ }
41
+
42
+ transition(to: LoopState): void {
43
+ const validTargets: readonly LoopState[] = VALID_TRANSITIONS[this.context.state];
44
+ if (!validTargets.includes(to)) {
45
+ throw new Error(`Invalid loop state transition: ${this.context.state} -> ${to}`);
46
+ }
47
+
48
+ this.context = Object.freeze({
49
+ ...this.context,
50
+ state: to,
51
+ });
52
+ }
53
+
54
+ getContext(): LoopContext {
55
+ return Object.freeze({
56
+ ...this.context,
57
+ accumulatedContext: Object.freeze([...this.context.accumulatedContext]),
58
+ verificationResults: Object.freeze([...this.context.verificationResults]),
59
+ });
60
+ }
61
+
62
+ incrementIteration(): boolean {
63
+ const nextIteration = this.context.currentIteration + 1;
64
+ this.context = Object.freeze({
65
+ ...this.context,
66
+ currentIteration: nextIteration,
67
+ lastIterationAt: new Date().toISOString(),
68
+ });
69
+
70
+ return nextIteration > this.context.maxIterations;
71
+ }
72
+
73
+ addContext(text: string): void {
74
+ this.context = Object.freeze({
75
+ ...this.context,
76
+ accumulatedContext: Object.freeze([...this.context.accumulatedContext, text]),
77
+ });
78
+ }
79
+
80
+ addVerificationResult(result: VerificationResult): void {
81
+ this.context = Object.freeze({
82
+ ...this.context,
83
+ verificationResults: Object.freeze([...this.context.verificationResults, result]),
84
+ });
85
+ }
86
+ }
87
+
88
+ export const loopStateMachineConstants = Object.freeze({
89
+ DEFAULT_MAX_ITERATIONS,
90
+ HARD_MAX_ITERATIONS,
91
+ });