@pushary/agent-hooks 0.83.2 → 0.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/dist/bin/pushary-bell-hook.js +5 -5
- package/dist/bin/pushary-bell.js +15 -14
- package/dist/bin/pushary-claude.js +93 -143
- package/dist/bin/pushary-clean.js +13 -12
- package/dist/bin/pushary-codex-bridge.d.ts +1 -0
- package/dist/bin/pushary-codex-bridge.js +349 -0
- package/dist/bin/pushary-codex-hook.js +18 -14
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +13 -12
- package/dist/bin/pushary-daemon.js +331 -41
- package/dist/bin/pushary-doctor.js +48 -25
- package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
- package/dist/bin/pushary-gemini-bridge.js +299 -0
- package/dist/bin/pushary-gemini-hook.js +28 -21
- package/dist/bin/pushary-hook.js +14 -13
- package/dist/bin/pushary-login.js +15 -14
- package/dist/bin/pushary-logout.js +12 -11
- package/dist/bin/pushary-mode.js +10 -9
- package/dist/bin/pushary-notification-hook.js +5 -5
- package/dist/bin/pushary-permission-denied-hook.js +9 -9
- package/dist/bin/pushary-permission-hook.js +9 -9
- package/dist/bin/pushary-post-hook.js +5 -5
- package/dist/bin/pushary-prompt-hook.js +5 -5
- package/dist/bin/pushary-session-end-hook.js +5 -5
- package/dist/bin/pushary-session-start-hook.js +5 -5
- package/dist/bin/pushary-setup.js +42 -36
- package/dist/bin/pushary-stats.js +6 -5
- package/dist/bin/pushary-status.js +17 -16
- package/dist/bin/pushary-stop-hook.js +5 -5
- package/dist/bin/pushary-stopfailure-hook.js +5 -5
- package/dist/bin/pushary-suggestions.js +7 -6
- package/dist/bin/pushary-upgrade.js +18 -17
- package/dist/bin/pushary-wait.js +10 -9
- package/dist/bin/pushary.js +8 -6
- package/dist/{chunk-SAA3VBDK.js → chunk-437T77TY.js} +3 -3
- package/dist/{chunk-U27BZWFT.js → chunk-6LC4WSCL.js} +1 -1
- package/dist/{chunk-7WWZPII5.js → chunk-7PZPJSWI.js} +1 -1
- package/dist/{chunk-4QQOKEOS.js → chunk-BLDDXHHA.js} +6 -6
- package/dist/{chunk-7PO2VEA4.js → chunk-CCCJ6BPW.js} +40 -34
- package/dist/chunk-CCH775Y6.js +343 -0
- package/dist/chunk-DINDL2CF.js +45 -0
- package/dist/{chunk-TXF4QZSQ.js → chunk-DOXSWT4Q.js} +1 -1
- package/dist/chunk-ERYKGH4J.js +140 -0
- package/dist/{chunk-QR6MVVQ7.js → chunk-F52SIIDG.js} +4 -4
- package/dist/{chunk-26D2YMDU.js → chunk-FBGBJXVC.js} +1 -1
- package/dist/chunk-GCKAWHFP.js +124 -0
- package/dist/{chunk-QF7WTF2C.js → chunk-HZNOAJGB.js} +2 -2
- package/dist/chunk-ILB6UOAF.js +192 -0
- package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
- package/dist/{chunk-WDRRULBH.js → chunk-K7CSRGKZ.js} +4 -4
- package/dist/{chunk-ZUKH2NPJ.js → chunk-MGJBC6DD.js} +1 -44
- package/dist/{chunk-GQLB2GBJ.js → chunk-MHBC2XUP.js} +1 -1
- package/dist/{chunk-YWRJGYUH.js → chunk-MQMPG5X4.js} +28 -2
- package/dist/{chunk-3AGM3TDM.js → chunk-NJ7JT26G.js} +1 -1
- package/dist/chunk-NRGRUOAR.js +25 -0
- package/dist/{chunk-EHLTWCDZ.js → chunk-SEAHGHCE.js} +1 -1
- package/dist/{chunk-NDC3TBHI.js → chunk-UKSQWTBH.js} +1 -1
- package/dist/{chunk-UVAC2KHH.js → chunk-UXWEE3QI.js} +1 -1
- package/dist/{chunk-MUEW424A.js → chunk-VT4IVERX.js} +50 -3
- package/dist/{chunk-DHAZ6SOT.js → chunk-YR5TSPXB.js} +81 -25
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.js +9 -9
- package/package.json +3 -1
|
@@ -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
|
-
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
EXIT
|
|
9
|
+
} from "./chunk-KZERVKTD.js";
|
|
10
10
|
import {
|
|
11
11
|
getBaseUrl
|
|
12
12
|
} from "./chunk-2UMNXADU.js";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
isValidApiKey
|
|
15
|
+
} from "./chunk-MQMPG5X4.js";
|
|
16
16
|
|
|
17
17
|
// src/session.ts
|
|
18
18
|
var IDENTITY_PATH = "/api/v1/server/identity";
|
|
@@ -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-
|
|
3
|
+
} from "./chunk-DOXSWT4Q.js";
|
|
4
4
|
import {
|
|
5
5
|
hasCodexHooks
|
|
6
|
-
} from "./chunk-
|
|
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,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-
|
|
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
|
};
|
|
@@ -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,
|
|
@@ -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
|
+
};
|