@raidiant/notifai 10.1.0 → 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 (204) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +3 -0
  3. package/dist/cli-bin.d.ts +21 -1
  4. package/dist/cli-bin.js +141 -10
  5. package/dist/cli-bin.js.map +1 -1
  6. package/dist/cli-update-contract.d.ts +7 -0
  7. package/dist/cli-update-contract.js +10 -0
  8. package/dist/cli-update-contract.js.map +1 -0
  9. package/dist/client.d.ts +6 -1
  10. package/dist/client.js +1 -0
  11. package/dist/client.js.map +1 -1
  12. package/dist/codex-wake.d.ts +2 -0
  13. package/dist/codex-wake.js +2 -1
  14. package/dist/codex-wake.js.map +1 -1
  15. package/dist/commands-auth.d.ts +15 -1
  16. package/dist/commands-auth.js +29 -11
  17. package/dist/commands-auth.js.map +1 -1
  18. package/dist/commands-core.d.ts +9 -2
  19. package/dist/commands-core.js +11 -4
  20. package/dist/commands-core.js.map +1 -1
  21. package/dist/commands-devices.d.ts +10 -1
  22. package/dist/commands-devices.js +17 -13
  23. package/dist/commands-devices.js.map +1 -1
  24. package/dist/commands-doctor.js +93 -49
  25. package/dist/commands-doctor.js.map +1 -1
  26. package/dist/commands-hooks.js +47 -10
  27. package/dist/commands-hooks.js.map +1 -1
  28. package/dist/commands-init.d.ts +16 -0
  29. package/dist/commands-init.js +254 -93
  30. package/dist/commands-init.js.map +1 -1
  31. package/dist/commands-send-support.js +3 -0
  32. package/dist/commands-send-support.js.map +1 -1
  33. package/dist/commands-send.d.ts +1 -1
  34. package/dist/commands-send.js +121 -35
  35. package/dist/commands-send.js.map +1 -1
  36. package/dist/commands-skill.js +72 -54
  37. package/dist/commands-skill.js.map +1 -1
  38. package/dist/commands-update.d.ts +10 -0
  39. package/dist/commands-update.js +141 -0
  40. package/dist/commands-update.js.map +1 -0
  41. package/dist/commands.d.ts +2 -1
  42. package/dist/commands.js +2 -1
  43. package/dist/commands.js.map +1 -1
  44. package/dist/hook-adapter.d.ts +2 -0
  45. package/dist/hook-adapter.js +12 -1
  46. package/dist/hook-adapter.js.map +1 -1
  47. package/dist/hooks.d.ts +43 -2
  48. package/dist/hooks.js +163 -21
  49. package/dist/hooks.js.map +1 -1
  50. package/dist/invocation-context.d.ts +1 -0
  51. package/dist/invocation-context.js +9 -1
  52. package/dist/invocation-context.js.map +1 -1
  53. package/dist/main-run.d.ts +1 -0
  54. package/dist/main-run.js +42 -0
  55. package/dist/main-run.js.map +1 -0
  56. package/dist/main.js +10 -31
  57. package/dist/main.js.map +1 -1
  58. package/dist/native-skills.d.ts +13 -3
  59. package/dist/native-skills.js +51 -12
  60. package/dist/native-skills.js.map +1 -1
  61. package/dist/node-floor.d.ts +19 -0
  62. package/dist/node-floor.js +39 -0
  63. package/dist/node-floor.js.map +1 -0
  64. package/dist/orca-session-title.d.ts +5 -4
  65. package/dist/orca-session-title.js +32 -19
  66. package/dist/orca-session-title.js.map +1 -1
  67. package/dist/platform.d.ts +11 -17
  68. package/dist/platform.js +20 -23
  69. package/dist/platform.js.map +1 -1
  70. package/dist/program.d.ts +2 -1
  71. package/dist/program.js +22 -5
  72. package/dist/program.js.map +1 -1
  73. package/dist/question-settlement-process.d.ts +15 -0
  74. package/dist/question-settlement-process.js +35 -0
  75. package/dist/question-settlement-process.js.map +1 -0
  76. package/dist/readiness.d.ts +25 -1
  77. package/dist/readiness.js +32 -2
  78. package/dist/readiness.js.map +1 -1
  79. package/dist/release.d.ts +5 -5
  80. package/dist/release.js +5 -5
  81. package/dist/setup-destinations.d.ts +23 -0
  82. package/dist/setup-destinations.js +44 -0
  83. package/dist/setup-destinations.js.map +1 -0
  84. package/dist/skill-integrity.d.ts +49 -0
  85. package/dist/skill-integrity.js +154 -0
  86. package/dist/skill-integrity.js.map +1 -0
  87. package/dist/skill-source/manifest.json +16 -0
  88. package/dist/skill-source/notifai/SKILL.md +390 -0
  89. package/dist/skill-source/notifai/references/harness-setup.md +220 -0
  90. package/package.json +11 -14
  91. package/src/atomic-file.ts +0 -198
  92. package/src/claude-wake.test.ts +0 -321
  93. package/src/claude-wake.ts +0 -499
  94. package/src/cli-bin.test.ts +0 -38
  95. package/src/cli-bin.ts +0 -67
  96. package/src/cli-release.test.ts +0 -46
  97. package/src/cli-release.ts +0 -78
  98. package/src/client.test.ts +0 -390
  99. package/src/client.ts +0 -345
  100. package/src/codex-wake.test.ts +0 -254
  101. package/src/codex-wake.ts +0 -297
  102. package/src/command-session.ts +0 -38
  103. package/src/commands-agent-sessions.ts +0 -72
  104. package/src/commands-auth.ts +0 -205
  105. package/src/commands-config.ts +0 -336
  106. package/src/commands-core.ts +0 -298
  107. package/src/commands-devices.test.ts +0 -15
  108. package/src/commands-devices.ts +0 -173
  109. package/src/commands-doctor.ts +0 -1440
  110. package/src/commands-guidance.ts +0 -181
  111. package/src/commands-harness-context.ts +0 -207
  112. package/src/commands-hook-shape.ts +0 -69
  113. package/src/commands-hooks.ts +0 -2268
  114. package/src/commands-init.ts +0 -878
  115. package/src/commands-io.ts +0 -108
  116. package/src/commands-logs.ts +0 -243
  117. package/src/commands-project.ts +0 -47
  118. package/src/commands-send-support.test.ts +0 -156
  119. package/src/commands-send-support.ts +0 -311
  120. package/src/commands-send.ts +0 -769
  121. package/src/commands-setup-proof.test.ts +0 -72
  122. package/src/commands-setup-proof.ts +0 -124
  123. package/src/commands-skill.test.ts +0 -44
  124. package/src/commands-skill.ts +0 -204
  125. package/src/commands-sounds.test.ts +0 -106
  126. package/src/commands-sounds.ts +0 -49
  127. package/src/commands.test.ts +0 -7828
  128. package/src/commands.ts +0 -75
  129. package/src/config-schema.test.ts +0 -153
  130. package/src/config-schema.ts +0 -349
  131. package/src/config.test.ts +0 -581
  132. package/src/config.ts +0 -475
  133. package/src/credentials.test.ts +0 -403
  134. package/src/credentials.ts +0 -403
  135. package/src/file-lock-process-worker.test.ts +0 -64
  136. package/src/file-lock.test.ts +0 -320
  137. package/src/file-lock.ts +0 -393
  138. package/src/fixtures/hermes-0.20.6-classic-cli-local.json +0 -50
  139. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +0 -57
  140. package/src/generated-session-label.ts +0 -155
  141. package/src/guidance-content.ts +0 -278
  142. package/src/guidance-render.ts +0 -56
  143. package/src/guidance.test.ts +0 -327
  144. package/src/guidance.ts +0 -233
  145. package/src/harness-integration.test.ts +0 -294
  146. package/src/harnesses.test.ts +0 -149
  147. package/src/harnesses.ts +0 -164
  148. package/src/hook-adapter.test.ts +0 -496
  149. package/src/hook-adapter.ts +0 -410
  150. package/src/hook-input.test.ts +0 -39
  151. package/src/hook-input.ts +0 -72
  152. package/src/hooks.test.ts +0 -5549
  153. package/src/hooks.ts +0 -3759
  154. package/src/install-hooks.test.ts +0 -1614
  155. package/src/install-hooks.ts +0 -1501
  156. package/src/interactive.test.ts +0 -387
  157. package/src/interactive.ts +0 -858
  158. package/src/invocation-context.test.ts +0 -352
  159. package/src/invocation-context.ts +0 -197
  160. package/src/logging-process-worker.test.ts +0 -45
  161. package/src/logging.test.ts +0 -544
  162. package/src/logging.ts +0 -698
  163. package/src/main.ts +0 -36
  164. package/src/native-skills.test.ts +0 -115
  165. package/src/native-skills.ts +0 -162
  166. package/src/onboarding-docs.test.ts +0 -25
  167. package/src/openclaw-plugin.ts +0 -409
  168. package/src/opencode-plugin.ts +0 -298
  169. package/src/orca-session-title.test.ts +0 -54
  170. package/src/orca-session-title.ts +0 -103
  171. package/src/pack-feedback-slice.test.ts +0 -212
  172. package/src/pack-feedback-slice.ts +0 -187
  173. package/src/platform.test.ts +0 -152
  174. package/src/platform.ts +0 -173
  175. package/src/program.test.ts +0 -282
  176. package/src/program.ts +0 -818
  177. package/src/project-enablement.test.ts +0 -55
  178. package/src/project-enablement.ts +0 -99
  179. package/src/question-caps.test.ts +0 -49
  180. package/src/question-timing.ts +0 -56
  181. package/src/readiness.test.ts +0 -25
  182. package/src/readiness.ts +0 -179
  183. package/src/release.ts +0 -51
  184. package/src/send-attempts.test.ts +0 -143
  185. package/src/send-attempts.ts +0 -188
  186. package/src/send.test.ts +0 -28
  187. package/src/send.ts +0 -436
  188. package/src/session-activation.ts +0 -61
  189. package/src/session-end-log-lock-worker.test.ts +0 -35
  190. package/src/session-labels.test.ts +0 -490
  191. package/src/session-labels.ts +0 -474
  192. package/src/skill.test.ts +0 -415
  193. package/src/sound-ref.test.ts +0 -31
  194. package/src/sound-ref.ts +0 -34
  195. package/src/trust-policy.test.ts +0 -701
  196. package/src/ui/banner.ts +0 -95
  197. package/src/ui/config-view.test.ts +0 -90
  198. package/src/ui/config-view.ts +0 -138
  199. package/src/ui/help.test.ts +0 -16
  200. package/src/ui/help.ts +0 -128
  201. package/src/ui/theme.test.ts +0 -92
  202. package/src/ui/theme.ts +0 -265
  203. package/src/url-policy.ts +0 -399
  204. package/tsconfig.json +0 -10
package/src/file-lock.ts DELETED
@@ -1,393 +0,0 @@
1
- import { randomBytes } from 'node:crypto'
2
- import {
3
- chmodSync,
4
- closeSync,
5
- fstatSync,
6
- lstatSync,
7
- mkdirSync,
8
- openSync,
9
- readdirSync,
10
- renameSync,
11
- rmdirSync,
12
- unlinkSync,
13
- } from 'node:fs'
14
- import path from 'node:path'
15
-
16
- /**
17
- * Serialize a short cross-process filesystem transaction.
18
- *
19
- * Each contender publishes all of its ownership in one unique filename. A
20
- * two-phase bakery lock then orders live contenders by ticket and process
21
- * identity. Because paths are never reused, recovering an entry left by a dead
22
- * process cannot remove a later owner's entry; because process liveness, rather
23
- * than age, decides recovery, a delayed holder is never mistaken for a dead one.
24
- *
25
- * The rendezvous directory lives only as long as it has entries: the last
26
- * contender out removes it, so an idle machine keeps no lock state. That makes
27
- * the directory itself a contended resource. A contender registering while
28
- * another releases can find it gone, half-deleted, or already replaced by a
29
- * third contender, and none of those is a broken lock — so publication retries
30
- * within the caller's deadline and pins its rendezvous only once its own entry
31
- * is in the directory, which is the moment removal becomes impossible.
32
- *
33
- * Acquisition is deliberately bounded because both harness hooks and logging
34
- * must fail open rather than hold an agent turn indefinitely.
35
- */
36
- const FILE_LOCK_WAIT_MS = 1_000
37
- const FILE_LOCK_POLL_MS = 5
38
- const FILE_LOCK_TOKEN_BYTES = 12
39
- const ENTRY_PATTERN = /^(choosing|ticket-(\d+))-(\d+)-([0-9a-f]+)$/
40
- const lockSleep = new Int32Array(new SharedArrayBuffer(4))
41
-
42
- export type FileLockObservation =
43
- /** The rendezvous has been scanned and this contender is about to publish. */
44
- | { phase: 'registering'; entry: string }
45
- | { phase: 'choosing-published'; entry: string }
46
- | { phase: 'stale-entry'; entry: string }
47
- | { phase: 'waiting'; blockers: string[] }
48
-
49
- interface FileLockOptions {
50
- waitMs?: number
51
- observe?: (observation: FileLockObservation) => void
52
- /** Installer transactions report lock replacement after a successful action. */
53
- strictRelease?: boolean
54
- }
55
-
56
- interface FileIdentity {
57
- dev: number
58
- ino: number
59
- }
60
-
61
- /**
62
- * Identity for one published lock entry.
63
- *
64
- * Linux can immediately reuse an inode after an unlinked entry is replaced at
65
- * the same path. The creation/change timestamp keeps that replacement distinct
66
- * even when its device and inode numbers happen to match the removed entry.
67
- */
68
- interface EntryIdentity {
69
- dev: bigint
70
- ino: bigint
71
- ctimeNs: bigint
72
- }
73
-
74
- interface ChoosingEntry {
75
- kind: 'choosing'
76
- name: string
77
- pid: number
78
- token: string
79
- }
80
-
81
- interface TicketEntry {
82
- kind: 'ticket'
83
- name: string
84
- pid: number
85
- token: string
86
- ticket: bigint
87
- }
88
-
89
- type LockEntry = ChoosingEntry | TicketEntry
90
-
91
- function parseEntry(name: string): LockEntry | null {
92
- const match = ENTRY_PATTERN.exec(name)
93
- if (match === null) return null
94
- const pid = Number(match[3])
95
- if (!Number.isSafeInteger(pid) || pid <= 0) return null
96
- const token = match[4]!
97
- if (match[1] === 'choosing') return { kind: 'choosing', name, pid, token }
98
- try {
99
- const ticket = BigInt(match[2]!)
100
- if (ticket <= 0n) return null
101
- return { kind: 'ticket', name, pid, token, ticket }
102
- } catch {
103
- return null
104
- }
105
- }
106
-
107
- function processIsAlive(pid: number): boolean {
108
- if (pid === process.pid) return true
109
- try {
110
- process.kill(pid, 0)
111
- return true
112
- } catch (err) {
113
- const code = (err as NodeJS.ErrnoException).code
114
- if (code === 'ESRCH') return false
115
- // EPERM means the process exists but is owned by somebody else. Unknown
116
- // failures are also treated as live: losing recovery is safer than allowing
117
- // two holders into the transaction.
118
- return true
119
- }
120
- }
121
-
122
- function currentUid(): number | undefined {
123
- return typeof process.getuid === 'function' ? process.getuid() : undefined
124
- }
125
-
126
- function assertOwnedDirectory(directory: string): FileIdentity {
127
- const stat = lstatSync(directory)
128
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
129
- throw new Error(`${directory} is not a regular directory; refusing to use it as a file lock.`)
130
- }
131
- const uid = currentUid()
132
- if (uid !== undefined && stat.uid !== uid) {
133
- throw new Error(`${directory} is owned by uid ${stat.uid}, not the current user.`)
134
- }
135
- return { dev: stat.dev, ino: stat.ino }
136
- }
137
-
138
- /**
139
- * The rendezvous a contender was using is no longer the directory at that path.
140
- *
141
- * Fatal once an entry is published — a contender's own entry makes the
142
- * directory non-empty, so no cooperating releaser can remove it. Before
143
- * publication it means only that a releaser removed an empty rendezvous while
144
- * this contender was registering in it, which is ordinary contention.
145
- */
146
- class RendezvousReplaced extends Error {}
147
-
148
- function assertSameDirectory(directory: string, expected: FileIdentity): void {
149
- const current = lstatSync(directory)
150
- if (
151
- current.isSymbolicLink() ||
152
- !current.isDirectory() ||
153
- current.dev !== expected.dev ||
154
- current.ino !== expected.ino
155
- ) {
156
- throw new RendezvousReplaced(`${directory} changed while the file lock was active.`)
157
- }
158
- }
159
-
160
- function entryIdentity(stat: { dev: bigint; ino: bigint; ctimeNs: bigint }): EntryIdentity {
161
- return {
162
- dev: stat.dev,
163
- ino: stat.ino,
164
- ctimeNs: stat.ctimeNs,
165
- }
166
- }
167
-
168
- function sameEntryIdentity(file: string, expected: EntryIdentity): boolean {
169
- try {
170
- const current = lstatSync(file, { bigint: true })
171
- return (
172
- current.dev === expected.dev &&
173
- current.ino === expected.ino &&
174
- current.ctimeNs === expected.ctimeNs
175
- )
176
- } catch {
177
- return false
178
- }
179
- }
180
-
181
- /** Live protocol entries, pruning only unique paths whose process is gone. */
182
- function liveEntries(
183
- directory: string,
184
- directoryIdentity: FileIdentity,
185
- observe: FileLockOptions['observe'],
186
- ): LockEntry[] {
187
- assertSameDirectory(directory, directoryIdentity)
188
- const live: LockEntry[] = []
189
- for (const name of readdirSync(directory)) {
190
- const entry = parseEntry(name)
191
- if (entry === null) continue
192
- const entryPath = path.join(directory, name)
193
- let stat
194
- try {
195
- stat = lstatSync(entryPath)
196
- } catch (err) {
197
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') continue
198
- throw err
199
- }
200
- const uid = currentUid()
201
- if (stat.isSymbolicLink() || !stat.isFile() || (uid !== undefined && stat.uid !== uid)) {
202
- throw new Error(`${entryPath} is not a current-user-owned regular file-lock entry.`)
203
- }
204
- if (processIsAlive(entry.pid)) {
205
- live.push(entry)
206
- continue
207
- }
208
- observe?.({ phase: 'stale-entry', entry: name })
209
- try {
210
- // Recheck the unique dead owner's inode so delayed recovery cannot reap
211
- // a replacement published at the same path by external interference.
212
- const current = lstatSync(entryPath)
213
- if (current.dev === stat.dev && current.ino === stat.ino) unlinkSync(entryPath)
214
- } catch {
215
- // Another contender already recovered this exact dead owner's unique path.
216
- }
217
- }
218
- return live
219
- }
220
-
221
- function publishEmpty(file: string): EntryIdentity {
222
- const handle = openSync(file, 'wx', 0o600)
223
- try {
224
- return entryIdentity(fstatSync(handle, { bigint: true }))
225
- } finally {
226
- closeSync(handle)
227
- }
228
- }
229
-
230
- /**
231
- * True when a publication attempt lost its rendezvous rather than found a
232
- * broken one.
233
- *
234
- * A releaser removes an empty rendezvous directory the moment the last entry
235
- * goes, so a contender registering at that instant sees the removal from
236
- * whichever side it reached first. All three are the same event: `ENOENT` when
237
- * the directory is already gone, `EINVAL` when APFS reports a directory that is
238
- * still being deleted, and a replaced identity when a third contender recreated
239
- * it first. None of them says anything about the lock's integrity, because a
240
- * contender with no published entry owns nothing yet.
241
- */
242
- function lostTheRendezvous(err: unknown): boolean {
243
- if (err instanceof RendezvousReplaced) return true
244
- const code = (err as NodeJS.ErrnoException).code
245
- return code === 'ENOENT' || code === 'EINVAL'
246
- }
247
-
248
- function publishChoosing(
249
- directory: string,
250
- name: string,
251
- deadline: number,
252
- observe: FileLockOptions['observe'],
253
- ): { file: string; fileIdentity: EntryIdentity; directoryIdentity: FileIdentity } {
254
- const file = path.join(directory, name)
255
- for (;;) {
256
- try {
257
- mkdirSync(directory, { recursive: true, mode: 0o700 })
258
- const registrar = assertOwnedDirectory(directory)
259
- chmodSync(directory, 0o700)
260
- // Recover before registration so a delayed stale cleanup can only name an
261
- // old owner's unique path, never this contender's future one.
262
- liveEntries(directory, registrar, observe)
263
- observe?.({ phase: 'registering', entry: name })
264
- const fileIdentity = publishEmpty(file)
265
- // Pin the rendezvous that holds the entry, not the one scanned before it
266
- // existed. `open` resolves the path atomically, so the entry lands in
267
- // whichever directory the path named at that instant — and from then on
268
- // that directory is non-empty, so no cooperating releaser can remove it.
269
- // Reading the identity here therefore names the contender's real
270
- // rendezvous even when the scanned one was removed and recreated
271
- // underneath it.
272
- const directoryIdentity = assertOwnedDirectory(directory)
273
- if (!sameEntryIdentity(file, fileIdentity)) {
274
- throw new RendezvousReplaced(`${directory} no longer holds ${name}.`)
275
- }
276
- return { file, fileIdentity, directoryIdentity }
277
- } catch (err) {
278
- if (!lostTheRendezvous(err)) throw err
279
- // Recreate it and register again; the deadline, not the number of losses,
280
- // decides when contention becomes a failure.
281
- if (Date.now() >= deadline) {
282
- throw new Error(
283
- `timed out publishing file lock contender in ${directory}: ${(err as Error).message}`,
284
- )
285
- }
286
- Atomics.wait(lockSleep, 0, 0, FILE_LOCK_POLL_MS)
287
- }
288
- }
289
- }
290
-
291
- function comesBefore(entry: TicketEntry, ticket: bigint, pid: number, token: string): boolean {
292
- if (entry.ticket !== ticket) return entry.ticket < ticket
293
- if (entry.pid !== pid) return entry.pid < pid
294
- return entry.token < token
295
- }
296
-
297
- export function withFileLock<T>(file: string, action: () => T, options: FileLockOptions = {}): T {
298
- const waitMs = options.waitMs ?? FILE_LOCK_WAIT_MS
299
- const deadline = Date.now() + waitMs
300
- const pid = process.pid
301
- const token = randomBytes(FILE_LOCK_TOKEN_BYTES).toString('hex')
302
- const choosingName = `choosing-${pid}-${token}`
303
- let ownedPath: string | null = null
304
- let ownedIdentity: EntryIdentity | null = null
305
- let directoryIdentity: FileIdentity | null = null
306
- let operationFailed = false
307
-
308
- try {
309
- const published = publishChoosing(file, choosingName, deadline, options.observe)
310
- ownedPath = published.file
311
- ownedIdentity = published.fileIdentity
312
- directoryIdentity = published.directoryIdentity
313
- options.observe?.({ phase: 'choosing-published', entry: choosingName })
314
- let highest = 0n
315
- for (const entry of liveEntries(file, directoryIdentity, options.observe)) {
316
- if (entry.kind === 'ticket' && entry.ticket > highest) highest = entry.ticket
317
- }
318
- const ticket = highest + 1n
319
- const ticketName = `ticket-${ticket}-${pid}-${token}`
320
- const ticketPath = path.join(file, ticketName)
321
-
322
- // The rename is the doorway: observers see either a chooser or its complete
323
- // ticket, never a gap in which a live contender has no published state.
324
- renameSync(ownedPath, ticketPath)
325
- ownedPath = ticketPath
326
- ownedIdentity = entryIdentity(lstatSync(ticketPath, { bigint: true }))
327
-
328
- for (;;) {
329
- const blockers: string[] = []
330
- for (const entry of liveEntries(file, directoryIdentity, options.observe)) {
331
- if (entry.pid === pid && entry.token === token) continue
332
- if (entry.kind === 'choosing' || comesBefore(entry, ticket, pid, token)) {
333
- blockers.push(entry.name)
334
- }
335
- }
336
- if (blockers.length === 0) {
337
- assertSameDirectory(file, directoryIdentity)
338
- return action()
339
- }
340
- options.observe?.({ phase: 'waiting', blockers })
341
- if (Date.now() >= deadline) throw new Error(`timed out waiting for file lock ${file}`)
342
- Atomics.wait(lockSleep, 0, 0, FILE_LOCK_POLL_MS)
343
- }
344
- } catch (err) {
345
- operationFailed = true
346
- throw err
347
- } finally {
348
- let releaseChanged = false
349
- if (ownedPath !== null && ownedIdentity !== null) {
350
- try {
351
- if (sameEntryIdentity(ownedPath, ownedIdentity)) unlinkSync(ownedPath)
352
- else releaseChanged = true
353
- } catch {
354
- // External cleanup cannot turn a release failure into a caller failure.
355
- }
356
- }
357
- try {
358
- if (directoryIdentity !== null) assertSameDirectory(file, directoryIdentity)
359
- rmdirSync(file)
360
- } catch {
361
- // Another contender has published, or external cleanup already removed it.
362
- }
363
- if (releaseChanged && options.strictRelease === true && !operationFailed) {
364
- throw new Error(`${ownedPath ?? file} changed while held; refusing to remove the replacement.`)
365
- }
366
- }
367
- }
368
-
369
- /**
370
- * Serialize a read/merge/write transaction for one user-owned target file.
371
- *
372
- * This is the single installer-facing lock policy: the target's direct parent
373
- * is a real current-user-owned directory, remains the same inode throughout,
374
- * and the bakery rendezvous lives beside the target rather than inside it.
375
- */
376
- export function withTargetFileLock<T>(
377
- target: string,
378
- action: () => T,
379
- options: Omit<FileLockOptions, 'strictRelease'> = {},
380
- ): T {
381
- const parent = path.dirname(target)
382
- mkdirSync(parent, { recursive: true })
383
- const parentIdentity = assertOwnedDirectory(parent)
384
- const lockDirectory = path.join(parent, `.${path.basename(target)}.notifai.lock`)
385
- return withFileLock(
386
- lockDirectory,
387
- () => {
388
- assertSameDirectory(parent, parentIdentity)
389
- return action()
390
- },
391
- { ...options, strictRelease: true },
392
- )
393
- }
@@ -1,50 +0,0 @@
1
- {
2
- "vendor": "NousResearch/hermes-agent",
3
- "release": "0.20.6",
4
- "commit": "5f75ec197b16d1a055688fb9df63ac49586a2230",
5
- "instance": {
6
- "harness": "hermes",
7
- "surface": "classic-cli",
8
- "terminalBackend": "local"
9
- },
10
- "durableSessionId": "20260828_111302_29a404",
11
- "observedToolSubprocessEnv": {
12
- "HERMES_SESSION_ID": "20260828_111302_29a404"
13
- },
14
- "cwd": {
15
- "invocation": "/workspace",
16
- "shellHookCwdIsProcessCwd": true,
17
- "promptSectionCwdRequiresTerminalOverride": true
18
- },
19
- "evidence": {
20
- "nativeHostOs": "macos",
21
- "portableFixtureFamilies": ["posix", "windows"],
22
- "nativeOsUnrun": ["linux", "windows"]
23
- },
24
- "compression": {
25
- "defaultMode": "in-place",
26
- "sessionIdBefore": "20260828_120000_inplace",
27
- "sessionIdAfter": "20260828_120000_inplace",
28
- "legacyRotatingSessionId": "20260828_111114_88ae83"
29
- },
30
- "supported": ["deliberate-send", "source-context-from-invocation-cwd"],
31
- "unsupported": [
32
- "managed-setup",
33
- "lifecycle-activation",
34
- "question-routing"
35
- ],
36
- "deferred": [
37
- "tui",
38
- "gateway",
39
- "api",
40
- "acp-session",
41
- "safe-mode",
42
- "native-linux",
43
- "native-windows",
44
- "live-auto-compression",
45
- "interactive-reset",
46
- "docker-backend",
47
- "ssh-backend",
48
- "other-remote-backends"
49
- ]
50
- }
@@ -1,57 +0,0 @@
1
- {
2
- "vendor": "openclaw/openclaw",
3
- "release": "2026.7.1-2",
4
- "commit": "0790d9f",
5
- "instance": {
6
- "harness": "openclaw",
7
- "surface": "agent-local",
8
- "execHost": "gateway"
9
- },
10
- "durableSessionKey": "agent:main:probe-owner",
11
- "rotatingSessionId": "ae6686a4-d632-4750-b73b-56faec5fff9a",
12
- "observedPluginEnv": {
13
- "NOTIFAI_ACTIVE_HARNESS": "openclaw",
14
- "NOTIFAI_ACTIVE_SESSION_ID": "agent:main:probe-owner"
15
- },
16
- "worker": {
17
- "sessionKeySubstring": ":subagent:",
18
- "provedSessionKey": "agent:main:subagent:probe-worker",
19
- "missingIdentity": "fail-closed-worker"
20
- },
21
- "hooks": {
22
- "proven": {
23
- "activation": "before_prompt_build",
24
- "stop": "agent_end",
25
- "execMarkers": "resolve_exec_env"
26
- },
27
- "unprovenOnThisSurface": {
28
- "presence": "message_received",
29
- "sessionEnd": "session_end"
30
- }
31
- },
32
- "evidence": {
33
- "nativeHostOs": "macos",
34
- "command": "openclaw agent --local",
35
- "nativeOsUnrun": ["linux", "windows"]
36
- },
37
- "supported": [
38
- "managed-setup",
39
- "lifecycle-activation",
40
- "source-context-from-session-key"
41
- ],
42
- "unsupported": [
43
- "question-routing",
44
- "command-stop-as-turn-end",
45
- "2026.2.26-cli"
46
- ],
47
- "deferred": [
48
- "gateway-embedded",
49
- "gateway-remote",
50
- "agent-exec",
51
- "acp-nested",
52
- "exec-sandbox",
53
- "exec-node",
54
- "native-linux",
55
- "native-windows"
56
- ]
57
- }
@@ -1,155 +0,0 @@
1
- const FNV1A_64_OFFSET = 0xcbf29ce484222325n
2
- const FNV1A_64_PRIME = 0x100000001b3n
3
- const WORD_COUNT = 64n
4
-
5
- /** Frozen word lists keep generated fallbacks stable for a given opaque id. */
6
- const ADJECTIVES = [
7
- 'Amber',
8
- 'Arctic',
9
- 'Ash',
10
- 'Azure',
11
- 'Blue',
12
- 'Bold',
13
- 'Bright',
14
- 'Bronze',
15
- 'Calm',
16
- 'Cedar',
17
- 'Clear',
18
- 'Copper',
19
- 'Coral',
20
- 'Crisp',
21
- 'Dawn',
22
- 'Deep',
23
- 'Ember',
24
- 'Fern',
25
- 'Frost',
26
- 'Gentle',
27
- 'Golden',
28
- 'Green',
29
- 'Harbor',
30
- 'Hazel',
31
- 'Indigo',
32
- 'Ivory',
33
- 'Jade',
34
- 'Keen',
35
- 'Lively',
36
- 'Lunar',
37
- 'Maple',
38
- 'Meadow',
39
- 'Mellow',
40
- 'Misty',
41
- 'Noble',
42
- 'Ocean',
43
- 'Olive',
44
- 'Onyx',
45
- 'Quiet',
46
- 'Rapid',
47
- 'Red',
48
- 'River',
49
- 'Royal',
50
- 'Sage',
51
- 'Silver',
52
- 'Solar',
53
- 'Steady',
54
- 'Stone',
55
- 'Sunny',
56
- 'Swift',
57
- 'Teal',
58
- 'Timber',
59
- 'True',
60
- 'Velvet',
61
- 'Verdant',
62
- 'Violet',
63
- 'Warm',
64
- 'Wild',
65
- 'Winter',
66
- 'Wise',
67
- 'Witty',
68
- 'Yellow',
69
- 'Young',
70
- 'Zealous',
71
- ] as const
72
-
73
- const NOUNS = [
74
- 'Alpaca',
75
- 'Antelope',
76
- 'Badger',
77
- 'Bear',
78
- 'Beaver',
79
- 'Bison',
80
- 'Bobcat',
81
- 'Caribou',
82
- 'Cougar',
83
- 'Coyote',
84
- 'Crane',
85
- 'Deer',
86
- 'Dolphin',
87
- 'Dragonfly',
88
- 'Eagle',
89
- 'Elk',
90
- 'Falcon',
91
- 'Finch',
92
- 'Firefly',
93
- 'Fox',
94
- 'Gazelle',
95
- 'Gecko',
96
- 'Hare',
97
- 'Heron',
98
- 'Ibex',
99
- 'Jay',
100
- 'Kestrel',
101
- 'Kingfisher',
102
- 'Koala',
103
- 'Lark',
104
- 'Leopard',
105
- 'Lynx',
106
- 'Magpie',
107
- 'Mantis',
108
- 'Marten',
109
- 'Moose',
110
- 'Osprey',
111
- 'Otter',
112
- 'Owl',
113
- 'Panda',
114
- 'Panther',
115
- 'Pelican',
116
- 'Penguin',
117
- 'Puma',
118
- 'Quail',
119
- 'Rabbit',
120
- 'Raven',
121
- 'Robin',
122
- 'Sable',
123
- 'Salmon',
124
- 'Seal',
125
- 'Shark',
126
- 'Sparrow',
127
- 'Stag',
128
- 'Swan',
129
- 'Tiger',
130
- 'Tortoise',
131
- 'Trout',
132
- 'Turtle',
133
- 'Viper',
134
- 'Whale',
135
- 'Wolf',
136
- 'Wren',
137
- 'Yak',
138
- ] as const
139
-
140
- function fnv1a64(value: string): bigint {
141
- let hash = FNV1A_64_OFFSET
142
- for (const byte of new TextEncoder().encode(value)) {
143
- hash ^= BigInt(byte)
144
- hash = BigInt.asUintN(64, hash * FNV1A_64_PRIME)
145
- }
146
- return hash
147
- }
148
-
149
- /** Deterministic fallback for a session whose work has no semantic title. */
150
- export function generatedSessionLabel(sessionId: string): string {
151
- const hash = fnv1a64(sessionId)
152
- const adjective = ADJECTIVES[Number(hash % WORD_COUNT)]
153
- const noun = NOUNS[Number((hash >> 6n) % WORD_COUNT)]
154
- return `${adjective} ${noun}`
155
- }