@ouro.bot/cli 0.1.0-alpha.724 → 0.1.0-alpha.725

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 (54) hide show
  1. package/changelog.json +12 -0
  2. package/dist/heart/active-work.js +10 -3
  3. package/dist/heart/core.js +343 -77
  4. package/dist/heart/daemon/cli-exec.js +72 -14
  5. package/dist/heart/daemon/cli-help.js +13 -2
  6. package/dist/heart/daemon/cli-parse.js +82 -1
  7. package/dist/heart/daemon/daemon.js +286 -33
  8. package/dist/heart/daemon/doctor.js +21 -6
  9. package/dist/heart/daemon/habit-private-wake.js +6 -1
  10. package/dist/heart/habits/habit-cancel.js +810 -0
  11. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  12. package/dist/heart/habits/habit-migration.js +28 -23
  13. package/dist/heart/habits/habit-parser.js +72 -8
  14. package/dist/heart/habits/habit-runtime-state.js +3 -24
  15. package/dist/heart/habits/habit-scheduler.js +268 -50
  16. package/dist/heart/habits/habit-session.js +16 -0
  17. package/dist/heart/hatch/hatch-flow.js +6 -4
  18. package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
  19. package/dist/heart/orientation-frame.js +58 -1
  20. package/dist/heart/provider-attempt.js +4 -1
  21. package/dist/heart/providers/anthropic.js +23 -2
  22. package/dist/heart/start-of-turn-packet.js +14 -11
  23. package/dist/heart/streaming.js +565 -185
  24. package/dist/mind/obligation-steering.js +15 -9
  25. package/dist/mind/prompt.js +58 -26
  26. package/dist/nerves/coverage/run-artifacts.js +55 -35
  27. package/dist/nerves/review/cli.js +2 -2
  28. package/dist/nerves/review/core.js +14 -7
  29. package/dist/repertoire/tools-base.js +2 -0
  30. package/dist/repertoire/tools-habits.js +103 -0
  31. package/dist/repertoire/tools.js +18 -3
  32. package/dist/rsvp/cli.js +126 -20
  33. package/dist/rsvp/diagnostics.js +12 -2
  34. package/dist/rsvp/habit-policy.js +7 -0
  35. package/dist/rsvp/habit-stage.js +19 -4
  36. package/dist/rsvp/native-habit-runner.js +151 -11
  37. package/dist/rsvp/outbound-state.js +360 -0
  38. package/dist/senses/bluebubbles/client.js +90 -17
  39. package/dist/senses/bluebubbles/inbound-log.js +1 -0
  40. package/dist/senses/bluebubbles/index.js +662 -425
  41. package/dist/senses/bluebubbles/model.js +57 -13
  42. package/dist/senses/bluebubbles/mutation-log.js +21 -10
  43. package/dist/senses/bluebubbles/reaction-policy.js +55 -0
  44. package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
  45. package/dist/senses/cli/tui-store.js +1 -0
  46. package/dist/senses/cli.js +1 -0
  47. package/dist/senses/habit-lifecycle-guard.js +24 -0
  48. package/dist/senses/ingress-evidence.js +2 -0
  49. package/dist/senses/pipeline.js +90 -24
  50. package/dist/senses/private-runtime-worker.js +21 -20
  51. package/dist/senses/private-runtime.js +79 -29
  52. package/dist/senses/shared-turn.js +1 -0
  53. package/dist/senses/teams.js +1 -0
  54. package/package.json +6 -1
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
39
39
  const runtime_1 = require("../../nerves/runtime");
40
40
  const habit_parser_1 = require("./habit-parser");
41
41
  const habit_runtime_state_1 = require("./habit-runtime-state");
42
+ const habit_lifecycle_1 = require("./habit-lifecycle");
42
43
  const frontmatter_1 = require("../../util/frontmatter");
43
44
  /** Fields that belong to the task system and should be stripped from migrated habits. */
44
45
  const TASK_ONLY_FIELDS = new Set([
@@ -54,15 +55,13 @@ const TASK_ONLY_FIELDS = new Set([
54
55
  ]);
55
56
  /** Regex matching the YYYY-MM-DD-HHMM- timestamp prefix in task filenames. */
56
57
  const TIMESTAMP_PREFIX = /^\d{4}-\d{2}-\d{2}-\d{4}-/;
57
- /** Map old task statuses to habit statuses. */
58
- function mapStatus(taskStatus) {
59
- if (taskStatus === "processing")
60
- return "active";
61
- if (taskStatus === "paused")
62
- return "paused";
58
+ /** Map known old task statuses while preserving explicit unknown values for fail-closed parsing. */
59
+ function mapStatus(taskStatus, wasExplicit) {
60
+ if (!wasExplicit || taskStatus === "processing")
61
+ return { skip: false, status: "active" };
63
62
  if (taskStatus === "done")
64
- return null; // skip done habits
65
- return "active"; // default to active for unknown statuses
63
+ return { skip: true, status: taskStatus };
64
+ return { skip: false, status: taskStatus };
66
65
  }
67
66
  /** Strip timestamp prefix from filename to get the slug name. */
68
67
  function stripTimestampPrefix(filename) {
@@ -99,16 +98,6 @@ function migrateHabitsFromTaskSystem(bundleRoot) {
99
98
  for (const file of mdFiles) {
100
99
  const slugName = stripTimestampPrefix(file);
101
100
  const targetPath = path.join(newHabitsDir, slugName);
102
- // Skip if already migrated
103
- if (fs.existsSync(targetPath)) {
104
- (0, runtime_1.emitNervesEvent)({
105
- component: "daemon",
106
- event: "daemon.habit_migration_skip",
107
- message: "habit already exists at target, skipping",
108
- meta: { file, targetPath },
109
- });
110
- continue;
111
- }
112
101
  const sourcePath = path.join(oldHabitsDir, file);
113
102
  let content;
114
103
  try {
@@ -126,11 +115,14 @@ function migrateHabitsFromTaskSystem(bundleRoot) {
126
115
  continue;
127
116
  const rawFrontmatter = lines.slice(1, closing).join("\n");
128
117
  const body = lines.slice(closing + 1).join("\n").trim();
118
+ if (!(0, habit_parser_1.isHabitFrontmatterSyntaxValid)(rawFrontmatter))
119
+ continue;
129
120
  const frontmatter = (0, frontmatter_1.parseFrontmatter)(rawFrontmatter);
130
121
  // Check status — skip done habits
131
- const rawStatus = typeof frontmatter.status === "string" ? frontmatter.status : "processing";
132
- const habitStatus = mapStatus(rawStatus);
133
- if (habitStatus === null) {
122
+ const hasExplicitStatus = Object.prototype.hasOwnProperty.call(frontmatter, "status");
123
+ const rawStatus = hasExplicitStatus ? frontmatter.status : undefined;
124
+ const mappedStatus = mapStatus(rawStatus, hasExplicitStatus);
125
+ if (mappedStatus.skip) {
134
126
  (0, runtime_1.emitNervesEvent)({
135
127
  component: "daemon",
136
128
  event: "daemon.habit_migration_skip",
@@ -150,7 +142,7 @@ function migrateHabitsFromTaskSystem(bundleRoot) {
150
142
  newFrontmatter.title = frontmatter.title;
151
143
  if (typeof frontmatter.cadence === "string")
152
144
  newFrontmatter.cadence = frontmatter.cadence;
153
- newFrontmatter.status = habitStatus;
145
+ newFrontmatter.status = mappedStatus.status;
154
146
  newFrontmatter.created = typeof frontmatter.created === "string" ? frontmatter.created : "null";
155
147
  // Add any other non-task fields from original
156
148
  for (const [key, value] of Object.entries(frontmatter)) {
@@ -166,7 +158,20 @@ function migrateHabitsFromTaskSystem(bundleRoot) {
166
158
  newFrontmatter[key] = value;
167
159
  }
168
160
  const rendered = (0, habit_parser_1.renderHabitFile)(newFrontmatter, body);
169
- fs.writeFileSync(targetPath, rendered, "utf-8");
161
+ const publication = (0, habit_lifecycle_1.publishNewHabitDefinition)({
162
+ agentRoot: bundleRoot,
163
+ habitId: path.basename(slugName, ".md"),
164
+ bytes: rendered,
165
+ });
166
+ if (publication === "exists") {
167
+ (0, runtime_1.emitNervesEvent)({
168
+ component: "daemon",
169
+ event: "daemon.habit_migration_skip",
170
+ message: "habit already exists at target, skipping",
171
+ meta: { file, targetPath },
172
+ });
173
+ continue;
174
+ }
170
175
  if (legacyLastRun) {
171
176
  (0, habit_runtime_state_1.writeHabitLastRun)(bundleRoot, path.basename(slugName, ".md"), legacyLastRun);
172
177
  }
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isHabitFrontmatterSyntaxValid = isHabitFrontmatterSyntaxValid;
37
+ exports.createDegradedHabitFile = createDegradedHabitFile;
36
38
  exports.parseHabitFile = parseHabitFile;
37
39
  exports.renderHabitFile = renderHabitFile;
38
40
  const path = __importStar(require("path"));
@@ -40,7 +42,7 @@ const frontmatter_1 = require("../../util/frontmatter");
40
42
  const runtime_1 = require("../../nerves/runtime");
41
43
  const habit_policy_1 = require("../../rsvp/habit-policy");
42
44
  function isHabitStatus(value) {
43
- return value === "active" || value === "paused";
45
+ return value === "active" || value === "paused" || value === "cancelled";
44
46
  }
45
47
  function parseToolsField(raw) {
46
48
  if (raw === undefined || raw === null)
@@ -110,18 +112,63 @@ function parseContinuity(raw) {
110
112
  const mode = record ? record.mode : null;
111
113
  return { mode: mode === "stateful" ? "stateful" : "fresh" };
112
114
  }
115
+ function isHabitFrontmatterSyntaxValid(rawFrontmatter) {
116
+ const lines = rawFrontmatter.split(/\r?\n/);
117
+ for (let index = 0; index < lines.length; index += 1) {
118
+ const line = lines[index];
119
+ if (!line.trim())
120
+ continue;
121
+ const match = /^([A-Za-z0-9_:-]+):\s*(.*)$/.exec(line);
122
+ if (!match)
123
+ return false;
124
+ if (match[2].length > 0)
125
+ continue;
126
+ let cursor = index + 1;
127
+ while (cursor < lines.length && /^\s*-\s+/.test(lines[cursor]))
128
+ cursor += 1;
129
+ if (cursor > index + 1) {
130
+ index = cursor - 1;
131
+ continue;
132
+ }
133
+ while (cursor < lines.length && /^\s+[A-Za-z0-9_:-]+:\s*/.test(lines[cursor]))
134
+ cursor += 1;
135
+ index = cursor - 1;
136
+ }
137
+ return true;
138
+ }
113
139
  function extractFrontmatterAndBody(content) {
114
140
  const lines = content.split(/\r?\n/);
115
141
  if (lines[0]?.trim() !== "---") {
116
- return null;
142
+ return { kind: "legacy_body", body: content.trim() };
117
143
  }
118
144
  const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
119
145
  if (closing === -1) {
120
- return null;
146
+ return { kind: "degraded", reason: "unterminated_frontmatter", body: content.trim() };
121
147
  }
122
148
  const rawFrontmatter = lines.slice(1, closing).join("\n");
123
149
  const body = lines.slice(closing + 1).join("\n").trim();
124
- return { frontmatter: (0, frontmatter_1.parseFrontmatter)(rawFrontmatter), body };
150
+ if (!isHabitFrontmatterSyntaxValid(rawFrontmatter)) {
151
+ return { kind: "degraded", reason: "malformed_frontmatter", body };
152
+ }
153
+ return { kind: "frontmatter", frontmatter: (0, frontmatter_1.parseFrontmatter)(rawFrontmatter), body };
154
+ }
155
+ function createDegradedHabitFile(filePath, degradedReason, body = "", degradedDetail = null) {
156
+ const stem = path.basename(filePath, ".md");
157
+ return {
158
+ name: stem,
159
+ title: stem,
160
+ cadence: null,
161
+ status: "degraded",
162
+ degradedReason,
163
+ degradedDetail,
164
+ lastRun: null,
165
+ created: null,
166
+ tools: undefined,
167
+ origin: null,
168
+ surface: { family: false, originator: false, extra: [] },
169
+ continuity: { mode: "fresh" },
170
+ body: body.trim(),
171
+ };
125
172
  }
126
173
  function parseHabitFile(content, filePath) {
127
174
  (0, runtime_1.emitNervesEvent)({
@@ -132,7 +179,7 @@ function parseHabitFile(content, filePath) {
132
179
  });
133
180
  const stem = path.basename(filePath, ".md");
134
181
  const parsed = extractFrontmatterAndBody(content);
135
- if (!parsed) {
182
+ if (parsed.kind === "legacy_body") {
136
183
  return {
137
184
  name: stem,
138
185
  title: stem,
@@ -144,21 +191,38 @@ function parseHabitFile(content, filePath) {
144
191
  origin: null,
145
192
  surface: { family: true, originator: true, extra: [] },
146
193
  continuity: { mode: "fresh" },
147
- body: content.trim(),
194
+ body: parsed.body,
148
195
  };
149
196
  }
197
+ if (parsed.kind === "degraded") {
198
+ return createDegradedHabitFile(filePath, parsed.reason, parsed.body);
199
+ }
150
200
  const { frontmatter, body } = parsed;
151
201
  const rawTitle = frontmatter.title;
152
202
  const title = typeof rawTitle === "string" && rawTitle.length > 0 ? rawTitle : stem;
153
203
  const rawCadence = frontmatter.cadence;
154
204
  const cadence = typeof rawCadence === "string" && rawCadence.length > 0 ? rawCadence : null;
155
205
  const rawStatus = frontmatter.status;
156
- const status = typeof rawStatus === "string" && isHabitStatus(rawStatus) ? rawStatus : "active";
206
+ const status = rawStatus === undefined
207
+ ? "active"
208
+ : typeof rawStatus === "string" && isHabitStatus(rawStatus)
209
+ ? rawStatus
210
+ : null;
211
+ if (status === null) {
212
+ return createDegradedHabitFile(filePath, "invalid_status", body);
213
+ }
157
214
  const rawLastRun = frontmatter.lastRun ?? frontmatter.last_run;
158
215
  const lastRun = typeof rawLastRun === "string" && rawLastRun.length > 0 ? rawLastRun : null;
159
216
  const rawCreated = frontmatter.created;
160
217
  const created = typeof rawCreated === "string" && rawCreated.length > 0 ? rawCreated : null;
161
- const rsvp = (0, habit_policy_1.parseRsvpHabitMetadata)(frontmatter.rsvp);
218
+ let rsvp;
219
+ try {
220
+ rsvp = (0, habit_policy_1.parseRsvpHabitMetadata)(frontmatter.rsvp);
221
+ }
222
+ catch (error) {
223
+ const degradedDetail = (0, habit_policy_1.rsvpHabitMetadataErrorDetail)(error);
224
+ return createDegradedHabitFile(filePath, "invalid_metadata", body, degradedDetail);
225
+ }
162
226
  const tools = rsvp ? [...habit_policy_1.RSVP_HABIT_ALLOWED_TOOLS] : parseToolsField(frontmatter.tools);
163
227
  const origin = parseOrigin(frontmatter.origin);
164
228
  const surface = parseSurface(frontmatter.surface);
@@ -37,7 +37,6 @@ exports.readHabitLastRun = readHabitLastRun;
37
37
  exports.applyHabitRuntimeState = applyHabitRuntimeState;
38
38
  exports.writeHabitLastRun = writeHabitLastRun;
39
39
  exports.recordHabitRun = recordHabitRun;
40
- const fs = __importStar(require("fs"));
41
40
  const path = __importStar(require("path"));
42
41
  const json_store_1 = require("../../arc/json-store");
43
42
  const runtime_1 = require("../../nerves/runtime");
@@ -50,21 +49,6 @@ function isNonEmptyString(value) {
50
49
  function nullableCursor(value) {
51
50
  return isNonEmptyString(value) ? value.trim() : null;
52
51
  }
53
- function stripLegacyLastRunFromDefinition(definitionPath) {
54
- const content = fs.readFileSync(definitionPath, "utf-8");
55
- const lines = content.split(/\r?\n/);
56
- if (lines[0]?.trim() !== "---")
57
- return;
58
- const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
59
- if (closing === -1)
60
- return;
61
- const frontmatterLines = lines.slice(1, closing);
62
- const filtered = frontmatterLines.filter((line) => !/^\s*lastRun\s*:/.test(line) && !/^\s*last_run\s*:/.test(line));
63
- if (filtered.length === frontmatterLines.length)
64
- return;
65
- const nextContent = ["---", ...filtered, "---", ...lines.slice(closing + 1)].join("\n");
66
- fs.writeFileSync(definitionPath, nextContent, "utf-8");
67
- }
68
52
  function readHabitLastRun(agentRoot, habitName) {
69
53
  const record = (0, json_store_1.readJsonFile)(habitRuntimeStateDir(agentRoot), habitName);
70
54
  return isNonEmptyString(record?.lastRun) ? record.lastRun : null;
@@ -103,12 +87,7 @@ function writeHabitLastRun(agentRoot, habitName, lastRun, updatedAt = lastRun, o
103
87
  }
104
88
  function recordHabitRun(agentRoot, habitName, lastRun, options = {}) {
105
89
  writeHabitLastRun(agentRoot, habitName, lastRun, lastRun, options);
106
- if (!options.definitionPath)
107
- return;
108
- try {
109
- stripLegacyLastRunFromDefinition(options.definitionPath);
110
- }
111
- catch {
112
- // Missing/deleted habit files should never block runtime-state recording.
113
- }
90
+ // Runtime cursor state is the sole authority for lastRun. Definition files
91
+ // are lifecycle-owned and must never be opportunistically rewritten from a
92
+ // run-completion path, even for legacy-field cleanup.
114
93
  }