@ouro.bot/cli 0.1.0-alpha.713 → 0.1.0-alpha.715

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,18 @@
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.715",
6
+ "changes": [
7
+ "Keep RSVP routine-specific report copy in bundle metadata while leaving harness fixtures and help generic."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.714",
12
+ "changes": [
13
+ "Keep RSVP habit naming and report copy in agent bundle metadata instead of compiled harness defaults."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.713",
6
18
  "changes": [
@@ -148,7 +148,7 @@ exports.COMMAND_REGISTRY = {
148
148
  category: "Habits",
149
149
  description: "AislePlanner-backed RSVP habit operations, triage, receipts, ledgers, migration checks, refreshes, and smoke tests",
150
150
  usage: "ouro rsvp <doctor|incident|cutover|legacy-render|replay|config|habit|import-legacy|refresh|compare|smoke> ...",
151
- example: "ouro rsvp doctor --agent slugger --json",
151
+ example: "ouro rsvp doctor --agent rsvp-agent --json",
152
152
  subcommands: [
153
153
  "doctor",
154
154
  "incident",
@@ -456,17 +456,17 @@ const SUBCOMMAND_HELP = {
456
456
  "rsvp doctor": {
457
457
  description: "Check native RSVP configuration, credentials, BlueBubbles attachment, ledgers, receipts, and migration readiness without sending messages; use --json --strict in triage",
458
458
  usage: "ouro rsvp doctor [--agent <name>] [--strict] [--output <path>] [--json]",
459
- example: "ouro rsvp doctor --agent slugger --strict --json",
459
+ example: "ouro rsvp doctor --agent rsvp-agent --strict --json",
460
460
  },
461
461
  "rsvp incident": {
462
462
  description: "Collect a redacted local RSVP diagnostic incident bundle with doctor status, receipts, and ledger pointers without contacting the daemon",
463
463
  usage: "ouro rsvp incident [--agent <name>] [--output <path>] [--json]",
464
- example: "ouro rsvp incident --agent slugger --output /tmp/rsvp-incident.json",
464
+ example: "ouro rsvp incident --agent rsvp-agent --output /tmp/rsvp-incident.json",
465
465
  },
466
466
  "rsvp cutover": {
467
467
  description: "Inspect or retire legacy RSVP sender state before native live send",
468
468
  usage: "ouro rsvp cutover [--agent <name>] --legacy-root <path> --action check|quarantine-launchd|retire-legacy-send-config [--yes] [--output <path>] [--json]",
469
- example: "ouro rsvp cutover --agent slugger --legacy-root ~/Projects/rsvp-tracker --action check --json",
469
+ example: "ouro rsvp cutover --agent rsvp-agent --legacy-root ~/Projects/rsvp-tracker --action check --json",
470
470
  },
471
471
  "rsvp legacy-render": {
472
472
  description: "Render the legacy RSVP script's local snapshot/report shape for comparison without mutating native state",
@@ -476,37 +476,37 @@ const SUBCOMMAND_HELP = {
476
476
  "rsvp replay": {
477
477
  description: "Replay an offline fixture through deterministic native RSVP report/query logic without live BlueBubbles or AislePlanner access",
478
478
  usage: "ouro rsvp replay [--agent <name>] --fixture <path> [--output <path>] [--json]",
479
- example: "ouro rsvp replay --agent slugger --fixture /tmp/rsvp-replay.json --json",
479
+ example: "ouro rsvp replay --agent rsvp-agent --fixture /tmp/rsvp-replay.json --json",
480
480
  },
481
481
  "rsvp config import-legacy": {
482
482
  description: "Import legacy RSVP coordinates into native config and vault-backed runtime/config; mutates only with --yes",
483
483
  usage: "ouro rsvp config import-legacy [--agent <name>] --legacy-root <path> --mode shadow|live [--yes] [--output <path>] [--json]",
484
- example: "ouro rsvp config import-legacy --agent slugger --legacy-root ~/Projects/rsvp-tracker --mode shadow --yes",
484
+ example: "ouro rsvp config import-legacy --agent rsvp-agent --legacy-root ~/Projects/rsvp-tracker --mode shadow --yes",
485
485
  },
486
486
  "rsvp habit stage": {
487
- description: "Stage the daily RSVP refresh habit with a fixed cron cadence, usually the 10 AM dogfood run",
488
- usage: "ouro rsvp habit stage [--agent <name>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]",
489
- example: "ouro rsvp habit stage --agent slugger --mode shadow --cadence '0 10 * * *'",
487
+ description: "Stage an RSVP refresh habit with bundle-owned routine naming and report copy",
488
+ usage: "ouro rsvp habit stage [--agent <name>] [--name <rsvp-name>] [--title <title>] [--report-title <title>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]",
489
+ example: "ouro rsvp habit stage --agent rsvp-agent --name rsvp-updates --title 'RSVP Updates' --mode shadow --cadence '0 10 * * *'",
490
490
  },
491
491
  "rsvp import-legacy": {
492
492
  description: "Compatibility alias for `ouro rsvp config import-legacy`; mutates only with --yes",
493
493
  usage: "ouro rsvp import-legacy [--agent <name>] --legacy-root <path> --mode shadow|live [--yes] [--output <path>] [--json]",
494
- example: "ouro rsvp import-legacy --agent slugger --legacy-root ~/Projects/rsvp-tracker --mode shadow --yes",
494
+ example: "ouro rsvp import-legacy --agent rsvp-agent --legacy-root ~/Projects/rsvp-tracker --mode shadow --yes",
495
495
  },
496
496
  "rsvp refresh": {
497
497
  description: "Run the native RSVP refresh path and write receipts; defaults to dry-run behavior unless --allow-send is explicit",
498
- usage: "ouro rsvp refresh [--agent <name>] --mode shadow|live [--no-send|--allow-send] [--output <path>] [--json]",
499
- example: "ouro rsvp refresh --agent slugger --mode shadow --no-send --json",
498
+ usage: "ouro rsvp refresh [--agent <name>] [--habit <rsvp-name>] --mode shadow|live [--no-send|--allow-send] [--output <path>] [--json]",
499
+ example: "ouro rsvp refresh --agent rsvp-agent --habit rsvp-updates --mode shadow --no-send --json",
500
500
  },
501
501
  "rsvp compare": {
502
502
  description: "Compare native and legacy RSVP render outputs for migration verification",
503
503
  usage: "ouro rsvp compare [--agent <name>] --native <path> --legacy <path> [--output <path>] [--json]",
504
- example: "ouro rsvp compare --agent slugger --native /tmp/native.json --legacy /tmp/legacy.json",
504
+ example: "ouro rsvp compare --agent rsvp-agent --native /tmp/native.json --legacy /tmp/legacy.json",
505
505
  },
506
506
  "rsvp smoke": {
507
507
  description: "Smoke-test RSVP follow-up behavior through a target sense; preflight is no-send and live mode requires --allow-send",
508
508
  usage: "ouro rsvp smoke [--agent <name>] --mode preflight|live --surface bluebubbles [--question <text>] [--allow-send] [--output <path>] [--replay-output <path>] [--json]",
509
- example: "ouro rsvp smoke --agent slugger --mode preflight --surface bluebubbles --question 'who is pending?'",
509
+ example: "ouro rsvp smoke --agent rsvp-agent --mode preflight --surface bluebubbles --question 'who is pending?'",
510
510
  },
511
511
  "mail import-mbox": {
512
512
  description: "Import a HEY or other MBOX export into an existing delegated Mailroom source grant",
@@ -1716,6 +1716,19 @@ function rsvpPath(value, label) {
1716
1716
  throw new Error(`rsvp ${label} requires a non-empty path`);
1717
1717
  return trimmed;
1718
1718
  }
1719
+ function rsvpText(value, label) {
1720
+ const trimmed = value.trim();
1721
+ if (!trimmed || /[\r\n\t]/.test(trimmed))
1722
+ throw new Error(`rsvp ${label} requires non-empty text`);
1723
+ return trimmed;
1724
+ }
1725
+ function rsvpHabitName(value, label) {
1726
+ const trimmed = rsvpText(value, label);
1727
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(trimmed) || !trimmed.startsWith("rsvp-")) {
1728
+ throw new Error(`rsvp ${label} must start with rsvp- and contain only letters, numbers, underscores, or hyphens`);
1729
+ }
1730
+ return trimmed;
1731
+ }
1719
1732
  function validRsvpCronCadence(value) {
1720
1733
  const fields = value.trim().split(/\s+/);
1721
1734
  return fields.length === 5 && fields.every((field) => field.length > 0);
@@ -1733,7 +1746,7 @@ function parseRsvpCommand(args) {
1733
1746
  if (subcommand === "habit") {
1734
1747
  const nested = args[1];
1735
1748
  if (nested !== "stage")
1736
- throw new Error("Usage: ouro rsvp habit stage [--agent <name>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]");
1749
+ throw new Error("Usage: ouro rsvp habit stage [--agent <name>] [--name <rsvp-name>] [--title <title>] [--report-title <title>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]");
1737
1750
  return parseRsvpHabitStageCommand(args.slice(2));
1738
1751
  }
1739
1752
  if (subcommand === "doctor")
@@ -1968,6 +1981,9 @@ function parseRsvpImportLegacyCommand(args, kind) {
1968
1981
  }
1969
1982
  function parseRsvpHabitStageCommand(args) {
1970
1983
  const { agent, rest } = extractAgentFlag(args);
1984
+ let habitName;
1985
+ let title;
1986
+ let reportTitle;
1971
1987
  let mode;
1972
1988
  let cadence;
1973
1989
  let json = false;
@@ -1981,6 +1997,18 @@ function parseRsvpHabitStageCommand(args) {
1981
1997
  mode = value;
1982
1998
  continue;
1983
1999
  }
2000
+ if (token === "--name" && rest[i + 1]) {
2001
+ habitName = rsvpHabitName(rest[++i], "--name");
2002
+ continue;
2003
+ }
2004
+ if (token === "--title" && rest[i + 1]) {
2005
+ title = rsvpText(rest[++i], "--title");
2006
+ continue;
2007
+ }
2008
+ if (token === "--report-title" && rest[i + 1]) {
2009
+ reportTitle = rsvpText(rest[++i], "--report-title");
2010
+ continue;
2011
+ }
1984
2012
  if (token === "--cadence" && rest[i + 1]) {
1985
2013
  cadence = rest[++i];
1986
2014
  continue;
@@ -1993,7 +2021,7 @@ function parseRsvpHabitStageCommand(args) {
1993
2021
  outputPath = rsvpPath(rest[++i], "--output");
1994
2022
  continue;
1995
2023
  }
1996
- throw new Error("Usage: ouro rsvp habit stage [--agent <name>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]");
2024
+ throw new Error("Usage: ouro rsvp habit stage [--agent <name>] [--name <rsvp-name>] [--title <title>] [--report-title <title>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]");
1997
2025
  }
1998
2026
  if (!mode)
1999
2027
  throw new Error("rsvp habit stage requires --mode shadow|live");
@@ -2002,6 +2030,9 @@ function parseRsvpHabitStageCommand(args) {
2002
2030
  return {
2003
2031
  kind: "rsvp.habit.stage",
2004
2032
  ...(agent ? { agent } : {}),
2033
+ ...(habitName ? { habitName } : {}),
2034
+ ...(title ? { title } : {}),
2035
+ ...(reportTitle ? { reportTitle } : {}),
2005
2036
  mode,
2006
2037
  cadence,
2007
2038
  ...(json ? { json: true } : {}),
@@ -2010,6 +2041,7 @@ function parseRsvpHabitStageCommand(args) {
2010
2041
  }
2011
2042
  function parseRsvpRefreshCommand(args) {
2012
2043
  const { agent, rest } = extractAgentFlag(args);
2044
+ let habitName;
2013
2045
  let mode = "shadow";
2014
2046
  let noSend = false;
2015
2047
  let allowSend = false;
@@ -2024,6 +2056,10 @@ function parseRsvpRefreshCommand(args) {
2024
2056
  mode = value;
2025
2057
  continue;
2026
2058
  }
2059
+ if (token === "--habit" && rest[i + 1]) {
2060
+ habitName = rsvpHabitName(rest[++i], "--habit");
2061
+ continue;
2062
+ }
2027
2063
  if (token === "--no-send") {
2028
2064
  noSend = true;
2029
2065
  continue;
@@ -2040,7 +2076,7 @@ function parseRsvpRefreshCommand(args) {
2040
2076
  outputPath = rsvpPath(rest[++i], "--output");
2041
2077
  continue;
2042
2078
  }
2043
- throw new Error("Usage: ouro rsvp refresh [--agent <name>] --mode shadow|live [--no-send|--allow-send] [--output <path>] [--json]");
2079
+ throw new Error("Usage: ouro rsvp refresh [--agent <name>] [--habit <rsvp-name>] --mode shadow|live [--no-send|--allow-send] [--output <path>] [--json]");
2044
2080
  }
2045
2081
  if (noSend && allowSend)
2046
2082
  throw new Error("rsvp refresh cannot combine --allow-send and --no-send");
@@ -2051,6 +2087,7 @@ function parseRsvpRefreshCommand(args) {
2051
2087
  return {
2052
2088
  kind: "rsvp.refresh",
2053
2089
  ...(agent ? { agent } : {}),
2090
+ ...(habitName ? { habitName } : {}),
2054
2091
  mode,
2055
2092
  ...(noSend ? { noSend: true } : {}),
2056
2093
  ...(allowSend ? { allowSend: true } : {}),
package/dist/rsvp/cli.js CHANGED
@@ -38,6 +38,7 @@ const fs = __importStar(require("node:fs"));
38
38
  const os = __importStar(require("node:os"));
39
39
  const path = __importStar(require("node:path"));
40
40
  const doctor_1 = require("../heart/daemon/doctor");
41
+ const habit_parser_1 = require("../heart/habits/habit-parser");
41
42
  const identity_1 = require("../heart/identity");
42
43
  const machine_identity_1 = require("../heart/machine-identity");
43
44
  const runtime_credentials_1 = require("../heart/runtime-credentials");
@@ -47,6 +48,7 @@ const aisleplanner_client_1 = require("./aisleplanner-client");
47
48
  const config_1 = require("./config");
48
49
  const cutover_1 = require("./cutover");
49
50
  const diff_renderer_1 = require("./diff-renderer");
51
+ const habit_policy_1 = require("./habit-policy");
50
52
  const habit_stage_1 = require("./habit-stage");
51
53
  const incident_bundle_1 = require("./incident-bundle");
52
54
  const migration_1 = require("./migration");
@@ -160,6 +162,61 @@ function writeSnapshotFiles(agentRoot, snapshot) {
160
162
  fs.writeFileSync(snapshotFilePath(agentRoot, snapshot.snapshotId), `${JSON.stringify(snapshot, null, 2)}\n`, "utf-8");
161
163
  fs.writeFileSync(latestSnapshotPath(agentRoot), `${JSON.stringify(snapshot, null, 2)}\n`, "utf-8");
162
164
  }
165
+ const RSVP_HABIT_REPORT_LABEL_KEYS = [
166
+ "firstRunLabel",
167
+ "noChangesLabel",
168
+ "newRsvpsLabel",
169
+ "statusChangesLabel",
170
+ "newGuestsLabel",
171
+ "removedGuestsLabel",
172
+ ];
173
+ function reportCopyFromHabit(title, rsvp) {
174
+ const copy = { title: rsvp.reportTitle ?? title };
175
+ for (const key of RSVP_HABIT_REPORT_LABEL_KEYS) {
176
+ const value = rsvp[key];
177
+ if (value)
178
+ copy[key] = value;
179
+ }
180
+ return copy;
181
+ }
182
+ function requireRsvpHabitReportCopy(agentRoot, habitName) {
183
+ const habitPath = path.join(agentRoot, "habits", `${habitName}.md`);
184
+ if (!fs.existsSync(habitPath))
185
+ throw new Error(`RSVP habit ${habitName} not found at ${habitPath}`);
186
+ const habit = (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath);
187
+ if (!habit.rsvp)
188
+ throw new Error(`RSVP habit ${habitName} metadata is required before explicit refresh`);
189
+ return reportCopyFromHabit(habit.title, habit.rsvp);
190
+ }
191
+ function activeRsvpHabitReportCopy(agentRoot) {
192
+ const habitsDir = path.join(agentRoot, "habits");
193
+ if (!fs.existsSync(habitsDir))
194
+ return null;
195
+ for (const entry of fs.readdirSync(habitsDir).filter((name) => name.endsWith(".md")).sort()) {
196
+ const habitPath = path.join(habitsDir, entry);
197
+ try {
198
+ const habit = (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath);
199
+ if (habit.status === "active" && habit.rsvp && (0, habit_policy_1.isRsvpHabitName)(habit.name)) {
200
+ return reportCopyFromHabit(habit.title, habit.rsvp);
201
+ }
202
+ }
203
+ catch (error) {
204
+ (0, runtime_1.emitNervesEvent)({
205
+ level: "error",
206
+ component: "rsvp",
207
+ event: "rsvp.habit_report_copy_error",
208
+ message: "failed to read RSVP habit report copy",
209
+ meta: { habitPath, error: String(error) },
210
+ });
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ function resolveRsvpReportCopy(agentRoot, habitName) {
216
+ if (habitName)
217
+ return requireRsvpHabitReportCopy(agentRoot, habitName);
218
+ return activeRsvpHabitReportCopy(agentRoot) ?? {};
219
+ }
163
220
  function parseSnapshotFromFile(filePath) {
164
221
  const result = (0, snapshot_1.parseRsvpSnapshot)(readJsonFile(filePath));
165
222
  if (!result.ok)
@@ -499,6 +556,9 @@ async function executeHabitStage(command, deps) {
499
556
  const result = (0, habit_stage_1.stageRsvpHabit)({
500
557
  agent: command.agent,
501
558
  agentRoot,
559
+ ...(command.habitName ? { habitName: command.habitName } : {}),
560
+ ...(command.title ? { title: command.title } : {}),
561
+ ...(command.reportTitle ? { reportTitle: command.reportTitle } : {}),
502
562
  mode: command.mode,
503
563
  cadence: command.cadence,
504
564
  });
@@ -517,6 +577,7 @@ async function executeRefresh(command, deps) {
517
577
  requires: "--agent",
518
578
  };
519
579
  }
580
+ const reportCopy = resolveRsvpReportCopy(agentRoot, command.habitName);
520
581
  const configResult = resolveRsvpConfig(agentRoot);
521
582
  if (!configResult.ok) {
522
583
  return basePayload(command, false, "RSVP refresh requires native RSVP config before live work can run", {
@@ -582,7 +643,7 @@ async function executeRefresh(command, deps) {
582
643
  });
583
644
  writeSnapshotFiles(agentRoot, currentSnapshot);
584
645
  const delta = (0, diff_renderer_1.computeRsvpDelta)(previousSnapshot, currentSnapshot);
585
- const reportText = (0, diff_renderer_1.renderRsvpReport)(delta);
646
+ const reportText = (0, diff_renderer_1.renderRsvpReport)(delta, reportCopy);
586
647
  const outboundDecision = (0, outbound_state_1.decideRsvpOutboundReport)({ agentRoot, currentSnapshot, reportText });
587
648
  const wantsLiveSend = command.mode === "live" && command.allowSend === true && outboundDecision.action === "send";
588
649
  if (wantsLiveSend) {
@@ -1,9 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_RSVP_REPORT_COPY = void 0;
3
4
  exports.guestDisplayName = guestDisplayName;
4
5
  exports.computeRsvpDelta = computeRsvpDelta;
5
6
  exports.renderRsvpReport = renderRsvpReport;
6
7
  const runtime_1 = require("../nerves/runtime");
8
+ exports.DEFAULT_RSVP_REPORT_COPY = {
9
+ title: "RSVP Update",
10
+ firstRunLabel: "Current RSVP summary:",
11
+ noChangesLabel: "No changes since last check.",
12
+ newRsvpsLabel: "New RSVPs:",
13
+ statusChangesLabel: "Status changes:",
14
+ newGuestsLabel: "New guests added:",
15
+ removedGuestsLabel: "Guests removed:",
16
+ };
17
+ function reportCopy(input = {}) {
18
+ return {
19
+ ...exports.DEFAULT_RSVP_REPORT_COPY,
20
+ ...Object.fromEntries(Object.entries(input).filter(([, value]) => typeof value === "string" && value.trim().length > 0)),
21
+ };
22
+ }
7
23
  function byId(left, right) {
8
24
  return left.id.localeCompare(right.id);
9
25
  }
@@ -105,38 +121,39 @@ function hasChanges(delta) {
105
121
  || delta.newGuests.length > 0
106
122
  || delta.removedGuests.length > 0;
107
123
  }
108
- function renderRsvpReport(delta) {
109
- const lines = ["RSVP Update — Ari & Rachel", ""];
124
+ function renderRsvpReport(delta, copyInput = {}) {
125
+ const copy = reportCopy(copyInput);
126
+ const lines = [copy.title, ""];
110
127
  if (delta.isFirstRun) {
111
- lines.push("Current RSVP summary:", "");
128
+ lines.push(copy.firstRunLabel, "");
112
129
  }
113
130
  else if (!hasChanges(delta)) {
114
- lines.push("No changes since last check.", "");
131
+ lines.push(copy.noChangesLabel, "");
115
132
  }
116
133
  else {
117
134
  if (delta.newRsvps.length > 0) {
118
- lines.push("New RSVPs:");
135
+ lines.push(copy.newRsvpsLabel);
119
136
  for (const guest of delta.newRsvps) {
120
137
  lines.push(` • ${reportName(guest)} — ${statusLabel(guest.newStatus)}`);
121
138
  }
122
139
  lines.push("");
123
140
  }
124
141
  if (delta.statusChanges.length > 0) {
125
- lines.push("Status changes:");
142
+ lines.push(copy.statusChangesLabel);
126
143
  for (const guest of delta.statusChanges) {
127
144
  lines.push(` • ${reportName(guest)}: ${statusLabel(guest.oldStatus)} → ${statusLabel(guest.newStatus)}`);
128
145
  }
129
146
  lines.push("");
130
147
  }
131
148
  if (delta.newGuests.length > 0) {
132
- lines.push("New guests added:");
149
+ lines.push(copy.newGuestsLabel);
133
150
  for (const guest of delta.newGuests) {
134
151
  lines.push(` • ${reportName(guest)} (${statusLabel(guest.status)})`);
135
152
  }
136
153
  lines.push("");
137
154
  }
138
155
  if (delta.removedGuests.length > 0) {
139
- lines.push("Guests removed:");
156
+ lines.push(copy.removedGuestsLabel);
140
157
  for (const guest of delta.removedGuests) {
141
158
  lines.push(` • ${reportName(guest)}`);
142
159
  }
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RSVP_HABIT_ALLOWED_TOOLS = exports.RSVP_HABIT_NAME = exports.RSVP_HABIT_POLICY_VERSION = void 0;
3
+ exports.RSVP_HABIT_ALLOWED_TOOLS = exports.DEFAULT_RSVP_HABIT_NAME = exports.RSVP_HABIT_POLICY_VERSION = void 0;
4
4
  exports.isRsvpHabitName = isRsvpHabitName;
5
5
  exports.parseRsvpHabitMetadata = parseRsvpHabitMetadata;
6
6
  exports.rsvpHabitRuntimePolicy = rsvpHabitRuntimePolicy;
7
7
  const runtime_1 = require("../nerves/runtime");
8
8
  exports.RSVP_HABIT_POLICY_VERSION = "rsvp-habit/v1";
9
- exports.RSVP_HABIT_NAME = "rsvp-ari-rachel";
9
+ exports.DEFAULT_RSVP_HABIT_NAME = "rsvp-updates";
10
10
  exports.RSVP_HABIT_ALLOWED_TOOLS = ["rsvp_query", "rsvp_summary"];
11
11
  function isRecord(value) {
12
12
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -28,8 +28,12 @@ function requiredBoolean(record, key) {
28
28
  return false;
29
29
  throw new Error(`RSVP habit metadata requires boolean ${key}`);
30
30
  }
31
+ function optionalString(record, key) {
32
+ const value = record[key];
33
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
34
+ }
31
35
  function isRsvpHabitName(name) {
32
- return name === exports.RSVP_HABIT_NAME || name.startsWith("rsvp-");
36
+ return name.startsWith("rsvp-");
33
37
  }
34
38
  function parseRsvpHabitMetadata(raw) {
35
39
  if (raw === undefined || raw === null)
@@ -51,7 +55,7 @@ function parseRsvpHabitMetadata(raw) {
51
55
  const source = requiredString(raw, "source");
52
56
  if (source !== "aisleplanner")
53
57
  throw new Error("RSVP habit metadata source must be aisleplanner");
54
- return {
58
+ const parsed = {
55
59
  policyVersion: exports.RSVP_HABIT_POLICY_VERSION,
56
60
  mode,
57
61
  sense,
@@ -63,6 +67,20 @@ function parseRsvpHabitMetadata(raw) {
63
67
  idempotencyRef: requiredString(raw, "idempotencyRef"),
64
68
  liveSendEligible: requiredBoolean(raw, "liveSendEligible"),
65
69
  };
70
+ for (const key of [
71
+ "reportTitle",
72
+ "firstRunLabel",
73
+ "noChangesLabel",
74
+ "newRsvpsLabel",
75
+ "statusChangesLabel",
76
+ "newGuestsLabel",
77
+ "removedGuestsLabel",
78
+ ]) {
79
+ const value = optionalString(raw, key);
80
+ if (value)
81
+ parsed[key] = value;
82
+ }
83
+ return parsed;
66
84
  }
67
85
  function rsvpHabitRuntimePolicy(metadata) {
68
86
  const policy = {
@@ -41,7 +41,7 @@ const runtime_1 = require("../nerves/runtime");
41
41
  const outbound_state_1 = require("./outbound-state");
42
42
  const spend_ledger_1 = require("./spend-ledger");
43
43
  const habit_policy_1 = require("./habit-policy");
44
- function defaultRsvpHabitMetadata(mode) {
44
+ function defaultRsvpHabitMetadata(mode, reportTitle) {
45
45
  return {
46
46
  policyVersion: habit_policy_1.RSVP_HABIT_POLICY_VERSION,
47
47
  mode,
@@ -53,6 +53,7 @@ function defaultRsvpHabitMetadata(mode) {
53
53
  budgetRef: "state/rsvp/spend-ledger.json",
54
54
  idempotencyRef: "state/rsvp/outbound-state.json",
55
55
  liveSendEligible: false,
56
+ reportTitle,
56
57
  };
57
58
  }
58
59
  function stagedHabitBody() {
@@ -61,12 +62,21 @@ function stagedHabitBody() {
61
62
  "Use the latest native snapshot to answer follow-up questions, and only surface RSVP updates through the guarded outbound policy.",
62
63
  ].join("\n\n");
63
64
  }
65
+ function normalizeHabitName(name) {
66
+ const normalized = name?.trim() || habit_policy_1.DEFAULT_RSVP_HABIT_NAME;
67
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(normalized) || !(0, habit_policy_1.isRsvpHabitName)(normalized)) {
68
+ throw new Error("RSVP habit name must start with rsvp- and contain only letters, numbers, underscores, or hyphens");
69
+ }
70
+ return normalized;
71
+ }
64
72
  function stageRsvpHabit(input) {
65
- const habitPath = path.join(input.agentRoot, "habits", `${habit_policy_1.RSVP_HABIT_NAME}.md`);
66
- const rsvp = defaultRsvpHabitMetadata(input.mode);
73
+ const habitName = normalizeHabitName(input.habitName);
74
+ const title = input.title?.trim() || "RSVP Updates";
75
+ const rsvp = defaultRsvpHabitMetadata(input.mode, input.reportTitle?.trim() || title);
76
+ const habitPath = path.join(input.agentRoot, "habits", `${habitName}.md`);
67
77
  const created = (input.now ?? new Date()).toISOString();
68
78
  const content = (0, habit_parser_1.renderHabitFile)({
69
- title: "RSVP Ari & Rachel",
79
+ title,
70
80
  status: "active",
71
81
  cadence: input.cadence,
72
82
  created,
@@ -82,14 +92,14 @@ function stageRsvpHabit(input) {
82
92
  component: "rsvp",
83
93
  event: "rsvp.habit_staged",
84
94
  message: "staged native RSVP habit",
85
- meta: { agent: input.agent, habitName: habit_policy_1.RSVP_HABIT_NAME, mode: input.mode, cadence: input.cadence },
95
+ meta: { agent: input.agent, habitName, mode: input.mode, cadence: input.cadence },
86
96
  });
87
97
  return {
88
98
  ok: true,
89
99
  sideEffect: true,
90
100
  agent: input.agent,
91
- name: habit_policy_1.RSVP_HABIT_NAME,
92
- habitName: habit_policy_1.RSVP_HABIT_NAME,
101
+ name: habitName,
102
+ habitName,
93
103
  habitPath,
94
104
  mode: input.mode,
95
105
  cadence: input.cadence,
@@ -88,6 +88,7 @@ function refreshCommandFor(input, sendAllowed, mode) {
88
88
  return {
89
89
  kind: "rsvp.refresh",
90
90
  agent: input.agent,
91
+ habitName: input.habitName,
91
92
  mode,
92
93
  json: true,
93
94
  ...(sendAllowed ? { allowSend: true } : { noSend: true }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.713",
3
+ "version": "0.1.0-alpha.715",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",