@ouro.bot/cli 0.1.0-alpha.696 → 0.1.0-alpha.698

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/changelog.json +18 -0
  2. package/dist/heart/agent-entry.js +32 -2
  3. package/dist/heart/autonomy-budget.js +434 -0
  4. package/dist/heart/core.js +11 -0
  5. package/dist/heart/daemon/cadence.js +110 -0
  6. package/dist/heart/daemon/cli-exec.js +20 -0
  7. package/dist/heart/daemon/cli-help.js +74 -0
  8. package/dist/heart/daemon/cli-parse.js +466 -0
  9. package/dist/heart/daemon/daemon-entry.js +104 -3
  10. package/dist/heart/daemon/daemon.js +24 -3
  11. package/dist/heart/daemon/doctor.js +151 -0
  12. package/dist/heart/daemon/stale-bundle-prune.js +23 -6
  13. package/dist/heart/habits/habit-parser.js +4 -1
  14. package/dist/heart/habits/habit-scheduler.js +46 -38
  15. package/dist/heart/habits/habit-session.js +2 -3
  16. package/dist/heart/provider-credentials.js +16 -0
  17. package/dist/heart/run-ledger.js +247 -0
  18. package/dist/heart/runtime-credentials.js +12 -3
  19. package/dist/mind/prompt-budget.js +380 -0
  20. package/dist/repertoire/tools-base.js +2 -0
  21. package/dist/repertoire/tools-rsvp.js +139 -0
  22. package/dist/rsvp/aisleplanner-client.js +354 -0
  23. package/dist/rsvp/cli.js +704 -0
  24. package/dist/rsvp/config.js +327 -0
  25. package/dist/rsvp/cutover.js +431 -0
  26. package/dist/rsvp/diagnostics.js +222 -0
  27. package/dist/rsvp/diff-renderer.js +161 -0
  28. package/dist/rsvp/habit-policy.js +85 -0
  29. package/dist/rsvp/habit-stage.js +93 -0
  30. package/dist/rsvp/incident-bundle.js +97 -0
  31. package/dist/rsvp/migration.js +204 -0
  32. package/dist/rsvp/outbound-state.js +254 -0
  33. package/dist/rsvp/query.js +118 -0
  34. package/dist/rsvp/replay.js +233 -0
  35. package/dist/rsvp/snapshot.js +232 -0
  36. package/dist/senses/bluebubbles/client.js +13 -7
  37. package/dist/senses/bluebubbles/index.js +227 -22
  38. package/dist/senses/bluebubbles/outbound-state.js +304 -0
  39. package/dist/senses/bluebubbles/replay.js +147 -0
  40. package/dist/senses/context-packet-ledger.js +245 -0
  41. package/dist/senses/context-packets.js +154 -0
  42. package/dist/senses/pipeline.js +91 -1
  43. package/dist/senses/private-runtime-worker.js +170 -31
  44. package/dist/senses/private-runtime.js +13 -1
  45. package/dist/senses/voice/twilio-phone.js +9 -5
  46. package/package.json +1 -1
package/changelog.json CHANGED
@@ -1,6 +1,24 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.698",
6
+ "changes": [
7
+ "Open the daemon socket before provider credential warm-up and preload only configured lane providers so slow vault reads cannot make ouro up falsely report a dead daemon.",
8
+ "Require explicitly selected provider credential refreshes to load every requested provider before replacing cached provider state.",
9
+ "Return structured RSVP smoke guidance when the latest snapshot is missing or invalid, with hermetic CLI coverage that cannot send live BlueBubbles messages from tests.",
10
+ "Keep native RSVP update notifications on the legacy message skeleton while replacing only the obsolete script-vs-Slugger footer copy."
11
+ ]
12
+ },
13
+ {
14
+ "version": "0.1.0-alpha.697",
15
+ "changes": [
16
+ "Add BlueBubbles same-thread context, native RSVP habit, autonomy budgets, run ledger attribution, diagnostics, cutover, and verification guardrails.",
17
+ "Harden Twilio phone agent-root resolution so test and runtime callers use explicit bundle roots without falling back to the host AgentBundles tree.",
18
+ "Gate native RSVP live sends on a current passing legacy cutover check so refreshes and smoke tests cannot compete with the old RSVP sender.",
19
+ "Align RSVP doctor cutover preflight with the native config's legacy root so triage reports the same sender state enforced by live sends."
20
+ ]
21
+ },
4
22
  {
5
23
  "version": "0.1.0-alpha.696",
6
24
  "changes": [
@@ -119,6 +119,35 @@ if (!ipcState.installed) {
119
119
  message: "starting private-runtime process entrypoint",
120
120
  meta: { entry: "private-runtime", agentName },
121
121
  });
122
+ async function selectedProviderTargets(agentName) {
123
+ try {
124
+ const [{ readAgentConfigForAgent }, { getAgentBundlesRoot }] = await Promise.all([
125
+ Promise.resolve().then(() => __importStar(require("./auth/auth-flow"))),
126
+ Promise.resolve().then(() => __importStar(require("./identity"))),
127
+ ]);
128
+ const { config } = readAgentConfigForAgent(agentName, getAgentBundlesRoot());
129
+ return [...new Set([config.humanFacing.provider, config.agentFacing.provider])];
130
+ }
131
+ catch (error) {
132
+ (0, runtime_1.emitNervesEvent)({
133
+ level: "warn",
134
+ component: "senses",
135
+ event: "senses.provider_refresh_skipped",
136
+ message: "skipping private-runtime provider credential refresh because agent config could not be read",
137
+ meta: {
138
+ entry: "private-runtime",
139
+ agentName,
140
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error agent-config failures @preserve */ String(error),
141
+ },
142
+ });
143
+ return [];
144
+ }
145
+ }
146
+ function providerPoolMissingTargets(providerPool, providers) {
147
+ if (!providerPool.ok)
148
+ return true;
149
+ return providers.some((provider) => !providerPool.pool.providers[provider]);
150
+ }
122
151
  // Dynamic import: agent-entry is process-start wiring that starts a sense process.
123
152
  // Using dynamic import avoids a static heart/ -> senses/ dependency.
124
153
  Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).then(async ({ readMachineRuntimeCredentialConfig, readRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig, refreshRuntimeCredentialConfig, applyRuntimeCredentialBootstrapMessage, waitForRuntimeCredentialBootstrap, }) => {
@@ -135,8 +164,9 @@ Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).the
135
164
  void refreshRuntimeCredentialConfig(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
136
165
  }
137
166
  const providerPool = readProviderCredentialPool(agentName);
138
- if (!providerPool.ok || Object.keys(providerPool.pool.providers).length === 0) {
139
- await refreshProviderCredentialPool(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
167
+ const providerTargets = await selectedProviderTargets(agentName);
168
+ if (providerTargets.length > 0 && providerPoolMissingTargets(providerPool, providerTargets)) {
169
+ await refreshProviderCredentialPool(agentName, { preserveCachedOnFailure: true, providers: providerTargets }).catch(() => undefined);
140
170
  }
141
171
  /* v8 ignore next 7 -- process-start best-effort machine credential refresh runs in a child entrypoint and is covered operationally by daemon startup tests @preserve */
142
172
  if (!readMachineRuntimeCredentialConfig(agentName).ok) {
@@ -0,0 +1,434 @@
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.AUTONOMY_BUDGET_DEFAULT_POLICY = void 0;
37
+ exports.autonomyBudgetStatePath = autonomyBudgetStatePath;
38
+ exports.autonomyReceiptsDir = autonomyReceiptsDir;
39
+ exports.readAutonomyBudgetState = readAutonomyBudgetState;
40
+ exports.readAutonomyStormBreakers = readAutonomyStormBreakers;
41
+ exports.reserveAutonomyBudget = reserveAutonomyBudget;
42
+ exports.recordAutonomyFailure = recordAutonomyFailure;
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const runtime_1 = require("../nerves/runtime");
46
+ const run_ledger_1 = require("./run-ledger");
47
+ exports.AUTONOMY_BUDGET_DEFAULT_POLICY = {
48
+ agentProactivePaidTurnsPerHour: 6,
49
+ agentProactivePaidTurnsPerDay: 40,
50
+ senseRecoveryPaidTurnsPer15m: 3,
51
+ senseRecoveryPaidTurnsPerDay: 12,
52
+ habitPaidTurnsPerDay: 4,
53
+ duplicateRecoveryTtlMs: 10 * 60 * 1000,
54
+ duplicateHabitTtlMs: 24 * 60 * 60 * 1000,
55
+ stormFailureThreshold: 3,
56
+ stormFailureWindowMs: 10 * 60 * 1000,
57
+ stormBlockMs: 30 * 60 * 1000,
58
+ };
59
+ function autonomyRoot(agentRoot) {
60
+ return path.join(agentRoot, "state", "autonomy");
61
+ }
62
+ function autonomyBudgetStatePath(agentRoot) {
63
+ return path.join(autonomyRoot(agentRoot), "budgets.json");
64
+ }
65
+ function autonomyReceiptsDir(agentRoot) {
66
+ return path.join(autonomyRoot(agentRoot), "receipts");
67
+ }
68
+ function stormBreakersPath(agentRoot) {
69
+ return path.join(autonomyRoot(agentRoot), "storm-breakers.jsonl");
70
+ }
71
+ function nowIso(input) {
72
+ return input ?? new Date().toISOString();
73
+ }
74
+ function parseMs(value) {
75
+ return Date.parse(value);
76
+ }
77
+ function sameCivilDay(left, right) {
78
+ return left.slice(0, 10) === right.slice(0, 10);
79
+ }
80
+ function emptyState() {
81
+ return {
82
+ schemaVersion: 1,
83
+ updatedAt: new Date().toISOString(),
84
+ reservations: [],
85
+ failures: [],
86
+ };
87
+ }
88
+ function isReservation(value) {
89
+ const row = value;
90
+ return !!row
91
+ && row.schemaVersion === 1
92
+ && typeof row.reservedAt === "string"
93
+ && (row.status === undefined || row.status === "reserved" || row.status === "failed")
94
+ && typeof row.agent === "string"
95
+ && typeof row.triggerType === "string"
96
+ && typeof row.sourceKind === "string"
97
+ && typeof row.senseOrHabit === "string"
98
+ && typeof row.targetHash === "string"
99
+ && typeof row.idempotencyKey === "string"
100
+ && row.contentStored === false;
101
+ }
102
+ function isFailure(value) {
103
+ const row = value;
104
+ return !!row
105
+ && row.schemaVersion === 1
106
+ && typeof row.occurredAt === "string"
107
+ && typeof row.fingerprint === "string"
108
+ && typeof row.agent === "string"
109
+ && typeof row.triggerType === "string"
110
+ && typeof row.sourceKind === "string"
111
+ && typeof row.senseOrHabit === "string"
112
+ && typeof row.provider === "string"
113
+ && typeof row.targetHash === "string"
114
+ && typeof row.normalizedErrorName === "string"
115
+ && typeof row.normalizedErrorCode === "string"
116
+ && typeof row.codeLocation === "string"
117
+ && typeof row.idempotencyBucket === "string"
118
+ && row.contentStored === false;
119
+ }
120
+ function readAutonomyBudgetState(agentRoot) {
121
+ const filePath = autonomyBudgetStatePath(agentRoot);
122
+ if (!fs.existsSync(filePath))
123
+ return emptyState();
124
+ try {
125
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
126
+ return {
127
+ schemaVersion: 1,
128
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
129
+ reservations: Array.isArray(parsed.reservations) ? parsed.reservations.filter(isReservation) : [],
130
+ failures: Array.isArray(parsed.failures) ? parsed.failures.filter(isFailure) : [],
131
+ };
132
+ }
133
+ catch {
134
+ return emptyState();
135
+ }
136
+ }
137
+ function writeAutonomyBudgetState(agentRoot, state) {
138
+ const filePath = autonomyBudgetStatePath(agentRoot);
139
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
140
+ fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
141
+ }
142
+ function receiptIdFor(input) {
143
+ return `autr_${(0, run_ledger_1.runLedgerHash)(input).slice("sha256:".length, "sha256:".length + 32)}`;
144
+ }
145
+ function writeReceipt(agentRoot, decision) {
146
+ const receiptId = receiptIdFor(decision);
147
+ const receipt = {
148
+ schemaVersion: 1,
149
+ receiptId,
150
+ status: decision.status,
151
+ reason: decision.reason,
152
+ actor: decision.actor,
153
+ decidedAt: decision.decidedAt,
154
+ agent: decision.agent,
155
+ triggerType: decision.triggerType,
156
+ sourceKind: decision.sourceKind,
157
+ senseOrHabit: decision.senseOrHabit,
158
+ targetHash: decision.targetHash,
159
+ idempotencyKey: decision.idempotencyKey,
160
+ contentStored: false,
161
+ };
162
+ const filePath = path.join(autonomyReceiptsDir(agentRoot), `${receiptId}.json`);
163
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
164
+ fs.writeFileSync(filePath, `${JSON.stringify(receipt, null, 2)}\n`, "utf-8");
165
+ return receipt;
166
+ }
167
+ function duplicateTtlMs(triggerType, policy) {
168
+ if (triggerType === "habit")
169
+ return policy.duplicateHabitTtlMs;
170
+ return policy.duplicateRecoveryTtlMs;
171
+ }
172
+ function paidAutonomyTrigger(triggerType) {
173
+ return triggerType === "proactive" || triggerType === "recovery" || triggerType === "habit" || triggerType === "reload";
174
+ }
175
+ function inWindow(at, now, windowMs) {
176
+ const atMs = parseMs(at);
177
+ const nowMs = parseMs(now);
178
+ return Number.isFinite(atMs) && Number.isFinite(nowMs) && nowMs - atMs >= 0 && nowMs - atMs <= windowMs;
179
+ }
180
+ function reservationFor(request, decidedAt) {
181
+ return {
182
+ schemaVersion: 1,
183
+ reservedAt: decidedAt,
184
+ status: "reserved",
185
+ agent: request.agent,
186
+ triggerType: request.triggerType,
187
+ sourceKind: request.sourceKind,
188
+ senseOrHabit: request.senseOrHabit,
189
+ targetHash: (0, run_ledger_1.runLedgerHash)(request.target),
190
+ idempotencyKey: request.idempotencyKey,
191
+ contentStored: false,
192
+ };
193
+ }
194
+ function stormFingerprint(input) {
195
+ return (0, run_ledger_1.runLedgerHash)({
196
+ agent: input.agent,
197
+ triggerType: input.triggerType,
198
+ sourceKind: input.sourceKind,
199
+ senseOrHabit: input.senseOrHabit,
200
+ provider: input.provider,
201
+ targetHash: (0, run_ledger_1.runLedgerHash)(input.target),
202
+ normalizedErrorName: input.normalizedErrorName,
203
+ normalizedErrorCode: input.normalizedErrorCode,
204
+ codeLocation: input.codeLocation,
205
+ idempotencyBucket: input.idempotencyBucket,
206
+ });
207
+ }
208
+ function failureRecord(input, occurredAt) {
209
+ return {
210
+ schemaVersion: 1,
211
+ occurredAt,
212
+ fingerprint: stormFingerprint(input),
213
+ agent: input.agent,
214
+ triggerType: input.triggerType,
215
+ sourceKind: input.sourceKind,
216
+ senseOrHabit: input.senseOrHabit,
217
+ provider: input.provider,
218
+ targetHash: (0, run_ledger_1.runLedgerHash)(input.target),
219
+ normalizedErrorName: input.normalizedErrorName,
220
+ normalizedErrorCode: input.normalizedErrorCode,
221
+ codeLocation: input.codeLocation,
222
+ idempotencyBucket: input.idempotencyBucket,
223
+ contentStored: false,
224
+ };
225
+ }
226
+ function readAutonomyStormBreakers(agentRoot) {
227
+ const filePath = stormBreakersPath(agentRoot);
228
+ if (!fs.existsSync(filePath))
229
+ return [];
230
+ return fs.readFileSync(filePath, "utf-8")
231
+ .split("\n")
232
+ .filter((line) => line.trim().length > 0)
233
+ .flatMap((line) => {
234
+ try {
235
+ const parsed = JSON.parse(line);
236
+ return parsed.schemaVersion === 1 && parsed.contentStored === false ? [parsed] : [];
237
+ }
238
+ catch {
239
+ return [];
240
+ }
241
+ });
242
+ }
243
+ function appendStormBreaker(agentRoot, breaker) {
244
+ const filePath = stormBreakersPath(agentRoot);
245
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
246
+ fs.appendFileSync(filePath, `${JSON.stringify(breaker)}\n`, "utf-8");
247
+ }
248
+ function activeStormBreaker(agentRoot, input, now) {
249
+ const fingerprint = stormFingerprint(input);
250
+ const nowMs = parseMs(now);
251
+ return readAutonomyStormBreakers(agentRoot)
252
+ .filter((breaker) => breaker.fingerprint === fingerprint)
253
+ .filter((breaker) => parseMs(breaker.blockedUntil) > nowMs)
254
+ .sort((left, right) => right.blockedAt.localeCompare(left.blockedAt))[0] ?? null;
255
+ }
256
+ function blockDecision(agentRoot, request, decidedAt, status, reason) {
257
+ const targetHash = (0, run_ledger_1.runLedgerHash)(request.target);
258
+ const base = {
259
+ status,
260
+ actor: "agent-runnable",
261
+ reason,
262
+ decidedAt,
263
+ agent: request.agent,
264
+ triggerType: request.triggerType,
265
+ sourceKind: request.sourceKind,
266
+ senseOrHabit: request.senseOrHabit,
267
+ targetHash,
268
+ idempotencyKey: request.idempotencyKey,
269
+ };
270
+ const receipt = writeReceipt(agentRoot, base);
271
+ (0, runtime_1.emitNervesEvent)({
272
+ level: "warn",
273
+ component: "heart",
274
+ event: "heart.autonomy_budget_blocked",
275
+ message: "autonomy trigger blocked before paid work",
276
+ meta: {
277
+ agent: request.agent,
278
+ triggerType: request.triggerType,
279
+ sourceKind: request.sourceKind,
280
+ senseOrHabit: request.senseOrHabit,
281
+ status,
282
+ reason,
283
+ receiptId: receipt.receiptId,
284
+ },
285
+ });
286
+ return { allowed: false, ...base, receiptId: receipt.receiptId };
287
+ }
288
+ function budgetBlockReason(state, request, now, policy) {
289
+ if (!paidAutonomyTrigger(request.triggerType))
290
+ return null;
291
+ const agentRecentHour = state.reservations.filter((row) => row.agent === request.agent
292
+ && paidAutonomyTrigger(row.triggerType)
293
+ && inWindow(row.reservedAt, now, 60 * 60 * 1000)).length;
294
+ if (agentRecentHour >= policy.agentProactivePaidTurnsPerHour)
295
+ return "agent proactive paid turn budget exceeded for rolling hour";
296
+ const agentToday = state.reservations.filter((row) => row.agent === request.agent
297
+ && paidAutonomyTrigger(row.triggerType)
298
+ && sameCivilDay(row.reservedAt, now)).length;
299
+ if (agentToday >= policy.agentProactivePaidTurnsPerDay)
300
+ return "agent proactive paid turn budget exceeded for day";
301
+ if (request.triggerType === "recovery" && request.sourceKind === "sense") {
302
+ const recentRecovery = state.reservations.filter((row) => row.agent === request.agent
303
+ && row.triggerType === "recovery"
304
+ && row.sourceKind === request.sourceKind
305
+ && row.senseOrHabit === request.senseOrHabit
306
+ && inWindow(row.reservedAt, now, 15 * 60 * 1000)).length;
307
+ if (recentRecovery >= policy.senseRecoveryPaidTurnsPer15m)
308
+ return "sense recovery paid turn budget exceeded for rolling 15 minutes";
309
+ const recoveryToday = state.reservations.filter((row) => row.agent === request.agent
310
+ && row.triggerType === "recovery"
311
+ && row.sourceKind === request.sourceKind
312
+ && row.senseOrHabit === request.senseOrHabit
313
+ && sameCivilDay(row.reservedAt, now)).length;
314
+ if (recoveryToday >= policy.senseRecoveryPaidTurnsPerDay)
315
+ return "sense recovery paid turn budget exceeded for day";
316
+ }
317
+ if (request.triggerType === "habit") {
318
+ const habitToday = state.reservations.filter((row) => row.agent === request.agent
319
+ && row.triggerType === "habit"
320
+ && row.senseOrHabit === request.senseOrHabit
321
+ && sameCivilDay(row.reservedAt, now)).length;
322
+ if (habitToday >= policy.habitPaidTurnsPerDay)
323
+ return "habit paid turn budget exceeded for day";
324
+ }
325
+ return null;
326
+ }
327
+ function reserveAutonomyBudget(agentRoot, request, policy = exports.AUTONOMY_BUDGET_DEFAULT_POLICY) {
328
+ const decidedAt = nowIso(request.now);
329
+ const state = readAutonomyBudgetState(agentRoot);
330
+ const targetHash = (0, run_ledger_1.runLedgerHash)(request.target);
331
+ const duplicate = state.reservations.find((row) => row.idempotencyKey === request.idempotencyKey
332
+ && row.status !== "failed"
333
+ && inWindow(row.reservedAt, decidedAt, duplicateTtlMs(request.triggerType, policy)));
334
+ if (duplicate)
335
+ return blockDecision(agentRoot, request, decidedAt, "duplicate", "duplicate trigger suppressed");
336
+ if (request.storm && activeStormBreaker(agentRoot, request.storm, decidedAt)) {
337
+ return blockDecision(agentRoot, request, decidedAt, "blocked", "storm breaker active for this trigger fingerprint");
338
+ }
339
+ const reason = budgetBlockReason(state, request, decidedAt, policy);
340
+ if (reason)
341
+ return blockDecision(agentRoot, request, decidedAt, "blocked", reason);
342
+ const reservation = reservationFor(request, decidedAt);
343
+ const nextState = {
344
+ schemaVersion: 1,
345
+ updatedAt: decidedAt,
346
+ reservations: [...state.reservations, reservation],
347
+ failures: state.failures,
348
+ };
349
+ writeAutonomyBudgetState(agentRoot, nextState);
350
+ (0, runtime_1.emitNervesEvent)({
351
+ component: "heart",
352
+ event: "heart.autonomy_budget_allowed",
353
+ message: "autonomy trigger reserved budget before paid work",
354
+ meta: {
355
+ agent: request.agent,
356
+ triggerType: request.triggerType,
357
+ sourceKind: request.sourceKind,
358
+ senseOrHabit: request.senseOrHabit,
359
+ targetHash,
360
+ },
361
+ });
362
+ return {
363
+ allowed: true,
364
+ status: "allowed",
365
+ actor: "agent-runnable",
366
+ reason: "budget reserved",
367
+ decidedAt,
368
+ agent: request.agent,
369
+ triggerType: request.triggerType,
370
+ sourceKind: request.sourceKind,
371
+ senseOrHabit: request.senseOrHabit,
372
+ targetHash,
373
+ idempotencyKey: request.idempotencyKey,
374
+ };
375
+ }
376
+ function recordAutonomyFailure(agentRoot, input, policy = exports.AUTONOMY_BUDGET_DEFAULT_POLICY) {
377
+ const occurredAt = nowIso(input.occurredAt);
378
+ const record = failureRecord(input, occurredAt);
379
+ const state = readAutonomyBudgetState(agentRoot);
380
+ const targetHash = (0, run_ledger_1.runLedgerHash)(input.target);
381
+ const reservations = state.reservations.map((row) => row.agent === input.agent
382
+ && row.triggerType === input.triggerType
383
+ && row.sourceKind === input.sourceKind
384
+ && row.senseOrHabit === input.senseOrHabit
385
+ && row.targetHash === targetHash
386
+ && row.status !== "failed"
387
+ ? { ...row, status: "failed" }
388
+ : row);
389
+ const nextFailures = [...state.failures, record];
390
+ writeAutonomyBudgetState(agentRoot, {
391
+ schemaVersion: 1,
392
+ updatedAt: occurredAt,
393
+ reservations,
394
+ failures: nextFailures,
395
+ });
396
+ const recent = nextFailures.filter((row) => row.fingerprint === record.fingerprint
397
+ && inWindow(row.occurredAt, occurredAt, policy.stormFailureWindowMs));
398
+ if (recent.length >= policy.stormFailureThreshold && !activeStormBreaker(agentRoot, input, occurredAt)) {
399
+ const breaker = {
400
+ schemaVersion: 1,
401
+ fingerprint: record.fingerprint,
402
+ agent: record.agent,
403
+ triggerType: record.triggerType,
404
+ sourceKind: record.sourceKind,
405
+ senseOrHabit: record.senseOrHabit,
406
+ provider: record.provider,
407
+ targetHash: record.targetHash,
408
+ normalizedErrorName: record.normalizedErrorName,
409
+ normalizedErrorCode: record.normalizedErrorCode,
410
+ codeLocation: record.codeLocation,
411
+ idempotencyBucket: record.idempotencyBucket,
412
+ blockedAt: occurredAt,
413
+ blockedUntil: new Date(parseMs(occurredAt) + policy.stormBlockMs).toISOString(),
414
+ failureCount: recent.length,
415
+ contentStored: false,
416
+ };
417
+ appendStormBreaker(agentRoot, breaker);
418
+ (0, runtime_1.emitNervesEvent)({
419
+ level: "warn",
420
+ component: "heart",
421
+ event: "heart.autonomy_storm_breaker",
422
+ message: "autonomy storm breaker activated",
423
+ meta: {
424
+ agent: breaker.agent,
425
+ triggerType: breaker.triggerType,
426
+ sourceKind: breaker.sourceKind,
427
+ senseOrHabit: breaker.senseOrHabit,
428
+ blockedUntil: breaker.blockedUntil,
429
+ failureCount: breaker.failureCount,
430
+ },
431
+ });
432
+ }
433
+ return record;
434
+ }
@@ -21,6 +21,7 @@ const friends_1 = require("@ouro.bot/friends");
21
21
  const tools_2 = require("../repertoire/tools");
22
22
  const runtime_1 = require("../nerves/runtime");
23
23
  const context_1 = require("../mind/context");
24
+ const prompt_budget_1 = require("../mind/prompt-budget");
24
25
  const prompt_1 = require("../mind/prompt");
25
26
  const kept_notes_1 = require("./kept-notes");
26
27
  const error_classification_1 = require("./providers/error-classification");
@@ -964,6 +965,16 @@ async function runAgent(messages, callbacks, channel, signal, options) {
964
965
  callbacks.onModelStart();
965
966
  habitCallbackBufferRef.current = habitSession ? createHabitCallbackBuffer(callbacks) : null;
966
967
  try {
968
+ const promptBudget = (0, prompt_budget_1.applyPromptBudget)({
969
+ messages,
970
+ provider: providerRuntime.id,
971
+ model: providerRuntime.model,
972
+ contextWindowTokens: (0, config_1.getContextConfig)().maxTokens,
973
+ });
974
+ if (promptBudget.status !== "within_budget") {
975
+ messages.splice(0, messages.length, ...promptBudget.messages);
976
+ providerRuntime.resetTurnState(messages);
977
+ }
967
978
  return await providerRuntime.streamTurn({
968
979
  messages,
969
980
  activeTools,
@@ -3,8 +3,55 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_CADENCE_MS = void 0;
4
4
  exports.parseCadenceToCron = parseCadenceToCron;
5
5
  exports.parseCadenceToMs = parseCadenceToMs;
6
+ exports.nextCadenceRunAt = nextCadenceRunAt;
7
+ exports.cadenceFallbackDelayMs = cadenceFallbackDelayMs;
8
+ exports.evaluateCadenceDue = evaluateCadenceDue;
6
9
  const runtime_1 = require("../../nerves/runtime");
7
10
  exports.DEFAULT_CADENCE_MS = 30 * 60 * 1000; // 30 minutes
11
+ function parseFixedDailyCron(raw) {
12
+ if (typeof raw !== "string")
13
+ return null;
14
+ const parts = raw.trim().split(/\s+/);
15
+ if (parts.length !== 5)
16
+ return null;
17
+ const minuteRaw = parts[0];
18
+ const hourRaw = parts[1];
19
+ const day = parts[2];
20
+ const month = parts[3];
21
+ const weekday = parts[4];
22
+ if (day !== "*" || month !== "*" || weekday !== "*")
23
+ return null;
24
+ if (!/^\d+$/.test(minuteRaw) || !/^\d+$/.test(hourRaw))
25
+ return null;
26
+ const minute = Number.parseInt(minuteRaw, 10);
27
+ const hour = Number.parseInt(hourRaw, 10);
28
+ if (minute < 0 || minute > 59 || hour < 0 || hour > 23)
29
+ return null;
30
+ return { minute, hour };
31
+ }
32
+ function coerceTimeMs(value) {
33
+ const time = value instanceof Date ? value.getTime() : typeof value === "number" ? value : Date.parse(value);
34
+ return Number.isFinite(time) ? time : null;
35
+ }
36
+ function localOccurrenceForDay(referenceMs, fixed) {
37
+ const occurrence = new Date(referenceMs);
38
+ occurrence.setHours(fixed.hour, fixed.minute, 0, 0);
39
+ return occurrence;
40
+ }
41
+ function previousFixedDailyOccurrence(fixed, nowMs) {
42
+ const occurrence = localOccurrenceForDay(nowMs, fixed);
43
+ if (occurrence.getTime() > nowMs) {
44
+ occurrence.setDate(occurrence.getDate() - 1);
45
+ }
46
+ return occurrence;
47
+ }
48
+ function nextFixedDailyOccurrence(fixed, fromMs) {
49
+ const occurrence = localOccurrenceForDay(fromMs, fixed);
50
+ if (occurrence.getTime() <= fromMs) {
51
+ occurrence.setDate(occurrence.getDate() + 1);
52
+ }
53
+ return occurrence;
54
+ }
8
55
  /**
9
56
  * Parse a cadence shorthand (e.g. "30m", "2h", "1d") into a cron string.
10
57
  * Also accepts raw cron strings (5 space-separated fields) and passes them through.
@@ -68,3 +115,66 @@ function parseCadenceToMs(raw) {
68
115
  return interval * 60 * 60 * 1000;
69
116
  return interval * 24 * 60 * 60 * 1000;
70
117
  }
118
+ function nextCadenceRunAt(raw, from) {
119
+ const fromMs = coerceTimeMs(from);
120
+ if (fromMs === null)
121
+ return null;
122
+ const intervalMs = parseCadenceToMs(raw);
123
+ if (intervalMs !== null) {
124
+ return new Date(fromMs + intervalMs).toISOString();
125
+ }
126
+ const fixed = parseFixedDailyCron(raw);
127
+ if (!fixed)
128
+ return null;
129
+ const next = nextFixedDailyOccurrence(fixed, fromMs);
130
+ (0, runtime_1.emitNervesEvent)({
131
+ event: "daemon.fixed_cadence_next_run_computed",
132
+ component: "daemon",
133
+ message: "computed fixed daily cadence next run",
134
+ meta: { raw, nextRunAt: next.toISOString() },
135
+ });
136
+ return next.toISOString();
137
+ }
138
+ function cadenceFallbackDelayMs(raw, nowMs) {
139
+ const intervalMs = parseCadenceToMs(raw);
140
+ if (intervalMs !== null)
141
+ return intervalMs;
142
+ const nextRunAt = nextCadenceRunAt(raw, nowMs);
143
+ if (!nextRunAt)
144
+ return null;
145
+ const delay = Date.parse(nextRunAt) - nowMs;
146
+ return delay;
147
+ }
148
+ function evaluateCadenceDue(raw, lastRun, nowMs) {
149
+ const intervalMs = parseCadenceToMs(raw);
150
+ if (intervalMs !== null) {
151
+ if (lastRun === null) {
152
+ return { due: true, elapsedMs: Infinity, occurrenceId: `overdue:first-run:${String(raw).trim()}` };
153
+ }
154
+ const lastRunMs = Date.parse(lastRun);
155
+ if (!Number.isFinite(lastRunMs))
156
+ return { due: false, elapsedMs: 0, occurrenceId: null };
157
+ const elapsedMs = nowMs - lastRunMs;
158
+ return {
159
+ due: elapsedMs >= intervalMs,
160
+ elapsedMs,
161
+ occurrenceId: elapsedMs >= intervalMs ? `overdue:last-run:${lastRun}:cadence:${String(raw).trim()}` : null,
162
+ };
163
+ }
164
+ const fixed = parseFixedDailyCron(raw);
165
+ if (!fixed)
166
+ return null;
167
+ const occurrence = previousFixedDailyOccurrence(fixed, nowMs);
168
+ const elapsedMs = lastRun === null ? Infinity : nowMs - occurrence.getTime();
169
+ const todayOccurrence = localOccurrenceForDay(nowMs, fixed);
170
+ const hasReachedTodayOccurrence = todayOccurrence.getTime() <= nowMs;
171
+ const lastRunMs = lastRun === null ? null : Date.parse(lastRun);
172
+ const due = lastRunMs === null
173
+ ? hasReachedTodayOccurrence
174
+ : Number.isFinite(lastRunMs) && lastRunMs < occurrence.getTime();
175
+ return {
176
+ due,
177
+ elapsedMs,
178
+ occurrenceId: due ? `fixed-daily:${occurrence.toISOString()}:cadence:${String(raw).trim()}` : null,
179
+ };
180
+ }