@kodrunhq/opencode-autopilot 1.16.0 → 1.17.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/bin/inspect.ts +2 -2
- package/package.json +1 -1
- package/src/config/index.ts +29 -0
- package/src/config/migrations.ts +196 -0
- package/src/config/v7.ts +45 -0
- package/src/config.ts +3 -3
- package/src/health/checks.ts +97 -0
- package/src/health/types.ts +1 -1
- package/src/index.ts +25 -2
- package/src/kernel/transaction.ts +48 -0
- package/src/kernel/types.ts +1 -2
- package/src/logging/domains.ts +39 -0
- package/src/logging/forensic-writer.ts +177 -0
- package/src/logging/index.ts +4 -0
- package/src/logging/logger.ts +44 -0
- package/src/logging/performance.ts +59 -0
- package/src/logging/rotation.ts +261 -0
- package/src/logging/types.ts +33 -0
- package/src/memory/capture-utils.ts +149 -0
- package/src/memory/capture.ts +16 -197
- package/src/memory/decay.ts +11 -2
- package/src/memory/injector.ts +4 -1
- package/src/memory/lessons.ts +85 -0
- package/src/memory/observations.ts +177 -0
- package/src/memory/preferences.ts +718 -0
- package/src/memory/projects.ts +83 -0
- package/src/memory/repository.ts +46 -1001
- package/src/memory/retrieval.ts +5 -1
- package/src/observability/context-display.ts +8 -0
- package/src/observability/event-handlers.ts +44 -6
- package/src/observability/forensic-log.ts +10 -2
- package/src/observability/forensic-schemas.ts +9 -1
- package/src/observability/log-reader.ts +20 -1
- package/src/orchestrator/error-context.ts +24 -0
- package/src/orchestrator/handlers/build-utils.ts +118 -0
- package/src/orchestrator/handlers/build.ts +13 -148
- package/src/orchestrator/handlers/retrospective.ts +0 -1
- package/src/orchestrator/lesson-memory.ts +7 -2
- package/src/orchestrator/orchestration-logger.ts +46 -31
- package/src/orchestrator/progress.ts +63 -0
- package/src/review/memory.ts +11 -3
- package/src/review/parse-findings.ts +116 -0
- package/src/review/pipeline.ts +3 -107
- package/src/review/selection.ts +38 -4
- package/src/scoring/time-provider.ts +23 -0
- package/src/tools/doctor.ts +2 -2
- package/src/tools/logs.ts +32 -6
- package/src/tools/orchestrate.ts +11 -9
- package/src/tools/replay.ts +42 -0
- package/src/tools/review.ts +8 -2
- package/src/tools/summary.ts +43 -0
- package/src/utils/random.ts +33 -0
- package/src/ux/session-summary.ts +56 -0
package/bin/inspect.ts
CHANGED
|
@@ -249,7 +249,7 @@ export async function inspectCliCore(
|
|
|
249
249
|
);
|
|
250
250
|
}
|
|
251
251
|
case "project": {
|
|
252
|
-
const details = getProjectDetails(parsed.projectRef
|
|
252
|
+
const details = getProjectDetails(parsed.projectRef ?? "", dbInput);
|
|
253
253
|
if (details === null) {
|
|
254
254
|
return makeError(`Project not found: ${parsed.projectRef}`, parsed.json);
|
|
255
255
|
}
|
|
@@ -260,7 +260,7 @@ export async function inspectCliCore(
|
|
|
260
260
|
);
|
|
261
261
|
}
|
|
262
262
|
case "paths": {
|
|
263
|
-
const details = getProjectDetails(parsed.projectRef
|
|
263
|
+
const details = getProjectDetails(parsed.projectRef ?? "", dbInput);
|
|
264
264
|
if (details === null) {
|
|
265
265
|
return makeError(`Project not found: ${parsed.projectRef}`, parsed.json);
|
|
266
266
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodrunhq/opencode-autopilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.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": [
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CONFIG_PATH,
|
|
3
|
+
confidenceConfigSchema,
|
|
4
|
+
confidenceDefaults,
|
|
5
|
+
createDefaultConfig,
|
|
6
|
+
isFirstLoad,
|
|
7
|
+
loadConfig,
|
|
8
|
+
memoryConfigSchema,
|
|
9
|
+
memoryDefaults,
|
|
10
|
+
orchestratorConfigSchema,
|
|
11
|
+
orchestratorDefaults,
|
|
12
|
+
type PluginConfig,
|
|
13
|
+
pluginConfigSchema,
|
|
14
|
+
saveConfig,
|
|
15
|
+
} from "../config";
|
|
16
|
+
// Re-export migration schemas and functions for backward compatibility
|
|
17
|
+
export {
|
|
18
|
+
migrateV1toV2,
|
|
19
|
+
migrateV2toV3,
|
|
20
|
+
migrateV3toV4,
|
|
21
|
+
migrateV4toV5,
|
|
22
|
+
migrateV5toV6,
|
|
23
|
+
pluginConfigSchemaV1,
|
|
24
|
+
pluginConfigSchemaV2,
|
|
25
|
+
pluginConfigSchemaV3,
|
|
26
|
+
pluginConfigSchemaV4,
|
|
27
|
+
pluginConfigSchemaV5,
|
|
28
|
+
} from "./migrations";
|
|
29
|
+
export { migrateV6toV7, type PluginConfigV7, v7ConfigDefaults } from "./v7";
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
confidenceConfigSchema,
|
|
4
|
+
confidenceDefaults,
|
|
5
|
+
memoryConfigSchema,
|
|
6
|
+
memoryDefaults,
|
|
7
|
+
orchestratorConfigSchema,
|
|
8
|
+
orchestratorDefaults,
|
|
9
|
+
} from "../config";
|
|
10
|
+
import {
|
|
11
|
+
fallbackConfigSchema,
|
|
12
|
+
fallbackDefaults,
|
|
13
|
+
testModeDefaults,
|
|
14
|
+
} from "../orchestrator/fallback/fallback-config";
|
|
15
|
+
import { AGENT_REGISTRY, ALL_GROUP_IDS } from "../registry/model-groups";
|
|
16
|
+
|
|
17
|
+
export const pluginConfigSchemaV1 = z.object({
|
|
18
|
+
version: z.literal(1),
|
|
19
|
+
configured: z.boolean(),
|
|
20
|
+
models: z.record(z.string(), z.string()),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type PluginConfigV1 = z.infer<typeof pluginConfigSchemaV1>;
|
|
24
|
+
|
|
25
|
+
export const pluginConfigSchemaV2 = z.object({
|
|
26
|
+
version: z.literal(2),
|
|
27
|
+
configured: z.boolean(),
|
|
28
|
+
models: z.record(z.string(), z.string()),
|
|
29
|
+
orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
|
|
30
|
+
confidence: confidenceConfigSchema.default(confidenceDefaults),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type PluginConfigV2 = z.infer<typeof pluginConfigSchemaV2>;
|
|
34
|
+
|
|
35
|
+
export const pluginConfigSchemaV3 = z.object({
|
|
36
|
+
version: z.literal(3),
|
|
37
|
+
configured: z.boolean(),
|
|
38
|
+
models: z.record(z.string(), z.string()),
|
|
39
|
+
orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
|
|
40
|
+
confidence: confidenceConfigSchema.default(confidenceDefaults),
|
|
41
|
+
fallback: fallbackConfigSchema.default(fallbackDefaults),
|
|
42
|
+
fallback_models: z.union([z.string(), z.array(z.string())]).optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type PluginConfigV3 = z.infer<typeof pluginConfigSchemaV3>;
|
|
46
|
+
|
|
47
|
+
const groupModelAssignmentSchema = z.object({
|
|
48
|
+
primary: z.string().min(1),
|
|
49
|
+
fallbacks: z.array(z.string().min(1)).default([]),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const agentOverrideSchema = z.object({
|
|
53
|
+
primary: z.string().min(1),
|
|
54
|
+
fallbacks: z.array(z.string().min(1)).optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const pluginConfigSchemaV4 = z
|
|
58
|
+
.object({
|
|
59
|
+
version: z.literal(4),
|
|
60
|
+
configured: z.boolean(),
|
|
61
|
+
groups: z.record(z.string(), groupModelAssignmentSchema).default({}),
|
|
62
|
+
overrides: z.record(z.string(), agentOverrideSchema).default({}),
|
|
63
|
+
orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
|
|
64
|
+
confidence: confidenceConfigSchema.default(confidenceDefaults),
|
|
65
|
+
fallback: fallbackConfigSchema.default(fallbackDefaults),
|
|
66
|
+
})
|
|
67
|
+
.superRefine((config, ctx) => {
|
|
68
|
+
for (const groupId of Object.keys(config.groups)) {
|
|
69
|
+
if (!ALL_GROUP_IDS.includes(groupId as (typeof ALL_GROUP_IDS)[number])) {
|
|
70
|
+
ctx.addIssue({
|
|
71
|
+
code: z.ZodIssueCode.custom,
|
|
72
|
+
path: ["groups", groupId],
|
|
73
|
+
message: `Unknown group id "${groupId}". Expected one of: ${ALL_GROUP_IDS.join(", ")}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export type PluginConfigV4 = z.infer<typeof pluginConfigSchemaV4>;
|
|
80
|
+
|
|
81
|
+
export const pluginConfigSchemaV5 = z
|
|
82
|
+
.object({
|
|
83
|
+
version: z.literal(5),
|
|
84
|
+
configured: z.boolean(),
|
|
85
|
+
groups: z.record(z.string(), groupModelAssignmentSchema).default({}),
|
|
86
|
+
overrides: z.record(z.string(), agentOverrideSchema).default({}),
|
|
87
|
+
orchestrator: orchestratorConfigSchema.default(orchestratorDefaults),
|
|
88
|
+
confidence: confidenceConfigSchema.default(confidenceDefaults),
|
|
89
|
+
fallback: fallbackConfigSchema.default(fallbackDefaults),
|
|
90
|
+
memory: memoryConfigSchema.default(memoryDefaults),
|
|
91
|
+
})
|
|
92
|
+
.superRefine((config, ctx) => {
|
|
93
|
+
for (const groupId of Object.keys(config.groups)) {
|
|
94
|
+
if (!ALL_GROUP_IDS.includes(groupId as (typeof ALL_GROUP_IDS)[number])) {
|
|
95
|
+
ctx.addIssue({
|
|
96
|
+
code: z.ZodIssueCode.custom,
|
|
97
|
+
path: ["groups", groupId],
|
|
98
|
+
message: `Unknown group id "${groupId}". Expected one of: ${ALL_GROUP_IDS.join(", ")}`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export type PluginConfigV5 = z.infer<typeof pluginConfigSchemaV5>;
|
|
105
|
+
|
|
106
|
+
export function migrateV1toV2(v1Config: PluginConfigV1): PluginConfigV2 {
|
|
107
|
+
return {
|
|
108
|
+
version: 2 as const,
|
|
109
|
+
configured: v1Config.configured,
|
|
110
|
+
models: v1Config.models,
|
|
111
|
+
orchestrator: orchestratorDefaults,
|
|
112
|
+
confidence: confidenceDefaults,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function migrateV2toV3(v2Config: PluginConfigV2): PluginConfigV3 {
|
|
117
|
+
return {
|
|
118
|
+
version: 3 as const,
|
|
119
|
+
configured: v2Config.configured,
|
|
120
|
+
models: v2Config.models,
|
|
121
|
+
orchestrator: v2Config.orchestrator,
|
|
122
|
+
confidence: v2Config.confidence,
|
|
123
|
+
fallback: fallbackDefaults,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function migrateV3toV4(v3Config: PluginConfigV3): PluginConfigV4 {
|
|
128
|
+
const groups: Record<string, { primary: string; fallbacks: string[] }> = {};
|
|
129
|
+
const overrides: Record<string, { primary: string }> = {};
|
|
130
|
+
|
|
131
|
+
for (const [agentName, modelId] of Object.entries(v3Config.models)) {
|
|
132
|
+
const entry = AGENT_REGISTRY[agentName];
|
|
133
|
+
if (!entry) {
|
|
134
|
+
overrides[agentName] = { primary: modelId };
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const groupId = entry.group;
|
|
139
|
+
if (!groups[groupId]) {
|
|
140
|
+
groups[groupId] = { primary: modelId, fallbacks: [] };
|
|
141
|
+
} else if (groups[groupId].primary !== modelId) {
|
|
142
|
+
overrides[agentName] = { primary: modelId };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const globalFallbacks = v3Config.fallback_models
|
|
147
|
+
? typeof v3Config.fallback_models === "string"
|
|
148
|
+
? [v3Config.fallback_models]
|
|
149
|
+
: [...v3Config.fallback_models]
|
|
150
|
+
: [];
|
|
151
|
+
|
|
152
|
+
for (const group of Object.values(groups)) {
|
|
153
|
+
if (group.fallbacks.length === 0 && globalFallbacks.length > 0) {
|
|
154
|
+
group.fallbacks = [...globalFallbacks];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
version: 4 as const,
|
|
160
|
+
configured: v3Config.configured,
|
|
161
|
+
groups,
|
|
162
|
+
overrides,
|
|
163
|
+
orchestrator: v3Config.orchestrator,
|
|
164
|
+
confidence: v3Config.confidence,
|
|
165
|
+
fallback: v3Config.fallback,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function migrateV4toV5(v4Config: PluginConfigV4): PluginConfigV5 {
|
|
170
|
+
return {
|
|
171
|
+
version: 5 as const,
|
|
172
|
+
configured: v4Config.configured,
|
|
173
|
+
groups: v4Config.groups,
|
|
174
|
+
overrides: v4Config.overrides,
|
|
175
|
+
orchestrator: v4Config.orchestrator,
|
|
176
|
+
confidence: v4Config.confidence,
|
|
177
|
+
fallback: v4Config.fallback,
|
|
178
|
+
memory: memoryDefaults,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function migrateV5toV6(
|
|
183
|
+
v5Config: PluginConfigV5,
|
|
184
|
+
_fallbackDefaultsV6: typeof fallbackDefaults,
|
|
185
|
+
) {
|
|
186
|
+
return {
|
|
187
|
+
version: 6 as const,
|
|
188
|
+
configured: v5Config.configured,
|
|
189
|
+
groups: v5Config.groups,
|
|
190
|
+
overrides: v5Config.overrides,
|
|
191
|
+
orchestrator: v5Config.orchestrator,
|
|
192
|
+
confidence: v5Config.confidence,
|
|
193
|
+
fallback: { ...v5Config.fallback, testMode: testModeDefaults },
|
|
194
|
+
memory: v5Config.memory,
|
|
195
|
+
};
|
|
196
|
+
}
|
package/src/config/v7.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PluginConfig } from "../config";
|
|
2
|
+
|
|
3
|
+
export type PluginConfigV7 = Omit<PluginConfig, "version"> & {
|
|
4
|
+
readonly version: 7;
|
|
5
|
+
readonly background?: {
|
|
6
|
+
readonly enabled: boolean;
|
|
7
|
+
readonly maxConcurrent: number;
|
|
8
|
+
readonly defaultTimeout: number;
|
|
9
|
+
};
|
|
10
|
+
readonly autonomy?: {
|
|
11
|
+
readonly enabled: boolean;
|
|
12
|
+
readonly verification: "strict" | "normal" | "lenient";
|
|
13
|
+
readonly maxIterations: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function migrateV6toV7(v6Config: PluginConfig): PluginConfigV7 {
|
|
18
|
+
return {
|
|
19
|
+
...v6Config,
|
|
20
|
+
version: 7,
|
|
21
|
+
background: {
|
|
22
|
+
enabled: true,
|
|
23
|
+
maxConcurrent: 5,
|
|
24
|
+
defaultTimeout: 300000,
|
|
25
|
+
},
|
|
26
|
+
autonomy: {
|
|
27
|
+
enabled: false,
|
|
28
|
+
verification: "normal",
|
|
29
|
+
maxIterations: 10,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const v7ConfigDefaults = {
|
|
35
|
+
background: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
maxConcurrent: 5,
|
|
38
|
+
defaultTimeout: 300000,
|
|
39
|
+
},
|
|
40
|
+
autonomy: {
|
|
41
|
+
enabled: false,
|
|
42
|
+
verification: "normal",
|
|
43
|
+
maxIterations: 10,
|
|
44
|
+
},
|
|
45
|
+
} as const;
|
package/src/config.ts
CHANGED
|
@@ -53,8 +53,8 @@ export const confidenceConfigSchema = z.object({
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
// Pre-compute full defaults for nested schema defaults
|
|
56
|
-
const orchestratorDefaults = orchestratorConfigSchema.parse({});
|
|
57
|
-
const confidenceDefaults = confidenceConfigSchema.parse({});
|
|
56
|
+
export const orchestratorDefaults = orchestratorConfigSchema.parse({});
|
|
57
|
+
export const confidenceDefaults = confidenceConfigSchema.parse({});
|
|
58
58
|
|
|
59
59
|
// --- V2 schema (internal, for migration) ---
|
|
60
60
|
|
|
@@ -90,7 +90,7 @@ export const memoryConfigSchema = z.object({
|
|
|
90
90
|
decayHalfLifeDays: z.number().min(7).max(365).default(90),
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
const memoryDefaults = memoryConfigSchema.parse({});
|
|
93
|
+
export const memoryDefaults = memoryConfigSchema.parse({});
|
|
94
94
|
|
|
95
95
|
// --- V4 sub-schemas ---
|
|
96
96
|
|
package/src/health/checks.ts
CHANGED
|
@@ -44,6 +44,103 @@ export async function configHealthCheck(configPath?: string): Promise<HealthResu
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const LATEST_CONFIG_VERSION = 6;
|
|
48
|
+
|
|
49
|
+
export async function configVersionCheck(configPath?: string): Promise<HealthResult> {
|
|
50
|
+
try {
|
|
51
|
+
const config = await loadConfig(configPath);
|
|
52
|
+
if (config === null) {
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
name: "config-version",
|
|
55
|
+
status: "fail" as const,
|
|
56
|
+
message: "Config file not found",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (config.version < LATEST_CONFIG_VERSION) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
name: "config-version",
|
|
62
|
+
status: "warn" as const,
|
|
63
|
+
message: `Config v${config.version} is outdated (latest: v${LATEST_CONFIG_VERSION}). Auto-migration will upgrade on next load.`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
name: "config-version",
|
|
68
|
+
status: "pass" as const,
|
|
69
|
+
message: `Config is on latest version (v${config.version})`,
|
|
70
|
+
});
|
|
71
|
+
} catch (error: unknown) {
|
|
72
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
name: "config-version",
|
|
75
|
+
status: "fail" as const,
|
|
76
|
+
message: `Config version check failed: ${msg}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const REQUIRED_GROUPS: readonly string[] = Object.freeze([
|
|
82
|
+
"architects",
|
|
83
|
+
"challengers",
|
|
84
|
+
"builders",
|
|
85
|
+
"reviewers",
|
|
86
|
+
"red-team",
|
|
87
|
+
"researchers",
|
|
88
|
+
"communicators",
|
|
89
|
+
"utilities",
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
export async function configGroupsCheck(configPath?: string): Promise<HealthResult> {
|
|
93
|
+
try {
|
|
94
|
+
const config = await loadConfig(configPath);
|
|
95
|
+
if (config === null) {
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
name: "config-groups",
|
|
98
|
+
status: "fail" as const,
|
|
99
|
+
message: "Config file not found",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const assignedGroups = Object.keys(config.groups);
|
|
104
|
+
const missingGroups = REQUIRED_GROUPS.filter((g) => !assignedGroups.includes(g));
|
|
105
|
+
|
|
106
|
+
if (missingGroups.length > 0) {
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
name: "config-groups",
|
|
109
|
+
status: "warn" as const,
|
|
110
|
+
message: `Missing model assignments for groups: ${missingGroups.join(", ")}`,
|
|
111
|
+
details: Object.freeze(missingGroups),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const groupsWithoutPrimary = assignedGroups.filter((g) => {
|
|
116
|
+
const group = config.groups[g];
|
|
117
|
+
return !group?.primary;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (groupsWithoutPrimary.length > 0) {
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
name: "config-groups",
|
|
123
|
+
status: "warn" as const,
|
|
124
|
+
message: `Groups without primary model: ${groupsWithoutPrimary.join(", ")}`,
|
|
125
|
+
details: Object.freeze(groupsWithoutPrimary),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
name: "config-groups",
|
|
131
|
+
status: "pass" as const,
|
|
132
|
+
message: `All ${REQUIRED_GROUPS.length} required groups have primary models assigned`,
|
|
133
|
+
});
|
|
134
|
+
} catch (error: unknown) {
|
|
135
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
name: "config-groups",
|
|
138
|
+
status: "fail" as const,
|
|
139
|
+
message: `Config groups check failed: ${msg}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
47
144
|
/** Standard agent names, derived from the agents barrel export. */
|
|
48
145
|
const STANDARD_AGENT_NAMES: readonly string[] = Object.freeze([
|
|
49
146
|
"researcher",
|
package/src/health/types.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { isFirstLoad, loadConfig } from "./config";
|
|
|
4
4
|
import { runHealthChecks } from "./health/runner";
|
|
5
5
|
import { createAntiSlopHandler } from "./hooks/anti-slop";
|
|
6
6
|
import { installAssets } from "./installer";
|
|
7
|
+
import { getLogger, initLoggers } from "./logging/domains";
|
|
7
8
|
import {
|
|
8
9
|
createMemoryCaptureHandler,
|
|
9
10
|
createMemoryChatMessageHandler,
|
|
@@ -55,17 +56,36 @@ import { ocReview } from "./tools/review";
|
|
|
55
56
|
import { ocSessionStats } from "./tools/session-stats";
|
|
56
57
|
import { ocState } from "./tools/state";
|
|
57
58
|
import { ocStocktake } from "./tools/stocktake";
|
|
59
|
+
import { ocSummary } from "./tools/summary";
|
|
58
60
|
import { ocUpdateDocs } from "./tools/update-docs";
|
|
59
61
|
|
|
60
62
|
let openCodeConfig: Config | null = null;
|
|
61
63
|
|
|
64
|
+
let processHandlersRegistered = false;
|
|
65
|
+
function registerProcessHandlers() {
|
|
66
|
+
if (processHandlersRegistered) return;
|
|
67
|
+
processHandlersRegistered = true;
|
|
68
|
+
process.on("uncaughtException", (error) => {
|
|
69
|
+
getLogger("system").error("Uncaught exception", {
|
|
70
|
+
error: error instanceof Error ? error.stack : String(error),
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
process.on("unhandledRejection", (reason) => {
|
|
74
|
+
getLogger("system").error("Unhandled rejection", {
|
|
75
|
+
reason: reason instanceof Error ? reason.stack : String(reason),
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
const plugin: Plugin = async (input) => {
|
|
63
81
|
const client = input.client;
|
|
82
|
+
initLoggers(process.cwd());
|
|
83
|
+
registerProcessHandlers();
|
|
64
84
|
|
|
65
85
|
// Self-healing asset installation on every load
|
|
66
86
|
const installResult = await installAssets();
|
|
67
87
|
if (installResult.errors.length > 0) {
|
|
68
|
-
|
|
88
|
+
getLogger("system").warn("Asset installation errors", { errors: installResult.errors });
|
|
69
89
|
}
|
|
70
90
|
|
|
71
91
|
// Discover available providers/models in the background (non-blocking).
|
|
@@ -102,7 +122,9 @@ const plugin: Plugin = async (input) => {
|
|
|
102
122
|
|
|
103
123
|
// Retention pruning on load (non-blocking per D-14)
|
|
104
124
|
pruneOldLogs().catch((err) => {
|
|
105
|
-
|
|
125
|
+
getLogger("system").error("Log retention pruning failed", {
|
|
126
|
+
error: err instanceof Error ? err.stack : String(err),
|
|
127
|
+
});
|
|
106
128
|
});
|
|
107
129
|
|
|
108
130
|
// --- Fallback subsystem initialization ---
|
|
@@ -307,6 +329,7 @@ const plugin: Plugin = async (input) => {
|
|
|
307
329
|
oc_logs: ocLogs,
|
|
308
330
|
oc_session_stats: ocSessionStats,
|
|
309
331
|
oc_pipeline_report: ocPipelineReport,
|
|
332
|
+
oc_summary: ocSummary,
|
|
310
333
|
oc_mock_fallback: ocMockFallback,
|
|
311
334
|
oc_stocktake: ocStocktake,
|
|
312
335
|
oc_update_docs: ocUpdateDocs,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
export interface TransactionOptions {
|
|
4
|
+
maxRetries?: number;
|
|
5
|
+
backoffMs?: number;
|
|
6
|
+
useImmediate?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function withTransaction<T>(db: Database, fn: () => T, options: TransactionOptions = {}): T {
|
|
10
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
11
|
+
const backoffMs = options.backoffMs ?? 100;
|
|
12
|
+
const useImmediate = options.useImmediate ?? true;
|
|
13
|
+
|
|
14
|
+
let attempts = 0;
|
|
15
|
+
while (true) {
|
|
16
|
+
try {
|
|
17
|
+
if (useImmediate) {
|
|
18
|
+
db.run("BEGIN IMMEDIATE");
|
|
19
|
+
try {
|
|
20
|
+
const result = fn();
|
|
21
|
+
db.run("COMMIT");
|
|
22
|
+
return result;
|
|
23
|
+
} catch (innerError) {
|
|
24
|
+
db.run("ROLLBACK");
|
|
25
|
+
throw innerError;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const transaction = db.transaction(fn);
|
|
30
|
+
return transaction();
|
|
31
|
+
} catch (error: unknown) {
|
|
32
|
+
const e = error as Error;
|
|
33
|
+
const isBusyError =
|
|
34
|
+
e.message &&
|
|
35
|
+
(e.message.includes("database is locked") ||
|
|
36
|
+
e.message.includes("SQLITE_BUSY") ||
|
|
37
|
+
e.message.includes("database table is locked"));
|
|
38
|
+
|
|
39
|
+
if (isBusyError && attempts < maxRetries) {
|
|
40
|
+
attempts++;
|
|
41
|
+
const waitTime = backoffMs * attempts;
|
|
42
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, waitTime);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/kernel/types.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ForensicEvent } from "../observability/forensic-types";
|
|
2
|
-
import type { LessonMemory } from "../orchestrator/lesson-types";
|
|
3
2
|
import type { PipelineState } from "../orchestrator/types";
|
|
4
|
-
import type {
|
|
3
|
+
import type { ReviewState } from "../review/types";
|
|
5
4
|
|
|
6
5
|
export const KERNEL_STATE_CONFLICT_CODE = "E_STATE_CONFLICT";
|
|
7
6
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createForensicSink } from "./forensic-writer";
|
|
2
|
+
import { BaseLogger } from "./logger";
|
|
3
|
+
import type { LogEntry, Logger, LogMetadata, LogSink } from "./types";
|
|
4
|
+
|
|
5
|
+
export class MultiplexSink implements LogSink {
|
|
6
|
+
constructor(private readonly sinks: readonly LogSink[]) {}
|
|
7
|
+
|
|
8
|
+
write(entry: LogEntry): void {
|
|
9
|
+
for (const sink of this.sinks) {
|
|
10
|
+
sink.write(entry);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let rootLogger: Logger | null = null;
|
|
16
|
+
|
|
17
|
+
export function initLoggers(projectRoot: string, sinks?: readonly LogSink[]): void {
|
|
18
|
+
const resolvedSinks = sinks ?? [createForensicSink(projectRoot)];
|
|
19
|
+
rootLogger = new BaseLogger(new MultiplexSink(resolvedSinks), { domain: "system" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getLogger(domain: string, subsystem?: string): Logger {
|
|
23
|
+
if (!rootLogger) {
|
|
24
|
+
return new BaseLogger(
|
|
25
|
+
{
|
|
26
|
+
write(entry: LogEntry): void {
|
|
27
|
+
console.log(entry.level, entry.message);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
compactMetadata(domain, subsystem),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return rootLogger.child(compactMetadata(domain, subsystem));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function compactMetadata(domain: string, subsystem?: string): LogMetadata {
|
|
38
|
+
return subsystem ? { domain, subsystem } : { domain };
|
|
39
|
+
}
|