@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 +12 -0
- package/dist/heart/daemon/cli-help.js +14 -14
- package/dist/heart/daemon/cli-parse.js +40 -3
- package/dist/rsvp/cli.js +62 -1
- package/dist/rsvp/diff-renderer.js +25 -8
- package/dist/rsvp/habit-policy.js +22 -4
- package/dist/rsvp/habit-stage.js +17 -7
- package/dist/rsvp/native-habit-runner.js +1 -0
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
488
|
-
usage: "ouro rsvp habit stage [--agent <name>] --mode shadow|live --cadence '<cron>' [--output <path>] [--json]",
|
|
489
|
-
example: "ouro rsvp habit stage --agent
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
124
|
+
function renderRsvpReport(delta, copyInput = {}) {
|
|
125
|
+
const copy = reportCopy(copyInput);
|
|
126
|
+
const lines = [copy.title, ""];
|
|
110
127
|
if (delta.isFirstRun) {
|
|
111
|
-
lines.push(
|
|
128
|
+
lines.push(copy.firstRunLabel, "");
|
|
112
129
|
}
|
|
113
130
|
else if (!hasChanges(delta)) {
|
|
114
|
-
lines.push(
|
|
131
|
+
lines.push(copy.noChangesLabel, "");
|
|
115
132
|
}
|
|
116
133
|
else {
|
|
117
134
|
if (delta.newRsvps.length > 0) {
|
|
118
|
-
lines.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 = {
|
package/dist/rsvp/habit-stage.js
CHANGED
|
@@ -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
|
|
66
|
-
const
|
|
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
|
|
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
|
|
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:
|
|
92
|
-
habitName
|
|
101
|
+
name: habitName,
|
|
102
|
+
habitName,
|
|
93
103
|
habitPath,
|
|
94
104
|
mode: input.mode,
|
|
95
105
|
cadence: input.cadence,
|