sentinelayer-cli 0.17.0 → 0.17.1
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/README.md +16 -6
- package/package.json +3 -2
- package/src/commands/legacy-args.js +1 -0
- package/src/commands/omargate.js +1 -0
- package/src/commands/session.js +302 -25
- package/src/events/schema.js +21 -0
- package/src/legacy-cli.js +16 -0
- package/src/review/investor-dd-devtestbot.js +83 -8
- package/src/review/investor-dd-file-loop.js +83 -6
- package/src/review/investor-dd-orchestrator.js +42 -1
- package/src/review/investor-dd-progress.js +351 -0
- package/src/review/investor-dd-usage.js +227 -0
- package/src/session/daemon.js +341 -2
- package/src/session/recap.js +288 -69
- package/src/session/sync.js +1 -4
package/src/session/daemon.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./checkpoints.js";
|
|
35
35
|
import { resolveSessionPaths } from "./paths.js";
|
|
36
36
|
import {
|
|
37
|
+
DEFAULT_RECAP_ACTIVITY_THRESHOLD,
|
|
37
38
|
DEFAULT_RECAP_INACTIVITY_MS,
|
|
38
39
|
DEFAULT_RECAP_INTERVAL_MS,
|
|
39
40
|
emitPeriodicRecap,
|
|
@@ -58,7 +59,34 @@ const RENEWAL_LEAD_MS = 60 * 60 * 1000;
|
|
|
58
59
|
const DEFAULT_STALE_AGENT_SECONDS = 90;
|
|
59
60
|
const DEFAULT_RECAP_INTERVAL_MS_OVERRIDE = DEFAULT_RECAP_INTERVAL_MS;
|
|
60
61
|
const DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE = DEFAULT_RECAP_INACTIVITY_MS;
|
|
62
|
+
const DEFAULT_RECAP_ACTIVITY_THRESHOLD_OVERRIDE = DEFAULT_RECAP_ACTIVITY_THRESHOLD;
|
|
61
63
|
const DEFAULT_CHECKPOINT_INTERVAL_MS = 60_000;
|
|
64
|
+
const DEFAULT_CHECKPOINT_EVENT_THRESHOLD = DEFAULT_CHECKPOINT_MIN_EVENTS;
|
|
65
|
+
const DEFAULT_CHECKPOINT_IDLE_MS = DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE;
|
|
66
|
+
const CHECKPOINT_MEANINGFUL_EVENT_NAMES = new Set([
|
|
67
|
+
"file_lock",
|
|
68
|
+
"file_unlock",
|
|
69
|
+
"finding",
|
|
70
|
+
"help_request",
|
|
71
|
+
"help_response",
|
|
72
|
+
"session_admin_kill",
|
|
73
|
+
"session_message",
|
|
74
|
+
"task_accepted",
|
|
75
|
+
"task_assign",
|
|
76
|
+
"task_blocked",
|
|
77
|
+
"task_completed",
|
|
78
|
+
]);
|
|
79
|
+
const CHECKPOINT_IGNORED_EVENT_NAMES = new Set([
|
|
80
|
+
"agent_heartbeat",
|
|
81
|
+
"agent_join",
|
|
82
|
+
"agent_status",
|
|
83
|
+
"context_briefing",
|
|
84
|
+
"daemon_alert",
|
|
85
|
+
"session_checkpoint",
|
|
86
|
+
"session_listen_error",
|
|
87
|
+
"session_recap",
|
|
88
|
+
"session_usage",
|
|
89
|
+
]);
|
|
62
90
|
|
|
63
91
|
const SENTI_MODEL = "gpt-5.4-mini";
|
|
64
92
|
const SENTI_IDENTITY = Object.freeze({
|
|
@@ -176,10 +204,14 @@ function createSentiState({
|
|
|
176
204
|
tickIntervalMs,
|
|
177
205
|
recapIntervalMs,
|
|
178
206
|
recapInactivityMs,
|
|
207
|
+
recapActivityThreshold,
|
|
179
208
|
checkpointGenerator,
|
|
180
209
|
checkpointIntervalMs,
|
|
181
210
|
checkpointMinEvents,
|
|
182
211
|
checkpointMaxEvents,
|
|
212
|
+
checkpointEventThreshold,
|
|
213
|
+
checkpointIdleMs,
|
|
214
|
+
checkpointCloseoutOnStop,
|
|
183
215
|
helpResponder,
|
|
184
216
|
llmInvoker,
|
|
185
217
|
telemetrySessionId,
|
|
@@ -195,12 +227,19 @@ function createSentiState({
|
|
|
195
227
|
tickIntervalMs,
|
|
196
228
|
recapIntervalMs,
|
|
197
229
|
recapInactivityMs,
|
|
230
|
+
recapActivityThreshold,
|
|
198
231
|
checkpointGenerator,
|
|
199
232
|
checkpointIntervalMs,
|
|
200
233
|
checkpointMinEvents,
|
|
201
234
|
checkpointMaxEvents,
|
|
235
|
+
checkpointEventThreshold,
|
|
236
|
+
checkpointIdleMs,
|
|
237
|
+
checkpointCloseoutOnStop,
|
|
202
238
|
checkpointGenerationInFlight: false,
|
|
203
239
|
lastCheckpointAttemptAt: null,
|
|
240
|
+
lastCheckpointSourceEventAt: null,
|
|
241
|
+
lastCheckpointSourceSequenceId: null,
|
|
242
|
+
lastCheckpointSourceCursor: null,
|
|
204
243
|
lastCheckpointResult: null,
|
|
205
244
|
helpResponder,
|
|
206
245
|
llmInvoker,
|
|
@@ -840,6 +879,169 @@ function parseEpoch(value, fallbackIso = new Date().toISOString()) {
|
|
|
840
879
|
return Date.parse(normalizeIsoTimestamp(value, fallbackIso)) || 0;
|
|
841
880
|
}
|
|
842
881
|
|
|
882
|
+
function eventSequenceId(event = {}) {
|
|
883
|
+
const parsed = Number(event.sequenceId ?? event.sequence_id);
|
|
884
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
return Math.floor(parsed);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function eventCursor(event = {}) {
|
|
891
|
+
return normalizeString(event.cursor || event.eventId || event.idempotencyToken || event.ts);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function isMeaningfulCheckpointSourceEvent(event = {}) {
|
|
895
|
+
const eventName = normalizeString(event.event).toLowerCase();
|
|
896
|
+
if (!eventName || CHECKPOINT_IGNORED_EVENT_NAMES.has(eventName)) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
if (CHECKPOINT_MEANINGFUL_EVENT_NAMES.has(eventName) || eventName.startsWith("task_")) {
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function isEventAfterCheckpointAnchor(event = {}, daemonState = {}) {
|
|
906
|
+
const anchorSequence = Number(daemonState.lastCheckpointSourceSequenceId);
|
|
907
|
+
const sequence = eventSequenceId(event);
|
|
908
|
+
if (Number.isFinite(anchorSequence) && anchorSequence > 0 && sequence !== null) {
|
|
909
|
+
return sequence > anchorSequence;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const anchorAt = normalizeString(daemonState.lastCheckpointSourceEventAt);
|
|
913
|
+
if (!anchorAt) {
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
const anchorEpoch = parseEpoch(anchorAt, anchorAt);
|
|
917
|
+
const eventEpoch = parseEpoch(event.ts, anchorAt);
|
|
918
|
+
if (eventEpoch > anchorEpoch) {
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
if (eventEpoch < anchorEpoch) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const anchorCursor = normalizeString(daemonState.lastCheckpointSourceCursor);
|
|
926
|
+
if (!anchorCursor) {
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
return eventCursor(event) !== anchorCursor;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function rememberCheckpointSource(daemonState, event = {}, nowIso = new Date().toISOString()) {
|
|
933
|
+
daemonState.lastCheckpointSourceEventAt = normalizeIsoTimestamp(event.ts, nowIso);
|
|
934
|
+
daemonState.lastCheckpointSourceSequenceId = eventSequenceId(event);
|
|
935
|
+
daemonState.lastCheckpointSourceCursor = eventCursor(event);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
async function resolveCheckpointTrigger(
|
|
939
|
+
daemonState,
|
|
940
|
+
nowIso = new Date().toISOString(),
|
|
941
|
+
{ force = false, forceReason = "" } = {}
|
|
942
|
+
) {
|
|
943
|
+
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
944
|
+
const nowEpoch = parseEpoch(normalizedNow, normalizedNow);
|
|
945
|
+
const eventThreshold = Math.min(
|
|
946
|
+
200,
|
|
947
|
+
normalizePositiveInteger(
|
|
948
|
+
daemonState.checkpointEventThreshold,
|
|
949
|
+
DEFAULT_CHECKPOINT_EVENT_THRESHOLD
|
|
950
|
+
)
|
|
951
|
+
);
|
|
952
|
+
const idleMs = normalizePositiveInteger(
|
|
953
|
+
daemonState.checkpointIdleMs,
|
|
954
|
+
DEFAULT_CHECKPOINT_IDLE_MS
|
|
955
|
+
);
|
|
956
|
+
const tail = Math.min(
|
|
957
|
+
200,
|
|
958
|
+
Math.max(
|
|
959
|
+
eventThreshold,
|
|
960
|
+
normalizePositiveInteger(daemonState.checkpointMaxEvents, DEFAULT_CHECKPOINT_MAX_EVENTS)
|
|
961
|
+
)
|
|
962
|
+
);
|
|
963
|
+
const events = await readStream(daemonState.sessionId, {
|
|
964
|
+
targetPath: daemonState.targetPath,
|
|
965
|
+
tail,
|
|
966
|
+
});
|
|
967
|
+
const sourceEvents = events.filter(isMeaningfulCheckpointSourceEvent);
|
|
968
|
+
const uncheckpointedSourceEvents = sourceEvents.filter((event) =>
|
|
969
|
+
isEventAfterCheckpointAnchor(event, daemonState)
|
|
970
|
+
);
|
|
971
|
+
const latestSourceEvent = sourceEvents.length > 0 ? sourceEvents[sourceEvents.length - 1] : null;
|
|
972
|
+
const latestUncheckpointedSourceEvent =
|
|
973
|
+
uncheckpointedSourceEvents.length > 0
|
|
974
|
+
? uncheckpointedSourceEvents[uncheckpointedSourceEvents.length - 1]
|
|
975
|
+
: null;
|
|
976
|
+
const latestSourceEventAt = latestSourceEvent
|
|
977
|
+
? normalizeIsoTimestamp(latestSourceEvent.ts, normalizedNow)
|
|
978
|
+
: null;
|
|
979
|
+
const latestUncheckpointedSourceEventAt = latestUncheckpointedSourceEvent
|
|
980
|
+
? normalizeIsoTimestamp(latestUncheckpointedSourceEvent.ts, normalizedNow)
|
|
981
|
+
: null;
|
|
982
|
+
const sourceIdleMs = latestUncheckpointedSourceEvent
|
|
983
|
+
? Math.max(0, nowEpoch - parseEpoch(latestUncheckpointedSourceEvent.ts, normalizedNow))
|
|
984
|
+
: null;
|
|
985
|
+
const policy = {
|
|
986
|
+
eventThreshold,
|
|
987
|
+
idleMs,
|
|
988
|
+
sourceEventCount: uncheckpointedSourceEvents.length,
|
|
989
|
+
totalSourceEventCount: sourceEvents.length,
|
|
990
|
+
latestSourceEventAt,
|
|
991
|
+
latestUncheckpointedSourceEventAt,
|
|
992
|
+
lastCheckpointSourceEventAt: daemonState.lastCheckpointSourceEventAt || null,
|
|
993
|
+
sourceIdleMs,
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
if (!latestUncheckpointedSourceEvent) {
|
|
997
|
+
return {
|
|
998
|
+
shouldAttempt: false,
|
|
999
|
+
trigger: "",
|
|
1000
|
+
reason: force ? "checkpoint_closeout_no_new_source_events" : "checkpoint_no_new_source_events",
|
|
1001
|
+
sourceEvent: null,
|
|
1002
|
+
policy,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (force) {
|
|
1007
|
+
return {
|
|
1008
|
+
shouldAttempt: true,
|
|
1009
|
+
trigger: normalizeString(forceReason) || "closeout",
|
|
1010
|
+
reason: "",
|
|
1011
|
+
sourceEvent: latestUncheckpointedSourceEvent,
|
|
1012
|
+
policy,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (uncheckpointedSourceEvents.length >= eventThreshold) {
|
|
1017
|
+
return {
|
|
1018
|
+
shouldAttempt: true,
|
|
1019
|
+
trigger: "event_threshold",
|
|
1020
|
+
reason: "",
|
|
1021
|
+
sourceEvent: latestUncheckpointedSourceEvent,
|
|
1022
|
+
policy,
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (sourceIdleMs !== null && sourceIdleMs >= idleMs) {
|
|
1027
|
+
return {
|
|
1028
|
+
shouldAttempt: true,
|
|
1029
|
+
trigger: "inactivity",
|
|
1030
|
+
reason: "",
|
|
1031
|
+
sourceEvent: latestUncheckpointedSourceEvent,
|
|
1032
|
+
policy,
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return {
|
|
1037
|
+
shouldAttempt: false,
|
|
1038
|
+
trigger: "",
|
|
1039
|
+
reason: "checkpoint_event_count_wait",
|
|
1040
|
+
sourceEvent: latestUncheckpointedSourceEvent,
|
|
1041
|
+
policy,
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
|
|
843
1045
|
function createHealthSummaryBase(nowIso, session, agents) {
|
|
844
1046
|
return {
|
|
845
1047
|
sessionId: session.sessionId,
|
|
@@ -855,6 +1057,13 @@ function createHealthSummaryBase(nowIso, session, agents) {
|
|
|
855
1057
|
cursor: null,
|
|
856
1058
|
reason: "",
|
|
857
1059
|
},
|
|
1060
|
+
recap: {
|
|
1061
|
+
emitted: false,
|
|
1062
|
+
mode: "",
|
|
1063
|
+
reason: "",
|
|
1064
|
+
eventId: null,
|
|
1065
|
+
sourceEventCount: 0,
|
|
1066
|
+
},
|
|
858
1067
|
checkpoint: {
|
|
859
1068
|
attempted: false,
|
|
860
1069
|
ok: false,
|
|
@@ -1090,7 +1299,8 @@ async function pollAndRelayHumanMessages(
|
|
|
1090
1299
|
async function maybeGenerateSessionCheckpoint(
|
|
1091
1300
|
daemonState,
|
|
1092
1301
|
summary,
|
|
1093
|
-
nowIso = new Date().toISOString()
|
|
1302
|
+
nowIso = new Date().toISOString(),
|
|
1303
|
+
{ force = false, forceReason = "" } = {}
|
|
1094
1304
|
) {
|
|
1095
1305
|
const generator = daemonState.checkpointGenerator;
|
|
1096
1306
|
if (typeof generator !== "function") {
|
|
@@ -1103,6 +1313,16 @@ async function maybeGenerateSessionCheckpoint(
|
|
|
1103
1313
|
}
|
|
1104
1314
|
|
|
1105
1315
|
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
1316
|
+
const trigger = await resolveCheckpointTrigger(daemonState, normalizedNow, {
|
|
1317
|
+
force,
|
|
1318
|
+
forceReason,
|
|
1319
|
+
});
|
|
1320
|
+
summary.checkpoint.policy = trigger.policy;
|
|
1321
|
+
if (!trigger.shouldAttempt) {
|
|
1322
|
+
summary.checkpoint.reason = trigger.reason;
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1106
1326
|
const nowEpoch = parseEpoch(normalizedNow, normalizedNow);
|
|
1107
1327
|
const lastAttemptAt = normalizeString(daemonState.lastCheckpointAttemptAt);
|
|
1108
1328
|
const lastAttemptEpoch = lastAttemptAt ? parseEpoch(lastAttemptAt, normalizedNow) : 0;
|
|
@@ -1110,7 +1330,7 @@ async function maybeGenerateSessionCheckpoint(
|
|
|
1110
1330
|
daemonState.checkpointIntervalMs,
|
|
1111
1331
|
DEFAULT_CHECKPOINT_INTERVAL_MS
|
|
1112
1332
|
);
|
|
1113
|
-
if (lastAttemptEpoch > 0 && nowEpoch - lastAttemptEpoch < intervalMs) {
|
|
1333
|
+
if (!force && lastAttemptEpoch > 0 && nowEpoch - lastAttemptEpoch < intervalMs) {
|
|
1114
1334
|
summary.checkpoint.reason = "checkpoint_cadence_wait";
|
|
1115
1335
|
return;
|
|
1116
1336
|
}
|
|
@@ -1135,9 +1355,16 @@ async function maybeGenerateSessionCheckpoint(
|
|
|
1135
1355
|
reason: normalizeString(result?.reason),
|
|
1136
1356
|
checkpointId: normalizeString(result?.checkpointId || checkpoint?.checkpointId || checkpoint?.checkpoint_id) || null,
|
|
1137
1357
|
eventCount: Number.isFinite(Number(result?.eventCount)) ? Math.max(0, Math.floor(Number(result.eventCount))) : null,
|
|
1358
|
+
trigger: trigger.trigger,
|
|
1359
|
+
sourceEventCount: trigger.policy.sourceEventCount,
|
|
1360
|
+
latestSourceEventAt: trigger.policy.latestUncheckpointedSourceEventAt,
|
|
1361
|
+
policy: trigger.policy,
|
|
1138
1362
|
};
|
|
1139
1363
|
summary.checkpoint = normalizedResult;
|
|
1140
1364
|
daemonState.lastCheckpointResult = normalizedResult;
|
|
1365
|
+
if (result?.ok !== false || result?.created || result?.duplicate) {
|
|
1366
|
+
rememberCheckpointSource(daemonState, trigger.sourceEvent, normalizedNow);
|
|
1367
|
+
}
|
|
1141
1368
|
} catch (error) {
|
|
1142
1369
|
const failure = {
|
|
1143
1370
|
attempted: true,
|
|
@@ -1147,6 +1374,10 @@ async function maybeGenerateSessionCheckpoint(
|
|
|
1147
1374
|
reason: normalizeString(error?.message) || "checkpoint_generation_failed",
|
|
1148
1375
|
checkpointId: null,
|
|
1149
1376
|
eventCount: null,
|
|
1377
|
+
trigger: trigger.trigger,
|
|
1378
|
+
sourceEventCount: trigger.policy.sourceEventCount,
|
|
1379
|
+
latestSourceEventAt: trigger.policy.latestUncheckpointedSourceEventAt,
|
|
1380
|
+
policy: trigger.policy,
|
|
1150
1381
|
};
|
|
1151
1382
|
summary.checkpoint = failure;
|
|
1152
1383
|
daemonState.lastCheckpointResult = failure;
|
|
@@ -1155,6 +1386,42 @@ async function maybeGenerateSessionCheckpoint(
|
|
|
1155
1386
|
}
|
|
1156
1387
|
}
|
|
1157
1388
|
|
|
1389
|
+
async function maybeEmitPeriodicRecap(
|
|
1390
|
+
daemonState,
|
|
1391
|
+
summary,
|
|
1392
|
+
nowIso = new Date().toISOString()
|
|
1393
|
+
) {
|
|
1394
|
+
const emitter = daemonState.recapEmitter;
|
|
1395
|
+
if (!emitter || typeof emitter.tickNow !== "function") {
|
|
1396
|
+
summary.recap.reason = "disabled";
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
const emitted = await emitter.tickNow(nowIso);
|
|
1401
|
+
const emitterState =
|
|
1402
|
+
typeof emitter.getState === "function" ? emitter.getState() : {};
|
|
1403
|
+
const decision =
|
|
1404
|
+
emitterState.lastDecision && typeof emitterState.lastDecision === "object"
|
|
1405
|
+
? emitterState.lastDecision
|
|
1406
|
+
: null;
|
|
1407
|
+
if (!emitted) {
|
|
1408
|
+
summary.recap.mode = normalizeString(decision?.mode);
|
|
1409
|
+
summary.recap.reason = normalizeString(decision?.reason);
|
|
1410
|
+
summary.recap.sourceEventCount = Number(decision?.policy?.sourceEventCount || 0);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
summary.recap.emitted = true;
|
|
1414
|
+
summary.recap.mode = normalizeString(emitted.payload?.mode || decision?.mode);
|
|
1415
|
+
summary.recap.reason = "";
|
|
1416
|
+
summary.recap.eventId = normalizeString(emitted.eventId) || null;
|
|
1417
|
+
summary.recap.sourceEventCount = Number(
|
|
1418
|
+
emitted.payload?.sourceEventCount || decision?.sourceEventCount || 0
|
|
1419
|
+
);
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
summary.recap.reason = normalizeString(error?.message) || "recap_emit_failed";
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1158
1425
|
export async function runSentiHealthTick(
|
|
1159
1426
|
sessionId,
|
|
1160
1427
|
{
|
|
@@ -1189,10 +1456,14 @@ export async function runSentiHealthTick(
|
|
|
1189
1456
|
tickIntervalMs: DAEMON_TICK_INTERVAL_MS,
|
|
1190
1457
|
recapIntervalMs: DEFAULT_RECAP_INTERVAL_MS_OVERRIDE,
|
|
1191
1458
|
recapInactivityMs: DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE,
|
|
1459
|
+
recapActivityThreshold: DEFAULT_RECAP_ACTIVITY_THRESHOLD_OVERRIDE,
|
|
1192
1460
|
checkpointGenerator: null,
|
|
1193
1461
|
checkpointIntervalMs: DEFAULT_CHECKPOINT_INTERVAL_MS,
|
|
1194
1462
|
checkpointMinEvents: DEFAULT_CHECKPOINT_MIN_EVENTS,
|
|
1195
1463
|
checkpointMaxEvents: DEFAULT_CHECKPOINT_MAX_EVENTS,
|
|
1464
|
+
checkpointEventThreshold: DEFAULT_CHECKPOINT_EVENT_THRESHOLD,
|
|
1465
|
+
checkpointIdleMs: DEFAULT_CHECKPOINT_IDLE_MS,
|
|
1466
|
+
checkpointCloseoutOnStop: true,
|
|
1196
1467
|
helpResponder: null,
|
|
1197
1468
|
llmInvoker: invokeViaProxy,
|
|
1198
1469
|
telemetrySessionId: null,
|
|
@@ -1216,6 +1487,7 @@ export async function runSentiHealthTick(
|
|
|
1216
1487
|
await emitConflictAlerts(resolvedDaemonState, summary, filteredAgents, normalizedNow);
|
|
1217
1488
|
await maybeRenewActiveSession(resolvedDaemonState, summary, session, normalizedNow);
|
|
1218
1489
|
await pollAndRelayHumanMessages(resolvedDaemonState, summary, normalizedNow);
|
|
1490
|
+
await maybeEmitPeriodicRecap(resolvedDaemonState, summary, normalizedNow);
|
|
1219
1491
|
await maybeGenerateSessionCheckpoint(resolvedDaemonState, summary, normalizedNow);
|
|
1220
1492
|
return summary;
|
|
1221
1493
|
}
|
|
@@ -1231,10 +1503,14 @@ export async function startSenti(
|
|
|
1231
1503
|
helpRequestTimeoutMs = HELP_REQUEST_TIMEOUT_MS,
|
|
1232
1504
|
recapIntervalMs = DEFAULT_RECAP_INTERVAL_MS_OVERRIDE,
|
|
1233
1505
|
recapInactivityMs = DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE,
|
|
1506
|
+
recapActivityThreshold = DEFAULT_RECAP_ACTIVITY_THRESHOLD_OVERRIDE,
|
|
1234
1507
|
checkpointGenerator = generateSessionCheckpointBestEffort,
|
|
1235
1508
|
checkpointIntervalMs = DEFAULT_CHECKPOINT_INTERVAL_MS,
|
|
1236
1509
|
checkpointMinEvents = DEFAULT_CHECKPOINT_MIN_EVENTS,
|
|
1237
1510
|
checkpointMaxEvents = DEFAULT_CHECKPOINT_MAX_EVENTS,
|
|
1511
|
+
checkpointEventThreshold = DEFAULT_CHECKPOINT_EVENT_THRESHOLD,
|
|
1512
|
+
checkpointIdleMs = DEFAULT_CHECKPOINT_IDLE_MS,
|
|
1513
|
+
checkpointCloseoutOnStop = true,
|
|
1238
1514
|
helpResponder = null,
|
|
1239
1515
|
llmInvoker = invokeViaProxy,
|
|
1240
1516
|
} = {}
|
|
@@ -1274,6 +1550,10 @@ export async function startSenti(
|
|
|
1274
1550
|
recapInactivityMs,
|
|
1275
1551
|
DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE
|
|
1276
1552
|
);
|
|
1553
|
+
const normalizedRecapActivityThreshold = Math.min(
|
|
1554
|
+
200,
|
|
1555
|
+
normalizePositiveInteger(recapActivityThreshold, DEFAULT_RECAP_ACTIVITY_THRESHOLD_OVERRIDE)
|
|
1556
|
+
);
|
|
1277
1557
|
const normalizedCheckpointIntervalMs = normalizePositiveInteger(
|
|
1278
1558
|
checkpointIntervalMs,
|
|
1279
1559
|
DEFAULT_CHECKPOINT_INTERVAL_MS
|
|
@@ -1286,6 +1566,14 @@ export async function startSenti(
|
|
|
1286
1566
|
normalizedCheckpointMinEvents,
|
|
1287
1567
|
Math.min(200, normalizePositiveInteger(checkpointMaxEvents, DEFAULT_CHECKPOINT_MAX_EVENTS))
|
|
1288
1568
|
);
|
|
1569
|
+
const normalizedCheckpointEventThreshold = Math.min(
|
|
1570
|
+
200,
|
|
1571
|
+
normalizePositiveInteger(checkpointEventThreshold, DEFAULT_CHECKPOINT_EVENT_THRESHOLD)
|
|
1572
|
+
);
|
|
1573
|
+
const normalizedCheckpointIdleMs = normalizePositiveInteger(
|
|
1574
|
+
checkpointIdleMs,
|
|
1575
|
+
DEFAULT_CHECKPOINT_IDLE_MS
|
|
1576
|
+
);
|
|
1289
1577
|
const nowIso = new Date().toISOString();
|
|
1290
1578
|
const telemetrySession = startTelemetrySession(`session daemon ${normalizedSessionId}`);
|
|
1291
1579
|
const daemonState = createSentiState({
|
|
@@ -1299,10 +1587,14 @@ export async function startSenti(
|
|
|
1299
1587
|
tickIntervalMs: normalizedTickIntervalMs,
|
|
1300
1588
|
recapIntervalMs: normalizedRecapIntervalMs,
|
|
1301
1589
|
recapInactivityMs: normalizedRecapInactivityMs,
|
|
1590
|
+
recapActivityThreshold: normalizedRecapActivityThreshold,
|
|
1302
1591
|
checkpointGenerator: typeof checkpointGenerator === "function" ? checkpointGenerator : null,
|
|
1303
1592
|
checkpointIntervalMs: normalizedCheckpointIntervalMs,
|
|
1304
1593
|
checkpointMinEvents: normalizedCheckpointMinEvents,
|
|
1305
1594
|
checkpointMaxEvents: normalizedCheckpointMaxEvents,
|
|
1595
|
+
checkpointEventThreshold: normalizedCheckpointEventThreshold,
|
|
1596
|
+
checkpointIdleMs: normalizedCheckpointIdleMs,
|
|
1597
|
+
checkpointCloseoutOnStop: checkpointCloseoutOnStop !== false,
|
|
1306
1598
|
helpResponder,
|
|
1307
1599
|
llmInvoker: typeof llmInvoker === "function" ? llmInvoker : invokeViaProxy,
|
|
1308
1600
|
telemetrySessionId: telemetrySession?.id || null,
|
|
@@ -1377,6 +1669,45 @@ export async function startSenti(
|
|
|
1377
1669
|
daemonState.recapEmitter = null;
|
|
1378
1670
|
}
|
|
1379
1671
|
|
|
1672
|
+
let checkpointCloseout = null;
|
|
1673
|
+
if (daemonState.checkpointCloseoutOnStop) {
|
|
1674
|
+
const checkpointNowIso = new Date().toISOString();
|
|
1675
|
+
try {
|
|
1676
|
+
const closeoutSession = await getSession(normalizedSessionId, {
|
|
1677
|
+
targetPath: normalizedTargetPath,
|
|
1678
|
+
});
|
|
1679
|
+
const closeoutAgents = await listAgents(normalizedSessionId, {
|
|
1680
|
+
targetPath: normalizedTargetPath,
|
|
1681
|
+
includeInactive: false,
|
|
1682
|
+
});
|
|
1683
|
+
const closeoutSummary = createHealthSummaryBase(
|
|
1684
|
+
checkpointNowIso,
|
|
1685
|
+
closeoutSession || session,
|
|
1686
|
+
closeoutAgents
|
|
1687
|
+
);
|
|
1688
|
+
await maybeGenerateSessionCheckpoint(
|
|
1689
|
+
daemonState,
|
|
1690
|
+
closeoutSummary,
|
|
1691
|
+
checkpointNowIso,
|
|
1692
|
+
{
|
|
1693
|
+
force: true,
|
|
1694
|
+
forceReason: "closeout",
|
|
1695
|
+
}
|
|
1696
|
+
);
|
|
1697
|
+
checkpointCloseout = closeoutSummary.checkpoint;
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
checkpointCloseout = {
|
|
1700
|
+
attempted: true,
|
|
1701
|
+
ok: false,
|
|
1702
|
+
created: false,
|
|
1703
|
+
duplicate: false,
|
|
1704
|
+
reason: normalizeString(error?.message) || "checkpoint_closeout_failed",
|
|
1705
|
+
checkpointId: null,
|
|
1706
|
+
eventCount: null,
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1380
1711
|
let runtimeStopSummary = null;
|
|
1381
1712
|
try {
|
|
1382
1713
|
runtimeStopSummary = await stopRuntimeRunsForSession(normalizedSessionId, {
|
|
@@ -1408,6 +1739,7 @@ export async function startSenti(
|
|
|
1408
1739
|
target: SENTI_IDENTITY.id,
|
|
1409
1740
|
reason: normalizeString(reason) || "manual_stop",
|
|
1410
1741
|
runtimeStops: runtimeStopSummary?.stoppedCount || 0,
|
|
1742
|
+
checkpointCloseout,
|
|
1411
1743
|
},
|
|
1412
1744
|
{
|
|
1413
1745
|
targetPath: normalizedTargetPath,
|
|
@@ -1424,6 +1756,7 @@ export async function startSenti(
|
|
|
1424
1756
|
sessionId: normalizedSessionId,
|
|
1425
1757
|
targetPath: normalizedTargetPath,
|
|
1426
1758
|
reason: normalizeString(reason) || "manual_stop",
|
|
1759
|
+
checkpointCloseout,
|
|
1427
1760
|
runtimeStopSummary,
|
|
1428
1761
|
event: killedEvent,
|
|
1429
1762
|
};
|
|
@@ -1448,10 +1781,15 @@ export async function startSenti(
|
|
|
1448
1781
|
staleAlertedAgents: [...daemonState.staleAlertedAgents],
|
|
1449
1782
|
pendingHelpRequests: daemonState.pendingHelpTimers.size,
|
|
1450
1783
|
recapRunning: Boolean(daemonState.recapEmitter?.isRunning?.()),
|
|
1784
|
+
recapActivityThreshold: daemonState.recapActivityThreshold,
|
|
1451
1785
|
checkpointIntervalMs: daemonState.checkpointIntervalMs,
|
|
1452
1786
|
checkpointMinEvents: daemonState.checkpointMinEvents,
|
|
1453
1787
|
checkpointMaxEvents: daemonState.checkpointMaxEvents,
|
|
1788
|
+
checkpointEventThreshold: daemonState.checkpointEventThreshold,
|
|
1789
|
+
checkpointIdleMs: daemonState.checkpointIdleMs,
|
|
1454
1790
|
lastCheckpointAttemptAt: daemonState.lastCheckpointAttemptAt,
|
|
1791
|
+
lastCheckpointSourceEventAt: daemonState.lastCheckpointSourceEventAt,
|
|
1792
|
+
lastCheckpointSourceSequenceId: daemonState.lastCheckpointSourceSequenceId,
|
|
1455
1793
|
lastCheckpointResult: daemonState.lastCheckpointResult,
|
|
1456
1794
|
humanMessageCursor: daemonState.humanMessageCursor,
|
|
1457
1795
|
}),
|
|
@@ -1466,6 +1804,7 @@ export async function startSenti(
|
|
|
1466
1804
|
targetPath: normalizedTargetPath,
|
|
1467
1805
|
intervalMs: daemonState.recapIntervalMs,
|
|
1468
1806
|
inactivityMs: daemonState.recapInactivityMs,
|
|
1807
|
+
newEventThreshold: daemonState.recapActivityThreshold,
|
|
1469
1808
|
});
|
|
1470
1809
|
|
|
1471
1810
|
if (autoStart) {
|