@pushary/agent-hooks 0.84.0 → 0.85.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +24 -0
  3. package/dist/bin/pushary-bell-hook.js +5 -3
  4. package/dist/bin/pushary-bell.js +8 -7
  5. package/dist/bin/pushary-claude.js +29 -20
  6. package/dist/bin/pushary-clean.js +6 -5
  7. package/dist/bin/pushary-codex-bridge.js +8 -7
  8. package/dist/bin/pushary-codex-hook.js +11 -14
  9. package/dist/bin/pushary-codex.js +1 -1
  10. package/dist/bin/pushary-connect.js +5 -5
  11. package/dist/bin/pushary-daemon.js +36 -18
  12. package/dist/bin/pushary-disconnect.d.ts +1 -0
  13. package/dist/bin/pushary-disconnect.js +125 -0
  14. package/dist/bin/pushary-doctor.js +9 -9
  15. package/dist/bin/pushary-gemini-bridge.js +8 -7
  16. package/dist/bin/pushary-gemini-hook.js +12 -15
  17. package/dist/bin/pushary-hook.js +13 -20
  18. package/dist/bin/pushary-login.js +2 -2
  19. package/dist/bin/pushary-logout.js +3 -3
  20. package/dist/bin/pushary-mode.js +2 -2
  21. package/dist/bin/pushary-notification-hook.js +6 -9
  22. package/dist/bin/pushary-permission-denied-hook.js +11 -9
  23. package/dist/bin/pushary-permission-hook.js +11 -9
  24. package/dist/bin/pushary-post-hook.js +6 -9
  25. package/dist/bin/pushary-prompt-hook.js +6 -9
  26. package/dist/bin/pushary-session-end-hook.js +6 -6
  27. package/dist/bin/pushary-session-start-hook.js +6 -6
  28. package/dist/bin/pushary-setup.js +23 -24
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +2 -2
  31. package/dist/bin/pushary-stop-hook.js +6 -6
  32. package/dist/bin/pushary-stopfailure-hook.js +6 -6
  33. package/dist/bin/pushary-suggestions.js +2 -2
  34. package/dist/bin/pushary-upgrade.js +13 -8
  35. package/dist/bin/pushary-wait.js +2 -2
  36. package/dist/bin/pushary.js +2 -1
  37. package/dist/{chunk-HZNOAJGB.js → chunk-4QPOMJUB.js} +1 -1
  38. package/dist/chunk-64PXDATC.js +187 -0
  39. package/dist/chunk-6CUUA7HO.js +59 -0
  40. package/dist/{chunk-UXWEE3QI.js → chunk-ADISVXZL.js} +1 -1
  41. package/dist/chunk-AXRATIS6.js +70 -0
  42. package/dist/{chunk-YJCMI5GF.js → chunk-ERYKGH4J.js} +11 -4
  43. package/dist/{chunk-OPMSMF5R.js → chunk-EXMUM226.js} +21 -8
  44. package/dist/{chunk-NWYGW7HY.js → chunk-GCKAWHFP.js} +9 -2
  45. package/dist/{chunk-QNBA5OXQ.js → chunk-GFM3STWO.js} +3 -3
  46. package/dist/{chunk-MGJBC6DD.js → chunk-GOEXZ5QJ.js} +1 -0
  47. package/dist/{chunk-37AD5K7W.js → chunk-GXIFADGD.js} +1 -1
  48. package/dist/{chunk-SEAHGHCE.js → chunk-HVZIHTOO.js} +33 -0
  49. package/dist/chunk-PWTKKQQI.js +27 -0
  50. package/dist/{chunk-J4KFVOCQ.js → chunk-SQOFGPNX.js} +1 -1
  51. package/dist/{chunk-CCH775Y6.js → chunk-WEAQQSHQ.js} +1 -1
  52. package/dist/{chunk-EVJ6ULHN.js → chunk-YKW6JCWI.js} +68 -29
  53. package/dist/src/index.d.ts +1 -1
  54. package/dist/src/index.js +6 -4
  55. package/package.json +3 -2
  56. package/dist/chunk-UKSQWTBH.js +0 -208
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.85.0
4
+
5
+ ### A hook command that cannot outlive what it points at
6
+
7
+ Every hook command written into `~/.claude/settings.json`, `~/.codex/hooks.json`
8
+ and `~/.gemini/settings.json`, the free bell included, is now wrapped so that a missing binary, a removed
9
+ `node`, a changed npm prefix or a moved home directory exits 0 and the agent reads
10
+ "no opinion". Before, any of those turned every tool call into a failing hook.
11
+ The guard is tested on every shell that can be `/bin/sh`, including dash, and with
12
+ `node` off PATH, which used to exit 127 and print on every tool call.
13
+
14
+ Codex hashes the exact command string to trust a hook, so `pushary upgrade` rewrites
15
+ the trust entry when it re-applies the hooks.
16
+
17
+ ### Hooks registered for the CLI you have
18
+
19
+ Claude Code events are registered from a table with a version floor per event.
20
+ `SubagentStart`, `SubagentStop`, `PreCompact`, `PostCompact` and `TeammateIdle`
21
+ are added on a CLI that understands them, and `pushary upgrade` re-registers
22
+ against the CLI you have now. Disconnect removes every event in the table; a
23
+ hand-typed list used to leave the five observability events behind.
24
+
25
+ ### `pushary disconnect <agent>`
26
+
27
+ Removes the MCP server, the hooks, the skill and, for Codex, the trust entries and
28
+ the API key from `config.toml`, for one agent, leaving the others alone. The bin
29
+ map entry was written in a shape the build silently dropped, and the command read
30
+ the dispatcher's own token as the agent name; both are fixed before this, its
31
+ first release, and a test now holds every bin entry to the one shape tsup builds.
32
+
33
+ ### A kill switch per hook path
34
+
35
+ `~/.pushary/admission-rules.json` (`{"version":1,"disabled":["claude:PostToolUse"]}`)
36
+ silences one event on one agent, or one agent entirely, without a release. Every
37
+ hook binary reads its input through this check, from the same file the macOS
38
+ helper reads, so one file governs both transports. Deny-only: nothing in the file
39
+ can enable a hook.
40
+
41
+ ### `PostToolUse` no longer pays two round trips per tool call
42
+
43
+ The telemetry hooks reuse the mode state the gate fetched for the same session
44
+ within the last 60 seconds. The gate itself still reads it live, so a remote stop
45
+ and a scope contract are never served stale where they decide anything.
46
+
47
+ ## 0.84.1
48
+
49
+ ### Remote starts survive lost acknowledgements without double-launching
50
+
51
+ A phone-started agent now claims exclusive process ownership before the provider
52
+ starts. If the daemon restarts or the launch acknowledgement is lost, retrying the
53
+ same request reconnects to the existing process instead of starting a second one.
54
+
55
+ ### Repository approval scopes reach Codex and Gemini
56
+
57
+ Codex and Gemini sessions now carry their repository identity through approval
58
+ requests, so repository-scoped rules stay inside the repository that created them.
59
+
60
+ ### Encrypted transcripts retry safely
61
+
62
+ Transcript writers now retry while a session key is temporarily unavailable and
63
+ stop on a real key conflict, preventing undecryptable transcript entries from being
64
+ accepted or silently dropped.
65
+
3
66
  ## 0.84.0
4
67
 
5
68
  ### The phone can start an agent on a machine that is not running one
package/README.md CHANGED
@@ -108,6 +108,29 @@ npx @pushary/agent-hooks@latest doctor
108
108
 
109
109
  Any other MCP client can use notify and ask by pointing at the Pushary MCP endpoint directly.
110
110
 
111
+ ## Hook events, gated by version
112
+
113
+ Claude Code 2.1.239 names thirty-one hook events and the set grows release by release,
114
+ so the ones we register are gated on the version actually installed. The version is
115
+ read from disk and never by running the binary: `codex --version` once submitted the
116
+ Codex CLI to XProtect, which killed it and moved the 181 MB binary to the Trash, so
117
+ setup deleted the user's installation as a side effect of asking its version.
118
+
119
+ An unreadable version registers only the events that predate gating. Each registration
120
+ records `registered|<version>`, which is what lets an upgrade pick up newer events
121
+ without anyone re-running setup.
122
+
123
+ ## Admission rules
124
+
125
+ Publishing this package is a version bump merged to main, and a hook change is not
126
+ live until that happens, so a hook that floods on one event would otherwise need a
127
+ release to stop. `~/.pushary/admission-rules.json` can disable an `(agent, event)`
128
+ pair as data.
129
+
130
+ Deny-only by construction: a rule can silence a hook we ship, and no shape of the file
131
+ can enable one we do not. A file that cannot be read admits everything, because
132
+ failing closed would let one malformed download silence every hook on the machine.
133
+
111
134
  ## Commands
112
135
 
113
136
  <!-- Managed: every command below is declared in src/cli/commands.ts, and a test
@@ -123,6 +146,7 @@ logout Remove the key this machine stores, and say what it does not remove
123
146
  connect Connect a phone without re-running setup (--app for the Pushary app)
124
147
  bell Free local bell when an agent finishes or needs you. No account, no key, no network
125
148
  doctor Verify your Pushary installation is working
149
+ disconnect Turn off one agent (claude|codex|gemini|cursor)
126
150
  clean Remove all Pushary configuration (--yes for non-interactive)
127
151
  mode Switch approval mode (push_only, push_first, terminal_only)
128
152
  wait Show or set the "wait for your phone" ladder (pushary wait 45)
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  handleBell
4
4
  } from "../chunk-7PZPJSWI.js";
5
+ import {
6
+ readHookInput
7
+ } from "../chunk-6CUUA7HO.js";
5
8
  import "../chunk-K7CSRGKZ.js";
6
9
  import "../chunk-BC3VCZ3E.js";
7
10
  import "../chunk-6MTNS63X.js";
@@ -11,10 +14,9 @@ import "../chunk-MQMPG5X4.js";
11
14
 
12
15
  // bin/pushary-bell-hook.ts
13
16
  var main = async () => {
14
- let rawInput = "";
15
17
  try {
16
- for await (const chunk of process.stdin) rawInput += chunk;
17
- handleBell(rawInput.trim() ? JSON.parse(rawInput) : {});
18
+ const input = await readHookInput("claude");
19
+ if (input) handleBell(input);
18
20
  } catch {
19
21
  }
20
22
  process.stdout.write("{}");
@@ -6,9 +6,13 @@ import {
6
6
  ring,
7
7
  upgradeThreshold
8
8
  } from "../chunk-7PZPJSWI.js";
9
+ import {
10
+ guardBinary
11
+ } from "../chunk-PWTKKQQI.js";
12
+ import "../chunk-VT4IVERX.js";
9
13
  import {
10
14
  claudeSettings
11
- } from "../chunk-SEAHGHCE.js";
15
+ } from "../chunk-HVZIHTOO.js";
12
16
  import {
13
17
  createIo
14
18
  } from "../chunk-5RUIFDTP.js";
@@ -18,8 +22,8 @@ import {
18
22
  } from "../chunk-GMXKITVA.js";
19
23
  import {
20
24
  exitOnHelpFlag
21
- } from "../chunk-J4KFVOCQ.js";
22
- import "../chunk-MGJBC6DD.js";
25
+ } from "../chunk-SQOFGPNX.js";
26
+ import "../chunk-GOEXZ5QJ.js";
23
27
  import "../chunk-BC3VCZ3E.js";
24
28
  import "../chunk-DINDL2CF.js";
25
29
  import {
@@ -53,10 +57,7 @@ var isBellHook = (entry) => {
53
57
  var BELL_TIMEOUT_SECONDS = 5;
54
58
  var BELL_NOTIFICATION_MATCHER = "idle_prompt|agent_needs_input|agent_completed";
55
59
  var addBellHooks = (settings, binDir) => {
56
- const resolve = (name) => {
57
- const path = binDir ? join(binDir, name) : name;
58
- return /\s/.test(path) ? `"${path}"` : path;
59
- };
60
+ const resolve = (name) => guardBinary(binDir ? join(binDir, name) : name);
60
61
  const hooks = ensureRecord(settings, "hooks");
61
62
  const notification = (Array.isArray(hooks.Notification) ? hooks.Notification : []).filter((entry) => !isBellHook(entry));
62
63
  notification.push({
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureDaemonRunning
4
- } from "../chunk-CCH775Y6.js";
4
+ } from "../chunk-WEAQQSHQ.js";
5
5
  import {
6
6
  buildTranscriptRecords,
7
7
  decodePublicKey,
@@ -9,21 +9,22 @@ import {
9
9
  generateSessionKey,
10
10
  uploadTranscriptRecords,
11
11
  wrapSessionKey
12
- } from "../chunk-NWYGW7HY.js";
12
+ } from "../chunk-GCKAWHFP.js";
13
13
  import {
14
- startProcessOwnership
15
- } from "../chunk-EVJ6ULHN.js";
14
+ startProcessOwnership,
15
+ withSpawnProcessOwnership
16
+ } from "../chunk-YKW6JCWI.js";
16
17
  import {
17
18
  needsShell,
18
19
  spawnClaude
19
20
  } from "../chunk-XHKBHWLX.js";
20
21
  import "../chunk-VT4IVERX.js";
21
- import "../chunk-SEAHGHCE.js";
22
+ import "../chunk-HVZIHTOO.js";
22
23
  import {
23
24
  KILL_REASON,
24
25
  isDeferAnswer,
25
26
  resolveGate
26
- } from "../chunk-37AD5K7W.js";
27
+ } from "../chunk-GXIFADGD.js";
27
28
  import {
28
29
  askUser,
29
30
  cancelQuestion,
@@ -35,7 +36,7 @@ import {
35
36
  reportEvent,
36
37
  scopePathFor,
37
38
  waitForAnswer
38
- } from "../chunk-OPMSMF5R.js";
39
+ } from "../chunk-EXMUM226.js";
39
40
  import {
40
41
  getMachineId
41
42
  } from "../chunk-RN3NOEJF.js";
@@ -113,9 +114,9 @@ var runLocalPassthrough = (binary, args2) => {
113
114
  cleanup();
114
115
  resolve2(127);
115
116
  });
116
- child.on("exit", (code, signal) => {
117
+ child.on("exit", (code2, signal) => {
117
118
  cleanup();
118
- if (typeof code === "number") resolve2(code);
119
+ if (typeof code2 === "number") resolve2(code2);
119
120
  else if (signal) resolve2(128 + (SIGNAL_NUMBERS[signal] ?? 0));
120
121
  else resolve2(0);
121
122
  });
@@ -139,9 +140,11 @@ var createSyncState = () => ({
139
140
  sessionKey: null,
140
141
  wrappedKey: null,
141
142
  seen: /* @__PURE__ */ new Set(),
142
- keyUploaded: false
143
+ keyUploaded: false,
144
+ keyRejected: false
143
145
  });
144
146
  var syncTick = async (lines, state, uploader, sessionId, machineId) => {
147
+ if (state.keyRejected) return state;
145
148
  let sessionKey = state.sessionKey;
146
149
  let wrappedKey = state.wrappedKey;
147
150
  if (!sessionKey) {
@@ -152,9 +155,9 @@ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
152
155
  }
153
156
  const built = buildTranscriptRecords(lines, sessionKey, state.seen);
154
157
  if (built.records.length === 0) {
155
- return { sessionKey, wrappedKey, seen: built.seen, keyUploaded: state.keyUploaded };
158
+ return { ...state, sessionKey, wrappedKey, seen: built.seen };
156
159
  }
157
- const ok = await uploader.uploadRecords({
160
+ const result = await uploader.uploadRecords({
158
161
  sessionId,
159
162
  machineId,
160
163
  dataEncryptionKey: state.keyUploaded ? void 0 : wrappedKey ?? void 0,
@@ -163,8 +166,9 @@ var syncTick = async (lines, state, uploader, sessionId, machineId) => {
163
166
  return {
164
167
  sessionKey,
165
168
  wrappedKey,
166
- seen: ok ? built.seen : state.seen,
167
- keyUploaded: state.keyUploaded || ok
169
+ seen: result.ok ? built.seen : state.seen,
170
+ keyUploaded: state.keyUploaded || result.keyRegistered,
171
+ keyRejected: result.keyConflict
168
172
  };
169
173
  };
170
174
  var readLines = (path) => {
@@ -258,7 +262,7 @@ var ensureClaudeSdk = async (log) => {
258
262
  { cwd: dir, stdio: "ignore", shell: process.platform === "win32" }
259
263
  );
260
264
  child.on("error", () => resolve2(false));
261
- child.on("close", (code) => resolve2(code === 0));
265
+ child.on("close", (code2) => resolve2(code2 === 0));
262
266
  });
263
267
  if (!installed) {
264
268
  log?.(`[pushary] could not auto-install the SDK. Install it manually: npm i -g ${SDK_SPEC}`);
@@ -1039,13 +1043,13 @@ var runLocalLeg = (session, deps = {}) => {
1039
1043
  }
1040
1044
  finish();
1041
1045
  });
1042
- child.on("exit", (code, signal) => {
1046
+ child.on("exit", (code2, signal) => {
1043
1047
  if (signal === "SIGTERM" && legAbort.signal.aborted) {
1044
1048
  } else if (!exitReason) {
1045
1049
  if (session.input.bufferedCount > 0) {
1046
1050
  exitReason = "switch";
1047
- } else if (typeof code === "number") {
1048
- exitReason = { type: "exit", code };
1051
+ } else if (typeof code2 === "number") {
1052
+ exitReason = { type: "exit", code: code2 };
1049
1053
  } else if (signal) {
1050
1054
  exitReason = { type: "exit", code: SIGNAL_EXIT_BASE + (SIGNAL_NUMBERS2[signal] ?? 0) };
1051
1055
  } else {
@@ -1492,10 +1496,15 @@ var runClaudeWrapper = async (args2) => {
1492
1496
 
1493
1497
  // bin/pushary-claude.ts
1494
1498
  var args = resolveClaudeArgs(process.argv);
1495
- ensureDaemonRunning().then(() => runClaudeWrapper(args)).then((code) => process.exit(code)).catch((err) => {
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) => {
1496
1504
  process.stderr.write(
1497
1505
  `[pushary] wrapper error: ${err instanceof Error ? err.message : String(err)}
1498
1506
  `
1499
1507
  );
1500
- process.exit(1);
1508
+ return 1;
1501
1509
  });
1510
+ process.exit(code);
@@ -4,14 +4,15 @@ import {
4
4
  shortenHome,
5
5
  unregisterPluginLocation,
6
6
  vscodeSettingsTargets
7
- } from "../chunk-UXWEE3QI.js";
7
+ } from "../chunk-ADISVXZL.js";
8
8
  import {
9
9
  removeGeminiSettings
10
10
  } from "../chunk-DOXSWT4Q.js";
11
11
  import {
12
12
  removeClaudeMcpServers,
13
13
  removePusharySettings
14
- } from "../chunk-UKSQWTBH.js";
14
+ } from "../chunk-64PXDATC.js";
15
+ import "../chunk-PWTKKQQI.js";
15
16
  import {
16
17
  execNpm
17
18
  } from "../chunk-VT4IVERX.js";
@@ -32,14 +33,14 @@ import {
32
33
  pusharyDir,
33
34
  removeCodexHooks,
34
35
  vscodePluginDir
35
- } from "../chunk-SEAHGHCE.js";
36
+ } from "../chunk-HVZIHTOO.js";
36
37
  import {
37
38
  rejectUnknownFlags
38
39
  } from "../chunk-GMXKITVA.js";
39
40
  import {
40
41
  exitOnHelpFlag
41
- } from "../chunk-J4KFVOCQ.js";
42
- import "../chunk-MGJBC6DD.js";
42
+ } from "../chunk-SQOFGPNX.js";
43
+ import "../chunk-GOEXZ5QJ.js";
43
44
  import {
44
45
  removeClaudeAlias
45
46
  } from "../chunk-BC3VCZ3E.js";
@@ -6,18 +6,19 @@ import {
6
6
  postLiveSession,
7
7
  postLiveSessionUntilAccepted,
8
8
  startLiveSessionHeartbeat
9
- } from "../chunk-YJCMI5GF.js";
10
- import "../chunk-NWYGW7HY.js";
9
+ } from "../chunk-ERYKGH4J.js";
10
+ import "../chunk-GCKAWHFP.js";
11
11
  import {
12
- startProcessOwnership
13
- } from "../chunk-EVJ6ULHN.js";
12
+ startProcessOwnership,
13
+ withSpawnProcessOwnership
14
+ } from "../chunk-YKW6JCWI.js";
14
15
  import {
15
16
  spawnClaude
16
17
  } from "../chunk-XHKBHWLX.js";
17
18
  import {
18
19
  resolveBinary
19
20
  } from "../chunk-VT4IVERX.js";
20
- import "../chunk-SEAHGHCE.js";
21
+ import "../chunk-HVZIHTOO.js";
21
22
  import {
22
23
  getMachineId
23
24
  } from "../chunk-RN3NOEJF.js";
@@ -327,7 +328,7 @@ var abort = new AbortController();
327
328
  var signals = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
328
329
  var stop = () => abort.abort();
329
330
  for (const signal of signals) process.on(signal, stop);
330
- var outcome = await runCodexBridge({
331
+ var outcome = await withSpawnProcessOwnership(spawnId, () => runCodexBridge({
331
332
  apiKey: getApiKey(),
332
333
  baseUrl: getBaseUrl(),
333
334
  machineId: getMachineId(),
@@ -343,6 +344,6 @@ var outcome = await runCodexBridge({
343
344
  },
344
345
  signal: abort.signal,
345
346
  transcripts: process.env.PUSHARY_TRANSCRIPTS === "1"
346
- });
347
+ }));
347
348
  for (const signal of signals) process.off(signal, stop);
348
349
  process.exit(outcome === "stopped" ? 0 : 1);
@@ -3,12 +3,15 @@ import {
3
3
  isGatingMoment,
4
4
  recordKeylessMoment
5
5
  } from "../chunk-NJ7JT26G.js";
6
+ import {
7
+ readHookInput
8
+ } from "../chunk-6CUUA7HO.js";
6
9
  import {
7
10
  KILL_REASON,
8
11
  denyReasonFrom,
9
12
  isDeferAnswer,
10
13
  resolveGate
11
- } from "../chunk-37AD5K7W.js";
14
+ } from "../chunk-GXIFADGD.js";
12
15
  import {
13
16
  CODEX_AGENT,
14
17
  DEFAULT_SESSION,
@@ -28,6 +31,7 @@ import {
28
31
  handleStop,
29
32
  handleUserPrompt,
30
33
  permissionTimeoutDecision,
34
+ policyRequestFor,
31
35
  preToolUseTimeoutDecision,
32
36
  readLastPrompt,
33
37
  repoKeyFor,
@@ -39,7 +43,7 @@ import {
39
43
  toPolicyLookup,
40
44
  toPolicyLookups,
41
45
  waitForAnswer
42
- } from "../chunk-OPMSMF5R.js";
46
+ } from "../chunk-EXMUM226.js";
43
47
  import {
44
48
  getMachineId
45
49
  } from "../chunk-RN3NOEJF.js";
@@ -143,6 +147,7 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
143
147
  action: description.slice(0, DECISION_LINE_MAX),
144
148
  blocker: deriveBlocker(mode),
145
149
  actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {}),
150
+ repoKey: repoKeyFor(input.cwd),
146
151
  // Codex has always sent this; it is what claimNotify and markApproved already
147
152
  // key on locally. Passing it through lets the server collapse a retried create
148
153
  // the same way, instead of putting the same approval on the phone twice.
@@ -177,9 +182,10 @@ var notifyApprovalNeeded = async (apiKey, input, lookup) => {
177
182
  };
178
183
  var codexGate = async (apiKey, input) => {
179
184
  const modeState = await fetchModeState(apiKey, input.session_id);
185
+ const policyRequest = policyRequestFor("codex");
180
186
  const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
181
187
  const lookups = toPolicyLookups(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
182
- const config = await getPolicy(apiKey, modeState.policyVersion, "codex", false, input.session_id);
188
+ const config = await getPolicy(apiKey, modeState.policyVersion, policyRequest.agent, policyRequest.repoAware, input.session_id);
183
189
  const verdict = resolveGate({
184
190
  modeState,
185
191
  config,
@@ -307,17 +313,8 @@ var emit = (wire) => {
307
313
  if (wire) process.stdout.write(JSON.stringify(wire));
308
314
  };
309
315
  var main = async () => {
310
- let rawInput = "";
311
- for await (const chunk of process.stdin) {
312
- rawInput += chunk;
313
- }
314
- if (!rawInput.trim()) {
315
- process.exit(0);
316
- }
317
- let input;
318
- try {
319
- input = JSON.parse(rawInput);
320
- } catch {
316
+ const input = await readHookInput("codex");
317
+ if (!input) {
321
318
  process.exit(0);
322
319
  }
323
320
  try {
@@ -3,7 +3,7 @@ import {
3
3
  askUser,
4
4
  reportEvent,
5
5
  waitForAnswer
6
- } from "../chunk-OPMSMF5R.js";
6
+ } from "../chunk-EXMUM226.js";
7
7
  import {
8
8
  getMachineId
9
9
  } from "../chunk-RN3NOEJF.js";
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ printKeyCheck
4
+ } from "../chunk-FBGBJXVC.js";
2
5
  import {
3
6
  confirmAppConnection,
4
7
  connectDevice,
5
8
  printConnectInstructions
6
9
  } from "../chunk-6LC4WSCL.js";
7
10
  import "../chunk-3EGEA4KH.js";
8
- import {
9
- printKeyCheck
10
- } from "../chunk-FBGBJXVC.js";
11
11
  import {
12
12
  checkApiKey
13
13
  } from "../chunk-F52SIIDG.js";
@@ -22,8 +22,8 @@ import {
22
22
  } from "../chunk-GMXKITVA.js";
23
23
  import {
24
24
  exitOnHelpFlag
25
- } from "../chunk-J4KFVOCQ.js";
26
- import "../chunk-MGJBC6DD.js";
25
+ } from "../chunk-SQOFGPNX.js";
26
+ import "../chunk-GOEXZ5QJ.js";
27
27
  import "../chunk-BC3VCZ3E.js";
28
28
  import "../chunk-DINDL2CF.js";
29
29
  import "../chunk-6MTNS63X.js";
@@ -8,15 +8,16 @@ import {
8
8
  acquireDaemonLease,
9
9
  readDaemonStatus,
10
10
  requestDaemonHandoff
11
- } from "../chunk-CCH775Y6.js";
11
+ } from "../chunk-WEAQQSHQ.js";
12
12
  import {
13
13
  CODEX_BRIDGE_CAPABILITY,
14
14
  CODEX_RESUME_CAPABILITY,
15
15
  geminiLiveCapabilities
16
16
  } from "../chunk-NRGRUOAR.js";
17
17
  import {
18
+ findLiveProcessBySpawnId,
18
19
  ownsLiveProcess
19
- } from "../chunk-EVJ6ULHN.js";
20
+ } from "../chunk-YKW6JCWI.js";
20
21
  import {
21
22
  spawnClaude
22
23
  } from "../chunk-XHKBHWLX.js";
@@ -25,14 +26,14 @@ import {
25
26
  resolveGlobalPackageVersion,
26
27
  resolvePackageVersionFromBinary
27
28
  } from "../chunk-VT4IVERX.js";
28
- import "../chunk-SEAHGHCE.js";
29
+ import "../chunk-HVZIHTOO.js";
29
30
  import {
30
31
  getMachineId
31
32
  } from "../chunk-RN3NOEJF.js";
32
33
  import {
33
34
  exitOnHelpFlag
34
- } from "../chunk-J4KFVOCQ.js";
35
- import "../chunk-MGJBC6DD.js";
35
+ } from "../chunk-SQOFGPNX.js";
36
+ import "../chunk-GOEXZ5QJ.js";
36
37
  import "../chunk-DINDL2CF.js";
37
38
  import "../chunk-6MTNS63X.js";
38
39
  import {
@@ -135,6 +136,23 @@ var spawnAcknowledgement = (request, machineId, outcome) => {
135
136
  }
136
137
  };
137
138
  };
139
+ var launchSpawnClaim = async (request, runtime) => {
140
+ const existingPid = await runtime.findExistingPid(request.id);
141
+ if (existingPid !== null) {
142
+ await runtime.acknowledge(spawnAcknowledgement(request, runtime.machineId, { state: "started", pid: existingPid }));
143
+ return;
144
+ }
145
+ let pid;
146
+ try {
147
+ pid = await runtime.launch(request);
148
+ } catch (err) {
149
+ const message = err instanceof Error ? err.message : String(err);
150
+ runtime.reportLaunchError(message);
151
+ await runtime.acknowledge(spawnAcknowledgement(request, runtime.machineId, { state: "failed", message }));
152
+ return;
153
+ }
154
+ await runtime.acknowledge(spawnAcknowledgement(request, runtime.machineId, { state: "started", pid }));
155
+ };
138
156
  var DrainError = class extends Error {
139
157
  constructor(status) {
140
158
  super(`drain ${status}`);
@@ -397,19 +415,19 @@ var runSpawnDaemon = async () => {
397
415
  }
398
416
  throw lastError ?? new Error("spawn acknowledgement stopped");
399
417
  };
400
- const launchClaim = async (request) => {
401
- let pid;
402
- try {
403
- pid = await launch(request);
404
- } catch (err) {
405
- const message = err instanceof Error ? err.message : String(err);
406
- stderr(`[pushary] could not launch session: ${message}
407
- `);
408
- await acknowledge(spawnAcknowledgement(request, machineId, { state: "failed", message }));
409
- return;
410
- }
411
- await acknowledge(spawnAcknowledgement(request, machineId, { state: "started", pid }));
412
- };
418
+ const launchClaim = (request) => launchSpawnClaim(request, {
419
+ machineId,
420
+ findExistingPid: async (spawnId) => {
421
+ for (const [pid, launchedSpawnId] of launchedProcesses) {
422
+ if (launchedSpawnId === spawnId) return pid;
423
+ }
424
+ return findLiveProcessBySpawnId(spawnId);
425
+ },
426
+ launch,
427
+ acknowledge,
428
+ reportLaunchError: (message) => stderr(`[pushary] could not launch session: ${message}
429
+ `)
430
+ });
413
431
  const nextDelay = () => {
414
432
  if (errorStreak > 0) return Math.min(SLOW_POLL_MS * 2 ** (errorStreak - 1), MAX_ERROR_BACKOFF_MS);
415
433
  return idleTicks >= FAST_DECAY_TICKS ? SLOW_POLL_MS : FAST_POLL_MS;
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node