@ouro.bot/cli 0.1.0-alpha.723 → 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 +295 -275
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +132 -15
- 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 +82 -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/mail.js +129 -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/senses/cli.js
CHANGED
|
@@ -311,6 +311,7 @@ function createCliCallbacks() {
|
|
|
311
311
|
const streamer = new MarkdownStreamer();
|
|
312
312
|
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
313
313
|
return {
|
|
314
|
+
settleOutputMode: "final_only",
|
|
314
315
|
onModelStart: () => {
|
|
315
316
|
currentSpinner?.stop();
|
|
316
317
|
setSpinner(null);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.privateRuntimeHabitRejectionReason = privateRuntimeHabitRejectionReason;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
function privateRuntimeHabitRejectionReason(habit, boundary) {
|
|
6
|
+
if (habit.status === "active")
|
|
7
|
+
return null;
|
|
8
|
+
const reason = `habit status ${habit.status} is non-executable before private runtime execution`;
|
|
9
|
+
(0, runtime_1.emitNervesEvent)({
|
|
10
|
+
level: "warn",
|
|
11
|
+
component: "senses",
|
|
12
|
+
event: "senses.habit_lifecycle_rejected",
|
|
13
|
+
message: "habit lifecycle state rejected before private runtime execution",
|
|
14
|
+
meta: {
|
|
15
|
+
boundary,
|
|
16
|
+
habitName: habit.name,
|
|
17
|
+
status: habit.status,
|
|
18
|
+
degradedReason: habit.status === "degraded" ? habit.degradedReason : null,
|
|
19
|
+
detail: habit.status === "degraded" ? habit.degradedDetail : null,
|
|
20
|
+
reason,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return reason;
|
|
24
|
+
}
|
package/dist/senses/mail.js
CHANGED
|
@@ -50,6 +50,125 @@ const smtp_ingress_1 = require("../mailroom/smtp-ingress");
|
|
|
50
50
|
function readRegistry(registryPath) {
|
|
51
51
|
return JSON.parse(fs.readFileSync(registryPath, "utf-8"));
|
|
52
52
|
}
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
function unverifiableCoverage(checkedAt, reason) {
|
|
57
|
+
return { status: "could-not-verify", declaredKeyIds: [], absentKeyIds: [], reason, checkedAt };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Key ids the registry says mail can currently be encrypted to for this agent.
|
|
61
|
+
*
|
|
62
|
+
* Filtered to this agent because the hosted registry is domain-wide: counting
|
|
63
|
+
* every agent's records would report another agent's keys as absent on every
|
|
64
|
+
* cycle, and a check that always fails is a check operators learn to ignore.
|
|
65
|
+
*
|
|
66
|
+
* The match is case-insensitive. The harness sends `agentId: agent` verbatim to
|
|
67
|
+
* hosted Mail Control while this repo's own hosted ensure-response fixture
|
|
68
|
+
* returns the lowercased form for that same request, so a registry record's
|
|
69
|
+
* agent id can differ from the sense's agent name by case alone. Case-sensitive
|
|
70
|
+
* matching would then declare nothing and report `could-not-verify` forever —
|
|
71
|
+
* the same silence this probe exists to end. Widening the match cannot produce
|
|
72
|
+
* a false `absent` unless two agents differ only by case, which the store and
|
|
73
|
+
* address resolvers could not tell apart either.
|
|
74
|
+
*
|
|
75
|
+
* Disabled source grants are excluded because `resolveMailAddress`
|
|
76
|
+
* (`mailroom/core.ts`) refuses their alias outright, so no new mail can be
|
|
77
|
+
* encrypted to their key and a missing private half is not a live gap.
|
|
78
|
+
*/
|
|
79
|
+
function registryDeclaredKeyIds(registry, agentName) {
|
|
80
|
+
const wanted = agentName.toLowerCase();
|
|
81
|
+
const isThisAgent = (agentId) => agentId.toLowerCase() === wanted;
|
|
82
|
+
const declared = [
|
|
83
|
+
...registry.mailboxes.filter((mailbox) => isThisAgent(mailbox.agentId)).map((mailbox) => mailbox.keyId),
|
|
84
|
+
...registry.sourceGrants.filter((grant) => isThisAgent(grant.agentId) && grant.enabled).map((grant) => grant.keyId),
|
|
85
|
+
];
|
|
86
|
+
return [...new Set(declared.filter((keyId) => keyId.trim().length > 0))];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Mail key ids the vault can actually decrypt with, read fresh rather than
|
|
90
|
+
* taken from the reader resolution captured at sense startup — a sense that has
|
|
91
|
+
* been up for days would otherwise measure a key set the vault has since
|
|
92
|
+
* changed, and report a rotation as a loss.
|
|
93
|
+
*
|
|
94
|
+
* Throws rather than returning empty when the read is untrustworthy, so the
|
|
95
|
+
* caller classifies it `could-not-verify` instead of `absent`. An empty-but-
|
|
96
|
+
* present `privateKeys` record is a real observation and is not a throw.
|
|
97
|
+
*/
|
|
98
|
+
function vaultMailKeyIds(agentName) {
|
|
99
|
+
const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agentName);
|
|
100
|
+
if (!runtime.ok)
|
|
101
|
+
throw new Error(`vault ${runtime.itemPath} read is ${runtime.reason}`);
|
|
102
|
+
const mailroom = runtime.config.mailroom;
|
|
103
|
+
const privateKeys = isRecord(mailroom) ? mailroom.privateKeys : undefined;
|
|
104
|
+
if (!isRecord(privateKeys))
|
|
105
|
+
throw new Error(`vault ${runtime.itemPath} has no mailroom.privateKeys record`);
|
|
106
|
+
return Object.entries(privateKeys)
|
|
107
|
+
.filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0)
|
|
108
|
+
.map((entry) => entry[0]);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Never throws: every failure degrades to `could-not-verify`. `scan()` is
|
|
112
|
+
* awaited during sense startup, so a throw here would stop the mail sense from
|
|
113
|
+
* starting — an integrity probe must not be able to take mail down.
|
|
114
|
+
*/
|
|
115
|
+
async function computeMailKeyCoverage(input) {
|
|
116
|
+
try {
|
|
117
|
+
const registry = await input.readRegistry(input.config);
|
|
118
|
+
const declaredKeyIds = registryDeclaredKeyIds(registry, input.agentName);
|
|
119
|
+
if (declaredKeyIds.length === 0) {
|
|
120
|
+
// Nothing to check against is not the same as nothing wrong: reporting
|
|
121
|
+
// `covered` for an empty declaration would turn an unprovisioned or
|
|
122
|
+
// wrong registry into a green light.
|
|
123
|
+
return unverifiableCoverage(input.checkedAt, `mailroom registry declares no mail keys for ${input.agentName}`);
|
|
124
|
+
}
|
|
125
|
+
const present = new Set(vaultMailKeyIds(input.agentName));
|
|
126
|
+
const absentKeyIds = declaredKeyIds.filter((keyId) => !present.has(keyId));
|
|
127
|
+
return {
|
|
128
|
+
status: absentKeyIds.length === 0 ? "covered" : "absent",
|
|
129
|
+
declaredKeyIds,
|
|
130
|
+
absentKeyIds,
|
|
131
|
+
reason: null,
|
|
132
|
+
checkedAt: input.checkedAt,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return unverifiableCoverage(input.checkedAt, error instanceof Error ? error.message : String(error));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Coverage has to push, not wait to be asked. `ouro doctor` is on-demand — it
|
|
141
|
+
* has no scheduled invocation — so a doctor line alone leaves a gap invisible
|
|
142
|
+
* until someone happens to run it, which is how `mail_slugger-hey_6e7b4bfa34c0b826`
|
|
143
|
+
* stayed unnoticed for 23 hours while mail encrypted to it piled up unreadable.
|
|
144
|
+
*/
|
|
145
|
+
function emitMailKeyCoverageEvents(agentName, coverage) {
|
|
146
|
+
(0, runtime_1.emitNervesEvent)({
|
|
147
|
+
component: "senses",
|
|
148
|
+
event: "senses.mail_key_coverage_recorded",
|
|
149
|
+
message: "mail key coverage recorded",
|
|
150
|
+
meta: {
|
|
151
|
+
agentName,
|
|
152
|
+
status: coverage.status,
|
|
153
|
+
declaredCount: coverage.declaredKeyIds.length,
|
|
154
|
+
absentCount: coverage.absentKeyIds.length,
|
|
155
|
+
reason: coverage.reason,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
if (coverage.status !== "absent")
|
|
159
|
+
return;
|
|
160
|
+
(0, runtime_1.emitNervesEvent)({
|
|
161
|
+
level: "error",
|
|
162
|
+
component: "senses",
|
|
163
|
+
event: "senses.mail_key_coverage_absent",
|
|
164
|
+
message: `mailroom registry declares mail keys with no private half in ${agentName}'s vault: ${coverage.absentKeyIds.join(", ")}`,
|
|
165
|
+
meta: {
|
|
166
|
+
agentName,
|
|
167
|
+
absentKeyIds: coverage.absentKeyIds,
|
|
168
|
+
declaredKeyIds: coverage.declaredKeyIds,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
53
172
|
function validPort(value) {
|
|
54
173
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 65535 ? value : 0;
|
|
55
174
|
}
|
|
@@ -280,6 +399,7 @@ async function startMailSenseApp(options) {
|
|
|
280
399
|
const importDiscoveryPath = importDiscoveryStatePath(options.agentName);
|
|
281
400
|
let lastScanAt = null;
|
|
282
401
|
let lastQueuedCount = 0;
|
|
402
|
+
let lastKeyCoverage = unverifiableCoverage(new Date(now()).toISOString(), "mail key coverage not probed yet");
|
|
283
403
|
const scan = async () => {
|
|
284
404
|
const scanStartedAt = new Date(now()).toISOString();
|
|
285
405
|
let queuedCount = 0;
|
|
@@ -322,6 +442,13 @@ async function startMailSenseApp(options) {
|
|
|
322
442
|
meta: { agentName: options.agentName, error: error instanceof Error ? error.message : String(error) },
|
|
323
443
|
});
|
|
324
444
|
}
|
|
445
|
+
lastKeyCoverage = await computeMailKeyCoverage({
|
|
446
|
+
agentName: options.agentName,
|
|
447
|
+
config: resolved.config,
|
|
448
|
+
readRegistry: options.readRegistry ?? reader_1.readMailroomRegistry,
|
|
449
|
+
checkedAt: scanStartedAt,
|
|
450
|
+
});
|
|
451
|
+
emitMailKeyCoverageEvents(options.agentName, lastKeyCoverage);
|
|
325
452
|
lastScanAt = scanStartedAt;
|
|
326
453
|
lastQueuedCount = queuedCount;
|
|
327
454
|
writeRuntimeState(runtimePath, {
|
|
@@ -336,6 +463,7 @@ async function startMailSenseApp(options) {
|
|
|
336
463
|
storeLabel: resolved.storeLabel,
|
|
337
464
|
lastScanAt,
|
|
338
465
|
lastQueuedCount,
|
|
466
|
+
keyCoverage: lastKeyCoverage,
|
|
339
467
|
updatedAt: new Date(now()).toISOString(),
|
|
340
468
|
});
|
|
341
469
|
};
|
|
@@ -382,6 +510,7 @@ async function startMailSenseApp(options) {
|
|
|
382
510
|
storeLabel: resolved.storeLabel,
|
|
383
511
|
lastScanAt,
|
|
384
512
|
lastQueuedCount,
|
|
513
|
+
keyCoverage: lastKeyCoverage,
|
|
385
514
|
updatedAt: new Date(now()).toISOString(),
|
|
386
515
|
});
|
|
387
516
|
(0, runtime_1.emitNervesEvent)({
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.emitObligationTransitionEpisodes = emitObligationTransitionEpisodes;
|
|
42
|
+
exports.selectCheckpointCurrentAsk = selectCheckpointCurrentAsk;
|
|
42
43
|
exports.handleInboundTurn = handleInboundTurn;
|
|
43
44
|
const fs = __importStar(require("fs"));
|
|
44
45
|
const path = __importStar(require("path"));
|
|
@@ -203,6 +204,16 @@ function latestUserAuthoredText(messages, continuityIngressTexts) {
|
|
|
203
204
|
.filter(Boolean);
|
|
204
205
|
return userMessages[userMessages.length - 1];
|
|
205
206
|
}
|
|
207
|
+
function selectCheckpointCurrentAsk(input) {
|
|
208
|
+
const frameSpeech = input.orientationFrame?.currentUserSpeech
|
|
209
|
+
.map((speech) => speech.trim())
|
|
210
|
+
.filter(Boolean)
|
|
211
|
+
.at(-1) ?? null;
|
|
212
|
+
if (input.orientationFrame?.speechKind === "reaction")
|
|
213
|
+
return frameSpeech;
|
|
214
|
+
const currentUtterance = input.currentUserMessage?.trim();
|
|
215
|
+
return currentUtterance || frameSpeech;
|
|
216
|
+
}
|
|
206
217
|
function sessionRefForFlightRecorder(session) {
|
|
207
218
|
return `${session.friendId}/${session.channel}/${session.key}`;
|
|
208
219
|
}
|
|
@@ -212,6 +223,22 @@ function isTerminalWithoutContinuation(outcome, hasActiveContinuation) {
|
|
|
212
223
|
|| outcome === "rested"
|
|
213
224
|
|| outcome === "superseded");
|
|
214
225
|
}
|
|
226
|
+
function selectPostTurnContinuityState(input) {
|
|
227
|
+
const continuingState = {
|
|
228
|
+
...(input.mustResolveBeforeHandoff ? { mustResolveBeforeHandoff: true } : {}),
|
|
229
|
+
...(typeof input.lastFriendActivityAt === "string" ? { lastFriendActivityAt: input.lastFriendActivityAt } : {}),
|
|
230
|
+
};
|
|
231
|
+
const clearsMustResolveBeforeHandoff = input.outcome === "settled"
|
|
232
|
+
|| input.outcome === "blocked"
|
|
233
|
+
|| input.outcome === "superseded"
|
|
234
|
+
|| input.outcome === "observed";
|
|
235
|
+
if (!clearsMustResolveBeforeHandoff) {
|
|
236
|
+
return Object.keys(continuingState).length > 0 ? continuingState : undefined;
|
|
237
|
+
}
|
|
238
|
+
return typeof input.lastFriendActivityAt === "string"
|
|
239
|
+
? { lastFriendActivityAt: input.lastFriendActivityAt }
|
|
240
|
+
: undefined;
|
|
241
|
+
}
|
|
215
242
|
function readPostTurnFlightRecorderArcSnapshot(agentRoot) {
|
|
216
243
|
const latest = (0, flight_recorder_1.readFlightRecorderResume)(agentRoot);
|
|
217
244
|
return {
|
|
@@ -223,7 +250,7 @@ function readPostTurnFlightRecorderArcSnapshot(agentRoot) {
|
|
|
223
250
|
unverifiedClaimIds: latest.unverifiedClaimIds,
|
|
224
251
|
};
|
|
225
252
|
}
|
|
226
|
-
function
|
|
253
|
+
function recordTurnFlightRecorderCheckpoint(input) {
|
|
227
254
|
const hasActiveContinuation = input.mustResolveBeforeHandoff
|
|
228
255
|
|| input.activeObligationIds.length > 0
|
|
229
256
|
|| input.activeReturnObligationIds.length > 0
|
|
@@ -240,9 +267,11 @@ function recordPostTurnFlightRecorderCheckpoint(input) {
|
|
|
240
267
|
? "inspect the latest session and wait for new input before acting"
|
|
241
268
|
: "continue the current held work and update Arc/Desk with the next checkpoint");
|
|
242
269
|
(0, flight_recorder_1.recordFlightRecorderEvent)(input.agentRoot, {
|
|
243
|
-
kind: "post_turn_persisted",
|
|
270
|
+
kind: input.sessionPersisted ? "post_turn_persisted" : "blocker_detected",
|
|
244
271
|
sessionRef: sessionRefForFlightRecorder(input.currentSession),
|
|
245
|
-
summary:
|
|
272
|
+
summary: input.sessionPersisted
|
|
273
|
+
? `persisted ${input.currentSession.channel}/${input.currentSession.key} turn with outcome ${input.outcome}`
|
|
274
|
+
: `runAgent threw before session persistence for ${input.currentSession.channel}/${input.currentSession.key}`,
|
|
246
275
|
currentAsk: input.currentAsk,
|
|
247
276
|
nextSafeAction,
|
|
248
277
|
blockedBecause,
|
|
@@ -255,6 +284,7 @@ function recordPostTurnFlightRecorderCheckpoint(input) {
|
|
|
255
284
|
unverifiedClaimIds: input.unverifiedClaimIds,
|
|
256
285
|
meta: {
|
|
257
286
|
outcome: input.outcome,
|
|
287
|
+
sessionPersisted: input.sessionPersisted,
|
|
258
288
|
mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
|
|
259
289
|
sessionPath: input.currentSession.sessionPath,
|
|
260
290
|
},
|
|
@@ -447,6 +477,9 @@ function prepareInboundRunLedger(input) {
|
|
|
447
477
|
}
|
|
448
478
|
}
|
|
449
479
|
async function handleInboundTurn(input) {
|
|
480
|
+
// Continuity resumption is structured caller intent. Never infer it from the
|
|
481
|
+
// current message or from similarity to prior obligations.
|
|
482
|
+
const resumePriorWork = input.runAgentOptions?.resumePriorWork === true;
|
|
450
483
|
// Reset session-scoped state when the session changes
|
|
451
484
|
const sessionKey = `${input.channel}/${input.sessionKey ?? "session"}`;
|
|
452
485
|
if (sessionKey !== _lastSessionKey) {
|
|
@@ -826,7 +859,7 @@ async function handleInboundTurn(input) {
|
|
|
826
859
|
if (capabilities) {
|
|
827
860
|
startOfTurnPacket.capabilities = capabilities;
|
|
828
861
|
}
|
|
829
|
-
renderedStartOfTurnPacket = (0, start_of_turn_packet_1.renderStartOfTurnPacket)(startOfTurnPacket);
|
|
862
|
+
renderedStartOfTurnPacket = (0, start_of_turn_packet_1.renderStartOfTurnPacket)(startOfTurnPacket, { resumePriorWork });
|
|
830
863
|
if (!renderedStartOfTurnPacket)
|
|
831
864
|
renderedStartOfTurnPacket = undefined;
|
|
832
865
|
// Update self-presence
|
|
@@ -854,6 +887,7 @@ async function handleInboundTurn(input) {
|
|
|
854
887
|
?? latestUserAuthoredText(input.messages, input.continuityIngressTexts);
|
|
855
888
|
let runAgentOptions = {
|
|
856
889
|
...input.runAgentOptions,
|
|
890
|
+
resumePriorWork,
|
|
857
891
|
...(orientationFrame ? { orientationFrame } : {}),
|
|
858
892
|
...(liveLatencyMode ? { skipKeptNotes: true } : {}),
|
|
859
893
|
bridgeContext,
|
|
@@ -892,6 +926,10 @@ async function handleInboundTurn(input) {
|
|
|
892
926
|
resolvedContext,
|
|
893
927
|
runAgentOptions,
|
|
894
928
|
}));
|
|
929
|
+
const checkpointCurrentAsk = selectCheckpointCurrentAsk({
|
|
930
|
+
currentUserMessage: runAgentOptions.toolContext?.currentUserMessage ?? currentUserMessage,
|
|
931
|
+
orientationFrame: runAgentOptions.orientationFrame,
|
|
932
|
+
});
|
|
895
933
|
const runLedger = prepareInboundRunLedger({
|
|
896
934
|
channel: input.channel,
|
|
897
935
|
senseType: input.capabilities.senseType,
|
|
@@ -920,6 +958,37 @@ async function handleInboundTurn(input) {
|
|
|
920
958
|
usage: (0, run_ledger_1.usageMetadataFromUsageData)(undefined, "reported-unavailable"),
|
|
921
959
|
errorName: error instanceof Error ? error.name : "NonErrorThrown",
|
|
922
960
|
}));
|
|
961
|
+
try {
|
|
962
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
963
|
+
const postTurnArc = readPostTurnFlightRecorderArcSnapshot(agentRoot);
|
|
964
|
+
recordTurnFlightRecorderCheckpoint({
|
|
965
|
+
agentRoot,
|
|
966
|
+
currentSession,
|
|
967
|
+
currentAsk: checkpointCurrentAsk,
|
|
968
|
+
nextSafeAction: activeWorkFrame.resumeHandle?.nextAction
|
|
969
|
+
?? activeWorkFrame.primaryObligation?.nextAction?.trim()
|
|
970
|
+
?? null,
|
|
971
|
+
outcome: "errored",
|
|
972
|
+
sessionPersisted: false,
|
|
973
|
+
mustResolveBeforeHandoff,
|
|
974
|
+
activeObligationIds: postTurnArc.activeObligationIds,
|
|
975
|
+
activeReturnObligationIds: postTurnArc.activeReturnObligationIds,
|
|
976
|
+
activePacketIds: postTurnArc.activePacketIds,
|
|
977
|
+
openEvolutionCaseIds: postTurnArc.openEvolutionCaseIds,
|
|
978
|
+
recentClaimIds: postTurnArc.recentClaimIds,
|
|
979
|
+
unverifiedClaimIds: postTurnArc.unverifiedClaimIds,
|
|
980
|
+
});
|
|
981
|
+
await refreshContextLossSentinelNonFatal({ agentName: (0, identity_1.getAgentName)(), agentRoot, trigger: "post_turn" });
|
|
982
|
+
}
|
|
983
|
+
catch (checkpointError) {
|
|
984
|
+
(0, runtime_1.emitNervesEvent)({
|
|
985
|
+
level: "warn",
|
|
986
|
+
component: "senses",
|
|
987
|
+
event: "senses.flight_recorder_checkpoint_error",
|
|
988
|
+
message: "failed to record thrown-turn flight recorder checkpoint",
|
|
989
|
+
meta: { error: checkpointError instanceof Error ? checkpointError.message : String(checkpointError) },
|
|
990
|
+
});
|
|
991
|
+
}
|
|
923
992
|
throw error;
|
|
924
993
|
}
|
|
925
994
|
const runLedgerLifecycle = result.outcome === "errored" ? "error" : "completed";
|
|
@@ -946,19 +1015,22 @@ async function handleInboundTurn(input) {
|
|
|
946
1015
|
/* v8 ignore next -- defensive: error always set when errored @preserve */
|
|
947
1016
|
result.error?.message ?? "unknown error", classification, currentProvider, currentBinding.model, agentName, inventory, {}, { currentLane });
|
|
948
1017
|
input.failoverState.pending = failoverContext;
|
|
949
|
-
|
|
1018
|
+
const failoverNextState = selectPostTurnContinuityState({
|
|
1019
|
+
outcome: "errored",
|
|
1020
|
+
mustResolveBeforeHandoff,
|
|
1021
|
+
lastFriendActivityAt,
|
|
1022
|
+
});
|
|
1023
|
+
await input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, failoverNextState);
|
|
950
1024
|
try {
|
|
951
1025
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
952
1026
|
const postTurnArc = readPostTurnFlightRecorderArcSnapshot(agentRoot);
|
|
953
|
-
|
|
1027
|
+
recordTurnFlightRecorderCheckpoint({
|
|
954
1028
|
agentRoot,
|
|
955
1029
|
currentSession,
|
|
956
|
-
currentAsk:
|
|
957
|
-
?? activeWorkFrame.primaryObligation?.content?.trim()
|
|
958
|
-
?? currentUserMessage
|
|
959
|
-
?? null,
|
|
1030
|
+
currentAsk: checkpointCurrentAsk,
|
|
960
1031
|
nextSafeAction: failoverContext.userMessage,
|
|
961
1032
|
outcome: "errored",
|
|
1033
|
+
sessionPersisted: true,
|
|
962
1034
|
mustResolveBeforeHandoff,
|
|
963
1035
|
activeObligationIds: postTurnArc.activeObligationIds,
|
|
964
1036
|
activeReturnObligationIds: postTurnArc.activeReturnObligationIds,
|
|
@@ -1012,30 +1084,24 @@ async function handleInboundTurn(input) {
|
|
|
1012
1084
|
// Episode emission is non-fatal
|
|
1013
1085
|
}
|
|
1014
1086
|
// Step 6: postTurn
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
? (typeof lastFriendActivityAt === "string"
|
|
1021
|
-
? { lastFriendActivityAt }
|
|
1022
|
-
: undefined)
|
|
1023
|
-
: (Object.keys(continuingState).length > 0 ? continuingState : undefined);
|
|
1087
|
+
const nextState = selectPostTurnContinuityState({
|
|
1088
|
+
outcome: result.outcome,
|
|
1089
|
+
mustResolveBeforeHandoff,
|
|
1090
|
+
lastFriendActivityAt,
|
|
1091
|
+
});
|
|
1024
1092
|
await input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, nextState);
|
|
1025
1093
|
try {
|
|
1026
1094
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
1027
1095
|
const postTurnArc = readPostTurnFlightRecorderArcSnapshot(agentRoot);
|
|
1028
|
-
|
|
1096
|
+
recordTurnFlightRecorderCheckpoint({
|
|
1029
1097
|
agentRoot,
|
|
1030
1098
|
currentSession,
|
|
1031
|
-
currentAsk:
|
|
1032
|
-
?? activeWorkFrame.primaryObligation?.content?.trim()
|
|
1033
|
-
?? currentUserMessage
|
|
1034
|
-
?? null,
|
|
1099
|
+
currentAsk: checkpointCurrentAsk,
|
|
1035
1100
|
nextSafeAction: activeWorkFrame.resumeHandle?.nextAction
|
|
1036
1101
|
?? activeWorkFrame.primaryObligation?.nextAction?.trim()
|
|
1037
1102
|
?? null,
|
|
1038
1103
|
outcome: result.outcome ?? "observed",
|
|
1104
|
+
sessionPersisted: true,
|
|
1039
1105
|
mustResolveBeforeHandoff,
|
|
1040
1106
|
activeObligationIds: postTurnArc.activeObligationIds,
|
|
1041
1107
|
activeReturnObligationIds: postTurnArc.activeReturnObligationIds,
|
|
@@ -55,6 +55,7 @@ const habit_policy_1 = require("../rsvp/habit-policy");
|
|
|
55
55
|
const spend_ledger_1 = require("../rsvp/spend-ledger");
|
|
56
56
|
const run_ledger_1 = require("../heart/run-ledger");
|
|
57
57
|
const autonomy_budget_1 = require("../heart/autonomy-budget");
|
|
58
|
+
const habit_lifecycle_guard_1 = require("./habit-lifecycle-guard");
|
|
58
59
|
function isUsageData(value) {
|
|
59
60
|
const usage = value;
|
|
60
61
|
return !!usage
|
|
@@ -275,21 +276,6 @@ function deriveHabitSummarySnapshot(habitRun) {
|
|
|
275
276
|
nextLikelyStep: null,
|
|
276
277
|
};
|
|
277
278
|
}
|
|
278
|
-
function fallbackHabitFile(habitName) {
|
|
279
|
-
return {
|
|
280
|
-
name: habitName,
|
|
281
|
-
title: habitName,
|
|
282
|
-
cadence: null,
|
|
283
|
-
status: "active",
|
|
284
|
-
lastRun: null,
|
|
285
|
-
created: null,
|
|
286
|
-
tools: [],
|
|
287
|
-
origin: null,
|
|
288
|
-
surface: { family: false, originator: false, extra: [] },
|
|
289
|
-
continuity: { mode: "fresh" },
|
|
290
|
-
body: "",
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
279
|
function readHabitForRun(agentRoot, habitName, errors) {
|
|
294
280
|
const habitPath = path.join(agentRoot, "habits", `${habitName}.md`);
|
|
295
281
|
try {
|
|
@@ -305,16 +291,20 @@ function readHabitForRun(agentRoot, habitName, errors) {
|
|
|
305
291
|
message: "habit file could not be read for habit session",
|
|
306
292
|
meta: { habitName, habitPath, reason },
|
|
307
293
|
});
|
|
308
|
-
return
|
|
294
|
+
return (0, habit_parser_1.createDegradedHabitFile)(habitPath, "read_error");
|
|
309
295
|
}
|
|
310
296
|
}
|
|
311
297
|
async function prepareHabitRun(habitName, trigger, startedAt) {
|
|
312
298
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
313
299
|
const errors = [];
|
|
314
300
|
const habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, readHabitForRun(agentRoot, habitName, errors));
|
|
301
|
+
if (habit.status === "degraded" && habit.degradedDetail !== null) {
|
|
302
|
+
errors.push(habit.degradedDetail);
|
|
303
|
+
}
|
|
304
|
+
const lifecycleBlockedReason = (0, habit_lifecycle_guard_1.privateRuntimeHabitRejectionReason)(habit, "private-runtime-worker");
|
|
315
305
|
const blockedReason = (0, habit_policy_1.isRsvpHabitName)(habitName) && !habit.rsvp
|
|
316
306
|
? "RSVP habit metadata is required before private runtime execution"
|
|
317
|
-
:
|
|
307
|
+
: lifecycleBlockedReason;
|
|
318
308
|
if (blockedReason && !errors.includes(blockedReason))
|
|
319
309
|
errors.push(blockedReason);
|
|
320
310
|
const runId = (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt));
|
|
@@ -422,6 +412,11 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
422
412
|
async function reuseHeartbeatOkRest(habitName) {
|
|
423
413
|
const nowIso = new Date(nowSource()).toISOString();
|
|
424
414
|
const habitRun = await prepareHabitRun(habitName, "overdue", nowIso);
|
|
415
|
+
if (habitRun.blockedReason) {
|
|
416
|
+
clearHeartbeatRestShield();
|
|
417
|
+
recordHabitCompletion(habitRun, nowIso);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
425
420
|
habitRun.results.push({ turnOutcome: "rested", restStatus: "HEARTBEAT_OK" });
|
|
426
421
|
recordHabitCompletion(habitRun, nowIso);
|
|
427
422
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -475,9 +470,9 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
475
470
|
});
|
|
476
471
|
}
|
|
477
472
|
}
|
|
478
|
-
async function run(reason, taskId, habitName, awaitName, trigger, privateTurnDecision) {
|
|
473
|
+
async function run(reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend) {
|
|
479
474
|
if (running) {
|
|
480
|
-
queue.push({ reason, taskId, habitName, awaitName, trigger, privateTurnDecision });
|
|
475
|
+
queue.push({ reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend });
|
|
481
476
|
return;
|
|
482
477
|
}
|
|
483
478
|
running = true;
|
|
@@ -488,12 +483,14 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
488
483
|
let nextAwaitName = awaitName;
|
|
489
484
|
let nextTrigger = trigger;
|
|
490
485
|
let nextPrivateTurnDecision = privateTurnDecision;
|
|
486
|
+
let nextNoSend = reason === "habit" ? noSend : undefined;
|
|
491
487
|
let nextHabitRun = null;
|
|
492
488
|
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
493
489
|
runLoop: do {
|
|
494
490
|
const currentReason = nextReason;
|
|
495
491
|
const currentHabitName = nextHabitName;
|
|
496
492
|
const currentTrigger = nextTrigger ?? "overdue";
|
|
493
|
+
const currentNoSend = nextNoSend;
|
|
497
494
|
const currentHabitRun = currentReason === "habit" && currentHabitName
|
|
498
495
|
? nextHabitRun && nextHabitRun.habit.name === currentHabitName
|
|
499
496
|
? nextHabitRun
|
|
@@ -545,6 +542,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
545
542
|
taskId: nextTaskId,
|
|
546
543
|
habitName: nextHabitName,
|
|
547
544
|
awaitName: nextAwaitName,
|
|
545
|
+
...(currentNoSend ? { noSend: true } : {}),
|
|
548
546
|
...(nextPrivateTurnDecision ? { privateTurnDecision: nextPrivateTurnDecision } : {}),
|
|
549
547
|
...(currentHabitRun
|
|
550
548
|
? {
|
|
@@ -614,6 +612,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
614
612
|
nextAwaitName = next.awaitName;
|
|
615
613
|
nextTrigger = next.trigger;
|
|
616
614
|
nextPrivateTurnDecision = next.privateTurnDecision;
|
|
615
|
+
nextNoSend = next.reason === "habit" ? next.noSend : undefined;
|
|
617
616
|
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
618
617
|
continue runLoop;
|
|
619
618
|
}
|
|
@@ -650,6 +649,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
650
649
|
nextAwaitName = undefined;
|
|
651
650
|
nextTrigger = currentTrigger;
|
|
652
651
|
nextPrivateTurnDecision = undefined;
|
|
652
|
+
nextNoSend = currentNoSend;
|
|
653
653
|
nextHabitRun = currentHabitRun;
|
|
654
654
|
}
|
|
655
655
|
else {
|
|
@@ -661,6 +661,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
661
661
|
nextAwaitName = undefined;
|
|
662
662
|
nextTrigger = undefined;
|
|
663
663
|
nextPrivateTurnDecision = undefined;
|
|
664
|
+
nextNoSend = undefined;
|
|
664
665
|
}
|
|
665
666
|
continue;
|
|
666
667
|
}
|
|
@@ -684,7 +685,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
684
685
|
return;
|
|
685
686
|
}
|
|
686
687
|
recordHabitFireForRecursion(habitName);
|
|
687
|
-
await run("habit", undefined, maybeMessage.habitName, undefined, maybeMessage.trigger ?? "overdue", maybeMessage.privateTurnDecision);
|
|
688
|
+
await run("habit", undefined, maybeMessage.habitName, undefined, maybeMessage.trigger ?? "overdue", maybeMessage.privateTurnDecision, maybeMessage.noSend);
|
|
688
689
|
return;
|
|
689
690
|
}
|
|
690
691
|
if (maybeMessage.type === "await") {
|