@hua-labs/tap 0.4.2 → 0.5.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 +27 -87
- package/dist/bridges/codex-app-server-auth-gateway.mjs +23 -4
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +5 -224
- package/dist/bridges/codex-app-server-bridge.mjs +706 -1109
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +15 -1
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/bridges/gemini-ide-companion-runner.mjs +28 -21973
- package/dist/bridges/gemini-ide-companion-runner.mjs.map +1 -1
- package/dist/cli.mjs +2014 -646
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +59 -1
- package/dist/index.mjs +5550 -26566
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +1235 -21479
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +6 -4
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
// src/bridges/codex-app-server-bridge.ts
|
|
2
|
-
import { pathToFileURL as
|
|
3
|
-
import { resolve as
|
|
2
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3
|
+
import { resolve as resolve2 } from "path";
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// scripts/codex-app-server-bridge.ts
|
|
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";
|
|
6
19
|
var DEFAULT_AGENT = String.fromCharCode(50728);
|
|
7
20
|
var DEFAULT_APP_SERVER_URL = "ws://127.0.0.1:4501";
|
|
8
21
|
var AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
@@ -20,61 +33,6 @@ var HEADLESS_WARMUP_PROMPT = [
|
|
|
20
33
|
var HEADLESS_WARMUP_TIMEOUT_MS = 3e4;
|
|
21
34
|
var TURN_COMPLETION_POLL_MS = 250;
|
|
22
35
|
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
|
-
}
|
|
78
36
|
function normalizeThreadCwd(cwd) {
|
|
79
37
|
return resolve(cwd).replace(/\\/g, "/").toLowerCase();
|
|
80
38
|
}
|
|
@@ -101,170 +59,6 @@ function chooseLoadedThreadForCwd(cwd, threads) {
|
|
|
101
59
|
});
|
|
102
60
|
return matching[0] ?? null;
|
|
103
61
|
}
|
|
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
|
-
}
|
|
268
62
|
function printHelp() {
|
|
269
63
|
console.log(`Codex App Server bridge
|
|
270
64
|
|
|
@@ -286,7 +80,6 @@ Options:
|
|
|
286
80
|
--app-server-url=<ws-url>
|
|
287
81
|
--gateway-token-file=<path>
|
|
288
82
|
--busy-mode=wait|steer
|
|
289
|
-
--log-level=debug|info|warn|error
|
|
290
83
|
--thread-id=<id>
|
|
291
84
|
--ephemeral
|
|
292
85
|
--help
|
|
@@ -445,42 +238,50 @@ function parseArgs(argv) {
|
|
|
445
238
|
}
|
|
446
239
|
continue;
|
|
447
240
|
}
|
|
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
|
-
}
|
|
459
241
|
throw new Error(`Unknown argument: ${flag}`);
|
|
460
242
|
}
|
|
461
243
|
return parsed;
|
|
462
244
|
}
|
|
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
|
+
}
|
|
463
268
|
function resolveRepoRoot(explicit) {
|
|
464
269
|
if (explicit) {
|
|
465
|
-
return
|
|
270
|
+
return resolve(explicit);
|
|
466
271
|
}
|
|
467
272
|
return process.cwd();
|
|
468
273
|
}
|
|
469
|
-
function resolveTapConfigPath(repoRoot, input) {
|
|
470
|
-
const converted = normalizeTapPath(input);
|
|
471
|
-
return isAbsolute2(converted) ? resolve3(converted) : resolve3(repoRoot, converted);
|
|
472
|
-
}
|
|
473
274
|
function resolveCommsDir(repoRoot, explicit) {
|
|
474
275
|
if (explicit) {
|
|
475
|
-
return
|
|
276
|
+
return resolve(convertTapPath(explicit));
|
|
476
277
|
}
|
|
477
|
-
const tapConfigPath =
|
|
478
|
-
if (!
|
|
278
|
+
const tapConfigPath = join(repoRoot, ".tap-config");
|
|
279
|
+
if (!existsSync(tapConfigPath)) {
|
|
479
280
|
throw new Error(
|
|
480
281
|
"Unable to resolve comms directory. Pass --comms-dir explicitly."
|
|
481
282
|
);
|
|
482
283
|
}
|
|
483
|
-
const configText =
|
|
284
|
+
const configText = readFileSync(tapConfigPath, "utf8");
|
|
484
285
|
const match = configText.match(/^TAP_COMMS_DIR="?(.*?)"?$/m);
|
|
485
286
|
if (!match?.[1]) {
|
|
486
287
|
throw new Error(
|
|
@@ -501,195 +302,352 @@ function resolvePreferredAgentName(requested) {
|
|
|
501
302
|
}
|
|
502
303
|
return null;
|
|
503
304
|
}
|
|
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
|
+
}
|
|
504
315
|
function sanitizeStateSegment(agentName) {
|
|
505
316
|
const normalized = agentName.trim().replace(/[<>:"/\\|?*\x00-\x1f]/g, "-").replace(/[. ]+$/g, "");
|
|
506
317
|
return normalized || "agent";
|
|
507
318
|
}
|
|
508
319
|
function buildDefaultStateDir(repoRoot, preferredAgentName) {
|
|
509
320
|
const suffix = preferredAgentName?.trim() ? `-${sanitizeStateSegment(preferredAgentName)}` : "";
|
|
510
|
-
return
|
|
321
|
+
return resolve(join(repoRoot, ".tmp", `codex-app-server-bridge${suffix}`));
|
|
511
322
|
}
|
|
512
323
|
function resolveStateDir(repoRoot, explicit, preferredAgentName) {
|
|
513
|
-
const root = explicit ?
|
|
324
|
+
const root = explicit ? resolve(explicit) : buildDefaultStateDir(repoRoot, preferredAgentName);
|
|
514
325
|
ensureDir(root);
|
|
515
|
-
ensureDir(
|
|
516
|
-
ensureDir(
|
|
326
|
+
ensureDir(join(root, "processed"));
|
|
327
|
+
ensureDir(join(root, "logs"));
|
|
517
328
|
return root;
|
|
518
329
|
}
|
|
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
|
+
}
|
|
519
351
|
function readGatewayTokenFile(tokenFile) {
|
|
520
|
-
const token =
|
|
352
|
+
const token = readFileSync(tokenFile, "utf8").trim();
|
|
521
353
|
if (!token) {
|
|
522
354
|
throw new Error(`Gateway token file is empty: ${tokenFile}`);
|
|
523
355
|
}
|
|
524
356
|
return token;
|
|
525
357
|
}
|
|
526
|
-
function
|
|
527
|
-
const
|
|
528
|
-
|
|
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
|
-
};
|
|
563
|
-
}
|
|
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];
|
|
358
|
+
function resolveTapConfigPath(repoRoot, input) {
|
|
359
|
+
const converted = convertTapPath(input);
|
|
360
|
+
return isAbsolute(converted) ? resolve(converted) : resolve(repoRoot, converted);
|
|
583
361
|
}
|
|
584
|
-
function
|
|
585
|
-
|
|
586
|
-
|
|
362
|
+
function readThreadState(stateDir) {
|
|
363
|
+
const threadPath = join(stateDir, "thread.json");
|
|
364
|
+
if (!existsSync(threadPath)) {
|
|
365
|
+
return null;
|
|
587
366
|
}
|
|
588
|
-
|
|
589
|
-
|
|
367
|
+
try {
|
|
368
|
+
const parsed = JSON.parse(
|
|
369
|
+
readFileSync(threadPath, "utf8")
|
|
370
|
+
);
|
|
371
|
+
if (parsed.threadId) {
|
|
372
|
+
return parsed;
|
|
373
|
+
}
|
|
374
|
+
} catch {
|
|
375
|
+
return null;
|
|
590
376
|
}
|
|
591
|
-
|
|
592
|
-
|
|
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;
|
|
393
|
+
}
|
|
394
|
+
const parsed = Date.parse(value);
|
|
395
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
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;
|
|
593
404
|
}
|
|
594
|
-
return
|
|
405
|
+
return isAbsolute(normalized) || /^[A-Za-z]:[\\/]/.test(normalized) || normalized.startsWith("\\\\");
|
|
595
406
|
}
|
|
596
|
-
function
|
|
597
|
-
|
|
598
|
-
|
|
407
|
+
function loadResumableThreadState(stateDir, fallbackAppServerUrl) {
|
|
408
|
+
const savedThread = readThreadState(stateDir);
|
|
409
|
+
const heartbeat = readHeartbeatState(stateDir);
|
|
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;
|
|
599
439
|
}
|
|
600
|
-
|
|
601
|
-
|
|
440
|
+
return preferred;
|
|
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"
|
|
602
455
|
);
|
|
603
|
-
|
|
604
|
-
|
|
456
|
+
}
|
|
457
|
+
function getGeneralInboxCutoff(stateDir, lookbackMinutes, processExistingMessages) {
|
|
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
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
473
|
+
writeFileSync(cutoffPath, `${cutoff.toISOString()}
|
|
474
|
+
`, "utf8");
|
|
475
|
+
return cutoff;
|
|
476
|
+
}
|
|
477
|
+
function recipientMatchesAgent(recipient, agentId, agentName) {
|
|
478
|
+
const normalizedRecipient = recipient.trim();
|
|
479
|
+
if (!normalizedRecipient) {
|
|
480
|
+
return false;
|
|
605
481
|
}
|
|
606
|
-
|
|
482
|
+
const aliases = /* @__PURE__ */ new Set([
|
|
483
|
+
agentId.trim(),
|
|
484
|
+
agentId.trim().replace(/-/g, "_"),
|
|
485
|
+
agentId.trim().replace(/_/g, "-"),
|
|
486
|
+
agentName.trim(),
|
|
487
|
+
agentName.trim().replace(/-/g, "_"),
|
|
488
|
+
agentName.trim().replace(/_/g, "-")
|
|
489
|
+
]);
|
|
490
|
+
return normalizedRecipient === "\uC804\uCCB4" || normalizedRecipient === "all" || aliases.has(normalizedRecipient);
|
|
607
491
|
}
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
|
|
492
|
+
function isOwnMessageSender(sender, agentId, agentName) {
|
|
493
|
+
const normalizedSender = sender.trim();
|
|
494
|
+
if (!normalizedSender) {
|
|
495
|
+
return false;
|
|
611
496
|
}
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
497
|
+
const aliases = /* @__PURE__ */ new Set([
|
|
498
|
+
agentId.trim(),
|
|
499
|
+
agentId.trim().replace(/-/g, "_"),
|
|
500
|
+
agentId.trim().replace(/_/g, "-"),
|
|
501
|
+
agentName.trim(),
|
|
502
|
+
agentName.trim().replace(/-/g, "_"),
|
|
503
|
+
agentName.trim().replace(/_/g, "-")
|
|
504
|
+
]);
|
|
505
|
+
return aliases.has(normalizedSender);
|
|
506
|
+
}
|
|
507
|
+
function decodeRouteSegment(value) {
|
|
508
|
+
try {
|
|
509
|
+
return decodeURIComponent(value);
|
|
510
|
+
} catch {
|
|
511
|
+
return value;
|
|
617
512
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
513
|
+
}
|
|
514
|
+
function parseInboxFrontmatter(body) {
|
|
515
|
+
if (!body) {
|
|
516
|
+
return null;
|
|
621
517
|
}
|
|
622
|
-
|
|
518
|
+
const frontmatter = body.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
519
|
+
if (!frontmatter) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
let sender = "";
|
|
523
|
+
let recipient = "";
|
|
524
|
+
let subject = "";
|
|
525
|
+
for (const line of frontmatter[1].split(/\r?\n/)) {
|
|
526
|
+
const separator = line.indexOf(":");
|
|
527
|
+
if (separator <= 0) continue;
|
|
528
|
+
const key = line.slice(0, separator).trim();
|
|
529
|
+
const value = line.slice(separator + 1).trim();
|
|
530
|
+
if (key === "from") sender = value;
|
|
531
|
+
if (key === "to") recipient = value;
|
|
532
|
+
if (key === "subject") subject = value;
|
|
533
|
+
}
|
|
534
|
+
if (!sender || !recipient || !subject) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
return { sender, recipient, subject };
|
|
623
538
|
}
|
|
624
|
-
function
|
|
625
|
-
const
|
|
539
|
+
function getInboxRoute(fileName, body) {
|
|
540
|
+
const frontmatterRoute = parseInboxFrontmatter(body);
|
|
541
|
+
if (frontmatterRoute) {
|
|
542
|
+
return frontmatterRoute;
|
|
543
|
+
}
|
|
544
|
+
const stem = fileName.replace(/\.md$/i, "");
|
|
545
|
+
const parts = stem.split("-");
|
|
546
|
+
let offset = 0;
|
|
547
|
+
if (parts[0] && /^\d{8}$/.test(parts[0])) {
|
|
548
|
+
offset = 1;
|
|
549
|
+
}
|
|
626
550
|
return {
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
}
|
|
551
|
+
sender: decodeRouteSegment(parts[offset] ?? ""),
|
|
552
|
+
recipient: decodeRouteSegment(parts[offset + 1] ?? ""),
|
|
553
|
+
subject: decodeRouteSegment(parts.slice(offset + 2).join("-"))
|
|
639
554
|
};
|
|
640
555
|
}
|
|
641
|
-
|
|
642
|
-
// ../../scripts/bridge/bridge-candidates.ts
|
|
643
|
-
var routingLogger = createBridgeLogger("routing");
|
|
644
556
|
function buildMarkerId(filePath, mtimeMs) {
|
|
645
557
|
return createHash("sha1").update(`${filePath}|${mtimeMs}`).digest("hex");
|
|
646
558
|
}
|
|
647
559
|
function getProcessedMarkerPath(stateDir, markerId) {
|
|
648
|
-
return
|
|
560
|
+
return join(stateDir, "processed", `${markerId}.done`);
|
|
649
561
|
}
|
|
650
562
|
function loadHeartbeats(commsDir) {
|
|
651
563
|
try {
|
|
652
|
-
return JSON.parse(
|
|
564
|
+
return JSON.parse(readFileSync(join(commsDir, "heartbeats.json"), "utf8"));
|
|
653
565
|
} catch {
|
|
654
566
|
return {};
|
|
655
567
|
}
|
|
656
568
|
}
|
|
569
|
+
function formatAgentLabel(agentIdOrName, displayName) {
|
|
570
|
+
const normalizedId = agentIdOrName.trim();
|
|
571
|
+
const normalizedName = displayName?.trim();
|
|
572
|
+
if (!normalizedId) {
|
|
573
|
+
return normalizedName ?? agentIdOrName;
|
|
574
|
+
}
|
|
575
|
+
if (!normalizedName || normalizedName === normalizedId) {
|
|
576
|
+
return normalizedId;
|
|
577
|
+
}
|
|
578
|
+
return `${normalizedName} [${normalizedId}]`;
|
|
579
|
+
}
|
|
580
|
+
function resolveAddressLabel(address, heartbeats) {
|
|
581
|
+
const normalized = address.trim();
|
|
582
|
+
if (!normalized || normalized === "\uC804\uCCB4" || normalized === "all") {
|
|
583
|
+
return address;
|
|
584
|
+
}
|
|
585
|
+
const direct = heartbeats[normalized];
|
|
586
|
+
if (direct?.agent?.trim()) {
|
|
587
|
+
return formatAgentLabel(normalized, direct.agent);
|
|
588
|
+
}
|
|
589
|
+
for (const [agentId, heartbeat] of Object.entries(heartbeats)) {
|
|
590
|
+
if (heartbeat.agent?.trim() === normalized) {
|
|
591
|
+
return formatAgentLabel(agentId, heartbeat.agent);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return normalized;
|
|
595
|
+
}
|
|
596
|
+
function resolveCurrentAgentName(agentId, fallbackAgentName, heartbeats) {
|
|
597
|
+
const currentName = heartbeats[agentId]?.agent?.trim();
|
|
598
|
+
if (currentName) {
|
|
599
|
+
return currentName;
|
|
600
|
+
}
|
|
601
|
+
for (const heartbeat of Object.values(heartbeats)) {
|
|
602
|
+
if (heartbeat.id?.trim() === agentId && heartbeat.agent?.trim()) {
|
|
603
|
+
return heartbeat.agent.trim();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return fallbackAgentName;
|
|
607
|
+
}
|
|
608
|
+
function refreshAgentIdentity(options, heartbeats) {
|
|
609
|
+
const nextAgentName = resolveCurrentAgentName(
|
|
610
|
+
options.agentId,
|
|
611
|
+
options.agentName,
|
|
612
|
+
heartbeats
|
|
613
|
+
);
|
|
614
|
+
if (nextAgentName !== options.agentName) {
|
|
615
|
+
options.agentName = nextAgentName;
|
|
616
|
+
persistAgentName(options.stateDir, nextAgentName);
|
|
617
|
+
}
|
|
618
|
+
return nextAgentName;
|
|
619
|
+
}
|
|
620
|
+
var HEADLESS_SKIP_PATTERNS = [
|
|
621
|
+
/리뷰\s*요청/,
|
|
622
|
+
/review[- ]?request/i,
|
|
623
|
+
/재리뷰/,
|
|
624
|
+
/re-?review/i
|
|
625
|
+
];
|
|
657
626
|
function shouldSkipInHeadlessMode(fileName, body) {
|
|
658
627
|
if (process.env.TAP_HEADLESS !== "true") return false;
|
|
659
628
|
const combined = `${fileName}
|
|
660
629
|
${body}`;
|
|
661
630
|
return HEADLESS_SKIP_PATTERNS.some((p) => p.test(combined));
|
|
662
631
|
}
|
|
663
|
-
function collectCandidates(inboxDir, agentId, agentName
|
|
632
|
+
function collectCandidates(inboxDir, agentId, agentName) {
|
|
664
633
|
const entries = readdirSync(inboxDir, { withFileTypes: true }).filter(
|
|
665
634
|
(entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")
|
|
666
635
|
).map((entry) => {
|
|
667
|
-
const filePath =
|
|
636
|
+
const filePath = join(inboxDir, entry.name);
|
|
668
637
|
const stats = statSync(filePath);
|
|
669
638
|
return { entry, filePath, stats };
|
|
670
639
|
}).sort((left, right) => left.stats.mtimeMs - right.stats.mtimeMs);
|
|
671
640
|
const candidates = [];
|
|
672
|
-
let filteredByRecipient = 0;
|
|
673
|
-
let filteredBySelf = 0;
|
|
674
|
-
let filteredByHeadless = 0;
|
|
675
641
|
for (const item of entries) {
|
|
676
|
-
|
|
677
|
-
try {
|
|
678
|
-
body = readFileSync4(item.filePath, "utf8");
|
|
679
|
-
} catch {
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
642
|
+
const body = readFileSync(item.filePath, "utf8");
|
|
682
643
|
const route = getInboxRoute(item.entry.name, body);
|
|
683
|
-
if (!recipientMatchesAgent(route.recipient, agentId, agentName)
|
|
684
|
-
filteredByRecipient += 1;
|
|
644
|
+
if (!recipientMatchesAgent(route.recipient, agentId, agentName)) {
|
|
685
645
|
continue;
|
|
686
646
|
}
|
|
687
|
-
if (isOwnMessageSender(route.sender, agentId, agentName)
|
|
688
|
-
filteredBySelf += 1;
|
|
647
|
+
if (isOwnMessageSender(route.sender, agentId, agentName)) {
|
|
689
648
|
continue;
|
|
690
649
|
}
|
|
691
650
|
if (shouldSkipInHeadlessMode(item.entry.name, body)) {
|
|
692
|
-
filteredByHeadless += 1;
|
|
693
651
|
continue;
|
|
694
652
|
}
|
|
695
653
|
candidates.push({
|
|
@@ -699,58 +657,34 @@ function collectCandidates(inboxDir, agentId, agentName, aliasName) {
|
|
|
699
657
|
sender: route.sender,
|
|
700
658
|
recipient: route.recipient,
|
|
701
659
|
subject: route.subject,
|
|
702
|
-
body
|
|
660
|
+
body,
|
|
703
661
|
mtimeMs: item.stats.mtimeMs
|
|
704
662
|
});
|
|
705
663
|
}
|
|
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
|
-
});
|
|
717
664
|
return candidates;
|
|
718
665
|
}
|
|
719
666
|
function getPendingCandidates(options, cutoff) {
|
|
720
|
-
const inboxDir =
|
|
721
|
-
if (!
|
|
667
|
+
const inboxDir = join(options.commsDir, "inbox");
|
|
668
|
+
if (!existsSync(inboxDir)) {
|
|
722
669
|
throw new Error(`Inbox directory not found: ${inboxDir}`);
|
|
723
670
|
}
|
|
724
671
|
const heartbeats = loadHeartbeats(options.commsDir);
|
|
725
|
-
const
|
|
672
|
+
const agentName = refreshAgentIdentity(options, heartbeats);
|
|
726
673
|
const cutoffMs = cutoff.getTime();
|
|
727
674
|
const candidates = collectCandidates(
|
|
728
675
|
inboxDir,
|
|
729
676
|
options.agentId,
|
|
730
|
-
|
|
731
|
-
// M205: Also accept messages addressed to the heartbeat-refreshed name
|
|
732
|
-
refreshedName !== options.agentName ? refreshedName : void 0
|
|
677
|
+
agentName
|
|
733
678
|
).filter((candidate) => {
|
|
734
679
|
if (candidate.mtimeMs < cutoffMs) {
|
|
735
680
|
return false;
|
|
736
681
|
}
|
|
737
|
-
return !
|
|
682
|
+
return !existsSync(
|
|
738
683
|
getProcessedMarkerPath(options.stateDir, candidate.markerId)
|
|
739
684
|
);
|
|
740
685
|
});
|
|
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
|
-
});
|
|
748
686
|
return { heartbeats, candidates };
|
|
749
687
|
}
|
|
750
|
-
|
|
751
|
-
// ../../scripts/bridge/bridge-format.ts
|
|
752
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
753
|
-
import { join as join5 } from "path";
|
|
754
688
|
function buildUserInput(candidate, agentName, heartbeats) {
|
|
755
689
|
const sender = resolveAddressLabel(candidate.sender || "unknown", heartbeats);
|
|
756
690
|
const recipient = resolveAddressLabel(
|
|
@@ -789,7 +723,7 @@ function writeProcessedMarker(stateDir, candidate, dispatchMode, threadId, turnI
|
|
|
789
723
|
turnId,
|
|
790
724
|
markedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
791
725
|
};
|
|
792
|
-
|
|
726
|
+
writeFileSync(
|
|
793
727
|
getProcessedMarkerPath(stateDir, candidate.markerId),
|
|
794
728
|
`${JSON.stringify(payload, null, 2)}
|
|
795
729
|
`,
|
|
@@ -809,369 +743,83 @@ function writeLastDispatch(stateDir, candidate, dispatchMode, threadId, turnId)
|
|
|
809
743
|
turnId,
|
|
810
744
|
dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
811
745
|
};
|
|
812
|
-
|
|
813
|
-
|
|
746
|
+
writeFileSync(
|
|
747
|
+
join(stateDir, "last-dispatch.json"),
|
|
814
748
|
`${JSON.stringify(payload, null, 2)}
|
|
815
749
|
`,
|
|
816
750
|
"utf8"
|
|
817
751
|
);
|
|
818
752
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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, "***");
|
|
753
|
+
function formatJsonRpcError(error) {
|
|
754
|
+
if (!error) {
|
|
755
|
+
return "Unknown App Server error";
|
|
756
|
+
}
|
|
757
|
+
return JSON.stringify(
|
|
758
|
+
{
|
|
759
|
+
code: error.code,
|
|
760
|
+
message: error.message,
|
|
761
|
+
data: error.data
|
|
762
|
+
},
|
|
763
|
+
null,
|
|
764
|
+
2
|
|
765
|
+
);
|
|
835
766
|
}
|
|
836
767
|
function delay(ms) {
|
|
837
768
|
return new Promise((resolvePromise) => {
|
|
838
769
|
setTimeout(resolvePromise, ms);
|
|
839
770
|
});
|
|
840
771
|
}
|
|
841
|
-
function
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
772
|
+
async function waitForTurnCompletion(client, turnId, timeoutMs) {
|
|
773
|
+
const deadline = Date.now() + timeoutMs;
|
|
774
|
+
let nextRefreshAt = Date.now();
|
|
775
|
+
while (Date.now() < deadline) {
|
|
776
|
+
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
777
|
+
return client.lastTurnStatus;
|
|
778
|
+
}
|
|
779
|
+
if (Date.now() >= nextRefreshAt) {
|
|
780
|
+
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
781
|
+
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
782
|
+
return client.lastTurnStatus;
|
|
783
|
+
}
|
|
784
|
+
nextRefreshAt = Date.now() + TURN_COMPLETION_REFRESH_MS;
|
|
785
|
+
}
|
|
786
|
+
await delay(
|
|
787
|
+
Math.min(TURN_COMPLETION_POLL_MS, Math.max(deadline - Date.now(), 0))
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
791
|
+
if (!client.activeTurnId || client.activeTurnId !== turnId) {
|
|
792
|
+
return client.lastTurnStatus;
|
|
793
|
+
}
|
|
794
|
+
throw new Error(`Timed out waiting for turn ${turnId} to complete`);
|
|
795
|
+
}
|
|
796
|
+
async function maybeBootstrapHeadlessTurn(options, cutoff, client) {
|
|
797
|
+
if (process.env.TAP_HEADLESS !== "true" && process.env.TAP_COLD_START_WARMUP !== "true") {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
const { candidates } = getPendingCandidates(options, cutoff);
|
|
801
|
+
if (candidates.length > 0 || client.activeTurnId || client.lastTurnStatus !== null) {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
logStatus("headless cold-start: sending warmup turn");
|
|
805
|
+
const turnId = await client.startTurn(HEADLESS_WARMUP_PROMPT);
|
|
806
|
+
if (!turnId) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
"Headless cold-start warmup failed: turn/start did not return a turn id. Run: npx @hua-labs/tap doctor"
|
|
809
|
+
);
|
|
845
810
|
}
|
|
846
811
|
try {
|
|
847
|
-
const
|
|
848
|
-
|
|
812
|
+
const status = await waitForTurnCompletion(
|
|
813
|
+
client,
|
|
814
|
+
turnId,
|
|
815
|
+
HEADLESS_WARMUP_TIMEOUT_MS
|
|
849
816
|
);
|
|
850
|
-
if (
|
|
851
|
-
|
|
817
|
+
if (status !== "completed") {
|
|
818
|
+
throw new Error(
|
|
819
|
+
`turn ${turnId} finished with status ${status ?? "unknown"}`
|
|
820
|
+
);
|
|
852
821
|
}
|
|
853
|
-
|
|
854
|
-
return null;
|
|
855
|
-
}
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
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") {
|
|
1145
|
-
return false;
|
|
1146
|
-
}
|
|
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
|
-
});
|
|
822
|
+
logStatus(`headless cold-start warmup completed (${status})`);
|
|
1175
823
|
return true;
|
|
1176
824
|
} catch (error) {
|
|
1177
825
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -1180,8 +828,13 @@ async function maybeBootstrapHeadlessTurn(options, cutoff, client) {
|
|
|
1180
828
|
);
|
|
1181
829
|
}
|
|
1182
830
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
831
|
+
function shouldRetrySteerAsStart(error) {
|
|
832
|
+
if (!(error instanceof Error)) {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
const message = error.message.toLowerCase();
|
|
836
|
+
return message.includes("no active turn") || message.includes("expectedturnid") || message.includes("turn/steer failed") && (message.includes("active turn") || message.includes("not found"));
|
|
837
|
+
}
|
|
1185
838
|
async function readSocketData(data) {
|
|
1186
839
|
if (typeof data === "string") {
|
|
1187
840
|
return data;
|
|
@@ -1199,27 +852,11 @@ async function readSocketData(data) {
|
|
|
1199
852
|
}
|
|
1200
853
|
return String(data);
|
|
1201
854
|
}
|
|
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;
|
|
1217
855
|
var AppServerClient = class {
|
|
1218
856
|
socket = null;
|
|
1219
857
|
url;
|
|
1220
858
|
gatewayToken;
|
|
1221
859
|
logger;
|
|
1222
|
-
clientId = nextAppServerClientId++;
|
|
1223
860
|
nextId = 1;
|
|
1224
861
|
pending = /* @__PURE__ */ new Map();
|
|
1225
862
|
connected = false;
|
|
@@ -1243,12 +880,6 @@ var AppServerClient = class {
|
|
|
1243
880
|
if (this.connected && this.socket?.readyState === WebSocket.OPEN) {
|
|
1244
881
|
return;
|
|
1245
882
|
}
|
|
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
|
-
}
|
|
1252
883
|
const wsOptions = {};
|
|
1253
884
|
if (this.gatewayToken) {
|
|
1254
885
|
wsOptions.protocols = [`${AUTH_SUBPROTOCOL_PREFIX}${this.gatewayToken}`];
|
|
@@ -1274,11 +905,7 @@ var AppServerClient = class {
|
|
|
1274
905
|
"open",
|
|
1275
906
|
() => {
|
|
1276
907
|
this.connected = true;
|
|
1277
|
-
this.logger
|
|
1278
|
-
clientId: this.clientId,
|
|
1279
|
-
url: this.url,
|
|
1280
|
-
authenticated: Boolean(this.gatewayToken)
|
|
1281
|
-
});
|
|
908
|
+
this.logger(`connected to app-server at ${this.url}`);
|
|
1282
909
|
resolveOnce();
|
|
1283
910
|
},
|
|
1284
911
|
{ once: true }
|
|
@@ -1287,12 +914,7 @@ var AppServerClient = class {
|
|
|
1287
914
|
const error = new Error(
|
|
1288
915
|
`Failed to connect to App Server at ${this.url}`
|
|
1289
916
|
);
|
|
1290
|
-
this.lastError =
|
|
1291
|
-
this.logger.error("failed to connect to app-server", {
|
|
1292
|
-
clientId: this.clientId,
|
|
1293
|
-
url: this.url,
|
|
1294
|
-
error: this.lastError
|
|
1295
|
-
});
|
|
917
|
+
this.lastError = error.message;
|
|
1296
918
|
rejectOnce(error);
|
|
1297
919
|
});
|
|
1298
920
|
this.socket?.addEventListener("close", () => {
|
|
@@ -1300,10 +922,7 @@ var AppServerClient = class {
|
|
|
1300
922
|
this.initialized = false;
|
|
1301
923
|
this.activeTurnId = null;
|
|
1302
924
|
this.turnStartedAt = null;
|
|
1303
|
-
this.logger
|
|
1304
|
-
clientId: this.clientId,
|
|
1305
|
-
url: this.url
|
|
1306
|
-
});
|
|
925
|
+
this.logger("disconnected from app-server");
|
|
1307
926
|
this.rejectPending(new Error("App Server connection closed"));
|
|
1308
927
|
});
|
|
1309
928
|
this.socket?.addEventListener("message", (event) => {
|
|
@@ -1340,20 +959,13 @@ var AppServerClient = class {
|
|
|
1340
959
|
});
|
|
1341
960
|
const resumedThreadId = resumeResponse?.thread?.id ?? explicitThreadId;
|
|
1342
961
|
await this.refreshThreadState(resumedThreadId);
|
|
1343
|
-
this.logger
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
activeTurnId: this.activeTurnId
|
|
1347
|
-
});
|
|
962
|
+
this.logger(
|
|
963
|
+
`resumed thread ${resumedThreadId}${this.activeTurnId ? ` (active turn ${this.activeTurnId})` : ""}`
|
|
964
|
+
);
|
|
1348
965
|
return resumedThreadId;
|
|
1349
966
|
} catch (error) {
|
|
1350
|
-
this.logger
|
|
1351
|
-
|
|
1352
|
-
{
|
|
1353
|
-
clientId: this.clientId,
|
|
1354
|
-
threadId: explicitThreadId,
|
|
1355
|
-
error: sanitizeErrorForPersistence(String(error))
|
|
1356
|
-
}
|
|
967
|
+
this.logger(
|
|
968
|
+
`thread resume failed for ${explicitThreadId}; starting a fresh thread (${String(error)})`
|
|
1357
969
|
);
|
|
1358
970
|
}
|
|
1359
971
|
}
|
|
@@ -1363,12 +975,9 @@ var AppServerClient = class {
|
|
|
1363
975
|
}
|
|
1364
976
|
if (savedThread?.threadId) {
|
|
1365
977
|
if (savedThread.cwd && !threadCwdMatches(cwd, savedThread.cwd)) {
|
|
1366
|
-
this.logger
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
savedCwd: savedThread.cwd,
|
|
1370
|
-
expectedCwd: cwd
|
|
1371
|
-
});
|
|
978
|
+
this.logger(
|
|
979
|
+
`saved thread ${savedThread.threadId} cwd ${savedThread.cwd} does not match ${cwd}; skipping saved thread`
|
|
980
|
+
);
|
|
1372
981
|
} else {
|
|
1373
982
|
try {
|
|
1374
983
|
const resumeResponse = await this.request("thread/resume", {
|
|
@@ -1378,33 +987,23 @@ var AppServerClient = class {
|
|
|
1378
987
|
const resumedThreadId = resumeResponse?.thread?.id ?? savedThread.threadId;
|
|
1379
988
|
await this.refreshThreadState(resumedThreadId);
|
|
1380
989
|
if (!threadCwdMatches(cwd, this.currentThreadCwd)) {
|
|
1381
|
-
this.logger
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
expectedCwd: cwd,
|
|
1385
|
-
actualCwd: this.currentThreadCwd ?? "unknown"
|
|
1386
|
-
});
|
|
990
|
+
this.logger(
|
|
991
|
+
`saved thread ${resumedThreadId} cwd ${this.currentThreadCwd ?? "unknown"} does not match ${cwd}; starting a fresh thread`
|
|
992
|
+
);
|
|
1387
993
|
this.threadId = null;
|
|
1388
994
|
this.currentThreadCwd = null;
|
|
1389
995
|
this.activeTurnId = null;
|
|
1390
996
|
this.turnStartedAt = null;
|
|
1391
997
|
this.lastTurnStatus = null;
|
|
1392
998
|
} else {
|
|
1393
|
-
this.logger
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
activeTurnId: this.activeTurnId
|
|
1397
|
-
});
|
|
999
|
+
this.logger(
|
|
1000
|
+
`resumed saved thread ${resumedThreadId}${this.activeTurnId ? ` (active turn ${this.activeTurnId})` : ""}`
|
|
1001
|
+
);
|
|
1398
1002
|
return resumedThreadId;
|
|
1399
1003
|
}
|
|
1400
1004
|
} catch (error) {
|
|
1401
|
-
this.logger
|
|
1402
|
-
|
|
1403
|
-
{
|
|
1404
|
-
clientId: this.clientId,
|
|
1405
|
-
threadId: savedThread.threadId,
|
|
1406
|
-
error: sanitizeErrorForPersistence(String(error))
|
|
1407
|
-
}
|
|
1005
|
+
this.logger(
|
|
1006
|
+
`saved thread resume failed for ${savedThread.threadId}; starting a fresh thread (${String(error)})`
|
|
1408
1007
|
);
|
|
1409
1008
|
}
|
|
1410
1009
|
}
|
|
@@ -1424,12 +1023,7 @@ var AppServerClient = class {
|
|
|
1424
1023
|
this.currentThreadCwd = this.currentThreadCwd ?? cwd;
|
|
1425
1024
|
this.activeTurnId = null;
|
|
1426
1025
|
this.lastTurnStatus = null;
|
|
1427
|
-
this.logger
|
|
1428
|
-
clientId: this.clientId,
|
|
1429
|
-
threadId: startedThreadId,
|
|
1430
|
-
cwd: this.currentThreadCwd,
|
|
1431
|
-
ephemeral
|
|
1432
|
-
});
|
|
1026
|
+
this.logger(`started thread ${startedThreadId}`);
|
|
1433
1027
|
return startedThreadId;
|
|
1434
1028
|
}
|
|
1435
1029
|
async findLoadedThread(cwd) {
|
|
@@ -1467,21 +1061,14 @@ var AppServerClient = class {
|
|
|
1467
1061
|
const chosen = chooseLoadedThreadForCwd(cwd, threads);
|
|
1468
1062
|
if (!chosen) {
|
|
1469
1063
|
if (threads.length > 0) {
|
|
1470
|
-
this.logger
|
|
1471
|
-
clientId: this.clientId,
|
|
1472
|
-
cwd,
|
|
1473
|
-
loadedThreadCount: threads.length
|
|
1474
|
-
});
|
|
1064
|
+
this.logger(`loaded threads exist but none match cwd ${cwd}`);
|
|
1475
1065
|
}
|
|
1476
1066
|
return null;
|
|
1477
1067
|
}
|
|
1478
1068
|
this.syncThreadStateFromThread(chosen.thread);
|
|
1479
|
-
this.logger
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
activeTurnId: this.activeTurnId,
|
|
1483
|
-
cwd: chosen.cwd
|
|
1484
|
-
});
|
|
1069
|
+
this.logger(
|
|
1070
|
+
`attached to loaded thread ${chosen.id}${this.activeTurnId ? ` (active turn ${this.activeTurnId})` : ""}`
|
|
1071
|
+
);
|
|
1485
1072
|
return chosen.id;
|
|
1486
1073
|
}
|
|
1487
1074
|
async startTurn(inputText) {
|
|
@@ -1520,18 +1107,7 @@ var AppServerClient = class {
|
|
|
1520
1107
|
return turnId;
|
|
1521
1108
|
}
|
|
1522
1109
|
isBusy() {
|
|
1523
|
-
|
|
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;
|
|
1110
|
+
return Boolean(this.activeTurnId);
|
|
1535
1111
|
}
|
|
1536
1112
|
async refreshCurrentThreadState() {
|
|
1537
1113
|
if (!this.threadId) {
|
|
@@ -1565,33 +1141,12 @@ var AppServerClient = class {
|
|
|
1565
1141
|
this.currentThreadCwd = typeof thread?.cwd === "string" ? thread.cwd : null;
|
|
1566
1142
|
let activeTurnId = null;
|
|
1567
1143
|
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
|
-
}
|
|
1578
1144
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
1579
1145
|
for (const turn of turns) {
|
|
1580
1146
|
if (typeof turn?.status === "string") {
|
|
1581
1147
|
lastTurnStatus = turn.status;
|
|
1582
1148
|
}
|
|
1583
1149
|
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
|
-
}
|
|
1595
1150
|
activeTurnId = turn.id;
|
|
1596
1151
|
}
|
|
1597
1152
|
}
|
|
@@ -1614,12 +1169,7 @@ var AppServerClient = class {
|
|
|
1614
1169
|
this.pending.delete(message.id);
|
|
1615
1170
|
if (message.error) {
|
|
1616
1171
|
const errorText = formatJsonRpcError(message.error);
|
|
1617
|
-
this.lastError =
|
|
1618
|
-
this.logger.error("app-server request failed", {
|
|
1619
|
-
clientId: this.clientId,
|
|
1620
|
-
method: pending.method,
|
|
1621
|
-
error: this.lastError
|
|
1622
|
-
});
|
|
1172
|
+
this.lastError = errorText;
|
|
1623
1173
|
pending.reject(new Error(`${pending.method} failed: ${errorText}`));
|
|
1624
1174
|
return;
|
|
1625
1175
|
}
|
|
@@ -1634,10 +1184,6 @@ var AppServerClient = class {
|
|
|
1634
1184
|
}
|
|
1635
1185
|
this.lastNotificationMethod = message.method;
|
|
1636
1186
|
this.lastNotificationAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1637
|
-
this.logger.debug("received app-server notification", {
|
|
1638
|
-
clientId: this.clientId,
|
|
1639
|
-
method: message.method
|
|
1640
|
-
});
|
|
1641
1187
|
this.handleNotification(message.method, message.params);
|
|
1642
1188
|
}
|
|
1643
1189
|
handleNotification(method, params) {
|
|
@@ -1649,28 +1195,18 @@ var AppServerClient = class {
|
|
|
1649
1195
|
if (typeof params?.thread?.cwd === "string") {
|
|
1650
1196
|
this.currentThreadCwd = params.thread.cwd;
|
|
1651
1197
|
}
|
|
1652
|
-
this.logger
|
|
1653
|
-
clientId: this.clientId,
|
|
1654
|
-
threadId: params?.thread?.id ?? null,
|
|
1655
|
-
cwd: params?.thread?.cwd ?? null
|
|
1656
|
-
});
|
|
1198
|
+
this.logger(`thread started ${params?.thread?.id ?? ""}`.trim());
|
|
1657
1199
|
break;
|
|
1658
1200
|
case "thread/status/changed":
|
|
1659
|
-
this.logger
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
status: params?.thread?.status?.type ?? params?.status?.type ?? "unknown"
|
|
1663
|
-
});
|
|
1201
|
+
this.logger(
|
|
1202
|
+
`thread status changed (${params?.thread?.status?.type ?? params?.status?.type ?? "unknown"})`
|
|
1203
|
+
);
|
|
1664
1204
|
break;
|
|
1665
1205
|
case "turn/started":
|
|
1666
1206
|
if (params?.turn?.id) {
|
|
1667
1207
|
this.activeTurnId = params.turn.id;
|
|
1668
1208
|
this.turnStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1669
|
-
this.logger
|
|
1670
|
-
clientId: this.clientId,
|
|
1671
|
-
threadId: this.threadId,
|
|
1672
|
-
turnId: params.turn.id
|
|
1673
|
-
});
|
|
1209
|
+
this.logger(`turn started ${params.turn.id}`);
|
|
1674
1210
|
}
|
|
1675
1211
|
break;
|
|
1676
1212
|
case "turn/completed": {
|
|
@@ -1679,22 +1215,15 @@ var AppServerClient = class {
|
|
|
1679
1215
|
this.activeTurnId = null;
|
|
1680
1216
|
this.turnStartedAt = null;
|
|
1681
1217
|
const elapsedMs = prevTurnStartedAt ? Date.now() - new Date(prevTurnStartedAt).getTime() : null;
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
elapsedSeconds: elapsedMs !== null ? Math.round(elapsedMs / 1e3) : void 0
|
|
1687
|
-
});
|
|
1218
|
+
const elapsedSuffix = elapsedMs !== null ? ` \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed` : "";
|
|
1219
|
+
this.logger(
|
|
1220
|
+
`turn completed (${this.lastTurnStatus ?? "unknown"})${elapsedSuffix}`
|
|
1221
|
+
);
|
|
1688
1222
|
break;
|
|
1689
1223
|
}
|
|
1690
1224
|
case "error":
|
|
1691
|
-
this.lastError =
|
|
1692
|
-
|
|
1693
|
-
);
|
|
1694
|
-
this.logger.error("app-server error notification", {
|
|
1695
|
-
clientId: this.clientId,
|
|
1696
|
-
error: this.lastError
|
|
1697
|
-
});
|
|
1225
|
+
this.lastError = JSON.stringify(params ?? {}, null, 2);
|
|
1226
|
+
this.logger(`app-server error notification: ${this.lastError}`);
|
|
1698
1227
|
break;
|
|
1699
1228
|
default:
|
|
1700
1229
|
break;
|
|
@@ -1727,110 +1256,252 @@ var AppServerClient = class {
|
|
|
1727
1256
|
}
|
|
1728
1257
|
this.pending.clear();
|
|
1729
1258
|
}
|
|
1730
|
-
};
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1259
|
+
};
|
|
1260
|
+
var heartbeatCount = 0;
|
|
1261
|
+
function writeHeartbeat(options, client, health) {
|
|
1262
|
+
if (client?.threadId) {
|
|
1263
|
+
const savedThread = readThreadState(options.stateDir);
|
|
1264
|
+
persistThreadState(
|
|
1265
|
+
options.stateDir,
|
|
1266
|
+
client.threadId,
|
|
1267
|
+
options.appServerUrl,
|
|
1268
|
+
options.ephemeral,
|
|
1269
|
+
client.currentThreadCwd ?? savedThread?.cwd ?? null
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
const payload = {
|
|
1273
|
+
pid: process.pid,
|
|
1274
|
+
agent: options.agentName,
|
|
1275
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1276
|
+
pollSeconds: options.pollSeconds,
|
|
1277
|
+
appServerUrl: options.appServerUrl,
|
|
1278
|
+
connected: client?.connected ?? false,
|
|
1279
|
+
initialized: client?.initialized ?? false,
|
|
1280
|
+
threadId: client?.threadId ?? null,
|
|
1281
|
+
threadCwd: client?.currentThreadCwd ?? null,
|
|
1282
|
+
activeTurnId: client?.activeTurnId ?? null,
|
|
1283
|
+
turnStartedAt: client?.turnStartedAt ?? null,
|
|
1284
|
+
lastTurnStatus: client?.lastTurnStatus ?? null,
|
|
1285
|
+
lastNotificationMethod: client?.lastNotificationMethod ?? null,
|
|
1286
|
+
lastNotificationAt: client?.lastNotificationAt ?? null,
|
|
1287
|
+
lastError: sanitizeErrorForPersistence(client?.lastError ?? null),
|
|
1288
|
+
lastSuccessfulAppServerAt: client?.lastSuccessfulAppServerAt ?? null,
|
|
1289
|
+
lastSuccessfulAppServerMethod: client?.lastSuccessfulAppServerMethod ?? null,
|
|
1290
|
+
consecutiveFailureCount: health.consecutiveFailureCount,
|
|
1291
|
+
busyMode: options.busyMode
|
|
1292
|
+
};
|
|
1293
|
+
writeFileSync(
|
|
1294
|
+
join(options.stateDir, "heartbeat.json"),
|
|
1295
|
+
`${JSON.stringify(payload, null, 2)}
|
|
1296
|
+
`,
|
|
1297
|
+
"utf8"
|
|
1298
|
+
);
|
|
1299
|
+
heartbeatCount += 1;
|
|
1300
|
+
if (heartbeatCount % 5 === 0) {
|
|
1301
|
+
logStatus(
|
|
1302
|
+
`heartbeat: connected=${payload.connected}, thread=${payload.threadId ?? "null"}, turns=${payload.activeTurnId ? "active" : "0"}`
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
const status = client?.connected ? "active" : "idle";
|
|
1306
|
+
updateCommsHeartbeat(options, status);
|
|
1740
1307
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1308
|
+
var COMMS_HEARTBEAT_LOCK_TIMEOUT_MS = 2e3;
|
|
1309
|
+
var COMMS_LOCK_STALE_AGE_MS = 1e4;
|
|
1310
|
+
function acquireCommsLock(lockPath) {
|
|
1311
|
+
const deadline = Date.now() + COMMS_HEARTBEAT_LOCK_TIMEOUT_MS;
|
|
1312
|
+
while (Date.now() < deadline) {
|
|
1313
|
+
try {
|
|
1314
|
+
writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
1315
|
+
return true;
|
|
1316
|
+
} catch {
|
|
1317
|
+
try {
|
|
1318
|
+
const lockAge = Date.now() - statSync(lockPath).mtimeMs;
|
|
1319
|
+
if (lockAge > COMMS_LOCK_STALE_AGE_MS) {
|
|
1320
|
+
unlinkSync(lockPath);
|
|
1321
|
+
try {
|
|
1322
|
+
writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
1323
|
+
return true;
|
|
1324
|
+
} catch {
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
} catch {
|
|
1328
|
+
}
|
|
1329
|
+
const start = Date.now();
|
|
1330
|
+
while (Date.now() - start < 50) {
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1745
1333
|
}
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
function releaseCommsLock(lockPath) {
|
|
1746
1337
|
try {
|
|
1747
|
-
|
|
1338
|
+
unlinkSync(lockPath);
|
|
1748
1339
|
} catch {
|
|
1749
|
-
return null;
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
function parseUpdatedAt(value) {
|
|
1753
|
-
if (!value) {
|
|
1754
|
-
return 0;
|
|
1755
|
-
}
|
|
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
1340
|
}
|
|
1767
|
-
return isAbsolute3(normalized) || /^[A-Za-z]:[\\/]/.test(normalized) || normalized.startsWith("\\\\");
|
|
1768
1341
|
}
|
|
1769
|
-
function
|
|
1770
|
-
const
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
return savedThread;
|
|
1775
|
-
}
|
|
1776
|
-
if (!appServerUrlMatches(fallbackAppServerUrl, heartbeat?.appServerUrl)) {
|
|
1777
|
-
return savedThread;
|
|
1778
|
-
}
|
|
1779
|
-
if (!hasValidHeartbeatThreadCwd(heartbeat?.threadCwd)) {
|
|
1780
|
-
return savedThread;
|
|
1342
|
+
function updateCommsHeartbeat(options, status) {
|
|
1343
|
+
const heartbeatsPath = join(options.commsDir, "heartbeats.json");
|
|
1344
|
+
const lockPath = join(options.commsDir, ".heartbeats.lock");
|
|
1345
|
+
if (!acquireCommsLock(lockPath)) {
|
|
1346
|
+
return;
|
|
1781
1347
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
cwd: heartbeatBackedThread.cwd ?? savedThread.cwd ?? null
|
|
1348
|
+
try {
|
|
1349
|
+
let store = {};
|
|
1350
|
+
try {
|
|
1351
|
+
store = JSON.parse(readFileSync(heartbeatsPath, "utf-8"));
|
|
1352
|
+
} catch {
|
|
1353
|
+
}
|
|
1354
|
+
const key = options.agentId;
|
|
1355
|
+
const existing = store[key];
|
|
1356
|
+
store[key] = {
|
|
1357
|
+
id: options.agentId,
|
|
1358
|
+
agent: options.agentName,
|
|
1359
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1360
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1361
|
+
joinedAt: existing?.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1362
|
+
status
|
|
1798
1363
|
};
|
|
1799
|
-
|
|
1800
|
-
|
|
1364
|
+
const tmpPath = heartbeatsPath + ".tmp." + process.pid;
|
|
1365
|
+
writeFileSync(tmpPath, JSON.stringify(store, null, 2), "utf-8");
|
|
1366
|
+
renameSync(tmpPath, heartbeatsPath);
|
|
1367
|
+
} catch {
|
|
1368
|
+
} finally {
|
|
1369
|
+
releaseCommsLock(lockPath);
|
|
1801
1370
|
}
|
|
1802
|
-
return preferred;
|
|
1803
1371
|
}
|
|
1804
|
-
function
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1372
|
+
async function dispatchCandidate(client, options, candidate, heartbeats) {
|
|
1373
|
+
const input = buildUserInput(candidate, options.agentName, heartbeats);
|
|
1374
|
+
logStatus(
|
|
1375
|
+
`dispatching from ${candidate.sender || "unknown"}: ${candidate.subject || "(none)"}`
|
|
1376
|
+
);
|
|
1377
|
+
if (client.isBusy()) {
|
|
1378
|
+
if (options.busyMode !== "steer") {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1811
1381
|
try {
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1382
|
+
const turnId2 = await client.steerTurn(input);
|
|
1383
|
+
writeProcessedMarker(
|
|
1384
|
+
options.stateDir,
|
|
1385
|
+
candidate,
|
|
1386
|
+
"steer",
|
|
1387
|
+
client.threadId,
|
|
1388
|
+
turnId2
|
|
1389
|
+
);
|
|
1390
|
+
writeLastDispatch(
|
|
1391
|
+
options.stateDir,
|
|
1392
|
+
candidate,
|
|
1393
|
+
"steer",
|
|
1394
|
+
client.threadId,
|
|
1395
|
+
turnId2
|
|
1396
|
+
);
|
|
1397
|
+
logStatus(`steered active turn with ${candidate.fileName}`);
|
|
1398
|
+
return true;
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
await client.refreshCurrentThreadState().catch(() => void 0);
|
|
1401
|
+
if (!client.isBusy()) {
|
|
1402
|
+
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1818
1403
|
}
|
|
1819
|
-
|
|
1404
|
+
if (shouldRetrySteerAsStart(error)) {
|
|
1405
|
+
client.activeTurnId = null;
|
|
1406
|
+
client.turnStartedAt = null;
|
|
1407
|
+
logStatus(
|
|
1408
|
+
`steer fallback -> start for ${candidate.fileName} (${String(error)})`
|
|
1409
|
+
);
|
|
1410
|
+
return dispatchCandidate(client, options, candidate, heartbeats);
|
|
1411
|
+
}
|
|
1412
|
+
throw error;
|
|
1820
1413
|
}
|
|
1821
1414
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1415
|
+
const turnId = await client.startTurn(input);
|
|
1416
|
+
writeProcessedMarker(
|
|
1417
|
+
options.stateDir,
|
|
1418
|
+
candidate,
|
|
1419
|
+
"start",
|
|
1420
|
+
client.threadId,
|
|
1421
|
+
turnId
|
|
1422
|
+
);
|
|
1423
|
+
writeLastDispatch(
|
|
1424
|
+
options.stateDir,
|
|
1425
|
+
candidate,
|
|
1426
|
+
"start",
|
|
1427
|
+
client.threadId,
|
|
1428
|
+
turnId
|
|
1429
|
+
);
|
|
1430
|
+
logStatus(`dispatched ${candidate.fileName} to thread ${client.threadId}`);
|
|
1431
|
+
return true;
|
|
1432
|
+
}
|
|
1433
|
+
async function runScan(options, cutoff, client) {
|
|
1434
|
+
const { heartbeats, candidates } = getPendingCandidates(options, cutoff);
|
|
1435
|
+
for (const candidate of candidates) {
|
|
1436
|
+
if (options.dryRun) {
|
|
1437
|
+
logStatus(`dry-run candidate ${candidate.fileName}`);
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
if (!client) {
|
|
1441
|
+
throw new Error("App Server client is not available");
|
|
1442
|
+
}
|
|
1443
|
+
const dispatched = await dispatchCandidate(
|
|
1444
|
+
client,
|
|
1445
|
+
options,
|
|
1446
|
+
candidate,
|
|
1447
|
+
heartbeats
|
|
1448
|
+
);
|
|
1449
|
+
if (!dispatched && options.busyMode === "wait") {
|
|
1450
|
+
return false;
|
|
1451
|
+
}
|
|
1452
|
+
return true;
|
|
1824
1453
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
async function waitForTurnDrain(options, client, health) {
|
|
1457
|
+
const deadline = Date.now() + options.waitAfterDispatchSeconds * 1e3;
|
|
1458
|
+
while (Date.now() < deadline) {
|
|
1459
|
+
writeHeartbeat(options, client, health);
|
|
1460
|
+
if (!client.activeTurnId) {
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
await delay(1e3);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
function buildOptions(argv) {
|
|
1467
|
+
const parsed = parseArgs(argv);
|
|
1468
|
+
const repoRoot = resolveRepoRoot(parsed.repoRoot);
|
|
1469
|
+
const commsDir = resolveCommsDir(repoRoot, parsed.commsDir);
|
|
1470
|
+
const preferredAgentName = resolvePreferredAgentName(parsed.agentName);
|
|
1471
|
+
const stateDir = resolveStateDir(
|
|
1472
|
+
repoRoot,
|
|
1473
|
+
parsed.stateDir,
|
|
1474
|
+
preferredAgentName
|
|
1475
|
+
);
|
|
1476
|
+
const agentName = resolveAgentName(preferredAgentName, stateDir);
|
|
1477
|
+
const agentId = resolveAgentId(agentName);
|
|
1478
|
+
persistAgentName(stateDir, agentName);
|
|
1479
|
+
const gatewayTokenFile = parsed.gatewayTokenFile?.trim() || process.env.TAP_GATEWAY_TOKEN_FILE?.trim() || null;
|
|
1480
|
+
const appServerUrl = parsed.appServerUrl?.trim() || process.env.CODEX_APP_SERVER_URL || DEFAULT_APP_SERVER_URL;
|
|
1481
|
+
return {
|
|
1482
|
+
repoRoot,
|
|
1483
|
+
commsDir,
|
|
1484
|
+
agentId,
|
|
1485
|
+
stateDir,
|
|
1486
|
+
agentName,
|
|
1487
|
+
pollSeconds: parsed.pollSeconds ?? 5,
|
|
1488
|
+
reconnectSeconds: parsed.reconnectSeconds ?? 5,
|
|
1489
|
+
messageLookbackMinutes: parsed.messageLookbackMinutes ?? 10,
|
|
1490
|
+
processExistingMessages: parsed.processExistingMessages,
|
|
1491
|
+
dryRun: parsed.dryRun,
|
|
1492
|
+
runOnce: parsed.runOnce,
|
|
1493
|
+
waitAfterDispatchSeconds: parsed.waitAfterDispatchSeconds ?? 0,
|
|
1494
|
+
appServerUrl,
|
|
1495
|
+
connectAppServerUrl: appServerUrl,
|
|
1496
|
+
gatewayToken: gatewayTokenFile ? readGatewayTokenFile(gatewayTokenFile) : null,
|
|
1497
|
+
gatewayTokenFile,
|
|
1498
|
+
busyMode: parsed.busyMode ?? "steer",
|
|
1499
|
+
threadId: parsed.threadId?.trim() || null,
|
|
1500
|
+
ephemeral: parsed.ephemeral
|
|
1501
|
+
};
|
|
1829
1502
|
}
|
|
1830
1503
|
async function main() {
|
|
1831
1504
|
const options = buildOptions(process.argv.slice(2));
|
|
1832
|
-
configureBridgeLogging(options.logLevel);
|
|
1833
|
-
const logger = createBridgeLogger("bridge");
|
|
1834
1505
|
const cutoff = getGeneralInboxCutoff(
|
|
1835
1506
|
options.stateDir,
|
|
1836
1507
|
options.messageLookbackMinutes,
|
|
@@ -1840,20 +1511,28 @@ async function main() {
|
|
|
1840
1511
|
options.stateDir,
|
|
1841
1512
|
options.appServerUrl
|
|
1842
1513
|
);
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
}
|
|
1514
|
+
logStatus("codex app-server bridge ready");
|
|
1515
|
+
console.log(` repo: ${options.repoRoot}`);
|
|
1516
|
+
console.log(` comms: ${options.commsDir}`);
|
|
1517
|
+
console.log(` agent: ${options.agentName}`);
|
|
1518
|
+
console.log(` state: ${options.stateDir}`);
|
|
1519
|
+
console.log(` app-server: ${options.appServerUrl}`);
|
|
1520
|
+
console.log(` busy-mode: ${options.busyMode}`);
|
|
1521
|
+
if (options.waitAfterDispatchSeconds > 0) {
|
|
1522
|
+
console.log(
|
|
1523
|
+
` wait: ${options.waitAfterDispatchSeconds}s after dispatch`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
console.log(
|
|
1527
|
+
` lookback: ${options.processExistingMessages ? "existing messages" : `${options.messageLookbackMinutes} minute(s)`}`
|
|
1528
|
+
);
|
|
1529
|
+
if (options.threadId || initialSavedThread?.threadId) {
|
|
1530
|
+
console.log(
|
|
1531
|
+
` thread: ${options.threadId ?? initialSavedThread?.threadId}`
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1855
1534
|
if (options.dryRun) {
|
|
1856
|
-
|
|
1535
|
+
logStatus("dry-run mode enabled");
|
|
1857
1536
|
}
|
|
1858
1537
|
let client = null;
|
|
1859
1538
|
const health = {
|
|
@@ -1865,7 +1544,7 @@ async function main() {
|
|
|
1865
1544
|
if (!client || !client.connected) {
|
|
1866
1545
|
client = new AppServerClient(
|
|
1867
1546
|
options.connectAppServerUrl,
|
|
1868
|
-
|
|
1547
|
+
logStatus,
|
|
1869
1548
|
options.gatewayToken
|
|
1870
1549
|
);
|
|
1871
1550
|
await client.connect();
|
|
@@ -1873,10 +1552,6 @@ async function main() {
|
|
|
1873
1552
|
options.stateDir,
|
|
1874
1553
|
options.appServerUrl
|
|
1875
1554
|
);
|
|
1876
|
-
logger.debug("resolved resumable thread state", {
|
|
1877
|
-
savedThreadId: savedThread?.threadId,
|
|
1878
|
-
savedThreadCwd: savedThread?.cwd ?? null
|
|
1879
|
-
});
|
|
1880
1555
|
const threadId = await client.ensureThread(
|
|
1881
1556
|
options.threadId,
|
|
1882
1557
|
savedThread,
|
|
@@ -1901,14 +1576,8 @@ async function main() {
|
|
|
1901
1576
|
}
|
|
1902
1577
|
}
|
|
1903
1578
|
}
|
|
1904
|
-
const
|
|
1905
|
-
if (
|
|
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) {
|
|
1579
|
+
const dispatched = await runScan(options, cutoff, client);
|
|
1580
|
+
if (dispatched && client && options.waitAfterDispatchSeconds > 0) {
|
|
1912
1581
|
await waitForTurnDrain(options, client, health);
|
|
1913
1582
|
}
|
|
1914
1583
|
health.consecutiveFailureCount = 0;
|
|
@@ -1916,28 +1585,22 @@ async function main() {
|
|
|
1916
1585
|
if (options.runOnce) {
|
|
1917
1586
|
break;
|
|
1918
1587
|
}
|
|
1919
|
-
await
|
|
1588
|
+
await delay(options.pollSeconds * 1e3);
|
|
1920
1589
|
} catch (error) {
|
|
1921
1590
|
const message = error instanceof Error ? error.message : String(error);
|
|
1922
|
-
|
|
1923
|
-
error: sanitizeErrorForPersistence(message)
|
|
1924
|
-
});
|
|
1591
|
+
logStatus(`bridge error: ${message}`);
|
|
1925
1592
|
if (client) {
|
|
1926
|
-
client.lastError =
|
|
1593
|
+
client.lastError = message;
|
|
1927
1594
|
}
|
|
1928
1595
|
health.consecutiveFailureCount += 1;
|
|
1929
1596
|
writeHeartbeat(options, client, health);
|
|
1930
1597
|
if (options.runOnce) {
|
|
1931
|
-
|
|
1932
|
-
throw new Error(sanitized ?? message);
|
|
1598
|
+
throw error;
|
|
1933
1599
|
}
|
|
1934
1600
|
client?.disconnect().catch(() => void 0);
|
|
1935
1601
|
client = null;
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
consecutiveFailureCount: health.consecutiveFailureCount
|
|
1939
|
-
});
|
|
1940
|
-
await delay2(options.reconnectSeconds * 1e3);
|
|
1602
|
+
logStatus(`reconnecting in ${options.reconnectSeconds}s...`);
|
|
1603
|
+
await delay(options.reconnectSeconds * 1e3);
|
|
1941
1604
|
}
|
|
1942
1605
|
}
|
|
1943
1606
|
await client?.disconnect();
|
|
@@ -1945,32 +1608,24 @@ async function main() {
|
|
|
1945
1608
|
function isDirectExecution() {
|
|
1946
1609
|
const entry = process.argv[1];
|
|
1947
1610
|
if (!entry) return false;
|
|
1948
|
-
return import.meta.url === pathToFileURL(
|
|
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;
|
|
1611
|
+
return import.meta.url === pathToFileURL(resolve(entry)).href;
|
|
1958
1612
|
}
|
|
1959
|
-
if (
|
|
1613
|
+
if (isDirectExecution()) {
|
|
1960
1614
|
main().catch((error) => {
|
|
1961
|
-
|
|
1962
|
-
|
|
1615
|
+
console.error(
|
|
1616
|
+
error instanceof Error ? error.stack ?? error.message : String(error)
|
|
1617
|
+
);
|
|
1963
1618
|
process.exitCode = 1;
|
|
1964
1619
|
});
|
|
1965
1620
|
}
|
|
1966
1621
|
|
|
1967
1622
|
// src/bridges/codex-app-server-bridge.ts
|
|
1968
|
-
function
|
|
1623
|
+
function isDirectExecution2() {
|
|
1969
1624
|
const entry = process.argv[1];
|
|
1970
1625
|
if (!entry) return false;
|
|
1971
|
-
return import.meta.url ===
|
|
1626
|
+
return import.meta.url === pathToFileURL2(resolve2(entry)).href;
|
|
1972
1627
|
}
|
|
1973
|
-
if (
|
|
1628
|
+
if (isDirectExecution2()) {
|
|
1974
1629
|
main().catch((error) => {
|
|
1975
1630
|
console.error(
|
|
1976
1631
|
error instanceof Error ? error.stack ?? error.message : String(error)
|
|
@@ -1979,77 +1634,19 @@ if (isDirectExecution3()) {
|
|
|
1979
1634
|
});
|
|
1980
1635
|
}
|
|
1981
1636
|
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,
|
|
1989
1637
|
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,
|
|
1998
1638
|
buildOptions,
|
|
1999
1639
|
buildUserInput,
|
|
2000
|
-
canonicalize,
|
|
2001
1640
|
chooseLoadedThreadForCwd,
|
|
2002
|
-
collectCandidates,
|
|
2003
|
-
dispatchCandidate,
|
|
2004
|
-
formatAgentLabel,
|
|
2005
|
-
formatJsonRpcError,
|
|
2006
|
-
getGeneralInboxCutoff,
|
|
2007
|
-
getInboxRoute,
|
|
2008
|
-
getInboxRouteFromFilename,
|
|
2009
|
-
getPendingCandidates,
|
|
2010
|
-
getProcessedMarkerPath,
|
|
2011
|
-
isDirectExecution,
|
|
2012
1641
|
isOwnMessageSender,
|
|
2013
|
-
isTurnStale,
|
|
2014
|
-
isTurnStuckOnApproval,
|
|
2015
|
-
loadHeartbeats,
|
|
2016
1642
|
loadResumableThreadState,
|
|
2017
1643
|
main,
|
|
2018
1644
|
maybeBootstrapHeadlessTurn,
|
|
2019
|
-
normalizeAgentToken,
|
|
2020
|
-
normalizeThreadCwd,
|
|
2021
|
-
parseArgs,
|
|
2022
|
-
parseBridgeFrontmatter,
|
|
2023
|
-
persistAgentName,
|
|
2024
|
-
persistThreadState,
|
|
2025
|
-
readGatewayTokenFile,
|
|
2026
|
-
readHeartbeatState,
|
|
2027
|
-
readSocketData,
|
|
2028
|
-
readThreadState,
|
|
2029
1645
|
recipientMatchesAgent,
|
|
2030
|
-
refreshAgentIdentity,
|
|
2031
|
-
releaseCommsLock,
|
|
2032
1646
|
resolveAddressLabel,
|
|
2033
1647
|
resolveAgentId,
|
|
2034
|
-
resolveAgentName,
|
|
2035
|
-
resolveCommsDir,
|
|
2036
1648
|
resolveCurrentAgentName,
|
|
2037
|
-
resolvePreferredAgentName,
|
|
2038
|
-
resolveRepoRoot,
|
|
2039
|
-
resolveStateDir,
|
|
2040
|
-
resolveTapConfigPath,
|
|
2041
|
-
runScan,
|
|
2042
|
-
sanitizeErrorForPersistence,
|
|
2043
|
-
sanitizeStateSegment,
|
|
2044
|
-
shouldRetrySteerAsStart,
|
|
2045
|
-
shouldSkipInHeadlessMode,
|
|
2046
|
-
stripBridgeFrontmatter,
|
|
2047
1649
|
threadCwdMatches,
|
|
2048
|
-
|
|
2049
|
-
waitForTurnCompletion,
|
|
2050
|
-
waitForTurnDrain,
|
|
2051
|
-
writeHeartbeat,
|
|
2052
|
-
writeLastDispatch,
|
|
2053
|
-
writeProcessedMarker
|
|
1650
|
+
waitForTurnCompletion
|
|
2054
1651
|
};
|
|
2055
1652
|
//# sourceMappingURL=codex-app-server-bridge.mjs.map
|