@kodrunhq/opencode-autopilot 1.18.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.
- package/README.md +95 -13
- package/assets/commands/oc-update-docs.md +1 -1
- package/package.json +1 -1
- package/src/agents/index.ts +0 -12
- package/src/agents/pipeline/index.ts +0 -4
- package/src/autonomy/completion.ts +52 -0
- package/src/autonomy/controller.ts +144 -0
- package/src/autonomy/index.ts +25 -0
- package/src/autonomy/injector.ts +49 -0
- package/src/autonomy/state.ts +91 -0
- package/src/autonomy/types.ts +30 -0
- package/src/autonomy/verification.ts +86 -0
- package/src/background/database.ts +170 -0
- package/src/background/executor.ts +174 -0
- package/src/background/index.ts +8 -0
- package/src/background/manager.ts +232 -0
- package/src/background/repository.ts +174 -0
- package/src/background/schema.ts +24 -0
- package/src/background/sdk-runner.ts +40 -0
- package/src/background/slot-manager.ts +41 -0
- package/src/background/state-machine.ts +19 -0
- package/src/context/budget.ts +45 -0
- package/src/context/compaction-handler.ts +58 -0
- package/src/context/discovery.ts +94 -0
- package/src/context/index.ts +14 -0
- package/src/context/injector.ts +119 -0
- package/src/context/types.ts +24 -0
- package/src/health/checks.ts +145 -2
- package/src/health/index.ts +7 -1
- package/src/health/runner.ts +6 -0
- package/src/index.ts +113 -6
- package/src/installer.ts +13 -0
- package/src/kernel/index.ts +6 -0
- package/src/kernel/migrations.ts +50 -0
- package/src/kernel/retry.ts +49 -0
- package/src/kernel/schema.ts +9 -1
- package/src/kernel/transaction.ts +40 -12
- package/src/logging/forensic-writer.ts +6 -2
- package/src/logging/index.ts +2 -0
- package/src/mcp/index.ts +34 -0
- package/src/mcp/manager.ts +206 -0
- package/src/mcp/scope-filter.ts +44 -0
- package/src/mcp/types.ts +38 -0
- package/src/orchestrator/arena.ts +7 -1
- package/src/orchestrator/fallback/event-handler.ts +12 -1
- package/src/orchestrator/handlers/challenge.ts +8 -1
- package/src/orchestrator/handlers/plan.ts +8 -1
- package/src/orchestrator/handlers/recon.ts +8 -1
- package/src/orchestrator/handlers/types.ts +2 -2
- package/src/orchestrator/lesson-memory.ts +6 -1
- package/src/orchestrator/orchestration-logger.ts +15 -3
- package/src/orchestrator/skill-injection.ts +7 -1
- package/src/orchestrator/state.ts +6 -1
- package/src/recovery/classifier.ts +127 -0
- package/src/recovery/event-handler.ts +263 -0
- package/src/recovery/index.ts +20 -0
- package/src/recovery/orchestrator.ts +180 -0
- package/src/recovery/persistence.ts +87 -0
- package/src/recovery/strategies.ts +107 -0
- package/src/recovery/types.ts +31 -0
- package/src/registry/model-groups.ts +2 -19
- package/src/registry/resolver.ts +38 -9
- package/src/review/agent-catalog.ts +83 -251
- package/src/review/agents/architecture-verifier.ts +41 -0
- package/src/review/agents/code-hygiene-auditor.ts +40 -0
- package/src/review/agents/correctness-auditor.ts +41 -0
- package/src/review/agents/frontend-auditor.ts +39 -0
- package/src/review/agents/index.ts +15 -42
- package/src/review/agents/language-idioms-auditor.ts +39 -0
- package/src/review/agents/security-auditor.ts +12 -8
- package/src/review/stack-gate.ts +2 -6
- package/src/routing/categories.ts +111 -0
- package/src/routing/classifier.ts +152 -0
- package/src/routing/engine.ts +89 -0
- package/src/routing/index.ts +4 -0
- package/src/routing/types.ts +14 -0
- package/src/skills/adaptive-injector.ts +34 -3
- package/src/skills/loader.ts +4 -0
- package/src/tools/background.ts +196 -0
- package/src/tools/delegate.ts +205 -0
- package/src/tools/loop.ts +94 -0
- package/src/tools/recover.ts +172 -0
- package/src/types/recovery.ts +10 -0
- package/src/ux/context-warnings.ts +81 -0
- package/src/ux/error-hints.ts +38 -0
- package/src/ux/index.ts +7 -0
- package/src/ux/notifications.ts +67 -0
- package/src/ux/progress.ts +77 -0
- package/src/ux/session-summary.ts +67 -0
- package/src/ux/task-status.ts +109 -0
- package/src/ux/types.ts +24 -0
- package/src/agents/db-specialist.ts +0 -295
- package/src/agents/devops.ts +0 -352
- package/src/agents/documenter.ts +0 -44
- package/src/agents/frontend-engineer.ts +0 -541
- package/src/agents/pipeline/oc-explorer.ts +0 -46
- package/src/agents/pipeline/oc-retrospector.ts +0 -42
- package/src/review/agents/auth-flow-verifier.ts +0 -47
- package/src/review/agents/concurrency-checker.ts +0 -47
- package/src/review/agents/dead-code-scanner.ts +0 -47
- package/src/review/agents/go-idioms-auditor.ts +0 -46
- package/src/review/agents/python-django-auditor.ts +0 -46
- package/src/review/agents/react-patterns-auditor.ts +0 -46
- package/src/review/agents/rust-safety-auditor.ts +0 -46
- package/src/review/agents/scope-intent-verifier.ts +0 -45
- package/src/review/agents/silent-failure-hunter.ts +0 -45
- package/src/review/agents/spec-checker.ts +0 -45
- package/src/review/agents/state-mgmt-auditor.ts +0 -46
- package/src/review/agents/type-soundness.ts +0 -46
- 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 •
|
|
17
|
+
Idea to shipped code • 13-agent code review • Adversarial model diversity • Background task management • 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 +
|
|
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":
|
|
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
|
-
###
|
|
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,
|
|
198
|
-
| **Stack-aware** (auto-selected) |
|
|
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
|
|
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->
|
|
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/
|
|
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
|
-
|
|
395
|
+
1790+ tests across 190 files. No build step -- Bun runs TypeScript natively.
|
|
314
396
|
|
|
315
397
|
## License
|
|
316
398
|
|
|
@@ -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:
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodrunhq/opencode-autopilot",
|
|
3
|
-
"version": "1.
|
|
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": [
|
package/src/agents/index.ts
CHANGED
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type LoopState = "idle" | "running" | "verifying" | "complete" | "failed" | "max_iterations";
|
|
2
|
+
|
|
3
|
+
export interface LoopContext {
|
|
4
|
+
readonly taskDescription: string;
|
|
5
|
+
readonly maxIterations: number;
|
|
6
|
+
readonly currentIteration: number;
|
|
7
|
+
readonly state: LoopState;
|
|
8
|
+
readonly startedAt: string;
|
|
9
|
+
readonly lastIterationAt: string | null;
|
|
10
|
+
readonly accumulatedContext: readonly string[];
|
|
11
|
+
readonly verificationResults: readonly VerificationResult[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface VerificationResult {
|
|
15
|
+
readonly passed: boolean;
|
|
16
|
+
readonly checks: readonly VerificationCheck[];
|
|
17
|
+
readonly timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface VerificationCheck {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly passed: boolean;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LoopOptions {
|
|
27
|
+
readonly maxIterations?: number;
|
|
28
|
+
readonly verifyOnComplete?: boolean;
|
|
29
|
+
readonly cooldownMs?: number;
|
|
30
|
+
}
|