@ouro.bot/cli 0.1.0-alpha.695 → 0.1.0-alpha.697
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/changelog.json +15 -0
- package/dist/arc/flight-recorder.js +139 -21
- package/dist/arc/obligations.js +28 -6
- package/dist/arc/packets.js +48 -0
- package/dist/heart/autonomy-budget.js +434 -0
- package/dist/heart/core.js +11 -0
- package/dist/heart/daemon/cadence.js +110 -0
- package/dist/heart/daemon/cli-exec.js +20 -0
- package/dist/heart/daemon/cli-help.js +74 -0
- package/dist/heart/daemon/cli-parse.js +466 -0
- package/dist/heart/daemon/daemon.js +24 -3
- package/dist/heart/daemon/doctor.js +151 -0
- package/dist/heart/daemon/stale-bundle-prune.js +23 -6
- package/dist/heart/habits/habit-parser.js +4 -1
- package/dist/heart/habits/habit-scheduler.js +46 -38
- package/dist/heart/habits/habit-session.js +2 -3
- package/dist/heart/run-ledger.js +247 -0
- package/dist/heart/runtime-credentials.js +12 -3
- package/dist/heart/work-card.js +1 -11
- package/dist/mind/prompt-budget.js +380 -0
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-obligations.js +39 -0
- package/dist/repertoire/tools-rsvp.js +139 -0
- package/dist/rsvp/aisleplanner-client.js +354 -0
- package/dist/rsvp/cli.js +679 -0
- package/dist/rsvp/config.js +327 -0
- package/dist/rsvp/cutover.js +431 -0
- package/dist/rsvp/diagnostics.js +222 -0
- package/dist/rsvp/diff-renderer.js +159 -0
- package/dist/rsvp/habit-policy.js +85 -0
- package/dist/rsvp/habit-stage.js +93 -0
- package/dist/rsvp/incident-bundle.js +97 -0
- package/dist/rsvp/migration.js +204 -0
- package/dist/rsvp/outbound-state.js +254 -0
- package/dist/rsvp/query.js +118 -0
- package/dist/rsvp/replay.js +233 -0
- package/dist/rsvp/snapshot.js +232 -0
- package/dist/senses/bluebubbles/client.js +13 -7
- package/dist/senses/bluebubbles/index.js +227 -22
- package/dist/senses/bluebubbles/outbound-state.js +304 -0
- package/dist/senses/bluebubbles/replay.js +147 -0
- package/dist/senses/context-packet-ledger.js +245 -0
- package/dist/senses/context-packets.js +154 -0
- package/dist/senses/pipeline.js +92 -12
- package/dist/senses/private-runtime-worker.js +170 -31
- package/dist/senses/private-runtime.js +13 -1
- package/dist/senses/voice/twilio-phone.js +9 -5
- package/package.json +1 -1
|
@@ -0,0 +1,431 @@
|
|
|
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.checkRsvpCutover = checkRsvpCutover;
|
|
37
|
+
exports.runRsvpCutover = runRsvpCutover;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const os = __importStar(require("node:os"));
|
|
40
|
+
const path = __importStar(require("node:path"));
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
42
|
+
const machine_identity_1 = require("../heart/machine-identity");
|
|
43
|
+
const runtime_credentials_1 = require("../heart/runtime-credentials");
|
|
44
|
+
const runtime_1 = require("../nerves/runtime");
|
|
45
|
+
const LEGACY_LAUNCH_AGENT_LABEL = "com.arimendelow.rsvp-tracker";
|
|
46
|
+
const EMPTY_ROLLBACK = {
|
|
47
|
+
configBackupPath: "",
|
|
48
|
+
launchAgentBackupPath: "",
|
|
49
|
+
manifestPath: "",
|
|
50
|
+
};
|
|
51
|
+
function normalizeDeps(deps = {}) {
|
|
52
|
+
return {
|
|
53
|
+
existsSync: deps.existsSync ?? fs.existsSync,
|
|
54
|
+
readFileSync: deps.readFileSync ?? ((filePath) => fs.readFileSync(filePath, "utf-8")),
|
|
55
|
+
writeFileSync: deps.writeFileSync ?? ((filePath, contents) => fs.writeFileSync(filePath, contents, "utf-8")),
|
|
56
|
+
mkdirSync: deps.mkdirSync ?? ((dirPath) => fs.mkdirSync(dirPath, { recursive: true })),
|
|
57
|
+
renameSync: deps.renameSync ?? fs.renameSync,
|
|
58
|
+
copyFileSync: deps.copyFileSync ?? fs.copyFileSync,
|
|
59
|
+
now: deps.now ?? (() => new Date()),
|
|
60
|
+
spawnSync: deps.spawnSync ?? node_child_process_1.spawnSync,
|
|
61
|
+
platform: deps.platform ?? process.platform,
|
|
62
|
+
getUid: deps.getUid ?? (() => process.getuid()),
|
|
63
|
+
homeDir: deps.homeDir ?? os.homedir,
|
|
64
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
65
|
+
getLaunchAgentState: deps.getLaunchAgentState,
|
|
66
|
+
getLegacyProcessState: deps.getLegacyProcessState,
|
|
67
|
+
checkNativeBlueBubblesCredential: deps.checkNativeBlueBubblesCredential,
|
|
68
|
+
unloadLaunchAgent: deps.unloadLaunchAgent,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function asRecord(value) {
|
|
72
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
73
|
+
? value
|
|
74
|
+
: null;
|
|
75
|
+
}
|
|
76
|
+
function text(value) {
|
|
77
|
+
return typeof value === "string" ? value.trim() : "";
|
|
78
|
+
}
|
|
79
|
+
function safeStamp(now) {
|
|
80
|
+
return now.toISOString().replace(/[:.]/g, "-");
|
|
81
|
+
}
|
|
82
|
+
function rollbackDir(legacyRoot, now) {
|
|
83
|
+
return path.join(legacyRoot, ".ouro-cutover", safeStamp(now));
|
|
84
|
+
}
|
|
85
|
+
function buildBlueBubblesHealthUrl(baseUrl, password) {
|
|
86
|
+
const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
87
|
+
const url = new URL("api/v1/message/count", root);
|
|
88
|
+
url.searchParams.set("password", password);
|
|
89
|
+
return url.toString();
|
|
90
|
+
}
|
|
91
|
+
function readLegacyConfig(legacyRoot, deps) {
|
|
92
|
+
const configPath = path.join(legacyRoot, "config.json");
|
|
93
|
+
if (!deps.existsSync(configPath))
|
|
94
|
+
return { status: "missing" };
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(deps.readFileSync(configPath));
|
|
97
|
+
const config = asRecord(parsed);
|
|
98
|
+
return config ? { status: "ok", config } : { status: "malformed" };
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return { status: "malformed" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function legacyConfigSendInactive(legacyRoot, deps) {
|
|
105
|
+
const legacy = readLegacyConfig(legacyRoot, deps);
|
|
106
|
+
if (legacy.status === "missing")
|
|
107
|
+
return true;
|
|
108
|
+
if (legacy.status !== "ok")
|
|
109
|
+
return false;
|
|
110
|
+
const bluebubbles = asRecord(legacy.config?.bluebubbles);
|
|
111
|
+
if (!bluebubbles)
|
|
112
|
+
return true;
|
|
113
|
+
if (bluebubbles.disabled === true || bluebubbles.enabled === false || bluebubbles.send_enabled === false || bluebubbles.sendEnabled === false)
|
|
114
|
+
return true;
|
|
115
|
+
const hasLiveCoordinates = !!text(bluebubbles.server_url ?? bluebubbles.serverUrl)
|
|
116
|
+
&& !!text(bluebubbles.chat_guid ?? bluebubbles.chatGuid)
|
|
117
|
+
&& !!text(bluebubbles.secrets_path ?? bluebubbles.secretsPath);
|
|
118
|
+
return !hasLiveCoordinates;
|
|
119
|
+
}
|
|
120
|
+
async function defaultLaunchAgentState(input, deps) {
|
|
121
|
+
if (deps.platform !== "darwin") {
|
|
122
|
+
return { label: input.label, loaded: false, source: "unavailable" };
|
|
123
|
+
}
|
|
124
|
+
const uid = deps.getUid();
|
|
125
|
+
if (uid === null)
|
|
126
|
+
return { label: input.label, loaded: false, source: "unavailable" };
|
|
127
|
+
const result = deps.spawnSync("launchctl", ["print", `gui/${uid}/${input.label}`], {
|
|
128
|
+
encoding: "utf-8",
|
|
129
|
+
timeout: 5_000,
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
label: input.label,
|
|
133
|
+
loaded: result.status === 0,
|
|
134
|
+
plistPath: firstExistingPath([
|
|
135
|
+
path.join(input.legacyRoot, `${input.label}.plist`),
|
|
136
|
+
path.join(deps.homeDir(), "Library", "LaunchAgents", `${input.label}.plist`),
|
|
137
|
+
], deps),
|
|
138
|
+
source: "launchctl",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function defaultLegacyProcessState(input, deps) {
|
|
142
|
+
const result = deps.spawnSync("ps", ["-axo", "pid=,command="], {
|
|
143
|
+
encoding: "utf-8",
|
|
144
|
+
timeout: 5_000,
|
|
145
|
+
});
|
|
146
|
+
if (result.status !== 0 || typeof result.stdout !== "string") {
|
|
147
|
+
return { running: false, count: 0, source: "unavailable" };
|
|
148
|
+
}
|
|
149
|
+
const legacyRoot = input.legacyRoot;
|
|
150
|
+
const count = result.stdout
|
|
151
|
+
.split("\n")
|
|
152
|
+
.filter((line) => line.includes("rsvp_tracker.py") || line.includes(legacyRoot))
|
|
153
|
+
.filter((line) => !line.includes("vitest") && !line.includes("node "))
|
|
154
|
+
.length;
|
|
155
|
+
return { running: count > 0, count, source: "ps" };
|
|
156
|
+
}
|
|
157
|
+
async function defaultNativeBlueBubblesCredentialHealth(input, deps) {
|
|
158
|
+
if (!input.agent) {
|
|
159
|
+
return { ok: false, detail: "native BlueBubbles credential check requires an agent" };
|
|
160
|
+
}
|
|
161
|
+
const machineId = (0, machine_identity_1.loadOrCreateMachineIdentity)({ homeDir: deps.homeDir() }).machineId;
|
|
162
|
+
const runtimeConfig = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(input.agent, machineId, { preserveCachedOnFailure: true });
|
|
163
|
+
if (!runtimeConfig.ok) {
|
|
164
|
+
return { ok: false, detail: "machine runtime/config unavailable" };
|
|
165
|
+
}
|
|
166
|
+
const bluebubbles = asRecord(runtimeConfig.config.bluebubbles);
|
|
167
|
+
const bluebubblesSense = asRecord(runtimeConfig.config.bluebubblesChannel);
|
|
168
|
+
const serverUrl = text(bluebubbles?.serverUrl);
|
|
169
|
+
const password = text(bluebubbles?.password);
|
|
170
|
+
if (!serverUrl || !password) {
|
|
171
|
+
return { ok: false, detail: "machine runtime/config missing bluebubbles serverUrl/password" };
|
|
172
|
+
}
|
|
173
|
+
const requestTimeoutMs = typeof bluebubblesSense?.requestTimeoutMs === "number" && Number.isFinite(bluebubblesSense.requestTimeoutMs)
|
|
174
|
+
? bluebubblesSense.requestTimeoutMs
|
|
175
|
+
: 30_000;
|
|
176
|
+
try {
|
|
177
|
+
const response = await deps.fetchImpl(buildBlueBubblesHealthUrl(serverUrl, password), {
|
|
178
|
+
method: "GET",
|
|
179
|
+
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
180
|
+
});
|
|
181
|
+
return response.ok
|
|
182
|
+
? { ok: true, detail: "native BlueBubbles credential healthy" }
|
|
183
|
+
: { ok: false, detail: `native BlueBubbles health probe returned HTTP ${response.status}` };
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return { ok: false, detail: "native BlueBubbles health probe failed" };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function defaultUnloadLaunchAgent(input, deps) {
|
|
190
|
+
if (deps.platform !== "darwin")
|
|
191
|
+
return { ok: true, detail: "launchd not applicable on this platform" };
|
|
192
|
+
const uid = deps.getUid();
|
|
193
|
+
if (uid === null)
|
|
194
|
+
return { ok: false, detail: "current uid unavailable" };
|
|
195
|
+
const bootout = deps.spawnSync("launchctl", ["bootout", `gui/${uid}/${input.label}`], {
|
|
196
|
+
encoding: "utf-8",
|
|
197
|
+
timeout: 5_000,
|
|
198
|
+
});
|
|
199
|
+
if (bootout.status === 0)
|
|
200
|
+
return { ok: true };
|
|
201
|
+
if (input.plistPath) {
|
|
202
|
+
const unload = deps.spawnSync("launchctl", ["unload", "-w", input.plistPath], {
|
|
203
|
+
encoding: "utf-8",
|
|
204
|
+
timeout: 5_000,
|
|
205
|
+
});
|
|
206
|
+
if (unload.status === 0)
|
|
207
|
+
return { ok: true };
|
|
208
|
+
}
|
|
209
|
+
return { ok: true, detail: "legacy LaunchAgent was not loaded or launchctl did not report it as loaded" };
|
|
210
|
+
}
|
|
211
|
+
function firstExistingPath(candidates, deps) {
|
|
212
|
+
return candidates.find((candidate) => !!candidate && deps.existsSync(candidate));
|
|
213
|
+
}
|
|
214
|
+
function launchAgentPlistCandidates(legacyRoot, state, deps) {
|
|
215
|
+
const candidates = [
|
|
216
|
+
state.plistPath,
|
|
217
|
+
path.join(legacyRoot, `${LEGACY_LAUNCH_AGENT_LABEL}.plist`),
|
|
218
|
+
path.join(deps.homeDir(), "Library", "LaunchAgents", `${LEGACY_LAUNCH_AGENT_LABEL}.plist`),
|
|
219
|
+
];
|
|
220
|
+
return [...new Set(candidates.filter((candidate) => !!candidate && deps.existsSync(candidate)))];
|
|
221
|
+
}
|
|
222
|
+
function denialReasons(checks) {
|
|
223
|
+
const reasons = [];
|
|
224
|
+
if (!checks.launchAgentInactive)
|
|
225
|
+
reasons.push("legacy LaunchAgent is still loaded");
|
|
226
|
+
if (!checks.legacyProcessInactive)
|
|
227
|
+
reasons.push("legacy RSVP process is still running");
|
|
228
|
+
if (!checks.legacyConfigSendInactive)
|
|
229
|
+
reasons.push("legacy RSVP config can still send BlueBubbles messages");
|
|
230
|
+
if (!checks.legacyLiveSendInactive)
|
|
231
|
+
reasons.push("legacy live-send path is still active");
|
|
232
|
+
if (!checks.nativeBlueBubblesCredentialHealthy)
|
|
233
|
+
reasons.push("native BlueBubbles credential is not healthy");
|
|
234
|
+
return reasons;
|
|
235
|
+
}
|
|
236
|
+
function report(input) {
|
|
237
|
+
const reasons = denialReasons(input.checks);
|
|
238
|
+
const sendAllowed = reasons.length === 0;
|
|
239
|
+
return {
|
|
240
|
+
ok: input.ok ?? true,
|
|
241
|
+
action: input.action,
|
|
242
|
+
sideEffect: input.sideEffect,
|
|
243
|
+
...(input.agent ? { agent: input.agent } : {}),
|
|
244
|
+
legacyRoot: input.legacyRoot,
|
|
245
|
+
checks: input.checks,
|
|
246
|
+
sendAllowed,
|
|
247
|
+
denialReasons: reasons,
|
|
248
|
+
message: sendAllowed
|
|
249
|
+
? "native RSVP live send is allowed"
|
|
250
|
+
: "native RSVP live send is blocked until cutover gates pass",
|
|
251
|
+
...(input.requires ? { requires: input.requires } : {}),
|
|
252
|
+
rollback: {
|
|
253
|
+
...EMPTY_ROLLBACK,
|
|
254
|
+
...input.rollback,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function buildChecks(input, deps) {
|
|
259
|
+
if (!deps.existsSync(input.legacyRoot)) {
|
|
260
|
+
return {
|
|
261
|
+
launchAgentInactive: true,
|
|
262
|
+
legacyProcessInactive: true,
|
|
263
|
+
legacyConfigSendInactive: true,
|
|
264
|
+
legacyLiveSendInactive: true,
|
|
265
|
+
nativeBlueBubblesCredentialHealthy: false,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const launchAgentState = deps.getLaunchAgentState
|
|
269
|
+
? await deps.getLaunchAgentState({
|
|
270
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
271
|
+
legacyRoot: input.legacyRoot,
|
|
272
|
+
})
|
|
273
|
+
: await defaultLaunchAgentState({
|
|
274
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
275
|
+
legacyRoot: input.legacyRoot,
|
|
276
|
+
}, deps);
|
|
277
|
+
const processState = deps.getLegacyProcessState
|
|
278
|
+
? await deps.getLegacyProcessState({ legacyRoot: input.legacyRoot })
|
|
279
|
+
: await defaultLegacyProcessState({ legacyRoot: input.legacyRoot }, deps);
|
|
280
|
+
const configInactive = legacyConfigSendInactive(input.legacyRoot, deps);
|
|
281
|
+
const liveInactive = !launchAgentState.loaded && !processState.running && configInactive;
|
|
282
|
+
const nativeCredential = deps.checkNativeBlueBubblesCredential
|
|
283
|
+
? await deps.checkNativeBlueBubblesCredential({ agent: input.agent })
|
|
284
|
+
: await defaultNativeBlueBubblesCredentialHealth({ agent: input.agent }, deps);
|
|
285
|
+
return {
|
|
286
|
+
launchAgentInactive: !launchAgentState.loaded,
|
|
287
|
+
legacyProcessInactive: !processState.running,
|
|
288
|
+
legacyConfigSendInactive: configInactive,
|
|
289
|
+
legacyLiveSendInactive: liveInactive,
|
|
290
|
+
nativeBlueBubblesCredentialHealthy: nativeCredential.ok,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function checkRsvpCutover(input) {
|
|
294
|
+
const deps = normalizeDeps(input.deps);
|
|
295
|
+
const checks = await buildChecks(input, deps);
|
|
296
|
+
const result = report({
|
|
297
|
+
action: "check",
|
|
298
|
+
sideEffect: false,
|
|
299
|
+
agent: input.agent,
|
|
300
|
+
legacyRoot: input.legacyRoot,
|
|
301
|
+
checks,
|
|
302
|
+
});
|
|
303
|
+
(0, runtime_1.emitNervesEvent)({
|
|
304
|
+
component: "rsvp",
|
|
305
|
+
event: "rsvp.cutover_checked",
|
|
306
|
+
message: "checked RSVP legacy cutover gates",
|
|
307
|
+
meta: {
|
|
308
|
+
agent: input.agent,
|
|
309
|
+
sendAllowed: result.sendAllowed,
|
|
310
|
+
denialCount: result.denialReasons.length,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
function writeRollbackManifest(input) {
|
|
316
|
+
const manifestPath = path.join(rollbackDir(input.legacyRoot, input.deps.now()), `${input.action}-rollback.json`);
|
|
317
|
+
input.deps.mkdirSync(path.dirname(manifestPath));
|
|
318
|
+
const rollback = {
|
|
319
|
+
...EMPTY_ROLLBACK,
|
|
320
|
+
...input.rollback,
|
|
321
|
+
manifestPath,
|
|
322
|
+
};
|
|
323
|
+
input.deps.writeFileSync(manifestPath, `${JSON.stringify({
|
|
324
|
+
action: input.action,
|
|
325
|
+
createdAt: input.deps.now().toISOString(),
|
|
326
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
327
|
+
rollback,
|
|
328
|
+
}, null, 2)}\n`);
|
|
329
|
+
return rollback;
|
|
330
|
+
}
|
|
331
|
+
async function retireLegacySendConfig(input, deps) {
|
|
332
|
+
const configPath = path.join(input.legacyRoot, "config.json");
|
|
333
|
+
const backupPath = path.join(rollbackDir(input.legacyRoot, deps.now()), "config.json");
|
|
334
|
+
deps.mkdirSync(path.dirname(backupPath));
|
|
335
|
+
deps.copyFileSync(configPath, backupPath);
|
|
336
|
+
const legacy = readLegacyConfig(input.legacyRoot, deps);
|
|
337
|
+
const config = legacy.status === "ok" ? { ...legacy.config } : {};
|
|
338
|
+
config.bluebubbles = {
|
|
339
|
+
send_enabled: false,
|
|
340
|
+
enabled: false,
|
|
341
|
+
disabled: true,
|
|
342
|
+
disabled_by: "ouro rsvp cutover",
|
|
343
|
+
disabled_at: deps.now().toISOString(),
|
|
344
|
+
};
|
|
345
|
+
deps.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
346
|
+
return writeRollbackManifest({
|
|
347
|
+
legacyRoot: input.legacyRoot,
|
|
348
|
+
action: "retire-legacy-send-config",
|
|
349
|
+
deps,
|
|
350
|
+
rollback: { configBackupPath: backupPath },
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
async function quarantineLaunchAgent(input, deps) {
|
|
354
|
+
const state = deps.getLaunchAgentState
|
|
355
|
+
? await deps.getLaunchAgentState({
|
|
356
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
357
|
+
legacyRoot: input.legacyRoot,
|
|
358
|
+
})
|
|
359
|
+
: await defaultLaunchAgentState({
|
|
360
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
361
|
+
legacyRoot: input.legacyRoot,
|
|
362
|
+
}, deps);
|
|
363
|
+
const candidates = launchAgentPlistCandidates(input.legacyRoot, state, deps);
|
|
364
|
+
const plistPath = candidates[0];
|
|
365
|
+
if (deps.unloadLaunchAgent) {
|
|
366
|
+
await deps.unloadLaunchAgent({
|
|
367
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
368
|
+
legacyRoot: input.legacyRoot,
|
|
369
|
+
...(plistPath ? { plistPath } : {}),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
await defaultUnloadLaunchAgent({
|
|
374
|
+
label: LEGACY_LAUNCH_AGENT_LABEL,
|
|
375
|
+
...(plistPath ? { plistPath } : {}),
|
|
376
|
+
}, deps);
|
|
377
|
+
}
|
|
378
|
+
let backupPath = "";
|
|
379
|
+
if (plistPath) {
|
|
380
|
+
backupPath = path.join(rollbackDir(input.legacyRoot, deps.now()), path.basename(plistPath));
|
|
381
|
+
deps.mkdirSync(path.dirname(backupPath));
|
|
382
|
+
deps.renameSync(plistPath, backupPath);
|
|
383
|
+
}
|
|
384
|
+
return writeRollbackManifest({
|
|
385
|
+
legacyRoot: input.legacyRoot,
|
|
386
|
+
action: "quarantine-launchd",
|
|
387
|
+
deps,
|
|
388
|
+
rollback: { launchAgentBackupPath: backupPath },
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
async function runRsvpCutover(input) {
|
|
392
|
+
const deps = normalizeDeps(input.deps);
|
|
393
|
+
if (input.action !== "check" && !input.yes) {
|
|
394
|
+
const checks = await buildChecks(input, deps);
|
|
395
|
+
return report({
|
|
396
|
+
action: input.action,
|
|
397
|
+
sideEffect: false,
|
|
398
|
+
agent: input.agent,
|
|
399
|
+
legacyRoot: input.legacyRoot,
|
|
400
|
+
checks,
|
|
401
|
+
ok: false,
|
|
402
|
+
requires: "--yes",
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (input.action === "check") {
|
|
406
|
+
return checkRsvpCutover(input);
|
|
407
|
+
}
|
|
408
|
+
const rollback = input.action === "retire-legacy-send-config"
|
|
409
|
+
? await retireLegacySendConfig(input, deps)
|
|
410
|
+
: await quarantineLaunchAgent(input, deps);
|
|
411
|
+
const checks = await buildChecks(input, deps);
|
|
412
|
+
const result = report({
|
|
413
|
+
action: input.action,
|
|
414
|
+
sideEffect: true,
|
|
415
|
+
agent: input.agent,
|
|
416
|
+
legacyRoot: input.legacyRoot,
|
|
417
|
+
checks,
|
|
418
|
+
rollback,
|
|
419
|
+
});
|
|
420
|
+
(0, runtime_1.emitNervesEvent)({
|
|
421
|
+
component: "rsvp",
|
|
422
|
+
event: "rsvp.cutover_action_executed",
|
|
423
|
+
message: "executed RSVP cutover action",
|
|
424
|
+
meta: {
|
|
425
|
+
agent: input.agent,
|
|
426
|
+
action: input.action,
|
|
427
|
+
sendAllowed: result.sendAllowed,
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
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.collectRsvpDiagnostics = collectRsvpDiagnostics;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const habit_parser_1 = require("../heart/habits/habit-parser");
|
|
39
|
+
const runtime_1 = require("../nerves/runtime");
|
|
40
|
+
const habit_policy_1 = require("./habit-policy");
|
|
41
|
+
function isRecord(value) {
|
|
42
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
function readJson(filePath, deps) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(deps.readFileSync(filePath));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function numberField(record, key) {
|
|
53
|
+
const value = record[key];
|
|
54
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
55
|
+
}
|
|
56
|
+
function stringField(record, key) {
|
|
57
|
+
const value = record[key];
|
|
58
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
59
|
+
}
|
|
60
|
+
function countsFrom(value) {
|
|
61
|
+
if (!isRecord(value))
|
|
62
|
+
return null;
|
|
63
|
+
const attending = numberField(value, "attending");
|
|
64
|
+
const declined = numberField(value, "declined");
|
|
65
|
+
const pending = numberField(value, "pending");
|
|
66
|
+
return attending === null || declined === null || pending === null
|
|
67
|
+
? null
|
|
68
|
+
: { attending, declined, pending };
|
|
69
|
+
}
|
|
70
|
+
function readLedgerRows(filePath, deps) {
|
|
71
|
+
if (!deps.existsSync(filePath))
|
|
72
|
+
return [];
|
|
73
|
+
const lines = deps.readFileSync(filePath).split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
74
|
+
const rows = [];
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(line);
|
|
78
|
+
if (isRecord(parsed))
|
|
79
|
+
rows.push(parsed);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore malformed rows in this high-level health summary.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return rows;
|
|
86
|
+
}
|
|
87
|
+
function rowTimestamp(row) {
|
|
88
|
+
const raw = stringField(row, "createdAt") ?? stringField(row, "anchorTimestamp") ?? "";
|
|
89
|
+
const parsed = Date.parse(raw);
|
|
90
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
91
|
+
}
|
|
92
|
+
function contextPacketLedgerHealth(agentRoot, deps) {
|
|
93
|
+
const candidates = [
|
|
94
|
+
path.join(agentRoot, "state", "senses", "context-packets", "bluebubbles", "ledger.jsonl"),
|
|
95
|
+
path.join(agentRoot, "state", "senses", "context-packets", "ledger.jsonl"),
|
|
96
|
+
];
|
|
97
|
+
const rowsByPath = candidates
|
|
98
|
+
.map((ledgerPath) => ({ ledgerPath, rows: readLedgerRows(ledgerPath, deps) }))
|
|
99
|
+
.filter((entry) => entry.rows.length > 0);
|
|
100
|
+
if (rowsByPath.length === 0) {
|
|
101
|
+
return { status: "warn", detail: "no context packet ledger rows found", rows: 0 };
|
|
102
|
+
}
|
|
103
|
+
const selected = rowsByPath[0];
|
|
104
|
+
const latest = [...selected.rows].sort((left, right) => rowTimestamp(right) - rowTimestamp(left))[0];
|
|
105
|
+
const latestPacketId = stringField(latest, "packetId") ?? stringField(latest, "id") ?? undefined;
|
|
106
|
+
return {
|
|
107
|
+
status: latestPacketId ? "pass" : "warn",
|
|
108
|
+
detail: latestPacketId ? `latestPacketId=${latestPacketId}; rows=${selected.rows.length}` : `rows=${selected.rows.length}; latest packet id missing`,
|
|
109
|
+
...(latestPacketId ? { latestPacketId } : {}),
|
|
110
|
+
ledgerPath: selected.ledgerPath,
|
|
111
|
+
rows: selected.rows.length,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function habitScheduleHealth(agentRoot, deps) {
|
|
115
|
+
const habitsDir = path.join(agentRoot, "habits");
|
|
116
|
+
if (!deps.existsSync(habitsDir))
|
|
117
|
+
return { status: "warn", detail: "habits directory missing" };
|
|
118
|
+
try {
|
|
119
|
+
const rsvpHabits = deps.readdirSync(habitsDir)
|
|
120
|
+
.filter((name) => name.endsWith(".md") && (0, habit_policy_1.isRsvpHabitName)(name.replace(/\.md$/, "")))
|
|
121
|
+
.map((name) => {
|
|
122
|
+
const filePath = path.join(habitsDir, name);
|
|
123
|
+
return (0, habit_parser_1.parseHabitFile)(deps.readFileSync(filePath), filePath);
|
|
124
|
+
});
|
|
125
|
+
const active = rsvpHabits.find((habit) => habit.status === "active");
|
|
126
|
+
if (!active)
|
|
127
|
+
return { status: "warn", detail: "no active RSVP habit found" };
|
|
128
|
+
if (!active.rsvp)
|
|
129
|
+
return { status: "fail", detail: `activeHabit=${active.name}; typed RSVP habit metadata missing` };
|
|
130
|
+
const cadence = active.cadence ?? "unspecified";
|
|
131
|
+
return {
|
|
132
|
+
status: "pass",
|
|
133
|
+
detail: [
|
|
134
|
+
`activeHabit=${active.name}`,
|
|
135
|
+
`cadence=${cadence}`,
|
|
136
|
+
`sense=${active.rsvp.sense}`,
|
|
137
|
+
`source=${active.rsvp.source}`,
|
|
138
|
+
`snapshotRef=${active.rsvp.snapshotRef}`,
|
|
139
|
+
].join("; "),
|
|
140
|
+
activeHabit: active.name,
|
|
141
|
+
cadence,
|
|
142
|
+
policyVersion: active.rsvp.policyVersion,
|
|
143
|
+
sense: active.rsvp.sense,
|
|
144
|
+
source: active.rsvp.source,
|
|
145
|
+
routeRef: active.rsvp.routeRef,
|
|
146
|
+
snapshotRef: active.rsvp.snapshotRef,
|
|
147
|
+
outboundStateRef: active.rsvp.outboundStateRef,
|
|
148
|
+
budgetRef: active.rsvp.budgetRef,
|
|
149
|
+
idempotencyRef: active.rsvp.idempotencyRef,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return { status: "fail", detail: `habit schedule unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function latestFetchHealth(agentRoot, deps) {
|
|
157
|
+
const latestPath = path.join(agentRoot, "state", "rsvp", "snapshots", "latest.json");
|
|
158
|
+
if (!deps.existsSync(latestPath))
|
|
159
|
+
return { status: "warn", detail: "latest RSVP snapshot missing" };
|
|
160
|
+
const parsed = readJson(latestPath, deps);
|
|
161
|
+
if (!isRecord(parsed))
|
|
162
|
+
return { status: "fail", detail: "latest RSVP snapshot is malformed" };
|
|
163
|
+
const snapshotId = stringField(parsed, "snapshotId");
|
|
164
|
+
const counts = countsFrom(parsed.counts) ?? countsFrom(parsed.summary);
|
|
165
|
+
if (!snapshotId || !counts)
|
|
166
|
+
return { status: "fail", detail: "latest RSVP snapshot missing id or counts" };
|
|
167
|
+
return {
|
|
168
|
+
status: "pass",
|
|
169
|
+
detail: `snapshotId=${snapshotId}; pending=${counts.pending}`,
|
|
170
|
+
snapshotId,
|
|
171
|
+
counts,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function deliveryReconciliationHealth(agentRoot, deps) {
|
|
175
|
+
const legacyLedgerPath = path.join(agentRoot, "state", "rsvp", "outbound", "ledger.json");
|
|
176
|
+
if (deps.existsSync(legacyLedgerPath)) {
|
|
177
|
+
const parsed = readJson(legacyLedgerPath, deps);
|
|
178
|
+
const reservations = isRecord(parsed) && Array.isArray(parsed.reservations) ? parsed.reservations : [];
|
|
179
|
+
const accepted = reservations.filter((entry) => isRecord(entry) && entry.status === "accepted").length;
|
|
180
|
+
return { status: "pass", detail: `accepted=${accepted}`, accepted };
|
|
181
|
+
}
|
|
182
|
+
const statePath = path.join(agentRoot, "state", "rsvp", "outbound-state.json");
|
|
183
|
+
if (!deps.existsSync(statePath))
|
|
184
|
+
return { status: "warn", detail: "RSVP outbound state missing", accepted: 0 };
|
|
185
|
+
const parsed = readJson(statePath, deps);
|
|
186
|
+
if (!isRecord(parsed))
|
|
187
|
+
return { status: "fail", detail: "RSVP outbound state is malformed", accepted: 0 };
|
|
188
|
+
const accepted = isRecord(parsed.baseline) ? 1 : 0;
|
|
189
|
+
return { status: "pass", detail: `accepted=${accepted}`, accepted };
|
|
190
|
+
}
|
|
191
|
+
function spendTimelineHealth(agentRoot, deps) {
|
|
192
|
+
const spendPath = path.join(agentRoot, "state", "rsvp", "spend-ledger.json");
|
|
193
|
+
if (!deps.existsSync(spendPath))
|
|
194
|
+
return { status: "warn", detail: "RSVP spend ledger missing", runs: 0 };
|
|
195
|
+
const parsed = readJson(spendPath, deps);
|
|
196
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.runs)) {
|
|
197
|
+
return { status: "fail", detail: "RSVP spend ledger is malformed", runs: 0 };
|
|
198
|
+
}
|
|
199
|
+
return { status: "pass", detail: `runs=${parsed.runs.length}`, runs: parsed.runs.length };
|
|
200
|
+
}
|
|
201
|
+
function collectRsvpDiagnostics(agentRoot, deps) {
|
|
202
|
+
const diagnostics = {
|
|
203
|
+
contextPacketLedger: contextPacketLedgerHealth(agentRoot, deps),
|
|
204
|
+
habitSchedule: habitScheduleHealth(agentRoot, deps),
|
|
205
|
+
latestFetch: latestFetchHealth(agentRoot, deps),
|
|
206
|
+
deliveryReconciliation: deliveryReconciliationHealth(agentRoot, deps),
|
|
207
|
+
spendTimeline: spendTimelineHealth(agentRoot, deps),
|
|
208
|
+
};
|
|
209
|
+
(0, runtime_1.emitNervesEvent)({
|
|
210
|
+
component: "rsvp",
|
|
211
|
+
event: "rsvp.diagnostics_collected",
|
|
212
|
+
message: "collected RSVP diagnostics",
|
|
213
|
+
meta: {
|
|
214
|
+
contextPacketLedger: diagnostics.contextPacketLedger.status,
|
|
215
|
+
habitSchedule: diagnostics.habitSchedule.status,
|
|
216
|
+
latestFetch: diagnostics.latestFetch.status,
|
|
217
|
+
deliveryReconciliation: diagnostics.deliveryReconciliation.status,
|
|
218
|
+
spendTimeline: diagnostics.spendTimeline.status,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
return diagnostics;
|
|
222
|
+
}
|