@ouro.bot/cli 0.1.0-alpha.667 → 0.1.0-alpha.668

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 CHANGED
@@ -1,6 +1,12 @@
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.668",
6
+ "changes": [
7
+ "Harden flight-recorder reconciliation so terminal fulfilled-only repairs are not continuable work, unverifiable active obligation ids are preserved and degraded, parseable-invalid obligation records are excluded from verified recovery state, synthesized recovery actions get fresh provenance, reads remain side-effect-free, and legacy obligation records stay visible in Mailbox, Workbench, and Work Card summaries."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.667",
6
12
  "changes": [
@@ -50,6 +50,7 @@ const path = __importStar(require("path"));
50
50
  const crypto_1 = require("crypto");
51
51
  const session_events_1 = require("../heart/session-events");
52
52
  const runtime_1 = require("../nerves/runtime");
53
+ const obligations_1 = require("./obligations");
53
54
  function isHabitRunTrigger(value) {
54
55
  return value === "cron"
55
56
  || value === "launchd"
@@ -201,7 +202,7 @@ function normalizeResumeInvariants(resume) {
201
202
  ];
202
203
  const missingChanged = resume.missing.join("\n") !== missing.join("\n");
203
204
  const issues = [
204
- ...(resume.canContinue && !resume.hasCompleteState ? ["canContinue true while hasCompleteState false"] : []),
205
+ ...(resume.canContinue && !hasCompleteState ? ["canContinue true while hasCompleteState false"] : []),
205
206
  ...(resume.canContinue && !hasCurrentAsk ? ["canContinue true without currentAsk"] : []),
206
207
  ...(resume.canContinue && !hasNextSafeAction ? ["canContinue true without nextSafeAction"] : []),
207
208
  ...(resume.canContinue && resume.blockedBecause.length > 0 ? ["canContinue true while blocked"] : []),
@@ -226,7 +227,117 @@ function normalizeResumeInvariants(resume) {
226
227
  : resume.recorderHealth,
227
228
  };
228
229
  }
229
- /* v8 ignore stop */
230
+ const FLIGHT_RECORDER_RECONCILE_SOURCE_ID = "reconcile:active-obligations";
231
+ function remainingArcWorkDescriptions(resume) {
232
+ return [
233
+ ...resume.activeReturnObligationIds.map((id) => `return obligation ${id}`),
234
+ ...resume.activePacketIds.map((id) => `packet ${id}`),
235
+ ...resume.openEvolutionCaseIds.map((id) => `evolution case ${id}`),
236
+ ];
237
+ }
238
+ function nextSafeActionAfterObligationReconcile(resume, activeObligations, staleActiveObligationIds, unverifiableActiveObligationIds) {
239
+ if (unverifiableActiveObligationIds.length > 0) {
240
+ return (0, session_events_1.capStructuredRecordString)(`inspect unverifiable active obligations before acting: ${unverifiableActiveObligationIds.join(", ")}`);
241
+ }
242
+ const firstActive = activeObligations[0];
243
+ if (firstActive) {
244
+ const detail = firstActive.nextAction?.trim() || firstActive.content;
245
+ return (0, session_events_1.capStructuredRecordString)(`continue open obligation ${firstActive.id}: ${detail}`);
246
+ }
247
+ const remainingWork = remainingArcWorkDescriptions(resume);
248
+ if (remainingWork.length > 0) {
249
+ return (0, session_events_1.capStructuredRecordString)(`continue remaining Arc work: ${remainingWork.slice(0, 5).join(", ")}`);
250
+ }
251
+ return (0, session_events_1.capStructuredRecordString)(`wait for new input; reconciled completed or missing obligations: ${staleActiveObligationIds.join(", ")}`);
252
+ }
253
+ function reconcileActiveObligations(agentRoot, resume) {
254
+ const obligations = (0, obligations_1.readVerifiedObligations)(agentRoot);
255
+ const activeObligations = (0, obligations_1.readVerifiedPendingObligations)(agentRoot);
256
+ const canonicalIdSet = new Set(obligations.map((obligation) => obligation.id));
257
+ const openIdSet = new Set(activeObligations.map((obligation) => obligation.id));
258
+ const resumeIdSet = new Set(resume.activeObligationIds);
259
+ const staleActiveObligationIds = resume.activeObligationIds.filter((id) => canonicalIdSet.has(id) && !openIdSet.has(id));
260
+ const unverifiableActiveObligationIds = resume.activeObligationIds.filter((id) => !canonicalIdSet.has(id));
261
+ const missingActiveObligationIds = activeObligations
262
+ .map((obligation) => obligation.id)
263
+ .filter((id) => !resumeIdSet.has(id));
264
+ if (staleActiveObligationIds.length === 0
265
+ && missingActiveObligationIds.length === 0
266
+ && unverifiableActiveObligationIds.length === 0) {
267
+ return { resume, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds };
268
+ }
269
+ const activeObligationIds = uniqueStrings([
270
+ ...resume.activeObligationIds.filter((id) => !staleActiveObligationIds.includes(id)),
271
+ ...missingActiveObligationIds,
272
+ ]);
273
+ const hasActiveArcContinuation = activeObligationIds.length > 0
274
+ || resume.activeReturnObligationIds.length > 0
275
+ || resume.activePacketIds.length > 0
276
+ || resume.openEvolutionCaseIds.length > 0;
277
+ const isTerminalWait = staleActiveObligationIds.length > 0 && !hasActiveArcContinuation;
278
+ const mustSynthesizeAction = staleActiveObligationIds.length > 0
279
+ || unverifiableActiveObligationIds.length > 0
280
+ || !nonEmpty(resume.nextSafeAction.value);
281
+ const nextSafeActionValue = mustSynthesizeAction
282
+ ? nextSafeActionAfterObligationReconcile(resume, activeObligations, staleActiveObligationIds, unverifiableActiveObligationIds)
283
+ : resume.nextSafeAction.value;
284
+ const recorderHealth = unverifiableActiveObligationIds.length > 0
285
+ ? {
286
+ status: resume.recorderHealth.status === "unavailable" ? "unavailable" : "degraded",
287
+ issues: uniqueStrings([
288
+ ...resume.recorderHealth.issues,
289
+ `active obligation ids could not be verified in arc/obligations: ${unverifiableActiveObligationIds.join(", ")}`,
290
+ ]),
291
+ }
292
+ : resume.recorderHealth;
293
+ const canContinue = nonEmpty(resume.currentAsk.value)
294
+ && nonEmpty(nextSafeActionValue)
295
+ && resume.blockedBecause.length === 0
296
+ && recorderHealth.status === "ok"
297
+ && hasActiveArcContinuation
298
+ && !isTerminalWait;
299
+ return {
300
+ resume: normalizeResumeInvariants({
301
+ ...resume,
302
+ canContinue,
303
+ recorderHealth,
304
+ activeObligationIds,
305
+ nextSafeAction: {
306
+ ...resume.nextSafeAction,
307
+ value: nextSafeActionValue,
308
+ sourceEventIds: mustSynthesizeAction
309
+ ? [FLIGHT_RECORDER_RECONCILE_SOURCE_ID]
310
+ : resume.nextSafeAction.sourceEventIds,
311
+ stopBefore: unverifiableActiveObligationIds.length > 0
312
+ ? uniqueStrings([...resume.nextSafeAction.stopBefore, "acting on unverifiable obligation state"])
313
+ : resume.nextSafeAction.stopBefore,
314
+ },
315
+ }),
316
+ staleActiveObligationIds,
317
+ missingActiveObligationIds,
318
+ unverifiableActiveObligationIds,
319
+ };
320
+ }
321
+ function normalizeResumeForAgentRoot(agentRoot, resume) {
322
+ return reconcileActiveObligations(agentRoot, normalizeResumeInvariants(resume));
323
+ }
324
+ function emitFlightRecorderReconciled(agentRoot, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds) {
325
+ if (staleActiveObligationIds.length === 0
326
+ && missingActiveObligationIds.length === 0
327
+ && unverifiableActiveObligationIds.length === 0)
328
+ return;
329
+ (0, runtime_1.emitNervesEvent)({
330
+ component: "mind",
331
+ event: "mind.flight_recorder_resume_reconciled",
332
+ message: "flight recorder resume reconciled with canonical Arc state",
333
+ meta: {
334
+ agentRoot,
335
+ staleActiveObligationIds,
336
+ missingActiveObligationIds,
337
+ unverifiableActiveObligationIds,
338
+ },
339
+ });
340
+ }
230
341
  function latestFromEvent(event, previous) {
231
342
  const currentAskValue = event.currentAsk !== undefined ? event.currentAsk : previous.currentAsk.value;
232
343
  const nextSafeActionValue = event.nextSafeAction !== undefined ? event.nextSafeAction : previous.nextSafeAction.value;
@@ -279,7 +390,12 @@ function readFlightRecorderResume(agentRoot) {
279
390
  if (!isFlightRecorderResume(parsed)) {
280
391
  throw new Error("latest.json has invalid flight-recorder resume shape");
281
392
  }
282
- const resume = normalizeResumeInvariants(parsed);
393
+ const { resume, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds, } = normalizeResumeForAgentRoot(agentRoot, parsed);
394
+ if (staleActiveObligationIds.length > 0
395
+ || missingActiveObligationIds.length > 0
396
+ || unverifiableActiveObligationIds.length > 0) {
397
+ emitFlightRecorderReconciled(agentRoot, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds);
398
+ }
283
399
  (0, runtime_1.emitNervesEvent)({
284
400
  component: "mind",
285
401
  event: "mind.flight_recorder_resume_read",
@@ -302,8 +418,9 @@ function readFlightRecorderResume(agentRoot) {
302
418
  }
303
419
  }
304
420
  function writeFlightRecorderResume(agentRoot, resume) {
305
- const safeResume = normalizeResumeInvariants(resume);
421
+ const { resume: safeResume, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds, } = normalizeResumeForAgentRoot(agentRoot, resume);
306
422
  atomicWriteJson(flightRecorderLatestPath(agentRoot), safeResume);
423
+ emitFlightRecorderReconciled(agentRoot, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds);
307
424
  (0, runtime_1.emitNervesEvent)({
308
425
  component: "mind",
309
426
  event: "mind.flight_recorder_resume_written",
@@ -38,6 +38,8 @@ exports.isOpenObligation = isOpenObligation;
38
38
  exports.createObligation = createObligation;
39
39
  exports.readObligations = readObligations;
40
40
  exports.readPendingObligations = readPendingObligations;
41
+ exports.readVerifiedObligations = readVerifiedObligations;
42
+ exports.readVerifiedPendingObligations = readVerifiedPendingObligations;
41
43
  exports.advanceObligation = advanceObligation;
42
44
  exports.fulfillObligation = fulfillObligation;
43
45
  exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
@@ -64,6 +66,31 @@ function isOpenObligationStatus(status) {
64
66
  function isOpenObligation(obligation) {
65
67
  return isOpenObligationStatus(obligation.status);
66
68
  }
69
+ function isReadableObligation(value) {
70
+ if (!value || typeof value !== "object" || Array.isArray(value))
71
+ return false;
72
+ const obligation = value;
73
+ return typeof obligation.id === "string"
74
+ && typeof obligation.content === "string";
75
+ }
76
+ function isVerifiedObligationStatus(value) {
77
+ return value === "pending"
78
+ || value === "investigating"
79
+ || value === "waiting_for_merge"
80
+ || value === "updating_runtime"
81
+ || value === "fulfilled";
82
+ }
83
+ function isVerifiedObligation(value) {
84
+ if (!isReadableObligation(value))
85
+ return false;
86
+ const obligation = value;
87
+ return isVerifiedObligationStatus(obligation.status)
88
+ && typeof obligation.createdAt === "string"
89
+ && !!obligation.origin
90
+ && typeof obligation.origin.friendId === "string"
91
+ && typeof obligation.origin.channel === "string"
92
+ && typeof obligation.origin.key === "string";
93
+ }
67
94
  function createObligation(agentRoot, input) {
68
95
  const now = new Date().toISOString();
69
96
  const id = (0, json_store_1.generateTimestampId)();
@@ -92,11 +119,18 @@ function createObligation(agentRoot, input) {
92
119
  }
93
120
  function readObligations(agentRoot) {
94
121
  const all = (0, json_store_1.readJsonDir)(obligationsDir(agentRoot));
95
- return all.filter((parsed) => typeof parsed.id === "string" && typeof parsed.content === "string");
122
+ return all.filter(isReadableObligation);
96
123
  }
97
124
  function readPendingObligations(agentRoot) {
98
125
  return readObligations(agentRoot).filter(isOpenObligation);
99
126
  }
127
+ function readVerifiedObligations(agentRoot) {
128
+ const all = (0, json_store_1.readJsonDir)(obligationsDir(agentRoot));
129
+ return all.filter(isVerifiedObligation);
130
+ }
131
+ function readVerifiedPendingObligations(agentRoot) {
132
+ return readVerifiedObligations(agentRoot).filter(isOpenObligation);
133
+ }
100
134
  function advanceObligation(agentRoot, obligationId, update) {
101
135
  const dir = obligationsDir(agentRoot);
102
136
  const obligation = (0, json_store_1.readJsonFile)(dir, obligationId);
@@ -127,14 +127,24 @@ function normalizeObligationCurrentSurface(currentSurface, updatedAt, liveCoding
127
127
  && (Date.now() - updatedAtMs) <= STALE_CODING_SURFACE_WINDOW_MS;
128
128
  return recentlyTouched ? currentSurface : null;
129
129
  }
130
+ function legacyObligationTimestamp(obligation) {
131
+ if (typeof obligation.updatedAt === "string")
132
+ return obligation.updatedAt;
133
+ if (typeof obligation.createdAt === "string")
134
+ return obligation.createdAt;
135
+ return "";
136
+ }
137
+ function legacyObligationStatus(obligation) {
138
+ return typeof obligation.status === "string" ? obligation.status : "pending";
139
+ }
130
140
  function readObligationSummary(agentRoot) {
131
141
  const liveCodingSurfaceLabels = buildLiveCodingSurfaceLabels(agentRoot);
132
142
  const items = (0, obligations_1.readPendingObligations)(agentRoot)
133
143
  .map((obligation) => {
134
- const updatedAt = obligation.updatedAt ?? obligation.createdAt;
144
+ const updatedAt = legacyObligationTimestamp(obligation);
135
145
  return {
136
146
  id: obligation.id,
137
- status: obligation.status,
147
+ status: legacyObligationStatus(obligation),
138
148
  content: obligation.content,
139
149
  updatedAt,
140
150
  nextAction: obligation.nextAction ?? null,
@@ -134,14 +134,18 @@ function scanArcSourceIssues(agentRoot) {
134
134
  }
135
135
  function obligationItem(obligation) {
136
136
  const freshness = obligation.meaning?.stalenessClass === "at-risk" ? "stale_risky" : "current";
137
+ const status = typeof obligation.status === "string" ? obligation.status : "pending";
138
+ const updatedAt = typeof obligation.updatedAt === "string"
139
+ ? obligation.updatedAt
140
+ : typeof obligation.createdAt === "string" ? obligation.createdAt : undefined;
137
141
  return {
138
142
  id: obligation.id,
139
143
  title: obligation.content,
140
- status: obligation.status,
144
+ status,
141
145
  source: source("obligation", obligationLocator(obligation.id), freshness),
142
146
  ...(obligation.latestNote ? { summary: obligation.latestNote } : {}),
143
147
  ...(obligation.nextAction ? { nextAction: obligation.nextAction } : {}),
144
- updatedAt: obligation.updatedAt ?? obligation.createdAt,
148
+ ...(updatedAt ? { updatedAt } : {}),
145
149
  };
146
150
  }
147
151
  function returnObligationItem(obligation) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.667",
3
+ "version": "0.1.0-alpha.668",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",