@pushary/agent-hooks 0.83.1 → 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-FFT7L2VE.js → chunk-37AD5K7W.js} +11 -11
- package/dist/{chunk-BT7NTTIW.js → chunk-437T77TY.js} +3 -3
- package/dist/{chunk-7GTDHV7O.js → chunk-6LC4WSCL.js} +1 -1
- package/dist/{chunk-UAWTJMXA.js → chunk-7PZPJSWI.js} +1 -1
- package/dist/chunk-CCH775Y6.js +343 -0
- package/dist/chunk-DINDL2CF.js +45 -0
- package/dist/{chunk-DMKSHZKF.js → chunk-DOXSWT4Q.js} +1 -1
- package/dist/chunk-EVJ6ULHN.js +153 -0
- package/dist/{chunk-CXGJTN74.js → chunk-F52SIIDG.js} +4 -4
- package/dist/{chunk-3JDIGKPV.js → chunk-FBGBJXVC.js} +1 -1
- package/dist/{chunk-V6MCBCUC.js → chunk-HZNOAJGB.js} +2 -2
- package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
- package/dist/{chunk-RZP3Z2WR.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-QA5HP7CR.js → chunk-MQMPG5X4.js} +59 -3
- package/dist/{chunk-2R63XKLG.js → chunk-NJ7JT26G.js} +1 -1
- package/dist/chunk-NRGRUOAR.js +25 -0
- package/dist/chunk-NWYGW7HY.js +117 -0
- package/dist/{chunk-C4SQY777.js → chunk-OPMSMF5R.js} +42 -63
- package/dist/{chunk-E7YX7CTM.js → chunk-QNBA5OXQ.js} +81 -25
- package/dist/{chunk-O55DONTG.js → chunk-SEAHGHCE.js} +1 -1
- package/dist/{chunk-U752S5RE.js → chunk-UKSQWTBH.js} +1 -1
- package/dist/{chunk-HKKERYZP.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",
|
|
@@ -447,6 +471,36 @@ var isSafeReadOnlyCommand = (command) => {
|
|
|
447
471
|
}
|
|
448
472
|
return true;
|
|
449
473
|
};
|
|
474
|
+
var isAutoApprove = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
|
|
475
|
+
var isAutoDeny = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
|
|
476
|
+
var verdictForPolicy = (policy) => {
|
|
477
|
+
if (isAutoDeny(policy)) return "deny";
|
|
478
|
+
if (isAutoApprove(policy)) return "allow";
|
|
479
|
+
return "requires_human";
|
|
480
|
+
};
|
|
481
|
+
var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
|
|
482
|
+
let best;
|
|
483
|
+
let bestWeight = 0;
|
|
484
|
+
let bestScoped = -1;
|
|
485
|
+
let bestLength = -1;
|
|
486
|
+
for (const candidate of policies) {
|
|
487
|
+
if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
|
|
488
|
+
if (candidate.sessionId && candidate.sessionId !== sessionId) continue;
|
|
489
|
+
const rank = matchToolPatternAny(candidate.tool, toolName, args);
|
|
490
|
+
if (rank === "none") continue;
|
|
491
|
+
const weight = matchRankWeight(rank);
|
|
492
|
+
const scoped = candidate.sessionId ? 2 : candidate.repoKey ? 1 : 0;
|
|
493
|
+
const length = rank === "prefix" ? candidate.tool.length : -1;
|
|
494
|
+
const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
|
|
495
|
+
if (better) {
|
|
496
|
+
best = { policy: candidate, rank };
|
|
497
|
+
bestWeight = weight;
|
|
498
|
+
bestScoped = scoped;
|
|
499
|
+
bestLength = length;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return best;
|
|
503
|
+
};
|
|
450
504
|
var API_KEY_PATTERN = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
|
|
451
505
|
var isValidApiKey = (value) => API_KEY_PATTERN.test(value);
|
|
452
506
|
var ACTION_BODY_MAX = 4e3;
|
|
@@ -782,18 +836,20 @@ var isScopeContract = (value) => {
|
|
|
782
836
|
|
|
783
837
|
export {
|
|
784
838
|
isApprovalMode,
|
|
839
|
+
parseAgentQuestionAnswers,
|
|
785
840
|
normalizeInstallSource,
|
|
786
841
|
HOOK_BUDGETS,
|
|
787
842
|
hookWaitDeadline,
|
|
788
843
|
hookWaitClamped,
|
|
789
844
|
effectiveWaitSeconds,
|
|
790
|
-
isRepoKeyMatch,
|
|
791
845
|
normalizeRepoRemote,
|
|
792
846
|
localRepoKey,
|
|
793
|
-
matchRankWeight,
|
|
794
847
|
policyArgForms,
|
|
795
|
-
matchToolPatternAny,
|
|
796
848
|
isSafeReadOnlyCommand,
|
|
849
|
+
isAutoApprove,
|
|
850
|
+
isAutoDeny,
|
|
851
|
+
verdictForPolicy,
|
|
852
|
+
selectGoverningRule,
|
|
797
853
|
isValidApiKey,
|
|
798
854
|
ACTION_BODY_MAX,
|
|
799
855
|
DECISION_LINE_MAX,
|
|
@@ -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,30 +10,27 @@ 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,
|
|
14
21
|
classifyDecisionRisk,
|
|
15
22
|
isApprovalMode,
|
|
16
|
-
|
|
23
|
+
isAutoApprove,
|
|
24
|
+
isAutoDeny,
|
|
17
25
|
isSafeReadOnlyCommand,
|
|
18
26
|
isScopeContract,
|
|
19
27
|
localRepoKey,
|
|
20
|
-
matchRankWeight,
|
|
21
|
-
matchToolPatternAny,
|
|
22
28
|
normalizeRepoRemote,
|
|
23
29
|
policyArgForms,
|
|
24
30
|
redactSecrets,
|
|
25
|
-
redactSecretsDeep
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
getMachineId
|
|
29
|
-
} from "./chunk-RN3NOEJF.js";
|
|
30
|
-
import {
|
|
31
|
-
getApiKey,
|
|
32
|
-
getBaseUrl
|
|
33
|
-
} from "./chunk-2UMNXADU.js";
|
|
31
|
+
redactSecretsDeep,
|
|
32
|
+
selectGoverningRule
|
|
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;
|
|
@@ -117,30 +120,6 @@ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
|
|
|
117
120
|
throw new Error("Failed to fetch policy and no cached policy available");
|
|
118
121
|
}
|
|
119
122
|
};
|
|
120
|
-
var findBestMatch = (policies, toolName, args, repoKey) => {
|
|
121
|
-
let best;
|
|
122
|
-
let bestWeight = 0;
|
|
123
|
-
let bestScoped = -1;
|
|
124
|
-
let bestLength = -1;
|
|
125
|
-
for (const candidate of policies) {
|
|
126
|
-
if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
|
|
127
|
-
const rank = matchToolPatternAny(candidate.tool, toolName, args);
|
|
128
|
-
if (rank === "none") continue;
|
|
129
|
-
const weight = matchRankWeight(rank);
|
|
130
|
-
const scoped = candidate.repoKey ? 1 : 0;
|
|
131
|
-
const length = rank === "prefix" ? candidate.tool.length : -1;
|
|
132
|
-
const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
|
|
133
|
-
if (better) {
|
|
134
|
-
best = { policy: candidate, rank };
|
|
135
|
-
bestWeight = weight;
|
|
136
|
-
bestScoped = scoped;
|
|
137
|
-
bestLength = length;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return best;
|
|
141
|
-
};
|
|
142
|
-
var isAutoApprove = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
|
|
143
|
-
var isAutoDeny = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
|
|
144
123
|
var autoApprove = (tool) => ({
|
|
145
124
|
tool,
|
|
146
125
|
timeoutSeconds: 0,
|
|
@@ -148,20 +127,20 @@ var autoApprove = (tool) => ({
|
|
|
148
127
|
mode: "terminal_only",
|
|
149
128
|
pushFirstSeconds: 0
|
|
150
129
|
});
|
|
151
|
-
var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey) => {
|
|
130
|
+
var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
|
|
152
131
|
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
153
132
|
const arg = args[0];
|
|
154
|
-
const match =
|
|
133
|
+
const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
|
|
155
134
|
const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
|
|
156
135
|
if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
|
|
157
136
|
return "safe_readonly";
|
|
158
137
|
}
|
|
159
138
|
return "policy_timeout";
|
|
160
139
|
};
|
|
161
|
-
var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey) => {
|
|
140
|
+
var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
|
|
162
141
|
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
163
142
|
const arg = args[0];
|
|
164
|
-
const match =
|
|
143
|
+
const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
|
|
165
144
|
let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
|
|
166
145
|
tool: toolName,
|
|
167
146
|
timeoutSeconds: config.defaultTimeoutSeconds,
|
|
@@ -196,9 +175,9 @@ var restraintRank = (policy) => {
|
|
|
196
175
|
return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
|
|
197
176
|
};
|
|
198
177
|
var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
|
|
199
|
-
var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey) => {
|
|
200
|
-
if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey);
|
|
201
|
-
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);
|
|
202
181
|
};
|
|
203
182
|
var isModeStateDegraded = (state) => state?.degraded === true;
|
|
204
183
|
var MODE_GRACE_MS = 60 * 1e3;
|
|
@@ -954,12 +933,13 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
|
|
|
954
933
|
};
|
|
955
934
|
var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
|
|
956
935
|
var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
957
|
-
var readFreshCachedPolicy = (apiKey, agentType) => {
|
|
936
|
+
var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
|
|
958
937
|
const request = policyRequestFor(agentType);
|
|
959
938
|
const path = policyCacheFile(apiKey, request.agent, request.repoAware);
|
|
960
939
|
if (!existsSync5(path)) return null;
|
|
961
940
|
const cached = JSON.parse(readFileSync4(path, "utf-8"));
|
|
962
941
|
if (!isPolicyConfig(cached)) return null;
|
|
942
|
+
if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
963
943
|
if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
|
|
964
944
|
return cached;
|
|
965
945
|
};
|
|
@@ -967,13 +947,13 @@ var autoDecisionEnabled = () => {
|
|
|
967
947
|
const flag = process.env.PUSHARY_AUTO_DECISION_BEACON;
|
|
968
948
|
return flag === "1" || flag === "true";
|
|
969
949
|
};
|
|
970
|
-
var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
|
|
950
|
+
var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd, sessionId) => {
|
|
971
951
|
try {
|
|
972
952
|
if (liveMode.kill) return "terminal";
|
|
973
953
|
if (!autoDecisionEnabled()) return void 0;
|
|
974
|
-
const policy = readFreshCachedPolicy(getApiKey(), agentType);
|
|
954
|
+
const policy = readFreshCachedPolicy(getApiKey(), agentType, sessionId);
|
|
975
955
|
if (!policy) return void 0;
|
|
976
|
-
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
|
|
956
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd), sessionId);
|
|
977
957
|
if (isAutoApprove(resolved)) return "policy_auto";
|
|
978
958
|
if (resolved.mode === "push_only" || resolved.mode === "push_first") return "human";
|
|
979
959
|
return "terminal";
|
|
@@ -981,16 +961,16 @@ var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
|
|
|
981
961
|
return void 0;
|
|
982
962
|
}
|
|
983
963
|
};
|
|
984
|
-
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd) => {
|
|
964
|
+
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd, sessionId) => {
|
|
985
965
|
try {
|
|
986
966
|
if (liveMode.kill) return void 0;
|
|
987
967
|
if (!autoDecisionEnabled()) return void 0;
|
|
988
|
-
const policy = readFreshCachedPolicy(getApiKey(), agentType);
|
|
968
|
+
const policy = readFreshCachedPolicy(getApiKey(), agentType, sessionId);
|
|
989
969
|
if (!policy) return void 0;
|
|
990
|
-
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
|
|
970
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd), sessionId);
|
|
991
971
|
if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
|
|
992
972
|
return {
|
|
993
|
-
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd)),
|
|
973
|
+
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd), sessionId),
|
|
994
974
|
toolName,
|
|
995
975
|
toolTarget: deriveToolTarget(toolName, toolInput)
|
|
996
976
|
};
|
|
@@ -1033,7 +1013,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1033
1013
|
machineId: event.machineId ?? getMachineId(),
|
|
1034
1014
|
// Lets the phone's Stop end this process instead of only denying its next
|
|
1035
1015
|
// tool call. Ours alone: never another process, never a parent.
|
|
1036
|
-
pid: event.pid
|
|
1016
|
+
pid: event.pid,
|
|
1037
1017
|
repoKey: event.repoKey ?? repoKeyFor(),
|
|
1038
1018
|
installMode: detectInstallMode(),
|
|
1039
1019
|
// How the binary was INVOKED this time and which front door produced the
|
|
@@ -1055,6 +1035,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1055
1035
|
}),
|
|
1056
1036
|
signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
|
|
1057
1037
|
});
|
|
1038
|
+
if (options.requireOk && !res.ok) throw new Error(`/api/agent/event rejected (${res.status})`);
|
|
1058
1039
|
try {
|
|
1059
1040
|
return await res.json();
|
|
1060
1041
|
} catch {
|
|
@@ -1080,8 +1061,8 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1080
1061
|
for (const l of late) removePendingQuestion(sessionKey, l.correlationId);
|
|
1081
1062
|
}
|
|
1082
1063
|
const isMirrorOnly = NON_GATED_MIRROR_TOOLS.has(input.tool_name);
|
|
1083
|
-
const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, agent.type, input.cwd);
|
|
1084
|
-
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;
|
|
1085
1066
|
const toolReport = reportEvent({
|
|
1086
1067
|
event: isError ? "tool_error" : "tool_complete",
|
|
1087
1068
|
agentType: agent.type,
|
|
@@ -1338,8 +1319,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1338
1319
|
|
|
1339
1320
|
export {
|
|
1340
1321
|
getPolicy,
|
|
1341
|
-
isAutoApprove,
|
|
1342
|
-
isAutoDeny,
|
|
1343
1322
|
resolveAutoResolveOrigin,
|
|
1344
1323
|
resolvePolicy,
|
|
1345
1324
|
resolvePolicyAcross,
|