@hua-labs/tap 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 -687
- 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 +327 -70
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +6 -4
- 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,304 +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.replace(/-/g, "_");
|
|
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
|
-
const
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const normalizedSender = sender.trim();
|
|
488
|
-
if (!normalizedSender) {
|
|
489
|
-
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;
|
|
490
617
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
function getInboxRoute(fileName) {
|
|
496
|
-
const stem = fileName.replace(/\.md$/i, "");
|
|
497
|
-
const parts = stem.split("-");
|
|
498
|
-
let offset = 0;
|
|
499
|
-
if (parts[0] && /^\d{8}$/.test(parts[0])) {
|
|
500
|
-
offset = 1;
|
|
618
|
+
if (level === "warn") {
|
|
619
|
+
console.warn(line);
|
|
620
|
+
return;
|
|
501
621
|
}
|
|
622
|
+
console.log(line);
|
|
623
|
+
}
|
|
624
|
+
function createBridgeLogger(scope) {
|
|
625
|
+
const scopedMessage = (message) => `[${scope}] ${message}`;
|
|
502
626
|
return {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
+
}
|
|
506
639
|
};
|
|
507
640
|
}
|
|
641
|
+
|
|
642
|
+
// ../../scripts/bridge/bridge-candidates.ts
|
|
643
|
+
var routingLogger = createBridgeLogger("routing");
|
|
508
644
|
function buildMarkerId(filePath, mtimeMs) {
|
|
509
645
|
return createHash("sha1").update(`${filePath}|${mtimeMs}`).digest("hex");
|
|
510
646
|
}
|
|
511
647
|
function getProcessedMarkerPath(stateDir, markerId) {
|
|
512
|
-
return
|
|
648
|
+
return join4(stateDir, "processed", `${markerId}.done`);
|
|
513
649
|
}
|
|
514
650
|
function loadHeartbeats(commsDir) {
|
|
515
651
|
try {
|
|
516
|
-
return JSON.parse(
|
|
652
|
+
return JSON.parse(readFileSync4(join4(commsDir, "heartbeats.json"), "utf8"));
|
|
517
653
|
} catch {
|
|
518
654
|
return {};
|
|
519
655
|
}
|
|
520
656
|
}
|
|
521
|
-
function formatAgentLabel(agentIdOrName, displayName) {
|
|
522
|
-
const normalizedId = agentIdOrName.trim();
|
|
523
|
-
const normalizedName = displayName?.trim();
|
|
524
|
-
if (!normalizedId) {
|
|
525
|
-
return normalizedName ?? agentIdOrName;
|
|
526
|
-
}
|
|
527
|
-
if (!normalizedName || normalizedName === normalizedId) {
|
|
528
|
-
return normalizedId;
|
|
529
|
-
}
|
|
530
|
-
return `${normalizedName} [${normalizedId}]`;
|
|
531
|
-
}
|
|
532
|
-
function resolveAddressLabel(address, heartbeats) {
|
|
533
|
-
const normalized = address.trim();
|
|
534
|
-
if (!normalized || normalized === "\uC804\uCCB4" || normalized === "all") {
|
|
535
|
-
return address;
|
|
536
|
-
}
|
|
537
|
-
const direct = heartbeats[normalized];
|
|
538
|
-
if (direct?.agent?.trim()) {
|
|
539
|
-
return formatAgentLabel(normalized, direct.agent);
|
|
540
|
-
}
|
|
541
|
-
for (const [agentId, heartbeat] of Object.entries(heartbeats)) {
|
|
542
|
-
if (heartbeat.agent?.trim() === normalized) {
|
|
543
|
-
return formatAgentLabel(agentId, heartbeat.agent);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
return normalized;
|
|
547
|
-
}
|
|
548
|
-
function resolveCurrentAgentName(agentId, fallbackAgentName, heartbeats) {
|
|
549
|
-
const currentName = heartbeats[agentId]?.agent?.trim();
|
|
550
|
-
if (currentName) {
|
|
551
|
-
return currentName;
|
|
552
|
-
}
|
|
553
|
-
for (const heartbeat of Object.values(heartbeats)) {
|
|
554
|
-
if (heartbeat.id?.trim() === agentId && heartbeat.agent?.trim()) {
|
|
555
|
-
return heartbeat.agent.trim();
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
return fallbackAgentName;
|
|
559
|
-
}
|
|
560
|
-
function refreshAgentIdentity(options, heartbeats) {
|
|
561
|
-
const nextAgentName = resolveCurrentAgentName(
|
|
562
|
-
options.agentId,
|
|
563
|
-
options.agentName,
|
|
564
|
-
heartbeats
|
|
565
|
-
);
|
|
566
|
-
if (nextAgentName !== options.agentName) {
|
|
567
|
-
options.agentName = nextAgentName;
|
|
568
|
-
persistAgentName(options.stateDir, nextAgentName);
|
|
569
|
-
}
|
|
570
|
-
return nextAgentName;
|
|
571
|
-
}
|
|
572
|
-
var HEADLESS_SKIP_PATTERNS = [
|
|
573
|
-
/리뷰\s*요청/,
|
|
574
|
-
/review[- ]?request/i,
|
|
575
|
-
/재리뷰/,
|
|
576
|
-
/re-?review/i
|
|
577
|
-
];
|
|
578
657
|
function shouldSkipInHeadlessMode(fileName, body) {
|
|
579
658
|
if (process.env.TAP_HEADLESS !== "true") return false;
|
|
580
659
|
const combined = `${fileName}
|
|
581
660
|
${body}`;
|
|
582
661
|
return HEADLESS_SKIP_PATTERNS.some((p) => p.test(combined));
|
|
583
662
|
}
|
|
584
|
-
function collectCandidates(inboxDir, agentId, agentName) {
|
|
663
|
+
function collectCandidates(inboxDir, agentId, agentName, aliasName) {
|
|
585
664
|
const entries = readdirSync(inboxDir, { withFileTypes: true }).filter(
|
|
586
665
|
(entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")
|
|
587
666
|
).map((entry) => {
|
|
588
|
-
const filePath =
|
|
667
|
+
const filePath = join4(inboxDir, entry.name);
|
|
589
668
|
const stats = statSync(filePath);
|
|
590
669
|
return { entry, filePath, stats };
|
|
591
670
|
}).sort((left, right) => left.stats.mtimeMs - right.stats.mtimeMs);
|
|
592
671
|
const candidates = [];
|
|
672
|
+
let filteredByRecipient = 0;
|
|
673
|
+
let filteredBySelf = 0;
|
|
674
|
+
let filteredByHeadless = 0;
|
|
593
675
|
for (const item of entries) {
|
|
594
|
-
|
|
595
|
-
|
|
676
|
+
let body;
|
|
677
|
+
try {
|
|
678
|
+
body = readFileSync4(item.filePath, "utf8");
|
|
679
|
+
} catch {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const route = getInboxRoute(item.entry.name, body);
|
|
683
|
+
if (!recipientMatchesAgent(route.recipient, agentId, agentName) && !(aliasName && recipientMatchesAgent(route.recipient, agentId, aliasName))) {
|
|
684
|
+
filteredByRecipient += 1;
|
|
596
685
|
continue;
|
|
597
686
|
}
|
|
598
|
-
if (isOwnMessageSender(route.sender, agentId, agentName)) {
|
|
687
|
+
if (isOwnMessageSender(route.sender, agentId, agentName) || aliasName && isOwnMessageSender(route.sender, agentId, aliasName)) {
|
|
688
|
+
filteredBySelf += 1;
|
|
599
689
|
continue;
|
|
600
690
|
}
|
|
601
|
-
const body = readFileSync(item.filePath, "utf8");
|
|
602
691
|
if (shouldSkipInHeadlessMode(item.entry.name, body)) {
|
|
692
|
+
filteredByHeadless += 1;
|
|
603
693
|
continue;
|
|
604
694
|
}
|
|
605
695
|
candidates.push({
|
|
@@ -609,34 +699,58 @@ function collectCandidates(inboxDir, agentId, agentName) {
|
|
|
609
699
|
sender: route.sender,
|
|
610
700
|
recipient: route.recipient,
|
|
611
701
|
subject: route.subject,
|
|
612
|
-
body,
|
|
702
|
+
body: stripBridgeFrontmatter(body),
|
|
613
703
|
mtimeMs: item.stats.mtimeMs
|
|
614
704
|
});
|
|
615
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
|
+
});
|
|
616
717
|
return candidates;
|
|
617
718
|
}
|
|
618
719
|
function getPendingCandidates(options, cutoff) {
|
|
619
|
-
const inboxDir =
|
|
620
|
-
if (!
|
|
720
|
+
const inboxDir = join4(options.commsDir, "inbox");
|
|
721
|
+
if (!existsSync4(inboxDir)) {
|
|
621
722
|
throw new Error(`Inbox directory not found: ${inboxDir}`);
|
|
622
723
|
}
|
|
623
724
|
const heartbeats = loadHeartbeats(options.commsDir);
|
|
624
|
-
const
|
|
725
|
+
const refreshedName = refreshAgentIdentity(options, heartbeats);
|
|
625
726
|
const cutoffMs = cutoff.getTime();
|
|
626
727
|
const candidates = collectCandidates(
|
|
627
728
|
inboxDir,
|
|
628
729
|
options.agentId,
|
|
629
|
-
agentName
|
|
730
|
+
options.agentName,
|
|
731
|
+
// M205: Also accept messages addressed to the heartbeat-refreshed name
|
|
732
|
+
refreshedName !== options.agentName ? refreshedName : void 0
|
|
630
733
|
).filter((candidate) => {
|
|
631
734
|
if (candidate.mtimeMs < cutoffMs) {
|
|
632
735
|
return false;
|
|
633
736
|
}
|
|
634
|
-
return !
|
|
737
|
+
return !existsSync4(
|
|
635
738
|
getProcessedMarkerPath(options.stateDir, candidate.markerId)
|
|
636
739
|
);
|
|
637
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
|
+
});
|
|
638
748
|
return { heartbeats, candidates };
|
|
639
749
|
}
|
|
750
|
+
|
|
751
|
+
// ../../scripts/bridge/bridge-format.ts
|
|
752
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
753
|
+
import { join as join5 } from "path";
|
|
640
754
|
function buildUserInput(candidate, agentName, heartbeats) {
|
|
641
755
|
const sender = resolveAddressLabel(candidate.sender || "unknown", heartbeats);
|
|
642
756
|
const recipient = resolveAddressLabel(
|
|
@@ -675,7 +789,7 @@ function writeProcessedMarker(stateDir, candidate, dispatchMode, threadId, turnI
|
|
|
675
789
|
turnId,
|
|
676
790
|
markedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
677
791
|
};
|
|
678
|
-
|
|
792
|
+
writeFileSync3(
|
|
679
793
|
getProcessedMarkerPath(stateDir, candidate.markerId),
|
|
680
794
|
`${JSON.stringify(payload, null, 2)}
|
|
681
795
|
`,
|
|
@@ -695,98 +809,379 @@ function writeLastDispatch(stateDir, candidate, dispatchMode, threadId, turnId)
|
|
|
695
809
|
turnId,
|
|
696
810
|
dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
697
811
|
};
|
|
698
|
-
|
|
699
|
-
|
|
812
|
+
writeFileSync3(
|
|
813
|
+
join5(stateDir, "last-dispatch.json"),
|
|
700
814
|
`${JSON.stringify(payload, null, 2)}
|
|
701
815
|
`,
|
|
702
816
|
"utf8"
|
|
703
817
|
);
|
|
704
818
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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, "***");
|
|
718
835
|
}
|
|
719
836
|
function delay(ms) {
|
|
720
837
|
return new Promise((resolvePromise) => {
|
|
721
838
|
setTimeout(resolvePromise, ms);
|
|
722
839
|
});
|
|
723
840
|
}
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
729
|
-
return client.lastTurnStatus;
|
|
730
|
-
}
|
|
731
|
-
if (Date.now() >= nextRefreshAt) {
|
|
732
|
-
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
733
|
-
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
734
|
-
return client.lastTurnStatus;
|
|
735
|
-
}
|
|
736
|
-
nextRefreshAt = Date.now() + TURN_COMPLETION_REFRESH_MS;
|
|
737
|
-
}
|
|
738
|
-
await delay(
|
|
739
|
-
Math.min(TURN_COMPLETION_POLL_MS, Math.max(deadline - Date.now(), 0))
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
743
|
-
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
744
|
-
return client.lastTurnStatus;
|
|
745
|
-
}
|
|
746
|
-
throw new Error(`Timed out waiting for turn ${turnId} to complete`);
|
|
747
|
-
}
|
|
748
|
-
async function maybeBootstrapHeadlessTurn(options, cutoff, client) {
|
|
749
|
-
if (process.env.TAP_HEADLESS !== "true" && process.env.TAP_COLD_START_WARMUP !== "true") {
|
|
750
|
-
return false;
|
|
751
|
-
}
|
|
752
|
-
const { candidates } = getPendingCandidates(options, cutoff);
|
|
753
|
-
if (candidates.length > 0 || client.activeTurnId || client.lastTurnStatus !== null) {
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
logStatus("headless cold-start: sending warmup turn");
|
|
757
|
-
const turnId = await client.startTurn(HEADLESS_WARMUP_PROMPT);
|
|
758
|
-
if (!turnId) {
|
|
759
|
-
throw new Error(
|
|
760
|
-
"Headless cold-start warmup failed: turn/start did not return a turn id. Run: npx @hua-labs/tap doctor"
|
|
761
|
-
);
|
|
841
|
+
function readThreadState(stateDir) {
|
|
842
|
+
const threadPath = join6(stateDir, "thread.json");
|
|
843
|
+
if (!existsSync5(threadPath)) {
|
|
844
|
+
return null;
|
|
762
845
|
}
|
|
763
846
|
try {
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
turnId,
|
|
767
|
-
HEADLESS_WARMUP_TIMEOUT_MS
|
|
847
|
+
const parsed = JSON.parse(
|
|
848
|
+
readFileSync5(threadPath, "utf8")
|
|
768
849
|
);
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
`turn ${turnId} finished with status ${status ?? "unknown"}`
|
|
772
|
-
);
|
|
850
|
+
if (parsed.threadId) {
|
|
851
|
+
return parsed;
|
|
773
852
|
}
|
|
774
|
-
|
|
775
|
-
return
|
|
776
|
-
} catch (error) {
|
|
777
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
778
|
-
throw new Error(
|
|
779
|
-
`Headless cold-start warmup failed: ${reason}. Run: npx @hua-labs/tap doctor`
|
|
780
|
-
);
|
|
853
|
+
} catch {
|
|
854
|
+
return null;
|
|
781
855
|
}
|
|
856
|
+
return null;
|
|
782
857
|
}
|
|
783
|
-
function
|
|
784
|
-
|
|
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") {
|
|
785
1145
|
return false;
|
|
786
1146
|
}
|
|
787
|
-
const
|
|
788
|
-
|
|
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
|
+
}
|
|
789
1182
|
}
|
|
1183
|
+
|
|
1184
|
+
// ../../scripts/bridge/bridge-ws-client.ts
|
|
790
1185
|
async function readSocketData(data) {
|
|
791
1186
|
if (typeof data === "string") {
|
|
792
1187
|
return data;
|
|
@@ -804,11 +1199,27 @@ async function readSocketData(data) {
|
|
|
804
1199
|
}
|
|
805
1200
|
return String(data);
|
|
806
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;
|
|
807
1217
|
var AppServerClient = class {
|
|
808
1218
|
socket = null;
|
|
809
1219
|
url;
|
|
810
1220
|
gatewayToken;
|
|
811
1221
|
logger;
|
|
1222
|
+
clientId = nextAppServerClientId++;
|
|
812
1223
|
nextId = 1;
|
|
813
1224
|
pending = /* @__PURE__ */ new Map();
|
|
814
1225
|
connected = false;
|
|
@@ -832,6 +1243,12 @@ var AppServerClient = class {
|
|
|
832
1243
|
if (this.connected && this.socket?.readyState === WebSocket.OPEN) {
|
|
833
1244
|
return;
|
|
834
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
|
+
}
|
|
835
1252
|
const wsOptions = {};
|
|
836
1253
|
if (this.gatewayToken) {
|
|
837
1254
|
wsOptions.protocols = [`${AUTH_SUBPROTOCOL_PREFIX}${this.gatewayToken}`];
|
|
@@ -857,7 +1274,11 @@ var AppServerClient = class {
|
|
|
857
1274
|
"open",
|
|
858
1275
|
() => {
|
|
859
1276
|
this.connected = true;
|
|
860
|
-
this.logger(
|
|
1277
|
+
this.logger.info("connected to app-server", {
|
|
1278
|
+
clientId: this.clientId,
|
|
1279
|
+
url: this.url,
|
|
1280
|
+
authenticated: Boolean(this.gatewayToken)
|
|
1281
|
+
});
|
|
861
1282
|
resolveOnce();
|
|
862
1283
|
},
|
|
863
1284
|
{ once: true }
|
|
@@ -866,7 +1287,12 @@ var AppServerClient = class {
|
|
|
866
1287
|
const error = new Error(
|
|
867
1288
|
`Failed to connect to App Server at ${this.url}`
|
|
868
1289
|
);
|
|
869
|
-
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
|
+
});
|
|
870
1296
|
rejectOnce(error);
|
|
871
1297
|
});
|
|
872
1298
|
this.socket?.addEventListener("close", () => {
|
|
@@ -874,7 +1300,10 @@ var AppServerClient = class {
|
|
|
874
1300
|
this.initialized = false;
|
|
875
1301
|
this.activeTurnId = null;
|
|
876
1302
|
this.turnStartedAt = null;
|
|
877
|
-
this.logger("disconnected from app-server"
|
|
1303
|
+
this.logger.warn("disconnected from app-server", {
|
|
1304
|
+
clientId: this.clientId,
|
|
1305
|
+
url: this.url
|
|
1306
|
+
});
|
|
878
1307
|
this.rejectPending(new Error("App Server connection closed"));
|
|
879
1308
|
});
|
|
880
1309
|
this.socket?.addEventListener("message", (event) => {
|
|
@@ -911,13 +1340,20 @@ var AppServerClient = class {
|
|
|
911
1340
|
});
|
|
912
1341
|
const resumedThreadId = resumeResponse?.thread?.id ?? explicitThreadId;
|
|
913
1342
|
await this.refreshThreadState(resumedThreadId);
|
|
914
|
-
this.logger(
|
|
915
|
-
|
|
916
|
-
|
|
1343
|
+
this.logger.info("resumed explicit thread", {
|
|
1344
|
+
clientId: this.clientId,
|
|
1345
|
+
threadId: resumedThreadId,
|
|
1346
|
+
activeTurnId: this.activeTurnId
|
|
1347
|
+
});
|
|
917
1348
|
return resumedThreadId;
|
|
918
1349
|
} catch (error) {
|
|
919
|
-
this.logger(
|
|
920
|
-
|
|
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
|
+
}
|
|
921
1357
|
);
|
|
922
1358
|
}
|
|
923
1359
|
}
|
|
@@ -927,9 +1363,12 @@ var AppServerClient = class {
|
|
|
927
1363
|
}
|
|
928
1364
|
if (savedThread?.threadId) {
|
|
929
1365
|
if (savedThread.cwd && !threadCwdMatches(cwd, savedThread.cwd)) {
|
|
930
|
-
this.logger(
|
|
931
|
-
|
|
932
|
-
|
|
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
|
+
});
|
|
933
1372
|
} else {
|
|
934
1373
|
try {
|
|
935
1374
|
const resumeResponse = await this.request("thread/resume", {
|
|
@@ -939,23 +1378,33 @@ var AppServerClient = class {
|
|
|
939
1378
|
const resumedThreadId = resumeResponse?.thread?.id ?? savedThread.threadId;
|
|
940
1379
|
await this.refreshThreadState(resumedThreadId);
|
|
941
1380
|
if (!threadCwdMatches(cwd, this.currentThreadCwd)) {
|
|
942
|
-
this.logger(
|
|
943
|
-
|
|
944
|
-
|
|
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
|
+
});
|
|
945
1387
|
this.threadId = null;
|
|
946
1388
|
this.currentThreadCwd = null;
|
|
947
1389
|
this.activeTurnId = null;
|
|
948
1390
|
this.turnStartedAt = null;
|
|
949
1391
|
this.lastTurnStatus = null;
|
|
950
1392
|
} else {
|
|
951
|
-
this.logger(
|
|
952
|
-
|
|
953
|
-
|
|
1393
|
+
this.logger.info("resumed saved thread", {
|
|
1394
|
+
clientId: this.clientId,
|
|
1395
|
+
threadId: resumedThreadId,
|
|
1396
|
+
activeTurnId: this.activeTurnId
|
|
1397
|
+
});
|
|
954
1398
|
return resumedThreadId;
|
|
955
1399
|
}
|
|
956
1400
|
} catch (error) {
|
|
957
|
-
this.logger(
|
|
958
|
-
|
|
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
|
+
}
|
|
959
1408
|
);
|
|
960
1409
|
}
|
|
961
1410
|
}
|
|
@@ -975,7 +1424,12 @@ var AppServerClient = class {
|
|
|
975
1424
|
this.currentThreadCwd = this.currentThreadCwd ?? cwd;
|
|
976
1425
|
this.activeTurnId = null;
|
|
977
1426
|
this.lastTurnStatus = null;
|
|
978
|
-
this.logger(
|
|
1427
|
+
this.logger.info("started thread", {
|
|
1428
|
+
clientId: this.clientId,
|
|
1429
|
+
threadId: startedThreadId,
|
|
1430
|
+
cwd: this.currentThreadCwd,
|
|
1431
|
+
ephemeral
|
|
1432
|
+
});
|
|
979
1433
|
return startedThreadId;
|
|
980
1434
|
}
|
|
981
1435
|
async findLoadedThread(cwd) {
|
|
@@ -1013,14 +1467,21 @@ var AppServerClient = class {
|
|
|
1013
1467
|
const chosen = chooseLoadedThreadForCwd(cwd, threads);
|
|
1014
1468
|
if (!chosen) {
|
|
1015
1469
|
if (threads.length > 0) {
|
|
1016
|
-
this.logger(
|
|
1470
|
+
this.logger.debug("loaded threads exist but none match cwd", {
|
|
1471
|
+
clientId: this.clientId,
|
|
1472
|
+
cwd,
|
|
1473
|
+
loadedThreadCount: threads.length
|
|
1474
|
+
});
|
|
1017
1475
|
}
|
|
1018
1476
|
return null;
|
|
1019
1477
|
}
|
|
1020
1478
|
this.syncThreadStateFromThread(chosen.thread);
|
|
1021
|
-
this.logger(
|
|
1022
|
-
|
|
1023
|
-
|
|
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
|
+
});
|
|
1024
1485
|
return chosen.id;
|
|
1025
1486
|
}
|
|
1026
1487
|
async startTurn(inputText) {
|
|
@@ -1059,7 +1520,18 @@ var AppServerClient = class {
|
|
|
1059
1520
|
return turnId;
|
|
1060
1521
|
}
|
|
1061
1522
|
isBusy() {
|
|
1062
|
-
|
|
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;
|
|
1063
1535
|
}
|
|
1064
1536
|
async refreshCurrentThreadState() {
|
|
1065
1537
|
if (!this.threadId) {
|
|
@@ -1093,12 +1565,33 @@ var AppServerClient = class {
|
|
|
1093
1565
|
this.currentThreadCwd = typeof thread?.cwd === "string" ? thread.cwd : null;
|
|
1094
1566
|
let activeTurnId = null;
|
|
1095
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
|
+
}
|
|
1096
1578
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
1097
1579
|
for (const turn of turns) {
|
|
1098
1580
|
if (typeof turn?.status === "string") {
|
|
1099
1581
|
lastTurnStatus = turn.status;
|
|
1100
1582
|
}
|
|
1101
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
|
+
}
|
|
1102
1595
|
activeTurnId = turn.id;
|
|
1103
1596
|
}
|
|
1104
1597
|
}
|
|
@@ -1121,7 +1614,12 @@ var AppServerClient = class {
|
|
|
1121
1614
|
this.pending.delete(message.id);
|
|
1122
1615
|
if (message.error) {
|
|
1123
1616
|
const errorText = formatJsonRpcError(message.error);
|
|
1124
|
-
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
|
+
});
|
|
1125
1623
|
pending.reject(new Error(`${pending.method} failed: ${errorText}`));
|
|
1126
1624
|
return;
|
|
1127
1625
|
}
|
|
@@ -1136,6 +1634,10 @@ var AppServerClient = class {
|
|
|
1136
1634
|
}
|
|
1137
1635
|
this.lastNotificationMethod = message.method;
|
|
1138
1636
|
this.lastNotificationAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1637
|
+
this.logger.debug("received app-server notification", {
|
|
1638
|
+
clientId: this.clientId,
|
|
1639
|
+
method: message.method
|
|
1640
|
+
});
|
|
1139
1641
|
this.handleNotification(message.method, message.params);
|
|
1140
1642
|
}
|
|
1141
1643
|
handleNotification(method, params) {
|
|
@@ -1147,18 +1649,28 @@ var AppServerClient = class {
|
|
|
1147
1649
|
if (typeof params?.thread?.cwd === "string") {
|
|
1148
1650
|
this.currentThreadCwd = params.thread.cwd;
|
|
1149
1651
|
}
|
|
1150
|
-
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
|
+
});
|
|
1151
1657
|
break;
|
|
1152
1658
|
case "thread/status/changed":
|
|
1153
|
-
this.logger(
|
|
1154
|
-
|
|
1155
|
-
|
|
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
|
+
});
|
|
1156
1664
|
break;
|
|
1157
1665
|
case "turn/started":
|
|
1158
1666
|
if (params?.turn?.id) {
|
|
1159
1667
|
this.activeTurnId = params.turn.id;
|
|
1160
1668
|
this.turnStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1161
|
-
this.logger(
|
|
1669
|
+
this.logger.info("turn started", {
|
|
1670
|
+
clientId: this.clientId,
|
|
1671
|
+
threadId: this.threadId,
|
|
1672
|
+
turnId: params.turn.id
|
|
1673
|
+
});
|
|
1162
1674
|
}
|
|
1163
1675
|
break;
|
|
1164
1676
|
case "turn/completed": {
|
|
@@ -1167,15 +1679,22 @@ var AppServerClient = class {
|
|
|
1167
1679
|
this.activeTurnId = null;
|
|
1168
1680
|
this.turnStartedAt = null;
|
|
1169
1681
|
const elapsedMs = prevTurnStartedAt ? Date.now() - new Date(prevTurnStartedAt).getTime() : null;
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
+
});
|
|
1174
1688
|
break;
|
|
1175
1689
|
}
|
|
1176
1690
|
case "error":
|
|
1177
|
-
this.lastError =
|
|
1178
|
-
|
|
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
|
+
});
|
|
1179
1698
|
break;
|
|
1180
1699
|
default:
|
|
1181
1700
|
break;
|
|
@@ -1183,277 +1702,135 @@ var AppServerClient = class {
|
|
|
1183
1702
|
}
|
|
1184
1703
|
request(method, params) {
|
|
1185
1704
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
1186
|
-
throw new Error(`Cannot call ${method}; App Server socket is not open`);
|
|
1187
|
-
}
|
|
1188
|
-
const id = this.nextId;
|
|
1189
|
-
this.nextId += 1;
|
|
1190
|
-
const request = {
|
|
1191
|
-
jsonrpc: "2.0",
|
|
1192
|
-
id,
|
|
1193
|
-
method,
|
|
1194
|
-
params
|
|
1195
|
-
};
|
|
1196
|
-
return new Promise((resolvePromise, rejectPromise) => {
|
|
1197
|
-
this.pending.set(id, {
|
|
1198
|
-
resolve: resolvePromise,
|
|
1199
|
-
reject: rejectPromise,
|
|
1200
|
-
method
|
|
1201
|
-
});
|
|
1202
|
-
this.socket?.send(JSON.stringify(request));
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
rejectPending(error) {
|
|
1206
|
-
for (const pending of this.pending.values()) {
|
|
1207
|
-
pending.reject(error);
|
|
1208
|
-
}
|
|
1209
|
-
this.pending.clear();
|
|
1210
|
-
}
|
|
1211
|
-
};
|
|
1212
|
-
var heartbeatCount = 0;
|
|
1213
|
-
function writeHeartbeat(options, client, health) {
|
|
1214
|
-
if (client?.threadId) {
|
|
1215
|
-
const savedThread = readThreadState(options.stateDir);
|
|
1216
|
-
persistThreadState(
|
|
1217
|
-
options.stateDir,
|
|
1218
|
-
client.threadId,
|
|
1219
|
-
options.appServerUrl,
|
|
1220
|
-
options.ephemeral,
|
|
1221
|
-
client.currentThreadCwd ?? savedThread?.cwd ?? null
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
const payload = {
|
|
1225
|
-
pid: process.pid,
|
|
1226
|
-
agent: options.agentName,
|
|
1227
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1228
|
-
pollSeconds: options.pollSeconds,
|
|
1229
|
-
appServerUrl: options.appServerUrl,
|
|
1230
|
-
connected: client?.connected ?? false,
|
|
1231
|
-
initialized: client?.initialized ?? false,
|
|
1232
|
-
threadId: client?.threadId ?? null,
|
|
1233
|
-
threadCwd: client?.currentThreadCwd ?? null,
|
|
1234
|
-
activeTurnId: client?.activeTurnId ?? null,
|
|
1235
|
-
turnStartedAt: client?.turnStartedAt ?? null,
|
|
1236
|
-
lastTurnStatus: client?.lastTurnStatus ?? null,
|
|
1237
|
-
lastNotificationMethod: client?.lastNotificationMethod ?? null,
|
|
1238
|
-
lastNotificationAt: client?.lastNotificationAt ?? null,
|
|
1239
|
-
lastError: sanitizeErrorForPersistence(client?.lastError ?? null),
|
|
1240
|
-
lastSuccessfulAppServerAt: client?.lastSuccessfulAppServerAt ?? null,
|
|
1241
|
-
lastSuccessfulAppServerMethod: client?.lastSuccessfulAppServerMethod ?? null,
|
|
1242
|
-
consecutiveFailureCount: health.consecutiveFailureCount,
|
|
1243
|
-
busyMode: options.busyMode
|
|
1244
|
-
};
|
|
1245
|
-
writeFileSync(
|
|
1246
|
-
join(options.stateDir, "heartbeat.json"),
|
|
1247
|
-
`${JSON.stringify(payload, null, 2)}
|
|
1248
|
-
`,
|
|
1249
|
-
"utf8"
|
|
1250
|
-
);
|
|
1251
|
-
heartbeatCount += 1;
|
|
1252
|
-
if (heartbeatCount % 5 === 0) {
|
|
1253
|
-
logStatus(
|
|
1254
|
-
`heartbeat: connected=${payload.connected}, thread=${payload.threadId ?? "null"}, turns=${payload.activeTurnId ? "active" : "0"}`
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
const status = client?.connected ? "active" : "idle";
|
|
1258
|
-
updateCommsHeartbeat(options, status);
|
|
1259
|
-
}
|
|
1260
|
-
var COMMS_HEARTBEAT_LOCK_TIMEOUT_MS = 2e3;
|
|
1261
|
-
var COMMS_LOCK_STALE_AGE_MS = 1e4;
|
|
1262
|
-
function acquireCommsLock(lockPath) {
|
|
1263
|
-
const deadline = Date.now() + COMMS_HEARTBEAT_LOCK_TIMEOUT_MS;
|
|
1264
|
-
while (Date.now() < deadline) {
|
|
1265
|
-
try {
|
|
1266
|
-
writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
1267
|
-
return true;
|
|
1268
|
-
} catch {
|
|
1269
|
-
try {
|
|
1270
|
-
const lockAge = Date.now() - statSync(lockPath).mtimeMs;
|
|
1271
|
-
if (lockAge > COMMS_LOCK_STALE_AGE_MS) {
|
|
1272
|
-
unlinkSync(lockPath);
|
|
1273
|
-
try {
|
|
1274
|
-
writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
1275
|
-
return true;
|
|
1276
|
-
} catch {
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
} catch {
|
|
1280
|
-
}
|
|
1281
|
-
const start = Date.now();
|
|
1282
|
-
while (Date.now() - start < 50) {
|
|
1283
|
-
}
|
|
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);
|
|
1284
1727
|
}
|
|
1728
|
+
this.pending.clear();
|
|
1285
1729
|
}
|
|
1286
|
-
|
|
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
|
+
});
|
|
1287
1740
|
}
|
|
1288
|
-
function
|
|
1741
|
+
function readHeartbeatState(stateDir) {
|
|
1742
|
+
const heartbeatPath = join7(stateDir, "heartbeat.json");
|
|
1743
|
+
if (!existsSync6(heartbeatPath)) {
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1289
1746
|
try {
|
|
1290
|
-
|
|
1747
|
+
return JSON.parse(readFileSync6(heartbeatPath, "utf8"));
|
|
1291
1748
|
} catch {
|
|
1749
|
+
return null;
|
|
1292
1750
|
}
|
|
1293
1751
|
}
|
|
1294
|
-
function
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
if (!acquireCommsLock(lockPath)) {
|
|
1298
|
-
return;
|
|
1752
|
+
function parseUpdatedAt(value) {
|
|
1753
|
+
if (!value) {
|
|
1754
|
+
return 0;
|
|
1299
1755
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
|
1315
1798
|
};
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
renameSync(tmpPath, heartbeatsPath);
|
|
1319
|
-
} catch {
|
|
1320
|
-
} finally {
|
|
1321
|
-
releaseCommsLock(lockPath);
|
|
1799
|
+
} else if (parseUpdatedAt(heartbeat?.updatedAt) > parseUpdatedAt(savedThread.updatedAt)) {
|
|
1800
|
+
preferred = heartbeatBackedThread;
|
|
1322
1801
|
}
|
|
1802
|
+
return preferred;
|
|
1323
1803
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
);
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
return false;
|
|
1332
|
-
}
|
|
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)) {
|
|
1333
1811
|
try {
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
turnId2
|
|
1341
|
-
);
|
|
1342
|
-
writeLastDispatch(
|
|
1343
|
-
options.stateDir,
|
|
1344
|
-
candidate,
|
|
1345
|
-
"steer",
|
|
1346
|
-
client.threadId,
|
|
1347
|
-
turnId2
|
|
1348
|
-
);
|
|
1349
|
-
logStatus(`steered active turn with ${candidate.fileName}`);
|
|
1350
|
-
return true;
|
|
1351
|
-
} catch (error) {
|
|
1352
|
-
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
1353
|
-
if (!client.isBusy()) {
|
|
1354
|
-
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1355
|
-
}
|
|
1356
|
-
if (shouldRetrySteerAsStart(error)) {
|
|
1357
|
-
client.activeTurnId = null;
|
|
1358
|
-
client.turnStartedAt = null;
|
|
1359
|
-
logStatus(
|
|
1360
|
-
`steer fallback -> start for ${candidate.fileName} (${String(error)})`
|
|
1361
|
-
);
|
|
1362
|
-
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;
|
|
1363
1818
|
}
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
const turnId = await client.startTurn(input);
|
|
1368
|
-
writeProcessedMarker(
|
|
1369
|
-
options.stateDir,
|
|
1370
|
-
candidate,
|
|
1371
|
-
"start",
|
|
1372
|
-
client.threadId,
|
|
1373
|
-
turnId
|
|
1374
|
-
);
|
|
1375
|
-
writeLastDispatch(
|
|
1376
|
-
options.stateDir,
|
|
1377
|
-
candidate,
|
|
1378
|
-
"start",
|
|
1379
|
-
client.threadId,
|
|
1380
|
-
turnId
|
|
1381
|
-
);
|
|
1382
|
-
logStatus(`dispatched ${candidate.fileName} to thread ${client.threadId}`);
|
|
1383
|
-
return true;
|
|
1384
|
-
}
|
|
1385
|
-
async function runScan(options, cutoff, client) {
|
|
1386
|
-
const { heartbeats, candidates } = getPendingCandidates(options, cutoff);
|
|
1387
|
-
for (const candidate of candidates) {
|
|
1388
|
-
if (options.dryRun) {
|
|
1389
|
-
logStatus(`dry-run candidate ${candidate.fileName}`);
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
|
-
if (!client) {
|
|
1393
|
-
throw new Error("App Server client is not available");
|
|
1394
|
-
}
|
|
1395
|
-
const dispatched = await dispatchCandidate(
|
|
1396
|
-
client,
|
|
1397
|
-
options,
|
|
1398
|
-
candidate,
|
|
1399
|
-
heartbeats
|
|
1400
|
-
);
|
|
1401
|
-
if (!dispatched && options.busyMode === "wait") {
|
|
1402
|
-
return false;
|
|
1819
|
+
} catch {
|
|
1403
1820
|
}
|
|
1404
|
-
return true;
|
|
1405
1821
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
async function waitForTurnDrain(options, client, health) {
|
|
1409
|
-
const deadline = Date.now() + options.waitAfterDispatchSeconds * 1e3;
|
|
1410
|
-
while (Date.now() < deadline) {
|
|
1411
|
-
writeHeartbeat(options, client, health);
|
|
1412
|
-
if (!client.activeTurnId) {
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
await delay(1e3);
|
|
1822
|
+
if (lookbackCutoff) {
|
|
1823
|
+
return lookbackCutoff;
|
|
1416
1824
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
const commsDir = resolveCommsDir(repoRoot, parsed.commsDir);
|
|
1422
|
-
const preferredAgentName = resolvePreferredAgentName(parsed.agentName);
|
|
1423
|
-
const stateDir = resolveStateDir(
|
|
1424
|
-
repoRoot,
|
|
1425
|
-
parsed.stateDir,
|
|
1426
|
-
preferredAgentName
|
|
1427
|
-
);
|
|
1428
|
-
const agentName = resolveAgentName(preferredAgentName, stateDir);
|
|
1429
|
-
const agentId = resolveAgentId(agentName);
|
|
1430
|
-
persistAgentName(stateDir, agentName);
|
|
1431
|
-
const gatewayTokenFile = parsed.gatewayTokenFile?.trim() || process.env.TAP_GATEWAY_TOKEN_FILE?.trim() || null;
|
|
1432
|
-
const appServerUrl = parsed.appServerUrl?.trim() || process.env.CODEX_APP_SERVER_URL || DEFAULT_APP_SERVER_URL;
|
|
1433
|
-
return {
|
|
1434
|
-
repoRoot,
|
|
1435
|
-
commsDir,
|
|
1436
|
-
agentId,
|
|
1437
|
-
stateDir,
|
|
1438
|
-
agentName,
|
|
1439
|
-
pollSeconds: parsed.pollSeconds ?? 5,
|
|
1440
|
-
reconnectSeconds: parsed.reconnectSeconds ?? 5,
|
|
1441
|
-
messageLookbackMinutes: parsed.messageLookbackMinutes ?? 10,
|
|
1442
|
-
processExistingMessages: parsed.processExistingMessages,
|
|
1443
|
-
dryRun: parsed.dryRun,
|
|
1444
|
-
runOnce: parsed.runOnce,
|
|
1445
|
-
waitAfterDispatchSeconds: parsed.waitAfterDispatchSeconds ?? 0,
|
|
1446
|
-
appServerUrl,
|
|
1447
|
-
connectAppServerUrl: appServerUrl,
|
|
1448
|
-
gatewayToken: gatewayTokenFile ? readGatewayTokenFile(gatewayTokenFile) : null,
|
|
1449
|
-
gatewayTokenFile,
|
|
1450
|
-
busyMode: parsed.busyMode ?? "steer",
|
|
1451
|
-
threadId: parsed.threadId?.trim() || null,
|
|
1452
|
-
ephemeral: parsed.ephemeral
|
|
1453
|
-
};
|
|
1825
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1826
|
+
writeFileSync5(cutoffPath, `${cutoff.toISOString()}
|
|
1827
|
+
`, "utf8");
|
|
1828
|
+
return cutoff;
|
|
1454
1829
|
}
|
|
1455
1830
|
async function main() {
|
|
1456
1831
|
const options = buildOptions(process.argv.slice(2));
|
|
1832
|
+
configureBridgeLogging(options.logLevel);
|
|
1833
|
+
const logger = createBridgeLogger("bridge");
|
|
1457
1834
|
const cutoff = getGeneralInboxCutoff(
|
|
1458
1835
|
options.stateDir,
|
|
1459
1836
|
options.messageLookbackMinutes,
|
|
@@ -1463,28 +1840,20 @@ async function main() {
|
|
|
1463
1840
|
options.stateDir,
|
|
1464
1841
|
options.appServerUrl
|
|
1465
1842
|
);
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
console.log(
|
|
1479
|
-
` lookback: ${options.processExistingMessages ? "existing messages" : `${options.messageLookbackMinutes} minute(s)`}`
|
|
1480
|
-
);
|
|
1481
|
-
if (options.threadId || initialSavedThread?.threadId) {
|
|
1482
|
-
console.log(
|
|
1483
|
-
` thread: ${options.threadId ?? initialSavedThread?.threadId}`
|
|
1484
|
-
);
|
|
1485
|
-
}
|
|
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
|
+
});
|
|
1486
1855
|
if (options.dryRun) {
|
|
1487
|
-
|
|
1856
|
+
logger.info("dry-run mode enabled");
|
|
1488
1857
|
}
|
|
1489
1858
|
let client = null;
|
|
1490
1859
|
const health = {
|
|
@@ -1496,7 +1865,7 @@ async function main() {
|
|
|
1496
1865
|
if (!client || !client.connected) {
|
|
1497
1866
|
client = new AppServerClient(
|
|
1498
1867
|
options.connectAppServerUrl,
|
|
1499
|
-
|
|
1868
|
+
createBridgeLogger("app-server"),
|
|
1500
1869
|
options.gatewayToken
|
|
1501
1870
|
);
|
|
1502
1871
|
await client.connect();
|
|
@@ -1504,6 +1873,10 @@ async function main() {
|
|
|
1504
1873
|
options.stateDir,
|
|
1505
1874
|
options.appServerUrl
|
|
1506
1875
|
);
|
|
1876
|
+
logger.debug("resolved resumable thread state", {
|
|
1877
|
+
savedThreadId: savedThread?.threadId,
|
|
1878
|
+
savedThreadCwd: savedThread?.cwd ?? null
|
|
1879
|
+
});
|
|
1507
1880
|
const threadId = await client.ensureThread(
|
|
1508
1881
|
options.threadId,
|
|
1509
1882
|
savedThread,
|
|
@@ -1528,8 +1901,14 @@ async function main() {
|
|
|
1528
1901
|
}
|
|
1529
1902
|
}
|
|
1530
1903
|
}
|
|
1531
|
-
const
|
|
1532
|
-
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) {
|
|
1533
1912
|
await waitForTurnDrain(options, client, health);
|
|
1534
1913
|
}
|
|
1535
1914
|
health.consecutiveFailureCount = 0;
|
|
@@ -1537,22 +1916,28 @@ async function main() {
|
|
|
1537
1916
|
if (options.runOnce) {
|
|
1538
1917
|
break;
|
|
1539
1918
|
}
|
|
1540
|
-
await
|
|
1919
|
+
await delay2(options.pollSeconds * 1e3);
|
|
1541
1920
|
} catch (error) {
|
|
1542
1921
|
const message = error instanceof Error ? error.message : String(error);
|
|
1543
|
-
|
|
1922
|
+
logger.error("bridge error", {
|
|
1923
|
+
error: sanitizeErrorForPersistence(message)
|
|
1924
|
+
});
|
|
1544
1925
|
if (client) {
|
|
1545
|
-
client.lastError = message;
|
|
1926
|
+
client.lastError = sanitizeErrorForPersistence(message);
|
|
1546
1927
|
}
|
|
1547
1928
|
health.consecutiveFailureCount += 1;
|
|
1548
1929
|
writeHeartbeat(options, client, health);
|
|
1549
1930
|
if (options.runOnce) {
|
|
1550
|
-
|
|
1931
|
+
const sanitized = sanitizeErrorForPersistence(message);
|
|
1932
|
+
throw new Error(sanitized ?? message);
|
|
1551
1933
|
}
|
|
1552
1934
|
client?.disconnect().catch(() => void 0);
|
|
1553
1935
|
client = null;
|
|
1554
|
-
|
|
1555
|
-
|
|
1936
|
+
logger.warn("reconnecting after bridge error", {
|
|
1937
|
+
reconnectSeconds: options.reconnectSeconds,
|
|
1938
|
+
consecutiveFailureCount: health.consecutiveFailureCount
|
|
1939
|
+
});
|
|
1940
|
+
await delay2(options.reconnectSeconds * 1e3);
|
|
1556
1941
|
}
|
|
1557
1942
|
}
|
|
1558
1943
|
await client?.disconnect();
|
|
@@ -1560,24 +1945,32 @@ async function main() {
|
|
|
1560
1945
|
function isDirectExecution() {
|
|
1561
1946
|
const entry = process.argv[1];
|
|
1562
1947
|
if (!entry) return false;
|
|
1563
|
-
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;
|
|
1564
1958
|
}
|
|
1565
|
-
if (
|
|
1959
|
+
if (isDirectExecution2()) {
|
|
1566
1960
|
main().catch((error) => {
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
);
|
|
1961
|
+
const raw = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1962
|
+
console.error(sanitizeErrorForPersistence(raw));
|
|
1570
1963
|
process.exitCode = 1;
|
|
1571
1964
|
});
|
|
1572
1965
|
}
|
|
1573
1966
|
|
|
1574
1967
|
// src/bridges/codex-app-server-bridge.ts
|
|
1575
|
-
function
|
|
1968
|
+
function isDirectExecution3() {
|
|
1576
1969
|
const entry = process.argv[1];
|
|
1577
1970
|
if (!entry) return false;
|
|
1578
|
-
return import.meta.url ===
|
|
1971
|
+
return import.meta.url === pathToFileURL3(resolve6(entry)).href;
|
|
1579
1972
|
}
|
|
1580
|
-
if (
|
|
1973
|
+
if (isDirectExecution3()) {
|
|
1581
1974
|
main().catch((error) => {
|
|
1582
1975
|
console.error(
|
|
1583
1976
|
error instanceof Error ? error.stack ?? error.message : String(error)
|
|
@@ -1586,19 +1979,77 @@ if (isDirectExecution2()) {
|
|
|
1586
1979
|
});
|
|
1587
1980
|
}
|
|
1588
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,
|
|
1589
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,
|
|
1590
1998
|
buildOptions,
|
|
1591
1999
|
buildUserInput,
|
|
2000
|
+
canonicalize,
|
|
1592
2001
|
chooseLoadedThreadForCwd,
|
|
2002
|
+
collectCandidates,
|
|
2003
|
+
dispatchCandidate,
|
|
2004
|
+
formatAgentLabel,
|
|
2005
|
+
formatJsonRpcError,
|
|
2006
|
+
getGeneralInboxCutoff,
|
|
2007
|
+
getInboxRoute,
|
|
2008
|
+
getInboxRouteFromFilename,
|
|
2009
|
+
getPendingCandidates,
|
|
2010
|
+
getProcessedMarkerPath,
|
|
2011
|
+
isDirectExecution,
|
|
1593
2012
|
isOwnMessageSender,
|
|
2013
|
+
isTurnStale,
|
|
2014
|
+
isTurnStuckOnApproval,
|
|
2015
|
+
loadHeartbeats,
|
|
1594
2016
|
loadResumableThreadState,
|
|
1595
2017
|
main,
|
|
1596
2018
|
maybeBootstrapHeadlessTurn,
|
|
2019
|
+
normalizeAgentToken,
|
|
2020
|
+
normalizeThreadCwd,
|
|
2021
|
+
parseArgs,
|
|
2022
|
+
parseBridgeFrontmatter,
|
|
2023
|
+
persistAgentName,
|
|
2024
|
+
persistThreadState,
|
|
2025
|
+
readGatewayTokenFile,
|
|
2026
|
+
readHeartbeatState,
|
|
2027
|
+
readSocketData,
|
|
2028
|
+
readThreadState,
|
|
1597
2029
|
recipientMatchesAgent,
|
|
2030
|
+
refreshAgentIdentity,
|
|
2031
|
+
releaseCommsLock,
|
|
1598
2032
|
resolveAddressLabel,
|
|
1599
2033
|
resolveAgentId,
|
|
2034
|
+
resolveAgentName,
|
|
2035
|
+
resolveCommsDir,
|
|
1600
2036
|
resolveCurrentAgentName,
|
|
2037
|
+
resolvePreferredAgentName,
|
|
2038
|
+
resolveRepoRoot,
|
|
2039
|
+
resolveStateDir,
|
|
2040
|
+
resolveTapConfigPath,
|
|
2041
|
+
runScan,
|
|
2042
|
+
sanitizeErrorForPersistence,
|
|
2043
|
+
sanitizeStateSegment,
|
|
2044
|
+
shouldRetrySteerAsStart,
|
|
2045
|
+
shouldSkipInHeadlessMode,
|
|
2046
|
+
stripBridgeFrontmatter,
|
|
1601
2047
|
threadCwdMatches,
|
|
1602
|
-
|
|
2048
|
+
updateCommsHeartbeat,
|
|
2049
|
+
waitForTurnCompletion,
|
|
2050
|
+
waitForTurnDrain,
|
|
2051
|
+
writeHeartbeat,
|
|
2052
|
+
writeLastDispatch,
|
|
2053
|
+
writeProcessedMarker
|
|
1603
2054
|
};
|
|
1604
2055
|
//# sourceMappingURL=codex-app-server-bridge.mjs.map
|