@raidiant/notifai 1.0.1 → 2.0.0
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 +50 -0
- package/README.md +17 -7
- package/dist/client.d.ts +5 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/commands.d.ts +10 -1
- package/dist/commands.js +151 -54
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +5 -9
- package/dist/config.js.map +1 -1
- package/dist/credentials.d.ts +54 -3
- package/dist/credentials.js +237 -18
- package/dist/credentials.js.map +1 -1
- package/dist/file-lock.js +15 -6
- package/dist/file-lock.js.map +1 -1
- package/dist/hook-adapter.d.ts +6 -2
- package/dist/hook-adapter.js +110 -29
- package/dist/hook-adapter.js.map +1 -1
- package/dist/hooks.d.ts +15 -1
- package/dist/hooks.js +183 -12
- package/dist/hooks.js.map +1 -1
- package/dist/install-hooks.d.ts +50 -21
- package/dist/install-hooks.js +141 -57
- package/dist/install-hooks.js.map +1 -1
- package/dist/logging.d.ts +1 -1
- package/dist/logging.js +4 -0
- package/dist/logging.js.map +1 -1
- package/dist/main.js +14 -3
- package/dist/main.js.map +1 -1
- package/dist/native-skills.js +5 -10
- package/dist/native-skills.js.map +1 -1
- package/dist/opencode-plugin.d.ts +7 -2
- package/dist/opencode-plugin.js +142 -4
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/platform.d.ts +73 -0
- package/dist/platform.js +143 -0
- package/dist/platform.js.map +1 -0
- package/dist/ui/config-view.js +5 -1
- package/dist/ui/config-view.js.map +1 -1
- package/package.json +11 -10
- package/src/client.test.ts +53 -0
- package/src/client.ts +24 -0
- package/src/commands.test.ts +351 -30
- package/src/commands.ts +207 -49
- package/src/config.ts +11 -9
- package/src/credentials.test.ts +291 -0
- package/src/credentials.ts +274 -17
- package/src/file-lock.ts +34 -9
- package/src/hook-adapter.test.ts +136 -0
- package/src/hook-adapter.ts +120 -28
- package/src/hooks.test.ts +175 -1
- package/src/hooks.ts +241 -12
- package/src/install-hooks.test.ts +174 -33
- package/src/install-hooks.ts +186 -54
- package/src/logging.ts +4 -0
- package/src/main.ts +17 -2
- package/src/native-skills.ts +5 -10
- package/src/opencode-plugin.ts +148 -3
- package/src/platform.test.ts +152 -0
- package/src/platform.ts +173 -0
- package/src/skill.test.ts +32 -1
- package/src/ui/config-view.ts +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
|
9
9
|
and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
|
|
10
10
|
|
|
11
|
+
## [2.0.0](https://github.com/Raidiant-io/notifai/compare/v1.1.0...v2.0.0) (2026-08-13)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### ⚠ BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* **cli:** hide the service URL, move personal prefs, and shorten setup closes
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **cli:** pack a bounded consented slice of the local log ([6fb3d68](https://github.com/Raidiant-io/notifai/commit/6fb3d68cd0057ab901c031c6012318fe464949a9))
|
|
21
|
+
* **cli:** send a test notification by kind, not by inventing a sound ([b90c8ed](https://github.com/Raidiant-io/notifai/commit/b90c8edcbf2ba98db09414701a309cef88aa1cc4))
|
|
22
|
+
* **cli:** support Windows and Linux harness workflows ([779ab6b](https://github.com/Raidiant-io/notifai/commit/779ab6b075900a7a431b809ddcb715fd80836688))
|
|
23
|
+
* compose native skills setup into init ([de6318a](https://github.com/Raidiant-io/notifai/commit/de6318a71166ef75774a217176f1e020116e177b))
|
|
24
|
+
* **skill:** make agents set Notifai up and skip sound/level unless overriding ([9ffd87c](https://github.com/Raidiant-io/notifai/commit/9ffd87c0c79e539f8b92e94ac453407d58bca30f))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* **cli:** cover Windows terminal behavior ([cda7abd](https://github.com/Raidiant-io/notifai/commit/cda7abd134744b2cac44204dd3eb1b20b9038edc))
|
|
30
|
+
* **cli:** drop unfinished import changes from the skill rewrite ([2fd49b2](https://github.com/Raidiant-io/notifai/commit/2fd49b2ba4d4f218138da5dbc3455f1ac07adaf6))
|
|
31
|
+
* **cli:** hide the service URL, move personal prefs, and shorten setup closes ([5b9fe52](https://github.com/Raidiant-io/notifai/commit/5b9fe52d9dc5296e5c3b328a82682a36719d891b))
|
|
32
|
+
* **cli:** install hooks at one scope, not project and global ([f4cd237](https://github.com/Raidiant-io/notifai/commit/f4cd2376b57d9104fbcdcca83990ad9dfe09b296))
|
|
33
|
+
* **cli:** isolate Codex hooks and reply routing ([#1](https://github.com/Raidiant-io/notifai/issues/1)) ([d060b6a](https://github.com/Raidiant-io/notifai/commit/d060b6a18b5937d82f74619a6c1bc711243be26c))
|
|
34
|
+
* **cli:** keep Codex hook install on ~/.codex, ignore CODEX_HOME ([3f75f13](https://github.com/Raidiant-io/notifai/commit/3f75f131c24534d3813a336bb72422f78e24a972))
|
|
35
|
+
* **cli:** offer labeled hooks install and uninstall in the interactive app ([f93d0ae](https://github.com/Raidiant-io/notifai/commit/f93d0ae1c981694de6abb317f93f1647dd1d64fa))
|
|
36
|
+
* **cli:** preserve Codex hook trust on reinstall ([#3](https://github.com/Raidiant-io/notifai/issues/3)) ([5e2c2f4](https://github.com/Raidiant-io/notifai/commit/5e2c2f4d618276573f810a989f41a89ab1053564))
|
|
37
|
+
* **cli:** report observed setup receipts ([99d04bb](https://github.com/Raidiant-io/notifai/commit/99d04bbd487681db3023468fbd88b77a1ba13ceb))
|
|
38
|
+
* **cli:** skip readiness work that cannot change the next action ([7fc2295](https://github.com/Raidiant-io/notifai/commit/7fc2295e4a98b07f0116bad17ea93b8f98b265d1))
|
|
39
|
+
* **cli:** stabilize Linux portability checks ([1462693](https://github.com/Raidiant-io/notifai/commit/146269306ae3dfc91f3559cce802460d3e058925))
|
|
40
|
+
* **cli:** write Codex hooks in the layer's existing representation ([7589862](https://github.com/Raidiant-io/notifai/commit/758986296009c1d50bed66a79a5863ea977aa87e))
|
|
41
|
+
* **cli:** write Codex hooks to config.toml, not hooks.json ([3cc6bb8](https://github.com/Raidiant-io/notifai/commit/3cc6bb8bc9bb592ee4abce3f1421d7c0c6f56d32))
|
|
42
|
+
* make CLI package installable from npm ([67bd6be](https://github.com/Raidiant-io/notifai/commit/67bd6be77d648a12ff0d3036a5853b6a4391bebb))
|
|
43
|
+
|
|
44
|
+
## [1.1.0](https://github.com/Raidiant-io/notifai/compare/v1.0.1...v1.1.0) (2026-08-13)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* **cli:** support Windows and Linux harness workflows ([779ab6b](https://github.com/Raidiant-io/notifai/commit/779ab6b075900a7a431b809ddcb715fd80836688))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Bug Fixes
|
|
53
|
+
|
|
54
|
+
* **cli:** cover Windows terminal behavior ([cda7abd](https://github.com/Raidiant-io/notifai/commit/cda7abd134744b2cac44204dd3eb1b20b9038edc))
|
|
55
|
+
* **cli:** isolate Codex hooks and reply routing ([#1](https://github.com/Raidiant-io/notifai/issues/1)) ([d060b6a](https://github.com/Raidiant-io/notifai/commit/d060b6a18b5937d82f74619a6c1bc711243be26c))
|
|
56
|
+
* **cli:** keep Codex hook install on ~/.codex, ignore CODEX_HOME ([3f75f13](https://github.com/Raidiant-io/notifai/commit/3f75f131c24534d3813a336bb72422f78e24a972))
|
|
57
|
+
* **cli:** preserve Codex hook trust on reinstall ([#3](https://github.com/Raidiant-io/notifai/issues/3)) ([5e2c2f4](https://github.com/Raidiant-io/notifai/commit/5e2c2f4d618276573f810a989f41a89ab1053564))
|
|
58
|
+
* **cli:** report observed setup receipts ([99d04bb](https://github.com/Raidiant-io/notifai/commit/99d04bbd487681db3023468fbd88b77a1ba13ceb))
|
|
59
|
+
* **cli:** stabilize Linux portability checks ([1462693](https://github.com/Raidiant-io/notifai/commit/146269306ae3dfc91f3559cce802460d3e058925))
|
|
60
|
+
|
|
11
61
|
## [Unreleased]
|
|
12
62
|
|
|
13
63
|
## [1.0.1] - 2026-08-13
|
package/README.md
CHANGED
|
@@ -56,6 +56,10 @@ For a harness that cannot resume an idle agent turn, `notifai send
|
|
|
56
56
|
|
|
57
57
|
## Agent harnesses
|
|
58
58
|
|
|
59
|
+
The CLI and managed hook adapters support macOS, Linux, and Windows. Receiving
|
|
60
|
+
still requires the current iPhone or macOS Companion App; native Windows and
|
|
61
|
+
Linux Companion Apps are not shipped yet.
|
|
62
|
+
|
|
59
63
|
```sh
|
|
60
64
|
notifai hooks install
|
|
61
65
|
```
|
|
@@ -67,13 +71,19 @@ or add one it did not see. This is what lets an agent's question reach
|
|
|
67
71
|
your phone without the agent having to cooperate — no question detection,
|
|
68
72
|
no state in a context window that compaction will eat.
|
|
69
73
|
|
|
70
|
-
Every harness definition calls one stable user-level adapter
|
|
71
|
-
`~/.notifai/bin/hook-adapter
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
Every harness definition calls one stable user-level adapter under the account
|
|
75
|
+
home (`~/.notifai/bin/hook-adapter` on macOS/Linux and the corresponding user
|
|
76
|
+
profile path on Windows). Node, package manager, CLI version, checkout,
|
|
77
|
+
configuration directories, and notification preferences are resolved behind
|
|
78
|
+
it, so upgrades and configuration changes do not rewrite trusted hook
|
|
79
|
+
definitions. Windows invokes its JavaScript adapter through the registered Node
|
|
80
|
+
executable; macOS/Linux retain the POSIX adapter and its stable command bytes.
|
|
81
|
+
Claude Code Stop returns immediately into the live inbox route on macOS/Linux;
|
|
82
|
+
on Windows, where upstream exposes no inbox socket, Stop stays open and returns
|
|
83
|
+
the accepted answer through the harness continuation. The first migration may
|
|
84
|
+
require one Codex `/hooks` approval; later repairs keep the same identity.
|
|
85
|
+
Codex's Stop definition omits its timeout so the host default remains
|
|
86
|
+
authoritative; prompt-submit and session-end retain fixed short limits.
|
|
77
87
|
|
|
78
88
|
`notifai doctor` compares Codex trust on a best-effort basis against Codex's
|
|
79
89
|
current persisted representation. Notifai never writes that trust store;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountAccessResponse, BeginPairingResponse, CapabilityDocument, CreateMediaUploadRequestT, CreateMediaUploadResponse, EvidenceSnapshot, ListDevicesResponse, ListRepliesResponse, Platform, PollPairingResponse, SubmissionReceipt, SubmitNotificationRequestT } from '@raidiant/notifai-protocol';
|
|
1
|
+
import type { AccountAccessResponse, BeginPairingResponse, CapabilityDocument, CreateMediaUploadRequestT, CreateMediaUploadResponse, EvidenceSnapshot, GetAgentAcknowledgementResponse, ListDevicesResponse, ListRepliesResponse, Platform, PollPairingResponse, PutAgentAcknowledgementRequestT, PutAgentAcknowledgementResponse, SubmissionReceipt, SubmitNotificationRequestT } from '@raidiant/notifai-protocol';
|
|
2
2
|
import type { Logger } from './logging.js';
|
|
3
3
|
export declare class ApiCallError extends Error {
|
|
4
4
|
status: number;
|
|
@@ -34,6 +34,10 @@ export interface ApiClient {
|
|
|
34
34
|
}): Promise<ListRepliesResponse>;
|
|
35
35
|
/** Retire a question and return the replies committed before the close fence. */
|
|
36
36
|
closeReplies(requestId: string): Promise<ListRepliesResponse>;
|
|
37
|
+
putAgentAcknowledgement(requestId: string, body: PutAgentAcknowledgementRequestT): Promise<PutAgentAcknowledgementResponse>;
|
|
38
|
+
agentAcknowledgement(requestId: string, options: {
|
|
39
|
+
waitSeconds: number;
|
|
40
|
+
}): Promise<GetAgentAcknowledgementResponse>;
|
|
37
41
|
createMediaUpload(body: CreateMediaUploadRequestT): Promise<CreateMediaUploadResponse>;
|
|
38
42
|
uploadMedia(grant: CreateMediaUploadResponse, bytes: Uint8Array): Promise<void>;
|
|
39
43
|
health(): Promise<boolean>;
|
package/dist/client.js
CHANGED
|
@@ -129,6 +129,8 @@ export function createClient(baseUrl, bearer, options = {}) {
|
|
|
129
129
|
evidence: (requestId) => call('GET', `/api/v1/notifications/${requestId}`),
|
|
130
130
|
replies: (requestId, { waitSeconds, afterSeq }) => call('GET', `/api/v1/notifications/${encodeURIComponent(requestId)}/replies?wait_seconds=${waitSeconds}&after_seq=${afterSeq}`, undefined, waitSeconds),
|
|
131
131
|
closeReplies: (requestId) => call('POST', `/api/v1/notifications/${encodeURIComponent(requestId)}/replies/close`),
|
|
132
|
+
putAgentAcknowledgement: (requestId, body) => call('PUT', `/api/v1/notifications/${encodeURIComponent(requestId)}/agent-acknowledgement`, body),
|
|
133
|
+
agentAcknowledgement: (requestId, { waitSeconds }) => call('GET', `/api/v1/notifications/${encodeURIComponent(requestId)}/agent-acknowledgement?wait_seconds=${waitSeconds}`, undefined, waitSeconds),
|
|
132
134
|
createMediaUpload: (body) => call('POST', '/api/v1/media', body),
|
|
133
135
|
uploadMedia: async (grant, bytes) => {
|
|
134
136
|
let response;
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/commands.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export interface CommandDeps {
|
|
|
61
61
|
hookAdapterHome?: string;
|
|
62
62
|
/** Test seam; production uses this process's Node and CLI paths. */
|
|
63
63
|
hookInstallTarget?: HookAdapterTarget;
|
|
64
|
+
/** Test seam; production uses process.platform for adapter format and quoting. */
|
|
65
|
+
hookPlatform?: NodeJS.Platform;
|
|
64
66
|
/** Test seam; production uses fetch against base_url. */
|
|
65
67
|
clientFactory?: (baseUrl: string, bearer: string | null, options?: ClientOptions) => ApiClient;
|
|
66
68
|
/** Test seam for bounded polling without wall-clock sleeps. */
|
|
@@ -228,8 +230,15 @@ export declare function buildQuestions(flags: AskFlags, question: string | undef
|
|
|
228
230
|
* for `ask_grace_seconds` before it reaches any device.
|
|
229
231
|
*/
|
|
230
232
|
export declare function askCommand(deps: CommandDeps, question: string | undefined, flags: AskFlags): number;
|
|
233
|
+
/** Record the one Agent Acknowledgement associated with a replied-to request. */
|
|
234
|
+
export declare function acknowledgeCommand(deps: CommandDeps, requestId: string, flags: {
|
|
235
|
+
text?: string;
|
|
236
|
+
json?: boolean;
|
|
237
|
+
}): Promise<number>;
|
|
231
238
|
/** Retire a question so a late answer is rejected rather than silently lost. */
|
|
232
|
-
export declare function closeCommand(deps: CommandDeps, requestId: string
|
|
239
|
+
export declare function closeCommand(deps: CommandDeps, requestId: string, flags?: {
|
|
240
|
+
json?: boolean;
|
|
241
|
+
}): Promise<number>;
|
|
233
242
|
export interface HooksInstallFlags {
|
|
234
243
|
harness?: string;
|
|
235
244
|
global?: boolean;
|
package/dist/commands.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
2
|
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
3
|
import os from 'node:os';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
|
|
7
|
-
import { CAPABILITIES_V1, QUESTION_TEXT_MAX_LENGTH, REPLY_MAX_QUESTIONS, REPLY_MAX_WINDOW_SECONDS, validateDraft, } from '@raidiant/notifai-protocol';
|
|
6
|
+
import { AGENT_ACKNOWLEDGEMENT_MAX_LENGTH, CAPABILITIES_V1, QUESTION_TEXT_MAX_LENGTH, REPLY_MAX_QUESTIONS, REPLY_MAX_WINDOW_SECONDS, validateDraft, } from '@raidiant/notifai-protocol';
|
|
8
7
|
import { sha256Hex } from '@raidiant/notifai-protocol/node';
|
|
9
8
|
import { ApiCallError, NetworkError, createClient, isRetryableReplyPollError, } from './client.js';
|
|
10
|
-
import { BOOLEAN_CONFIG_KEYS, CONFIG_KEYS, LOG_LEVELS, NUMERIC_CONFIG_KEYS, configBounds, configDefaultValue, findProjectConfigPath, personalProjectConfigPath, globalConfigPath, loadConfig, sessionConfigPath, } from './config.js';
|
|
9
|
+
import { BOOLEAN_CONFIG_KEYS, CONFIG_KEYS, LOG_LEVELS, NUMERIC_CONFIG_KEYS, configBounds, configDefaultValue, findProjectConfigPath, personalProjectConfigPath, globalConfigPath, loadConfig, sessionConfigPath, stateDir, } from './config.js';
|
|
11
10
|
import { acceptedValues, configInfo } from './config-schema.js';
|
|
12
11
|
import { packageVersion, skillsSource } from './release.js';
|
|
13
12
|
import { atomicWriteFileSync } from './atomic-file.js';
|
|
14
13
|
import { withTargetFileLock } from './file-lock.js';
|
|
15
14
|
import { renderConfigExplain, renderConfigList, renderConfigPlain, } from './ui/config-view.js';
|
|
16
15
|
import { firstBlocker, } from './readiness.js';
|
|
17
|
-
import { WAITER_CEILING_SECONDS, handleSessionEnd, handleStop, handleUserPromptSubmit, parseHookInput, pruneAbandonedSessions, readLiveProjectSessionPointers, readMatchingProjectSessionPointer, readProjectSession, readProjectSessionPointer, readSessionState, registerQuestion, } from './hooks.js';
|
|
16
|
+
import { WAITER_CEILING_SECONDS, clearAcknowledgementObligation, handleSessionEnd, handleStop, handleUserPromptSubmit, parseHookInput, pruneAbandonedSessions, readLiveProjectSessionPointers, readMatchingProjectSessionPointer, readProjectSession, readProjectSessionPointer, readSessionState, registerQuestion, } from './hooks.js';
|
|
18
17
|
import { LOG_EVENTS, activeLogPath, archiveLogPaths, logConfigResolved, logSettingsFrom, logsDiskUsage, nullLogger, readLogRecords, renderRecord, } from './logging.js';
|
|
19
18
|
import { BLOCKING_STOP_TIMEOUT_SECONDS, CLAUDE_ASYNC_STOP_TIMEOUT_SECONDS, HARNESSES, applyPlan, buildCursorHookConfig, buildHookConfig, collapseUnusedCodexRepresentation, codexLayerDir, codexLayerPaths, codexRepresentationProblems, codexTrustProblems, codexProjectRoot, detectHarness, detectedHarnesses, findInstallations, handlerEvent, hookDefinitionFiles, inspectCodexLayer, loadCursorSettings, loadSettings, mergeCursorHooks, mergeHooks, removeCursorHooks, removeHooks, settingsFile, } from './install-hooks.js';
|
|
20
19
|
import { HARNESS_CAPABILITIES, HARNESS_LABELS } from './harnesses.js';
|
|
@@ -23,6 +22,7 @@ import { codexWakeRoute, inspectCodexResume, } from './codex-wake.js';
|
|
|
23
22
|
import { inspectHookAdapter, installHookAdapter, isNpxAdapterTarget, } from './hook-adapter.js';
|
|
24
23
|
import { isOurOpencodePlugin, opencodePluginSource, } from './opencode-plugin.js';
|
|
25
24
|
import { CHOICE_USAGE, buildDraft, formatReceipt, parseChoices, receiptExitCode, slugify, } from './send.js';
|
|
25
|
+
import { openUrl } from './platform.js';
|
|
26
26
|
/** The invocation's logger, or one that discards everything. */
|
|
27
27
|
export function log(deps) {
|
|
28
28
|
return deps.logger ?? nullLogger();
|
|
@@ -422,6 +422,7 @@ export async function sendCommand(deps, flags) {
|
|
|
422
422
|
title: flags.title,
|
|
423
423
|
overall: receipt.overall,
|
|
424
424
|
replayed: receipt.replayed,
|
|
425
|
+
agent_acknowledgement_required: receipt.agent_acknowledgement_required,
|
|
425
426
|
deliveries: receipt.deliveries.length,
|
|
426
427
|
wait_seconds: waitSeconds,
|
|
427
428
|
exit: receiptExit,
|
|
@@ -452,6 +453,9 @@ export async function sendCommand(deps, flags) {
|
|
|
452
453
|
type: 'reply_result',
|
|
453
454
|
request_id: receipt.request_id,
|
|
454
455
|
replies: [],
|
|
456
|
+
agent_acknowledgement_required: receipt.agent_acknowledgement_required,
|
|
457
|
+
agent_acknowledgement: null,
|
|
458
|
+
acknowledgement_command: null,
|
|
455
459
|
degraded: false,
|
|
456
460
|
})
|
|
457
461
|
: JSON.stringify(receipt, null, 2));
|
|
@@ -467,17 +471,16 @@ export async function sendCommand(deps, flags) {
|
|
|
467
471
|
});
|
|
468
472
|
recordReplies(deps, receipt.request_id, result.response.replies);
|
|
469
473
|
if (flags.json) {
|
|
470
|
-
deps.io.out(JSON.stringify(
|
|
471
|
-
type: 'reply_result',
|
|
472
|
-
request_id: receipt.request_id,
|
|
473
|
-
replies: result.response.replies,
|
|
474
|
-
degraded: result.degraded,
|
|
475
|
-
}));
|
|
474
|
+
deps.io.out(JSON.stringify(replyResultJson(result.response, result.degraded)));
|
|
476
475
|
}
|
|
477
|
-
else if (result.response.replies.length > 0)
|
|
476
|
+
else if (result.response.replies.length > 0) {
|
|
478
477
|
printReplies(deps, result.response.replies);
|
|
479
|
-
|
|
478
|
+
printAcknowledgementStatus(deps, result.response);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
480
481
|
printNoReply(deps, receipt.request_id, result.response.reply_expires_at);
|
|
482
|
+
printAcknowledgementStatus(deps, result.response);
|
|
483
|
+
}
|
|
481
484
|
if (result.degraded) {
|
|
482
485
|
log(deps).error('cli.error', {
|
|
483
486
|
kind: 'network',
|
|
@@ -601,15 +604,17 @@ export async function repliesCommand(deps, requestedId, flags) {
|
|
|
601
604
|
});
|
|
602
605
|
recordReplies(deps, requestId, result.response.replies);
|
|
603
606
|
if (flags.json) {
|
|
604
|
-
jsonBodies.push(
|
|
607
|
+
jsonBodies.push(replyResultJson(result.response, result.degraded));
|
|
605
608
|
}
|
|
606
609
|
else if (result.response.replies.length > 0) {
|
|
607
610
|
if (flags.pending === true)
|
|
608
611
|
deps.io.out(`pending request ${requestId}`);
|
|
609
612
|
printReplies(deps, result.response.replies);
|
|
613
|
+
printAcknowledgementStatus(deps, result.response);
|
|
610
614
|
}
|
|
611
615
|
else {
|
|
612
616
|
printNoReply(deps, requestId, result.response.reply_expires_at);
|
|
617
|
+
printAcknowledgementStatus(deps, result.response);
|
|
613
618
|
}
|
|
614
619
|
anyReplies ||= result.response.replies.length > 0;
|
|
615
620
|
if (result.degraded)
|
|
@@ -687,7 +692,13 @@ export async function waitForReply(client, requestId, options) {
|
|
|
687
692
|
throw lastTransientError;
|
|
688
693
|
return {
|
|
689
694
|
response: lastResponse ??
|
|
690
|
-
{
|
|
695
|
+
{
|
|
696
|
+
request_id: requestId,
|
|
697
|
+
reply_expires_at: null,
|
|
698
|
+
agent_acknowledgement_required: false,
|
|
699
|
+
agent_acknowledgement: null,
|
|
700
|
+
replies: [],
|
|
701
|
+
},
|
|
691
702
|
timedOut: true,
|
|
692
703
|
// A poll succeeded at some point, so we do not throw — but the last thing
|
|
693
704
|
// we know is that we could not reach the server. Reporting that as a plain
|
|
@@ -723,6 +734,39 @@ function degradedWaitWarning(requestId) {
|
|
|
723
734
|
function isNonNegativeInteger(value) {
|
|
724
735
|
return Number.isInteger(value) && value >= 0;
|
|
725
736
|
}
|
|
737
|
+
function acknowledgementCommand(requestId, required, acknowledgement, hasReply = true) {
|
|
738
|
+
return required && acknowledgement === null && hasReply
|
|
739
|
+
? `notifai acknowledge ${requestId} --text <text>`
|
|
740
|
+
: null;
|
|
741
|
+
}
|
|
742
|
+
function replyResultJson(response, degraded) {
|
|
743
|
+
return {
|
|
744
|
+
type: 'reply_result',
|
|
745
|
+
request_id: response.request_id,
|
|
746
|
+
reply_expires_at: response.reply_expires_at,
|
|
747
|
+
replies: response.replies,
|
|
748
|
+
agent_acknowledgement_required: response.agent_acknowledgement_required,
|
|
749
|
+
agent_acknowledgement: response.agent_acknowledgement,
|
|
750
|
+
acknowledgement_command: acknowledgementCommand(response.request_id, response.agent_acknowledgement_required, response.agent_acknowledgement, response.replies.length > 0),
|
|
751
|
+
degraded,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function printAcknowledgementStatus(deps, response) {
|
|
755
|
+
if (!response.agent_acknowledgement_required) {
|
|
756
|
+
deps.io.out('Agent Acknowledgement: not required for this request.');
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (response.agent_acknowledgement !== null) {
|
|
760
|
+
deps.io.out(`Agent Acknowledgement: recorded at ${response.agent_acknowledgement.created_at}: ${response.agent_acknowledgement.text}`);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (response.replies.length > 0) {
|
|
764
|
+
deps.io.out('Agent Acknowledgement required.');
|
|
765
|
+
deps.io.out(`next: Run \`${acknowledgementCommand(response.request_id, true, null)}\` with concrete text saying what you will do because of the reply.`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
deps.io.out('Agent Acknowledgement: required after a user reply; no reply is recorded yet.');
|
|
769
|
+
}
|
|
726
770
|
function recordReplies(deps, requestId, replies) {
|
|
727
771
|
const logger = log(deps);
|
|
728
772
|
for (const reply of replies) {
|
|
@@ -1128,6 +1172,8 @@ function stopWakeRoute(deps, harness, sessionId, cwd) {
|
|
|
1128
1172
|
if (sessionId === undefined)
|
|
1129
1173
|
return undefined;
|
|
1130
1174
|
if (harness === 'claude-code') {
|
|
1175
|
+
if ((deps.hookPlatform ?? process.platform) === 'win32')
|
|
1176
|
+
return undefined;
|
|
1131
1177
|
return claudeWakeRoute({
|
|
1132
1178
|
sessionId,
|
|
1133
1179
|
cwd,
|
|
@@ -1316,7 +1362,7 @@ export function askCommand(deps, question, flags) {
|
|
|
1316
1362
|
const { active, contested } = resolveActiveHarness(deps.env, deps.cwd, now);
|
|
1317
1363
|
let sessionId;
|
|
1318
1364
|
if (active !== null) {
|
|
1319
|
-
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome);
|
|
1365
|
+
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
|
|
1320
1366
|
const activeInstalled = installations.some((installation) => installation.harness === active.harness);
|
|
1321
1367
|
if (!activeInstalled) {
|
|
1322
1368
|
for (const line of diagnoseActiveHarnessSession(active, 'not-installed', installations, contested)) {
|
|
@@ -1518,11 +1564,11 @@ function activeQuestionRouteProblems(deps, active, installations) {
|
|
|
1518
1564
|
}
|
|
1519
1565
|
}
|
|
1520
1566
|
}
|
|
1521
|
-
for (const problem of inspectHookAdapter(deps.hookAdapterHome).problems) {
|
|
1567
|
+
for (const problem of inspectHookAdapter(deps.hookAdapterHome, deps.hookPlatform).problems) {
|
|
1522
1568
|
problems.push(problem);
|
|
1523
1569
|
}
|
|
1524
1570
|
for (const installation of matching)
|
|
1525
|
-
problems.push(...stopShapeProblems(installation));
|
|
1571
|
+
problems.push(...stopShapeProblems(installation, deps.hookPlatform));
|
|
1526
1572
|
problems.push(...codexTrustProblems(matching, deps.env));
|
|
1527
1573
|
return [...new Set(problems)];
|
|
1528
1574
|
}
|
|
@@ -1574,7 +1620,7 @@ function diagnoseActiveHarnessSession(active, problem, installations, contested
|
|
|
1574
1620
|
* checked after a prompt before assuming that a new session is required.
|
|
1575
1621
|
*/
|
|
1576
1622
|
function diagnoseMissingSession(deps) {
|
|
1577
|
-
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome);
|
|
1623
|
+
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
|
|
1578
1624
|
if (installations.length === 0) {
|
|
1579
1625
|
return [
|
|
1580
1626
|
'Could not tell which harness session this is: no Notifai hooks are installed for this project.',
|
|
@@ -1613,15 +1659,76 @@ function hookActivationAdvice(installations) {
|
|
|
1613
1659
|
}
|
|
1614
1660
|
return `${advice.join('. ')}.`;
|
|
1615
1661
|
}
|
|
1662
|
+
/** Record the one Agent Acknowledgement associated with a replied-to request. */
|
|
1663
|
+
export async function acknowledgeCommand(deps, requestId, flags) {
|
|
1664
|
+
const text = flags.text?.trim() ?? '';
|
|
1665
|
+
if (text.length === 0) {
|
|
1666
|
+
deps.io.err('--text is required and must contain non-whitespace text.');
|
|
1667
|
+
return EXIT.usage;
|
|
1668
|
+
}
|
|
1669
|
+
if (text.length > AGENT_ACKNOWLEDGEMENT_MAX_LENGTH) {
|
|
1670
|
+
deps.io.err(`--text must be at most ${AGENT_ACKNOWLEDGEMENT_MAX_LENGTH} characters after trimming.`);
|
|
1671
|
+
return EXIT.usage;
|
|
1672
|
+
}
|
|
1673
|
+
const logger = log(deps);
|
|
1674
|
+
const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
|
|
1675
|
+
logger.info('acknowledgement.attempted', {
|
|
1676
|
+
request_id: requestId,
|
|
1677
|
+
text,
|
|
1678
|
+
characters: text.length,
|
|
1679
|
+
});
|
|
1680
|
+
const authed = authedClient(deps, config);
|
|
1681
|
+
if (!authed)
|
|
1682
|
+
return EXIT.auth;
|
|
1683
|
+
try {
|
|
1684
|
+
const result = await authed.client.putAgentAcknowledgement(requestId, { text });
|
|
1685
|
+
const output = {
|
|
1686
|
+
request_id: requestId,
|
|
1687
|
+
outcome: result.status,
|
|
1688
|
+
acknowledgement: result.agent_acknowledgement,
|
|
1689
|
+
agent_acknowledgement_required: true,
|
|
1690
|
+
};
|
|
1691
|
+
logger.info('acknowledgement.outcome', {
|
|
1692
|
+
request_id: requestId,
|
|
1693
|
+
outcome: result.status,
|
|
1694
|
+
text: result.agent_acknowledgement.text,
|
|
1695
|
+
created_at: result.agent_acknowledgement.created_at,
|
|
1696
|
+
agent_acknowledgement_required: true,
|
|
1697
|
+
});
|
|
1698
|
+
const sessionId = readProjectSession(deps.cwd, deps.env, (deps.now ?? Date.now)());
|
|
1699
|
+
if (sessionId !== null) {
|
|
1700
|
+
clearAcknowledgementObligation(sessionId, deps.env, requestId);
|
|
1701
|
+
}
|
|
1702
|
+
if (flags.json)
|
|
1703
|
+
deps.io.out(JSON.stringify(output, null, 2));
|
|
1704
|
+
else {
|
|
1705
|
+
deps.io.out(`Agent Acknowledgement ${result.status} for ${requestId} at ${result.agent_acknowledgement.created_at}.`);
|
|
1706
|
+
}
|
|
1707
|
+
return EXIT.ok;
|
|
1708
|
+
}
|
|
1709
|
+
catch (err) {
|
|
1710
|
+
return reportError(deps, err, { operation: 'agent_acknowledgement', request_id: requestId });
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1616
1713
|
/** Retire a question so a late answer is rejected rather than silently lost. */
|
|
1617
|
-
export async function closeCommand(deps, requestId) {
|
|
1714
|
+
export async function closeCommand(deps, requestId, flags = {}) {
|
|
1618
1715
|
const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
|
|
1619
1716
|
const authed = authedClient(deps, config);
|
|
1620
1717
|
if (!authed)
|
|
1621
1718
|
return EXIT.auth;
|
|
1622
1719
|
try {
|
|
1623
|
-
await authed.client.closeReplies(requestId);
|
|
1624
|
-
|
|
1720
|
+
const response = await authed.client.closeReplies(requestId);
|
|
1721
|
+
if (flags.json) {
|
|
1722
|
+
deps.io.out(JSON.stringify({
|
|
1723
|
+
...response,
|
|
1724
|
+
acknowledgement_command: acknowledgementCommand(response.request_id, response.agent_acknowledgement_required, response.agent_acknowledgement, response.replies.length > 0),
|
|
1725
|
+
}, null, 2));
|
|
1726
|
+
}
|
|
1727
|
+
else {
|
|
1728
|
+
deps.io.out(`Closed the reply window for ${requestId}.`);
|
|
1729
|
+
if (response.replies.length > 0)
|
|
1730
|
+
printAcknowledgementStatus(deps, response);
|
|
1731
|
+
}
|
|
1625
1732
|
return EXIT.ok;
|
|
1626
1733
|
}
|
|
1627
1734
|
catch (err) {
|
|
@@ -1687,16 +1794,18 @@ export function hooksInstallCommand(deps, flags) {
|
|
|
1687
1794
|
return EXIT.usage;
|
|
1688
1795
|
const adapterTarget = resolveHookAdapterTarget(deps, flags);
|
|
1689
1796
|
const scriptPath = flags.scriptPath ?? fileHookInstallTarget(adapterTarget)?.scriptPath ?? process.argv[1] ?? 'notifai';
|
|
1797
|
+
const hookPlatform = deps.hookPlatform ?? process.platform;
|
|
1798
|
+
const nodePath = adapterTarget.execPath;
|
|
1690
1799
|
let adapterPath;
|
|
1691
1800
|
try {
|
|
1692
|
-
adapterPath = installHookAdapter(adapterTarget, deps.hookAdapterHome).path;
|
|
1801
|
+
adapterPath = installHookAdapter(adapterTarget, deps.hookAdapterHome, hookPlatform).path;
|
|
1693
1802
|
}
|
|
1694
1803
|
catch (err) {
|
|
1695
1804
|
deps.io.err(`Could not prepare the stable hook adapter: ${String(err)}`);
|
|
1696
1805
|
return EXIT.failed;
|
|
1697
1806
|
}
|
|
1698
1807
|
const wantGlobal = flags.global === true;
|
|
1699
|
-
const existing = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome).filter((installation) => installation.harness === harness);
|
|
1808
|
+
const existing = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === harness);
|
|
1700
1809
|
const otherScope = existing.filter((installation) => installation.global !== wantGlobal);
|
|
1701
1810
|
if (!wantGlobal && otherScope.some((installation) => installation.global)) {
|
|
1702
1811
|
const globalFile = otherScope.find((installation) => installation.global)?.file;
|
|
@@ -1708,13 +1817,15 @@ export function hooksInstallCommand(deps, flags) {
|
|
|
1708
1817
|
return EXIT.failed;
|
|
1709
1818
|
}
|
|
1710
1819
|
}
|
|
1711
|
-
const file = settingsFile(harness, wantGlobal, deps.cwd, deps.env);
|
|
1820
|
+
const file = settingsFile(harness, wantGlobal, deps.cwd, deps.env, hookPlatform);
|
|
1712
1821
|
// OpenCode's adapter is a generated plugin module rather than a handler
|
|
1713
1822
|
// merged into a settings document, so it owns the whole file.
|
|
1714
1823
|
if (harness === 'opencode') {
|
|
1715
1824
|
return installOpencodePlugin(deps, file, {
|
|
1716
1825
|
adapterPath,
|
|
1717
1826
|
timeoutSeconds: BLOCKING_STOP_TIMEOUT_SECONDS,
|
|
1827
|
+
platform: hookPlatform,
|
|
1828
|
+
nodePath,
|
|
1718
1829
|
});
|
|
1719
1830
|
}
|
|
1720
1831
|
if (harness === 'cursor') {
|
|
@@ -1724,6 +1835,8 @@ export function hooksInstallCommand(deps, flags) {
|
|
|
1724
1835
|
const result = mergeCursorHooks(document, buildCursorHookConfig({
|
|
1725
1836
|
adapterPath,
|
|
1726
1837
|
harness: 'cursor',
|
|
1838
|
+
platform: hookPlatform,
|
|
1839
|
+
nodePath,
|
|
1727
1840
|
}), scriptPath);
|
|
1728
1841
|
applyPlan(file, result.document);
|
|
1729
1842
|
return result;
|
|
@@ -1745,6 +1858,8 @@ export function hooksInstallCommand(deps, flags) {
|
|
|
1745
1858
|
const result = mergeHooks(document, buildHookConfig({
|
|
1746
1859
|
adapterPath,
|
|
1747
1860
|
harness,
|
|
1861
|
+
platform: hookPlatform,
|
|
1862
|
+
nodePath,
|
|
1748
1863
|
}), scriptPath);
|
|
1749
1864
|
applyPlan(file, result.document);
|
|
1750
1865
|
return result;
|
|
@@ -1818,7 +1933,7 @@ export function hooksUninstallCommand(deps, flags) {
|
|
|
1818
1933
|
if (!harness)
|
|
1819
1934
|
return EXIT.usage;
|
|
1820
1935
|
const scriptPath = flags.scriptPath ?? process.argv[1] ?? 'notifai';
|
|
1821
|
-
const file = settingsFile(harness, flags.global ?? false, deps.cwd, deps.env);
|
|
1936
|
+
const file = settingsFile(harness, flags.global ?? false, deps.cwd, deps.env, deps.hookPlatform);
|
|
1822
1937
|
if (harness === 'opencode') {
|
|
1823
1938
|
try {
|
|
1824
1939
|
return withTargetFileLock(file, () => {
|
|
@@ -2515,9 +2630,7 @@ function setupProofPath(deps) {
|
|
|
2515
2630
|
// the resolved absolute path is still a stable local identity for it.
|
|
2516
2631
|
}
|
|
2517
2632
|
const digest = createHash('sha256').update(projectDir).digest('hex').slice(0, 32);
|
|
2518
|
-
|
|
2519
|
-
const base = xdg && xdg !== '' ? xdg : path.join(os.homedir(), '.local', 'state');
|
|
2520
|
-
return path.join(base, 'notifai', 'setup-proofs', `${digest}.json`);
|
|
2633
|
+
return path.join(stateDir(deps.env), 'setup-proofs', `${digest}.json`);
|
|
2521
2634
|
}
|
|
2522
2635
|
function readSetupProof(deps) {
|
|
2523
2636
|
const file = setupProofPath(deps);
|
|
@@ -2816,6 +2929,7 @@ async function runSetupProof(deps) {
|
|
|
2816
2929
|
const observed = observedCompanionReceipt(snapshot, proof.device_id);
|
|
2817
2930
|
if (observed) {
|
|
2818
2931
|
spinner?.stop(`Receipt observed from ${observed.delivery.device_name}`);
|
|
2932
|
+
deps.io.out(`Companion Receipt (the app's delivery confirmation) observed from ${observed.delivery.device_name}.`);
|
|
2819
2933
|
return 'closed';
|
|
2820
2934
|
}
|
|
2821
2935
|
lastError = null;
|
|
@@ -3559,7 +3673,7 @@ function remedyLine(state) {
|
|
|
3559
3673
|
* is, is each check's own call (`HookCheck`).
|
|
3560
3674
|
*/
|
|
3561
3675
|
function hookStates(deps) {
|
|
3562
|
-
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome);
|
|
3676
|
+
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
|
|
3563
3677
|
const active = activeHarnessSession(deps.env, deps.cwd, (deps.now ?? Date.now)());
|
|
3564
3678
|
const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
|
|
3565
3679
|
const settings = {
|
|
@@ -3647,12 +3761,12 @@ function hookStates(deps) {
|
|
|
3647
3761
|
* hashes into `trusted_hash`, and an untrusted handler is simply not run.
|
|
3648
3762
|
* - Everything else blocks its turn and needs a ceiling above the wait.
|
|
3649
3763
|
*/
|
|
3650
|
-
function stopShapeProblems(installation) {
|
|
3764
|
+
function stopShapeProblems(installation, platform = process.platform) {
|
|
3651
3765
|
if (installation.harness === 'codex')
|
|
3652
3766
|
return [];
|
|
3653
3767
|
const problems = [];
|
|
3654
3768
|
for (const handler of installation.handlers.filter((entry) => handlerEvent(entry.command) === 'stop')) {
|
|
3655
|
-
if (installation.harness === 'claude-code') {
|
|
3769
|
+
if (installation.harness === 'claude-code' && platform !== 'win32') {
|
|
3656
3770
|
if (handler.async !== true) {
|
|
3657
3771
|
problems.push(`${installation.file} declares a blocking Stop handler; the Claude Code wake route needs \`async: true\` so the turn ends while the waiter runs`);
|
|
3658
3772
|
}
|
|
@@ -3669,7 +3783,7 @@ function stopShapeProblems(installation) {
|
|
|
3669
3783
|
}
|
|
3670
3784
|
function hookChecks(deps) {
|
|
3671
3785
|
const checks = [];
|
|
3672
|
-
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome);
|
|
3786
|
+
const installations = findInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform);
|
|
3673
3787
|
// Not having hooks is a setup someone chose, not a fault: `send` works
|
|
3674
3788
|
// without them. A setup that cannot work is what deserves to go red.
|
|
3675
3789
|
if (installations.length === 0) {
|
|
@@ -3766,7 +3880,7 @@ function hookChecks(deps) {
|
|
|
3766
3880
|
: `${stale.join('; ')} — rerun \`notifai hooks install\` to drop ${stale.length === 1 ? 'it' : 'them'}`,
|
|
3767
3881
|
});
|
|
3768
3882
|
const adapterProblems = installations.flatMap((installation) => (installation.problems ?? []).map((problem) => `${installation.file}: ${problem}`));
|
|
3769
|
-
const sharedAdapter = inspectHookAdapter(deps.hookAdapterHome);
|
|
3883
|
+
const sharedAdapter = inspectHookAdapter(deps.hookAdapterHome, deps.hookPlatform);
|
|
3770
3884
|
adapterProblems.push(...sharedAdapter.problems);
|
|
3771
3885
|
if (adapterProblems.length > 0) {
|
|
3772
3886
|
checks.push({
|
|
@@ -3794,7 +3908,7 @@ function hookChecks(deps) {
|
|
|
3794
3908
|
},
|
|
3795
3909
|
}),
|
|
3796
3910
|
});
|
|
3797
|
-
const shapeProblems = installations.flatMap((installation) => stopShapeProblems(installation).map((problem) => `${problem} — run \`notifai hooks install --harness ${installation.harness}${installation.global ? ' --global' : ''}\``));
|
|
3911
|
+
const shapeProblems = installations.flatMap((installation) => stopShapeProblems(installation, deps.hookPlatform).map((problem) => `${problem} — run \`notifai hooks install --harness ${installation.harness}${installation.global ? ' --global' : ''}\``));
|
|
3798
3912
|
checks.push({
|
|
3799
3913
|
name: 'hooks (stop shape)',
|
|
3800
3914
|
ok: shapeProblems.length === 0,
|
|
@@ -3963,7 +4077,7 @@ function wakeRouteCheck(deps, active, activeInstallations) {
|
|
|
3963
4077
|
if (active.harness === 'claude-code') {
|
|
3964
4078
|
const readiness = inspectClaudeInbox({
|
|
3965
4079
|
pid: deps.claudeSourcePid ?? claudeSessionPid(deps.env),
|
|
3966
|
-
platform: process.platform,
|
|
4080
|
+
platform: deps.hookPlatform ?? process.platform,
|
|
3967
4081
|
readDescriptor: deps.claudeWake?.readDescriptor ?? systemClaudeWakeAdapters(deps.env).readDescriptor,
|
|
3968
4082
|
socketExists: (socketPath) => existsSync(socketPath),
|
|
3969
4083
|
});
|
|
@@ -3978,7 +4092,7 @@ function wakeRouteCheck(deps, active, activeInstallations) {
|
|
|
3978
4092
|
}
|
|
3979
4093
|
if (active.harness === 'codex') {
|
|
3980
4094
|
const readiness = inspectCodexResume(deps.env, {
|
|
3981
|
-
platform: process.platform,
|
|
4095
|
+
platform: deps.hookPlatform ?? process.platform,
|
|
3982
4096
|
directoryExists: (directory) => existsSync(directory),
|
|
3983
4097
|
});
|
|
3984
4098
|
return {
|
|
@@ -4133,24 +4247,7 @@ export function realIo(env = process.env) {
|
|
|
4133
4247
|
return log.error(message);
|
|
4134
4248
|
}
|
|
4135
4249
|
},
|
|
4136
|
-
openUrl
|
|
4137
|
-
try {
|
|
4138
|
-
if (process.platform === 'darwin') {
|
|
4139
|
-
spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
4140
|
-
}
|
|
4141
|
-
else if (process.platform === 'win32') {
|
|
4142
|
-
// `start` is a cmd builtin; spawn('start', …) cannot work on Windows.
|
|
4143
|
-
// Empty title argument keeps URLs with special characters intact.
|
|
4144
|
-
spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();
|
|
4145
|
-
}
|
|
4146
|
-
else {
|
|
4147
|
-
spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
catch {
|
|
4151
|
-
// Browser opening is best-effort; the URL is printed anyway.
|
|
4152
|
-
}
|
|
4153
|
-
},
|
|
4250
|
+
openUrl,
|
|
4154
4251
|
};
|
|
4155
4252
|
}
|
|
4156
4253
|
//# sourceMappingURL=commands.js.map
|