@pushary/agent-hooks 0.84.0 → 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 CHANGED
@@ -1,5 +1,24 @@
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
+
3
22
  ## 0.84.0
4
23
 
5
24
  ### The phone can start an agent on a machine that is not running one
@@ -9,10 +9,11 @@ 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-ILB6UOAF.js";
16
17
  import {
17
18
  needsShell,
18
19
  spawnClaude
@@ -23,7 +24,7 @@ import {
23
24
  KILL_REASON,
24
25
  isDeferAnswer,
25
26
  resolveGate
26
- } from "../chunk-37AD5K7W.js";
27
+ } from "../chunk-BLDDXHHA.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-CCCJ6BPW.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);
@@ -6,11 +6,12 @@ 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-ILB6UOAF.js";
14
15
  import {
15
16
  spawnClaude
16
17
  } from "../chunk-XHKBHWLX.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);
@@ -8,7 +8,7 @@ import {
8
8
  denyReasonFrom,
9
9
  isDeferAnswer,
10
10
  resolveGate
11
- } from "../chunk-37AD5K7W.js";
11
+ } from "../chunk-BLDDXHHA.js";
12
12
  import {
13
13
  CODEX_AGENT,
14
14
  DEFAULT_SESSION,
@@ -28,6 +28,7 @@ import {
28
28
  handleStop,
29
29
  handleUserPrompt,
30
30
  permissionTimeoutDecision,
31
+ policyRequestFor,
31
32
  preToolUseTimeoutDecision,
32
33
  readLastPrompt,
33
34
  repoKeyFor,
@@ -39,7 +40,7 @@ import {
39
40
  toPolicyLookup,
40
41
  toPolicyLookups,
41
42
  waitForAnswer
42
- } from "../chunk-OPMSMF5R.js";
43
+ } from "../chunk-CCCJ6BPW.js";
43
44
  import {
44
45
  getMachineId
45
46
  } from "../chunk-RN3NOEJF.js";
@@ -143,6 +144,7 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
143
144
  action: description.slice(0, DECISION_LINE_MAX),
144
145
  blocker: deriveBlocker(mode),
145
146
  actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {}),
147
+ repoKey: repoKeyFor(input.cwd),
146
148
  // Codex has always sent this; it is what claimNotify and markApproved already
147
149
  // key on locally. Passing it through lets the server collapse a retried create
148
150
  // the same way, instead of putting the same approval on the phone twice.
@@ -177,9 +179,10 @@ var notifyApprovalNeeded = async (apiKey, input, lookup) => {
177
179
  };
178
180
  var codexGate = async (apiKey, input) => {
179
181
  const modeState = await fetchModeState(apiKey, input.session_id);
182
+ const policyRequest = policyRequestFor("codex");
180
183
  const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
181
184
  const lookups = toPolicyLookups(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
182
- const config = await getPolicy(apiKey, modeState.policyVersion, "codex", false, input.session_id);
185
+ const config = await getPolicy(apiKey, modeState.policyVersion, policyRequest.agent, policyRequest.repoAware, input.session_id);
183
186
  const verdict = resolveGate({
184
187
  modeState,
185
188
  config,
@@ -3,7 +3,7 @@ import {
3
3
  askUser,
4
4
  reportEvent,
5
5
  waitForAnswer
6
- } from "../chunk-OPMSMF5R.js";
6
+ } from "../chunk-CCCJ6BPW.js";
7
7
  import {
8
8
  getMachineId
9
9
  } from "../chunk-RN3NOEJF.js";
@@ -15,8 +15,9 @@ import {
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-ILB6UOAF.js";
20
21
  import {
21
22
  spawnClaude
22
23
  } from "../chunk-XHKBHWLX.js";
@@ -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;
@@ -5,11 +5,12 @@ import {
5
5
  postLiveSession,
6
6
  postLiveSessionUntilAccepted,
7
7
  startLiveSessionHeartbeat
8
- } from "../chunk-YJCMI5GF.js";
9
- import "../chunk-NWYGW7HY.js";
8
+ } from "../chunk-ERYKGH4J.js";
9
+ import "../chunk-GCKAWHFP.js";
10
10
  import {
11
- startProcessOwnership
12
- } from "../chunk-EVJ6ULHN.js";
11
+ startProcessOwnership,
12
+ withSpawnProcessOwnership
13
+ } from "../chunk-ILB6UOAF.js";
13
14
  import {
14
15
  spawnClaude
15
16
  } from "../chunk-XHKBHWLX.js";
@@ -280,7 +281,7 @@ var abort = new AbortController();
280
281
  var signals = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
281
282
  var stop = () => abort.abort();
282
283
  for (const signal of signals) process.on(signal, stop);
283
- var outcome = await runGeminiBridge({
284
+ var outcome = await withSpawnProcessOwnership(spawnId, () => runGeminiBridge({
284
285
  apiKey: getApiKey(),
285
286
  baseUrl: getBaseUrl(),
286
287
  machineId: getMachineId(),
@@ -293,6 +294,6 @@ var outcome = await runGeminiBridge({
293
294
  command: { command: process.env.PUSHARY_GEMINI_COMMAND?.trim() || resolveBinary("gemini") || "gemini" },
294
295
  signal: abort.signal,
295
296
  transcripts: process.env.PUSHARY_TRANSCRIPTS === "1"
296
- });
297
+ }));
297
298
  for (const signal of signals) process.off(signal, stop);
298
299
  process.exit(outcome === "stopped" ? 0 : 1);
@@ -8,7 +8,7 @@ import {
8
8
  denyReasonFrom,
9
9
  isDeferAnswer,
10
10
  resolveGate
11
- } from "../chunk-37AD5K7W.js";
11
+ } from "../chunk-BLDDXHHA.js";
12
12
  import {
13
13
  DEFAULT_SESSION,
14
14
  askUser,
@@ -22,6 +22,7 @@ import {
22
22
  handlePostToolUse,
23
23
  handleStop,
24
24
  handleUserPrompt,
25
+ policyRequestFor,
25
26
  readLastPrompt,
26
27
  readLastUserPrompt,
27
28
  repoKeyFor,
@@ -30,7 +31,7 @@ import {
30
31
  scopePathFor,
31
32
  sendNotification,
32
33
  waitForAnswer
33
- } from "../chunk-OPMSMF5R.js";
34
+ } from "../chunk-CCCJ6BPW.js";
34
35
  import {
35
36
  getMachineId
36
37
  } from "../chunk-RN3NOEJF.js";
@@ -127,7 +128,8 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode, pollInterval
127
128
  intent: readLastPrompt(input.session_id || DEFAULT_SESSION) ?? (input.transcript_path ? readLastUserPrompt(input.transcript_path) : void 0),
128
129
  action: description.slice(0, DECISION_LINE_MAX),
129
130
  blocker: deriveBlocker(mode),
130
- actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {})
131
+ actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {}),
132
+ repoKey: repoKeyFor(input.cwd)
131
133
  });
132
134
  if (result.suppressed || result.noDevices) {
133
135
  return {
@@ -219,8 +221,9 @@ var decideBeforeTool = async (input) => {
219
221
  }
220
222
  try {
221
223
  const modeState = await fetchModeState(apiKey, input.session_id);
224
+ const policyRequest = policyRequestFor("gemini_cli");
222
225
  const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
223
- const config = await getPolicy(apiKey, modeState.policyVersion, "gemini_cli", false, input.session_id);
226
+ const config = await getPolicy(apiKey, modeState.policyVersion, policyRequest.agent, policyRequest.repoAware, input.session_id);
224
227
  const scopePath = modeState.scope ? scopePathFor(lookup.tool, lookup.input, input.cwd) : void 0;
225
228
  const verdict = resolveGate({
226
229
  modeState,
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-QNBA5OXQ.js";
4
+ } from "../chunk-YR5TSPXB.js";
5
5
  import "../chunk-UKSQWTBH.js";
6
6
  import "../chunk-NJ7JT26G.js";
7
- import "../chunk-37AD5K7W.js";
8
- import "../chunk-OPMSMF5R.js";
7
+ import "../chunk-BLDDXHHA.js";
8
+ import "../chunk-CCCJ6BPW.js";
9
9
  import "../chunk-RN3NOEJF.js";
10
10
  import {
11
11
  exitOnHelpFlag
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleNotification
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionDenied
4
- } from "../chunk-QNBA5OXQ.js";
4
+ } from "../chunk-YR5TSPXB.js";
5
5
  import "../chunk-UKSQWTBH.js";
6
6
  import "../chunk-NJ7JT26G.js";
7
- import "../chunk-37AD5K7W.js";
8
- import "../chunk-OPMSMF5R.js";
7
+ import "../chunk-BLDDXHHA.js";
8
+ import "../chunk-CCCJ6BPW.js";
9
9
  import "../chunk-RN3NOEJF.js";
10
10
  import "../chunk-BSZYIAZL.js";
11
11
  import "../chunk-SAF6HGAA.js";
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionRequest
4
- } from "../chunk-QNBA5OXQ.js";
4
+ } from "../chunk-YR5TSPXB.js";
5
5
  import "../chunk-UKSQWTBH.js";
6
6
  import "../chunk-NJ7JT26G.js";
7
- import "../chunk-37AD5K7W.js";
8
- import "../chunk-OPMSMF5R.js";
7
+ import "../chunk-BLDDXHHA.js";
8
+ import "../chunk-CCCJ6BPW.js";
9
9
  import "../chunk-RN3NOEJF.js";
10
10
  import "../chunk-BSZYIAZL.js";
11
11
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePostToolUse
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleUserPrompt
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionEnd
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleSessionStart
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -60,7 +60,7 @@ import {
60
60
  } from "../chunk-SEAHGHCE.js";
61
61
  import {
62
62
  reportEvent
63
- } from "../chunk-OPMSMF5R.js";
63
+ } from "../chunk-CCCJ6BPW.js";
64
64
  import "../chunk-RN3NOEJF.js";
65
65
  import {
66
66
  confirmAppConnection,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStopFailure
4
- } from "../chunk-OPMSMF5R.js";
4
+ } from "../chunk-CCCJ6BPW.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
6
  import "../chunk-BSZYIAZL.js";
7
7
  import "../chunk-SAF6HGAA.js";
@@ -2,7 +2,7 @@ import {
2
2
  isModeStateDegraded,
3
3
  resolveAutoResolveOrigin,
4
4
  resolvePolicyAcross
5
- } from "./chunk-OPMSMF5R.js";
5
+ } from "./chunk-CCCJ6BPW.js";
6
6
  import {
7
7
  evaluateScope,
8
8
  scopeChangeReason,
@@ -60,7 +60,7 @@ var CACHE_TTL_MS = 60 * 1e3;
60
60
  var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
61
61
  var policyRequestFor = (agentType) => ({
62
62
  agent: agentType,
63
- repoAware: agentType === "claude_code"
63
+ repoAware: ["claude_code", "codex", "gemini_cli"].includes(agentType)
64
64
  });
65
65
  var policyCacheFile = (apiKey, agent, repoAware = false) => {
66
66
  const parts = [apiKey, agent, repoAware ? "repoAware" : void 0].filter(Boolean);
@@ -1318,6 +1318,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1318
1318
  };
1319
1319
 
1320
1320
  export {
1321
+ policyRequestFor,
1321
1322
  getPolicy,
1322
1323
  resolveAutoResolveOrigin,
1323
1324
  resolvePolicy,
@@ -5,7 +5,7 @@ import {
5
5
  generateSessionKey,
6
6
  uploadTranscriptRecords,
7
7
  wrapSessionKey
8
- } from "./chunk-NWYGW7HY.js";
8
+ } from "./chunk-GCKAWHFP.js";
9
9
 
10
10
  // src/live-session.ts
11
11
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -72,8 +72,10 @@ var LiveTranscriptRelay = class {
72
72
  sessionKey = null;
73
73
  wrappedKey = null;
74
74
  keyUploaded = false;
75
+ keyRejected = false;
75
76
  flushing = null;
76
77
  add(value) {
78
+ if (this.keyRejected) return;
77
79
  const raw = JSON.stringify(value);
78
80
  if (raw.length > MAX_TRANSCRIPT_TEXT) return;
79
81
  if (this.queue.length === MAX_TRANSCRIPT_QUEUE) this.queue.shift();
@@ -106,14 +108,19 @@ var LiveTranscriptRelay = class {
106
108
  this.queue.splice(0, count);
107
109
  return true;
108
110
  }
109
- const uploaded = await uploadTranscriptRecords(this.options.apiKey, {
111
+ const result = await uploadTranscriptRecords(this.options.apiKey, {
110
112
  sessionId: this.sessionId,
111
113
  machineId: this.options.machineId,
112
114
  dataEncryptionKey: this.keyUploaded ? void 0 : this.wrappedKey ?? void 0,
113
115
  records
114
116
  }, this.options.baseUrl);
115
- if (uploaded) {
116
- this.keyUploaded = true;
117
+ if (result.keyConflict) {
118
+ this.keyRejected = true;
119
+ this.queue.length = 0;
120
+ return false;
121
+ }
122
+ if (result.ok) {
123
+ this.keyUploaded ||= result.keyRegistered;
117
124
  this.queue.splice(0, count);
118
125
  return true;
119
126
  }
@@ -101,9 +101,16 @@ var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) =>
101
101
  body: JSON.stringify(params),
102
102
  signal: AbortSignal.timeout(15e3)
103
103
  });
104
- return res.ok;
104
+ const body = await res.json().catch(() => null);
105
+ const keyRegistered = body?.keyRegistered === true;
106
+ const keyConflict = body?.keyConflict === true;
107
+ return {
108
+ ok: res.ok && !keyConflict && (params.dataEncryptionKey === void 0 || keyRegistered),
109
+ keyRegistered,
110
+ keyConflict
111
+ };
105
112
  } catch {
106
- return false;
113
+ return { ok: false, keyRegistered: false, keyConflict: false };
107
114
  }
108
115
  };
109
116
 
@@ -91,7 +91,11 @@ var startProcessOwnership = (identity, options = {}) => {
91
91
  proof = { ...identity, port: address.port, token };
92
92
  mkdirSync(dir, { recursive: true, mode: 448 });
93
93
  pruneProofs(dir);
94
- writeFileSync(path, JSON.stringify(proof), { encoding: "utf8", mode: 384 });
94
+ writeFileSync(path, JSON.stringify(proof), {
95
+ encoding: "utf8",
96
+ mode: 384,
97
+ flag: options.exclusive ? "wx" : "w"
98
+ });
95
99
  resolve();
96
100
  } catch {
97
101
  server.close();
@@ -113,41 +117,76 @@ var startProcessOwnership = (identity, options = {}) => {
113
117
  }
114
118
  };
115
119
  };
120
+ var withSpawnProcessOwnership = async (spawnId, run, options = {}) => {
121
+ if (!spawnId) return run();
122
+ const sessionId = `spawn:${spawnId}`;
123
+ const ownership = startProcessOwnership({
124
+ sessionId,
125
+ pid: process.pid,
126
+ spawnId
127
+ }, { ...options, exclusive: true });
128
+ await ownership.ready;
129
+ if (!await ownsLiveProcess({ sessionId, pid: process.pid, spawnId }, options)) {
130
+ ownership.stop();
131
+ throw new Error("Could not establish spawned process ownership");
132
+ }
133
+ try {
134
+ return await run();
135
+ } finally {
136
+ ownership.stop();
137
+ }
138
+ };
116
139
  var ownsLiveProcess = async (request, options = {}) => {
117
140
  const stored = readJsonSafe(proofPath(ownershipDir(options), request.sessionId));
118
141
  if (stored.kind !== "ok" || !isProof(stored.value)) return false;
119
142
  const proof = stored.value;
120
143
  if (proof.sessionId !== request.sessionId || proof.pid !== request.pid) return false;
121
144
  if (request.spawnId && proof.spawnId !== request.spawnId) return false;
122
- return new Promise((resolve) => {
123
- let settled = false;
124
- let raw = "";
125
- const socket = createConnection({ host: "127.0.0.1", port: proof.port });
126
- const finish = (owned) => {
127
- if (settled) return;
128
- settled = true;
129
- socket.destroy();
130
- resolve(owned);
131
- };
132
- socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
133
- socket.on("error", () => finish(false));
134
- socket.on("connect", () => socket.write(proof.token));
135
- socket.on("data", (chunk) => {
136
- raw += String(chunk);
137
- if (raw.length > MAX_PROOF_BYTES) finish(false);
138
- });
139
- socket.on("end", () => {
140
- try {
141
- const live = JSON.parse(raw);
142
- finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
143
- } catch {
144
- finish(false);
145
- }
146
- });
147
- });
145
+ return verifyProof(proof, options);
148
146
  };
147
+ var findLiveProcessBySpawnId = async (spawnId, options = {}) => {
148
+ try {
149
+ const dir = ownershipDir(options);
150
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
151
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
152
+ const stored = readJsonSafe(join(dir, entry.name));
153
+ if (stored.kind !== "ok" || !isProof(stored.value) || stored.value.spawnId !== spawnId) continue;
154
+ if (await verifyProof(stored.value, options)) return stored.value.pid;
155
+ }
156
+ } catch {
157
+ }
158
+ return null;
159
+ };
160
+ var verifyProof = (proof, options) => new Promise((resolve) => {
161
+ let settled = false;
162
+ let raw = "";
163
+ const socket = createConnection({ host: "127.0.0.1", port: proof.port });
164
+ const finish = (owned) => {
165
+ if (settled) return;
166
+ settled = true;
167
+ socket.destroy();
168
+ resolve(owned);
169
+ };
170
+ socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
171
+ socket.on("error", () => finish(false));
172
+ socket.on("connect", () => socket.write(proof.token));
173
+ socket.on("data", (chunk) => {
174
+ raw += String(chunk);
175
+ if (raw.length > MAX_PROOF_BYTES) finish(false);
176
+ });
177
+ socket.on("end", () => {
178
+ try {
179
+ const live = JSON.parse(raw);
180
+ finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
181
+ } catch {
182
+ finish(false);
183
+ }
184
+ });
185
+ });
149
186
 
150
187
  export {
151
188
  startProcessOwnership,
152
- ownsLiveProcess
189
+ withSpawnProcessOwnership,
190
+ ownsLiveProcess,
191
+ findLiveProcessBySpawnId
153
192
  };
@@ -9,7 +9,7 @@ import {
9
9
  denyReasonFrom,
10
10
  isDeferAnswer,
11
11
  resolveGate
12
- } from "./chunk-37AD5K7W.js";
12
+ } from "./chunk-BLDDXHHA.js";
13
13
  import {
14
14
  DEFAULT_SESSION,
15
15
  askUser,
@@ -29,7 +29,7 @@ import {
29
29
  sendNotification,
30
30
  throttlePass,
31
31
  waitForAnswer
32
- } from "./chunk-OPMSMF5R.js";
32
+ } from "./chunk-CCCJ6BPW.js";
33
33
  import {
34
34
  getMachineId
35
35
  } from "./chunk-RN3NOEJF.js";
package/dist/src/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-QNBA5OXQ.js";
3
+ } from "../chunk-YR5TSPXB.js";
4
4
  import "../chunk-UKSQWTBH.js";
5
5
  import "../chunk-NJ7JT26G.js";
6
- import "../chunk-37AD5K7W.js";
6
+ import "../chunk-BLDDXHHA.js";
7
7
  import {
8
8
  askUser,
9
9
  cancelQuestion,
@@ -16,7 +16,7 @@ import {
16
16
  reportEvent,
17
17
  resolvePolicy,
18
18
  waitForAnswer
19
- } from "../chunk-OPMSMF5R.js";
19
+ } from "../chunk-CCCJ6BPW.js";
20
20
  import "../chunk-RN3NOEJF.js";
21
21
  import "../chunk-BSZYIAZL.js";
22
22
  import "../chunk-SAF6HGAA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.84.0",
3
+ "version": "0.84.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",