@hua-labs/tap 0.3.0 → 0.4.0
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/README.md +0 -9
- package/dist/bridges/codex-app-server-auth-gateway.d.mts +9 -1
- package/dist/bridges/codex-app-server-auth-gateway.mjs +183 -14
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +224 -5
- package/dist/bridges/codex-app-server-bridge.mjs +1138 -683
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +17 -2
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +703 -95
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +502 -57
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +329 -72
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +3 -2
- package/LICENSE +0 -21
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
// src/bridges/codex-app-server-bridge.ts
|
|
2
|
-
import { pathToFileURL as
|
|
3
|
-
import { resolve as
|
|
2
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
3
|
+
import { resolve as resolve6 } from "path";
|
|
4
4
|
|
|
5
|
-
// ../../scripts/
|
|
6
|
-
import { createHash } from "crypto";
|
|
7
|
-
import {
|
|
8
|
-
existsSync,
|
|
9
|
-
mkdirSync,
|
|
10
|
-
readdirSync,
|
|
11
|
-
readFileSync,
|
|
12
|
-
renameSync,
|
|
13
|
-
statSync,
|
|
14
|
-
unlinkSync,
|
|
15
|
-
writeFileSync
|
|
16
|
-
} from "fs";
|
|
17
|
-
import { isAbsolute, join, resolve } from "path";
|
|
18
|
-
import { pathToFileURL } from "url";
|
|
5
|
+
// ../../scripts/bridge/bridge-types.ts
|
|
19
6
|
var DEFAULT_AGENT = String.fromCharCode(50728);
|
|
20
7
|
var DEFAULT_APP_SERVER_URL = "ws://127.0.0.1:4501";
|
|
21
8
|
var AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
@@ -33,6 +20,61 @@ var HEADLESS_WARMUP_PROMPT = [
|
|
|
33
20
|
var HEADLESS_WARMUP_TIMEOUT_MS = 3e4;
|
|
34
21
|
var TURN_COMPLETION_POLL_MS = 250;
|
|
35
22
|
var TURN_COMPLETION_REFRESH_MS = 1e3;
|
|
23
|
+
var HEADLESS_SKIP_PATTERNS = [
|
|
24
|
+
/리뷰\s*요청/,
|
|
25
|
+
/review[- ]?request/i,
|
|
26
|
+
/재리뷰/,
|
|
27
|
+
/re-?review/i
|
|
28
|
+
];
|
|
29
|
+
var COMMS_HEARTBEAT_LOCK_TIMEOUT_MS = 2e3;
|
|
30
|
+
var COMMS_LOCK_STALE_AGE_MS = 1e4;
|
|
31
|
+
var STALE_TURN_MS = 5 * 60 * 1e3;
|
|
32
|
+
|
|
33
|
+
// ../../scripts/bridge/bridge-routing.ts
|
|
34
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
35
|
+
import { join, resolve } from "path";
|
|
36
|
+
|
|
37
|
+
// ../tap-plugin/channels/tap-identity.ts
|
|
38
|
+
var BROADCAST_RECIPIENTS = /* @__PURE__ */ new Set(["\uC804\uCCB4", "all"]);
|
|
39
|
+
function trimAddress(value) {
|
|
40
|
+
return value?.trim() ?? "";
|
|
41
|
+
}
|
|
42
|
+
function canonicalizeAgentId(value) {
|
|
43
|
+
return trimAddress(value).replace(/-/g, "_");
|
|
44
|
+
}
|
|
45
|
+
function isBroadcastRecipient(value) {
|
|
46
|
+
return BROADCAST_RECIPIENTS.has(trimAddress(value));
|
|
47
|
+
}
|
|
48
|
+
function sameRoutingAddress(left, right) {
|
|
49
|
+
const normalizedLeft = trimAddress(left);
|
|
50
|
+
const normalizedRight = trimAddress(right);
|
|
51
|
+
if (!normalizedLeft || !normalizedRight) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (isBroadcastRecipient(normalizedLeft) && isBroadcastRecipient(normalizedRight)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return normalizedLeft === normalizedRight || canonicalizeAgentId(normalizedLeft) === canonicalizeAgentId(normalizedRight);
|
|
58
|
+
}
|
|
59
|
+
function matchesAgentRecipient(recipient, agentId, agentName) {
|
|
60
|
+
const normalizedRecipient = trimAddress(recipient);
|
|
61
|
+
if (!normalizedRecipient) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return isBroadcastRecipient(normalizedRecipient) || sameRoutingAddress(normalizedRecipient, agentId) || normalizedRecipient === trimAddress(agentName);
|
|
65
|
+
}
|
|
66
|
+
function isOwnMessageAddress(sender, agentId, agentName) {
|
|
67
|
+
const normalizedSender = trimAddress(sender);
|
|
68
|
+
if (!normalizedSender) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return sameRoutingAddress(normalizedSender, agentId) || normalizedSender === trimAddress(agentName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ../../scripts/bridge/bridge-routing.ts
|
|
75
|
+
function canonicalize(id) {
|
|
76
|
+
return canonicalizeAgentId(id);
|
|
77
|
+
}
|
|
36
78
|
function normalizeThreadCwd(cwd) {
|
|
37
79
|
return resolve(cwd).replace(/\\/g, "/").toLowerCase();
|
|
38
80
|
}
|
|
@@ -59,6 +101,170 @@ function chooseLoadedThreadForCwd(cwd, threads) {
|
|
|
59
101
|
});
|
|
60
102
|
return matching[0] ?? null;
|
|
61
103
|
}
|
|
104
|
+
function normalizeAgentToken(value) {
|
|
105
|
+
const normalized = value?.trim();
|
|
106
|
+
if (!normalized || PLACEHOLDER_AGENT_VALUES.has(normalized)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return canonicalize(normalized);
|
|
110
|
+
}
|
|
111
|
+
function resolveAgentId(preferredAgentName) {
|
|
112
|
+
return normalizeAgentToken(process.env.TAP_AGENT_ID) ?? normalizeAgentToken(preferredAgentName) ?? "unknown";
|
|
113
|
+
}
|
|
114
|
+
function resolveAgentName(preferredAgentName, stateDir) {
|
|
115
|
+
if (preferredAgentName?.trim()) {
|
|
116
|
+
return preferredAgentName.trim();
|
|
117
|
+
}
|
|
118
|
+
const agentFile = join(stateDir, "agent-name.txt");
|
|
119
|
+
if (existsSync(agentFile)) {
|
|
120
|
+
const candidate = readFileSync(agentFile, "utf8").trim();
|
|
121
|
+
if (candidate) {
|
|
122
|
+
return candidate;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return DEFAULT_AGENT;
|
|
126
|
+
}
|
|
127
|
+
function resolveCurrentAgentName(agentId, fallbackAgentName, heartbeats) {
|
|
128
|
+
const currentName = heartbeats[agentId]?.agent?.trim();
|
|
129
|
+
if (currentName) {
|
|
130
|
+
return currentName;
|
|
131
|
+
}
|
|
132
|
+
for (const heartbeat of Object.values(heartbeats)) {
|
|
133
|
+
if (heartbeat.id?.trim() === agentId && heartbeat.agent?.trim()) {
|
|
134
|
+
return heartbeat.agent.trim();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return fallbackAgentName;
|
|
138
|
+
}
|
|
139
|
+
function resolveAddressLabel(address, heartbeats) {
|
|
140
|
+
const normalized = address.trim();
|
|
141
|
+
if (!normalized || normalized === "\uC804\uCCB4" || normalized === "all") {
|
|
142
|
+
return address;
|
|
143
|
+
}
|
|
144
|
+
const direct = heartbeats[normalized];
|
|
145
|
+
if (direct?.agent?.trim()) {
|
|
146
|
+
return formatAgentLabel(normalized, direct.agent);
|
|
147
|
+
}
|
|
148
|
+
for (const [agentId, heartbeat] of Object.entries(heartbeats)) {
|
|
149
|
+
if (heartbeat.agent?.trim() === normalized) {
|
|
150
|
+
return formatAgentLabel(agentId, heartbeat.agent);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return normalized;
|
|
154
|
+
}
|
|
155
|
+
function persistAgentName(stateDir, agentName) {
|
|
156
|
+
writeFileSync(join(stateDir, "agent-name.txt"), `${agentName}
|
|
157
|
+
`, "utf8");
|
|
158
|
+
}
|
|
159
|
+
function formatAgentLabel(agentIdOrName, displayName) {
|
|
160
|
+
const normalizedId = agentIdOrName.trim();
|
|
161
|
+
const normalizedName = displayName?.trim();
|
|
162
|
+
if (!normalizedId) {
|
|
163
|
+
return normalizedName ?? agentIdOrName;
|
|
164
|
+
}
|
|
165
|
+
if (!normalizedName || normalizedName === normalizedId) {
|
|
166
|
+
return normalizedId;
|
|
167
|
+
}
|
|
168
|
+
return `${normalizedName} [${normalizedId}]`;
|
|
169
|
+
}
|
|
170
|
+
function refreshAgentIdentity(options, heartbeats) {
|
|
171
|
+
const nextAgentName = resolveCurrentAgentName(
|
|
172
|
+
options.agentId,
|
|
173
|
+
options.agentName,
|
|
174
|
+
heartbeats
|
|
175
|
+
);
|
|
176
|
+
if (nextAgentName !== options.agentName) {
|
|
177
|
+
persistAgentName(options.stateDir, nextAgentName);
|
|
178
|
+
}
|
|
179
|
+
return nextAgentName;
|
|
180
|
+
}
|
|
181
|
+
function recipientMatchesAgent(recipient, agentId, agentName) {
|
|
182
|
+
return matchesAgentRecipient(recipient, agentId, agentName);
|
|
183
|
+
}
|
|
184
|
+
function isOwnMessageSender(sender, agentId, agentName) {
|
|
185
|
+
return isOwnMessageAddress(sender, agentId, agentName);
|
|
186
|
+
}
|
|
187
|
+
function isTurnStuckOnApproval(activeFlags) {
|
|
188
|
+
return activeFlags.includes("waitingOnApproval");
|
|
189
|
+
}
|
|
190
|
+
function isTurnStale(turnStartedAt, nowMs = Date.now()) {
|
|
191
|
+
if (!turnStartedAt) return false;
|
|
192
|
+
return nowMs - new Date(turnStartedAt).getTime() > STALE_TURN_MS;
|
|
193
|
+
}
|
|
194
|
+
function shouldRetrySteerAsStart(error) {
|
|
195
|
+
if (!(error instanceof Error)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const message = error.message.toLowerCase();
|
|
199
|
+
return message.includes("no active turn") || message.includes("expectedturnid") || message.includes("turn/steer failed") && (message.includes("active turn") || message.includes("not found"));
|
|
200
|
+
}
|
|
201
|
+
function parseBridgeFrontmatter(content) {
|
|
202
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
203
|
+
if (!match) return null;
|
|
204
|
+
const fields = {};
|
|
205
|
+
for (const line of match[1].split("\n")) {
|
|
206
|
+
const kv = line.match(/^(\w+):\s*(.+)$/);
|
|
207
|
+
if (kv) fields[kv[1]] = kv[2].trim();
|
|
208
|
+
}
|
|
209
|
+
if (!fields.from || !fields.to) return null;
|
|
210
|
+
return {
|
|
211
|
+
sender: fields.from,
|
|
212
|
+
recipient: fields.to,
|
|
213
|
+
subject: fields.subject ?? ""
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function stripBridgeFrontmatter(content) {
|
|
217
|
+
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, "");
|
|
218
|
+
}
|
|
219
|
+
function getInboxRoute(fileName, body) {
|
|
220
|
+
if (body) {
|
|
221
|
+
const fm = parseBridgeFrontmatter(body);
|
|
222
|
+
if (fm) return fm;
|
|
223
|
+
}
|
|
224
|
+
return getInboxRouteFromFilename(fileName);
|
|
225
|
+
}
|
|
226
|
+
function getInboxRouteFromFilename(fileName) {
|
|
227
|
+
const stem = fileName.replace(/\.md$/i, "");
|
|
228
|
+
const parts = stem.split("-");
|
|
229
|
+
let offset = 0;
|
|
230
|
+
if (parts[0] && /^\d{8}$/.test(parts[0])) {
|
|
231
|
+
offset = 1;
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
sender: parts[offset] ?? "",
|
|
235
|
+
recipient: parts[offset + 1] ?? "",
|
|
236
|
+
subject: parts.slice(offset + 2).join("-")
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ../../scripts/bridge/bridge-config.ts
|
|
241
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3 } from "fs";
|
|
242
|
+
import { isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "path";
|
|
243
|
+
|
|
244
|
+
// src/config/resolve.ts
|
|
245
|
+
import * as fs from "fs";
|
|
246
|
+
import * as path from "path";
|
|
247
|
+
function normalizeTapPath(input) {
|
|
248
|
+
const trimmed = input.trim().replace(/^["'`]+|["'`]+$/g, "");
|
|
249
|
+
if (/^[A-Za-z]:[\\/]/.test(trimmed)) {
|
|
250
|
+
return trimmed;
|
|
251
|
+
}
|
|
252
|
+
if (process.platform === "win32") {
|
|
253
|
+
const match = trimmed.match(/^\/([A-Za-z])\/(.*)$/);
|
|
254
|
+
if (match) {
|
|
255
|
+
return `${match[1].toUpperCase()}:\\${match[2].replace(/\//g, "\\")}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return trimmed;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ../../scripts/bridge/bridge-config.ts
|
|
262
|
+
function ensureDir(target) {
|
|
263
|
+
if (!existsSync3(target)) {
|
|
264
|
+
mkdirSync(target, { recursive: true });
|
|
265
|
+
}
|
|
266
|
+
return resolve3(target);
|
|
267
|
+
}
|
|
62
268
|
function printHelp() {
|
|
63
269
|
console.log(`Codex App Server bridge
|
|
64
270
|
|
|
@@ -80,6 +286,7 @@ Options:
|
|
|
80
286
|
--app-server-url=<ws-url>
|
|
81
287
|
--gateway-token-file=<path>
|
|
82
288
|
--busy-mode=wait|steer
|
|
289
|
+
--log-level=debug|info|warn|error
|
|
83
290
|
--thread-id=<id>
|
|
84
291
|
--ephemeral
|
|
85
292
|
--help
|
|
@@ -238,50 +445,42 @@ function parseArgs(argv) {
|
|
|
238
445
|
}
|
|
239
446
|
continue;
|
|
240
447
|
}
|
|
448
|
+
if (flag.startsWith("--log-level")) {
|
|
449
|
+
const value = readFlagValue(argv, index, "--log-level");
|
|
450
|
+
if (value !== "debug" && value !== "info" && value !== "warn" && value !== "error") {
|
|
451
|
+
throw new Error(`Invalid --log-level: ${value}`);
|
|
452
|
+
}
|
|
453
|
+
parsed.logLevel = value;
|
|
454
|
+
if (consumesNext) {
|
|
455
|
+
index += 1;
|
|
456
|
+
}
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
241
459
|
throw new Error(`Unknown argument: ${flag}`);
|
|
242
460
|
}
|
|
243
461
|
return parsed;
|
|
244
462
|
}
|
|
245
|
-
function timestamp() {
|
|
246
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", " UTC");
|
|
247
|
-
}
|
|
248
|
-
function logStatus(message) {
|
|
249
|
-
console.log(`[${timestamp()}] ${message}`);
|
|
250
|
-
}
|
|
251
|
-
function ensureDir(target) {
|
|
252
|
-
if (!existsSync(target)) {
|
|
253
|
-
mkdirSync(target, { recursive: true });
|
|
254
|
-
}
|
|
255
|
-
return resolve(target);
|
|
256
|
-
}
|
|
257
|
-
function convertTapPath(input) {
|
|
258
|
-
const trimmed = input.trim().replace(/^["'`]+|["'`]+$/g, "");
|
|
259
|
-
if (/^[A-Za-z]:\\/.test(trimmed)) {
|
|
260
|
-
return trimmed;
|
|
261
|
-
}
|
|
262
|
-
const match = trimmed.match(/^\/([A-Za-z])\/(.*)$/);
|
|
263
|
-
if (match) {
|
|
264
|
-
return `${match[1].toUpperCase()}:\\${match[2].replace(/\//g, "\\")}`;
|
|
265
|
-
}
|
|
266
|
-
return trimmed;
|
|
267
|
-
}
|
|
268
463
|
function resolveRepoRoot(explicit) {
|
|
269
464
|
if (explicit) {
|
|
270
|
-
return
|
|
465
|
+
return resolve3(explicit);
|
|
271
466
|
}
|
|
272
467
|
return process.cwd();
|
|
273
468
|
}
|
|
469
|
+
function resolveTapConfigPath(repoRoot, input) {
|
|
470
|
+
const converted = normalizeTapPath(input);
|
|
471
|
+
return isAbsolute2(converted) ? resolve3(converted) : resolve3(repoRoot, converted);
|
|
472
|
+
}
|
|
274
473
|
function resolveCommsDir(repoRoot, explicit) {
|
|
275
474
|
if (explicit) {
|
|
276
|
-
return
|
|
475
|
+
return resolve3(normalizeTapPath(explicit));
|
|
277
476
|
}
|
|
278
|
-
const tapConfigPath =
|
|
279
|
-
if (!
|
|
477
|
+
const tapConfigPath = join3(repoRoot, ".tap-config");
|
|
478
|
+
if (!existsSync3(tapConfigPath)) {
|
|
280
479
|
throw new Error(
|
|
281
480
|
"Unable to resolve comms directory. Pass --comms-dir explicitly."
|
|
282
481
|
);
|
|
283
482
|
}
|
|
284
|
-
const configText =
|
|
483
|
+
const configText = readFileSync3(tapConfigPath, "utf8");
|
|
285
484
|
const match = configText.match(/^TAP_COMMS_DIR="?(.*?)"?$/m);
|
|
286
485
|
if (!match?.[1]) {
|
|
287
486
|
throw new Error(
|
|
@@ -302,300 +501,195 @@ function resolvePreferredAgentName(requested) {
|
|
|
302
501
|
}
|
|
303
502
|
return null;
|
|
304
503
|
}
|
|
305
|
-
function normalizeAgentToken(value) {
|
|
306
|
-
const normalized = value?.trim();
|
|
307
|
-
if (!normalized || PLACEHOLDER_AGENT_VALUES.has(normalized)) {
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
return normalized;
|
|
311
|
-
}
|
|
312
|
-
function resolveAgentId(preferredAgentName) {
|
|
313
|
-
return normalizeAgentToken(process.env.TAP_AGENT_ID) ?? normalizeAgentToken(preferredAgentName) ?? "unknown";
|
|
314
|
-
}
|
|
315
504
|
function sanitizeStateSegment(agentName) {
|
|
316
505
|
const normalized = agentName.trim().replace(/[<>:"/\\|?*\x00-\x1f]/g, "-").replace(/[. ]+$/g, "");
|
|
317
506
|
return normalized || "agent";
|
|
318
507
|
}
|
|
319
508
|
function buildDefaultStateDir(repoRoot, preferredAgentName) {
|
|
320
509
|
const suffix = preferredAgentName?.trim() ? `-${sanitizeStateSegment(preferredAgentName)}` : "";
|
|
321
|
-
return
|
|
510
|
+
return resolve3(join3(repoRoot, ".tmp", `codex-app-server-bridge${suffix}`));
|
|
322
511
|
}
|
|
323
512
|
function resolveStateDir(repoRoot, explicit, preferredAgentName) {
|
|
324
|
-
const root = explicit ?
|
|
513
|
+
const root = explicit ? resolve3(explicit) : buildDefaultStateDir(repoRoot, preferredAgentName);
|
|
325
514
|
ensureDir(root);
|
|
326
|
-
ensureDir(
|
|
327
|
-
ensureDir(
|
|
515
|
+
ensureDir(join3(root, "processed"));
|
|
516
|
+
ensureDir(join3(root, "logs"));
|
|
328
517
|
return root;
|
|
329
518
|
}
|
|
330
|
-
function resolveAgentName(preferredAgentName, stateDir) {
|
|
331
|
-
if (preferredAgentName?.trim()) {
|
|
332
|
-
return preferredAgentName.trim();
|
|
333
|
-
}
|
|
334
|
-
const agentFile = join(stateDir, "agent-name.txt");
|
|
335
|
-
if (existsSync(agentFile)) {
|
|
336
|
-
const candidate = readFileSync(agentFile, "utf8").trim();
|
|
337
|
-
if (candidate) {
|
|
338
|
-
return candidate;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return DEFAULT_AGENT;
|
|
342
|
-
}
|
|
343
|
-
function persistAgentName(stateDir, agentName) {
|
|
344
|
-
writeFileSync(join(stateDir, "agent-name.txt"), `${agentName}
|
|
345
|
-
`, "utf8");
|
|
346
|
-
}
|
|
347
|
-
function sanitizeErrorForPersistence(error) {
|
|
348
|
-
if (!error) return null;
|
|
349
|
-
return error.replace(/([?&])tap_token=[^\s&)"'}]+/gi, "$1tap_token=***").replace(/"tap_token"\s*:\s*"[^"]*"/g, '"tap_token":"***"').replace(/tap-auth-[A-Za-z0-9_-]+/g, "tap-auth-***").replace(/Bearer\s+[A-Za-z0-9_.-]+/gi, "Bearer ***");
|
|
350
|
-
}
|
|
351
519
|
function readGatewayTokenFile(tokenFile) {
|
|
352
|
-
const token =
|
|
520
|
+
const token = readFileSync3(tokenFile, "utf8").trim();
|
|
353
521
|
if (!token) {
|
|
354
522
|
throw new Error(`Gateway token file is empty: ${tokenFile}`);
|
|
355
523
|
}
|
|
356
524
|
return token;
|
|
357
525
|
}
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
526
|
+
function buildOptions(argv) {
|
|
527
|
+
const parsed = parseArgs(argv);
|
|
528
|
+
const repoRoot = resolveRepoRoot(parsed.repoRoot);
|
|
529
|
+
const commsDir = resolveCommsDir(repoRoot, parsed.commsDir);
|
|
530
|
+
const preferredAgentName = resolvePreferredAgentName(parsed.agentName);
|
|
531
|
+
const stateDir = resolveStateDir(
|
|
532
|
+
repoRoot,
|
|
533
|
+
parsed.stateDir,
|
|
534
|
+
preferredAgentName
|
|
535
|
+
);
|
|
536
|
+
const agentName = resolveAgentName(preferredAgentName, stateDir);
|
|
537
|
+
const agentId = resolveAgentId(agentName);
|
|
538
|
+
persistAgentName(stateDir, agentName);
|
|
539
|
+
const gatewayTokenFile = parsed.gatewayTokenFile?.trim() || process.env.TAP_GATEWAY_TOKEN_FILE?.trim() || null;
|
|
540
|
+
const appServerUrl = parsed.appServerUrl?.trim() || process.env.CODEX_APP_SERVER_URL || DEFAULT_APP_SERVER_URL;
|
|
541
|
+
return {
|
|
542
|
+
repoRoot,
|
|
543
|
+
commsDir,
|
|
544
|
+
agentId,
|
|
545
|
+
stateDir,
|
|
546
|
+
agentName,
|
|
547
|
+
pollSeconds: parsed.pollSeconds ?? 5,
|
|
548
|
+
reconnectSeconds: parsed.reconnectSeconds ?? 5,
|
|
549
|
+
messageLookbackMinutes: parsed.messageLookbackMinutes ?? 10,
|
|
550
|
+
processExistingMessages: parsed.processExistingMessages,
|
|
551
|
+
dryRun: parsed.dryRun,
|
|
552
|
+
runOnce: parsed.runOnce,
|
|
553
|
+
waitAfterDispatchSeconds: parsed.waitAfterDispatchSeconds ?? 0,
|
|
554
|
+
appServerUrl,
|
|
555
|
+
connectAppServerUrl: appServerUrl,
|
|
556
|
+
gatewayToken: gatewayTokenFile ? readGatewayTokenFile(gatewayTokenFile) : null,
|
|
557
|
+
gatewayTokenFile,
|
|
558
|
+
busyMode: parsed.busyMode ?? "steer",
|
|
559
|
+
logLevel: parsed.logLevel ?? "info",
|
|
560
|
+
threadId: parsed.threadId?.trim() || null,
|
|
561
|
+
ephemeral: parsed.ephemeral
|
|
562
|
+
};
|
|
361
563
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
564
|
+
|
|
565
|
+
// ../../scripts/bridge/bridge-candidates.ts
|
|
566
|
+
import { createHash } from "crypto";
|
|
567
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync, statSync } from "fs";
|
|
568
|
+
import { join as join4 } from "path";
|
|
569
|
+
|
|
570
|
+
// ../../scripts/bridge/bridge-logging.ts
|
|
571
|
+
var LOG_LEVEL_PRIORITY = {
|
|
572
|
+
debug: 10,
|
|
573
|
+
info: 20,
|
|
574
|
+
warn: 30,
|
|
575
|
+
error: 40
|
|
576
|
+
};
|
|
577
|
+
var currentLogLevel = "info";
|
|
578
|
+
function configureBridgeLogging(level) {
|
|
579
|
+
currentLogLevel = level;
|
|
580
|
+
}
|
|
581
|
+
function shouldLog(level) {
|
|
582
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[currentLogLevel];
|
|
583
|
+
}
|
|
584
|
+
function formatValue(value) {
|
|
585
|
+
if (typeof value === "string") {
|
|
586
|
+
return JSON.stringify(value);
|
|
366
587
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
readFileSync(threadPath, "utf8")
|
|
370
|
-
);
|
|
371
|
-
if (parsed.threadId) {
|
|
372
|
-
return parsed;
|
|
373
|
-
}
|
|
374
|
-
} catch {
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
function readHeartbeatState(stateDir) {
|
|
380
|
-
const heartbeatPath = join(stateDir, "heartbeat.json");
|
|
381
|
-
if (!existsSync(heartbeatPath)) {
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
try {
|
|
385
|
-
return JSON.parse(readFileSync(heartbeatPath, "utf8"));
|
|
386
|
-
} catch {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
function parseUpdatedAt(value) {
|
|
391
|
-
if (!value) {
|
|
392
|
-
return 0;
|
|
588
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
589
|
+
return String(value);
|
|
393
590
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
function appServerUrlMatches(expectedAppServerUrl, actualAppServerUrl) {
|
|
398
|
-
return actualAppServerUrl?.trim() === expectedAppServerUrl;
|
|
399
|
-
}
|
|
400
|
-
function hasValidHeartbeatThreadCwd(threadCwd) {
|
|
401
|
-
const normalized = threadCwd?.trim();
|
|
402
|
-
if (!normalized) {
|
|
403
|
-
return false;
|
|
591
|
+
if (value === null) {
|
|
592
|
+
return "null";
|
|
404
593
|
}
|
|
405
|
-
return
|
|
594
|
+
return JSON.stringify(value);
|
|
406
595
|
}
|
|
407
|
-
function
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const heartbeatThreadId = heartbeat?.threadId?.trim();
|
|
411
|
-
if (!heartbeatThreadId) {
|
|
412
|
-
return savedThread;
|
|
413
|
-
}
|
|
414
|
-
if (!appServerUrlMatches(fallbackAppServerUrl, heartbeat?.appServerUrl)) {
|
|
415
|
-
return savedThread;
|
|
416
|
-
}
|
|
417
|
-
if (!hasValidHeartbeatThreadCwd(heartbeat?.threadCwd)) {
|
|
418
|
-
return savedThread;
|
|
419
|
-
}
|
|
420
|
-
const heartbeatBackedThread = {
|
|
421
|
-
threadId: heartbeatThreadId,
|
|
422
|
-
updatedAt: heartbeat?.updatedAt ?? savedThread?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
423
|
-
appServerUrl: heartbeat?.appServerUrl || savedThread?.appServerUrl || fallbackAppServerUrl,
|
|
424
|
-
ephemeral: savedThread?.ephemeral ?? false,
|
|
425
|
-
cwd: heartbeat?.threadCwd ?? (savedThread?.threadId === heartbeatThreadId ? savedThread.cwd ?? null : null)
|
|
426
|
-
};
|
|
427
|
-
let preferred = savedThread;
|
|
428
|
-
if (!savedThread?.threadId) {
|
|
429
|
-
preferred = heartbeatBackedThread;
|
|
430
|
-
} else if (savedThread.threadId === heartbeatThreadId) {
|
|
431
|
-
preferred = {
|
|
432
|
-
...savedThread,
|
|
433
|
-
updatedAt: heartbeatBackedThread.updatedAt ?? savedThread.updatedAt,
|
|
434
|
-
appServerUrl: heartbeatBackedThread.appServerUrl,
|
|
435
|
-
cwd: heartbeatBackedThread.cwd ?? savedThread.cwd ?? null
|
|
436
|
-
};
|
|
437
|
-
} else if (parseUpdatedAt(heartbeat?.updatedAt) > parseUpdatedAt(savedThread.updatedAt)) {
|
|
438
|
-
preferred = heartbeatBackedThread;
|
|
596
|
+
function formatContext(context) {
|
|
597
|
+
if (!context) {
|
|
598
|
+
return "";
|
|
439
599
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
function persistThreadState(stateDir, threadId, appServerUrl, ephemeral, cwd) {
|
|
443
|
-
const payload = {
|
|
444
|
-
threadId,
|
|
445
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
446
|
-
appServerUrl,
|
|
447
|
-
ephemeral,
|
|
448
|
-
cwd
|
|
449
|
-
};
|
|
450
|
-
writeFileSync(
|
|
451
|
-
join(stateDir, "thread.json"),
|
|
452
|
-
`${JSON.stringify(payload, null, 2)}
|
|
453
|
-
`,
|
|
454
|
-
"utf8"
|
|
600
|
+
const entries = Object.entries(context).filter(
|
|
601
|
+
([, value]) => value !== void 0
|
|
455
602
|
);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (processExistingMessages) {
|
|
459
|
-
return /* @__PURE__ */ new Date(0);
|
|
460
|
-
}
|
|
461
|
-
if (lookbackMinutes > 0) {
|
|
462
|
-
return new Date(Date.now() - lookbackMinutes * 6e4);
|
|
463
|
-
}
|
|
464
|
-
const cutoffPath = join(stateDir, "general-inbox-cutoff.txt");
|
|
465
|
-
if (existsSync(cutoffPath)) {
|
|
466
|
-
try {
|
|
467
|
-
return new Date(readFileSync(cutoffPath, "utf8").trim());
|
|
468
|
-
} catch {
|
|
469
|
-
return /* @__PURE__ */ new Date();
|
|
470
|
-
}
|
|
603
|
+
if (entries.length === 0) {
|
|
604
|
+
return "";
|
|
471
605
|
}
|
|
472
|
-
|
|
473
|
-
writeFileSync(cutoffPath, `${cutoff.toISOString()}
|
|
474
|
-
`, "utf8");
|
|
475
|
-
return cutoff;
|
|
606
|
+
return ` ${entries.map(([key, value]) => `${key}=${formatValue(value)}`).join(" ")}`;
|
|
476
607
|
}
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
return false;
|
|
608
|
+
function logBridge(level, message, context) {
|
|
609
|
+
if (!shouldLog(level)) {
|
|
610
|
+
return;
|
|
481
611
|
}
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return false;
|
|
612
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", " UTC");
|
|
613
|
+
const line = `[${ts}] ${level.toUpperCase()} ${message}${formatContext(context)}`;
|
|
614
|
+
if (level === "error") {
|
|
615
|
+
console.error(line);
|
|
616
|
+
return;
|
|
488
617
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const stem = fileName.replace(/\.md$/i, "");
|
|
493
|
-
const parts = stem.split("-");
|
|
494
|
-
let offset = 0;
|
|
495
|
-
if (parts[0] && /^\d{8}$/.test(parts[0])) {
|
|
496
|
-
offset = 1;
|
|
618
|
+
if (level === "warn") {
|
|
619
|
+
console.warn(line);
|
|
620
|
+
return;
|
|
497
621
|
}
|
|
622
|
+
console.log(line);
|
|
623
|
+
}
|
|
624
|
+
function createBridgeLogger(scope) {
|
|
625
|
+
const scopedMessage = (message) => `[${scope}] ${message}`;
|
|
498
626
|
return {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
627
|
+
debug(message, context) {
|
|
628
|
+
logBridge("debug", scopedMessage(message), context);
|
|
629
|
+
},
|
|
630
|
+
info(message, context) {
|
|
631
|
+
logBridge("info", scopedMessage(message), context);
|
|
632
|
+
},
|
|
633
|
+
warn(message, context) {
|
|
634
|
+
logBridge("warn", scopedMessage(message), context);
|
|
635
|
+
},
|
|
636
|
+
error(message, context) {
|
|
637
|
+
logBridge("error", scopedMessage(message), context);
|
|
638
|
+
}
|
|
502
639
|
};
|
|
503
640
|
}
|
|
641
|
+
|
|
642
|
+
// ../../scripts/bridge/bridge-candidates.ts
|
|
643
|
+
var routingLogger = createBridgeLogger("routing");
|
|
504
644
|
function buildMarkerId(filePath, mtimeMs) {
|
|
505
645
|
return createHash("sha1").update(`${filePath}|${mtimeMs}`).digest("hex");
|
|
506
646
|
}
|
|
507
647
|
function getProcessedMarkerPath(stateDir, markerId) {
|
|
508
|
-
return
|
|
648
|
+
return join4(stateDir, "processed", `${markerId}.done`);
|
|
509
649
|
}
|
|
510
650
|
function loadHeartbeats(commsDir) {
|
|
511
651
|
try {
|
|
512
|
-
return JSON.parse(
|
|
652
|
+
return JSON.parse(readFileSync4(join4(commsDir, "heartbeats.json"), "utf8"));
|
|
513
653
|
} catch {
|
|
514
654
|
return {};
|
|
515
655
|
}
|
|
516
656
|
}
|
|
517
|
-
function formatAgentLabel(agentIdOrName, displayName) {
|
|
518
|
-
const normalizedId = agentIdOrName.trim();
|
|
519
|
-
const normalizedName = displayName?.trim();
|
|
520
|
-
if (!normalizedId) {
|
|
521
|
-
return normalizedName ?? agentIdOrName;
|
|
522
|
-
}
|
|
523
|
-
if (!normalizedName || normalizedName === normalizedId) {
|
|
524
|
-
return normalizedId;
|
|
525
|
-
}
|
|
526
|
-
return `${normalizedName} [${normalizedId}]`;
|
|
527
|
-
}
|
|
528
|
-
function resolveAddressLabel(address, heartbeats) {
|
|
529
|
-
const normalized = address.trim();
|
|
530
|
-
if (!normalized || normalized === "\uC804\uCCB4" || normalized === "all") {
|
|
531
|
-
return address;
|
|
532
|
-
}
|
|
533
|
-
const direct = heartbeats[normalized];
|
|
534
|
-
if (direct?.agent?.trim()) {
|
|
535
|
-
return formatAgentLabel(normalized, direct.agent);
|
|
536
|
-
}
|
|
537
|
-
for (const [agentId, heartbeat] of Object.entries(heartbeats)) {
|
|
538
|
-
if (heartbeat.agent?.trim() === normalized) {
|
|
539
|
-
return formatAgentLabel(agentId, heartbeat.agent);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return normalized;
|
|
543
|
-
}
|
|
544
|
-
function resolveCurrentAgentName(agentId, fallbackAgentName, heartbeats) {
|
|
545
|
-
const currentName = heartbeats[agentId]?.agent?.trim();
|
|
546
|
-
if (currentName) {
|
|
547
|
-
return currentName;
|
|
548
|
-
}
|
|
549
|
-
for (const heartbeat of Object.values(heartbeats)) {
|
|
550
|
-
if (heartbeat.id?.trim() === agentId && heartbeat.agent?.trim()) {
|
|
551
|
-
return heartbeat.agent.trim();
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
return fallbackAgentName;
|
|
555
|
-
}
|
|
556
|
-
function refreshAgentIdentity(options, heartbeats) {
|
|
557
|
-
const nextAgentName = resolveCurrentAgentName(
|
|
558
|
-
options.agentId,
|
|
559
|
-
options.agentName,
|
|
560
|
-
heartbeats
|
|
561
|
-
);
|
|
562
|
-
if (nextAgentName !== options.agentName) {
|
|
563
|
-
options.agentName = nextAgentName;
|
|
564
|
-
persistAgentName(options.stateDir, nextAgentName);
|
|
565
|
-
}
|
|
566
|
-
return nextAgentName;
|
|
567
|
-
}
|
|
568
|
-
var HEADLESS_SKIP_PATTERNS = [
|
|
569
|
-
/리뷰\s*요청/,
|
|
570
|
-
/review[- ]?request/i,
|
|
571
|
-
/재리뷰/,
|
|
572
|
-
/re-?review/i
|
|
573
|
-
];
|
|
574
657
|
function shouldSkipInHeadlessMode(fileName, body) {
|
|
575
658
|
if (process.env.TAP_HEADLESS !== "true") return false;
|
|
576
659
|
const combined = `${fileName}
|
|
577
660
|
${body}`;
|
|
578
661
|
return HEADLESS_SKIP_PATTERNS.some((p) => p.test(combined));
|
|
579
662
|
}
|
|
580
|
-
function collectCandidates(inboxDir, agentId, agentName) {
|
|
663
|
+
function collectCandidates(inboxDir, agentId, agentName, aliasName) {
|
|
581
664
|
const entries = readdirSync(inboxDir, { withFileTypes: true }).filter(
|
|
582
665
|
(entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")
|
|
583
666
|
).map((entry) => {
|
|
584
|
-
const filePath =
|
|
667
|
+
const filePath = join4(inboxDir, entry.name);
|
|
585
668
|
const stats = statSync(filePath);
|
|
586
669
|
return { entry, filePath, stats };
|
|
587
670
|
}).sort((left, right) => left.stats.mtimeMs - right.stats.mtimeMs);
|
|
588
671
|
const candidates = [];
|
|
672
|
+
let filteredByRecipient = 0;
|
|
673
|
+
let filteredBySelf = 0;
|
|
674
|
+
let filteredByHeadless = 0;
|
|
589
675
|
for (const item of entries) {
|
|
590
|
-
|
|
591
|
-
|
|
676
|
+
let body;
|
|
677
|
+
try {
|
|
678
|
+
body = readFileSync4(item.filePath, "utf8");
|
|
679
|
+
} catch {
|
|
592
680
|
continue;
|
|
593
681
|
}
|
|
594
|
-
|
|
682
|
+
const route = getInboxRoute(item.entry.name, body);
|
|
683
|
+
if (!recipientMatchesAgent(route.recipient, agentId, agentName) && !(aliasName && recipientMatchesAgent(route.recipient, agentId, aliasName))) {
|
|
684
|
+
filteredByRecipient += 1;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (isOwnMessageSender(route.sender, agentId, agentName) || aliasName && isOwnMessageSender(route.sender, agentId, aliasName)) {
|
|
688
|
+
filteredBySelf += 1;
|
|
595
689
|
continue;
|
|
596
690
|
}
|
|
597
|
-
const body = readFileSync(item.filePath, "utf8");
|
|
598
691
|
if (shouldSkipInHeadlessMode(item.entry.name, body)) {
|
|
692
|
+
filteredByHeadless += 1;
|
|
599
693
|
continue;
|
|
600
694
|
}
|
|
601
695
|
candidates.push({
|
|
@@ -605,34 +699,58 @@ function collectCandidates(inboxDir, agentId, agentName) {
|
|
|
605
699
|
sender: route.sender,
|
|
606
700
|
recipient: route.recipient,
|
|
607
701
|
subject: route.subject,
|
|
608
|
-
body,
|
|
702
|
+
body: stripBridgeFrontmatter(body),
|
|
609
703
|
mtimeMs: item.stats.mtimeMs
|
|
610
704
|
});
|
|
611
705
|
}
|
|
706
|
+
routingLogger.debug("candidate scan completed", {
|
|
707
|
+
inboxDir,
|
|
708
|
+
scanned: entries.length,
|
|
709
|
+
matched: candidates.length,
|
|
710
|
+
filteredByRecipient,
|
|
711
|
+
filteredBySelf,
|
|
712
|
+
filteredByHeadless,
|
|
713
|
+
agentId,
|
|
714
|
+
agentName,
|
|
715
|
+
aliasName
|
|
716
|
+
});
|
|
612
717
|
return candidates;
|
|
613
718
|
}
|
|
614
719
|
function getPendingCandidates(options, cutoff) {
|
|
615
|
-
const inboxDir =
|
|
616
|
-
if (!
|
|
720
|
+
const inboxDir = join4(options.commsDir, "inbox");
|
|
721
|
+
if (!existsSync4(inboxDir)) {
|
|
617
722
|
throw new Error(`Inbox directory not found: ${inboxDir}`);
|
|
618
723
|
}
|
|
619
724
|
const heartbeats = loadHeartbeats(options.commsDir);
|
|
620
|
-
const
|
|
725
|
+
const refreshedName = refreshAgentIdentity(options, heartbeats);
|
|
621
726
|
const cutoffMs = cutoff.getTime();
|
|
622
727
|
const candidates = collectCandidates(
|
|
623
728
|
inboxDir,
|
|
624
729
|
options.agentId,
|
|
625
|
-
agentName
|
|
730
|
+
options.agentName,
|
|
731
|
+
// M205: Also accept messages addressed to the heartbeat-refreshed name
|
|
732
|
+
refreshedName !== options.agentName ? refreshedName : void 0
|
|
626
733
|
).filter((candidate) => {
|
|
627
734
|
if (candidate.mtimeMs < cutoffMs) {
|
|
628
735
|
return false;
|
|
629
736
|
}
|
|
630
|
-
return !
|
|
737
|
+
return !existsSync4(
|
|
631
738
|
getProcessedMarkerPath(options.stateDir, candidate.markerId)
|
|
632
739
|
);
|
|
633
740
|
});
|
|
741
|
+
routingLogger.debug("pending candidates resolved", {
|
|
742
|
+
agentId: options.agentId,
|
|
743
|
+
configuredName: options.agentName,
|
|
744
|
+
refreshedName: refreshedName !== options.agentName ? refreshedName : void 0,
|
|
745
|
+
candidateCount: candidates.length,
|
|
746
|
+
cutoff: cutoff.toISOString()
|
|
747
|
+
});
|
|
634
748
|
return { heartbeats, candidates };
|
|
635
749
|
}
|
|
750
|
+
|
|
751
|
+
// ../../scripts/bridge/bridge-format.ts
|
|
752
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
753
|
+
import { join as join5 } from "path";
|
|
636
754
|
function buildUserInput(candidate, agentName, heartbeats) {
|
|
637
755
|
const sender = resolveAddressLabel(candidate.sender || "unknown", heartbeats);
|
|
638
756
|
const recipient = resolveAddressLabel(
|
|
@@ -671,7 +789,7 @@ function writeProcessedMarker(stateDir, candidate, dispatchMode, threadId, turnI
|
|
|
671
789
|
turnId,
|
|
672
790
|
markedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
673
791
|
};
|
|
674
|
-
|
|
792
|
+
writeFileSync3(
|
|
675
793
|
getProcessedMarkerPath(stateDir, candidate.markerId),
|
|
676
794
|
`${JSON.stringify(payload, null, 2)}
|
|
677
795
|
`,
|
|
@@ -691,98 +809,379 @@ function writeLastDispatch(stateDir, candidate, dispatchMode, threadId, turnId)
|
|
|
691
809
|
turnId,
|
|
692
810
|
dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
693
811
|
};
|
|
694
|
-
|
|
695
|
-
|
|
812
|
+
writeFileSync3(
|
|
813
|
+
join5(stateDir, "last-dispatch.json"),
|
|
696
814
|
`${JSON.stringify(payload, null, 2)}
|
|
697
815
|
`,
|
|
698
816
|
"utf8"
|
|
699
817
|
);
|
|
700
818
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
819
|
+
|
|
820
|
+
// ../../scripts/bridge/bridge-dispatch.ts
|
|
821
|
+
import {
|
|
822
|
+
existsSync as existsSync5,
|
|
823
|
+
readFileSync as readFileSync5,
|
|
824
|
+
renameSync as renameSync2,
|
|
825
|
+
statSync as statSync2,
|
|
826
|
+
unlinkSync,
|
|
827
|
+
writeFileSync as writeFileSync4
|
|
828
|
+
} from "fs";
|
|
829
|
+
import { join as join6 } from "path";
|
|
830
|
+
var dispatchLogger = createBridgeLogger("dispatch");
|
|
831
|
+
var heartbeatLogger = createBridgeLogger("heartbeat");
|
|
832
|
+
function sanitizeErrorForPersistence(error) {
|
|
833
|
+
if (!error) return null;
|
|
834
|
+
return error.replace(/([?&])tap_token=[^\s&)"'}]+/gi, "$1tap_token=***").replace(/([?&])token=[^\s&)"'}]+/gi, "$1token=***").replace(/([?&])secret=[^\s&)"'}]+/gi, "$1secret=***").replace(/([?&])key=[^\s&)"'}]+/gi, "$1key=***").replace(/"tap_token"\s*:\s*"[^"]*"/g, '"tap_token":"***"').replace(/"token"\s*:\s*"[^"]*"/g, '"token":"***"').replace(/"secret"\s*:\s*"[^"]*"/g, '"secret":"***"').replace(/"password"\s*:\s*"[^"]*"/g, '"password":"***"').replace(/"authorization"\s*:\s*"[^"]*"/gi, '"authorization":"***"').replace(/tap-auth-[A-Za-z0-9_.\-/+=]+/g, "tap-auth-***").replace(/Bearer\s+[A-Za-z0-9_.\-/+=]+/gi, "Bearer ***").replace(/(?<=[=:"\s])[A-Za-z0-9_\-/+=]{40,}(?=["\s&)}'}\],]|$)/g, "***");
|
|
714
835
|
}
|
|
715
836
|
function delay(ms) {
|
|
716
837
|
return new Promise((resolvePromise) => {
|
|
717
838
|
setTimeout(resolvePromise, ms);
|
|
718
839
|
});
|
|
719
840
|
}
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
725
|
-
return client.lastTurnStatus;
|
|
726
|
-
}
|
|
727
|
-
if (Date.now() >= nextRefreshAt) {
|
|
728
|
-
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
729
|
-
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
730
|
-
return client.lastTurnStatus;
|
|
731
|
-
}
|
|
732
|
-
nextRefreshAt = Date.now() + TURN_COMPLETION_REFRESH_MS;
|
|
733
|
-
}
|
|
734
|
-
await delay(
|
|
735
|
-
Math.min(TURN_COMPLETION_POLL_MS, Math.max(deadline - Date.now(), 0))
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
739
|
-
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
740
|
-
return client.lastTurnStatus;
|
|
741
|
-
}
|
|
742
|
-
throw new Error(`Timed out waiting for turn ${turnId} to complete`);
|
|
743
|
-
}
|
|
744
|
-
async function maybeBootstrapHeadlessTurn(options, cutoff, client) {
|
|
745
|
-
if (process.env.TAP_HEADLESS !== "true" && process.env.TAP_COLD_START_WARMUP !== "true") {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
const { candidates } = getPendingCandidates(options, cutoff);
|
|
749
|
-
if (candidates.length > 0 || client.activeTurnId || client.lastTurnStatus !== null) {
|
|
750
|
-
return false;
|
|
751
|
-
}
|
|
752
|
-
logStatus("headless cold-start: sending warmup turn");
|
|
753
|
-
const turnId = await client.startTurn(HEADLESS_WARMUP_PROMPT);
|
|
754
|
-
if (!turnId) {
|
|
755
|
-
throw new Error(
|
|
756
|
-
"Headless cold-start warmup failed: turn/start did not return a turn id. Run: npx @hua-labs/tap doctor"
|
|
757
|
-
);
|
|
841
|
+
function readThreadState(stateDir) {
|
|
842
|
+
const threadPath = join6(stateDir, "thread.json");
|
|
843
|
+
if (!existsSync5(threadPath)) {
|
|
844
|
+
return null;
|
|
758
845
|
}
|
|
759
846
|
try {
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
turnId,
|
|
763
|
-
HEADLESS_WARMUP_TIMEOUT_MS
|
|
847
|
+
const parsed = JSON.parse(
|
|
848
|
+
readFileSync5(threadPath, "utf8")
|
|
764
849
|
);
|
|
765
|
-
if (
|
|
766
|
-
|
|
767
|
-
`turn ${turnId} finished with status ${status ?? "unknown"}`
|
|
768
|
-
);
|
|
850
|
+
if (parsed.threadId) {
|
|
851
|
+
return parsed;
|
|
769
852
|
}
|
|
770
|
-
|
|
771
|
-
return
|
|
772
|
-
} catch (error) {
|
|
773
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
774
|
-
throw new Error(
|
|
775
|
-
`Headless cold-start warmup failed: ${reason}. Run: npx @hua-labs/tap doctor`
|
|
776
|
-
);
|
|
853
|
+
} catch {
|
|
854
|
+
return null;
|
|
777
855
|
}
|
|
856
|
+
return null;
|
|
778
857
|
}
|
|
779
|
-
function
|
|
780
|
-
|
|
858
|
+
function persistThreadState(stateDir, threadId, appServerUrl, ephemeral, cwd) {
|
|
859
|
+
const payload = {
|
|
860
|
+
threadId,
|
|
861
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
862
|
+
appServerUrl,
|
|
863
|
+
ephemeral,
|
|
864
|
+
cwd
|
|
865
|
+
};
|
|
866
|
+
writeFileSync4(
|
|
867
|
+
join6(stateDir, "thread.json"),
|
|
868
|
+
`${JSON.stringify(payload, null, 2)}
|
|
869
|
+
`,
|
|
870
|
+
"utf8"
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function acquireCommsLock(lockPath) {
|
|
874
|
+
const deadline = Date.now() + COMMS_HEARTBEAT_LOCK_TIMEOUT_MS;
|
|
875
|
+
while (Date.now() < deadline) {
|
|
876
|
+
try {
|
|
877
|
+
writeFileSync4(lockPath, String(process.pid), { flag: "wx" });
|
|
878
|
+
return true;
|
|
879
|
+
} catch {
|
|
880
|
+
try {
|
|
881
|
+
const lockAge = Date.now() - statSync2(lockPath).mtimeMs;
|
|
882
|
+
if (lockAge > COMMS_LOCK_STALE_AGE_MS) {
|
|
883
|
+
unlinkSync(lockPath);
|
|
884
|
+
try {
|
|
885
|
+
writeFileSync4(lockPath, String(process.pid), { flag: "wx" });
|
|
886
|
+
return true;
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
const start = Date.now();
|
|
893
|
+
while (Date.now() - start < 50) {
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
function releaseCommsLock(lockPath) {
|
|
900
|
+
try {
|
|
901
|
+
unlinkSync(lockPath);
|
|
902
|
+
} catch {
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function updateCommsHeartbeat(options, status) {
|
|
906
|
+
const heartbeatsPath = join6(options.commsDir, "heartbeats.json");
|
|
907
|
+
const lockPath = join6(options.commsDir, ".heartbeats.lock");
|
|
908
|
+
if (!acquireCommsLock(lockPath)) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
let store = {};
|
|
913
|
+
try {
|
|
914
|
+
store = JSON.parse(readFileSync5(heartbeatsPath, "utf-8"));
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
917
|
+
const key = options.agentId;
|
|
918
|
+
const existing = store[key];
|
|
919
|
+
store[key] = {
|
|
920
|
+
id: options.agentId,
|
|
921
|
+
agent: options.agentName,
|
|
922
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
923
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
924
|
+
joinedAt: existing?.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
925
|
+
status
|
|
926
|
+
};
|
|
927
|
+
const tmpPath = heartbeatsPath + ".tmp." + process.pid;
|
|
928
|
+
writeFileSync4(tmpPath, JSON.stringify(store, null, 2), "utf-8");
|
|
929
|
+
renameSync2(tmpPath, heartbeatsPath);
|
|
930
|
+
} catch {
|
|
931
|
+
} finally {
|
|
932
|
+
releaseCommsLock(lockPath);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
var heartbeatCount = 0;
|
|
936
|
+
function writeHeartbeat(options, client, health) {
|
|
937
|
+
if (client?.threadId) {
|
|
938
|
+
const savedThread = readThreadState(options.stateDir);
|
|
939
|
+
persistThreadState(
|
|
940
|
+
options.stateDir,
|
|
941
|
+
client.threadId,
|
|
942
|
+
options.appServerUrl,
|
|
943
|
+
options.ephemeral,
|
|
944
|
+
client.currentThreadCwd ?? savedThread?.cwd ?? null
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
const payload = {
|
|
948
|
+
pid: process.pid,
|
|
949
|
+
agent: options.agentName,
|
|
950
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
951
|
+
pollSeconds: options.pollSeconds,
|
|
952
|
+
appServerUrl: options.appServerUrl,
|
|
953
|
+
authenticated: Boolean(options.gatewayToken),
|
|
954
|
+
connected: client?.connected ?? false,
|
|
955
|
+
initialized: client?.initialized ?? false,
|
|
956
|
+
threadId: client?.threadId ?? null,
|
|
957
|
+
threadCwd: client?.currentThreadCwd ?? null,
|
|
958
|
+
activeTurnId: client?.activeTurnId ?? null,
|
|
959
|
+
turnStartedAt: client?.turnStartedAt ?? null,
|
|
960
|
+
lastTurnStatus: client?.lastTurnStatus ?? null,
|
|
961
|
+
lastNotificationMethod: client?.lastNotificationMethod ?? null,
|
|
962
|
+
lastNotificationAt: client?.lastNotificationAt ?? null,
|
|
963
|
+
lastError: sanitizeErrorForPersistence(client?.lastError ?? null),
|
|
964
|
+
lastSuccessfulAppServerAt: client?.lastSuccessfulAppServerAt ?? null,
|
|
965
|
+
lastSuccessfulAppServerMethod: client?.lastSuccessfulAppServerMethod ?? null,
|
|
966
|
+
consecutiveFailureCount: health.consecutiveFailureCount,
|
|
967
|
+
busyMode: options.busyMode
|
|
968
|
+
};
|
|
969
|
+
writeFileSync4(
|
|
970
|
+
join6(options.stateDir, "heartbeat.json"),
|
|
971
|
+
`${JSON.stringify(payload, null, 2)}
|
|
972
|
+
`,
|
|
973
|
+
"utf8"
|
|
974
|
+
);
|
|
975
|
+
heartbeatCount += 1;
|
|
976
|
+
if (heartbeatCount % 5 === 0) {
|
|
977
|
+
heartbeatLogger.debug("heartbeat written", {
|
|
978
|
+
connected: payload.connected,
|
|
979
|
+
threadId: payload.threadId ?? "null",
|
|
980
|
+
activeTurnId: payload.activeTurnId ?? null,
|
|
981
|
+
consecutiveFailureCount: payload.consecutiveFailureCount
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
const status = client?.connected ? "active" : "idle";
|
|
985
|
+
updateCommsHeartbeat(options, status);
|
|
986
|
+
}
|
|
987
|
+
async function dispatchCandidate(client, options, candidate, heartbeats) {
|
|
988
|
+
const input = buildUserInput(candidate, options.agentName, heartbeats);
|
|
989
|
+
dispatchLogger.info("dispatching candidate", {
|
|
990
|
+
sender: candidate.sender || "unknown",
|
|
991
|
+
recipient: candidate.recipient || options.agentName,
|
|
992
|
+
subject: candidate.subject || "(none)",
|
|
993
|
+
fileName: candidate.fileName,
|
|
994
|
+
threadId: client.threadId,
|
|
995
|
+
activeTurnId: client.activeTurnId,
|
|
996
|
+
busyMode: options.busyMode
|
|
997
|
+
});
|
|
998
|
+
if (client.isBusy()) {
|
|
999
|
+
if (options.busyMode !== "steer") {
|
|
1000
|
+
dispatchLogger.debug("bridge busy and steer disabled", {
|
|
1001
|
+
fileName: candidate.fileName,
|
|
1002
|
+
activeTurnId: client.activeTurnId
|
|
1003
|
+
});
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
try {
|
|
1007
|
+
const turnId2 = await client.steerTurn(input);
|
|
1008
|
+
writeProcessedMarker(
|
|
1009
|
+
options.stateDir,
|
|
1010
|
+
candidate,
|
|
1011
|
+
"steer",
|
|
1012
|
+
client.threadId,
|
|
1013
|
+
turnId2
|
|
1014
|
+
);
|
|
1015
|
+
writeLastDispatch(
|
|
1016
|
+
options.stateDir,
|
|
1017
|
+
candidate,
|
|
1018
|
+
"steer",
|
|
1019
|
+
client.threadId,
|
|
1020
|
+
turnId2
|
|
1021
|
+
);
|
|
1022
|
+
dispatchLogger.info("steered active turn", {
|
|
1023
|
+
fileName: candidate.fileName,
|
|
1024
|
+
threadId: client.threadId,
|
|
1025
|
+
turnId: turnId2
|
|
1026
|
+
});
|
|
1027
|
+
return true;
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
1030
|
+
if (!client.isBusy()) {
|
|
1031
|
+
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1032
|
+
}
|
|
1033
|
+
if (shouldRetrySteerAsStart(error)) {
|
|
1034
|
+
client.activeTurnId = null;
|
|
1035
|
+
client.turnStartedAt = null;
|
|
1036
|
+
dispatchLogger.warn("steer fallback to start", {
|
|
1037
|
+
fileName: candidate.fileName,
|
|
1038
|
+
threadId: client.threadId,
|
|
1039
|
+
error: sanitizeErrorForPersistence(String(error))
|
|
1040
|
+
});
|
|
1041
|
+
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1042
|
+
}
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const turnId = await client.startTurn(input);
|
|
1047
|
+
writeProcessedMarker(
|
|
1048
|
+
options.stateDir,
|
|
1049
|
+
candidate,
|
|
1050
|
+
"start",
|
|
1051
|
+
client.threadId,
|
|
1052
|
+
turnId
|
|
1053
|
+
);
|
|
1054
|
+
writeLastDispatch(
|
|
1055
|
+
options.stateDir,
|
|
1056
|
+
candidate,
|
|
1057
|
+
"start",
|
|
1058
|
+
client.threadId,
|
|
1059
|
+
turnId
|
|
1060
|
+
);
|
|
1061
|
+
dispatchLogger.info("started turn for candidate", {
|
|
1062
|
+
fileName: candidate.fileName,
|
|
1063
|
+
threadId: client.threadId,
|
|
1064
|
+
turnId
|
|
1065
|
+
});
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
async function runScan(options, cutoff, client) {
|
|
1069
|
+
const { heartbeats, candidates } = getPendingCandidates(options, cutoff);
|
|
1070
|
+
if (candidates.length === 0) {
|
|
1071
|
+
dispatchLogger.debug("no pending candidates", {
|
|
1072
|
+
cutoff: cutoff.toISOString(),
|
|
1073
|
+
agentName: options.agentName
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
let maxMtimeMs = 0;
|
|
1077
|
+
for (const candidate of candidates) {
|
|
1078
|
+
if (options.dryRun) {
|
|
1079
|
+
dispatchLogger.info("dry-run candidate", {
|
|
1080
|
+
fileName: candidate.fileName,
|
|
1081
|
+
sender: candidate.sender,
|
|
1082
|
+
recipient: candidate.recipient
|
|
1083
|
+
});
|
|
1084
|
+
maxMtimeMs = Math.max(maxMtimeMs, candidate.mtimeMs);
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
if (!client) {
|
|
1088
|
+
throw new Error("App Server client is not available");
|
|
1089
|
+
}
|
|
1090
|
+
const dispatched = await dispatchCandidate(
|
|
1091
|
+
client,
|
|
1092
|
+
options,
|
|
1093
|
+
candidate,
|
|
1094
|
+
heartbeats
|
|
1095
|
+
);
|
|
1096
|
+
if (!dispatched && options.busyMode === "wait") {
|
|
1097
|
+
return { dispatched: false, maxMtimeMs };
|
|
1098
|
+
}
|
|
1099
|
+
maxMtimeMs = Math.max(maxMtimeMs, candidate.mtimeMs);
|
|
1100
|
+
return { dispatched: true, maxMtimeMs };
|
|
1101
|
+
}
|
|
1102
|
+
return { dispatched: false, maxMtimeMs: 0 };
|
|
1103
|
+
}
|
|
1104
|
+
async function waitForTurnDrain(options, client, health) {
|
|
1105
|
+
const deadline = Date.now() + options.waitAfterDispatchSeconds * 1e3;
|
|
1106
|
+
while (Date.now() < deadline) {
|
|
1107
|
+
writeHeartbeat(options, client, health);
|
|
1108
|
+
if (!client.activeTurnId) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
await delay(1e3);
|
|
1112
|
+
}
|
|
1113
|
+
dispatchLogger.warn("wait-after-dispatch deadline reached", {
|
|
1114
|
+
threadId: client.threadId,
|
|
1115
|
+
activeTurnId: client.activeTurnId,
|
|
1116
|
+
waitAfterDispatchSeconds: options.waitAfterDispatchSeconds
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
async function waitForTurnCompletion(client, turnId, timeoutMs) {
|
|
1120
|
+
const deadline = Date.now() + timeoutMs;
|
|
1121
|
+
let nextRefreshAt = Date.now();
|
|
1122
|
+
while (Date.now() < deadline) {
|
|
1123
|
+
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
1124
|
+
return client.lastTurnStatus;
|
|
1125
|
+
}
|
|
1126
|
+
if (Date.now() >= nextRefreshAt) {
|
|
1127
|
+
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
1128
|
+
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
1129
|
+
return client.lastTurnStatus;
|
|
1130
|
+
}
|
|
1131
|
+
nextRefreshAt = Date.now() + TURN_COMPLETION_REFRESH_MS;
|
|
1132
|
+
}
|
|
1133
|
+
await delay(
|
|
1134
|
+
Math.min(TURN_COMPLETION_POLL_MS, Math.max(deadline - Date.now(), 0))
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
1138
|
+
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
1139
|
+
return client.lastTurnStatus;
|
|
1140
|
+
}
|
|
1141
|
+
throw new Error(`Timed out waiting for turn ${turnId} to complete`);
|
|
1142
|
+
}
|
|
1143
|
+
async function maybeBootstrapHeadlessTurn(options, cutoff, client) {
|
|
1144
|
+
if (process.env.TAP_HEADLESS !== "true" && process.env.TAP_COLD_START_WARMUP !== "true") {
|
|
781
1145
|
return false;
|
|
782
1146
|
}
|
|
783
|
-
const
|
|
784
|
-
|
|
1147
|
+
const { candidates } = getPendingCandidates(options, cutoff);
|
|
1148
|
+
if (candidates.length > 0 || client.activeTurnId || client.lastTurnStatus !== null) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
dispatchLogger.info("headless cold-start warmup starting", {
|
|
1152
|
+
threadId: client.activeTurnId
|
|
1153
|
+
});
|
|
1154
|
+
const turnId = await client.startTurn(HEADLESS_WARMUP_PROMPT);
|
|
1155
|
+
if (!turnId) {
|
|
1156
|
+
throw new Error(
|
|
1157
|
+
"Headless cold-start warmup failed: turn/start did not return a turn id. Run: npx @hua-labs/tap doctor"
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
const status = await waitForTurnCompletion(
|
|
1162
|
+
client,
|
|
1163
|
+
turnId,
|
|
1164
|
+
HEADLESS_WARMUP_TIMEOUT_MS
|
|
1165
|
+
);
|
|
1166
|
+
if (status !== "completed") {
|
|
1167
|
+
throw new Error(
|
|
1168
|
+
`turn ${turnId} finished with status ${status ?? "unknown"}`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
dispatchLogger.info("headless cold-start warmup completed", {
|
|
1172
|
+
turnId,
|
|
1173
|
+
status
|
|
1174
|
+
});
|
|
1175
|
+
return true;
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1178
|
+
throw new Error(
|
|
1179
|
+
`Headless cold-start warmup failed: ${reason}. Run: npx @hua-labs/tap doctor`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
785
1182
|
}
|
|
1183
|
+
|
|
1184
|
+
// ../../scripts/bridge/bridge-ws-client.ts
|
|
786
1185
|
async function readSocketData(data) {
|
|
787
1186
|
if (typeof data === "string") {
|
|
788
1187
|
return data;
|
|
@@ -800,11 +1199,27 @@ async function readSocketData(data) {
|
|
|
800
1199
|
}
|
|
801
1200
|
return String(data);
|
|
802
1201
|
}
|
|
1202
|
+
function formatJsonRpcError(error) {
|
|
1203
|
+
if (!error) {
|
|
1204
|
+
return "Unknown App Server error";
|
|
1205
|
+
}
|
|
1206
|
+
return JSON.stringify(
|
|
1207
|
+
{
|
|
1208
|
+
code: error.code,
|
|
1209
|
+
message: error.message,
|
|
1210
|
+
data: error.data
|
|
1211
|
+
},
|
|
1212
|
+
null,
|
|
1213
|
+
2
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
var nextAppServerClientId = 1;
|
|
803
1217
|
var AppServerClient = class {
|
|
804
1218
|
socket = null;
|
|
805
1219
|
url;
|
|
806
1220
|
gatewayToken;
|
|
807
1221
|
logger;
|
|
1222
|
+
clientId = nextAppServerClientId++;
|
|
808
1223
|
nextId = 1;
|
|
809
1224
|
pending = /* @__PURE__ */ new Map();
|
|
810
1225
|
connected = false;
|
|
@@ -828,6 +1243,12 @@ var AppServerClient = class {
|
|
|
828
1243
|
if (this.connected && this.socket?.readyState === WebSocket.OPEN) {
|
|
829
1244
|
return;
|
|
830
1245
|
}
|
|
1246
|
+
if (!this.gatewayToken) {
|
|
1247
|
+
this.logger.warn(
|
|
1248
|
+
"connecting without auth token \u2014 app-server session is unprotected. Use --gateway-token-file or TAP_GATEWAY_TOKEN_FILE to enable auth.",
|
|
1249
|
+
{ url: this.url }
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
831
1252
|
const wsOptions = {};
|
|
832
1253
|
if (this.gatewayToken) {
|
|
833
1254
|
wsOptions.protocols = [`${AUTH_SUBPROTOCOL_PREFIX}${this.gatewayToken}`];
|
|
@@ -853,7 +1274,11 @@ var AppServerClient = class {
|
|
|
853
1274
|
"open",
|
|
854
1275
|
() => {
|
|
855
1276
|
this.connected = true;
|
|
856
|
-
this.logger(
|
|
1277
|
+
this.logger.info("connected to app-server", {
|
|
1278
|
+
clientId: this.clientId,
|
|
1279
|
+
url: this.url,
|
|
1280
|
+
authenticated: Boolean(this.gatewayToken)
|
|
1281
|
+
});
|
|
857
1282
|
resolveOnce();
|
|
858
1283
|
},
|
|
859
1284
|
{ once: true }
|
|
@@ -862,7 +1287,12 @@ var AppServerClient = class {
|
|
|
862
1287
|
const error = new Error(
|
|
863
1288
|
`Failed to connect to App Server at ${this.url}`
|
|
864
1289
|
);
|
|
865
|
-
this.lastError = error.message;
|
|
1290
|
+
this.lastError = sanitizeErrorForPersistence(error.message);
|
|
1291
|
+
this.logger.error("failed to connect to app-server", {
|
|
1292
|
+
clientId: this.clientId,
|
|
1293
|
+
url: this.url,
|
|
1294
|
+
error: this.lastError
|
|
1295
|
+
});
|
|
866
1296
|
rejectOnce(error);
|
|
867
1297
|
});
|
|
868
1298
|
this.socket?.addEventListener("close", () => {
|
|
@@ -870,7 +1300,10 @@ var AppServerClient = class {
|
|
|
870
1300
|
this.initialized = false;
|
|
871
1301
|
this.activeTurnId = null;
|
|
872
1302
|
this.turnStartedAt = null;
|
|
873
|
-
this.logger("disconnected from app-server"
|
|
1303
|
+
this.logger.warn("disconnected from app-server", {
|
|
1304
|
+
clientId: this.clientId,
|
|
1305
|
+
url: this.url
|
|
1306
|
+
});
|
|
874
1307
|
this.rejectPending(new Error("App Server connection closed"));
|
|
875
1308
|
});
|
|
876
1309
|
this.socket?.addEventListener("message", (event) => {
|
|
@@ -907,13 +1340,20 @@ var AppServerClient = class {
|
|
|
907
1340
|
});
|
|
908
1341
|
const resumedThreadId = resumeResponse?.thread?.id ?? explicitThreadId;
|
|
909
1342
|
await this.refreshThreadState(resumedThreadId);
|
|
910
|
-
this.logger(
|
|
911
|
-
|
|
912
|
-
|
|
1343
|
+
this.logger.info("resumed explicit thread", {
|
|
1344
|
+
clientId: this.clientId,
|
|
1345
|
+
threadId: resumedThreadId,
|
|
1346
|
+
activeTurnId: this.activeTurnId
|
|
1347
|
+
});
|
|
913
1348
|
return resumedThreadId;
|
|
914
1349
|
} catch (error) {
|
|
915
|
-
this.logger(
|
|
916
|
-
|
|
1350
|
+
this.logger.warn(
|
|
1351
|
+
"explicit thread resume failed; starting fresh thread",
|
|
1352
|
+
{
|
|
1353
|
+
clientId: this.clientId,
|
|
1354
|
+
threadId: explicitThreadId,
|
|
1355
|
+
error: sanitizeErrorForPersistence(String(error))
|
|
1356
|
+
}
|
|
917
1357
|
);
|
|
918
1358
|
}
|
|
919
1359
|
}
|
|
@@ -923,9 +1363,12 @@ var AppServerClient = class {
|
|
|
923
1363
|
}
|
|
924
1364
|
if (savedThread?.threadId) {
|
|
925
1365
|
if (savedThread.cwd && !threadCwdMatches(cwd, savedThread.cwd)) {
|
|
926
|
-
this.logger(
|
|
927
|
-
|
|
928
|
-
|
|
1366
|
+
this.logger.warn("saved thread cwd mismatch; skipping saved thread", {
|
|
1367
|
+
clientId: this.clientId,
|
|
1368
|
+
threadId: savedThread.threadId,
|
|
1369
|
+
savedCwd: savedThread.cwd,
|
|
1370
|
+
expectedCwd: cwd
|
|
1371
|
+
});
|
|
929
1372
|
} else {
|
|
930
1373
|
try {
|
|
931
1374
|
const resumeResponse = await this.request("thread/resume", {
|
|
@@ -935,23 +1378,33 @@ var AppServerClient = class {
|
|
|
935
1378
|
const resumedThreadId = resumeResponse?.thread?.id ?? savedThread.threadId;
|
|
936
1379
|
await this.refreshThreadState(resumedThreadId);
|
|
937
1380
|
if (!threadCwdMatches(cwd, this.currentThreadCwd)) {
|
|
938
|
-
this.logger(
|
|
939
|
-
|
|
940
|
-
|
|
1381
|
+
this.logger.warn("saved thread resumed with mismatched cwd", {
|
|
1382
|
+
clientId: this.clientId,
|
|
1383
|
+
threadId: resumedThreadId,
|
|
1384
|
+
expectedCwd: cwd,
|
|
1385
|
+
actualCwd: this.currentThreadCwd ?? "unknown"
|
|
1386
|
+
});
|
|
941
1387
|
this.threadId = null;
|
|
942
1388
|
this.currentThreadCwd = null;
|
|
943
1389
|
this.activeTurnId = null;
|
|
944
1390
|
this.turnStartedAt = null;
|
|
945
1391
|
this.lastTurnStatus = null;
|
|
946
1392
|
} else {
|
|
947
|
-
this.logger(
|
|
948
|
-
|
|
949
|
-
|
|
1393
|
+
this.logger.info("resumed saved thread", {
|
|
1394
|
+
clientId: this.clientId,
|
|
1395
|
+
threadId: resumedThreadId,
|
|
1396
|
+
activeTurnId: this.activeTurnId
|
|
1397
|
+
});
|
|
950
1398
|
return resumedThreadId;
|
|
951
1399
|
}
|
|
952
1400
|
} catch (error) {
|
|
953
|
-
this.logger(
|
|
954
|
-
|
|
1401
|
+
this.logger.warn(
|
|
1402
|
+
"saved thread resume failed; starting fresh thread",
|
|
1403
|
+
{
|
|
1404
|
+
clientId: this.clientId,
|
|
1405
|
+
threadId: savedThread.threadId,
|
|
1406
|
+
error: sanitizeErrorForPersistence(String(error))
|
|
1407
|
+
}
|
|
955
1408
|
);
|
|
956
1409
|
}
|
|
957
1410
|
}
|
|
@@ -971,7 +1424,12 @@ var AppServerClient = class {
|
|
|
971
1424
|
this.currentThreadCwd = this.currentThreadCwd ?? cwd;
|
|
972
1425
|
this.activeTurnId = null;
|
|
973
1426
|
this.lastTurnStatus = null;
|
|
974
|
-
this.logger(
|
|
1427
|
+
this.logger.info("started thread", {
|
|
1428
|
+
clientId: this.clientId,
|
|
1429
|
+
threadId: startedThreadId,
|
|
1430
|
+
cwd: this.currentThreadCwd,
|
|
1431
|
+
ephemeral
|
|
1432
|
+
});
|
|
975
1433
|
return startedThreadId;
|
|
976
1434
|
}
|
|
977
1435
|
async findLoadedThread(cwd) {
|
|
@@ -1009,14 +1467,21 @@ var AppServerClient = class {
|
|
|
1009
1467
|
const chosen = chooseLoadedThreadForCwd(cwd, threads);
|
|
1010
1468
|
if (!chosen) {
|
|
1011
1469
|
if (threads.length > 0) {
|
|
1012
|
-
this.logger(
|
|
1470
|
+
this.logger.debug("loaded threads exist but none match cwd", {
|
|
1471
|
+
clientId: this.clientId,
|
|
1472
|
+
cwd,
|
|
1473
|
+
loadedThreadCount: threads.length
|
|
1474
|
+
});
|
|
1013
1475
|
}
|
|
1014
1476
|
return null;
|
|
1015
1477
|
}
|
|
1016
1478
|
this.syncThreadStateFromThread(chosen.thread);
|
|
1017
|
-
this.logger(
|
|
1018
|
-
|
|
1019
|
-
|
|
1479
|
+
this.logger.info("attached to loaded thread", {
|
|
1480
|
+
clientId: this.clientId,
|
|
1481
|
+
threadId: chosen.id,
|
|
1482
|
+
activeTurnId: this.activeTurnId,
|
|
1483
|
+
cwd: chosen.cwd
|
|
1484
|
+
});
|
|
1020
1485
|
return chosen.id;
|
|
1021
1486
|
}
|
|
1022
1487
|
async startTurn(inputText) {
|
|
@@ -1055,7 +1520,18 @@ var AppServerClient = class {
|
|
|
1055
1520
|
return turnId;
|
|
1056
1521
|
}
|
|
1057
1522
|
isBusy() {
|
|
1058
|
-
|
|
1523
|
+
if (!this.activeTurnId) return false;
|
|
1524
|
+
if (isTurnStale(this.turnStartedAt)) {
|
|
1525
|
+
this.logger.warn("active turn is stale; treating bridge as idle", {
|
|
1526
|
+
clientId: this.clientId,
|
|
1527
|
+
turnId: this.activeTurnId,
|
|
1528
|
+
turnStartedAt: this.turnStartedAt
|
|
1529
|
+
});
|
|
1530
|
+
this.activeTurnId = null;
|
|
1531
|
+
this.turnStartedAt = null;
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
return true;
|
|
1059
1535
|
}
|
|
1060
1536
|
async refreshCurrentThreadState() {
|
|
1061
1537
|
if (!this.threadId) {
|
|
@@ -1089,12 +1565,33 @@ var AppServerClient = class {
|
|
|
1089
1565
|
this.currentThreadCwd = typeof thread?.cwd === "string" ? thread.cwd : null;
|
|
1090
1566
|
let activeTurnId = null;
|
|
1091
1567
|
let lastTurnStatus = null;
|
|
1568
|
+
const threadActiveFlags = Array.isArray(
|
|
1569
|
+
thread?.status?.activeFlags
|
|
1570
|
+
) ? thread.status.activeFlags : [];
|
|
1571
|
+
const threadStuckOnApproval = isTurnStuckOnApproval(threadActiveFlags);
|
|
1572
|
+
if (threadStuckOnApproval) {
|
|
1573
|
+
this.logger.warn("thread waitingOnApproval; ignoring in-progress turns", {
|
|
1574
|
+
clientId: this.clientId,
|
|
1575
|
+
threadId: this.threadId
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1092
1578
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
1093
1579
|
for (const turn of turns) {
|
|
1094
1580
|
if (typeof turn?.status === "string") {
|
|
1095
1581
|
lastTurnStatus = turn.status;
|
|
1096
1582
|
}
|
|
1097
1583
|
if (turn?.status === "inProgress" && typeof turn.id === "string") {
|
|
1584
|
+
if (threadStuckOnApproval) {
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
const turnActiveFlags = Array.isArray(turn.activeFlags) ? turn.activeFlags : [];
|
|
1588
|
+
if (isTurnStuckOnApproval(turnActiveFlags)) {
|
|
1589
|
+
this.logger.warn("turn waitingOnApproval; ignoring turn as active", {
|
|
1590
|
+
clientId: this.clientId,
|
|
1591
|
+
turnId: turn.id
|
|
1592
|
+
});
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1098
1595
|
activeTurnId = turn.id;
|
|
1099
1596
|
}
|
|
1100
1597
|
}
|
|
@@ -1117,7 +1614,12 @@ var AppServerClient = class {
|
|
|
1117
1614
|
this.pending.delete(message.id);
|
|
1118
1615
|
if (message.error) {
|
|
1119
1616
|
const errorText = formatJsonRpcError(message.error);
|
|
1120
|
-
this.lastError = errorText;
|
|
1617
|
+
this.lastError = sanitizeErrorForPersistence(errorText);
|
|
1618
|
+
this.logger.error("app-server request failed", {
|
|
1619
|
+
clientId: this.clientId,
|
|
1620
|
+
method: pending.method,
|
|
1621
|
+
error: this.lastError
|
|
1622
|
+
});
|
|
1121
1623
|
pending.reject(new Error(`${pending.method} failed: ${errorText}`));
|
|
1122
1624
|
return;
|
|
1123
1625
|
}
|
|
@@ -1132,6 +1634,10 @@ var AppServerClient = class {
|
|
|
1132
1634
|
}
|
|
1133
1635
|
this.lastNotificationMethod = message.method;
|
|
1134
1636
|
this.lastNotificationAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1637
|
+
this.logger.debug("received app-server notification", {
|
|
1638
|
+
clientId: this.clientId,
|
|
1639
|
+
method: message.method
|
|
1640
|
+
});
|
|
1135
1641
|
this.handleNotification(message.method, message.params);
|
|
1136
1642
|
}
|
|
1137
1643
|
handleNotification(method, params) {
|
|
@@ -1143,18 +1649,28 @@ var AppServerClient = class {
|
|
|
1143
1649
|
if (typeof params?.thread?.cwd === "string") {
|
|
1144
1650
|
this.currentThreadCwd = params.thread.cwd;
|
|
1145
1651
|
}
|
|
1146
|
-
this.logger(
|
|
1652
|
+
this.logger.info("thread started notification", {
|
|
1653
|
+
clientId: this.clientId,
|
|
1654
|
+
threadId: params?.thread?.id ?? null,
|
|
1655
|
+
cwd: params?.thread?.cwd ?? null
|
|
1656
|
+
});
|
|
1147
1657
|
break;
|
|
1148
1658
|
case "thread/status/changed":
|
|
1149
|
-
this.logger(
|
|
1150
|
-
|
|
1151
|
-
|
|
1659
|
+
this.logger.debug("thread status changed", {
|
|
1660
|
+
clientId: this.clientId,
|
|
1661
|
+
threadId: params?.thread?.id ?? this.threadId,
|
|
1662
|
+
status: params?.thread?.status?.type ?? params?.status?.type ?? "unknown"
|
|
1663
|
+
});
|
|
1152
1664
|
break;
|
|
1153
1665
|
case "turn/started":
|
|
1154
1666
|
if (params?.turn?.id) {
|
|
1155
1667
|
this.activeTurnId = params.turn.id;
|
|
1156
1668
|
this.turnStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1157
|
-
this.logger(
|
|
1669
|
+
this.logger.info("turn started", {
|
|
1670
|
+
clientId: this.clientId,
|
|
1671
|
+
threadId: this.threadId,
|
|
1672
|
+
turnId: params.turn.id
|
|
1673
|
+
});
|
|
1158
1674
|
}
|
|
1159
1675
|
break;
|
|
1160
1676
|
case "turn/completed": {
|
|
@@ -1163,15 +1679,22 @@ var AppServerClient = class {
|
|
|
1163
1679
|
this.activeTurnId = null;
|
|
1164
1680
|
this.turnStartedAt = null;
|
|
1165
1681
|
const elapsedMs = prevTurnStartedAt ? Date.now() - new Date(prevTurnStartedAt).getTime() : null;
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1682
|
+
this.logger.info("turn completed", {
|
|
1683
|
+
clientId: this.clientId,
|
|
1684
|
+
threadId: this.threadId,
|
|
1685
|
+
status: this.lastTurnStatus ?? "unknown",
|
|
1686
|
+
elapsedSeconds: elapsedMs !== null ? Math.round(elapsedMs / 1e3) : void 0
|
|
1687
|
+
});
|
|
1170
1688
|
break;
|
|
1171
1689
|
}
|
|
1172
1690
|
case "error":
|
|
1173
|
-
this.lastError =
|
|
1174
|
-
|
|
1691
|
+
this.lastError = sanitizeErrorForPersistence(
|
|
1692
|
+
JSON.stringify(params ?? {}, null, 2)
|
|
1693
|
+
);
|
|
1694
|
+
this.logger.error("app-server error notification", {
|
|
1695
|
+
clientId: this.clientId,
|
|
1696
|
+
error: this.lastError
|
|
1697
|
+
});
|
|
1175
1698
|
break;
|
|
1176
1699
|
default:
|
|
1177
1700
|
break;
|
|
@@ -1179,277 +1702,135 @@ var AppServerClient = class {
|
|
|
1179
1702
|
}
|
|
1180
1703
|
request(method, params) {
|
|
1181
1704
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
1182
|
-
throw new Error(`Cannot call ${method}; App Server socket is not open`);
|
|
1183
|
-
}
|
|
1184
|
-
const id = this.nextId;
|
|
1185
|
-
this.nextId += 1;
|
|
1186
|
-
const request = {
|
|
1187
|
-
jsonrpc: "2.0",
|
|
1188
|
-
id,
|
|
1189
|
-
method,
|
|
1190
|
-
params
|
|
1191
|
-
};
|
|
1192
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
1193
|
-
this.pending.set(id, {
|
|
1194
|
-
resolve: resolvePromise,
|
|
1195
|
-
reject: rejectPromise,
|
|
1196
|
-
method
|
|
1197
|
-
});
|
|
1198
|
-
this.socket?.send(JSON.stringify(request));
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
rejectPending(error) {
|
|
1202
|
-
for (const pending of this.pending.values()) {
|
|
1203
|
-
pending.reject(error);
|
|
1204
|
-
}
|
|
1205
|
-
this.pending.clear();
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
var heartbeatCount = 0;
|
|
1209
|
-
function writeHeartbeat(options, client, health) {
|
|
1210
|
-
if (client?.threadId) {
|
|
1211
|
-
const savedThread = readThreadState(options.stateDir);
|
|
1212
|
-
persistThreadState(
|
|
1213
|
-
options.stateDir,
|
|
1214
|
-
client.threadId,
|
|
1215
|
-
options.appServerUrl,
|
|
1216
|
-
options.ephemeral,
|
|
1217
|
-
client.currentThreadCwd ?? savedThread?.cwd ?? null
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
const payload = {
|
|
1221
|
-
pid: process.pid,
|
|
1222
|
-
agent: options.agentName,
|
|
1223
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1224
|
-
pollSeconds: options.pollSeconds,
|
|
1225
|
-
appServerUrl: options.appServerUrl,
|
|
1226
|
-
connected: client?.connected ?? false,
|
|
1227
|
-
initialized: client?.initialized ?? false,
|
|
1228
|
-
threadId: client?.threadId ?? null,
|
|
1229
|
-
threadCwd: client?.currentThreadCwd ?? null,
|
|
1230
|
-
activeTurnId: client?.activeTurnId ?? null,
|
|
1231
|
-
turnStartedAt: client?.turnStartedAt ?? null,
|
|
1232
|
-
lastTurnStatus: client?.lastTurnStatus ?? null,
|
|
1233
|
-
lastNotificationMethod: client?.lastNotificationMethod ?? null,
|
|
1234
|
-
lastNotificationAt: client?.lastNotificationAt ?? null,
|
|
1235
|
-
lastError: sanitizeErrorForPersistence(client?.lastError ?? null),
|
|
1236
|
-
lastSuccessfulAppServerAt: client?.lastSuccessfulAppServerAt ?? null,
|
|
1237
|
-
lastSuccessfulAppServerMethod: client?.lastSuccessfulAppServerMethod ?? null,
|
|
1238
|
-
consecutiveFailureCount: health.consecutiveFailureCount,
|
|
1239
|
-
busyMode: options.busyMode
|
|
1240
|
-
};
|
|
1241
|
-
writeFileSync(
|
|
1242
|
-
join(options.stateDir, "heartbeat.json"),
|
|
1243
|
-
`${JSON.stringify(payload, null, 2)}
|
|
1244
|
-
`,
|
|
1245
|
-
"utf8"
|
|
1246
|
-
);
|
|
1247
|
-
heartbeatCount += 1;
|
|
1248
|
-
if (heartbeatCount % 5 === 0) {
|
|
1249
|
-
logStatus(
|
|
1250
|
-
`heartbeat: connected=${payload.connected}, thread=${payload.threadId ?? "null"}, turns=${payload.activeTurnId ? "active" : "0"}`
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
const status = client?.connected ? "active" : "idle";
|
|
1254
|
-
updateCommsHeartbeat(options, status);
|
|
1255
|
-
}
|
|
1256
|
-
var COMMS_HEARTBEAT_LOCK_TIMEOUT_MS = 2e3;
|
|
1257
|
-
var COMMS_LOCK_STALE_AGE_MS = 1e4;
|
|
1258
|
-
function acquireCommsLock(lockPath) {
|
|
1259
|
-
const deadline = Date.now() + COMMS_HEARTBEAT_LOCK_TIMEOUT_MS;
|
|
1260
|
-
while (Date.now() < deadline) {
|
|
1261
|
-
try {
|
|
1262
|
-
writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
1263
|
-
return true;
|
|
1264
|
-
} catch {
|
|
1265
|
-
try {
|
|
1266
|
-
const lockAge = Date.now() - statSync(lockPath).mtimeMs;
|
|
1267
|
-
if (lockAge > COMMS_LOCK_STALE_AGE_MS) {
|
|
1268
|
-
unlinkSync(lockPath);
|
|
1269
|
-
try {
|
|
1270
|
-
writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
1271
|
-
return true;
|
|
1272
|
-
} catch {
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
} catch {
|
|
1276
|
-
}
|
|
1277
|
-
const start = Date.now();
|
|
1278
|
-
while (Date.now() - start < 50) {
|
|
1279
|
-
}
|
|
1705
|
+
throw new Error(`Cannot call ${method}; App Server socket is not open`);
|
|
1706
|
+
}
|
|
1707
|
+
const id = this.nextId;
|
|
1708
|
+
this.nextId += 1;
|
|
1709
|
+
const request = {
|
|
1710
|
+
jsonrpc: "2.0",
|
|
1711
|
+
id,
|
|
1712
|
+
method,
|
|
1713
|
+
params
|
|
1714
|
+
};
|
|
1715
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
1716
|
+
this.pending.set(id, {
|
|
1717
|
+
resolve: resolvePromise,
|
|
1718
|
+
reject: rejectPromise,
|
|
1719
|
+
method
|
|
1720
|
+
});
|
|
1721
|
+
this.socket?.send(JSON.stringify(request));
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
rejectPending(error) {
|
|
1725
|
+
for (const pending of this.pending.values()) {
|
|
1726
|
+
pending.reject(error);
|
|
1280
1727
|
}
|
|
1728
|
+
this.pending.clear();
|
|
1281
1729
|
}
|
|
1282
|
-
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
// ../../scripts/bridge/bridge-main.ts
|
|
1733
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1734
|
+
import { isAbsolute as isAbsolute3, join as join7, resolve as resolve4 } from "path";
|
|
1735
|
+
import { pathToFileURL } from "url";
|
|
1736
|
+
function delay2(ms) {
|
|
1737
|
+
return new Promise((resolvePromise) => {
|
|
1738
|
+
setTimeout(resolvePromise, ms);
|
|
1739
|
+
});
|
|
1283
1740
|
}
|
|
1284
|
-
function
|
|
1741
|
+
function readHeartbeatState(stateDir) {
|
|
1742
|
+
const heartbeatPath = join7(stateDir, "heartbeat.json");
|
|
1743
|
+
if (!existsSync6(heartbeatPath)) {
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1285
1746
|
try {
|
|
1286
|
-
|
|
1747
|
+
return JSON.parse(readFileSync6(heartbeatPath, "utf8"));
|
|
1287
1748
|
} catch {
|
|
1749
|
+
return null;
|
|
1288
1750
|
}
|
|
1289
1751
|
}
|
|
1290
|
-
function
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
if (!acquireCommsLock(lockPath)) {
|
|
1294
|
-
return;
|
|
1752
|
+
function parseUpdatedAt(value) {
|
|
1753
|
+
if (!value) {
|
|
1754
|
+
return 0;
|
|
1295
1755
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1756
|
+
const parsed = Date.parse(value);
|
|
1757
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1758
|
+
}
|
|
1759
|
+
function appServerUrlMatches(expectedAppServerUrl, actualAppServerUrl) {
|
|
1760
|
+
return actualAppServerUrl?.trim() === expectedAppServerUrl;
|
|
1761
|
+
}
|
|
1762
|
+
function hasValidHeartbeatThreadCwd(threadCwd) {
|
|
1763
|
+
const normalized = threadCwd?.trim();
|
|
1764
|
+
if (!normalized) {
|
|
1765
|
+
return false;
|
|
1766
|
+
}
|
|
1767
|
+
return isAbsolute3(normalized) || /^[A-Za-z]:[\\/]/.test(normalized) || normalized.startsWith("\\\\");
|
|
1768
|
+
}
|
|
1769
|
+
function loadResumableThreadState(stateDir, fallbackAppServerUrl) {
|
|
1770
|
+
const savedThread = readThreadState(stateDir);
|
|
1771
|
+
const heartbeat = readHeartbeatState(stateDir);
|
|
1772
|
+
const heartbeatThreadId = heartbeat?.threadId?.trim();
|
|
1773
|
+
if (!heartbeatThreadId) {
|
|
1774
|
+
return savedThread;
|
|
1775
|
+
}
|
|
1776
|
+
if (!appServerUrlMatches(fallbackAppServerUrl, heartbeat?.appServerUrl)) {
|
|
1777
|
+
return savedThread;
|
|
1778
|
+
}
|
|
1779
|
+
if (!hasValidHeartbeatThreadCwd(heartbeat?.threadCwd)) {
|
|
1780
|
+
return savedThread;
|
|
1781
|
+
}
|
|
1782
|
+
const heartbeatBackedThread = {
|
|
1783
|
+
threadId: heartbeatThreadId,
|
|
1784
|
+
updatedAt: heartbeat?.updatedAt ?? savedThread?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1785
|
+
appServerUrl: heartbeat?.appServerUrl || savedThread?.appServerUrl || fallbackAppServerUrl,
|
|
1786
|
+
ephemeral: savedThread?.ephemeral ?? false,
|
|
1787
|
+
cwd: heartbeat?.threadCwd ?? (savedThread?.threadId === heartbeatThreadId ? savedThread.cwd ?? null : null)
|
|
1788
|
+
};
|
|
1789
|
+
let preferred = savedThread;
|
|
1790
|
+
if (!savedThread?.threadId) {
|
|
1791
|
+
preferred = heartbeatBackedThread;
|
|
1792
|
+
} else if (savedThread.threadId === heartbeatThreadId) {
|
|
1793
|
+
preferred = {
|
|
1794
|
+
...savedThread,
|
|
1795
|
+
updatedAt: heartbeatBackedThread.updatedAt ?? savedThread.updatedAt,
|
|
1796
|
+
appServerUrl: heartbeatBackedThread.appServerUrl,
|
|
1797
|
+
cwd: heartbeatBackedThread.cwd ?? savedThread.cwd ?? null
|
|
1311
1798
|
};
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
renameSync(tmpPath, heartbeatsPath);
|
|
1315
|
-
} catch {
|
|
1316
|
-
} finally {
|
|
1317
|
-
releaseCommsLock(lockPath);
|
|
1799
|
+
} else if (parseUpdatedAt(heartbeat?.updatedAt) > parseUpdatedAt(savedThread.updatedAt)) {
|
|
1800
|
+
preferred = heartbeatBackedThread;
|
|
1318
1801
|
}
|
|
1802
|
+
return preferred;
|
|
1319
1803
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
);
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
return false;
|
|
1328
|
-
}
|
|
1804
|
+
function getGeneralInboxCutoff(stateDir, lookbackMinutes, processExistingMessages) {
|
|
1805
|
+
if (processExistingMessages) {
|
|
1806
|
+
return /* @__PURE__ */ new Date(0);
|
|
1807
|
+
}
|
|
1808
|
+
const lookbackCutoff = lookbackMinutes > 0 ? new Date(Date.now() - lookbackMinutes * 6e4) : null;
|
|
1809
|
+
const cutoffPath = join7(stateDir, "general-inbox-cutoff.txt");
|
|
1810
|
+
if (existsSync6(cutoffPath)) {
|
|
1329
1811
|
try {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
turnId2
|
|
1337
|
-
);
|
|
1338
|
-
writeLastDispatch(
|
|
1339
|
-
options.stateDir,
|
|
1340
|
-
candidate,
|
|
1341
|
-
"steer",
|
|
1342
|
-
client.threadId,
|
|
1343
|
-
turnId2
|
|
1344
|
-
);
|
|
1345
|
-
logStatus(`steered active turn with ${candidate.fileName}`);
|
|
1346
|
-
return true;
|
|
1347
|
-
} catch (error) {
|
|
1348
|
-
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
1349
|
-
if (!client.isBusy()) {
|
|
1350
|
-
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1351
|
-
}
|
|
1352
|
-
if (shouldRetrySteerAsStart(error)) {
|
|
1353
|
-
client.activeTurnId = null;
|
|
1354
|
-
client.turnStartedAt = null;
|
|
1355
|
-
logStatus(
|
|
1356
|
-
`steer fallback -> start for ${candidate.fileName} (${String(error)})`
|
|
1357
|
-
);
|
|
1358
|
-
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1812
|
+
const saved = new Date(readFileSync6(cutoffPath, "utf8").trim());
|
|
1813
|
+
if (!isNaN(saved.getTime())) {
|
|
1814
|
+
if (lookbackCutoff && lookbackCutoff > saved) {
|
|
1815
|
+
return lookbackCutoff;
|
|
1816
|
+
}
|
|
1817
|
+
return saved;
|
|
1359
1818
|
}
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
const turnId = await client.startTurn(input);
|
|
1364
|
-
writeProcessedMarker(
|
|
1365
|
-
options.stateDir,
|
|
1366
|
-
candidate,
|
|
1367
|
-
"start",
|
|
1368
|
-
client.threadId,
|
|
1369
|
-
turnId
|
|
1370
|
-
);
|
|
1371
|
-
writeLastDispatch(
|
|
1372
|
-
options.stateDir,
|
|
1373
|
-
candidate,
|
|
1374
|
-
"start",
|
|
1375
|
-
client.threadId,
|
|
1376
|
-
turnId
|
|
1377
|
-
);
|
|
1378
|
-
logStatus(`dispatched ${candidate.fileName} to thread ${client.threadId}`);
|
|
1379
|
-
return true;
|
|
1380
|
-
}
|
|
1381
|
-
async function runScan(options, cutoff, client) {
|
|
1382
|
-
const { heartbeats, candidates } = getPendingCandidates(options, cutoff);
|
|
1383
|
-
for (const candidate of candidates) {
|
|
1384
|
-
if (options.dryRun) {
|
|
1385
|
-
logStatus(`dry-run candidate ${candidate.fileName}`);
|
|
1386
|
-
continue;
|
|
1387
|
-
}
|
|
1388
|
-
if (!client) {
|
|
1389
|
-
throw new Error("App Server client is not available");
|
|
1390
|
-
}
|
|
1391
|
-
const dispatched = await dispatchCandidate(
|
|
1392
|
-
client,
|
|
1393
|
-
options,
|
|
1394
|
-
candidate,
|
|
1395
|
-
heartbeats
|
|
1396
|
-
);
|
|
1397
|
-
if (!dispatched && options.busyMode === "wait") {
|
|
1398
|
-
return false;
|
|
1819
|
+
} catch {
|
|
1399
1820
|
}
|
|
1400
|
-
return true;
|
|
1401
1821
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
async function waitForTurnDrain(options, client, health) {
|
|
1405
|
-
const deadline = Date.now() + options.waitAfterDispatchSeconds * 1e3;
|
|
1406
|
-
while (Date.now() < deadline) {
|
|
1407
|
-
writeHeartbeat(options, client, health);
|
|
1408
|
-
if (!client.activeTurnId) {
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
await delay(1e3);
|
|
1822
|
+
if (lookbackCutoff) {
|
|
1823
|
+
return lookbackCutoff;
|
|
1412
1824
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
const commsDir = resolveCommsDir(repoRoot, parsed.commsDir);
|
|
1418
|
-
const preferredAgentName = resolvePreferredAgentName(parsed.agentName);
|
|
1419
|
-
const stateDir = resolveStateDir(
|
|
1420
|
-
repoRoot,
|
|
1421
|
-
parsed.stateDir,
|
|
1422
|
-
preferredAgentName
|
|
1423
|
-
);
|
|
1424
|
-
const agentName = resolveAgentName(preferredAgentName, stateDir);
|
|
1425
|
-
const agentId = resolveAgentId(agentName);
|
|
1426
|
-
persistAgentName(stateDir, agentName);
|
|
1427
|
-
const gatewayTokenFile = parsed.gatewayTokenFile?.trim() || process.env.TAP_GATEWAY_TOKEN_FILE?.trim() || null;
|
|
1428
|
-
const appServerUrl = parsed.appServerUrl?.trim() || process.env.CODEX_APP_SERVER_URL || DEFAULT_APP_SERVER_URL;
|
|
1429
|
-
return {
|
|
1430
|
-
repoRoot,
|
|
1431
|
-
commsDir,
|
|
1432
|
-
agentId,
|
|
1433
|
-
stateDir,
|
|
1434
|
-
agentName,
|
|
1435
|
-
pollSeconds: parsed.pollSeconds ?? 5,
|
|
1436
|
-
reconnectSeconds: parsed.reconnectSeconds ?? 5,
|
|
1437
|
-
messageLookbackMinutes: parsed.messageLookbackMinutes ?? 10,
|
|
1438
|
-
processExistingMessages: parsed.processExistingMessages,
|
|
1439
|
-
dryRun: parsed.dryRun,
|
|
1440
|
-
runOnce: parsed.runOnce,
|
|
1441
|
-
waitAfterDispatchSeconds: parsed.waitAfterDispatchSeconds ?? 0,
|
|
1442
|
-
appServerUrl,
|
|
1443
|
-
connectAppServerUrl: appServerUrl,
|
|
1444
|
-
gatewayToken: gatewayTokenFile ? readGatewayTokenFile(gatewayTokenFile) : null,
|
|
1445
|
-
gatewayTokenFile,
|
|
1446
|
-
busyMode: parsed.busyMode ?? "steer",
|
|
1447
|
-
threadId: parsed.threadId?.trim() || null,
|
|
1448
|
-
ephemeral: parsed.ephemeral
|
|
1449
|
-
};
|
|
1825
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1826
|
+
writeFileSync5(cutoffPath, `${cutoff.toISOString()}
|
|
1827
|
+
`, "utf8");
|
|
1828
|
+
return cutoff;
|
|
1450
1829
|
}
|
|
1451
1830
|
async function main() {
|
|
1452
1831
|
const options = buildOptions(process.argv.slice(2));
|
|
1832
|
+
configureBridgeLogging(options.logLevel);
|
|
1833
|
+
const logger = createBridgeLogger("bridge");
|
|
1453
1834
|
const cutoff = getGeneralInboxCutoff(
|
|
1454
1835
|
options.stateDir,
|
|
1455
1836
|
options.messageLookbackMinutes,
|
|
@@ -1459,28 +1840,20 @@ async function main() {
|
|
|
1459
1840
|
options.stateDir,
|
|
1460
1841
|
options.appServerUrl
|
|
1461
1842
|
);
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
console.log(
|
|
1475
|
-
` lookback: ${options.processExistingMessages ? "existing messages" : `${options.messageLookbackMinutes} minute(s)`}`
|
|
1476
|
-
);
|
|
1477
|
-
if (options.threadId || initialSavedThread?.threadId) {
|
|
1478
|
-
console.log(
|
|
1479
|
-
` thread: ${options.threadId ?? initialSavedThread?.threadId}`
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1843
|
+
logger.info("codex app-server bridge ready", {
|
|
1844
|
+
repoRoot: options.repoRoot,
|
|
1845
|
+
commsDir: options.commsDir,
|
|
1846
|
+
agentName: options.agentName,
|
|
1847
|
+
stateDir: options.stateDir,
|
|
1848
|
+
appServerUrl: options.appServerUrl,
|
|
1849
|
+
busyMode: options.busyMode,
|
|
1850
|
+
logLevel: options.logLevel,
|
|
1851
|
+
waitAfterDispatchSeconds: options.waitAfterDispatchSeconds > 0 ? options.waitAfterDispatchSeconds : void 0,
|
|
1852
|
+
lookback: options.processExistingMessages ? "existing messages" : `${options.messageLookbackMinutes} minute(s)`,
|
|
1853
|
+
threadId: options.threadId ?? initialSavedThread?.threadId
|
|
1854
|
+
});
|
|
1482
1855
|
if (options.dryRun) {
|
|
1483
|
-
|
|
1856
|
+
logger.info("dry-run mode enabled");
|
|
1484
1857
|
}
|
|
1485
1858
|
let client = null;
|
|
1486
1859
|
const health = {
|
|
@@ -1492,7 +1865,7 @@ async function main() {
|
|
|
1492
1865
|
if (!client || !client.connected) {
|
|
1493
1866
|
client = new AppServerClient(
|
|
1494
1867
|
options.connectAppServerUrl,
|
|
1495
|
-
|
|
1868
|
+
createBridgeLogger("app-server"),
|
|
1496
1869
|
options.gatewayToken
|
|
1497
1870
|
);
|
|
1498
1871
|
await client.connect();
|
|
@@ -1500,6 +1873,10 @@ async function main() {
|
|
|
1500
1873
|
options.stateDir,
|
|
1501
1874
|
options.appServerUrl
|
|
1502
1875
|
);
|
|
1876
|
+
logger.debug("resolved resumable thread state", {
|
|
1877
|
+
savedThreadId: savedThread?.threadId,
|
|
1878
|
+
savedThreadCwd: savedThread?.cwd ?? null
|
|
1879
|
+
});
|
|
1503
1880
|
const threadId = await client.ensureThread(
|
|
1504
1881
|
options.threadId,
|
|
1505
1882
|
savedThread,
|
|
@@ -1524,8 +1901,14 @@ async function main() {
|
|
|
1524
1901
|
}
|
|
1525
1902
|
}
|
|
1526
1903
|
}
|
|
1527
|
-
const
|
|
1528
|
-
if (dispatched &&
|
|
1904
|
+
const scanResult = await runScan(options, cutoff, client);
|
|
1905
|
+
if (scanResult.dispatched && scanResult.maxMtimeMs > 0) {
|
|
1906
|
+
const cutoffPath = join7(options.stateDir, "general-inbox-cutoff.txt");
|
|
1907
|
+
const advancedCutoff = new Date(scanResult.maxMtimeMs);
|
|
1908
|
+
writeFileSync5(cutoffPath, `${advancedCutoff.toISOString()}
|
|
1909
|
+
`, "utf8");
|
|
1910
|
+
}
|
|
1911
|
+
if (scanResult.dispatched && client && options.waitAfterDispatchSeconds > 0) {
|
|
1529
1912
|
await waitForTurnDrain(options, client, health);
|
|
1530
1913
|
}
|
|
1531
1914
|
health.consecutiveFailureCount = 0;
|
|
@@ -1533,22 +1916,28 @@ async function main() {
|
|
|
1533
1916
|
if (options.runOnce) {
|
|
1534
1917
|
break;
|
|
1535
1918
|
}
|
|
1536
|
-
await
|
|
1919
|
+
await delay2(options.pollSeconds * 1e3);
|
|
1537
1920
|
} catch (error) {
|
|
1538
1921
|
const message = error instanceof Error ? error.message : String(error);
|
|
1539
|
-
|
|
1922
|
+
logger.error("bridge error", {
|
|
1923
|
+
error: sanitizeErrorForPersistence(message)
|
|
1924
|
+
});
|
|
1540
1925
|
if (client) {
|
|
1541
|
-
client.lastError = message;
|
|
1926
|
+
client.lastError = sanitizeErrorForPersistence(message);
|
|
1542
1927
|
}
|
|
1543
1928
|
health.consecutiveFailureCount += 1;
|
|
1544
1929
|
writeHeartbeat(options, client, health);
|
|
1545
1930
|
if (options.runOnce) {
|
|
1546
|
-
|
|
1931
|
+
const sanitized = sanitizeErrorForPersistence(message);
|
|
1932
|
+
throw new Error(sanitized ?? message);
|
|
1547
1933
|
}
|
|
1548
1934
|
client?.disconnect().catch(() => void 0);
|
|
1549
1935
|
client = null;
|
|
1550
|
-
|
|
1551
|
-
|
|
1936
|
+
logger.warn("reconnecting after bridge error", {
|
|
1937
|
+
reconnectSeconds: options.reconnectSeconds,
|
|
1938
|
+
consecutiveFailureCount: health.consecutiveFailureCount
|
|
1939
|
+
});
|
|
1940
|
+
await delay2(options.reconnectSeconds * 1e3);
|
|
1552
1941
|
}
|
|
1553
1942
|
}
|
|
1554
1943
|
await client?.disconnect();
|
|
@@ -1556,24 +1945,32 @@ async function main() {
|
|
|
1556
1945
|
function isDirectExecution() {
|
|
1557
1946
|
const entry = process.argv[1];
|
|
1558
1947
|
if (!entry) return false;
|
|
1559
|
-
return import.meta.url === pathToFileURL(
|
|
1948
|
+
return import.meta.url === pathToFileURL(resolve4(entry)).href;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// ../../scripts/codex-app-server-bridge.ts
|
|
1952
|
+
import { resolve as resolve5 } from "path";
|
|
1953
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1954
|
+
function isDirectExecution2() {
|
|
1955
|
+
const entry = process.argv[1];
|
|
1956
|
+
if (!entry) return false;
|
|
1957
|
+
return import.meta.url === pathToFileURL2(resolve5(entry)).href;
|
|
1560
1958
|
}
|
|
1561
|
-
if (
|
|
1959
|
+
if (isDirectExecution2()) {
|
|
1562
1960
|
main().catch((error) => {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
);
|
|
1961
|
+
const raw = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1962
|
+
console.error(sanitizeErrorForPersistence(raw));
|
|
1566
1963
|
process.exitCode = 1;
|
|
1567
1964
|
});
|
|
1568
1965
|
}
|
|
1569
1966
|
|
|
1570
1967
|
// src/bridges/codex-app-server-bridge.ts
|
|
1571
|
-
function
|
|
1968
|
+
function isDirectExecution3() {
|
|
1572
1969
|
const entry = process.argv[1];
|
|
1573
1970
|
if (!entry) return false;
|
|
1574
|
-
return import.meta.url ===
|
|
1971
|
+
return import.meta.url === pathToFileURL3(resolve6(entry)).href;
|
|
1575
1972
|
}
|
|
1576
|
-
if (
|
|
1973
|
+
if (isDirectExecution3()) {
|
|
1577
1974
|
main().catch((error) => {
|
|
1578
1975
|
console.error(
|
|
1579
1976
|
error instanceof Error ? error.stack ?? error.message : String(error)
|
|
@@ -1582,19 +1979,77 @@ if (isDirectExecution2()) {
|
|
|
1582
1979
|
});
|
|
1583
1980
|
}
|
|
1584
1981
|
export {
|
|
1982
|
+
AUTH_SUBPROTOCOL_PREFIX,
|
|
1983
|
+
AppServerClient,
|
|
1984
|
+
COMMS_HEARTBEAT_LOCK_TIMEOUT_MS,
|
|
1985
|
+
COMMS_LOCK_STALE_AGE_MS,
|
|
1986
|
+
DEFAULT_AGENT,
|
|
1987
|
+
DEFAULT_APP_SERVER_URL,
|
|
1988
|
+
HEADLESS_SKIP_PATTERNS,
|
|
1585
1989
|
HEADLESS_WARMUP_PROMPT,
|
|
1990
|
+
HEADLESS_WARMUP_TIMEOUT_MS,
|
|
1991
|
+
PLACEHOLDER_AGENT_VALUES,
|
|
1992
|
+
STALE_TURN_MS,
|
|
1993
|
+
TURN_COMPLETION_POLL_MS,
|
|
1994
|
+
TURN_COMPLETION_REFRESH_MS,
|
|
1995
|
+
acquireCommsLock,
|
|
1996
|
+
buildDefaultStateDir,
|
|
1997
|
+
buildMarkerId,
|
|
1586
1998
|
buildOptions,
|
|
1587
1999
|
buildUserInput,
|
|
2000
|
+
canonicalize,
|
|
1588
2001
|
chooseLoadedThreadForCwd,
|
|
2002
|
+
collectCandidates,
|
|
2003
|
+
dispatchCandidate,
|
|
2004
|
+
formatAgentLabel,
|
|
2005
|
+
formatJsonRpcError,
|
|
2006
|
+
getGeneralInboxCutoff,
|
|
2007
|
+
getInboxRoute,
|
|
2008
|
+
getInboxRouteFromFilename,
|
|
2009
|
+
getPendingCandidates,
|
|
2010
|
+
getProcessedMarkerPath,
|
|
2011
|
+
isDirectExecution,
|
|
1589
2012
|
isOwnMessageSender,
|
|
2013
|
+
isTurnStale,
|
|
2014
|
+
isTurnStuckOnApproval,
|
|
2015
|
+
loadHeartbeats,
|
|
1590
2016
|
loadResumableThreadState,
|
|
1591
2017
|
main,
|
|
1592
2018
|
maybeBootstrapHeadlessTurn,
|
|
2019
|
+
normalizeAgentToken,
|
|
2020
|
+
normalizeThreadCwd,
|
|
2021
|
+
parseArgs,
|
|
2022
|
+
parseBridgeFrontmatter,
|
|
2023
|
+
persistAgentName,
|
|
2024
|
+
persistThreadState,
|
|
2025
|
+
readGatewayTokenFile,
|
|
2026
|
+
readHeartbeatState,
|
|
2027
|
+
readSocketData,
|
|
2028
|
+
readThreadState,
|
|
1593
2029
|
recipientMatchesAgent,
|
|
2030
|
+
refreshAgentIdentity,
|
|
2031
|
+
releaseCommsLock,
|
|
1594
2032
|
resolveAddressLabel,
|
|
1595
2033
|
resolveAgentId,
|
|
2034
|
+
resolveAgentName,
|
|
2035
|
+
resolveCommsDir,
|
|
1596
2036
|
resolveCurrentAgentName,
|
|
2037
|
+
resolvePreferredAgentName,
|
|
2038
|
+
resolveRepoRoot,
|
|
2039
|
+
resolveStateDir,
|
|
2040
|
+
resolveTapConfigPath,
|
|
2041
|
+
runScan,
|
|
2042
|
+
sanitizeErrorForPersistence,
|
|
2043
|
+
sanitizeStateSegment,
|
|
2044
|
+
shouldRetrySteerAsStart,
|
|
2045
|
+
shouldSkipInHeadlessMode,
|
|
2046
|
+
stripBridgeFrontmatter,
|
|
1597
2047
|
threadCwdMatches,
|
|
1598
|
-
|
|
2048
|
+
updateCommsHeartbeat,
|
|
2049
|
+
waitForTurnCompletion,
|
|
2050
|
+
waitForTurnDrain,
|
|
2051
|
+
writeHeartbeat,
|
|
2052
|
+
writeLastDispatch,
|
|
2053
|
+
writeProcessedMarker
|
|
1599
2054
|
};
|
|
1600
2055
|
//# sourceMappingURL=codex-app-server-bridge.mjs.map
|