@raidiant/notifai 10.1.0 → 10.1.2
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.md +37 -0
- package/README.md +3 -0
- package/dist/cli-bin.d.ts +21 -1
- package/dist/cli-bin.js +141 -10
- package/dist/cli-bin.js.map +1 -1
- package/dist/cli-update-contract.d.ts +7 -0
- package/dist/cli-update-contract.js +10 -0
- package/dist/cli-update-contract.js.map +1 -0
- package/dist/client.d.ts +6 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/codex-wake.d.ts +2 -0
- package/dist/codex-wake.js +2 -1
- package/dist/codex-wake.js.map +1 -1
- package/dist/commands-auth.d.ts +15 -1
- package/dist/commands-auth.js +29 -11
- package/dist/commands-auth.js.map +1 -1
- package/dist/commands-core.d.ts +9 -2
- package/dist/commands-core.js +11 -4
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-devices.d.ts +10 -1
- package/dist/commands-devices.js +17 -13
- package/dist/commands-devices.js.map +1 -1
- package/dist/commands-doctor.js +93 -49
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-hooks.js +47 -10
- package/dist/commands-hooks.js.map +1 -1
- package/dist/commands-init.d.ts +16 -0
- package/dist/commands-init.js +254 -93
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send-support.js +3 -0
- package/dist/commands-send-support.js.map +1 -1
- package/dist/commands-send.d.ts +1 -1
- package/dist/commands-send.js +121 -35
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-skill.js +72 -54
- package/dist/commands-skill.js.map +1 -1
- package/dist/commands-update.d.ts +10 -0
- package/dist/commands-update.js +141 -0
- package/dist/commands-update.js.map +1 -0
- package/dist/commands.d.ts +2 -1
- package/dist/commands.js +2 -1
- package/dist/commands.js.map +1 -1
- package/dist/hook-adapter.d.ts +2 -0
- package/dist/hook-adapter.js +12 -1
- package/dist/hook-adapter.js.map +1 -1
- package/dist/hooks.d.ts +43 -2
- package/dist/hooks.js +163 -21
- package/dist/hooks.js.map +1 -1
- package/dist/invocation-context.d.ts +1 -0
- package/dist/invocation-context.js +9 -1
- package/dist/invocation-context.js.map +1 -1
- package/dist/main-run.d.ts +1 -0
- package/dist/main-run.js +42 -0
- package/dist/main-run.js.map +1 -0
- package/dist/main.js +10 -31
- package/dist/main.js.map +1 -1
- package/dist/native-skills.d.ts +13 -3
- package/dist/native-skills.js +51 -12
- package/dist/native-skills.js.map +1 -1
- package/dist/node-floor.d.ts +19 -0
- package/dist/node-floor.js +39 -0
- package/dist/node-floor.js.map +1 -0
- package/dist/orca-session-title.d.ts +5 -4
- package/dist/orca-session-title.js +32 -19
- package/dist/orca-session-title.js.map +1 -1
- package/dist/platform.d.ts +11 -17
- package/dist/platform.js +20 -23
- package/dist/platform.js.map +1 -1
- package/dist/program.d.ts +2 -1
- package/dist/program.js +22 -5
- package/dist/program.js.map +1 -1
- package/dist/question-settlement-process.d.ts +15 -0
- package/dist/question-settlement-process.js +35 -0
- package/dist/question-settlement-process.js.map +1 -0
- package/dist/readiness.d.ts +25 -1
- package/dist/readiness.js +32 -2
- package/dist/readiness.js.map +1 -1
- package/dist/release.d.ts +5 -5
- package/dist/release.js +5 -5
- package/dist/setup-destinations.d.ts +23 -0
- package/dist/setup-destinations.js +44 -0
- package/dist/setup-destinations.js.map +1 -0
- package/dist/skill-integrity.d.ts +49 -0
- package/dist/skill-integrity.js +154 -0
- package/dist/skill-integrity.js.map +1 -0
- package/dist/skill-source/manifest.json +16 -0
- package/dist/skill-source/notifai/SKILL.md +390 -0
- package/dist/skill-source/notifai/references/harness-setup.md +220 -0
- package/package.json +11 -14
- package/src/atomic-file.ts +0 -198
- package/src/claude-wake.test.ts +0 -321
- package/src/claude-wake.ts +0 -499
- package/src/cli-bin.test.ts +0 -38
- package/src/cli-bin.ts +0 -67
- package/src/cli-release.test.ts +0 -46
- package/src/cli-release.ts +0 -78
- package/src/client.test.ts +0 -390
- package/src/client.ts +0 -345
- package/src/codex-wake.test.ts +0 -254
- package/src/codex-wake.ts +0 -297
- package/src/command-session.ts +0 -38
- package/src/commands-agent-sessions.ts +0 -72
- package/src/commands-auth.ts +0 -205
- package/src/commands-config.ts +0 -336
- package/src/commands-core.ts +0 -298
- package/src/commands-devices.test.ts +0 -15
- package/src/commands-devices.ts +0 -173
- package/src/commands-doctor.ts +0 -1440
- package/src/commands-guidance.ts +0 -181
- package/src/commands-harness-context.ts +0 -207
- package/src/commands-hook-shape.ts +0 -69
- package/src/commands-hooks.ts +0 -2268
- package/src/commands-init.ts +0 -878
- package/src/commands-io.ts +0 -108
- package/src/commands-logs.ts +0 -243
- package/src/commands-project.ts +0 -47
- package/src/commands-send-support.test.ts +0 -156
- package/src/commands-send-support.ts +0 -311
- package/src/commands-send.ts +0 -769
- package/src/commands-setup-proof.test.ts +0 -72
- package/src/commands-setup-proof.ts +0 -124
- package/src/commands-skill.test.ts +0 -44
- package/src/commands-skill.ts +0 -204
- package/src/commands-sounds.test.ts +0 -106
- package/src/commands-sounds.ts +0 -49
- package/src/commands.test.ts +0 -7828
- package/src/commands.ts +0 -75
- package/src/config-schema.test.ts +0 -153
- package/src/config-schema.ts +0 -349
- package/src/config.test.ts +0 -581
- package/src/config.ts +0 -475
- package/src/credentials.test.ts +0 -403
- package/src/credentials.ts +0 -403
- package/src/file-lock-process-worker.test.ts +0 -64
- package/src/file-lock.test.ts +0 -320
- package/src/file-lock.ts +0 -393
- package/src/fixtures/hermes-0.20.6-classic-cli-local.json +0 -50
- package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +0 -57
- package/src/generated-session-label.ts +0 -155
- package/src/guidance-content.ts +0 -278
- package/src/guidance-render.ts +0 -56
- package/src/guidance.test.ts +0 -327
- package/src/guidance.ts +0 -233
- package/src/harness-integration.test.ts +0 -294
- package/src/harnesses.test.ts +0 -149
- package/src/harnesses.ts +0 -164
- package/src/hook-adapter.test.ts +0 -496
- package/src/hook-adapter.ts +0 -410
- package/src/hook-input.test.ts +0 -39
- package/src/hook-input.ts +0 -72
- package/src/hooks.test.ts +0 -5549
- package/src/hooks.ts +0 -3759
- package/src/install-hooks.test.ts +0 -1614
- package/src/install-hooks.ts +0 -1501
- package/src/interactive.test.ts +0 -387
- package/src/interactive.ts +0 -858
- package/src/invocation-context.test.ts +0 -352
- package/src/invocation-context.ts +0 -197
- package/src/logging-process-worker.test.ts +0 -45
- package/src/logging.test.ts +0 -544
- package/src/logging.ts +0 -698
- package/src/main.ts +0 -36
- package/src/native-skills.test.ts +0 -115
- package/src/native-skills.ts +0 -162
- package/src/onboarding-docs.test.ts +0 -25
- package/src/openclaw-plugin.ts +0 -409
- package/src/opencode-plugin.ts +0 -298
- package/src/orca-session-title.test.ts +0 -54
- package/src/orca-session-title.ts +0 -103
- package/src/pack-feedback-slice.test.ts +0 -212
- package/src/pack-feedback-slice.ts +0 -187
- package/src/platform.test.ts +0 -152
- package/src/platform.ts +0 -173
- package/src/program.test.ts +0 -282
- package/src/program.ts +0 -818
- package/src/project-enablement.test.ts +0 -55
- package/src/project-enablement.ts +0 -99
- package/src/question-caps.test.ts +0 -49
- package/src/question-timing.ts +0 -56
- package/src/readiness.test.ts +0 -25
- package/src/readiness.ts +0 -179
- package/src/release.ts +0 -51
- package/src/send-attempts.test.ts +0 -143
- package/src/send-attempts.ts +0 -188
- package/src/send.test.ts +0 -28
- package/src/send.ts +0 -436
- package/src/session-activation.ts +0 -61
- package/src/session-end-log-lock-worker.test.ts +0 -35
- package/src/session-labels.test.ts +0 -490
- package/src/session-labels.ts +0 -474
- package/src/skill.test.ts +0 -415
- package/src/sound-ref.test.ts +0 -31
- package/src/sound-ref.ts +0 -34
- package/src/trust-policy.test.ts +0 -701
- package/src/ui/banner.ts +0 -95
- package/src/ui/config-view.test.ts +0 -90
- package/src/ui/config-view.ts +0 -138
- package/src/ui/help.test.ts +0 -16
- package/src/ui/help.ts +0 -128
- package/src/ui/theme.test.ts +0 -92
- package/src/ui/theme.ts +0 -265
- package/src/url-policy.ts +0 -399
- package/tsconfig.json +0 -10
package/dist/hooks.js
CHANGED
|
@@ -148,10 +148,11 @@ export function findOwningSession(id, env) {
|
|
|
148
148
|
const sessionId = persisted.session_id;
|
|
149
149
|
const state = persisted;
|
|
150
150
|
const pendingMatch = pendingList(state).some((entry) => entry.question_id === id || entry.request_id === id);
|
|
151
|
-
const retiringMatch = (state.retiring ?? []).some((entry) => entry.request_id === id);
|
|
151
|
+
const retiringMatch = (state.retiring ?? []).some((entry) => entry.question_id === id || entry.request_id === id);
|
|
152
|
+
const historyMatch = (state.question_history ?? []).some((entry) => entry.question_id === id || entry.request_id === id);
|
|
152
153
|
const acknowledgementMatch = (state.acknowledgement_due ?? []).some((entry) => entry.request_id === id);
|
|
153
154
|
const acceptedMatch = state.accepted?.answers.some(({ pending }) => pending.question_id === id || pending.request_id === id) ?? false;
|
|
154
|
-
if (pendingMatch || retiringMatch || acknowledgementMatch || acceptedMatch) {
|
|
155
|
+
if (pendingMatch || retiringMatch || historyMatch || acknowledgementMatch || acceptedMatch) {
|
|
155
156
|
matches.push(sessionId);
|
|
156
157
|
if (matches.length > 1)
|
|
157
158
|
return { sessionId: null, ambiguous: true };
|
|
@@ -859,15 +860,16 @@ export function parkForRetirement(sessionId, env, pending, state) {
|
|
|
859
860
|
updateSessionState(sessionId, env, (current) => {
|
|
860
861
|
const already = current.retiring ?? [];
|
|
861
862
|
const existing = already.findIndex((entry) => entry.request_id === retirement.request_id);
|
|
862
|
-
if (existing < 0)
|
|
863
|
-
return { ...current, retiring: [...already, retirement] };
|
|
863
|
+
if (existing < 0) {
|
|
864
|
+
return rememberQuestionState({ ...current, retiring: [...already, retirement] }, pending, state === 'answered' ? 'answered' : 'retired');
|
|
865
|
+
}
|
|
864
866
|
const retiring = [...already];
|
|
865
867
|
// An observed answer is final truth and upgrades an earlier supersession
|
|
866
868
|
// parked while the submission callback was racing a newer question.
|
|
867
869
|
if (retirement.state === 'answered' && retiring[existing].state !== 'answered') {
|
|
868
870
|
retiring[existing] = retirement;
|
|
869
871
|
}
|
|
870
|
-
return { ...current, retiring };
|
|
872
|
+
return rememberQuestionState({ ...current, retiring }, pending, state === 'answered' ? 'answered' : 'retired');
|
|
871
873
|
});
|
|
872
874
|
}
|
|
873
875
|
/**
|
|
@@ -896,6 +898,7 @@ function retiringQuestion(pending, state, cwd) {
|
|
|
896
898
|
}
|
|
897
899
|
const source = sourceContextAtHookEvent(pending.source, cwd);
|
|
898
900
|
return {
|
|
901
|
+
...(pending.question_id !== undefined ? { question_id: pending.question_id } : {}),
|
|
899
902
|
request_id: requestId,
|
|
900
903
|
collapse_key: collapseKey,
|
|
901
904
|
device_ids: [...deviceIds],
|
|
@@ -1133,6 +1136,122 @@ async function pollPendingReplies(ctx, live, timeoutSeconds) {
|
|
|
1133
1136
|
function pendingList(state) {
|
|
1134
1137
|
return Array.isArray(state.pending) ? state.pending : [];
|
|
1135
1138
|
}
|
|
1139
|
+
const QUESTION_HISTORY_CAP = 50;
|
|
1140
|
+
/** Keep a bounded, content-free map from local identity to its terminal state. */
|
|
1141
|
+
function rememberQuestionState(current, question, state) {
|
|
1142
|
+
if (question.question_id === undefined)
|
|
1143
|
+
return current;
|
|
1144
|
+
const existing = (current.question_history ?? []).find((entry) => entry.question_id === question.question_id);
|
|
1145
|
+
const terminalState = existing?.state === 'withdrawn' || existing?.state === 'answered'
|
|
1146
|
+
? existing.state
|
|
1147
|
+
: state === 'answered'
|
|
1148
|
+
? 'answered'
|
|
1149
|
+
: state;
|
|
1150
|
+
const requestId = question.request_id ?? existing?.request_id;
|
|
1151
|
+
const frozenRequestId = requestId === undefined
|
|
1152
|
+
? question.submission?.request_id ?? existing?.frozen_request_id
|
|
1153
|
+
: undefined;
|
|
1154
|
+
const record = {
|
|
1155
|
+
question_id: question.question_id,
|
|
1156
|
+
state: terminalState,
|
|
1157
|
+
...(requestId === undefined ? {} : { request_id: requestId }),
|
|
1158
|
+
...(frozenRequestId === undefined ? {} : { frozen_request_id: frozenRequestId }),
|
|
1159
|
+
};
|
|
1160
|
+
const history = [
|
|
1161
|
+
...(current.question_history ?? []).filter((entry) => entry.question_id !== question.question_id),
|
|
1162
|
+
record,
|
|
1163
|
+
].slice(-QUESTION_HISTORY_CAP);
|
|
1164
|
+
return { ...current, question_history: history };
|
|
1165
|
+
}
|
|
1166
|
+
function pendingQuestionView(entry) {
|
|
1167
|
+
if (entry.question_id === undefined)
|
|
1168
|
+
return null;
|
|
1169
|
+
if (entry.request_id !== undefined) {
|
|
1170
|
+
return {
|
|
1171
|
+
question_id: entry.question_id,
|
|
1172
|
+
state: 'live',
|
|
1173
|
+
submitted: true,
|
|
1174
|
+
request_id: entry.request_id,
|
|
1175
|
+
frozen_request_id: null,
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
if (entry.submission !== undefined) {
|
|
1179
|
+
return {
|
|
1180
|
+
question_id: entry.question_id,
|
|
1181
|
+
state: 'frozen',
|
|
1182
|
+
submitted: null,
|
|
1183
|
+
request_id: null,
|
|
1184
|
+
frozen_request_id: entry.submission.request_id,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
question_id: entry.question_id,
|
|
1189
|
+
state: 'local',
|
|
1190
|
+
submitted: false,
|
|
1191
|
+
request_id: null,
|
|
1192
|
+
frozen_request_id: null,
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
/** Read one stable local question identity without minting or submitting anything. */
|
|
1196
|
+
export function inspectQuestionState(questionId, env) {
|
|
1197
|
+
const owner = findOwningSession(questionId, env);
|
|
1198
|
+
if (owner.ambiguous || owner.sessionId === null) {
|
|
1199
|
+
return { found: false, ambiguous: owner.ambiguous };
|
|
1200
|
+
}
|
|
1201
|
+
const state = readSessionState(owner.sessionId, env);
|
|
1202
|
+
const pending = pendingList(state).find((entry) => entry.question_id === questionId);
|
|
1203
|
+
const pendingView = pending === undefined ? null : pendingQuestionView(pending);
|
|
1204
|
+
if (pendingView !== null) {
|
|
1205
|
+
return { found: true, session_id: owner.sessionId, question: pendingView };
|
|
1206
|
+
}
|
|
1207
|
+
const accepted = state.accepted?.answers.find(({ pending: entry }) => entry.question_id === questionId)?.pending;
|
|
1208
|
+
if (accepted?.question_id !== undefined) {
|
|
1209
|
+
return {
|
|
1210
|
+
found: true,
|
|
1211
|
+
session_id: owner.sessionId,
|
|
1212
|
+
question: {
|
|
1213
|
+
question_id: accepted.question_id,
|
|
1214
|
+
state: 'answered',
|
|
1215
|
+
submitted: true,
|
|
1216
|
+
request_id: accepted.request_id ?? null,
|
|
1217
|
+
frozen_request_id: null,
|
|
1218
|
+
},
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
const retiring = (state.retiring ?? []).find((entry) => entry.question_id === questionId);
|
|
1222
|
+
if (retiring?.question_id !== undefined) {
|
|
1223
|
+
return {
|
|
1224
|
+
found: true,
|
|
1225
|
+
session_id: owner.sessionId,
|
|
1226
|
+
question: {
|
|
1227
|
+
question_id: retiring.question_id,
|
|
1228
|
+
state: retiring.state === 'answered' ? 'answered' : 'retired',
|
|
1229
|
+
submitted: true,
|
|
1230
|
+
request_id: retiring.request_id,
|
|
1231
|
+
frozen_request_id: null,
|
|
1232
|
+
},
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
const history = (state.question_history ?? []).find((entry) => entry.question_id === questionId);
|
|
1236
|
+
if (history !== undefined) {
|
|
1237
|
+
return {
|
|
1238
|
+
found: true,
|
|
1239
|
+
session_id: owner.sessionId,
|
|
1240
|
+
question: {
|
|
1241
|
+
question_id: history.question_id,
|
|
1242
|
+
state: history.state,
|
|
1243
|
+
submitted: history.request_id !== undefined
|
|
1244
|
+
? true
|
|
1245
|
+
: history.frozen_request_id !== undefined
|
|
1246
|
+
? null
|
|
1247
|
+
: false,
|
|
1248
|
+
request_id: history.request_id ?? null,
|
|
1249
|
+
frozen_request_id: history.frozen_request_id ?? null,
|
|
1250
|
+
},
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return { found: false, ambiguous: false };
|
|
1254
|
+
}
|
|
1136
1255
|
/** Registration identity — the convention every racing writer compares by. */
|
|
1137
1256
|
function isSamePending(a, b) {
|
|
1138
1257
|
if (a.question_id !== undefined || b.question_id !== undefined) {
|
|
@@ -1148,7 +1267,7 @@ function isSamePending(a, b) {
|
|
|
1148
1267
|
function isTerminalDraftRejection(err) {
|
|
1149
1268
|
return err.status === 422 || (err.status === 400 && err.code === 'invalid_request');
|
|
1150
1269
|
}
|
|
1151
|
-
export function dropPendingQuestion(sessionId, env, entry) {
|
|
1270
|
+
export function dropPendingQuestion(sessionId, env, entry, terminalState = 'retired') {
|
|
1152
1271
|
updateSessionState(sessionId, env, (current) => {
|
|
1153
1272
|
const pending = pendingList(current).filter((candidate) => !isSamePending(candidate, entry));
|
|
1154
1273
|
const next = { ...current };
|
|
@@ -1156,7 +1275,7 @@ export function dropPendingQuestion(sessionId, env, entry) {
|
|
|
1156
1275
|
next.pending = pending;
|
|
1157
1276
|
else
|
|
1158
1277
|
delete next.pending;
|
|
1159
|
-
return next;
|
|
1278
|
+
return rememberQuestionState(next, entry, terminalState);
|
|
1160
1279
|
});
|
|
1161
1280
|
}
|
|
1162
1281
|
/**
|
|
@@ -1177,7 +1296,7 @@ export function withdrawUnpushedQuestions(sessionId, env) {
|
|
|
1177
1296
|
next.pending = remaining;
|
|
1178
1297
|
else
|
|
1179
1298
|
delete next.pending;
|
|
1180
|
-
return next;
|
|
1299
|
+
return withdrawn.reduce((remembered, entry) => rememberQuestionState(remembered, entry, 'withdrawn'), next);
|
|
1181
1300
|
});
|
|
1182
1301
|
return withdrawn;
|
|
1183
1302
|
}
|
|
@@ -1336,7 +1455,7 @@ function stageAcceptedAnswers(ctx, sessionId, answered, remaining) {
|
|
|
1336
1455
|
next.acknowledgement_due = acknowledgementDue;
|
|
1337
1456
|
else
|
|
1338
1457
|
delete next.acknowledgement_due;
|
|
1339
|
-
return next;
|
|
1458
|
+
return answered.reduce((remembered, answer) => rememberQuestionState(remembered, answer.pending, 'answered'), next);
|
|
1340
1459
|
});
|
|
1341
1460
|
return accepted;
|
|
1342
1461
|
}
|
|
@@ -1530,7 +1649,7 @@ async function retirePendings(ctx, envelope, sessionId, entries, state) {
|
|
|
1530
1649
|
next.retiring = retiring;
|
|
1531
1650
|
else
|
|
1532
1651
|
delete next.retiring;
|
|
1533
|
-
return next;
|
|
1652
|
+
return entries.reduce((remembered, entry) => rememberQuestionState(remembered, entry, state === 'answered' ? 'answered' : 'retired'), next);
|
|
1534
1653
|
});
|
|
1535
1654
|
// Network retirement is deliberately deferred to a later no-question hook.
|
|
1536
1655
|
// The state write above is the durable operation; blocking here creates a
|
|
@@ -1610,7 +1729,7 @@ export async function handleUserPromptSubmit(ctx, envelope) {
|
|
|
1610
1729
|
// Park before dropping `pending`. If the process dies between these writes,
|
|
1611
1730
|
// the next hook sees both copies and dedupes them; the old order could die in
|
|
1612
1731
|
// the gap after erasing the only request/collapse/device identifiers.
|
|
1613
|
-
updateSessionState(sessionId, ctx.env, (current) => {
|
|
1732
|
+
const updated = updateSessionState(sessionId, ctx.env, (current) => {
|
|
1614
1733
|
const retiring = [...(current.retiring ?? [])];
|
|
1615
1734
|
const matchedNow = pendingAnsweredByPrompt(envelope.prompt, pendingList(current));
|
|
1616
1735
|
const unmatched = pendingList(current).filter((entry) => !matchedNow.some((item) => isSamePending(item, entry)));
|
|
@@ -1647,7 +1766,7 @@ export async function handleUserPromptSubmit(ctx, envelope) {
|
|
|
1647
1766
|
if (current.continuation !== undefined) {
|
|
1648
1767
|
next.continuation = { ...current.continuation, count: 0 };
|
|
1649
1768
|
}
|
|
1650
|
-
return next;
|
|
1769
|
+
return matchedNow.reduce((remembered, entry) => rememberQuestionState(remembered, entry, entry.request_id === undefined ? 'withdrawn' : 'retired'), next);
|
|
1651
1770
|
});
|
|
1652
1771
|
// The bridge that lets a plain `notifai ask` find the hook's canonical
|
|
1653
1772
|
// session: an agent shell command gets no hook payload, and not every
|
|
@@ -1660,7 +1779,10 @@ export async function handleUserPromptSubmit(ctx, envelope) {
|
|
|
1660
1779
|
// model. Keep the journal and let the next Stop use its acknowledged
|
|
1661
1780
|
// continuation channel instead of recreating the crash-before-stdout gap.
|
|
1662
1781
|
notes.push('the late device answer will continue the agent at this turn’s Stop');
|
|
1663
|
-
return {
|
|
1782
|
+
return {
|
|
1783
|
+
notes,
|
|
1784
|
+
settlementRequired: pendingList(updated).some((entry) => entry.request_id === undefined),
|
|
1785
|
+
};
|
|
1664
1786
|
}
|
|
1665
1787
|
const retired = await drainRetirements(ctx, sessionId, ctx.env);
|
|
1666
1788
|
const orphaned = await drainOrphanRetirements(ctx, ctx.env, ctx.now());
|
|
@@ -1668,7 +1790,10 @@ export async function handleUserPromptSubmit(ctx, envelope) {
|
|
|
1668
1790
|
if (swept.length > 0) {
|
|
1669
1791
|
notes.push(`retired question${swept.length > 1 ? 's' : ''} ${swept.join(', ')}`);
|
|
1670
1792
|
}
|
|
1671
|
-
return {
|
|
1793
|
+
return {
|
|
1794
|
+
notes,
|
|
1795
|
+
settlementRequired: pendingList(updated).some((entry) => entry.request_id === undefined),
|
|
1796
|
+
};
|
|
1672
1797
|
}
|
|
1673
1798
|
// ---------------------------------------------------------------------------
|
|
1674
1799
|
// Stop — the turn ended; escalate a registered question
|
|
@@ -1678,7 +1803,7 @@ export async function handleUserPromptSubmit(ctx, envelope) {
|
|
|
1678
1803
|
* `notifai ask`. Guessing from the last assistant message was the alternative
|
|
1679
1804
|
* and it is not worth it: a false positive here hijacks the terminal.
|
|
1680
1805
|
*/
|
|
1681
|
-
export async function handleStop(ctx, envelope, processDeadlineAt = ctx.now() + QUESTION_WAITER_CEILING_SECONDS * 1000, route = hookContinuationRoute()) {
|
|
1806
|
+
export async function handleStop(ctx, envelope, processDeadlineAt = ctx.now() + QUESTION_WAITER_CEILING_SECONDS * 1000, route = hookContinuationRoute(), recordStop = true) {
|
|
1682
1807
|
const sessionId = envelope.session_id;
|
|
1683
1808
|
if (!sessionId) {
|
|
1684
1809
|
gate(ctx, 'held', 'no-session', { stage: 'queued' });
|
|
@@ -1689,6 +1814,7 @@ export async function handleStop(ctx, envelope, processDeadlineAt = ctx.now() +
|
|
|
1689
1814
|
envelope,
|
|
1690
1815
|
route,
|
|
1691
1816
|
processDeadlineAt,
|
|
1817
|
+
recordStop,
|
|
1692
1818
|
});
|
|
1693
1819
|
}
|
|
1694
1820
|
/**
|
|
@@ -1734,11 +1860,13 @@ export async function runEscalationWaiter(ctx, options) {
|
|
|
1734
1860
|
// This event marker is diagnostic evidence in its own right. Record it
|
|
1735
1861
|
// before every early return so doctor can distinguish a working Stop route
|
|
1736
1862
|
// from the prompt-only state that previously looked healthy.
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1863
|
+
if (options.recordStop !== false) {
|
|
1864
|
+
updateSessionState(sessionId, ctx.env, (current) => ({
|
|
1865
|
+
...current,
|
|
1866
|
+
...(ctx.harness === undefined ? {} : { harness: ctx.harness }),
|
|
1867
|
+
last_stop_at: ctx.now(),
|
|
1868
|
+
}));
|
|
1869
|
+
}
|
|
1742
1870
|
// The waiter, not its host, owns this lease. It spans grace, submission,
|
|
1743
1871
|
// polling, close fencing, route delivery, and every retirement path.
|
|
1744
1872
|
// Real clock, deliberately, not `ctx.now` — the claim answers whether
|
|
@@ -2585,6 +2713,16 @@ export function handleSessionEnd(env, envelope, now = Date.now()) {
|
|
|
2585
2713
|
if (envelope.cwd !== undefined)
|
|
2586
2714
|
clearMatchingProjectSession(envelope.cwd, env, sessionId);
|
|
2587
2715
|
const state = readSessionState(sessionId, env);
|
|
2716
|
+
let stateWithHistory = state;
|
|
2717
|
+
for (const entry of pendingList(state)) {
|
|
2718
|
+
stateWithHistory = rememberQuestionState(stateWithHistory, entry, entry.request_id === undefined && entry.submission === undefined ? 'withdrawn' : 'retired');
|
|
2719
|
+
}
|
|
2720
|
+
for (const answer of state.accepted?.answers ?? []) {
|
|
2721
|
+
stateWithHistory = rememberQuestionState(stateWithHistory, answer.pending, 'answered');
|
|
2722
|
+
}
|
|
2723
|
+
for (const retirement of state.retiring ?? []) {
|
|
2724
|
+
stateWithHistory = rememberQuestionState(stateWithHistory, retirement, retirement.state === 'answered' ? 'answered' : 'retired');
|
|
2725
|
+
}
|
|
2588
2726
|
const orphans = [...(state.retiring ?? [])];
|
|
2589
2727
|
const retirementCandidates = [
|
|
2590
2728
|
...pendingList(state),
|
|
@@ -2613,7 +2751,7 @@ export function handleSessionEnd(env, envelope, now = Date.now()) {
|
|
|
2613
2751
|
notes.push(`queued ${orphans.length} question${orphans.length > 1 ? 's' : ''} for retirement on the next hook`);
|
|
2614
2752
|
}
|
|
2615
2753
|
if (state.accepted !== undefined || (state.acknowledgement_due?.length ?? 0) > 0) {
|
|
2616
|
-
const preserved = { ...
|
|
2754
|
+
const preserved = { ...stateWithHistory };
|
|
2617
2755
|
delete preserved.pending;
|
|
2618
2756
|
delete preserved.retiring;
|
|
2619
2757
|
delete preserved.acknowledgement_blocks;
|
|
@@ -2634,7 +2772,11 @@ export function handleSessionEnd(env, envelope, now = Date.now()) {
|
|
|
2634
2772
|
},
|
|
2635
2773
|
};
|
|
2636
2774
|
}
|
|
2775
|
+
const history = stateWithHistory.question_history;
|
|
2637
2776
|
clearSessionState(sessionId, env);
|
|
2777
|
+
if ((history?.length ?? 0) > 0) {
|
|
2778
|
+
writeSessionState(sessionId, env, { question_history: history });
|
|
2779
|
+
}
|
|
2638
2780
|
return { notes, log: { outcome: 'cleaned', queued_retirements: orphans.length } };
|
|
2639
2781
|
}
|
|
2640
2782
|
// ---------------------------------------------------------------------------
|