@raidiant/notifai 10.1.1 → 10.1.3

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 (168) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/commands-core.d.ts +4 -1
  3. package/dist/commands-core.js.map +1 -1
  4. package/dist/commands-doctor.js +2 -7
  5. package/dist/commands-doctor.js.map +1 -1
  6. package/dist/commands-hooks.d.ts +12 -0
  7. package/dist/commands-hooks.js +15 -1
  8. package/dist/commands-hooks.js.map +1 -1
  9. package/dist/commands-init.js +9 -6
  10. package/dist/commands-init.js.map +1 -1
  11. package/dist/commands-send-support.js +10 -11
  12. package/dist/commands-send-support.js.map +1 -1
  13. package/dist/commands-skill.js +72 -54
  14. package/dist/commands-skill.js.map +1 -1
  15. package/dist/harness-session-title.d.ts +22 -0
  16. package/dist/harness-session-title.js +74 -0
  17. package/dist/harness-session-title.js.map +1 -0
  18. package/dist/invocation-context.d.ts +1 -0
  19. package/dist/invocation-context.js +15 -1
  20. package/dist/invocation-context.js.map +1 -1
  21. package/dist/native-skills.d.ts +13 -3
  22. package/dist/native-skills.js +51 -12
  23. package/dist/native-skills.js.map +1 -1
  24. package/dist/node-floor.d.ts +2 -1
  25. package/dist/node-floor.js +9 -5
  26. package/dist/node-floor.js.map +1 -1
  27. package/dist/orca-session-title.d.ts +5 -4
  28. package/dist/orca-session-title.js +32 -19
  29. package/dist/orca-session-title.js.map +1 -1
  30. package/dist/platform.d.ts +11 -17
  31. package/dist/platform.js +20 -23
  32. package/dist/platform.js.map +1 -1
  33. package/dist/release.d.ts +5 -5
  34. package/dist/release.js +5 -5
  35. package/dist/session-labels.d.ts +2 -0
  36. package/dist/session-labels.js +20 -9
  37. package/dist/session-labels.js.map +1 -1
  38. package/dist/setup-destinations.js +1 -1
  39. package/dist/setup-destinations.js.map +1 -1
  40. package/dist/skill-integrity.d.ts +49 -0
  41. package/dist/skill-integrity.js +154 -0
  42. package/dist/skill-integrity.js.map +1 -0
  43. package/dist/skill-source/manifest.json +16 -0
  44. package/dist/skill-source/notifai/SKILL.md +390 -0
  45. package/dist/skill-source/notifai/references/harness-setup.md +220 -0
  46. package/package.json +11 -14
  47. package/src/atomic-file.ts +0 -198
  48. package/src/claude-wake.test.ts +0 -321
  49. package/src/claude-wake.ts +0 -499
  50. package/src/cli-bin.test.ts +0 -176
  51. package/src/cli-bin.ts +0 -233
  52. package/src/cli-release.test.ts +0 -46
  53. package/src/cli-release.ts +0 -78
  54. package/src/cli-update-contract.ts +0 -9
  55. package/src/cli-update.test.ts +0 -125
  56. package/src/client.test.ts +0 -390
  57. package/src/client.ts +0 -352
  58. package/src/codex-wake.test.ts +0 -254
  59. package/src/codex-wake.ts +0 -301
  60. package/src/command-session.ts +0 -38
  61. package/src/commands-agent-sessions.ts +0 -72
  62. package/src/commands-auth.ts +0 -238
  63. package/src/commands-config.ts +0 -336
  64. package/src/commands-core.ts +0 -312
  65. package/src/commands-devices.ts +0 -180
  66. package/src/commands-doctor.ts +0 -1501
  67. package/src/commands-guidance.ts +0 -181
  68. package/src/commands-harness-context.ts +0 -207
  69. package/src/commands-hook-shape.ts +0 -69
  70. package/src/commands-hooks.ts +0 -2318
  71. package/src/commands-init.ts +0 -1055
  72. package/src/commands-io.ts +0 -108
  73. package/src/commands-logs.ts +0 -243
  74. package/src/commands-project.ts +0 -47
  75. package/src/commands-send-support.test.ts +0 -156
  76. package/src/commands-send-support.ts +0 -311
  77. package/src/commands-send.ts +0 -875
  78. package/src/commands-setup-proof.test.ts +0 -72
  79. package/src/commands-setup-proof.ts +0 -124
  80. package/src/commands-skill.test.ts +0 -44
  81. package/src/commands-skill.ts +0 -204
  82. package/src/commands-sounds.test.ts +0 -106
  83. package/src/commands-sounds.ts +0 -49
  84. package/src/commands-update.ts +0 -180
  85. package/src/commands.test.ts +0 -8297
  86. package/src/commands.ts +0 -77
  87. package/src/config-schema.test.ts +0 -153
  88. package/src/config-schema.ts +0 -349
  89. package/src/config.test.ts +0 -581
  90. package/src/config.ts +0 -475
  91. package/src/credentials.test.ts +0 -403
  92. package/src/credentials.ts +0 -403
  93. package/src/file-lock-process-worker.test.ts +0 -64
  94. package/src/file-lock.test.ts +0 -320
  95. package/src/file-lock.ts +0 -393
  96. package/src/fixtures/hermes-0.20.6-classic-cli-local.json +0 -50
  97. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +0 -57
  98. package/src/generated-session-label.ts +0 -155
  99. package/src/guidance-content.ts +0 -278
  100. package/src/guidance-render.ts +0 -56
  101. package/src/guidance.test.ts +0 -327
  102. package/src/guidance.ts +0 -233
  103. package/src/harness-integration.test.ts +0 -294
  104. package/src/harnesses.test.ts +0 -149
  105. package/src/harnesses.ts +0 -164
  106. package/src/hook-adapter.test.ts +0 -496
  107. package/src/hook-adapter.ts +0 -424
  108. package/src/hook-input.test.ts +0 -39
  109. package/src/hook-input.ts +0 -72
  110. package/src/hooks.test.ts +0 -5824
  111. package/src/hooks.ts +0 -4011
  112. package/src/install-hooks.test.ts +0 -1614
  113. package/src/install-hooks.ts +0 -1501
  114. package/src/interactive.test.ts +0 -387
  115. package/src/interactive.ts +0 -858
  116. package/src/invocation-context.test.ts +0 -352
  117. package/src/invocation-context.ts +0 -197
  118. package/src/logging-process-worker.test.ts +0 -45
  119. package/src/logging.test.ts +0 -544
  120. package/src/logging.ts +0 -698
  121. package/src/main-run.ts +0 -45
  122. package/src/main.ts +0 -12
  123. package/src/native-skills.test.ts +0 -115
  124. package/src/native-skills.ts +0 -162
  125. package/src/node-floor.test.ts +0 -27
  126. package/src/node-floor.ts +0 -37
  127. package/src/onboarding-docs.test.ts +0 -25
  128. package/src/openclaw-plugin.ts +0 -409
  129. package/src/opencode-plugin.ts +0 -298
  130. package/src/orca-session-title.test.ts +0 -54
  131. package/src/orca-session-title.ts +0 -103
  132. package/src/pack-feedback-slice.test.ts +0 -212
  133. package/src/pack-feedback-slice.ts +0 -187
  134. package/src/platform.test.ts +0 -152
  135. package/src/platform.ts +0 -173
  136. package/src/program.test.ts +0 -317
  137. package/src/program.ts +0 -842
  138. package/src/project-enablement.test.ts +0 -55
  139. package/src/project-enablement.ts +0 -99
  140. package/src/question-caps.test.ts +0 -49
  141. package/src/question-settlement-process.ts +0 -47
  142. package/src/question-timing.ts +0 -56
  143. package/src/readiness.test.ts +0 -25
  144. package/src/readiness.ts +0 -212
  145. package/src/release.ts +0 -51
  146. package/src/send-attempts.test.ts +0 -143
  147. package/src/send-attempts.ts +0 -188
  148. package/src/send.test.ts +0 -28
  149. package/src/send.ts +0 -436
  150. package/src/session-activation.ts +0 -61
  151. package/src/session-end-log-lock-worker.test.ts +0 -35
  152. package/src/session-labels.test.ts +0 -490
  153. package/src/session-labels.ts +0 -474
  154. package/src/setup-destinations.test.ts +0 -41
  155. package/src/setup-destinations.ts +0 -50
  156. package/src/skill.test.ts +0 -426
  157. package/src/sound-ref.test.ts +0 -31
  158. package/src/sound-ref.ts +0 -34
  159. package/src/trust-policy.test.ts +0 -701
  160. package/src/ui/banner.ts +0 -95
  161. package/src/ui/config-view.test.ts +0 -90
  162. package/src/ui/config-view.ts +0 -138
  163. package/src/ui/help.test.ts +0 -16
  164. package/src/ui/help.ts +0 -128
  165. package/src/ui/theme.test.ts +0 -92
  166. package/src/ui/theme.ts +0 -265
  167. package/src/url-policy.ts +0 -399
  168. package/tsconfig.json +0 -10
@@ -1,390 +0,0 @@
1
- import { createServer, type Server } from 'node:http'
2
- import type { AddressInfo } from 'node:net'
3
- import { afterEach, describe, expect, it } from 'vitest'
4
- import { CAPABILITIES_HEADER, CLI_VERSION_HEADER } from '@raidiant/notifai-protocol'
5
- import { ApiCallError, NetworkError, createClient } from './client.js'
6
-
7
- /**
8
- * These run against a real socket rather than a stubbed fetch, because the
9
- * failure being fixed is a property of the transport: a server that accepts the
10
- * connection and then says nothing. A mock that resolves or rejects cannot
11
- * reproduce it.
12
- */
13
- let server: Server | undefined
14
-
15
- afterEach(async () => {
16
- if (server) {
17
- const closing = server
18
- server = undefined
19
- await new Promise<void>((resolve) => closing.close(() => resolve()))
20
- }
21
- })
22
-
23
- /** Starts a server with the given behaviour and returns its base URL. */
24
- async function serving(handler: Parameters<typeof createServer>[1]): Promise<string> {
25
- server = createServer(handler)
26
- await new Promise<void>((resolve) => server?.listen(0, '127.0.0.1', resolve))
27
- const { port } = server.address() as AddressInfo
28
- return `http://127.0.0.1:${port}`
29
- }
30
-
31
- describe('a server that never answers', () => {
32
- it('gives up instead of hanging until the harness kills the hook', async () => {
33
- // Accepts the request, holds the socket open, writes nothing — ever.
34
- const held: unknown[] = []
35
- const baseUrl = await serving((_request, response) => {
36
- held.push(response)
37
- })
38
- const client = createClient(baseUrl, null, { timeoutMs: 300 })
39
-
40
- const started = Date.now()
41
- await expect(client.listDevices()).rejects.toBeInstanceOf(NetworkError)
42
- // The point is that it returns at all; the bound is loose so a busy CI box
43
- // does not make this flaky.
44
- expect(Date.now() - started).toBeLessThan(5_000)
45
- expect(held).toHaveLength(1)
46
- })
47
-
48
- it('says the server went quiet rather than reporting a bare abort', async () => {
49
- const baseUrl = await serving(() => {})
50
- const client = createClient(baseUrl, null, { timeoutMs: 300 })
51
-
52
- await expect(client.listDevices()).rejects.toThrow(/did not respond within/)
53
- })
54
-
55
- it('clamps a late request to the caller whole-operation deadline', async () => {
56
- const baseUrl = await serving(() => {})
57
- const started = Date.now()
58
- const client = createClient(baseUrl, null, {
59
- timeoutMs: 5_000,
60
- deadlineAt: started + 200,
61
- })
62
-
63
- await expect(client.listDevices()).rejects.toBeInstanceOf(NetworkError)
64
- expect(Date.now() - started).toBeLessThan(2_000)
65
- })
66
-
67
- it('allows for the long poll it asked the server to hold', async () => {
68
- // The deadline is per request, so a 25s long poll must not be cut off by
69
- // the ordinary budget — otherwise waiting for a reply could never work.
70
- let seen: string | undefined
71
- const baseUrl = await serving((request, response) => {
72
- seen = request.url
73
- response.setHeader('content-type', 'application/json')
74
- response.end(JSON.stringify({ request_id: 'req_1', reply_expires_at: null, replies: [] }))
75
- })
76
- const client = createClient(baseUrl, null, { timeoutMs: 300 })
77
-
78
- const result = await client.replies('req_1', { waitSeconds: 25, afterSeq: 0 })
79
-
80
- expect(seen).toContain('wait_seconds=25')
81
- expect(result.replies).toEqual([])
82
- })
83
-
84
- it('encodes pairing and evidence path segments', async () => {
85
- const seen: string[] = []
86
- const baseUrl = await serving((request, response) => {
87
- seen.push(`${request.method} ${request.url}`)
88
- response.setHeader('content-type', 'application/json')
89
- if (request.url?.includes('/poll')) {
90
- response.end(JSON.stringify({ status: 'pending' }))
91
- return
92
- }
93
- response.end(
94
- JSON.stringify({
95
- request_id: 'req/encoded',
96
- overall: 'accepted',
97
- deliveries: [],
98
- warnings: [],
99
- }),
100
- )
101
- })
102
- const client = createClient(baseUrl, 'Bearer test')
103
-
104
- await client.pollPairing('pair/id', 'verifier')
105
- await client.evidence('req/encoded')
106
-
107
- expect(seen).toEqual([
108
- 'POST /api/v1/pairings/pair%2Fid/poll',
109
- 'GET /api/v1/notifications/req%2Fencoded',
110
- ])
111
- })
112
-
113
- it('lists Account custom sounds from the catalog endpoint', async () => {
114
- let seen: string | undefined
115
- const baseUrl = await serving((request, response) => {
116
- seen = `${request.method} ${request.url}`
117
- response.setHeader('content-type', 'application/json')
118
- response.end(JSON.stringify({ sounds: [] }))
119
- })
120
- const result = await createClient(baseUrl, 'Bearer test').listSounds()
121
- expect(seen).toBe('GET /api/v1/sounds')
122
- expect(result).toEqual({ sounds: [] })
123
- })
124
-
125
- it('refuses redirects on bearer-authenticated calls', async () => {
126
- let followed = false
127
- const baseUrl = await serving((request, response) => {
128
- if (request.url === '/api/v1/devices') {
129
- response.statusCode = 302
130
- response.setHeader('location', '/stolen')
131
- response.end()
132
- return
133
- }
134
- followed = true
135
- response.end('stolen')
136
- })
137
-
138
- await expect(createClient(baseUrl, 'Bearer secret').listDevices()).rejects.toBeInstanceOf(
139
- NetworkError,
140
- )
141
- expect(followed).toBe(false)
142
- })
143
-
144
- it('still follows redirects when the call has no bearer', async () => {
145
- const baseUrl = await serving((request, response) => {
146
- if (request.url === '/healthz') {
147
- response.statusCode = 302
148
- response.setHeader('location', '/healthz-ok')
149
- response.end()
150
- return
151
- }
152
- response.setHeader('content-type', 'application/json')
153
- response.end('{}')
154
- })
155
-
156
- await expect(createClient(baseUrl, null).health()).resolves.toBe(true)
157
- })
158
-
159
- it('puts and fetches Agent Acknowledgements on the encoded request path', async () => {
160
- const seen: { method?: string; url?: string; body?: unknown }[] = []
161
- const baseUrl = await serving((request, response) => {
162
- let raw = ''
163
- request.on('data', (chunk) => {
164
- raw += String(chunk)
165
- })
166
- request.on('end', () => {
167
- seen.push({
168
- method: request.method,
169
- url: request.url,
170
- ...(raw === '' ? {} : { body: JSON.parse(raw) as unknown }),
171
- })
172
- response.setHeader('content-type', 'application/json')
173
- if (request.method === 'PUT') {
174
- response.end(
175
- JSON.stringify({
176
- status: 'recorded',
177
- agent_acknowledgement: {
178
- text: 'I will deploy.',
179
- created_at: '2026-08-13T12:01:00.000Z',
180
- },
181
- }),
182
- )
183
- } else {
184
- response.end(
185
- JSON.stringify({
186
- request_id: 'req/encoded',
187
- agent_acknowledgement_required: true,
188
- agent_acknowledgement: null,
189
- }),
190
- )
191
- }
192
- })
193
- })
194
- const client = createClient(baseUrl, 'Bearer test')
195
-
196
- await client.putAgentAcknowledgement('req/encoded', { text: 'I will deploy.' })
197
- await client.agentAcknowledgement('req/encoded', { waitSeconds: 25 })
198
-
199
- expect(seen).toEqual([
200
- {
201
- method: 'PUT',
202
- url: '/api/v1/notifications/req%2Fencoded/agent-acknowledgement',
203
- body: { text: 'I will deploy.' },
204
- },
205
- {
206
- method: 'GET',
207
- url: '/api/v1/notifications/req%2Fencoded/agent-acknowledgement?wait_seconds=25',
208
- },
209
- ])
210
- })
211
-
212
- it('finalizes provider metadata before treating a storage upload as usable', async () => {
213
- const seen: { method?: string; url?: string; authorization?: string; bytes: number }[] = []
214
- const baseUrl = await serving((request, response) => {
215
- const chunks: Buffer[] = []
216
- request.on('data', (chunk) => chunks.push(Buffer.from(chunk)))
217
- request.on('end', () => {
218
- seen.push({
219
- method: request.method,
220
- url: request.url,
221
- ...(request.headers.authorization
222
- ? { authorization: request.headers.authorization }
223
- : {}),
224
- bytes: Buffer.concat(chunks).byteLength,
225
- })
226
- if (request.method === 'POST') {
227
- response.setHeader('content-type', 'application/json')
228
- response.end(JSON.stringify({ media_id: 'med_1', size_bytes: 4, status: 'ready' }))
229
- } else {
230
- response.statusCode = 200
231
- response.end()
232
- }
233
- })
234
- })
235
- const client = createClient(baseUrl, 'Bearer machine')
236
-
237
- await client.uploadMedia(
238
- {
239
- media_id: 'med_1',
240
- upload_url: `${baseUrl}/signed-upload`,
241
- upload_headers: { 'content-type': 'image/png' },
242
- expires_at: '2026-08-14T12:00:00.000Z',
243
- },
244
- new Uint8Array([1, 2, 3, 4]),
245
- )
246
-
247
- expect(seen).toEqual([
248
- { method: 'PUT', url: '/signed-upload', bytes: 4 },
249
- {
250
- method: 'POST',
251
- url: '/api/v1/media/med_1/finalize',
252
- authorization: 'Bearer machine',
253
- bytes: 0,
254
- },
255
- ])
256
- })
257
-
258
- it('refuses a signed upload redirect without replaying its headers or finalizing', async () => {
259
- let followed = false
260
- let finalized = false
261
- const baseUrl = await serving((request, response) => {
262
- if (request.url === '/signed-upload') {
263
- response.statusCode = 302
264
- response.setHeader('location', `${baseUrl}/stolen`)
265
- response.end()
266
- return
267
- }
268
- if (request.url === '/stolen') followed = true
269
- if (request.url === '/api/v1/media/med_1/finalize') finalized = true
270
- response.statusCode = 200
271
- response.end()
272
- })
273
- const client = createClient(baseUrl, 'Bearer machine')
274
-
275
- await expect(
276
- client.uploadMedia(
277
- {
278
- media_id: 'med_1',
279
- upload_url: `${baseUrl}/signed-upload`,
280
- upload_headers: { authorization: 'Signed secret' },
281
- expires_at: '2026-08-14T12:00:00.000Z',
282
- },
283
- new Uint8Array([1, 2, 3, 4]),
284
- ),
285
- ).rejects.toThrow(/redirect is refused/i)
286
- expect(followed).toBe(false)
287
- expect(finalized).toBe(false)
288
- })
289
-
290
- it('treats a truncated body as a transport failure, not a crash', async () => {
291
- // A body that stops mid-JSON used to escape as a raw parse error outside
292
- // the retry path, so nothing backed off and retried it.
293
- const baseUrl = await serving((_request, response) => {
294
- response.setHeader('content-type', 'application/json')
295
- response.write('{"devices": [')
296
- response.destroy()
297
- })
298
- const client = createClient(baseUrl, null, { timeoutMs: 500 })
299
-
300
- await expect(client.listDevices()).rejects.toBeInstanceOf(NetworkError)
301
- })
302
- })
303
-
304
- describe('compatibility metadata transport', () => {
305
- it('advertises named CLI capabilities only on authenticated traffic', async () => {
306
- const seen: Array<{
307
- url: string | undefined
308
- cliVersion: string | string[] | undefined
309
- capabilities: string | string[] | undefined
310
- }> = []
311
- const baseUrl = await serving((request, response) => {
312
- seen.push({
313
- url: request.url,
314
- cliVersion: request.headers[CLI_VERSION_HEADER],
315
- capabilities: request.headers[CAPABILITIES_HEADER],
316
- })
317
- response.setHeader('content-type', 'application/json')
318
- response.end(
319
- JSON.stringify({
320
- schema_version: 1,
321
- platform: 'macos',
322
- payload_limit_bytes: 4096,
323
- sounds: [],
324
- interruption_levels: [],
325
- fields: [],
326
- }),
327
- )
328
- })
329
- const options = {
330
- cliVersion: '5.0.0',
331
- capabilities: ['agent_acknowledgement'] as const,
332
- }
333
-
334
- await createClient(baseUrl, 'Bearer machine', options).capabilities('macos', '0.1.0', '42')
335
- await createClient(baseUrl, null, options).capabilities('macos')
336
-
337
- expect(seen).toEqual([
338
- {
339
- url: '/api/v1/capabilities/macos?app_version=0.1.0&app_build=42',
340
- cliVersion: '5.0.0',
341
- capabilities: 'agent_acknowledgement',
342
- },
343
- {
344
- url: '/api/v1/capabilities/macos',
345
- cliVersion: undefined,
346
- capabilities: undefined,
347
- },
348
- ])
349
- })
350
-
351
- it('keeps recovery typed when a feature is unavailable', async () => {
352
- const baseUrl = await serving((_request, response) => {
353
- response.statusCode = 422
354
- response.setHeader('content-type', 'application/json')
355
- response.end(
356
- JSON.stringify({
357
- error: {
358
- code: 'feature_unavailable',
359
- message: "The selected device can't answer questions.",
360
- next_action: 'run an untrusted server command',
361
- recovery_action: 'update_companion',
362
- details: {
363
- affected_operation: 'answer_questions',
364
- missing_capabilities: ['answer'],
365
- },
366
- },
367
- }),
368
- )
369
- })
370
-
371
- let caught: unknown
372
- try {
373
- await createClient(baseUrl, 'Bearer machine').compatibility()
374
- } catch (error) {
375
- caught = error
376
- }
377
-
378
- expect(caught).toBeInstanceOf(ApiCallError)
379
- expect(caught).toMatchObject({
380
- status: 422,
381
- code: 'feature_unavailable',
382
- recoveryAction: 'update_companion',
383
- nextAction: 'run an untrusted server command',
384
- details: {
385
- affected_operation: 'answer_questions',
386
- missing_capabilities: ['answer'],
387
- },
388
- })
389
- })
390
- })