@pushary/agent-hooks 1.9.9 → 1.9.11
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 +15 -0
- package/README.md +1 -1
- package/dist/bin/pushary-bell-hook.js +25 -21
- package/dist/bin/pushary-bell.js +9 -9
- package/dist/bin/pushary-claude.js +31 -34
- package/dist/bin/pushary-clean.js +108 -34
- package/dist/bin/pushary-codex-bridge.js +20 -22
- package/dist/bin/pushary-codex-hook.js +33 -36
- package/dist/bin/pushary-codex.js +12 -14
- package/dist/bin/pushary-connect.js +27 -29
- package/dist/bin/pushary-cowork.js +5 -5
- package/dist/bin/pushary-daemon.js +31 -33
- package/dist/bin/pushary-disconnect.js +29 -29
- package/dist/bin/pushary-doctor.js +60 -50
- package/dist/bin/pushary-elicitation-hook.js +26 -26
- package/dist/bin/pushary-gemini-bridge.js +13 -13
- package/dist/bin/pushary-gemini-hook.js +30 -33
- package/dist/bin/pushary-hook.js +31 -33
- package/dist/bin/pushary-login.js +12 -12
- package/dist/bin/pushary-logout.js +18 -78
- package/dist/bin/pushary-mcp.js +9 -9
- package/dist/bin/pushary-mode.js +12 -12
- package/dist/bin/pushary-notification-hook.js +24 -26
- package/dist/bin/pushary-opencode-hook.js +29 -32
- package/dist/bin/pushary-permission-denied-hook.js +29 -31
- package/dist/bin/pushary-permission-hook.js +28 -30
- package/dist/bin/pushary-post-hook.js +24 -26
- package/dist/bin/pushary-prompt-hook.js +24 -26
- package/dist/bin/pushary-session-end-hook.js +24 -26
- package/dist/bin/pushary-session-start-hook.js +28 -30
- package/dist/bin/pushary-setup.js +62 -46
- package/dist/bin/pushary-stats.js +5 -5
- package/dist/bin/pushary-status.js +30 -32
- package/dist/bin/pushary-stop-hook.js +24 -26
- package/dist/bin/pushary-stopfailure-hook.js +24 -26
- package/dist/bin/pushary-suggestions.js +12 -12
- package/dist/bin/pushary-transcript-register.js +17 -17
- package/dist/bin/pushary-transcripts.js +7 -7
- package/dist/bin/pushary-upgrade.js +30 -32
- package/dist/bin/pushary-wait.js +12 -12
- package/dist/bin/pushary.js +6 -6
- package/dist/{chunk-IIVZZMSO.js → chunk-2AQ4VZSA.js} +1 -1
- package/dist/{chunk-APP43AW7.js → chunk-2GG77ADC.js} +1 -1
- package/dist/{chunk-KOWFRUIT.js → chunk-2NAQRPZQ.js} +1 -1
- package/dist/{chunk-Z23ZBYDW.js → chunk-4FWWHCYM.js} +4 -4
- package/dist/{chunk-ZGADIZDS.js → chunk-4HR2ICME.js} +1 -1
- package/dist/{chunk-E5HOHIUV.js → chunk-4RENRBSB.js} +4 -4
- package/dist/{chunk-RQ32ADJP.js → chunk-5WAYSUWV.js} +2 -2
- package/dist/{chunk-BYLKCZ2G.js → chunk-7RWQZQV2.js} +14 -14
- package/dist/{chunk-KQFTBQ66.js → chunk-A6J5Z2HO.js} +514 -313
- package/dist/{chunk-EIAEUG7G.js → chunk-AIWZRRSK.js} +2 -2
- package/dist/{chunk-H5T27W7M.js → chunk-BDRXDO25.js} +2 -2
- package/dist/{chunk-UAP5DS6P.js → chunk-BMP2BLIR.js} +3 -3
- package/dist/{chunk-VIIHC42X.js → chunk-BZI7EBTB.js} +1 -1
- package/dist/{chunk-TO3PS646.js → chunk-C6QM5SJR.js} +2 -2
- package/dist/{chunk-BNSKVIAW.js → chunk-CBDJ2X5P.js} +2 -2
- package/dist/{chunk-COQ64C4F.js → chunk-CHDAH4HP.js} +8 -8
- package/dist/{chunk-M3STIAGU.js → chunk-CZYMYM7X.js} +3 -3
- package/dist/{chunk-MD4SVNT6.js → chunk-E6YQW43K.js} +1 -1
- package/dist/{chunk-5N5L6DNN.js → chunk-EJVYEOKC.js} +3 -3
- package/dist/{chunk-AI7RMFVV.js → chunk-ERXY2SIW.js} +19 -16
- package/dist/{chunk-IRHABAQT.js → chunk-FJCOOFT7.js} +1 -1
- package/dist/{chunk-NGKUXZID.js → chunk-GA4J6YFN.js} +2 -2
- package/dist/{chunk-WRPYCIV2.js → chunk-GUNZHSTL.js} +136 -8
- package/dist/{chunk-T6ZN4TEF.js → chunk-HX5TI6DP.js} +1 -1
- package/dist/{chunk-QRXPWW3M.js → chunk-I4I4UH62.js} +3 -3
- package/dist/{chunk-6AYSKVLL.js → chunk-KBN3UP2R.js} +3 -3
- package/dist/{chunk-UF6RBP5V.js → chunk-KEV2MOJH.js} +46 -21
- package/dist/{chunk-BZLHTMT6.js → chunk-KIVFWGG6.js} +12 -10
- package/dist/{chunk-HBWYMRR5.js → chunk-LBAMPNDM.js} +5 -4
- package/dist/{chunk-LENBKYRB.js → chunk-LUHNNZSH.js} +2 -2
- package/dist/{chunk-R5RIVOPL.js → chunk-LX6EWNRK.js} +15 -3
- package/dist/{chunk-5DCDQ5VW.js → chunk-M54ATCOZ.js} +2 -2
- package/dist/{chunk-6W5M3MSE.js → chunk-MHHXR7DT.js} +4 -4
- package/dist/{chunk-NUVGDBZC.js → chunk-MITMDERZ.js} +1 -1
- package/dist/{chunk-QTIUJZRX.js → chunk-MJLTABWF.js} +3 -3
- package/dist/{chunk-QIOIO7VN.js → chunk-MX3LPP3K.js} +6 -6
- package/dist/{chunk-F7R3SZ4Q.js → chunk-N6GEI3PK.js} +2 -2
- package/dist/{chunk-462EQA6K.js → chunk-N7HXP4PZ.js} +1 -1
- package/dist/{chunk-Q6C6ILUS.js → chunk-OKSU74QH.js} +2 -2
- package/dist/{chunk-AZOKPWVI.js → chunk-P2EJN7UB.js} +1 -1
- package/dist/{chunk-OAYWO5NP.js → chunk-P7N7FJ7W.js} +1 -1
- package/dist/{chunk-DP3WX7FW.js → chunk-PRJNUNCV.js} +2 -2
- package/dist/{chunk-SZ4Q6ZVS.js → chunk-PTGUJZSD.js} +5 -5
- package/dist/chunk-SB2GN6OP.js +71 -0
- package/dist/{chunk-DCCSULXK.js → chunk-STUJXZC2.js} +4 -4
- package/dist/{chunk-MLGIVVSG.js → chunk-TVVUJTUW.js} +1 -1
- package/dist/{chunk-5OP5MQI7.js → chunk-V6IW5WMK.js} +1 -1
- package/dist/{chunk-5RGA636G.js → chunk-VOP5U2VX.js} +145 -9
- package/dist/{chunk-K545B734.js → chunk-WCELLK4P.js} +2 -2
- package/dist/{chunk-VHPFNLRS.js → chunk-WHRYW4P2.js} +5 -5
- package/dist/{chunk-G7RIDXAT.js → chunk-XYCTS3WP.js} +1 -1
- package/dist/{chunk-6EBGOHDJ.js → chunk-YETKAI5Q.js} +6 -6
- package/dist/{chunk-REU2VQPH.js → chunk-Z4AOR6RZ.js} +15 -13
- package/dist/{chunk-B5NMSP3V.js → chunk-ZTSMQYST.js} +3 -5
- package/dist/{global-upgrade-H272QOGD.js → global-upgrade-VT2JS7DR.js} +6 -6
- package/dist/{paths-HMVOTFUN.js → paths-WRBEUNEY.js} +2 -2
- package/dist/{reapply-Q5NHDTVU.js → reapply-BCR6U7HO.js} +20 -22
- package/dist/src/index.js +19 -21
- package/dist/{upgrade-lock-L3GHFIRW.js → upgrade-lock-VRYMTPZQ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-N6IOSZOE.js +0 -185
- package/dist/chunk-UPIZSCYD.js +0 -142
|
@@ -1,49 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_SESSION,
|
|
3
|
-
isDefaultSession,
|
|
4
|
-
listPendingQuestions,
|
|
5
|
-
listPendingQuestionsForToolUse,
|
|
6
|
-
markPendingCommandDelivered,
|
|
7
|
-
readPendingCommand,
|
|
8
|
-
removePendingCommand,
|
|
9
|
-
removePendingQuestion,
|
|
10
|
-
removePendingSession,
|
|
11
|
-
savePendingCommand
|
|
12
|
-
} from "./chunk-N6IOSZOE.js";
|
|
13
1
|
import {
|
|
14
2
|
IDLE_PROMPT_NOTIFICATION,
|
|
15
3
|
NOTIFICATION_PUSH_TYPES,
|
|
16
4
|
POSTTOOLUSE_MIRROR_ONLY_TOOLS
|
|
17
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-E6YQW43K.js";
|
|
18
6
|
import {
|
|
19
7
|
cancelQuestion,
|
|
8
|
+
clientErrorKindOf,
|
|
20
9
|
isPolicyConfig,
|
|
10
|
+
reportClientError,
|
|
21
11
|
sendNotification,
|
|
22
12
|
waitForAnswer
|
|
23
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GUNZHSTL.js";
|
|
24
14
|
import {
|
|
25
15
|
withRetry
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import {
|
|
28
|
-
clientErrorKindOf,
|
|
29
|
-
reportClientError
|
|
30
|
-
} from "./chunk-UPIZSCYD.js";
|
|
16
|
+
} from "./chunk-KBN3UP2R.js";
|
|
31
17
|
import {
|
|
32
18
|
getMachineId
|
|
33
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-2NAQRPZQ.js";
|
|
34
20
|
import {
|
|
35
21
|
reportedInstallSource
|
|
36
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-CBDJ2X5P.js";
|
|
37
23
|
import {
|
|
38
24
|
canonicalJson
|
|
39
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-4FWWHCYM.js";
|
|
40
26
|
import {
|
|
41
27
|
writeFileAtomic
|
|
42
28
|
} from "./chunk-T7E75EKA.js";
|
|
43
29
|
import {
|
|
44
30
|
getApiKey,
|
|
45
31
|
getBaseUrl
|
|
46
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-FJCOOFT7.js";
|
|
47
33
|
import {
|
|
48
34
|
ACTION_BODY_MAX,
|
|
49
35
|
AGENT_LABELS_BY_TYPE,
|
|
@@ -60,160 +46,19 @@ import {
|
|
|
60
46
|
gateOutputFor,
|
|
61
47
|
hookCompatibleProfileBySource,
|
|
62
48
|
isAutoApprove,
|
|
49
|
+
isTokenCount,
|
|
63
50
|
localRepoKey,
|
|
64
51
|
modeStateFromResponse,
|
|
65
52
|
normalizeRepoRemote,
|
|
66
53
|
pendingCommandEvents,
|
|
67
54
|
redactSecrets,
|
|
68
55
|
redactSecretsDeep,
|
|
56
|
+
reportableSessionId,
|
|
69
57
|
resolveAutoResolveOrigin,
|
|
70
58
|
resolvePolicy,
|
|
71
59
|
toToolLookup,
|
|
72
60
|
truncateLine
|
|
73
|
-
} from "./chunk-
|
|
74
|
-
|
|
75
|
-
// src/policy.ts
|
|
76
|
-
import { createHash } from "crypto";
|
|
77
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
78
|
-
import { join } from "path";
|
|
79
|
-
import { tmpdir } from "os";
|
|
80
|
-
var CACHE_TTL_MS = 60 * 1e3;
|
|
81
|
-
var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
|
|
82
|
-
var policyRequestFor = (agentType) => ({
|
|
83
|
-
agent: agentType,
|
|
84
|
-
repoAware: REPO_AWARE_AGENTS.includes(agentType)
|
|
85
|
-
});
|
|
86
|
-
var policyCacheFile = (apiKey, agent, repoAware = false) => {
|
|
87
|
-
const parts = [apiKey, agent, repoAware ? "repoAware" : void 0, "sessionToolAware"].filter(Boolean);
|
|
88
|
-
const hash = createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
|
|
89
|
-
return join(tmpdir(), `pushary-policy-${hash}.json`);
|
|
90
|
-
};
|
|
91
|
-
var fetchPolicy = async (apiKey, agent, repoAware = false, sessionId) => {
|
|
92
|
-
return withRetry(async () => {
|
|
93
|
-
const baseUrl = getBaseUrl();
|
|
94
|
-
const params = new URLSearchParams();
|
|
95
|
-
if (agent) params.set("agent", agent);
|
|
96
|
-
if (repoAware) params.set("repoAware", "1");
|
|
97
|
-
params.set("scopeAware", "1");
|
|
98
|
-
params.set("sessionToolAware", "1");
|
|
99
|
-
if (sessionId) params.set("sessionId", sessionId);
|
|
100
|
-
const query = params.toString();
|
|
101
|
-
const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
|
|
102
|
-
const response = await fetch(url, {
|
|
103
|
-
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
104
|
-
signal: AbortSignal.timeout(1e4)
|
|
105
|
-
});
|
|
106
|
-
if (!response.ok) {
|
|
107
|
-
throw new Error(`Failed to fetch policy: ${response.status}`);
|
|
108
|
-
}
|
|
109
|
-
const raw = await response.json();
|
|
110
|
-
if (!isPolicyConfig(raw)) throw new Error("Invalid policy response");
|
|
111
|
-
return raw;
|
|
112
|
-
}, { maxAttempts: 2 });
|
|
113
|
-
};
|
|
114
|
-
var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessionId) => {
|
|
115
|
-
const path = policyCacheFile(apiKey, agent, repoAware);
|
|
116
|
-
let staleCache = null;
|
|
117
|
-
if (existsSync(path)) {
|
|
118
|
-
try {
|
|
119
|
-
const stat = readFileSync(path, "utf-8");
|
|
120
|
-
const cached = JSON.parse(stat);
|
|
121
|
-
if (!isPolicyConfig(cached)) throw new Error("Corrupted cache");
|
|
122
|
-
const versionStale = expectedVersion != null && (cached._policyVersion ?? null) !== expectedVersion;
|
|
123
|
-
const sessionStale = (cached._sessionId ?? null) !== (sessionId ?? null);
|
|
124
|
-
const age = cached._cachedAt ? Date.now() - cached._cachedAt : 0;
|
|
125
|
-
const ttlFresh = !cached._cachedAt || age < CACHE_TTL_MS;
|
|
126
|
-
if (ttlFresh && !versionStale && !sessionStale) {
|
|
127
|
-
return cached;
|
|
128
|
-
}
|
|
129
|
-
if (!sessionStale && age < STALE_CACHE_MAX_MS) staleCache = cached;
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const policy = await fetchPolicy(apiKey, agent, repoAware, sessionId);
|
|
135
|
-
try {
|
|
136
|
-
writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null, _sessionId: sessionId ?? null }), { encoding: "utf-8", mode: 384 });
|
|
137
|
-
} catch {
|
|
138
|
-
}
|
|
139
|
-
return policy;
|
|
140
|
-
} catch {
|
|
141
|
-
if (staleCache) return staleCache;
|
|
142
|
-
throw new Error("Failed to fetch policy and no cached policy available");
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
var MODE_GRACE_MS = 60 * 1e3;
|
|
146
|
-
var modeStateCacheFile = (apiKey) => {
|
|
147
|
-
const hash = createHash("sha256").update(`mode:${apiKey}`).digest("hex").slice(0, 12);
|
|
148
|
-
return join(tmpdir(), `pushary-mode-${hash}.json`);
|
|
149
|
-
};
|
|
150
|
-
var readLastGoodMode = (apiKey) => {
|
|
151
|
-
try {
|
|
152
|
-
const path = modeStateCacheFile(apiKey);
|
|
153
|
-
if (!existsSync(path)) return null;
|
|
154
|
-
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
155
|
-
if (!parsed || typeof parsed.at !== "number" || !parsed.state) return null;
|
|
156
|
-
return parsed;
|
|
157
|
-
} catch {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
var writeLastGoodMode = (apiKey, state, sessionId) => {
|
|
162
|
-
try {
|
|
163
|
-
const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
|
|
164
|
-
writeFileSync(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
|
|
165
|
-
} catch {
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
var unknownMode = () => ({
|
|
169
|
-
mode: null,
|
|
170
|
-
kill: false,
|
|
171
|
-
policyVersion: null,
|
|
172
|
-
relayUrl: null,
|
|
173
|
-
trainingConsent: false,
|
|
174
|
-
scope: null,
|
|
175
|
-
degraded: true
|
|
176
|
-
});
|
|
177
|
-
var degradedFallback = (apiKey, authoritative = false) => {
|
|
178
|
-
if (authoritative) return unknownMode();
|
|
179
|
-
const last = readLastGoodMode(apiKey);
|
|
180
|
-
if (last && Date.now() - last.at < MODE_GRACE_MS) {
|
|
181
|
-
return { ...last.state, degraded: false };
|
|
182
|
-
}
|
|
183
|
-
return unknownMode();
|
|
184
|
-
};
|
|
185
|
-
var MODE_REUSE_MS = CACHE_TTL_MS;
|
|
186
|
-
var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
|
|
187
|
-
const last = readLastGoodMode(apiKey);
|
|
188
|
-
if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
189
|
-
if (Date.now() - last.at >= maxAgeMs) return null;
|
|
190
|
-
return { ...last.state, degraded: false };
|
|
191
|
-
};
|
|
192
|
-
var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0, signal) => {
|
|
193
|
-
if (reuseWithinMs > 0) {
|
|
194
|
-
const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
|
|
195
|
-
if (reusable) return reusable;
|
|
196
|
-
}
|
|
197
|
-
try {
|
|
198
|
-
const baseUrl = getBaseUrl();
|
|
199
|
-
const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
|
|
200
|
-
const response = await fetch(url, {
|
|
201
|
-
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
202
|
-
signal: AbortSignal.any([AbortSignal.timeout(3e3), ...signal ? [signal] : []])
|
|
203
|
-
});
|
|
204
|
-
if (!response.ok) {
|
|
205
|
-
void reportClientError({ kind: "http_status", code: `mode_http_${response.status}` });
|
|
206
|
-
return degradedFallback(apiKey, response.status === 401 || response.status === 403);
|
|
207
|
-
}
|
|
208
|
-
const state = modeStateFromResponse(await response.json());
|
|
209
|
-
writeLastGoodMode(apiKey, state, sessionId);
|
|
210
|
-
return state;
|
|
211
|
-
} catch (error) {
|
|
212
|
-
void reportClientError({ kind: clientErrorKindOf(error), code: "mode_fetch_failed" });
|
|
213
|
-
return degradedFallback(apiKey);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
|
|
61
|
+
} from "./chunk-VOP5U2VX.js";
|
|
217
62
|
|
|
218
63
|
// ../hook-mapping/src/describe.ts
|
|
219
64
|
import { isAbsolute, relative, resolve } from "path";
|
|
@@ -612,7 +457,8 @@ var AGENT_IDENTITIES = {
|
|
|
612
457
|
codebuddy: { type: "codebuddy", label: AGENT_LABELS_BY_TYPE.codebuddy },
|
|
613
458
|
qoder: { type: "qoder", label: AGENT_LABELS_BY_TYPE.qoder },
|
|
614
459
|
droid: { type: "droid", label: AGENT_LABELS_BY_TYPE.droid },
|
|
615
|
-
devin: { type: "devin", label: AGENT_LABELS_BY_TYPE.devin }
|
|
460
|
+
devin: { type: "devin", label: AGENT_LABELS_BY_TYPE.devin },
|
|
461
|
+
antigravity: { type: "antigravity_cli", label: AGENT_LABELS_BY_TYPE.antigravity_cli }
|
|
616
462
|
};
|
|
617
463
|
var TEAMMATE_IDLE_ACTION = "A teammate went idle";
|
|
618
464
|
var CLAUDE_MEANINGS = {
|
|
@@ -644,6 +490,7 @@ var MEANING_BY_HOOK = {
|
|
|
644
490
|
qoder: compatibleMeanings("qoder"),
|
|
645
491
|
droid: compatibleMeanings("droid"),
|
|
646
492
|
devin: compatibleMeanings("devin"),
|
|
493
|
+
antigravity: compatibleMeanings("antigravity_cli"),
|
|
647
494
|
codex: {
|
|
648
495
|
PostToolUse: { kind: "tool", event: "tool_complete" },
|
|
649
496
|
UserPromptSubmit: { kind: "prompt", event: "user_prompt" },
|
|
@@ -789,14 +636,7 @@ var sessionIdentityOf = (payload) => {
|
|
|
789
636
|
return { sessionId: agentId, parentSessionId: sessionId };
|
|
790
637
|
};
|
|
791
638
|
|
|
792
|
-
// ../hook-mapping/src/
|
|
793
|
-
var CODEX_TURN_CONTEXT_TYPE = "turn_context";
|
|
794
|
-
var CODEX_AUTO_REVIEWER = "auto_review";
|
|
795
|
-
var CODEX_GUARDIAN_THREAD_SOURCE = "guardian_review";
|
|
796
|
-
var CODEX_PLANNED_ACTION_MARKER = "Planned action JSON:\n";
|
|
797
|
-
var CODEX_APPROVAL_REQUEST_END = ">>> APPROVAL REQUEST END";
|
|
798
|
-
var CODEX_SHELL_TOOL = "exec_command";
|
|
799
|
-
var CODEX_SHELL_COMMAND_FLAGS = ["-lc", "-c"];
|
|
639
|
+
// ../hook-mapping/src/json-record.ts
|
|
800
640
|
var asRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
801
641
|
var parseRecord = (text) => {
|
|
802
642
|
try {
|
|
@@ -805,6 +645,15 @@ var parseRecord = (text) => {
|
|
|
805
645
|
return null;
|
|
806
646
|
}
|
|
807
647
|
};
|
|
648
|
+
|
|
649
|
+
// ../hook-mapping/src/codex-rollout.ts
|
|
650
|
+
var CODEX_TURN_CONTEXT_TYPE = "turn_context";
|
|
651
|
+
var CODEX_AUTO_REVIEWER = "auto_review";
|
|
652
|
+
var CODEX_GUARDIAN_THREAD_SOURCE = "guardian_review";
|
|
653
|
+
var CODEX_PLANNED_ACTION_MARKER = "Planned action JSON:\n";
|
|
654
|
+
var CODEX_APPROVAL_REQUEST_END = ">>> APPROVAL REQUEST END";
|
|
655
|
+
var CODEX_SHELL_TOOL = "exec_command";
|
|
656
|
+
var CODEX_SHELL_COMMAND_FLAGS = ["-lc", "-c"];
|
|
808
657
|
var nonEmptyString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
809
658
|
var isTimestamp = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
810
659
|
var readCodexTurnContext = (line) => {
|
|
@@ -888,8 +737,249 @@ var codexReviewerDenialsSince = (reviews, since) => {
|
|
|
888
737
|
});
|
|
889
738
|
};
|
|
890
739
|
|
|
740
|
+
// ../hook-mapping/src/transcript-usage.ts
|
|
741
|
+
var TRANSCRIPT_ASSISTANT_TYPE = "assistant";
|
|
742
|
+
var TRANSCRIPT_SYNTHETIC_MODEL = "<synthetic>";
|
|
743
|
+
var TRANSCRIPT_RECENT_ID_LIMIT = 200;
|
|
744
|
+
var TRANSCRIPT_USAGE_EVENTS = ["PostToolUse", "Stop"];
|
|
745
|
+
var TRANSCRIPT_USAGE_SOURCES = ["claude", "qwen", "codebuddy", "qoder", "droid", "devin"];
|
|
746
|
+
var COST_TRACKING_ENV = "PUSHARY_COST_TRACKING";
|
|
747
|
+
var COST_TRACKING_OFF = "off";
|
|
748
|
+
var TURN_END_EVENT = "Stop";
|
|
749
|
+
var DEFAULT_MODEL_PRICES = [
|
|
750
|
+
{ match: "opus-4-1", in: 15, out: 75 },
|
|
751
|
+
{ match: "opus-4-0", in: 15, out: 75 },
|
|
752
|
+
{ match: "3-opus", in: 15, out: 75 },
|
|
753
|
+
{ match: "opus", in: 5, out: 25 },
|
|
754
|
+
{ match: "haiku", in: 1, out: 5 },
|
|
755
|
+
{ match: "sonnet", in: 3, out: 15 }
|
|
756
|
+
];
|
|
757
|
+
var FALLBACK_MODEL_PRICE = { match: "", in: 3, out: 15 };
|
|
758
|
+
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
759
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
760
|
+
var TOKENS_PER_PRICE_UNIT = 1e6;
|
|
761
|
+
var isPrice = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
762
|
+
var isModelPrice = (value) => {
|
|
763
|
+
const price = asRecord(value);
|
|
764
|
+
return price !== null && typeof price.match === "string" && isPrice(price.in) && isPrice(price.out);
|
|
765
|
+
};
|
|
766
|
+
var estimateMessageCostUsd = (usage, model, prices = DEFAULT_MODEL_PRICES) => {
|
|
767
|
+
const price = prices.find((candidate) => model.includes(candidate.match)) ?? FALLBACK_MODEL_PRICE;
|
|
768
|
+
const perTokenIn = price.in / TOKENS_PER_PRICE_UNIT;
|
|
769
|
+
const perTokenOut = price.out / TOKENS_PER_PRICE_UNIT;
|
|
770
|
+
return usage.inputTokens * perTokenIn + usage.outputTokens * perTokenOut + usage.cacheCreationTokens * perTokenIn * CACHE_WRITE_MULTIPLIER + usage.cacheReadTokens * perTokenIn * CACHE_READ_MULTIPLIER;
|
|
771
|
+
};
|
|
772
|
+
var transcriptTokensIn = (usage) => usage.inputTokens + usage.cacheCreationTokens + usage.cacheReadTokens;
|
|
773
|
+
var tokenCountOf = (value) => isTokenCount(value) ? value : 0;
|
|
774
|
+
var nonEmptyString2 = (value) => typeof value === "string" && value.length > 0 ? value : null;
|
|
775
|
+
var readTranscriptMessageUsage = (line) => {
|
|
776
|
+
const entry = parseRecord(line);
|
|
777
|
+
if (entry?.type !== TRANSCRIPT_ASSISTANT_TYPE) return null;
|
|
778
|
+
const message = asRecord(entry.message);
|
|
779
|
+
const usage = asRecord(message?.usage);
|
|
780
|
+
if (!message || !usage) return null;
|
|
781
|
+
const model = typeof message.model === "string" ? message.model : "";
|
|
782
|
+
if (model.includes(TRANSCRIPT_SYNTHETIC_MODEL)) return null;
|
|
783
|
+
return {
|
|
784
|
+
...typeof message.id === "string" ? { id: message.id } : {},
|
|
785
|
+
model,
|
|
786
|
+
usage: {
|
|
787
|
+
inputTokens: tokenCountOf(usage.input_tokens),
|
|
788
|
+
outputTokens: tokenCountOf(usage.output_tokens),
|
|
789
|
+
cacheCreationTokens: tokenCountOf(usage.cache_creation_input_tokens),
|
|
790
|
+
cacheReadTokens: tokenCountOf(usage.cache_read_input_tokens)
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
};
|
|
794
|
+
var rememberTranscriptMessage = (recentIds, id) => {
|
|
795
|
+
if (!id) return { counted: true, recentIds };
|
|
796
|
+
if (recentIds.includes(id)) return { counted: false, recentIds };
|
|
797
|
+
return { counted: true, recentIds: [...recentIds, id].slice(-TRANSCRIPT_RECENT_ID_LIMIT) };
|
|
798
|
+
};
|
|
799
|
+
var isTranscriptUsageEvent = (event) => TRANSCRIPT_USAGE_EVENTS.includes(event);
|
|
800
|
+
var endsASubagentTurn = (hook) => hook.event === TURN_END_EVENT && nonEmptyString2(hook.payload.agent_id) !== null;
|
|
801
|
+
var transcriptUsageRequest = (hook) => {
|
|
802
|
+
if (hook.costTracking === COST_TRACKING_OFF) return null;
|
|
803
|
+
if (!TRANSCRIPT_USAGE_SOURCES.includes(hook.source) || !isTranscriptUsageEvent(hook.event)) return null;
|
|
804
|
+
if (endsASubagentTurn(hook)) return null;
|
|
805
|
+
const transcriptPath = nonEmptyString2(hook.payload.transcript_path);
|
|
806
|
+
const sessionId = reportableSessionId(hook.payload.session_id);
|
|
807
|
+
return transcriptPath && sessionId ? { transcriptPath, sessionId } : null;
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/pending.ts
|
|
811
|
+
import { join } from "path";
|
|
812
|
+
import { homedir, tmpdir } from "os";
|
|
813
|
+
import { createHash } from "crypto";
|
|
814
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync, unlinkSync, rmSync, statSync } from "fs";
|
|
815
|
+
var PENDING_DIR = join(tmpdir(), "pushary-pending");
|
|
816
|
+
var commandRoot = () => process.env.PUSHARY_COMMAND_DIR?.trim() || join(homedir(), ".pushary", "run", "commands");
|
|
817
|
+
var DEFAULT_SESSION = "_no_session";
|
|
818
|
+
var GRACE_MS = 10 * 60 * 1e3;
|
|
819
|
+
var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
820
|
+
var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
|
|
821
|
+
var commandDirFor = (sessionId, namespace = "") => join(
|
|
822
|
+
commandRoot(),
|
|
823
|
+
createHash("sha256").update(namespace).digest("hex").slice(0, 16),
|
|
824
|
+
sanitize(sessionId)
|
|
825
|
+
);
|
|
826
|
+
var legacyCommandFile = (sessionId, namespace = "") => join(commandDirFor(sessionId, namespace), "command.json");
|
|
827
|
+
var commandFile = (sessionId, id, namespace = "") => join(commandDirFor(sessionId, namespace), `${createHash("sha256").update(id).digest("hex")}.json`);
|
|
828
|
+
var readCommandFile = (file) => {
|
|
829
|
+
try {
|
|
830
|
+
const value = JSON.parse(readFileSync(file, "utf-8"));
|
|
831
|
+
if (typeof value.id !== "string" || typeof value.text !== "string" || typeof value.expiresAtMs !== "number") return null;
|
|
832
|
+
if (value.expiresAtMs <= Date.now()) {
|
|
833
|
+
unlinkSync(file);
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
...value,
|
|
838
|
+
delivered: value.delivered === true,
|
|
839
|
+
...value.failed === true ? { failed: true } : {}
|
|
840
|
+
};
|
|
841
|
+
} catch {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
var readPendingCommandById = (sessionId, id, namespace = "") => {
|
|
846
|
+
const current = readCommandFile(commandFile(sessionId, id, namespace));
|
|
847
|
+
if (current?.id === id) return current;
|
|
848
|
+
const legacy = readCommandFile(legacyCommandFile(sessionId, namespace));
|
|
849
|
+
return legacy?.id === id ? legacy : null;
|
|
850
|
+
};
|
|
851
|
+
var pruneExpiredCommands = (dir) => {
|
|
852
|
+
let files;
|
|
853
|
+
try {
|
|
854
|
+
files = readdirSync(dir).filter((file) => file.endsWith(".json"));
|
|
855
|
+
} catch {
|
|
856
|
+
return [];
|
|
857
|
+
}
|
|
858
|
+
return files.flatMap((file) => {
|
|
859
|
+
const command = readCommandFile(join(dir, file));
|
|
860
|
+
return command ? [command] : [];
|
|
861
|
+
});
|
|
862
|
+
};
|
|
863
|
+
var pruneExpiredCommandStore = () => {
|
|
864
|
+
let namespaces;
|
|
865
|
+
try {
|
|
866
|
+
namespaces = readdirSync(commandRoot(), { withFileTypes: true });
|
|
867
|
+
} catch {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
for (const namespace of namespaces.filter((entry) => entry.isDirectory())) {
|
|
871
|
+
const namespaceDir = join(commandRoot(), namespace.name);
|
|
872
|
+
let sessions;
|
|
873
|
+
try {
|
|
874
|
+
sessions = readdirSync(namespaceDir, { withFileTypes: true });
|
|
875
|
+
} catch {
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
for (const session of sessions.filter((entry) => entry.isDirectory())) {
|
|
879
|
+
pruneExpiredCommands(join(namespaceDir, session.name));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
var readPendingCommand = (sessionId, namespace = "") => {
|
|
884
|
+
const commands = pruneExpiredCommands(commandDirFor(sessionId, namespace));
|
|
885
|
+
return commands.find((command) => !command.delivered && !command.failed) ?? commands[0] ?? null;
|
|
886
|
+
};
|
|
887
|
+
var savePendingCommand = (sessionId, command, namespace = "") => {
|
|
888
|
+
const dir = commandDirFor(sessionId, namespace);
|
|
889
|
+
try {
|
|
890
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
891
|
+
pruneExpiredCommandStore();
|
|
892
|
+
if (readPendingCommandById(sessionId, command.id, namespace)) return false;
|
|
893
|
+
writeFileSync(commandFile(sessionId, command.id, namespace), JSON.stringify(command), { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
894
|
+
return true;
|
|
895
|
+
} catch {
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
var updatePendingCommand = (sessionId, id, update, namespace = "") => {
|
|
900
|
+
const command = readPendingCommandById(sessionId, id, namespace);
|
|
901
|
+
if (!command) return false;
|
|
902
|
+
const currentFile = commandFile(sessionId, id, namespace);
|
|
903
|
+
const file = existsSync(currentFile) ? currentFile : legacyCommandFile(sessionId, namespace);
|
|
904
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
905
|
+
try {
|
|
906
|
+
writeFileSync(temporary, JSON.stringify({ ...command, ...update, text: "" }), { encoding: "utf-8", mode: 384 });
|
|
907
|
+
renameSync(temporary, file);
|
|
908
|
+
return true;
|
|
909
|
+
} catch {
|
|
910
|
+
try {
|
|
911
|
+
unlinkSync(temporary);
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
var markPendingCommandDelivered = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: true, failed: void 0 }, namespace);
|
|
918
|
+
var markPendingCommandFailed = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: false, failed: true }, namespace);
|
|
919
|
+
var removePendingCommand = (sessionId, id, namespace = "") => {
|
|
920
|
+
if (!readPendingCommandById(sessionId, id, namespace)) return;
|
|
921
|
+
try {
|
|
922
|
+
unlinkSync(commandFile(sessionId, id, namespace));
|
|
923
|
+
} catch {
|
|
924
|
+
}
|
|
925
|
+
try {
|
|
926
|
+
if (readCommandFile(legacyCommandFile(sessionId, namespace))?.id === id) {
|
|
927
|
+
unlinkSync(legacyCommandFile(sessionId, namespace));
|
|
928
|
+
}
|
|
929
|
+
} catch {
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
|
|
933
|
+
var listPendingQuestionsForToolUse = (sessionId, toolUseId) => {
|
|
934
|
+
if (!toolUseId) return [];
|
|
935
|
+
const dir = dirFor(sessionId);
|
|
936
|
+
let files;
|
|
937
|
+
try {
|
|
938
|
+
files = readdirSync(dir);
|
|
939
|
+
} catch {
|
|
940
|
+
return [];
|
|
941
|
+
}
|
|
942
|
+
return files.filter((name) => {
|
|
943
|
+
try {
|
|
944
|
+
return readFileSync(join(dir, name), "utf-8") === toolUseId;
|
|
945
|
+
} catch {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
};
|
|
950
|
+
var listPendingQuestions = (sessionId) => {
|
|
951
|
+
const dir = dirFor(sessionId);
|
|
952
|
+
let files;
|
|
953
|
+
try {
|
|
954
|
+
files = readdirSync(dir);
|
|
955
|
+
} catch {
|
|
956
|
+
return [];
|
|
957
|
+
}
|
|
958
|
+
if (!isDefaultSession(sessionId)) return files;
|
|
959
|
+
const cutoff = Date.now() - GRACE_MS;
|
|
960
|
+
return files.filter((name) => {
|
|
961
|
+
try {
|
|
962
|
+
return statSync(join(dir, name)).mtimeMs < cutoff;
|
|
963
|
+
} catch {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
};
|
|
968
|
+
var removePendingQuestion = (sessionId, correlationId) => {
|
|
969
|
+
try {
|
|
970
|
+
unlinkSync(join(dirFor(sessionId), correlationId));
|
|
971
|
+
} catch {
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
var removePendingSession = (sessionId) => {
|
|
975
|
+
try {
|
|
976
|
+
rmSync(dirFor(sessionId), { recursive: true, force: true });
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
|
|
891
981
|
// src/repo.ts
|
|
892
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
982
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
893
983
|
import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "path";
|
|
894
984
|
var MAX_PARENT_WALK = 64;
|
|
895
985
|
var GIT_FILE_PREFIX = "gitdir:";
|
|
@@ -899,7 +989,7 @@ var findGitDir = (startDir) => {
|
|
|
899
989
|
const candidate = join2(current, ".git");
|
|
900
990
|
try {
|
|
901
991
|
if (existsSync2(candidate)) {
|
|
902
|
-
if (
|
|
992
|
+
if (statSync2(candidate).isDirectory()) return candidate;
|
|
903
993
|
const pointer = readFileSync2(candidate, "utf-8").trim();
|
|
904
994
|
if (pointer.startsWith(GIT_FILE_PREFIX)) {
|
|
905
995
|
const target = pointer.slice(GIT_FILE_PREFIX.length).trim();
|
|
@@ -972,51 +1062,172 @@ var repoKeyFor = (cwd) => {
|
|
|
972
1062
|
return derived;
|
|
973
1063
|
};
|
|
974
1064
|
|
|
975
|
-
// src/
|
|
976
|
-
import { closeSync, mkdirSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1065
|
+
// src/policy.ts
|
|
977
1066
|
import { createHash as createHash2 } from "crypto";
|
|
1067
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
978
1068
|
import { join as join3 } from "path";
|
|
979
1069
|
import { tmpdir as tmpdir2 } from "os";
|
|
980
|
-
var
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
];
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
var
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1070
|
+
var CACHE_TTL_MS = 60 * 1e3;
|
|
1071
|
+
var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
|
|
1072
|
+
var policyRequestFor = (agentType) => ({
|
|
1073
|
+
agent: agentType,
|
|
1074
|
+
repoAware: REPO_AWARE_AGENTS.includes(agentType)
|
|
1075
|
+
});
|
|
1076
|
+
var policyCacheFile = (apiKey, agent, repoAware = false) => {
|
|
1077
|
+
const parts = [apiKey, agent, repoAware ? "repoAware" : void 0, "sessionToolAware"].filter(Boolean);
|
|
1078
|
+
const hash = createHash2("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
|
|
1079
|
+
return join3(tmpdir2(), `pushary-policy-${hash}.json`);
|
|
1080
|
+
};
|
|
1081
|
+
var fetchPolicy = async (apiKey, agent, repoAware = false, sessionId) => {
|
|
1082
|
+
return withRetry(async () => {
|
|
1083
|
+
const baseUrl = getBaseUrl();
|
|
1084
|
+
const params = new URLSearchParams();
|
|
1085
|
+
if (agent) params.set("agent", agent);
|
|
1086
|
+
if (repoAware) params.set("repoAware", "1");
|
|
1087
|
+
params.set("scopeAware", "1");
|
|
1088
|
+
params.set("sessionToolAware", "1");
|
|
1089
|
+
if (sessionId) params.set("sessionId", sessionId);
|
|
1090
|
+
const query = params.toString();
|
|
1091
|
+
const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
|
|
1092
|
+
const response = await fetch(url, {
|
|
1093
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
1094
|
+
signal: AbortSignal.timeout(1e4)
|
|
1095
|
+
});
|
|
1096
|
+
if (!response.ok) {
|
|
1097
|
+
throw new Error(`Failed to fetch policy: ${response.status}`);
|
|
1098
|
+
}
|
|
1099
|
+
const raw = await response.json();
|
|
1100
|
+
if (!isPolicyConfig(raw)) throw new Error("Invalid policy response");
|
|
1101
|
+
return raw;
|
|
1102
|
+
}, { maxAttempts: 2 });
|
|
1103
|
+
};
|
|
1104
|
+
var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessionId) => {
|
|
1105
|
+
const path = policyCacheFile(apiKey, agent, repoAware);
|
|
1106
|
+
let staleCache = null;
|
|
1107
|
+
if (existsSync3(path)) {
|
|
1108
|
+
try {
|
|
1109
|
+
const stat = readFileSync3(path, "utf-8");
|
|
1110
|
+
const cached = JSON.parse(stat);
|
|
1111
|
+
if (!isPolicyConfig(cached)) throw new Error("Corrupted cache");
|
|
1112
|
+
const versionStale = expectedVersion != null && (cached._policyVersion ?? null) !== expectedVersion;
|
|
1113
|
+
const sessionStale = (cached._sessionId ?? null) !== (sessionId ?? null);
|
|
1114
|
+
const age = cached._cachedAt ? Date.now() - cached._cachedAt : 0;
|
|
1115
|
+
const ttlFresh = !cached._cachedAt || age < CACHE_TTL_MS;
|
|
1116
|
+
if (ttlFresh && !versionStale && !sessionStale) {
|
|
1117
|
+
return cached;
|
|
1118
|
+
}
|
|
1119
|
+
if (!sessionStale && age < STALE_CACHE_MAX_MS) staleCache = cached;
|
|
1120
|
+
} catch {
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
const policy = await fetchPolicy(apiKey, agent, repoAware, sessionId);
|
|
1125
|
+
try {
|
|
1126
|
+
writeFileSync2(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null, _sessionId: sessionId ?? null }), { encoding: "utf-8", mode: 384 });
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
return policy;
|
|
1130
|
+
} catch {
|
|
1131
|
+
if (staleCache) return staleCache;
|
|
1132
|
+
throw new Error("Failed to fetch policy and no cached policy available");
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
var MODE_GRACE_MS = 60 * 1e3;
|
|
1136
|
+
var modeStateCacheFile = (apiKey) => {
|
|
1137
|
+
const hash = createHash2("sha256").update(`mode:${apiKey}`).digest("hex").slice(0, 12);
|
|
1138
|
+
return join3(tmpdir2(), `pushary-mode-${hash}.json`);
|
|
1139
|
+
};
|
|
1140
|
+
var readLastGoodMode = (apiKey) => {
|
|
1141
|
+
try {
|
|
1142
|
+
const path = modeStateCacheFile(apiKey);
|
|
1143
|
+
if (!existsSync3(path)) return null;
|
|
1144
|
+
const parsed = JSON.parse(readFileSync3(path, "utf-8"));
|
|
1145
|
+
if (!parsed || typeof parsed.at !== "number" || !parsed.state) return null;
|
|
1146
|
+
return parsed;
|
|
1147
|
+
} catch {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
var writeLastGoodMode = (apiKey, state, sessionId) => {
|
|
1152
|
+
try {
|
|
1153
|
+
const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
|
|
1154
|
+
writeFileSync2(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
|
|
1155
|
+
} catch {
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
var unknownMode = () => ({
|
|
1159
|
+
mode: null,
|
|
1160
|
+
kill: false,
|
|
1161
|
+
policyVersion: null,
|
|
1162
|
+
relayUrl: null,
|
|
1163
|
+
trainingConsent: false,
|
|
1164
|
+
scope: null,
|
|
1165
|
+
degraded: true
|
|
1166
|
+
});
|
|
1167
|
+
var degradedFallback = (apiKey, authoritative = false) => {
|
|
1168
|
+
if (authoritative) return unknownMode();
|
|
1169
|
+
const last = readLastGoodMode(apiKey);
|
|
1170
|
+
if (last && Date.now() - last.at < MODE_GRACE_MS) {
|
|
1171
|
+
return { ...last.state, degraded: false };
|
|
1172
|
+
}
|
|
1173
|
+
return unknownMode();
|
|
1174
|
+
};
|
|
1175
|
+
var MODE_REUSE_MS = CACHE_TTL_MS;
|
|
1176
|
+
var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
|
|
1177
|
+
const last = readLastGoodMode(apiKey);
|
|
1178
|
+
if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
1179
|
+
if (Date.now() - last.at >= maxAgeMs) return null;
|
|
1180
|
+
return { ...last.state, degraded: false };
|
|
1181
|
+
};
|
|
1182
|
+
var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0, signal) => {
|
|
1183
|
+
if (reuseWithinMs > 0) {
|
|
1184
|
+
const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
|
|
1185
|
+
if (reusable) return reusable;
|
|
1186
|
+
}
|
|
1187
|
+
try {
|
|
1188
|
+
const baseUrl = getBaseUrl();
|
|
1189
|
+
const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
|
|
1190
|
+
const response = await fetch(url, {
|
|
1191
|
+
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
1192
|
+
signal: AbortSignal.any([AbortSignal.timeout(3e3), ...signal ? [signal] : []])
|
|
1193
|
+
});
|
|
1194
|
+
if (!response.ok) {
|
|
1195
|
+
void reportClientError({ kind: "http_status", code: `mode_http_${response.status}` });
|
|
1196
|
+
return degradedFallback(apiKey, response.status === 401 || response.status === 403);
|
|
1197
|
+
}
|
|
1198
|
+
const state = modeStateFromResponse(await response.json());
|
|
1199
|
+
writeLastGoodMode(apiKey, state, sessionId);
|
|
1200
|
+
return state;
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
void reportClientError({ kind: clientErrorKindOf(error), code: "mode_fetch_failed" });
|
|
1203
|
+
return degradedFallback(apiKey);
|
|
1204
|
+
}
|
|
997
1205
|
};
|
|
1206
|
+
var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
|
|
1207
|
+
|
|
1208
|
+
// src/usage.ts
|
|
1209
|
+
import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync4, readSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1210
|
+
import { createHash as createHash3 } from "crypto";
|
|
1211
|
+
import { join as join4 } from "path";
|
|
1212
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
1213
|
+
var READ_CHUNK_BYTES = 1024 * 1024;
|
|
998
1214
|
var priceTable = () => {
|
|
999
1215
|
const raw = process.env.PUSHARY_MODEL_PRICING?.trim();
|
|
1000
|
-
if (!raw) return
|
|
1216
|
+
if (!raw) return DEFAULT_MODEL_PRICES;
|
|
1001
1217
|
try {
|
|
1002
1218
|
const parsed = JSON.parse(raw);
|
|
1003
1219
|
if (Array.isArray(parsed) && parsed.length > 0 && parsed.every(isModelPrice)) return parsed;
|
|
1004
1220
|
} catch {
|
|
1005
1221
|
}
|
|
1006
|
-
return
|
|
1007
|
-
};
|
|
1008
|
-
var estimateCostUsd = (usage, model) => {
|
|
1009
|
-
const price = priceTable().find((p) => model.includes(p.match)) ?? FALLBACK_PRICE;
|
|
1010
|
-
const perTokenIn = price.in / 1e6;
|
|
1011
|
-
const perTokenOut = price.out / 1e6;
|
|
1012
|
-
return usage.inputTokens * perTokenIn + usage.outputTokens * perTokenOut + usage.cacheCreationTokens * perTokenIn * CACHE_WRITE_MULTIPLIER + usage.cacheReadTokens * perTokenIn * CACHE_READ_MULTIPLIER;
|
|
1222
|
+
return DEFAULT_MODEL_PRICES;
|
|
1013
1223
|
};
|
|
1014
|
-
var
|
|
1015
|
-
var
|
|
1224
|
+
var estimateCostUsd = (usage, model) => estimateMessageCostUsd(usage, model, priceTable());
|
|
1225
|
+
var stateDir = () => process.env.PUSHARY_USAGE_DIR?.trim() || join4(tmpdir3(), "pushary-usage");
|
|
1226
|
+
var stateFile = (sessionId) => join4(stateDir(), sessionId.replace(/[^a-zA-Z0-9_-]/g, "_"));
|
|
1016
1227
|
var emptyState = () => ({ offset: 0, tokensIn: 0, tokensOut: 0, costUsd: 0, recentIds: [] });
|
|
1017
1228
|
var readState = (path) => {
|
|
1018
1229
|
try {
|
|
1019
|
-
const parsed = JSON.parse(
|
|
1230
|
+
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
1020
1231
|
if (typeof parsed.offset === "number" && parsed.offset >= 0 && typeof parsed.tokensIn === "number" && typeof parsed.tokensOut === "number" && typeof parsed.costUsd === "number" && Array.isArray(parsed.recentIds)) {
|
|
1021
1232
|
return {
|
|
1022
1233
|
offset: parsed.offset,
|
|
@@ -1048,34 +1259,15 @@ var readRange = (path, start, end) => {
|
|
|
1048
1259
|
closeSync(fd);
|
|
1049
1260
|
}
|
|
1050
1261
|
};
|
|
1051
|
-
var toCount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
1052
1262
|
var applyLine = (state, line) => {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
if (!usage || typeof usage !== "object") return 0;
|
|
1062
|
-
const model = typeof parsed.message?.model === "string" ? parsed.message.model : "";
|
|
1063
|
-
if (model.includes("<synthetic>")) return 0;
|
|
1064
|
-
const id = typeof parsed.message?.id === "string" ? parsed.message.id : null;
|
|
1065
|
-
if (id) {
|
|
1066
|
-
if (state.recentIds.includes(id)) return 0;
|
|
1067
|
-
state.recentIds.push(id);
|
|
1068
|
-
if (state.recentIds.length > RECENT_ID_LIMIT) state.recentIds.splice(0, state.recentIds.length - RECENT_ID_LIMIT);
|
|
1069
|
-
}
|
|
1070
|
-
const messageUsage = {
|
|
1071
|
-
inputTokens: toCount(usage.input_tokens),
|
|
1072
|
-
outputTokens: toCount(usage.output_tokens),
|
|
1073
|
-
cacheCreationTokens: toCount(usage.cache_creation_input_tokens),
|
|
1074
|
-
cacheReadTokens: toCount(usage.cache_read_input_tokens)
|
|
1075
|
-
};
|
|
1076
|
-
const cost = estimateCostUsd(messageUsage, model);
|
|
1077
|
-
state.tokensIn += messageUsage.inputTokens + messageUsage.cacheCreationTokens + messageUsage.cacheReadTokens;
|
|
1078
|
-
state.tokensOut += messageUsage.outputTokens;
|
|
1263
|
+
const message = readTranscriptMessageUsage(line);
|
|
1264
|
+
if (!message) return 0;
|
|
1265
|
+
const memory = rememberTranscriptMessage(state.recentIds, message.id);
|
|
1266
|
+
if (!memory.counted) return 0;
|
|
1267
|
+
state.recentIds = [...memory.recentIds];
|
|
1268
|
+
const cost = estimateCostUsd(message.usage, message.model);
|
|
1269
|
+
state.tokensIn += transcriptTokensIn(message.usage);
|
|
1270
|
+
state.tokensOut += message.usage.outputTokens;
|
|
1079
1271
|
state.costUsd += cost;
|
|
1080
1272
|
return cost;
|
|
1081
1273
|
};
|
|
@@ -1091,7 +1283,7 @@ var extractUserText = (content) => {
|
|
|
1091
1283
|
};
|
|
1092
1284
|
var readLastUserPrompt = (transcriptPath) => {
|
|
1093
1285
|
try {
|
|
1094
|
-
const size =
|
|
1286
|
+
const size = statSync3(transcriptPath).size;
|
|
1095
1287
|
const start = Math.max(0, size - USER_PROMPT_TAIL_BYTES);
|
|
1096
1288
|
const lines = readRange(transcriptPath, start, size).toString("utf-8").split("\n");
|
|
1097
1289
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
@@ -1114,7 +1306,7 @@ var readLastUserPrompt = (transcriptPath) => {
|
|
|
1114
1306
|
};
|
|
1115
1307
|
var readNewUsage = (transcriptPath, sessionId) => {
|
|
1116
1308
|
try {
|
|
1117
|
-
const file =
|
|
1309
|
+
const file = statSync3(transcriptPath);
|
|
1118
1310
|
const size = file.size;
|
|
1119
1311
|
const path = stateFile(sessionId);
|
|
1120
1312
|
let state = readState(path);
|
|
@@ -1132,14 +1324,14 @@ var readNewUsage = (transcriptPath, sessionId) => {
|
|
|
1132
1324
|
state.offset += lastNewline + 1;
|
|
1133
1325
|
}
|
|
1134
1326
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1327
|
+
mkdirSync2(stateDir(), { recursive: true });
|
|
1328
|
+
writeFileSync3(path, JSON.stringify(state), "utf-8");
|
|
1137
1329
|
if (state.tokensIn === 0 && state.tokensOut === 0) return null;
|
|
1138
1330
|
return {
|
|
1139
1331
|
// Identical cursor-range reports share an identity across transport retries.
|
|
1140
1332
|
// Native message identity distinguishes an in-place rotation of the range.
|
|
1141
1333
|
// This does not serialize readers of different, potentially overlapping ranges.
|
|
1142
|
-
eventId:
|
|
1334
|
+
eventId: createHash3("sha256").update(JSON.stringify([
|
|
1143
1335
|
sessionId,
|
|
1144
1336
|
file.dev,
|
|
1145
1337
|
file.ino,
|
|
@@ -1215,10 +1407,10 @@ var permissionTimeoutDecision = (timeoutAction) => {
|
|
|
1215
1407
|
var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
|
|
1216
1408
|
|
|
1217
1409
|
// src/codex-call-decision.ts
|
|
1218
|
-
import { createHash as
|
|
1219
|
-
import { existsSync as
|
|
1220
|
-
import { tmpdir as
|
|
1221
|
-
import { join as
|
|
1410
|
+
import { createHash as createHash4 } from "crypto";
|
|
1411
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1412
|
+
import { tmpdir as tmpdir4, userInfo } from "os";
|
|
1413
|
+
import { join as join5 } from "path";
|
|
1222
1414
|
var CODEX_DECISION_TTL_MS = 60 * 1e3;
|
|
1223
1415
|
var userScope = () => {
|
|
1224
1416
|
try {
|
|
@@ -1229,8 +1421,8 @@ var userScope = () => {
|
|
|
1229
1421
|
return "default";
|
|
1230
1422
|
}
|
|
1231
1423
|
};
|
|
1232
|
-
var decisionDir = () =>
|
|
1233
|
-
var decisionFile = (key) =>
|
|
1424
|
+
var decisionDir = () => join5(tmpdir4(), `pushary-codex-decisions-${userScope()}`);
|
|
1425
|
+
var decisionFile = (key) => join5(decisionDir(), key);
|
|
1234
1426
|
var PRESENTATION_ONLY_INPUT_KEYS = ["description"];
|
|
1235
1427
|
var identifyingToolInput = (toolInput) => {
|
|
1236
1428
|
if (!toolInput) return {};
|
|
@@ -1249,7 +1441,7 @@ var codexCallKey = (identity) => {
|
|
|
1249
1441
|
identity.toolName,
|
|
1250
1442
|
identifyingToolInput(identity.toolInput)
|
|
1251
1443
|
]);
|
|
1252
|
-
return
|
|
1444
|
+
return createHash4("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
|
|
1253
1445
|
};
|
|
1254
1446
|
var parseDecision = (raw) => {
|
|
1255
1447
|
try {
|
|
@@ -1268,10 +1460,10 @@ var parseDecision = (raw) => {
|
|
|
1268
1460
|
};
|
|
1269
1461
|
var sweepExpired = (dir, nowMs) => {
|
|
1270
1462
|
try {
|
|
1271
|
-
for (const name of
|
|
1272
|
-
const path =
|
|
1463
|
+
for (const name of readdirSync2(dir)) {
|
|
1464
|
+
const path = join5(dir, name);
|
|
1273
1465
|
try {
|
|
1274
|
-
if (nowMs -
|
|
1466
|
+
if (nowMs - statSync4(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync2(path, { force: true });
|
|
1275
1467
|
} catch {
|
|
1276
1468
|
continue;
|
|
1277
1469
|
}
|
|
@@ -1284,9 +1476,9 @@ var recordCodexCallDecision = (key, decision) => {
|
|
|
1284
1476
|
if (!key) return;
|
|
1285
1477
|
try {
|
|
1286
1478
|
const dir = decisionDir();
|
|
1287
|
-
if (!
|
|
1479
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
1288
1480
|
else sweepExpired(dir, Date.now());
|
|
1289
|
-
|
|
1481
|
+
writeFileSync4(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
|
|
1290
1482
|
} catch {
|
|
1291
1483
|
return;
|
|
1292
1484
|
}
|
|
@@ -1295,9 +1487,9 @@ var consumeCodexCallDecision = (key) => {
|
|
|
1295
1487
|
if (!key) return null;
|
|
1296
1488
|
const path = decisionFile(key);
|
|
1297
1489
|
try {
|
|
1298
|
-
const fresh = Date.now() -
|
|
1299
|
-
const raw =
|
|
1300
|
-
|
|
1490
|
+
const fresh = Date.now() - statSync4(path).mtimeMs < CODEX_DECISION_TTL_MS;
|
|
1491
|
+
const raw = readFileSync5(path, "utf-8");
|
|
1492
|
+
rmSync2(path, { force: true });
|
|
1301
1493
|
return fresh ? parseDecision(raw) : null;
|
|
1302
1494
|
} catch {
|
|
1303
1495
|
return null;
|
|
@@ -1314,32 +1506,32 @@ var phoneDrivenEnv = (base) => ({
|
|
|
1314
1506
|
});
|
|
1315
1507
|
|
|
1316
1508
|
// src/throttle.ts
|
|
1317
|
-
import { join as
|
|
1318
|
-
import { tmpdir as
|
|
1319
|
-
import { createHash as
|
|
1320
|
-
import { existsSync as
|
|
1321
|
-
var THROTTLE_DIR =
|
|
1322
|
-
var markerPath = (key) =>
|
|
1509
|
+
import { join as join6 } from "path";
|
|
1510
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
1511
|
+
import { createHash as createHash5 } from "crypto";
|
|
1512
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync5, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "fs";
|
|
1513
|
+
var THROTTLE_DIR = join6(tmpdir5(), "pushary-throttle");
|
|
1514
|
+
var markerPath = (key) => join6(THROTTLE_DIR, createHash5("sha256").update(key).digest("hex").slice(0, 16));
|
|
1323
1515
|
var claimThrottle = (key, windowMs) => {
|
|
1324
1516
|
try {
|
|
1325
1517
|
const path = markerPath(key);
|
|
1326
|
-
if (
|
|
1327
|
-
|
|
1328
|
-
|
|
1518
|
+
if (existsSync5(path) && Date.now() - statSync5(path).mtimeMs < windowMs) return "throttled";
|
|
1519
|
+
mkdirSync4(THROTTLE_DIR, { recursive: true });
|
|
1520
|
+
writeFileSync5(path, "", "utf-8");
|
|
1329
1521
|
return "passed";
|
|
1330
1522
|
} catch {
|
|
1331
1523
|
return "unavailable";
|
|
1332
1524
|
}
|
|
1333
1525
|
};
|
|
1334
1526
|
var releaseThrottle = (key) => {
|
|
1335
|
-
|
|
1527
|
+
rmSync3(markerPath(key), { force: true });
|
|
1336
1528
|
};
|
|
1337
1529
|
|
|
1338
1530
|
// src/blocked-action-hint.ts
|
|
1339
|
-
import { createHash as
|
|
1340
|
-
import { existsSync as
|
|
1341
|
-
import { tmpdir as
|
|
1342
|
-
import { join as
|
|
1531
|
+
import { createHash as createHash6 } from "crypto";
|
|
1532
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync6, utimesSync } from "fs";
|
|
1533
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
1534
|
+
import { join as join7 } from "path";
|
|
1343
1535
|
var BLOCKED_ACTION_HINT_TTL_MS = BLOCKED_ACTION_GRANT_TTL_SECONDS * 1e3;
|
|
1344
1536
|
var FINGERPRINT = /^[0-9a-f]{64}$/;
|
|
1345
1537
|
var isFingerprint = (value) => typeof value === "string" && FINGERPRINT.test(value);
|
|
@@ -1354,17 +1546,17 @@ var blockedActionFingerprint = ({ source = "claude", sessionId, toolName, toolIn
|
|
|
1354
1546
|
const [canonicalInput] = canonical.toolInputs;
|
|
1355
1547
|
if (!canonicalInput) return null;
|
|
1356
1548
|
const subject = blockedActionSubject(source, sessionId, canonical.toolName, canonicalInput, cwd);
|
|
1357
|
-
return subject ?
|
|
1549
|
+
return subject ? createHash6("sha256").update(blockedActionIdentity(subject), "utf8").digest("hex") : null;
|
|
1358
1550
|
};
|
|
1359
|
-
var hintDir = () =>
|
|
1360
|
-
var hintFile = (fingerprint) =>
|
|
1551
|
+
var hintDir = () => join7(tmpdir6(), `pushary-blocked-action-hints-${userScope()}`);
|
|
1552
|
+
var hintFile = (fingerprint) => join7(hintDir(), fingerprint);
|
|
1361
1553
|
var isExpired = (mtimeMs, nowMs) => nowMs - mtimeMs >= BLOCKED_ACTION_HINT_TTL_MS;
|
|
1362
1554
|
var sweepExpired2 = (dir, nowMs) => {
|
|
1363
1555
|
try {
|
|
1364
|
-
for (const name of
|
|
1365
|
-
const path =
|
|
1556
|
+
for (const name of readdirSync3(dir)) {
|
|
1557
|
+
const path = join7(dir, name);
|
|
1366
1558
|
try {
|
|
1367
|
-
if (isExpired(
|
|
1559
|
+
if (isExpired(statSync6(path).mtimeMs, nowMs)) rmSync4(path, { force: true });
|
|
1368
1560
|
} catch {
|
|
1369
1561
|
continue;
|
|
1370
1562
|
}
|
|
@@ -1377,7 +1569,7 @@ var recordBlockedActionHint = (fingerprint, approved) => {
|
|
|
1377
1569
|
if (!isFingerprint(fingerprint)) return false;
|
|
1378
1570
|
try {
|
|
1379
1571
|
const dir = hintDir();
|
|
1380
|
-
if (!
|
|
1572
|
+
if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
1381
1573
|
else sweepExpired2(dir, Date.now());
|
|
1382
1574
|
writeFileAtomic(hintFile(fingerprint), JSON.stringify({ fingerprint, ...approved }), 384);
|
|
1383
1575
|
return true;
|
|
@@ -1388,13 +1580,13 @@ var recordBlockedActionHint = (fingerprint, approved) => {
|
|
|
1388
1580
|
var readLiveHint = (fingerprint) => {
|
|
1389
1581
|
const path = hintFile(fingerprint);
|
|
1390
1582
|
try {
|
|
1391
|
-
const stat =
|
|
1583
|
+
const stat = statSync6(path);
|
|
1392
1584
|
if (!stat.isFile()) return null;
|
|
1393
1585
|
if (isExpired(stat.mtimeMs, Date.now())) {
|
|
1394
|
-
|
|
1586
|
+
rmSync4(path, { force: true });
|
|
1395
1587
|
return null;
|
|
1396
1588
|
}
|
|
1397
|
-
const parsed = JSON.parse(
|
|
1589
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
1398
1590
|
return parsed?.fingerprint === fingerprint ? parsed : null;
|
|
1399
1591
|
} catch {
|
|
1400
1592
|
return null;
|
|
@@ -1409,7 +1601,7 @@ var approvalIn = (fingerprint, sessionId) => {
|
|
|
1409
1601
|
var approvalAwaitingRetry = (sessionId) => {
|
|
1410
1602
|
if (!sessionId) return null;
|
|
1411
1603
|
try {
|
|
1412
|
-
for (const name of
|
|
1604
|
+
for (const name of readdirSync3(hintDir())) {
|
|
1413
1605
|
const approval = isFingerprint(name) ? approvalIn(name, sessionId) : null;
|
|
1414
1606
|
if (approval) return approval;
|
|
1415
1607
|
}
|
|
@@ -1421,7 +1613,7 @@ var approvalAwaitingRetry = (sessionId) => {
|
|
|
1421
1613
|
var markApprovalReminded = (approval) => {
|
|
1422
1614
|
const path = hintFile(approval.fingerprint);
|
|
1423
1615
|
try {
|
|
1424
|
-
const { atime, mtime } =
|
|
1616
|
+
const { atime, mtime } = statSync6(path);
|
|
1425
1617
|
const { fingerprint, sessionId, toolName } = approval;
|
|
1426
1618
|
writeFileAtomic(path, JSON.stringify({ fingerprint, sessionId, toolName, reminded: true }), 384);
|
|
1427
1619
|
utimesSync(path, atime, mtime);
|
|
@@ -1432,64 +1624,64 @@ var markApprovalReminded = (approval) => {
|
|
|
1432
1624
|
var forgetBlockedActionHint = (fingerprint) => {
|
|
1433
1625
|
if (!isFingerprint(fingerprint)) return;
|
|
1434
1626
|
try {
|
|
1435
|
-
|
|
1627
|
+
rmSync4(hintFile(fingerprint), { force: true });
|
|
1436
1628
|
} catch {
|
|
1437
1629
|
return;
|
|
1438
1630
|
}
|
|
1439
1631
|
};
|
|
1440
1632
|
|
|
1441
1633
|
// src/background-work.ts
|
|
1442
|
-
import { createHash as
|
|
1443
|
-
import { mkdirSync as
|
|
1444
|
-
import { tmpdir as
|
|
1445
|
-
import { join as
|
|
1634
|
+
import { createHash as createHash7 } from "crypto";
|
|
1635
|
+
import { mkdirSync as mkdirSync6, rmSync as rmSync5, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
1636
|
+
import { tmpdir as tmpdir7 } from "os";
|
|
1637
|
+
import { join as join8 } from "path";
|
|
1446
1638
|
var BACKGROUND_WORK_TTL_MS = 60 * 60 * 1e3;
|
|
1447
|
-
var backgroundWorkDir = () =>
|
|
1448
|
-
var markerFor = (sessionKey) =>
|
|
1639
|
+
var backgroundWorkDir = () => join8(tmpdir7(), "pushary-background-work");
|
|
1640
|
+
var markerFor = (sessionKey) => join8(backgroundWorkDir(), createHash7("sha256").update(sessionKey).digest("hex"));
|
|
1449
1641
|
var hasRunningBackgroundTasks = (backgroundTasks) => Array.isArray(backgroundTasks) && backgroundTasks.length > 0;
|
|
1450
1642
|
var noteBackgroundWork = (sessionKey, running) => {
|
|
1451
1643
|
try {
|
|
1452
1644
|
if (!running) {
|
|
1453
|
-
|
|
1645
|
+
rmSync5(markerFor(sessionKey), { force: true });
|
|
1454
1646
|
return;
|
|
1455
1647
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1648
|
+
mkdirSync6(backgroundWorkDir(), { recursive: true, mode: 448 });
|
|
1649
|
+
writeFileSync6(markerFor(sessionKey), "", { mode: 384 });
|
|
1458
1650
|
} catch {
|
|
1459
1651
|
return;
|
|
1460
1652
|
}
|
|
1461
1653
|
};
|
|
1462
1654
|
var isWaitingOnBackgroundWork = (sessionKey, nowMs = Date.now()) => {
|
|
1463
1655
|
try {
|
|
1464
|
-
return nowMs -
|
|
1656
|
+
return nowMs - statSync7(markerFor(sessionKey)).mtimeMs < BACKGROUND_WORK_TTL_MS;
|
|
1465
1657
|
} catch {
|
|
1466
1658
|
return false;
|
|
1467
1659
|
}
|
|
1468
1660
|
};
|
|
1469
1661
|
|
|
1470
1662
|
// src/events.ts
|
|
1471
|
-
import { basename as basename3, join as
|
|
1472
|
-
import { existsSync as
|
|
1473
|
-
import { tmpdir as
|
|
1474
|
-
var INTENT_DIR =
|
|
1663
|
+
import { basename as basename3, join as join9 } from "path";
|
|
1664
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
1665
|
+
import { tmpdir as tmpdir8 } from "os";
|
|
1666
|
+
var INTENT_DIR = join9(tmpdir8(), "pushary-intent");
|
|
1475
1667
|
var INTENT_MAX_BYTES = 2048;
|
|
1476
1668
|
var INTENT_GRACE_MS = 10 * 60 * 1e3;
|
|
1477
1669
|
var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
1478
|
-
var intentFile = (sessionId) =>
|
|
1670
|
+
var intentFile = (sessionId) => join9(INTENT_DIR, sanitizeSession(sessionId));
|
|
1479
1671
|
var saveLastPrompt = (sessionId, prompt) => {
|
|
1480
1672
|
try {
|
|
1481
1673
|
const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
|
|
1482
1674
|
if (!trimmed) return;
|
|
1483
|
-
|
|
1484
|
-
|
|
1675
|
+
mkdirSync7(INTENT_DIR, { recursive: true });
|
|
1676
|
+
writeFileSync7(intentFile(sessionId), trimmed, "utf-8");
|
|
1485
1677
|
} catch {
|
|
1486
1678
|
}
|
|
1487
1679
|
};
|
|
1488
1680
|
var readLastPrompt = (sessionId) => {
|
|
1489
1681
|
try {
|
|
1490
1682
|
const path = intentFile(sessionId);
|
|
1491
|
-
if (isDefaultSession(sessionId) &&
|
|
1492
|
-
const value =
|
|
1683
|
+
if (isDefaultSession(sessionId) && statSync8(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
|
|
1684
|
+
const value = readFileSync7(path, "utf-8").trim();
|
|
1493
1685
|
return value.length > 0 ? value : void 0;
|
|
1494
1686
|
} catch {
|
|
1495
1687
|
return void 0;
|
|
@@ -1553,8 +1745,8 @@ var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
1553
1745
|
var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
|
|
1554
1746
|
const request = policyRequestFor(agentType);
|
|
1555
1747
|
const path = policyCacheFile(apiKey, request.agent, request.repoAware);
|
|
1556
|
-
if (!
|
|
1557
|
-
const cached = JSON.parse(
|
|
1748
|
+
if (!existsSync7(path)) return null;
|
|
1749
|
+
const cached = JSON.parse(readFileSync7(path, "utf-8"));
|
|
1558
1750
|
if (!isPolicyConfig(cached)) return null;
|
|
1559
1751
|
if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
1560
1752
|
if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
|
|
@@ -1597,10 +1789,12 @@ var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd, s
|
|
|
1597
1789
|
};
|
|
1598
1790
|
var AUTO_DECISION_WINDOW_MS = 15 * 60 * 1e3;
|
|
1599
1791
|
var HOOK_ACTIVE_WINDOW_MS = 30 * 60 * 1e3;
|
|
1600
|
-
var deriveUsage = (
|
|
1601
|
-
|
|
1792
|
+
var deriveUsage = (payload, agent, event) => {
|
|
1793
|
+
const source = HOOK_SOURCES.find((candidate) => AGENT_IDENTITIES[candidate].type === agent.type);
|
|
1794
|
+
const request = source ? transcriptUsageRequest({ source, event, payload, costTracking: process.env[COST_TRACKING_ENV] }) : null;
|
|
1795
|
+
if (!request) return void 0;
|
|
1602
1796
|
try {
|
|
1603
|
-
return readNewUsage(transcriptPath, sessionId
|
|
1797
|
+
return readNewUsage(request.transcriptPath, request.sessionId) ?? void 0;
|
|
1604
1798
|
} catch {
|
|
1605
1799
|
return void 0;
|
|
1606
1800
|
}
|
|
@@ -1789,7 +1983,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1789
1983
|
decisionSource,
|
|
1790
1984
|
...beacon ? { decisionOrigin: beacon.decisionOrigin, toolName: beacon.toolName, toolTarget: beacon.toolTarget } : {},
|
|
1791
1985
|
meta: receiptsEnabled ? deriveReceiptMeta(lookup.tool, lookup.input, toolResult, input.cwd ?? process.cwd()) : void 0,
|
|
1792
|
-
usage: deriveUsage(input
|
|
1986
|
+
usage: deriveUsage(input, agent, "PostToolUse")
|
|
1793
1987
|
}, {
|
|
1794
1988
|
maxAttempts: 1,
|
|
1795
1989
|
budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())),
|
|
@@ -1868,7 +2062,7 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1868
2062
|
agentName: `${agent.label} - ${projectName}`,
|
|
1869
2063
|
action: summarizeStop(input),
|
|
1870
2064
|
...sessionIdentityOf(input),
|
|
1871
|
-
usage: deriveUsage(input
|
|
2065
|
+
usage: deriveUsage(input, agent, "Stop")
|
|
1872
2066
|
}, { maxAttempts: 1, budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())), signal })
|
|
1873
2067
|
];
|
|
1874
2068
|
if (late.length > 0) {
|
|
@@ -2053,11 +2247,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
2053
2247
|
};
|
|
2054
2248
|
|
|
2055
2249
|
export {
|
|
2056
|
-
policyRequestFor,
|
|
2057
|
-
getPolicy,
|
|
2058
|
-
fetchModeState,
|
|
2059
|
-
fetchModeOverride,
|
|
2060
|
-
phoneDrivenEnv,
|
|
2061
2250
|
HOOK_FALLBACK_MAX,
|
|
2062
2251
|
UNNAMED_ACTION,
|
|
2063
2252
|
describeToolCall,
|
|
@@ -2075,9 +2264,21 @@ export {
|
|
|
2075
2264
|
isCodexGuardianRolloutHead,
|
|
2076
2265
|
scanCodexGuardianReviews,
|
|
2077
2266
|
codexReviewerDenialsSince,
|
|
2267
|
+
phoneDrivenEnv,
|
|
2268
|
+
DEFAULT_SESSION,
|
|
2269
|
+
readPendingCommandById,
|
|
2270
|
+
readPendingCommand,
|
|
2271
|
+
savePendingCommand,
|
|
2272
|
+
markPendingCommandDelivered,
|
|
2273
|
+
markPendingCommandFailed,
|
|
2274
|
+
removePendingCommand,
|
|
2078
2275
|
repoKeyFor,
|
|
2079
2276
|
claimThrottle,
|
|
2080
2277
|
releaseThrottle,
|
|
2278
|
+
policyRequestFor,
|
|
2279
|
+
getPolicy,
|
|
2280
|
+
fetchModeState,
|
|
2281
|
+
fetchModeOverride,
|
|
2081
2282
|
readLastUserPrompt,
|
|
2082
2283
|
CODEX_AGENT,
|
|
2083
2284
|
codexAllow,
|