@pushary/agent-hooks 0.92.1 → 0.93.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +5 -5
- package/dist/bin/pushary-claude.js +41 -25
- package/dist/bin/pushary-clean.js +11 -11
- package/dist/bin/pushary-codex-bridge.js +10 -10
- package/dist/bin/pushary-codex-hook.js +48 -14
- package/dist/bin/pushary-codex.js +9 -8
- package/dist/bin/pushary-connect.js +7 -7
- package/dist/bin/pushary-daemon.js +13 -13
- package/dist/bin/pushary-disconnect.js +5 -5
- package/dist/bin/pushary-doctor.js +27 -20
- package/dist/bin/pushary-elicitation-hook.js +5 -5
- package/dist/bin/pushary-gemini-bridge.js +10 -10
- package/dist/bin/pushary-gemini-hook.js +10 -9
- package/dist/bin/pushary-hook.js +12 -12
- package/dist/bin/pushary-login.js +7 -7
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +9 -8
- package/dist/bin/pushary-opencode-hook.js +10 -9
- package/dist/bin/pushary-permission-denied-hook.js +12 -12
- package/dist/bin/pushary-permission-hook.js +12 -12
- package/dist/bin/pushary-post-hook.js +9 -8
- package/dist/bin/pushary-prompt-hook.js +9 -8
- package/dist/bin/pushary-session-end-hook.js +9 -8
- package/dist/bin/pushary-session-start-hook.js +13 -13
- package/dist/bin/pushary-setup.js +21 -21
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +9 -9
- package/dist/bin/pushary-stop-hook.js +9 -8
- package/dist/bin/pushary-stopfailure-hook.js +9 -8
- package/dist/bin/pushary-transcripts.js +4 -4
- package/dist/bin/pushary-upgrade.js +12 -12
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-VHWCVD42.js → chunk-2ORRI66J.js} +1 -1
- package/dist/{chunk-I3ORVBTP.js → chunk-36LR2LOT.js} +5 -5
- package/dist/{chunk-7FE2KABY.js → chunk-46P3VES5.js} +1 -1
- package/dist/{chunk-L4E2BIJ2.js → chunk-4PE2SQVM.js} +57 -4
- package/dist/{chunk-YXDYKECP.js → chunk-5HGLZGI4.js} +138 -86
- package/dist/{chunk-OSALSIUF.js → chunk-6BIMJIRG.js} +1 -1
- package/dist/{chunk-UZNKVRLE.js → chunk-6WYL3XIP.js} +1 -1
- package/dist/{chunk-CELX75MW.js → chunk-BSEAOCCQ.js} +1 -1
- package/dist/{chunk-QZCFVWA7.js → chunk-EQRS3SGM.js} +3 -1
- package/dist/{chunk-BSZYIAZL.js → chunk-EYRGBYZG.js} +2 -1
- package/dist/{chunk-QCXKQJTG.js → chunk-FYOIW5RV.js} +3 -3
- package/dist/{chunk-ZMKKK6DC.js → chunk-HSAVPZ46.js} +3 -3
- package/dist/{chunk-2ACC5M7F.js → chunk-NVPYRDCT.js} +1 -1
- package/dist/{chunk-CNPAT3HL.js → chunk-OB4RF2TF.js} +2 -2
- package/dist/{chunk-JW4KO5AF.js → chunk-ODL52O5V.js} +1 -1
- package/dist/{chunk-I463AEG3.js → chunk-OYNPLEXP.js} +1 -1
- package/dist/{chunk-2XQSN66Q.js → chunk-QGA2ZB4I.js} +1 -1
- package/dist/{chunk-RQPQSJJH.js → chunk-QKNLW7H6.js} +26 -11
- package/dist/{chunk-F7OURW3C.js → chunk-RER3FRTD.js} +7 -7
- package/dist/{chunk-5NAZ7SFS.js → chunk-RH3TM7TO.js} +1 -1
- package/dist/{chunk-PYA4FLKI.js → chunk-ROHFVGXM.js} +2 -2
- package/dist/{chunk-I7W3UTDK.js → chunk-RX75MRGC.js} +10 -8
- package/dist/{chunk-4TFXV5MW.js → chunk-TDUTNYAQ.js} +1 -1
- package/dist/{chunk-BXTJVM7O.js → chunk-UZFVNEEU.js} +6 -6
- package/dist/{chunk-Z6RFZKY4.js → chunk-WY4ALDYN.js} +1 -1
- package/dist/chunk-Y3UGCQQC.js +53 -0
- package/dist/{chunk-CV2YD2JR.js → chunk-Y6XXFXVB.js} +43 -11
- package/dist/{chunk-3HS4YGHB.js → chunk-YANV5TGK.js} +3 -3
- package/dist/{chunk-VNMKIJZB.js → chunk-Z6CP5ZZD.js} +3 -0
- package/dist/{chunk-HDORB7WD.js → chunk-ZO2XG3CX.js} +1 -1
- package/dist/{chunk-SQYBP3QQ.js → chunk-ZTJF6ANY.js} +2 -2
- package/dist/src/index.d.ts +33 -3
- package/dist/src/index.js +13 -13
- package/package.json +2 -2
- package/dist/chunk-2LAHXHBR.js +0 -23
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
FILE_TARGET_TOOLS,
|
|
6
6
|
redactSecrets,
|
|
7
7
|
redactSecretsDeep
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-Z6CP5ZZD.js";
|
|
9
9
|
|
|
10
10
|
// ../hook-mapping/src/describe.ts
|
|
11
11
|
import { isAbsolute, relative, resolve } from "path";
|
|
@@ -20,8 +20,14 @@ var hookPrefixes = {
|
|
|
20
20
|
// Without an entry here the fallback stringifies the whole tool input, and for
|
|
21
21
|
// ExitPlanMode that input IS the plan: the action line became a truncated dump of
|
|
22
22
|
// the plan markdown. The plan belongs in the body, not the one-line summary.
|
|
23
|
-
ExitPlanMode: () => "finished planning and wants to start coding"
|
|
24
|
-
|
|
23
|
+
ExitPlanMode: () => "finished planning and wants to start coding",
|
|
24
|
+
// Mirrored on PostToolUse since the matcher was widened. Without entries the
|
|
25
|
+
// fallback prints `Task: done` and `WebSearch: done`, which name the tool and
|
|
26
|
+
// drop the only part a person reads: which subagent, and what it looked for.
|
|
27
|
+
Task: (input) => `subagent: ${describeTaskCall(input)}`,
|
|
28
|
+
WebSearch: (input) => `web search: ${firstString(input.query) ?? "(no query)"}`
|
|
29
|
+
};
|
|
30
|
+
var describeTaskCall = (input) => firstString(input.description) ?? firstString(input.subagent_type) ?? "(unnamed)";
|
|
25
31
|
var describeTodoProgress = (todos) => {
|
|
26
32
|
if (!Array.isArray(todos)) return "updated the plan";
|
|
27
33
|
const items = todos.filter((t) => !!t && typeof t === "object");
|
|
@@ -42,7 +48,9 @@ var eventPrefixes = {
|
|
|
42
48
|
Edit: (input) => `edited: ${input.file_path ?? "unknown"}`,
|
|
43
49
|
MultiEdit: (input) => `edited: ${input.file_path ?? "unknown"}`,
|
|
44
50
|
Read: (input) => `read: ${input.file_path ?? "unknown"}`,
|
|
45
|
-
TodoWrite: (input) => describeTodoProgress(input.todos)
|
|
51
|
+
TodoWrite: (input) => describeTodoProgress(input.todos),
|
|
52
|
+
Task: (input) => `ran subagent: ${describeTaskCall(input)}`,
|
|
53
|
+
WebSearch: (input) => `searched: ${firstString(input.query) ?? "the web"}`
|
|
46
54
|
};
|
|
47
55
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
48
56
|
var parseMcpToolName = (toolName) => {
|
|
@@ -287,6 +295,7 @@ var AGENT_IDENTITIES = {
|
|
|
287
295
|
vscode: { type: "vscode", label: AGENT_LABELS_BY_TYPE.vscode },
|
|
288
296
|
opencode: { type: "opencode", label: AGENT_LABELS_BY_TYPE.opencode }
|
|
289
297
|
};
|
|
298
|
+
var TEAMMATE_IDLE_ACTION = "A teammate went idle";
|
|
290
299
|
var TASK_TITLE_MAX_LENGTH = 120;
|
|
291
300
|
var STOP_SUMMARY_MAX_LENGTH = 200;
|
|
292
301
|
var SESSION_END_ACTIONS = {
|
|
@@ -333,6 +342,45 @@ var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "
|
|
|
333
342
|
var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
|
|
334
343
|
var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
|
|
335
344
|
var asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
345
|
+
var NOTIFICATION_ACTION_MAX_LENGTH = 200;
|
|
346
|
+
var STOP_FAILURE_REASON_MAX_LENGTH = 300;
|
|
347
|
+
var notificationTypeOf = (payload) => asString(payload.notification_type) ?? asString(payload.type);
|
|
348
|
+
var notificationAction = (payload) => {
|
|
349
|
+
const text = asString(payload.message) ?? asString(payload.title);
|
|
350
|
+
const collapsed = redactSecrets(text ?? "").replace(/\s+/g, " ").trim();
|
|
351
|
+
return collapsed ? collapsed.slice(0, NOTIFICATION_ACTION_MAX_LENGTH) : "Notification";
|
|
352
|
+
};
|
|
353
|
+
var STOP_FAILURE_REASONS = {
|
|
354
|
+
rate_limit: "Rate limited by the API",
|
|
355
|
+
overloaded: "The API is overloaded right now",
|
|
356
|
+
authentication_failed: "Authentication failed",
|
|
357
|
+
billing_error: "A billing error stopped the turn"
|
|
358
|
+
};
|
|
359
|
+
var STOP_FAILURE_FALLBACK = "The turn ended on an API error (rate limit, billing, or auth).";
|
|
360
|
+
var describeStopFailure = (error) => {
|
|
361
|
+
const fromString = (value) => {
|
|
362
|
+
const trimmed = value.trim();
|
|
363
|
+
if (!trimmed) return void 0;
|
|
364
|
+
return STOP_FAILURE_REASONS[trimmed] ?? redactSecrets(trimmed).slice(0, STOP_FAILURE_REASON_MAX_LENGTH);
|
|
365
|
+
};
|
|
366
|
+
if (typeof error === "string") {
|
|
367
|
+
const described = fromString(error);
|
|
368
|
+
if (described) return described;
|
|
369
|
+
} else if (error && typeof error === "object") {
|
|
370
|
+
for (const key of ["type", "message", "error", "code"]) {
|
|
371
|
+
const value = error[key];
|
|
372
|
+
if (typeof value === "string") {
|
|
373
|
+
const described = fromString(value);
|
|
374
|
+
if (described) return described;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return STOP_FAILURE_FALLBACK;
|
|
379
|
+
};
|
|
380
|
+
var permissionDeniedAction = (toolName) => {
|
|
381
|
+
const tool = asString(toolName);
|
|
382
|
+
return tool ? `Auto-mode blocked ${tool}` : "Auto-mode blocked a tool call";
|
|
383
|
+
};
|
|
336
384
|
var sessionIdentityOf = (payload) => {
|
|
337
385
|
const sessionId = asString(payload.session_id);
|
|
338
386
|
const agentId = asString(payload.agent_id);
|
|
@@ -356,11 +404,16 @@ export {
|
|
|
356
404
|
deriveAction,
|
|
357
405
|
deriveBlocker,
|
|
358
406
|
AGENT_IDENTITIES,
|
|
407
|
+
TEAMMATE_IDLE_ACTION,
|
|
359
408
|
summarizeStop,
|
|
360
409
|
subagentStopSummary,
|
|
361
410
|
compactionAction,
|
|
362
411
|
sessionStartAction,
|
|
363
412
|
sessionEndAction,
|
|
364
413
|
taskTitleFrom,
|
|
414
|
+
notificationTypeOf,
|
|
415
|
+
notificationAction,
|
|
416
|
+
describeStopFailure,
|
|
417
|
+
permissionDeniedAction,
|
|
365
418
|
sessionIdentityOf
|
|
366
419
|
};
|
|
@@ -13,14 +13,18 @@ import {
|
|
|
13
13
|
savePendingCommand,
|
|
14
14
|
sendNotification,
|
|
15
15
|
waitForAnswer
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RER3FRTD.js";
|
|
17
17
|
import {
|
|
18
18
|
AGENT_IDENTITIES,
|
|
19
|
+
TEAMMATE_IDLE_ACTION,
|
|
19
20
|
compactionAction,
|
|
20
21
|
deriveReceiptMeta,
|
|
21
22
|
deriveToolTarget,
|
|
23
|
+
describeStopFailure,
|
|
22
24
|
describeToolCall,
|
|
23
25
|
isToolResultError,
|
|
26
|
+
notificationAction,
|
|
27
|
+
notificationTypeOf,
|
|
24
28
|
parseApplyPatchFiles,
|
|
25
29
|
parseApplyPatchOps,
|
|
26
30
|
sessionEndAction,
|
|
@@ -30,16 +34,20 @@ import {
|
|
|
30
34
|
summarizeStop,
|
|
31
35
|
taskTitleFrom,
|
|
32
36
|
toolResultErrorText
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-4PE2SQVM.js";
|
|
38
|
+
import {
|
|
39
|
+
NOTIFICATION_PUSH_TYPES,
|
|
40
|
+
POSTTOOLUSE_MIRROR_ONLY_TOOLS
|
|
41
|
+
} from "./chunk-Y3UGCQQC.js";
|
|
34
42
|
import {
|
|
35
43
|
getMachineId
|
|
36
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-NVPYRDCT.js";
|
|
37
45
|
import {
|
|
38
46
|
withRetry
|
|
39
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-EYRGBYZG.js";
|
|
40
48
|
import {
|
|
41
49
|
reportedInstallSource
|
|
42
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-ODL52O5V.js";
|
|
43
51
|
import {
|
|
44
52
|
getApiKey,
|
|
45
53
|
getBaseUrl
|
|
@@ -55,7 +63,7 @@ import {
|
|
|
55
63
|
pendingCommandEvents,
|
|
56
64
|
resolveAutoResolveOrigin,
|
|
57
65
|
resolvePolicy
|
|
58
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-Z6CP5ZZD.js";
|
|
59
67
|
|
|
60
68
|
// src/policy.ts
|
|
61
69
|
import { createHash } from "crypto";
|
|
@@ -174,7 +182,7 @@ var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
|
|
|
174
182
|
if (Date.now() - last.at >= maxAgeMs) return null;
|
|
175
183
|
return { ...last.state, degraded: false };
|
|
176
184
|
};
|
|
177
|
-
var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
185
|
+
var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0, signal) => {
|
|
178
186
|
if (reuseWithinMs > 0) {
|
|
179
187
|
const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
|
|
180
188
|
if (reusable) return reusable;
|
|
@@ -184,7 +192,7 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
|
184
192
|
const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
|
|
185
193
|
const response = await fetch(url, {
|
|
186
194
|
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
187
|
-
signal: AbortSignal.timeout(3e3)
|
|
195
|
+
signal: AbortSignal.any([AbortSignal.timeout(3e3), ...signal ? [signal] : []])
|
|
188
196
|
});
|
|
189
197
|
if (!response.ok) {
|
|
190
198
|
return degradedFallback(apiKey, response.status === 401 || response.status === 403);
|
|
@@ -284,6 +292,7 @@ var repoKeyFor = (cwd) => {
|
|
|
284
292
|
|
|
285
293
|
// src/usage.ts
|
|
286
294
|
import { closeSync, mkdirSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
295
|
+
import { createHash as createHash2 } from "crypto";
|
|
287
296
|
import { join as join3 } from "path";
|
|
288
297
|
import { tmpdir as tmpdir2 } from "os";
|
|
289
298
|
var DEFAULT_PRICES = [
|
|
@@ -423,10 +432,12 @@ var readLastUserPrompt = (transcriptPath) => {
|
|
|
423
432
|
};
|
|
424
433
|
var readNewUsage = (transcriptPath, sessionId) => {
|
|
425
434
|
try {
|
|
426
|
-
const
|
|
435
|
+
const file = statSync2(transcriptPath);
|
|
436
|
+
const size = file.size;
|
|
427
437
|
const path = stateFile(sessionId);
|
|
428
438
|
let state = readState(path);
|
|
429
439
|
if (size < state.offset) state = { ...emptyState(), recentIds: state.recentIds };
|
|
440
|
+
const startOffset = state.offset;
|
|
430
441
|
let deltaUsd = 0;
|
|
431
442
|
if (size > state.offset) {
|
|
432
443
|
const buffer = readRange(transcriptPath, state.offset, size);
|
|
@@ -443,6 +454,18 @@ var readNewUsage = (transcriptPath, sessionId) => {
|
|
|
443
454
|
writeFileSync2(path, JSON.stringify(state), "utf-8");
|
|
444
455
|
if (state.tokensIn === 0 && state.tokensOut === 0) return null;
|
|
445
456
|
return {
|
|
457
|
+
// Identical cursor-range reports share an identity across transport retries.
|
|
458
|
+
// Native message identity distinguishes an in-place rotation of the range.
|
|
459
|
+
// This does not serialize readers of different, potentially overlapping ranges.
|
|
460
|
+
eventId: createHash2("sha256").update(JSON.stringify([
|
|
461
|
+
sessionId,
|
|
462
|
+
file.dev,
|
|
463
|
+
file.ino,
|
|
464
|
+
startOffset,
|
|
465
|
+
state.offset,
|
|
466
|
+
state.recentIds.at(-1)
|
|
467
|
+
])).digest("hex").slice(0, 32),
|
|
468
|
+
reportedAt: Date.now(),
|
|
446
469
|
tokensIn: state.tokensIn,
|
|
447
470
|
tokensOut: state.tokensOut,
|
|
448
471
|
costUsd: state.costUsd,
|
|
@@ -521,10 +544,10 @@ var phoneDrivenEnv = (base) => ({
|
|
|
521
544
|
// src/throttle.ts
|
|
522
545
|
import { join as join4 } from "path";
|
|
523
546
|
import { tmpdir as tmpdir3 } from "os";
|
|
524
|
-
import { createHash as
|
|
547
|
+
import { createHash as createHash3 } from "crypto";
|
|
525
548
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
526
549
|
var THROTTLE_DIR = join4(tmpdir3(), "pushary-throttle");
|
|
527
|
-
var markerPath = (key) => join4(THROTTLE_DIR,
|
|
550
|
+
var markerPath = (key) => join4(THROTTLE_DIR, createHash3("sha256").update(key).digest("hex").slice(0, 16));
|
|
528
551
|
var throttlePass = (key, windowMs) => {
|
|
529
552
|
try {
|
|
530
553
|
const path = markerPath(key);
|
|
@@ -569,20 +592,21 @@ var pendingQuestionsToSettle = (sessionId, toolUseId) => {
|
|
|
569
592
|
const settledElsewhere = toolUseId ? listPendingQuestionsForToolUse(sessionId, toolUseId) : [];
|
|
570
593
|
return [.../* @__PURE__ */ new Set([...settledElsewhere, ...listPendingQuestions(sessionId)])];
|
|
571
594
|
};
|
|
572
|
-
var reconcileQuestions = async (sessionId, files) => {
|
|
595
|
+
var reconcileQuestions = async (sessionId, files, signal) => {
|
|
573
596
|
const late = [];
|
|
574
597
|
try {
|
|
575
598
|
if (files.length === 0) return late;
|
|
576
599
|
const apiKey = getApiKey();
|
|
600
|
+
if (signal) signal = AbortSignal.any([signal, AbortSignal.timeout(2e3)]);
|
|
577
601
|
for (const correlationId of files) {
|
|
602
|
+
if (signal?.aborted) break;
|
|
578
603
|
try {
|
|
579
|
-
const answer = await waitForAnswer(apiKey, correlationId, 1e3);
|
|
604
|
+
const answer = await waitForAnswer(apiKey, correlationId, 1e3, signal);
|
|
580
605
|
if (answer.answered) {
|
|
581
606
|
late.push({ correlationId, value: answer.value });
|
|
582
607
|
continue;
|
|
583
608
|
}
|
|
584
|
-
await cancelQuestion(apiKey, correlationId)
|
|
585
|
-
});
|
|
609
|
+
await cancelQuestion(apiKey, correlationId, signal);
|
|
586
610
|
} catch {
|
|
587
611
|
continue;
|
|
588
612
|
}
|
|
@@ -592,7 +616,7 @@ var reconcileQuestions = async (sessionId, files) => {
|
|
|
592
616
|
}
|
|
593
617
|
return late;
|
|
594
618
|
};
|
|
595
|
-
var reconcilePendingQuestions = (sessionId, toolUseId) => reconcileQuestions(sessionId, pendingQuestionsToSettle(sessionId, toolUseId));
|
|
619
|
+
var reconcilePendingQuestions = (sessionId, toolUseId, signal) => reconcileQuestions(sessionId, pendingQuestionsToSettle(sessionId, toolUseId), signal);
|
|
596
620
|
var describeLateAnswer = (value) => {
|
|
597
621
|
const v = (value ?? "").trim();
|
|
598
622
|
if (v === "yes") return "approved it";
|
|
@@ -605,7 +629,7 @@ var buildLateAnswerContext = (late) => {
|
|
|
605
629
|
const parts = late.map((l) => `you ${describeLateAnswer(l.value)} on your phone`);
|
|
606
630
|
return `Pushary: a permission prompt you were sent was answered late \u2014 ${parts.join("; ")} \u2014 after the terminal already took over. Treat this as the user's latest intent; do not assume the earlier terminal decision matched it.`;
|
|
607
631
|
};
|
|
608
|
-
var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
|
|
632
|
+
var notifyLateAnswers = async (apiKey, late, agentName, sessionId, signal) => {
|
|
609
633
|
if (late.length === 0) return;
|
|
610
634
|
const summary = late.map((l) => describeLateAnswer(l.value)).join(", ");
|
|
611
635
|
await sendNotification(apiKey, {
|
|
@@ -613,7 +637,7 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
|
|
|
613
637
|
body: `You ${summary} after the agent had already moved on.`,
|
|
614
638
|
agentName,
|
|
615
639
|
sessionId
|
|
616
|
-
}, 2500).catch(() => {
|
|
640
|
+
}, 2500, signal).catch(() => {
|
|
617
641
|
});
|
|
618
642
|
};
|
|
619
643
|
var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
|
|
@@ -687,8 +711,10 @@ var consumesActivityContext = (agentType) => {
|
|
|
687
711
|
return pendingCommandEvents(source).some((event) => event !== "Stop");
|
|
688
712
|
};
|
|
689
713
|
var detectInstallMode = () => (process.argv[1] ?? "").includes("_npx") ? "npx" : "cli";
|
|
690
|
-
var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, machineId, timeoutMs, state = "delivered") => {
|
|
714
|
+
var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, machineId, timeoutMs, state = "delivered", signal) => {
|
|
691
715
|
try {
|
|
716
|
+
signal?.throwIfAborted();
|
|
717
|
+
if (timeoutMs <= 0) return false;
|
|
692
718
|
const response = await fetch(`${baseUrl}/api/agent/command/ack`, {
|
|
693
719
|
method: "POST",
|
|
694
720
|
headers: {
|
|
@@ -702,13 +728,14 @@ var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, ma
|
|
|
702
728
|
state,
|
|
703
729
|
...state === "failed" ? { failureCode: "consumer_rejected" } : {}
|
|
704
730
|
}),
|
|
705
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
731
|
+
signal: AbortSignal.any([AbortSignal.timeout(timeoutMs), ...signal ? [signal] : []])
|
|
706
732
|
});
|
|
707
733
|
return response.ok;
|
|
708
734
|
} catch {
|
|
709
735
|
return false;
|
|
710
736
|
}
|
|
711
737
|
};
|
|
738
|
+
var ACK_RESERVE_MS = 1500;
|
|
712
739
|
var reportEvent = async (event, options = {}) => {
|
|
713
740
|
const apiKey = options.apiKey ?? getApiKey();
|
|
714
741
|
const baseUrl = getBaseUrl();
|
|
@@ -716,7 +743,14 @@ var reportEvent = async (event, options = {}) => {
|
|
|
716
743
|
const sessionKey = event.sessionId || DEFAULT_SESSION;
|
|
717
744
|
const receiptNamespace = `${apiKey}:${machineId}:${event.agentType}`;
|
|
718
745
|
const clientCanDrain = consumesActivityContext(event.agentType) && options.timeoutMs === void 0;
|
|
746
|
+
const deadlineMs = options.budgetMs === void 0 ? void 0 : Date.now() + options.budgetMs;
|
|
747
|
+
const ackReserveMs = deadlineMs === void 0 ? 0 : Math.min(ACK_RESERVE_MS, Math.floor((options.budgetMs ?? 0) * 0.4));
|
|
748
|
+
const postTimeoutMs = (fallback) => deadlineMs === void 0 ? fallback : Math.max(0, Math.min(fallback, deadlineMs - ackReserveMs - Date.now()));
|
|
749
|
+
const ackTimeoutMs = (fallback) => deadlineMs === void 0 ? fallback : Math.max(0, Math.min(fallback, deadlineMs - Date.now()));
|
|
719
750
|
return withRetry(async () => {
|
|
751
|
+
options.signal?.throwIfAborted();
|
|
752
|
+
const timeoutMs = postTimeoutMs(options.timeoutMs ?? 1e4);
|
|
753
|
+
if (timeoutMs <= 0) return null;
|
|
720
754
|
const storedCommand = clientCanDrain ? readPendingCommand(sessionKey, receiptNamespace) : null;
|
|
721
755
|
const res = await fetch(`${baseUrl}/api/agent/event`, {
|
|
722
756
|
method: "POST",
|
|
@@ -744,11 +778,11 @@ var reportEvent = async (event, options = {}) => {
|
|
|
744
778
|
...driverFromEnv(process.env) ? { driver: driverFromEnv(process.env) } : {},
|
|
745
779
|
canDrainCommand: clientCanDrain && !storedCommand,
|
|
746
780
|
ackMode: clientCanDrain && !storedCommand,
|
|
747
|
-
skipCommandDrain: storedCommand != null
|
|
781
|
+
skipCommandDrain: options.skipDrain === true || storedCommand != null
|
|
748
782
|
}),
|
|
749
|
-
signal: AbortSignal.timeout(options.
|
|
783
|
+
signal: AbortSignal.any([AbortSignal.timeout(timeoutMs), ...options.signal ? [options.signal] : []])
|
|
750
784
|
});
|
|
751
|
-
if (options.requireOk && !res.ok) throw new Error(`/api/agent/event rejected (${res.status})`);
|
|
785
|
+
if ((options.requireOk || event.usage !== void 0) && !res.ok) throw new Error(`/api/agent/event rejected (${res.status})`);
|
|
752
786
|
try {
|
|
753
787
|
const result = await res.json();
|
|
754
788
|
const withoutPending = () => ({ ...result, pendingCommand: void 0, pendingCommandId: void 0 });
|
|
@@ -760,8 +794,9 @@ var reportEvent = async (event, options = {}) => {
|
|
|
760
794
|
storedCommand.id,
|
|
761
795
|
event.sessionId,
|
|
762
796
|
machineId,
|
|
763
|
-
options.timeoutMs ?? 1e4,
|
|
764
|
-
failed ? "failed" : "delivered"
|
|
797
|
+
ackTimeoutMs(options.timeoutMs ?? 1e4),
|
|
798
|
+
failed ? "failed" : "delivered",
|
|
799
|
+
options.signal
|
|
765
800
|
);
|
|
766
801
|
if (!acknowledged) return withoutPending();
|
|
767
802
|
if (storedCommand.delivered || failed) {
|
|
@@ -785,7 +820,9 @@ var reportEvent = async (event, options = {}) => {
|
|
|
785
820
|
result.pendingCommandId,
|
|
786
821
|
event.sessionId,
|
|
787
822
|
machineId,
|
|
788
|
-
options.timeoutMs ?? 1e4
|
|
823
|
+
ackTimeoutMs(options.timeoutMs ?? 1e4),
|
|
824
|
+
"delivered",
|
|
825
|
+
options.signal
|
|
789
826
|
);
|
|
790
827
|
if (!acknowledged || !markPendingCommandDelivered(sessionKey, result.pendingCommandId, receiptNamespace)) {
|
|
791
828
|
return withoutPending();
|
|
@@ -797,13 +834,17 @@ var reportEvent = async (event, options = {}) => {
|
|
|
797
834
|
}
|
|
798
835
|
}, { maxAttempts: options.maxAttempts ?? 2, baseDelayMs: 300 });
|
|
799
836
|
};
|
|
800
|
-
var NON_GATED_MIRROR_TOOLS =
|
|
837
|
+
var NON_GATED_MIRROR_TOOLS = new Set(POSTTOOLUSE_MIRROR_ONLY_TOOLS);
|
|
838
|
+
var HOOK_BUDGET_MS = 9e3;
|
|
839
|
+
var DRAINING_REPORT_BUDGET_MS = 8e3;
|
|
801
840
|
var settleQuestionsForToolUse = async (sessionId, toolUseId) => {
|
|
802
841
|
if (!toolUseId) return;
|
|
803
842
|
const late = await reconcileQuestions(sessionId, listPendingQuestionsForToolUse(sessionId, toolUseId));
|
|
804
843
|
for (const answer of late) removePendingQuestion(sessionId, answer.correlationId);
|
|
805
844
|
};
|
|
806
845
|
var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
846
|
+
const deadline = Date.now() + HOOK_BUDGET_MS;
|
|
847
|
+
const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
|
|
807
848
|
try {
|
|
808
849
|
const projectName = basename2(input.cwd ?? process.cwd());
|
|
809
850
|
const action = describeToolCall(input.tool_name, input.tool_input, "event");
|
|
@@ -812,8 +853,10 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
812
853
|
const isError = isToolResultError(toolResult);
|
|
813
854
|
const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
|
|
814
855
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
815
|
-
const liveMode = await
|
|
816
|
-
|
|
856
|
+
const [liveMode, late] = await Promise.all([
|
|
857
|
+
fetchModeState(getApiKey(), input.session_id, MODE_REUSE_MS, signal),
|
|
858
|
+
reconcilePendingQuestions(sessionKey, input.tool_use_id, signal)
|
|
859
|
+
]);
|
|
817
860
|
let additionalContext;
|
|
818
861
|
if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
|
|
819
862
|
additionalContext = buildLateAnswerContext(late);
|
|
@@ -833,7 +876,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
833
876
|
...beacon ? { decisionOrigin: beacon.decisionOrigin, toolName: beacon.toolName, toolTarget: beacon.toolTarget } : {},
|
|
834
877
|
meta: receiptsEnabled ? deriveReceiptMeta(lookup.tool, lookup.input, toolResult, input.cwd ?? process.cwd()) : void 0,
|
|
835
878
|
usage: deriveUsage(input.transcript_path, input.session_id)
|
|
836
|
-
}, { maxAttempts: 1 });
|
|
879
|
+
}, { maxAttempts: 1, budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())), signal });
|
|
837
880
|
const extraReports = [];
|
|
838
881
|
if (throttlePass(`hookactive:${sessionKey}`, HOOK_ACTIVE_WINDOW_MS)) {
|
|
839
882
|
extraReports.push(
|
|
@@ -844,7 +887,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
844
887
|
agentName: `${agent.label} - ${projectName}`,
|
|
845
888
|
sessionId: input.session_id
|
|
846
889
|
},
|
|
847
|
-
{ maxAttempts: 1, timeoutMs: 800 }
|
|
890
|
+
{ maxAttempts: 1, timeoutMs: 800, signal }
|
|
848
891
|
)
|
|
849
892
|
);
|
|
850
893
|
}
|
|
@@ -858,13 +901,15 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
858
901
|
}
|
|
859
902
|
};
|
|
860
903
|
var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
904
|
+
const deadline = Date.now() + HOOK_BUDGET_MS;
|
|
905
|
+
const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
|
|
861
906
|
try {
|
|
862
907
|
const projectName = basename2(input.cwd ?? process.cwd());
|
|
863
908
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
864
909
|
const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
|
|
865
910
|
const taskTitle = titlesEnabled ? taskTitleFrom(input.prompt) : void 0;
|
|
866
911
|
if (input.prompt) saveLastPrompt(sessionKey, input.prompt);
|
|
867
|
-
const late = await reconcilePendingQuestions(sessionKey);
|
|
912
|
+
const late = await reconcilePendingQuestions(sessionKey, void 0, signal);
|
|
868
913
|
let additionalContext;
|
|
869
914
|
if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
|
|
870
915
|
additionalContext = buildLateAnswerContext(late);
|
|
@@ -877,7 +922,7 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
877
922
|
agentName: `${agent.label} - ${projectName}`,
|
|
878
923
|
sessionId: input.session_id,
|
|
879
924
|
taskTitle
|
|
880
|
-
}, { maxAttempts: 1,
|
|
925
|
+
}, { maxAttempts: 1, budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())), signal })
|
|
881
926
|
]);
|
|
882
927
|
if (consumesActivityContext(agent.type)) {
|
|
883
928
|
additionalContext = mergeAdditionalContext(additionalContext, reported);
|
|
@@ -888,10 +933,12 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
888
933
|
}
|
|
889
934
|
};
|
|
890
935
|
var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
936
|
+
const deadline = Date.now() + HOOK_BUDGET_MS;
|
|
937
|
+
const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
|
|
891
938
|
try {
|
|
892
939
|
const projectName = basename2(input.cwd ?? process.cwd());
|
|
893
940
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
894
|
-
const late = await reconcilePendingQuestions(sessionKey);
|
|
941
|
+
const late = await reconcilePendingQuestions(sessionKey, void 0, signal);
|
|
895
942
|
const tasks = [
|
|
896
943
|
reportEvent({
|
|
897
944
|
event: "session_end",
|
|
@@ -900,13 +947,13 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
900
947
|
action: summarizeStop(input),
|
|
901
948
|
...sessionIdentityOf(input),
|
|
902
949
|
usage: deriveUsage(input.transcript_path, input.session_id)
|
|
903
|
-
}, { maxAttempts: 1 })
|
|
950
|
+
}, { maxAttempts: 1, budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())), signal })
|
|
904
951
|
];
|
|
905
952
|
if (late.length > 0) {
|
|
906
|
-
tasks.push(notifyLateAnswers(getApiKey(), late, `${agent.label} - ${projectName}`, input.session_id));
|
|
953
|
+
tasks.push(notifyLateAnswers(getApiKey(), late, `${agent.label} - ${projectName}`, input.session_id, signal));
|
|
907
954
|
for (const l of late) removePendingQuestion(sessionKey, l.correlationId);
|
|
908
955
|
}
|
|
909
|
-
if (!isDefaultSession(sessionKey)) removePendingSession(sessionKey);
|
|
956
|
+
if (!isDefaultSession(sessionKey) && listPendingQuestions(sessionKey).length === 0) removePendingSession(sessionKey);
|
|
910
957
|
const [reported] = await Promise.allSettled(tasks);
|
|
911
958
|
const pendingCommand = reported.status === "fulfilled" ? reported.value?.pendingCommand : void 0;
|
|
912
959
|
if (typeof pendingCommand === "string" && pendingCommand.trim().length > 0) {
|
|
@@ -931,7 +978,10 @@ var handleSubagentStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
931
978
|
agentName: `${agent.label} - ${projectName}`,
|
|
932
979
|
action: subagentStopSummary(input),
|
|
933
980
|
...sessionIdentityOf(input)
|
|
934
|
-
|
|
981
|
+
// A subagent's end is a `session_end` like any other as far as the route is
|
|
982
|
+
// concerned, and this caller discards the response, so without this the
|
|
983
|
+
// parent's queued instruction is popped and thrown away.
|
|
984
|
+
}, { maxAttempts: 1, timeoutMs: SUBAGENT_STOP_REPORT_TIMEOUT_MS, skipDrain: true });
|
|
935
985
|
} catch {
|
|
936
986
|
}
|
|
937
987
|
};
|
|
@@ -945,7 +995,7 @@ var handleCompaction = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
945
995
|
agentType: agent.type,
|
|
946
996
|
agentName: `${agent.label} - ${projectName}`,
|
|
947
997
|
action: compactionAction(input.hook_event_name, input.trigger),
|
|
948
|
-
|
|
998
|
+
...sessionIdentityOf(input)
|
|
949
999
|
}, { maxAttempts: 1, timeoutMs: COMPACTION_REPORT_TIMEOUT_MS });
|
|
950
1000
|
} catch {
|
|
951
1001
|
}
|
|
@@ -988,14 +1038,39 @@ var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT, options = {}) =>
|
|
|
988
1038
|
} catch {
|
|
989
1039
|
}
|
|
990
1040
|
};
|
|
991
|
-
var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set(["idle_prompt", "agent_needs_input"]);
|
|
992
1041
|
var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
|
|
993
1042
|
var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1043
|
+
const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
|
|
994
1044
|
try {
|
|
995
1045
|
const projectName = basename2(input.cwd ?? process.cwd());
|
|
996
|
-
const notifType = input
|
|
1046
|
+
const notifType = notificationTypeOf(input) ?? "";
|
|
997
1047
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
998
|
-
|
|
1048
|
+
const teammateIdle = input.hook_event_name === "TeammateIdle";
|
|
1049
|
+
const action = teammateIdle ? TEAMMATE_IDLE_ACTION : notificationAction(input);
|
|
1050
|
+
const failed = !teammateIdle && notifType === "error";
|
|
1051
|
+
await reportEvent({
|
|
1052
|
+
event: teammateIdle ? "teammate_idle" : failed ? "error" : "notification",
|
|
1053
|
+
agentType: agent.type,
|
|
1054
|
+
agentName: `${agent.label} - ${projectName}`,
|
|
1055
|
+
action,
|
|
1056
|
+
sessionId: input.session_id,
|
|
1057
|
+
error: failed ? action : void 0
|
|
1058
|
+
// `timeoutMs` is not decoration. It is this file's way of saying "side
|
|
1059
|
+
// report", and it sets `clientCanDrain` false. Without it this call
|
|
1060
|
+
// advertises itself as able to consume a phone-queued instruction, and the
|
|
1061
|
+
// stored-command replay inside `reportEvent` hands one back, acknowledges it
|
|
1062
|
+
// as delivered and marks the local receipt delivered, to a caller that
|
|
1063
|
+
// discards the return value. The notification hook prints nothing, so the
|
|
1064
|
+
// instruction is destroyed rather than delayed. Hoisting this report out of
|
|
1065
|
+
// the else-branch put every notification type on that path, not just the
|
|
1066
|
+
// quiet ones.
|
|
1067
|
+
//
|
|
1068
|
+
// 2.5s rather than the 4s a side report usually gets: on the push branch
|
|
1069
|
+
// this is followed by a ~3s `fetchModeState` and a 4s `sendNotification`,
|
|
1070
|
+
// and all three have to fit inside Claude Code's 10s Notification budget.
|
|
1071
|
+
}, { maxAttempts: 1, timeoutMs: 2500, signal }).catch(() => {
|
|
1072
|
+
});
|
|
1073
|
+
if (!teammateIdle && NOTIFICATION_PUSH_TYPES.has(notifType)) {
|
|
999
1074
|
if (!throttlePass(`notify:${sessionKey}:${notifType}`, NOTIFICATION_THROTTLE_MS)) return;
|
|
1000
1075
|
let apiKey;
|
|
1001
1076
|
try {
|
|
@@ -1003,61 +1078,24 @@ var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1003
1078
|
} catch {
|
|
1004
1079
|
return;
|
|
1005
1080
|
}
|
|
1006
|
-
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1081
|
+
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS, signal).catch(() => null);
|
|
1007
1082
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1008
1083
|
await sendNotification(apiKey, {
|
|
1009
1084
|
title: `${agent.label} is waiting`,
|
|
1010
|
-
body:
|
|
1085
|
+
body: input.message ? action.slice(0, 200) : "Your agent is waiting for you.",
|
|
1011
1086
|
agentName: `${agent.label} - ${projectName}`,
|
|
1012
1087
|
sessionId: input.session_id,
|
|
1013
1088
|
// Makes this an `update` server-side, so resolveSiteMode gates it on
|
|
1014
1089
|
// the wildcard policy too. `info` because nothing finished.
|
|
1015
1090
|
context: { type: "info" }
|
|
1016
|
-
}, 4e3).catch(() => {
|
|
1091
|
+
}, 4e3, signal).catch(() => {
|
|
1017
1092
|
});
|
|
1018
|
-
return;
|
|
1019
1093
|
}
|
|
1020
|
-
await reportEvent({
|
|
1021
|
-
event: notifType === "error" ? "error" : "notification",
|
|
1022
|
-
agentType: agent.type,
|
|
1023
|
-
agentName: `${agent.label} - ${projectName}`,
|
|
1024
|
-
action: input.message ?? input.title ?? "Notification",
|
|
1025
|
-
sessionId: input.session_id,
|
|
1026
|
-
error: notifType === "error" ? input.message ?? input.title : void 0
|
|
1027
|
-
}).catch(() => {
|
|
1028
|
-
});
|
|
1029
1094
|
} catch {
|
|
1030
1095
|
}
|
|
1031
1096
|
};
|
|
1032
|
-
var STOPFAILURE_MAX_ERROR_LENGTH = 300;
|
|
1033
|
-
var STOPFAILURE_REASONS = {
|
|
1034
|
-
rate_limit: "Rate limited by the API",
|
|
1035
|
-
overloaded: "The API is overloaded right now",
|
|
1036
|
-
authentication_failed: "Authentication failed",
|
|
1037
|
-
billing_error: "A billing error stopped the turn"
|
|
1038
|
-
};
|
|
1039
|
-
var describeStopFailure = (error) => {
|
|
1040
|
-
const fromString = (s) => {
|
|
1041
|
-
const v = s.trim();
|
|
1042
|
-
if (!v) return void 0;
|
|
1043
|
-
return STOPFAILURE_REASONS[v] ?? v.slice(0, STOPFAILURE_MAX_ERROR_LENGTH);
|
|
1044
|
-
};
|
|
1045
|
-
if (typeof error === "string") {
|
|
1046
|
-
const described = fromString(error);
|
|
1047
|
-
if (described) return described;
|
|
1048
|
-
} else if (error && typeof error === "object") {
|
|
1049
|
-
const e = error;
|
|
1050
|
-
for (const key of ["type", "message", "error", "code"]) {
|
|
1051
|
-
const val = e[key];
|
|
1052
|
-
if (typeof val === "string") {
|
|
1053
|
-
const described = fromString(val);
|
|
1054
|
-
if (described) return described;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
return "The turn ended on an API error (rate limit, billing, or auth).";
|
|
1059
|
-
};
|
|
1060
1097
|
var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1098
|
+
const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
|
|
1061
1099
|
try {
|
|
1062
1100
|
let apiKey;
|
|
1063
1101
|
try {
|
|
@@ -1066,16 +1104,29 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1066
1104
|
return;
|
|
1067
1105
|
}
|
|
1068
1106
|
const projectName = basename2(input.cwd ?? process.cwd());
|
|
1069
|
-
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1070
|
-
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1071
1107
|
const reason = describeStopFailure(input.error);
|
|
1108
|
+
await reportEvent({
|
|
1109
|
+
event: "turn_error",
|
|
1110
|
+
agentType: agent.type,
|
|
1111
|
+
agentName: `${agent.label} - ${projectName}`,
|
|
1112
|
+
action: reason,
|
|
1113
|
+
error: reason,
|
|
1114
|
+
sessionId: input.session_id
|
|
1115
|
+
// Same reason as the Notification report above, and the same budget shape:
|
|
1116
|
+
// this is followed by a ~3s fetchModeState and a 4s sendNotification inside
|
|
1117
|
+
// Claude Code's 10s StopFailure budget. The stopfailure hook prints nothing,
|
|
1118
|
+
// so without this the phone's queued instruction is taken and destroyed.
|
|
1119
|
+
}, { maxAttempts: 1, timeoutMs: 2500, signal }).catch(() => {
|
|
1120
|
+
});
|
|
1121
|
+
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS, signal).catch(() => null);
|
|
1122
|
+
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1072
1123
|
await sendNotification(apiKey, {
|
|
1073
1124
|
title: `${agent.label} stopped on an error`,
|
|
1074
1125
|
body: reason,
|
|
1075
1126
|
agentName: `${agent.label} - ${projectName}`,
|
|
1076
1127
|
sessionId: input.session_id,
|
|
1077
1128
|
context: { type: "error", errorMessage: reason }
|
|
1078
|
-
}, 4e3).catch(() => {
|
|
1129
|
+
}, 4e3, signal).catch(() => {
|
|
1079
1130
|
});
|
|
1080
1131
|
} catch {
|
|
1081
1132
|
}
|
|
@@ -1101,6 +1152,7 @@ export {
|
|
|
1101
1152
|
permissionTimeoutDecision,
|
|
1102
1153
|
preToolUseTimeoutDecision,
|
|
1103
1154
|
readLastPrompt,
|
|
1155
|
+
CLAUDE_CODE_AGENT,
|
|
1104
1156
|
reportEvent,
|
|
1105
1157
|
settleQuestionsForToolUse,
|
|
1106
1158
|
handlePostToolUse,
|