@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.
- package/changelog.json +12 -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
|
@@ -488,6 +488,13 @@ function isValidHabitPokeCommand(command) {
|
|
|
488
488
|
&& typeof command.habitName === "string"
|
|
489
489
|
&& command.habitName.trim().length > 0;
|
|
490
490
|
}
|
|
491
|
+
function isValidHabitProbeCommand(command) {
|
|
492
|
+
return typeof command.agent === "string"
|
|
493
|
+
&& command.agent.trim().length > 0
|
|
494
|
+
&& typeof command.habitName === "string"
|
|
495
|
+
&& command.habitName.trim().length > 0
|
|
496
|
+
&& (command.noSend === undefined || command.noSend === true);
|
|
497
|
+
}
|
|
491
498
|
function taskIdFromTaskPokePrivateWakeCommand(command) {
|
|
492
499
|
if (command.kind !== "private.wake" || command.triggerSource !== "task-poke")
|
|
493
500
|
return null;
|
|
@@ -1162,6 +1169,10 @@ class OuroDaemon {
|
|
|
1162
1169
|
originRefs: [{ kind: "daemon-command", id: "inner.wake" }],
|
|
1163
1170
|
};
|
|
1164
1171
|
}
|
|
1172
|
+
const hasNoSendCapability = command.originRefs?.some((ref) => ref.kind === "capability" && ref.id === "no-send") ?? false;
|
|
1173
|
+
if ((command.noSend === true) !== hasNoSendCapability) {
|
|
1174
|
+
throw new Error("private-runtime no-send capability does not match its bound origin reference");
|
|
1175
|
+
}
|
|
1165
1176
|
return {
|
|
1166
1177
|
agent: command.agent,
|
|
1167
1178
|
origin: "daemon.private.wake",
|
|
@@ -1210,37 +1221,110 @@ class OuroDaemon {
|
|
|
1210
1221
|
],
|
|
1211
1222
|
};
|
|
1212
1223
|
}
|
|
1213
|
-
|
|
1214
|
-
const agentRoot = path.join(this.bundlesRoot, `${
|
|
1215
|
-
const habitPath = path.join(agentRoot, "habits", `${
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1224
|
+
resolveHabitDispatch(options) {
|
|
1225
|
+
const agentRoot = path.join(this.bundlesRoot, `${options.agent}.ouro`);
|
|
1226
|
+
const habitPath = path.join(agentRoot, "habits", `${options.habitName}.md`);
|
|
1227
|
+
const isRsvp = (0, habit_policy_1.isRsvpHabitName)(options.habitName);
|
|
1228
|
+
let readFailure = null;
|
|
1229
|
+
let habit;
|
|
1230
|
+
if (!fs.existsSync(habitPath)) {
|
|
1231
|
+
readFailure = "missing";
|
|
1232
|
+
habit = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error", "", "habit file not found");
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1220
1235
|
try {
|
|
1221
|
-
|
|
1236
|
+
habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath));
|
|
1222
1237
|
}
|
|
1223
1238
|
catch (error) {
|
|
1224
|
-
|
|
1239
|
+
readFailure = "read";
|
|
1240
|
+
habit = (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error", "", messageFromHabitPokeError(error));
|
|
1225
1241
|
}
|
|
1226
|
-
if (!parsedHabit.rsvp)
|
|
1227
|
-
return { skipReason: "RSVP habit metadata is required before private runtime wake" };
|
|
1228
1242
|
}
|
|
1229
|
-
if (
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1243
|
+
if (habit.status !== "active") {
|
|
1244
|
+
let skipReason = `habit status ${habit.status} is non-executable`;
|
|
1245
|
+
if (isRsvp && habit.status === "degraded") {
|
|
1246
|
+
if (readFailure === "missing") {
|
|
1247
|
+
skipReason = "RSVP habit file not found";
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
skipReason = `RSVP habit metadata invalid: ${habit.degradedDetail ?? habit.degradedReason}`;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
else if (readFailure === "missing"
|
|
1254
|
+
&& options.source === "habit-poke"
|
|
1255
|
+
&& options.trigger !== "manual"
|
|
1256
|
+
&& options.trigger !== "poke") {
|
|
1257
|
+
skipReason = "habit file not found";
|
|
1258
|
+
}
|
|
1259
|
+
return { kind: "skipped", habit, skipReason };
|
|
1260
|
+
}
|
|
1261
|
+
if (isRsvp && !habit.rsvp) {
|
|
1262
|
+
return {
|
|
1263
|
+
kind: "skipped",
|
|
1264
|
+
habit,
|
|
1265
|
+
skipReason: "RSVP habit metadata is required before private runtime wake",
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
if (options.trigger === "poke" || options.trigger === "manual") {
|
|
1269
|
+
return { kind: "ready", habit };
|
|
1270
|
+
}
|
|
1271
|
+
if (typeof options.occurrenceId === "string" && options.occurrenceId.trim().length > 0) {
|
|
1272
|
+
return { kind: "ready", habit, occurrenceId: options.occurrenceId.trim() };
|
|
1273
|
+
}
|
|
1274
|
+
if (habit.cadence === null) {
|
|
1275
|
+
return { kind: "skipped", habit, skipReason: "habit has no cadence" };
|
|
1276
|
+
}
|
|
1277
|
+
if (habit.lastRun === null) {
|
|
1278
|
+
return { kind: "ready", habit, occurrenceId: `${options.trigger}:first-run:${habit.cadence}` };
|
|
1279
|
+
}
|
|
1280
|
+
return {
|
|
1281
|
+
kind: "ready",
|
|
1282
|
+
habit,
|
|
1283
|
+
occurrenceId: `${options.trigger}:last-run:${habit.lastRun}:cadence:${habit.cadence}`,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
emitHabitDispatchSkipped(options) {
|
|
1287
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1288
|
+
level: "warn",
|
|
1289
|
+
component: "daemon",
|
|
1290
|
+
event: "daemon.habit_dispatch_skipped",
|
|
1291
|
+
message: "habit dispatch rejected by current lifecycle state",
|
|
1292
|
+
meta: {
|
|
1293
|
+
agent: options.agent,
|
|
1294
|
+
habitName: options.habitName,
|
|
1295
|
+
trigger: options.trigger,
|
|
1296
|
+
status: options.resolution.habit.status,
|
|
1297
|
+
degradedReason: options.resolution.habit.status === "degraded"
|
|
1298
|
+
? options.resolution.habit.degradedReason
|
|
1299
|
+
: null,
|
|
1300
|
+
detail: options.resolution.habit.status === "degraded"
|
|
1301
|
+
? options.resolution.habit.degradedDetail
|
|
1302
|
+
: null,
|
|
1303
|
+
reason: options.resolution.skipReason,
|
|
1304
|
+
},
|
|
1305
|
+
});
|
|
1242
1306
|
}
|
|
1243
|
-
|
|
1307
|
+
emitHabitDispatchReady(options) {
|
|
1308
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1309
|
+
component: "daemon",
|
|
1310
|
+
event: "daemon.habit_dispatch_ready",
|
|
1311
|
+
message: "habit dispatch accepted by current lifecycle state",
|
|
1312
|
+
meta: {
|
|
1313
|
+
agent: options.agent,
|
|
1314
|
+
habitName: options.habitName,
|
|
1315
|
+
trigger: options.trigger,
|
|
1316
|
+
status: options.resolution.habit.status,
|
|
1317
|
+
occurrenceId: options.resolution.occurrenceId ?? null,
|
|
1318
|
+
},
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
skippedHabitDispatchResponse(options) {
|
|
1322
|
+
return {
|
|
1323
|
+
ok: true,
|
|
1324
|
+
message: `skipped scheduled habit ${options.habitName} for ${options.agent}: ${options.skipReason}`,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
async runNativeRsvpHabit(command, trigger, occurrenceId, noSend = false) {
|
|
1244
1328
|
const runner = this.rsvpHabitRunner ?? (async (input) => {
|
|
1245
1329
|
const { runNativeRsvpHabit } = await Promise.resolve().then(() => __importStar(require("../../rsvp/native-habit-runner")));
|
|
1246
1330
|
return runNativeRsvpHabit(input);
|
|
@@ -1251,7 +1335,48 @@ class OuroDaemon {
|
|
|
1251
1335
|
habitName: command.habitName,
|
|
1252
1336
|
trigger,
|
|
1253
1337
|
...(occurrenceId ? { occurrenceId } : {}),
|
|
1338
|
+
...(noSend ? { noSend: true } : {}),
|
|
1254
1339
|
});
|
|
1340
|
+
if (noSend) {
|
|
1341
|
+
const payload = result.payload;
|
|
1342
|
+
const status = payload?.status;
|
|
1343
|
+
const transportInvocationCount = payload?.transportInvocationCount;
|
|
1344
|
+
const contractError = "native RSVP habit probe violated the immutable no-send result contract";
|
|
1345
|
+
if (!result.ok) {
|
|
1346
|
+
return {
|
|
1347
|
+
ok: false,
|
|
1348
|
+
error: result.message,
|
|
1349
|
+
data: {
|
|
1350
|
+
ok: false,
|
|
1351
|
+
noSend: true,
|
|
1352
|
+
status: typeof status === "string" ? status : "degraded",
|
|
1353
|
+
transportInvocationCount: null,
|
|
1354
|
+
error: result.message,
|
|
1355
|
+
},
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
if (payload?.noSend !== true
|
|
1359
|
+
|| typeof status !== "string"
|
|
1360
|
+
|| typeof transportInvocationCount !== "number"
|
|
1361
|
+
|| transportInvocationCount !== 0) {
|
|
1362
|
+
return {
|
|
1363
|
+
ok: false,
|
|
1364
|
+
error: contractError,
|
|
1365
|
+
data: {
|
|
1366
|
+
ok: false,
|
|
1367
|
+
noSend: true,
|
|
1368
|
+
status: typeof status === "string" ? status : "degraded",
|
|
1369
|
+
transportInvocationCount: null,
|
|
1370
|
+
error: contractError,
|
|
1371
|
+
},
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
ok: true,
|
|
1376
|
+
message: result.message,
|
|
1377
|
+
data: { noSend: true, status, transportInvocationCount: 0 },
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1255
1380
|
return {
|
|
1256
1381
|
ok: result.ok,
|
|
1257
1382
|
message: result.message,
|
|
@@ -1281,6 +1406,7 @@ class OuroDaemon {
|
|
|
1281
1406
|
type: "habit",
|
|
1282
1407
|
habitName: habitMessage.habitName,
|
|
1283
1408
|
trigger: habitMessage.trigger,
|
|
1409
|
+
...(habitMessage.noSend ? { noSend: true } : {}),
|
|
1284
1410
|
privateTurnDecision: decision,
|
|
1285
1411
|
};
|
|
1286
1412
|
}
|
|
@@ -1616,8 +1742,43 @@ class OuroDaemon {
|
|
|
1616
1742
|
}
|
|
1617
1743
|
case "inner.wake":
|
|
1618
1744
|
return this.handlePrivateRuntimeWake(command);
|
|
1619
|
-
case "private.wake":
|
|
1745
|
+
case "private.wake": {
|
|
1746
|
+
const scheduledHabit = (0, habit_private_wake_1.habitMessageFromPrivateWakeCommand)(command);
|
|
1747
|
+
if (!scheduledHabit || !this.hasManagedPrivateRuntime(command.agent)) {
|
|
1748
|
+
return this.handlePrivateRuntimeWake(command);
|
|
1749
|
+
}
|
|
1750
|
+
const occurrenceRef = command.originRefs?.find((ref) => ref.kind === "habit-occurrence");
|
|
1751
|
+
const occurrenceId = typeof occurrenceRef?.id === "string" && occurrenceRef.id.trim().length > 0
|
|
1752
|
+
? occurrenceRef.id.trim()
|
|
1753
|
+
: undefined;
|
|
1754
|
+
const resolution = this.resolveHabitDispatch({
|
|
1755
|
+
agent: command.agent,
|
|
1756
|
+
habitName: scheduledHabit.habitName,
|
|
1757
|
+
trigger: scheduledHabit.trigger,
|
|
1758
|
+
source: "private-wake",
|
|
1759
|
+
...(occurrenceId ? { occurrenceId } : {}),
|
|
1760
|
+
});
|
|
1761
|
+
if (resolution.kind === "skipped") {
|
|
1762
|
+
this.emitHabitDispatchSkipped({
|
|
1763
|
+
agent: command.agent,
|
|
1764
|
+
habitName: scheduledHabit.habitName,
|
|
1765
|
+
trigger: scheduledHabit.trigger,
|
|
1766
|
+
resolution,
|
|
1767
|
+
});
|
|
1768
|
+
return this.skippedHabitDispatchResponse({
|
|
1769
|
+
agent: command.agent,
|
|
1770
|
+
habitName: scheduledHabit.habitName,
|
|
1771
|
+
skipReason: resolution.skipReason,
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
this.emitHabitDispatchReady({
|
|
1775
|
+
agent: command.agent,
|
|
1776
|
+
habitName: scheduledHabit.habitName,
|
|
1777
|
+
trigger: scheduledHabit.trigger,
|
|
1778
|
+
resolution,
|
|
1779
|
+
});
|
|
1620
1780
|
return this.handlePrivateRuntimeWake(command);
|
|
1781
|
+
}
|
|
1621
1782
|
case "chat.connect":
|
|
1622
1783
|
await this.processManager.startAgent(command.agent);
|
|
1623
1784
|
return {
|
|
@@ -1663,23 +1824,115 @@ class OuroDaemon {
|
|
|
1663
1824
|
error: `No managed agent '${command.agent}' is registered with daemon-managed private runtime.`,
|
|
1664
1825
|
};
|
|
1665
1826
|
}
|
|
1666
|
-
const
|
|
1667
|
-
|
|
1827
|
+
const resolution = this.resolveHabitDispatch({
|
|
1828
|
+
agent: command.agent,
|
|
1829
|
+
habitName: command.habitName,
|
|
1830
|
+
trigger,
|
|
1831
|
+
source: "habit-poke",
|
|
1832
|
+
...(command.occurrenceId ? { occurrenceId: command.occurrenceId } : {}),
|
|
1833
|
+
});
|
|
1834
|
+
if (resolution.kind === "skipped") {
|
|
1835
|
+
this.emitHabitDispatchSkipped({
|
|
1836
|
+
agent: command.agent,
|
|
1837
|
+
habitName: command.habitName,
|
|
1838
|
+
trigger,
|
|
1839
|
+
resolution,
|
|
1840
|
+
});
|
|
1841
|
+
return this.skippedHabitDispatchResponse({
|
|
1842
|
+
agent: command.agent,
|
|
1843
|
+
habitName: command.habitName,
|
|
1844
|
+
skipReason: resolution.skipReason,
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
this.emitHabitDispatchReady({
|
|
1848
|
+
agent: command.agent,
|
|
1849
|
+
habitName: command.habitName,
|
|
1850
|
+
trigger,
|
|
1851
|
+
resolution,
|
|
1852
|
+
});
|
|
1853
|
+
if ((0, habit_policy_1.isRsvpHabitName)(command.habitName)) {
|
|
1854
|
+
return this.runNativeRsvpHabit(command, trigger, resolution.occurrenceId);
|
|
1855
|
+
}
|
|
1856
|
+
return this.handlePrivateRuntimeWake((0, habit_private_wake_1.buildHabitPrivateWakeCommand)({
|
|
1857
|
+
agent: command.agent,
|
|
1858
|
+
habitName: command.habitName,
|
|
1859
|
+
trigger,
|
|
1860
|
+
sourceRef: { kind: "daemon-command", id: "habit.poke" },
|
|
1861
|
+
occurrenceId: resolution.occurrenceId,
|
|
1862
|
+
}));
|
|
1863
|
+
}
|
|
1864
|
+
case "habit.probe": {
|
|
1865
|
+
if (!isValidHabitProbeCommand(command)) {
|
|
1866
|
+
return {
|
|
1867
|
+
ok: false,
|
|
1868
|
+
error: "Invalid habit.probe payload: expected non-empty agent/habitName and noSend=true.",
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
const trigger = "manual";
|
|
1872
|
+
if (!(0, habit_policy_1.isRsvpHabitName)(command.habitName) && !this.hasManagedPrivateRuntime(command.agent)) {
|
|
1873
|
+
return {
|
|
1874
|
+
ok: false,
|
|
1875
|
+
error: `No managed agent '${command.agent}' is registered with daemon-managed private runtime.`,
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
const resolution = this.resolveHabitDispatch({
|
|
1879
|
+
agent: command.agent,
|
|
1880
|
+
habitName: command.habitName,
|
|
1881
|
+
trigger,
|
|
1882
|
+
source: "habit-probe",
|
|
1883
|
+
});
|
|
1884
|
+
if (resolution.kind === "skipped") {
|
|
1885
|
+
this.emitHabitDispatchSkipped({
|
|
1886
|
+
agent: command.agent,
|
|
1887
|
+
habitName: command.habitName,
|
|
1888
|
+
trigger,
|
|
1889
|
+
resolution,
|
|
1890
|
+
});
|
|
1668
1891
|
return {
|
|
1669
1892
|
ok: true,
|
|
1670
|
-
message: `
|
|
1893
|
+
message: `habit probe rejected by lifecycle: status=${resolution.habit.status}`,
|
|
1894
|
+
data: {
|
|
1895
|
+
noSend: true,
|
|
1896
|
+
status: resolution.habit.status,
|
|
1897
|
+
transportInvocationCount: 0,
|
|
1898
|
+
},
|
|
1671
1899
|
};
|
|
1672
1900
|
}
|
|
1901
|
+
this.emitHabitDispatchReady({
|
|
1902
|
+
agent: command.agent,
|
|
1903
|
+
habitName: command.habitName,
|
|
1904
|
+
trigger,
|
|
1905
|
+
resolution,
|
|
1906
|
+
});
|
|
1673
1907
|
if ((0, habit_policy_1.isRsvpHabitName)(command.habitName)) {
|
|
1674
|
-
return this.runNativeRsvpHabit(command, trigger, occurrenceId);
|
|
1908
|
+
return this.runNativeRsvpHabit(command, trigger, resolution.occurrenceId, true);
|
|
1675
1909
|
}
|
|
1676
|
-
|
|
1910
|
+
const wake = await this.handlePrivateRuntimeWake((0, habit_private_wake_1.buildHabitPrivateWakeCommand)({
|
|
1677
1911
|
agent: command.agent,
|
|
1678
1912
|
habitName: command.habitName,
|
|
1679
1913
|
trigger,
|
|
1680
|
-
sourceRef: { kind: "daemon-command", id: "habit.
|
|
1681
|
-
occurrenceId,
|
|
1914
|
+
sourceRef: { kind: "daemon-command", id: "habit.probe" },
|
|
1915
|
+
occurrenceId: resolution.occurrenceId,
|
|
1916
|
+
noSend: true,
|
|
1682
1917
|
}));
|
|
1918
|
+
if (!wake.ok)
|
|
1919
|
+
return wake;
|
|
1920
|
+
const denied = wake.data
|
|
1921
|
+
?.decision?.executable === false;
|
|
1922
|
+
const error = denied
|
|
1923
|
+
? "non-RSVP habit probe was not executed because private-runtime policy denied execution"
|
|
1924
|
+
: "non-RSVP habit probe completion is asynchronous; zero-transport evidence is unverified";
|
|
1925
|
+
return {
|
|
1926
|
+
ok: false,
|
|
1927
|
+
error,
|
|
1928
|
+
data: {
|
|
1929
|
+
ok: false,
|
|
1930
|
+
noSend: true,
|
|
1931
|
+
status: resolution.habit.status,
|
|
1932
|
+
transportInvocationCount: null,
|
|
1933
|
+
error,
|
|
1934
|
+
},
|
|
1935
|
+
};
|
|
1683
1936
|
}
|
|
1684
1937
|
case "await.poke": {
|
|
1685
1938
|
return this.handlePrivateRuntimeWake(this.buildAwaitPrivateWakeCommand(command));
|
|
@@ -503,19 +503,34 @@ function checkHabits(deps) {
|
|
|
503
503
|
}
|
|
504
504
|
checks.push({ label: `${agentDir} habits dir`, status: "pass", detail: habitsDir });
|
|
505
505
|
const activeScheduledHabits = [];
|
|
506
|
+
const habitLifecycleRows = [];
|
|
506
507
|
let unreadableHabits = 0;
|
|
507
|
-
for (const file of deps.readdirSync(habitsDir).filter((entry) => entry.endsWith(".md"))) {
|
|
508
|
+
for (const file of deps.readdirSync(habitsDir).filter((entry) => entry.endsWith(".md") && entry !== "README.md").sort()) {
|
|
508
509
|
const filePath = `${habitsDir}/${file}`;
|
|
510
|
+
let habit;
|
|
509
511
|
try {
|
|
510
|
-
|
|
511
|
-
if (habit.status === "active" && habit.cadence && (0, cadence_1.parseCadenceToCron)(habit.cadence) !== null) {
|
|
512
|
-
activeScheduledHabits.push(habit.name);
|
|
513
|
-
}
|
|
512
|
+
habit = (0, habit_parser_1.parseHabitFile)(deps.readFileSync(filePath), filePath);
|
|
514
513
|
}
|
|
515
|
-
catch {
|
|
514
|
+
catch (error) {
|
|
516
515
|
unreadableHabits += 1;
|
|
516
|
+
habit = (0, habit_parser_1.createDegradedHabitFile)(filePath, "read_error", "", error instanceof Error ? error.message : String(error));
|
|
517
|
+
}
|
|
518
|
+
habitLifecycleRows.push(habit);
|
|
519
|
+
if (habit.status === "active" && habit.cadence && (0, cadence_1.parseCadenceToCron)(habit.cadence) !== null) {
|
|
520
|
+
activeScheduledHabits.push(habit.name);
|
|
517
521
|
}
|
|
518
522
|
}
|
|
523
|
+
if (habitLifecycleRows.length > 0) {
|
|
524
|
+
const hasDegradedHabit = habitLifecycleRows.some((habit) => habit.status === "degraded");
|
|
525
|
+
checks.push({
|
|
526
|
+
id: "habits.lifecycle",
|
|
527
|
+
label: `${agentDir} habit lifecycle`,
|
|
528
|
+
status: hasDegradedHabit ? "fail" : "pass",
|
|
529
|
+
detail: habitLifecycleRows.map((habit) => habit.status === "degraded"
|
|
530
|
+
? `${habit.name}=degraded(${habit.degradedReason})`
|
|
531
|
+
: `${habit.name}=${habit.status}`).join(", "),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
519
534
|
if (unreadableHabits > 0) {
|
|
520
535
|
checks.push({
|
|
521
536
|
label: `${agentDir} habit files`,
|
|
@@ -32,7 +32,9 @@ function buildHabitPrivateWakeCommand(options) {
|
|
|
32
32
|
{ kind: "habit-trigger", id: options.trigger },
|
|
33
33
|
{ kind: "habit-occurrence", id: occurrenceId },
|
|
34
34
|
options.sourceRef,
|
|
35
|
+
...(options.noSend ? [{ kind: "capability", id: "no-send" }] : []),
|
|
35
36
|
],
|
|
37
|
+
...(options.noSend ? { noSend: true } : {}),
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
function habitMessageFromPrivateWakeCommand(command) {
|
|
@@ -42,8 +44,11 @@ function habitMessageFromPrivateWakeCommand(command) {
|
|
|
42
44
|
return null;
|
|
43
45
|
const habitRef = command.originRefs?.find((ref) => ref.kind === "habit" && typeof ref.id === "string");
|
|
44
46
|
const triggerRef = command.originRefs?.find((ref) => ref.kind === "habit-trigger" && typeof ref.id === "string");
|
|
47
|
+
const noSendRef = command.originRefs?.some((ref) => ref.kind === "capability" && ref.id === "no-send") ?? false;
|
|
45
48
|
if (!habitRef || typeof habitRef.id !== "string" || !triggerRef || typeof triggerRef.id !== "string")
|
|
46
49
|
return null;
|
|
50
|
+
if ((command.noSend === true) !== noSendRef)
|
|
51
|
+
return null;
|
|
47
52
|
const habitName = habitRef.id.trim();
|
|
48
53
|
const trigger = triggerRef.id.trim();
|
|
49
54
|
if (habitName.length === 0)
|
|
@@ -52,5 +57,5 @@ function habitMessageFromPrivateWakeCommand(command) {
|
|
|
52
57
|
return null;
|
|
53
58
|
if (command.triggerSource !== `habit-${trigger}`)
|
|
54
59
|
return null;
|
|
55
|
-
return { habitName, trigger };
|
|
60
|
+
return { habitName, trigger, ...(noSendRef ? { noSend: true } : {}) };
|
|
56
61
|
}
|