@ouro.bot/cli 0.1.0-alpha.553 → 0.1.0-alpha.555

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.
@@ -1,54 +0,0 @@
1
- # diagnose-bootstrap-drift
2
-
3
- Bootstrap drift fires when the agent's declared intent (`agent.json`) and its observed runtime binding (`state/providers.json`) disagree on provider or model.
4
-
5
- ## Inputs from the finding inventory
6
-
7
- The user message includes a `driftFindings` JSON block when drift was detected during boot. Each entry is a `DriftFinding` from `src/heart/daemon/drift-detection.ts`:
8
-
9
- ```ts
10
- interface DriftFinding {
11
- agent: string
12
- lane: "outward" | "inner" // outward = human-facing, inner = agent-to-agent
13
- intentProvider: string // what agent.json declares
14
- intentModel: string // what agent.json declares
15
- observedProvider: string // what state/providers.json records
16
- observedModel: string // what state/providers.json records
17
- reason: "provider-model-changed"
18
- repairCommand: string // copy-pasteable `ouro use ...` invocation
19
- }
20
- ```
21
-
22
- A finding fires when `intentProvider !== observedProvider` OR `intentModel !== observedModel`. `state/providers.json` missing entirely is treated as "no observation, nothing to drift against" (initialization in flight, not drift) and emits no finding.
23
-
24
- ## Diagnosis
25
-
26
- Each `DriftFinding` indicates per-lane disagreement. Compare the intent and observed fields to characterize the situation:
27
-
28
- | Pattern | What it means | Proposed action |
29
- |---|---|---|
30
- | `intentProvider !== observedProvider` | Different providers on the same lane | `provider-use` pinning the intent provider+model |
31
- | `intentProvider === observedProvider` AND `intentModel !== observedModel` | Same provider, different model | `provider-use` pinning the intent model |
32
- | Either side carries an unexpected/legacy value | Likely stale state from a pre-rename bootstrap | `provider-use` with `--force` to rewrite the binding |
33
-
34
- The `repairCommand` field on each finding already contains the canonical `ouro use --agent X --lane Y --provider Z --model M` invocation that resolves the drift. Surface that command in the proposal; the operator runs it after confirmation in `interactive-repair.ts`.
35
-
36
- ## Proposed action shape
37
-
38
- ```json
39
- {
40
- "kind": "provider-use",
41
- "agent": "slugger",
42
- "lane": "outward",
43
- "provider": "anthropic",
44
- "model": "claude-opus-4-7",
45
- "reason": "drift on outward lane: agent.json declares anthropic/claude-opus-4-7 but state/providers.json recorded openai-codex/claude-sonnet-4.6"
46
- }
47
- ```
48
-
49
- The `kind: "provider-use"` action is one of the seven typed `RepairAction` variants in `src/heart/daemon/readiness-repair.ts`; the parser in `agentic-repair.ts:parseRepairProposals` validates it.
50
-
51
- ## When NOT to fire
52
-
53
- - `driftFindings` is empty / not present in the user message — nothing to diagnose.
54
- - A finding's `intentProvider` or `intentModel` is empty / missing — the intent itself is malformed; surface in `notes` rather than proposing an action.
@@ -1,146 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.detectProviderBindingDrift = detectProviderBindingDrift;
37
- exports.loadDriftInputsForAgent = loadDriftInputsForAgent;
38
- const fs = __importStar(require("fs"));
39
- const path = __importStar(require("path"));
40
- const provider_state_1 = require("../provider-state");
41
- /**
42
- * Pull the per-lane intent out of an agent.json view, preferring the new
43
- * `outward`/`inner` keys over the legacy `humanFacing`/`agentFacing` keys
44
- * when both are present. Returns `null` if neither set carries a usable
45
- * binding for this lane (the comparator treats that as "no intent to
46
- * compare against" and emits no drift for that lane).
47
- */
48
- function resolveLaneIntent(agentJson, lane) {
49
- const newKey = lane === "outward" ? agentJson.outward : agentJson.inner;
50
- if (newKey && typeof newKey.provider === "string" && typeof newKey.model === "string") {
51
- return { provider: newKey.provider, model: newKey.model };
52
- }
53
- const legacy = lane === "outward" ? agentJson.humanFacing : agentJson.agentFacing;
54
- if (legacy && typeof legacy.provider === "string" && typeof legacy.model === "string") {
55
- return { provider: legacy.provider, model: legacy.model };
56
- }
57
- return null;
58
- }
59
- function buildRepairCommand(agentName, lane, provider, model) {
60
- return `ouro use --agent ${agentName} --lane ${lane} --provider ${provider} --model ${model}`;
61
- }
62
- /**
63
- * Pure intent-vs-observed comparator. Emits one `DriftFinding` per lane
64
- * whose intent (agent.json) does not match its observation
65
- * (state/providers.json).
66
- *
67
- * Returns `[]` when:
68
- * - `providerState === null` (fresh install, nothing to drift against), OR
69
- * - both lanes match, OR
70
- * - a lane has no intent in agent.json AND no observation in providerState
71
- * (deferred to other layers — drift detection is silent on that lane).
72
- */
73
- function detectProviderBindingDrift(input) {
74
- if (input.providerState === null) {
75
- return [];
76
- }
77
- const findings = [];
78
- const lanes = ["outward", "inner"];
79
- for (const lane of lanes) {
80
- const intent = resolveLaneIntent(input.agentJson, lane);
81
- if (!intent)
82
- continue;
83
- const observed = input.providerState.lanes[lane];
84
- if (intent.provider === observed.provider && intent.model === observed.model) {
85
- continue;
86
- }
87
- findings.push({
88
- agent: input.agentName,
89
- lane,
90
- intentProvider: intent.provider,
91
- intentModel: intent.model,
92
- observedProvider: observed.provider,
93
- observedModel: observed.model,
94
- reason: "provider-model-changed",
95
- repairCommand: buildRepairCommand(input.agentName, lane, intent.provider, intent.model),
96
- });
97
- }
98
- return findings;
99
- }
100
- function agentRootFor(bundlesRoot, agentName) {
101
- return path.join(bundlesRoot, `${agentName}.ouro`);
102
- }
103
- function readAgentJson(agentJsonPath) {
104
- let raw;
105
- try {
106
- raw = fs.readFileSync(agentJsonPath, "utf-8");
107
- }
108
- catch {
109
- throw new Error(`agent.json not found at ${agentJsonPath}`);
110
- }
111
- let parsed;
112
- try {
113
- parsed = JSON.parse(raw);
114
- }
115
- catch (error) {
116
- throw new Error(`agent.json at ${agentJsonPath} contains invalid JSON: ${String(error)}`);
117
- }
118
- // The drift loader is intentionally permissive: it parses the file as a
119
- // typed `AgentConfig` view but does not validate every field. The
120
- // comparator (Unit 1) is the one that decides which intent fields are
121
- // usable; non-conforming bindings just silently skip drift detection
122
- // on that lane. Stricter validation belongs to `loadAgentConfig` in
123
- // identity.ts (which also has side effects we don't want here).
124
- return parsed;
125
- }
126
- /**
127
- * Loader for the drift comparator. Reads the per-agent `agent.json` and
128
- * `state/providers.json` off disk, returning typed inputs ready to feed
129
- * into `detectProviderBindingDrift`.
130
- *
131
- * - Throws when `agent.json` is missing or unparseable. The caller decides
132
- * whether to swallow (drift detection has no opinion on a broken
133
- * `agent.json` — that's the existing `agent-config-check` flow's job).
134
- * - Returns `providerState: null` when `state/providers.json` is missing
135
- * (fresh install) or invalid (the comparator interprets `null` as "no
136
- * observation, nothing to drift against").
137
- * - Never writes to disk.
138
- */
139
- function loadDriftInputsForAgent(bundlesRoot, agentName) {
140
- const agentRoot = agentRootFor(bundlesRoot, agentName);
141
- const agentJsonPath = path.join(agentRoot, "agent.json");
142
- const agentJson = readAgentJson(agentJsonPath);
143
- const stateResult = (0, provider_state_1.readProviderState)(agentRoot);
144
- const providerState = stateResult.ok ? stateResult.state : null;
145
- return { agentJson, providerState };
146
- }
@@ -1,216 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.validateProviderState = validateProviderState;
37
- exports.getProviderStatePath = getProviderStatePath;
38
- exports.readProviderState = readProviderState;
39
- exports.writeProviderState = writeProviderState;
40
- exports.bootstrapProviderStateFromAgentConfig = bootstrapProviderStateFromAgentConfig;
41
- const fs = __importStar(require("fs"));
42
- const path = __importStar(require("path"));
43
- const runtime_1 = require("../nerves/runtime");
44
- const identity_1 = require("./identity");
45
- const LANES = ["outward", "inner"];
46
- const VALID_SOURCES = new Set(["bootstrap", "local"]);
47
- const VALID_READINESS = new Set(["ready", "failed", "stale", "unknown"]);
48
- function isProvider(value) {
49
- return typeof value === "string" && Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
50
- }
51
- function isNonEmptyString(value) {
52
- return typeof value === "string" && value.trim().length > 0;
53
- }
54
- function validateBinding(value, label) {
55
- if (!value || typeof value !== "object" || Array.isArray(value)) {
56
- throw new Error(`${label} must be an object`);
57
- }
58
- const record = value;
59
- if (!isProvider(record.provider))
60
- throw new Error(`${label}.provider must be a valid provider`);
61
- if (!isNonEmptyString(record.model))
62
- throw new Error(`${label}.model must be a non-empty string`);
63
- if (!VALID_SOURCES.has(record.source)) {
64
- throw new Error(`${label}.source must be bootstrap or local`);
65
- }
66
- if (!isNonEmptyString(record.updatedAt))
67
- throw new Error(`${label}.updatedAt must be a non-empty string`);
68
- return {
69
- provider: record.provider,
70
- model: record.model,
71
- source: record.source,
72
- updatedAt: record.updatedAt,
73
- };
74
- }
75
- function validateReadiness(value, label) {
76
- if (!value || typeof value !== "object" || Array.isArray(value)) {
77
- throw new Error(`${label} must be an object`);
78
- }
79
- const record = value;
80
- if (!VALID_READINESS.has(record.status)) {
81
- throw new Error(`${label}.status must be ready, failed, stale, or unknown`);
82
- }
83
- if (!isProvider(record.provider))
84
- throw new Error(`${label}.provider must be a valid provider`);
85
- if (!isNonEmptyString(record.model))
86
- throw new Error(`${label}.model must be a non-empty string`);
87
- if (record.checkedAt !== undefined && typeof record.checkedAt !== "string") {
88
- throw new Error(`${label}.checkedAt must be a string when present`);
89
- }
90
- if (record.credentialRevision !== undefined && typeof record.credentialRevision !== "string") {
91
- throw new Error(`${label}.credentialRevision must be a string when present`);
92
- }
93
- if (record.error !== undefined && typeof record.error !== "string") {
94
- throw new Error(`${label}.error must be a string when present`);
95
- }
96
- if (record.attempts !== undefined && typeof record.attempts !== "number") {
97
- throw new Error(`${label}.attempts must be a number when present`);
98
- }
99
- return {
100
- status: record.status,
101
- provider: record.provider,
102
- model: record.model,
103
- ...(record.checkedAt !== undefined ? { checkedAt: record.checkedAt } : {}),
104
- ...(record.credentialRevision !== undefined ? { credentialRevision: record.credentialRevision } : {}),
105
- ...(record.error !== undefined ? { error: record.error } : {}),
106
- ...(record.attempts !== undefined ? { attempts: record.attempts } : {}),
107
- };
108
- }
109
- function validateProviderState(value) {
110
- if (!value || typeof value !== "object" || Array.isArray(value)) {
111
- throw new Error("provider state must be an object");
112
- }
113
- const record = value;
114
- if (record.schemaVersion !== 1)
115
- throw new Error("schemaVersion must be 1");
116
- if (!isNonEmptyString(record.machineId))
117
- throw new Error("machineId must be a non-empty string");
118
- if (!isNonEmptyString(record.updatedAt))
119
- throw new Error("updatedAt must be a non-empty string");
120
- if (!record.lanes || typeof record.lanes !== "object" || Array.isArray(record.lanes)) {
121
- throw new Error("lanes must be an object");
122
- }
123
- const rawLanes = record.lanes;
124
- const lanes = {
125
- outward: validateBinding(rawLanes.outward, "outward"),
126
- inner: validateBinding(rawLanes.inner, "inner"),
127
- };
128
- if (!record.readiness || typeof record.readiness !== "object" || Array.isArray(record.readiness)) {
129
- throw new Error("readiness must be an object");
130
- }
131
- const rawReadiness = record.readiness;
132
- const readiness = {};
133
- for (const lane of LANES) {
134
- if (rawReadiness[lane] !== undefined) {
135
- readiness[lane] = validateReadiness(rawReadiness[lane], `${lane}.readiness`);
136
- }
137
- }
138
- return {
139
- schemaVersion: 1,
140
- machineId: record.machineId,
141
- updatedAt: record.updatedAt,
142
- lanes,
143
- readiness,
144
- };
145
- }
146
- function getProviderStatePath(agentRoot) {
147
- return path.join(agentRoot, "state", "providers.json");
148
- }
149
- function readProviderState(agentRoot) {
150
- const statePath = getProviderStatePath(agentRoot);
151
- let raw;
152
- try {
153
- if (!fs.existsSync(statePath)) {
154
- return { ok: false, reason: "missing", statePath, error: "provider state not found" };
155
- }
156
- }
157
- catch (error) {
158
- return { ok: false, reason: "invalid", statePath, error: String(error) };
159
- }
160
- try {
161
- raw = fs.readFileSync(statePath, "utf-8");
162
- }
163
- catch (error) {
164
- const code = error.code;
165
- if (code === "ENOENT") {
166
- return { ok: false, reason: "missing", statePath, error: "provider state not found" };
167
- }
168
- return { ok: false, reason: "invalid", statePath, error: String(error) };
169
- }
170
- try {
171
- const state = validateProviderState(JSON.parse(raw));
172
- (0, runtime_1.emitNervesEvent)({
173
- component: "config/identity",
174
- event: "config.provider_state_read",
175
- message: "read provider state",
176
- meta: { statePath, machineId: state.machineId },
177
- });
178
- return { ok: true, statePath, state };
179
- }
180
- catch (error) {
181
- return { ok: false, reason: "invalid", statePath, error: String(error) };
182
- }
183
- }
184
- function writeProviderState(agentRoot, state) {
185
- const statePath = getProviderStatePath(agentRoot);
186
- const validated = validateProviderState(state);
187
- fs.mkdirSync(path.dirname(statePath), { recursive: true });
188
- fs.writeFileSync(statePath, `${JSON.stringify(validated, null, 2)}\n`, "utf-8");
189
- (0, runtime_1.emitNervesEvent)({
190
- component: "config/identity",
191
- event: "config.provider_state_written",
192
- message: "wrote provider state",
193
- meta: { statePath, machineId: validated.machineId },
194
- });
195
- }
196
- function binding(provider, model, updatedAt) {
197
- return {
198
- provider,
199
- model,
200
- source: "bootstrap",
201
- updatedAt,
202
- };
203
- }
204
- function bootstrapProviderStateFromAgentConfig(input) {
205
- const updatedAt = input.now.toISOString();
206
- return {
207
- schemaVersion: 1,
208
- machineId: input.machineId,
209
- updatedAt,
210
- lanes: {
211
- outward: binding(input.agentConfig.humanFacing.provider, input.agentConfig.humanFacing.model, updatedAt),
212
- inner: binding(input.agentConfig.agentFacing.provider, input.agentConfig.agentFacing.model, updatedAt),
213
- },
214
- readiness: {},
215
- };
216
- }