@pushary/agent-hooks 0.83.2 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/dist/bin/pushary-bell-hook.js +5 -5
- package/dist/bin/pushary-bell.js +15 -14
- package/dist/bin/pushary-claude.js +72 -131
- 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 +348 -0
- package/dist/bin/pushary-codex-hook.js +15 -14
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +13 -12
- package/dist/bin/pushary-daemon.js +313 -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 +298 -0
- package/dist/bin/pushary-gemini-hook.js +24 -20
- 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-4QQOKEOS.js → chunk-37AD5K7W.js} +6 -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-CCH775Y6.js +343 -0
- package/dist/chunk-DINDL2CF.js +45 -0
- package/dist/{chunk-TXF4QZSQ.js → chunk-DOXSWT4Q.js} +1 -1
- package/dist/chunk-EVJ6ULHN.js +153 -0
- package/dist/{chunk-QR6MVVQ7.js → chunk-F52SIIDG.js} +4 -4
- package/dist/{chunk-26D2YMDU.js → chunk-FBGBJXVC.js} +1 -1
- package/dist/{chunk-QF7WTF2C.js → chunk-HZNOAJGB.js} +2 -2
- 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-NWYGW7HY.js +117 -0
- package/dist/{chunk-7PO2VEA4.js → chunk-OPMSMF5R.js} +38 -33
- package/dist/{chunk-DHAZ6SOT.js → chunk-QNBA5OXQ.js} +81 -25
- 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-YJCMI5GF.js +133 -0
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.js +9 -9
- package/package.json +3 -1
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
return res.ok;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
decodePublicKey,
|
|
112
|
+
generateSessionKey,
|
|
113
|
+
wrapSessionKey,
|
|
114
|
+
buildTranscriptRecords,
|
|
115
|
+
fetchAccountPublicKey,
|
|
116
|
+
uploadTranscriptRecords
|
|
117
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMachineId
|
|
3
|
+
} from "./chunk-RN3NOEJF.js";
|
|
1
4
|
import {
|
|
2
5
|
callMcpTool,
|
|
3
6
|
withRetry
|
|
@@ -7,7 +10,11 @@ import {
|
|
|
7
10
|
} from "./chunk-SAF6HGAA.js";
|
|
8
11
|
import {
|
|
9
12
|
reportedInstallSource
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-437T77TY.js";
|
|
14
|
+
import {
|
|
15
|
+
getApiKey,
|
|
16
|
+
getBaseUrl
|
|
17
|
+
} from "./chunk-2UMNXADU.js";
|
|
11
18
|
import {
|
|
12
19
|
ACTION_BODY_MAX,
|
|
13
20
|
DECISION_LINE_MAX,
|
|
@@ -23,14 +30,7 @@ import {
|
|
|
23
30
|
redactSecrets,
|
|
24
31
|
redactSecretsDeep,
|
|
25
32
|
selectGoverningRule
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import {
|
|
28
|
-
getMachineId
|
|
29
|
-
} from "./chunk-RN3NOEJF.js";
|
|
30
|
-
import {
|
|
31
|
-
getApiKey,
|
|
32
|
-
getBaseUrl
|
|
33
|
-
} from "./chunk-2UMNXADU.js";
|
|
33
|
+
} from "./chunk-MQMPG5X4.js";
|
|
34
34
|
|
|
35
35
|
// src/policy.ts
|
|
36
36
|
import { createHash } from "crypto";
|
|
@@ -67,12 +67,14 @@ var policyCacheFile = (apiKey, agent, repoAware = false) => {
|
|
|
67
67
|
const hash = createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
|
|
68
68
|
return join(tmpdir(), `pushary-policy-${hash}.json`);
|
|
69
69
|
};
|
|
70
|
-
var fetchPolicy = async (apiKey, agent, repoAware = false) => {
|
|
70
|
+
var fetchPolicy = async (apiKey, agent, repoAware = false, sessionId) => {
|
|
71
71
|
return withRetry(async () => {
|
|
72
72
|
const baseUrl = getBaseUrl();
|
|
73
73
|
const params = new URLSearchParams();
|
|
74
74
|
if (agent) params.set("agent", agent);
|
|
75
75
|
if (repoAware) params.set("repoAware", "1");
|
|
76
|
+
params.set("scopeAware", "1");
|
|
77
|
+
if (sessionId) params.set("sessionId", sessionId);
|
|
76
78
|
const query = params.toString();
|
|
77
79
|
const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
|
|
78
80
|
const response = await fetch(url, {
|
|
@@ -87,7 +89,7 @@ var fetchPolicy = async (apiKey, agent, repoAware = false) => {
|
|
|
87
89
|
return raw;
|
|
88
90
|
}, { maxAttempts: 2 });
|
|
89
91
|
};
|
|
90
|
-
var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
|
|
92
|
+
var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessionId) => {
|
|
91
93
|
const path = policyCacheFile(apiKey, agent, repoAware);
|
|
92
94
|
let staleCache = null;
|
|
93
95
|
if (existsSync(path)) {
|
|
@@ -96,19 +98,20 @@ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
|
|
|
96
98
|
const cached = JSON.parse(stat);
|
|
97
99
|
if (!isPolicyConfig(cached)) throw new Error("Corrupted cache");
|
|
98
100
|
const versionStale = expectedVersion != null && (cached._policyVersion ?? null) !== expectedVersion;
|
|
101
|
+
const sessionStale = (cached._sessionId ?? null) !== (sessionId ?? null);
|
|
99
102
|
const age = cached._cachedAt ? Date.now() - cached._cachedAt : 0;
|
|
100
103
|
const ttlFresh = !cached._cachedAt || age < CACHE_TTL_MS;
|
|
101
|
-
if (ttlFresh && !versionStale) {
|
|
104
|
+
if (ttlFresh && !versionStale && !sessionStale) {
|
|
102
105
|
return cached;
|
|
103
106
|
}
|
|
104
|
-
if (age < STALE_CACHE_MAX_MS) staleCache = cached;
|
|
107
|
+
if (!sessionStale && age < STALE_CACHE_MAX_MS) staleCache = cached;
|
|
105
108
|
} catch {
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
try {
|
|
109
|
-
const policy = await fetchPolicy(apiKey, agent, repoAware);
|
|
112
|
+
const policy = await fetchPolicy(apiKey, agent, repoAware, sessionId);
|
|
110
113
|
try {
|
|
111
|
-
writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null }), { encoding: "utf-8", mode: 384 });
|
|
114
|
+
writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null, _sessionId: sessionId ?? null }), { encoding: "utf-8", mode: 384 });
|
|
112
115
|
} catch {
|
|
113
116
|
}
|
|
114
117
|
return policy;
|
|
@@ -124,20 +127,20 @@ var autoApprove = (tool) => ({
|
|
|
124
127
|
mode: "terminal_only",
|
|
125
128
|
pushFirstSeconds: 0
|
|
126
129
|
});
|
|
127
|
-
var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey) => {
|
|
130
|
+
var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
|
|
128
131
|
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
129
132
|
const arg = args[0];
|
|
130
|
-
const match = selectGoverningRule(config.policies, toolName, args, repoKey);
|
|
133
|
+
const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
|
|
131
134
|
const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
|
|
132
135
|
if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
|
|
133
136
|
return "safe_readonly";
|
|
134
137
|
}
|
|
135
138
|
return "policy_timeout";
|
|
136
139
|
};
|
|
137
|
-
var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey) => {
|
|
140
|
+
var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
|
|
138
141
|
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
139
142
|
const arg = args[0];
|
|
140
|
-
const match = selectGoverningRule(config.policies, toolName, args, repoKey);
|
|
143
|
+
const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
|
|
141
144
|
let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
|
|
142
145
|
tool: toolName,
|
|
143
146
|
timeoutSeconds: config.defaultTimeoutSeconds,
|
|
@@ -172,9 +175,9 @@ var restraintRank = (policy) => {
|
|
|
172
175
|
return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
|
|
173
176
|
};
|
|
174
177
|
var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
|
|
175
|
-
var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey) => {
|
|
176
|
-
if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey);
|
|
177
|
-
return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey)).reduce(strictestPolicy);
|
|
178
|
+
var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey, sessionId) => {
|
|
179
|
+
if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey, sessionId);
|
|
180
|
+
return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey, sessionId)).reduce(strictestPolicy);
|
|
178
181
|
};
|
|
179
182
|
var isModeStateDegraded = (state) => state?.degraded === true;
|
|
180
183
|
var MODE_GRACE_MS = 60 * 1e3;
|
|
@@ -930,12 +933,13 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
|
|
|
930
933
|
};
|
|
931
934
|
var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
|
|
932
935
|
var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
933
|
-
var readFreshCachedPolicy = (apiKey, agentType) => {
|
|
936
|
+
var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
|
|
934
937
|
const request = policyRequestFor(agentType);
|
|
935
938
|
const path = policyCacheFile(apiKey, request.agent, request.repoAware);
|
|
936
939
|
if (!existsSync5(path)) return null;
|
|
937
940
|
const cached = JSON.parse(readFileSync4(path, "utf-8"));
|
|
938
941
|
if (!isPolicyConfig(cached)) return null;
|
|
942
|
+
if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
939
943
|
if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
|
|
940
944
|
return cached;
|
|
941
945
|
};
|
|
@@ -943,13 +947,13 @@ var autoDecisionEnabled = () => {
|
|
|
943
947
|
const flag = process.env.PUSHARY_AUTO_DECISION_BEACON;
|
|
944
948
|
return flag === "1" || flag === "true";
|
|
945
949
|
};
|
|
946
|
-
var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
|
|
950
|
+
var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd, sessionId) => {
|
|
947
951
|
try {
|
|
948
952
|
if (liveMode.kill) return "terminal";
|
|
949
953
|
if (!autoDecisionEnabled()) return void 0;
|
|
950
|
-
const policy = readFreshCachedPolicy(getApiKey(), agentType);
|
|
954
|
+
const policy = readFreshCachedPolicy(getApiKey(), agentType, sessionId);
|
|
951
955
|
if (!policy) return void 0;
|
|
952
|
-
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
|
|
956
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd), sessionId);
|
|
953
957
|
if (isAutoApprove(resolved)) return "policy_auto";
|
|
954
958
|
if (resolved.mode === "push_only" || resolved.mode === "push_first") return "human";
|
|
955
959
|
return "terminal";
|
|
@@ -957,16 +961,16 @@ var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
|
|
|
957
961
|
return void 0;
|
|
958
962
|
}
|
|
959
963
|
};
|
|
960
|
-
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd) => {
|
|
964
|
+
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd, sessionId) => {
|
|
961
965
|
try {
|
|
962
966
|
if (liveMode.kill) return void 0;
|
|
963
967
|
if (!autoDecisionEnabled()) return void 0;
|
|
964
|
-
const policy = readFreshCachedPolicy(getApiKey(), agentType);
|
|
968
|
+
const policy = readFreshCachedPolicy(getApiKey(), agentType, sessionId);
|
|
965
969
|
if (!policy) return void 0;
|
|
966
|
-
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
|
|
970
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd), sessionId);
|
|
967
971
|
if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
|
|
968
972
|
return {
|
|
969
|
-
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd)),
|
|
973
|
+
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd), sessionId),
|
|
970
974
|
toolName,
|
|
971
975
|
toolTarget: deriveToolTarget(toolName, toolInput)
|
|
972
976
|
};
|
|
@@ -1009,7 +1013,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1009
1013
|
machineId: event.machineId ?? getMachineId(),
|
|
1010
1014
|
// Lets the phone's Stop end this process instead of only denying its next
|
|
1011
1015
|
// tool call. Ours alone: never another process, never a parent.
|
|
1012
|
-
pid: event.pid
|
|
1016
|
+
pid: event.pid,
|
|
1013
1017
|
repoKey: event.repoKey ?? repoKeyFor(),
|
|
1014
1018
|
installMode: detectInstallMode(),
|
|
1015
1019
|
// How the binary was INVOKED this time and which front door produced the
|
|
@@ -1031,6 +1035,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1031
1035
|
}),
|
|
1032
1036
|
signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
|
|
1033
1037
|
});
|
|
1038
|
+
if (options.requireOk && !res.ok) throw new Error(`/api/agent/event rejected (${res.status})`);
|
|
1034
1039
|
try {
|
|
1035
1040
|
return await res.json();
|
|
1036
1041
|
} catch {
|
|
@@ -1056,8 +1061,8 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1056
1061
|
for (const l of late) removePendingQuestion(sessionKey, l.correlationId);
|
|
1057
1062
|
}
|
|
1058
1063
|
const isMirrorOnly = NON_GATED_MIRROR_TOOLS.has(input.tool_name);
|
|
1059
|
-
const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, agent.type, input.cwd);
|
|
1060
|
-
const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, agent.type, input.cwd) : void 0;
|
|
1064
|
+
const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, agent.type, input.cwd, input.session_id);
|
|
1065
|
+
const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, agent.type, input.cwd, input.session_id) : void 0;
|
|
1061
1066
|
const toolReport = reportEvent({
|
|
1062
1067
|
event: isError ? "tool_error" : "tool_complete",
|
|
1063
1068
|
agentType: agent.type,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRETOOLUSE_HANDLED_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UKSQWTBH.js";
|
|
4
4
|
import {
|
|
5
5
|
isGatingMoment,
|
|
6
6
|
recordKeylessMoment
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NJ7JT26G.js";
|
|
8
8
|
import {
|
|
9
9
|
denyReasonFrom,
|
|
10
10
|
isDeferAnswer,
|
|
11
11
|
resolveGate
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-37AD5K7W.js";
|
|
13
13
|
import {
|
|
14
14
|
DEFAULT_SESSION,
|
|
15
15
|
askUser,
|
|
@@ -29,13 +29,7 @@ import {
|
|
|
29
29
|
sendNotification,
|
|
30
30
|
throttlePass,
|
|
31
31
|
waitForAnswer
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import {
|
|
34
|
-
buildDecisionEpisodeFeatures,
|
|
35
|
-
effectiveWaitSeconds,
|
|
36
|
-
hookWaitClamped,
|
|
37
|
-
hookWaitDeadline
|
|
38
|
-
} from "./chunk-YWRJGYUH.js";
|
|
32
|
+
} from "./chunk-OPMSMF5R.js";
|
|
39
33
|
import {
|
|
40
34
|
getMachineId
|
|
41
35
|
} from "./chunk-RN3NOEJF.js";
|
|
@@ -43,6 +37,13 @@ import {
|
|
|
43
37
|
getApiKey,
|
|
44
38
|
getBaseUrl
|
|
45
39
|
} from "./chunk-2UMNXADU.js";
|
|
40
|
+
import {
|
|
41
|
+
buildDecisionEpisodeFeatures,
|
|
42
|
+
effectiveWaitSeconds,
|
|
43
|
+
hookWaitClamped,
|
|
44
|
+
hookWaitDeadline,
|
|
45
|
+
parseAgentQuestionAnswers
|
|
46
|
+
} from "./chunk-MQMPG5X4.js";
|
|
46
47
|
|
|
47
48
|
// src/decision-episode.ts
|
|
48
49
|
var EPISODE_PATH = "/api/agent/decision-episode";
|
|
@@ -271,6 +272,7 @@ var dispatchModeHandler = async (apiKey, input, toolPolicy, machineId, trainingC
|
|
|
271
272
|
action: deriveAction(input.tool_name, input.tool_input),
|
|
272
273
|
blocker: scopeReason ?? deriveBlocker(toolPolicy.mode),
|
|
273
274
|
actionBody: deriveActionBody(input.tool_name, input.tool_input),
|
|
275
|
+
repoKey: repoKeyFor(input.cwd),
|
|
274
276
|
// Only when the breach is what caused the question, so an ordinary approval
|
|
275
277
|
// can never silently widen a scope.
|
|
276
278
|
scopePath: scopeReason ? scopePath : void 0,
|
|
@@ -352,10 +354,25 @@ var shouldDeferToNativeMode = (permissionMode, toolName) => {
|
|
|
352
354
|
var MAX_PHONE_QUESTIONS = 4;
|
|
353
355
|
var parseQuestion = (raw) => {
|
|
354
356
|
const q = raw;
|
|
355
|
-
if (!q || typeof q.question !== "string"
|
|
356
|
-
const
|
|
357
|
+
if (!q || typeof q.question !== "string") return void 0;
|
|
358
|
+
const options = Array.isArray(q.options) ? q.options.flatMap((option) => {
|
|
359
|
+
if (!option || typeof option !== "object") return [];
|
|
360
|
+
const value = option;
|
|
361
|
+
if (typeof value.label !== "string" || value.label.length === 0) return [];
|
|
362
|
+
return [{
|
|
363
|
+
label: value.label,
|
|
364
|
+
...typeof value.description === "string" ? { description: value.description } : {}
|
|
365
|
+
}];
|
|
366
|
+
}) : [];
|
|
367
|
+
const labels = options.map((option) => option.label);
|
|
357
368
|
if (labels.length < 2) return void 0;
|
|
358
|
-
return {
|
|
369
|
+
return {
|
|
370
|
+
question: q.question,
|
|
371
|
+
header: typeof q.header === "string" ? q.header : void 0,
|
|
372
|
+
multiSelect: q.multiSelect === true,
|
|
373
|
+
options,
|
|
374
|
+
labels
|
|
375
|
+
};
|
|
359
376
|
};
|
|
360
377
|
var parseQuestions = (toolInput) => {
|
|
361
378
|
const questions = toolInput.questions;
|
|
@@ -364,20 +381,49 @@ var parseQuestions = (toolInput) => {
|
|
|
364
381
|
const parsed = [];
|
|
365
382
|
for (const raw of questions) {
|
|
366
383
|
const one = parseQuestion(raw);
|
|
367
|
-
if (!one) return void 0;
|
|
384
|
+
if (!one || parsed.some((question) => question.question === one.question)) return void 0;
|
|
368
385
|
parsed.push(one);
|
|
369
386
|
}
|
|
370
387
|
return parsed;
|
|
371
388
|
};
|
|
372
|
-
var questionContext = (
|
|
373
|
-
const subject =
|
|
389
|
+
var questionContext = (question, index, total, project) => {
|
|
390
|
+
const subject = question.header ? `${question.header}: your agent is asking in ${project}` : `Your agent is asking in ${project}`;
|
|
374
391
|
return total > 1 ? `${subject} (${index + 1} of ${total})` : subject;
|
|
375
392
|
};
|
|
376
|
-
var
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
393
|
+
var encodeAnswer = (question, value) => question.multiSelect ? JSON.stringify([value]) : value;
|
|
394
|
+
var askAsOneCard = async (apiKey, input, parsed, projectName, deadline) => {
|
|
395
|
+
const only = parsed[0];
|
|
396
|
+
let result;
|
|
397
|
+
try {
|
|
398
|
+
result = await askUser(apiKey, {
|
|
399
|
+
question: only.question,
|
|
400
|
+
type: "select",
|
|
401
|
+
options: [...only.labels],
|
|
402
|
+
questions: parsed.map(({ labels: _, ...question }) => question),
|
|
403
|
+
context: questionContext(only, 0, 1, projectName),
|
|
404
|
+
agentName: `Claude Code - ${projectName}`,
|
|
405
|
+
sessionId: input.session_id,
|
|
406
|
+
machineId: getMachineId(),
|
|
407
|
+
toolName: "AskUserQuestion",
|
|
408
|
+
toolUseId: input.tool_use_id
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
return void 0;
|
|
412
|
+
}
|
|
413
|
+
if (result.suppressed || result.noDevices) {
|
|
414
|
+
await cancelQuestion(apiKey, result.correlationId).catch(() => {
|
|
415
|
+
});
|
|
416
|
+
return void 0;
|
|
417
|
+
}
|
|
418
|
+
const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
|
|
419
|
+
const values = answer.answered && answer.value ? parseAgentQuestionAnswers(parsed, answer.value) : null;
|
|
420
|
+
if (!values || isDeferAnswer(answer.value)) {
|
|
421
|
+
savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
return values;
|
|
425
|
+
};
|
|
426
|
+
var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline) => {
|
|
381
427
|
const answers = {};
|
|
382
428
|
for (const [index, question] of parsed.entries()) {
|
|
383
429
|
let result;
|
|
@@ -410,8 +456,17 @@ var handleAskUserQuestion = async (apiKey, input) => {
|
|
|
410
456
|
savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
|
|
411
457
|
return void 0;
|
|
412
458
|
}
|
|
413
|
-
answers[question.question] = value;
|
|
459
|
+
answers[question.question] = encodeAnswer(question, value);
|
|
414
460
|
}
|
|
461
|
+
return answers;
|
|
462
|
+
};
|
|
463
|
+
var handleAskUserQuestion = async (apiKey, input) => {
|
|
464
|
+
const parsed = parseQuestions(input.tool_input ?? {});
|
|
465
|
+
if (!parsed) return void 0;
|
|
466
|
+
const projectName = basename(input.cwd ?? process.cwd());
|
|
467
|
+
const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
|
|
468
|
+
const answers = parsed.length === 1 ? await askAsOneCard(apiKey, input, parsed, projectName, deadline) : await askQuestionByQuestion(apiKey, input, parsed, projectName, deadline);
|
|
469
|
+
if (!answers) return void 0;
|
|
415
470
|
return allowWithInput({ ...input.tool_input ?? {}, answers });
|
|
416
471
|
};
|
|
417
472
|
var handleKeyless = (input) => {
|
|
@@ -438,7 +493,8 @@ var claudeGateInput = (modeState, config, input) => {
|
|
|
438
493
|
toolInputs: [input.tool_input ?? {}],
|
|
439
494
|
scopePaths: scopePath ? [scopePath] : [],
|
|
440
495
|
cwd: input.cwd,
|
|
441
|
-
repoKey: repoKeyFor(input.cwd)
|
|
496
|
+
repoKey: repoKeyFor(input.cwd),
|
|
497
|
+
sessionId: input.session_id
|
|
442
498
|
};
|
|
443
499
|
};
|
|
444
500
|
var handlePreToolUse = async (input) => {
|
|
@@ -451,7 +507,7 @@ var handlePreToolUse = async (input) => {
|
|
|
451
507
|
}
|
|
452
508
|
try {
|
|
453
509
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
454
|
-
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
|
|
510
|
+
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true, input.session_id);
|
|
455
511
|
const verdict = resolveGate(claudeGateInput(modeState, policy, input));
|
|
456
512
|
if (verdict.kind === "kill") return deny(verdict.reason);
|
|
457
513
|
if (input.tool_name === "AskUserQuestion") {
|
|
@@ -509,7 +565,7 @@ var handlePermissionRequest = async (input) => {
|
|
|
509
565
|
}
|
|
510
566
|
try {
|
|
511
567
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
512
|
-
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
|
|
568
|
+
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true, input.session_id);
|
|
513
569
|
const verdict = resolveGate(claudeGateInput(modeState, policy, input));
|
|
514
570
|
switch (verdict.kind) {
|
|
515
571
|
case "kill":
|