@raidiant/notifai 11.7.1 → 12.0.0-beta.11
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 +53 -0
- package/NOTICE +16 -0
- package/THIRD_PARTY_NOTICES +725 -0
- package/bin/notifai.mjs +11420 -0
- package/data/install.ps1 +318 -0
- package/data/release.json +1 -0
- package/inventory.json +1 -0
- package/npm-adapter-files.json +52 -0
- package/package.json +14 -44
- package/README.md +0 -168
- package/dist/agent-update-notice.d.ts +0 -7
- package/dist/agent-update-notice.js +0 -77
- package/dist/agent-update-notice.js.map +0 -1
- package/dist/atomic-file.d.ts +0 -30
- package/dist/atomic-file.js +0 -148
- package/dist/atomic-file.js.map +0 -1
- package/dist/attendant-update.d.ts +0 -15
- package/dist/attendant-update.js +0 -96
- package/dist/attendant-update.js.map +0 -1
- package/dist/claude-wake.d.ts +0 -108
- package/dist/claude-wake.js +0 -369
- package/dist/claude-wake.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -26
- package/dist/cli-bin.js +0 -220
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli-contract.d.ts +0 -16
- package/dist/cli-contract.js +0 -27
- package/dist/cli-contract.js.map +0 -1
- package/dist/cli-release.d.ts +0 -36
- package/dist/cli-release.js +0 -85
- package/dist/cli-release.js.map +0 -1
- package/dist/client.d.ts +0 -109
- package/dist/client.js +0 -217
- package/dist/client.js.map +0 -1
- package/dist/codex-answer-control.d.ts +0 -14
- package/dist/codex-answer-control.js +0 -41
- package/dist/codex-answer-control.js.map +0 -1
- package/dist/codex-answer-presentation.d.ts +0 -18
- package/dist/codex-answer-presentation.js +0 -113
- package/dist/codex-answer-presentation.js.map +0 -1
- package/dist/codex-input-lifecycle.d.ts +0 -10
- package/dist/codex-input-lifecycle.js +0 -63
- package/dist/codex-input-lifecycle.js.map +0 -1
- package/dist/codex-native-control.d.ts +0 -13
- package/dist/codex-native-control.js +0 -120
- package/dist/codex-native-control.js.map +0 -1
- package/dist/codex-native-turn.d.ts +0 -37
- package/dist/codex-native-turn.js +0 -155
- package/dist/codex-native-turn.js.map +0 -1
- package/dist/codex-question-bindings.d.ts +0 -76
- package/dist/codex-question-bindings.js +0 -151
- package/dist/codex-question-bindings.js.map +0 -1
- package/dist/codex-queue-control.d.ts +0 -21
- package/dist/codex-queue-control.js +0 -59
- package/dist/codex-queue-control.js.map +0 -1
- package/dist/codex-tool-messages.d.ts +0 -7
- package/dist/codex-tool-messages.js +0 -126
- package/dist/codex-tool-messages.js.map +0 -1
- package/dist/codex-wake.d.ts +0 -58
- package/dist/codex-wake.js +0 -135
- package/dist/codex-wake.js.map +0 -1
- package/dist/command-session.d.ts +0 -14
- package/dist/command-session.js +0 -31
- package/dist/command-session.js.map +0 -1
- package/dist/commands-acknowledge.d.ts +0 -13
- package/dist/commands-acknowledge.js +0 -116
- package/dist/commands-acknowledge.js.map +0 -1
- package/dist/commands-agent-sessions.d.ts +0 -6
- package/dist/commands-agent-sessions.js +0 -51
- package/dist/commands-agent-sessions.js.map +0 -1
- package/dist/commands-ask.d.ts +0 -62
- package/dist/commands-ask.js +0 -503
- package/dist/commands-ask.js.map +0 -1
- package/dist/commands-auth.d.ts +0 -76
- package/dist/commands-auth.js +0 -421
- package/dist/commands-auth.js.map +0 -1
- package/dist/commands-close.d.ts +0 -6
- package/dist/commands-close.js +0 -273
- package/dist/commands-close.js.map +0 -1
- package/dist/commands-config.d.ts +0 -31
- package/dist/commands-config.js +0 -272
- package/dist/commands-config.js.map +0 -1
- package/dist/commands-core.d.ts +0 -145
- package/dist/commands-core.js +0 -190
- package/dist/commands-core.js.map +0 -1
- package/dist/commands-devices.d.ts +0 -41
- package/dist/commands-devices.js +0 -144
- package/dist/commands-devices.js.map +0 -1
- package/dist/commands-doctor.d.ts +0 -25
- package/dist/commands-doctor.js +0 -1553
- package/dist/commands-doctor.js.map +0 -1
- package/dist/commands-guidance.d.ts +0 -27
- package/dist/commands-guidance.js +0 -150
- package/dist/commands-guidance.js.map +0 -1
- package/dist/commands-harness-context.d.ts +0 -57
- package/dist/commands-harness-context.js +0 -102
- package/dist/commands-harness-context.js.map +0 -1
- package/dist/commands-hook-attend.d.ts +0 -70
- package/dist/commands-hook-attend.js +0 -655
- package/dist/commands-hook-attend.js.map +0 -1
- package/dist/commands-hook-diagnostics.d.ts +0 -37
- package/dist/commands-hook-diagnostics.js +0 -136
- package/dist/commands-hook-diagnostics.js.map +0 -1
- package/dist/commands-hook-install.d.ts +0 -26
- package/dist/commands-hook-install.js +0 -784
- package/dist/commands-hook-install.js.map +0 -1
- package/dist/commands-hook-run.d.ts +0 -29
- package/dist/commands-hook-run.js +0 -933
- package/dist/commands-hook-run.js.map +0 -1
- package/dist/commands-hook-shape.d.ts +0 -19
- package/dist/commands-hook-shape.js +0 -48
- package/dist/commands-hook-shape.js.map +0 -1
- package/dist/commands-init.d.ts +0 -42
- package/dist/commands-init.js +0 -901
- package/dist/commands-init.js.map +0 -1
- package/dist/commands-io.d.ts +0 -2
- package/dist/commands-io.js +0 -117
- package/dist/commands-io.js.map +0 -1
- package/dist/commands-logs.d.ts +0 -30
- package/dist/commands-logs.js +0 -201
- package/dist/commands-logs.js.map +0 -1
- package/dist/commands-native-acknowledge.d.ts +0 -8
- package/dist/commands-native-acknowledge.js +0 -114
- package/dist/commands-native-acknowledge.js.map +0 -1
- package/dist/commands-project.d.ts +0 -4
- package/dist/commands-project.js +0 -41
- package/dist/commands-project.js.map +0 -1
- package/dist/commands-receive.d.ts +0 -3
- package/dist/commands-receive.js +0 -59
- package/dist/commands-receive.js.map +0 -1
- package/dist/commands-send-support.d.ts +0 -59
- package/dist/commands-send-support.js +0 -258
- package/dist/commands-send-support.js.map +0 -1
- package/dist/commands-send.d.ts +0 -34
- package/dist/commands-send.js +0 -900
- package/dist/commands-send.js.map +0 -1
- package/dist/commands-setup-proof.d.ts +0 -43
- package/dist/commands-setup-proof.js +0 -134
- package/dist/commands-setup-proof.js.map +0 -1
- package/dist/commands-skill.d.ts +0 -21
- package/dist/commands-skill.js +0 -199
- package/dist/commands-skill.js.map +0 -1
- package/dist/commands-sounds.d.ts +0 -12
- package/dist/commands-sounds.js +0 -35
- package/dist/commands-sounds.js.map +0 -1
- package/dist/commands-update-check.d.ts +0 -6
- package/dist/commands-update-check.js +0 -102
- package/dist/commands-update-check.js.map +0 -1
- package/dist/commands-update-resume.d.ts +0 -7
- package/dist/commands-update-resume.js +0 -143
- package/dist/commands-update-resume.js.map +0 -1
- package/dist/commands-update-skill.d.ts +0 -5
- package/dist/commands-update-skill.js +0 -36
- package/dist/commands-update-skill.js.map +0 -1
- package/dist/commands-update.d.ts +0 -11
- package/dist/commands-update.js +0 -286
- package/dist/commands-update.js.map +0 -1
- package/dist/commands.d.ts +0 -26
- package/dist/commands.js +0 -27
- package/dist/commands.js.map +0 -1
- package/dist/config-schema.d.ts +0 -62
- package/dist/config-schema.js +0 -302
- package/dist/config-schema.js.map +0 -1
- package/dist/config.d.ts +0 -181
- package/dist/config.js +0 -357
- package/dist/config.js.map +0 -1
- package/dist/credentials.d.ts +0 -96
- package/dist/credentials.js +0 -342
- package/dist/credentials.js.map +0 -1
- package/dist/file-lock.d.ts +0 -34
- package/dist/file-lock.js +0 -367
- package/dist/file-lock.js.map +0 -1
- package/dist/generated-session-label.d.ts +0 -2
- package/dist/generated-session-label.js +0 -152
- package/dist/generated-session-label.js.map +0 -1
- package/dist/guidance-content.d.ts +0 -53
- package/dist/guidance-content.js +0 -248
- package/dist/guidance-content.js.map +0 -1
- package/dist/guidance-render.d.ts +0 -25
- package/dist/guidance-render.js +0 -47
- package/dist/guidance-render.js.map +0 -1
- package/dist/guidance.d.ts +0 -69
- package/dist/guidance.js +0 -173
- package/dist/guidance.js.map +0 -1
- package/dist/harness-session-title.d.ts +0 -22
- package/dist/harness-session-title.js +0 -74
- package/dist/harness-session-title.js.map +0 -1
- package/dist/harnesses.d.ts +0 -65
- package/dist/harnesses.js +0 -159
- package/dist/harnesses.js.map +0 -1
- package/dist/hermes-attendant.d.ts +0 -38
- package/dist/hermes-attendant.js +0 -337
- package/dist/hermes-attendant.js.map +0 -1
- package/dist/hermes-plugin.d.ts +0 -11
- package/dist/hermes-plugin.js +0 -299
- package/dist/hermes-plugin.js.map +0 -1
- package/dist/hook-acknowledgements.d.ts +0 -70
- package/dist/hook-acknowledgements.js +0 -490
- package/dist/hook-acknowledgements.js.map +0 -1
- package/dist/hook-adapter.d.ts +0 -48
- package/dist/hook-adapter.js +0 -419
- package/dist/hook-adapter.js.map +0 -1
- package/dist/hook-events.d.ts +0 -105
- package/dist/hook-events.js +0 -130
- package/dist/hook-events.js.map +0 -1
- package/dist/hook-gates.d.ts +0 -7
- package/dist/hook-gates.js +0 -20
- package/dist/hook-gates.js.map +0 -1
- package/dist/hook-input.d.ts +0 -10
- package/dist/hook-input.js +0 -64
- package/dist/hook-input.js.map +0 -1
- package/dist/hook-lifecycle.d.ts +0 -112
- package/dist/hook-lifecycle.js +0 -2031
- package/dist/hook-lifecycle.js.map +0 -1
- package/dist/hook-project-sessions.d.ts +0 -28
- package/dist/hook-project-sessions.js +0 -141
- package/dist/hook-project-sessions.js.map +0 -1
- package/dist/hook-question-lock.d.ts +0 -29
- package/dist/hook-question-lock.js +0 -217
- package/dist/hook-question-lock.js.map +0 -1
- package/dist/hook-question-retirement.d.ts +0 -74
- package/dist/hook-question-retirement.js +0 -314
- package/dist/hook-question-retirement.js.map +0 -1
- package/dist/hook-question-state.d.ts +0 -58
- package/dist/hook-question-state.js +0 -239
- package/dist/hook-question-state.js.map +0 -1
- package/dist/hook-session-state.d.ts +0 -127
- package/dist/hook-session-state.js +0 -488
- package/dist/hook-session-state.js.map +0 -1
- package/dist/hook-types.d.ts +0 -476
- package/dist/hook-types.js +0 -4
- package/dist/hook-types.js.map +0 -1
- package/dist/injection-render.d.ts +0 -36
- package/dist/injection-render.js +0 -61
- package/dist/injection-render.js.map +0 -1
- package/dist/install-hooks.d.ts +0 -470
- package/dist/install-hooks.js +0 -1622
- package/dist/install-hooks.js.map +0 -1
- package/dist/integration-health.d.ts +0 -26
- package/dist/integration-health.js +0 -133
- package/dist/integration-health.js.map +0 -1
- package/dist/interactive.d.ts +0 -22
- package/dist/interactive.js +0 -644
- package/dist/interactive.js.map +0 -1
- package/dist/invocation-context.d.ts +0 -47
- package/dist/invocation-context.js +0 -164
- package/dist/invocation-context.js.map +0 -1
- package/dist/local-path.d.ts +0 -7
- package/dist/local-path.js +0 -27
- package/dist/local-path.js.map +0 -1
- package/dist/logging.d.ts +0 -157
- package/dist/logging.js +0 -599
- package/dist/logging.js.map +0 -1
- package/dist/main-run.d.ts +0 -1
- package/dist/main-run.js +0 -42
- package/dist/main-run.js.map +0 -1
- package/dist/main.d.ts +0 -2
- package/dist/main.js +0 -12
- package/dist/main.js.map +0 -1
- package/dist/native-answer-operation.d.ts +0 -70
- package/dist/native-answer-operation.js +0 -247
- package/dist/native-answer-operation.js.map +0 -1
- package/dist/native-skills.d.ts +0 -64
- package/dist/native-skills.js +0 -167
- package/dist/native-skills.js.map +0 -1
- package/dist/node-floor.d.ts +0 -19
- package/dist/node-floor.js +0 -32
- package/dist/node-floor.js.map +0 -1
- package/dist/npm-invocation.d.ts +0 -34
- package/dist/npm-invocation.js +0 -53
- package/dist/npm-invocation.js.map +0 -1
- package/dist/openclaw-continuation-bridge.d.ts +0 -7
- package/dist/openclaw-continuation-bridge.js +0 -64
- package/dist/openclaw-continuation-bridge.js.map +0 -1
- package/dist/openclaw-gateway-readiness.d.ts +0 -1
- package/dist/openclaw-gateway-readiness.js +0 -24
- package/dist/openclaw-gateway-readiness.js.map +0 -1
- package/dist/openclaw-generation.d.ts +0 -21
- package/dist/openclaw-generation.js +0 -115
- package/dist/openclaw-generation.js.map +0 -1
- package/dist/openclaw-message-bridge.d.ts +0 -8
- package/dist/openclaw-message-bridge.js +0 -93
- package/dist/openclaw-message-bridge.js.map +0 -1
- package/dist/openclaw-pending.d.ts +0 -8
- package/dist/openclaw-pending.js +0 -47
- package/dist/openclaw-pending.js.map +0 -1
- package/dist/openclaw-plugin.d.ts +0 -75
- package/dist/openclaw-plugin.js +0 -1353
- package/dist/openclaw-plugin.js.map +0 -1
- package/dist/openclaw-session-access.d.ts +0 -12
- package/dist/openclaw-session-access.js +0 -79
- package/dist/openclaw-session-access.js.map +0 -1
- package/dist/opencode-plugin.d.ts +0 -88
- package/dist/opencode-plugin.js +0 -286
- package/dist/opencode-plugin.js.map +0 -1
- package/dist/orca-session-title.d.ts +0 -11
- package/dist/orca-session-title.js +0 -111
- package/dist/orca-session-title.js.map +0 -1
- package/dist/pack-feedback-slice.d.ts +0 -35
- package/dist/pack-feedback-slice.js +0 -126
- package/dist/pack-feedback-slice.js.map +0 -1
- package/dist/pairing-qr.d.ts +0 -5
- package/dist/pairing-qr.js +0 -24
- package/dist/pairing-qr.js.map +0 -1
- package/dist/pending-pairing.d.ts +0 -38
- package/dist/pending-pairing.js +0 -70
- package/dist/pending-pairing.js.map +0 -1
- package/dist/platform.d.ts +0 -67
- package/dist/platform.js +0 -140
- package/dist/platform.js.map +0 -1
- package/dist/process-identity.d.ts +0 -26
- package/dist/process-identity.js +0 -82
- package/dist/process-identity.js.map +0 -1
- package/dist/program.d.ts +0 -59
- package/dist/program.js +0 -691
- package/dist/program.js.map +0 -1
- package/dist/project-enablement.d.ts +0 -21
- package/dist/project-enablement.js +0 -71
- package/dist/project-enablement.js.map +0 -1
- package/dist/question-settlement-process.d.ts +0 -17
- package/dist/question-settlement-process.js +0 -35
- package/dist/question-settlement-process.js.map +0 -1
- package/dist/question-timing.d.ts +0 -46
- package/dist/question-timing.js +0 -48
- package/dist/question-timing.js.map +0 -1
- package/dist/readiness.d.ts +0 -153
- package/dist/readiness.js +0 -149
- package/dist/readiness.js.map +0 -1
- package/dist/release.d.ts +0 -31
- package/dist/release.js +0 -52
- package/dist/release.js.map +0 -1
- package/dist/send-attempts.d.ts +0 -26
- package/dist/send-attempts.js +0 -139
- package/dist/send-attempts.js.map +0 -1
- package/dist/send.d.ts +0 -115
- package/dist/send.js +0 -421
- package/dist/send.js.map +0 -1
- package/dist/session-activation.d.ts +0 -13
- package/dist/session-activation.js +0 -64
- package/dist/session-activation.js.map +0 -1
- package/dist/session-attendant-probe.d.ts +0 -85
- package/dist/session-attendant-probe.js +0 -173
- package/dist/session-attendant-probe.js.map +0 -1
- package/dist/session-attendant-state.d.ts +0 -70
- package/dist/session-attendant-state.js +0 -256
- package/dist/session-attendant-state.js.map +0 -1
- package/dist/session-attendant.d.ts +0 -143
- package/dist/session-attendant.js +0 -520
- package/dist/session-attendant.js.map +0 -1
- package/dist/session-delivery.d.ts +0 -249
- package/dist/session-delivery.js +0 -576
- package/dist/session-delivery.js.map +0 -1
- package/dist/session-handoff.d.ts +0 -98
- package/dist/session-handoff.js +0 -118
- package/dist/session-handoff.js.map +0 -1
- package/dist/session-input-wakes.d.ts +0 -78
- package/dist/session-input-wakes.js +0 -239
- package/dist/session-input-wakes.js.map +0 -1
- package/dist/session-inputs.d.ts +0 -57
- package/dist/session-inputs.js +0 -434
- package/dist/session-inputs.js.map +0 -1
- package/dist/session-labels.d.ts +0 -56
- package/dist/session-labels.js +0 -396
- package/dist/session-labels.js.map +0 -1
- package/dist/session-message-handoff.d.ts +0 -39
- package/dist/session-message-handoff.js +0 -107
- package/dist/session-message-handoff.js.map +0 -1
- package/dist/setup-destinations.d.ts +0 -23
- package/dist/setup-destinations.js +0 -44
- package/dist/setup-destinations.js.map +0 -1
- package/dist/skill-integrity.d.ts +0 -54
- package/dist/skill-integrity.js +0 -164
- package/dist/skill-integrity.js.map +0 -1
- package/dist/skill-source/manifest.json +0 -40
- package/dist/skill-source/notifai/SKILL.md +0 -350
- package/dist/skill-source/notifai/references/diagnostics.md +0 -36
- package/dist/skill-source/notifai/references/harness-setup.md +0 -384
- package/dist/skill-source/notifai/references/native-questions.md +0 -51
- package/dist/skill-source/notifai/references/notes-and-edits.md +0 -54
- package/dist/skill-source/notifai/references/send-details.md +0 -14
- package/dist/skill-source/notifai/references/updates.md +0 -116
- package/dist/skill-source/notifai/references/writing-examples.md +0 -56
- package/dist/sound-ref.d.ts +0 -23
- package/dist/sound-ref.js +0 -28
- package/dist/sound-ref.js.map +0 -1
- package/dist/ui/banner.d.ts +0 -22
- package/dist/ui/banner.js +0 -87
- package/dist/ui/banner.js.map +0 -1
- package/dist/ui/config-view.d.ts +0 -30
- package/dist/ui/config-view.js +0 -108
- package/dist/ui/config-view.js.map +0 -1
- package/dist/ui/help.d.ts +0 -44
- package/dist/ui/help.js +0 -107
- package/dist/ui/help.js.map +0 -1
- package/dist/ui/theme.d.ts +0 -90
- package/dist/ui/theme.js +0 -244
- package/dist/ui/theme.js.map +0 -1
- package/dist/update-handoff.d.ts +0 -29
- package/dist/update-handoff.js +0 -69
- package/dist/update-handoff.js.map +0 -1
- package/dist/url-policy.d.ts +0 -85
- package/dist/url-policy.js +0 -369
- package/dist/url-policy.js.map +0 -1
- package/dist/version.d.ts +0 -40
- package/dist/version.js +0 -132
- package/dist/version.js.map +0 -1
- package/dist/wake-support.d.ts +0 -40
- package/dist/wake-support.js +0 -95
- package/dist/wake-support.js.map +0 -1
|
@@ -1,655 +0,0 @@
|
|
|
1
|
-
import { hasSessionInputs, stageSessionMessages, wakeSessionInputs, wakeCodexSessionInputs, reconcileSessionInputWakes } from './session-inputs.js';
|
|
2
|
-
/**
|
|
3
|
-
* `notifai hook attend`: the asynchronous handler that becomes an Agent
|
|
4
|
-
* Session's Session Attendant, or exits within milliseconds when a healthy
|
|
5
|
-
* attendant for this exact session incarnation already runs.
|
|
6
|
-
*
|
|
7
|
-
* Installed on SessionStart next to the short activation handler (which it
|
|
8
|
-
* never delays: the harness does not wait for an async handler), and on
|
|
9
|
-
* UserPromptSubmit and Stop to re-arm a session whose attendant died.
|
|
10
|
-
*/
|
|
11
|
-
import { existsSync, readFileSync, realpathSync, readdirSync } from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import { createHash } from 'node:crypto';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { EXIT, makeClient } from './commands-core.js';
|
|
16
|
-
import { claudeSessionPid } from './commands-harness-context.js';
|
|
17
|
-
import { loadConfig } from './config.js';
|
|
18
|
-
import { inspectHookAdapter, isNpxAdapterTarget } from './hook-adapter.js';
|
|
19
|
-
import { acquireClaimFile, claimHolderMayRun, readClaimFile, releaseClaimFile, requestClaimHandoff } from './hook-question-lock.js';
|
|
20
|
-
import { beginSessionIncarnation, endsIncarnation, readSessionEndMarker, readSessionIncarnation, readSessionState, refreshSessionMarkers, rotateSessionIncarnation, sessionNotified, sessionHasEnded, updateSessionState, happenedBefore, } from './hook-session-state.js';
|
|
21
|
-
import { findInstallations, findLegacyProjectInstallations, handlerEvent } from './install-hooks.js';
|
|
22
|
-
import { logSettingsFrom } from './logging.js';
|
|
23
|
-
import { processExecutableName, processStartTime } from './process-identity.js';
|
|
24
|
-
import { projectBinding, projectEnabled } from './project-enablement.js';
|
|
25
|
-
import { packageVersion } from './release.js';
|
|
26
|
-
import { runSessionAttendant, systemAttendantClock, } from './session-attendant.js';
|
|
27
|
-
import { attendantSupport, claudeAttendanceProbe, codexAttendanceProbe, openclawAttendanceProbe, systemClaudeProbeAdapters, } from './session-attendant-probe.js';
|
|
28
|
-
import { attendantClaimPath, attendantStatusPath, observedCodexTurnActivity, readAttendantLease, recordTurnEnd, recordTurnStart, reconcileNativeTurn, turnActivityPath, writeAttendantStatus, } from './session-attendant-state.js';
|
|
29
|
-
import { CLI_PACKAGE_NAME } from './cli-contract.js';
|
|
30
|
-
import { inspectClaudeInbox, systemClaudeWakeAdapters } from './claude-wake.js';
|
|
31
|
-
import { currentProcessIdentity } from './process-identity.js';
|
|
32
|
-
import { inspectCodexQueue, systemCodexWakeAdapters } from './codex-wake.js';
|
|
33
|
-
import { claudeSourceDescriptor, deliverIntoClaudeSession, deliverIntoCodexThread } from './session-handoff.js';
|
|
34
|
-
import { handOffSessionMessages } from './session-message-handoff.js';
|
|
35
|
-
import { compareVersions } from './version.js';
|
|
36
|
-
import { readOpenclawGeneration } from './openclaw-generation.js';
|
|
37
|
-
import { integrationFaultNotice } from './integration-health.js';
|
|
38
|
-
import { openclawBridgeActivity, openclawMessageBridge, openclawMessageBridgeAvailable } from './openclaw-message-bridge.js';
|
|
39
|
-
import { readNativeTurnSnapshot } from './codex-native-turn.js';
|
|
40
|
-
import { codexInputObserver, refreshCodexInputActivity } from './codex-input-lifecycle.js';
|
|
41
|
-
// Captured when this module loads, so an in-place build cannot make a resident
|
|
42
|
-
// writer mistake the replacement files for its own loaded implementation.
|
|
43
|
-
export const attendantRuntimeRevision = (() => {
|
|
44
|
-
const file = fileURLToPath(import.meta.url), extension = path.extname(file), directory = path.dirname(file);
|
|
45
|
-
const hash = createHash('sha256');
|
|
46
|
-
for (const name of readdirSync(directory).filter(name => name.endsWith(extension) &&
|
|
47
|
-
!name.endsWith('.d.ts') && !name.endsWith('.test.ts')).sort()) {
|
|
48
|
-
hash.update(name).update('\0').update(readFileSync(path.join(directory, name)));
|
|
49
|
-
}
|
|
50
|
-
return hash.digest('hex');
|
|
51
|
-
})();
|
|
52
|
-
const SUPERSEDED_OWNER_WAIT_MS = 6_000;
|
|
53
|
-
/**
|
|
54
|
-
* Bound on Codex SessionEnd's `ended` report. Codex allows SessionEnd at most
|
|
55
|
-
* three seconds, and the CLI's own start and durable cleanup come first.
|
|
56
|
-
*/
|
|
57
|
-
export const CODEX_SESSION_END_REPORT_MS = 1_000;
|
|
58
|
-
export async function attendHook(deps, input) {
|
|
59
|
-
const { envelope, harness, cwd, logger } = input;
|
|
60
|
-
const seams = deps.attendant ?? {};
|
|
61
|
-
const end = (outcome, data = {}) => {
|
|
62
|
-
if (input.recovery !== undefined)
|
|
63
|
-
logger.info('attendant.state', { source: 'update-resume', outcome, ...data });
|
|
64
|
-
else
|
|
65
|
-
logger.info('hook.end', { hook: 'attend', outcome, decided: false, ...data });
|
|
66
|
-
return EXIT.ok;
|
|
67
|
-
};
|
|
68
|
-
// Read once, now: an in-place reinstall replaces the manifest on disk, and
|
|
69
|
-
// rereading it later would compare the installed files with themselves.
|
|
70
|
-
const runningVersion = deps.runningVersion === undefined ? packageVersion() : deps.runningVersion;
|
|
71
|
-
const sessionId = envelope.session_id;
|
|
72
|
-
if (sessionId === undefined)
|
|
73
|
-
return end('ignored', { reason: 'missing-session-id' });
|
|
74
|
-
const support = attendantSupport(harness, deps.hookPlatform ?? process.platform);
|
|
75
|
-
if (!support.supported)
|
|
76
|
-
return end('unsupported', { reason: support.reason });
|
|
77
|
-
if (harness !== 'claude-code' && harness !== 'codex' && harness !== 'openclaw') {
|
|
78
|
-
return end('unsupported', { reason: 'harness-has-no-attendant' });
|
|
79
|
-
}
|
|
80
|
-
const starting = envelope.hook_event_name === 'SessionStart' || envelope.source !== undefined;
|
|
81
|
-
const claimFile = attendantClaimPath(sessionId, deps.env);
|
|
82
|
-
// The hook adapter names the harness process that ran it. Claude Code also
|
|
83
|
-
// names itself; Codex is only ever the declared parent.
|
|
84
|
-
const pid = input.recovery?.harnessProcess.pid ?? (harness === 'codex' || harness === 'openclaw'
|
|
85
|
-
? declaredHookSourcePid(deps.env)
|
|
86
|
-
: declaredHookSourcePid(deps.env) ?? claudeSessionPid(deps.env));
|
|
87
|
-
if (pid === undefined)
|
|
88
|
-
return end('ignored', { reason: 'harness-process-unproven' });
|
|
89
|
-
const nativeOwner = harness === 'codex'
|
|
90
|
-
? seams.harnessProcess ?? (() => {
|
|
91
|
-
const start = processStartTime(pid);
|
|
92
|
-
return start === null ? null : { pid, start };
|
|
93
|
-
})()
|
|
94
|
-
: null;
|
|
95
|
-
const ownsNative = (key) => {
|
|
96
|
-
const current = readSessionIncarnation(sessionId, deps.env);
|
|
97
|
-
return nativeOwner !== null && current?.key === key &&
|
|
98
|
-
envelope.agent_id === undefined && envelope.agent_type === undefined &&
|
|
99
|
-
current.harness_process?.pid === nativeOwner.pid && current.harness_process.start === nativeOwner.start &&
|
|
100
|
-
(input.recovery === undefined || current.key === input.recovery.key) &&
|
|
101
|
-
!happenedBefore(input.invokedAt, current.start) && !sessionHasEnded(sessionId, deps.env);
|
|
102
|
-
};
|
|
103
|
-
const observeNative = (key, initial = false) => {
|
|
104
|
-
if (!ownsNative(key))
|
|
105
|
-
return;
|
|
106
|
-
if (envelope.hook_event_name === 'Stop') {
|
|
107
|
-
refreshCodexInputActivity(sessionId, deps.env, key, envelope.transcript_path);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (envelope.hook_event_name === 'Interrupt') {
|
|
111
|
-
recordCodexTurnEnd(envelope, sessionId, deps.env);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (!initial && envelope.hook_event_name !== 'UserPromptSubmit' && input.recovery === undefined)
|
|
115
|
-
return;
|
|
116
|
-
const native = readNativeTurnSnapshot(input.recovery?.transcriptPath ?? envelope.transcript_path, sessionId, deps.env);
|
|
117
|
-
const turnId = envelope.turn_id ?? (initial || input.recovery !== undefined ? native?.latest.id : undefined);
|
|
118
|
-
if (native === null || turnId === undefined)
|
|
119
|
-
return;
|
|
120
|
-
try {
|
|
121
|
-
if (reconcileNativeTurn(sessionId, deps.env, key, turnId, native, () => ownsNative(key), initial)) {
|
|
122
|
-
updateSessionState(sessionId, deps.env, state => ({ ...state, codex_native_turn: {
|
|
123
|
-
key, turn_id: turnId, transcript_path: native.file,
|
|
124
|
-
} }));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch { /* A busy observation lock must not cost this session its attendant. */ }
|
|
128
|
-
};
|
|
129
|
-
if (harness === 'codex') {
|
|
130
|
-
const current = readSessionIncarnation(sessionId, deps.env);
|
|
131
|
-
if (input.recovery !== undefined && (current === null || !ownsNative(current.key))) {
|
|
132
|
-
return end('ignored', { reason: 'recovery-owner-changed' });
|
|
133
|
-
}
|
|
134
|
-
if (current !== null)
|
|
135
|
-
observeNative(current.key);
|
|
136
|
-
}
|
|
137
|
-
// Interrupt is a bounded observation, never a replacement attendant.
|
|
138
|
-
if (envelope.hook_event_name === 'Interrupt')
|
|
139
|
-
return end('recorded');
|
|
140
|
-
if (!starting) {
|
|
141
|
-
// Re-arm fast path, before any configuration or gate work: this runs on
|
|
142
|
-
// every prompt and every turn end, and almost always finds its owner.
|
|
143
|
-
const current = readSessionIncarnation(sessionId, deps.env);
|
|
144
|
-
const holder = readClaimFile(claimFile);
|
|
145
|
-
if (current !== null &&
|
|
146
|
-
current.harness_process?.pid === (seams.harnessProcess?.pid ?? pid) &&
|
|
147
|
-
holder?.['incarnation'] === current.incarnation &&
|
|
148
|
-
claimHolderMayRun(holder)) {
|
|
149
|
-
// An authenticated native event can hand the resident writer over to
|
|
150
|
-
// installed code. The old owner exits through claim-lost, not SessionEnd.
|
|
151
|
-
const upgrade = harness === 'codex' && ownsNative(current.key) &&
|
|
152
|
-
runningVersion !== null && (holder['runtime_version'] !== runningVersion ||
|
|
153
|
-
holder['runtime_revision'] !== attendantRuntimeRevision) && holder['pid'] !== process.pid &&
|
|
154
|
-
(seams.gates ?? (() => attendantGates(deps, cwd, sessionId, harness, runningVersion)))().ok &&
|
|
155
|
-
(holder['handoff'] === true || (typeof holder['token'] === 'string' &&
|
|
156
|
-
requestClaimHandoff(claimFile, holder['token'], current.incarnation)));
|
|
157
|
-
if (!upgrade)
|
|
158
|
-
return end('owner-present', { same_incarnation: true, holder_alive: true });
|
|
159
|
-
logger.info('attendant.state', { reason: 'installed-runtime-changed', phase: 'handoff' });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const assessGates = seams.gates ?? (() => attendantGates(deps, cwd, sessionId, harness, runningVersion));
|
|
163
|
-
const gates = () => {
|
|
164
|
-
const result = assessGates();
|
|
165
|
-
if (!result.ok && !['project-disabled', 'enablement-unavailable'].includes(result.reason)) {
|
|
166
|
-
// Preserve evidence before the missing contract withdraws this observer.
|
|
167
|
-
// An explicit loss invalidates a recent healthy cache immediately.
|
|
168
|
-
integrationFaultNotice({ ...deps, cwd }, harness, false, true);
|
|
169
|
-
}
|
|
170
|
-
return result;
|
|
171
|
-
};
|
|
172
|
-
try {
|
|
173
|
-
const config = loadConfig({ cwd, env: deps.env, sessionId });
|
|
174
|
-
logger.adopt(logSettingsFrom(config));
|
|
175
|
-
logger.bind({ project: config.project.value });
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
// The gate below reports enablement it cannot read.
|
|
179
|
-
}
|
|
180
|
-
const gate = gates();
|
|
181
|
-
if (!gate.ok)
|
|
182
|
-
return end('ignored', { reason: gate.reason });
|
|
183
|
-
// Codex runs a hook through `$SHELL -lc`. bash and zsh exec the adapter, so
|
|
184
|
-
// its parent is Codex; a shell that does not would be named instead and
|
|
185
|
-
// outlive a crashed Codex. Only the Codex executable itself is proof.
|
|
186
|
-
if (seams.harnessProcess === undefined && harness === 'codex' && processExecutableName(pid) !== 'codex') {
|
|
187
|
-
return end('ignored', { reason: 'harness-process-not-codex' });
|
|
188
|
-
}
|
|
189
|
-
const harnessProcess = seams.harnessProcess ?? (() => {
|
|
190
|
-
const start = processStartTime(pid);
|
|
191
|
-
return start === null ? null : { pid, start };
|
|
192
|
-
})();
|
|
193
|
-
if (harnessProcess === null)
|
|
194
|
-
return end('ignored', { reason: 'harness-process-unproven' });
|
|
195
|
-
const openclawGeneration = harness === 'openclaw'
|
|
196
|
-
? readOpenclawGeneration(sessionId, deps.env)
|
|
197
|
-
: null;
|
|
198
|
-
if (harness === 'openclaw' && (openclawGeneration === null ||
|
|
199
|
-
openclawGeneration.ended || !openclawGeneration.activated ||
|
|
200
|
-
readSessionIncarnation(sessionId, deps.env)?.openclaw_generation !== openclawGeneration.id)) {
|
|
201
|
-
return end('ignored', { reason: 'stale-openclaw-generation' });
|
|
202
|
-
}
|
|
203
|
-
const clock = seams.clock ?? systemAttendantClock;
|
|
204
|
-
let record = input.recovery !== undefined ? readSessionIncarnation(sessionId, deps.env) : await withLockRetry(clock, () => beginSessionIncarnation(sessionId, deps.env, {
|
|
205
|
-
stamp: input.invokedAt,
|
|
206
|
-
harnessProcess,
|
|
207
|
-
clearEarlierEnd: starting,
|
|
208
|
-
...(openclawGeneration === null ? {} : { openclawGeneration: openclawGeneration.id }),
|
|
209
|
-
}));
|
|
210
|
-
if (record === null || (input.recovery !== undefined && !ownsNative(record.key)))
|
|
211
|
-
return end('ignored', { reason: 'recovery-owner-changed' });
|
|
212
|
-
if (harness === 'codex' && starting)
|
|
213
|
-
observeNative(record.key, true);
|
|
214
|
-
// One live attendant per session. A holder serving an older incarnation of
|
|
215
|
-
// this session (an in-process resume moments after a clear) steps aside
|
|
216
|
-
// within one probe; a holder serving this one is healthy, so this exits.
|
|
217
|
-
const waitUntil = clock.monotonic() + (seams.supersededOwnerWaitMs ?? SUPERSEDED_OWNER_WAIT_MS);
|
|
218
|
-
let token = null;
|
|
219
|
-
while (true) {
|
|
220
|
-
token = acquireClaimFile(claimFile, { incarnation: record.incarnation, runtime_version: runningVersion, runtime_revision: attendantRuntimeRevision }, clock.wall());
|
|
221
|
-
if (token !== null)
|
|
222
|
-
break;
|
|
223
|
-
const holder = readClaimFile(claimFile);
|
|
224
|
-
if ((holder?.['incarnation'] === record.incarnation && holder['handoff'] !== true) || clock.monotonic() >= waitUntil) {
|
|
225
|
-
return end('owner-present', {
|
|
226
|
-
same_incarnation: holder?.['incarnation'] === record.incarnation,
|
|
227
|
-
holder_alive: claimHolderMayRun(holder),
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
await clock.sleep(250, new AbortController().signal).catch(() => undefined);
|
|
231
|
-
if (input.recovery !== undefined && !ownsNative(record.key))
|
|
232
|
-
return end('ignored', { reason: 'recovery-owner-changed' });
|
|
233
|
-
record = readSessionIncarnation(sessionId, deps.env) ?? record;
|
|
234
|
-
}
|
|
235
|
-
logger.info(input.recovery === undefined ? 'hook.end' : 'attendant.state', {
|
|
236
|
-
hook: 'attend',
|
|
237
|
-
outcome: 'attending',
|
|
238
|
-
decided: false,
|
|
239
|
-
source: input.recovery === undefined ? envelope.source ?? envelope.hook_event_name ?? null : 'update-resume',
|
|
240
|
-
});
|
|
241
|
-
const served = record;
|
|
242
|
-
const codexActivity = harness === 'codex'
|
|
243
|
-
? codexInputObserver(sessionId, deps.env, served.key, clock.monotonic, input.recovery?.transcriptPath ?? envelope.transcript_path)
|
|
244
|
-
: null;
|
|
245
|
-
const probeAdapters = seams.probeAdapters ?? systemClaudeProbeAdapters(deps.env);
|
|
246
|
-
// SessionEnd names the incarnation it ended; an end of any other one,
|
|
247
|
-
// earlier or later, is not this attendant's.
|
|
248
|
-
const endedByHook = () => endsIncarnation(readSessionEndMarker(sessionId, deps.env), served);
|
|
249
|
-
const probe = harness === 'codex'
|
|
250
|
-
? codexAttendanceProbe({
|
|
251
|
-
harness: harnessProcess,
|
|
252
|
-
endedByHook,
|
|
253
|
-
activity: () => {
|
|
254
|
-
// Presence remains live through a read gap. The binary service
|
|
255
|
-
// activity is last-observed; scheduling reads afresh.
|
|
256
|
-
const activity = codexActivity.observe();
|
|
257
|
-
if (activity === 'unknown')
|
|
258
|
-
logger.debug('attendant.state', { activity: 'cached', reason: 'native-activity-unreadable' });
|
|
259
|
-
return observedCodexTurnActivity(sessionId, deps.env, served.key);
|
|
260
|
-
},
|
|
261
|
-
adapters: probeAdapters,
|
|
262
|
-
})
|
|
263
|
-
: harness === 'openclaw'
|
|
264
|
-
? openclawAttendanceProbe({
|
|
265
|
-
sessionKey: sessionId,
|
|
266
|
-
generationId: served.openclaw_generation ?? '',
|
|
267
|
-
gateway: harnessProcess,
|
|
268
|
-
env: deps.env,
|
|
269
|
-
endedByHook,
|
|
270
|
-
activity: openclawBridgeActivity,
|
|
271
|
-
})
|
|
272
|
-
: claudeAttendanceProbe({ sessionId, harness: harnessProcess, endedByHook, adapters: probeAdapters });
|
|
273
|
-
let client;
|
|
274
|
-
const connect = () => {
|
|
275
|
-
if (client !== undefined)
|
|
276
|
-
return client;
|
|
277
|
-
const credential = deps.store.load();
|
|
278
|
-
client = credential
|
|
279
|
-
? makeClient(deps, credential.baseUrl, `Bearer nfm_${credential.machineId}.${credential.secret}`)
|
|
280
|
-
: null;
|
|
281
|
-
return client;
|
|
282
|
-
};
|
|
283
|
-
const codexWakeNeeded = codexActivity?.mayWake ?? null;
|
|
284
|
-
const messages = sessionMessageWriter({
|
|
285
|
-
deps,
|
|
286
|
-
harness: harness,
|
|
287
|
-
sessionId,
|
|
288
|
-
cwd: envelope.cwd ?? cwd,
|
|
289
|
-
harnessPid: harnessProcess.pid,
|
|
290
|
-
writer: seams.writer === undefined ? currentProcessIdentity() : seams.writer,
|
|
291
|
-
clock,
|
|
292
|
-
logger,
|
|
293
|
-
mayWake: codexWakeNeeded ?? (() => true),
|
|
294
|
-
unknownAllowed: codexActivity?.unknownAllowed ?? (() => false),
|
|
295
|
-
});
|
|
296
|
-
let wakeCleanup = null;
|
|
297
|
-
const signals = seams.signalled === undefined ? terminationSignal() : null;
|
|
298
|
-
try {
|
|
299
|
-
const result = await runSessionAttendant({
|
|
300
|
-
sessionId,
|
|
301
|
-
incarnation: record.incarnation,
|
|
302
|
-
incarnationNow: () => readSessionIncarnation(sessionId, deps.env)?.incarnation ?? null,
|
|
303
|
-
rotateIncarnation: (expected) => {
|
|
304
|
-
const next = rotateSessionIncarnation(sessionId, deps.env, expected);
|
|
305
|
-
if (next === null)
|
|
306
|
-
return null;
|
|
307
|
-
// Keep the claim naming the incarnation it serves.
|
|
308
|
-
releaseClaimFile(claimFile, token);
|
|
309
|
-
token = acquireClaimFile(claimFile, { incarnation: next.incarnation, runtime_version: runningVersion, runtime_revision: attendantRuntimeRevision }, clock.wall());
|
|
310
|
-
return token === null ? null : next.incarnation;
|
|
311
|
-
},
|
|
312
|
-
probe,
|
|
313
|
-
// A claim that vanished or changed hands fences this attendant: another
|
|
314
|
-
// may already serve the same incarnation.
|
|
315
|
-
claimHeld: () => token !== null && readClaimFile(claimFile)?.['token'] === token,
|
|
316
|
-
notified: () => sessionNotified(sessionId, deps.env),
|
|
317
|
-
gates,
|
|
318
|
-
client: connect,
|
|
319
|
-
serverSupportsAttendance: async (api) => (await api.compatibility()).server_capabilities.includes('session_attendance'),
|
|
320
|
-
// The service accepts notes for this session only while an attendant
|
|
321
|
-
// that can hand them in place says so.
|
|
322
|
-
acceptsMessages: messages !== null,
|
|
323
|
-
...(harness === 'claude-code' && messages !== null ? {
|
|
324
|
-
localInputPending: () => !readSessionState(sessionId, deps.env).input_wake?.queued &&
|
|
325
|
-
hasSessionInputs(sessionId, deps.env, null),
|
|
326
|
-
} : {}),
|
|
327
|
-
...(codexWakeNeeded === null ? {} : {
|
|
328
|
-
localInputPending: () => !readSessionState(sessionId, deps.env).input_wake?.queued &&
|
|
329
|
-
hasSessionInputs(sessionId, deps.env, readAttendantLease(sessionId, deps.env)) && codexWakeNeeded(),
|
|
330
|
-
}),
|
|
331
|
-
...(messages === null
|
|
332
|
-
? {}
|
|
333
|
-
: {
|
|
334
|
-
onMessages: async (batch, attendant) => {
|
|
335
|
-
const api = connect();
|
|
336
|
-
return api === null ? 'done' : messages(api, batch, attendant);
|
|
337
|
-
},
|
|
338
|
-
}),
|
|
339
|
-
clock,
|
|
340
|
-
logger,
|
|
341
|
-
writeStatus: (status) => writeAttendantStatus(sessionId, deps.env, status),
|
|
342
|
-
onProbe: () => {
|
|
343
|
-
if (harness === 'codex' && wakeCleanup === null) {
|
|
344
|
-
wakeCleanup = reconcileSessionInputWakes(sessionId, deps.env, deps.codexQueueControl)
|
|
345
|
-
.catch(() => undefined).finally(() => { wakeCleanup = null; });
|
|
346
|
-
}
|
|
347
|
-
// Observe locally without consuming the agent's next context notice.
|
|
348
|
-
// This catches removal of the very hook that would report the fault.
|
|
349
|
-
const notice = integrationFaultNotice({ ...deps, cwd: envelope.cwd ?? cwd, now: () => clock.wall() }, harness, false);
|
|
350
|
-
if (notice !== undefined)
|
|
351
|
-
logger.info('attendant.state', { integration: 'needs-attention' });
|
|
352
|
-
},
|
|
353
|
-
heartbeat: () => {
|
|
354
|
-
refreshSessionMarkers(sessionId, deps.env, clock.wall(), [
|
|
355
|
-
claimFile,
|
|
356
|
-
attendantStatusPath(sessionId, deps.env),
|
|
357
|
-
turnActivityPath(sessionId, deps.env),
|
|
358
|
-
]);
|
|
359
|
-
},
|
|
360
|
-
signalled: seams.signalled ?? signals.promise,
|
|
361
|
-
...(seams.probeIntervalMs === undefined ? {} : { probeIntervalMs: seams.probeIntervalMs }),
|
|
362
|
-
...(seams.waitSeconds === undefined ? {} : { waitSeconds: seams.waitSeconds }),
|
|
363
|
-
});
|
|
364
|
-
seams.onExit?.(result);
|
|
365
|
-
}
|
|
366
|
-
finally {
|
|
367
|
-
signals?.dispose();
|
|
368
|
-
if (token !== null)
|
|
369
|
-
releaseClaimFile(claimFile, token);
|
|
370
|
-
}
|
|
371
|
-
return EXIT.ok;
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* The attendant's Session Message writer, or null when this session cannot
|
|
375
|
-
* take a message in place: for Claude Code no inbox socket (`--bare`, an older
|
|
376
|
-
* Claude Code, an unsupported protocol), for Codex no thread id, and for
|
|
377
|
-
* either no provable writer identity. Null keeps the attendant presence-only,
|
|
378
|
-
* and the service accepts no notes for the session.
|
|
379
|
-
*/
|
|
380
|
-
function sessionMessageWriter(input) {
|
|
381
|
-
const { deps, sessionId, harnessPid, writer, clock, logger } = input;
|
|
382
|
-
if (writer === null)
|
|
383
|
-
return null;
|
|
384
|
-
const sequencerFor = (client) => ({
|
|
385
|
-
sessionId,
|
|
386
|
-
env: deps.env,
|
|
387
|
-
client,
|
|
388
|
-
monotonic: () => clock.monotonic(),
|
|
389
|
-
wall: () => clock.wall(),
|
|
390
|
-
sleep: (milliseconds) => clock.sleep(milliseconds, new AbortController().signal),
|
|
391
|
-
writer,
|
|
392
|
-
log: logger,
|
|
393
|
-
});
|
|
394
|
-
if (input.harness === 'codex')
|
|
395
|
-
return codexMessageWriter({ deps, sessionId, cwd: input.cwd, logger, mayWake: input.mayWake, unknownAllowed: input.unknownAllowed });
|
|
396
|
-
if (input.harness === 'openclaw') {
|
|
397
|
-
if (!openclawMessageBridgeAvailable(deps.env))
|
|
398
|
-
return null;
|
|
399
|
-
const generation = readOpenclawGeneration(sessionId, deps.env);
|
|
400
|
-
if (generation === null || generation.ended)
|
|
401
|
-
return null;
|
|
402
|
-
const send = openclawMessageBridge();
|
|
403
|
-
return (client, batch, attendant) => handOffSessionMessages(batch, attendant, {
|
|
404
|
-
sequencer: sequencerFor(client),
|
|
405
|
-
openclawGeneration: generation.id,
|
|
406
|
-
write: (text, begin, guard, _writerGroup, message) => {
|
|
407
|
-
return send(message.message_id, sessionId, generation.id, text, begin, guard);
|
|
408
|
-
},
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
if (input.harness !== 'claude-code')
|
|
412
|
-
return null;
|
|
413
|
-
const adapters = deps.claudeWake ?? systemClaudeWakeAdapters(deps.env);
|
|
414
|
-
const inbox = inspectClaudeInbox({
|
|
415
|
-
pid: harnessPid,
|
|
416
|
-
platform: deps.hookPlatform ?? process.platform,
|
|
417
|
-
readDescriptor: adapters.readDescriptor,
|
|
418
|
-
socketExists: existsSync,
|
|
419
|
-
});
|
|
420
|
-
if (inbox.state !== 'ready') {
|
|
421
|
-
logger.info('attendant.state', { messages: 'unavailable', reason: inbox.reason });
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
return async (_client, batch, attendant) => {
|
|
425
|
-
const generation = attendant.generation();
|
|
426
|
-
if (generation === null || !attendant.mayWrite())
|
|
427
|
-
return 'retry-soon';
|
|
428
|
-
stageSessionMessages(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }, batch);
|
|
429
|
-
if (!hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }))
|
|
430
|
-
return 'done';
|
|
431
|
-
await wakeSessionInputs(sessionId, deps.env, async (text) => {
|
|
432
|
-
const result = await deliverIntoClaudeSession({
|
|
433
|
-
sessionId, sourcePid: harnessPid,
|
|
434
|
-
sourceDescriptor: claudeSourceDescriptor(sessionId, harnessPid, adapters),
|
|
435
|
-
adapters, text, begin: () => attendant.mayWrite(), holdAfterSend: false, writer: 'Session Attendant',
|
|
436
|
-
});
|
|
437
|
-
return result.status === 'written';
|
|
438
|
-
}, logger);
|
|
439
|
-
return 'done';
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
/** Stage pending input for foreground consumption and coalesce its native wake. */
|
|
443
|
-
function codexMessageWriter(input) {
|
|
444
|
-
const { deps, sessionId, cwd, logger, mayWake } = input;
|
|
445
|
-
const queue = inspectCodexQueue(sessionId, deps.env);
|
|
446
|
-
if (queue.state !== 'ready') {
|
|
447
|
-
logger.info('attendant.state', { messages: 'unavailable', reason: queue.reason });
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
|
-
const adapters = deps.codexWake ?? systemCodexWakeAdapters(deps.env);
|
|
451
|
-
if (adapters.available?.() === false) {
|
|
452
|
-
// A queue writer that cannot start would leave every note unconfirmed.
|
|
453
|
-
logger.info('attendant.state', { messages: 'unavailable', reason: 'codex-executable-not-found' });
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
return async (_client, batch, attendant) => {
|
|
457
|
-
const incarnation = readSessionIncarnation(sessionId, deps.env);
|
|
458
|
-
const generation = attendant.generation();
|
|
459
|
-
if (incarnation === null || generation === null || !attendant.mayWrite())
|
|
460
|
-
return 'retry-soon';
|
|
461
|
-
stageSessionMessages(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }, batch);
|
|
462
|
-
if (await wakeCodexSessionInputs({ sessionId, env: deps.env,
|
|
463
|
-
lease: { incarnation: attendant.incarnation(), generation },
|
|
464
|
-
mayWrite: () => attendant.mayWrite(), mayWake,
|
|
465
|
-
unknownAllowed: input.unknownAllowed,
|
|
466
|
-
...(deps.codexQueueControl === undefined ? {} : { connect: deps.codexQueueControl }),
|
|
467
|
-
}))
|
|
468
|
-
return 'done';
|
|
469
|
-
if (!hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }))
|
|
470
|
-
return 'done';
|
|
471
|
-
if (!mayWake())
|
|
472
|
-
return 'done';
|
|
473
|
-
// The resident observes completion even when no further tool runs. Recheck
|
|
474
|
-
// eligibility and pending input immediately before queue admission; a hook
|
|
475
|
-
// may have consumed the batch since the last attendance exchange.
|
|
476
|
-
await wakeSessionInputs(sessionId, deps.env, async (text) => {
|
|
477
|
-
if (!attendant.mayWrite() || !mayWake() ||
|
|
478
|
-
!hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }))
|
|
479
|
-
return false;
|
|
480
|
-
const result = await deliverIntoCodexThread({
|
|
481
|
-
threadId: queue.threadId, cwd, env: deps.env, adapters, text,
|
|
482
|
-
begin: () => attendant.mayWrite() && mayWake() &&
|
|
483
|
-
hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }),
|
|
484
|
-
});
|
|
485
|
-
return result.status === 'written';
|
|
486
|
-
}, logger);
|
|
487
|
-
return 'done';
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
/** Record the Codex turn this turn end or interrupt closes. */
|
|
491
|
-
function recordCodexTurnEnd(envelope, sessionId, env) {
|
|
492
|
-
const turnId = envelope.turn_id;
|
|
493
|
-
const event = envelope.hook_event_name;
|
|
494
|
-
if (typeof turnId !== 'string' || turnId === '' || (event !== 'Stop' && event !== 'Interrupt'))
|
|
495
|
-
return;
|
|
496
|
-
try {
|
|
497
|
-
recordTurnEnd(sessionId, env, turnId);
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
// Activity is a hint; a busy lock never costs the session its attendant.
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Record the Codex turn this prompt starts. Called from the synchronous
|
|
505
|
-
* UserPromptSubmit hook: Codex runs it before the turn and one turn at a time,
|
|
506
|
-
* which is what orders recorded starts. Never throws.
|
|
507
|
-
*/
|
|
508
|
-
export function recordCodexTurnStart(envelope, env) {
|
|
509
|
-
const sessionId = envelope.session_id;
|
|
510
|
-
const turnId = envelope.turn_id;
|
|
511
|
-
if (sessionId === undefined || typeof turnId !== 'string' || turnId === '')
|
|
512
|
-
return;
|
|
513
|
-
try {
|
|
514
|
-
const current = readSessionIncarnation(sessionId, env);
|
|
515
|
-
if (current !== null)
|
|
516
|
-
recordTurnStart(sessionId, env, current.key, turnId);
|
|
517
|
-
}
|
|
518
|
-
catch {
|
|
519
|
-
// Activity is a hint; it never delays or fails the User's prompt.
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Re-checked before every exchange: the User-owned Project Enablement, and
|
|
524
|
-
* the installed CLI/hook contract. Hooks removed or replaced by a build that
|
|
525
|
-
* does not attend, or an installed CLI older than this attendant, withdraw it.
|
|
526
|
-
*/
|
|
527
|
-
export function attendantGates(deps, cwd, sessionId, harness, runningVersion) {
|
|
528
|
-
try {
|
|
529
|
-
const config = loadConfig({ cwd, env: deps.env, sessionId });
|
|
530
|
-
if (!projectEnabled(projectBinding(cwd, deps.env, config.project.value))) {
|
|
531
|
-
return { ok: false, reason: 'project-disabled' };
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
catch {
|
|
535
|
-
return { ok: false, reason: 'enablement-unavailable' };
|
|
536
|
-
}
|
|
537
|
-
// The handler that started this attendant lives in the Machine layer, or in
|
|
538
|
-
// a Project layer an older build wrote; either keeps the contract in place.
|
|
539
|
-
const attendInstalled = [
|
|
540
|
-
...findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform),
|
|
541
|
-
...findLegacyProjectInstallations(cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform),
|
|
542
|
-
]
|
|
543
|
-
.filter((installation) => installation.harness === harness)
|
|
544
|
-
.some((installation) => harness === 'openclaw'
|
|
545
|
-
? (installation.problems ?? []).length === 0
|
|
546
|
-
: installation.handlers.some((handler) => handlerEvent(handler.command) === 'attend'));
|
|
547
|
-
if (!attendInstalled)
|
|
548
|
-
return { ok: false, reason: 'attend-handler-removed' };
|
|
549
|
-
// Fail closed: an installed contract this attendant cannot establish is not
|
|
550
|
-
// one it may keep attending under.
|
|
551
|
-
const installed = installedCliVersion(inspectHookAdapter(deps.hookAdapterHome, deps.hookPlatform).target);
|
|
552
|
-
if (runningVersion === null || installed === null)
|
|
553
|
-
return { ok: false, reason: 'cli-contract-unknown' };
|
|
554
|
-
const order = compareVersions(installed, runningVersion);
|
|
555
|
-
if (order === 'unparseable')
|
|
556
|
-
return { ok: false, reason: 'cli-contract-unknown' };
|
|
557
|
-
if (order === 'before')
|
|
558
|
-
return { ok: false, reason: 'cli-downgraded' };
|
|
559
|
-
return { ok: true };
|
|
560
|
-
}
|
|
561
|
-
function installedCliVersion(target) {
|
|
562
|
-
if (target === null)
|
|
563
|
-
return null;
|
|
564
|
-
if (isNpxAdapterTarget(target)) {
|
|
565
|
-
// Installers pin npx targets to an exact version: `@raidiant/notifai@1.2.3`.
|
|
566
|
-
const prefix = `${CLI_PACKAGE_NAME}@`;
|
|
567
|
-
return target.spec.startsWith(prefix) ? target.spec.slice(prefix.length) : null;
|
|
568
|
-
}
|
|
569
|
-
try {
|
|
570
|
-
const manifest = path.join(path.dirname(realpathSync(target.scriptPath)), '..', 'package.json');
|
|
571
|
-
const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
572
|
-
return typeof parsed.version === 'string' ? parsed.version : null;
|
|
573
|
-
}
|
|
574
|
-
catch {
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* The session-state lock is shared with the synchronous activation handler
|
|
580
|
-
* running beside this one. This handler is asynchronous and nobody waits on
|
|
581
|
-
* it, so it outlasts a busy moment instead of giving the session up.
|
|
582
|
-
*/
|
|
583
|
-
async function withLockRetry(clock, action) {
|
|
584
|
-
for (let attempt = 1;; attempt += 1) {
|
|
585
|
-
try {
|
|
586
|
-
return action();
|
|
587
|
-
}
|
|
588
|
-
catch (err) {
|
|
589
|
-
if (attempt >= 8 || !(err instanceof Error) || !err.message.startsWith('timed out waiting for file lock')) {
|
|
590
|
-
throw err;
|
|
591
|
-
}
|
|
592
|
-
await clock.sleep(250 * attempt, new AbortController().signal);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
function declaredHookSourcePid(env) {
|
|
597
|
-
const value = Number(env['NOTIFAI_HOOK_SOURCE_PID']);
|
|
598
|
-
return Number.isInteger(value) && value > 0 ? value : undefined;
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Terminal close reaches the attendant as SIGTERM with no SessionEnd; treat
|
|
602
|
-
* any termination signal as "ending, report once".
|
|
603
|
-
*/
|
|
604
|
-
function terminationSignal() {
|
|
605
|
-
const signals = ['SIGTERM', 'SIGHUP', 'SIGINT'];
|
|
606
|
-
let resolve;
|
|
607
|
-
const promise = new Promise((done) => {
|
|
608
|
-
resolve = done;
|
|
609
|
-
});
|
|
610
|
-
const onSignal = () => resolve();
|
|
611
|
-
for (const signal of signals)
|
|
612
|
-
process.on(signal, onSignal);
|
|
613
|
-
return {
|
|
614
|
-
promise,
|
|
615
|
-
dispose: () => {
|
|
616
|
-
for (const signal of signals)
|
|
617
|
-
process.off(signal, onSignal);
|
|
618
|
-
},
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Codex's SessionEnd reports the attended session `ended`.
|
|
623
|
-
*
|
|
624
|
-
* On Codex the attendant cannot: Codex SIGKILLs every unfinished async hook's
|
|
625
|
-
* process group the moment SessionEnd returns, before the attendant's next
|
|
626
|
-
* probe could see the end marker, and may kill it before SessionEnd starts.
|
|
627
|
-
* SessionEnd therefore ends the saved lease of the current incarnation, even
|
|
628
|
-
* without a living attendant. Its exact incarnation and generation let the
|
|
629
|
-
* service fence it as it would the attendant's own report. Without saved
|
|
630
|
-
* fencing identity there is nothing safe to end; a failed report leaves the
|
|
631
|
-
* lease to lapse, and presence reads out of reach.
|
|
632
|
-
*/
|
|
633
|
-
export async function reportCodexSessionEnded(deps, sessionId, lease) {
|
|
634
|
-
if (lease === null)
|
|
635
|
-
return 'no-lease';
|
|
636
|
-
const credential = deps.store.load();
|
|
637
|
-
if (!credential)
|
|
638
|
-
return 'not-paired';
|
|
639
|
-
const client = makeClient(deps, credential.baseUrl, `Bearer nfm_${credential.machineId}.${credential.secret}`, {
|
|
640
|
-
timeoutMs: CODEX_SESSION_END_REPORT_MS,
|
|
641
|
-
});
|
|
642
|
-
const bound = new AbortController();
|
|
643
|
-
const timer = setTimeout(() => bound.abort(), CODEX_SESSION_END_REPORT_MS);
|
|
644
|
-
try {
|
|
645
|
-
await client.attend(sessionId, { incarnation: lease.incarnation, generation: lease.generation, state: 'ended' }, { waitSeconds: 0, signal: bound.signal });
|
|
646
|
-
return 'reported';
|
|
647
|
-
}
|
|
648
|
-
catch {
|
|
649
|
-
return 'failed';
|
|
650
|
-
}
|
|
651
|
-
finally {
|
|
652
|
-
clearTimeout(timer);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
//# sourceMappingURL=commands-hook-attend.js.map
|