@raidiant/notifai 10.1.6 → 11.0.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.
Files changed (161) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +28 -16
  3. package/dist/claude-wake.d.ts +1 -2
  4. package/dist/claude-wake.js +15 -61
  5. package/dist/claude-wake.js.map +1 -1
  6. package/dist/cli-bin.js +3 -14
  7. package/dist/cli-bin.js.map +1 -1
  8. package/dist/cli-contract.d.ts +12 -0
  9. package/dist/cli-contract.js +19 -0
  10. package/dist/cli-contract.js.map +1 -0
  11. package/dist/cli-release.d.ts +0 -1
  12. package/dist/cli-release.js +5 -21
  13. package/dist/cli-release.js.map +1 -1
  14. package/dist/client.d.ts +2 -2
  15. package/dist/codex-wake.d.ts +1 -1
  16. package/dist/codex-wake.js +8 -36
  17. package/dist/codex-wake.js.map +1 -1
  18. package/dist/command-session.js +2 -1
  19. package/dist/command-session.js.map +1 -1
  20. package/dist/commands-acknowledge.d.ts +13 -0
  21. package/dist/commands-acknowledge.js +67 -0
  22. package/dist/commands-acknowledge.js.map +1 -0
  23. package/dist/commands-ask.d.ts +63 -0
  24. package/dist/commands-ask.js +407 -0
  25. package/dist/commands-ask.js.map +1 -0
  26. package/dist/commands-close.d.ts +6 -0
  27. package/dist/commands-close.js +197 -0
  28. package/dist/commands-close.js.map +1 -0
  29. package/dist/commands-config.js +2 -2
  30. package/dist/commands-config.js.map +1 -1
  31. package/dist/commands-core.js +2 -2
  32. package/dist/commands-core.js.map +1 -1
  33. package/dist/commands-doctor.js +89 -134
  34. package/dist/commands-doctor.js.map +1 -1
  35. package/dist/commands-harness-context.d.ts +0 -1
  36. package/dist/commands-harness-context.js +2 -4
  37. package/dist/commands-harness-context.js.map +1 -1
  38. package/dist/commands-hook-diagnostics.d.ts +27 -0
  39. package/dist/commands-hook-diagnostics.js +97 -0
  40. package/dist/commands-hook-diagnostics.js.map +1 -0
  41. package/dist/commands-hook-install.d.ts +26 -0
  42. package/dist/commands-hook-install.js +549 -0
  43. package/dist/commands-hook-install.js.map +1 -0
  44. package/dist/commands-hook-run.d.ts +29 -0
  45. package/dist/commands-hook-run.js +517 -0
  46. package/dist/commands-hook-run.js.map +1 -0
  47. package/dist/commands-init.d.ts +2 -7
  48. package/dist/commands-init.js +59 -92
  49. package/dist/commands-init.js.map +1 -1
  50. package/dist/commands-send.js +35 -3
  51. package/dist/commands-send.js.map +1 -1
  52. package/dist/commands-skill.js +5 -5
  53. package/dist/commands-skill.js.map +1 -1
  54. package/dist/commands-update.js +17 -31
  55. package/dist/commands-update.js.map +1 -1
  56. package/dist/commands.d.ts +11 -6
  57. package/dist/commands.js +9 -4
  58. package/dist/commands.js.map +1 -1
  59. package/dist/config-schema.js +2 -2
  60. package/dist/config-schema.js.map +1 -1
  61. package/dist/config.d.ts +14 -4
  62. package/dist/config.js +35 -9
  63. package/dist/config.js.map +1 -1
  64. package/dist/file-lock.d.ts +3 -0
  65. package/dist/file-lock.js +33 -17
  66. package/dist/file-lock.js.map +1 -1
  67. package/dist/guidance-content.js +30 -15
  68. package/dist/guidance-content.js.map +1 -1
  69. package/dist/harnesses.d.ts +2 -2
  70. package/dist/harnesses.js +10 -2
  71. package/dist/harnesses.js.map +1 -1
  72. package/dist/hook-acknowledgements.d.ts +42 -0
  73. package/dist/hook-acknowledgements.js +316 -0
  74. package/dist/hook-acknowledgements.js.map +1 -0
  75. package/dist/hook-adapter.js +7 -4
  76. package/dist/hook-adapter.js.map +1 -1
  77. package/dist/hook-events.d.ts +6 -0
  78. package/dist/hook-events.js +22 -0
  79. package/dist/hook-events.js.map +1 -0
  80. package/dist/hook-gates.d.ts +7 -0
  81. package/dist/hook-gates.js +21 -0
  82. package/dist/hook-gates.js.map +1 -0
  83. package/dist/hook-lifecycle.d.ts +93 -0
  84. package/dist/{hooks.js → hook-lifecycle.js} +61 -1359
  85. package/dist/hook-lifecycle.js.map +1 -0
  86. package/dist/hook-project-sessions.d.ts +28 -0
  87. package/dist/hook-project-sessions.js +141 -0
  88. package/dist/hook-project-sessions.js.map +1 -0
  89. package/dist/hook-question-lock.d.ts +7 -0
  90. package/dist/hook-question-lock.js +173 -0
  91. package/dist/hook-question-lock.js.map +1 -0
  92. package/dist/hook-question-retirement.d.ts +57 -0
  93. package/dist/hook-question-retirement.js +261 -0
  94. package/dist/hook-question-retirement.js.map +1 -0
  95. package/dist/hook-question-state.d.ts +45 -0
  96. package/dist/hook-question-state.js +196 -0
  97. package/dist/hook-question-state.js.map +1 -0
  98. package/dist/hook-session-state.d.ts +31 -0
  99. package/dist/hook-session-state.js +249 -0
  100. package/dist/hook-session-state.js.map +1 -0
  101. package/dist/{hooks.d.ts → hook-types.d.ts} +17 -236
  102. package/dist/hook-types.js +4 -0
  103. package/dist/hook-types.js.map +1 -0
  104. package/dist/install-hooks.d.ts +72 -60
  105. package/dist/install-hooks.js +292 -211
  106. package/dist/install-hooks.js.map +1 -1
  107. package/dist/interactive.d.ts +3 -16
  108. package/dist/interactive.js +19 -95
  109. package/dist/interactive.js.map +1 -1
  110. package/dist/local-path.d.ts +7 -0
  111. package/dist/local-path.js +27 -0
  112. package/dist/local-path.js.map +1 -0
  113. package/dist/node-floor.js +3 -10
  114. package/dist/node-floor.js.map +1 -1
  115. package/dist/npm-invocation.d.ts +34 -0
  116. package/dist/npm-invocation.js +53 -0
  117. package/dist/npm-invocation.js.map +1 -0
  118. package/dist/openclaw-plugin.d.ts +12 -3
  119. package/dist/openclaw-plugin.js +42 -16
  120. package/dist/openclaw-plugin.js.map +1 -1
  121. package/dist/opencode-plugin.d.ts +2 -2
  122. package/dist/opencode-plugin.js +4 -6
  123. package/dist/opencode-plugin.js.map +1 -1
  124. package/dist/program.js +11 -13
  125. package/dist/program.js.map +1 -1
  126. package/dist/project-enablement.d.ts +6 -0
  127. package/dist/project-enablement.js +11 -27
  128. package/dist/project-enablement.js.map +1 -1
  129. package/dist/question-settlement-process.d.ts +1 -1
  130. package/dist/readiness.d.ts +3 -3
  131. package/dist/readiness.js +14 -6
  132. package/dist/readiness.js.map +1 -1
  133. package/dist/send.d.ts +8 -4
  134. package/dist/send.js +145 -26
  135. package/dist/send.js.map +1 -1
  136. package/dist/session-activation.d.ts +3 -1
  137. package/dist/session-activation.js +16 -0
  138. package/dist/session-activation.js.map +1 -1
  139. package/dist/skill-source/manifest.json +4 -4
  140. package/dist/skill-source/notifai/SKILL.md +67 -63
  141. package/dist/skill-source/notifai/references/harness-setup.md +62 -28
  142. package/dist/ui/config-view.d.ts +0 -2
  143. package/dist/ui/config-view.js +1 -5
  144. package/dist/ui/config-view.js.map +1 -1
  145. package/dist/ui/theme.d.ts +0 -3
  146. package/dist/ui/theme.js +0 -1
  147. package/dist/ui/theme.js.map +1 -1
  148. package/dist/version.d.ts +22 -0
  149. package/dist/version.js +51 -0
  150. package/dist/version.js.map +1 -0
  151. package/dist/wake-support.d.ts +8 -0
  152. package/dist/wake-support.js +38 -0
  153. package/dist/wake-support.js.map +1 -0
  154. package/package.json +2 -2
  155. package/dist/cli-update-contract.d.ts +0 -7
  156. package/dist/cli-update-contract.js +0 -10
  157. package/dist/cli-update-contract.js.map +0 -1
  158. package/dist/commands-hooks.d.ts +0 -160
  159. package/dist/commands-hooks.js +0 -1798
  160. package/dist/commands-hooks.js.map +0 -1
  161. package/dist/hooks.js.map +0 -1
@@ -1,14 +1,14 @@
1
1
  import { NOTIFICATION_CONTRACT_FINGERPRINT, PLATFORMS, SHIPPED_CLI_CAPABILITIES, } from '@raidiant/notifai-protocol';
2
2
  import { existsSync } from 'node:fs';
3
- import path from 'node:path';
4
3
  import { inspectClaudeInbox, systemClaudeWakeAdapters } from './claude-wake.js';
5
4
  import { ApiCallError } from './client.js';
6
5
  import { inspectCodexResume } from './codex-wake.js';
7
6
  import {} from './config.js';
8
- import { HARNESS_CAPABILITIES, HARNESS_LABELS, HERMES_QUESTION_ROUTING_UNAVAILABLE, isHookInstallableHarness, } from './harnesses.js';
7
+ import { HARNESS_LABELS, HERMES_QUESTION_ROUTING_UNAVAILABLE, isHookInstallableHarness, questionRoutingCapability, } from './harnesses.js';
9
8
  import { hookAdapterTargetsArtifact, inspectHookAdapter, isNpxAdapterTarget, } from './hook-adapter.js';
10
- import { readLiveProjectSessionPointers, readProjectSessionPointer, readSessionState, } from './hooks.js';
11
- import { NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS, QUESTION_STOP_TIMEOUT_SECONDS, codexCoexistenceNotes, codexHomeNote, codexLayerDir, codexLayerPaths, codexProjectRoot, codexRepresentationProblems, codexTrustProblems, detectedHarnesses, findInstallations, handlerEvent, inspectCodexLayer, } from './install-hooks.js';
9
+ import { readLiveProjectSessionPointers, readProjectSessionPointer, } from './hook-project-sessions.js';
10
+ import { readSessionState } from './hook-session-state.js';
11
+ import { NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS, QUESTION_STOP_TIMEOUT_SECONDS, codexCoexistenceNotes, codexHomeNote, codexRepresentationProblems, codexTrustProblems, detectedHarnesses, findInstallations, findLegacyProjectInstallations, handlerEvent, } from './install-hooks.js';
12
12
  import { inferInvocationContext } from './invocation-context.js';
13
13
  import { firstBlocker, readinessJson, } from './readiness.js';
14
14
  import { packageVersion } from './release.js';
@@ -17,12 +17,14 @@ import { deviceInstallRemedy, readyCompanionDevices } from './commands-devices.j
17
17
  import { setupAccessUrl } from './setup-destinations.js';
18
18
  import { claudeSessionPid, resolveActiveHarness, } from './commands-harness-context.js';
19
19
  import { stopShapeProblems } from './commands-hook-shape.js';
20
- import { CODEX_ACTIVATION_INSTALLATION_MISSING_PROBLEM, CODEX_FRESH_SESSION_USER_ACTION, CODEX_HOOK_APPROVAL_USER_ACTION, CODEX_STOP_DEFINITION_NOT_SINGULAR_PROBLEM, CODEX_STALE_STOP_DEFINITION_PROBLEM, HOOK_EVENTS, activeQuestionRouteProblems, hookActivationAdvice, requiredHookEvents, } from './commands-hooks.js';
20
+ import { CODEX_FRESH_SESSION_USER_ACTION, CODEX_HOOK_APPROVAL_USER_ACTION, CODEX_STOP_DEFINITION_NOT_SINGULAR_PROBLEM, CODEX_STALE_STOP_DEFINITION_PROBLEM, activeQuestionRouteProblems, hookActivationAdvice, } from './commands-hook-diagnostics.js';
21
+ import { HOOK_EVENTS, requiredHookEvents } from './hook-events.js';
21
22
  import { cliBinReadiness, inspectCliInstallations } from './cli-bin.js';
22
23
  import { latestPublishedCliVersion, newerPublishedCli, shouldConsultCliRegistry, thisCliVersion, } from './cli-release.js';
23
24
  import { SETUP_PROOF_STALE_MS, observedCompanionReceipt, readSetupProof, setupProofProject, setupProofIsStale, setupProofApplies, } from './commands-setup-proof.js';
24
25
  import { skillReadiness } from './commands-skill.js';
25
26
  import { projectBinding, projectEnabled } from './project-enablement.js';
27
+ import { CLI_UPDATE_AVAILABLE, SERVICE_UPDATE_IN_PROGRESS } from './cli-contract.js';
26
28
  // ---------------------------------------------------------------------------
27
29
  // doctor
28
30
  // ---------------------------------------------------------------------------
@@ -84,7 +86,7 @@ async function compatibilityCheck(client, deps) {
84
86
  status: 'optional-gap',
85
87
  detail: scheduled
86
88
  ? 'Update Notifai soon to keep sending notifications.'
87
- : 'A newer Notifai is available.',
89
+ : CLI_UPDATE_AVAILABLE,
88
90
  technical,
89
91
  remedy: {
90
92
  by: 'user-here',
@@ -98,7 +100,7 @@ async function compatibilityCheck(client, deps) {
98
100
  id: 'contract',
99
101
  title: 'Notifai update',
100
102
  status: 'optional-gap',
101
- detail: 'The service is being updated; try again later.',
103
+ detail: SERVICE_UPDATE_IN_PROGRESS,
102
104
  technical,
103
105
  remedy: {
104
106
  by: 'user-here',
@@ -117,7 +119,7 @@ async function compatibilityCheck(client, deps) {
117
119
  id: 'contract',
118
120
  title: 'Notifai update',
119
121
  status: 'optional-gap',
120
- detail: 'The service is being updated; try again later.',
122
+ detail: SERVICE_UPDATE_IN_PROGRESS,
121
123
  technical,
122
124
  remedy: {
123
125
  by: 'user-here',
@@ -139,7 +141,7 @@ async function compatibilityCheck(client, deps) {
139
141
  id: 'contract',
140
142
  title: 'Notifai update',
141
143
  status: 'optional-gap',
142
- detail: 'The service is being updated; try again later.',
144
+ detail: SERVICE_UPDATE_IN_PROGRESS,
143
145
  technical: {
144
146
  error: err instanceof ApiCallError
145
147
  ? { code: err.code, status: err.status }
@@ -168,13 +170,8 @@ function projectReadiness(deps, config) {
168
170
  return {
169
171
  id: 'project',
170
172
  title: 'Project identity',
171
- status: 'optional-gap',
172
- detail: `"${inferred}" is inferred for each send; init can stamp it into shared config`,
173
- remedy: {
174
- by: 'cli',
175
- summary: 'make the inferred Project identity explicit for every checkout',
176
- command: 'notifai init',
177
- },
173
+ status: 'ready',
174
+ detail: `"${inferred}" (inferred from Git or the current directory)`,
178
175
  };
179
176
  }
180
177
  return {
@@ -514,7 +511,7 @@ async function applyRegistryRecommendation(deps, json, states) {
514
511
  if (contract === undefined || contract.status === 'gap')
515
512
  return;
516
513
  contract.status = 'optional-gap';
517
- contract.detail = 'A newer Notifai is available.';
514
+ contract.detail = CLI_UPDATE_AVAILABLE;
518
515
  contract.remedy = {
519
516
  by: 'user-here',
520
517
  summary: 'update Notifai',
@@ -707,7 +704,7 @@ export function remedyLine(state) {
707
704
  * a remedy.
708
705
  */
709
706
  function hookStates(deps) {
710
- const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
707
+ const installations = findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform);
711
708
  const { active, contested } = resolveActiveHarness(deps.env, deps.cwd, (deps.now ?? Date.now)());
712
709
  const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
713
710
  const settings = {
@@ -787,7 +784,7 @@ function hookStates(deps) {
787
784
  'hooks (adapter)': 'Hook adapter',
788
785
  'hooks (trust)': 'Codex hook trust',
789
786
  'hooks (stop shape)': 'Turn-end hook shape',
790
- 'hooks (duplicates)': 'Duplicate hook installs',
787
+ 'hooks (legacy project install)': 'Leftover project hook install',
791
788
  'hooks (codex representation)': 'Codex hook representation',
792
789
  'hooks (question admission)': 'Question admission',
793
790
  'hooks (fired)': 'Hooks have run here',
@@ -808,13 +805,14 @@ function hookStates(deps) {
808
805
  : 'gap',
809
806
  detail: check.detail,
810
807
  ...(check.technical === undefined ? {} : { technical: check.technical }),
811
- ...(check.ok
808
+ ...(check.ok || (check.reportOnly === true && check.remedy === undefined)
812
809
  ? {}
813
810
  : {
814
811
  remedy: {
815
812
  // The check's own remedy when it has one; the generic reinstall
816
813
  // line was wrong exactly where it mattered (an unfired pointer
817
- // needs a prompt, not `hooks install`).
814
+ // needs a prompt, not `hooks install`). A report-only capability
815
+ // with no remedy is intentionally absent, not repairable.
818
816
  ...(check.remedy ?? {
819
817
  summary: 'the detail above names what to change',
820
818
  command: 'notifai hooks install',
@@ -828,7 +826,7 @@ function hookStates(deps) {
828
826
  }
829
827
  function hookChecks(deps) {
830
828
  const checks = [];
831
- const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
829
+ const installations = findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform);
832
830
  // Not having hooks is a setup someone chose, not a fault: `send` works
833
831
  // without them. A setup that cannot work is what deserves to go red.
834
832
  if (installations.length === 0) {
@@ -842,9 +840,7 @@ function hookChecks(deps) {
842
840
  checks.push({
843
841
  name: 'hooks',
844
842
  ok: true,
845
- detail: installations
846
- .map((i) => `${i.harness} ${i.global ? 'global' : 'project'} (${i.file})`)
847
- .join(', '),
843
+ detail: installations.map((i) => `${i.harness} (${i.file})`).join(', '),
848
844
  });
849
845
  const wired = new Set(installations.map((installation) => installation.harness));
850
846
  const unwired = detectedHarnesses(deps.cwd, deps.env).filter((harness) => !wired.has(harness));
@@ -973,9 +969,8 @@ function hookChecks(deps) {
973
969
  checks.push({
974
970
  name: 'hooks (adapter)',
975
971
  ok: false,
976
- // A machine-global install for a harness that is not active in this
977
- // project is useful diagnosis, but it is not this project's failure.
978
- reportOnly: active === null && installations.every((installation) => installation.global),
972
+ // The adapter is one Machine-level file every harness runs through, so
973
+ // a defect in it is always this machine's to fix and always fixable.
979
974
  detail: adapterProblems.join('; '),
980
975
  ...(adapterMismatch
981
976
  ? { technical: { effective_cli_artifact: effectiveArtifact, registered_target: sharedAdapter.target } }
@@ -1008,38 +1003,38 @@ function hookChecks(deps) {
1008
1003
  },
1009
1004
  }),
1010
1005
  });
1011
- const shapeProblems = installations.flatMap((installation) => stopShapeProblems(installation, deps.hookPlatform).map((problem) => `${problem} — run \`notifai hooks install --harness ${installation.harness}${installation.global ? ' --global' : ''}\``));
1006
+ const shapeProblems = installations.flatMap((installation) => stopShapeProblems(installation, deps.hookPlatform).map((problem) => `${problem} — run \`notifai hooks install --harness ${installation.harness}\``));
1012
1007
  checks.push({
1013
1008
  name: 'hooks (stop shape)',
1014
1009
  ok: shapeProblems.length === 0,
1015
1010
  detail: shapeProblems.length === 0
1016
- ? `every installed Stop handler declares the shape its harness needs: Claude Code async and Codex blocking with an explicit ${QUESTION_STOP_TIMEOUT_SECONDS}s full-window budget; non-routing blocking hosts ${NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS}s`
1011
+ ? `every installed Stop handler declares the shape its harness and host need: Claude Code async on POSIX and blocking on Windows, Codex blocking, each with an explicit ${QUESTION_STOP_TIMEOUT_SECONDS}s full-window budget; non-routing blocking hosts ${NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS}s`
1017
1012
  : shapeProblems.join('; '),
1018
1013
  });
1019
- // Project and global definitions for one harness both fire. Stable adapter
1020
- // identity deliberately makes their command bytes equal, so comparing
1021
- // command targets would now hide this duplicate rather than diagnose it.
1022
- // Cursor is the exception to harness independence because it may also load
1023
- // Claude's user hook file. The shared adapter detects Cursor's hook-only
1024
- // environment and makes that compatibility copy a no-op; the native Cursor
1025
- // definition remains the single routing owner.
1026
- const duplicated = [...new Set(installations.map((i) => i.harness))]
1027
- .map((harness) => ({
1028
- harness,
1029
- installations: installations.filter((i) => i.harness === harness),
1030
- }))
1031
- .filter((entry) => entry.installations.some((installation) => installation.global) &&
1032
- entry.installations.some((installation) => !installation.global));
1033
- if (duplicated.length > 0) {
1014
+ // A Project-scoped definition an older build wrote still fires beside the
1015
+ // Machine one, so each event runs twice and the Project copy keeps serving a
1016
+ // definition no install refreshes. Notifai no longer creates these and never
1017
+ // accepts one as a working route: it is a gap with an exact removal command.
1018
+ const legacy = findLegacyProjectInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
1019
+ if (legacy.length > 0) {
1020
+ const harnesses = [...new Set(legacy.map((installation) => installation.harness))];
1021
+ const command = harnesses
1022
+ .map((harness) => `notifai hooks install --harness ${harness}`)
1023
+ .join(' && ');
1034
1024
  checks.push({
1035
- name: 'hooks (duplicates)',
1025
+ name: 'hooks (legacy project install)',
1036
1026
  ok: false,
1037
- detail: duplicated
1038
- .map((entry) => `${entry.harness}: ${entry.installations.length} hook definitions are active, so each event will fire all of them. Keep either project or global routing and uninstall the other: ${entry.installations.map((installation) => installation.file).join(', ')}`)
1039
- .join('; '),
1027
+ detail: `${legacy
1028
+ .map((installation) => `${installation.harness} (${installation.file})`)
1029
+ .join(', ')} — Notifai installs for this machine only, and a leftover Project copy fires beside it. Run \`${command}\` to remove it`,
1030
+ remedy: {
1031
+ by: 'cli',
1032
+ summary: 'remove the leftover Project-scoped Notifai hooks',
1033
+ command,
1034
+ },
1040
1035
  });
1041
1036
  }
1042
- const representationProblems = codexRepresentationProblems(deps.cwd, deps.env, deps.hookPlatform);
1037
+ const representationProblems = codexRepresentationProblems(deps.env, deps.hookPlatform);
1043
1038
  if (representationProblems.length > 0) {
1044
1039
  checks.push({
1045
1040
  name: 'hooks (codex representation)',
@@ -1048,7 +1043,7 @@ function hookChecks(deps) {
1048
1043
  });
1049
1044
  }
1050
1045
  else {
1051
- const coexistence = codexCoexistenceNotes(deps.cwd, deps.env, deps.hookPlatform);
1046
+ const coexistence = codexCoexistenceNotes(deps.env, deps.hookPlatform);
1052
1047
  if (coexistence.length > 0) {
1053
1048
  checks.push({
1054
1049
  name: 'hooks (codex representation)',
@@ -1062,48 +1057,33 @@ function hookChecks(deps) {
1062
1057
  checks.push({ name: 'hooks (codex home)', ok: true, detail: codexHome });
1063
1058
  }
1064
1059
  if (active !== null && activeInstallations.length > 0) {
1065
- // Installation inventory belongs to the invocation checkout, while exact
1066
- // question admission belongs to the checkout whose definition activated
1067
- // this Agent Session. Keep those diagnostics separate just as ask does.
1068
- const activationCwd = active.sessionId === undefined
1069
- ? deps.cwd
1070
- : readSessionState(active.sessionId, deps.env).activation_cwd ?? deps.cwd;
1071
- const admissionDeps = activationCwd === deps.cwd ? deps : { ...deps, cwd: activationCwd };
1072
- const admissionInstallations = activationCwd === deps.cwd
1073
- ? installations
1074
- : findInstallations(activationCwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
1075
- const admissionProblems = activeQuestionRouteProblems(admissionDeps, active, admissionInstallations);
1060
+ // One Machine installation per harness means the inventory no longer
1061
+ // depends on which checkout activated the session, so admission reads the
1062
+ // same installations doctor already listed.
1063
+ const admissionProblems = activeQuestionRouteProblems(deps, active, installations);
1076
1064
  checks.push({
1077
1065
  name: 'hooks (question admission)',
1078
1066
  ok: admissionProblems.length === 0,
1079
1067
  detail: admissionProblems.length === 0
1080
1068
  ? `the active ${active.label} route is exact, current, singular, trusted where applicable, and bounded by a live owner`
1081
1069
  : admissionProblems.join('; '),
1082
- ...(admissionProblems.some((problem) => problem.startsWith(CODEX_ACTIVATION_INSTALLATION_MISSING_PROBLEM))
1070
+ ...(admissionProblems.includes(CODEX_STOP_DEFINITION_NOT_SINGULAR_PROBLEM)
1083
1071
  ? {
1084
1072
  remedy: {
1085
1073
  by: 'cli',
1086
- summary: `install Codex hooks from the Agent Session activation checkout ${activationCwd}`,
1074
+ summary: 'restore exactly one current Codex Stop definition',
1087
1075
  command: 'notifai hooks install --harness codex',
1088
1076
  },
1089
1077
  }
1090
- : admissionProblems.includes(CODEX_STOP_DEFINITION_NOT_SINGULAR_PROBLEM)
1078
+ : admissionProblems.includes(CODEX_STALE_STOP_DEFINITION_PROBLEM)
1091
1079
  ? {
1092
1080
  remedy: {
1093
- by: 'cli',
1094
- summary: 'restore exactly one current Codex Stop definition',
1095
- command: 'notifai hooks install --harness codex',
1081
+ summary: 'start one fresh Codex session so it loads the current trusted Stop definition',
1082
+ command: 'notifai doctor',
1083
+ user_action: CODEX_FRESH_SESSION_USER_ACTION,
1096
1084
  },
1097
1085
  }
1098
- : admissionProblems.includes(CODEX_STALE_STOP_DEFINITION_PROBLEM)
1099
- ? {
1100
- remedy: {
1101
- summary: 'start one fresh Codex session so it loads the current trusted Stop definition',
1102
- command: 'notifai doctor',
1103
- user_action: CODEX_FRESH_SESSION_USER_ACTION,
1104
- },
1105
- }
1106
- : {}),
1086
+ : {}),
1107
1087
  });
1108
1088
  }
1109
1089
  // Which route this judges, and which remedy it prints, must both belong to
@@ -1122,7 +1102,7 @@ function hookChecks(deps) {
1122
1102
  const firedState = firedPointer === null ? null : readSessionState(firedPointer.sessionId, deps.env);
1123
1103
  const promptFired = firedState?.last_prompt_at !== undefined;
1124
1104
  const stopFired = firedState?.last_stop_at !== undefined;
1125
- const fired = firedPointer !== null && promptFired && stopFired;
1105
+ const fired = firedPointer !== null && promptFired;
1126
1106
  // Installations for other harnesses are irrelevant to the active one, and an
1127
1107
  // active harness with none of its own has nothing to activate: say that
1128
1108
  // instead of advising a prompt in some other harness. When the environment
@@ -1142,14 +1122,15 @@ function hookChecks(deps) {
1142
1122
  checks.push({
1143
1123
  name: 'hooks (fired)',
1144
1124
  ok: fired,
1145
- // Nothing here is a command. Whether the pointer is absent or the turn is
1146
- // half-finished, the only thing that closes this is a turn ending — which
1147
- // an agent running `init` inside that very turn cannot supply.
1125
+ // UserPromptSubmit proves that this exact session reached the installed
1126
+ // lifecycle path. A historical Stop is only telemetry: requiring one made
1127
+ // a first-turn question impossible even after the current Stop definition,
1128
+ // trust, shape, and continuation owner had all been proven above.
1148
1129
  reportOnly: true,
1149
1130
  detail: fired
1150
1131
  ? active === null
1151
- ? 'a session in this directory has run UserPromptSubmit and Stop'
1152
- : `the active ${active.label} session has run UserPromptSubmit and Stop`
1132
+ ? `a session in this directory has run UserPromptSubmit and is ready for this turn's Stop${stopFired ? '; an earlier Stop was also observed' : ''}`
1133
+ : `the active ${active.label} session has run UserPromptSubmit and is ready for this turn's Stop${stopFired ? '; an earlier Stop was also observed' : ''}`
1153
1134
  : firedPointer === null
1154
1135
  ? `no ${contested.length > 1
1155
1136
  ? `session pointer for any harness whose markers are present here (${contested
@@ -1158,14 +1139,14 @@ function hookChecks(deps) {
1158
1139
  : active === null
1159
1140
  ? 'session pointer'
1160
1141
  : `${active.label} session pointer`} from the last 24 hours — ${activationAdvice}`
1161
- : `the routed session has fired ${promptFired ? 'UserPromptSubmit' : 'neither required event'}, but Stop has not been observed — end one harmless turn, send a new prompt, then check again`,
1142
+ : `the routed session has not fired UserPromptSubmit — send one prompt, then check again`,
1162
1143
  ...(fired
1163
1144
  ? {}
1164
1145
  : {
1165
1146
  remedy: {
1166
1147
  summary: firedPointer === null
1167
1148
  ? `send one ${active === null || contested.length > 1 ? '' : `${active.label} `}prompt in a session here, then re-check`
1168
- : 'end one harmless turn, send a new prompt, then re-check',
1149
+ : 'send one prompt in the routed session, then re-check',
1169
1150
  command: 'notifai doctor',
1170
1151
  },
1171
1152
  }),
@@ -1177,37 +1158,36 @@ function hookChecks(deps) {
1177
1158
  checks.push({
1178
1159
  name: 'hooks (answer continuation)',
1179
1160
  ok: continuationHarnesses.length > 0 &&
1180
- continuationHarnesses.every((harness) => HARNESS_CAPABILITIES[harness].stopContinuation !== 'unsupported'),
1161
+ continuationHarnesses.every((harness) => questionRoutingCapability(harness, deps.hookPlatform ?? process.platform)
1162
+ .stopContinuation !== 'unsupported'),
1181
1163
  reportOnly: active === null,
1182
1164
  detail: continuationHarnesses.length === 0
1183
1165
  ? active === null
1184
1166
  ? 'no installed harness route to assess'
1185
1167
  : `the active ${active.label} session has no matching continuation adapter`
1186
1168
  : continuationHarnesses
1187
- .map((harness) => `${harness}: ${HARNESS_CAPABILITIES[harness].deliveryContract}`)
1169
+ .map((harness) => `${harness}: ${questionRoutingCapability(harness, deps.hookPlatform ?? process.platform).deliveryContract}`)
1188
1170
  .join('; '),
1189
1171
  });
1190
1172
  const wakeRoute = wakeRouteCheck(deps, active, activeInstallations);
1191
1173
  if (wakeRoute !== null)
1192
1174
  checks.push(wakeRoute);
1193
- const stray = codexStrayWorktreeCheck(deps);
1194
- if (stray !== null)
1195
- checks.push(stray);
1196
1175
  return checks;
1197
1176
  }
1198
1177
  /**
1199
- * Whether an answer arriving after this turn ends could actually reach this
1200
- * exact session — the question `notifai ask` really turns on, and the one no
1201
- * other check answers.
1178
+ * Whether an answer arriving after this turn's ordinary continuation returns
1179
+ * could start another turn in this exact session without a new User prompt.
1180
+ * This is a named optional capability, separate from the held Stop
1181
+ * continuation that already makes Question Routing usable.
1202
1182
  *
1203
1183
  * Read-only, and deliberately so: nothing here connects to a socket, takes a
1204
1184
  * lock, or sends a message. A diagnostic that wakes the agent it is diagnosing
1205
1185
  * would be its own bug report.
1206
1186
  *
1207
- * Everything it can report negatively is a degradation rather than a failure —
1208
- * the accepted journal still replays the answer at the session's next turn —
1209
- * so this is never a blocker. What it buys is that the reason has a name
1210
- * before the user notices the silence.
1187
+ * Everything it can report negatively is optional rather than a routing
1188
+ * failure: a held continuation can still return the answer, or the accepted
1189
+ * journal can replay it at the session's next turn. What it buys is that the
1190
+ * direct-wake capability and its limits have an explicit name.
1211
1191
  *
1212
1192
  * It never asks for `crossSessionInbound`. The poster is the session's own
1213
1193
  * hook child and takes the privileged own-child path, verified to be delivered
@@ -1219,9 +1199,19 @@ function wakeRouteCheck(deps, active, activeInstallations) {
1219
1199
  if (active === null || activeInstallations.length === 0)
1220
1200
  return null;
1221
1201
  if (active.harness === 'claude-code') {
1202
+ const platform = deps.hookPlatform ?? process.platform;
1203
+ if (platform === 'win32') {
1204
+ return {
1205
+ name: 'hooks (wake route)',
1206
+ ok: false,
1207
+ reportOnly: true,
1208
+ technical: { held_stop_continuation: true },
1209
+ detail: 'direct inbox wake is unavailable on Windows; the held Stop still returns the answer to this same Agent Session without another User prompt',
1210
+ };
1211
+ }
1222
1212
  const readiness = inspectClaudeInbox({
1223
1213
  pid: deps.claudeSourcePid ?? claudeSessionPid(deps.env),
1224
- platform: deps.hookPlatform ?? process.platform,
1214
+ platform,
1225
1215
  readDescriptor: deps.claudeWake?.readDescriptor ?? systemClaudeWakeAdapters(deps.env).readDescriptor,
1226
1216
  socketExists: (socketPath) => existsSync(socketPath),
1227
1217
  });
@@ -1243,6 +1233,7 @@ function wakeRouteCheck(deps, active, activeInstallations) {
1243
1233
  name: 'hooks (wake route)',
1244
1234
  ok: readiness.state === 'ready',
1245
1235
  reportOnly: true,
1236
+ technical: { held_stop_continuation: true },
1246
1237
  detail: readiness.state === 'ready'
1247
1238
  ? `the held Codex turn continues from its own hook, and after it returns ${readiness.lockDirectory} can prove a stopped thread unowned before resuming it`
1248
1239
  : `the held Codex turn still continues from its own hook, but after it returns nothing can be resumed: ${readiness.reason}. Answers wait for the next turn`,
@@ -1250,40 +1241,4 @@ function wakeRouteCheck(deps, active, activeInstallations) {
1250
1241
  }
1251
1242
  return null;
1252
1243
  }
1253
- /**
1254
- * A Codex hooks file sitting in a worktree, which Codex will never read.
1255
- *
1256
- * `settingsFile` now writes to the main repository, so this only fires for a
1257
- * file an older build left behind — but that file is indistinguishable from a
1258
- * working install if you go looking, and it is exactly what made this bug take
1259
- * a day to find. Omitted entirely when there is nothing to say.
1260
- */
1261
- function codexStrayWorktreeCheck(deps) {
1262
- const layer = codexLayerDir(deps.cwd);
1263
- if (layer === null)
1264
- return null;
1265
- const root = codexProjectRoot(deps.cwd);
1266
- const project = inspectCodexLayer(codexLayerPaths(false, deps.cwd, deps.env));
1267
- if (project.jsonEvents.length === 0 && project.tomlEvents.length === 0)
1268
- return null;
1269
- const strayJson = path.join(path.dirname(layer), '.codex', 'hooks.json');
1270
- const strayToml = path.join(path.dirname(layer), '.codex', 'config.toml');
1271
- const problems = [];
1272
- if (!existsSync(layer)) {
1273
- problems.push(`${layer} is missing, so Codex never looks for project hooks here`);
1274
- }
1275
- if (existsSync(strayJson) && path.resolve(strayJson) !== path.resolve(project.paths.hooksJson)) {
1276
- problems.push(`${strayJson} is never read — Codex reads ${project.writeTarget} instead`);
1277
- }
1278
- if (existsSync(strayToml) && path.resolve(strayToml) !== path.resolve(project.paths.configToml)) {
1279
- problems.push(`${strayToml} is never read — Codex reads ${project.writeTarget} instead`);
1280
- }
1281
- return {
1282
- name: 'hooks (codex worktree)',
1283
- ok: problems.length === 0,
1284
- detail: problems.length === 0
1285
- ? `worktree wired to the main repository at ${root}`
1286
- : `${problems.join('; ')}. Re-run \`notifai hooks install\` to fix.`,
1287
- };
1288
- }
1289
1244
  //# sourceMappingURL=commands-doctor.js.map