@raidiant/notifai 10.1.7 → 11.0.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/CHANGELOG.md +42 -0
- package/README.md +23 -13
- package/dist/claude-wake.d.ts +1 -2
- package/dist/claude-wake.js +15 -61
- package/dist/claude-wake.js.map +1 -1
- package/dist/cli-bin.js +3 -14
- package/dist/cli-bin.js.map +1 -1
- package/dist/cli-contract.d.ts +12 -0
- package/dist/cli-contract.js +19 -0
- package/dist/cli-contract.js.map +1 -0
- package/dist/cli-release.d.ts +0 -1
- package/dist/cli-release.js +5 -21
- package/dist/cli-release.js.map +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/codex-wake.d.ts +1 -1
- package/dist/codex-wake.js +8 -36
- package/dist/codex-wake.js.map +1 -1
- package/dist/command-session.js +2 -1
- package/dist/command-session.js.map +1 -1
- package/dist/commands-acknowledge.d.ts +13 -0
- package/dist/commands-acknowledge.js +67 -0
- package/dist/commands-acknowledge.js.map +1 -0
- package/dist/commands-ask.d.ts +63 -0
- package/dist/commands-ask.js +407 -0
- package/dist/commands-ask.js.map +1 -0
- package/dist/commands-close.d.ts +6 -0
- package/dist/commands-close.js +197 -0
- package/dist/commands-close.js.map +1 -0
- package/dist/commands-config.js +2 -2
- package/dist/commands-config.js.map +1 -1
- package/dist/commands-core.js +2 -2
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-doctor.js +61 -120
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-harness-context.d.ts +0 -1
- package/dist/commands-harness-context.js +2 -4
- package/dist/commands-harness-context.js.map +1 -1
- package/dist/commands-hook-diagnostics.d.ts +27 -0
- package/dist/commands-hook-diagnostics.js +97 -0
- package/dist/commands-hook-diagnostics.js.map +1 -0
- package/dist/commands-hook-install.d.ts +26 -0
- package/dist/commands-hook-install.js +549 -0
- package/dist/commands-hook-install.js.map +1 -0
- package/dist/commands-hook-run.d.ts +29 -0
- package/dist/commands-hook-run.js +517 -0
- package/dist/commands-hook-run.js.map +1 -0
- package/dist/commands-init.d.ts +2 -7
- package/dist/commands-init.js +59 -92
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send.js +35 -3
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-skill.js +5 -5
- package/dist/commands-skill.js.map +1 -1
- package/dist/commands-update.js +17 -31
- package/dist/commands-update.js.map +1 -1
- package/dist/commands.d.ts +11 -6
- package/dist/commands.js +9 -4
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +14 -4
- package/dist/config.js +35 -9
- package/dist/config.js.map +1 -1
- package/dist/file-lock.d.ts +3 -0
- package/dist/file-lock.js +33 -17
- package/dist/file-lock.js.map +1 -1
- package/dist/guidance-content.js +30 -15
- package/dist/guidance-content.js.map +1 -1
- package/dist/hook-acknowledgements.d.ts +42 -0
- package/dist/hook-acknowledgements.js +316 -0
- package/dist/hook-acknowledgements.js.map +1 -0
- package/dist/hook-events.d.ts +6 -0
- package/dist/hook-events.js +22 -0
- package/dist/hook-events.js.map +1 -0
- package/dist/hook-gates.d.ts +7 -0
- package/dist/hook-gates.js +21 -0
- package/dist/hook-gates.js.map +1 -0
- package/dist/hook-lifecycle.d.ts +93 -0
- package/dist/{hooks.js → hook-lifecycle.js} +61 -1359
- package/dist/hook-lifecycle.js.map +1 -0
- package/dist/hook-project-sessions.d.ts +28 -0
- package/dist/hook-project-sessions.js +141 -0
- package/dist/hook-project-sessions.js.map +1 -0
- package/dist/hook-question-lock.d.ts +7 -0
- package/dist/hook-question-lock.js +173 -0
- package/dist/hook-question-lock.js.map +1 -0
- package/dist/hook-question-retirement.d.ts +57 -0
- package/dist/hook-question-retirement.js +261 -0
- package/dist/hook-question-retirement.js.map +1 -0
- package/dist/hook-question-state.d.ts +45 -0
- package/dist/hook-question-state.js +196 -0
- package/dist/hook-question-state.js.map +1 -0
- package/dist/hook-session-state.d.ts +31 -0
- package/dist/hook-session-state.js +249 -0
- package/dist/hook-session-state.js.map +1 -0
- package/dist/{hooks.d.ts → hook-types.d.ts} +17 -236
- package/dist/hook-types.js +4 -0
- package/dist/hook-types.js.map +1 -0
- package/dist/install-hooks.d.ts +72 -60
- package/dist/install-hooks.js +292 -211
- package/dist/install-hooks.js.map +1 -1
- package/dist/interactive.d.ts +3 -16
- package/dist/interactive.js +19 -95
- package/dist/interactive.js.map +1 -1
- package/dist/local-path.d.ts +7 -0
- package/dist/local-path.js +27 -0
- package/dist/local-path.js.map +1 -0
- package/dist/node-floor.js +3 -10
- package/dist/node-floor.js.map +1 -1
- package/dist/npm-invocation.d.ts +34 -0
- package/dist/npm-invocation.js +53 -0
- package/dist/npm-invocation.js.map +1 -0
- package/dist/openclaw-plugin.d.ts +12 -3
- package/dist/openclaw-plugin.js +42 -16
- package/dist/openclaw-plugin.js.map +1 -1
- package/dist/opencode-plugin.d.ts +2 -2
- package/dist/opencode-plugin.js +4 -6
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/program.js +11 -13
- package/dist/program.js.map +1 -1
- package/dist/project-enablement.d.ts +6 -0
- package/dist/project-enablement.js +11 -27
- package/dist/project-enablement.js.map +1 -1
- package/dist/question-settlement-process.d.ts +1 -1
- package/dist/readiness.d.ts +0 -2
- package/dist/readiness.js +0 -4
- package/dist/readiness.js.map +1 -1
- package/dist/send.d.ts +8 -4
- package/dist/send.js +145 -26
- package/dist/send.js.map +1 -1
- package/dist/session-activation.d.ts +3 -1
- package/dist/session-activation.js +16 -0
- package/dist/session-activation.js.map +1 -1
- package/dist/skill-source/manifest.json +4 -4
- package/dist/skill-source/notifai/SKILL.md +60 -60
- package/dist/skill-source/notifai/references/harness-setup.md +41 -16
- package/dist/ui/config-view.d.ts +0 -2
- package/dist/ui/config-view.js +1 -5
- package/dist/ui/config-view.js.map +1 -1
- package/dist/ui/theme.d.ts +0 -3
- package/dist/ui/theme.js +0 -1
- package/dist/ui/theme.js.map +1 -1
- package/dist/version.d.ts +22 -0
- package/dist/version.js +51 -0
- package/dist/version.js.map +1 -0
- package/dist/wake-support.d.ts +8 -0
- package/dist/wake-support.js +38 -0
- package/dist/wake-support.js.map +1 -0
- package/package.json +2 -2
- package/dist/cli-update-contract.d.ts +0 -7
- package/dist/cli-update-contract.js +0 -10
- package/dist/cli-update-contract.js.map +0 -1
- package/dist/commands-hooks.d.ts +0 -160
- package/dist/commands-hooks.js +0 -1800
- package/dist/commands-hooks.js.map +0 -1
- package/dist/hooks.js.map +0 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/** Durable per-Agent-Session state, activation bookkeeping, and pruning. */
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { atomicWriteFileSync } from './atomic-file.js';
|
|
5
|
+
import { sanitizeSessionId, sessionConfigPath, stateDir } from './config.js';
|
|
6
|
+
import { withFileLock } from './file-lock.js';
|
|
7
|
+
export function sessionStatePath(sessionId, env) {
|
|
8
|
+
return path.join(stateDir(env), 'sessions', `${sanitizeSessionId(sessionId)}.json`);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Durable cancellation for observers that can outlive the harness process.
|
|
12
|
+
*
|
|
13
|
+
* Absence of session state is not enough: SessionEnd deliberately deletes it,
|
|
14
|
+
* and an in-flight submit/fence could otherwise recreate it afterwards. The
|
|
15
|
+
* marker shares the session-state lock, giving every racing writer one total
|
|
16
|
+
* order: either its state lands before SessionEnd and is cleaned up there, or
|
|
17
|
+
* it observes this marker and must not write or deliver into the ended session.
|
|
18
|
+
*/
|
|
19
|
+
function sessionEndMarkerPath(sessionId, env) {
|
|
20
|
+
return path.join(stateDir(env), 'sessions', `${sanitizeSessionId(sessionId)}.ended`);
|
|
21
|
+
}
|
|
22
|
+
export function sessionHasEnded(sessionId, env) {
|
|
23
|
+
return existsSync(sessionEndMarkerPath(sessionId, env));
|
|
24
|
+
}
|
|
25
|
+
export function markSessionEnded(sessionId, env, now) {
|
|
26
|
+
const stateFile = sessionStatePath(sessionId, env);
|
|
27
|
+
withFileLock(`${stateFile}.lock`, () => {
|
|
28
|
+
atomicWriteFileSync(sessionEndMarkerPath(sessionId, env), `${now}\n`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function clearSessionEndMarker(sessionId, env) {
|
|
32
|
+
const stateFile = sessionStatePath(sessionId, env);
|
|
33
|
+
withFileLock(`${stateFile}.lock`, () => {
|
|
34
|
+
rmSync(sessionEndMarkerPath(sessionId, env), { force: true });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function readSessionState(sessionId, env) {
|
|
38
|
+
const file = sessionStatePath(sessionId, env);
|
|
39
|
+
if (!existsSync(file))
|
|
40
|
+
return {};
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
43
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
44
|
+
return {};
|
|
45
|
+
const state = { ...parsed };
|
|
46
|
+
delete state.session_id;
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// A corrupt marker must not wedge the harness; treat it as "no evidence",
|
|
51
|
+
// which fails closed onto ordinary terminal behaviour.
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a stable question or request id to the one exact session that owns it.
|
|
57
|
+
*
|
|
58
|
+
* This deliberately searches lifecycle state, not checkout pointers: question
|
|
59
|
+
* identity is global on this machine, while a linked worktree is only where a
|
|
60
|
+
* command happened to run. Ambiguous or corrupt matches fail closed.
|
|
61
|
+
*/
|
|
62
|
+
export function findOwningSession(id, env) {
|
|
63
|
+
const directory = path.join(stateDir(env), 'sessions');
|
|
64
|
+
if (!existsSync(directory))
|
|
65
|
+
return { sessionId: null, ambiguous: false };
|
|
66
|
+
const matches = [];
|
|
67
|
+
for (const name of readdirSync(directory)) {
|
|
68
|
+
if (!name.endsWith('.json'))
|
|
69
|
+
continue;
|
|
70
|
+
let persisted = null;
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(readFileSync(path.join(directory, name), 'utf8'));
|
|
73
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
74
|
+
persisted = parsed;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Corrupt lifecycle state is not identity evidence.
|
|
79
|
+
}
|
|
80
|
+
if (persisted === null || typeof persisted.session_id !== 'string')
|
|
81
|
+
continue;
|
|
82
|
+
const sessionId = persisted.session_id;
|
|
83
|
+
const state = persisted;
|
|
84
|
+
const pendingMatch = pendingList(state).some((entry) => entry.question_id === id || entry.request_id === id);
|
|
85
|
+
const retiringMatch = (state.retiring ?? []).some((entry) => entry.question_id === id || entry.request_id === id);
|
|
86
|
+
const historyMatch = (state.question_history ?? []).some((entry) => entry.question_id === id || entry.request_id === id);
|
|
87
|
+
const acknowledgementMatch = (state.acknowledgement_due ?? []).some((entry) => entry.request_id === id);
|
|
88
|
+
const acceptedMatch = state.accepted?.answers.some(({ pending }) => pending.question_id === id || pending.request_id === id) ?? false;
|
|
89
|
+
if (pendingMatch || retiringMatch || historyMatch || acknowledgementMatch || acceptedMatch) {
|
|
90
|
+
matches.push(sessionId);
|
|
91
|
+
if (matches.length > 1)
|
|
92
|
+
return { sessionId: null, ambiguous: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { sessionId: matches[0] ?? null, ambiguous: false };
|
|
96
|
+
}
|
|
97
|
+
export function writeSessionState(sessionId, env, state) {
|
|
98
|
+
const file = sessionStatePath(sessionId, env);
|
|
99
|
+
withFileLock(`${file}.lock`, () => writeSessionStateUnlocked(file, sessionId, state));
|
|
100
|
+
}
|
|
101
|
+
export function clearSessionState(sessionId, env) {
|
|
102
|
+
const file = sessionStatePath(sessionId, env);
|
|
103
|
+
withFileLock(`${file}.lock`, () => {
|
|
104
|
+
rmSync(file, { force: true });
|
|
105
|
+
// The session override lives in a sibling file; leaving it behind meant a
|
|
106
|
+
// later session reusing the id silently inherited `ask_notifications = false`.
|
|
107
|
+
rmSync(sessionConfigPath(sessionId, env), { force: true });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export function writeSessionStateUnlocked(file, sessionId, state) {
|
|
111
|
+
atomicWriteFileSync(file, `${JSON.stringify({ ...state, session_id: sessionId }, null, 2)}\n`);
|
|
112
|
+
}
|
|
113
|
+
export function updateSessionState(sessionId, env, update) {
|
|
114
|
+
const file = sessionStatePath(sessionId, env);
|
|
115
|
+
return withFileLock(`${file}.lock`, () => {
|
|
116
|
+
const current = readSessionState(sessionId, env);
|
|
117
|
+
// SessionEnd and every asynchronous writer share this lock. Once the
|
|
118
|
+
// durable marker exists, no observer may recreate state for that session.
|
|
119
|
+
if (sessionHasEnded(sessionId, env))
|
|
120
|
+
return current;
|
|
121
|
+
const next = update(current);
|
|
122
|
+
writeSessionStateUnlocked(file, sessionId, next);
|
|
123
|
+
return next;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Record the current installation when an owner session starts. */
|
|
127
|
+
export function recordSessionStart(sessionId, env, harness, cwd, codexStopDefinitionFingerprint) {
|
|
128
|
+
// Harnesses may reuse a session id only by explicitly starting that session
|
|
129
|
+
// again. That lifecycle edge is the sole authority for clearing cancellation.
|
|
130
|
+
clearSessionEndMarker(sessionId, env);
|
|
131
|
+
updateSessionState(sessionId, env, (current) => {
|
|
132
|
+
const next = {
|
|
133
|
+
...current,
|
|
134
|
+
...(harness === undefined ? {} : { harness }),
|
|
135
|
+
...(cwd === undefined
|
|
136
|
+
? {}
|
|
137
|
+
: { activation_cwd: harness === 'codex' ? cwd : current.activation_cwd ?? cwd }),
|
|
138
|
+
};
|
|
139
|
+
// A Codex SessionStart materializes one new runtime generation. Absence of
|
|
140
|
+
// singular proof must replace prior proof too; retaining it would let a
|
|
141
|
+
// resumed session borrow the definition loaded by an earlier runtime.
|
|
142
|
+
if (harness === 'codex') {
|
|
143
|
+
delete next.codex_stop_definition_fingerprint;
|
|
144
|
+
if (codexStopDefinitionFingerprint !== undefined) {
|
|
145
|
+
next.codex_stop_definition_fingerprint = codexStopDefinitionFingerprint;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return next;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Atomically claim Cursor's first-Stop activation fallback once per conversation. */
|
|
152
|
+
export function claimCursorStopActivation(sessionId, env, now) {
|
|
153
|
+
const claimGuardMs = 30_000;
|
|
154
|
+
let claimed = false;
|
|
155
|
+
updateSessionState(sessionId, env, (current) => {
|
|
156
|
+
if (current.cursor_activation_confirmed_at !== undefined)
|
|
157
|
+
return current;
|
|
158
|
+
if (current.cursor_activation_claimed_at !== undefined &&
|
|
159
|
+
now - current.cursor_activation_claimed_at < claimGuardMs) {
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
claimed = true;
|
|
163
|
+
return { ...current, cursor_activation_claimed_at: now };
|
|
164
|
+
});
|
|
165
|
+
return claimed;
|
|
166
|
+
}
|
|
167
|
+
/** Cursor's loop_count proves the previous Stop follow-up reached the host. */
|
|
168
|
+
export function confirmCursorStopActivation(sessionId, env, now) {
|
|
169
|
+
let owned = false;
|
|
170
|
+
updateSessionState(sessionId, env, (current) => {
|
|
171
|
+
if (current.cursor_activation_confirmed_at !== undefined) {
|
|
172
|
+
owned = true;
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
175
|
+
if (current.cursor_activation_claimed_at === undefined)
|
|
176
|
+
return current;
|
|
177
|
+
owned = true;
|
|
178
|
+
return { ...current, cursor_activation_confirmed_at: now };
|
|
179
|
+
});
|
|
180
|
+
return owned;
|
|
181
|
+
}
|
|
182
|
+
/** A fresh/cleared Cursor lifecycle makes its next Stop the fallback owner again. */
|
|
183
|
+
export function resetCursorStopActivation(sessionId, env) {
|
|
184
|
+
updateSessionState(sessionId, env, (current) => {
|
|
185
|
+
if (current.cursor_activation_claimed_at === undefined &&
|
|
186
|
+
current.cursor_activation_confirmed_at === undefined) {
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
const next = { ...current };
|
|
190
|
+
delete next.cursor_activation_claimed_at;
|
|
191
|
+
delete next.cursor_activation_confirmed_at;
|
|
192
|
+
return next;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Session state a crashed harness left behind.
|
|
197
|
+
*
|
|
198
|
+
* `SessionEnd` removes session state and leaves a short-lived cancellation
|
|
199
|
+
* marker; a harness that crashes or is killed never reaches it. At roughly a
|
|
200
|
+
* hundred sessions a day that is tens of thousands of files a year, none of
|
|
201
|
+
* which anything reads.
|
|
202
|
+
*
|
|
203
|
+
* Opportunistic rather than scheduled: hooks are the only thing that runs, so
|
|
204
|
+
* a hook is where this has to live. It is rate-limited by its own stamp file
|
|
205
|
+
* so the common case costs one `stat`, not a directory walk — a Stop hook is
|
|
206
|
+
* on the critical path of every turn.
|
|
207
|
+
*/
|
|
208
|
+
const SESSION_PRUNE_AFTER_MS = 7 * 24 * 3600 * 1000;
|
|
209
|
+
const SESSION_PRUNE_INTERVAL_MS = 24 * 3600 * 1000;
|
|
210
|
+
export function pruneAbandonedSessions(env, now = Date.now(), maxAgeMs = SESSION_PRUNE_AFTER_MS) {
|
|
211
|
+
const directory = path.join(stateDir(env), 'sessions');
|
|
212
|
+
const stamp = path.join(stateDir(env), 'last-prune');
|
|
213
|
+
try {
|
|
214
|
+
if (existsSync(stamp) && now - statSync(stamp).mtimeMs < SESSION_PRUNE_INTERVAL_MS)
|
|
215
|
+
return 0;
|
|
216
|
+
mkdirSync(path.dirname(stamp), { recursive: true });
|
|
217
|
+
writeFileSync(stamp, '', { mode: 0o600 });
|
|
218
|
+
if (!existsSync(directory))
|
|
219
|
+
return 0;
|
|
220
|
+
let removed = 0;
|
|
221
|
+
for (const name of readdirSync(directory)) {
|
|
222
|
+
const file = path.join(directory, name);
|
|
223
|
+
try {
|
|
224
|
+
const age = now - statSync(file).mtimeMs;
|
|
225
|
+
// A negative age means the clock moved, not that the file is old.
|
|
226
|
+
// Deleting live session state on an NTP correction would lose a
|
|
227
|
+
// question already on the user's phone.
|
|
228
|
+
if (age <= maxAgeMs)
|
|
229
|
+
continue;
|
|
230
|
+
rmSync(file, { force: true });
|
|
231
|
+
removed += 1;
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// A file that vanished under us, or one we may not read. Neither is
|
|
235
|
+
// worth failing a hook for, and the next pass will see it again.
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return removed;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Housekeeping must never be the reason a turn fails.
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/** The registered-question queue. Anything but the current shape reads as empty. */
|
|
246
|
+
export function pendingList(state) {
|
|
247
|
+
return Array.isArray(state.pending) ? state.pending : [];
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=hook-session-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-session-state.js","sourceRoot":"","sources":["../src/hook-session-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
/** Value-free contracts shared across hook state and lifecycle modules. */
|
|
1
2
|
import type { LifecycleEndState, MediaItemT, NotificationDraftT, QuestionT, ReplyView, SourceContextT } from '@raidiant/notifai-protocol';
|
|
2
3
|
import { type ApiClient } from './client.js';
|
|
3
4
|
import { type CliConfig } from './config.js';
|
|
4
|
-
import type { Logger } from './logging.js';
|
|
5
5
|
import { type DeliveryRoute, type HookInstallableHarness } from './harnesses.js';
|
|
6
|
+
import type { Logger } from './logging.js';
|
|
6
7
|
/**
|
|
7
8
|
* Harness hook handlers.
|
|
8
9
|
*
|
|
@@ -117,7 +118,7 @@ export interface AcknowledgementDue {
|
|
|
117
118
|
*/
|
|
118
119
|
text_required?: boolean;
|
|
119
120
|
}
|
|
120
|
-
interface AcceptedAnswerDelivery {
|
|
121
|
+
export interface AcceptedAnswerDelivery {
|
|
121
122
|
answers: AnsweredPending[];
|
|
122
123
|
remaining: number;
|
|
123
124
|
recorded_at: number;
|
|
@@ -172,11 +173,22 @@ export interface OrphanRetirement extends RetiringQuestion {
|
|
|
172
173
|
/** Epoch ms when the entry was orphaned; entries beyond the TTL are dropped. */
|
|
173
174
|
enqueued_at: number;
|
|
174
175
|
}
|
|
176
|
+
/** A fully prepared question submission whose ownership deadline is fixed. */
|
|
177
|
+
export interface PendingSubmissionIntent {
|
|
178
|
+
request_id: string;
|
|
179
|
+
idempotency_key: string;
|
|
180
|
+
collapse_key: string;
|
|
181
|
+
device_ids: string[];
|
|
182
|
+
draft: NotificationDraftT;
|
|
183
|
+
owner_deadline_at: number;
|
|
184
|
+
}
|
|
175
185
|
export interface PendingQuestion {
|
|
176
186
|
/** Stable local identity across racing state writers and submit recovery. */
|
|
177
187
|
question_id?: string;
|
|
178
188
|
/** One-line summary: the single question's text, or the set's first. */
|
|
179
189
|
question: string;
|
|
190
|
+
/** Purpose-written plain text for native banners and notification lists. */
|
|
191
|
+
summary: string;
|
|
180
192
|
/**
|
|
181
193
|
* Epoch ms when `notifai ask` registered this. The grace window runs from
|
|
182
194
|
* here, not from the turn's end: a question the agent asked five minutes ago
|
|
@@ -188,7 +200,7 @@ export interface PendingQuestion {
|
|
|
188
200
|
* texts, choices, multi flags. What actually rides the push.
|
|
189
201
|
*/
|
|
190
202
|
questions?: QuestionT[];
|
|
191
|
-
/**
|
|
203
|
+
/** Optional standalone Markdown Body composed when the question was registered. */
|
|
192
204
|
body?: string;
|
|
193
205
|
/** Final ordered media collection; uploads complete before registration. */
|
|
194
206
|
media?: MediaItemT[];
|
|
@@ -213,7 +225,7 @@ export interface PendingQuestion {
|
|
|
213
225
|
submission?: PendingSubmissionIntent;
|
|
214
226
|
}
|
|
215
227
|
export type QuestionDeliveryState = 'local' | 'frozen' | 'live' | 'answered' | 'withdrawn' | 'retired';
|
|
216
|
-
type QuestionTerminalState = Extract<QuestionDeliveryState, 'answered' | 'withdrawn' | 'retired'>;
|
|
228
|
+
export type QuestionTerminalState = Extract<QuestionDeliveryState, 'answered' | 'withdrawn' | 'retired'>;
|
|
217
229
|
export interface QuestionHistoryEntry {
|
|
218
230
|
question_id: string;
|
|
219
231
|
state: QuestionTerminalState;
|
|
@@ -222,36 +234,6 @@ export interface QuestionHistoryEntry {
|
|
|
222
234
|
/** Reserved idempotent identity that was never locally promoted. */
|
|
223
235
|
frozen_request_id?: string;
|
|
224
236
|
}
|
|
225
|
-
export interface QuestionStateView {
|
|
226
|
-
question_id: string;
|
|
227
|
-
state: QuestionDeliveryState;
|
|
228
|
-
/** Null means a frozen submission may or may not have crossed the service boundary. */
|
|
229
|
-
submitted: boolean | null;
|
|
230
|
-
request_id: string | null;
|
|
231
|
-
frozen_request_id: string | null;
|
|
232
|
-
}
|
|
233
|
-
export type QuestionStateLookup = {
|
|
234
|
-
found: true;
|
|
235
|
-
session_id: string;
|
|
236
|
-
question: QuestionStateView;
|
|
237
|
-
} | {
|
|
238
|
-
found: false;
|
|
239
|
-
ambiguous: boolean;
|
|
240
|
-
};
|
|
241
|
-
interface PendingSubmissionIntent {
|
|
242
|
-
request_id: string;
|
|
243
|
-
idempotency_key: string;
|
|
244
|
-
collapse_key: string;
|
|
245
|
-
device_ids: string[];
|
|
246
|
-
draft: NotificationDraftT;
|
|
247
|
-
owner_deadline_at: number;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* The wire contract's shortest reply window. A question submitted with less
|
|
251
|
-
* than this is rejected outright, and accepting one would in any case let the
|
|
252
|
-
* server go on taking an answer after the waiter that owns it has returned.
|
|
253
|
-
*/
|
|
254
|
-
export declare const MIN_REPLY_WINDOW_SECONDS = 60;
|
|
255
237
|
export interface HookOutcome {
|
|
256
238
|
/** Written to stdout verbatim — the harness parses this as output. */
|
|
257
239
|
stdout?: string;
|
|
@@ -324,57 +306,6 @@ export interface EscalationWaiterOptions {
|
|
|
324
306
|
/** Stop owns diagnostics; a pre-Stop recovery owner must not impersonate it. */
|
|
325
307
|
recordStop?: boolean;
|
|
326
308
|
}
|
|
327
|
-
export declare function readSessionState(sessionId: string, env: NodeJS.ProcessEnv): SessionState;
|
|
328
|
-
/**
|
|
329
|
-
* Resolve a stable question or request id to the one exact session that owns it.
|
|
330
|
-
*
|
|
331
|
-
* This deliberately searches lifecycle state, not checkout pointers: question
|
|
332
|
-
* identity is global on this machine, while a linked worktree is only where a
|
|
333
|
-
* command happened to run. Ambiguous or corrupt matches fail closed.
|
|
334
|
-
*/
|
|
335
|
-
export declare function findOwningSession(id: string, env: NodeJS.ProcessEnv): {
|
|
336
|
-
sessionId: string | null;
|
|
337
|
-
ambiguous: boolean;
|
|
338
|
-
};
|
|
339
|
-
export declare function writeSessionState(sessionId: string, env: NodeJS.ProcessEnv, state: SessionState): void;
|
|
340
|
-
export declare function clearSessionState(sessionId: string, env: NodeJS.ProcessEnv): void;
|
|
341
|
-
/** Record the current installation when an owner session starts. */
|
|
342
|
-
export declare function recordSessionStart(sessionId: string, env: NodeJS.ProcessEnv, harness?: HookHarness, cwd?: string, codexStopDefinitionFingerprint?: string): void;
|
|
343
|
-
/** Atomically claim Cursor's first-Stop activation fallback once per conversation. */
|
|
344
|
-
export declare function claimCursorStopActivation(sessionId: string, env: NodeJS.ProcessEnv, now: number): boolean;
|
|
345
|
-
/** Cursor's loop_count proves the previous Stop follow-up reached the host. */
|
|
346
|
-
export declare function confirmCursorStopActivation(sessionId: string, env: NodeJS.ProcessEnv, now: number): boolean;
|
|
347
|
-
/** A fresh/cleared Cursor lifecycle makes its next Stop the fallback owner again. */
|
|
348
|
-
export declare function resetCursorStopActivation(sessionId: string, env: NodeJS.ProcessEnv): void;
|
|
349
|
-
export declare function pruneAbandonedSessions(env: NodeJS.ProcessEnv, now?: number, maxAgeMs?: number): number;
|
|
350
|
-
export declare function claimQuestionPush(sessionId: string, env: NodeJS.ProcessEnv, now?: number, beforeStaleReplace?: () => void, ownerDeadlineAt?: number): boolean;
|
|
351
|
-
export declare function releaseQuestionPush(sessionId: string, env: NodeJS.ProcessEnv): void;
|
|
352
|
-
export interface ProjectSessionPointer {
|
|
353
|
-
sessionId: string;
|
|
354
|
-
harness: HookHarness;
|
|
355
|
-
}
|
|
356
|
-
/** Records every live session working in a directory, for `notifai ask`. */
|
|
357
|
-
export declare function writeProjectSession(cwd: string, env: NodeJS.ProcessEnv, sessionId: string, now: number, harness: HookHarness): void;
|
|
358
|
-
/**
|
|
359
|
-
* Resolves the session working in `cwd`. Stale pointers are ignored rather than
|
|
360
|
-
* trusted: an id left by a session that ended days ago would send the question
|
|
361
|
-
* into state nothing is watching.
|
|
362
|
-
*/
|
|
363
|
-
export declare function readProjectSession(cwd: string, env: NodeJS.ProcessEnv, now: number, maxAgeMs?: number): string | null;
|
|
364
|
-
/** A live pointer keeps when it was last written, so callers can rank by it. */
|
|
365
|
-
export interface LiveProjectSessionPointer extends ProjectSessionPointer {
|
|
366
|
-
updatedAt: number;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Every session that has fired a hook in this directory recently, newest first.
|
|
370
|
-
*
|
|
371
|
-
* This is the only local record of which harness sessions are actually alive
|
|
372
|
-
* here, which makes it the tiebreaker when the environment alone cannot say
|
|
373
|
-
* which harness owns the current shell.
|
|
374
|
-
*/
|
|
375
|
-
export declare function readLiveProjectSessionPointers(cwd: string, env: NodeJS.ProcessEnv, now: number, maxAgeMs?: number): LiveProjectSessionPointer[];
|
|
376
|
-
export declare function readProjectSessionPointer(cwd: string, env: NodeJS.ProcessEnv, now: number, maxAgeMs?: number): ProjectSessionPointer | null;
|
|
377
|
-
export declare function readMatchingProjectSessionPointer(cwd: string, env: NodeJS.ProcessEnv, now: number, sessionId: string, harness: HookHarness, maxAgeMs?: number): ProjectSessionPointer | null;
|
|
378
309
|
export interface HookContext {
|
|
379
310
|
client: ApiClient;
|
|
380
311
|
config: CliConfig;
|
|
@@ -397,84 +328,9 @@ export interface HookContext {
|
|
|
397
328
|
*/
|
|
398
329
|
log?: Logger;
|
|
399
330
|
}
|
|
400
|
-
/**
|
|
401
|
-
* Record one gate decision.
|
|
402
|
-
*
|
|
403
|
-
* `verdict` is what happened to the question and `reason` is the closed-set
|
|
404
|
-
* name of the rule that decided it, so an agent can ask "why was it held" with
|
|
405
|
-
* a filter rather than by matching on prose. The prose still exists — it is what
|
|
406
|
-
* the user sees in the terminal — but it is free to be rewritten, and a log a
|
|
407
|
-
* filter cannot rely on is a log nobody filters.
|
|
408
|
-
*/
|
|
409
|
-
export declare const GATE_REASONS: readonly ["no-session", "no-question", "answered", "continuation-repeat", "continuation-limit", "delivery-limit", "acknowledgement-required", "acknowledgement-abandoned", "harness-cannot-continue", "notifications-off", "claimed-elsewhere", "elapsed", "proceeding"];
|
|
410
|
-
/**
|
|
411
|
-
* A runtime list rather than a bare type, so the guidance that tells agents to
|
|
412
|
-
* filter on these can be tested against the set the code can actually emit.
|
|
413
|
-
* `already-asked` and `no-devices` were declared here for a long time and never
|
|
414
|
-
* emitted once; documenting them taught agents to filter for silence.
|
|
415
|
-
*/
|
|
416
|
-
export type GateReason = (typeof GATE_REASONS)[number];
|
|
417
331
|
export type HookHarness = HookInstallableHarness;
|
|
418
|
-
/**
|
|
419
|
-
* Everything retirement needs. Narrower than a HookContext on purpose:
|
|
420
|
-
* `notifai ask` supersedes the previous question and it is a plain command with
|
|
421
|
-
* no hook payload, no idle probe and nothing to sleep for.
|
|
422
|
-
*/
|
|
423
|
-
export type RetireDeps = Pick<HookContext, 'client' | 'config'> & Pick<Partial<HookContext>, 'log'>;
|
|
424
|
-
/**
|
|
425
|
-
* Park a delivered question for retirement. Called at the moment we learn it is
|
|
426
|
-
* dead, which is frequently not a moment we can reach the network.
|
|
427
|
-
*
|
|
428
|
-
* Nothing is parked for a question that never reached a device: with no
|
|
429
|
-
* request_id there is nothing on any device to retire.
|
|
430
|
-
*/
|
|
431
|
-
export declare function parkForRetirement(sessionId: string, env: NodeJS.ProcessEnv, pending: PendingQuestion, state: LifecycleEndState): void;
|
|
432
|
-
/**
|
|
433
|
-
* Which outstanding questions this prompt plausibly answers.
|
|
434
|
-
*
|
|
435
|
-
* A unique closed-choice match is enough. A short confirmation such as "done"
|
|
436
|
-
* matches only when exactly one question is outstanding. Long unrelated
|
|
437
|
-
* prompts leave every question open for the reply window.
|
|
438
|
-
*/
|
|
439
|
-
export declare function pendingAnsweredByPrompt(prompt: string | undefined, pending: readonly PendingQuestion[]): PendingQuestion[];
|
|
440
|
-
/**
|
|
441
|
-
* Send every parked retirement, and forget the ones that landed. A failure
|
|
442
|
-
* leaves the entry in place for the next hook rather than losing it, which is
|
|
443
|
-
* the whole reason the queue exists.
|
|
444
|
-
*/
|
|
445
|
-
export declare function drainRetirements(ctx: RetireDeps, sessionId: string, env: NodeJS.ProcessEnv): Promise<string[]>;
|
|
446
|
-
/** Move retirements into the global queue; deduped so a retry costs nothing. */
|
|
447
|
-
export declare function orphanRetirements(env: NodeJS.ProcessEnv, entries: RetiringQuestion[], now: number): void;
|
|
448
|
-
/**
|
|
449
|
-
* Retire everything a dead session left behind. Failures stay queued for the
|
|
450
|
-
* next holder of a client; entries past the TTL are dropped as already dead.
|
|
451
|
-
*/
|
|
452
|
-
export declare function drainOrphanRetirements(ctx: RetireDeps, env: NodeJS.ProcessEnv, now: number): Promise<string[]>;
|
|
453
|
-
/**
|
|
454
|
-
* The one bound on answer deliveries, for every route.
|
|
455
|
-
*
|
|
456
|
-
* It caps two things that both used to be able to run away: how many times a
|
|
457
|
-
* single accepted answer may be handed to a route before the journal gives up
|
|
458
|
-
* on it, and how many answer-driven continuation generations one chain may
|
|
459
|
-
* produce.
|
|
460
|
-
*/
|
|
461
|
-
export declare const MAX_CONTINUATION_COUNT = 3;
|
|
462
|
-
/**
|
|
463
|
-
* How many turns an answer may be *held* before it is settled unread.
|
|
464
|
-
*
|
|
465
|
-
* A hold is not a delivery: the route reported that it handed nothing over, so
|
|
466
|
-
* the agent has not seen the answer and the wake-loop the continuation cap
|
|
467
|
-
* exists to stop has not happened. Counting holds against that cap meant three
|
|
468
|
-
* turns where a liveness probe could not reach a busy session were enough to
|
|
469
|
-
* discard an answer the user had already given.
|
|
470
|
-
*
|
|
471
|
-
* It still has to end. A route that can never deliver would otherwise retry
|
|
472
|
-
* every turn for the life of the session, so holds get their own, far looser
|
|
473
|
-
* bound — loose because each one costs nothing and the answer is still wanted.
|
|
474
|
-
*/
|
|
475
|
-
export declare const MAX_HELD_DELIVERIES = 20;
|
|
476
332
|
/** One answered registered question, with everything the agent needs to read it. */
|
|
477
|
-
interface AnsweredPending {
|
|
333
|
+
export interface AnsweredPending {
|
|
478
334
|
pending: PendingQuestion;
|
|
479
335
|
reply: ReplyView;
|
|
480
336
|
replies: ReplyView[];
|
|
@@ -483,78 +339,3 @@ interface AnsweredPending {
|
|
|
483
339
|
/** Whether that acknowledgement must carry text; the account's snapshot. */
|
|
484
340
|
agent_acknowledgement_text_required?: boolean | undefined;
|
|
485
341
|
}
|
|
486
|
-
/** Read one stable local question identity without minting or submitting anything. */
|
|
487
|
-
export declare function inspectQuestionState(questionId: string, env: NodeJS.ProcessEnv): QuestionStateLookup;
|
|
488
|
-
export declare function dropPendingQuestion(sessionId: string, env: NodeJS.ProcessEnv, entry: PendingQuestion, terminalState?: QuestionTerminalState): void;
|
|
489
|
-
/**
|
|
490
|
-
* Drop registrations that never reached a device, so a later Stop cannot push
|
|
491
|
-
* them. A retirement push would be noise: there is nothing on any device to
|
|
492
|
-
* withdraw. Returns the withdrawn entries in registration order.
|
|
493
|
-
*/
|
|
494
|
-
export declare function withdrawUnpushedQuestions(sessionId: string, env: NodeJS.ProcessEnv): PendingQuestion[];
|
|
495
|
-
export declare function clearAcknowledgementObligation(sessionId: string, env: NodeJS.ProcessEnv, requestId: string): boolean;
|
|
496
|
-
/**
|
|
497
|
-
* Records presence. Retires a live question only when this prompt plausibly
|
|
498
|
-
* answers it. Typing in the terminal is not by itself a resolution: the User
|
|
499
|
-
* may be answering a different question on a device.
|
|
500
|
-
*/
|
|
501
|
-
export declare function handleUserPromptSubmit(ctx: HookContext, envelope: HookEnvelope): Promise<HookOutcome>;
|
|
502
|
-
/**
|
|
503
|
-
* Only engages for a question the agent explicitly registered with
|
|
504
|
-
* `notifai ask`. Guessing from the last assistant message was the alternative
|
|
505
|
-
* and it is not worth it: a false positive here hijacks the terminal.
|
|
506
|
-
*/
|
|
507
|
-
export declare function handleStop(ctx: HookContext, envelope: HookEnvelope, processDeadlineAt?: number, route?: EscalationDeliveryRoute, recordStop?: boolean): Promise<HookOutcome>;
|
|
508
|
-
/**
|
|
509
|
-
* Own one registered question pipeline from its terminal-first timer through
|
|
510
|
-
* its final delivery or retirement. Hosts differ only in the injected route.
|
|
511
|
-
*/
|
|
512
|
-
export declare function runEscalationWaiter(ctx: HookContext, options: EscalationWaiterOptions): Promise<HookOutcome>;
|
|
513
|
-
/**
|
|
514
|
-
* The blocking Stop hook's own last meter: the answer becomes this process's
|
|
515
|
-
* stdout, and the harness admits it as a new user turn. Exported because a
|
|
516
|
-
* host adapter that has other routes still falls back to this one.
|
|
517
|
-
*/
|
|
518
|
-
export declare function hookContinuationRoute(): EscalationDeliveryRoute;
|
|
519
|
-
/**
|
|
520
|
-
* Claude Code gives SessionEnd hooks a 1.5-second shared budget and Codex 1
|
|
521
|
-
* second, so this cannot make a network call. It drops the local marker — but
|
|
522
|
-
* first moves anything still live on the user's devices into the global
|
|
523
|
-
* retirement queue, because this file was the only record of those ids and no
|
|
524
|
-
* hook for this session will ever run again. A question whose
|
|
525
|
-
* agent just exited can receive no answer, so it is orphaned as `expired`.
|
|
526
|
-
*/
|
|
527
|
-
export declare function handleSessionEnd(env: NodeJS.ProcessEnv, envelope: HookEnvelope, now?: number): HookOutcome;
|
|
528
|
-
/**
|
|
529
|
-
* Registrations still waiting for their first push. More than this in one turn
|
|
530
|
-
* means an agent is looping, not asking, and related questions belong in one
|
|
531
|
-
* `ask --form` instead.
|
|
532
|
-
*
|
|
533
|
-
* Deliberately counts only unasked entries. A question already on the user's
|
|
534
|
-
* devices is waiting on a person, not on the agent, and an answer stays
|
|
535
|
-
* accepted for a day — so counting live questions here would have turned a
|
|
536
|
-
* patient user into a reason the agent could no longer ask anything.
|
|
537
|
-
*/
|
|
538
|
-
export declare const MAX_PENDING_QUESTIONS = 4;
|
|
539
|
-
/**
|
|
540
|
-
* Everything this session has open at once, asked or not. The lock screen is
|
|
541
|
-
* the shared resource being protected here, not the agent's turn.
|
|
542
|
-
*/
|
|
543
|
-
export declare const MAX_LIVE_QUESTIONS = 10;
|
|
544
|
-
/**
|
|
545
|
-
* A session may hold several registered questions at once: a new `ask` never
|
|
546
|
-
* ends an earlier one. Each reaches the user as its own notification and is
|
|
547
|
-
* answerable independently. Superseding is reply semantics — a later reply
|
|
548
|
-
* corrects an earlier reply to the same question (that correction model lives
|
|
549
|
-
* server-side) — never question semantics: the old single-slot model silently
|
|
550
|
-
* discarded a question the moment a second was registered, and the user was
|
|
551
|
-
* pointed at a notification that never existed (2026-08-09).
|
|
552
|
-
*
|
|
553
|
-
* The queue is keyed on the session, not the project: several agents may be
|
|
554
|
-
* running in one project at once, and their questions must never interfere.
|
|
555
|
-
*/
|
|
556
|
-
export declare function registerQuestion(sessionId: string, env: NodeJS.ProcessEnv, question: PendingQuestion, now?: number): string;
|
|
557
|
-
/** Parses hook JSON from stdin, tolerating an empty or malformed body. */
|
|
558
|
-
export declare function parseHookInput(raw: string): HookEnvelope;
|
|
559
|
-
export declare function resolveHookConfig(cwd: string, env: NodeJS.ProcessEnv, sessionId: string | undefined): CliConfig;
|
|
560
|
-
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-types.js","sourceRoot":"","sources":["../src/hook-types.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAkB,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAmD,MAAM,gBAAgB,CAAA"}
|