@pushary/agent-hooks 0.89.2 → 0.89.4
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 +9 -0
- package/README.md +40 -0
- package/dist/bin/pushary-bell-hook.js +3 -3
- package/dist/bin/pushary-bell.js +9 -10
- package/dist/bin/pushary-claude.js +106 -24
- package/dist/bin/pushary-clean.js +28 -7
- package/dist/bin/pushary-codex-bridge.js +17 -7
- package/dist/bin/pushary-codex-hook.js +8 -7
- package/dist/bin/pushary-codex.js +6 -4
- package/dist/bin/pushary-connect.js +4 -4
- package/dist/bin/pushary-daemon.js +141 -21
- package/dist/bin/pushary-disconnect.js +3 -4
- package/dist/bin/pushary-doctor.js +45 -29
- package/dist/bin/pushary-elicitation-hook.js +4 -2
- package/dist/bin/pushary-gemini-bridge.js +16 -6
- package/dist/bin/pushary-gemini-hook.js +6 -4
- package/dist/bin/pushary-hook.js +9 -7
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +5 -6
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-notification-hook.js +6 -4
- package/dist/bin/pushary-opencode-hook.js +14 -6
- package/dist/bin/pushary-permission-denied-hook.js +7 -5
- package/dist/bin/pushary-permission-hook.js +7 -5
- package/dist/bin/pushary-post-hook.js +6 -4
- package/dist/bin/pushary-prompt-hook.js +6 -4
- package/dist/bin/pushary-session-end-hook.js +6 -4
- package/dist/bin/pushary-session-start-hook.js +6 -4
- package/dist/bin/pushary-setup.js +49 -28
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +7 -6
- package/dist/bin/pushary-stop-hook.js +6 -4
- package/dist/bin/pushary-stopfailure-hook.js +6 -4
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +63 -28
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/bin/pushary.js +1 -1
- package/dist/chunk-2HYPZLYY.js +45 -0
- package/dist/{chunk-ZYPS2XMT.js → chunk-4ZHQNBDP.js} +1 -1
- package/dist/{chunk-B7JE4XHD.js → chunk-7IJYOBHL.js} +1 -1
- package/dist/{chunk-NRGRUOAR.js → chunk-FWUEYXSD.js} +4 -0
- package/dist/{chunk-J3YDT4HM.js → chunk-GKAKAOKC.js} +68 -2
- package/dist/{chunk-GOEXZ5QJ.js → chunk-GPNOHKFO.js} +9 -2
- package/dist/{chunk-SQOFGPNX.js → chunk-KJ57OPCC.js} +1 -1
- package/dist/{chunk-LGXTBUT4.js → chunk-KOPUTJQC.js} +1 -0
- package/dist/{chunk-HEQVIIBN.js → chunk-MZBT2LXW.js} +4 -4
- package/dist/{chunk-6XSC6Z2K.js → chunk-NFI2K2ZQ.js} +80 -23
- package/dist/{chunk-K42SZKYF.js → chunk-NLMSSKES.js} +251 -6
- package/dist/{chunk-4VYUMYWD.js → chunk-OBI4YMCT.js} +17 -2
- package/dist/{chunk-LUCFN47K.js → chunk-QH4AA7QV.js} +1 -1
- package/dist/{chunk-VKVHY5HD.js → chunk-V37ZXMH3.js} +11 -3
- package/dist/{chunk-MHBC2XUP.js → chunk-VP7SDXYG.js} +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +7 -5
- package/package.json +1 -1
- package/dist/chunk-GIRT7677.js +0 -11
- package/dist/chunk-RN3NOEJF.js +0 -9
- package/dist/{chunk-LALHWDWL.js → chunk-W6NPWHBJ.js} +3 -3
package/dist/bin/pushary-wait.js
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
} from "../chunk-GMXKITVA.js";
|
|
15
15
|
import {
|
|
16
16
|
exitOnHelpFlag
|
|
17
|
-
} from "../chunk-
|
|
18
|
-
import "../chunk-
|
|
17
|
+
} from "../chunk-KJ57OPCC.js";
|
|
18
|
+
import "../chunk-GPNOHKFO.js";
|
|
19
19
|
import "../chunk-DINDL2CF.js";
|
|
20
20
|
import "../chunk-SAF6HGAA.js";
|
|
21
21
|
import {
|
package/dist/bin/pushary.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pusharyDir
|
|
3
|
+
} from "./chunk-V37ZXMH3.js";
|
|
4
|
+
import {
|
|
5
|
+
writeFileAtomic
|
|
6
|
+
} from "./chunk-6MTNS63X.js";
|
|
7
|
+
|
|
8
|
+
// src/identity.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13
|
+
var readMachineId = (filePath) => {
|
|
14
|
+
try {
|
|
15
|
+
const value = readFileSync(filePath, "utf8").trim();
|
|
16
|
+
return UUID.test(value) ? value : null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var getOrCreateMachineId = (options = {}) => {
|
|
22
|
+
const filePath = options.filePath ?? join(pusharyDir(), "machine-id");
|
|
23
|
+
const existing = readMachineId(filePath);
|
|
24
|
+
if (existing) return existing;
|
|
25
|
+
const id = (options.randomId ?? randomUUID)();
|
|
26
|
+
if (!UUID.test(id)) throw new Error("machine id generator returned an invalid UUID");
|
|
27
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
28
|
+
try {
|
|
29
|
+
writeFileSync(filePath, `${id}
|
|
30
|
+
`, { flag: "wx", mode: 384 });
|
|
31
|
+
return id;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err.code !== "EEXIST") throw err;
|
|
34
|
+
}
|
|
35
|
+
const winner = readMachineId(filePath);
|
|
36
|
+
if (winner) return winner;
|
|
37
|
+
writeFileAtomic(filePath, `${id}
|
|
38
|
+
`, 384);
|
|
39
|
+
return id;
|
|
40
|
+
};
|
|
41
|
+
var getMachineId = (options = {}) => getOrCreateMachineId(options);
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
getMachineId
|
|
45
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// src/live-capabilities.ts
|
|
2
|
+
var CLAUDE_BRIDGE_CAPABILITY = "claude-bridge";
|
|
3
|
+
var CLAUDE_RESUME_CAPABILITY = "claude-resume";
|
|
2
4
|
var CODEX_BRIDGE_CAPABILITY = "codex-bridge";
|
|
3
5
|
var CODEX_RESUME_CAPABILITY = "codex-resume";
|
|
4
6
|
var GEMINI_BRIDGE_CAPABILITY = "gemini-bridge";
|
|
@@ -19,6 +21,8 @@ var geminiLiveCapabilities = (rawVersion) => {
|
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export {
|
|
24
|
+
CLAUDE_BRIDGE_CAPABILITY,
|
|
25
|
+
CLAUDE_RESUME_CAPABILITY,
|
|
22
26
|
CODEX_BRIDGE_CAPABILITY,
|
|
23
27
|
CODEX_RESUME_CAPABILITY,
|
|
24
28
|
geminiLiveCapabilities
|
|
@@ -68,13 +68,74 @@ var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
|
|
|
68
68
|
|
|
69
69
|
// src/pending.ts
|
|
70
70
|
import { join } from "path";
|
|
71
|
-
import { tmpdir } from "os";
|
|
72
|
-
import {
|
|
71
|
+
import { homedir, tmpdir } from "os";
|
|
72
|
+
import { createHash } from "crypto";
|
|
73
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync, unlinkSync, rmSync, statSync } from "fs";
|
|
73
74
|
var PENDING_DIR = join(tmpdir(), "pushary-pending");
|
|
75
|
+
var COMMAND_DIR = join(homedir(), ".pushary", "run", "commands");
|
|
74
76
|
var DEFAULT_SESSION = "_no_session";
|
|
75
77
|
var GRACE_MS = 10 * 60 * 1e3;
|
|
76
78
|
var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
77
79
|
var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
|
|
80
|
+
var commandDirFor = (sessionId, namespace = "") => join(
|
|
81
|
+
COMMAND_DIR,
|
|
82
|
+
createHash("sha256").update(namespace).digest("hex").slice(0, 16),
|
|
83
|
+
sanitize(sessionId)
|
|
84
|
+
);
|
|
85
|
+
var commandFile = (sessionId, namespace = "") => join(commandDirFor(sessionId, namespace), "command.json");
|
|
86
|
+
var readPendingCommand = (sessionId, namespace = "") => {
|
|
87
|
+
try {
|
|
88
|
+
const value = JSON.parse(readFileSync(commandFile(sessionId, namespace), "utf-8"));
|
|
89
|
+
if (typeof value.id !== "string" || typeof value.text !== "string" || typeof value.expiresAtMs !== "number") return null;
|
|
90
|
+
if (value.expiresAtMs <= Date.now()) {
|
|
91
|
+
unlinkSync(commandFile(sessionId, namespace));
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
...value,
|
|
96
|
+
delivered: value.delivered === true,
|
|
97
|
+
...value.failed === true ? { failed: true } : {}
|
|
98
|
+
};
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var savePendingCommand = (sessionId, command, namespace = "") => {
|
|
104
|
+
const dir = commandDirFor(sessionId, namespace);
|
|
105
|
+
try {
|
|
106
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
107
|
+
writeFileSync(commandFile(sessionId, namespace), JSON.stringify(command), { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var updatePendingCommand = (sessionId, id, update, namespace = "") => {
|
|
114
|
+
const command = readPendingCommand(sessionId, namespace);
|
|
115
|
+
if (!command || command.id !== id) return false;
|
|
116
|
+
const file = commandFile(sessionId, namespace);
|
|
117
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
118
|
+
try {
|
|
119
|
+
writeFileSync(temporary, JSON.stringify({ ...command, ...update }), { encoding: "utf-8", mode: 384 });
|
|
120
|
+
renameSync(temporary, file);
|
|
121
|
+
return true;
|
|
122
|
+
} catch {
|
|
123
|
+
try {
|
|
124
|
+
unlinkSync(temporary);
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
var markPendingCommandDelivered = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: true, failed: void 0 }, namespace);
|
|
131
|
+
var markPendingCommandFailed = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: false, failed: true }, namespace);
|
|
132
|
+
var removePendingCommand = (sessionId, id, namespace = "") => {
|
|
133
|
+
if (readPendingCommand(sessionId, namespace)?.id !== id) return;
|
|
134
|
+
try {
|
|
135
|
+
unlinkSync(commandFile(sessionId, namespace));
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
};
|
|
78
139
|
var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
|
|
79
140
|
var savePendingQuestion = (sessionId, correlationId) => {
|
|
80
141
|
const dir = dirFor(sessionId);
|
|
@@ -120,6 +181,11 @@ export {
|
|
|
120
181
|
cancelQuestion,
|
|
121
182
|
sendNotification,
|
|
122
183
|
DEFAULT_SESSION,
|
|
184
|
+
readPendingCommand,
|
|
185
|
+
savePendingCommand,
|
|
186
|
+
markPendingCommandDelivered,
|
|
187
|
+
markPendingCommandFailed,
|
|
188
|
+
removePendingCommand,
|
|
123
189
|
isDefaultSession,
|
|
124
190
|
savePendingQuestion,
|
|
125
191
|
listPendingQuestions,
|
|
@@ -19,8 +19,8 @@ var COMMAND_LIST = [
|
|
|
19
19
|
module: "pushary-daemon",
|
|
20
20
|
summary: "Keep this machine reachable so your phone can START a new session",
|
|
21
21
|
detail: [
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"Install the native per-user service for restart-on-login and restart-on-failure.",
|
|
23
|
+
"A bare `pushary daemon` remains foreground mode for diagnostics."
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -114,6 +114,13 @@ var COMMAND_OPTIONS = {
|
|
|
114
114
|
claude: [
|
|
115
115
|
["--remote", "Start headless and drive it entirely from your phone"],
|
|
116
116
|
["-p <prompt>", "Initial prompt"]
|
|
117
|
+
],
|
|
118
|
+
daemon: [
|
|
119
|
+
["install", "Install and start the native per-user service"],
|
|
120
|
+
["start | stop | restart", "Control the installed service"],
|
|
121
|
+
["status", "Show local daemon health and PID"],
|
|
122
|
+
["uninstall", "Stop and remove the native service"],
|
|
123
|
+
["foreground", "Run attached to this terminal for diagnostics"]
|
|
117
124
|
]
|
|
118
125
|
};
|
|
119
126
|
var SETUP_OPTIONS = [
|
|
@@ -268,6 +268,7 @@ var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACT
|
|
|
268
268
|
var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
|
|
269
269
|
|
|
270
270
|
export {
|
|
271
|
+
HOOK_FALLBACK_MAX,
|
|
271
272
|
describeToolCall,
|
|
272
273
|
deriveToolTarget,
|
|
273
274
|
parseApplyPatchOps,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
readLastUserPrompt,
|
|
18
18
|
repoKeyFor,
|
|
19
19
|
throttlePass
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-NFI2K2ZQ.js";
|
|
21
21
|
import {
|
|
22
22
|
DEFAULT_SESSION,
|
|
23
23
|
askUser,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
pollForAnswer,
|
|
26
26
|
savePendingQuestion,
|
|
27
27
|
sendNotification
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-GKAKAOKC.js";
|
|
29
29
|
import {
|
|
30
30
|
deriveAction,
|
|
31
31
|
deriveActionBody,
|
|
@@ -33,10 +33,10 @@ import {
|
|
|
33
33
|
deriveToolTarget,
|
|
34
34
|
describeToolCall,
|
|
35
35
|
scopePathFor
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-KOPUTJQC.js";
|
|
37
37
|
import {
|
|
38
38
|
getMachineId
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-2HYPZLYY.js";
|
|
40
40
|
import {
|
|
41
41
|
getApiKey,
|
|
42
42
|
getBaseUrl
|
|
@@ -4,11 +4,15 @@ import {
|
|
|
4
4
|
isDefaultSession,
|
|
5
5
|
isPolicyConfig,
|
|
6
6
|
listPendingQuestions,
|
|
7
|
+
markPendingCommandDelivered,
|
|
8
|
+
readPendingCommand,
|
|
9
|
+
removePendingCommand,
|
|
7
10
|
removePendingQuestion,
|
|
8
11
|
removePendingSession,
|
|
12
|
+
savePendingCommand,
|
|
9
13
|
sendNotification,
|
|
10
14
|
waitForAnswer
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GKAKAOKC.js";
|
|
12
16
|
import {
|
|
13
17
|
AGENT_IDENTITIES,
|
|
14
18
|
deriveReceiptMeta,
|
|
@@ -22,10 +26,10 @@ import {
|
|
|
22
26
|
summarizeStop,
|
|
23
27
|
taskTitleFrom,
|
|
24
28
|
toolResultErrorText
|
|
25
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-KOPUTJQC.js";
|
|
26
30
|
import {
|
|
27
31
|
getMachineId
|
|
28
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-2HYPZLYY.js";
|
|
29
33
|
import {
|
|
30
34
|
withRetry
|
|
31
35
|
} from "./chunk-BSZYIAZL.js";
|
|
@@ -666,10 +670,37 @@ var consumesActivityContext = (agentType) => {
|
|
|
666
670
|
return pendingCommandEvents(source).some((event) => event !== "Stop");
|
|
667
671
|
};
|
|
668
672
|
var detectInstallMode = () => (process.argv[1] ?? "").includes("_npx") ? "npx" : "cli";
|
|
673
|
+
var acknowledgePendingCommand = async (apiKey, baseUrl, commandId, sessionId, machineId, timeoutMs, state = "delivered") => {
|
|
674
|
+
try {
|
|
675
|
+
const response = await fetch(`${baseUrl}/api/agent/command/ack`, {
|
|
676
|
+
method: "POST",
|
|
677
|
+
headers: {
|
|
678
|
+
"Content-Type": "application/json",
|
|
679
|
+
"Authorization": `Bearer ${apiKey}`
|
|
680
|
+
},
|
|
681
|
+
body: JSON.stringify({
|
|
682
|
+
commandId,
|
|
683
|
+
sessionId: sessionId ?? null,
|
|
684
|
+
machineId,
|
|
685
|
+
state,
|
|
686
|
+
...state === "failed" ? { failureCode: "consumer_rejected" } : {}
|
|
687
|
+
}),
|
|
688
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
689
|
+
});
|
|
690
|
+
return response.ok;
|
|
691
|
+
} catch {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
};
|
|
669
695
|
var reportEvent = async (event, options = {}) => {
|
|
670
696
|
const apiKey = options.apiKey ?? getApiKey();
|
|
671
697
|
const baseUrl = getBaseUrl();
|
|
698
|
+
const machineId = event.machineId ?? getMachineId();
|
|
699
|
+
const sessionKey = event.sessionId || DEFAULT_SESSION;
|
|
700
|
+
const receiptNamespace = `${apiKey}:${machineId}:${event.agentType}`;
|
|
701
|
+
const clientCanDrain = consumesActivityContext(event.agentType) && options.timeoutMs === void 0;
|
|
672
702
|
return withRetry(async () => {
|
|
703
|
+
const storedCommand = clientCanDrain ? readPendingCommand(sessionKey, receiptNamespace) : null;
|
|
673
704
|
const res = await fetch(`${baseUrl}/api/agent/event`, {
|
|
674
705
|
method: "POST",
|
|
675
706
|
headers: {
|
|
@@ -678,7 +709,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
678
709
|
},
|
|
679
710
|
body: JSON.stringify({
|
|
680
711
|
...event,
|
|
681
|
-
machineId
|
|
712
|
+
machineId,
|
|
682
713
|
// Lets the phone's Stop end this process instead of only denying its next
|
|
683
714
|
// tool call. Ours alone: never another process, never a parent.
|
|
684
715
|
pid: event.pid,
|
|
@@ -693,27 +724,56 @@ var reportEvent = async (event, options = {}) => {
|
|
|
693
724
|
// Link-back for a phone-spawned session: forward the spawn id the daemon
|
|
694
725
|
// set in the environment so the server can map spawnId -> sessionId.
|
|
695
726
|
...process.env.PUSHARY_SPAWN_ID ? { spawnId: process.env.PUSHARY_SPAWN_ID } : {},
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
// Only Claude Code consumes additionalContext from these hooks; Codex and
|
|
700
|
-
// Gemini keep their existing session_end-only drain, so they must NOT
|
|
701
|
-
// advertise it or the server would pop-and-drop their queued command.
|
|
702
|
-
//
|
|
703
|
-
// And only when this caller kept the full budget. The pop is a destructive
|
|
704
|
-
// LPOP that happens before the response is written, so a caller that
|
|
705
|
-
// abandons the request destroys the message: `user_prompt` runs on a human
|
|
706
|
-
// keystroke and gave it 800ms, which a cold function misses routinely.
|
|
707
|
-
// Asking the server to consume something we may not wait to receive is the
|
|
708
|
-
// one shape that loses it outright, and the instruction reaches the agent
|
|
709
|
-
// on its next tool call instead.
|
|
710
|
-
canDrainCommand: consumesActivityContext(event.agentType) && options.timeoutMs === void 0
|
|
727
|
+
canDrainCommand: clientCanDrain && !storedCommand,
|
|
728
|
+
ackMode: clientCanDrain && !storedCommand,
|
|
729
|
+
skipCommandDrain: storedCommand != null
|
|
711
730
|
}),
|
|
712
731
|
signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
|
|
713
732
|
});
|
|
714
733
|
if (options.requireOk && !res.ok) throw new Error(`/api/agent/event rejected (${res.status})`);
|
|
715
734
|
try {
|
|
716
|
-
|
|
735
|
+
const result = await res.json();
|
|
736
|
+
const withoutPending = () => ({ ...result, pendingCommand: void 0, pendingCommandId: void 0 });
|
|
737
|
+
if (storedCommand) {
|
|
738
|
+
const failed = storedCommand.failed === true;
|
|
739
|
+
const acknowledged = await acknowledgePendingCommand(
|
|
740
|
+
apiKey,
|
|
741
|
+
baseUrl,
|
|
742
|
+
storedCommand.id,
|
|
743
|
+
event.sessionId,
|
|
744
|
+
machineId,
|
|
745
|
+
options.timeoutMs ?? 1e4,
|
|
746
|
+
failed ? "failed" : "delivered"
|
|
747
|
+
);
|
|
748
|
+
if (!acknowledged) return withoutPending();
|
|
749
|
+
if (storedCommand.delivered || failed) {
|
|
750
|
+
removePendingCommand(sessionKey, storedCommand.id, receiptNamespace);
|
|
751
|
+
return withoutPending();
|
|
752
|
+
}
|
|
753
|
+
if (!markPendingCommandDelivered(sessionKey, storedCommand.id, receiptNamespace)) return withoutPending();
|
|
754
|
+
return { ...withoutPending(), pendingCommand: storedCommand.text, pendingCommandId: storedCommand.id };
|
|
755
|
+
}
|
|
756
|
+
if (typeof result.pendingCommandId === "string" && typeof result.pendingCommand === "string") {
|
|
757
|
+
const saved = savePendingCommand(sessionKey, {
|
|
758
|
+
id: result.pendingCommandId,
|
|
759
|
+
text: result.pendingCommand,
|
|
760
|
+
expiresAtMs: typeof result.pendingCommandExpiresAtMs === "number" ? result.pendingCommandExpiresAtMs : Date.now() + 36e5,
|
|
761
|
+
delivered: false
|
|
762
|
+
}, receiptNamespace);
|
|
763
|
+
if (!saved) return withoutPending();
|
|
764
|
+
const acknowledged = await acknowledgePendingCommand(
|
|
765
|
+
apiKey,
|
|
766
|
+
baseUrl,
|
|
767
|
+
result.pendingCommandId,
|
|
768
|
+
event.sessionId,
|
|
769
|
+
machineId,
|
|
770
|
+
options.timeoutMs ?? 1e4
|
|
771
|
+
);
|
|
772
|
+
if (!acknowledged || !markPendingCommandDelivered(sessionKey, result.pendingCommandId, receiptNamespace)) {
|
|
773
|
+
return withoutPending();
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return result;
|
|
717
777
|
} catch {
|
|
718
778
|
return null;
|
|
719
779
|
}
|
|
@@ -810,9 +870,6 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
810
870
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
811
871
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
812
872
|
const tasks = [
|
|
813
|
-
// maxAttempts:1 for the same reason as the activity-event drains: session_end
|
|
814
|
-
// always pops the command server-side (single-use LPOP), so a retry after a
|
|
815
|
-
// slow-but-successful response would pop a second command and drop the first.
|
|
816
873
|
reportEvent({
|
|
817
874
|
event: "session_end",
|
|
818
875
|
agentType: agent.type,
|