@ouro.bot/cli 0.1.0-alpha.696 → 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 +9 -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/mind/prompt-budget.js +380 -0
- package/dist/repertoire/tools-base.js +2 -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 +91 -1
- 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,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AISLEPLANNER_DEFAULT_TIMEOUT_MS = exports.AISLEPLANNER_DEFAULT_BASE_URL = exports.AISLEPLANNER_API_VERSION = void 0;
|
|
4
|
+
exports.fetchAislePlannerRsvps = fetchAislePlannerRsvps;
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
exports.AISLEPLANNER_API_VERSION = "2019-06-10";
|
|
7
|
+
exports.AISLEPLANNER_DEFAULT_BASE_URL = "https://www.aisleplanner.com";
|
|
8
|
+
exports.AISLEPLANNER_DEFAULT_TIMEOUT_MS = 30_000;
|
|
9
|
+
function createCookieJar() {
|
|
10
|
+
const cookies = new Map();
|
|
11
|
+
return {
|
|
12
|
+
setFromResponse(response) {
|
|
13
|
+
for (const header of setCookieHeaders(response.headers)) {
|
|
14
|
+
const pair = header.split(";", 1)[0].trim();
|
|
15
|
+
const separator = pair.indexOf("=");
|
|
16
|
+
if (separator <= 0)
|
|
17
|
+
continue;
|
|
18
|
+
cookies.set(pair.slice(0, separator), pair.slice(separator + 1));
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
get(name) {
|
|
22
|
+
return cookies.get(name);
|
|
23
|
+
},
|
|
24
|
+
header() {
|
|
25
|
+
return [...cookies.entries()].map(([key, value]) => `${key}=${value}`).join("; ");
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function setCookieHeaders(headers) {
|
|
30
|
+
const combined = headers.get("set-cookie");
|
|
31
|
+
return combined ? splitCombinedSetCookie(combined) : [];
|
|
32
|
+
}
|
|
33
|
+
function splitCombinedSetCookie(header) {
|
|
34
|
+
const parts = [];
|
|
35
|
+
let start = 0;
|
|
36
|
+
for (let index = 0; index < header.length; index += 1) {
|
|
37
|
+
if (header[index] !== ",")
|
|
38
|
+
continue;
|
|
39
|
+
const remainder = header.slice(index + 1);
|
|
40
|
+
if (/^\s*[^=;,\s]+=/.test(remainder)) {
|
|
41
|
+
parts.push(header.slice(start, index).trim());
|
|
42
|
+
start = index + 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
parts.push(header.slice(start).trim());
|
|
46
|
+
return parts.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
function decodedCookie(value) {
|
|
49
|
+
if (!value)
|
|
50
|
+
return undefined;
|
|
51
|
+
try {
|
|
52
|
+
return decodeURIComponent(value);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function baseUrlFor(input) {
|
|
59
|
+
const base = input.baseUrl ?? exports.AISLEPLANNER_DEFAULT_BASE_URL;
|
|
60
|
+
return base.endsWith("/") ? base : `${base}/`;
|
|
61
|
+
}
|
|
62
|
+
function apiUrl(baseUrl, path) {
|
|
63
|
+
return new URL(path.replace(/^\//, ""), baseUrl).toString();
|
|
64
|
+
}
|
|
65
|
+
function requestHeaders(jar, xsrfToken, json = false) {
|
|
66
|
+
return {
|
|
67
|
+
...(json ? { "Content-Type": "application/json" } : {}),
|
|
68
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
69
|
+
"X-AP-API-Version": exports.AISLEPLANNER_API_VERSION,
|
|
70
|
+
"X-XSRF-TOKEN": xsrfToken,
|
|
71
|
+
Cookie: jar.header(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function readBody(response) {
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
if (!text.trim())
|
|
77
|
+
return null;
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(text);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return text;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function bodyText(body) {
|
|
86
|
+
return typeof body === "string" ? body : JSON.stringify(body);
|
|
87
|
+
}
|
|
88
|
+
function hasMfaOrCaptcha(body) {
|
|
89
|
+
return /captcha|mfa|multi-factor|additional verification/i.test(bodyText(body));
|
|
90
|
+
}
|
|
91
|
+
function classifyHttpFailure(response, body, context) {
|
|
92
|
+
if (hasMfaOrCaptcha(body)) {
|
|
93
|
+
return {
|
|
94
|
+
reason: "mfa_or_captcha",
|
|
95
|
+
actor: "human-required",
|
|
96
|
+
message: "AislePlanner requires human verification before API access can continue",
|
|
97
|
+
httpStatus: response.status,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (response.status === 401 || response.status === 403) {
|
|
101
|
+
return {
|
|
102
|
+
reason: "auth_denied",
|
|
103
|
+
actor: "human-required",
|
|
104
|
+
message: "AislePlanner rejected the configured credentials",
|
|
105
|
+
httpStatus: response.status,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (response.status === 429) {
|
|
109
|
+
return {
|
|
110
|
+
reason: "rate_limited",
|
|
111
|
+
actor: "agent-runnable",
|
|
112
|
+
message: "AislePlanner rate limited the RSVP fetch",
|
|
113
|
+
httpStatus: response.status,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
reason: "http_error",
|
|
118
|
+
actor: "agent-runnable",
|
|
119
|
+
message: `AislePlanner ${context} request failed with HTTP ${response.status}`,
|
|
120
|
+
httpStatus: response.status,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function classifyThrown(error) {
|
|
124
|
+
const name = error instanceof Error ? error.name : "";
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
if (/AbortError|TimeoutError|timed out|aborted/i.test(`${name} ${message}`)) {
|
|
127
|
+
return {
|
|
128
|
+
reason: "network_timeout",
|
|
129
|
+
actor: "agent-runnable",
|
|
130
|
+
message: "AislePlanner request timed out",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
reason: "network_error",
|
|
135
|
+
actor: "agent-runnable",
|
|
136
|
+
message: "AislePlanner request failed before a response was received",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function request(fetchFn, jar, url, init, context) {
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetchFn(url, init);
|
|
142
|
+
jar.setFromResponse(response);
|
|
143
|
+
const body = await readBody(response);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
return { ok: false, failure: classifyHttpFailure(response, body, context) };
|
|
146
|
+
}
|
|
147
|
+
return { ok: true, response, body };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return { ok: false, failure: classifyThrown(error) };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function asRecord(value) {
|
|
154
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
155
|
+
}
|
|
156
|
+
function readString(value) {
|
|
157
|
+
return typeof value === "string" ? value : "";
|
|
158
|
+
}
|
|
159
|
+
function readGroupId(value) {
|
|
160
|
+
return typeof value === "string" || typeof value === "number" ? value : null;
|
|
161
|
+
}
|
|
162
|
+
function idString(value) {
|
|
163
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
164
|
+
const id = String(value).trim();
|
|
165
|
+
if (id)
|
|
166
|
+
return id;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function normalizeGuestRows(body) {
|
|
171
|
+
if (!Array.isArray(body))
|
|
172
|
+
return null;
|
|
173
|
+
const guests = {};
|
|
174
|
+
for (const entry of body) {
|
|
175
|
+
const row = asRecord(entry);
|
|
176
|
+
const id = idString(row?.id);
|
|
177
|
+
if (!row || !id)
|
|
178
|
+
return null;
|
|
179
|
+
guests[id] = {
|
|
180
|
+
first_name: readString(row.first_name),
|
|
181
|
+
last_name: readString(row.last_name),
|
|
182
|
+
group_id: readGroupId(row.group_id),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return guests;
|
|
186
|
+
}
|
|
187
|
+
function normalizeEventGuestRows(body) {
|
|
188
|
+
if (!Array.isArray(body))
|
|
189
|
+
return null;
|
|
190
|
+
const eventGuests = new Map();
|
|
191
|
+
for (const entry of body) {
|
|
192
|
+
const row = asRecord(entry);
|
|
193
|
+
const id = idString(row?.wedding_guest_id);
|
|
194
|
+
const status = row?.attending_status;
|
|
195
|
+
if (!row || !id || !(status === undefined || status === null || typeof status === "string"))
|
|
196
|
+
return null;
|
|
197
|
+
eventGuests.set(id, status ?? null);
|
|
198
|
+
}
|
|
199
|
+
return eventGuests;
|
|
200
|
+
}
|
|
201
|
+
function joinGuests(allGuests, eventGuests) {
|
|
202
|
+
const guests = {};
|
|
203
|
+
for (const [id, status] of eventGuests) {
|
|
204
|
+
const guest = allGuests[id];
|
|
205
|
+
if (!guest)
|
|
206
|
+
return null;
|
|
207
|
+
guests[id] = {
|
|
208
|
+
first_name: guest.first_name,
|
|
209
|
+
last_name: guest.last_name,
|
|
210
|
+
group_id: guest.group_id,
|
|
211
|
+
attending_status: status,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return guests;
|
|
215
|
+
}
|
|
216
|
+
function canUseStaleFallback(reason) {
|
|
217
|
+
return ["network_timeout", "network_error", "rate_limited"].includes(reason);
|
|
218
|
+
}
|
|
219
|
+
function withStaleFallback(input, liveFailure) {
|
|
220
|
+
if (!input.staleFallback || !canUseStaleFallback(liveFailure.reason))
|
|
221
|
+
return null;
|
|
222
|
+
(0, runtime_1.emitNervesEvent)({
|
|
223
|
+
component: "rsvp",
|
|
224
|
+
event: "rsvp.aisleplanner_stale_fallback",
|
|
225
|
+
message: "using stale RSVP snapshot after live AislePlanner fetch failure",
|
|
226
|
+
meta: {
|
|
227
|
+
agent: input.agent,
|
|
228
|
+
reason: liveFailure.reason,
|
|
229
|
+
staleSnapshotId: input.staleFallback.snapshotId,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
ok: true,
|
|
234
|
+
source: "stale-fallback",
|
|
235
|
+
fetchedAt: input.staleFallback.fetchedAt,
|
|
236
|
+
staleSnapshotId: input.staleFallback.snapshotId,
|
|
237
|
+
liveFailure,
|
|
238
|
+
guests: input.staleFallback.guests,
|
|
239
|
+
allGuests: input.staleFallback.allGuests,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function finalizeFailure(input, liveFailure) {
|
|
243
|
+
(0, runtime_1.emitNervesEvent)({
|
|
244
|
+
component: "rsvp",
|
|
245
|
+
event: "rsvp.aisleplanner_fetch_error",
|
|
246
|
+
message: "AislePlanner RSVP fetch failed",
|
|
247
|
+
meta: {
|
|
248
|
+
agent: input.agent,
|
|
249
|
+
reason: liveFailure.reason,
|
|
250
|
+
httpStatus: liveFailure.httpStatus ?? null,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return withStaleFallback(input, liveFailure) ?? { ok: false, ...liveFailure };
|
|
254
|
+
}
|
|
255
|
+
async function fetchAislePlannerRsvps(input) {
|
|
256
|
+
const fetchFn = input.fetchFn ?? fetch;
|
|
257
|
+
const timeoutMs = input.timeoutMs ?? exports.AISLEPLANNER_DEFAULT_TIMEOUT_MS;
|
|
258
|
+
const baseUrl = baseUrlFor(input);
|
|
259
|
+
const jar = createCookieJar();
|
|
260
|
+
(0, runtime_1.emitNervesEvent)({
|
|
261
|
+
component: "rsvp",
|
|
262
|
+
event: "rsvp.aisleplanner_fetch_start",
|
|
263
|
+
message: "fetching RSVP state from AislePlanner",
|
|
264
|
+
meta: {
|
|
265
|
+
agent: input.agent,
|
|
266
|
+
weddingId: input.weddingId,
|
|
267
|
+
eventId: input.eventId,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
const signinPage = await request(fetchFn, jar, apiUrl(baseUrl, "/signin"), {
|
|
271
|
+
method: "GET",
|
|
272
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
273
|
+
}, "signin");
|
|
274
|
+
if (!signinPage.ok)
|
|
275
|
+
return finalizeFailure(input, signinPage.failure);
|
|
276
|
+
const initialXsrf = decodedCookie(jar.get("XSRF-TOKEN"));
|
|
277
|
+
if (!initialXsrf) {
|
|
278
|
+
return finalizeFailure(input, {
|
|
279
|
+
reason: "missing_xsrf",
|
|
280
|
+
actor: "agent-runnable",
|
|
281
|
+
message: "AislePlanner signin did not return an XSRF token",
|
|
282
|
+
httpStatus: signinPage.response.status,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const signin = await request(fetchFn, jar, apiUrl(baseUrl, "/api/account/signin"), {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: requestHeaders(jar, initialXsrf, true),
|
|
288
|
+
body: JSON.stringify({
|
|
289
|
+
username: input.credentials.username,
|
|
290
|
+
password: input.credentials.password,
|
|
291
|
+
}),
|
|
292
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
293
|
+
}, "signin");
|
|
294
|
+
if (!signin.ok)
|
|
295
|
+
return finalizeFailure(input, signin.failure);
|
|
296
|
+
const liveXsrf = decodedCookie(jar.get("XSRF-TOKEN")) ?? initialXsrf;
|
|
297
|
+
const guestsResponse = await request(fetchFn, jar, apiUrl(baseUrl, `/api/wedding/${input.weddingId}/guests`), {
|
|
298
|
+
method: "GET",
|
|
299
|
+
headers: requestHeaders(jar, liveXsrf),
|
|
300
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
301
|
+
}, "read");
|
|
302
|
+
if (!guestsResponse.ok)
|
|
303
|
+
return finalizeFailure(input, guestsResponse.failure);
|
|
304
|
+
const allGuests = normalizeGuestRows(guestsResponse.body);
|
|
305
|
+
if (!allGuests) {
|
|
306
|
+
return finalizeFailure(input, {
|
|
307
|
+
reason: "schema_drift",
|
|
308
|
+
actor: "agent-runnable",
|
|
309
|
+
message: "AislePlanner guest payload no longer matches the expected schema",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
const eventGuestsResponse = await request(fetchFn, jar, apiUrl(baseUrl, `/api/wedding/${input.weddingId}/events/${input.eventId}/guests`), {
|
|
313
|
+
method: "GET",
|
|
314
|
+
headers: requestHeaders(jar, liveXsrf),
|
|
315
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
316
|
+
}, "read");
|
|
317
|
+
if (!eventGuestsResponse.ok)
|
|
318
|
+
return finalizeFailure(input, eventGuestsResponse.failure);
|
|
319
|
+
const eventGuests = normalizeEventGuestRows(eventGuestsResponse.body);
|
|
320
|
+
if (!eventGuests) {
|
|
321
|
+
return finalizeFailure(input, {
|
|
322
|
+
reason: "schema_drift",
|
|
323
|
+
actor: "agent-runnable",
|
|
324
|
+
message: "AislePlanner event guest payload no longer matches the expected schema",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const guests = joinGuests(allGuests, eventGuests);
|
|
328
|
+
if (!guests) {
|
|
329
|
+
return finalizeFailure(input, {
|
|
330
|
+
reason: "schema_drift",
|
|
331
|
+
actor: "agent-runnable",
|
|
332
|
+
message: "AislePlanner event guest payload references guests absent from the wedding guest list",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
(0, runtime_1.emitNervesEvent)({
|
|
336
|
+
component: "rsvp",
|
|
337
|
+
event: "rsvp.aisleplanner_fetch_end",
|
|
338
|
+
message: "fetched RSVP state from AislePlanner",
|
|
339
|
+
meta: {
|
|
340
|
+
agent: input.agent,
|
|
341
|
+
weddingId: input.weddingId,
|
|
342
|
+
eventId: input.eventId,
|
|
343
|
+
guestCount: Object.keys(guests).length,
|
|
344
|
+
allGuestCount: Object.keys(allGuests).length,
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
return {
|
|
348
|
+
ok: true,
|
|
349
|
+
source: "live",
|
|
350
|
+
fetchedAt: input.fetchedAt ?? new Date().toISOString(),
|
|
351
|
+
guests,
|
|
352
|
+
allGuests,
|
|
353
|
+
};
|
|
354
|
+
}
|