@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.
Files changed (64) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/bin/pushary-bell-hook.js +5 -5
  3. package/dist/bin/pushary-bell.js +15 -14
  4. package/dist/bin/pushary-claude.js +93 -143
  5. package/dist/bin/pushary-clean.js +13 -12
  6. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  7. package/dist/bin/pushary-codex-bridge.js +349 -0
  8. package/dist/bin/pushary-codex-hook.js +18 -14
  9. package/dist/bin/pushary-codex.js +7 -7
  10. package/dist/bin/pushary-connect.js +13 -12
  11. package/dist/bin/pushary-daemon.js +331 -41
  12. package/dist/bin/pushary-doctor.js +48 -25
  13. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  14. package/dist/bin/pushary-gemini-bridge.js +299 -0
  15. package/dist/bin/pushary-gemini-hook.js +28 -21
  16. package/dist/bin/pushary-hook.js +14 -13
  17. package/dist/bin/pushary-login.js +15 -14
  18. package/dist/bin/pushary-logout.js +12 -11
  19. package/dist/bin/pushary-mode.js +10 -9
  20. package/dist/bin/pushary-notification-hook.js +5 -5
  21. package/dist/bin/pushary-permission-denied-hook.js +9 -9
  22. package/dist/bin/pushary-permission-hook.js +9 -9
  23. package/dist/bin/pushary-post-hook.js +5 -5
  24. package/dist/bin/pushary-prompt-hook.js +5 -5
  25. package/dist/bin/pushary-session-end-hook.js +5 -5
  26. package/dist/bin/pushary-session-start-hook.js +5 -5
  27. package/dist/bin/pushary-setup.js +42 -36
  28. package/dist/bin/pushary-stats.js +6 -5
  29. package/dist/bin/pushary-status.js +17 -16
  30. package/dist/bin/pushary-stop-hook.js +5 -5
  31. package/dist/bin/pushary-stopfailure-hook.js +5 -5
  32. package/dist/bin/pushary-suggestions.js +7 -6
  33. package/dist/bin/pushary-upgrade.js +18 -17
  34. package/dist/bin/pushary-wait.js +10 -9
  35. package/dist/bin/pushary.js +8 -6
  36. package/dist/{chunk-SAA3VBDK.js → chunk-437T77TY.js} +3 -3
  37. package/dist/{chunk-U27BZWFT.js → chunk-6LC4WSCL.js} +1 -1
  38. package/dist/{chunk-7WWZPII5.js → chunk-7PZPJSWI.js} +1 -1
  39. package/dist/{chunk-4QQOKEOS.js → chunk-BLDDXHHA.js} +6 -6
  40. package/dist/{chunk-7PO2VEA4.js → chunk-CCCJ6BPW.js} +40 -34
  41. package/dist/chunk-CCH775Y6.js +343 -0
  42. package/dist/chunk-DINDL2CF.js +45 -0
  43. package/dist/{chunk-TXF4QZSQ.js → chunk-DOXSWT4Q.js} +1 -1
  44. package/dist/chunk-ERYKGH4J.js +140 -0
  45. package/dist/{chunk-QR6MVVQ7.js → chunk-F52SIIDG.js} +4 -4
  46. package/dist/{chunk-26D2YMDU.js → chunk-FBGBJXVC.js} +1 -1
  47. package/dist/chunk-GCKAWHFP.js +124 -0
  48. package/dist/{chunk-QF7WTF2C.js → chunk-HZNOAJGB.js} +2 -2
  49. package/dist/chunk-ILB6UOAF.js +192 -0
  50. package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
  51. package/dist/{chunk-WDRRULBH.js → chunk-K7CSRGKZ.js} +4 -4
  52. package/dist/{chunk-ZUKH2NPJ.js → chunk-MGJBC6DD.js} +1 -44
  53. package/dist/{chunk-GQLB2GBJ.js → chunk-MHBC2XUP.js} +1 -1
  54. package/dist/{chunk-YWRJGYUH.js → chunk-MQMPG5X4.js} +28 -2
  55. package/dist/{chunk-3AGM3TDM.js → chunk-NJ7JT26G.js} +1 -1
  56. package/dist/chunk-NRGRUOAR.js +25 -0
  57. package/dist/{chunk-EHLTWCDZ.js → chunk-SEAHGHCE.js} +1 -1
  58. package/dist/{chunk-NDC3TBHI.js → chunk-UKSQWTBH.js} +1 -1
  59. package/dist/{chunk-UVAC2KHH.js → chunk-UXWEE3QI.js} +1 -1
  60. package/dist/{chunk-MUEW424A.js → chunk-VT4IVERX.js} +50 -3
  61. package/dist/{chunk-DHAZ6SOT.js → chunk-YR5TSPXB.js} +81 -25
  62. package/dist/src/index.d.ts +6 -3
  63. package/dist/src/index.js +9 -9
  64. package/package.json +3 -1
@@ -0,0 +1,140 @@
1
+ import {
2
+ buildTranscriptRecords,
3
+ decodePublicKey,
4
+ fetchAccountPublicKey,
5
+ generateSessionKey,
6
+ uploadTranscriptRecords,
7
+ wrapSessionKey
8
+ } from "./chunk-GCKAWHFP.js";
9
+
10
+ // src/live-session.ts
11
+ var MAX_TRANSCRIPT_QUEUE = 100;
12
+ var MAX_TRANSCRIPT_TEXT = 32 * 1024;
13
+ var LIVE_SESSION_HEARTBEAT_MS = 6e4;
14
+ var liveSessionSleep = (ms, signal) => new Promise((resolve) => {
15
+ if (signal.aborted) return resolve();
16
+ const timer = setTimeout(done, ms);
17
+ function done() {
18
+ clearTimeout(timer);
19
+ signal.removeEventListener("abort", done);
20
+ resolve();
21
+ }
22
+ signal.addEventListener("abort", done, { once: true });
23
+ });
24
+ var postLiveSession = (options, path, body) => {
25
+ const controller = new AbortController();
26
+ const abort = () => controller.abort();
27
+ const timeout = setTimeout(abort, options.requestTimeoutMs ?? 1e4);
28
+ if (options.signal.aborted) abort();
29
+ else options.signal.addEventListener("abort", abort, { once: true });
30
+ return fetch(`${options.baseUrl}${path}`, {
31
+ method: "POST",
32
+ headers: { Authorization: `Bearer ${options.apiKey}`, "Content-Type": "application/json" },
33
+ body: JSON.stringify(body),
34
+ signal: controller.signal
35
+ }).finally(() => {
36
+ clearTimeout(timeout);
37
+ options.signal.removeEventListener("abort", abort);
38
+ });
39
+ };
40
+ var postLiveSessionUntilAccepted = async (options, path, body) => {
41
+ while (!options.signal.aborted) {
42
+ try {
43
+ const response = await postLiveSession(options, path, body);
44
+ if (response.ok) return;
45
+ if (response.status < 500 && response.status !== 429) {
46
+ throw new Error(`${path} rejected (${response.status})`);
47
+ }
48
+ } catch (error) {
49
+ if (options.signal.aborted) throw error;
50
+ if (error instanceof Error && error.message.startsWith(`${path} rejected`)) throw error;
51
+ }
52
+ await liveSessionSleep(options.pollIntervalMs ?? 2e3, options.signal);
53
+ }
54
+ throw new Error("Live session stopped before it linked");
55
+ };
56
+ var startLiveSessionHeartbeat = (options, body, intervalMs = LIVE_SESSION_HEARTBEAT_MS) => {
57
+ const timer = setInterval(() => {
58
+ if (!options.signal.aborted) {
59
+ void postLiveSession(options, "/api/agent/event", body).catch(() => {
60
+ });
61
+ }
62
+ }, intervalMs);
63
+ timer.unref();
64
+ return () => clearInterval(timer);
65
+ };
66
+ var LiveTranscriptRelay = class {
67
+ constructor(options, sessionId) {
68
+ this.options = options;
69
+ this.sessionId = sessionId;
70
+ }
71
+ queue = [];
72
+ sessionKey = null;
73
+ wrappedKey = null;
74
+ keyUploaded = false;
75
+ keyRejected = false;
76
+ flushing = null;
77
+ add(value) {
78
+ if (this.keyRejected) return;
79
+ const raw = JSON.stringify(value);
80
+ if (raw.length > MAX_TRANSCRIPT_TEXT) return;
81
+ if (this.queue.length === MAX_TRANSCRIPT_QUEUE) this.queue.shift();
82
+ this.queue.push(raw);
83
+ void this.flush();
84
+ }
85
+ async flush() {
86
+ if (this.flushing) return this.flushing;
87
+ if (this.queue.length === 0) return;
88
+ this.flushing = this.drainQueue().finally(() => {
89
+ this.flushing = null;
90
+ });
91
+ return this.flushing;
92
+ }
93
+ async drainQueue() {
94
+ while (this.queue.length > 0 && await this.flushBatch()) {
95
+ }
96
+ }
97
+ async flushBatch() {
98
+ try {
99
+ if (!this.sessionKey) {
100
+ const publicKey = await fetchAccountPublicKey(this.options.apiKey, this.options.baseUrl);
101
+ if (!publicKey) return false;
102
+ this.sessionKey = generateSessionKey();
103
+ this.wrappedKey = wrapSessionKey(this.sessionKey, decodePublicKey(publicKey));
104
+ }
105
+ const count = Math.min(this.queue.length, 50);
106
+ const records = buildTranscriptRecords(this.queue.slice(0, count), this.sessionKey).records;
107
+ if (records.length === 0) {
108
+ this.queue.splice(0, count);
109
+ return true;
110
+ }
111
+ const result = await uploadTranscriptRecords(this.options.apiKey, {
112
+ sessionId: this.sessionId,
113
+ machineId: this.options.machineId,
114
+ dataEncryptionKey: this.keyUploaded ? void 0 : this.wrappedKey ?? void 0,
115
+ records
116
+ }, this.options.baseUrl);
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;
124
+ this.queue.splice(0, count);
125
+ return true;
126
+ }
127
+ return false;
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+ };
133
+
134
+ export {
135
+ liveSessionSleep,
136
+ postLiveSession,
137
+ postLiveSessionUntilAccepted,
138
+ startLiveSessionHeartbeat,
139
+ LiveTranscriptRelay
140
+ };
@@ -5,14 +5,14 @@ import {
5
5
  NetworkError
6
6
  } from "./chunk-7QLSKOSU.js";
7
7
  import {
8
- isValidApiKey
9
- } from "./chunk-YWRJGYUH.js";
8
+ EXIT
9
+ } from "./chunk-KZERVKTD.js";
10
10
  import {
11
11
  getBaseUrl
12
12
  } from "./chunk-2UMNXADU.js";
13
13
  import {
14
- EXIT
15
- } from "./chunk-KZERVKTD.js";
14
+ isValidApiKey
15
+ } from "./chunk-MQMPG5X4.js";
16
16
 
17
17
  // src/session.ts
18
18
  var IDENTITY_PATH = "/api/v1/server/identity";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-QR6MVVQ7.js";
4
+ } from "./chunk-F52SIIDG.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -0,0 +1,124 @@
1
+ import {
2
+ getBaseUrl
3
+ } from "./chunk-2UMNXADU.js";
4
+ import {
5
+ redactSecrets
6
+ } from "./chunk-MQMPG5X4.js";
7
+
8
+ // src/crypto.ts
9
+ import nacl from "tweetnacl";
10
+ var EPH_PUB_LEN = nacl.box.publicKeyLength;
11
+ var BOX_NONCE_LEN = nacl.box.nonceLength;
12
+ var SECRETBOX_NONCE_LEN = nacl.secretbox.nonceLength;
13
+ var SESSION_KEY_LEN = nacl.secretbox.keyLength;
14
+ var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
15
+ var fromBase64 = (value) => new Uint8Array(Buffer.from(value, "base64"));
16
+ var utf8Encode = (text) => new Uint8Array(Buffer.from(text, "utf-8"));
17
+ var decodePublicKey = (value) => fromBase64(value);
18
+ var generateSessionKey = () => nacl.randomBytes(SESSION_KEY_LEN);
19
+ var wrapSessionKey = (sessionKey, accountPublicKey) => {
20
+ const ephemeral = nacl.box.keyPair();
21
+ const nonce = nacl.randomBytes(BOX_NONCE_LEN);
22
+ const box = nacl.box(sessionKey, nonce, accountPublicKey, ephemeral.secretKey);
23
+ const bundle = new Uint8Array(EPH_PUB_LEN + BOX_NONCE_LEN + box.length);
24
+ bundle.set(ephemeral.publicKey, 0);
25
+ bundle.set(nonce, EPH_PUB_LEN);
26
+ bundle.set(box, EPH_PUB_LEN + BOX_NONCE_LEN);
27
+ return toBase64(bundle);
28
+ };
29
+ var encryptRecord = (plaintext, sessionKey) => {
30
+ const nonce = nacl.randomBytes(SECRETBOX_NONCE_LEN);
31
+ const box = nacl.secretbox(utf8Encode(plaintext), nonce, sessionKey);
32
+ const bundle = new Uint8Array(SECRETBOX_NONCE_LEN + box.length);
33
+ bundle.set(nonce, 0);
34
+ bundle.set(box, SECRETBOX_NONCE_LEN);
35
+ return toBase64(bundle);
36
+ };
37
+
38
+ // src/transcript-capture.ts
39
+ import { createHash } from "crypto";
40
+ var INTERNAL_TYPES = /* @__PURE__ */ new Set([
41
+ "file-history-snapshot",
42
+ "change",
43
+ "queue-operation",
44
+ "summary"
45
+ ]);
46
+ var redactDeep = (value) => {
47
+ if (typeof value === "string") return redactSecrets(value);
48
+ if (Array.isArray(value)) return value.map(redactDeep);
49
+ if (value && typeof value === "object") {
50
+ const out = {};
51
+ for (const [key, inner] of Object.entries(value)) {
52
+ out[key] = redactDeep(inner);
53
+ }
54
+ return out;
55
+ }
56
+ return value;
57
+ };
58
+ var localIdFor = (parsed, rawLine) => typeof parsed.uuid === "string" && parsed.uuid.length > 0 ? parsed.uuid : createHash("sha256").update(rawLine).digest("hex").slice(0, 32);
59
+ var buildTranscriptRecords = (lines, sessionKey, seen = /* @__PURE__ */ new Set()) => {
60
+ const records = [];
61
+ const nextSeen = new Set(seen);
62
+ for (const line of lines) {
63
+ const trimmed = line.trim();
64
+ if (!trimmed) continue;
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(trimmed);
68
+ } catch {
69
+ continue;
70
+ }
71
+ const type = typeof parsed.type === "string" ? parsed.type : "";
72
+ if (INTERNAL_TYPES.has(type)) continue;
73
+ const localId = localIdFor(parsed, trimmed);
74
+ if (nextSeen.has(localId)) continue;
75
+ nextSeen.add(localId);
76
+ const content = encryptRecord(JSON.stringify(redactDeep(parsed)), sessionKey);
77
+ records.push({ localId, content });
78
+ }
79
+ return { records, seen: nextSeen };
80
+ };
81
+
82
+ // src/transcript-api.ts
83
+ var fetchAccountPublicKey = async (apiKey, baseUrl = getBaseUrl()) => {
84
+ try {
85
+ const res = await fetch(`${baseUrl}/api/agent/encryption-account`, {
86
+ headers: { Authorization: `Bearer ${apiKey}` },
87
+ signal: AbortSignal.timeout(5e3)
88
+ });
89
+ if (!res.ok) return null;
90
+ const data = await res.json();
91
+ return typeof data.publicKey === "string" && data.publicKey.length > 0 ? data.publicKey : null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ };
96
+ var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) => {
97
+ try {
98
+ const res = await fetch(`${baseUrl}/api/agent/transcript`, {
99
+ method: "POST",
100
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
101
+ body: JSON.stringify(params),
102
+ signal: AbortSignal.timeout(15e3)
103
+ });
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
+ };
112
+ } catch {
113
+ return { ok: false, keyRegistered: false, keyConflict: false };
114
+ }
115
+ };
116
+
117
+ export {
118
+ decodePublicKey,
119
+ generateSessionKey,
120
+ wrapSessionKey,
121
+ buildTranscriptRecords,
122
+ fetchAccountPublicKey,
123
+ uploadTranscriptRecords
124
+ };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  hasGeminiHooks
3
- } from "./chunk-TXF4QZSQ.js";
3
+ } from "./chunk-DOXSWT4Q.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-EHLTWCDZ.js";
6
+ } from "./chunk-SEAHGHCE.js";
7
7
 
8
8
  // src/diagnostics/wiring.ts
9
9
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -0,0 +1,192 @@
1
+ import {
2
+ pusharyDir
3
+ } from "./chunk-SEAHGHCE.js";
4
+ import {
5
+ readJsonSafe
6
+ } from "./chunk-6MTNS63X.js";
7
+
8
+ // src/process-ownership.ts
9
+ import { createHash, randomUUID } from "crypto";
10
+ import { mkdirSync, readdirSync, unlinkSync, writeFileSync } from "fs";
11
+ import { createConnection, createServer } from "net";
12
+ import { join } from "path";
13
+ var MAX_PROOF_BYTES = 1024;
14
+ var ownershipDir = (options) => options.dir ?? join(pusharyDir(), "processes");
15
+ var proofPath = (dir, sessionId) => join(dir, `${createHash("sha256").update(sessionId).digest("hex")}.json`);
16
+ var isProof = (value) => {
17
+ if (!value || typeof value !== "object") return false;
18
+ const proof = value;
19
+ return typeof proof.sessionId === "string" && Number.isInteger(proof.pid) && proof.pid > 1 && Number.isInteger(proof.port) && proof.port > 0 && proof.port <= 65535 && typeof proof.token === "string" && proof.token.length > 0 && (proof.spawnId === void 0 || typeof proof.spawnId === "string");
20
+ };
21
+ var removeProof = (path, token) => {
22
+ const current = readJsonSafe(path);
23
+ if (current.kind !== "ok" || current.value.token !== token) return;
24
+ try {
25
+ unlinkSync(path);
26
+ } catch {
27
+ }
28
+ };
29
+ var processIsAlive = (pid) => {
30
+ try {
31
+ process.kill(pid, 0);
32
+ return true;
33
+ } catch (error) {
34
+ return error.code === "EPERM";
35
+ }
36
+ };
37
+ var pruneProofs = (dir) => {
38
+ try {
39
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
40
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
41
+ const path = join(dir, entry.name);
42
+ const stored = readJsonSafe(path);
43
+ if (stored.kind !== "ok" || !isProof(stored.value) || !processIsAlive(stored.value.pid)) unlinkSync(path);
44
+ }
45
+ } catch {
46
+ }
47
+ };
48
+ var startProcessOwnership = (identity, options = {}) => {
49
+ const dir = ownershipDir(options);
50
+ const path = proofPath(dir, identity.sessionId);
51
+ const token = randomUUID();
52
+ let stopped = false;
53
+ let proof = null;
54
+ let settleReady = () => {
55
+ };
56
+ const server = createServer((socket) => {
57
+ let request = "";
58
+ socket.setTimeout(500, () => socket.destroy());
59
+ socket.on("error", () => {
60
+ });
61
+ socket.on("data", (chunk) => {
62
+ request += String(chunk);
63
+ if (!proof || request.length > token.length) socket.destroy();
64
+ else if (request.length === token.length) {
65
+ if (request === token) socket.end(JSON.stringify(proof));
66
+ else socket.destroy();
67
+ }
68
+ });
69
+ });
70
+ server.maxConnections = 4;
71
+ const ready = new Promise((resolve) => {
72
+ settleReady = resolve;
73
+ const resolveOnce = () => resolve();
74
+ server.once("error", resolveOnce);
75
+ server.listen(0, "127.0.0.1", () => {
76
+ server.off("error", resolveOnce);
77
+ server.on("error", () => {
78
+ });
79
+ if (stopped) {
80
+ server.close();
81
+ resolve();
82
+ return;
83
+ }
84
+ const address = server.address();
85
+ if (!address || typeof address === "string") {
86
+ server.close();
87
+ resolve();
88
+ return;
89
+ }
90
+ try {
91
+ proof = { ...identity, port: address.port, token };
92
+ mkdirSync(dir, { recursive: true, mode: 448 });
93
+ pruneProofs(dir);
94
+ writeFileSync(path, JSON.stringify(proof), {
95
+ encoding: "utf8",
96
+ mode: 384,
97
+ flag: options.exclusive ? "wx" : "w"
98
+ });
99
+ resolve();
100
+ } catch {
101
+ server.close();
102
+ resolve();
103
+ }
104
+ });
105
+ });
106
+ const cleanup = () => removeProof(path, token);
107
+ process.once("exit", cleanup);
108
+ return {
109
+ ready,
110
+ stop() {
111
+ if (stopped) return;
112
+ stopped = true;
113
+ settleReady();
114
+ process.off("exit", cleanup);
115
+ server.close();
116
+ cleanup();
117
+ }
118
+ };
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
+ };
139
+ var ownsLiveProcess = async (request, options = {}) => {
140
+ const stored = readJsonSafe(proofPath(ownershipDir(options), request.sessionId));
141
+ if (stored.kind !== "ok" || !isProof(stored.value)) return false;
142
+ const proof = stored.value;
143
+ if (proof.sessionId !== request.sessionId || proof.pid !== request.pid) return false;
144
+ if (request.spawnId && proof.spawnId !== request.spawnId) return false;
145
+ return verifyProof(proof, options);
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
+ });
186
+
187
+ export {
188
+ startProcessOwnership,
189
+ withSpawnProcessOwnership,
190
+ ownsLiveProcess,
191
+ findLiveProcessBySpawnId
192
+ };
@@ -1,7 +1,9 @@
1
1
  import {
2
- getPackageVersion,
3
2
  renderCommandHelp
4
- } from "./chunk-ZUKH2NPJ.js";
3
+ } from "./chunk-MGJBC6DD.js";
4
+ import {
5
+ getPackageVersion
6
+ } from "./chunk-DINDL2CF.js";
5
7
  import {
6
8
  EXIT
7
9
  } from "./chunk-KZERVKTD.js";
@@ -1,15 +1,15 @@
1
+ import {
2
+ resolveShellRc
3
+ } from "./chunk-BC3VCZ3E.js";
1
4
  import {
2
5
  backupFile,
3
6
  readJsonSafe,
4
7
  writeFileAtomic,
5
8
  writeJsonAtomic
6
9
  } from "./chunk-6MTNS63X.js";
7
- import {
8
- resolveShellRc
9
- } from "./chunk-BC3VCZ3E.js";
10
10
  import {
11
11
  resolveInstallSourceFor
12
- } from "./chunk-SAA3VBDK.js";
12
+ } from "./chunk-437T77TY.js";
13
13
  import {
14
14
  configFilePath
15
15
  } from "./chunk-2UMNXADU.js";
@@ -192,52 +192,9 @@ var renderCommandHelp = (name, version) => {
192
192
  ].join("\n");
193
193
  };
194
194
 
195
- // src/cli/version.ts
196
- import { readFileSync } from "fs";
197
- import { dirname, join, parse } from "path";
198
- import { fileURLToPath } from "url";
199
- var PACKAGE_NAME = "@pushary/agent-hooks";
200
- var findOwnPackageJson = (start) => {
201
- const { root } = parse(start);
202
- let dir = start;
203
- while (true) {
204
- const candidate = join(dir, "package.json");
205
- try {
206
- const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
207
- if (pkg.name === PACKAGE_NAME) return candidate;
208
- } catch {
209
- }
210
- if (dir === root) return void 0;
211
- const parent = dirname(dir);
212
- if (parent === dir) return void 0;
213
- dir = parent;
214
- }
215
- };
216
- var getPackageVersion = () => {
217
- try {
218
- const found = findOwnPackageJson(dirname(fileURLToPath(import.meta.url)));
219
- if (!found) return "0.0.0";
220
- const pkg = JSON.parse(readFileSync(found, "utf-8"));
221
- return pkg.version ?? "0.0.0";
222
- } catch {
223
- return "0.0.0";
224
- }
225
- };
226
- var isNewerVersion = (candidate, current) => {
227
- const a = candidate.split(".").map((part) => parseInt(part, 10) || 0);
228
- const b = current.split(".").map((part) => parseInt(part, 10) || 0);
229
- for (let i = 0; i < 3; i++) {
230
- if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
231
- if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
232
- }
233
- return false;
234
- };
235
-
236
195
  export {
237
196
  COMMANDS,
238
197
  isCommandName,
239
198
  renderHelp,
240
- renderCommandHelp,
241
- getPackageVersion,
242
- isNewerVersion
199
+ renderCommandHelp
243
200
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-RN3NOEJF.js";
4
4
  import {
5
5
  getPackageVersion
6
- } from "./chunk-ZUKH2NPJ.js";
6
+ } from "./chunk-DINDL2CF.js";
7
7
  import {
8
8
  getBaseUrl
9
9
  } from "./chunk-2UMNXADU.js";
@@ -1,6 +1,30 @@
1
1
  // ../contracts/src/index.ts
2
2
  var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
3
3
  var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
4
+ var parseAgentQuestionAnswers = (questions, value) => {
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(value);
8
+ } catch {
9
+ return null;
10
+ }
11
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
12
+ const answers = parsed;
13
+ if (Object.keys(answers).length !== questions.length) return null;
14
+ for (const question of questions) {
15
+ const answer = answers[question.question];
16
+ if (typeof answer !== "string" || answer.trim().length === 0) return null;
17
+ if (!question.multiSelect) continue;
18
+ try {
19
+ const selected = JSON.parse(answer);
20
+ if (!Array.isArray(selected)) continue;
21
+ const labels = new Set(question.options.map((option) => option.label));
22
+ if (selected.length === 0 || new Set(selected).size !== selected.length || !selected.every((label) => typeof label === "string" && labels.has(label))) return null;
23
+ } catch {
24
+ }
25
+ }
26
+ return answers;
27
+ };
4
28
  var INSTALL_SOURCES = [
5
29
  /** `npx @pushary/agent-hooks@latest ...`, the documented one-liner. */
6
30
  "npx",
@@ -454,17 +478,18 @@ var verdictForPolicy = (policy) => {
454
478
  if (isAutoApprove(policy)) return "allow";
455
479
  return "requires_human";
456
480
  };
457
- var selectGoverningRule = (policies, toolName, args, repoKey) => {
481
+ var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
458
482
  let best;
459
483
  let bestWeight = 0;
460
484
  let bestScoped = -1;
461
485
  let bestLength = -1;
462
486
  for (const candidate of policies) {
463
487
  if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
488
+ if (candidate.sessionId && candidate.sessionId !== sessionId) continue;
464
489
  const rank = matchToolPatternAny(candidate.tool, toolName, args);
465
490
  if (rank === "none") continue;
466
491
  const weight = matchRankWeight(rank);
467
- const scoped = candidate.repoKey ? 1 : 0;
492
+ const scoped = candidate.sessionId ? 2 : candidate.repoKey ? 1 : 0;
468
493
  const length = rank === "prefix" ? candidate.tool.length : -1;
469
494
  const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
470
495
  if (better) {
@@ -811,6 +836,7 @@ var isScopeContract = (value) => {
811
836
 
812
837
  export {
813
838
  isApprovalMode,
839
+ parseAgentQuestionAnswers,
814
840
  normalizeInstallSource,
815
841
  HOOK_BUDGETS,
816
842
  hookWaitDeadline,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-YWRJGYUH.js";
3
+ } from "./chunk-MQMPG5X4.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -0,0 +1,25 @@
1
+ // src/live-capabilities.ts
2
+ var CODEX_BRIDGE_CAPABILITY = "codex-bridge";
3
+ var CODEX_RESUME_CAPABILITY = "codex-resume";
4
+ var GEMINI_BRIDGE_CAPABILITY = "gemini-bridge";
5
+ var GEMINI_RESUME_CAPABILITY = "gemini-resume";
6
+ var GEMINI_BRIDGE_MIN_VERSION = [0, 40, 0];
7
+ var version = (raw) => {
8
+ const match = raw?.match(/^(\d+)\.(\d+)\.(\d+)/);
9
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
10
+ };
11
+ var geminiLiveCapabilities = (rawVersion) => {
12
+ const installed = version(rawVersion);
13
+ if (!installed) return [];
14
+ for (let index = 0; index < GEMINI_BRIDGE_MIN_VERSION.length; index++) {
15
+ if (installed[index] > GEMINI_BRIDGE_MIN_VERSION[index]) break;
16
+ if (installed[index] < GEMINI_BRIDGE_MIN_VERSION[index]) return [];
17
+ }
18
+ return [GEMINI_BRIDGE_CAPABILITY, GEMINI_RESUME_CAPABILITY];
19
+ };
20
+
21
+ export {
22
+ CODEX_BRIDGE_CAPABILITY,
23
+ CODEX_RESUME_CAPABILITY,
24
+ geminiLiveCapabilities
25
+ };