@raidiant/notifai 0.3.0 → 0.4.0

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 (80) hide show
  1. package/README.md +20 -4
  2. package/dist/atomic-file.d.ts +4 -0
  3. package/dist/atomic-file.js +60 -10
  4. package/dist/atomic-file.js.map +1 -1
  5. package/dist/client.d.ts +25 -2
  6. package/dist/client.js +45 -1
  7. package/dist/client.js.map +1 -1
  8. package/dist/commands.d.ts +56 -7
  9. package/dist/commands.js +980 -318
  10. package/dist/commands.js.map +1 -1
  11. package/dist/config-schema.d.ts +1 -1
  12. package/dist/config-schema.js +46 -8
  13. package/dist/config-schema.js.map +1 -1
  14. package/dist/config.d.ts +38 -3
  15. package/dist/config.js +61 -14
  16. package/dist/config.js.map +1 -1
  17. package/dist/file-lock.d.ts +26 -0
  18. package/dist/file-lock.js +268 -0
  19. package/dist/file-lock.js.map +1 -0
  20. package/dist/harnesses.d.ts +28 -0
  21. package/dist/harnesses.js +40 -0
  22. package/dist/harnesses.js.map +1 -0
  23. package/dist/hook-adapter.d.ts +27 -0
  24. package/dist/hook-adapter.js +188 -0
  25. package/dist/hook-adapter.js.map +1 -0
  26. package/dist/hook-input.d.ts +10 -0
  27. package/dist/hook-input.js +64 -0
  28. package/dist/hook-input.js.map +1 -0
  29. package/dist/hooks.d.ts +66 -10
  30. package/dist/hooks.js +751 -276
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/install-hooks.d.ts +40 -16
  33. package/dist/install-hooks.js +217 -73
  34. package/dist/install-hooks.js.map +1 -1
  35. package/dist/interactive.d.ts +1 -1
  36. package/dist/interactive.js +3 -3
  37. package/dist/interactive.js.map +1 -1
  38. package/dist/logging.d.ts +142 -0
  39. package/dist/logging.js +533 -0
  40. package/dist/logging.js.map +1 -0
  41. package/dist/main.js +97 -36
  42. package/dist/main.js.map +1 -1
  43. package/dist/opencode-plugin.d.ts +7 -7
  44. package/dist/opencode-plugin.js +12 -13
  45. package/dist/opencode-plugin.js.map +1 -1
  46. package/dist/ui/config-view.js +3 -0
  47. package/dist/ui/config-view.js.map +1 -1
  48. package/dist/ui/theme.d.ts +1 -1
  49. package/package.json +3 -2
  50. package/src/atomic-file.ts +86 -7
  51. package/src/client.test.ts +12 -0
  52. package/src/client.ts +72 -4
  53. package/src/commands.test.ts +891 -43
  54. package/src/commands.ts +1133 -332
  55. package/src/config-schema.ts +51 -9
  56. package/src/config.test.ts +6 -5
  57. package/src/config.ts +85 -16
  58. package/src/file-lock-process-worker.test.ts +64 -0
  59. package/src/file-lock.test.ts +279 -0
  60. package/src/file-lock.ts +318 -0
  61. package/src/harnesses.test.ts +48 -0
  62. package/src/harnesses.ts +59 -0
  63. package/src/hook-adapter.test.ts +187 -0
  64. package/src/hook-adapter.ts +212 -0
  65. package/src/hook-input.test.ts +39 -0
  66. package/src/hook-input.ts +72 -0
  67. package/src/hooks.test.ts +1318 -240
  68. package/src/hooks.ts +1046 -296
  69. package/src/install-hooks.test.ts +90 -41
  70. package/src/install-hooks.ts +259 -74
  71. package/src/interactive.ts +3 -3
  72. package/src/logging-process-worker.test.ts +39 -0
  73. package/src/logging.test.ts +453 -0
  74. package/src/logging.ts +639 -0
  75. package/src/main.ts +113 -32
  76. package/src/opencode-plugin.ts +15 -16
  77. package/src/session-end-log-lock-worker.test.ts +35 -0
  78. package/src/skill.test.ts +11 -0
  79. package/src/ui/config-view.test.ts +4 -2
  80. package/src/ui/config-view.ts +3 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-input.js","sourceRoot":"","sources":["../src/hook-input.ts"],"names":[],"mappings":"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"}
package/dist/hooks.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import type { LifecycleEndState, QuestionT, ReplyView } from '@raidiant/notifai-protocol';
2
- import type { ApiClient } from './client.js';
1
+ import type { LifecycleEndState, NotificationDraftT, QuestionT, ReplyView } from '@raidiant/notifai-protocol';
2
+ import { type ApiClient } from './client.js';
3
3
  import { type CliConfig } from './config.js';
4
+ import type { Logger } from './logging.js';
5
+ import { type Harness } from './harnesses.js';
4
6
  /**
5
7
  * Harness hook handlers.
6
8
  *
7
9
  * The supported harnesses expose the same useful lifecycle joints: a turn-end
8
- * event and an event that fires when the user submits a prompt. Claude Code,
9
- * Codex, and Cursor can continue directly from a turn-end answer. OpenCode
10
- * preserves the pending answer at turn end and injects it on the next prompt.
11
- * No question detection or context-window state is needed in either path.
10
+ * event and an event that fires when the user submits a prompt. Claude Code
11
+ * and Codex can continue directly from a turn-end answer. Harnesses without a
12
+ * proven exact-session continuation fail closed at question admission.
12
13
  *
13
14
  * The load-bearing constraint is that these hooks are synchronous: while one
14
15
  * blocks, the harness cannot show its own prompt either. So a hook may only
@@ -32,6 +33,8 @@ export interface HookEnvelope {
32
33
  export interface SessionState {
33
34
  /** Epoch ms of the user's last prompt in this session — our presence signal. */
34
35
  last_prompt_at?: number;
36
+ /** Epoch ms of the last observed Stop hook, distinct from prompt routing. */
37
+ last_stop_at?: number;
35
38
  /**
36
39
  * Questions registered by `notifai ask`, in registration order, each
37
40
  * awaiting escalation or its answer. A list, deliberately: registering a
@@ -59,6 +62,19 @@ export interface SessionState {
59
62
  answered_at: number;
60
63
  count: number;
61
64
  };
65
+ /**
66
+ * A phone answer durably captured but not yet acknowledged by a successor
67
+ * hook. It stays here until the next Stop proves the harness processed the
68
+ * continuation; a crash before stdout therefore replays instead of erasing
69
+ * the user's answer.
70
+ */
71
+ accepted?: AcceptedAnswerDelivery;
72
+ }
73
+ interface AcceptedAnswerDelivery {
74
+ answers: AnsweredPending[];
75
+ remaining: number;
76
+ recorded_at: number;
77
+ continuation_count?: number;
62
78
  }
63
79
  /** A delivered question awaiting its retirement push. */
64
80
  export interface RetiringQuestion {
@@ -88,6 +104,8 @@ export interface OrphanRetirement extends RetiringQuestion {
88
104
  enqueued_at: number;
89
105
  }
90
106
  export interface PendingQuestion {
107
+ /** Stable local identity across racing state writers and submit recovery. */
108
+ question_id?: string;
91
109
  /** One-line summary: the single question's text, or the set's first. */
92
110
  question: string;
93
111
  /**
@@ -108,6 +126,18 @@ export interface PendingQuestion {
108
126
  collapse_key?: string;
109
127
  /** Exact fanout of the live question; routing config may change afterwards. */
110
128
  device_ids?: string[];
129
+ /** Absolute end of the server reply window and its local continuation owner. */
130
+ reply_deadline_at?: number;
131
+ /** Frozen before the first network byte, so an ambiguous submit is replayable. */
132
+ submission?: PendingSubmissionIntent;
133
+ }
134
+ interface PendingSubmissionIntent {
135
+ request_id: string;
136
+ idempotency_key: string;
137
+ collapse_key: string;
138
+ device_ids: string[];
139
+ draft: NotificationDraftT;
140
+ owner_deadline_at: number;
111
141
  }
112
142
  export type GraceOutcome =
113
143
  /** The window elapsed with the user still gone; escalate. */
@@ -141,6 +171,8 @@ export interface HookOutcome {
141
171
  stdout?: string;
142
172
  /** Diagnostics; harnesses surface hook stderr in the transcript. */
143
173
  notes: string[];
174
+ /** Structured lifecycle detail that belongs on hook.end without user text. */
175
+ log?: Record<string, unknown>;
144
176
  }
145
177
  export declare function readSessionState(sessionId: string, env: NodeJS.ProcessEnv): SessionState;
146
178
  export declare function writeSessionState(sessionId: string, env: NodeJS.ProcessEnv, state: SessionState): void;
@@ -148,7 +180,7 @@ export declare function clearSessionState(sessionId: string, env: NodeJS.Process
148
180
  export declare function pruneAbandonedSessions(env: NodeJS.ProcessEnv, now?: number, maxAgeMs?: number): number;
149
181
  export declare function claimQuestionPush(sessionId: string, env: NodeJS.ProcessEnv, now?: number, beforeStaleReplace?: () => void): boolean;
150
182
  export declare function releaseQuestionPush(sessionId: string, env: NodeJS.ProcessEnv): void;
151
- /** Records which session is working in a directory, for `notifai ask`. */
183
+ /** Records every live session working in a directory, for `notifai ask`. */
152
184
  export declare function writeProjectSession(cwd: string, env: NodeJS.ProcessEnv, sessionId: string, now: number, harness: HookHarness): void;
153
185
  export interface ProjectSessionPointer {
154
186
  sessionId: string;
@@ -161,6 +193,7 @@ export interface ProjectSessionPointer {
161
193
  */
162
194
  export declare function readProjectSession(cwd: string, env: NodeJS.ProcessEnv, now: number, maxAgeMs?: number): string | null;
163
195
  export declare function readProjectSessionPointer(cwd: string, env: NodeJS.ProcessEnv, now: number, maxAgeMs?: number): ProjectSessionPointer | null;
196
+ export declare function readMatchingProjectSessionPointer(cwd: string, env: NodeJS.ProcessEnv, now: number, sessionId: string, harness: HookHarness, maxAgeMs?: number): ProjectSessionPointer | null;
164
197
  /**
165
198
  * Absent means the user is not at this machine's keyboard.
166
199
  *
@@ -203,10 +236,26 @@ export interface HookContext {
203
236
  timedOut: boolean;
204
237
  degraded?: boolean;
205
238
  }>;
206
- /** OpenCode cannot consume Stop stdout, so its adapter must never wait there. */
239
+ /** The active harness selects the native continuation output adapter. */
207
240
  harness?: HookHarness;
241
+ /**
242
+ * The local record. A hook's decisions are invisible from everywhere else —
243
+ * its stderr belongs to the harness and its usual outcome is to do nothing —
244
+ * so this is the only account of why a question did or did not travel.
245
+ */
246
+ log?: Logger;
208
247
  }
209
- export type HookHarness = 'claude-code' | 'codex' | 'cursor' | 'opencode';
248
+ /**
249
+ * Record one gate decision.
250
+ *
251
+ * `verdict` is what happened to the question and `reason` is the closed-set
252
+ * name of the rule that decided it, so an agent can ask "why was it held" with
253
+ * a filter rather than by matching on prose. The prose still exists — it is what
254
+ * the user sees in the terminal — but it is free to be rewritten, and a log a
255
+ * filter cannot rely on is a log nobody filters.
256
+ */
257
+ export type GateReason = 'no-session' | 'no-question' | 'answered' | 'already-asked' | 'continuation-repeat' | 'continuation-limit' | 'notifications-off' | 'user-present' | 'claimed-elsewhere' | 'user-returned' | 'no-devices' | 'proceeding';
258
+ export type HookHarness = Harness;
210
259
  /**
211
260
  * Everything retirement needs. Narrower than a HookContext on purpose:
212
261
  * `notifai ask` supersedes the previous question and it is a plain command with
@@ -234,6 +283,12 @@ export declare function orphanRetirements(env: NodeJS.ProcessEnv, entries: Retir
234
283
  * next holder of a client; entries past the TTL are dropped as already dead.
235
284
  */
236
285
  export declare function drainOrphanRetirements(ctx: RetireDeps, env: NodeJS.ProcessEnv, now: number): Promise<string[]>;
286
+ /** One answered registered question, with everything the agent needs to read it. */
287
+ interface AnsweredPending {
288
+ pending: PendingQuestion;
289
+ reply: ReplyView;
290
+ replies: ReplyView[];
291
+ }
237
292
  /**
238
293
  * Records presence and retires anything still asking on companion devices. This is the
239
294
  * "answered in the terminal" case from the original design: we cannot tell
@@ -246,7 +301,7 @@ export declare function handleUserPromptSubmit(ctx: HookContext, envelope: HookE
246
301
  * `notifai ask`. Guessing from the last assistant message was the alternative
247
302
  * and it is not worth it: a false positive here hijacks the terminal.
248
303
  */
249
- export declare function handleStop(ctx: HookContext, envelope: HookEnvelope): Promise<HookOutcome>;
304
+ export declare function handleStop(ctx: HookContext, envelope: HookEnvelope, processDeadlineAt?: number): Promise<HookOutcome>;
250
305
  /**
251
306
  * Claude Code gives SessionEnd hooks a 1.5-second shared budget and Codex 1
252
307
  * second, so this cannot make a network call. It drops the local marker — but
@@ -279,3 +334,4 @@ export declare function registerQuestion(sessionId: string, env: NodeJS.ProcessE
279
334
  /** Parses hook JSON from stdin, tolerating an empty or malformed body. */
280
335
  export declare function parseHookInput(raw: string): HookEnvelope;
281
336
  export declare function resolveHookConfig(cwd: string, env: NodeJS.ProcessEnv, sessionId: string | undefined): CliConfig;
337
+ export {};