@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.
Files changed (110) hide show
  1. package/README.md +95 -13
  2. package/assets/commands/oc-update-docs.md +1 -1
  3. package/package.json +1 -1
  4. package/src/agents/index.ts +0 -12
  5. package/src/agents/pipeline/index.ts +0 -4
  6. package/src/autonomy/completion.ts +52 -0
  7. package/src/autonomy/controller.ts +144 -0
  8. package/src/autonomy/index.ts +25 -0
  9. package/src/autonomy/injector.ts +49 -0
  10. package/src/autonomy/state.ts +91 -0
  11. package/src/autonomy/types.ts +30 -0
  12. package/src/autonomy/verification.ts +86 -0
  13. package/src/background/database.ts +170 -0
  14. package/src/background/executor.ts +174 -0
  15. package/src/background/index.ts +8 -0
  16. package/src/background/manager.ts +232 -0
  17. package/src/background/repository.ts +174 -0
  18. package/src/background/schema.ts +24 -0
  19. package/src/background/sdk-runner.ts +40 -0
  20. package/src/background/slot-manager.ts +41 -0
  21. package/src/background/state-machine.ts +19 -0
  22. package/src/context/budget.ts +45 -0
  23. package/src/context/compaction-handler.ts +58 -0
  24. package/src/context/discovery.ts +94 -0
  25. package/src/context/index.ts +14 -0
  26. package/src/context/injector.ts +119 -0
  27. package/src/context/types.ts +24 -0
  28. package/src/health/checks.ts +145 -2
  29. package/src/health/index.ts +7 -1
  30. package/src/health/runner.ts +6 -0
  31. package/src/index.ts +113 -6
  32. package/src/installer.ts +13 -0
  33. package/src/kernel/index.ts +6 -0
  34. package/src/kernel/migrations.ts +50 -0
  35. package/src/kernel/retry.ts +49 -0
  36. package/src/kernel/schema.ts +9 -1
  37. package/src/kernel/transaction.ts +40 -12
  38. package/src/logging/forensic-writer.ts +6 -2
  39. package/src/logging/index.ts +2 -0
  40. package/src/mcp/index.ts +34 -0
  41. package/src/mcp/manager.ts +206 -0
  42. package/src/mcp/scope-filter.ts +44 -0
  43. package/src/mcp/types.ts +38 -0
  44. package/src/orchestrator/arena.ts +7 -1
  45. package/src/orchestrator/fallback/event-handler.ts +12 -1
  46. package/src/orchestrator/handlers/challenge.ts +8 -1
  47. package/src/orchestrator/handlers/plan.ts +8 -1
  48. package/src/orchestrator/handlers/recon.ts +8 -1
  49. package/src/orchestrator/handlers/types.ts +2 -2
  50. package/src/orchestrator/lesson-memory.ts +6 -1
  51. package/src/orchestrator/orchestration-logger.ts +15 -3
  52. package/src/orchestrator/skill-injection.ts +7 -1
  53. package/src/orchestrator/state.ts +6 -1
  54. package/src/recovery/classifier.ts +127 -0
  55. package/src/recovery/event-handler.ts +263 -0
  56. package/src/recovery/index.ts +20 -0
  57. package/src/recovery/orchestrator.ts +180 -0
  58. package/src/recovery/persistence.ts +87 -0
  59. package/src/recovery/strategies.ts +107 -0
  60. package/src/recovery/types.ts +31 -0
  61. package/src/registry/model-groups.ts +2 -19
  62. package/src/registry/resolver.ts +38 -9
  63. package/src/review/agent-catalog.ts +83 -251
  64. package/src/review/agents/architecture-verifier.ts +41 -0
  65. package/src/review/agents/code-hygiene-auditor.ts +40 -0
  66. package/src/review/agents/correctness-auditor.ts +41 -0
  67. package/src/review/agents/frontend-auditor.ts +39 -0
  68. package/src/review/agents/index.ts +15 -42
  69. package/src/review/agents/language-idioms-auditor.ts +39 -0
  70. package/src/review/agents/security-auditor.ts +12 -8
  71. package/src/review/stack-gate.ts +2 -6
  72. package/src/routing/categories.ts +111 -0
  73. package/src/routing/classifier.ts +152 -0
  74. package/src/routing/engine.ts +89 -0
  75. package/src/routing/index.ts +4 -0
  76. package/src/routing/types.ts +14 -0
  77. package/src/skills/adaptive-injector.ts +34 -3
  78. package/src/skills/loader.ts +4 -0
  79. package/src/tools/background.ts +196 -0
  80. package/src/tools/delegate.ts +205 -0
  81. package/src/tools/loop.ts +94 -0
  82. package/src/tools/recover.ts +172 -0
  83. package/src/types/recovery.ts +10 -0
  84. package/src/ux/context-warnings.ts +81 -0
  85. package/src/ux/error-hints.ts +38 -0
  86. package/src/ux/index.ts +7 -0
  87. package/src/ux/notifications.ts +67 -0
  88. package/src/ux/progress.ts +77 -0
  89. package/src/ux/session-summary.ts +67 -0
  90. package/src/ux/task-status.ts +109 -0
  91. package/src/ux/types.ts +24 -0
  92. package/src/agents/db-specialist.ts +0 -295
  93. package/src/agents/devops.ts +0 -352
  94. package/src/agents/documenter.ts +0 -44
  95. package/src/agents/frontend-engineer.ts +0 -541
  96. package/src/agents/pipeline/oc-explorer.ts +0 -46
  97. package/src/agents/pipeline/oc-retrospector.ts +0 -42
  98. package/src/review/agents/auth-flow-verifier.ts +0 -47
  99. package/src/review/agents/concurrency-checker.ts +0 -47
  100. package/src/review/agents/dead-code-scanner.ts +0 -47
  101. package/src/review/agents/go-idioms-auditor.ts +0 -46
  102. package/src/review/agents/python-django-auditor.ts +0 -46
  103. package/src/review/agents/react-patterns-auditor.ts +0 -46
  104. package/src/review/agents/rust-safety-auditor.ts +0 -46
  105. package/src/review/agents/scope-intent-verifier.ts +0 -45
  106. package/src/review/agents/silent-failure-hunter.ts +0 -45
  107. package/src/review/agents/spec-checker.ts +0 -45
  108. package/src/review/agents/state-mgmt-auditor.ts +0 -46
  109. package/src/review/agents/type-soundness.ts +0 -46
  110. package/src/review/agents/wiring-inspector.ts +0 -46
@@ -0,0 +1,87 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import { withTransaction } from "../kernel/transaction";
3
+ import type { RecoveryAttempt, RecoveryState } from "./types";
4
+
5
+ interface RecoveryStateRow {
6
+ readonly session_id: string;
7
+ readonly state_json: string;
8
+ readonly updated_at: string;
9
+ }
10
+
11
+ function isRecoveryAttempt(value: unknown): value is RecoveryAttempt {
12
+ if (value === null || typeof value !== "object") {
13
+ return false;
14
+ }
15
+
16
+ const candidate = value as Record<string, unknown>;
17
+ return (
18
+ typeof candidate.attemptNumber === "number" &&
19
+ typeof candidate.strategy === "string" &&
20
+ typeof candidate.errorCategory === "string" &&
21
+ typeof candidate.timestamp === "string" &&
22
+ typeof candidate.success === "boolean" &&
23
+ (candidate.error === undefined || typeof candidate.error === "string")
24
+ );
25
+ }
26
+
27
+ function isRecoveryState(value: unknown): value is RecoveryState {
28
+ if (value === null || typeof value !== "object") {
29
+ return false;
30
+ }
31
+
32
+ const candidate = value as Record<string, unknown>;
33
+ return (
34
+ typeof candidate.sessionId === "string" &&
35
+ Array.isArray(candidate.attempts) &&
36
+ candidate.attempts.every(isRecoveryAttempt) &&
37
+ (candidate.currentStrategy === null || typeof candidate.currentStrategy === "string") &&
38
+ typeof candidate.maxAttempts === "number" &&
39
+ typeof candidate.isRecovering === "boolean" &&
40
+ (candidate.lastError === null || typeof candidate.lastError === "string")
41
+ );
42
+ }
43
+
44
+ function parseState(row: RecoveryStateRow | null): RecoveryState | null {
45
+ if (!row) {
46
+ return null;
47
+ }
48
+
49
+ const parsed = JSON.parse(row.state_json) as unknown;
50
+ return isRecoveryState(parsed) ? Object.freeze(parsed) : null;
51
+ }
52
+
53
+ export function saveRecoveryState(db: Database, state: RecoveryState): void {
54
+ const updatedAt = new Date().toISOString();
55
+ withTransaction(db, () => {
56
+ db.run(
57
+ `INSERT INTO recovery_state (session_id, state_json, updated_at)
58
+ VALUES (?, ?, ?)
59
+ ON CONFLICT(session_id) DO UPDATE SET
60
+ state_json = excluded.state_json,
61
+ updated_at = excluded.updated_at`,
62
+ [state.sessionId, JSON.stringify(state), updatedAt],
63
+ );
64
+ });
65
+ }
66
+
67
+ export function loadRecoveryState(db: Database, sessionId: string): RecoveryState | null {
68
+ const row = db
69
+ .query("SELECT session_id, state_json, updated_at FROM recovery_state WHERE session_id = ?")
70
+ .get(sessionId) as RecoveryStateRow | null;
71
+ return parseState(row);
72
+ }
73
+
74
+ export function clearRecoveryState(db: Database, sessionId: string): void {
75
+ withTransaction(db, () => {
76
+ db.run("DELETE FROM recovery_state WHERE session_id = ?", [sessionId]);
77
+ });
78
+ }
79
+
80
+ export function listRecoveryStates(db: Database): readonly RecoveryState[] {
81
+ const rows = db
82
+ .query("SELECT session_id, state_json, updated_at FROM recovery_state ORDER BY updated_at DESC")
83
+ .all() as RecoveryStateRow[];
84
+ return Object.freeze(
85
+ rows.map(parseState).filter((state): state is RecoveryState => state !== null),
86
+ );
87
+ }
@@ -0,0 +1,107 @@
1
+ import type { ErrorCategory, RecoveryAction } from "../types/recovery";
2
+ import type { RecoveryState } from "./types";
3
+
4
+ export type RecoveryStrategyResolver = (context: RecoveryState) => RecoveryAction;
5
+
6
+ function createAction(
7
+ strategy: RecoveryAction["strategy"],
8
+ errorCategory: ErrorCategory,
9
+ maxAttempts: number,
10
+ backoffMs: number,
11
+ metadata: Record<string, unknown> = {},
12
+ ): RecoveryAction {
13
+ return Object.freeze({
14
+ strategy,
15
+ errorCategory,
16
+ maxAttempts,
17
+ backoffMs,
18
+ metadata,
19
+ });
20
+ }
21
+
22
+ function getAttemptIndex(context: RecoveryState): number {
23
+ return context.attempts.length;
24
+ }
25
+
26
+ function retryWithBackoff(category: ErrorCategory): RecoveryStrategyResolver {
27
+ return (context) => {
28
+ const attemptIndex = getAttemptIndex(context);
29
+ return createAction("retry", category, context.maxAttempts, 1000 * 2 ** attemptIndex, {
30
+ retryMode: "backoff",
31
+ });
32
+ };
33
+ }
34
+
35
+ function fallbackModel(category: ErrorCategory): RecoveryStrategyResolver {
36
+ return (context) =>
37
+ createAction("fallback_model", category, context.maxAttempts, 0, {
38
+ switchModel: true,
39
+ });
40
+ }
41
+
42
+ function compactAndRetry(category: ErrorCategory): RecoveryStrategyResolver {
43
+ return (context) =>
44
+ createAction("compact_and_retry", category, context.maxAttempts, 500, {
45
+ compactContext: true,
46
+ });
47
+ }
48
+
49
+ function restartSession(category: ErrorCategory): RecoveryStrategyResolver {
50
+ return (context) =>
51
+ createAction("restart_session", category, context.maxAttempts, 0, {
52
+ restartSession: true,
53
+ });
54
+ }
55
+
56
+ function reduceContext(category: ErrorCategory): RecoveryStrategyResolver {
57
+ return (context) =>
58
+ createAction("reduce_context", category, context.maxAttempts, 250, {
59
+ trimContext: true,
60
+ });
61
+ }
62
+
63
+ function skipAndContinue(category: ErrorCategory): RecoveryStrategyResolver {
64
+ return (context) =>
65
+ createAction("skip_and_continue", category, context.maxAttempts, 0, {
66
+ skipCurrentStep: true,
67
+ });
68
+ }
69
+
70
+ function userPrompt(category: ErrorCategory): RecoveryStrategyResolver {
71
+ return (context) => createAction("user_prompt", category, context.maxAttempts, 0);
72
+ }
73
+
74
+ function abort(category: ErrorCategory): RecoveryStrategyResolver {
75
+ return (context) => createAction("abort", category, context.maxAttempts, 0);
76
+ }
77
+
78
+ export function getStrategy(category: ErrorCategory): RecoveryStrategyResolver {
79
+ switch (category) {
80
+ case "rate_limit":
81
+ case "timeout":
82
+ case "network":
83
+ case "service_unavailable":
84
+ return retryWithBackoff(category);
85
+ case "quota_exceeded":
86
+ case "empty_content":
87
+ case "thinking_block_error":
88
+ return fallbackModel(category);
89
+ case "tool_result_overflow":
90
+ return compactAndRetry(category);
91
+ case "context_window_exceeded":
92
+ return (context) =>
93
+ getAttemptIndex(context) > 0
94
+ ? reduceContext(category)(context)
95
+ : compactAndRetry(category)(context);
96
+ case "session_corruption":
97
+ return restartSession(category);
98
+ case "agent_loop_stuck":
99
+ return skipAndContinue(category);
100
+ case "validation":
101
+ return userPrompt(category);
102
+ case "auth_failure":
103
+ return abort(category);
104
+ default:
105
+ return retryWithBackoff(category);
106
+ }
107
+ }
@@ -0,0 +1,31 @@
1
+ import type { ErrorCategory, RecoveryAction, RecoveryStrategy } from "../types/recovery";
2
+
3
+ export interface ClassificationResult {
4
+ readonly category: ErrorCategory;
5
+ readonly confidence: number;
6
+ readonly reasoning: string;
7
+ readonly isRecoverable: boolean;
8
+ }
9
+
10
+ export interface RecoveryAttempt {
11
+ readonly attemptNumber: number;
12
+ readonly strategy: RecoveryStrategy;
13
+ readonly errorCategory: ErrorCategory;
14
+ readonly timestamp: string;
15
+ readonly success: boolean;
16
+ readonly error?: string;
17
+ }
18
+
19
+ export interface RecoveryState {
20
+ readonly sessionId: string;
21
+ readonly attempts: readonly RecoveryAttempt[];
22
+ readonly currentStrategy: RecoveryStrategy | null;
23
+ readonly maxAttempts: number;
24
+ readonly isRecovering: boolean;
25
+ readonly lastError: string | null;
26
+ }
27
+
28
+ export interface RecoveryActionEnvelope {
29
+ readonly action: RecoveryAction;
30
+ readonly state: RecoveryState;
31
+ }
@@ -11,56 +11,39 @@ function deepFreeze<T extends object>(obj: T): Readonly<T> {
11
11
 
12
12
  export const AGENT_REGISTRY: Readonly<Record<string, AgentEntry>> = deepFreeze({
13
13
  // ── Architects ─────────────────────────────────────────────
14
- // Deep reasoning: system design, task decomposition, orchestration
15
14
  "oc-architect": { group: "architects" },
16
15
  "oc-planner": { group: "architects" },
17
16
  autopilot: { group: "architects" },
18
17
  planner: { group: "architects" },
19
18
 
20
19
  // ── Challengers ────────────────────────────────────────────
21
- // Adversarial to Architects: critique proposals, enhance ideas
22
20
  "oc-critic": { group: "challengers" },
23
21
  "oc-challenger": { group: "challengers" },
24
22
 
25
23
  // ── Builders ───────────────────────────────────────────────
26
- // Code generation and debugging
27
24
  "oc-implementer": { group: "builders" },
25
+ coder: { group: "builders" },
28
26
  debugger: { group: "builders" },
29
27
 
30
28
  // ── Reviewers ──────────────────────────────────────────────
31
- // Code analysis, adversarial to Builders
32
- // NOTE: The 21 internal ReviewAgent objects (logic-auditor, security-auditor,
33
- // etc.) are NOT in this registry. They use the ReviewAgent type from
34
- // src/review/types.ts, not AgentConfig. The review pipeline resolves their
35
- // model via resolveModelForGroup("reviewers") directly.
36
29
  "oc-reviewer": { group: "reviewers" },
37
30
  reviewer: { group: "reviewers" },
38
31
 
39
32
  // ── Red Team ───────────────────────────────────────────────
40
- // Final adversarial pass
41
- // NOTE: red-team and product-thinker are ALSO internal ReviewAgent objects
42
- // in STAGE3_AGENTS (src/review/agents/index.ts). They appear here so the
43
- // review pipeline can resolve their model via resolveModelForGroup("red-team")
44
- // separately from the "reviewers" group.
45
33
  "red-team": { group: "red-team" },
46
34
  "product-thinker": { group: "red-team" },
47
35
 
48
36
  // ── Researchers ────────────────────────────────────────────
49
- // Domain research, feasibility analysis
50
37
  "oc-researcher": { group: "researchers" },
51
38
  researcher: { group: "researchers" },
52
39
 
53
40
  // ── Communicators ──────────────────────────────────────────
54
- // Docs, changelogs, lesson extraction
55
41
  "oc-shipper": { group: "communicators" },
56
- documenter: { group: "communicators" },
57
- "oc-retrospector": { group: "communicators" },
58
42
 
59
43
  // ── Utilities ──────────────────────────────────────────────
60
- // Fast lookups, prompt tuning, PR scanning
61
- "oc-explorer": { group: "utilities" },
62
44
  metaprompter: { group: "utilities" },
63
45
  "pr-reviewer": { group: "utilities" },
46
+ "security-auditor": { group: "reviewers" },
64
47
  });
65
48
 
66
49
  export const GROUP_DEFINITIONS: Readonly<Record<GroupId, GroupDefinition>> = deepFreeze({
@@ -1,6 +1,15 @@
1
1
  import { AGENT_REGISTRY } from "./model-groups";
2
2
  import type { AgentOverride, GroupId, GroupModelAssignment, ResolvedModel } from "./types";
3
3
 
4
+ const DEPRECATED_AGENT_REMAP: Readonly<Record<string, string>> = Object.freeze({
5
+ documenter: "coder",
6
+ devops: "coder",
7
+ "frontend-engineer": "coder",
8
+ "db-specialist": "coder",
9
+ "oc-explorer": "oc-researcher",
10
+ "oc-retrospector": "oc-shipper",
11
+ });
12
+
4
13
  /**
5
14
  * Extract model family (provider) from a model string.
6
15
  * "anthropic/claude-opus-4-6" → "anthropic"
@@ -17,15 +26,15 @@ export function extractFamily(model: string): string {
17
26
  *
18
27
  * Resolution order:
19
28
  * 1. Per-agent override in overrides[agentName]
20
- * 2. Agent's group in AGENT_REGISTRY groups[groupId]
21
- * 3. null (agent uses OpenCode's default model)
29
+ * 2. Deprecated agent remapresolve via new agent name
30
+ * 3. Agent's group in AGENT_REGISTRY groups[groupId]
31
+ * 4. null (agent uses OpenCode's default model)
22
32
  */
23
33
  export function resolveModelForAgent(
24
34
  agentName: string,
25
35
  groups: Readonly<Record<string, GroupModelAssignment>>,
26
36
  overrides: Readonly<Record<string, AgentOverride>>,
27
37
  ): ResolvedModel | null {
28
- // Tier 1: per-agent override
29
38
  const override = overrides[agentName];
30
39
  if (override) {
31
40
  return {
@@ -35,7 +44,30 @@ export function resolveModelForAgent(
35
44
  };
36
45
  }
37
46
 
38
- // Tier 2: group assignment
47
+ const remappedName = DEPRECATED_AGENT_REMAP[agentName];
48
+ if (remappedName) {
49
+ const remappedOverride = overrides[remappedName];
50
+ if (remappedOverride) {
51
+ return {
52
+ primary: remappedOverride.primary,
53
+ fallbacks: remappedOverride.fallbacks ?? [],
54
+ source: "override",
55
+ };
56
+ }
57
+
58
+ const remappedEntry = AGENT_REGISTRY[remappedName];
59
+ if (remappedEntry) {
60
+ const remappedGroup = groups[remappedEntry.group];
61
+ if (remappedGroup) {
62
+ return {
63
+ primary: remappedGroup.primary,
64
+ fallbacks: remappedGroup.fallbacks,
65
+ source: "group",
66
+ };
67
+ }
68
+ }
69
+ }
70
+
39
71
  const entry = AGENT_REGISTRY[agentName];
40
72
  if (entry) {
41
73
  const group = groups[entry.group];
@@ -48,17 +80,12 @@ export function resolveModelForAgent(
48
80
  }
49
81
  }
50
82
 
51
- // Tier 3: no assignment
52
83
  return null;
53
84
  }
54
85
 
55
86
  /**
56
87
  * Resolve model for a group directly (used by review pipeline for
57
88
  * internal ReviewAgent objects that are not in AGENT_REGISTRY).
58
- *
59
- * Used by:
60
- * - Review pipeline for the 19 universal+specialized agents → "reviewers"
61
- * - Review pipeline for red-team + product-thinker → "red-team"
62
89
  */
63
90
  export function resolveModelForGroup(
64
91
  groupId: GroupId,
@@ -72,3 +99,5 @@ export function resolveModelForGroup(
72
99
  source: "group",
73
100
  };
74
101
  }
102
+
103
+ export { DEPRECATED_AGENT_REMAP };