@pushary/agent-hooks 0.89.13 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/data/SKILL.md +1 -1
- package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
- package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +8 -8
- package/dist/bin/pushary-claude.js +72 -21
- package/dist/bin/pushary-clean.js +16 -42
- package/dist/bin/pushary-codex-bridge.js +37 -12
- package/dist/bin/pushary-codex-hook.js +45 -12
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +11 -10
- package/dist/bin/pushary-daemon.js +186 -62
- package/dist/bin/pushary-disconnect.js +8 -8
- package/dist/bin/pushary-doctor.js +108 -25
- package/dist/bin/pushary-elicitation-hook.js +4 -4
- package/dist/bin/pushary-gemini-bridge.js +33 -11
- package/dist/bin/pushary-gemini-hook.js +21 -10
- package/dist/bin/pushary-hook.js +13 -13
- package/dist/bin/pushary-login.js +18 -7
- package/dist/bin/pushary-logout.js +6 -6
- package/dist/bin/pushary-mode.js +28 -21
- package/dist/bin/pushary-notification-hook.js +7 -7
- package/dist/bin/pushary-opencode-hook.js +8 -8
- package/dist/bin/pushary-permission-denied-hook.js +11 -11
- package/dist/bin/pushary-permission-hook.js +11 -11
- package/dist/bin/pushary-post-hook.js +14 -8
- package/dist/bin/pushary-prompt-hook.js +7 -7
- package/dist/bin/pushary-session-end-hook.js +7 -7
- package/dist/bin/pushary-session-start-hook.js +7 -7
- package/dist/bin/pushary-setup.js +168 -45
- package/dist/bin/pushary-stats.js +4 -4
- package/dist/bin/pushary-status.js +11 -10
- package/dist/bin/pushary-stop-hook.js +14 -8
- package/dist/bin/pushary-stopfailure-hook.js +7 -7
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-transcripts.js +6 -6
- package/dist/bin/pushary-upgrade.js +15 -15
- package/dist/bin/pushary-wait.js +5 -5
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-B2HND22S.js → chunk-2LAHXHBR.js} +1 -1
- package/dist/{chunk-GMJDQDXU.js → chunk-43GMYSS4.js} +1 -0
- package/dist/{chunk-JTBDBKHU.js → chunk-54ELC7D7.js} +1 -1
- package/dist/{chunk-C56VHSXU.js → chunk-5NAZ7SFS.js} +1 -1
- package/dist/{chunk-4IPSMEGT.js → chunk-7FE2KABY.js} +12 -2
- package/dist/{chunk-RAEPAUNJ.js → chunk-7TPQEWUZ.js} +1 -1
- package/dist/{chunk-ZO43JTT3.js → chunk-AJYV3VGU.js} +1 -1
- package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
- package/dist/{chunk-5MNTWAAJ.js → chunk-B7UZHEBL.js} +29 -22
- package/dist/{chunk-MNTZKFZI.js → chunk-BQBEH6TV.js} +5 -5
- package/dist/{chunk-WHDSJ6PD.js → chunk-BVBEURWJ.js} +1 -1
- package/dist/{chunk-ZWEKONU6.js → chunk-CELX75MW.js} +1 -1
- package/dist/{chunk-ZP3SNUP7.js → chunk-CNPAT3HL.js} +3 -2
- package/dist/{chunk-YXT55PYF.js → chunk-DYP6SMOR.js} +1 -1
- package/dist/chunk-DZDI62LY.js +145 -0
- package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
- package/dist/{chunk-7JZCADBR.js → chunk-F2ICEWZL.js} +101 -19
- package/dist/{chunk-VASZ6TWW.js → chunk-FQL5E6NG.js} +2 -2
- package/dist/{chunk-JHJHCMAF.js → chunk-FXEVRLHC.js} +20 -2
- package/dist/{chunk-JVLB5CAJ.js → chunk-GL3FFM7A.js} +7 -4
- package/dist/{chunk-5YEF55AX.js → chunk-H2QZRQR2.js} +1 -1
- package/dist/{chunk-S4BLNILH.js → chunk-I463AEG3.js} +1 -1
- package/dist/{chunk-ANMHIZZY.js → chunk-JW4KO5AF.js} +1 -1
- package/dist/{chunk-3YGGEDJK.js → chunk-KUZWXPUS.js} +3 -2
- package/dist/{chunk-N4QJ3CLX.js → chunk-L4E2BIJ2.js} +80 -7
- package/dist/{chunk-NBDU3R3G.js → chunk-OSALSIUF.js} +1 -1
- package/dist/{chunk-KNLKHJZR.js → chunk-PYA4FLKI.js} +32 -21
- package/dist/chunk-QCGNGEGE.js +15 -0
- package/dist/chunk-S6LYP7QA.js +127 -0
- package/dist/{chunk-OX35BSPV.js → chunk-TDZFPMSZ.js} +70 -11
- package/dist/{chunk-M3UABYRT.js → chunk-UZNKVRLE.js} +1 -1
- package/dist/{chunk-FOGW6O2Y.js → chunk-VHWCVD42.js} +1 -1
- package/dist/{chunk-QINYXLA5.js → chunk-VNMKIJZB.js} +15 -2
- package/dist/{chunk-CBGGUEKE.js → chunk-Y6BWPERV.js} +90 -7
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +11 -11
- package/package.json +1 -1
- package/dist/chunk-MSTHT2M7.js +0 -72
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.90.0
|
|
4
|
+
|
|
5
|
+
### A hook that needs an answer is allowed to wait for one
|
|
6
|
+
|
|
7
|
+
Claude Code hooks are now registered at the 600 second budget Claude itself
|
|
8
|
+
allows, so a policy that waits two minutes for a phone approval is no longer cut
|
|
9
|
+
off at 120 seconds and turned into a denial the user never saw. Anything a hook
|
|
10
|
+
does that is not a decision keeps its short timeout.
|
|
11
|
+
|
|
12
|
+
A phone card is now withdrawn when the tool it guarded completes or the agent's
|
|
13
|
+
own client denies it, so an approval cannot arrive for something that already
|
|
14
|
+
finished.
|
|
15
|
+
|
|
16
|
+
### The prompts that never reached the phone
|
|
17
|
+
|
|
18
|
+
Codex permission requests are no longer filtered by a shell matcher, so a browser
|
|
19
|
+
use request, a managed network request and an MCP tool request all reach the
|
|
20
|
+
phone the same way a shell escalation does. Codex in bypass mode now decides
|
|
21
|
+
before the tool runs rather than after it, which is what push-first has always
|
|
22
|
+
meant everywhere else.
|
|
23
|
+
|
|
24
|
+
Gemini reports its turn boundary through AfterAgent and its teardown through
|
|
25
|
+
SessionEnd, so a finished Gemini turn is a finished turn rather than a session
|
|
26
|
+
that stayed open.
|
|
27
|
+
|
|
28
|
+
A finished subagent now ends its own session instead of ending its parent's, and
|
|
29
|
+
appears under the session that started it. Context compaction is reported as
|
|
30
|
+
itself rather than as a tool that ran.
|
|
31
|
+
|
|
32
|
+
Pushary's own tools are never gated by Pushary, on the server exactly as in the
|
|
33
|
+
hook, so asking the user a question can no longer wait on approval to ask it.
|
|
34
|
+
|
|
35
|
+
### Sessions end, and a daemon that cannot log in says so
|
|
36
|
+
|
|
37
|
+
A session that goes quiet is closed rather than left standing forever, and the
|
|
38
|
+
daemon reaps the sessions it started. `pushary daemon status` lists what it is
|
|
39
|
+
holding, how old each one is, and how much memory it is using.
|
|
40
|
+
|
|
41
|
+
A daemon whose key is rejected now records that and stops, instead of restarting
|
|
42
|
+
into the same rejection until something notices. `pushary doctor` says the daemon
|
|
43
|
+
needs a login, and logging in clears it.
|
|
44
|
+
|
|
45
|
+
### Setup and doctor tell the truth about this machine
|
|
46
|
+
|
|
47
|
+
`pushary doctor` names the other tools holding the same agent hooks and how long
|
|
48
|
+
each of them waits, checks that the bridge the Mac app installed can be found,
|
|
49
|
+
and reports which Node the hooks will actually run under.
|
|
50
|
+
|
|
51
|
+
`pushary setup` gained `--verify none` for an unattended install, prints what it
|
|
52
|
+
would also do during a dry run, offers a way out of pairing, and can take over
|
|
53
|
+
hooks another tool is holding with `--take-over-hooks`. `pushary mode` now sets a
|
|
54
|
+
site's mode permanently unless told how long to keep it.
|
|
55
|
+
|
|
56
|
+
### Machine identity and clean
|
|
57
|
+
|
|
58
|
+
`pushary clean` leaves the machine identity shared with the Mac app intact, keeps
|
|
59
|
+
one entry rather than two, and treats the identity file as the CLI's to own and
|
|
60
|
+
the app's to read. Transcript sync seals its recovery ledger and admits a gap
|
|
61
|
+
rather than syncing bookkeeping around it.
|
|
62
|
+
|
|
3
63
|
## 0.89.13
|
|
4
64
|
|
|
5
65
|
### Synced transcripts survive large turns and restarts safely
|
package/data/SKILL.md
CHANGED
|
@@ -348,7 +348,7 @@ else:
|
|
|
348
348
|
|
|
349
349
|
If the user answers in chat before the push response arrives, call `cancel_question` before acting. If it returns `handoffAction: "stop"`, stop. Otherwise, if it returns false, poll once for 1 second and honor any phone answer that won the race.
|
|
350
350
|
|
|
351
|
-
**A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it).
|
|
351
|
+
**A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it). The four modes are "When I'm out" (`push_first`, the default), "Every time" (`push_only`), "Updates" (`notify_only`) and "Terminal" (`terminal_only`). In When I'm out, ask_user blocks for the push-first window (45 seconds by default) and the phone is only asked when the user is away from their terminal or their Mac; in Every time it blocks for the policy timeout and the phone is always asked; in Updates it returns immediately with `answered: false` after telling the phone, because the decision belongs in the current client; in Terminal nothing reaches the phone and it also returns immediately with `answered: false`. Always check `answered` rather than assuming the call blocked, and pass `timeoutMs` only when you need a shorter wait than the site policy.
|
|
352
352
|
|
|
353
353
|
## Identifying Your Agent
|
|
354
354
|
|
|
@@ -341,7 +341,7 @@ else:
|
|
|
341
341
|
|
|
342
342
|
If the user answers in chat before the push response arrives, call `cancel_question` before acting. If it returns `handoffAction: "stop"`, stop. Otherwise, if it returns false, poll once for 1 second and honor any phone answer that won the race.
|
|
343
343
|
|
|
344
|
-
**A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it).
|
|
344
|
+
**A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it). The four modes are "When I'm out" (`push_first`, the default), "Every time" (`push_only`), "Updates" (`notify_only`) and "Terminal" (`terminal_only`). In When I'm out, ask_user blocks for the push-first window (45 seconds by default) and the phone is only asked when the user is away from their terminal or their Mac; in Every time it blocks for the policy timeout and the phone is always asked; in Updates it returns immediately with `answered: false` after telling the phone, because the decision belongs in the current client; in Terminal nothing reaches the phone and it also returns immediately with `answered: false`. Always check `answered` rather than assuming the call blocked, and pass `timeoutMs` only when you need a shorter wait than the site policy.
|
|
345
345
|
|
|
346
346
|
## Identifying Your Agent
|
|
347
347
|
|
|
@@ -341,7 +341,7 @@ else:
|
|
|
341
341
|
|
|
342
342
|
If the user answers in chat before the push response arrives, call `cancel_question` before acting. If it returns `handoffAction: "stop"`, stop. Otherwise, if it returns false, poll once for 1 second and honor any phone answer that won the race.
|
|
343
343
|
|
|
344
|
-
**A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it).
|
|
344
|
+
**A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it). The four modes are "When I'm out" (`push_first`, the default), "Every time" (`push_only`), "Updates" (`notify_only`) and "Terminal" (`terminal_only`). In When I'm out, ask_user blocks for the push-first window (45 seconds by default) and the phone is only asked when the user is away from their terminal or their Mac; in Every time it blocks for the policy timeout and the phone is always asked; in Updates it returns immediately with `answered: false` after telling the phone, because the decision belongs in the current client; in Terminal nothing reaches the phone and it also returns immediately with `answered: false`. Always check `answered` rather than assuming the call blocked, and pass `timeoutMs` only when you need a shorter wait than the site policy.
|
|
345
345
|
|
|
346
346
|
## Identifying Your Agent
|
|
347
347
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleBell
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-5NAZ7SFS.js";
|
|
5
5
|
import {
|
|
6
6
|
readHookInput
|
|
7
7
|
} from "../chunk-6CUUA7HO.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-OSALSIUF.js";
|
|
9
9
|
import "../chunk-BC3VCZ3E.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-JW4KO5AF.js";
|
|
11
11
|
import "../chunk-4LL6LG5X.js";
|
|
12
12
|
import "../chunk-2UMNXADU.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-VNMKIJZB.js";
|
|
14
14
|
|
|
15
15
|
// bin/pushary-bell-hook.ts
|
|
16
16
|
var main = async () => {
|
package/dist/bin/pushary-bell.js
CHANGED
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
liveAgentCount,
|
|
6
6
|
ring,
|
|
7
7
|
upgradeThreshold
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-5NAZ7SFS.js";
|
|
9
9
|
import {
|
|
10
10
|
guardBinary
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-EZIEXHYM.js";
|
|
12
12
|
import "../chunk-L4ZCFMWW.js";
|
|
13
13
|
import {
|
|
14
14
|
claudeSettings
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-KUZWXPUS.js";
|
|
16
16
|
import {
|
|
17
17
|
createIo
|
|
18
18
|
} from "../chunk-5RUIFDTP.js";
|
|
@@ -21,22 +21,22 @@ import {
|
|
|
21
21
|
} from "../chunk-GMXKITVA.js";
|
|
22
22
|
import {
|
|
23
23
|
exitOnHelpFlag
|
|
24
|
-
} from "../chunk-
|
|
25
|
-
import "../chunk-
|
|
24
|
+
} from "../chunk-BVBEURWJ.js";
|
|
25
|
+
import "../chunk-43GMYSS4.js";
|
|
26
26
|
import "../chunk-DINDL2CF.js";
|
|
27
|
-
import "../chunk-
|
|
27
|
+
import "../chunk-OSALSIUF.js";
|
|
28
28
|
import "../chunk-BC3VCZ3E.js";
|
|
29
29
|
import "../chunk-7QLSKOSU.js";
|
|
30
30
|
import {
|
|
31
31
|
EXIT
|
|
32
32
|
} from "../chunk-KZERVKTD.js";
|
|
33
|
-
import "../chunk-
|
|
33
|
+
import "../chunk-JW4KO5AF.js";
|
|
34
34
|
import {
|
|
35
35
|
readJsonSafe,
|
|
36
36
|
writeJsonAtomic
|
|
37
37
|
} from "../chunk-4LL6LG5X.js";
|
|
38
38
|
import "../chunk-2UMNXADU.js";
|
|
39
|
-
import "../chunk-
|
|
39
|
+
import "../chunk-VNMKIJZB.js";
|
|
40
40
|
|
|
41
41
|
// src/bell/install.ts
|
|
42
42
|
import { join } from "path";
|
|
@@ -7,24 +7,26 @@ import {
|
|
|
7
7
|
} from "../chunk-YKLCWWEI.js";
|
|
8
8
|
import {
|
|
9
9
|
ensureDaemonRunning
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-F2ICEWZL.js";
|
|
11
11
|
import {
|
|
12
12
|
buildTranscriptRecords,
|
|
13
13
|
decodePublicKey,
|
|
14
14
|
generateSessionKey,
|
|
15
15
|
wrapSessionKey
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-7FE2KABY.js";
|
|
17
17
|
import {
|
|
18
18
|
fetchTranscriptRecipients,
|
|
19
19
|
uploadTranscriptRecords
|
|
20
20
|
} from "../chunk-QZCFVWA7.js";
|
|
21
21
|
import {
|
|
22
22
|
transcriptsEnabled
|
|
23
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-UZNKVRLE.js";
|
|
24
24
|
import {
|
|
25
|
+
SESSION_IDLE_TIMEOUT_MS,
|
|
26
|
+
describeIdleWindow,
|
|
25
27
|
startProcessOwnership,
|
|
26
28
|
withSpawnProcessOwnership
|
|
27
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-Y6BWPERV.js";
|
|
28
30
|
import {
|
|
29
31
|
needsShell,
|
|
30
32
|
spawnClaude
|
|
@@ -36,9 +38,10 @@ import {
|
|
|
36
38
|
import {
|
|
37
39
|
fetchModeState,
|
|
38
40
|
getPolicy,
|
|
41
|
+
phoneDrivenEnv,
|
|
39
42
|
repoKeyFor,
|
|
40
43
|
reportEvent
|
|
41
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-TDZFPMSZ.js";
|
|
42
45
|
import {
|
|
43
46
|
DEFAULT_SESSION,
|
|
44
47
|
askUser,
|
|
@@ -50,25 +53,25 @@ import {
|
|
|
50
53
|
removePendingCommand,
|
|
51
54
|
savePendingCommand,
|
|
52
55
|
waitForAnswer
|
|
53
|
-
} from "../chunk-
|
|
56
|
+
} from "../chunk-FXEVRLHC.js";
|
|
54
57
|
import {
|
|
55
58
|
UNNAMED_ACTION,
|
|
56
59
|
deriveToolTarget,
|
|
57
60
|
describeToolCall,
|
|
58
61
|
scopePathFor
|
|
59
|
-
} from "../chunk-
|
|
62
|
+
} from "../chunk-L4E2BIJ2.js";
|
|
60
63
|
import {
|
|
61
64
|
getMachineId
|
|
62
|
-
} from "../chunk-
|
|
63
|
-
import "../chunk-
|
|
65
|
+
} from "../chunk-54ELC7D7.js";
|
|
66
|
+
import "../chunk-KUZWXPUS.js";
|
|
64
67
|
import "../chunk-DINDL2CF.js";
|
|
65
|
-
import "../chunk-
|
|
68
|
+
import "../chunk-OSALSIUF.js";
|
|
66
69
|
import "../chunk-BC3VCZ3E.js";
|
|
67
70
|
import "../chunk-BSZYIAZL.js";
|
|
68
71
|
import "../chunk-SAF6HGAA.js";
|
|
69
72
|
import "../chunk-7QLSKOSU.js";
|
|
70
73
|
import "../chunk-KZERVKTD.js";
|
|
71
|
-
import "../chunk-
|
|
74
|
+
import "../chunk-JW4KO5AF.js";
|
|
72
75
|
import "../chunk-4LL6LG5X.js";
|
|
73
76
|
import {
|
|
74
77
|
getApiKey,
|
|
@@ -77,7 +80,7 @@ import {
|
|
|
77
80
|
import {
|
|
78
81
|
KILL_REASON,
|
|
79
82
|
resolveGate
|
|
80
|
-
} from "../chunk-
|
|
83
|
+
} from "../chunk-VNMKIJZB.js";
|
|
81
84
|
|
|
82
85
|
// src/wrapper/claudeBinary.ts
|
|
83
86
|
import { statSync } from "fs";
|
|
@@ -1367,14 +1370,30 @@ var runLocalLeg = (session, deps = {}) => {
|
|
|
1367
1370
|
|
|
1368
1371
|
// src/wrapper/remoteLoop.ts
|
|
1369
1372
|
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
1373
|
+
var scheduleWithTimer = (fire, delayMs) => {
|
|
1374
|
+
const timer = setTimeout(fire, delayMs);
|
|
1375
|
+
timer.unref?.();
|
|
1376
|
+
return () => clearTimeout(timer);
|
|
1377
|
+
};
|
|
1378
|
+
var isTurnActivity = (message) => message.type !== "system" && message.type !== "result";
|
|
1379
|
+
var isInit = (message) => message.type === "system" && message.subtype === "init";
|
|
1370
1380
|
var DEFAULT_MAX_RESTARTS = 5;
|
|
1371
1381
|
var runRemoteLoop = async (deps) => {
|
|
1372
1382
|
const maxRestarts = deps.maxRestarts ?? DEFAULT_MAX_RESTARTS;
|
|
1373
1383
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1374
1384
|
const backoff = deps.backoffMs ?? ((attempt) => Math.min(1e3 * 2 ** attempt, 15e3));
|
|
1385
|
+
const idleTimeoutMs = deps.idleTimeoutMs ?? SESSION_IDLE_TIMEOUT_MS;
|
|
1386
|
+
const scheduleIdle = deps.scheduleIdle ?? scheduleWithTimer;
|
|
1375
1387
|
let resumeId = deps.initialResume;
|
|
1376
1388
|
let totalCost = 0;
|
|
1377
1389
|
let restarts = 0;
|
|
1390
|
+
let idleExpired = false;
|
|
1391
|
+
let cancelIdle;
|
|
1392
|
+
const disarmIdle = () => {
|
|
1393
|
+
cancelIdle?.();
|
|
1394
|
+
cancelIdle = void 0;
|
|
1395
|
+
};
|
|
1396
|
+
const ended = (exitCode, endedBy) => ({ exitCode, totalCostUsd: totalCost, endedBy });
|
|
1378
1397
|
while (!deps.signal.aborted) {
|
|
1379
1398
|
deps.input.detachConsumer();
|
|
1380
1399
|
let query;
|
|
@@ -1392,7 +1411,7 @@ var runRemoteLoop = async (deps) => {
|
|
|
1392
1411
|
});
|
|
1393
1412
|
} catch (err) {
|
|
1394
1413
|
deps.log?.(`[pushary] remote mode: query failed to start: ${errMsg(err)}`);
|
|
1395
|
-
return
|
|
1414
|
+
return ended(1, "failed");
|
|
1396
1415
|
}
|
|
1397
1416
|
const onAbort = () => {
|
|
1398
1417
|
deps.input.close();
|
|
@@ -1401,10 +1420,24 @@ var runRemoteLoop = async (deps) => {
|
|
|
1401
1420
|
};
|
|
1402
1421
|
if (deps.signal.aborted) onAbort();
|
|
1403
1422
|
else deps.signal.addEventListener("abort", onAbort, { once: true });
|
|
1423
|
+
let turnOpen = false;
|
|
1424
|
+
const stillResting = (deliveredAtRest) => !turnOpen && deps.input.bufferedCount === 0 && deps.input.stats.delivered === deliveredAtRest;
|
|
1425
|
+
const armIdle = () => {
|
|
1426
|
+
disarmIdle();
|
|
1427
|
+
const deliveredAtRest = deps.input.stats.delivered;
|
|
1428
|
+
cancelIdle = scheduleIdle(() => {
|
|
1429
|
+
cancelIdle = void 0;
|
|
1430
|
+
if (!stillResting(deliveredAtRest)) return;
|
|
1431
|
+
idleExpired = true;
|
|
1432
|
+
deps.log?.(`[pushary] remote mode: no phone instruction for ${describeIdleWindow(idleTimeoutMs)}, ending the session`);
|
|
1433
|
+
onAbort();
|
|
1434
|
+
}, idleTimeoutMs);
|
|
1435
|
+
};
|
|
1436
|
+
const restsAfter = (message) => deps.input.bufferedCount === 0 && (message.type === "result" || isInit(message) && !turnOpen);
|
|
1404
1437
|
try {
|
|
1405
1438
|
for await (const message of query) {
|
|
1406
1439
|
deps.onMessage?.(message);
|
|
1407
|
-
if (message
|
|
1440
|
+
if (isInit(message) && typeof message.session_id === "string") {
|
|
1408
1441
|
resumeId = message.session_id;
|
|
1409
1442
|
deps.onSessionId?.(resumeId);
|
|
1410
1443
|
}
|
|
@@ -1412,16 +1445,23 @@ var runRemoteLoop = async (deps) => {
|
|
|
1412
1445
|
totalCost = message.total_cost_usd;
|
|
1413
1446
|
deps.onCost?.(totalCost);
|
|
1414
1447
|
}
|
|
1448
|
+
if (message.type === "result") turnOpen = false;
|
|
1449
|
+
else if (isTurnActivity(message)) turnOpen = true;
|
|
1450
|
+
if (restsAfter(message)) armIdle();
|
|
1415
1451
|
}
|
|
1452
|
+
disarmIdle();
|
|
1416
1453
|
deps.signal.removeEventListener("abort", onAbort);
|
|
1417
|
-
|
|
1454
|
+
if (idleExpired) return ended(0, "idle");
|
|
1455
|
+
return deps.signal.aborted ? ended(130, "aborted") : ended(0, "input-closed");
|
|
1418
1456
|
} catch (err) {
|
|
1457
|
+
disarmIdle();
|
|
1419
1458
|
deps.signal.removeEventListener("abort", onAbort);
|
|
1420
|
-
if (
|
|
1459
|
+
if (idleExpired) return ended(0, "idle");
|
|
1460
|
+
if (deps.signal.aborted) return ended(130, "aborted");
|
|
1421
1461
|
restarts++;
|
|
1422
1462
|
if (restarts > maxRestarts) {
|
|
1423
1463
|
deps.log?.(`[pushary] remote mode: too many restarts, giving up: ${errMsg(err)}`);
|
|
1424
|
-
return
|
|
1464
|
+
return ended(1, "failed");
|
|
1425
1465
|
}
|
|
1426
1466
|
deps.log?.(
|
|
1427
1467
|
`[pushary] remote mode: transient error, resuming session (attempt ${restarts}/${maxRestarts}): ${errMsg(err)}`
|
|
@@ -1429,15 +1469,18 @@ var runRemoteLoop = async (deps) => {
|
|
|
1429
1469
|
await sleep(backoff(restarts));
|
|
1430
1470
|
}
|
|
1431
1471
|
}
|
|
1432
|
-
return
|
|
1472
|
+
return ended(130, "aborted");
|
|
1433
1473
|
};
|
|
1434
1474
|
|
|
1435
1475
|
// src/wrapper/remoteLeg.ts
|
|
1436
1476
|
var CTRL_RIGHT_BRACKET = "";
|
|
1437
1477
|
var CTRL_C = "";
|
|
1438
|
-
var
|
|
1478
|
+
var terminalAttended = () => Boolean(process.stdin.isTTY);
|
|
1479
|
+
var runRemoteLeg = async (session, sdk, deps = {}) => {
|
|
1439
1480
|
const legAbort = new AbortController();
|
|
1481
|
+
const attended = deps.attended ?? terminalAttended();
|
|
1440
1482
|
let switchBack = false;
|
|
1483
|
+
let idleEnded = false;
|
|
1441
1484
|
const control = {
|
|
1442
1485
|
// The running query already drains the shared input queue, so a freshly pushed
|
|
1443
1486
|
// phone command is delivered without any action here.
|
|
@@ -1472,7 +1515,7 @@ var runRemoteLeg = async (session, sdk) => {
|
|
|
1472
1515
|
permissionMode: session.permissionMode,
|
|
1473
1516
|
// The SDK REPLACES the subprocess env; spread process.env and mark the
|
|
1474
1517
|
// recursion guard so a wrapper-spawned claude cannot re-enter the wrapper.
|
|
1475
|
-
env: { ...process.env, [WRAPPER_ACTIVE_ENV]: "1" },
|
|
1518
|
+
env: phoneDrivenEnv({ ...process.env, [WRAPPER_ACTIVE_ENV]: "1" }),
|
|
1476
1519
|
cwd: process.cwd(),
|
|
1477
1520
|
// Run the user's OWN claude (auth + version parity); Windows .cmd shims keep
|
|
1478
1521
|
// the SDK's bundled binary since the SDK cannot spawn a shim directly.
|
|
@@ -1480,13 +1523,20 @@ var runRemoteLeg = async (session, sdk) => {
|
|
|
1480
1523
|
onSessionId: (id) => session.setSessionId(id),
|
|
1481
1524
|
onMessage: (message) => session.streamMessage(message),
|
|
1482
1525
|
log: (message) => process.stderr.write(`${message}
|
|
1483
|
-
`)
|
|
1526
|
+
`),
|
|
1527
|
+
idleTimeoutMs: deps.idleTimeoutMs,
|
|
1528
|
+
scheduleIdle: deps.scheduleIdle
|
|
1484
1529
|
});
|
|
1485
1530
|
exitCode = result.exitCode;
|
|
1531
|
+
idleEnded = result.endedBy === "idle";
|
|
1486
1532
|
} finally {
|
|
1487
1533
|
session.stdin.releaseToChild();
|
|
1488
1534
|
session.setActiveLeg(null);
|
|
1489
1535
|
}
|
|
1536
|
+
if (idleEnded && attended) {
|
|
1537
|
+
process.stderr.write("[pushary] handing the terminal back to local mode\n");
|
|
1538
|
+
return "switch";
|
|
1539
|
+
}
|
|
1490
1540
|
return switchBack ? "switch" : { type: "exit", code: exitCode };
|
|
1491
1541
|
};
|
|
1492
1542
|
|
|
@@ -1616,6 +1666,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1616
1666
|
if (!sessionId) return Promise.resolve();
|
|
1617
1667
|
try {
|
|
1618
1668
|
await ensureOwnership();
|
|
1669
|
+
ownership?.touch();
|
|
1619
1670
|
await reportEvent(
|
|
1620
1671
|
{ event, agentType: "claude_code", agentName, action, sessionId, machineId, pid: process.pid },
|
|
1621
1672
|
{ maxAttempts: event === "session_start" ? 4 : 2, timeoutMs: 4e3, requireOk: event === "session_start" }
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
HERMES_CONFIG_SCRIPT,
|
|
4
|
+
createGuards,
|
|
4
5
|
resolveHermesPython,
|
|
5
6
|
runHermesPythonScript
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-AZPIQG4G.js";
|
|
7
8
|
import {
|
|
8
9
|
removeInstructionBlock,
|
|
9
10
|
shortenHome,
|
|
10
11
|
unregisterPluginLocation,
|
|
11
12
|
vscodeSettingsTargets
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-FQL5E6NG.js";
|
|
13
14
|
import {
|
|
14
15
|
pusharyHookExecutable
|
|
15
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-DZDI62LY.js";
|
|
16
17
|
import {
|
|
17
18
|
OPENCODE_PLUGIN_SIBLING,
|
|
18
19
|
OPENCODE_PLUGIN_SIBLING_CONTENTS,
|
|
@@ -22,25 +23,25 @@ import {
|
|
|
22
23
|
openCodePluginDir,
|
|
23
24
|
openCodePluginFile,
|
|
24
25
|
removeOpenCodeMcpServer
|
|
25
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-CELX75MW.js";
|
|
26
27
|
import {
|
|
27
28
|
isClaudeMcpSnapshot,
|
|
28
29
|
removeClaudeMcpServers,
|
|
29
30
|
removeGeminiSettings,
|
|
30
31
|
removePusharySettings,
|
|
31
32
|
restoreClaudeMcpServer
|
|
32
|
-
} from "../chunk-
|
|
33
|
-
import "../chunk-
|
|
34
|
-
import "../chunk-
|
|
33
|
+
} from "../chunk-GL3FFM7A.js";
|
|
34
|
+
import "../chunk-2LAHXHBR.js";
|
|
35
|
+
import "../chunk-EZIEXHYM.js";
|
|
35
36
|
import {
|
|
36
37
|
disableDaemonService
|
|
37
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-F2ICEWZL.js";
|
|
38
39
|
import "../chunk-XHKBHWLX.js";
|
|
39
40
|
import {
|
|
40
41
|
execNpm
|
|
41
42
|
} from "../chunk-L4ZCFMWW.js";
|
|
42
|
-
import "../chunk-
|
|
43
|
-
import "../chunk-
|
|
43
|
+
import "../chunk-L4E2BIJ2.js";
|
|
44
|
+
import "../chunk-54ELC7D7.js";
|
|
44
45
|
import {
|
|
45
46
|
claudeJson,
|
|
46
47
|
claudeMcpReceipt,
|
|
@@ -63,14 +64,14 @@ import {
|
|
|
63
64
|
removeCodexHookTrust,
|
|
64
65
|
removeCodexHooks,
|
|
65
66
|
vscodePluginDir
|
|
66
|
-
} from "../chunk-
|
|
67
|
+
} from "../chunk-KUZWXPUS.js";
|
|
67
68
|
import {
|
|
68
69
|
rejectUnknownFlags
|
|
69
70
|
} from "../chunk-GMXKITVA.js";
|
|
70
71
|
import {
|
|
71
72
|
exitOnHelpFlag
|
|
72
|
-
} from "../chunk-
|
|
73
|
-
import "../chunk-
|
|
73
|
+
} from "../chunk-BVBEURWJ.js";
|
|
74
|
+
import "../chunk-43GMYSS4.js";
|
|
74
75
|
import "../chunk-DINDL2CF.js";
|
|
75
76
|
import {
|
|
76
77
|
removeClaudeAlias
|
|
@@ -84,7 +85,7 @@ import "../chunk-2UMNXADU.js";
|
|
|
84
85
|
import {
|
|
85
86
|
CURSOR_GATE_EVENTS,
|
|
86
87
|
HOOK_LOCATOR_BINARY
|
|
87
|
-
} from "../chunk-
|
|
88
|
+
} from "../chunk-VNMKIJZB.js";
|
|
88
89
|
|
|
89
90
|
// bin/pushary-clean.ts
|
|
90
91
|
import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
|
|
@@ -93,24 +94,6 @@ import { homedir, tmpdir } from "os";
|
|
|
93
94
|
import { confirm } from "@inquirer/prompts";
|
|
94
95
|
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
95
96
|
|
|
96
|
-
// src/cli/dry-run.ts
|
|
97
|
-
import { execSync } from "child_process";
|
|
98
|
-
import { rmSync, writeFileSync } from "fs";
|
|
99
|
-
var createGuards = (isDryRun, note) => ({
|
|
100
|
-
write: (path, contents) => {
|
|
101
|
-
if (isDryRun()) return note("rewrite", path);
|
|
102
|
-
writeFileSync(path, contents, "utf-8");
|
|
103
|
-
},
|
|
104
|
-
remove: (path, options = {}) => {
|
|
105
|
-
if (isDryRun()) return note("remove ", path);
|
|
106
|
-
rmSync(path, { recursive: true, force: options.force ?? false });
|
|
107
|
-
},
|
|
108
|
-
exec: (command, options, describe) => {
|
|
109
|
-
if (isDryRun()) return note("run ", describe);
|
|
110
|
-
execSync(command, options);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
97
|
// src/app-owned-paths.ts
|
|
115
98
|
var APP_OWNED_ENTRIES = [
|
|
116
99
|
// PusharyPaths.bin: bridgeStub, bridgeCache
|
|
@@ -122,16 +105,7 @@ var APP_OWNED_ENTRIES = [
|
|
|
122
105
|
// Shared: AgentInstaller.vscodePlugin writes .pushary/plugins/vscode, and so
|
|
123
106
|
// does the CLI. Not attributable, so it is kept whenever the app is installed
|
|
124
107
|
// rather than removed and left registered against a path that is gone.
|
|
125
|
-
"plugins"
|
|
126
|
-
// Shared, and the one whose loss is silent. `identity.ts` writes this Mac's id
|
|
127
|
-
// here and `DeviceIdentity.current()` reads it in preference to its own
|
|
128
|
-
// UserDefaults copy, precisely so one Mac has one id. Deleting it while the app
|
|
129
|
-
// is installed does not stop the app: it falls back to the copy it minted
|
|
130
|
-
// before that reconciliation existed, and the Mac starts reporting two ids that
|
|
131
|
-
// can never compare equal. A session tagged with the other one is then a
|
|
132
|
-
// session the app refuses to fetch messages for, and one `TerminalJump` calls
|
|
133
|
-
// `.onAnotherMachine`.
|
|
134
|
-
"machine-id"
|
|
108
|
+
"plugins"
|
|
135
109
|
];
|
|
136
110
|
var isAppOwned = (name) => APP_OWNED_ENTRIES.includes(name);
|
|
137
111
|
|
|
@@ -6,16 +6,19 @@ import {
|
|
|
6
6
|
postLiveSession,
|
|
7
7
|
postLiveSessionUntilAccepted,
|
|
8
8
|
startLiveSessionHeartbeat
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-CNPAT3HL.js";
|
|
10
|
+
import "../chunk-7FE2KABY.js";
|
|
11
11
|
import "../chunk-QZCFVWA7.js";
|
|
12
12
|
import {
|
|
13
13
|
transcriptsEnabled
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-UZNKVRLE.js";
|
|
15
15
|
import {
|
|
16
|
+
SESSION_IDLE_TIMEOUT_MS,
|
|
17
|
+
describeIdleWindow,
|
|
18
|
+
idleDeadlinePassed,
|
|
16
19
|
startProcessOwnership,
|
|
17
20
|
withSpawnProcessOwnership
|
|
18
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-Y6BWPERV.js";
|
|
19
22
|
import {
|
|
20
23
|
spawnClaude
|
|
21
24
|
} from "../chunk-XHKBHWLX.js";
|
|
@@ -24,17 +27,17 @@ import {
|
|
|
24
27
|
} from "../chunk-L4ZCFMWW.js";
|
|
25
28
|
import {
|
|
26
29
|
getMachineId
|
|
27
|
-
} from "../chunk-
|
|
28
|
-
import "../chunk-
|
|
29
|
-
import "../chunk-
|
|
30
|
+
} from "../chunk-54ELC7D7.js";
|
|
31
|
+
import "../chunk-KUZWXPUS.js";
|
|
32
|
+
import "../chunk-OSALSIUF.js";
|
|
30
33
|
import "../chunk-BC3VCZ3E.js";
|
|
31
|
-
import "../chunk-
|
|
34
|
+
import "../chunk-JW4KO5AF.js";
|
|
32
35
|
import "../chunk-4LL6LG5X.js";
|
|
33
36
|
import {
|
|
34
37
|
getApiKey,
|
|
35
38
|
getBaseUrl
|
|
36
39
|
} from "../chunk-2UMNXADU.js";
|
|
37
|
-
import "../chunk-
|
|
40
|
+
import "../chunk-VNMKIJZB.js";
|
|
38
41
|
|
|
39
42
|
// bin/pushary-codex-bridge.ts
|
|
40
43
|
import { existsSync } from "fs";
|
|
@@ -187,15 +190,21 @@ var rpcTurnId = (result) => {
|
|
|
187
190
|
};
|
|
188
191
|
var runCodexBridge = async (options) => {
|
|
189
192
|
const timeoutMs = options.requestTimeoutMs ?? 1e4;
|
|
193
|
+
const now = options.now ?? Date.now;
|
|
194
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? SESSION_IDLE_TIMEOUT_MS;
|
|
190
195
|
const rpc = new JsonRpcClient(options.appServer, options.cwd, timeoutMs);
|
|
191
196
|
let threadId = options.resumeThreadId ?? null;
|
|
192
197
|
let activeTurnId = null;
|
|
198
|
+
let lastCompletedTurnId = null;
|
|
199
|
+
let idleSince = now();
|
|
200
|
+
let idledOut = false;
|
|
193
201
|
let relay = null;
|
|
194
202
|
let linked = false;
|
|
195
203
|
let stopHeartbeat = () => {
|
|
196
204
|
};
|
|
197
205
|
let ownership = null;
|
|
198
206
|
const messages = /* @__PURE__ */ new Map();
|
|
207
|
+
const idleDeadlineReached = () => activeTurnId === null && idleDeadlinePassed(idleSince, now(), idleTimeoutMs);
|
|
199
208
|
const reportBody = (event, action) => ({
|
|
200
209
|
event,
|
|
201
210
|
action,
|
|
@@ -213,7 +222,9 @@ var runCodexBridge = async (options) => {
|
|
|
213
222
|
const params = message.params ?? {};
|
|
214
223
|
if (message.method === "turn/started") activeTurnId = string(object(params.turn)?.id);
|
|
215
224
|
if (message.method === "turn/completed") {
|
|
225
|
+
lastCompletedTurnId = string(object(params.turn)?.id) ?? activeTurnId;
|
|
216
226
|
activeTurnId = null;
|
|
227
|
+
idleSince = now();
|
|
217
228
|
void report("stop", "Turn completed").catch(() => {
|
|
218
229
|
});
|
|
219
230
|
}
|
|
@@ -253,7 +264,12 @@ var runCodexBridge = async (options) => {
|
|
|
253
264
|
reportBody("session_start", options.resumeThreadId ? "Session resumed" : "Session started")
|
|
254
265
|
);
|
|
255
266
|
linked = true;
|
|
256
|
-
stopHeartbeat = startLiveSessionHeartbeat(
|
|
267
|
+
stopHeartbeat = startLiveSessionHeartbeat(
|
|
268
|
+
options,
|
|
269
|
+
reportBody("heartbeat", "Session online"),
|
|
270
|
+
void 0,
|
|
271
|
+
() => ownership?.touch()
|
|
272
|
+
);
|
|
257
273
|
const startTurn = async (text, commandId) => {
|
|
258
274
|
relay?.add({ type: "user", uuid: commandId, message: { role: "user", content: text } });
|
|
259
275
|
const result = activeTurnId ? await rpc.request("turn/steer", {
|
|
@@ -268,11 +284,16 @@ var runCodexBridge = async (options) => {
|
|
|
268
284
|
model: options.model ?? null,
|
|
269
285
|
clientUserMessageId: commandId
|
|
270
286
|
});
|
|
271
|
-
|
|
287
|
+
const startedTurnId = rpcTurnId(result);
|
|
288
|
+
if (startedTurnId && startedTurnId !== lastCompletedTurnId) activeTurnId = startedTurnId;
|
|
272
289
|
};
|
|
273
290
|
if (options.prompt) await startTurn(options.prompt, `spawn-${options.spawnId}`);
|
|
274
291
|
let pendingAck = null;
|
|
275
292
|
while (!options.signal.aborted && !rpc.isClosed) {
|
|
293
|
+
if (idleDeadlineReached()) {
|
|
294
|
+
idledOut = true;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
276
297
|
try {
|
|
277
298
|
if (pendingAck) {
|
|
278
299
|
const acknowledged = await postLiveSession(options, "/api/agent/command/ack", pendingAck);
|
|
@@ -292,6 +313,7 @@ var runCodexBridge = async (options) => {
|
|
|
292
313
|
const commandId = string(command?.id);
|
|
293
314
|
const text = string(command?.text);
|
|
294
315
|
if (commandId && text) {
|
|
316
|
+
idleSince = now();
|
|
295
317
|
try {
|
|
296
318
|
await startTurn(text, commandId);
|
|
297
319
|
pendingAck = {
|
|
@@ -321,7 +343,10 @@ var runCodexBridge = async (options) => {
|
|
|
321
343
|
await rpc.request("turn/interrupt", { threadId, turnId: activeTurnId }).catch(() => null);
|
|
322
344
|
}
|
|
323
345
|
await relay?.flush();
|
|
324
|
-
await report(
|
|
346
|
+
await report(
|
|
347
|
+
"session_closed",
|
|
348
|
+
idledOut ? `Session ended after ${describeIdleWindow(idleTimeoutMs)} idle` : "Session stopped"
|
|
349
|
+
).catch(() => null);
|
|
325
350
|
return "stopped";
|
|
326
351
|
} catch (error) {
|
|
327
352
|
const message = error instanceof Error ? error.message : String(error);
|