@pushary/agent-hooks 1.9.73 → 1.9.75
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 +13 -0
- package/dist/{api-JVWXPKEW.js → api-L3BIISGS.js} +10 -10
- package/dist/bin/pushary-bell-hook.js +26 -25
- package/dist/bin/pushary-bell.js +14 -14
- package/dist/bin/pushary-claude.js +54 -82
- package/dist/bin/pushary-clean.js +37 -37
- package/dist/bin/pushary-codex-bridge.js +24 -24
- package/dist/bin/pushary-codex-hook.js +35 -34
- package/dist/bin/pushary-codex-session.js +8 -8
- package/dist/bin/pushary-codex.js +15 -15
- package/dist/bin/pushary-connect.js +26 -26
- package/dist/bin/pushary-cowork.js +5 -5
- package/dist/bin/pushary-daemon.js +1292 -63
- package/dist/bin/pushary-disconnect.js +31 -31
- package/dist/bin/pushary-display-hook.d.ts +1 -0
- package/dist/bin/pushary-display-hook.js +57 -0
- package/dist/bin/pushary-doctor.js +50 -50
- package/dist/bin/pushary-elicitation-hook.js +28 -27
- package/dist/bin/pushary-gemini-bridge.js +16 -16
- package/dist/bin/pushary-gemini-hook.js +30 -29
- package/dist/bin/pushary-hook.js +30 -29
- package/dist/bin/pushary-login.js +20 -20
- package/dist/bin/pushary-logout.js +30 -30
- package/dist/bin/pushary-mcp.js +14 -14
- package/dist/bin/pushary-mode.js +18 -18
- package/dist/bin/pushary-notification-hook.js +25 -24
- package/dist/bin/pushary-opencode-hook.js +29 -28
- package/dist/bin/pushary-permission-denied-hook.js +30 -29
- package/dist/bin/pushary-permission-hook.js +30 -29
- package/dist/bin/pushary-post-hook.js +31 -29
- package/dist/bin/pushary-prompt-hook.js +31 -29
- package/dist/bin/pushary-session-end-hook.js +27 -26
- package/dist/bin/pushary-session-start-hook.js +35 -34
- package/dist/bin/pushary-setup.js +61 -60
- package/dist/bin/pushary-stats.js +6 -6
- package/dist/bin/pushary-status.js +31 -31
- package/dist/bin/pushary-stop-hook.js +31 -26
- package/dist/bin/pushary-stopfailure-hook.js +25 -24
- package/dist/bin/pushary-suggestions.js +18 -18
- package/dist/bin/pushary-transcript-register.js +17 -16
- package/dist/bin/pushary-transcripts.js +19 -19
- package/dist/bin/pushary-upgrade.js +45 -45
- package/dist/bin/pushary-wait.js +18 -18
- package/dist/bin/pushary.js +7 -7
- package/dist/{chunk-TQ2L3ECX.js → chunk-2DB7TBA7.js} +2 -2
- package/dist/{chunk-7S46LCKD.js → chunk-2J4IVUEA.js} +3 -2
- package/dist/{chunk-7M4AA7QF.js → chunk-32JJ5KV3.js} +4 -4
- package/dist/{chunk-PM2MHBMF.js → chunk-3PPL64AG.js} +3 -3
- package/dist/{chunk-UIF7HWHX.js → chunk-3SAF4QXU.js} +4 -4
- package/dist/{chunk-BLX6KRK5.js → chunk-4DH54LS7.js} +1 -1
- package/dist/{chunk-N4VRSFRZ.js → chunk-4GWKKUQ5.js} +5 -5
- package/dist/{chunk-4LAD5A2Q.js → chunk-4T25BBBO.js} +4 -4
- package/dist/{chunk-R5PZPROX.js → chunk-53D6FFVE.js} +1 -1
- package/dist/{chunk-U3EBM73X.js → chunk-5EQOUZYE.js} +2 -2
- package/dist/{chunk-WXV7J3DG.js → chunk-6BFLDHPM.js} +3 -3
- package/dist/{chunk-NJSW2REO.js → chunk-7FNSQMQ2.js} +4 -4
- package/dist/{chunk-EUEGCEA7.js → chunk-7O54GE5C.js} +3 -3
- package/dist/{chunk-RZVWEDIS.js → chunk-AUDYAVNM.js} +3 -3
- package/dist/{chunk-UUCRWEEU.js → chunk-BGEUAJYL.js} +2 -2
- package/dist/{chunk-4UIR25GA.js → chunk-BRG4XS2H.js} +1 -1
- package/dist/{chunk-F3WI32HG.js → chunk-CI6MDBQB.js} +7 -7
- package/dist/{chunk-SQ5XCMPB.js → chunk-CLHRSK5L.js} +4 -4
- package/dist/{chunk-JNTYPMVU.js → chunk-D4X4BQVQ.js} +10 -10
- package/dist/{chunk-7FROPPIH.js → chunk-DARPCQPY.js} +2 -2
- package/dist/{chunk-WJJVCDEU.js → chunk-DCHZ5JOI.js} +6 -6
- package/dist/{chunk-KDQLKND5.js → chunk-DOG3BQFO.js} +6 -6
- package/dist/{chunk-QMPM3CAN.js → chunk-DUOBFECC.js} +1 -1
- package/dist/{chunk-3QBKMSOZ.js → chunk-DX65IDBI.js} +1 -1
- package/dist/{chunk-3RAWW5DH.js → chunk-E5XYFIYL.js} +6 -6
- package/dist/{chunk-Z7AIYWZ2.js → chunk-EH5OLIP2.js} +2 -2
- package/dist/{chunk-IN3MPEFA.js → chunk-EM2RSWDA.js} +5 -5
- package/dist/{chunk-CMYFV3VC.js → chunk-EOGVWCLF.js} +5 -5
- package/dist/{chunk-CGWUVEOB.js → chunk-EVBG5YUO.js} +3 -3
- package/dist/{chunk-PNXNWZ5P.js → chunk-FEC3JUIH.js} +3 -3
- package/dist/{chunk-3JJI5O5N.js → chunk-GAAN3Y7V.js} +1 -1
- package/dist/{chunk-VBSPP4QY.js → chunk-HRRGIVIL.js} +3 -3
- package/dist/{chunk-2QB7HJVJ.js → chunk-HXV2SY2V.js} +2 -2
- package/dist/{chunk-JWBZUTYF.js → chunk-I6TIZMM3.js} +5 -5
- package/dist/{chunk-AXZ67XVN.js → chunk-I73UQU7K.js} +1 -1
- package/dist/{chunk-ZBZBYUNZ.js → chunk-JCZWWBBA.js} +5 -5
- package/dist/{chunk-AJWGZJFS.js → chunk-JFVLMWKY.js} +131 -28
- package/dist/{chunk-GBR6E7AY.js → chunk-JOMG6J64.js} +1 -1
- package/dist/{chunk-EITOMPFB.js → chunk-KDUWFGY2.js} +4 -4
- package/dist/{chunk-NGNBX3YH.js → chunk-LIDUPDEY.js} +4 -4
- package/dist/{chunk-YZ6DMGZZ.js → chunk-LRYZXY4V.js} +1 -1
- package/dist/{chunk-TTGBS5JB.js → chunk-M7BXAWUC.js} +3 -3
- package/dist/{chunk-KSLBS3II.js → chunk-MD4UJICD.js} +6 -5
- package/dist/{chunk-2HFC4W6J.js → chunk-NCTABQMC.js} +4 -3
- package/dist/{chunk-PGJMSREY.js → chunk-P4TWTQ6V.js} +8 -8
- package/dist/{chunk-XKQ7CQGG.js → chunk-PGLNE556.js} +1 -1
- package/dist/{chunk-2NG3ANMT.js → chunk-PV5XEBFI.js} +9 -9
- package/dist/{chunk-JYOR57ZW.js → chunk-QDUDXJAF.js} +3 -3
- package/dist/{chunk-AVHL2ZTS.js → chunk-QRS7FQHE.js} +455 -172
- package/dist/{chunk-TFDMJHAB.js → chunk-R7V6BPEV.js} +1 -1
- package/dist/{chunk-S63VIM7E.js → chunk-RPKFXNOJ.js} +1 -1
- package/dist/{chunk-56GK65Z6.js → chunk-SFJOGZQV.js} +3 -3
- package/dist/{chunk-VJUDBZGC.js → chunk-SHW6KK2K.js} +2 -2
- package/dist/{chunk-S7VMHSJW.js → chunk-SIJHPGUW.js} +5 -5
- package/dist/{chunk-7RX5TOU3.js → chunk-SNUHX5GQ.js} +6 -6
- package/dist/{chunk-O4EAMVSC.js → chunk-SOHJ37NA.js} +5 -5
- package/dist/{chunk-JC7XUZST.js → chunk-SONZCJY5.js} +22 -3
- package/dist/{chunk-FB262EOM.js → chunk-T6RWREWU.js} +1 -1
- package/dist/{chunk-QAWWTRDD.js → chunk-TPYZFYQ5.js} +4 -4
- package/dist/{chunk-FK3JSDH5.js → chunk-UBANQ4EZ.js} +13 -13
- package/dist/{chunk-EUHTC7K5.js → chunk-UEJTWQVF.js} +1 -1
- package/dist/{chunk-PPKIL4EL.js → chunk-UIS332AZ.js} +1 -1
- package/dist/{chunk-W47XZJSH.js → chunk-UU3CDLIX.js} +2 -2
- package/dist/{chunk-FAKEKFCI.js → chunk-VGHWK5Z2.js} +5 -3
- package/dist/{chunk-2JFEMKDG.js → chunk-WL3V2UV4.js} +6 -6
- package/dist/{chunk-OJ3ZD5VX.js → chunk-XO7LP3JC.js} +1 -1
- package/dist/{chunk-DT6VCMXS.js → chunk-Z7ORL6SC.js} +2 -2
- package/dist/{chunk-AC5TVOAH.js → chunk-ZX3KEXO3.js} +343 -84
- package/dist/{codex-app-cards-switch-M7VH7AZH.js → codex-app-cards-switch-CN3G7GZW.js} +11 -11
- package/dist/{codex-desktop-launch-6DASTBBA.js → codex-desktop-launch-GFZXCDSL.js} +1 -1
- package/dist/{codex-session-launch-2XSMFA4K.js → codex-session-launch-4F2UGQYE.js} +18 -18
- package/dist/{config-7QQOIFTW.js → config-BLHDUDCS.js} +3 -3
- package/dist/{global-upgrade-XAKX26AN.js → global-upgrade-IZEC22BQ.js} +10 -10
- package/dist/{identity-7AS5MBNM.js → identity-3HGTEP5Q.js} +4 -4
- package/dist/{live-capabilities-DV4F7MIB.js → live-capabilities-AZK664T7.js} +5 -5
- package/dist/{paths-GSMXYKT5.js → paths-ROMZUSEX.js} +3 -3
- package/dist/{reapply-MR7UWBXR.js → reapply-IJYRR4YK.js} +27 -27
- package/dist/src/index.js +31 -30
- package/dist/{upgrade-lock-6OTHLCBZ.js → upgrade-lock-FGEZPX56.js} +4 -4
- package/dist/{usage-H53PO3VQ.js → usage-HCURE44F.js} +3 -3
- package/package.json +4 -2
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
inspectConfiguredManagedProviders
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-D4X4BQVQ.js";
|
|
5
5
|
import {
|
|
6
6
|
HEARTBEAT_INTERVAL_MS,
|
|
7
7
|
sendHeartbeat
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-JCZWWBBA.js";
|
|
9
|
+
import "../chunk-SHW6KK2K.js";
|
|
10
10
|
import {
|
|
11
11
|
readSignedOutDiagnosis
|
|
12
|
-
} from "../chunk-
|
|
13
|
-
import
|
|
12
|
+
} from "../chunk-HXV2SY2V.js";
|
|
13
|
+
import {
|
|
14
|
+
HERMES_INSTALL_MARKER_FILE,
|
|
15
|
+
defaultHermesHome,
|
|
16
|
+
parseHermesInstallMarker
|
|
17
|
+
} from "../chunk-VGHWK5Z2.js";
|
|
14
18
|
import {
|
|
15
19
|
writeHuman
|
|
16
20
|
} from "../chunk-QCGNGEGE.js";
|
|
17
|
-
import "../chunk-
|
|
18
|
-
import "../chunk-
|
|
19
|
-
import "../chunk-
|
|
20
|
-
import "../chunk-
|
|
21
|
+
import "../chunk-MD4UJICD.js";
|
|
22
|
+
import "../chunk-7O54GE5C.js";
|
|
23
|
+
import "../chunk-UU3CDLIX.js";
|
|
24
|
+
import "../chunk-E5XYFIYL.js";
|
|
21
25
|
import {
|
|
22
26
|
exitOnHelpFlag
|
|
23
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-XO7LP3JC.js";
|
|
24
28
|
import {
|
|
25
29
|
codexBinaryIsBroken,
|
|
26
30
|
detectProviderLiveCapabilities,
|
|
@@ -29,8 +33,8 @@ import {
|
|
|
29
33
|
launchableProviders,
|
|
30
34
|
resolveControlPolicy,
|
|
31
35
|
trackProviderInventory
|
|
32
|
-
} from "../chunk-
|
|
33
|
-
import "../chunk-
|
|
36
|
+
} from "../chunk-KDUWFGY2.js";
|
|
37
|
+
import "../chunk-QDUDXJAF.js";
|
|
34
38
|
import {
|
|
35
39
|
SESSION_REAP_SILENCE_MS,
|
|
36
40
|
describeIdleWindow,
|
|
@@ -38,16 +42,16 @@ import {
|
|
|
38
42
|
listProcessProofs,
|
|
39
43
|
ownsLiveProcess,
|
|
40
44
|
silentSpawnedProofs
|
|
41
|
-
} from "../chunk-
|
|
42
|
-
import "../chunk-
|
|
45
|
+
} from "../chunk-FEC3JUIH.js";
|
|
46
|
+
import "../chunk-R7V6BPEV.js";
|
|
43
47
|
import {
|
|
44
48
|
readLocalAutoUpdateReport
|
|
45
|
-
} from "../chunk-
|
|
49
|
+
} from "../chunk-M7BXAWUC.js";
|
|
46
50
|
import {
|
|
47
51
|
startRegisteredTranscripts
|
|
48
|
-
} from "../chunk-
|
|
49
|
-
import "../chunk-
|
|
50
|
-
import "../chunk-
|
|
52
|
+
} from "../chunk-ZX3KEXO3.js";
|
|
53
|
+
import "../chunk-T6RWREWU.js";
|
|
54
|
+
import "../chunk-3SAF4QXU.js";
|
|
51
55
|
import {
|
|
52
56
|
DAEMON_LEASE_RENEW_INTERVAL_MS,
|
|
53
57
|
acquireDaemonLease,
|
|
@@ -64,52 +68,1273 @@ import {
|
|
|
64
68
|
requestDaemonHandoff,
|
|
65
69
|
stopDaemonRunning,
|
|
66
70
|
writeDaemonNeedsLogin
|
|
67
|
-
} from "../chunk-
|
|
68
|
-
import
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
} from "../chunk-DCHZ5JOI.js";
|
|
72
|
+
import {
|
|
73
|
+
hookOwnerOf
|
|
74
|
+
} from "../chunk-TPYZFYQ5.js";
|
|
75
|
+
import "../chunk-NCTABQMC.js";
|
|
76
|
+
import "../chunk-4DH54LS7.js";
|
|
71
77
|
import "../chunk-NMUI3ZWG.js";
|
|
72
|
-
import "../chunk-
|
|
73
|
-
import "../chunk-
|
|
78
|
+
import "../chunk-SONZCJY5.js";
|
|
79
|
+
import "../chunk-XMSDA5WA.js";
|
|
80
|
+
import "../chunk-RPKFXNOJ.js";
|
|
74
81
|
import {
|
|
75
82
|
spawnClaude
|
|
76
83
|
} from "../chunk-XHKBHWLX.js";
|
|
77
84
|
import {
|
|
78
85
|
readControlMode
|
|
79
|
-
} from "../chunk-
|
|
80
|
-
import
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
} from "../chunk-LIDUPDEY.js";
|
|
87
|
+
import {
|
|
88
|
+
pendingCommandReceipts,
|
|
89
|
+
removePendingCommand
|
|
90
|
+
} from "../chunk-JFVLMWKY.js";
|
|
91
|
+
import "../chunk-DUOBFECC.js";
|
|
92
|
+
import "../chunk-EOGVWCLF.js";
|
|
83
93
|
import "../chunk-POFBE6ZY.js";
|
|
84
|
-
import "../chunk-
|
|
85
|
-
import "../chunk-
|
|
86
|
-
import "../chunk-
|
|
94
|
+
import "../chunk-32JJ5KV3.js";
|
|
95
|
+
import "../chunk-SIJHPGUW.js";
|
|
96
|
+
import "../chunk-SOHJ37NA.js";
|
|
87
97
|
import {
|
|
88
98
|
getMachineId
|
|
89
|
-
} from "../chunk-
|
|
90
|
-
import "../chunk-
|
|
99
|
+
} from "../chunk-BGEUAJYL.js";
|
|
100
|
+
import "../chunk-DARPCQPY.js";
|
|
91
101
|
import {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
import "../chunk-7S46LCKD.js";
|
|
102
|
+
claudeConfigDir,
|
|
103
|
+
codexHome
|
|
104
|
+
} from "../chunk-2J4IVUEA.js";
|
|
96
105
|
import {
|
|
97
|
-
resolveBinary
|
|
98
|
-
|
|
106
|
+
resolveBinary,
|
|
107
|
+
writeJsonAtomicDurable
|
|
108
|
+
} from "../chunk-AUDYAVNM.js";
|
|
99
109
|
import {
|
|
100
110
|
getApiKey,
|
|
101
111
|
getBaseUrl
|
|
102
|
-
} from "../chunk-
|
|
103
|
-
import "../chunk-
|
|
112
|
+
} from "../chunk-53D6FFVE.js";
|
|
113
|
+
import "../chunk-PPY5N2E6.js";
|
|
114
|
+
import {
|
|
115
|
+
EXIT
|
|
116
|
+
} from "../chunk-KZERVKTD.js";
|
|
117
|
+
import "../chunk-UIS332AZ.js";
|
|
104
118
|
import "../chunk-QTOLNRET.js";
|
|
105
|
-
import
|
|
119
|
+
import {
|
|
120
|
+
CLAUDE_INBOX,
|
|
121
|
+
CLAUDE_INBOX_MAX_SESSIONS,
|
|
122
|
+
CLAUDE_INBOX_RECEIVER,
|
|
123
|
+
CLAUDE_INBOX_SETTLEMENTS,
|
|
124
|
+
CLAUDE_WAKE,
|
|
125
|
+
CLAUDE_WAKE_API,
|
|
126
|
+
COMMAND_RECOVERY,
|
|
127
|
+
NATIVE_INPUT,
|
|
128
|
+
NativeInputAgent,
|
|
129
|
+
claudeInboxAction,
|
|
130
|
+
claudeWakeDecision,
|
|
131
|
+
claudeWakeWaitingOn,
|
|
132
|
+
hermesPendingAdmissionId,
|
|
133
|
+
hermesReplayAdmissionId,
|
|
134
|
+
isClaudeInboxReceipt,
|
|
135
|
+
isCodexInputReceipt,
|
|
136
|
+
isCodexQueuedReceipt,
|
|
137
|
+
isCommandAcknowledgementReceipt,
|
|
138
|
+
nativeIdentifier,
|
|
139
|
+
nativeInputAction,
|
|
140
|
+
nativeRecord,
|
|
141
|
+
parseClaudeInboxClaim,
|
|
142
|
+
parseClaudeInboxPoll,
|
|
143
|
+
parseClaudeInboxSession,
|
|
144
|
+
parseHermesAdmission,
|
|
145
|
+
parseHermesReplayCursor,
|
|
146
|
+
qualifyHermesReceiptMode
|
|
147
|
+
} from "../chunk-QRS7FQHE.js";
|
|
106
148
|
import "../chunk-MLKGABMK.js";
|
|
107
149
|
|
|
108
|
-
// src/
|
|
109
|
-
import {
|
|
110
|
-
import { existsSync, statSync } from "fs";
|
|
150
|
+
// src/native-host-receiver.ts
|
|
151
|
+
import { join as join2 } from "path";
|
|
111
152
|
import { homedir } from "os";
|
|
112
|
-
import {
|
|
153
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
154
|
+
|
|
155
|
+
// src/native-host.ts
|
|
156
|
+
import WebSocket from "ws";
|
|
157
|
+
import { createConnection } from "net";
|
|
158
|
+
import { createHash } from "crypto";
|
|
159
|
+
import {
|
|
160
|
+
readdirSync,
|
|
161
|
+
lstatSync,
|
|
162
|
+
realpathSync,
|
|
163
|
+
readFileSync,
|
|
164
|
+
mkdirSync,
|
|
165
|
+
openSync,
|
|
166
|
+
writeSync,
|
|
167
|
+
fsyncSync,
|
|
168
|
+
closeSync
|
|
169
|
+
} from "fs";
|
|
170
|
+
import { dirname, join, resolve } from "path";
|
|
171
|
+
var privateNode = (path, kind) => {
|
|
172
|
+
try {
|
|
173
|
+
const s = lstatSync(path);
|
|
174
|
+
return s.uid === process.getuid?.() && (s.mode & 63) === 0 && (kind === "socket" ? s.isSocket() : s.isDirectory());
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var ownerControlledDirectory = (path) => {
|
|
180
|
+
try {
|
|
181
|
+
const node = lstatSync(path);
|
|
182
|
+
return node.isDirectory() && node.uid === process.getuid?.() && (node.mode & 18) === 0;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var syncDirectory = (path) => {
|
|
188
|
+
const fd = openSync(path, "r");
|
|
189
|
+
try {
|
|
190
|
+
fsyncSync(fd);
|
|
191
|
+
} finally {
|
|
192
|
+
closeSync(fd);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var Rpc = class _Rpc {
|
|
196
|
+
constructor(socket, timeout) {
|
|
197
|
+
this.socket = socket;
|
|
198
|
+
this.timeout = timeout;
|
|
199
|
+
socket.on("message", (data) => {
|
|
200
|
+
let v;
|
|
201
|
+
try {
|
|
202
|
+
v = JSON.parse(data.toString());
|
|
203
|
+
} catch {
|
|
204
|
+
socket.terminate();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!nativeRecord(v) || typeof v.id !== "number") return;
|
|
208
|
+
const p = this.pending.get(v.id);
|
|
209
|
+
if (!p) return;
|
|
210
|
+
this.pending.delete(v.id);
|
|
211
|
+
clearTimeout(p.timer);
|
|
212
|
+
if (v.error) p.reject(new Error("native RPC refused"));
|
|
213
|
+
else p.resolve(v.result);
|
|
214
|
+
});
|
|
215
|
+
socket.on("close", () => this.fail());
|
|
216
|
+
socket.on("error", () => this.fail());
|
|
217
|
+
}
|
|
218
|
+
sequence = 0;
|
|
219
|
+
pending = /* @__PURE__ */ new Map();
|
|
220
|
+
fail() {
|
|
221
|
+
for (const p of this.pending.values()) {
|
|
222
|
+
clearTimeout(p.timer);
|
|
223
|
+
p.reject(new Error("native RPC disconnected"));
|
|
224
|
+
}
|
|
225
|
+
this.pending.clear();
|
|
226
|
+
}
|
|
227
|
+
static async connect(url, protocols, timeout) {
|
|
228
|
+
const socket = new WebSocket(url, [...protocols], {
|
|
229
|
+
handshakeTimeout: timeout,
|
|
230
|
+
maxPayload: NATIVE_INPUT.maxFrameBytes,
|
|
231
|
+
followRedirects: false
|
|
232
|
+
});
|
|
233
|
+
const rpc = new _Rpc(socket, timeout);
|
|
234
|
+
await new Promise((resolve3, reject) => {
|
|
235
|
+
socket.once("open", resolve3);
|
|
236
|
+
socket.once("error", reject);
|
|
237
|
+
});
|
|
238
|
+
if (protocols.length && socket.protocol !== protocols[0]) {
|
|
239
|
+
rpc.close();
|
|
240
|
+
throw new Error("native protocol mismatch");
|
|
241
|
+
}
|
|
242
|
+
return rpc;
|
|
243
|
+
}
|
|
244
|
+
request(method, params) {
|
|
245
|
+
const id = ++this.sequence;
|
|
246
|
+
return new Promise((resolve3, reject) => {
|
|
247
|
+
const timer = setTimeout(() => {
|
|
248
|
+
this.pending.delete(id);
|
|
249
|
+
reject(new Error("native RPC timeout"));
|
|
250
|
+
}, this.timeout);
|
|
251
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
252
|
+
this.socket.send(
|
|
253
|
+
JSON.stringify({ jsonrpc: "2.0", id, method, params }),
|
|
254
|
+
(error) => {
|
|
255
|
+
if (error) {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
this.pending.delete(id);
|
|
258
|
+
reject(error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
notify(method) {
|
|
265
|
+
this.socket.send(JSON.stringify({ method }));
|
|
266
|
+
}
|
|
267
|
+
close() {
|
|
268
|
+
this.fail();
|
|
269
|
+
this.socket.terminate();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var controlRequest = (path, request, timeout) => new Promise((resolve3, reject) => {
|
|
273
|
+
const s = createConnection(path);
|
|
274
|
+
let buffer = "";
|
|
275
|
+
const timer = setTimeout(() => {
|
|
276
|
+
s.destroy();
|
|
277
|
+
reject(new Error("native control timeout"));
|
|
278
|
+
}, timeout);
|
|
279
|
+
s.on("connect", () => s.write(JSON.stringify(request) + "\n"));
|
|
280
|
+
s.on("error", reject);
|
|
281
|
+
s.on("close", () => {
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
reject(new Error("native control closed"));
|
|
284
|
+
});
|
|
285
|
+
s.on("data", (chunk) => {
|
|
286
|
+
buffer += chunk.toString();
|
|
287
|
+
if (buffer.length > 65536) {
|
|
288
|
+
s.destroy();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (!buffer.includes("\n")) return;
|
|
292
|
+
try {
|
|
293
|
+
resolve3(JSON.parse(buffer.slice(0, buffer.indexOf("\n"))));
|
|
294
|
+
} catch {
|
|
295
|
+
reject(new Error("invalid native control response"));
|
|
296
|
+
}
|
|
297
|
+
s.destroy();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
var NativeHostClient = class {
|
|
301
|
+
constructor(options) {
|
|
302
|
+
this.options = options;
|
|
303
|
+
this.timeout = options.timeoutMs ?? NATIVE_INPUT.rpcTimeoutMs;
|
|
304
|
+
}
|
|
305
|
+
timeout;
|
|
306
|
+
async connect() {
|
|
307
|
+
if ((this.options.platform ?? process.platform) !== "darwin")
|
|
308
|
+
throw new Error("native host platform unqualified");
|
|
309
|
+
const home = resolve(this.options.home);
|
|
310
|
+
if (this.options.agent === "codex" /* codex */) {
|
|
311
|
+
const published = join(home, NATIVE_INPUT.codexSocket);
|
|
312
|
+
const path2 = realpathSync(published);
|
|
313
|
+
if (lstatSync(published).uid !== process.getuid?.() || !privateNode(join(home, "app-server-control"), "directory") || !privateNode(dirname(path2), "directory") || !privateNode(path2, "socket"))
|
|
314
|
+
throw new Error("native control unavailable");
|
|
315
|
+
const rpc = await Rpc.connect(`ws+unix://${path2}:/`, [], this.timeout);
|
|
316
|
+
try {
|
|
317
|
+
const v2 = await rpc.request("initialize", {
|
|
318
|
+
clientInfo: { name: "pushary_native_input", version: "1" },
|
|
319
|
+
capabilities: { experimentalApi: true }
|
|
320
|
+
});
|
|
321
|
+
if (!nativeRecord(v2) || v2.codexHome !== home || typeof v2.userAgent !== "string" || !v2.userAgent.includes(`/${NATIVE_INPUT.codexVersion} `) || v2.platformOs !== "macos")
|
|
322
|
+
throw new Error("unqualified native host");
|
|
323
|
+
rpc.notify("initialized");
|
|
324
|
+
return { rpc, profile: home };
|
|
325
|
+
} catch (e) {
|
|
326
|
+
rpc.close();
|
|
327
|
+
throw e;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const path = join(home, NATIVE_INPUT.hermesSocket);
|
|
331
|
+
if (!ownerControlledDirectory(home) || !privateNode(path, "socket"))
|
|
332
|
+
throw new Error("native control unavailable");
|
|
333
|
+
const v = await controlRequest(
|
|
334
|
+
path,
|
|
335
|
+
{ protocol: 1, verb: "identify", id: 1 },
|
|
336
|
+
this.timeout
|
|
337
|
+
);
|
|
338
|
+
if (!nativeRecord(v)) throw new Error("invalid host");
|
|
339
|
+
const d = nativeRecord(v.result) ? v.result : v;
|
|
340
|
+
if (d.runtime_protocol !== 1 || d.state !== "ready" || !Array.isArray(d.served_profiles) || !nativeIdentifier(d.instance_id) || typeof d.api_origin !== "string")
|
|
341
|
+
throw new Error("unqualified native host");
|
|
342
|
+
const profiles = d.served_profiles.filter(
|
|
343
|
+
(p) => nativeRecord(p) && p.home === home
|
|
344
|
+
);
|
|
345
|
+
const profile = profiles[0];
|
|
346
|
+
if (profiles.length !== 1 || !nativeRecord(profile) || typeof profile.profile_id !== "string")
|
|
347
|
+
throw new Error("native profile mismatch");
|
|
348
|
+
const hermesReceiptMode = qualifyHermesReceiptMode(
|
|
349
|
+
d.code_sha,
|
|
350
|
+
d.capabilities
|
|
351
|
+
);
|
|
352
|
+
if (!hermesReceiptMode) throw new Error("native admission unavailable");
|
|
353
|
+
const url = new URL(d.api_origin);
|
|
354
|
+
if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || url.username || url.password || url.pathname !== "/")
|
|
355
|
+
throw new Error("nonlocal native host");
|
|
356
|
+
const t = await controlRequest(
|
|
357
|
+
path,
|
|
358
|
+
{
|
|
359
|
+
protocol: 1,
|
|
360
|
+
id: 1,
|
|
361
|
+
verb: "session-ticket",
|
|
362
|
+
params: {
|
|
363
|
+
profile_id: profile.profile_id,
|
|
364
|
+
instance_id: d.instance_id,
|
|
365
|
+
purpose: "interactive"
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
this.timeout
|
|
369
|
+
);
|
|
370
|
+
const ticket = nativeRecord(t) && nativeRecord(t.result) ? t.result : t;
|
|
371
|
+
if (!nativeRecord(ticket) || ticket.profile_id !== profile.profile_id || ticket.instance_id !== d.instance_id || ticket.runtime_protocol !== 1 || typeof ticket.ticket !== "string")
|
|
372
|
+
throw new Error("native ticket mismatch");
|
|
373
|
+
return {
|
|
374
|
+
rpc: await Rpc.connect(
|
|
375
|
+
`ws://${url.host}/api/ws`,
|
|
376
|
+
[
|
|
377
|
+
NATIVE_INPUT.hermesWebSocketProtocol,
|
|
378
|
+
NATIVE_INPUT.hermesTicketPrefix + ticket.ticket
|
|
379
|
+
],
|
|
380
|
+
this.timeout
|
|
381
|
+
),
|
|
382
|
+
profile: profile.profile_id,
|
|
383
|
+
hermesReceiptMode
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
async list(host) {
|
|
387
|
+
const { rpc } = host;
|
|
388
|
+
if (this.options.agent === "codex" /* codex */) {
|
|
389
|
+
const v2 = await rpc.request("thread/loaded/list", {
|
|
390
|
+
limit: NATIVE_INPUT.maxSessions
|
|
391
|
+
});
|
|
392
|
+
if (!nativeRecord(v2) || !Array.isArray(v2.data) || !v2.data.every(nativeIdentifier))
|
|
393
|
+
throw new Error("invalid loaded sessions");
|
|
394
|
+
const sessions = [];
|
|
395
|
+
for (const id of v2.data) {
|
|
396
|
+
const t = await this.thread(rpc, id, false);
|
|
397
|
+
if (t && typeof t.cwd === "string")
|
|
398
|
+
sessions.push({
|
|
399
|
+
sessionId: id,
|
|
400
|
+
cwd: t.cwd,
|
|
401
|
+
agent: this.options.agent
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return sessions;
|
|
405
|
+
}
|
|
406
|
+
const v = await rpc.request("session.list", {
|
|
407
|
+
limit: NATIVE_INPUT.maxSessions
|
|
408
|
+
});
|
|
409
|
+
if (!nativeRecord(v) || v.scope !== "live" || !Array.isArray(v.sessions))
|
|
410
|
+
throw new Error("invalid live sessions");
|
|
411
|
+
return v.sessions.filter(
|
|
412
|
+
(row) => nativeRecord(row) && nativeIdentifier(row.session_id) && (row.source === "cli" || row.source === "local")
|
|
413
|
+
).map((row) => ({
|
|
414
|
+
sessionId: String(row.session_id),
|
|
415
|
+
cwd: this.options.home,
|
|
416
|
+
agent: this.options.agent
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
async sessions() {
|
|
420
|
+
let host;
|
|
421
|
+
try {
|
|
422
|
+
host = await this.connect();
|
|
423
|
+
return await this.list(host);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
this.options.report?.(
|
|
426
|
+
error instanceof Error ? error.message : "native discovery failed"
|
|
427
|
+
);
|
|
428
|
+
return [];
|
|
429
|
+
} finally {
|
|
430
|
+
host?.rpc.close();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async thread(rpc, id, includeTurns = false) {
|
|
434
|
+
const v = await rpc.request("thread/read", { threadId: id, includeTurns });
|
|
435
|
+
return nativeRecord(v) && nativeRecord(v.thread) && v.thread.id === id ? v.thread : null;
|
|
436
|
+
}
|
|
437
|
+
async hasReceipt(rpc, sessionId, commandId) {
|
|
438
|
+
for (const method of [
|
|
439
|
+
"thread/queue/list" /* queueList */,
|
|
440
|
+
"thread/items/list" /* itemsList */
|
|
441
|
+
]) {
|
|
442
|
+
let cursor;
|
|
443
|
+
for (let page = 0; page < NATIVE_INPUT.receiptMaxPages; page++) {
|
|
444
|
+
const result = await rpc.request(method, {
|
|
445
|
+
threadId: sessionId,
|
|
446
|
+
...method === "thread/items/list" /* itemsList */ ? { sortDirection: "desc" } : {},
|
|
447
|
+
limit: NATIVE_INPUT.receiptPageSize,
|
|
448
|
+
...cursor ? { cursor } : {}
|
|
449
|
+
});
|
|
450
|
+
if (!nativeRecord(result) || !Array.isArray(result.data))
|
|
451
|
+
throw new Error("invalid native receipt page");
|
|
452
|
+
if (method === "thread/queue/list" /* queueList */) {
|
|
453
|
+
if (result.data.some((row) => isCodexQueuedReceipt(row, commandId)))
|
|
454
|
+
return true;
|
|
455
|
+
} else {
|
|
456
|
+
const items = result.data.flatMap(
|
|
457
|
+
(row) => nativeRecord(row) && nativeRecord(row.item) ? [row.item] : []
|
|
458
|
+
);
|
|
459
|
+
if (isCodexInputReceipt(
|
|
460
|
+
{ id: sessionId, turns: [{ items }] },
|
|
461
|
+
sessionId,
|
|
462
|
+
commandId
|
|
463
|
+
))
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
if (typeof result.nextCursor !== "string" || !result.nextCursor || result.nextCursor === cursor)
|
|
467
|
+
break;
|
|
468
|
+
cursor = result.nextCursor;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
async hasHermesReceipt(host, intent, snapshot) {
|
|
474
|
+
if (host.hermesReceiptMode === "submission" /* submission */) {
|
|
475
|
+
const receipt2 = parseHermesAdmission(
|
|
476
|
+
await host.rpc.request("prompt.receipt" /* receipt */, {
|
|
477
|
+
session_id: intent.sessionId,
|
|
478
|
+
submission_id: intent.commandId
|
|
479
|
+
}),
|
|
480
|
+
host.profile,
|
|
481
|
+
intent.sessionId
|
|
482
|
+
);
|
|
483
|
+
return receipt2 !== null && receipt2.status !== "unknown" /* unknown */;
|
|
484
|
+
}
|
|
485
|
+
let admissionId = hermesPendingAdmissionId(
|
|
486
|
+
snapshot,
|
|
487
|
+
host.profile,
|
|
488
|
+
intent.sessionId,
|
|
489
|
+
intent.commandId
|
|
490
|
+
);
|
|
491
|
+
if (!admissionId && intent.hermesCursor) {
|
|
492
|
+
const replay = await host.rpc.request("session.events.since" /* eventsSince */, {
|
|
493
|
+
session_id: intent.sessionId,
|
|
494
|
+
...intent.hermesCursor
|
|
495
|
+
});
|
|
496
|
+
admissionId = hermesReplayAdmissionId(
|
|
497
|
+
replay,
|
|
498
|
+
intent.hermesCursor,
|
|
499
|
+
intent.sessionId,
|
|
500
|
+
intent.commandId
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (!admissionId) return false;
|
|
504
|
+
const receipt = parseHermesAdmission(
|
|
505
|
+
await host.rpc.request("prompt.receipt" /* receipt */, {
|
|
506
|
+
session_id: intent.sessionId,
|
|
507
|
+
admission_id: admissionId
|
|
508
|
+
}),
|
|
509
|
+
host.profile,
|
|
510
|
+
intent.sessionId
|
|
511
|
+
);
|
|
512
|
+
return receipt?.admissionId === admissionId && receipt.status !== "unknown" /* unknown */;
|
|
513
|
+
}
|
|
514
|
+
journalPath(sessionId, commandId) {
|
|
515
|
+
return join(
|
|
516
|
+
this.options.journalDir,
|
|
517
|
+
createHash("sha256").update(`${sessionId}\0${commandId}`).digest("hex") + ".json"
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
readIntent(sessionId, commandId) {
|
|
521
|
+
try {
|
|
522
|
+
const file = this.journalPath(sessionId, commandId);
|
|
523
|
+
const node = lstatSync(file);
|
|
524
|
+
if (!node.isFile() || node.uid !== process.getuid?.() || node.size > 4096 || (node.mode & 63) !== 0)
|
|
525
|
+
throw new Error("unsafe intent");
|
|
526
|
+
const v = JSON.parse(readFileSync(file, "utf8"));
|
|
527
|
+
if (!nativeRecord(v) || v.version !== NATIVE_INPUT.journalVersion || v.agent !== this.options.agent || v.sessionId !== sessionId || v.commandId !== commandId || v.home !== resolve(this.options.home) || typeof v.submittedAtMs !== "number" || !Number.isSafeInteger(v.submittedAtMs) || typeof v.accepted !== "boolean" || v.hermesCursor !== void 0 && !parseHermesReplayCursor(v.hermesCursor))
|
|
528
|
+
throw new Error("invalid intent");
|
|
529
|
+
const hermesCursor = parseHermesReplayCursor(v.hermesCursor);
|
|
530
|
+
return {
|
|
531
|
+
version: v.version,
|
|
532
|
+
agent: v.agent,
|
|
533
|
+
sessionId,
|
|
534
|
+
commandId,
|
|
535
|
+
home: v.home,
|
|
536
|
+
submittedAtMs: v.submittedAtMs,
|
|
537
|
+
accepted: v.accepted,
|
|
538
|
+
settled: v.settled === true,
|
|
539
|
+
...hermesCursor ? { hermesCursor } : {}
|
|
540
|
+
};
|
|
541
|
+
} catch (e) {
|
|
542
|
+
if (e instanceof Error && "code" in e && e.code === "ENOENT") return null;
|
|
543
|
+
throw e;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
pendingSettlements() {
|
|
547
|
+
try {
|
|
548
|
+
return readdirSync(this.options.journalDir).filter((file) => /^[0-9a-f]{64}\.json$/.test(file)).flatMap((file) => {
|
|
549
|
+
try {
|
|
550
|
+
const raw = JSON.parse(
|
|
551
|
+
readFileSync(join(this.options.journalDir, file), "utf8")
|
|
552
|
+
);
|
|
553
|
+
if (!nativeRecord(raw) || !nativeIdentifier(raw.sessionId) || !nativeIdentifier(raw.commandId))
|
|
554
|
+
return [];
|
|
555
|
+
const intent = this.readIntent(raw.sessionId, raw.commandId);
|
|
556
|
+
return intent?.accepted && !intent.settled ? [{ sessionId: intent.sessionId, commandId: intent.commandId }] : [];
|
|
557
|
+
} catch {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
} catch {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
confirmSettlement(sessionId, commandId) {
|
|
566
|
+
const intent = this.readIntent(sessionId, commandId);
|
|
567
|
+
if (intent?.accepted)
|
|
568
|
+
writeJsonAtomicDurable(
|
|
569
|
+
this.journalPath(sessionId, commandId),
|
|
570
|
+
{ ...intent, settled: true },
|
|
571
|
+
384
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
writeIntent(sessionId, commandId, hermesCursor) {
|
|
575
|
+
mkdirSync(this.options.journalDir, { recursive: true, mode: 448 });
|
|
576
|
+
const fd = openSync(this.journalPath(sessionId, commandId), "wx", 384);
|
|
577
|
+
try {
|
|
578
|
+
writeSync(
|
|
579
|
+
fd,
|
|
580
|
+
JSON.stringify({
|
|
581
|
+
version: NATIVE_INPUT.journalVersion,
|
|
582
|
+
agent: this.options.agent,
|
|
583
|
+
sessionId,
|
|
584
|
+
commandId,
|
|
585
|
+
home: resolve(this.options.home),
|
|
586
|
+
submittedAtMs: Date.now(),
|
|
587
|
+
accepted: false,
|
|
588
|
+
...hermesCursor ? { hermesCursor } : {}
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
fsyncSync(fd);
|
|
592
|
+
} finally {
|
|
593
|
+
closeSync(fd);
|
|
594
|
+
}
|
|
595
|
+
syncDirectory(this.options.journalDir);
|
|
596
|
+
}
|
|
597
|
+
accept(intent) {
|
|
598
|
+
writeJsonAtomicDurable(
|
|
599
|
+
this.journalPath(intent.sessionId, intent.commandId),
|
|
600
|
+
{ ...intent, accepted: true },
|
|
601
|
+
384
|
|
602
|
+
);
|
|
603
|
+
return "accepted" /* accepted */;
|
|
604
|
+
}
|
|
605
|
+
async deliver(sessionId, command, signal) {
|
|
606
|
+
let intent;
|
|
607
|
+
try {
|
|
608
|
+
intent = this.readIntent(sessionId, command.id);
|
|
609
|
+
} catch {
|
|
610
|
+
return "outcome_unknown" /* outcomeUnknown */;
|
|
611
|
+
}
|
|
612
|
+
if (intent?.accepted) return "accepted" /* accepted */;
|
|
613
|
+
if (!intent && command.expiresAtMs <= Date.now())
|
|
614
|
+
return "expired" /* expired */;
|
|
615
|
+
let host;
|
|
616
|
+
let hermesCursor;
|
|
617
|
+
try {
|
|
618
|
+
host = await this.connect();
|
|
619
|
+
const { rpc } = host;
|
|
620
|
+
if (!(await this.list(host)).some((s) => s.sessionId === sessionId))
|
|
621
|
+
return intent ? "outcome_unknown" /* outcomeUnknown */ : "not_sent" /* notSent */;
|
|
622
|
+
if (this.options.agent === "codex" /* codex */) {
|
|
623
|
+
if (intent) {
|
|
624
|
+
if (await this.hasReceipt(rpc, sessionId, command.id))
|
|
625
|
+
return this.accept(intent);
|
|
626
|
+
} else {
|
|
627
|
+
const queue = await rpc.request("thread/queue/list" /* queueList */, {
|
|
628
|
+
threadId: sessionId,
|
|
629
|
+
limit: 1
|
|
630
|
+
});
|
|
631
|
+
if (!nativeRecord(queue) || !Array.isArray(queue.data))
|
|
632
|
+
throw new Error("native queue unavailable");
|
|
633
|
+
}
|
|
634
|
+
} else if (host.hermesReceiptMode === "submission" /* submission */) {
|
|
635
|
+
if (intent && await this.hasHermesReceipt(host, intent))
|
|
636
|
+
return this.accept(intent);
|
|
637
|
+
if (!intent) {
|
|
638
|
+
const snapshot = await rpc.request("session.resume" /* resume */, {
|
|
639
|
+
session_id: sessionId
|
|
640
|
+
});
|
|
641
|
+
if (!nativeRecord(snapshot) || snapshot.session_id !== sessionId)
|
|
642
|
+
throw new Error("native session mismatch");
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
const snapshot = await rpc.request("session.resume" /* resume */, {
|
|
646
|
+
session_id: sessionId
|
|
647
|
+
});
|
|
648
|
+
if (!nativeRecord(snapshot) || snapshot.session_id !== sessionId)
|
|
649
|
+
throw new Error("native session mismatch");
|
|
650
|
+
hermesCursor = parseHermesReplayCursor(snapshot) ?? void 0;
|
|
651
|
+
if (intent && await this.hasHermesReceipt(host, intent, snapshot))
|
|
652
|
+
return this.accept(intent);
|
|
653
|
+
if (!intent && !hermesCursor)
|
|
654
|
+
throw new Error("native replay cursor unavailable");
|
|
655
|
+
}
|
|
656
|
+
const action = nativeInputAction({
|
|
657
|
+
attempted: !!intent,
|
|
658
|
+
accepted: false,
|
|
659
|
+
nowMs: Date.now(),
|
|
660
|
+
expiresAtMs: Math.min(
|
|
661
|
+
command.expiresAtMs,
|
|
662
|
+
intent ? intent.submittedAtMs + NATIVE_INPUT.receiptWindowMs : Infinity
|
|
663
|
+
)
|
|
664
|
+
});
|
|
665
|
+
if (action === "expire" /* expire */) return "expired" /* expired */;
|
|
666
|
+
if (action === "outcome_unknown" /* outcomeUnknown */)
|
|
667
|
+
return "outcome_unknown" /* outcomeUnknown */;
|
|
668
|
+
if (action === "observe" /* observe */)
|
|
669
|
+
return "unconfirmed" /* unconfirmed */;
|
|
670
|
+
if (signal?.aborted) return "not_sent" /* notSent */;
|
|
671
|
+
if (!intent) {
|
|
672
|
+
this.writeIntent(sessionId, command.id, hermesCursor);
|
|
673
|
+
intent = this.readIntent(sessionId, command.id);
|
|
674
|
+
}
|
|
675
|
+
if (!intent) return "outcome_unknown" /* outcomeUnknown */;
|
|
676
|
+
if (this.options.agent === "hermes" /* hermes */) {
|
|
677
|
+
const receipt = parseHermesAdmission(
|
|
678
|
+
await rpc.request("prompt.submit" /* submit */, {
|
|
679
|
+
session_id: sessionId,
|
|
680
|
+
submission_id: command.id,
|
|
681
|
+
text: command.text
|
|
682
|
+
}),
|
|
683
|
+
host.profile,
|
|
684
|
+
sessionId
|
|
685
|
+
);
|
|
686
|
+
return receipt && receipt.status !== "unknown" /* unknown */ ? this.accept(intent) : "outcome_unknown" /* outcomeUnknown */;
|
|
687
|
+
}
|
|
688
|
+
const response = await rpc.request("thread/queue/add" /* queueAdd */, {
|
|
689
|
+
threadId: sessionId,
|
|
690
|
+
clientUserMessageId: command.id,
|
|
691
|
+
input: [{ type: "text", text: command.text, text_elements: [] }]
|
|
692
|
+
});
|
|
693
|
+
return nativeRecord(response) && isCodexQueuedReceipt(response.queuedSubmission, command.id) || await this.hasReceipt(rpc, sessionId, command.id) ? this.accept(intent) : "unconfirmed" /* unconfirmed */;
|
|
694
|
+
} catch (error) {
|
|
695
|
+
this.options.report?.(
|
|
696
|
+
error instanceof Error ? error.message : "native delivery failed"
|
|
697
|
+
);
|
|
698
|
+
return intent ? Date.now() >= Math.min(
|
|
699
|
+
command.expiresAtMs,
|
|
700
|
+
intent.submittedAtMs + NATIVE_INPUT.receiptWindowMs
|
|
701
|
+
) ? "outcome_unknown" /* outcomeUnknown */ : "unconfirmed" /* unconfirmed */ : "not_sent" /* notSent */;
|
|
702
|
+
} finally {
|
|
703
|
+
host?.rpc.close();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/hook-ownership-file.ts
|
|
709
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
710
|
+
var readHookOwner = (file) => {
|
|
711
|
+
try {
|
|
712
|
+
const value = JSON.parse(readFileSync2(file, "utf8"));
|
|
713
|
+
return nativeRecord(value) ? hookOwnerOf(value) : null;
|
|
714
|
+
} catch (error) {
|
|
715
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT" ? "none" : null;
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// src/native-input-receiver.ts
|
|
720
|
+
import { basename } from "path";
|
|
721
|
+
var startNativeInputReceiver = (apiKey, machineId, options) => {
|
|
722
|
+
const client = options.client;
|
|
723
|
+
const baseUrl = options.baseUrl ?? getBaseUrl();
|
|
724
|
+
const abort = new AbortController();
|
|
725
|
+
const busy = /* @__PURE__ */ new Set();
|
|
726
|
+
const heartbeats = /* @__PURE__ */ new Map();
|
|
727
|
+
const settlements = /* @__PURE__ */ new Map();
|
|
728
|
+
let scanning = false;
|
|
729
|
+
let cursor = 0;
|
|
730
|
+
const post = async (path, body) => {
|
|
731
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
732
|
+
method: "POST",
|
|
733
|
+
headers: {
|
|
734
|
+
"Content-Type": "application/json",
|
|
735
|
+
Authorization: `Bearer ${apiKey}`
|
|
736
|
+
},
|
|
737
|
+
body: JSON.stringify(body),
|
|
738
|
+
signal: AbortSignal.any([
|
|
739
|
+
abort.signal,
|
|
740
|
+
AbortSignal.timeout(CLAUDE_INBOX_RECEIVER.requestTimeoutMs)
|
|
741
|
+
])
|
|
742
|
+
});
|
|
743
|
+
if (!response.ok) throw new Error(`native inbox HTTP ${response.status}`);
|
|
744
|
+
return response.json();
|
|
745
|
+
};
|
|
746
|
+
const settle = async (commandId) => {
|
|
747
|
+
const pending = settlements.get(commandId);
|
|
748
|
+
if (!pending) return false;
|
|
749
|
+
try {
|
|
750
|
+
const receipt = await post("/api/agent/command/ack", {
|
|
751
|
+
...pending,
|
|
752
|
+
commandId,
|
|
753
|
+
machineId
|
|
754
|
+
});
|
|
755
|
+
if (!isCommandAcknowledgementReceipt(receipt))
|
|
756
|
+
throw new Error("native acknowledgement missing receipt");
|
|
757
|
+
client.confirmSettlement(pending.sessionId, commandId);
|
|
758
|
+
settlements.delete(commandId);
|
|
759
|
+
return true;
|
|
760
|
+
} catch (error) {
|
|
761
|
+
if (!abort.signal.aborted)
|
|
762
|
+
options.report?.(
|
|
763
|
+
error instanceof Error ? error.message : "native receipt recovery failed"
|
|
764
|
+
);
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
const receive = async (sessionId) => {
|
|
769
|
+
busy.add(sessionId);
|
|
770
|
+
try {
|
|
771
|
+
if ([...settlements.values()].some(
|
|
772
|
+
(receipt) => receipt.sessionId === sessionId
|
|
773
|
+
))
|
|
774
|
+
return;
|
|
775
|
+
const raw = await post("/api/agent/command/native", {
|
|
776
|
+
operation: "claim" /* claim */,
|
|
777
|
+
sessionId,
|
|
778
|
+
machineId
|
|
779
|
+
});
|
|
780
|
+
const command = parseClaudeInboxClaim(raw);
|
|
781
|
+
if (!command || abort.signal.aborted) return;
|
|
782
|
+
const outcome = await client.deliver(sessionId, command, abort.signal);
|
|
783
|
+
const settlement = CLAUDE_INBOX_SETTLEMENTS[outcome];
|
|
784
|
+
if (abort.signal.aborted || !settlement) return;
|
|
785
|
+
settlements.set(command.id, { sessionId, ...settlement });
|
|
786
|
+
await settle(command.id);
|
|
787
|
+
} catch (error) {
|
|
788
|
+
if (!abort.signal.aborted)
|
|
789
|
+
options.report?.(
|
|
790
|
+
error instanceof Error ? error.message : "native inbox delivery failed"
|
|
791
|
+
);
|
|
792
|
+
} finally {
|
|
793
|
+
busy.delete(sessionId);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
const tick = async () => {
|
|
797
|
+
if (scanning || abort.signal.aborted) return;
|
|
798
|
+
scanning = true;
|
|
799
|
+
try {
|
|
800
|
+
for (const receipt of client.pendingSettlements()) {
|
|
801
|
+
settlements.set(receipt.commandId, {
|
|
802
|
+
sessionId: receipt.sessionId,
|
|
803
|
+
state: "delivered"
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
for (const commandId of [...settlements.keys()]) await settle(commandId);
|
|
807
|
+
const sessions = (await client.sessions()).filter(options.ownsSession).slice(0, CLAUDE_INBOX_MAX_SESSIONS);
|
|
808
|
+
if (!sessions.length) return;
|
|
809
|
+
for (const session of sessions) {
|
|
810
|
+
if (Date.now() - (heartbeats.get(session.sessionId) ?? 0) < CLAUDE_INBOX_RECEIVER.heartbeatMs)
|
|
811
|
+
continue;
|
|
812
|
+
await post("/api/agent/event", {
|
|
813
|
+
event: "heartbeat",
|
|
814
|
+
agentType: options.agentType ?? "claude_code",
|
|
815
|
+
agentName: `${options.agentName ?? "Claude Code"} - ${basename(session.cwd)}`,
|
|
816
|
+
sessionId: session.sessionId,
|
|
817
|
+
machineId,
|
|
818
|
+
cwd: session.cwd,
|
|
819
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
820
|
+
canDrainCommand: false
|
|
821
|
+
});
|
|
822
|
+
heartbeats.set(session.sessionId, Date.now());
|
|
823
|
+
}
|
|
824
|
+
const raw = await post("/api/agent/command/native", {
|
|
825
|
+
operation: "poll" /* poll */,
|
|
826
|
+
machineId,
|
|
827
|
+
sessionIds: sessions.map((session) => session.sessionId)
|
|
828
|
+
});
|
|
829
|
+
const pending = parseClaudeInboxPoll(raw);
|
|
830
|
+
if (!pending) throw new Error("native inbox protocol mismatch");
|
|
831
|
+
const rotated = [...pending.slice(cursor), ...pending.slice(0, cursor)];
|
|
832
|
+
cursor = pending.length ? (cursor + CLAUDE_INBOX_RECEIVER.maxConcurrent) % pending.length : 0;
|
|
833
|
+
for (const sessionId of rotated) {
|
|
834
|
+
if (abort.signal.aborted || busy.size >= CLAUDE_INBOX_RECEIVER.maxConcurrent)
|
|
835
|
+
break;
|
|
836
|
+
if (!busy.has(sessionId) && sessions.some((session) => session.sessionId === sessionId))
|
|
837
|
+
void receive(sessionId);
|
|
838
|
+
}
|
|
839
|
+
} catch (error) {
|
|
840
|
+
const message = error instanceof Error ? error.message : "native inbox polling failed";
|
|
841
|
+
if (!abort.signal.aborted) options.report?.(message);
|
|
842
|
+
} finally {
|
|
843
|
+
scanning = false;
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
const timer = setInterval(() => {
|
|
847
|
+
void tick();
|
|
848
|
+
}, options.pollMs ?? CLAUDE_INBOX_RECEIVER.pollMs);
|
|
849
|
+
timer.unref();
|
|
850
|
+
void tick();
|
|
851
|
+
return () => {
|
|
852
|
+
abort.abort();
|
|
853
|
+
clearInterval(timer);
|
|
854
|
+
};
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/native-host-receiver.ts
|
|
858
|
+
var ownsHermes = (home) => {
|
|
859
|
+
try {
|
|
860
|
+
return parseHermesInstallMarker(
|
|
861
|
+
JSON.parse(
|
|
862
|
+
readFileSync3(
|
|
863
|
+
join2(home, "plugins", "pushary", HERMES_INSTALL_MARKER_FILE),
|
|
864
|
+
"utf8"
|
|
865
|
+
)
|
|
866
|
+
)
|
|
867
|
+
)?.installedBy === "cli";
|
|
868
|
+
} catch {
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
var startNativeHostReceivers = (apiKey, machineId, report) => {
|
|
873
|
+
const homes = {
|
|
874
|
+
["codex" /* codex */]: codexHome(),
|
|
875
|
+
["hermes" /* hermes */]: defaultHermesHome()
|
|
876
|
+
};
|
|
877
|
+
const stops = Object.values(NativeInputAgent).map((agent) => {
|
|
878
|
+
const home = homes[agent];
|
|
879
|
+
return startNativeInputReceiver(apiKey, machineId, {
|
|
880
|
+
client: new NativeHostClient({
|
|
881
|
+
agent,
|
|
882
|
+
home,
|
|
883
|
+
report,
|
|
884
|
+
journalDir: join2(homedir(), ".pushary", NATIVE_INPUT.journalDirectory)
|
|
885
|
+
}),
|
|
886
|
+
agentType: agent,
|
|
887
|
+
agentName: agent === "codex" /* codex */ ? "Codex" : "Hermes",
|
|
888
|
+
report,
|
|
889
|
+
ownsSession: () => agent === "codex" /* codex */ ? readHookOwner(join2(home, "hooks.json")) === "cli" : ownsHermes(home)
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
return () => {
|
|
893
|
+
for (const stop of stops) stop();
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/hook-command-receipts.ts
|
|
898
|
+
var reconcileCommandReceipts = async (apiKey, machineId, agentType, baseUrl, signal) => {
|
|
899
|
+
const namespace = `${apiKey}:${machineId}:${agentType}`;
|
|
900
|
+
for (const { sessionId, command } of pendingCommandReceipts(namespace)) {
|
|
901
|
+
signal?.throwIfAborted();
|
|
902
|
+
const state = command.delivered ? "delivered" : command.attempted ? "uncertain" : "failed";
|
|
903
|
+
const response = await fetch(`${baseUrl}/api/agent/command/ack`, {
|
|
904
|
+
method: "POST",
|
|
905
|
+
headers: {
|
|
906
|
+
"Content-Type": "application/json",
|
|
907
|
+
Authorization: `Bearer ${apiKey}`
|
|
908
|
+
},
|
|
909
|
+
body: JSON.stringify({
|
|
910
|
+
commandId: command.id,
|
|
911
|
+
sessionId,
|
|
912
|
+
machineId,
|
|
913
|
+
state,
|
|
914
|
+
failureCode: command.attempted ? "delivery_unconfirmed" : "expired"
|
|
915
|
+
}),
|
|
916
|
+
signal: AbortSignal.any([
|
|
917
|
+
AbortSignal.timeout(COMMAND_RECOVERY.acknowledgementTimeoutMs),
|
|
918
|
+
...signal ? [signal] : []
|
|
919
|
+
])
|
|
920
|
+
});
|
|
921
|
+
if (response.ok) removePendingCommand(sessionId, command.id, namespace);
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/claude-inbox-receiver.ts
|
|
926
|
+
import { join as join4, basename as basename2 } from "path";
|
|
927
|
+
import { homedir as homedir2 } from "os";
|
|
928
|
+
|
|
929
|
+
// src/claude-inbox.ts
|
|
930
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
931
|
+
import { execFileSync } from "child_process";
|
|
932
|
+
import {
|
|
933
|
+
closeSync as closeSync2,
|
|
934
|
+
fsyncSync as fsyncSync2,
|
|
935
|
+
lstatSync as lstatSync2,
|
|
936
|
+
mkdirSync as mkdirSync2,
|
|
937
|
+
openSync as openSync2,
|
|
938
|
+
readFileSync as readFileSync4,
|
|
939
|
+
readdirSync as readdirSync2,
|
|
940
|
+
readSync,
|
|
941
|
+
statSync,
|
|
942
|
+
writeFileSync,
|
|
943
|
+
unlinkSync
|
|
944
|
+
} from "fs";
|
|
945
|
+
import { dirname as dirname2, isAbsolute, join as join3, resolve as resolve2 } from "path";
|
|
946
|
+
import { Socket } from "net";
|
|
947
|
+
import { setTimeout as delay } from "timers/promises";
|
|
948
|
+
var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
949
|
+
var digest = (value) => createHash2("sha256").update(value).digest("hex");
|
|
950
|
+
var readObject = (file) => {
|
|
951
|
+
const stat = lstatSync2(file);
|
|
952
|
+
if (!stat.isFile() || stat.size > CLAUDE_INBOX.maxRecordBytes || stat.uid !== process.getuid?.())
|
|
953
|
+
throw new Error("Invalid inbox record");
|
|
954
|
+
return JSON.parse(readFileSync4(file, "utf8"));
|
|
955
|
+
};
|
|
956
|
+
var readKey = (file) => {
|
|
957
|
+
const value = readObject(file);
|
|
958
|
+
return object(value) && typeof value.peerToken === "string" && /^[0-9a-f]{32}$/.test(value.peerToken) && typeof value.procStart === "string" ? { peerToken: value.peerToken, procStart: value.procStart } : null;
|
|
959
|
+
};
|
|
960
|
+
var processStart = (pid) => {
|
|
961
|
+
if (process.platform !== "darwin") return null;
|
|
962
|
+
try {
|
|
963
|
+
return execFileSync("/bin/ps", ["-p", String(pid), "-o", "lstart="], {
|
|
964
|
+
encoding: "utf8",
|
|
965
|
+
env: { ...process.env, TZ: "UTC", LC_ALL: "C" },
|
|
966
|
+
timeout: CLAUDE_INBOX.connectTimeoutMs
|
|
967
|
+
}).trim();
|
|
968
|
+
} catch {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
var transcriptPath = (config, session) => join3(
|
|
973
|
+
config,
|
|
974
|
+
"projects",
|
|
975
|
+
resolve2(session.cwd).replace(/[^a-zA-Z0-9-]/g, "-"),
|
|
976
|
+
`${session.sessionId}.jsonl`
|
|
977
|
+
);
|
|
978
|
+
var transcriptSize = (file) => {
|
|
979
|
+
try {
|
|
980
|
+
return statSync(file).size;
|
|
981
|
+
} catch {
|
|
982
|
+
return 0;
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
var receiptPresent = (attempt) => {
|
|
986
|
+
let fd;
|
|
987
|
+
try {
|
|
988
|
+
fd = openSync2(attempt.transcript, "r");
|
|
989
|
+
const buffer = Buffer.alloc(CLAUDE_INBOX.maxTranscriptReadBytes);
|
|
990
|
+
const count = readSync(fd, buffer, 0, buffer.length, attempt.offset);
|
|
991
|
+
for (const line of buffer.subarray(0, count).toString("utf8").split("\n")) {
|
|
992
|
+
try {
|
|
993
|
+
if (isClaudeInboxReceipt(
|
|
994
|
+
JSON.parse(line),
|
|
995
|
+
attempt.sessionId,
|
|
996
|
+
attempt.commandId
|
|
997
|
+
))
|
|
998
|
+
return true;
|
|
999
|
+
} catch {
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return false;
|
|
1003
|
+
} catch {
|
|
1004
|
+
return false;
|
|
1005
|
+
} finally {
|
|
1006
|
+
if (fd !== void 0) closeSync2(fd);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
var connectInbox = (path) => new Promise((resolveConnection) => {
|
|
1010
|
+
const socket = new Socket();
|
|
1011
|
+
const timer = setTimeout(failed, CLAUDE_INBOX.connectTimeoutMs);
|
|
1012
|
+
function failed() {
|
|
1013
|
+
clearTimeout(timer);
|
|
1014
|
+
socket.destroy();
|
|
1015
|
+
resolveConnection(null);
|
|
1016
|
+
}
|
|
1017
|
+
socket.once("error", failed);
|
|
1018
|
+
socket.connect(path, () => {
|
|
1019
|
+
clearTimeout(timer);
|
|
1020
|
+
socket.removeListener("error", failed);
|
|
1021
|
+
socket.on("error", () => {
|
|
1022
|
+
});
|
|
1023
|
+
resolveConnection(socket);
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
var createAttempt = (file, attempt) => {
|
|
1027
|
+
mkdirSync2(dirname2(file), { recursive: true, mode: 448 });
|
|
1028
|
+
const fd = openSync2(file, "wx", 384);
|
|
1029
|
+
try {
|
|
1030
|
+
writeFileSync(fd, JSON.stringify(attempt));
|
|
1031
|
+
fsyncSync2(fd);
|
|
1032
|
+
} finally {
|
|
1033
|
+
closeSync2(fd);
|
|
1034
|
+
}
|
|
1035
|
+
const directory = openSync2(dirname2(file), "r");
|
|
1036
|
+
try {
|
|
1037
|
+
fsyncSync2(directory);
|
|
1038
|
+
} finally {
|
|
1039
|
+
closeSync2(directory);
|
|
1040
|
+
}
|
|
1041
|
+
return true;
|
|
1042
|
+
};
|
|
1043
|
+
var ClaudeInboxClient = class {
|
|
1044
|
+
options;
|
|
1045
|
+
constructor(options) {
|
|
1046
|
+
this.options = options;
|
|
1047
|
+
}
|
|
1048
|
+
sessions() {
|
|
1049
|
+
try {
|
|
1050
|
+
const sessions = readdirSync2(
|
|
1051
|
+
join3(this.options.configDir, "sessions")
|
|
1052
|
+
).filter((file) => /^\d+\.json$/.test(file));
|
|
1053
|
+
return sessions.flatMap((file) => {
|
|
1054
|
+
try {
|
|
1055
|
+
const session = parseClaudeInboxSession(
|
|
1056
|
+
readObject(join3(this.options.configDir, "sessions", file))
|
|
1057
|
+
);
|
|
1058
|
+
if (!session || file !== `${session.pid}.json` || !isAbsolute(session.cwd) || !isAbsolute(session.messagingSocketPath))
|
|
1059
|
+
return [];
|
|
1060
|
+
if ((this.options.processStart ?? processStart)(session.pid) !== session.procStart)
|
|
1061
|
+
return [];
|
|
1062
|
+
const socket = lstatSync2(session.messagingSocketPath);
|
|
1063
|
+
if (!socket.isSocket() || socket.uid !== process.getuid?.()) return [];
|
|
1064
|
+
return this.key(session) ? [session] : [];
|
|
1065
|
+
} catch {
|
|
1066
|
+
return [];
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
} catch {
|
|
1070
|
+
return [];
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
key(session) {
|
|
1074
|
+
const canonicalSocket = session.messagingSocketPath.replace(
|
|
1075
|
+
/^\/private(?=\/(?:tmp|var|etc)\/)/,
|
|
1076
|
+
""
|
|
1077
|
+
);
|
|
1078
|
+
const key = readKey(
|
|
1079
|
+
join3(
|
|
1080
|
+
this.options.configDir,
|
|
1081
|
+
"sessions",
|
|
1082
|
+
`${session.pid}.${digest(canonicalSocket)}.key`
|
|
1083
|
+
)
|
|
1084
|
+
);
|
|
1085
|
+
return key?.procStart === session.procStart ? key : null;
|
|
1086
|
+
}
|
|
1087
|
+
attempts(version) {
|
|
1088
|
+
const directory = join3(this.options.journalDir, version);
|
|
1089
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
1090
|
+
const files = readdirSync2(directory).filter((file) => /^\d{13}-[0-9a-f-]{36}\.json$/.test(file)).sort().reverse();
|
|
1091
|
+
const attempts = files.slice(0, CLAUDE_WAKE.retainedAttempts).map((name) => {
|
|
1092
|
+
const file = join3(directory, name);
|
|
1093
|
+
const value = readObject(file);
|
|
1094
|
+
if (!object(value) || value.version !== CLAUDE_INBOX.journalVersion || typeof value.sessionId !== "string" || typeof value.commandId !== "string" || typeof value.transcript !== "string" || !resolve2(value.transcript).startsWith(
|
|
1095
|
+
resolve2(this.options.configDir, "projects") + "/"
|
|
1096
|
+
) || typeof value.offset !== "number" || !Number.isSafeInteger(value.offset) || value.offset < 0 || typeof value.accepted !== "boolean" || typeof value.submittedAtMs !== "number" || !Number.isSafeInteger(value.submittedAtMs))
|
|
1097
|
+
throw new Error("Invalid wake receipt");
|
|
1098
|
+
const attempt = {
|
|
1099
|
+
version: value.version,
|
|
1100
|
+
sessionId: value.sessionId,
|
|
1101
|
+
commandId: value.commandId,
|
|
1102
|
+
transcript: value.transcript,
|
|
1103
|
+
offset: value.offset,
|
|
1104
|
+
accepted: value.accepted,
|
|
1105
|
+
submittedAtMs: value.submittedAtMs
|
|
1106
|
+
};
|
|
1107
|
+
if (!attempt.accepted && receiptPresent(attempt)) {
|
|
1108
|
+
const accepted = { ...attempt, accepted: true };
|
|
1109
|
+
writeJsonAtomicDurable(file, accepted, 384);
|
|
1110
|
+
return { file, attempt: accepted };
|
|
1111
|
+
}
|
|
1112
|
+
return { file, attempt };
|
|
1113
|
+
});
|
|
1114
|
+
for (const name of files.slice(CLAUDE_WAKE.retainedAttempts))
|
|
1115
|
+
unlinkSync(join3(directory, name));
|
|
1116
|
+
return attempts;
|
|
1117
|
+
}
|
|
1118
|
+
async wake(sessionId, signal) {
|
|
1119
|
+
const matches = this.sessions().filter(
|
|
1120
|
+
(candidate) => candidate.sessionId === sessionId
|
|
1121
|
+
);
|
|
1122
|
+
const session = matches.length === 1 ? matches[0] : null;
|
|
1123
|
+
if (!session || signal?.aborted) return "wake_not_sent" /* notSent */;
|
|
1124
|
+
try {
|
|
1125
|
+
const attempts = this.attempts(session.version);
|
|
1126
|
+
const decision = claudeWakeDecision({
|
|
1127
|
+
sessionId,
|
|
1128
|
+
idle: session.status === "idle",
|
|
1129
|
+
nowMs: Date.now(),
|
|
1130
|
+
attempts: attempts.map((entry) => entry.attempt)
|
|
1131
|
+
});
|
|
1132
|
+
if (decision) return decision;
|
|
1133
|
+
const key = this.key(session);
|
|
1134
|
+
if (!key) return "wake_not_sent" /* notSent */;
|
|
1135
|
+
const socket = await connectInbox(session.messagingSocketPath);
|
|
1136
|
+
if (!socket) return "wake_not_sent" /* notSent */;
|
|
1137
|
+
if (signal?.aborted || (this.options.processStart ?? processStart)(session.pid) !== session.procStart) {
|
|
1138
|
+
socket.destroy();
|
|
1139
|
+
return "wake_not_sent" /* notSent */;
|
|
1140
|
+
}
|
|
1141
|
+
const commandId = randomUUID();
|
|
1142
|
+
const transcript = transcriptPath(this.options.configDir, session);
|
|
1143
|
+
const attempt = {
|
|
1144
|
+
version: CLAUDE_INBOX.journalVersion,
|
|
1145
|
+
sessionId,
|
|
1146
|
+
commandId,
|
|
1147
|
+
transcript,
|
|
1148
|
+
offset: transcriptSize(transcript),
|
|
1149
|
+
accepted: false,
|
|
1150
|
+
submittedAtMs: Date.now()
|
|
1151
|
+
};
|
|
1152
|
+
const file = join3(
|
|
1153
|
+
this.options.journalDir,
|
|
1154
|
+
session.version,
|
|
1155
|
+
`${attempt.submittedAtMs}-${commandId}.json`
|
|
1156
|
+
);
|
|
1157
|
+
try {
|
|
1158
|
+
createAttempt(file, attempt);
|
|
1159
|
+
} catch {
|
|
1160
|
+
socket.destroy();
|
|
1161
|
+
return "wake_not_sent" /* notSent */;
|
|
1162
|
+
}
|
|
1163
|
+
const frame = {
|
|
1164
|
+
type: "user",
|
|
1165
|
+
session_id: sessionId,
|
|
1166
|
+
uuid: commandId,
|
|
1167
|
+
msg_id: commandId,
|
|
1168
|
+
from: CLAUDE_INBOX.sender,
|
|
1169
|
+
priority: "next",
|
|
1170
|
+
message: { role: "user", content: CLAUDE_WAKE.message }
|
|
1171
|
+
};
|
|
1172
|
+
socket.end(
|
|
1173
|
+
JSON.stringify({ type: "auth", token: key.peerToken }) + "\n" + JSON.stringify(frame) + "\n"
|
|
1174
|
+
);
|
|
1175
|
+
socket.setTimeout(CLAUDE_INBOX.connectTimeoutMs, () => socket.destroy());
|
|
1176
|
+
const outcome = await this.observe(
|
|
1177
|
+
file,
|
|
1178
|
+
attempt,
|
|
1179
|
+
attempt.submittedAtMs + CLAUDE_WAKE.receiptWindowMs
|
|
1180
|
+
);
|
|
1181
|
+
return outcome === "accepted" /* accepted */ ? "wake_accepted" /* accepted */ : "wake_unconfirmed" /* unconfirmed */;
|
|
1182
|
+
} catch {
|
|
1183
|
+
return "wake_not_sent" /* notSent */;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async observe(file, attempt, expiresAtMs) {
|
|
1187
|
+
const deliveryDeadline = Math.min(
|
|
1188
|
+
expiresAtMs,
|
|
1189
|
+
attempt.submittedAtMs + CLAUDE_INBOX.receiptWindowMs
|
|
1190
|
+
);
|
|
1191
|
+
if (claudeInboxAction({
|
|
1192
|
+
attempted: true,
|
|
1193
|
+
accepted: attempt.accepted,
|
|
1194
|
+
expiresAtMs: deliveryDeadline,
|
|
1195
|
+
nowMs: Date.now()
|
|
1196
|
+
}) === "acknowledge" /* acknowledge */)
|
|
1197
|
+
return "accepted" /* accepted */;
|
|
1198
|
+
const deadline = Math.min(
|
|
1199
|
+
deliveryDeadline,
|
|
1200
|
+
Date.now() + (this.options.receiptWindowMs ?? CLAUDE_INBOX.receiptWindowMs)
|
|
1201
|
+
);
|
|
1202
|
+
do {
|
|
1203
|
+
if (receiptPresent(attempt)) {
|
|
1204
|
+
try {
|
|
1205
|
+
writeJsonAtomicDurable(file, { ...attempt, accepted: true }, 384);
|
|
1206
|
+
} catch {
|
|
1207
|
+
return "unconfirmed" /* unconfirmed */;
|
|
1208
|
+
}
|
|
1209
|
+
return "accepted" /* accepted */;
|
|
1210
|
+
}
|
|
1211
|
+
if (Date.now() >= deadline) break;
|
|
1212
|
+
await delay(this.options.receiptPollMs ?? CLAUDE_INBOX.receiptPollMs);
|
|
1213
|
+
} while (Date.now() <= deadline);
|
|
1214
|
+
return deliveryDeadline <= Date.now() ? "outcome_unknown" /* outcomeUnknown */ : "unconfirmed" /* unconfirmed */;
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
// src/claude-inbox-receiver.ts
|
|
1219
|
+
var ownsSession = (session) => {
|
|
1220
|
+
const files = [
|
|
1221
|
+
join4(claudeConfigDir(), "settings.json"),
|
|
1222
|
+
join4(session.cwd, ".claude", "settings.json"),
|
|
1223
|
+
join4(session.cwd, ".claude", "settings.local.json")
|
|
1224
|
+
];
|
|
1225
|
+
const owners = files.map(readHookOwner);
|
|
1226
|
+
return owners.includes("cli") && !owners.includes("app") && !owners.includes(null);
|
|
1227
|
+
};
|
|
1228
|
+
var startClaudeInboxReceiver = (apiKey, machineId, options = {}) => {
|
|
1229
|
+
const client = options.client ?? new ClaudeInboxClient({
|
|
1230
|
+
configDir: claudeConfigDir(),
|
|
1231
|
+
journalDir: join4(homedir2(), ".pushary", CLAUDE_WAKE.directory)
|
|
1232
|
+
});
|
|
1233
|
+
const baseUrl = options.baseUrl ?? getBaseUrl();
|
|
1234
|
+
const abort = new AbortController();
|
|
1235
|
+
let scanning = false;
|
|
1236
|
+
let cursor = 0;
|
|
1237
|
+
const heartbeats = /* @__PURE__ */ new Map();
|
|
1238
|
+
const post = async (body, path = CLAUDE_WAKE_API) => {
|
|
1239
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
1240
|
+
method: "POST",
|
|
1241
|
+
headers: {
|
|
1242
|
+
"Content-Type": "application/json",
|
|
1243
|
+
Authorization: `Bearer ${apiKey}`
|
|
1244
|
+
},
|
|
1245
|
+
body: JSON.stringify(body),
|
|
1246
|
+
signal: AbortSignal.any([
|
|
1247
|
+
abort.signal,
|
|
1248
|
+
AbortSignal.timeout(CLAUDE_INBOX_RECEIVER.requestTimeoutMs)
|
|
1249
|
+
])
|
|
1250
|
+
});
|
|
1251
|
+
if (!response.ok) throw new Error(`Claude wake HTTP ${response.status}`);
|
|
1252
|
+
return response.json();
|
|
1253
|
+
};
|
|
1254
|
+
const report = async (sessionId, waitingOn) => {
|
|
1255
|
+
await post({
|
|
1256
|
+
operation: "report" /* report */,
|
|
1257
|
+
sessionId,
|
|
1258
|
+
machineId,
|
|
1259
|
+
waitingOn
|
|
1260
|
+
});
|
|
1261
|
+
};
|
|
1262
|
+
const tick = async () => {
|
|
1263
|
+
if (scanning || abort.signal.aborted) return;
|
|
1264
|
+
scanning = true;
|
|
1265
|
+
try {
|
|
1266
|
+
await reconcileCommandReceipts(
|
|
1267
|
+
apiKey,
|
|
1268
|
+
machineId,
|
|
1269
|
+
"claude_code",
|
|
1270
|
+
baseUrl,
|
|
1271
|
+
abort.signal
|
|
1272
|
+
);
|
|
1273
|
+
const sessions = client.sessions().filter(options.ownsSession ?? ownsSession).slice(0, CLAUDE_INBOX_MAX_SESSIONS);
|
|
1274
|
+
if (!sessions.length) return;
|
|
1275
|
+
for (const session of sessions) {
|
|
1276
|
+
if (Date.now() - (heartbeats.get(session.sessionId) ?? 0) < CLAUDE_INBOX_RECEIVER.heartbeatMs)
|
|
1277
|
+
continue;
|
|
1278
|
+
await post(
|
|
1279
|
+
{
|
|
1280
|
+
event: "heartbeat",
|
|
1281
|
+
agentType: "claude_code",
|
|
1282
|
+
agentName: `Claude Code - ${basename2(session.cwd)}`,
|
|
1283
|
+
sessionId: session.sessionId,
|
|
1284
|
+
machineId,
|
|
1285
|
+
cwd: session.cwd,
|
|
1286
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1287
|
+
canDrainCommand: false,
|
|
1288
|
+
skipCommandDrain: true
|
|
1289
|
+
},
|
|
1290
|
+
"/api/agent/event"
|
|
1291
|
+
);
|
|
1292
|
+
heartbeats.set(session.sessionId, Date.now());
|
|
1293
|
+
}
|
|
1294
|
+
const raw = await post({
|
|
1295
|
+
operation: "poll" /* poll */,
|
|
1296
|
+
machineId,
|
|
1297
|
+
sessionIds: sessions.map((session) => session.sessionId)
|
|
1298
|
+
});
|
|
1299
|
+
const pending = parseClaudeInboxPoll(raw);
|
|
1300
|
+
if (!pending) throw new Error("Claude wake protocol mismatch");
|
|
1301
|
+
const rotated = [...pending.slice(cursor), ...pending.slice(0, cursor)];
|
|
1302
|
+
const batch = rotated.slice(0, CLAUDE_INBOX_RECEIVER.maxConcurrent);
|
|
1303
|
+
cursor = pending.length ? (cursor + batch.length) % pending.length : 0;
|
|
1304
|
+
await Promise.all(
|
|
1305
|
+
batch.map(async (sessionId) => {
|
|
1306
|
+
if (!sessions.some((session) => session.sessionId === sessionId) || abort.signal.aborted)
|
|
1307
|
+
return;
|
|
1308
|
+
await report(sessionId, "waking" /* waking */);
|
|
1309
|
+
const outcome = await client.wake(sessionId, abort.signal);
|
|
1310
|
+
await report(sessionId, claudeWakeWaitingOn(outcome));
|
|
1311
|
+
})
|
|
1312
|
+
);
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
if (!abort.signal.aborted)
|
|
1315
|
+
options.report?.(
|
|
1316
|
+
error instanceof Error ? error.message : "Claude wake failed"
|
|
1317
|
+
);
|
|
1318
|
+
} finally {
|
|
1319
|
+
scanning = false;
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
const timer = setInterval(() => {
|
|
1323
|
+
void tick();
|
|
1324
|
+
}, options.pollMs ?? CLAUDE_INBOX_RECEIVER.pollMs);
|
|
1325
|
+
timer.unref();
|
|
1326
|
+
void tick();
|
|
1327
|
+
return () => {
|
|
1328
|
+
abort.abort();
|
|
1329
|
+
clearInterval(timer);
|
|
1330
|
+
};
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
// src/spawn-daemon.ts
|
|
1334
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
1335
|
+
import { existsSync, statSync as statSync2 } from "fs";
|
|
1336
|
+
import { homedir as homedir3 } from "os";
|
|
1337
|
+
import { basename as basename3, dirname as dirname3, join as join5 } from "path";
|
|
113
1338
|
|
|
114
1339
|
// src/stop-requests.ts
|
|
115
1340
|
var STOP_CAPABILITY = "stop";
|
|
@@ -292,13 +1517,13 @@ var resolveSpawnDirectory = (requested, options = {}) => {
|
|
|
292
1517
|
const fallbackDir = options.fallbackDir ?? process.cwd();
|
|
293
1518
|
const value = requested?.trim();
|
|
294
1519
|
if (!value) return { ok: true, cwd: fallbackDir };
|
|
295
|
-
const homeDir = options.homeDir ??
|
|
296
|
-
const cwd = value === "~" ? homeDir : value.startsWith("~/") || value.startsWith("~\\") ?
|
|
1520
|
+
const homeDir = options.homeDir ?? homedir3();
|
|
1521
|
+
const cwd = value === "~" ? homeDir : value.startsWith("~/") || value.startsWith("~\\") ? join5(homeDir, value.slice(2)) : value;
|
|
297
1522
|
if (!(options.exists ?? existsSync)(cwd)) {
|
|
298
1523
|
return { ok: false, message: `Directory does not exist: ${value}` };
|
|
299
1524
|
}
|
|
300
1525
|
try {
|
|
301
|
-
if (!(options.isDirectory ?? ((path) =>
|
|
1526
|
+
if (!(options.isDirectory ?? ((path) => statSync2(path).isDirectory()))(cwd)) {
|
|
302
1527
|
return { ok: false, message: `Not a directory: ${value}` };
|
|
303
1528
|
}
|
|
304
1529
|
} catch {
|
|
@@ -390,9 +1615,9 @@ var runSpawnDaemon = async () => {
|
|
|
390
1615
|
describedProviders = description;
|
|
391
1616
|
});
|
|
392
1617
|
describedProviders = describeLaunchableProviders(inventory.current.capabilities);
|
|
393
|
-
const siblingEntry = process.argv[1] ?
|
|
394
|
-
const codexBridgeEntry = process.argv[1] ?
|
|
395
|
-
const geminiBridgeEntry = process.argv[1] ?
|
|
1618
|
+
const siblingEntry = process.argv[1] ? join5(dirname3(process.argv[1]), "pushary.js") : "";
|
|
1619
|
+
const codexBridgeEntry = process.argv[1] ? join5(dirname3(process.argv[1]), "pushary-codex-bridge.js") : "pushary-codex-bridge";
|
|
1620
|
+
const geminiBridgeEntry = process.argv[1] ? join5(dirname3(process.argv[1]), "pushary-gemini-bridge.js") : "pushary-gemini-bridge";
|
|
396
1621
|
const spawnDirect = siblingEntry.length > 0 && existsSync(siblingEntry);
|
|
397
1622
|
const existing = readDaemonStatus();
|
|
398
1623
|
let handoff;
|
|
@@ -492,7 +1717,7 @@ var runSpawnDaemon = async () => {
|
|
|
492
1717
|
if (!plan) {
|
|
493
1718
|
return Promise.reject(new Error(FLAG_SHAPED_PROMPT_MESSAGE));
|
|
494
1719
|
}
|
|
495
|
-
return new Promise((
|
|
1720
|
+
return new Promise((resolve3, reject) => {
|
|
496
1721
|
try {
|
|
497
1722
|
const child = plan.direct ? spawn(plan.command, plan.args, { cwd, detached: true, stdio: "ignore", env }) : spawnClaude(plan.command, plan.args, { cwd, detached: true, stdio: "ignore", env });
|
|
498
1723
|
let started = false;
|
|
@@ -521,8 +1746,8 @@ var runSpawnDaemon = async () => {
|
|
|
521
1746
|
});
|
|
522
1747
|
}
|
|
523
1748
|
child.unref();
|
|
524
|
-
log(`spawn ${req.id}: launched a phone-requested ${plan.agent} session in ${
|
|
525
|
-
|
|
1749
|
+
log(`spawn ${req.id}: launched a phone-requested ${plan.agent} session in ${basename3(cwd)} (pid ${child.pid ?? "unknown"})`);
|
|
1750
|
+
resolve3(typeof child.pid === "number" ? child.pid : null);
|
|
526
1751
|
});
|
|
527
1752
|
} catch (err) {
|
|
528
1753
|
reject(err);
|
|
@@ -530,7 +1755,7 @@ var runSpawnDaemon = async () => {
|
|
|
530
1755
|
});
|
|
531
1756
|
};
|
|
532
1757
|
const kill = process.platform === "win32" ? (pid) => {
|
|
533
|
-
|
|
1758
|
+
execFileSync2("taskkill", windowsTaskkillArgs(pid), { stdio: "ignore" });
|
|
534
1759
|
} : process.kill.bind(process);
|
|
535
1760
|
const stopDeps = {
|
|
536
1761
|
kill: (pid, signal) => {
|
|
@@ -542,7 +1767,7 @@ var runSpawnDaemon = async () => {
|
|
|
542
1767
|
return launched || ownsLiveProcess(request);
|
|
543
1768
|
},
|
|
544
1769
|
selfPid: process.pid,
|
|
545
|
-
sleep: (ms) => new Promise((
|
|
1770
|
+
sleep: (ms) => new Promise((resolve3) => setTimeout(resolve3, ms)),
|
|
546
1771
|
isAlive: (pid) => {
|
|
547
1772
|
try {
|
|
548
1773
|
process.kill(pid, 0);
|
|
@@ -600,7 +1825,7 @@ var runSpawnDaemon = async () => {
|
|
|
600
1825
|
completedStops.delete(acknowledgement.stopId);
|
|
601
1826
|
break;
|
|
602
1827
|
}
|
|
603
|
-
if (attempt < 2) await new Promise((
|
|
1828
|
+
if (attempt < 2) await new Promise((resolve3) => setTimeout(resolve3, 200 * (attempt + 1)));
|
|
604
1829
|
}
|
|
605
1830
|
}
|
|
606
1831
|
async function apply() {
|
|
@@ -743,8 +1968,12 @@ var runSpawnDaemon = async () => {
|
|
|
743
1968
|
if (!stopped) timer = setTimeout(tick, jitter(nextDelay()));
|
|
744
1969
|
};
|
|
745
1970
|
const stopTranscripts = startRegisteredTranscripts(apiKey, machineId);
|
|
1971
|
+
const stopNativeHosts = startNativeHostReceivers(apiKey, machineId, log);
|
|
1972
|
+
const stopClaudeInbox = startClaudeInboxReceiver(apiKey, machineId, { report: log });
|
|
746
1973
|
const teardown = () => {
|
|
747
1974
|
stopTranscripts();
|
|
1975
|
+
stopClaudeInbox();
|
|
1976
|
+
stopNativeHosts();
|
|
748
1977
|
if (stopped) return;
|
|
749
1978
|
stopped = true;
|
|
750
1979
|
if (timer) clearTimeout(timer);
|
|
@@ -767,7 +1996,7 @@ var runSpawnDaemon = async () => {
|
|
|
767
1996
|
return;
|
|
768
1997
|
}
|
|
769
1998
|
const result = await sendHeartbeat(apiKey, {
|
|
770
|
-
repoKey:
|
|
1999
|
+
repoKey: basename3(process.cwd()),
|
|
771
2000
|
capabilities: [
|
|
772
2001
|
...launchAdvertisement(inventory.current.capabilities),
|
|
773
2002
|
STOP_CAPABILITY,
|
|
@@ -799,24 +2028,24 @@ var runSpawnDaemon = async () => {
|
|
|
799
2028
|
log(hasSpawnProvider(inventory.current.capabilities) ? `daemon online (pid ${process.pid}): this machine (${machineId}) can now be sent a new session from your phone. Ctrl-C to stop.` : `daemon online (pid ${process.pid}): no supported Claude, Codex, or Gemini CLI was found, so phone-start is unavailable. Ctrl-C to stop.`);
|
|
800
2029
|
timer = setTimeout(tick, 0);
|
|
801
2030
|
const signals = daemonSignals();
|
|
802
|
-
await new Promise((
|
|
2031
|
+
await new Promise((resolve3) => {
|
|
803
2032
|
const onSignal = () => finish();
|
|
804
2033
|
for (const signal of signals) process.on(signal, onSignal);
|
|
805
2034
|
finish = () => {
|
|
806
2035
|
teardown();
|
|
807
2036
|
for (const signal of signals) process.off(signal, onSignal);
|
|
808
|
-
|
|
2037
|
+
resolve3();
|
|
809
2038
|
};
|
|
810
2039
|
});
|
|
811
2040
|
return 0;
|
|
812
2041
|
};
|
|
813
2042
|
|
|
814
2043
|
// src/daemon-sessions.ts
|
|
815
|
-
import { execFileSync as
|
|
2044
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
816
2045
|
var readResidentSetKb = (pid) => {
|
|
817
2046
|
if (process.platform === "win32") return null;
|
|
818
2047
|
try {
|
|
819
|
-
const output =
|
|
2048
|
+
const output = execFileSync3("ps", ["-o", "rss=", "-p", String(pid)], {
|
|
820
2049
|
encoding: "utf8",
|
|
821
2050
|
stdio: ["ignore", "pipe", "ignore"]
|
|
822
2051
|
});
|