@pushary/agent-hooks 0.87.1 → 0.87.2
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.
|
@@ -36,6 +36,8 @@ import { existsSync } from "fs";
|
|
|
36
36
|
import { spawn } from "child_process";
|
|
37
37
|
import { basename } from "path";
|
|
38
38
|
var MAX_RPC_LINE = 1024 * 1024;
|
|
39
|
+
var STDERR_TAIL_LIMIT = 2e3;
|
|
40
|
+
var FAILURE_MESSAGE_MAX = 240;
|
|
39
41
|
var MAX_PENDING_RPC = 8;
|
|
40
42
|
var MAX_TRANSCRIPT_TEXT = 32 * 1024;
|
|
41
43
|
var MAX_MESSAGE_BUFFERS = 16;
|
|
@@ -46,20 +48,34 @@ var JsonRpcClient = class {
|
|
|
46
48
|
nextId = 1;
|
|
47
49
|
buffer = "";
|
|
48
50
|
closed = false;
|
|
51
|
+
stderrTail = "";
|
|
52
|
+
failure = null;
|
|
49
53
|
constructor(command, cwd, timeoutMs) {
|
|
50
54
|
const options = { cwd, env: process.env, stdio: ["pipe", "pipe", "pipe"] };
|
|
51
55
|
this.child = process.platform === "win32" ? spawnClaude(command.command, command.args ?? [], options) : spawn(command.command, command.args ?? [], options);
|
|
52
56
|
this.child.stdout?.setEncoding("utf8");
|
|
53
57
|
this.child.stdout?.on("data", (chunk) => this.read(String(chunk)));
|
|
54
|
-
this.child.stderr?.
|
|
58
|
+
this.child.stderr?.setEncoding("utf8");
|
|
59
|
+
this.child.stderr?.on("data", (chunk) => this.keepStderrTail(String(chunk)));
|
|
55
60
|
this.child.once("error", (error) => this.fail(error));
|
|
56
|
-
this.child.once("exit", () => this.fail(new Error(
|
|
61
|
+
this.child.once("exit", (code, signal) => this.fail(new Error(this.exitMessage(code, signal))));
|
|
57
62
|
this.timeoutMs = timeoutMs;
|
|
58
63
|
}
|
|
64
|
+
keepStderrTail(chunk) {
|
|
65
|
+
this.stderrTail = (this.stderrTail + chunk).slice(-STDERR_TAIL_LIMIT);
|
|
66
|
+
}
|
|
67
|
+
exitMessage(code, signal) {
|
|
68
|
+
const how = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
|
|
69
|
+
const tail = this.stderrTail.trim();
|
|
70
|
+
return tail ? `Codex app-server exited (${how}): ${tail}` : `Codex app-server exited (${how})`;
|
|
71
|
+
}
|
|
59
72
|
timeoutMs;
|
|
60
73
|
get isClosed() {
|
|
61
74
|
return this.closed;
|
|
62
75
|
}
|
|
76
|
+
get closedReason() {
|
|
77
|
+
return this.failure?.message ?? "Codex app-server exited";
|
|
78
|
+
}
|
|
63
79
|
onNotification(listener) {
|
|
64
80
|
this.notifications.add(listener);
|
|
65
81
|
return () => this.notifications.delete(listener);
|
|
@@ -142,6 +158,7 @@ var JsonRpcClient = class {
|
|
|
142
158
|
fail(error) {
|
|
143
159
|
if (this.closed) return;
|
|
144
160
|
this.closed = true;
|
|
161
|
+
this.failure = error;
|
|
145
162
|
this.rejectPending(error);
|
|
146
163
|
if (this.child.exitCode == null) this.child.kill();
|
|
147
164
|
}
|
|
@@ -281,7 +298,7 @@ var runCodexBridge = async (options) => {
|
|
|
281
298
|
await relay?.flush();
|
|
282
299
|
await liveSessionSleep(options.pollIntervalMs ?? 2e3, options.signal);
|
|
283
300
|
}
|
|
284
|
-
if (rpc.isClosed && !options.signal.aborted) throw new Error(
|
|
301
|
+
if (rpc.isClosed && !options.signal.aborted) throw new Error(rpc.closedReason);
|
|
285
302
|
if (activeTurnId) {
|
|
286
303
|
await rpc.request("turn/interrupt", { threadId, turnId: activeTurnId }).catch(() => null);
|
|
287
304
|
}
|
|
@@ -289,8 +306,8 @@ var runCodexBridge = async (options) => {
|
|
|
289
306
|
await report("session_closed", "Session stopped").catch(() => null);
|
|
290
307
|
return "stopped";
|
|
291
308
|
} catch (error) {
|
|
309
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
292
310
|
if (!linked && options.claimId) {
|
|
293
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
294
311
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
295
312
|
const acknowledged = await postLiveSession(options, "/api/agent/spawn/ack", {
|
|
296
313
|
spawnId: options.spawnId,
|
|
@@ -299,7 +316,7 @@ var runCodexBridge = async (options) => {
|
|
|
299
316
|
state: "failed",
|
|
300
317
|
failure: {
|
|
301
318
|
code: "bridge_failed",
|
|
302
|
-
message: message.slice(0,
|
|
319
|
+
message: message.slice(0, FAILURE_MESSAGE_MAX),
|
|
303
320
|
retryable: false
|
|
304
321
|
}
|
|
305
322
|
}).catch(() => null);
|
|
@@ -307,7 +324,7 @@ var runCodexBridge = async (options) => {
|
|
|
307
324
|
await liveSessionSleep(200 * (attempt + 1), options.signal);
|
|
308
325
|
}
|
|
309
326
|
}
|
|
310
|
-
await report("error",
|
|
327
|
+
await report("error", `Live bridge failed: ${message}`.slice(0, FAILURE_MESSAGE_MAX)).catch(() => null);
|
|
311
328
|
return "failed";
|
|
312
329
|
} finally {
|
|
313
330
|
stopHeartbeat();
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
printConnectInstructions,
|
|
82
82
|
setHumanStream,
|
|
83
83
|
verifyRoundTrip
|
|
84
|
-
} from "../chunk-
|
|
84
|
+
} from "../chunk-3LKYBFWA.js";
|
|
85
85
|
import "../chunk-3EGEA4KH.js";
|
|
86
86
|
import {
|
|
87
87
|
checkApiKey,
|
|
@@ -1865,7 +1865,8 @@ var main = async () => {
|
|
|
1865
1865
|
// `false` is allowed to hold back "Setup complete.".
|
|
1866
1866
|
roundTripAnswered: roundTrip === null ? null : roundTrip.kind === "answered" ? true : roundTrip.kind === "unanswered" ? false : null
|
|
1867
1867
|
});
|
|
1868
|
-
const
|
|
1868
|
+
const keyIsTrusted = keyFromPairing || keyCheck?.kind === "ok";
|
|
1869
|
+
const daemon = keyIsTrusted && completed.length > 0 ? await ensureDaemonRunning() : null;
|
|
1869
1870
|
console.log(` ${closing.ok ? green(bold(closing.headline)) : yellow(bold(closing.headline))}`);
|
|
1870
1871
|
if (failed.length > 0) {
|
|
1871
1872
|
console.log();
|
|
@@ -72,6 +72,15 @@ var describeAppRepair = (verdict) => {
|
|
|
72
72
|
];
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
var describeJustPairedRepair = (verdict) => {
|
|
76
|
+
if (verdict.kind === "nobody") {
|
|
77
|
+
return [
|
|
78
|
+
"The app asks for the notification permission after it grants the pairing.",
|
|
79
|
+
"Open the Pushary app and allow notifications. That is the whole remaining step."
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
return describeAppRepair(verdict) ?? [];
|
|
83
|
+
};
|
|
75
84
|
|
|
76
85
|
// src/setup/verify.ts
|
|
77
86
|
var plural2 = (count, one) => `${count} ${one}${count === 1 ? "" : "s"}`;
|
|
@@ -586,14 +595,55 @@ var describeExistingConnection = (channels) => {
|
|
|
586
595
|
}
|
|
587
596
|
return "Phone already connected";
|
|
588
597
|
};
|
|
598
|
+
var APP_REGISTER_TIMEOUT_MS = 45e3;
|
|
599
|
+
var appRegisterTimeoutMs = () => {
|
|
600
|
+
const raw = Number(process.env.PUSHARY_APP_REGISTER_TIMEOUT_MS);
|
|
601
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : APP_REGISTER_TIMEOUT_MS;
|
|
602
|
+
};
|
|
603
|
+
var ownedApp = (status) => status.yours?.appDevices ?? status.appDevices;
|
|
604
|
+
var waitForAppRegistration = async (apiKey) => {
|
|
605
|
+
const deadline = Date.now() + appRegisterTimeoutMs();
|
|
606
|
+
if (Date.now() >= deadline) return { kind: "timeout" };
|
|
607
|
+
const stop = startSpinner(() => {
|
|
608
|
+
const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
|
|
609
|
+
return `Waiting for your phone to register for notifications ${dim(`(${left}s)`)}`;
|
|
610
|
+
});
|
|
611
|
+
try {
|
|
612
|
+
while (Date.now() < deadline) {
|
|
613
|
+
await sleep(CONNECT_POLL_INTERVAL_MS);
|
|
614
|
+
const probe = await fetchChannels(apiKey);
|
|
615
|
+
if (probe.kind === "unauthorized") {
|
|
616
|
+
stop(yellow("!"), "The server stopped accepting this key");
|
|
617
|
+
return { kind: "unauthorized" };
|
|
618
|
+
}
|
|
619
|
+
if (probe.kind === "locked") {
|
|
620
|
+
stop(yellow("!"), "This workspace has no active plan");
|
|
621
|
+
return { kind: "locked" };
|
|
622
|
+
}
|
|
623
|
+
if (probe.kind !== "ok") continue;
|
|
624
|
+
if (!probe.status.precise || ownedApp(probe.status) > 0) {
|
|
625
|
+
stop(check, "Phone registered for notifications");
|
|
626
|
+
return { kind: "connected", channels: probe.status };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
stop(yellow("!"), "This phone has not registered for notifications yet");
|
|
632
|
+
return { kind: "timeout" };
|
|
633
|
+
};
|
|
589
634
|
var confirmAppConnection = async (apiKey) => {
|
|
590
635
|
const probe = await fetchChannels(apiKey);
|
|
591
636
|
if (probe.kind !== "ok") return describeProbeFailure(probe);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
637
|
+
let channels = probe.status;
|
|
638
|
+
if (channels.precise && ownedApp(channels) === 0) {
|
|
639
|
+
const settled = await waitForAppRegistration(apiKey);
|
|
640
|
+
if (settled.kind === "unauthorized") return describeProbeFailure({ kind: "unauthorized" });
|
|
641
|
+
if (settled.kind === "locked") return describeProbeFailure({ kind: "locked" });
|
|
642
|
+
if (settled.kind === "connected") channels = settled.channels;
|
|
643
|
+
}
|
|
644
|
+
if (channels.precise && ownedApp(channels) === 0) {
|
|
645
|
+
const repair = describeJustPairedRepair(reachVerdict(channels));
|
|
646
|
+
console.log(` ${yellow("!")} Paired, but this phone has not registered for notifications yet.`);
|
|
597
647
|
for (const line of repair) console.log(` ${dim(line)}`);
|
|
598
648
|
console.log(` ${dim("Then run")} ${cyan("pushary doctor")}${dim(".")}`);
|
|
599
649
|
return phoneFailure("app_not_registered", repair);
|