@ouro.bot/cli 0.1.0-alpha.724 → 0.1.0-alpha.726
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 +18 -0
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +72 -14
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +82 -1
- package/dist/heart/daemon/daemon.js +286 -33
- package/dist/heart/daemon/doctor.js +21 -6
- package/dist/heart/daemon/habit-private-wake.js +6 -1
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +28 -23
- package/dist/heart/habits/habit-parser.js +72 -8
- package/dist/heart/habits/habit-runtime-state.js +3 -24
- package/dist/heart/habits/habit-scheduler.js +268 -50
- package/dist/heart/habits/habit-session.js +16 -0
- package/dist/heart/hatch/hatch-flow.js +6 -4
- package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
- package/dist/heart/orientation-frame.js +58 -1
- package/dist/heart/provider-attempt.js +4 -1
- package/dist/heart/providers/anthropic.js +23 -2
- package/dist/heart/start-of-turn-packet.js +14 -11
- package/dist/heart/streaming.js +565 -185
- package/dist/mind/obligation-steering.js +15 -9
- package/dist/mind/prompt.js +58 -26
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/review/cli.js +2 -2
- package/dist/nerves/review/core.js +14 -7
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools.js +18 -3
- package/dist/rsvp/cli.js +126 -20
- package/dist/rsvp/diagnostics.js +12 -2
- package/dist/rsvp/habit-policy.js +7 -0
- package/dist/rsvp/habit-stage.js +19 -4
- package/dist/rsvp/native-habit-runner.js +151 -11
- package/dist/rsvp/outbound-state.js +360 -0
- package/dist/senses/bluebubbles/client.js +90 -17
- package/dist/senses/bluebubbles/inbound-log.js +1 -0
- package/dist/senses/bluebubbles/index.js +662 -425
- package/dist/senses/bluebubbles/model.js +57 -13
- package/dist/senses/bluebubbles/mutation-log.js +21 -10
- package/dist/senses/bluebubbles/reaction-policy.js +55 -0
- package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
- package/dist/senses/cli/tui-store.js +1 -0
- package/dist/senses/cli.js +1 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/pipeline.js +90 -24
- package/dist/senses/private-runtime-worker.js +21 -20
- package/dist/senses/private-runtime.js +79 -29
- package/dist/senses/shared-turn.js +1 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +6 -1
package/dist/rsvp/cli.js
CHANGED
|
@@ -179,13 +179,49 @@ function reportCopyFromHabit(title, rsvp) {
|
|
|
179
179
|
}
|
|
180
180
|
return copy;
|
|
181
181
|
}
|
|
182
|
+
function explicitHabitLifecycleError(habitName, habit, prefix) {
|
|
183
|
+
const degradedReason = habit.status === "degraded" ? habit.degradedReason : null;
|
|
184
|
+
const degradedDetail = habit.status === "degraded" ? habit.degradedDetail : null;
|
|
185
|
+
const reasonText = degradedReason ? ` reason=${degradedReason}` : "";
|
|
186
|
+
const detailText = degradedDetail ? ` detail=${degradedDetail}` : "";
|
|
187
|
+
const message = prefix
|
|
188
|
+
? `${prefix}; status=${habit.status}${reasonText}${detailText}`
|
|
189
|
+
: `RSVP habit ${habitName} lifecycle rejected: status=${habit.status}${reasonText}${detailText}`;
|
|
190
|
+
(0, runtime_1.emitNervesEvent)({
|
|
191
|
+
level: "warn",
|
|
192
|
+
component: "rsvp",
|
|
193
|
+
event: "rsvp.habit_lifecycle_rejected",
|
|
194
|
+
message,
|
|
195
|
+
meta: {
|
|
196
|
+
entryPoint: "explicit_refresh",
|
|
197
|
+
habitName,
|
|
198
|
+
status: habit.status,
|
|
199
|
+
degradedReason,
|
|
200
|
+
degradedDetail,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
return new Error(message);
|
|
204
|
+
}
|
|
182
205
|
function requireRsvpHabitReportCopy(agentRoot, habitName) {
|
|
183
206
|
const habitPath = path.join(agentRoot, "habits", `${habitName}.md`);
|
|
184
|
-
if (!fs.existsSync(habitPath))
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
if (!fs.existsSync(habitPath)) {
|
|
208
|
+
const missing = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error", "", "habit file not found");
|
|
209
|
+
throw explicitHabitLifecycleError(habitName, missing, `RSVP habit ${habitName} not found at ${habitPath}`);
|
|
210
|
+
}
|
|
211
|
+
let habit;
|
|
212
|
+
try {
|
|
213
|
+
habit = (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const unreadable = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error", "", String(error));
|
|
217
|
+
throw explicitHabitLifecycleError(habitName, unreadable);
|
|
218
|
+
}
|
|
219
|
+
if (habit.status !== "active")
|
|
220
|
+
throw explicitHabitLifecycleError(habitName, habit);
|
|
221
|
+
if (!habit.rsvp) {
|
|
222
|
+
const untyped = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "invalid_metadata", habit.body, "RSVP habit metadata is required");
|
|
223
|
+
throw explicitHabitLifecycleError(habitName, untyped, `RSVP habit ${habitName} metadata is required before explicit refresh`);
|
|
224
|
+
}
|
|
189
225
|
return reportCopyFromHabit(habit.title, habit.rsvp);
|
|
190
226
|
}
|
|
191
227
|
function activeRsvpHabitReportCopy(agentRoot) {
|
|
@@ -553,7 +589,7 @@ async function executeHabitStage(command, deps) {
|
|
|
553
589
|
requires: "--agent",
|
|
554
590
|
};
|
|
555
591
|
}
|
|
556
|
-
const result = (0, habit_stage_1.stageRsvpHabit)({
|
|
592
|
+
const result = await (0, habit_stage_1.stageRsvpHabit)({
|
|
557
593
|
agent: command.agent,
|
|
558
594
|
agentRoot,
|
|
559
595
|
...(command.habitName ? { habitName: command.habitName } : {}),
|
|
@@ -566,7 +602,7 @@ async function executeHabitStage(command, deps) {
|
|
|
566
602
|
habit: result,
|
|
567
603
|
});
|
|
568
604
|
}
|
|
569
|
-
async function
|
|
605
|
+
async function executeRefreshInner(command, deps, transportCounter, hardNoSend) {
|
|
570
606
|
const agentRoot = maybeAgentRoot(command, deps);
|
|
571
607
|
if (!command.agent || !agentRoot) {
|
|
572
608
|
return {
|
|
@@ -645,8 +681,9 @@ async function executeRefresh(command, deps) {
|
|
|
645
681
|
const delta = (0, diff_renderer_1.computeRsvpDelta)(previousSnapshot, currentSnapshot);
|
|
646
682
|
const reportText = (0, diff_renderer_1.renderRsvpReport)(delta, reportCopy);
|
|
647
683
|
const outboundDecision = (0, outbound_state_1.decideRsvpOutboundReport)({ agentRoot, currentSnapshot, reportText });
|
|
648
|
-
const
|
|
649
|
-
|
|
684
|
+
const requestedLiveSend = command.mode === "live" && command.allowSend === true && outboundDecision.action === "send";
|
|
685
|
+
const sendAllowedBeforeCutover = requestedLiveSend && !hardNoSend;
|
|
686
|
+
if (sendAllowedBeforeCutover) {
|
|
650
687
|
const cutover = await checkLiveSendCutover({
|
|
651
688
|
agent: command.agent,
|
|
652
689
|
config: configResult.config,
|
|
@@ -663,32 +700,90 @@ async function executeRefresh(command, deps) {
|
|
|
663
700
|
});
|
|
664
701
|
}
|
|
665
702
|
}
|
|
666
|
-
const sendAllowed =
|
|
703
|
+
const sendAllowed = requestedLiveSend && !hardNoSend;
|
|
667
704
|
let delivery;
|
|
705
|
+
let sendBoundary;
|
|
706
|
+
let sendBoundaryMessage;
|
|
668
707
|
if (sendAllowed) {
|
|
708
|
+
if (!command.habitName) {
|
|
709
|
+
return {
|
|
710
|
+
...basePayload(command, false, "RSVP live refresh send requires --habit <name>", {
|
|
711
|
+
allowSend: true,
|
|
712
|
+
sendAllowed: false,
|
|
713
|
+
requires: "--habit <name>",
|
|
714
|
+
refresh: {
|
|
715
|
+
snapshotId: currentSnapshot.snapshotId,
|
|
716
|
+
reportText,
|
|
717
|
+
outboundDecision: outboundDecision,
|
|
718
|
+
},
|
|
719
|
+
}),
|
|
720
|
+
ok: false,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
669
723
|
const bluebubbles = rsvpBlueBubblesClientConfig(configResult.config, machineRuntimeConfig);
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
724
|
+
const client = (0, client_1.createBlueBubblesClient)(bluebubbles.config, bluebubbles.channelConfig);
|
|
725
|
+
sendBoundary = await (0, outbound_state_1.executeRsvpSendBoundary)({
|
|
726
|
+
agentRoot,
|
|
727
|
+
habitId: command.habitName,
|
|
728
|
+
outboundIdempotencyKey: outboundDecision.idempotencyKey,
|
|
729
|
+
noSend: hardNoSend,
|
|
730
|
+
invokeTransport: async (markTransportInvoked) => client.sendText({
|
|
731
|
+
chat: blueBubblesChatFor(configResult.config),
|
|
732
|
+
text: reportText,
|
|
733
|
+
tempGuid: outboundDecision.idempotencyKey,
|
|
734
|
+
onTransportInvocation: () => {
|
|
735
|
+
transportCounter.count += 1;
|
|
736
|
+
markTransportInvoked();
|
|
737
|
+
},
|
|
738
|
+
}),
|
|
739
|
+
}, deps.rsvpSendBoundaryDeps);
|
|
740
|
+
const cleanupFailed = sendBoundary.errorCode === "lifecycle_lock_release_failed";
|
|
741
|
+
const acceptedProof = sendBoundary.boundaryState === "crossed" && !cleanupFailed;
|
|
742
|
+
sendBoundaryMessage = cleanupFailed
|
|
743
|
+
? "RSVP transport crossed but lifecycle lock cleanup failed"
|
|
744
|
+
: sendBoundary.boundaryState === "crossed"
|
|
745
|
+
? "RSVP refresh completed"
|
|
746
|
+
: sendBoundary.boundaryState === "not_crossed"
|
|
747
|
+
? "RSVP transport was definitively rejected"
|
|
748
|
+
: "RSVP transport delivery is unknown";
|
|
749
|
+
delivery = acceptedProof
|
|
750
|
+
? {
|
|
751
|
+
status: "accepted",
|
|
752
|
+
...normalizeDelivery({ messageGuid: sendBoundary.transportResult.messageGuid }),
|
|
753
|
+
}
|
|
754
|
+
: cleanupFailed
|
|
755
|
+
? {
|
|
756
|
+
status: "failed",
|
|
757
|
+
error: sendBoundaryMessage,
|
|
758
|
+
...normalizeDelivery({ messageGuid: sendBoundary.transportResult.messageGuid }),
|
|
759
|
+
}
|
|
760
|
+
: sendBoundary.boundaryState === "not_crossed"
|
|
761
|
+
? { status: "failed", error: sendBoundaryMessage }
|
|
762
|
+
: { status: "pending-manual-verification", error: sendBoundaryMessage };
|
|
676
763
|
(0, outbound_state_1.recordRsvpOutboundAttempt)({
|
|
677
764
|
agentRoot,
|
|
678
765
|
currentSnapshot,
|
|
679
766
|
reportText,
|
|
680
767
|
bluebubblesRecord: {
|
|
681
768
|
recordId: outboundDecision.idempotencyKey,
|
|
682
|
-
status:
|
|
769
|
+
status: acceptedProof
|
|
770
|
+
? "accepted"
|
|
771
|
+
: sendBoundary.boundaryState === "not_crossed" || cleanupFailed
|
|
772
|
+
? "failed"
|
|
773
|
+
: "pending-manual-verification",
|
|
683
774
|
tempGuid: outboundDecision.idempotencyKey,
|
|
684
|
-
...(
|
|
775
|
+
...(sendBoundary.transportResult.messageGuid
|
|
776
|
+
? { messageGuid: sendBoundary.transportResult.messageGuid }
|
|
777
|
+
: {}),
|
|
685
778
|
},
|
|
686
779
|
recordedAt: new Date().toISOString(),
|
|
687
780
|
});
|
|
688
781
|
}
|
|
689
|
-
|
|
690
|
-
|
|
782
|
+
const message = sendBoundaryMessage ?? "RSVP refresh completed";
|
|
783
|
+
const payload = basePayload(command, sendBoundary?.transportInvoked === true && sendBoundary.replayed === false, message, {
|
|
784
|
+
allowSend: command.mode === "live" && command.allowSend === true,
|
|
691
785
|
sendAllowed,
|
|
786
|
+
...(sendBoundary ? { sendBoundary: sendBoundary } : {}),
|
|
692
787
|
refresh: {
|
|
693
788
|
snapshotId: currentSnapshot.snapshotId,
|
|
694
789
|
reportText,
|
|
@@ -696,6 +791,17 @@ async function executeRefresh(command, deps) {
|
|
|
696
791
|
...(delivery ? { delivery } : {}),
|
|
697
792
|
},
|
|
698
793
|
});
|
|
794
|
+
return sendBoundary && !sendBoundary.ok ? { ...payload, ok: false } : payload;
|
|
795
|
+
}
|
|
796
|
+
async function executeRefresh(command, deps) {
|
|
797
|
+
const transportCounter = { count: 0 };
|
|
798
|
+
const hardNoSend = command.noSend === true;
|
|
799
|
+
const payload = await executeRefreshInner(command, deps, transportCounter, hardNoSend);
|
|
800
|
+
return {
|
|
801
|
+
...payload,
|
|
802
|
+
...(hardNoSend ? { noSend: true } : {}),
|
|
803
|
+
transportInvocationCount: transportCounter.count,
|
|
804
|
+
};
|
|
699
805
|
}
|
|
700
806
|
async function executeCompare(command) {
|
|
701
807
|
if (!fs.existsSync(command.nativePath) || !fs.existsSync(command.legacyPath)) {
|
package/dist/rsvp/diagnostics.js
CHANGED
|
@@ -118,15 +118,24 @@ function habitScheduleHealth(agentRoot, deps) {
|
|
|
118
118
|
try {
|
|
119
119
|
const rsvpHabits = deps.readdirSync(habitsDir)
|
|
120
120
|
.filter((name) => name.endsWith(".md") && (0, habit_policy_1.isRsvpHabitName)(name.replace(/\.md$/, "")))
|
|
121
|
+
.sort((left, right) => left.localeCompare(right))
|
|
121
122
|
.map((name) => {
|
|
122
123
|
const filePath = path.join(habitsDir, name);
|
|
123
124
|
return (0, habit_parser_1.parseHabitFile)(deps.readFileSync(filePath), filePath);
|
|
124
125
|
});
|
|
126
|
+
const habitStates = rsvpHabits.map((habit) => ({
|
|
127
|
+
name: habit.name,
|
|
128
|
+
status: habit.status,
|
|
129
|
+
degradedReason: habit.status === "degraded" ? habit.degradedReason : null,
|
|
130
|
+
}));
|
|
131
|
+
if (rsvpHabits.some((habit) => habit.status === "degraded")) {
|
|
132
|
+
return { status: "fail", detail: "RSVP habit definitions degraded", habitStates };
|
|
133
|
+
}
|
|
125
134
|
const active = rsvpHabits.find((habit) => habit.status === "active");
|
|
126
135
|
if (!active)
|
|
127
|
-
return { status: "warn", detail: "no active RSVP habit found" };
|
|
136
|
+
return { status: "warn", detail: "no active RSVP habit found", habitStates };
|
|
128
137
|
if (!active.rsvp)
|
|
129
|
-
return { status: "fail", detail: `activeHabit=${active.name}; typed RSVP habit metadata missing
|
|
138
|
+
return { status: "fail", detail: `activeHabit=${active.name}; typed RSVP habit metadata missing`, habitStates };
|
|
130
139
|
const cadence = active.cadence ?? "unspecified";
|
|
131
140
|
return {
|
|
132
141
|
status: "pass",
|
|
@@ -147,6 +156,7 @@ function habitScheduleHealth(agentRoot, deps) {
|
|
|
147
156
|
outboundStateRef: active.rsvp.outboundStateRef,
|
|
148
157
|
budgetRef: active.rsvp.budgetRef,
|
|
149
158
|
idempotencyRef: active.rsvp.idempotencyRef,
|
|
159
|
+
habitStates,
|
|
150
160
|
};
|
|
151
161
|
}
|
|
152
162
|
catch (error) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RSVP_HABIT_ALLOWED_TOOLS = exports.DEFAULT_RSVP_HABIT_NAME = exports.RSVP_HABIT_POLICY_VERSION = void 0;
|
|
4
4
|
exports.isRsvpHabitName = isRsvpHabitName;
|
|
5
|
+
exports.rsvpHabitMetadataErrorDetail = rsvpHabitMetadataErrorDetail;
|
|
5
6
|
exports.parseRsvpHabitMetadata = parseRsvpHabitMetadata;
|
|
6
7
|
exports.rsvpHabitRuntimePolicy = rsvpHabitRuntimePolicy;
|
|
7
8
|
const runtime_1 = require("../nerves/runtime");
|
|
@@ -35,6 +36,12 @@ function optionalString(record, key) {
|
|
|
35
36
|
function isRsvpHabitName(name) {
|
|
36
37
|
return name.startsWith("rsvp-");
|
|
37
38
|
}
|
|
39
|
+
function rsvpHabitMetadataErrorDetail(error) {
|
|
40
|
+
if (error instanceof Error && error.message.startsWith("RSVP habit metadata ")) {
|
|
41
|
+
return error.message;
|
|
42
|
+
}
|
|
43
|
+
return "RSVP habit metadata is invalid";
|
|
44
|
+
}
|
|
38
45
|
function parseRsvpHabitMetadata(raw) {
|
|
39
46
|
if (raw === undefined || raw === null)
|
|
40
47
|
return null;
|
package/dist/rsvp/habit-stage.js
CHANGED
|
@@ -33,14 +33,24 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RsvpHabitStageError = void 0;
|
|
36
37
|
exports.stageRsvpHabit = stageRsvpHabit;
|
|
37
|
-
const fs = __importStar(require("node:fs"));
|
|
38
38
|
const path = __importStar(require("node:path"));
|
|
39
|
+
const habit_lifecycle_1 = require("../heart/habits/habit-lifecycle");
|
|
39
40
|
const habit_parser_1 = require("../heart/habits/habit-parser");
|
|
40
41
|
const runtime_1 = require("../nerves/runtime");
|
|
41
42
|
const outbound_state_1 = require("./outbound-state");
|
|
42
43
|
const spend_ledger_1 = require("./spend-ledger");
|
|
43
44
|
const habit_policy_1 = require("./habit-policy");
|
|
45
|
+
class RsvpHabitStageError extends Error {
|
|
46
|
+
code;
|
|
47
|
+
constructor(code) {
|
|
48
|
+
super(code);
|
|
49
|
+
this.name = "RsvpHabitStageError";
|
|
50
|
+
this.code = code;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.RsvpHabitStageError = RsvpHabitStageError;
|
|
44
54
|
function defaultRsvpHabitMetadata(mode, reportTitle) {
|
|
45
55
|
return {
|
|
46
56
|
policyVersion: habit_policy_1.RSVP_HABIT_POLICY_VERSION,
|
|
@@ -69,7 +79,7 @@ function normalizeHabitName(name) {
|
|
|
69
79
|
}
|
|
70
80
|
return normalized;
|
|
71
81
|
}
|
|
72
|
-
function stageRsvpHabit(input) {
|
|
82
|
+
async function stageRsvpHabit(input, deps = {}) {
|
|
73
83
|
const habitName = normalizeHabitName(input.habitName);
|
|
74
84
|
const title = input.title?.trim() || "RSVP Updates";
|
|
75
85
|
const rsvp = defaultRsvpHabitMetadata(input.mode, input.reportTitle?.trim() || title);
|
|
@@ -84,8 +94,13 @@ function stageRsvpHabit(input) {
|
|
|
84
94
|
continuity: { mode: "stateful" },
|
|
85
95
|
rsvp,
|
|
86
96
|
}, stagedHabitBody());
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
const publication = (0, habit_lifecycle_1.publishNewHabitDefinition)({
|
|
98
|
+
agentRoot: input.agentRoot,
|
|
99
|
+
habitId: habitName,
|
|
100
|
+
bytes: content,
|
|
101
|
+
}, deps.lifecycle);
|
|
102
|
+
if (publication === "exists")
|
|
103
|
+
throw new RsvpHabitStageError("habit_stage_exists");
|
|
89
104
|
(0, outbound_state_1.ensureRsvpOutboundState)(input.agentRoot, created);
|
|
90
105
|
(0, spend_ledger_1.ensureRsvpSpendLedger)(input.agentRoot, created);
|
|
91
106
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -61,6 +61,47 @@ function nativeRunId(input) {
|
|
|
61
61
|
})).slice(0, 24);
|
|
62
62
|
return `rsvp-${input.trigger}-${digest}`;
|
|
63
63
|
}
|
|
64
|
+
function rejectionErrorCode(habit) {
|
|
65
|
+
return habit.status === "degraded"
|
|
66
|
+
? `habit_${habit.degradedReason}`
|
|
67
|
+
: `habit_status_${habit.status}`;
|
|
68
|
+
}
|
|
69
|
+
function rejectionMessage(habitName, habit) {
|
|
70
|
+
const reason = habit.status === "degraded" ? ` reason=${habit.degradedReason}` : "";
|
|
71
|
+
return `native RSVP habit ${habitName} rejected by lifecycle: status=${habit.status}${reason}`;
|
|
72
|
+
}
|
|
73
|
+
function resolveNativeRsvpHabit(agentRoot, habitPath, habitName) {
|
|
74
|
+
let habit;
|
|
75
|
+
if (!fs.existsSync(habitPath)) {
|
|
76
|
+
habit = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error", "", "habit file not found");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
try {
|
|
80
|
+
habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath));
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
habit = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error", "", String(error));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (habit.status !== "active") {
|
|
87
|
+
return {
|
|
88
|
+
kind: "rejected",
|
|
89
|
+
habit,
|
|
90
|
+
errorCode: rejectionErrorCode(habit),
|
|
91
|
+
message: rejectionMessage(habitName, habit),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (!habit.rsvp) {
|
|
95
|
+
const degraded = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "invalid_metadata", habit.body, "RSVP habit metadata is required");
|
|
96
|
+
return {
|
|
97
|
+
kind: "rejected",
|
|
98
|
+
habit: degraded,
|
|
99
|
+
errorCode: rejectionErrorCode(degraded),
|
|
100
|
+
message: rejectionMessage(habitName, degraded),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { kind: "ready", habit: habit };
|
|
104
|
+
}
|
|
64
105
|
function noopCliDeps(input, agentRoot) {
|
|
65
106
|
return {
|
|
66
107
|
socketPath: "",
|
|
@@ -92,7 +133,8 @@ function refreshCommandFor(input, sendAllowed, mode) {
|
|
|
92
133
|
habitName: input.habitName,
|
|
93
134
|
mode,
|
|
94
135
|
json: true,
|
|
95
|
-
...(sendAllowed ? { allowSend: true } : {
|
|
136
|
+
...(sendAllowed ? { allowSend: true } : {}),
|
|
137
|
+
...(input.noSend || !sendAllowed ? { noSend: true } : {}),
|
|
96
138
|
};
|
|
97
139
|
}
|
|
98
140
|
function permissionEnvelope(sendAllowed) {
|
|
@@ -139,6 +181,16 @@ function surfaceAttemptsFor(payload, requestedSend, errorMessage) {
|
|
|
139
181
|
if (payload.sendAllowed === true) {
|
|
140
182
|
const delivery = (refreshRecord(payload)?.delivery ?? {});
|
|
141
183
|
const rawStatus = stringField(delivery, "status") ?? stringField(delivery, "result");
|
|
184
|
+
if (rawStatus === "pending-manual-verification") {
|
|
185
|
+
return [{
|
|
186
|
+
recipient: "rsvp",
|
|
187
|
+
channel: "bluebubbles",
|
|
188
|
+
reason: "status",
|
|
189
|
+
result: "unavailable",
|
|
190
|
+
rawStatus,
|
|
191
|
+
error: "RSVP transport delivery is unknown",
|
|
192
|
+
}];
|
|
193
|
+
}
|
|
142
194
|
if (rawStatus === "failed" || rawStatus === "error") {
|
|
143
195
|
return [{
|
|
144
196
|
recipient: "rsvp",
|
|
@@ -327,6 +379,8 @@ function recordNativeRunLedger(input) {
|
|
|
327
379
|
...(input.lifecycle === "completed" || input.lifecycle === "error"
|
|
328
380
|
? { usage: (0, run_ledger_1.usageMetadataFromUsageData)(undefined, "none") }
|
|
329
381
|
: {}),
|
|
382
|
+
...(input.errorName ? { errorName: input.errorName } : {}),
|
|
383
|
+
...(input.errorCode ? { errorCode: input.errorCode } : {}),
|
|
330
384
|
target,
|
|
331
385
|
idempotencyScope: target,
|
|
332
386
|
sessionRef: {
|
|
@@ -365,12 +419,6 @@ async function runNativeRsvpHabit(input) {
|
|
|
365
419
|
runId,
|
|
366
420
|
},
|
|
367
421
|
});
|
|
368
|
-
const habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath));
|
|
369
|
-
if (!habit.rsvp)
|
|
370
|
-
throw new Error(`RSVP habit metadata is required before native execution: ${input.habitName}`);
|
|
371
|
-
const policy = (0, habit_policy_1.rsvpHabitRuntimePolicy)(habit.rsvp);
|
|
372
|
-
const command = refreshCommandFor(input, policy.sendAllowed, habit.rsvp.mode);
|
|
373
|
-
const deps = noopCliDeps(input, agentRoot);
|
|
374
422
|
recordNativeRunLedger({
|
|
375
423
|
agentRoot,
|
|
376
424
|
agent: input.agent,
|
|
@@ -381,11 +429,102 @@ async function runNativeRsvpHabit(input) {
|
|
|
381
429
|
startedAt,
|
|
382
430
|
lifecycle: "started",
|
|
383
431
|
});
|
|
432
|
+
const resolution = resolveNativeRsvpHabit(agentRoot, habitPath, input.habitName);
|
|
433
|
+
if (resolution.kind === "rejected") {
|
|
434
|
+
const endedAt = nowIso(input.now);
|
|
435
|
+
const degradedReason = resolution.habit.status === "degraded"
|
|
436
|
+
? resolution.habit.degradedReason
|
|
437
|
+
: null;
|
|
438
|
+
const degradedDetail = resolution.habit.status === "degraded"
|
|
439
|
+
? resolution.habit.degradedDetail
|
|
440
|
+
: null;
|
|
441
|
+
const payload = {
|
|
442
|
+
ok: false,
|
|
443
|
+
command: "rsvp.refresh",
|
|
444
|
+
sideEffect: false,
|
|
445
|
+
agent: input.agent,
|
|
446
|
+
requires: "active RSVP habit",
|
|
447
|
+
message: resolution.message,
|
|
448
|
+
status: resolution.habit.status,
|
|
449
|
+
degradedReason,
|
|
450
|
+
degradedDetail,
|
|
451
|
+
};
|
|
452
|
+
recordNativeRunLedger({
|
|
453
|
+
agentRoot,
|
|
454
|
+
agent: input.agent,
|
|
455
|
+
habitName: input.habitName,
|
|
456
|
+
runId,
|
|
457
|
+
trigger: input.trigger,
|
|
458
|
+
occurrenceId: input.occurrenceId,
|
|
459
|
+
startedAt,
|
|
460
|
+
endedAt,
|
|
461
|
+
lifecycle: "error",
|
|
462
|
+
errorName: "HabitLifecycleRejected",
|
|
463
|
+
errorCode: resolution.errorCode,
|
|
464
|
+
});
|
|
465
|
+
const rejectionMeta = {
|
|
466
|
+
entryPoint: "native_runner",
|
|
467
|
+
agent: input.agent,
|
|
468
|
+
habitName: input.habitName,
|
|
469
|
+
trigger: input.trigger,
|
|
470
|
+
occurrenceId: input.occurrenceId ?? null,
|
|
471
|
+
runId,
|
|
472
|
+
status: resolution.habit.status,
|
|
473
|
+
degradedReason,
|
|
474
|
+
degradedDetail,
|
|
475
|
+
errorCode: resolution.errorCode,
|
|
476
|
+
};
|
|
477
|
+
(0, runtime_1.emitNervesEvent)({
|
|
478
|
+
level: "warn",
|
|
479
|
+
component: "rsvp",
|
|
480
|
+
event: "rsvp.habit_lifecycle_rejected",
|
|
481
|
+
message: resolution.message,
|
|
482
|
+
meta: rejectionMeta,
|
|
483
|
+
});
|
|
484
|
+
(0, runtime_1.emitNervesEvent)({
|
|
485
|
+
level: "error",
|
|
486
|
+
component: "rsvp",
|
|
487
|
+
event: "rsvp.native_habit_error",
|
|
488
|
+
message: resolution.message,
|
|
489
|
+
meta: {
|
|
490
|
+
...rejectionMeta,
|
|
491
|
+
lifecycle: "error",
|
|
492
|
+
receiptWritten: false,
|
|
493
|
+
runtimeStateRecorded: false,
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
ok: false,
|
|
498
|
+
message: resolution.message,
|
|
499
|
+
lifecycle: "error",
|
|
500
|
+
runId,
|
|
501
|
+
payload,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const habit = resolution.habit;
|
|
505
|
+
const policy = (0, habit_policy_1.rsvpHabitRuntimePolicy)(habit.rsvp);
|
|
506
|
+
const effectiveSendAllowed = policy.sendAllowed && input.noSend !== true;
|
|
507
|
+
const command = refreshCommandFor(input, policy.sendAllowed, habit.rsvp.mode);
|
|
508
|
+
const deps = noopCliDeps(input, agentRoot);
|
|
384
509
|
let payload;
|
|
385
510
|
let errorMessage = null;
|
|
386
511
|
try {
|
|
387
512
|
payload = parseRefreshPayload(await (input.runRefresh ?? defaultRunRefresh)(command, deps));
|
|
513
|
+
payload = { ...payload, status: habit.status };
|
|
388
514
|
errorMessage = payloadError(payload);
|
|
515
|
+
if (input.noSend === true
|
|
516
|
+
&& (payload.noSend !== true
|
|
517
|
+
|| payload.transportInvocationCount !== 0
|
|
518
|
+
|| payload.sendAllowed === true
|
|
519
|
+
|| payload.sideEffect === true)) {
|
|
520
|
+
errorMessage = "RSVP refresh violated the immutable no-send result contract";
|
|
521
|
+
payload = {
|
|
522
|
+
...payload,
|
|
523
|
+
ok: false,
|
|
524
|
+
status: habit.status,
|
|
525
|
+
message: errorMessage,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
389
528
|
}
|
|
390
529
|
catch (error) {
|
|
391
530
|
payload = {
|
|
@@ -394,13 +533,14 @@ async function runNativeRsvpHabit(input) {
|
|
|
394
533
|
sideEffect: false,
|
|
395
534
|
agent: input.agent,
|
|
396
535
|
mode: habit.rsvp.mode,
|
|
536
|
+
status: habit.status,
|
|
397
537
|
message: error instanceof Error ? error.message : String(error),
|
|
398
538
|
};
|
|
399
539
|
errorMessage = stringField(payload, "message") ?? "RSVP refresh failed";
|
|
400
540
|
}
|
|
401
541
|
const endedAt = nowIso(input.now);
|
|
402
542
|
const lifecycle = errorMessage ? "error" : "completed";
|
|
403
|
-
const surfaceAttempts = surfaceAttemptsFor(payload,
|
|
543
|
+
const surfaceAttempts = surfaceAttemptsFor(payload, effectiveSendAllowed, errorMessage);
|
|
404
544
|
const errors = errorMessage ? [errorMessage] : [];
|
|
405
545
|
const producedRefs = producedRefsFor(payload);
|
|
406
546
|
recordNativeRunLedger({
|
|
@@ -419,7 +559,7 @@ async function runNativeRsvpHabit(input) {
|
|
|
419
559
|
trigger: input.trigger,
|
|
420
560
|
mode: habit.rsvp.mode,
|
|
421
561
|
source: habit.rsvp.source,
|
|
422
|
-
sendAllowed:
|
|
562
|
+
sendAllowed: effectiveSendAllowed,
|
|
423
563
|
startedAt,
|
|
424
564
|
endedAt,
|
|
425
565
|
payload,
|
|
@@ -436,8 +576,8 @@ async function runNativeRsvpHabit(input) {
|
|
|
436
576
|
startedAt,
|
|
437
577
|
endedAt,
|
|
438
578
|
operationId: `rsvp-native:${input.trigger}:${input.occurrenceId ?? runId}`,
|
|
439
|
-
permissionEnvelope: permissionEnvelope(
|
|
440
|
-
toolPolicy: toolPolicy(
|
|
579
|
+
permissionEnvelope: permissionEnvelope(effectiveSendAllowed),
|
|
580
|
+
toolPolicy: toolPolicy(effectiveSendAllowed),
|
|
441
581
|
producedRefs,
|
|
442
582
|
surfaceAttempts,
|
|
443
583
|
traceSteps,
|