@raidiant/notifai 10.1.1 → 10.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/commands-core.d.ts +1 -1
  3. package/dist/commands-init.js +9 -6
  4. package/dist/commands-init.js.map +1 -1
  5. package/dist/commands-send-support.js +3 -0
  6. package/dist/commands-send-support.js.map +1 -1
  7. package/dist/commands-skill.js +72 -54
  8. package/dist/commands-skill.js.map +1 -1
  9. package/dist/invocation-context.d.ts +1 -0
  10. package/dist/invocation-context.js +9 -1
  11. package/dist/invocation-context.js.map +1 -1
  12. package/dist/native-skills.d.ts +13 -3
  13. package/dist/native-skills.js +51 -12
  14. package/dist/native-skills.js.map +1 -1
  15. package/dist/node-floor.d.ts +2 -1
  16. package/dist/node-floor.js +9 -5
  17. package/dist/node-floor.js.map +1 -1
  18. package/dist/orca-session-title.d.ts +5 -4
  19. package/dist/orca-session-title.js +32 -19
  20. package/dist/orca-session-title.js.map +1 -1
  21. package/dist/platform.d.ts +11 -17
  22. package/dist/platform.js +20 -23
  23. package/dist/platform.js.map +1 -1
  24. package/dist/release.d.ts +5 -5
  25. package/dist/release.js +5 -5
  26. package/dist/setup-destinations.js +1 -1
  27. package/dist/setup-destinations.js.map +1 -1
  28. package/dist/skill-integrity.d.ts +49 -0
  29. package/dist/skill-integrity.js +154 -0
  30. package/dist/skill-integrity.js.map +1 -0
  31. package/dist/skill-source/manifest.json +16 -0
  32. package/dist/skill-source/notifai/SKILL.md +390 -0
  33. package/dist/skill-source/notifai/references/harness-setup.md +220 -0
  34. package/package.json +11 -14
  35. package/src/atomic-file.ts +0 -198
  36. package/src/claude-wake.test.ts +0 -321
  37. package/src/claude-wake.ts +0 -499
  38. package/src/cli-bin.test.ts +0 -176
  39. package/src/cli-bin.ts +0 -233
  40. package/src/cli-release.test.ts +0 -46
  41. package/src/cli-release.ts +0 -78
  42. package/src/cli-update-contract.ts +0 -9
  43. package/src/cli-update.test.ts +0 -125
  44. package/src/client.test.ts +0 -390
  45. package/src/client.ts +0 -352
  46. package/src/codex-wake.test.ts +0 -254
  47. package/src/codex-wake.ts +0 -301
  48. package/src/command-session.ts +0 -38
  49. package/src/commands-agent-sessions.ts +0 -72
  50. package/src/commands-auth.ts +0 -238
  51. package/src/commands-config.ts +0 -336
  52. package/src/commands-core.ts +0 -312
  53. package/src/commands-devices.ts +0 -180
  54. package/src/commands-doctor.ts +0 -1501
  55. package/src/commands-guidance.ts +0 -181
  56. package/src/commands-harness-context.ts +0 -207
  57. package/src/commands-hook-shape.ts +0 -69
  58. package/src/commands-hooks.ts +0 -2318
  59. package/src/commands-init.ts +0 -1055
  60. package/src/commands-io.ts +0 -108
  61. package/src/commands-logs.ts +0 -243
  62. package/src/commands-project.ts +0 -47
  63. package/src/commands-send-support.test.ts +0 -156
  64. package/src/commands-send-support.ts +0 -311
  65. package/src/commands-send.ts +0 -875
  66. package/src/commands-setup-proof.test.ts +0 -72
  67. package/src/commands-setup-proof.ts +0 -124
  68. package/src/commands-skill.test.ts +0 -44
  69. package/src/commands-skill.ts +0 -204
  70. package/src/commands-sounds.test.ts +0 -106
  71. package/src/commands-sounds.ts +0 -49
  72. package/src/commands-update.ts +0 -180
  73. package/src/commands.test.ts +0 -8297
  74. package/src/commands.ts +0 -77
  75. package/src/config-schema.test.ts +0 -153
  76. package/src/config-schema.ts +0 -349
  77. package/src/config.test.ts +0 -581
  78. package/src/config.ts +0 -475
  79. package/src/credentials.test.ts +0 -403
  80. package/src/credentials.ts +0 -403
  81. package/src/file-lock-process-worker.test.ts +0 -64
  82. package/src/file-lock.test.ts +0 -320
  83. package/src/file-lock.ts +0 -393
  84. package/src/fixtures/hermes-0.20.6-classic-cli-local.json +0 -50
  85. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +0 -57
  86. package/src/generated-session-label.ts +0 -155
  87. package/src/guidance-content.ts +0 -278
  88. package/src/guidance-render.ts +0 -56
  89. package/src/guidance.test.ts +0 -327
  90. package/src/guidance.ts +0 -233
  91. package/src/harness-integration.test.ts +0 -294
  92. package/src/harnesses.test.ts +0 -149
  93. package/src/harnesses.ts +0 -164
  94. package/src/hook-adapter.test.ts +0 -496
  95. package/src/hook-adapter.ts +0 -424
  96. package/src/hook-input.test.ts +0 -39
  97. package/src/hook-input.ts +0 -72
  98. package/src/hooks.test.ts +0 -5824
  99. package/src/hooks.ts +0 -4011
  100. package/src/install-hooks.test.ts +0 -1614
  101. package/src/install-hooks.ts +0 -1501
  102. package/src/interactive.test.ts +0 -387
  103. package/src/interactive.ts +0 -858
  104. package/src/invocation-context.test.ts +0 -352
  105. package/src/invocation-context.ts +0 -197
  106. package/src/logging-process-worker.test.ts +0 -45
  107. package/src/logging.test.ts +0 -544
  108. package/src/logging.ts +0 -698
  109. package/src/main-run.ts +0 -45
  110. package/src/main.ts +0 -12
  111. package/src/native-skills.test.ts +0 -115
  112. package/src/native-skills.ts +0 -162
  113. package/src/node-floor.test.ts +0 -27
  114. package/src/node-floor.ts +0 -37
  115. package/src/onboarding-docs.test.ts +0 -25
  116. package/src/openclaw-plugin.ts +0 -409
  117. package/src/opencode-plugin.ts +0 -298
  118. package/src/orca-session-title.test.ts +0 -54
  119. package/src/orca-session-title.ts +0 -103
  120. package/src/pack-feedback-slice.test.ts +0 -212
  121. package/src/pack-feedback-slice.ts +0 -187
  122. package/src/platform.test.ts +0 -152
  123. package/src/platform.ts +0 -173
  124. package/src/program.test.ts +0 -317
  125. package/src/program.ts +0 -842
  126. package/src/project-enablement.test.ts +0 -55
  127. package/src/project-enablement.ts +0 -99
  128. package/src/question-caps.test.ts +0 -49
  129. package/src/question-settlement-process.ts +0 -47
  130. package/src/question-timing.ts +0 -56
  131. package/src/readiness.test.ts +0 -25
  132. package/src/readiness.ts +0 -212
  133. package/src/release.ts +0 -51
  134. package/src/send-attempts.test.ts +0 -143
  135. package/src/send-attempts.ts +0 -188
  136. package/src/send.test.ts +0 -28
  137. package/src/send.ts +0 -436
  138. package/src/session-activation.ts +0 -61
  139. package/src/session-end-log-lock-worker.test.ts +0 -35
  140. package/src/session-labels.test.ts +0 -490
  141. package/src/session-labels.ts +0 -474
  142. package/src/setup-destinations.test.ts +0 -41
  143. package/src/setup-destinations.ts +0 -50
  144. package/src/skill.test.ts +0 -426
  145. package/src/sound-ref.test.ts +0 -31
  146. package/src/sound-ref.ts +0 -34
  147. package/src/trust-policy.test.ts +0 -701
  148. package/src/ui/banner.ts +0 -95
  149. package/src/ui/config-view.test.ts +0 -90
  150. package/src/ui/config-view.ts +0 -138
  151. package/src/ui/help.test.ts +0 -16
  152. package/src/ui/help.ts +0 -128
  153. package/src/ui/theme.test.ts +0 -92
  154. package/src/ui/theme.ts +0 -265
  155. package/src/url-policy.ts +0 -399
  156. 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
- })