@raysonmeng/agentbridge 0.1.12 → 0.1.14
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +1 -1
- package/dist/cli.js +1235 -503
- package/dist/daemon.js +1262 -432
- package/package.json +3 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +726 -183
- package/plugins/agentbridge/server/daemon.js +1262 -432
|
@@ -13662,6 +13662,7 @@ class StdioServerTransport {
|
|
|
13662
13662
|
// src/claude-adapter.ts
|
|
13663
13663
|
import { EventEmitter } from "events";
|
|
13664
13664
|
import { randomUUID } from "crypto";
|
|
13665
|
+
import { performance } from "perf_hooks";
|
|
13665
13666
|
|
|
13666
13667
|
// src/rotating-log.ts
|
|
13667
13668
|
import { appendFileSync, existsSync, renameSync, statSync, unlinkSync } from "fs";
|
|
@@ -13805,6 +13806,10 @@ function formatError2(error2) {
|
|
|
13805
13806
|
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
13806
13807
|
import { join } from "path";
|
|
13807
13808
|
import { homedir, platform } from "os";
|
|
13809
|
+
function resolveXdgStateBase(rawXdg = process.env.XDG_STATE_HOME) {
|
|
13810
|
+
const xdgState = rawXdg && rawXdg.length > 0 ? rawXdg : join(homedir(), ".local", "state");
|
|
13811
|
+
return join(xdgState, "agentbridge");
|
|
13812
|
+
}
|
|
13808
13813
|
|
|
13809
13814
|
class StateDirResolver {
|
|
13810
13815
|
stateDir;
|
|
@@ -13812,8 +13817,7 @@ class StateDirResolver {
|
|
|
13812
13817
|
if (platform() === "darwin") {
|
|
13813
13818
|
return join(homedir(), "Library", "Application Support", "AgentBridge");
|
|
13814
13819
|
}
|
|
13815
|
-
|
|
13816
|
-
return join(xdgState, "agentbridge");
|
|
13820
|
+
return resolveXdgStateBase(process.env.XDG_STATE_HOME);
|
|
13817
13821
|
}
|
|
13818
13822
|
constructor(envOverride) {
|
|
13819
13823
|
const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
|
|
@@ -13839,8 +13843,8 @@ class StateDirResolver {
|
|
|
13839
13843
|
get statusFile() {
|
|
13840
13844
|
return join(this.stateDir, "status.json");
|
|
13841
13845
|
}
|
|
13842
|
-
get
|
|
13843
|
-
return join(this.stateDir, "
|
|
13846
|
+
get daemonRecordFile() {
|
|
13847
|
+
return join(this.stateDir, "daemon.json");
|
|
13844
13848
|
}
|
|
13845
13849
|
get currentThreadFile() {
|
|
13846
13850
|
return join(this.stateDir, "current-thread.json");
|
|
@@ -13859,7 +13863,48 @@ class StateDirResolver {
|
|
|
13859
13863
|
}
|
|
13860
13864
|
}
|
|
13861
13865
|
|
|
13866
|
+
// src/budget/types.ts
|
|
13867
|
+
var STALE_MAX_AGE_SEC = 600;
|
|
13868
|
+
|
|
13869
|
+
// src/budget/budget-state.ts
|
|
13870
|
+
function isDecisionGrade(usage, now) {
|
|
13871
|
+
if (!usage)
|
|
13872
|
+
return false;
|
|
13873
|
+
const freshWindow = usage.fiveHour !== null && usage.fiveHour.resetEpoch > now || usage.weekly !== null && usage.weekly.resetEpoch > now;
|
|
13874
|
+
if (!freshWindow)
|
|
13875
|
+
return false;
|
|
13876
|
+
if (usage.fetchedAt > 0 && now - usage.fetchedAt > STALE_MAX_AGE_SEC)
|
|
13877
|
+
return false;
|
|
13878
|
+
return true;
|
|
13879
|
+
}
|
|
13880
|
+
|
|
13881
|
+
// src/budget/burn-view.ts
|
|
13882
|
+
function agentWeeklyFiveHourWindowsLeft(usage, now) {
|
|
13883
|
+
if (!usage || usage.stale || !usage.ok)
|
|
13884
|
+
return null;
|
|
13885
|
+
if (!isDecisionGrade(usage, now))
|
|
13886
|
+
return null;
|
|
13887
|
+
const weekly = usage.weekly;
|
|
13888
|
+
if (!weekly || weekly.resetEpoch <= now)
|
|
13889
|
+
return null;
|
|
13890
|
+
if (weekly.burnConfident !== true)
|
|
13891
|
+
return null;
|
|
13892
|
+
if (weekly.runwaySeconds === undefined)
|
|
13893
|
+
return null;
|
|
13894
|
+
return weekly.fiveHourWindowsLeft ?? null;
|
|
13895
|
+
}
|
|
13896
|
+
|
|
13862
13897
|
// src/budget/render.ts
|
|
13898
|
+
var DEFAULT_GUARD_HARD_PCT = 92;
|
|
13899
|
+
function resolveGuardHardHint(env = process.env) {
|
|
13900
|
+
const raw = env.AGENTBRIDGE_GUARD_HARD_HINT;
|
|
13901
|
+
if (raw === undefined || raw.trim() === "")
|
|
13902
|
+
return DEFAULT_GUARD_HARD_PCT;
|
|
13903
|
+
const parsed = Number(raw);
|
|
13904
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 100)
|
|
13905
|
+
return DEFAULT_GUARD_HARD_PCT;
|
|
13906
|
+
return parsed;
|
|
13907
|
+
}
|
|
13863
13908
|
function formatEpoch(epochSeconds) {
|
|
13864
13909
|
if (!epochSeconds || epochSeconds <= 0)
|
|
13865
13910
|
return "\u672A\u77E5";
|
|
@@ -13893,17 +13938,98 @@ function formatAgent(name, usage, snapshotAt) {
|
|
|
13893
13938
|
}
|
|
13894
13939
|
return `${name}\uFF1A${parts.join(" \xB7 ")}`;
|
|
13895
13940
|
}
|
|
13941
|
+
var WINDOW_LABELS = {
|
|
13942
|
+
fiveHour: "5h \u7A97\u53E3",
|
|
13943
|
+
weekly: "\u5468\u7A97\u53E3"
|
|
13944
|
+
};
|
|
13945
|
+
var RESET_TRUNCATION_EPSILON_SEC = 60;
|
|
13946
|
+
function formatDuration(seconds) {
|
|
13947
|
+
const totalMinutes = Math.max(0, Math.round(seconds / 60));
|
|
13948
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
13949
|
+
const minutes = totalMinutes % 60;
|
|
13950
|
+
if (hours === 0)
|
|
13951
|
+
return `${minutes}\u5206\u949F`;
|
|
13952
|
+
return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
|
|
13953
|
+
}
|
|
13954
|
+
function formatClockTime(epochSeconds) {
|
|
13955
|
+
const date4 = new Date(epochSeconds * 1000);
|
|
13956
|
+
const hh = String(date4.getHours()).padStart(2, "0");
|
|
13957
|
+
const mm = String(date4.getMinutes()).padStart(2, "0");
|
|
13958
|
+
return `${hh}:${mm}`;
|
|
13959
|
+
}
|
|
13960
|
+
function formatWindowRate(label, rate) {
|
|
13961
|
+
if (!rate)
|
|
13962
|
+
return null;
|
|
13963
|
+
if (!rate.confident)
|
|
13964
|
+
return `${label} \u91C7\u6837\u4E2D`;
|
|
13965
|
+
return `${label} \u2248${rate.pctPerHour.toFixed(2)}%/h`;
|
|
13966
|
+
}
|
|
13967
|
+
function formatRunwaySegment(runway, basisWindow, snapshotAt) {
|
|
13968
|
+
const truncatedByReset = basisWindow !== null && basisWindow.resetEpoch > 0 && snapshotAt + runway.seconds >= basisWindow.resetEpoch - RESET_TRUNCATION_EPSILON_SEC;
|
|
13969
|
+
const clock = runway.depletedAtEpoch ? formatClockTime(runway.depletedAtEpoch) : null;
|
|
13970
|
+
let clockNote;
|
|
13971
|
+
if (clock) {
|
|
13972
|
+
clockNote = truncatedByReset ? `\u81F3 ${clock} \u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C` : `\u81F3 ${clock}\uFF0C`;
|
|
13973
|
+
} else {
|
|
13974
|
+
clockNote = truncatedByReset ? "\u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C" : "";
|
|
13975
|
+
}
|
|
13976
|
+
return `\u7EA6\u53EF\u518D\u5DE5\u4F5C ${formatDuration(runway.seconds)}\uFF08${clockNote}${WINDOW_LABELS[runway.basis]}\u4E3A\u7EA6\u675F\uFF09`;
|
|
13977
|
+
}
|
|
13978
|
+
function formatBurnRateLine(name, usage, rates, runway, snapshotAt, guardHardPct) {
|
|
13979
|
+
const parts = [
|
|
13980
|
+
formatWindowRate("5h", rates.fiveHour),
|
|
13981
|
+
formatWindowRate("\u5468", rates.weekly)
|
|
13982
|
+
].filter((part) => part !== null);
|
|
13983
|
+
if (parts.length === 0 && !runway)
|
|
13984
|
+
return null;
|
|
13985
|
+
if (runway) {
|
|
13986
|
+
const basisWindow = usage ? usage[runway.basis] : null;
|
|
13987
|
+
parts.push(formatRunwaySegment(runway, basisWindow, snapshotAt));
|
|
13988
|
+
}
|
|
13989
|
+
if (guardHardPct !== null) {
|
|
13990
|
+
parts.push(`\u5916\u5C42 guard \u786C\u7EBF ${guardHardPct}%\uFF08v3 \u4E0D\u53EF\u8D8A\u8FC7\uFF1Brunway \u4E3A\u4E2D\u6027\u53E3\u5F84\uFF0CClaude \u4F1A\u5148\u5728\u786C\u7EBF\u88AB\u5916\u5C42\u505C\u4F4F\uFF09`);
|
|
13991
|
+
}
|
|
13992
|
+
return `${name} \u71C3\u5C3D\u7387\uFF1A${parts.join(" \xB7 ")}`;
|
|
13993
|
+
}
|
|
13994
|
+
function formatFiveHourWindowsLeftLine(snapshot) {
|
|
13995
|
+
const values = [];
|
|
13996
|
+
const claude = agentWeeklyFiveHourWindowsLeft(snapshot.claude, snapshot.updatedAt);
|
|
13997
|
+
const codex = agentWeeklyFiveHourWindowsLeft(snapshot.codex, snapshot.updatedAt);
|
|
13998
|
+
if (claude !== null)
|
|
13999
|
+
values.push(["Claude", claude]);
|
|
14000
|
+
if (codex !== null)
|
|
14001
|
+
values.push(["Codex", codex]);
|
|
14002
|
+
if (values.length === 0)
|
|
14003
|
+
return null;
|
|
14004
|
+
const unique = [...new Set(values.map(([, value]) => value.toFixed(1)))];
|
|
14005
|
+
if (unique.length === 1)
|
|
14006
|
+
return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ~${unique[0]} \u4E2A 5h \u7A97\u53E3`;
|
|
14007
|
+
const byAgent = values.map(([name, value]) => `${name} ~${value.toFixed(1)}`).join(" / ");
|
|
14008
|
+
return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ${byAgent} \u4E2A 5h \u7A97\u53E3`;
|
|
14009
|
+
}
|
|
13896
14010
|
var PHASE_LABELS = {
|
|
13897
14011
|
normal: "normal\uFF08\u6B63\u5E38\uFF09",
|
|
13898
14012
|
balance: "balance\uFF08\u9700\u5747\u8861\uFF09",
|
|
13899
14013
|
parallel: "parallel\uFF08\u5EFA\u8BAE\u5E76\u884C\u63D0\u901F\uFF09",
|
|
13900
14014
|
paused: "paused\uFF08\u9884\u7B97\u5E72\u9884\u4E2D\uFF09"
|
|
13901
14015
|
};
|
|
13902
|
-
function renderBudgetSnapshot(snapshot) {
|
|
14016
|
+
function renderBudgetSnapshot(snapshot, options = {}) {
|
|
14017
|
+
const guardHardPct = options.guardHardPct ?? resolveGuardHardHint();
|
|
13903
14018
|
const lines = [];
|
|
13904
14019
|
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase]} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}`);
|
|
13905
14020
|
lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
|
|
13906
14021
|
lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
|
|
14022
|
+
if (snapshot.burnRate) {
|
|
14023
|
+
const claudeLine = formatBurnRateLine("Claude", snapshot.claude, snapshot.burnRate.claude, snapshot.runway?.claude ?? null, snapshot.updatedAt, guardHardPct);
|
|
14024
|
+
if (claudeLine)
|
|
14025
|
+
lines.push(claudeLine);
|
|
14026
|
+
const codexLine = formatBurnRateLine("Codex", snapshot.codex, snapshot.burnRate.codex, snapshot.runway?.codex ?? null, snapshot.updatedAt, null);
|
|
14027
|
+
if (codexLine)
|
|
14028
|
+
lines.push(codexLine);
|
|
14029
|
+
}
|
|
14030
|
+
const fiveHourWindowsLeftLine = formatFiveHourWindowsLeftLine(snapshot);
|
|
14031
|
+
if (fiveHourWindowsLeftLine)
|
|
14032
|
+
lines.push(fiveHourWindowsLeftLine);
|
|
13907
14033
|
if (snapshot.claude && snapshot.codex) {
|
|
13908
14034
|
const abs = Math.abs(snapshot.driftPct);
|
|
13909
14035
|
if (abs > 0) {
|
|
@@ -13943,6 +14069,10 @@ function renderBudgetSnapshot(snapshot) {
|
|
|
13943
14069
|
var BUDGET_UNAVAILABLE_TEXT = "\u9884\u7B97\u611F\u77E5\u4E0D\u53EF\u7528\uFF1A\u672A\u68C0\u6D4B\u5230 agent-quota-guard \u63A2\u9488\uFF08~/.budget-guard/bin/budget-probe\uFF09\u6216 budget \u529F\u80FD\u5DF2\u7981\u7528\u3002\u534F\u4F5C\u4E0D\u53D7\u5F71\u54CD\u3002";
|
|
13944
14070
|
|
|
13945
14071
|
// src/claude-adapter.ts
|
|
14072
|
+
var DEFAULT_MAX_BUFFERED_MESSAGES = 100;
|
|
14073
|
+
var DEFAULT_MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
14074
|
+
var DEFAULT_DEDUPE_CAPACITY = 2048;
|
|
14075
|
+
var DEFAULT_DEDUPE_TTL_MS = 20 * 60 * 1000;
|
|
13946
14076
|
var CLAUDE_INSTRUCTIONS = [
|
|
13947
14077
|
"Codex is an AI coding agent (OpenAI) running in a separate session on the same machine.",
|
|
13948
14078
|
"",
|
|
@@ -13991,10 +14121,20 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
13991
14121
|
logFile;
|
|
13992
14122
|
logger;
|
|
13993
14123
|
pendingMessages = [];
|
|
14124
|
+
pendingMessageByteSizes = [];
|
|
14125
|
+
pendingMessageBytes = 0;
|
|
13994
14126
|
maxBufferedMessages;
|
|
14127
|
+
maxBufferedBytes;
|
|
13995
14128
|
droppedMessageCount = 0;
|
|
14129
|
+
oversizedMessageCount = 0;
|
|
14130
|
+
oversizedMessageBytes = 0;
|
|
14131
|
+
oversizedMessageSourceCounts = {};
|
|
14132
|
+
dedupeCapacity;
|
|
14133
|
+
dedupeTtlMs;
|
|
14134
|
+
monotonicNow;
|
|
14135
|
+
deliveredMessageIds = new Map;
|
|
13996
14136
|
budgetSnapshot = null;
|
|
13997
|
-
constructor(logFile = new StateDirResolver().logFile) {
|
|
14137
|
+
constructor(logFile = new StateDirResolver().logFile, options = {}) {
|
|
13998
14138
|
super();
|
|
13999
14139
|
this.logFile = logFile;
|
|
14000
14140
|
this.logger = createProcessLogger({ component: "ClaudeAdapter", logFile: this.logFile });
|
|
@@ -14005,7 +14145,11 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14005
14145
|
if (process.env.AGENTBRIDGE_MODE) {
|
|
14006
14146
|
this.log(`AGENTBRIDGE_MODE="${process.env.AGENTBRIDGE_MODE}" is no longer supported \u2014 ` + "pull mode was removed; push delivery (with per-message fallback queue) is always used.");
|
|
14007
14147
|
}
|
|
14008
|
-
this.maxBufferedMessages =
|
|
14148
|
+
this.maxBufferedMessages = positiveIntegerOr(options.maxBufferedMessages, parsePositiveIntegerEnv("AGENTBRIDGE_MAX_BUFFERED_MESSAGES", DEFAULT_MAX_BUFFERED_MESSAGES));
|
|
14149
|
+
this.maxBufferedBytes = positiveIntegerOr(options.maxBufferedBytes, parsePositiveIntegerEnv("AGENTBRIDGE_MAX_BUFFERED_BYTES", DEFAULT_MAX_BUFFERED_BYTES));
|
|
14150
|
+
this.dedupeCapacity = positiveIntegerOr(options.dedupeCapacity, DEFAULT_DEDUPE_CAPACITY);
|
|
14151
|
+
this.dedupeTtlMs = positiveIntegerOr(options.dedupeTtlMs, DEFAULT_DEDUPE_TTL_MS);
|
|
14152
|
+
this.monotonicNow = options.now ?? (() => performance.now());
|
|
14009
14153
|
this.server = new Server({ name: "agentbridge", version: "0.1.0" }, {
|
|
14010
14154
|
capabilities: {
|
|
14011
14155
|
experimental: { "claude/channel": {} },
|
|
@@ -14032,10 +14176,12 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14032
14176
|
}
|
|
14033
14177
|
async pushNotification(message) {
|
|
14034
14178
|
this.log(`pushNotification (instance=${this.instanceId}, msgId=${message.id}, len=${message.content.length})`);
|
|
14179
|
+
if (!this.rememberDelivery(message))
|
|
14180
|
+
return;
|
|
14035
14181
|
await this.pushViaChannel(message);
|
|
14036
14182
|
}
|
|
14037
14183
|
async pushViaChannel(message) {
|
|
14038
|
-
const
|
|
14184
|
+
const deliveryAttemptId = `codex_msg_${this.notificationIdPrefix}_${++this.notificationSeq}`;
|
|
14039
14185
|
const ts = new Date(message.timestamp).toISOString();
|
|
14040
14186
|
try {
|
|
14041
14187
|
await this.server.notification({
|
|
@@ -14044,7 +14190,8 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14044
14190
|
content: message.content,
|
|
14045
14191
|
meta: {
|
|
14046
14192
|
chat_id: this.sessionId,
|
|
14047
|
-
message_id:
|
|
14193
|
+
message_id: message.id,
|
|
14194
|
+
delivery_attempt_id: deliveryAttemptId,
|
|
14048
14195
|
user: "Codex",
|
|
14049
14196
|
user_id: "codex",
|
|
14050
14197
|
ts,
|
|
@@ -14052,39 +14199,93 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14052
14199
|
}
|
|
14053
14200
|
}
|
|
14054
14201
|
});
|
|
14055
|
-
this.log(`Pushed notification: ${
|
|
14202
|
+
this.log(`Pushed notification: ${message.id} (attempt=${deliveryAttemptId})`);
|
|
14056
14203
|
} catch (e) {
|
|
14057
14204
|
this.log(`Push notification failed: ${e.message}`);
|
|
14058
14205
|
this.queueFallbackMessage(message);
|
|
14059
14206
|
}
|
|
14060
14207
|
}
|
|
14208
|
+
rememberDelivery(message) {
|
|
14209
|
+
const now = this.monotonicNow();
|
|
14210
|
+
this.pruneDeliveredMessageIds(now);
|
|
14211
|
+
if (this.deliveredMessageIds.has(message.id)) {
|
|
14212
|
+
this.deliveredMessageIds.delete(message.id);
|
|
14213
|
+
this.deliveredMessageIds.set(message.id, now);
|
|
14214
|
+
this.log(`Duplicate Codex message suppressed (msgId=${message.id}, source=${message.source}, ` + `instance=${this.instanceId})`);
|
|
14215
|
+
return false;
|
|
14216
|
+
}
|
|
14217
|
+
this.deliveredMessageIds.set(message.id, now);
|
|
14218
|
+
while (this.deliveredMessageIds.size > this.dedupeCapacity) {
|
|
14219
|
+
const oldest = this.deliveredMessageIds.keys().next().value;
|
|
14220
|
+
if (oldest === undefined)
|
|
14221
|
+
break;
|
|
14222
|
+
this.deliveredMessageIds.delete(oldest);
|
|
14223
|
+
}
|
|
14224
|
+
return true;
|
|
14225
|
+
}
|
|
14226
|
+
pruneDeliveredMessageIds(now) {
|
|
14227
|
+
for (const [id, seenAt] of this.deliveredMessageIds) {
|
|
14228
|
+
if (now - seenAt <= this.dedupeTtlMs)
|
|
14229
|
+
break;
|
|
14230
|
+
this.deliveredMessageIds.delete(id);
|
|
14231
|
+
}
|
|
14232
|
+
}
|
|
14061
14233
|
queueFallbackMessage(message) {
|
|
14062
|
-
|
|
14063
|
-
|
|
14234
|
+
const messageBytes = utf8ByteLength(message.content);
|
|
14235
|
+
if (messageBytes > this.maxBufferedBytes) {
|
|
14236
|
+
this.oversizedMessageCount++;
|
|
14237
|
+
this.oversizedMessageBytes += messageBytes;
|
|
14238
|
+
this.oversizedMessageSourceCounts[message.source] = (this.oversizedMessageSourceCounts[message.source] ?? 0) + 1;
|
|
14239
|
+
this.log(`Fallback queue omitted oversized ${message.source} message ` + `(${formatBytes(messageBytes)} > ${formatBytes(this.maxBufferedBytes)}; ` + `total oversized: ${this.oversizedMessageCount})`);
|
|
14240
|
+
return;
|
|
14241
|
+
}
|
|
14242
|
+
let dropped = 0;
|
|
14243
|
+
while (this.pendingMessages.length >= this.maxBufferedMessages || this.pendingMessageBytes + messageBytes > this.maxBufferedBytes) {
|
|
14244
|
+
const droppedMessage = this.pendingMessages.shift();
|
|
14245
|
+
const droppedBytes = this.pendingMessageByteSizes.shift() ?? 0;
|
|
14246
|
+
if (!droppedMessage)
|
|
14247
|
+
break;
|
|
14248
|
+
this.pendingMessageBytes = Math.max(0, this.pendingMessageBytes - droppedBytes);
|
|
14064
14249
|
this.droppedMessageCount++;
|
|
14065
|
-
|
|
14250
|
+
dropped++;
|
|
14251
|
+
}
|
|
14252
|
+
if (dropped > 0) {
|
|
14253
|
+
this.log(`Fallback queue overflow: dropped ${dropped} oldest message${dropped > 1 ? "s" : ""} ` + `(${this.pendingMessages.length} pending, ${formatBytes(this.pendingMessageBytes)} buffered, ` + `${this.droppedMessageCount} dropped since last drain)`);
|
|
14066
14254
|
}
|
|
14067
14255
|
this.pendingMessages.push(message);
|
|
14068
|
-
this.
|
|
14256
|
+
this.pendingMessageByteSizes.push(messageBytes);
|
|
14257
|
+
this.pendingMessageBytes += messageBytes;
|
|
14258
|
+
this.log(`Queued fallback message (${this.pendingMessages.length} pending, ` + `${formatBytes(this.pendingMessageBytes)} buffered, instance=${this.instanceId})`);
|
|
14069
14259
|
}
|
|
14070
14260
|
drainMessages() {
|
|
14071
|
-
this.log(`get_messages called (instance=${this.instanceId}, pending=${this.pendingMessages.length}, dropped=${this.droppedMessageCount})`);
|
|
14072
|
-
if (this.pendingMessages.length === 0 && this.droppedMessageCount === 0) {
|
|
14261
|
+
this.log(`get_messages called (instance=${this.instanceId}, pending=${this.pendingMessages.length}, ` + `bytes=${this.pendingMessageBytes}, dropped=${this.droppedMessageCount}, oversized=${this.oversizedMessageCount})`);
|
|
14262
|
+
if (this.pendingMessages.length === 0 && this.droppedMessageCount === 0 && this.oversizedMessageCount === 0) {
|
|
14073
14263
|
return {
|
|
14074
14264
|
content: [{ type: "text", text: "No new messages from Codex." }]
|
|
14075
14265
|
};
|
|
14076
14266
|
}
|
|
14077
14267
|
const messages = this.pendingMessages;
|
|
14078
14268
|
this.pendingMessages = [];
|
|
14269
|
+
this.pendingMessageByteSizes = [];
|
|
14270
|
+
this.pendingMessageBytes = 0;
|
|
14079
14271
|
const dropped = this.droppedMessageCount;
|
|
14080
14272
|
this.droppedMessageCount = 0;
|
|
14273
|
+
const oversizedSourceCounts = this.oversizedMessageSourceCounts;
|
|
14274
|
+
const oversized = this.oversizedMessageCount;
|
|
14275
|
+
const oversizedBytes = this.oversizedMessageBytes;
|
|
14276
|
+
this.oversizedMessageSourceCounts = {};
|
|
14277
|
+
this.oversizedMessageCount = 0;
|
|
14278
|
+
this.oversizedMessageBytes = 0;
|
|
14081
14279
|
const count = messages.length;
|
|
14082
|
-
|
|
14280
|
+
const notices = [];
|
|
14083
14281
|
if (dropped > 0) {
|
|
14084
|
-
|
|
14282
|
+
notices.push(`${dropped} older message${dropped > 1 ? "s" : ""} ` + `${dropped > 1 ? "were" : "was"} dropped due to fallback queue overflow`);
|
|
14283
|
+
}
|
|
14284
|
+
if (oversized > 0) {
|
|
14285
|
+
for (const [source, sourceCount] of Object.entries(oversizedSourceCounts)) {
|
|
14286
|
+
notices.push(`${sourceCount} oversized message${sourceCount === 1 ? "" : "s"} ` + `from ${formatSource(source)} omitted ` + `(>${formatBytes(this.maxBufferedBytes)})`);
|
|
14287
|
+
}
|
|
14085
14288
|
}
|
|
14086
|
-
header += `
|
|
14087
|
-
chat_id: ${this.sessionId}`;
|
|
14088
14289
|
const formatted = messages.map((msg, i) => {
|
|
14089
14290
|
const ts = new Date(msg.timestamp).toISOString();
|
|
14090
14291
|
return `---
|
|
@@ -14093,14 +14294,25 @@ Codex: ${msg.content}`;
|
|
|
14093
14294
|
}).join(`
|
|
14094
14295
|
|
|
14095
14296
|
`);
|
|
14096
|
-
|
|
14297
|
+
const noticeText = notices.map((notice) => `WARNING: ${notice}`).join(`
|
|
14298
|
+
`);
|
|
14299
|
+
const parts = [];
|
|
14300
|
+
if (count > 0) {
|
|
14301
|
+
parts.push(`[${count} new message${count > 1 ? "s" : ""} from Codex]
|
|
14302
|
+
chat_id: ${this.sessionId}`);
|
|
14303
|
+
}
|
|
14304
|
+
if (noticeText)
|
|
14305
|
+
parts.push(noticeText);
|
|
14306
|
+
if (formatted)
|
|
14307
|
+
parts.push(formatted);
|
|
14308
|
+
this.log(`get_messages returning ${count} message(s) ` + `(instance=${this.instanceId}, dropped=${dropped}, oversized=${oversized}, oversizedBytes=${oversizedBytes})`);
|
|
14097
14309
|
return {
|
|
14098
14310
|
content: [
|
|
14099
14311
|
{
|
|
14100
14312
|
type: "text",
|
|
14101
|
-
text:
|
|
14313
|
+
text: parts.join(`
|
|
14102
14314
|
|
|
14103
|
-
|
|
14315
|
+
`)
|
|
14104
14316
|
}
|
|
14105
14317
|
]
|
|
14106
14318
|
};
|
|
@@ -14256,11 +14468,37 @@ ${formatted}`
|
|
|
14256
14468
|
this.logger.log(msg);
|
|
14257
14469
|
}
|
|
14258
14470
|
}
|
|
14471
|
+
function parsePositiveIntegerEnv(name, fallback) {
|
|
14472
|
+
return positiveIntegerOr(parseInt(process.env[name] ?? "", 10), fallback);
|
|
14473
|
+
}
|
|
14474
|
+
function positiveIntegerOr(value, fallback) {
|
|
14475
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
14476
|
+
}
|
|
14477
|
+
function utf8ByteLength(value) {
|
|
14478
|
+
return Buffer.byteLength(value, "utf8");
|
|
14479
|
+
}
|
|
14480
|
+
function formatSource(source) {
|
|
14481
|
+
return source === "codex" ? "Codex" : "Claude";
|
|
14482
|
+
}
|
|
14483
|
+
function formatBytes(bytes) {
|
|
14484
|
+
if (bytes < 1024)
|
|
14485
|
+
return `${bytes}B`;
|
|
14486
|
+
if (bytes % (1024 * 1024) === 0)
|
|
14487
|
+
return `${bytes / (1024 * 1024)}MiB`;
|
|
14488
|
+
if (bytes % 1024 === 0)
|
|
14489
|
+
return `${bytes / 1024}KiB`;
|
|
14490
|
+
return `${bytes}B`;
|
|
14491
|
+
}
|
|
14259
14492
|
|
|
14260
14493
|
// src/contract-version.ts
|
|
14261
14494
|
var CONTRACT_VERSION = 1;
|
|
14262
14495
|
|
|
14263
14496
|
// src/build-info.ts
|
|
14497
|
+
var CODE_HASH_SENTINEL = "source";
|
|
14498
|
+
function hasValidCodeHash(build) {
|
|
14499
|
+
const hash = build?.codeHash;
|
|
14500
|
+
return typeof hash === "string" && hash.length > 0 && hash !== CODE_HASH_SENTINEL;
|
|
14501
|
+
}
|
|
14264
14502
|
function defineString(value, fallback) {
|
|
14265
14503
|
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
14266
14504
|
}
|
|
@@ -14273,15 +14511,23 @@ function defineNumber(value, fallback) {
|
|
|
14273
14511
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
14274
14512
|
}
|
|
14275
14513
|
var BUILD_INFO = Object.freeze({
|
|
14276
|
-
version: defineString("0.1.
|
|
14277
|
-
commit: defineString("
|
|
14514
|
+
version: defineString("0.1.14", "0.0.0-source"),
|
|
14515
|
+
commit: defineString("f5a9562", "source"),
|
|
14278
14516
|
bundle: defineBundle("plugin"),
|
|
14279
|
-
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
14517
|
+
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
14518
|
+
codeHash: defineString("e05d18c3cc72", "source")
|
|
14280
14519
|
});
|
|
14281
14520
|
function sameRuntimeContract(a, b) {
|
|
14282
14521
|
if (!a || !b)
|
|
14283
14522
|
return false;
|
|
14284
|
-
|
|
14523
|
+
if (a.version !== b.version || a.contractVersion !== b.contractVersion)
|
|
14524
|
+
return false;
|
|
14525
|
+
if (hasValidCodeHash(a) && hasValidCodeHash(b))
|
|
14526
|
+
return a.codeHash === b.codeHash;
|
|
14527
|
+
return a.commit === b.commit;
|
|
14528
|
+
}
|
|
14529
|
+
function runtimeContractComparisonBasis(a, b) {
|
|
14530
|
+
return hasValidCodeHash(a) && hasValidCodeHash(b) ? "codeHash" : "commit";
|
|
14285
14531
|
}
|
|
14286
14532
|
function compatibleContractVersion(a, b) {
|
|
14287
14533
|
if (!a || !b)
|
|
@@ -14291,7 +14537,8 @@ function compatibleContractVersion(a, b) {
|
|
|
14291
14537
|
function formatBuildInfo(build) {
|
|
14292
14538
|
if (!build)
|
|
14293
14539
|
return "<unknown>";
|
|
14294
|
-
|
|
14540
|
+
const codeHash = hasValidCodeHash(build) ? `/code-${build.codeHash}` : "";
|
|
14541
|
+
return `${build.version}/${build.commit}/${build.bundle}/contract-v${build.contractVersion}${codeHash}`;
|
|
14295
14542
|
}
|
|
14296
14543
|
|
|
14297
14544
|
// src/daemon-client.ts
|
|
@@ -14302,12 +14549,84 @@ var CLOSE_CODE_REPLACED = 4001;
|
|
|
14302
14549
|
var CLOSE_CODE_EVICTED_STALE = 4002;
|
|
14303
14550
|
var CLOSE_CODE_PROBE_IN_PROGRESS = 4003;
|
|
14304
14551
|
var CLOSE_CODE_PAIR_MISMATCH = 4004;
|
|
14552
|
+
var CLOSE_CODE_TOKEN_MISMATCH = 4005;
|
|
14553
|
+
var CLOSE_CODE_CONTRACT_MISMATCH = 4006;
|
|
14305
14554
|
|
|
14306
14555
|
// src/interrupt-timing.ts
|
|
14307
14556
|
var CLIENT_REPLY_TIMEOUT_MS = 15000;
|
|
14308
14557
|
var INTERRUPT_CLIENT_MARGIN_MS = 2000;
|
|
14309
14558
|
var MAX_INTERRUPT_TIMEOUT_MS = CLIENT_REPLY_TIMEOUT_MS - INTERRUPT_CLIENT_MARGIN_MS;
|
|
14310
14559
|
|
|
14560
|
+
// src/pending-request-registry.ts
|
|
14561
|
+
class PendingRequestRegistry {
|
|
14562
|
+
entries = new Map;
|
|
14563
|
+
setTimer;
|
|
14564
|
+
clearTimer;
|
|
14565
|
+
constructor(deps = {}) {
|
|
14566
|
+
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
14567
|
+
this.clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
14568
|
+
}
|
|
14569
|
+
get size() {
|
|
14570
|
+
return this.entries.size;
|
|
14571
|
+
}
|
|
14572
|
+
has(id) {
|
|
14573
|
+
return this.entries.has(id);
|
|
14574
|
+
}
|
|
14575
|
+
register(id, options) {
|
|
14576
|
+
const existing = this.entries.get(id);
|
|
14577
|
+
if (existing) {
|
|
14578
|
+
this.clearTimer(existing.timer);
|
|
14579
|
+
this.entries.delete(id);
|
|
14580
|
+
}
|
|
14581
|
+
return new Promise((resolve, reject) => {
|
|
14582
|
+
const timer = this.setTimer(() => {
|
|
14583
|
+
if (!this.entries.has(id))
|
|
14584
|
+
return;
|
|
14585
|
+
this.entries.delete(id);
|
|
14586
|
+
options.onTimeout({ resolve, reject });
|
|
14587
|
+
}, options.timeoutMs);
|
|
14588
|
+
if (options.unref) {
|
|
14589
|
+
timer.unref?.();
|
|
14590
|
+
}
|
|
14591
|
+
this.entries.set(id, { resolve, reject, timer });
|
|
14592
|
+
});
|
|
14593
|
+
}
|
|
14594
|
+
settle(id, value) {
|
|
14595
|
+
const entry = this.entries.get(id);
|
|
14596
|
+
if (!entry)
|
|
14597
|
+
return false;
|
|
14598
|
+
this.clearTimer(entry.timer);
|
|
14599
|
+
this.entries.delete(id);
|
|
14600
|
+
entry.resolve(value);
|
|
14601
|
+
return true;
|
|
14602
|
+
}
|
|
14603
|
+
reject(id, error2) {
|
|
14604
|
+
const entry = this.entries.get(id);
|
|
14605
|
+
if (!entry)
|
|
14606
|
+
return false;
|
|
14607
|
+
this.clearTimer(entry.timer);
|
|
14608
|
+
this.entries.delete(id);
|
|
14609
|
+
entry.reject(error2);
|
|
14610
|
+
return true;
|
|
14611
|
+
}
|
|
14612
|
+
settleAll(value) {
|
|
14613
|
+
const make = typeof value === "function" ? value : () => value;
|
|
14614
|
+
for (const [id, entry] of this.entries) {
|
|
14615
|
+
this.clearTimer(entry.timer);
|
|
14616
|
+
this.entries.delete(id);
|
|
14617
|
+
entry.resolve(make(id));
|
|
14618
|
+
}
|
|
14619
|
+
}
|
|
14620
|
+
rejectAll(error2) {
|
|
14621
|
+
const make = typeof error2 === "function" ? error2 : () => error2;
|
|
14622
|
+
for (const [id, entry] of this.entries) {
|
|
14623
|
+
this.clearTimer(entry.timer);
|
|
14624
|
+
this.entries.delete(id);
|
|
14625
|
+
entry.reject(make(id));
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14629
|
+
|
|
14311
14630
|
// src/daemon-client.ts
|
|
14312
14631
|
var nextSocketId = 0;
|
|
14313
14632
|
|
|
@@ -14317,7 +14636,8 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14317
14636
|
ws = null;
|
|
14318
14637
|
wsId = 0;
|
|
14319
14638
|
nextRequestId = 1;
|
|
14320
|
-
pendingReplies = new
|
|
14639
|
+
pendingReplies = new PendingRequestRegistry;
|
|
14640
|
+
pendingEventWaiters = new PendingRequestRegistry;
|
|
14321
14641
|
constructor(url, options = {}) {
|
|
14322
14642
|
super();
|
|
14323
14643
|
this.url = url;
|
|
@@ -14363,82 +14683,73 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14363
14683
|
});
|
|
14364
14684
|
}
|
|
14365
14685
|
attachClaude() {
|
|
14686
|
+
const identity = this.resolveIdentity();
|
|
14366
14687
|
this.send({
|
|
14367
14688
|
type: "claude_connect",
|
|
14368
|
-
...
|
|
14689
|
+
...identity ? { identity } : {}
|
|
14369
14690
|
});
|
|
14370
14691
|
}
|
|
14692
|
+
resolveIdentity() {
|
|
14693
|
+
const opt = this.options.identity;
|
|
14694
|
+
return typeof opt === "function" ? opt() : opt;
|
|
14695
|
+
}
|
|
14371
14696
|
async attachClaudeAndWaitForStatus(timeoutMs = 1000) {
|
|
14372
14697
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
14373
14698
|
return null;
|
|
14374
14699
|
}
|
|
14375
|
-
return
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
if (timer) {
|
|
14383
|
-
clearTimeout(timer);
|
|
14384
|
-
timer = null;
|
|
14385
|
-
}
|
|
14386
|
-
this.off("status", onStatus);
|
|
14387
|
-
this.off("rejected", onRejected);
|
|
14388
|
-
this.off("disconnect", onDisconnect);
|
|
14389
|
-
};
|
|
14390
|
-
const finish = (value) => {
|
|
14391
|
-
cleanup();
|
|
14392
|
-
resolve(value);
|
|
14393
|
-
};
|
|
14394
|
-
const onStatus = (status) => finish(status);
|
|
14395
|
-
const onRejected = () => finish(null);
|
|
14396
|
-
const onDisconnect = () => finish(null);
|
|
14397
|
-
this.on("status", onStatus);
|
|
14398
|
-
this.on("rejected", onRejected);
|
|
14399
|
-
this.on("disconnect", onDisconnect);
|
|
14400
|
-
timer = setTimeout(() => {
|
|
14401
|
-
finish(null);
|
|
14402
|
-
}, timeoutMs);
|
|
14403
|
-
try {
|
|
14404
|
-
this.attachClaude();
|
|
14405
|
-
} catch {
|
|
14406
|
-
finish(null);
|
|
14407
|
-
}
|
|
14700
|
+
return this.awaitTypedResponse({
|
|
14701
|
+
key: "status",
|
|
14702
|
+
successEvent: "status",
|
|
14703
|
+
successValue: (status) => status,
|
|
14704
|
+
failValue: null,
|
|
14705
|
+
timeoutMs,
|
|
14706
|
+
send: () => this.attachClaude()
|
|
14408
14707
|
});
|
|
14409
14708
|
}
|
|
14410
14709
|
async probeIncumbent(timeoutMs = 3000) {
|
|
14411
14710
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
14412
14711
|
return { connected: false, alive: false };
|
|
14413
14712
|
}
|
|
14414
|
-
return
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
if (timer)
|
|
14422
|
-
clearTimeout(timer);
|
|
14423
|
-
this.off("incumbentStatus", onStatus);
|
|
14424
|
-
this.off("disconnect", onDisconnect);
|
|
14425
|
-
this.off("rejected", onRejected);
|
|
14426
|
-
resolve(value);
|
|
14427
|
-
};
|
|
14428
|
-
const onStatus = (s) => finish(s);
|
|
14429
|
-
const onDisconnect = () => finish({ connected: false, alive: false });
|
|
14430
|
-
const onRejected = () => finish({ connected: false, alive: false });
|
|
14431
|
-
this.on("incumbentStatus", onStatus);
|
|
14432
|
-
this.on("disconnect", onDisconnect);
|
|
14433
|
-
this.on("rejected", onRejected);
|
|
14434
|
-
timer = setTimeout(() => finish({ connected: false, alive: false }), timeoutMs);
|
|
14435
|
-
try {
|
|
14436
|
-
this.send({ type: "probe_incumbent" });
|
|
14437
|
-
} catch {
|
|
14438
|
-
finish({ connected: false, alive: false });
|
|
14439
|
-
}
|
|
14713
|
+
return this.awaitTypedResponse({
|
|
14714
|
+
key: "incumbent_status",
|
|
14715
|
+
successEvent: "incumbentStatus",
|
|
14716
|
+
successValue: (s) => s,
|
|
14717
|
+
failValue: { connected: false, alive: false },
|
|
14718
|
+
timeoutMs,
|
|
14719
|
+
send: () => this.send({ type: "probe_incumbent" })
|
|
14440
14720
|
});
|
|
14441
14721
|
}
|
|
14722
|
+
awaitTypedResponse(opts) {
|
|
14723
|
+
const { key, successEvent, successValue, failValue, timeoutMs, send } = opts;
|
|
14724
|
+
const onSuccess = (payload) => {
|
|
14725
|
+
this.pendingEventWaiters.settle(key, successValue(payload));
|
|
14726
|
+
};
|
|
14727
|
+
const onRejected = () => {
|
|
14728
|
+
this.pendingEventWaiters.settle(key, failValue);
|
|
14729
|
+
};
|
|
14730
|
+
const onDisconnect = () => {
|
|
14731
|
+
this.pendingEventWaiters.settle(key, failValue);
|
|
14732
|
+
};
|
|
14733
|
+
const pending = this.pendingEventWaiters.register(key, {
|
|
14734
|
+
timeoutMs,
|
|
14735
|
+
onTimeout: ({ resolve }) => resolve(failValue)
|
|
14736
|
+
});
|
|
14737
|
+
const cleanup = () => {
|
|
14738
|
+
this.off(successEvent, onSuccess);
|
|
14739
|
+
this.off("rejected", onRejected);
|
|
14740
|
+
this.off("disconnect", onDisconnect);
|
|
14741
|
+
};
|
|
14742
|
+
pending.finally(cleanup);
|
|
14743
|
+
this.on(successEvent, onSuccess);
|
|
14744
|
+
this.on("rejected", onRejected);
|
|
14745
|
+
this.on("disconnect", onDisconnect);
|
|
14746
|
+
try {
|
|
14747
|
+
send();
|
|
14748
|
+
} catch {
|
|
14749
|
+
this.pendingEventWaiters.settle(key, failValue);
|
|
14750
|
+
}
|
|
14751
|
+
return pending;
|
|
14752
|
+
}
|
|
14442
14753
|
async disconnect() {
|
|
14443
14754
|
if (!this.ws)
|
|
14444
14755
|
return;
|
|
@@ -14456,21 +14767,19 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14456
14767
|
return { success: false, error: "AgentBridge daemon is not connected." };
|
|
14457
14768
|
}
|
|
14458
14769
|
const requestId = `reply_${Date.now()}_${this.nextRequestId++}`;
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
...onBusy && onBusy !== "reject" ? { onBusy } : {},
|
|
14471
|
-
...idempotencyKey ? { idempotencyKey } : {}
|
|
14472
|
-
});
|
|
14770
|
+
const pending = this.pendingReplies.register(requestId, {
|
|
14771
|
+
timeoutMs: CLIENT_REPLY_TIMEOUT_MS,
|
|
14772
|
+
onTimeout: ({ resolve }) => resolve({ success: false, error: "Timed out waiting for AgentBridge daemon reply." })
|
|
14773
|
+
});
|
|
14774
|
+
this.send({
|
|
14775
|
+
type: "claude_to_codex",
|
|
14776
|
+
requestId,
|
|
14777
|
+
message,
|
|
14778
|
+
...requireReply ? { requireReply: true } : {},
|
|
14779
|
+
...onBusy && onBusy !== "reject" ? { onBusy } : {},
|
|
14780
|
+
...idempotencyKey ? { idempotencyKey } : {}
|
|
14473
14781
|
});
|
|
14782
|
+
return pending;
|
|
14474
14783
|
}
|
|
14475
14784
|
attachSocketHandlers(ws, socketId) {
|
|
14476
14785
|
ws.onmessage = (event) => {
|
|
@@ -14486,12 +14795,7 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14486
14795
|
this.emit("codexMessage", message.message);
|
|
14487
14796
|
return;
|
|
14488
14797
|
case "claude_to_codex_result": {
|
|
14489
|
-
|
|
14490
|
-
if (!pending)
|
|
14491
|
-
return;
|
|
14492
|
-
clearTimeout(pending.timer);
|
|
14493
|
-
this.pendingReplies.delete(message.requestId);
|
|
14494
|
-
pending.resolve({
|
|
14798
|
+
this.pendingReplies.settle(message.requestId, {
|
|
14495
14799
|
success: message.success,
|
|
14496
14800
|
error: message.error,
|
|
14497
14801
|
...message.code !== undefined ? { code: message.code } : {},
|
|
@@ -14522,7 +14826,7 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14522
14826
|
if (isCurrent) {
|
|
14523
14827
|
this.ws = null;
|
|
14524
14828
|
this.rejectPendingReplies("AgentBridge daemon disconnected.");
|
|
14525
|
-
if (event.code === CLOSE_CODE_REPLACED || event.code === CLOSE_CODE_EVICTED_STALE || event.code === CLOSE_CODE_PROBE_IN_PROGRESS || event.code === CLOSE_CODE_PAIR_MISMATCH) {
|
|
14829
|
+
if (event.code === CLOSE_CODE_REPLACED || event.code === CLOSE_CODE_EVICTED_STALE || event.code === CLOSE_CODE_PROBE_IN_PROGRESS || event.code === CLOSE_CODE_PAIR_MISMATCH || event.code === CLOSE_CODE_TOKEN_MISMATCH || event.code === CLOSE_CODE_CONTRACT_MISMATCH) {
|
|
14526
14830
|
this.emit("rejected", event.code);
|
|
14527
14831
|
} else {
|
|
14528
14832
|
this.emit("disconnect");
|
|
@@ -14532,11 +14836,7 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14532
14836
|
ws.onerror = () => {};
|
|
14533
14837
|
}
|
|
14534
14838
|
rejectPendingReplies(error2) {
|
|
14535
|
-
|
|
14536
|
-
clearTimeout(pending.timer);
|
|
14537
|
-
pending.resolve({ success: false, error: error2 });
|
|
14538
|
-
this.pendingReplies.delete(requestId);
|
|
14539
|
-
}
|
|
14839
|
+
this.pendingReplies.settleAll(() => ({ success: false, error: error2 }));
|
|
14540
14840
|
}
|
|
14541
14841
|
send(message) {
|
|
14542
14842
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
@@ -14552,9 +14852,44 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14552
14852
|
|
|
14553
14853
|
// src/daemon-lifecycle.ts
|
|
14554
14854
|
import { spawn } from "child_process";
|
|
14555
|
-
import { existsSync as existsSync3, readFileSync, statSync as statSync2, unlinkSync as
|
|
14855
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2, openSync as openSync2, closeSync as closeSync2, constants } from "fs";
|
|
14556
14856
|
import { fileURLToPath } from "url";
|
|
14557
14857
|
|
|
14858
|
+
// src/atomic-json.ts
|
|
14859
|
+
import * as fs from "fs";
|
|
14860
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
14861
|
+
import { dirname as dirname2 } from "path";
|
|
14862
|
+
function tmpPathFor(targetPath) {
|
|
14863
|
+
return `${targetPath}.tmp.${process.pid}.${randomUUID2()}`;
|
|
14864
|
+
}
|
|
14865
|
+
function atomicWriteText(path, content, options = {}) {
|
|
14866
|
+
fs.mkdirSync(dirname2(path), { recursive: true });
|
|
14867
|
+
const tmp = tmpPathFor(path);
|
|
14868
|
+
let renamed = false;
|
|
14869
|
+
const fd = fs.openSync(tmp, "w", options.mode ?? 438);
|
|
14870
|
+
try {
|
|
14871
|
+
try {
|
|
14872
|
+
fs.writeFileSync(fd, content, "utf-8");
|
|
14873
|
+
if (options.fsync)
|
|
14874
|
+
fs.fsyncSync(fd);
|
|
14875
|
+
} finally {
|
|
14876
|
+
fs.closeSync(fd);
|
|
14877
|
+
}
|
|
14878
|
+
fs.renameSync(tmp, path);
|
|
14879
|
+
renamed = true;
|
|
14880
|
+
} finally {
|
|
14881
|
+
if (!renamed) {
|
|
14882
|
+
try {
|
|
14883
|
+
fs.unlinkSync(tmp);
|
|
14884
|
+
} catch {}
|
|
14885
|
+
}
|
|
14886
|
+
}
|
|
14887
|
+
}
|
|
14888
|
+
function atomicWriteJson(path, value, options = {}) {
|
|
14889
|
+
atomicWriteText(path, JSON.stringify(value, null, 2) + `
|
|
14890
|
+
`, options);
|
|
14891
|
+
}
|
|
14892
|
+
|
|
14558
14893
|
// src/env-utils.ts
|
|
14559
14894
|
function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env) {
|
|
14560
14895
|
const raw = env[name];
|
|
@@ -14603,12 +14938,144 @@ function isAgentBridgeProcess(pid, lookup = commandForPid) {
|
|
|
14603
14938
|
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
14604
14939
|
}
|
|
14605
14940
|
|
|
14941
|
+
// src/daemon-record.ts
|
|
14942
|
+
import { readFileSync } from "fs";
|
|
14943
|
+
var defaultRead = (path) => readFileSync(path, "utf-8");
|
|
14944
|
+
function writeDaemonRecord(path, record3) {
|
|
14945
|
+
atomicWriteJson(path, record3);
|
|
14946
|
+
}
|
|
14947
|
+
function sanitizePorts(value) {
|
|
14948
|
+
if (typeof value !== "object" || value === null)
|
|
14949
|
+
return;
|
|
14950
|
+
const raw = value;
|
|
14951
|
+
const ports = {};
|
|
14952
|
+
if (typeof raw.appPort === "number")
|
|
14953
|
+
ports.appPort = raw.appPort;
|
|
14954
|
+
if (typeof raw.proxyPort === "number")
|
|
14955
|
+
ports.proxyPort = raw.proxyPort;
|
|
14956
|
+
if (typeof raw.controlPort === "number")
|
|
14957
|
+
ports.controlPort = raw.controlPort;
|
|
14958
|
+
return Object.keys(ports).length > 0 ? ports : undefined;
|
|
14959
|
+
}
|
|
14960
|
+
function readDaemonRecord(path, read = defaultRead) {
|
|
14961
|
+
let parsed;
|
|
14962
|
+
try {
|
|
14963
|
+
parsed = JSON.parse(read(path));
|
|
14964
|
+
} catch {
|
|
14965
|
+
return null;
|
|
14966
|
+
}
|
|
14967
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
14968
|
+
return null;
|
|
14969
|
+
const obj = parsed;
|
|
14970
|
+
if (typeof obj.pid !== "number" || !Number.isFinite(obj.pid))
|
|
14971
|
+
return null;
|
|
14972
|
+
const phase = obj.phase === "ready" ? "ready" : "booting";
|
|
14973
|
+
const record3 = { pid: obj.pid, phase };
|
|
14974
|
+
if (typeof obj.startedAt === "number")
|
|
14975
|
+
record3.startedAt = obj.startedAt;
|
|
14976
|
+
if (typeof obj.nonce === "string")
|
|
14977
|
+
record3.nonce = obj.nonce;
|
|
14978
|
+
if (obj.pairId === null || typeof obj.pairId === "string")
|
|
14979
|
+
record3.pairId = obj.pairId;
|
|
14980
|
+
if (obj.cwd === null || typeof obj.cwd === "string")
|
|
14981
|
+
record3.cwd = obj.cwd;
|
|
14982
|
+
if (obj.stateDir === null || typeof obj.stateDir === "string")
|
|
14983
|
+
record3.stateDir = obj.stateDir;
|
|
14984
|
+
if (typeof obj.proxyUrl === "string")
|
|
14985
|
+
record3.proxyUrl = obj.proxyUrl;
|
|
14986
|
+
if (typeof obj.appServerUrl === "string")
|
|
14987
|
+
record3.appServerUrl = obj.appServerUrl;
|
|
14988
|
+
const ports = sanitizePorts(obj.ports);
|
|
14989
|
+
if (ports !== undefined)
|
|
14990
|
+
record3.ports = ports;
|
|
14991
|
+
if (typeof obj.build === "object" && obj.build !== null) {
|
|
14992
|
+
record3.build = obj.build;
|
|
14993
|
+
}
|
|
14994
|
+
if (typeof obj.turnPhase === "string")
|
|
14995
|
+
record3.turnPhase = obj.turnPhase;
|
|
14996
|
+
if (typeof obj.turnInProgress === "boolean")
|
|
14997
|
+
record3.turnInProgress = obj.turnInProgress;
|
|
14998
|
+
if (typeof obj.attentionWindowActive === "boolean") {
|
|
14999
|
+
record3.attentionWindowActive = obj.attentionWindowActive;
|
|
15000
|
+
}
|
|
15001
|
+
return record3;
|
|
15002
|
+
}
|
|
15003
|
+
function synthesizeLegacyRecord(pidFilePath, statusFilePath, read = defaultRead) {
|
|
15004
|
+
let pidFromPidFile = null;
|
|
15005
|
+
try {
|
|
15006
|
+
const raw = read(pidFilePath).trim();
|
|
15007
|
+
const n = Number.parseInt(raw, 10);
|
|
15008
|
+
if (Number.isFinite(n))
|
|
15009
|
+
pidFromPidFile = n;
|
|
15010
|
+
} catch {}
|
|
15011
|
+
let status = null;
|
|
15012
|
+
try {
|
|
15013
|
+
const parsed = JSON.parse(read(statusFilePath));
|
|
15014
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
15015
|
+
status = parsed;
|
|
15016
|
+
} catch {}
|
|
15017
|
+
const pidFromStatus = status && typeof status.pid === "number" && Number.isFinite(status.pid) ? status.pid : null;
|
|
15018
|
+
const pid = pidFromPidFile ?? pidFromStatus;
|
|
15019
|
+
if (pid === null)
|
|
15020
|
+
return null;
|
|
15021
|
+
const record3 = {
|
|
15022
|
+
pid,
|
|
15023
|
+
phase: status ? "ready" : "booting"
|
|
15024
|
+
};
|
|
15025
|
+
if (status) {
|
|
15026
|
+
if (typeof status.proxyUrl === "string")
|
|
15027
|
+
record3.proxyUrl = status.proxyUrl;
|
|
15028
|
+
if (typeof status.appServerUrl === "string")
|
|
15029
|
+
record3.appServerUrl = status.appServerUrl;
|
|
15030
|
+
const controlPort = typeof status.controlPort === "number" ? status.controlPort : undefined;
|
|
15031
|
+
const proxyPort = portFromUrl(status.proxyUrl);
|
|
15032
|
+
const appPort = portFromUrl(status.appServerUrl);
|
|
15033
|
+
if (controlPort !== undefined || proxyPort !== undefined || appPort !== undefined) {
|
|
15034
|
+
record3.ports = {};
|
|
15035
|
+
if (appPort !== undefined)
|
|
15036
|
+
record3.ports.appPort = appPort;
|
|
15037
|
+
if (proxyPort !== undefined)
|
|
15038
|
+
record3.ports.proxyPort = proxyPort;
|
|
15039
|
+
if (controlPort !== undefined)
|
|
15040
|
+
record3.ports.controlPort = controlPort;
|
|
15041
|
+
}
|
|
15042
|
+
if (status.pairId === null || typeof status.pairId === "string")
|
|
15043
|
+
record3.pairId = status.pairId;
|
|
15044
|
+
if (status.cwd === null || typeof status.cwd === "string")
|
|
15045
|
+
record3.cwd = status.cwd;
|
|
15046
|
+
if (status.stateDir === null || typeof status.stateDir === "string")
|
|
15047
|
+
record3.stateDir = status.stateDir;
|
|
15048
|
+
if (typeof status.build === "object" && status.build !== null) {
|
|
15049
|
+
record3.build = status.build;
|
|
15050
|
+
}
|
|
15051
|
+
if (typeof status.turnPhase === "string")
|
|
15052
|
+
record3.turnPhase = status.turnPhase;
|
|
15053
|
+
if (typeof status.turnInProgress === "boolean")
|
|
15054
|
+
record3.turnInProgress = status.turnInProgress;
|
|
15055
|
+
if (typeof status.attentionWindowActive === "boolean") {
|
|
15056
|
+
record3.attentionWindowActive = status.attentionWindowActive;
|
|
15057
|
+
}
|
|
15058
|
+
}
|
|
15059
|
+
return record3;
|
|
15060
|
+
}
|
|
15061
|
+
function readUnifiedDaemonRecord(paths, read = defaultRead) {
|
|
15062
|
+
return readDaemonRecord(paths.daemonRecordFile, read) ?? synthesizeLegacyRecord(paths.pidFile, paths.statusFile, read);
|
|
15063
|
+
}
|
|
15064
|
+
function portFromUrl(url) {
|
|
15065
|
+
if (typeof url !== "string")
|
|
15066
|
+
return;
|
|
15067
|
+
const match = url.match(/:(\d+)(?:[/?]|$)/);
|
|
15068
|
+
return match ? Number.parseInt(match[1], 10) : undefined;
|
|
15069
|
+
}
|
|
15070
|
+
|
|
14606
15071
|
// src/daemon-lifecycle.ts
|
|
14607
15072
|
var DEFAULT_DAEMON_ENTRY = import.meta.url.endsWith(".ts") ? "./daemon.ts" : "./daemon.js";
|
|
14608
15073
|
var DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY || DEFAULT_DAEMON_ENTRY;
|
|
14609
15074
|
var DAEMON_PATH = fileURLToPath(new URL(DAEMON_ENTRY, import.meta.url));
|
|
14610
15075
|
var REUSE_READY_RETRIES = parsePositiveIntEnv("AGENTBRIDGE_REUSE_READY_RETRIES", 12);
|
|
14611
15076
|
var REUSE_READY_DELAY_MS = 250;
|
|
15077
|
+
var WAIT_READY_RETRIES = 40;
|
|
15078
|
+
var WAIT_READY_DELAY_MS = 250;
|
|
14612
15079
|
var HEALTH_FETCH_TIMEOUT_MS = 500;
|
|
14613
15080
|
var LOCK_IDENTITY_GRACE_MS = parsePositiveIntEnv("AGENTBRIDGE_LOCK_IDENTITY_GRACE_MS", 120000);
|
|
14614
15081
|
function isReuseVerdict(verdict) {
|
|
@@ -14646,22 +15113,33 @@ function classifyDaemon(expectedPairId, status, buildInfo) {
|
|
|
14646
15113
|
reason: "runtime build drift has a compatible contract and a live Codex TUI is attached"
|
|
14647
15114
|
};
|
|
14648
15115
|
}
|
|
15116
|
+
const basis = runtimeContractComparisonBasis(status.build, buildInfo) === "codeHash" ? "compared by codeHash" : "compared by commit stamp; legacy build without codeHash";
|
|
14649
15117
|
return {
|
|
14650
15118
|
verdict: "replace-drifted",
|
|
14651
|
-
reason: `runtime build ${formatBuildInfo(status.build)} does not match launcher
|
|
15119
|
+
reason: `runtime build ${formatBuildInfo(status.build)} does not match launcher ` + `${formatBuildInfo(buildInfo)} (${basis})`
|
|
14652
15120
|
};
|
|
14653
15121
|
}
|
|
14654
15122
|
return { verdict: "reuse", reason: "daemon pair and runtime contract match" };
|
|
14655
15123
|
}
|
|
15124
|
+
function resolveTiming(timing) {
|
|
15125
|
+
return {
|
|
15126
|
+
reuseReadyRetries: timing?.reuseReadyRetries ?? REUSE_READY_RETRIES,
|
|
15127
|
+
reuseReadyDelayMs: timing?.reuseReadyDelayMs ?? REUSE_READY_DELAY_MS,
|
|
15128
|
+
waitReadyRetries: timing?.waitReadyRetries ?? WAIT_READY_RETRIES,
|
|
15129
|
+
waitReadyDelayMs: timing?.waitReadyDelayMs ?? WAIT_READY_DELAY_MS
|
|
15130
|
+
};
|
|
15131
|
+
}
|
|
14656
15132
|
|
|
14657
15133
|
class DaemonLifecycle {
|
|
14658
15134
|
stateDir;
|
|
14659
15135
|
controlPort;
|
|
14660
15136
|
log;
|
|
15137
|
+
timing;
|
|
14661
15138
|
constructor(opts) {
|
|
14662
15139
|
this.stateDir = opts.stateDir;
|
|
14663
15140
|
this.controlPort = opts.controlPort;
|
|
14664
15141
|
this.log = opts.log;
|
|
15142
|
+
this.timing = resolveTiming(opts.timing);
|
|
14665
15143
|
}
|
|
14666
15144
|
get healthUrl() {
|
|
14667
15145
|
return `http://127.0.0.1:${this.controlPort}/healthz`;
|
|
@@ -14718,7 +15196,7 @@ class DaemonLifecycle {
|
|
|
14718
15196
|
break;
|
|
14719
15197
|
}
|
|
14720
15198
|
try {
|
|
14721
|
-
await this.waitForReady(
|
|
15199
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
14722
15200
|
return;
|
|
14723
15201
|
} catch {
|
|
14724
15202
|
this.log(`Daemon on control port ${this.controlPort} is healthy but not ready within reuse window \u2014 replacing`);
|
|
@@ -14731,7 +15209,7 @@ class DaemonLifecycle {
|
|
|
14731
15209
|
if (isProcessAlive(existingPid)) {
|
|
14732
15210
|
if (isAgentBridgeDaemon(existingPid)) {
|
|
14733
15211
|
try {
|
|
14734
|
-
await this.waitForReady(
|
|
15212
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
14735
15213
|
return;
|
|
14736
15214
|
} catch {
|
|
14737
15215
|
this.log(`Existing daemon process ${existingPid} never became ready \u2014 replacing`);
|
|
@@ -14759,7 +15237,7 @@ class DaemonLifecycle {
|
|
|
14759
15237
|
await this.kill(3000, status?.pid);
|
|
14760
15238
|
} else {
|
|
14761
15239
|
try {
|
|
14762
|
-
await this.waitForReady(
|
|
15240
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
14763
15241
|
return;
|
|
14764
15242
|
} catch {
|
|
14765
15243
|
this.log(`Daemon on control port ${this.controlPort} is healthy but not ready under startup lock \u2014 replacing`);
|
|
@@ -14768,7 +15246,7 @@ class DaemonLifecycle {
|
|
|
14768
15246
|
}
|
|
14769
15247
|
}
|
|
14770
15248
|
this.launch();
|
|
14771
|
-
await this.waitForReady();
|
|
15249
|
+
await this.waitForReady(this.timing.waitReadyRetries, this.timing.waitReadyDelayMs);
|
|
14772
15250
|
});
|
|
14773
15251
|
}
|
|
14774
15252
|
async isHealthy() {
|
|
@@ -14795,7 +15273,7 @@ class DaemonLifecycle {
|
|
|
14795
15273
|
return false;
|
|
14796
15274
|
}
|
|
14797
15275
|
}
|
|
14798
|
-
async waitForReady(maxRetries =
|
|
15276
|
+
async waitForReady(maxRetries = WAIT_READY_RETRIES, delayMs = WAIT_READY_DELAY_MS) {
|
|
14799
15277
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
14800
15278
|
if (await this.isReady())
|
|
14801
15279
|
return;
|
|
@@ -14803,7 +15281,7 @@ class DaemonLifecycle {
|
|
|
14803
15281
|
}
|
|
14804
15282
|
throw new Error(`Timed out waiting for AgentBridge daemon readiness on ${this.readyUrl}`);
|
|
14805
15283
|
}
|
|
14806
|
-
async waitForReadyAndOurs(maxRetries =
|
|
15284
|
+
async waitForReadyAndOurs(maxRetries = WAIT_READY_RETRIES, delayMs = WAIT_READY_DELAY_MS) {
|
|
14807
15285
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
14808
15286
|
if (await this.isReady()) {
|
|
14809
15287
|
const status = await this.fetchStatus();
|
|
@@ -14819,22 +15297,35 @@ class DaemonLifecycle {
|
|
|
14819
15297
|
}
|
|
14820
15298
|
throw new Error(`Timed out waiting for AgentBridge daemon readiness+identity on ${this.readyUrl} (control port ${this.controlPort})`);
|
|
14821
15299
|
}
|
|
15300
|
+
readDaemonRecord() {
|
|
15301
|
+
return readUnifiedDaemonRecord({
|
|
15302
|
+
daemonRecordFile: this.stateDir.daemonRecordFile,
|
|
15303
|
+
pidFile: this.stateDir.pidFile,
|
|
15304
|
+
statusFile: this.stateDir.statusFile
|
|
15305
|
+
});
|
|
15306
|
+
}
|
|
15307
|
+
writeDaemonRecord(record3) {
|
|
15308
|
+
writeDaemonRecord(this.stateDir.daemonRecordFile, record3);
|
|
15309
|
+
}
|
|
15310
|
+
removeDaemonRecord() {
|
|
15311
|
+
try {
|
|
15312
|
+
unlinkSync3(this.stateDir.daemonRecordFile);
|
|
15313
|
+
} catch {}
|
|
15314
|
+
}
|
|
14822
15315
|
readStatus() {
|
|
14823
15316
|
try {
|
|
14824
|
-
const raw =
|
|
15317
|
+
const raw = readFileSync2(this.stateDir.statusFile, "utf-8");
|
|
14825
15318
|
return JSON.parse(raw);
|
|
14826
15319
|
} catch {
|
|
14827
15320
|
return null;
|
|
14828
15321
|
}
|
|
14829
15322
|
}
|
|
14830
15323
|
writeStatus(status) {
|
|
14831
|
-
this.stateDir.
|
|
14832
|
-
writeFileSync(this.stateDir.statusFile, JSON.stringify(status, null, 2) + `
|
|
14833
|
-
`, "utf-8");
|
|
15324
|
+
atomicWriteJson(this.stateDir.statusFile, status);
|
|
14834
15325
|
}
|
|
14835
15326
|
readPid() {
|
|
14836
15327
|
try {
|
|
14837
|
-
const raw =
|
|
15328
|
+
const raw = readFileSync2(this.stateDir.pidFile, "utf-8").trim();
|
|
14838
15329
|
if (!raw)
|
|
14839
15330
|
return null;
|
|
14840
15331
|
const pid = Number.parseInt(raw, 10);
|
|
@@ -14844,28 +15335,27 @@ class DaemonLifecycle {
|
|
|
14844
15335
|
}
|
|
14845
15336
|
}
|
|
14846
15337
|
writePid(pid) {
|
|
14847
|
-
this.stateDir.
|
|
14848
|
-
|
|
14849
|
-
`, "utf-8");
|
|
15338
|
+
atomicWriteText(this.stateDir.pidFile, `${pid ?? process.pid}
|
|
15339
|
+
`);
|
|
14850
15340
|
}
|
|
14851
15341
|
removePidFile() {
|
|
14852
15342
|
try {
|
|
14853
|
-
|
|
15343
|
+
unlinkSync3(this.stateDir.pidFile);
|
|
14854
15344
|
} catch {}
|
|
14855
15345
|
}
|
|
14856
15346
|
removeStatusFile() {
|
|
14857
15347
|
try {
|
|
14858
|
-
|
|
15348
|
+
unlinkSync3(this.stateDir.statusFile);
|
|
14859
15349
|
} catch {}
|
|
14860
15350
|
}
|
|
14861
15351
|
markKilled() {
|
|
14862
15352
|
this.stateDir.ensure();
|
|
14863
|
-
|
|
15353
|
+
writeFileSync2(this.stateDir.killedFile, `${Date.now()}
|
|
14864
15354
|
`, "utf-8");
|
|
14865
15355
|
}
|
|
14866
15356
|
clearKilled() {
|
|
14867
15357
|
try {
|
|
14868
|
-
|
|
15358
|
+
unlinkSync3(this.stateDir.killedFile);
|
|
14869
15359
|
} catch {}
|
|
14870
15360
|
}
|
|
14871
15361
|
wasKilled() {
|
|
@@ -14887,8 +15377,10 @@ class DaemonLifecycle {
|
|
|
14887
15377
|
daemonProc.unref();
|
|
14888
15378
|
}
|
|
14889
15379
|
removeStalePidFile() {
|
|
14890
|
-
this.log("Removing stale
|
|
15380
|
+
this.log("Removing stale daemon identity files");
|
|
14891
15381
|
this.removePidFile();
|
|
15382
|
+
this.removeStatusFile();
|
|
15383
|
+
this.removeDaemonRecord();
|
|
14892
15384
|
}
|
|
14893
15385
|
async replaceUnhealthyDaemon(statusPid) {
|
|
14894
15386
|
await this.withStartupLockStrict(async (locked) => {
|
|
@@ -14904,7 +15396,7 @@ class DaemonLifecycle {
|
|
|
14904
15396
|
}
|
|
14905
15397
|
if (isReuseVerdict(classification.verdict)) {
|
|
14906
15398
|
try {
|
|
14907
|
-
await this.waitForReady(
|
|
15399
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
14908
15400
|
return;
|
|
14909
15401
|
} catch {}
|
|
14910
15402
|
}
|
|
@@ -14912,12 +15404,12 @@ class DaemonLifecycle {
|
|
|
14912
15404
|
this.log(`Killing unhealthy daemon on control port ${this.controlPort} and relaunching`);
|
|
14913
15405
|
await this.kill(3000, statusPid);
|
|
14914
15406
|
this.launch();
|
|
14915
|
-
await this.waitForReady();
|
|
15407
|
+
await this.waitForReady(this.timing.waitReadyRetries, this.timing.waitReadyDelayMs);
|
|
14916
15408
|
});
|
|
14917
15409
|
}
|
|
14918
15410
|
async waitForContendedStartupLock() {
|
|
14919
15411
|
this.log("Another process holds the startup lock, waiting for readiness+identity...");
|
|
14920
|
-
await this.waitForReadyAndOurs();
|
|
15412
|
+
await this.waitForReadyAndOurs(this.timing.waitReadyRetries, this.timing.waitReadyDelayMs);
|
|
14921
15413
|
}
|
|
14922
15414
|
async withStartupLockStrict(fn) {
|
|
14923
15415
|
const locked = this.acquireLockStrict();
|
|
@@ -14932,15 +15424,15 @@ class DaemonLifecycle {
|
|
|
14932
15424
|
this.stateDir.ensure();
|
|
14933
15425
|
let fd = null;
|
|
14934
15426
|
try {
|
|
14935
|
-
fd =
|
|
14936
|
-
|
|
15427
|
+
fd = openSync2(this.stateDir.lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
|
|
15428
|
+
writeFileSync2(fd, `${process.pid}
|
|
14937
15429
|
`);
|
|
14938
|
-
|
|
15430
|
+
closeSync2(fd);
|
|
14939
15431
|
return true;
|
|
14940
15432
|
} catch (err) {
|
|
14941
15433
|
if (fd !== null && err.code !== "EEXIST") {
|
|
14942
15434
|
try {
|
|
14943
|
-
|
|
15435
|
+
closeSync2(fd);
|
|
14944
15436
|
} catch {}
|
|
14945
15437
|
this.releaseLock();
|
|
14946
15438
|
}
|
|
@@ -14948,7 +15440,7 @@ class DaemonLifecycle {
|
|
|
14948
15440
|
if (reclaimed)
|
|
14949
15441
|
return false;
|
|
14950
15442
|
try {
|
|
14951
|
-
const holderPid = Number.parseInt(
|
|
15443
|
+
const holderPid = Number.parseInt(readFileSync2(this.stateDir.lockFile, "utf-8").trim(), 10);
|
|
14952
15444
|
if (Number.isFinite(holderPid) && !isProcessAlive(holderPid)) {
|
|
14953
15445
|
this.log(`Stale startup lock from dead process ${holderPid}, reclaiming`);
|
|
14954
15446
|
this.releaseLock();
|
|
@@ -14977,7 +15469,7 @@ class DaemonLifecycle {
|
|
|
14977
15469
|
}
|
|
14978
15470
|
releaseLock() {
|
|
14979
15471
|
try {
|
|
14980
|
-
|
|
15472
|
+
unlinkSync3(this.stateDir.lockFile);
|
|
14981
15473
|
} catch {}
|
|
14982
15474
|
}
|
|
14983
15475
|
async kill(gracefulTimeoutMs = 3000, pidOverride) {
|
|
@@ -15023,6 +15515,7 @@ class DaemonLifecycle {
|
|
|
15023
15515
|
cleanup() {
|
|
15024
15516
|
this.removePidFile();
|
|
15025
15517
|
this.removeStatusFile();
|
|
15518
|
+
this.removeDaemonRecord();
|
|
15026
15519
|
}
|
|
15027
15520
|
}
|
|
15028
15521
|
async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
@@ -15036,7 +15529,7 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
|
15036
15529
|
}
|
|
15037
15530
|
|
|
15038
15531
|
// src/config-service.ts
|
|
15039
|
-
import { readFileSync as
|
|
15532
|
+
import { readFileSync as readFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
15040
15533
|
import { join as join2 } from "path";
|
|
15041
15534
|
var DEFAULT_BUDGET_CONFIG = {
|
|
15042
15535
|
enabled: true,
|
|
@@ -15053,7 +15546,8 @@ var DEFAULT_BUDGET_CONFIG = {
|
|
|
15053
15546
|
full: null,
|
|
15054
15547
|
balanced: { effort: "medium" },
|
|
15055
15548
|
eco: { effort: "low" }
|
|
15056
|
-
}
|
|
15549
|
+
},
|
|
15550
|
+
strategy: "conserve"
|
|
15057
15551
|
};
|
|
15058
15552
|
var DEFAULT_CONFIG = {
|
|
15059
15553
|
version: "1.0",
|
|
@@ -15131,6 +15625,9 @@ function normalizeBoundedInteger(value, fallback, min, max) {
|
|
|
15131
15625
|
return fallback;
|
|
15132
15626
|
return parsed;
|
|
15133
15627
|
}
|
|
15628
|
+
function normalizeStrategy(value, fallback) {
|
|
15629
|
+
return value === "conserve" || value === "maximize" ? value : fallback;
|
|
15630
|
+
}
|
|
15134
15631
|
function normalizeBoolean(value, fallback) {
|
|
15135
15632
|
if (typeof value === "boolean")
|
|
15136
15633
|
return value;
|
|
@@ -15179,7 +15676,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
15179
15676
|
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
|
|
15180
15677
|
},
|
|
15181
15678
|
codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
|
|
15182
|
-
codexTiers
|
|
15679
|
+
codexTiers,
|
|
15680
|
+
strategy: normalizeStrategy(budget.strategy, fallback.strategy)
|
|
15183
15681
|
};
|
|
15184
15682
|
}
|
|
15185
15683
|
function normalizeConfig(raw) {
|
|
@@ -15192,13 +15690,13 @@ function normalizeConfig(raw) {
|
|
|
15192
15690
|
return {
|
|
15193
15691
|
version: typeof config2.version === "string" ? config2.version : DEFAULT_CONFIG.version,
|
|
15194
15692
|
codex: {
|
|
15195
|
-
appPort:
|
|
15196
|
-
proxyPort:
|
|
15693
|
+
appPort: normalizeBoundedInteger(codex.appPort ?? daemon.port, DEFAULT_CONFIG.codex.appPort, 1, 65535),
|
|
15694
|
+
proxyPort: normalizeBoundedInteger(codex.proxyPort ?? daemon.proxyPort, DEFAULT_CONFIG.codex.proxyPort, 1, 65535)
|
|
15197
15695
|
},
|
|
15198
15696
|
turnCoordination: {
|
|
15199
|
-
attentionWindowSeconds:
|
|
15697
|
+
attentionWindowSeconds: normalizeBoundedInteger(turnCoordination.attentionWindowSeconds, DEFAULT_CONFIG.turnCoordination.attentionWindowSeconds, 0, Number.MAX_SAFE_INTEGER)
|
|
15200
15698
|
},
|
|
15201
|
-
idleShutdownSeconds:
|
|
15699
|
+
idleShutdownSeconds: normalizeBoundedInteger(config2.idleShutdownSeconds, DEFAULT_CONFIG.idleShutdownSeconds, 1, Number.MAX_SAFE_INTEGER),
|
|
15202
15700
|
budget: normalizeBudgetConfig(config2.budget)
|
|
15203
15701
|
};
|
|
15204
15702
|
}
|
|
@@ -15217,7 +15715,7 @@ class ConfigService {
|
|
|
15217
15715
|
load() {
|
|
15218
15716
|
let raw;
|
|
15219
15717
|
try {
|
|
15220
|
-
raw =
|
|
15718
|
+
raw = readFileSync3(this.configPath, "utf-8");
|
|
15221
15719
|
} catch (err) {
|
|
15222
15720
|
if (err?.code === "ENOENT") {
|
|
15223
15721
|
return { state: "absent" };
|
|
@@ -15270,9 +15768,7 @@ class ConfigService {
|
|
|
15270
15768
|
};
|
|
15271
15769
|
}
|
|
15272
15770
|
save(config2) {
|
|
15273
|
-
this.
|
|
15274
|
-
writeFileSync2(this.configPath, JSON.stringify(config2, null, 2) + `
|
|
15275
|
-
`, "utf-8");
|
|
15771
|
+
atomicWriteJson(this.configPath, config2);
|
|
15276
15772
|
}
|
|
15277
15773
|
initDefaults() {
|
|
15278
15774
|
this.ensureConfigDir();
|
|
@@ -15288,34 +15784,46 @@ class ConfigService {
|
|
|
15288
15784
|
}
|
|
15289
15785
|
ensureConfigDir() {
|
|
15290
15786
|
if (!existsSync4(this.configDir)) {
|
|
15291
|
-
|
|
15787
|
+
mkdirSync3(this.configDir, { recursive: true });
|
|
15292
15788
|
}
|
|
15293
15789
|
}
|
|
15294
15790
|
}
|
|
15295
15791
|
|
|
15792
|
+
// src/cli-invocation.ts
|
|
15793
|
+
import { basename } from "path";
|
|
15794
|
+
var CLI_NAMES = ["abg", "agentbridge"];
|
|
15795
|
+
var DEFAULT_CLI_NAME = "abg";
|
|
15796
|
+
function cliInvocationName(argv = process.argv) {
|
|
15797
|
+
const raw = argv[1];
|
|
15798
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
15799
|
+
return DEFAULT_CLI_NAME;
|
|
15800
|
+
const name = basename(raw).replace(/\.(ts|js|mjs|cjs)$/, "");
|
|
15801
|
+
return isCliName(name) ? name : DEFAULT_CLI_NAME;
|
|
15802
|
+
}
|
|
15803
|
+
function isCliName(value) {
|
|
15804
|
+
return CLI_NAMES.includes(value);
|
|
15805
|
+
}
|
|
15806
|
+
|
|
15296
15807
|
// src/pair-registry.ts
|
|
15297
15808
|
import {
|
|
15298
|
-
closeSync as closeSync2,
|
|
15299
15809
|
existsSync as existsSync5,
|
|
15300
|
-
fsyncSync,
|
|
15301
15810
|
linkSync,
|
|
15302
15811
|
lstatSync,
|
|
15303
|
-
mkdirSync as
|
|
15304
|
-
openSync as openSync2,
|
|
15812
|
+
mkdirSync as mkdirSync4,
|
|
15305
15813
|
readdirSync,
|
|
15306
|
-
readFileSync as
|
|
15814
|
+
readFileSync as readFileSync4,
|
|
15307
15815
|
realpathSync,
|
|
15308
|
-
renameSync as renameSync2,
|
|
15309
15816
|
rmSync,
|
|
15310
15817
|
statSync as statSync3,
|
|
15311
|
-
unlinkSync as
|
|
15818
|
+
unlinkSync as unlinkSync4,
|
|
15312
15819
|
writeFileSync as writeFileSync3
|
|
15313
15820
|
} from "fs";
|
|
15314
|
-
import { createHash, randomUUID as
|
|
15315
|
-
import { basename, join as join3, resolve, sep } from "path";
|
|
15821
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
15822
|
+
import { basename as basename2, join as join3, resolve, sep } from "path";
|
|
15316
15823
|
var PAIR_BASE_PORT = 4500;
|
|
15317
15824
|
var PAIR_SLOT_STRIDE = 10;
|
|
15318
15825
|
var PAIR_ID_REGEX = /^[A-Za-z0-9._-]{1,64}$/;
|
|
15826
|
+
var RECLAIMABLE_MIN_AGE_MS = 24 * 60 * 60 * 1000;
|
|
15319
15827
|
var REGISTRY_FILE_NAME = "registry.json";
|
|
15320
15828
|
class PairError extends Error {
|
|
15321
15829
|
code;
|
|
@@ -15351,7 +15859,7 @@ function readRegistry(base) {
|
|
|
15351
15859
|
return { version: 1, pairs: [] };
|
|
15352
15860
|
let parsed;
|
|
15353
15861
|
try {
|
|
15354
|
-
parsed = JSON.parse(
|
|
15862
|
+
parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
15355
15863
|
} catch (err) {
|
|
15356
15864
|
throw new PairError("PAIR_REGISTRY_CORRUPT", `Registry JSON is not parseable at ${path}: ${err.message}`, {
|
|
15357
15865
|
path
|
|
@@ -15392,10 +15900,10 @@ function findPair(base, pairId) {
|
|
|
15392
15900
|
}
|
|
15393
15901
|
|
|
15394
15902
|
// src/pair-command.ts
|
|
15395
|
-
function pairScopedCommand(cmd) {
|
|
15903
|
+
function pairScopedCommand(cmd, name = cliInvocationName()) {
|
|
15396
15904
|
const pairId = process.env.AGENTBRIDGE_PAIR_ID;
|
|
15397
15905
|
if (!pairId)
|
|
15398
|
-
return
|
|
15906
|
+
return `${name} ${cmd}`;
|
|
15399
15907
|
let selector = process.env.AGENTBRIDGE_PAIR_NAME;
|
|
15400
15908
|
if (!selector) {
|
|
15401
15909
|
try {
|
|
@@ -15404,10 +15912,13 @@ function pairScopedCommand(cmd) {
|
|
|
15404
15912
|
selector = pairId;
|
|
15405
15913
|
}
|
|
15406
15914
|
}
|
|
15407
|
-
return
|
|
15915
|
+
return `${name} --pair ${selector} ${cmd}`;
|
|
15408
15916
|
}
|
|
15409
15917
|
|
|
15410
15918
|
// src/bridge-disabled-state.ts
|
|
15919
|
+
function shouldEmitReconnectSuccess(state) {
|
|
15920
|
+
return !state.daemonDisabled;
|
|
15921
|
+
}
|
|
15411
15922
|
function disabledReplyError(reason) {
|
|
15412
15923
|
const claudeCmd = pairScopedCommand("claude");
|
|
15413
15924
|
switch (reason) {
|
|
@@ -15420,7 +15931,7 @@ function disabledReplyError(reason) {
|
|
|
15420
15931
|
case "auto_recovery_exhausted":
|
|
15421
15932
|
return `AgentBridge auto-recovery gave up after exhausting its retry budget for the in-flight liveness probe contention. Retry manually with \`${claudeCmd}\`.`;
|
|
15422
15933
|
case "killed":
|
|
15423
|
-
return `AgentBridge is disabled by
|
|
15934
|
+
return `AgentBridge is disabled by \`${pairScopedCommand("kill")}\`. Restart Claude Code (\`${claudeCmd}\`), switch to a new conversation, or run \`/resume\` to reconnect.`;
|
|
15424
15935
|
}
|
|
15425
15936
|
}
|
|
15426
15937
|
|
|
@@ -15499,9 +16010,25 @@ function nonEmpty(value) {
|
|
|
15499
16010
|
return value && value.length > 0 ? value : null;
|
|
15500
16011
|
}
|
|
15501
16012
|
|
|
15502
|
-
// src/
|
|
15503
|
-
import {
|
|
16013
|
+
// src/control-token.ts
|
|
16014
|
+
import { chmodSync, readFileSync as readFileSync5 } from "fs";
|
|
15504
16015
|
import { join as join4 } from "path";
|
|
16016
|
+
var CONTROL_TOKEN_FILENAME = "control-token";
|
|
16017
|
+
function resolveControlTokenPath(stateDir) {
|
|
16018
|
+
return join4(stateDir, CONTROL_TOKEN_FILENAME);
|
|
16019
|
+
}
|
|
16020
|
+
function readControlToken(path) {
|
|
16021
|
+
try {
|
|
16022
|
+
const raw = readFileSync5(path, "utf-8").trim();
|
|
16023
|
+
return raw.length > 0 ? raw : null;
|
|
16024
|
+
} catch {
|
|
16025
|
+
return null;
|
|
16026
|
+
}
|
|
16027
|
+
}
|
|
16028
|
+
|
|
16029
|
+
// src/trace-log.ts
|
|
16030
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
16031
|
+
import { join as join5 } from "path";
|
|
15505
16032
|
var TRACE_RETENTION_DAYS = 7;
|
|
15506
16033
|
var TRACE_FILE_RE = /^trace-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
15507
16034
|
var SECRET_KEY_RE = /(token|secret|password|passwd|api[_-]?key|auth|cookie|session)/i;
|
|
@@ -15541,7 +16068,7 @@ function redactArgv(argv) {
|
|
|
15541
16068
|
}
|
|
15542
16069
|
function traceLogPath(cwd, timestamp) {
|
|
15543
16070
|
const day = timestamp.slice(0, 10);
|
|
15544
|
-
return
|
|
16071
|
+
return join5(cwd, ".agentbridge", "logs", `trace-${day}.jsonl`);
|
|
15545
16072
|
}
|
|
15546
16073
|
function appendTraceEvent(input) {
|
|
15547
16074
|
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
@@ -15555,9 +16082,9 @@ function appendTraceEvent(input) {
|
|
|
15555
16082
|
...input.env ? { env: pickRelevantEnv(input.env) } : {},
|
|
15556
16083
|
...input.data ? { data: redactData(input.data) } : {}
|
|
15557
16084
|
};
|
|
15558
|
-
const logsDir =
|
|
16085
|
+
const logsDir = join5(input.cwd, ".agentbridge", "logs");
|
|
15559
16086
|
const isNewDayFile = !existsSync6(path);
|
|
15560
|
-
|
|
16087
|
+
mkdirSync5(logsDir, { recursive: true });
|
|
15561
16088
|
if (isNewDayFile) {
|
|
15562
16089
|
pruneOldTraceLogs(logsDir, path, Date.parse(timestamp));
|
|
15563
16090
|
}
|
|
@@ -15578,12 +16105,12 @@ function pruneOldTraceLogs(logsDir, keepPath, nowMs) {
|
|
|
15578
16105
|
for (const name of entries) {
|
|
15579
16106
|
if (!TRACE_FILE_RE.test(name))
|
|
15580
16107
|
continue;
|
|
15581
|
-
const filePath =
|
|
16108
|
+
const filePath = join5(logsDir, name);
|
|
15582
16109
|
if (filePath === keepPath)
|
|
15583
16110
|
continue;
|
|
15584
16111
|
try {
|
|
15585
16112
|
if (statSync4(filePath).mtimeMs < cutoff) {
|
|
15586
|
-
|
|
16113
|
+
unlinkSync5(filePath);
|
|
15587
16114
|
}
|
|
15588
16115
|
} catch {}
|
|
15589
16116
|
}
|
|
@@ -15633,7 +16160,7 @@ var CONTROL_PORT = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
|
|
|
15633
16160
|
var daemonLifecycle = new DaemonLifecycle({ stateDir, controlPort: CONTROL_PORT, log });
|
|
15634
16161
|
var CONTROL_WS_URL = daemonLifecycle.controlWsUrl;
|
|
15635
16162
|
var claude = new ClaudeAdapter(stateDir.logFile);
|
|
15636
|
-
var daemonClient = new DaemonClient(CONTROL_WS_URL, { identity: currentClientIdentity
|
|
16163
|
+
var daemonClient = new DaemonClient(CONTROL_WS_URL, { identity: currentClientIdentity });
|
|
15637
16164
|
var shuttingDown = false;
|
|
15638
16165
|
var daemonDisabled = false;
|
|
15639
16166
|
var daemonDisabledReason = null;
|
|
@@ -15646,6 +16173,7 @@ var lastReconnectNotifyTs = 0;
|
|
|
15646
16173
|
var disabledRecoveryTimer = null;
|
|
15647
16174
|
var disabledRecoveryInFlight = false;
|
|
15648
16175
|
var disabledRecoveryAttempts = 0;
|
|
16176
|
+
var nextSystemMessageId = 0;
|
|
15649
16177
|
var DISABLED_RECOVERY_MAX_ATTEMPTS = 6;
|
|
15650
16178
|
var DISABLED_RECOVERY_CONFIRM_TIMEOUT_MS = 1000;
|
|
15651
16179
|
if (process.env.AGENTBRIDGE_TRACE === "1") {
|
|
@@ -15740,6 +16268,16 @@ daemonClient.on("rejected", async (code) => {
|
|
|
15740
16268
|
notificationId = "system_bridge_pair_mismatch";
|
|
15741
16269
|
notificationContent = `\u26A0\uFE0F AgentBridge daemon rejected this session \u2014 pair/cwd identity mismatch (this daemon belongs to a different pair or directory). Do NOT kill it; start Claude Code from the pair's own directory, or pick another pair name with \`agentbridge --pair <name> claude\`. AgentBridge \u62D2\u7EDD\u4E86\u6B64\u4F1A\u8BDD\u2014\u2014pair/\u76EE\u5F55\u8EAB\u4EFD\u4E0D\u5339\u914D\uFF08\u8BE5 daemon \u5C5E\u4E8E\u5176\u4ED6 pair \u6216\u76EE\u5F55\uFF09\u3002\u65E0\u9700 kill\uFF1B\u8BF7\u5230\u5BF9\u5E94\u76EE\u5F55\u542F\u52A8\uFF0C\u6216\u6362\u4E00\u4E2A pair \u540D\uFF1A\`agentbridge --pair <\u540D\u5B57> claude\`\u3002`;
|
|
15742
16270
|
break;
|
|
16271
|
+
case CLOSE_CODE_TOKEN_MISMATCH:
|
|
16272
|
+
reason = "rejected";
|
|
16273
|
+
notificationId = "system_bridge_token_mismatch";
|
|
16274
|
+
notificationContent = `\u26A0\uFE0F AgentBridge daemon rejected this session \u2014 control token mismatch (the daemon likely restarted and rotated its token). Start a fresh session with \`${pairScopedCommand("claude")}\` to pick up the current token. AgentBridge \u62D2\u7EDD\u4E86\u6B64\u4F1A\u8BDD\u2014\u2014\u63A7\u5236\u4EE4\u724C\u4E0D\u5339\u914D\uFF08daemon \u53EF\u80FD\u5DF2\u91CD\u542F\u5E76\u8F6E\u6362\u4EE4\u724C\uFF09\u3002\u8BF7\u7528 \`${pairScopedCommand("claude")}\` \u91CD\u65B0\u542F\u52A8\u4EE5\u83B7\u53D6\u6700\u65B0\u4EE4\u724C\u3002`;
|
|
16275
|
+
break;
|
|
16276
|
+
case CLOSE_CODE_CONTRACT_MISMATCH:
|
|
16277
|
+
reason = "rejected";
|
|
16278
|
+
notificationId = "system_bridge_contract_mismatch";
|
|
16279
|
+
notificationContent = `\u26A0\uFE0F AgentBridge daemon rejected this session \u2014 protocol contract mismatch. The installed plugin and the running daemon are built from out-of-sync protocol versions. Run \`bun run install:global\` to rebuild + reinstall, then close and reopen Claude Code. Do NOT kill other pairs \u2014 this is local build skew, not a session conflict. AgentBridge \u62D2\u7EDD\u4E86\u6B64\u4F1A\u8BDD\u2014\u2014\u534F\u8BAE\u5951\u7EA6\u7248\u672C\u4E0D\u5339\u914D\u3002\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6\u4E0E\u8FD0\u884C\u4E2D\u7684 daemon \u534F\u8BAE\u7248\u672C\u4E0D\u4E00\u81F4\u3002\u8BF7\u8FD0\u884C \`bun run install:global\` \u91CD\u65B0\u7F16\u8BD1\u5E76\u5B89\u88C5\uFF0C\u7136\u540E\u5173\u95ED\u5E76\u91CD\u65B0\u6253\u5F00 Claude Code\u3002\u8BF7\u52FF kill \u5176\u5B83 pair\u2014\u2014\u8FD9\u662F\u672C\u5730\u6784\u5EFA\u7248\u672C\u6F02\u79FB\uFF0C\u4E0D\u662F\u4F1A\u8BDD\u51B2\u7A81\u3002`;
|
|
16280
|
+
break;
|
|
15743
16281
|
default:
|
|
15744
16282
|
reason = "rejected";
|
|
15745
16283
|
notificationId = "system_bridge_replaced";
|
|
@@ -15759,7 +16297,7 @@ daemonClient.on("rejected", async (code) => {
|
|
|
15759
16297
|
claude.on("ready", async () => {
|
|
15760
16298
|
log("MCP server ready (push delivery) \u2014 ensuring AgentBridge daemon...");
|
|
15761
16299
|
if (daemonLifecycle.wasKilled()) {
|
|
15762
|
-
await enterDisabledState("Killed sentinel found \u2014 bridge staying idle", `\u26D4 AgentBridge was stopped by
|
|
16300
|
+
await enterDisabledState("Killed sentinel found \u2014 bridge staying idle", `\u26D4 AgentBridge was stopped by \`${pairScopedCommand("kill")}\`. Bridge is staying idle. Restart Claude Code (\`${pairScopedCommand("claude")}\`), switch to a new conversation, or run \`/resume\` to reconnect.`);
|
|
15763
16301
|
return;
|
|
15764
16302
|
}
|
|
15765
16303
|
try {
|
|
@@ -15821,7 +16359,7 @@ var reconnectTask = null;
|
|
|
15821
16359
|
async function notifyIfDaemonKilled(logMessage) {
|
|
15822
16360
|
if (!daemonLifecycle.wasKilled())
|
|
15823
16361
|
return false;
|
|
15824
|
-
await enterDisabledState(logMessage, `\u26D4 AgentBridge was stopped by
|
|
16362
|
+
await enterDisabledState(logMessage, `\u26D4 AgentBridge was stopped by \`${pairScopedCommand("kill")}\`. Bridge is staying idle. Restart Claude Code (\`${pairScopedCommand("claude")}\`), switch to a new conversation, or run \`/resume\` to reconnect.`);
|
|
15825
16363
|
return true;
|
|
15826
16364
|
}
|
|
15827
16365
|
async function notifyIfPairRemoved(logMessage) {
|
|
@@ -15858,6 +16396,9 @@ function reconnectToDaemon() {
|
|
|
15858
16396
|
}
|
|
15859
16397
|
try {
|
|
15860
16398
|
await connectToDaemon(true);
|
|
16399
|
+
if (!shouldEmitReconnectSuccess({ daemonDisabled })) {
|
|
16400
|
+
return;
|
|
16401
|
+
}
|
|
15861
16402
|
log("Reconnected to AgentBridge daemon successfully");
|
|
15862
16403
|
const now = Date.now();
|
|
15863
16404
|
if (now - lastReconnectNotifyTs >= RECONNECT_NOTIFY_COOLDOWN_MS) {
|
|
@@ -15976,13 +16517,14 @@ async function pollDisabledRecovery() {
|
|
|
15976
16517
|
}
|
|
15977
16518
|
function systemMessage(idPrefix, content) {
|
|
15978
16519
|
return {
|
|
15979
|
-
id: `${idPrefix}_${
|
|
16520
|
+
id: `${idPrefix}_${++nextSystemMessageId}`,
|
|
15980
16521
|
source: "codex",
|
|
15981
16522
|
content,
|
|
15982
16523
|
timestamp: Date.now()
|
|
15983
16524
|
};
|
|
15984
16525
|
}
|
|
15985
16526
|
function currentClientIdentity() {
|
|
16527
|
+
const controlToken = readControlToken(resolveControlTokenPath(stateDir.dir));
|
|
15986
16528
|
return {
|
|
15987
16529
|
pairId: process.env.AGENTBRIDGE_PAIR_ID ?? null,
|
|
15988
16530
|
pairName: process.env.AGENTBRIDGE_PAIR_NAME ?? null,
|
|
@@ -15990,7 +16532,8 @@ function currentClientIdentity() {
|
|
|
15990
16532
|
baseDir: process.env.AGENTBRIDGE_BASE_DIR ?? null,
|
|
15991
16533
|
stateDir: stateDir.dir,
|
|
15992
16534
|
clientPid: process.pid,
|
|
15993
|
-
contractVersion: BUILD_INFO.contractVersion
|
|
16535
|
+
contractVersion: BUILD_INFO.contractVersion,
|
|
16536
|
+
...controlToken ? { controlToken } : {}
|
|
15994
16537
|
};
|
|
15995
16538
|
}
|
|
15996
16539
|
function shutdown(reason) {
|