@raidiant/notifai 10.1.7 → 11.0.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.
Files changed (154) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +23 -13
  3. package/dist/claude-wake.d.ts +1 -2
  4. package/dist/claude-wake.js +15 -61
  5. package/dist/claude-wake.js.map +1 -1
  6. package/dist/cli-bin.js +3 -14
  7. package/dist/cli-bin.js.map +1 -1
  8. package/dist/cli-contract.d.ts +12 -0
  9. package/dist/cli-contract.js +19 -0
  10. package/dist/cli-contract.js.map +1 -0
  11. package/dist/cli-release.d.ts +0 -1
  12. package/dist/cli-release.js +5 -21
  13. package/dist/cli-release.js.map +1 -1
  14. package/dist/client.d.ts +2 -2
  15. package/dist/codex-wake.d.ts +1 -1
  16. package/dist/codex-wake.js +8 -36
  17. package/dist/codex-wake.js.map +1 -1
  18. package/dist/command-session.js +2 -1
  19. package/dist/command-session.js.map +1 -1
  20. package/dist/commands-acknowledge.d.ts +13 -0
  21. package/dist/commands-acknowledge.js +67 -0
  22. package/dist/commands-acknowledge.js.map +1 -0
  23. package/dist/commands-ask.d.ts +63 -0
  24. package/dist/commands-ask.js +407 -0
  25. package/dist/commands-ask.js.map +1 -0
  26. package/dist/commands-close.d.ts +6 -0
  27. package/dist/commands-close.js +197 -0
  28. package/dist/commands-close.js.map +1 -0
  29. package/dist/commands-config.js +2 -2
  30. package/dist/commands-config.js.map +1 -1
  31. package/dist/commands-core.js +2 -2
  32. package/dist/commands-core.js.map +1 -1
  33. package/dist/commands-doctor.js +61 -120
  34. package/dist/commands-doctor.js.map +1 -1
  35. package/dist/commands-harness-context.d.ts +0 -1
  36. package/dist/commands-harness-context.js +2 -4
  37. package/dist/commands-harness-context.js.map +1 -1
  38. package/dist/commands-hook-diagnostics.d.ts +27 -0
  39. package/dist/commands-hook-diagnostics.js +97 -0
  40. package/dist/commands-hook-diagnostics.js.map +1 -0
  41. package/dist/commands-hook-install.d.ts +26 -0
  42. package/dist/commands-hook-install.js +551 -0
  43. package/dist/commands-hook-install.js.map +1 -0
  44. package/dist/commands-hook-run.d.ts +29 -0
  45. package/dist/commands-hook-run.js +517 -0
  46. package/dist/commands-hook-run.js.map +1 -0
  47. package/dist/commands-init.d.ts +2 -7
  48. package/dist/commands-init.js +59 -92
  49. package/dist/commands-init.js.map +1 -1
  50. package/dist/commands-send.js +35 -3
  51. package/dist/commands-send.js.map +1 -1
  52. package/dist/commands-skill.js +5 -5
  53. package/dist/commands-skill.js.map +1 -1
  54. package/dist/commands-update.js +17 -31
  55. package/dist/commands-update.js.map +1 -1
  56. package/dist/commands.d.ts +11 -6
  57. package/dist/commands.js +9 -4
  58. package/dist/commands.js.map +1 -1
  59. package/dist/config.d.ts +14 -4
  60. package/dist/config.js +35 -9
  61. package/dist/config.js.map +1 -1
  62. package/dist/file-lock.d.ts +3 -0
  63. package/dist/file-lock.js +33 -17
  64. package/dist/file-lock.js.map +1 -1
  65. package/dist/guidance-content.js +30 -15
  66. package/dist/guidance-content.js.map +1 -1
  67. package/dist/hook-acknowledgements.d.ts +42 -0
  68. package/dist/hook-acknowledgements.js +316 -0
  69. package/dist/hook-acknowledgements.js.map +1 -0
  70. package/dist/hook-events.d.ts +6 -0
  71. package/dist/hook-events.js +22 -0
  72. package/dist/hook-events.js.map +1 -0
  73. package/dist/hook-gates.d.ts +7 -0
  74. package/dist/hook-gates.js +21 -0
  75. package/dist/hook-gates.js.map +1 -0
  76. package/dist/hook-lifecycle.d.ts +93 -0
  77. package/dist/{hooks.js → hook-lifecycle.js} +61 -1359
  78. package/dist/hook-lifecycle.js.map +1 -0
  79. package/dist/hook-project-sessions.d.ts +28 -0
  80. package/dist/hook-project-sessions.js +141 -0
  81. package/dist/hook-project-sessions.js.map +1 -0
  82. package/dist/hook-question-lock.d.ts +7 -0
  83. package/dist/hook-question-lock.js +173 -0
  84. package/dist/hook-question-lock.js.map +1 -0
  85. package/dist/hook-question-retirement.d.ts +57 -0
  86. package/dist/hook-question-retirement.js +261 -0
  87. package/dist/hook-question-retirement.js.map +1 -0
  88. package/dist/hook-question-state.d.ts +45 -0
  89. package/dist/hook-question-state.js +196 -0
  90. package/dist/hook-question-state.js.map +1 -0
  91. package/dist/hook-session-state.d.ts +31 -0
  92. package/dist/hook-session-state.js +249 -0
  93. package/dist/hook-session-state.js.map +1 -0
  94. package/dist/{hooks.d.ts → hook-types.d.ts} +17 -236
  95. package/dist/hook-types.js +4 -0
  96. package/dist/hook-types.js.map +1 -0
  97. package/dist/install-hooks.d.ts +75 -60
  98. package/dist/install-hooks.js +299 -209
  99. package/dist/install-hooks.js.map +1 -1
  100. package/dist/interactive.d.ts +3 -16
  101. package/dist/interactive.js +19 -95
  102. package/dist/interactive.js.map +1 -1
  103. package/dist/local-path.d.ts +7 -0
  104. package/dist/local-path.js +27 -0
  105. package/dist/local-path.js.map +1 -0
  106. package/dist/node-floor.js +3 -10
  107. package/dist/node-floor.js.map +1 -1
  108. package/dist/npm-invocation.d.ts +34 -0
  109. package/dist/npm-invocation.js +53 -0
  110. package/dist/npm-invocation.js.map +1 -0
  111. package/dist/openclaw-plugin.d.ts +12 -3
  112. package/dist/openclaw-plugin.js +42 -16
  113. package/dist/openclaw-plugin.js.map +1 -1
  114. package/dist/opencode-plugin.d.ts +2 -2
  115. package/dist/opencode-plugin.js +4 -6
  116. package/dist/opencode-plugin.js.map +1 -1
  117. package/dist/program.js +11 -13
  118. package/dist/program.js.map +1 -1
  119. package/dist/project-enablement.d.ts +6 -0
  120. package/dist/project-enablement.js +11 -27
  121. package/dist/project-enablement.js.map +1 -1
  122. package/dist/question-settlement-process.d.ts +1 -1
  123. package/dist/readiness.d.ts +0 -2
  124. package/dist/readiness.js +0 -4
  125. package/dist/readiness.js.map +1 -1
  126. package/dist/send.d.ts +8 -4
  127. package/dist/send.js +145 -26
  128. package/dist/send.js.map +1 -1
  129. package/dist/session-activation.d.ts +3 -1
  130. package/dist/session-activation.js +16 -0
  131. package/dist/session-activation.js.map +1 -1
  132. package/dist/skill-source/manifest.json +4 -4
  133. package/dist/skill-source/notifai/SKILL.md +60 -60
  134. package/dist/skill-source/notifai/references/harness-setup.md +42 -16
  135. package/dist/ui/config-view.d.ts +0 -2
  136. package/dist/ui/config-view.js +1 -5
  137. package/dist/ui/config-view.js.map +1 -1
  138. package/dist/ui/theme.d.ts +0 -3
  139. package/dist/ui/theme.js +0 -1
  140. package/dist/ui/theme.js.map +1 -1
  141. package/dist/version.d.ts +22 -0
  142. package/dist/version.js +51 -0
  143. package/dist/version.js.map +1 -0
  144. package/dist/wake-support.d.ts +8 -0
  145. package/dist/wake-support.js +38 -0
  146. package/dist/wake-support.js.map +1 -0
  147. package/package.json +2 -2
  148. package/dist/cli-update-contract.d.ts +0 -7
  149. package/dist/cli-update-contract.js +0 -10
  150. package/dist/cli-update-contract.js.map +0 -1
  151. package/dist/commands-hooks.d.ts +0 -160
  152. package/dist/commands-hooks.js +0 -1800
  153. package/dist/commands-hooks.js.map +0 -1
  154. package/dist/hooks.js.map +0 -1
@@ -0,0 +1,261 @@
1
+ import { REPLY_MAX_WINDOW_SECONDS } from '@raidiant/notifai-protocol';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { atomicWriteFileSync } from './atomic-file.js';
5
+ import { stateDir } from './config.js';
6
+ import { withFileLock } from './file-lock.js';
7
+ import { isSamePending, rememberQuestionState, sourceContextAtHookEvent, } from './hook-question-state.js';
8
+ import { pendingList, readSessionState, updateSessionState } from './hook-session-state.js';
9
+ /**
10
+ * A retirement is only worth syncing while the question it retires could still
11
+ * be answered, so this has to outlive the longest reply window a user can
12
+ * choose — it is derived from that bound rather than from a guess about how
13
+ * long questions stay open, which is exactly the assumption that broke when
14
+ * the window stopped being one hour. Past it the companion shows the question
15
+ * as dead on next open anyway. Also the backstop that keeps an unreachable
16
+ * server from growing the queue for ever.
17
+ */
18
+ const ORPHAN_TTL_MS = (REPLY_MAX_WINDOW_SECONDS + 3600) * 1000;
19
+ /** More orphans than this means something is looping; keep the newest. */
20
+ const ORPHAN_QUEUE_CAP = 50;
21
+ /**
22
+ * Close is a server-side serialization fence: a successful response contains
23
+ * every reply that committed before the window closed. `null` is deliberately
24
+ * different from an empty reply set — it means ownership could not be proven
25
+ * final and the local record must stay recoverable.
26
+ */
27
+ export async function finalizeReplies(ctx, requestId) {
28
+ try {
29
+ return await ctx.client.closeReplies(requestId);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /** Best effort only for already-journaled retirement debt. */
36
+ export async function closeQuietly(ctx, requestId) {
37
+ await finalizeReplies(ctx, requestId);
38
+ }
39
+ /**
40
+ * Close the reply window so the server can mint the retirement push. Client
41
+ * submissions cannot carry `question_retired`; that event is reserved for
42
+ * server-origin state syncs.
43
+ */
44
+ async function proveRetirement(ctx, requestId) {
45
+ const response = await finalizeReplies(ctx, requestId);
46
+ const proven = response !== null;
47
+ ctx.log?.info('hook.retirement', {
48
+ request_id: requestId,
49
+ attempted: true,
50
+ proven,
51
+ });
52
+ return proven;
53
+ }
54
+ /**
55
+ * Park a delivered question for retirement. Called at the moment we learn it is
56
+ * dead, which is frequently not a moment we can reach the network.
57
+ *
58
+ * Nothing is parked for a question that never reached a device: with no
59
+ * request_id there is nothing on any device to retire.
60
+ */
61
+ export function parkForRetirement(sessionId, env, pending, state) {
62
+ const retirement = retiringQuestion(pending, state);
63
+ if (retirement === null)
64
+ return;
65
+ updateSessionState(sessionId, env, (current) => {
66
+ const already = current.retiring ?? [];
67
+ const existing = already.findIndex((entry) => entry.request_id === retirement.request_id);
68
+ if (existing < 0) {
69
+ return rememberQuestionState({ ...current, retiring: [...already, retirement] }, pending, state === 'answered' ? 'answered' : 'retired');
70
+ }
71
+ const retiring = [...already];
72
+ // An observed answer is final truth and upgrades an earlier supersession
73
+ // parked while the submission callback was racing a newer question.
74
+ if (retirement.state === 'answered' && retiring[existing].state !== 'answered') {
75
+ retiring[existing] = retirement;
76
+ }
77
+ return rememberQuestionState({ ...current, retiring }, pending, state === 'answered' ? 'answered' : 'retired');
78
+ });
79
+ }
80
+ /**
81
+ * Convert live question state into a complete retirement instruction.
82
+ *
83
+ * A request/collapse pair without the original Delivery targets is not safe to
84
+ * send: re-resolving today's routing can miss a device that still shows the
85
+ * question or retire a device that never received it. Fail explicitly and keep
86
+ * the pending record instead of silently broadening or narrowing the fanout.
87
+ */
88
+ export function retiringQuestion(pending, state, cwd) {
89
+ // A submit journal is written before the first network byte. SessionEnd can
90
+ // therefore race a request that the server accepts after local cleanup. Its
91
+ // reserved request id and exact fanout are already sufficient retirement
92
+ // identity, even though the ordinary live fields have not been promoted yet.
93
+ const requestId = pending.request_id ?? pending.submission?.request_id;
94
+ const collapseKey = pending.collapse_key ?? pending.submission?.collapse_key;
95
+ const deviceIds = pending.device_ids ?? pending.submission?.device_ids;
96
+ const hasRequest = requestId !== undefined;
97
+ const hasCollapse = collapseKey !== undefined;
98
+ const hasDevices = deviceIds !== undefined && deviceIds.length > 0;
99
+ if (!hasRequest && !hasCollapse && !hasDevices)
100
+ return null;
101
+ if (!hasRequest || !hasCollapse || !hasDevices) {
102
+ throw new Error('live question state is incomplete; refusing to retire it without request, collapse, and device identifiers');
103
+ }
104
+ const source = sourceContextAtHookEvent(pending.source, cwd);
105
+ return {
106
+ ...(pending.question_id !== undefined ? { question_id: pending.question_id } : {}),
107
+ request_id: requestId,
108
+ collapse_key: collapseKey,
109
+ device_ids: [...deviceIds],
110
+ question: pending.question,
111
+ ...(pending.project !== undefined ? { project: pending.project } : {}),
112
+ ...(source !== undefined ? { source } : {}),
113
+ state,
114
+ };
115
+ }
116
+ const SHORT_CONFIRMATIONS = new Set(['done', 'yes', 'y', 'no', 'n', 'ok', 'okay']);
117
+ function normalizePrompt(value) {
118
+ return value.trim().replace(/\s+/g, ' ').toLowerCase();
119
+ }
120
+ function pendingChoiceLabels(pending) {
121
+ const fromQuestions = (pending.questions ?? []).flatMap((question) => (question.choices ?? []).flatMap((choice) => [choice.label, choice.id]));
122
+ return fromQuestions.map(normalizePrompt).filter((label) => label.length > 0);
123
+ }
124
+ function pendingHasMatchingChoice(pending, normalizedPrompt) {
125
+ return pendingChoiceLabels(pending).includes(normalizedPrompt);
126
+ }
127
+ /**
128
+ * Which outstanding questions this prompt plausibly answers.
129
+ *
130
+ * A unique closed-choice match is enough. A short confirmation such as "done"
131
+ * matches only when exactly one question is outstanding. Long unrelated
132
+ * prompts leave every question open for the reply window.
133
+ */
134
+ export function pendingAnsweredByPrompt(prompt, pending) {
135
+ const normalized = normalizePrompt(prompt ?? '');
136
+ if (normalized.length === 0 || pending.length === 0)
137
+ return [];
138
+ const labelled = pending.filter((entry) => pendingHasMatchingChoice(entry, normalized));
139
+ if (labelled.length === 1)
140
+ return labelled;
141
+ if (labelled.length > 1)
142
+ return [];
143
+ if (pending.length === 1 && SHORT_CONFIRMATIONS.has(normalized))
144
+ return [...pending];
145
+ return [];
146
+ }
147
+ /**
148
+ * Send every parked retirement, and forget the ones that landed. A failure
149
+ * leaves the entry in place for the next hook rather than losing it, which is
150
+ * the whole reason the queue exists.
151
+ */
152
+ export async function drainRetirements(ctx, sessionId, env) {
153
+ const queue = readSessionState(sessionId, env).retiring ?? [];
154
+ if (queue.length === 0)
155
+ return [];
156
+ const retired = [];
157
+ for (const entry of queue) {
158
+ const proven = await proveRetirement(ctx, entry.request_id);
159
+ if (proven) {
160
+ retired.push(entry.request_id);
161
+ // Persist each success before touching the next entry. A later corrupt
162
+ // record or interrupted process must not resurrect work already done.
163
+ updateSessionState(sessionId, env, (current) => ({
164
+ ...current,
165
+ retiring: (current.retiring ?? []).filter((candidate) => candidate.request_id !== entry.request_id),
166
+ }));
167
+ }
168
+ }
169
+ return retired;
170
+ }
171
+ function orphanQueuePath(env) {
172
+ return path.join(stateDir(env), 'retire-queue.json');
173
+ }
174
+ function readOrphanQueue(env) {
175
+ const file = orphanQueuePath(env);
176
+ if (!existsSync(file))
177
+ return [];
178
+ try {
179
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
180
+ return Array.isArray(parsed) ? parsed : [];
181
+ }
182
+ catch {
183
+ // Same stance as session state: corruption fails closed to "nothing queued".
184
+ return [];
185
+ }
186
+ }
187
+ function writeOrphanQueueUnlocked(file, queue) {
188
+ atomicWriteFileSync(file, `${JSON.stringify(queue, null, 2)}\n`);
189
+ }
190
+ function updateOrphanQueue(env, update) {
191
+ const file = orphanQueuePath(env);
192
+ return withFileLock(`${file}.lock`, () => {
193
+ const next = update(readOrphanQueue(env));
194
+ writeOrphanQueueUnlocked(file, next);
195
+ return next;
196
+ });
197
+ }
198
+ /** Move retirements into the global queue; deduped so a retry costs nothing. */
199
+ export function orphanRetirements(env, entries, now) {
200
+ if (entries.length === 0)
201
+ return;
202
+ updateOrphanQueue(env, (queue) => {
203
+ const known = new Set(queue.map((entry) => entry.request_id));
204
+ const added = entries
205
+ .filter((entry) => !known.has(entry.request_id))
206
+ .map((entry) => ({ ...entry, enqueued_at: now }));
207
+ return [...queue, ...added].slice(-ORPHAN_QUEUE_CAP);
208
+ });
209
+ }
210
+ /**
211
+ * Retire everything a dead session left behind. Failures stay queued for the
212
+ * next holder of a client; entries past the TTL are dropped as already dead.
213
+ */
214
+ export async function drainOrphanRetirements(ctx, env, now) {
215
+ const queue = readOrphanQueue(env);
216
+ if (queue.length === 0)
217
+ return [];
218
+ const done = [];
219
+ for (const entry of queue) {
220
+ const age = now - entry.enqueued_at;
221
+ // A negative age is a clock jump, not a fresh entry; retiring is idempotent
222
+ // and cheap, so treat it as due rather than letting it linger for ever.
223
+ if (age > ORPHAN_TTL_MS) {
224
+ done.push(entry.request_id);
225
+ updateOrphanQueue(env, (current) => current.filter((candidate) => candidate.request_id !== entry.request_id));
226
+ continue;
227
+ }
228
+ const proven = await proveRetirement(ctx, entry.request_id);
229
+ if (proven) {
230
+ done.push(entry.request_id);
231
+ updateOrphanQueue(env, (current) => current.filter((candidate) => candidate.request_id !== entry.request_id));
232
+ }
233
+ }
234
+ return done;
235
+ }
236
+ export async function retirePendings(ctx, envelope, sessionId, entries, state) {
237
+ updateSessionState(sessionId, ctx.env, (current) => {
238
+ const retiring = [...(current.retiring ?? [])];
239
+ for (const entry of entries) {
240
+ const retirement = retiringQuestion(entry, state, envelope.cwd);
241
+ if (retirement !== null && !retiring.some((item) => item.request_id === retirement.request_id)) {
242
+ retiring.push(retirement);
243
+ }
244
+ }
245
+ const pending = pendingList(current).filter((candidate) => !entries.some((entry) => isSamePending(candidate, entry)));
246
+ const next = { ...current };
247
+ if (pending.length > 0)
248
+ next.pending = pending;
249
+ else
250
+ delete next.pending;
251
+ if (retiring.length > 0)
252
+ next.retiring = retiring;
253
+ else
254
+ delete next.retiring;
255
+ return entries.reduce((remembered, entry) => rememberQuestionState(remembered, entry, state === 'answered' ? 'answered' : 'retired'), next);
256
+ });
257
+ // Network retirement is deliberately deferred to a later no-question hook.
258
+ // The state write above is the durable operation; blocking here creates a
259
+ // crash window before an answer can reach harness stdout.
260
+ }
261
+ //# sourceMappingURL=hook-question-retirement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-question-retirement.js","sourceRoot":"","sources":["../src/hook-question-retirement.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /** Question history, inspection, and atomic live-question state changes. */
2
+ import type { QuestionT, SourceContextT } from '@raidiant/notifai-protocol';
3
+ import type { PendingQuestion, QuestionDeliveryState, QuestionTerminalState, SessionState } from './hook-types.js';
4
+ export interface QuestionStateView {
5
+ question_id: string;
6
+ state: QuestionDeliveryState;
7
+ /** Null means a frozen submission may or may not have crossed the service boundary. */
8
+ submitted: boolean | null;
9
+ request_id: string | null;
10
+ frozen_request_id: string | null;
11
+ }
12
+ export type QuestionStateLookup = {
13
+ found: true;
14
+ session_id: string;
15
+ question: QuestionStateView;
16
+ } | {
17
+ found: false;
18
+ ambiguous: boolean;
19
+ };
20
+ /** Keep immutable session grouping while replacing per-event Git location authoritatively. */
21
+ export declare function sourceContextAtHookEvent(source: SourceContextT | undefined, cwd: string | undefined): SourceContextT | undefined;
22
+ export declare function summarizeRequestIds(entries: readonly PendingQuestion[]): {
23
+ ids: string[];
24
+ display: string;
25
+ };
26
+ type QuestionIdentity = Pick<PendingQuestion, 'question_id' | 'request_id' | 'submission'>;
27
+ /** Keep a bounded, content-free map from local identity to its terminal state. */
28
+ export declare function rememberQuestionState(current: SessionState, question: QuestionIdentity, state: QuestionTerminalState): SessionState;
29
+ /** Read one stable local question identity without minting or submitting anything. */
30
+ export declare function inspectQuestionState(questionId: string, env: NodeJS.ProcessEnv): QuestionStateLookup;
31
+ /** Registration identity — the convention every racing writer compares by. */
32
+ export declare function isSamePending(a: PendingQuestion, b: PendingQuestion): boolean;
33
+ export declare function dropPendingQuestion(sessionId: string, env: NodeJS.ProcessEnv, entry: PendingQuestion, terminalState?: QuestionTerminalState): void;
34
+ /**
35
+ * Drop registrations that never reached a device, so a later Stop cannot push
36
+ * them. A retirement push would be noise: there is nothing on any device to
37
+ * withdraw. Returns the withdrawn entries in registration order.
38
+ */
39
+ export declare function withdrawUnpushedQuestions(sessionId: string, env: NodeJS.ProcessEnv): PendingQuestion[];
40
+ export declare function clearFrozenSubmission(sessionId: string, env: NodeJS.ProcessEnv, entry: PendingQuestion): void;
41
+ /** The question set this pending record pushes, however it was registered. */
42
+ export declare function pendingQuestions(pending: PendingQuestion): QuestionT[];
43
+ /** Did any question in the set offer choices? Decides how replies combine. */
44
+ export declare function pendingHasChoices(pending: PendingQuestion): boolean;
45
+ export {};
@@ -0,0 +1,196 @@
1
+ import { findOwningSession, pendingList, readSessionState, updateSessionState, } from './hook-session-state.js';
2
+ import { inferInvocationContext } from './invocation-context.js';
3
+ /** Keep immutable session grouping while replacing per-event Git location authoritatively. */
4
+ export function sourceContextAtHookEvent(source, cwd) {
5
+ const invocation = cwd === undefined ? null : inferInvocationContext(cwd);
6
+ const current = {
7
+ ...(source?.session_id !== undefined ? { session_id: source.session_id } : {}),
8
+ ...(source?.session_label !== undefined ? { session_label: source.session_label } : {}),
9
+ ...(source?.harness !== undefined ? { harness: source.harness } : {}),
10
+ ...(invocation?.branch !== undefined ? { branch: invocation.branch } : {}),
11
+ ...(invocation?.worktree !== undefined ? { worktree: invocation.worktree } : {}),
12
+ };
13
+ return Object.keys(current).length === 0 ? undefined : current;
14
+ }
15
+ export function summarizeRequestIds(entries) {
16
+ const ids = entries.flatMap((entry) => entry.request_id === undefined ? [] : [entry.request_id]);
17
+ return { ids, display: ids.join(', ') };
18
+ }
19
+ const QUESTION_HISTORY_CAP = 50;
20
+ /** Keep a bounded, content-free map from local identity to its terminal state. */
21
+ export function rememberQuestionState(current, question, state) {
22
+ if (question.question_id === undefined)
23
+ return current;
24
+ const existing = (current.question_history ?? []).find((entry) => entry.question_id === question.question_id);
25
+ const terminalState = existing?.state === 'withdrawn' || existing?.state === 'answered'
26
+ ? existing.state
27
+ : state === 'answered'
28
+ ? 'answered'
29
+ : state;
30
+ const requestId = question.request_id ?? existing?.request_id;
31
+ const frozenRequestId = requestId === undefined
32
+ ? question.submission?.request_id ?? existing?.frozen_request_id
33
+ : undefined;
34
+ const record = {
35
+ question_id: question.question_id,
36
+ state: terminalState,
37
+ ...(requestId === undefined ? {} : { request_id: requestId }),
38
+ ...(frozenRequestId === undefined ? {} : { frozen_request_id: frozenRequestId }),
39
+ };
40
+ const history = [
41
+ ...(current.question_history ?? []).filter((entry) => entry.question_id !== question.question_id),
42
+ record,
43
+ ].slice(-QUESTION_HISTORY_CAP);
44
+ return { ...current, question_history: history };
45
+ }
46
+ function pendingQuestionView(entry) {
47
+ if (entry.question_id === undefined)
48
+ return null;
49
+ if (entry.request_id !== undefined) {
50
+ return {
51
+ question_id: entry.question_id,
52
+ state: 'live',
53
+ submitted: true,
54
+ request_id: entry.request_id,
55
+ frozen_request_id: null,
56
+ };
57
+ }
58
+ if (entry.submission !== undefined) {
59
+ return {
60
+ question_id: entry.question_id,
61
+ state: 'frozen',
62
+ submitted: null,
63
+ request_id: null,
64
+ frozen_request_id: entry.submission.request_id,
65
+ };
66
+ }
67
+ return {
68
+ question_id: entry.question_id,
69
+ state: 'local',
70
+ submitted: false,
71
+ request_id: null,
72
+ frozen_request_id: null,
73
+ };
74
+ }
75
+ /** Read one stable local question identity without minting or submitting anything. */
76
+ export function inspectQuestionState(questionId, env) {
77
+ const owner = findOwningSession(questionId, env);
78
+ if (owner.ambiguous || owner.sessionId === null) {
79
+ return { found: false, ambiguous: owner.ambiguous };
80
+ }
81
+ const state = readSessionState(owner.sessionId, env);
82
+ const pending = pendingList(state).find((entry) => entry.question_id === questionId);
83
+ const pendingView = pending === undefined ? null : pendingQuestionView(pending);
84
+ if (pendingView !== null) {
85
+ return { found: true, session_id: owner.sessionId, question: pendingView };
86
+ }
87
+ const accepted = state.accepted?.answers.find(({ pending: entry }) => entry.question_id === questionId)?.pending;
88
+ if (accepted?.question_id !== undefined) {
89
+ return {
90
+ found: true,
91
+ session_id: owner.sessionId,
92
+ question: {
93
+ question_id: accepted.question_id,
94
+ state: 'answered',
95
+ submitted: true,
96
+ request_id: accepted.request_id ?? null,
97
+ frozen_request_id: null,
98
+ },
99
+ };
100
+ }
101
+ const retiring = (state.retiring ?? []).find((entry) => entry.question_id === questionId);
102
+ if (retiring?.question_id !== undefined) {
103
+ return {
104
+ found: true,
105
+ session_id: owner.sessionId,
106
+ question: {
107
+ question_id: retiring.question_id,
108
+ state: retiring.state === 'answered' ? 'answered' : 'retired',
109
+ submitted: true,
110
+ request_id: retiring.request_id,
111
+ frozen_request_id: null,
112
+ },
113
+ };
114
+ }
115
+ const history = (state.question_history ?? []).find((entry) => entry.question_id === questionId);
116
+ if (history !== undefined) {
117
+ return {
118
+ found: true,
119
+ session_id: owner.sessionId,
120
+ question: {
121
+ question_id: history.question_id,
122
+ state: history.state,
123
+ submitted: history.request_id !== undefined
124
+ ? true
125
+ : history.frozen_request_id !== undefined
126
+ ? null
127
+ : false,
128
+ request_id: history.request_id ?? null,
129
+ frozen_request_id: history.frozen_request_id ?? null,
130
+ },
131
+ };
132
+ }
133
+ return { found: false, ambiguous: false };
134
+ }
135
+ /** Registration identity — the convention every racing writer compares by. */
136
+ export function isSamePending(a, b) {
137
+ if (a.question_id !== undefined || b.question_id !== undefined) {
138
+ return a.question_id !== undefined && a.question_id === b.question_id;
139
+ }
140
+ return a.question === b.question && a.asked_at === b.asked_at;
141
+ }
142
+ export function dropPendingQuestion(sessionId, env, entry, terminalState = 'retired') {
143
+ updateSessionState(sessionId, env, (current) => {
144
+ const pending = pendingList(current).filter((candidate) => !isSamePending(candidate, entry));
145
+ const next = { ...current };
146
+ if (pending.length > 0)
147
+ next.pending = pending;
148
+ else
149
+ delete next.pending;
150
+ return rememberQuestionState(next, entry, terminalState);
151
+ });
152
+ }
153
+ /**
154
+ * Drop registrations that never reached a device, so a later Stop cannot push
155
+ * them. A retirement push would be noise: there is nothing on any device to
156
+ * withdraw. Returns the withdrawn entries in registration order.
157
+ */
158
+ export function withdrawUnpushedQuestions(sessionId, env) {
159
+ let withdrawn = [];
160
+ updateSessionState(sessionId, env, (current) => {
161
+ const pending = pendingList(current);
162
+ withdrawn = pending.filter((entry) => entry.request_id === undefined);
163
+ if (withdrawn.length === 0)
164
+ return current;
165
+ const remaining = pending.filter((entry) => entry.request_id !== undefined);
166
+ const next = { ...current };
167
+ if (remaining.length > 0)
168
+ next.pending = remaining;
169
+ else
170
+ delete next.pending;
171
+ return withdrawn.reduce((remembered, entry) => rememberQuestionState(remembered, entry, 'withdrawn'), next);
172
+ });
173
+ return withdrawn;
174
+ }
175
+ export function clearFrozenSubmission(sessionId, env, entry) {
176
+ updateSessionState(sessionId, env, (current) => {
177
+ const list = pendingList(current);
178
+ const index = list.findIndex((candidate) => isSamePending(candidate, entry));
179
+ if (index < 0)
180
+ return current;
181
+ const next = [...list];
182
+ const copy = { ...next[index] };
183
+ delete copy.submission;
184
+ next[index] = copy;
185
+ return { ...current, pending: next };
186
+ });
187
+ }
188
+ /** The question set this pending record pushes, however it was registered. */
189
+ export function pendingQuestions(pending) {
190
+ return pending.questions ?? [{ id: 'q1', text: pending.question }];
191
+ }
192
+ /** Did any question in the set offer choices? Decides how replies combine. */
193
+ export function pendingHasChoices(pending) {
194
+ return pendingQuestions(pending).some((question) => question.choices !== undefined);
195
+ }
196
+ //# sourceMappingURL=hook-question-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-question-state.js","sourceRoot":"","sources":["../src/hook-question-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { HookHarness, PendingQuestion, SessionState } from './hook-types.js';
2
+ export declare function sessionStatePath(sessionId: string, env: NodeJS.ProcessEnv): string;
3
+ export declare function sessionHasEnded(sessionId: string, env: NodeJS.ProcessEnv): boolean;
4
+ export declare function markSessionEnded(sessionId: string, env: NodeJS.ProcessEnv, now: number): void;
5
+ export declare function readSessionState(sessionId: string, env: NodeJS.ProcessEnv): SessionState;
6
+ /**
7
+ * Resolve a stable question or request id to the one exact session that owns it.
8
+ *
9
+ * This deliberately searches lifecycle state, not checkout pointers: question
10
+ * identity is global on this machine, while a linked worktree is only where a
11
+ * command happened to run. Ambiguous or corrupt matches fail closed.
12
+ */
13
+ export declare function findOwningSession(id: string, env: NodeJS.ProcessEnv): {
14
+ sessionId: string | null;
15
+ ambiguous: boolean;
16
+ };
17
+ export declare function writeSessionState(sessionId: string, env: NodeJS.ProcessEnv, state: SessionState): void;
18
+ export declare function clearSessionState(sessionId: string, env: NodeJS.ProcessEnv): void;
19
+ export declare function writeSessionStateUnlocked(file: string, sessionId: string, state: SessionState): void;
20
+ export declare function updateSessionState(sessionId: string, env: NodeJS.ProcessEnv, update: (current: SessionState) => SessionState): SessionState;
21
+ /** Record the current installation when an owner session starts. */
22
+ export declare function recordSessionStart(sessionId: string, env: NodeJS.ProcessEnv, harness?: HookHarness, cwd?: string, codexStopDefinitionFingerprint?: string): void;
23
+ /** Atomically claim Cursor's first-Stop activation fallback once per conversation. */
24
+ export declare function claimCursorStopActivation(sessionId: string, env: NodeJS.ProcessEnv, now: number): boolean;
25
+ /** Cursor's loop_count proves the previous Stop follow-up reached the host. */
26
+ export declare function confirmCursorStopActivation(sessionId: string, env: NodeJS.ProcessEnv, now: number): boolean;
27
+ /** A fresh/cleared Cursor lifecycle makes its next Stop the fallback owner again. */
28
+ export declare function resetCursorStopActivation(sessionId: string, env: NodeJS.ProcessEnv): void;
29
+ export declare function pruneAbandonedSessions(env: NodeJS.ProcessEnv, now?: number, maxAgeMs?: number): number;
30
+ /** The registered-question queue. Anything but the current shape reads as empty. */
31
+ export declare function pendingList(state: SessionState): PendingQuestion[];