@pushary/agent-hooks 0.83.2 → 0.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/dist/bin/pushary-bell-hook.js +5 -5
- package/dist/bin/pushary-bell.js +15 -14
- package/dist/bin/pushary-claude.js +93 -143
- package/dist/bin/pushary-clean.js +13 -12
- package/dist/bin/pushary-codex-bridge.d.ts +1 -0
- package/dist/bin/pushary-codex-bridge.js +349 -0
- package/dist/bin/pushary-codex-hook.js +18 -14
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +13 -12
- package/dist/bin/pushary-daemon.js +331 -41
- package/dist/bin/pushary-doctor.js +48 -25
- package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
- package/dist/bin/pushary-gemini-bridge.js +299 -0
- package/dist/bin/pushary-gemini-hook.js +28 -21
- package/dist/bin/pushary-hook.js +14 -13
- package/dist/bin/pushary-login.js +15 -14
- package/dist/bin/pushary-logout.js +12 -11
- package/dist/bin/pushary-mode.js +10 -9
- package/dist/bin/pushary-notification-hook.js +5 -5
- package/dist/bin/pushary-permission-denied-hook.js +9 -9
- package/dist/bin/pushary-permission-hook.js +9 -9
- package/dist/bin/pushary-post-hook.js +5 -5
- package/dist/bin/pushary-prompt-hook.js +5 -5
- package/dist/bin/pushary-session-end-hook.js +5 -5
- package/dist/bin/pushary-session-start-hook.js +5 -5
- package/dist/bin/pushary-setup.js +42 -36
- package/dist/bin/pushary-stats.js +6 -5
- package/dist/bin/pushary-status.js +17 -16
- package/dist/bin/pushary-stop-hook.js +5 -5
- package/dist/bin/pushary-stopfailure-hook.js +5 -5
- package/dist/bin/pushary-suggestions.js +7 -6
- package/dist/bin/pushary-upgrade.js +18 -17
- package/dist/bin/pushary-wait.js +10 -9
- package/dist/bin/pushary.js +8 -6
- package/dist/{chunk-SAA3VBDK.js → chunk-437T77TY.js} +3 -3
- package/dist/{chunk-U27BZWFT.js → chunk-6LC4WSCL.js} +1 -1
- package/dist/{chunk-7WWZPII5.js → chunk-7PZPJSWI.js} +1 -1
- package/dist/{chunk-4QQOKEOS.js → chunk-BLDDXHHA.js} +6 -6
- package/dist/{chunk-7PO2VEA4.js → chunk-CCCJ6BPW.js} +40 -34
- package/dist/chunk-CCH775Y6.js +343 -0
- package/dist/chunk-DINDL2CF.js +45 -0
- package/dist/{chunk-TXF4QZSQ.js → chunk-DOXSWT4Q.js} +1 -1
- package/dist/chunk-ERYKGH4J.js +140 -0
- package/dist/{chunk-QR6MVVQ7.js → chunk-F52SIIDG.js} +4 -4
- package/dist/{chunk-26D2YMDU.js → chunk-FBGBJXVC.js} +1 -1
- package/dist/chunk-GCKAWHFP.js +124 -0
- package/dist/{chunk-QF7WTF2C.js → chunk-HZNOAJGB.js} +2 -2
- package/dist/chunk-ILB6UOAF.js +192 -0
- package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
- package/dist/{chunk-WDRRULBH.js → chunk-K7CSRGKZ.js} +4 -4
- package/dist/{chunk-ZUKH2NPJ.js → chunk-MGJBC6DD.js} +1 -44
- package/dist/{chunk-GQLB2GBJ.js → chunk-MHBC2XUP.js} +1 -1
- package/dist/{chunk-YWRJGYUH.js → chunk-MQMPG5X4.js} +28 -2
- package/dist/{chunk-3AGM3TDM.js → chunk-NJ7JT26G.js} +1 -1
- package/dist/chunk-NRGRUOAR.js +25 -0
- package/dist/{chunk-EHLTWCDZ.js → chunk-SEAHGHCE.js} +1 -1
- package/dist/{chunk-NDC3TBHI.js → chunk-UKSQWTBH.js} +1 -1
- package/dist/{chunk-UVAC2KHH.js → chunk-UXWEE3QI.js} +1 -1
- package/dist/{chunk-MUEW424A.js → chunk-VT4IVERX.js} +50 -3
- package/dist/{chunk-DHAZ6SOT.js → chunk-YR5TSPXB.js} +81 -25
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.js +9 -9
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.84.1
|
|
4
|
+
|
|
5
|
+
### Remote starts survive lost acknowledgements without double-launching
|
|
6
|
+
|
|
7
|
+
A phone-started agent now claims exclusive process ownership before the provider
|
|
8
|
+
starts. If the daemon restarts or the launch acknowledgement is lost, retrying the
|
|
9
|
+
same request reconnects to the existing process instead of starting a second one.
|
|
10
|
+
|
|
11
|
+
### Repository approval scopes reach Codex and Gemini
|
|
12
|
+
|
|
13
|
+
Codex and Gemini sessions now carry their repository identity through approval
|
|
14
|
+
requests, so repository-scoped rules stay inside the repository that created them.
|
|
15
|
+
|
|
16
|
+
### Encrypted transcripts retry safely
|
|
17
|
+
|
|
18
|
+
Transcript writers now retry while a session key is temporarily unavailable and
|
|
19
|
+
stop on a real key conflict, preventing undecryptable transcript entries from being
|
|
20
|
+
accepted or silently dropped.
|
|
21
|
+
|
|
22
|
+
## 0.84.0
|
|
23
|
+
|
|
24
|
+
### The phone can start an agent on a machine that is not running one
|
|
25
|
+
|
|
26
|
+
The phone could reach a session that already existed. Starting one meant being at
|
|
27
|
+
the keyboard. `pushary setup` and `pushary claude` now bring up a small background
|
|
28
|
+
process, `pushary-daemon`, which watches for a launch requested from the phone and
|
|
29
|
+
starts a real session with the prompt you sent.
|
|
30
|
+
|
|
31
|
+
It is a poller over a durable queue, not an open socket. Idle, it asks the server
|
|
32
|
+
for work every 30 seconds and sends one heartbeat a minute, and it carries a
|
|
33
|
+
prompt, never any code. One request is in flight at a time, each bounded by its
|
|
34
|
+
own timeout, with a spawn rate cap so a queue backlog cannot fill a machine with
|
|
35
|
+
agents. Exactly one daemon runs per machine: a second one asks the first to hand
|
|
36
|
+
over rather than racing it, and it declines to take over from a daemon that is
|
|
37
|
+
still healthy.
|
|
38
|
+
|
|
39
|
+
This is a background process that was not running before. `pushary doctor` reports
|
|
40
|
+
whether it is up, and `pushary daemon` in a terminal shows you what it is doing.
|
|
41
|
+
|
|
42
|
+
A launch is now acknowledged rather than assumed. The daemon claims a request,
|
|
43
|
+
confirms the session it started, and the phone shows what actually happened,
|
|
44
|
+
including the failure when a launch does not come up.
|
|
45
|
+
|
|
46
|
+
### Always allowing a tool no longer has to mean everywhere
|
|
47
|
+
|
|
48
|
+
"Approve and always allow" wrote one kind of rule: this tool, this whole
|
|
49
|
+
workspace, until you delete it. That is the right scope for `Read` and far too
|
|
50
|
+
wide for a push.
|
|
51
|
+
|
|
52
|
+
The approval now offers the scope alongside it. Workspace is the previous
|
|
53
|
+
behaviour. Repository binds the rule to the repository the ask came from, so a
|
|
54
|
+
rule written in one checkout stops governing another. Session binds it to the run
|
|
55
|
+
you are watching and goes away with it.
|
|
56
|
+
|
|
57
|
+
An older Pushary never sees the narrower rules. A CLI that does not ask for them
|
|
58
|
+
is sent workspace-wide rules only, so a rule you scoped to one session cannot be
|
|
59
|
+
quietly applied everywhere by something released before the idea existed.
|
|
60
|
+
|
|
61
|
+
### AskUserQuestion reaches the phone with its descriptions
|
|
62
|
+
|
|
63
|
+
A Claude Code question carries a header, a description per option, and sometimes
|
|
64
|
+
permission to pick more than one. The phone received the bare labels. It now
|
|
65
|
+
receives the question the way the agent wrote it.
|
|
66
|
+
|
|
67
|
+
One question goes out as one card carrying the whole shape. Every surface that
|
|
68
|
+
does not know that shape, the web decision page and Slack among them, still sees
|
|
69
|
+
an ordinary choice list and answers with a label, which the server reads back onto
|
|
70
|
+
the question. Two or more questions go out one at a time, which is what every
|
|
71
|
+
surface has always been able to answer: a bare label cannot say which of several
|
|
72
|
+
questions it belongs to, and a half-filled answer is worse for the agent than
|
|
73
|
+
being asked again in the terminal.
|
|
74
|
+
|
|
75
|
+
### Codex and Gemini sessions started from the phone stay live
|
|
76
|
+
|
|
77
|
+
A phone-started Codex or Gemini run now goes through a bridge that survives the
|
|
78
|
+
connection dropping and resumes the same session instead of opening a new one.
|
|
79
|
+
The machine advertises only what it can actually do, so a Gemini CLI too old for
|
|
80
|
+
the bridge is not offered as one, and the phone stops showing controls that would
|
|
81
|
+
fail.
|
|
82
|
+
|
|
83
|
+
Stopping is acknowledged the same way. A stop request names the session and the
|
|
84
|
+
process it belongs to, and the machine proves it owns that process before acting
|
|
85
|
+
on it, so a recycled pid cannot be mistaken for the agent you asked to stop.
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## 0.83.0 to 0.83.2
|
|
89
|
+
|
|
90
|
+
Three releases went out without an entry here. In order: `ask_user` became
|
|
91
|
+
idempotent, so a create that gets retried after a timeout resolves to the question
|
|
92
|
+
already asked instead of buzzing a phone twice. The pairing QR stopped encoding a
|
|
93
|
+
code only a browser could resolve. And the gate's two shared pieces, the verdict a
|
|
94
|
+
policy produces and the "most specific rule wins" precedence, moved into the
|
|
95
|
+
package the CLI and the server both read, with no change to what either decides.
|
|
96
|
+
|
|
3
97
|
## 0.82.0
|
|
4
98
|
|
|
5
99
|
### Setup reads properly when an agent is the one running it
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleBell
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-6MTNS63X.js";
|
|
4
|
+
} from "../chunk-7PZPJSWI.js";
|
|
5
|
+
import "../chunk-K7CSRGKZ.js";
|
|
7
6
|
import "../chunk-BC3VCZ3E.js";
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-6MTNS63X.js";
|
|
8
|
+
import "../chunk-437T77TY.js";
|
|
10
9
|
import "../chunk-2UMNXADU.js";
|
|
10
|
+
import "../chunk-MQMPG5X4.js";
|
|
11
11
|
|
|
12
12
|
// bin/pushary-bell-hook.ts
|
|
13
13
|
var main = async () => {
|
package/dist/bin/pushary-bell.js
CHANGED
|
@@ -5,33 +5,34 @@ import {
|
|
|
5
5
|
liveAgentCount,
|
|
6
6
|
ring,
|
|
7
7
|
upgradeThreshold
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-7PZPJSWI.js";
|
|
9
9
|
import {
|
|
10
10
|
claudeSettings
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-SEAHGHCE.js";
|
|
12
12
|
import {
|
|
13
13
|
createIo
|
|
14
14
|
} from "../chunk-5RUIFDTP.js";
|
|
15
|
-
import "../chunk-
|
|
16
|
-
import {
|
|
17
|
-
readJsonSafe,
|
|
18
|
-
writeJsonAtomic
|
|
19
|
-
} from "../chunk-6MTNS63X.js";
|
|
15
|
+
import "../chunk-K7CSRGKZ.js";
|
|
20
16
|
import {
|
|
21
17
|
parseFlags
|
|
22
18
|
} from "../chunk-GMXKITVA.js";
|
|
23
|
-
import "../chunk-BC3VCZ3E.js";
|
|
24
|
-
import "../chunk-SAA3VBDK.js";
|
|
25
|
-
import "../chunk-7QLSKOSU.js";
|
|
26
|
-
import "../chunk-YWRJGYUH.js";
|
|
27
19
|
import {
|
|
28
20
|
exitOnHelpFlag
|
|
29
|
-
} from "../chunk-
|
|
30
|
-
import "../chunk-
|
|
31
|
-
import "../chunk-
|
|
21
|
+
} from "../chunk-J4KFVOCQ.js";
|
|
22
|
+
import "../chunk-MGJBC6DD.js";
|
|
23
|
+
import "../chunk-BC3VCZ3E.js";
|
|
24
|
+
import "../chunk-DINDL2CF.js";
|
|
25
|
+
import {
|
|
26
|
+
readJsonSafe,
|
|
27
|
+
writeJsonAtomic
|
|
28
|
+
} from "../chunk-6MTNS63X.js";
|
|
29
|
+
import "../chunk-437T77TY.js";
|
|
30
|
+
import "../chunk-7QLSKOSU.js";
|
|
32
31
|
import {
|
|
33
32
|
EXIT
|
|
34
33
|
} from "../chunk-KZERVKTD.js";
|
|
34
|
+
import "../chunk-2UMNXADU.js";
|
|
35
|
+
import "../chunk-MQMPG5X4.js";
|
|
35
36
|
|
|
36
37
|
// src/bell/install.ts
|
|
37
38
|
import { join } from "path";
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ensureDaemonRunning
|
|
4
|
+
} from "../chunk-CCH775Y6.js";
|
|
5
|
+
import {
|
|
6
|
+
buildTranscriptRecords,
|
|
7
|
+
decodePublicKey,
|
|
8
|
+
fetchAccountPublicKey,
|
|
9
|
+
generateSessionKey,
|
|
10
|
+
uploadTranscriptRecords,
|
|
11
|
+
wrapSessionKey
|
|
12
|
+
} from "../chunk-GCKAWHFP.js";
|
|
13
|
+
import {
|
|
14
|
+
startProcessOwnership,
|
|
15
|
+
withSpawnProcessOwnership
|
|
16
|
+
} from "../chunk-ILB6UOAF.js";
|
|
17
|
+
import {
|
|
18
|
+
needsShell,
|
|
19
|
+
spawnClaude
|
|
20
|
+
} from "../chunk-XHKBHWLX.js";
|
|
21
|
+
import "../chunk-VT4IVERX.js";
|
|
22
|
+
import "../chunk-SEAHGHCE.js";
|
|
2
23
|
import {
|
|
3
24
|
KILL_REASON,
|
|
4
25
|
isDeferAnswer,
|
|
5
26
|
resolveGate
|
|
6
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-BLDDXHHA.js";
|
|
7
28
|
import {
|
|
8
29
|
askUser,
|
|
9
30
|
cancelQuestion,
|
|
@@ -15,26 +36,22 @@ import {
|
|
|
15
36
|
reportEvent,
|
|
16
37
|
scopePathFor,
|
|
17
38
|
waitForAnswer
|
|
18
|
-
} from "../chunk-
|
|
19
|
-
import "../chunk-BSZYIAZL.js";
|
|
20
|
-
import "../chunk-SAF6HGAA.js";
|
|
21
|
-
import "../chunk-SAA3VBDK.js";
|
|
22
|
-
import "../chunk-7QLSKOSU.js";
|
|
23
|
-
import {
|
|
24
|
-
redactSecrets
|
|
25
|
-
} from "../chunk-YWRJGYUH.js";
|
|
26
|
-
import {
|
|
27
|
-
needsShell,
|
|
28
|
-
spawnClaude
|
|
29
|
-
} from "../chunk-XHKBHWLX.js";
|
|
39
|
+
} from "../chunk-CCCJ6BPW.js";
|
|
30
40
|
import {
|
|
31
41
|
getMachineId
|
|
32
42
|
} from "../chunk-RN3NOEJF.js";
|
|
43
|
+
import "../chunk-DINDL2CF.js";
|
|
44
|
+
import "../chunk-6MTNS63X.js";
|
|
45
|
+
import "../chunk-BSZYIAZL.js";
|
|
46
|
+
import "../chunk-SAF6HGAA.js";
|
|
47
|
+
import "../chunk-437T77TY.js";
|
|
48
|
+
import "../chunk-7QLSKOSU.js";
|
|
49
|
+
import "../chunk-KZERVKTD.js";
|
|
33
50
|
import {
|
|
34
51
|
getApiKey,
|
|
35
52
|
getBaseUrl
|
|
36
53
|
} from "../chunk-2UMNXADU.js";
|
|
37
|
-
import "../chunk-
|
|
54
|
+
import "../chunk-MQMPG5X4.js";
|
|
38
55
|
|
|
39
56
|
// src/wrapper/claudeBinary.ts
|
|
40
57
|
import { statSync } from "fs";
|
|
@@ -97,9 +114,9 @@ var runLocalPassthrough = (binary, args2) => {
|
|
|
97
114
|
cleanup();
|
|
98
115
|
resolve2(127);
|
|
99
116
|
});
|
|
100
|
-
child.on("exit", (
|
|
117
|
+
child.on("exit", (code2, signal) => {
|
|
101
118
|
cleanup();
|
|
102
|
-
if (typeof
|
|
119
|
+
if (typeof code2 === "number") resolve2(code2);
|
|
103
120
|
else if (signal) resolve2(128 + (SIGNAL_NUMBERS[signal] ?? 0));
|
|
104
121
|
else resolve2(0);
|
|
105
122
|
});
|
|
@@ -113,112 +130,6 @@ import { basename } from "path";
|
|
|
113
130
|
import { homedir } from "os";
|
|
114
131
|
import { join as join2, resolve } from "path";
|
|
115
132
|
import { existsSync, readFileSync } from "fs";
|
|
116
|
-
|
|
117
|
-
// src/transcript-capture.ts
|
|
118
|
-
import { createHash } from "crypto";
|
|
119
|
-
|
|
120
|
-
// src/crypto.ts
|
|
121
|
-
import nacl from "tweetnacl";
|
|
122
|
-
var EPH_PUB_LEN = nacl.box.publicKeyLength;
|
|
123
|
-
var BOX_NONCE_LEN = nacl.box.nonceLength;
|
|
124
|
-
var SECRETBOX_NONCE_LEN = nacl.secretbox.nonceLength;
|
|
125
|
-
var SESSION_KEY_LEN = nacl.secretbox.keyLength;
|
|
126
|
-
var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
|
|
127
|
-
var fromBase64 = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
128
|
-
var utf8Encode = (text) => new Uint8Array(Buffer.from(text, "utf-8"));
|
|
129
|
-
var decodePublicKey = (value) => fromBase64(value);
|
|
130
|
-
var generateSessionKey = () => nacl.randomBytes(SESSION_KEY_LEN);
|
|
131
|
-
var wrapSessionKey = (sessionKey, accountPublicKey) => {
|
|
132
|
-
const ephemeral = nacl.box.keyPair();
|
|
133
|
-
const nonce = nacl.randomBytes(BOX_NONCE_LEN);
|
|
134
|
-
const box = nacl.box(sessionKey, nonce, accountPublicKey, ephemeral.secretKey);
|
|
135
|
-
const bundle = new Uint8Array(EPH_PUB_LEN + BOX_NONCE_LEN + box.length);
|
|
136
|
-
bundle.set(ephemeral.publicKey, 0);
|
|
137
|
-
bundle.set(nonce, EPH_PUB_LEN);
|
|
138
|
-
bundle.set(box, EPH_PUB_LEN + BOX_NONCE_LEN);
|
|
139
|
-
return toBase64(bundle);
|
|
140
|
-
};
|
|
141
|
-
var encryptRecord = (plaintext, sessionKey) => {
|
|
142
|
-
const nonce = nacl.randomBytes(SECRETBOX_NONCE_LEN);
|
|
143
|
-
const box = nacl.secretbox(utf8Encode(plaintext), nonce, sessionKey);
|
|
144
|
-
const bundle = new Uint8Array(SECRETBOX_NONCE_LEN + box.length);
|
|
145
|
-
bundle.set(nonce, 0);
|
|
146
|
-
bundle.set(box, SECRETBOX_NONCE_LEN);
|
|
147
|
-
return toBase64(bundle);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// src/transcript-capture.ts
|
|
151
|
-
var INTERNAL_TYPES = /* @__PURE__ */ new Set([
|
|
152
|
-
"file-history-snapshot",
|
|
153
|
-
"change",
|
|
154
|
-
"queue-operation",
|
|
155
|
-
"summary"
|
|
156
|
-
]);
|
|
157
|
-
var redactDeep = (value) => {
|
|
158
|
-
if (typeof value === "string") return redactSecrets(value);
|
|
159
|
-
if (Array.isArray(value)) return value.map(redactDeep);
|
|
160
|
-
if (value && typeof value === "object") {
|
|
161
|
-
const out = {};
|
|
162
|
-
for (const [key, inner] of Object.entries(value)) {
|
|
163
|
-
out[key] = redactDeep(inner);
|
|
164
|
-
}
|
|
165
|
-
return out;
|
|
166
|
-
}
|
|
167
|
-
return value;
|
|
168
|
-
};
|
|
169
|
-
var localIdFor = (parsed, rawLine) => typeof parsed.uuid === "string" && parsed.uuid.length > 0 ? parsed.uuid : createHash("sha256").update(rawLine).digest("hex").slice(0, 32);
|
|
170
|
-
var buildTranscriptRecords = (lines, sessionKey, seen = /* @__PURE__ */ new Set()) => {
|
|
171
|
-
const records = [];
|
|
172
|
-
const nextSeen = new Set(seen);
|
|
173
|
-
for (const line of lines) {
|
|
174
|
-
const trimmed = line.trim();
|
|
175
|
-
if (!trimmed) continue;
|
|
176
|
-
let parsed;
|
|
177
|
-
try {
|
|
178
|
-
parsed = JSON.parse(trimmed);
|
|
179
|
-
} catch {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
183
|
-
if (INTERNAL_TYPES.has(type)) continue;
|
|
184
|
-
const localId = localIdFor(parsed, trimmed);
|
|
185
|
-
if (nextSeen.has(localId)) continue;
|
|
186
|
-
nextSeen.add(localId);
|
|
187
|
-
const content = encryptRecord(JSON.stringify(redactDeep(parsed)), sessionKey);
|
|
188
|
-
records.push({ localId, content });
|
|
189
|
-
}
|
|
190
|
-
return { records, seen: nextSeen };
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// src/transcript-api.ts
|
|
194
|
-
var fetchAccountPublicKey = async (apiKey) => {
|
|
195
|
-
try {
|
|
196
|
-
const res = await fetch(`${getBaseUrl()}/api/agent/encryption-account`, {
|
|
197
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
198
|
-
signal: AbortSignal.timeout(5e3)
|
|
199
|
-
});
|
|
200
|
-
if (!res.ok) return null;
|
|
201
|
-
const data = await res.json();
|
|
202
|
-
return typeof data.publicKey === "string" && data.publicKey.length > 0 ? data.publicKey : null;
|
|
203
|
-
} catch {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
var uploadTranscriptRecords = async (apiKey, params) => {
|
|
208
|
-
try {
|
|
209
|
-
const res = await fetch(`${getBaseUrl()}/api/agent/transcript`, {
|
|
210
|
-
method: "POST",
|
|
211
|
-
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
212
|
-
body: JSON.stringify(params),
|
|
213
|
-
signal: AbortSignal.timeout(15e3)
|
|
214
|
-
});
|
|
215
|
-
return res.ok;
|
|
216
|
-
} catch {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// src/transcript-sync.ts
|
|
222
133
|
var transcriptsEnabled = () => process.env.PUSHARY_TRANSCRIPTS === "1";
|
|
223
134
|
var claudeTranscriptPath = (cwd, sessionId) => {
|
|
224
135
|
const projectId = resolve(cwd).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
@@ -229,9 +140,11 @@ var createSyncState = () => ({
|
|
|
229
140
|
sessionKey: null,
|
|
230
141
|
wrappedKey: null,
|
|
231
142
|
seen: /* @__PURE__ */ new Set(),
|
|
232
|
-
keyUploaded: false
|
|
143
|
+
keyUploaded: false,
|
|
144
|
+
keyRejected: false
|
|
233
145
|
});
|
|
234
146
|
var syncTick = async (lines, state, uploader, sessionId, machineId) => {
|
|
147
|
+
if (state.keyRejected) return state;
|
|
235
148
|
let sessionKey = state.sessionKey;
|
|
236
149
|
let wrappedKey = state.wrappedKey;
|
|
237
150
|
if (!sessionKey) {
|
|
@@ -242,9 +155,9 @@ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
|
|
|
242
155
|
}
|
|
243
156
|
const built = buildTranscriptRecords(lines, sessionKey, state.seen);
|
|
244
157
|
if (built.records.length === 0) {
|
|
245
|
-
return { sessionKey, wrappedKey, seen: built.seen
|
|
158
|
+
return { ...state, sessionKey, wrappedKey, seen: built.seen };
|
|
246
159
|
}
|
|
247
|
-
const
|
|
160
|
+
const result = await uploader.uploadRecords({
|
|
248
161
|
sessionId,
|
|
249
162
|
machineId,
|
|
250
163
|
dataEncryptionKey: state.keyUploaded ? void 0 : wrappedKey ?? void 0,
|
|
@@ -253,8 +166,9 @@ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
|
|
|
253
166
|
return {
|
|
254
167
|
sessionKey,
|
|
255
168
|
wrappedKey,
|
|
256
|
-
seen: ok ? built.seen : state.seen,
|
|
257
|
-
keyUploaded: state.keyUploaded ||
|
|
169
|
+
seen: result.ok ? built.seen : state.seen,
|
|
170
|
+
keyUploaded: state.keyUploaded || result.keyRegistered,
|
|
171
|
+
keyRejected: result.keyConflict
|
|
258
172
|
};
|
|
259
173
|
};
|
|
260
174
|
var readLines = (path) => {
|
|
@@ -348,7 +262,7 @@ var ensureClaudeSdk = async (log) => {
|
|
|
348
262
|
{ cwd: dir, stdio: "ignore", shell: process.platform === "win32" }
|
|
349
263
|
);
|
|
350
264
|
child.on("error", () => resolve2(false));
|
|
351
|
-
child.on("close", (
|
|
265
|
+
child.on("close", (code2) => resolve2(code2 === 0));
|
|
352
266
|
});
|
|
353
267
|
if (!installed) {
|
|
354
268
|
log?.(`[pushary] could not auto-install the SDK. Install it manually: npm i -g ${SDK_SPEC}`);
|
|
@@ -533,7 +447,7 @@ var createRemoteApprover = (deps) => {
|
|
|
533
447
|
try {
|
|
534
448
|
const sessionId = deps.getSessionId?.();
|
|
535
449
|
const modeState = await fetchModeState2(deps.apiKey, sessionId);
|
|
536
|
-
const policyConfig = await getPolicy2(deps.apiKey, modeState.policyVersion, "claude_code", true);
|
|
450
|
+
const policyConfig = await getPolicy2(deps.apiKey, modeState.policyVersion, "claude_code", true, sessionId);
|
|
537
451
|
const scopePath = modeState.scope ? scopePathFor(toolName, input, deps.cwd) : void 0;
|
|
538
452
|
const verdict = resolveGate({
|
|
539
453
|
modeState,
|
|
@@ -542,7 +456,8 @@ var createRemoteApprover = (deps) => {
|
|
|
542
456
|
toolInputs: [input],
|
|
543
457
|
scopePaths: scopePath ? [scopePath] : [],
|
|
544
458
|
cwd: deps.cwd,
|
|
545
|
-
repoKey: repoKeyFor(deps.cwd)
|
|
459
|
+
repoKey: repoKeyFor(deps.cwd),
|
|
460
|
+
sessionId
|
|
546
461
|
});
|
|
547
462
|
if (verdict.kind === "kill") return deny(KILL_REASON);
|
|
548
463
|
if (verdict.kind === "allow") return allow(input);
|
|
@@ -561,7 +476,8 @@ var createRemoteApprover = (deps) => {
|
|
|
561
476
|
sessionId,
|
|
562
477
|
machineId: deps.machineId,
|
|
563
478
|
toolName,
|
|
564
|
-
toolTarget
|
|
479
|
+
toolTarget,
|
|
480
|
+
repoKey: repoKeyFor(deps.cwd)
|
|
565
481
|
});
|
|
566
482
|
} catch {
|
|
567
483
|
return applyTimeout(policy, input);
|
|
@@ -1127,13 +1043,13 @@ var runLocalLeg = (session, deps = {}) => {
|
|
|
1127
1043
|
}
|
|
1128
1044
|
finish();
|
|
1129
1045
|
});
|
|
1130
|
-
child.on("exit", (
|
|
1046
|
+
child.on("exit", (code2, signal) => {
|
|
1131
1047
|
if (signal === "SIGTERM" && legAbort.signal.aborted) {
|
|
1132
1048
|
} else if (!exitReason) {
|
|
1133
1049
|
if (session.input.bufferedCount > 0) {
|
|
1134
1050
|
exitReason = "switch";
|
|
1135
|
-
} else if (typeof
|
|
1136
|
-
exitReason = { type: "exit", code };
|
|
1051
|
+
} else if (typeof code2 === "number") {
|
|
1052
|
+
exitReason = { type: "exit", code: code2 };
|
|
1137
1053
|
} else if (signal) {
|
|
1138
1054
|
exitReason = { type: "exit", code: SIGNAL_EXIT_BASE + (SIGNAL_NUMBERS2[signal] ?? 0) };
|
|
1139
1055
|
} else {
|
|
@@ -1294,6 +1210,7 @@ var driveModeLoop = async (deps) => {
|
|
|
1294
1210
|
var INPUT_QUEUE_CAP = 32;
|
|
1295
1211
|
var MIN_MODE_DWELL_MS = 300;
|
|
1296
1212
|
var RELAY_FALLBACK_POLL_MS = 6e4;
|
|
1213
|
+
var PRESENCE_HEARTBEAT_MS = 6e4;
|
|
1297
1214
|
var SESSION_FLAG_RE = /^(-r|--resume|-c|--continue|--session-id)(=.*)?$/;
|
|
1298
1215
|
var extractAssistantText = (message) => {
|
|
1299
1216
|
const content = message.message?.content;
|
|
@@ -1378,28 +1295,53 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1378
1295
|
});
|
|
1379
1296
|
relay.start();
|
|
1380
1297
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1298
|
+
let ownership;
|
|
1299
|
+
let ownershipSessionId;
|
|
1300
|
+
const ensureOwnership = async () => {
|
|
1301
|
+
if (!sessionId || ownershipSessionId === sessionId) return ownership?.ready;
|
|
1302
|
+
ownership?.stop();
|
|
1303
|
+
ownershipSessionId = sessionId;
|
|
1304
|
+
ownership = startProcessOwnership({
|
|
1305
|
+
sessionId,
|
|
1306
|
+
pid: process.pid,
|
|
1307
|
+
...process.env.PUSHARY_SPAWN_ID ? { spawnId: process.env.PUSHARY_SPAWN_ID } : {}
|
|
1388
1308
|
});
|
|
1309
|
+
await ownership.ready;
|
|
1310
|
+
};
|
|
1311
|
+
const announcePresence = async (event, action) => {
|
|
1312
|
+
if (!sessionId) return Promise.resolve();
|
|
1313
|
+
try {
|
|
1314
|
+
await ensureOwnership();
|
|
1315
|
+
await reportEvent(
|
|
1316
|
+
{ event, agentType: "claude_code", agentName, action, sessionId, machineId, pid: process.pid },
|
|
1317
|
+
{ maxAttempts: event === "session_start" ? 4 : 2, timeoutMs: 4e3, requireOk: event === "session_start" }
|
|
1318
|
+
);
|
|
1319
|
+
} catch {
|
|
1320
|
+
}
|
|
1389
1321
|
};
|
|
1390
1322
|
let stopTranscript;
|
|
1323
|
+
let presenceHeartbeat;
|
|
1391
1324
|
let transcriptStarted = false;
|
|
1392
1325
|
const ensureTranscriptSync = () => {
|
|
1393
1326
|
if (transcriptStarted || !transcriptsEnabled() || !sessionId) return;
|
|
1394
1327
|
transcriptStarted = true;
|
|
1395
1328
|
stopTranscript = startTranscriptSync({ apiKey, sessionId, cwd: process.cwd(), machineId });
|
|
1396
1329
|
};
|
|
1330
|
+
const ensurePresenceHeartbeat = () => {
|
|
1331
|
+
if (presenceHeartbeat || !sessionId) return;
|
|
1332
|
+
presenceHeartbeat = setInterval(
|
|
1333
|
+
() => void announcePresence("heartbeat", "Session online"),
|
|
1334
|
+
PRESENCE_HEARTBEAT_MS
|
|
1335
|
+
);
|
|
1336
|
+
presenceHeartbeat.unref();
|
|
1337
|
+
};
|
|
1397
1338
|
const setSessionId = (id) => {
|
|
1398
1339
|
const isNew = sessionId !== id;
|
|
1399
1340
|
sessionId = id;
|
|
1400
1341
|
sessionCreated = true;
|
|
1401
1342
|
relay?.reannounce();
|
|
1402
1343
|
if (isNew) void announcePresence("session_start", "Remote session ready \u2014 reachable from your phone");
|
|
1344
|
+
ensurePresenceHeartbeat();
|
|
1403
1345
|
ensureTranscriptSync();
|
|
1404
1346
|
};
|
|
1405
1347
|
const onPhoneCommand = (command) => {
|
|
@@ -1483,6 +1425,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1483
1425
|
"[pushary] reachable from your phone \u2014 send an instruction from the app when this goes idle (Ctrl-] keeps the terminal).\n"
|
|
1484
1426
|
);
|
|
1485
1427
|
void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
|
|
1428
|
+
ensurePresenceHeartbeat();
|
|
1486
1429
|
ensureTranscriptSync();
|
|
1487
1430
|
}
|
|
1488
1431
|
if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
|
|
@@ -1513,11 +1456,13 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1513
1456
|
process.removeListener("exit", restoreStdin);
|
|
1514
1457
|
relay?.stop();
|
|
1515
1458
|
poller.stop();
|
|
1459
|
+
if (presenceHeartbeat) clearInterval(presenceHeartbeat);
|
|
1516
1460
|
stopTranscript?.();
|
|
1517
1461
|
approver.teardown();
|
|
1518
1462
|
input.close();
|
|
1519
1463
|
stdin.dispose();
|
|
1520
1464
|
await announcePresence("session_closed", "Session ended");
|
|
1465
|
+
ownership?.stop();
|
|
1521
1466
|
if (totalCostUsd > 0) {
|
|
1522
1467
|
process.stderr.write(`[pushary] session cost: $${totalCostUsd.toFixed(4)}
|
|
1523
1468
|
`);
|
|
@@ -1551,10 +1496,15 @@ var runClaudeWrapper = async (args2) => {
|
|
|
1551
1496
|
|
|
1552
1497
|
// bin/pushary-claude.ts
|
|
1553
1498
|
var args = resolveClaudeArgs(process.argv);
|
|
1554
|
-
|
|
1499
|
+
var spawnId = process.env.PUSHARY_SPAWN_ID?.trim() || void 0;
|
|
1500
|
+
var code = await withSpawnProcessOwnership(spawnId, async () => {
|
|
1501
|
+
await ensureDaemonRunning();
|
|
1502
|
+
return runClaudeWrapper(args);
|
|
1503
|
+
}).catch((err) => {
|
|
1555
1504
|
process.stderr.write(
|
|
1556
1505
|
`[pushary] wrapper error: ${err instanceof Error ? err.message : String(err)}
|
|
1557
1506
|
`
|
|
1558
1507
|
);
|
|
1559
|
-
|
|
1508
|
+
return 1;
|
|
1560
1509
|
});
|
|
1510
|
+
process.exit(code);
|
|
@@ -4,14 +4,17 @@ import {
|
|
|
4
4
|
shortenHome,
|
|
5
5
|
unregisterPluginLocation,
|
|
6
6
|
vscodeSettingsTargets
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-UXWEE3QI.js";
|
|
8
8
|
import {
|
|
9
9
|
removeGeminiSettings
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-DOXSWT4Q.js";
|
|
11
11
|
import {
|
|
12
12
|
removeClaudeMcpServers,
|
|
13
13
|
removePusharySettings
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-UKSQWTBH.js";
|
|
15
|
+
import {
|
|
16
|
+
execNpm
|
|
17
|
+
} from "../chunk-VT4IVERX.js";
|
|
15
18
|
import {
|
|
16
19
|
claudeJson,
|
|
17
20
|
claudeSettings,
|
|
@@ -29,25 +32,23 @@ import {
|
|
|
29
32
|
pusharyDir,
|
|
30
33
|
removeCodexHooks,
|
|
31
34
|
vscodePluginDir
|
|
32
|
-
} from "../chunk-
|
|
35
|
+
} from "../chunk-SEAHGHCE.js";
|
|
33
36
|
import {
|
|
34
37
|
rejectUnknownFlags
|
|
35
38
|
} from "../chunk-GMXKITVA.js";
|
|
39
|
+
import {
|
|
40
|
+
exitOnHelpFlag
|
|
41
|
+
} from "../chunk-J4KFVOCQ.js";
|
|
42
|
+
import "../chunk-MGJBC6DD.js";
|
|
36
43
|
import {
|
|
37
44
|
removeClaudeAlias
|
|
38
45
|
} from "../chunk-BC3VCZ3E.js";
|
|
46
|
+
import "../chunk-DINDL2CF.js";
|
|
39
47
|
import "../chunk-7QLSKOSU.js";
|
|
40
|
-
import "../chunk-YWRJGYUH.js";
|
|
41
|
-
import {
|
|
42
|
-
execNpm
|
|
43
|
-
} from "../chunk-MUEW424A.js";
|
|
44
|
-
import {
|
|
45
|
-
exitOnHelpFlag
|
|
46
|
-
} from "../chunk-RRZZ7TFJ.js";
|
|
47
|
-
import "../chunk-ZUKH2NPJ.js";
|
|
48
48
|
import {
|
|
49
49
|
EXIT
|
|
50
50
|
} from "../chunk-KZERVKTD.js";
|
|
51
|
+
import "../chunk-MQMPG5X4.js";
|
|
51
52
|
|
|
52
53
|
// bin/pushary-clean.ts
|
|
53
54
|
import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|