@raidiant/notifai 10.0.0 → 10.1.1
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.
- package/CHANGELOG.md +31 -0
- package/README.md +4 -1
- package/dist/cli-bin.d.ts +21 -1
- package/dist/cli-bin.js +141 -10
- package/dist/cli-bin.js.map +1 -1
- package/dist/cli-update-contract.d.ts +7 -0
- package/dist/cli-update-contract.js +10 -0
- package/dist/cli-update-contract.js.map +1 -0
- package/dist/client.d.ts +6 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/codex-wake.d.ts +2 -0
- package/dist/codex-wake.js +2 -1
- package/dist/codex-wake.js.map +1 -1
- package/dist/commands-auth.d.ts +15 -1
- package/dist/commands-auth.js +32 -14
- package/dist/commands-auth.js.map +1 -1
- package/dist/commands-core.d.ts +10 -1
- package/dist/commands-core.js +13 -4
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-devices.d.ts +10 -1
- package/dist/commands-devices.js +17 -13
- package/dist/commands-devices.js.map +1 -1
- package/dist/commands-doctor.js +158 -71
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-harness-context.js +8 -0
- package/dist/commands-harness-context.js.map +1 -1
- package/dist/commands-hooks.js +169 -15
- package/dist/commands-hooks.js.map +1 -1
- package/dist/commands-init.d.ts +16 -0
- package/dist/commands-init.js +254 -91
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send.d.ts +1 -1
- package/dist/commands-send.js +121 -35
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-update.d.ts +10 -0
- package/dist/commands-update.js +141 -0
- package/dist/commands-update.js.map +1 -0
- package/dist/commands.d.ts +2 -1
- package/dist/commands.js +2 -1
- package/dist/commands.js.map +1 -1
- package/dist/harnesses.d.ts +2 -2
- package/dist/harnesses.js +22 -2
- package/dist/harnesses.js.map +1 -1
- package/dist/hook-adapter.d.ts +2 -0
- package/dist/hook-adapter.js +12 -1
- package/dist/hook-adapter.js.map +1 -1
- package/dist/hooks.d.ts +43 -2
- package/dist/hooks.js +163 -21
- package/dist/hooks.js.map +1 -1
- package/dist/install-hooks.js +15 -9
- package/dist/install-hooks.js.map +1 -1
- package/dist/interactive.js +20 -23
- package/dist/interactive.js.map +1 -1
- package/dist/main-run.d.ts +1 -0
- package/dist/main-run.js +42 -0
- package/dist/main-run.js.map +1 -0
- package/dist/main.js +10 -31
- package/dist/main.js.map +1 -1
- package/dist/node-floor.d.ts +18 -0
- package/dist/node-floor.js +35 -0
- package/dist/node-floor.js.map +1 -0
- package/dist/openclaw-plugin.d.ts +49 -0
- package/dist/openclaw-plugin.js +350 -0
- package/dist/openclaw-plugin.js.map +1 -0
- package/dist/program.d.ts +2 -1
- package/dist/program.js +31 -13
- package/dist/program.js.map +1 -1
- package/dist/question-settlement-process.d.ts +15 -0
- package/dist/question-settlement-process.js +35 -0
- package/dist/question-settlement-process.js.map +1 -0
- package/dist/readiness.d.ts +25 -1
- package/dist/readiness.js +32 -2
- package/dist/readiness.js.map +1 -1
- package/dist/session-activation.js +1 -1
- package/dist/session-activation.js.map +1 -1
- package/dist/setup-destinations.d.ts +23 -0
- package/dist/setup-destinations.js +44 -0
- package/dist/setup-destinations.js.map +1 -0
- package/dist/ui/help.d.ts +4 -5
- package/dist/ui/help.js +5 -6
- package/dist/ui/help.js.map +1 -1
- package/package.json +2 -2
- package/src/cli-bin.test.ts +145 -7
- package/src/cli-bin.ts +176 -10
- package/src/cli-update-contract.ts +9 -0
- package/src/cli-update.test.ts +125 -0
- package/src/client.ts +7 -0
- package/src/codex-wake.ts +5 -1
- package/src/commands-auth.ts +47 -13
- package/src/commands-core.ts +21 -4
- package/src/commands-devices.ts +23 -16
- package/src/commands-doctor.ts +197 -75
- package/src/commands-harness-context.ts +8 -0
- package/src/commands-hooks.ts +210 -18
- package/src/commands-init.ts +275 -92
- package/src/commands-send.ts +145 -39
- package/src/commands-update.ts +180 -0
- package/src/commands.test.ts +701 -53
- package/src/commands.ts +2 -0
- package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +57 -0
- package/src/harness-integration.test.ts +82 -0
- package/src/harnesses.test.ts +9 -1
- package/src/harnesses.ts +24 -2
- package/src/hook-adapter.ts +14 -0
- package/src/hooks.test.ts +275 -0
- package/src/hooks.ts +270 -18
- package/src/install-hooks.test.ts +208 -0
- package/src/install-hooks.ts +19 -9
- package/src/interactive.test.ts +31 -0
- package/src/interactive.ts +20 -26
- package/src/main-run.ts +45 -0
- package/src/main.ts +9 -33
- package/src/node-floor.test.ts +27 -0
- package/src/node-floor.ts +37 -0
- package/src/openclaw-plugin.ts +409 -0
- package/src/program.test.ts +35 -0
- package/src/program.ts +37 -12
- package/src/question-settlement-process.ts +47 -0
- package/src/readiness.ts +35 -2
- package/src/session-activation.ts +1 -1
- package/src/setup-destinations.test.ts +41 -0
- package/src/setup-destinations.ts +50 -0
- package/src/skill.test.ts +13 -2
- package/src/ui/help.test.ts +16 -0
- package/src/ui/help.ts +6 -7
- package/src/commands-devices.test.ts +0 -15
package/src/commands-doctor.ts
CHANGED
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
HERMES_QUESTION_ROUTING_UNAVAILABLE,
|
|
12
12
|
isHookInstallableHarness,
|
|
13
13
|
} from './harnesses.js'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
hookAdapterTargetsArtifact,
|
|
16
|
+
inspectHookAdapter,
|
|
17
|
+
isNpxAdapterTarget,
|
|
18
|
+
} from './hook-adapter.js'
|
|
15
19
|
import {
|
|
16
20
|
readLiveProjectSessionPointers,
|
|
17
21
|
readProjectSessionPointer,
|
|
@@ -48,14 +52,16 @@ import type { Tone } from './ui/theme.js'
|
|
|
48
52
|
import type { MachineCredential } from './credentials.js'
|
|
49
53
|
import {
|
|
50
54
|
EXIT,
|
|
51
|
-
|
|
55
|
+
SETUP_COMMAND,
|
|
52
56
|
diagnoseIgnoredOriginOverride,
|
|
53
57
|
loadLoggedConfig,
|
|
54
58
|
makeClient,
|
|
55
59
|
resolvedBaseUrl,
|
|
60
|
+
updateCliCommand,
|
|
56
61
|
type CommandDeps,
|
|
57
62
|
} from './commands-core.js'
|
|
58
|
-
import { deviceInstallRemedy, readyCompanionDevices
|
|
63
|
+
import { deviceInstallRemedy, readyCompanionDevices } from './commands-devices.js'
|
|
64
|
+
import { setupAccessUrl } from './setup-destinations.js'
|
|
59
65
|
import {
|
|
60
66
|
claudeSessionPid,
|
|
61
67
|
resolveActiveHarness,
|
|
@@ -68,7 +74,7 @@ import {
|
|
|
68
74
|
hookActivationAdvice,
|
|
69
75
|
requiredHookEvents,
|
|
70
76
|
} from './commands-hooks.js'
|
|
71
|
-
import { cliBinReadiness } from './cli-bin.js'
|
|
77
|
+
import { cliBinReadiness, inspectCliInstallations } from './cli-bin.js'
|
|
72
78
|
import {
|
|
73
79
|
latestPublishedCliVersion,
|
|
74
80
|
newerPublishedCli,
|
|
@@ -95,7 +101,7 @@ import { projectBinding, projectEnabled } from './project-enablement.js'
|
|
|
95
101
|
* versions and document integers are structured inventory, never routing or a
|
|
96
102
|
* definition of "up to date".
|
|
97
103
|
*/
|
|
98
|
-
async function compatibilityCheck(client: ApiClient): Promise<ReadinessState> {
|
|
104
|
+
async function compatibilityCheck(client: ApiClient, deps: CommandDeps): Promise<ReadinessState> {
|
|
99
105
|
try {
|
|
100
106
|
const [compatibility, documents] = await Promise.all([
|
|
101
107
|
client.compatibility(),
|
|
@@ -130,7 +136,7 @@ async function compatibilityCheck(client: ApiClient): Promise<ReadinessState> {
|
|
|
130
136
|
remedy: {
|
|
131
137
|
by: 'user-here',
|
|
132
138
|
summary: 'update Notifai',
|
|
133
|
-
command:
|
|
139
|
+
command: updateCliCommand(deps),
|
|
134
140
|
},
|
|
135
141
|
}
|
|
136
142
|
}
|
|
@@ -147,7 +153,7 @@ async function compatibilityCheck(client: ApiClient): Promise<ReadinessState> {
|
|
|
147
153
|
remedy: {
|
|
148
154
|
by: 'user-here',
|
|
149
155
|
summary: 'update Notifai',
|
|
150
|
-
command:
|
|
156
|
+
command: updateCliCommand(deps),
|
|
151
157
|
},
|
|
152
158
|
}
|
|
153
159
|
}
|
|
@@ -303,6 +309,110 @@ function remoteInvalidatedByConfig(
|
|
|
303
309
|
return server === undefined || !server.detail.includes(origin)
|
|
304
310
|
}
|
|
305
311
|
|
|
312
|
+
function isNoActivePlanError(err: unknown): err is ApiCallError {
|
|
313
|
+
return err instanceof ApiCallError && err.code === 'no_active_plan'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Device listing is grant-gated; access status is not. A paired Account whose
|
|
318
|
+
* plan lapsed must not be diagnosed as a revoked machine.
|
|
319
|
+
*/
|
|
320
|
+
async function probeAccount(
|
|
321
|
+
client: ApiClient,
|
|
322
|
+
credential: MachineCredential,
|
|
323
|
+
baseUrl: string,
|
|
324
|
+
): Promise<{
|
|
325
|
+
email: string | null
|
|
326
|
+
devices: RoutableDevice[] | null
|
|
327
|
+
lookupFailed: boolean
|
|
328
|
+
auth: ReadinessState
|
|
329
|
+
}> {
|
|
330
|
+
const [devicesOutcome, accessOutcome, requestOutcome] = await Promise.allSettled([
|
|
331
|
+
Promise.resolve().then(() => client.listDevices()),
|
|
332
|
+
Promise.resolve().then(() => client.accessStatus()),
|
|
333
|
+
Promise.resolve().then(() => client.accessRequest()),
|
|
334
|
+
])
|
|
335
|
+
const access = accessOutcome.status === 'fulfilled' ? accessOutcome.value : null
|
|
336
|
+
const pendingRequest =
|
|
337
|
+
requestOutcome.status === 'fulfilled' ? requestOutcome.value.request : null
|
|
338
|
+
const devices = devicesOutcome.status === 'fulfilled' ? devicesOutcome.value.devices : null
|
|
339
|
+
const email = access?.email ?? null
|
|
340
|
+
const devicesErr = devicesOutcome.status === 'rejected' ? devicesOutcome.reason : null
|
|
341
|
+
const noActivePlan =
|
|
342
|
+
access?.status === 'no_active_plan' || isNoActivePlanError(devicesErr)
|
|
343
|
+
|
|
344
|
+
if (noActivePlan) {
|
|
345
|
+
const next =
|
|
346
|
+
(isNoActivePlanError(devicesErr) ? devicesErr.nextAction : null) ??
|
|
347
|
+
`Open ${setupAccessUrl(baseUrl)} to set up access, then retry.`
|
|
348
|
+
const who = email ? ` (${email})` : ''
|
|
349
|
+
// Three values, not two. Someone who asked yesterday is waiting on a person,
|
|
350
|
+
// not on an action of theirs, and telling them to ask again — every run,
|
|
351
|
+
// forever — is the only thing this terminal could get wrong about a wait it
|
|
352
|
+
// cannot shorten.
|
|
353
|
+
return {
|
|
354
|
+
email,
|
|
355
|
+
devices: null,
|
|
356
|
+
lookupFailed: true,
|
|
357
|
+
auth:
|
|
358
|
+
pendingRequest === null
|
|
359
|
+
? {
|
|
360
|
+
id: 'auth',
|
|
361
|
+
title: 'Account',
|
|
362
|
+
status: 'gap',
|
|
363
|
+
detail: `this account does not have access to Notifai yet${who}`,
|
|
364
|
+
remedy: { by: 'user-elsewhere', summary: next },
|
|
365
|
+
}
|
|
366
|
+
: {
|
|
367
|
+
id: 'auth',
|
|
368
|
+
title: 'Account',
|
|
369
|
+
status: 'gap',
|
|
370
|
+
detail: `access requested on ${pendingRequest.requested_at.slice(0, 10)}${who} — waiting to be granted`,
|
|
371
|
+
remedy: {
|
|
372
|
+
by: 'user-elsewhere',
|
|
373
|
+
summary:
|
|
374
|
+
'nothing is needed from you; this resumes on the next run once access is granted',
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (devices !== null) {
|
|
381
|
+
return {
|
|
382
|
+
email,
|
|
383
|
+
devices,
|
|
384
|
+
lookupFailed: false,
|
|
385
|
+
auth: {
|
|
386
|
+
id: 'auth',
|
|
387
|
+
title: 'Account',
|
|
388
|
+
status: 'ready',
|
|
389
|
+
detail: email
|
|
390
|
+
? `machine ${credential.machineId} accepted (${email})`
|
|
391
|
+
: `machine ${credential.machineId} accepted`,
|
|
392
|
+
},
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const err = devicesErr ?? (accessOutcome.status === 'rejected' ? accessOutcome.reason : null)
|
|
397
|
+
return {
|
|
398
|
+
email: null,
|
|
399
|
+
devices: null,
|
|
400
|
+
lookupFailed: true,
|
|
401
|
+
auth: {
|
|
402
|
+
id: 'auth',
|
|
403
|
+
title: 'Account',
|
|
404
|
+
status: 'gap',
|
|
405
|
+
detail: err instanceof ApiCallError ? `${err.code}: ${err.message}` : String(err),
|
|
406
|
+
remedy: {
|
|
407
|
+
by: 'user-here',
|
|
408
|
+
summary: 'this machine is no longer recognised; pair it again',
|
|
409
|
+
command: SETUP_COMMAND,
|
|
410
|
+
interactive: true,
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
306
416
|
/**
|
|
307
417
|
* Read the whole setup once, in dependency order.
|
|
308
418
|
*
|
|
@@ -378,7 +488,7 @@ export async function assessReadiness(
|
|
|
378
488
|
remedy: {
|
|
379
489
|
by: 'user-here',
|
|
380
490
|
summary: 'sign in — this opens your browser to approve the machine',
|
|
381
|
-
command:
|
|
491
|
+
command: SETUP_COMMAND,
|
|
382
492
|
interactive: true,
|
|
383
493
|
},
|
|
384
494
|
},
|
|
@@ -426,7 +536,7 @@ export async function assessReadiness(
|
|
|
426
536
|
baseUrl,
|
|
427
537
|
`Bearer nfm_${credential.machineId}.${credential.secret}`,
|
|
428
538
|
)
|
|
429
|
-
states.push(await compatibilityCheck(compatibilityClient))
|
|
539
|
+
states.push(await compatibilityCheck(compatibilityClient, deps))
|
|
430
540
|
}
|
|
431
541
|
|
|
432
542
|
let accountEmail: string | null = null
|
|
@@ -437,39 +547,11 @@ export async function assessReadiness(
|
|
|
437
547
|
} else {
|
|
438
548
|
const client = makeClient(deps, baseUrl, `Bearer nfm_${credential.machineId}.${credential.secret}`)
|
|
439
549
|
accountClient = client
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
.catch(() => null as string | null),
|
|
446
|
-
])
|
|
447
|
-
accountDevices = devices
|
|
448
|
-
accountEmail = email
|
|
449
|
-
states.push({
|
|
450
|
-
id: 'auth',
|
|
451
|
-
title: 'Account',
|
|
452
|
-
status: 'ready',
|
|
453
|
-
detail: accountEmail
|
|
454
|
-
? `machine ${credential.machineId} accepted (${accountEmail})`
|
|
455
|
-
: `machine ${credential.machineId} accepted`,
|
|
456
|
-
})
|
|
457
|
-
} catch (err) {
|
|
458
|
-
// A credential the server rejects is revocation, not absence, and the
|
|
459
|
-
// remedy is the same sign-in either way.
|
|
460
|
-
accountLookupFailed = true
|
|
461
|
-
states.push({
|
|
462
|
-
id: 'auth',
|
|
463
|
-
title: 'Account',
|
|
464
|
-
status: 'gap',
|
|
465
|
-
detail: err instanceof ApiCallError ? `${err.code}: ${err.message}` : String(err),
|
|
466
|
-
remedy: {
|
|
467
|
-
by: 'user-here',
|
|
468
|
-
summary: 'this machine is no longer recognised; pair it again',
|
|
469
|
-
command: 'notifai login',
|
|
470
|
-
},
|
|
471
|
-
})
|
|
472
|
-
}
|
|
550
|
+
const probed = await probeAccount(client, credential, baseUrl)
|
|
551
|
+
accountDevices = probed.devices
|
|
552
|
+
accountEmail = probed.email
|
|
553
|
+
accountLookupFailed = probed.lookupFailed
|
|
554
|
+
states.push(probed.auth)
|
|
473
555
|
}
|
|
474
556
|
|
|
475
557
|
// Optional setup that works without a companion device must appear before the
|
|
@@ -509,7 +591,7 @@ export async function assessReadiness(
|
|
|
509
591
|
// dashboard origin — not a placeholder, and not typed by hand.
|
|
510
592
|
detail:
|
|
511
593
|
companionDevices.length === 0
|
|
512
|
-
?
|
|
594
|
+
? 'no active Companion App registered yet'
|
|
513
595
|
: `${companionDevices.map((d) => `${d.display_name} (${d.platform}, ${d.permission_status})`).join(', ')} — registered but not able to receive`,
|
|
514
596
|
remedy: {
|
|
515
597
|
by: 'user-elsewhere',
|
|
@@ -553,7 +635,7 @@ async function applyRegistryRecommendation(
|
|
|
553
635
|
contract.remedy = {
|
|
554
636
|
by: 'user-here',
|
|
555
637
|
summary: 'update Notifai',
|
|
556
|
-
command:
|
|
638
|
+
command: updateCliCommand(deps),
|
|
557
639
|
}
|
|
558
640
|
}
|
|
559
641
|
|
|
@@ -684,8 +766,12 @@ export async function doctorCommand(
|
|
|
684
766
|
if (s.id === 'contract') {
|
|
685
767
|
if (s.status === 'ready' || s.status === 'unknown') continue
|
|
686
768
|
deps.io.out(s.detail)
|
|
687
|
-
if (
|
|
688
|
-
|
|
769
|
+
if (
|
|
770
|
+
s.remedy?.by !== 'user-elsewhere' &&
|
|
771
|
+
s.remedy?.summary === 'update Notifai' &&
|
|
772
|
+
s.remedy.command !== undefined
|
|
773
|
+
) {
|
|
774
|
+
deps.io.out(s.remedy.command)
|
|
689
775
|
}
|
|
690
776
|
continue
|
|
691
777
|
}
|
|
@@ -754,14 +840,15 @@ export function remedyLine(state: ReadinessState): string {
|
|
|
754
840
|
* way and is not worth re-deriving. The judgment added here is which failures
|
|
755
841
|
* actually stand in the way.
|
|
756
842
|
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
843
|
+
* `doctor` is a report and keeps its strict verdict here: a Codex Stop handler
|
|
844
|
+
* that would swallow an answer is a real failure and reads as one. `init` is
|
|
845
|
+
* the other consumer, and it treats every state in this group as a report line
|
|
846
|
+
* rather than a blocker — Question Routing is optional automation, and nothing
|
|
847
|
+
* optional may stand between a send-capable setup and its delivery proof.
|
|
848
|
+
*
|
|
849
|
+
* `reportOnly` marks the checks that no command can close, only the passage of
|
|
850
|
+
* a turn. Those are report lines even in `doctor`, because "look again" is not
|
|
851
|
+
* a remedy.
|
|
765
852
|
*/
|
|
766
853
|
function hookStates(deps: CommandDeps): ReadinessState[] {
|
|
767
854
|
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform)
|
|
@@ -863,23 +950,21 @@ function checkTitle(name: string): string {
|
|
|
863
950
|
return CHECK_TITLES[name] ?? name
|
|
864
951
|
}
|
|
865
952
|
|
|
866
|
-
/** Real but not in the way; see the note above. */
|
|
867
|
-
const informational = new Set<string>()
|
|
868
953
|
return [
|
|
869
954
|
...hookChecks(deps).map((check) => ({
|
|
870
955
|
id: check.name.replace(/[ ()]+/g, '-').replace(/-$/, ''),
|
|
871
956
|
title: checkTitle(check.name),
|
|
872
957
|
status: check.ok
|
|
873
|
-
? 'ready' as const
|
|
874
|
-
: check.
|
|
875
|
-
? 'optional-gap' as const
|
|
876
|
-
: 'gap' as const,
|
|
958
|
+
? ('ready' as const)
|
|
959
|
+
: check.reportOnly === true
|
|
960
|
+
? ('optional-gap' as const)
|
|
961
|
+
: ('gap' as const),
|
|
877
962
|
detail: check.detail,
|
|
963
|
+
...(check.technical === undefined ? {} : { technical: check.technical }),
|
|
878
964
|
...(check.ok
|
|
879
965
|
? {}
|
|
880
966
|
: {
|
|
881
967
|
remedy: {
|
|
882
|
-
by: 'user-here' as const,
|
|
883
968
|
// The check's own remedy when it has one; the generic reinstall
|
|
884
969
|
// line was wrong exactly where it mattered (an unfired pointer
|
|
885
970
|
// needs a prompt, not `hooks install`).
|
|
@@ -887,6 +972,7 @@ function checkTitle(name: string): string {
|
|
|
887
972
|
summary: 'the detail above names what to change',
|
|
888
973
|
command: 'notifai hooks install',
|
|
889
974
|
}),
|
|
975
|
+
by: check.remedy?.by ?? ('user-here' as const),
|
|
890
976
|
},
|
|
891
977
|
}),
|
|
892
978
|
})),
|
|
@@ -896,13 +982,15 @@ function checkTitle(name: string): string {
|
|
|
896
982
|
|
|
897
983
|
|
|
898
984
|
interface HookCheck {
|
|
985
|
+
/** No command closes this; only a turn that has not happened yet. */
|
|
986
|
+
reportOnly?: boolean
|
|
899
987
|
name: string
|
|
900
988
|
ok: boolean
|
|
901
989
|
detail: string
|
|
902
|
-
|
|
903
|
-
informational?: boolean
|
|
990
|
+
technical?: unknown
|
|
904
991
|
/** A remedy truer than the generic `notifai hooks install`. */
|
|
905
992
|
remedy?: {
|
|
993
|
+
by?: 'cli' | 'user-here'
|
|
906
994
|
summary: string
|
|
907
995
|
command: string
|
|
908
996
|
user_action?: { code: string; harness: string; action: string; message: string }
|
|
@@ -937,7 +1025,7 @@ function hookChecks(deps: CommandDeps): HookCheck[] {
|
|
|
937
1025
|
checks.push({
|
|
938
1026
|
name: 'hooks (detected)',
|
|
939
1027
|
ok: false,
|
|
940
|
-
|
|
1028
|
+
reportOnly: true,
|
|
941
1029
|
detail: `${unwired.map((harness) => HARNESS_LABELS[harness]).join(', ')} detected on this machine but not wired`,
|
|
942
1030
|
remedy: {
|
|
943
1031
|
summary: 'install hooks for every detected harness',
|
|
@@ -959,14 +1047,14 @@ function hookChecks(deps: CommandDeps): HookCheck[] {
|
|
|
959
1047
|
checks.push({
|
|
960
1048
|
name: 'hooks (active harness)',
|
|
961
1049
|
ok: false,
|
|
962
|
-
|
|
1050
|
+
reportOnly: true,
|
|
963
1051
|
detail: `Several harness sessions could own this shell (${contested.map((candidate) => candidate.label).join(', ')}); routing readiness is intentionally not attributed to either one`,
|
|
964
1052
|
})
|
|
965
1053
|
} else if (active !== null && !isHookInstallableHarness(active.harness)) {
|
|
966
1054
|
checks.push({
|
|
967
1055
|
name: 'hooks (active harness)',
|
|
968
1056
|
ok: true,
|
|
969
|
-
|
|
1057
|
+
reportOnly: true,
|
|
970
1058
|
detail: `${active.label}: ${HERMES_QUESTION_ROUTING_UNAVAILABLE.deliveryContract}`,
|
|
971
1059
|
})
|
|
972
1060
|
} else if (active !== null) {
|
|
@@ -999,7 +1087,7 @@ function hookChecks(deps: CommandDeps): HookCheck[] {
|
|
|
999
1087
|
checks.push({
|
|
1000
1088
|
name: 'hooks (active session)',
|
|
1001
1089
|
ok: false,
|
|
1002
|
-
|
|
1090
|
+
reportOnly: true,
|
|
1003
1091
|
detail: `active ${active.label} session has not published exact lifecycle state — send one ${active.label} prompt, then check again`,
|
|
1004
1092
|
remedy: {
|
|
1005
1093
|
summary: `send one ${active.label} prompt — its hook publishes the routing pointer`,
|
|
@@ -1052,14 +1140,47 @@ function hookChecks(deps: CommandDeps): HookCheck[] {
|
|
|
1052
1140
|
)
|
|
1053
1141
|
const sharedAdapter = inspectHookAdapter(deps.hookAdapterHome, deps.hookPlatform)
|
|
1054
1142
|
adapterProblems.push(...sharedAdapter.problems)
|
|
1143
|
+
const runningTarget = deps.hookInstallTarget
|
|
1144
|
+
const runningArtifact =
|
|
1145
|
+
runningTarget !== undefined && !isNpxAdapterTarget(runningTarget)
|
|
1146
|
+
? runningTarget.scriptPath
|
|
1147
|
+
: process.argv[1]
|
|
1148
|
+
const cliInstallations = inspectCliInstallations(
|
|
1149
|
+
deps.env,
|
|
1150
|
+
deps.hookPlatform ?? process.platform,
|
|
1151
|
+
{
|
|
1152
|
+
...(runningArtifact === undefined ? {} : { runningArtifactPath: runningArtifact }),
|
|
1153
|
+
currentVersion: packageVersion(),
|
|
1154
|
+
},
|
|
1155
|
+
)
|
|
1156
|
+
const effectiveArtifact = cliInstallations.effective?.artifact_path ?? null
|
|
1157
|
+
const adapterMismatch =
|
|
1158
|
+
sharedAdapter.problems.length === 0 &&
|
|
1159
|
+
effectiveArtifact !== null &&
|
|
1160
|
+
!hookAdapterTargetsArtifact(sharedAdapter.target, effectiveArtifact)
|
|
1161
|
+
if (adapterMismatch) {
|
|
1162
|
+
adapterProblems.push('the registered CLI does not match the effective notifai command')
|
|
1163
|
+
}
|
|
1055
1164
|
if (adapterProblems.length > 0) {
|
|
1056
1165
|
checks.push({
|
|
1057
1166
|
name: 'hooks (adapter)',
|
|
1058
1167
|
ok: false,
|
|
1059
1168
|
// A machine-global install for a harness that is not active in this
|
|
1060
|
-
// project is useful diagnosis, but it
|
|
1061
|
-
|
|
1169
|
+
// project is useful diagnosis, but it is not this project's failure.
|
|
1170
|
+
reportOnly: active === null && installations.every((installation) => installation.global),
|
|
1062
1171
|
detail: adapterProblems.join('; '),
|
|
1172
|
+
...(adapterMismatch
|
|
1173
|
+
? { technical: { effective_cli_artifact: effectiveArtifact, registered_target: sharedAdapter.target } }
|
|
1174
|
+
: {}),
|
|
1175
|
+
...(adapterMismatch && adapterProblems.length === 1
|
|
1176
|
+
? {
|
|
1177
|
+
remedy: {
|
|
1178
|
+
by: 'cli' as const,
|
|
1179
|
+
summary: 'retarget Question Routing to the effective notifai command',
|
|
1180
|
+
command: 'notifai init',
|
|
1181
|
+
},
|
|
1182
|
+
}
|
|
1183
|
+
: {}),
|
|
1063
1184
|
})
|
|
1064
1185
|
}
|
|
1065
1186
|
|
|
@@ -1215,9 +1336,10 @@ function hookChecks(deps: CommandDeps): HookCheck[] {
|
|
|
1215
1336
|
checks.push({
|
|
1216
1337
|
name: 'hooks (fired)',
|
|
1217
1338
|
ok: fired,
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1220
|
-
|
|
1339
|
+
// Nothing here is a command. Whether the pointer is absent or the turn is
|
|
1340
|
+
// half-finished, the only thing that closes this is a turn ending — which
|
|
1341
|
+
// an agent running `init` inside that very turn cannot supply.
|
|
1342
|
+
reportOnly: true,
|
|
1221
1343
|
detail: fired
|
|
1222
1344
|
? active === null
|
|
1223
1345
|
? 'a session in this directory has run UserPromptSubmit and Stop'
|
|
@@ -1258,7 +1380,7 @@ function hookChecks(deps: CommandDeps): HookCheck[] {
|
|
|
1258
1380
|
continuationHarnesses.every(
|
|
1259
1381
|
(harness) => HARNESS_CAPABILITIES[harness].stopContinuation !== 'unsupported',
|
|
1260
1382
|
),
|
|
1261
|
-
|
|
1383
|
+
reportOnly: active === null,
|
|
1262
1384
|
detail:
|
|
1263
1385
|
continuationHarnesses.length === 0
|
|
1264
1386
|
? active === null
|
|
@@ -1315,7 +1437,7 @@ function wakeRouteCheck(
|
|
|
1315
1437
|
return {
|
|
1316
1438
|
name: 'hooks (wake route)',
|
|
1317
1439
|
ok: readiness.state === 'ready',
|
|
1318
|
-
|
|
1440
|
+
reportOnly: true,
|
|
1319
1441
|
detail:
|
|
1320
1442
|
readiness.state === 'ready'
|
|
1321
1443
|
? `this Claude Code ${readiness.version} session is listening on ${readiness.socketPath}, so an answer can start a turn here without you`
|
|
@@ -1330,7 +1452,7 @@ function wakeRouteCheck(
|
|
|
1330
1452
|
return {
|
|
1331
1453
|
name: 'hooks (wake route)',
|
|
1332
1454
|
ok: readiness.state === 'ready',
|
|
1333
|
-
|
|
1455
|
+
reportOnly: true,
|
|
1334
1456
|
detail:
|
|
1335
1457
|
readiness.state === 'ready'
|
|
1336
1458
|
? `the held Codex turn continues from its own hook, and after it returns ${readiness.lockDirectory} can prove a stopped thread unowned before resuming it`
|
|
@@ -51,6 +51,14 @@ function harnessEnvCandidates(env: NodeJS.ProcessEnv): ActiveHarnessSession[] {
|
|
|
51
51
|
...(sessionLabelPending ? { sessionLabelPending: true } : {}),
|
|
52
52
|
})
|
|
53
53
|
}
|
|
54
|
+
if (env['NOTIFAI_ACTIVE_HARNESS'] === 'openclaw') {
|
|
55
|
+
const sessionId = env['NOTIFAI_ACTIVE_SESSION_ID']
|
|
56
|
+
candidates.push({
|
|
57
|
+
harness: 'openclaw',
|
|
58
|
+
label: 'OpenClaw',
|
|
59
|
+
...(sessionId === undefined || sessionId === '' ? {} : { sessionId }),
|
|
60
|
+
})
|
|
61
|
+
}
|
|
54
62
|
if (env['CLAUDECODE'] === '1') {
|
|
55
63
|
const sessionId = env['CLAUDE_CODE_SESSION_ID']
|
|
56
64
|
candidates.push({
|