@hua-labs/tap 0.2.3 → 0.2.5
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 +194 -194
- package/dist/bridges/codex-app-server-auth-gateway.mjs +11 -3
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +11 -1
- package/dist/bridges/codex-app-server-bridge.mjs +109 -50
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +23 -17
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +1063 -237
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +446 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +65 -65
|
@@ -16,7 +16,7 @@ import { isAbsolute, join, resolve } from "path";
|
|
|
16
16
|
import { pathToFileURL } from "url";
|
|
17
17
|
var DEFAULT_AGENT = String.fromCharCode(50728);
|
|
18
18
|
var DEFAULT_APP_SERVER_URL = "ws://127.0.0.1:4501";
|
|
19
|
-
var
|
|
19
|
+
var AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
20
20
|
var PLACEHOLDER_AGENT_VALUES = /* @__PURE__ */ new Set([
|
|
21
21
|
"unknown",
|
|
22
22
|
"unnamed",
|
|
@@ -31,6 +31,30 @@ var HEADLESS_WARMUP_PROMPT = [
|
|
|
31
31
|
var HEADLESS_WARMUP_TIMEOUT_MS = 3e4;
|
|
32
32
|
var TURN_COMPLETION_POLL_MS = 250;
|
|
33
33
|
var TURN_COMPLETION_REFRESH_MS = 1e3;
|
|
34
|
+
function normalizeThreadCwd(cwd) {
|
|
35
|
+
return resolve(cwd).replace(/\\/g, "/").toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
function threadCwdMatches(expectedCwd, actualCwd) {
|
|
38
|
+
if (!actualCwd) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return normalizeThreadCwd(expectedCwd) === normalizeThreadCwd(actualCwd);
|
|
42
|
+
}
|
|
43
|
+
function chooseLoadedThreadForCwd(cwd, threads) {
|
|
44
|
+
const matching = threads.filter((thread) => threadCwdMatches(cwd, thread.cwd));
|
|
45
|
+
if (matching.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
matching.sort((left, right) => {
|
|
49
|
+
const leftActive = left.statusType === "active" ? 1 : 0;
|
|
50
|
+
const rightActive = right.statusType === "active" ? 1 : 0;
|
|
51
|
+
if (leftActive !== rightActive) {
|
|
52
|
+
return rightActive - leftActive;
|
|
53
|
+
}
|
|
54
|
+
return right.updatedAt - left.updatedAt;
|
|
55
|
+
});
|
|
56
|
+
return matching[0] ?? null;
|
|
57
|
+
}
|
|
34
58
|
function printHelp() {
|
|
35
59
|
console.log(`Codex App Server bridge
|
|
36
60
|
|
|
@@ -316,11 +340,6 @@ function persistAgentName(stateDir, agentName) {
|
|
|
316
340
|
writeFileSync(join(stateDir, "agent-name.txt"), `${agentName}
|
|
317
341
|
`, "utf8");
|
|
318
342
|
}
|
|
319
|
-
function buildProtectedAppServerUrl(appServerUrl, token) {
|
|
320
|
-
const url = new URL(appServerUrl);
|
|
321
|
-
url.searchParams.set(APP_SERVER_AUTH_QUERY_PARAM, token);
|
|
322
|
-
return url.toString().replace(/\/(?=\?|$)/, "");
|
|
323
|
-
}
|
|
324
343
|
function readGatewayTokenFile(tokenFile) {
|
|
325
344
|
const token = readFileSync(tokenFile, "utf8").trim();
|
|
326
345
|
if (!token) {
|
|
@@ -349,12 +368,13 @@ function readThreadState(stateDir) {
|
|
|
349
368
|
}
|
|
350
369
|
return null;
|
|
351
370
|
}
|
|
352
|
-
function persistThreadState(stateDir, threadId, appServerUrl, ephemeral) {
|
|
371
|
+
function persistThreadState(stateDir, threadId, appServerUrl, ephemeral, cwd) {
|
|
353
372
|
const payload = {
|
|
354
373
|
threadId,
|
|
355
374
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
356
375
|
appServerUrl,
|
|
357
|
-
ephemeral
|
|
376
|
+
ephemeral,
|
|
377
|
+
cwd
|
|
358
378
|
};
|
|
359
379
|
writeFileSync(
|
|
360
380
|
join(stateDir, "thread.json"),
|
|
@@ -651,7 +671,7 @@ async function waitForTurnCompletion(client, turnId, timeoutMs) {
|
|
|
651
671
|
throw new Error(`Timed out waiting for turn ${turnId} to complete`);
|
|
652
672
|
}
|
|
653
673
|
async function maybeBootstrapHeadlessTurn(options, cutoff, client) {
|
|
654
|
-
if (process.env.TAP_HEADLESS !== "true") {
|
|
674
|
+
if (process.env.TAP_HEADLESS !== "true" && process.env.TAP_COLD_START_WARMUP !== "true") {
|
|
655
675
|
return false;
|
|
656
676
|
}
|
|
657
677
|
const { candidates } = getPendingCandidates(options, cutoff);
|
|
@@ -712,12 +732,14 @@ async function readSocketData(data) {
|
|
|
712
732
|
var AppServerClient = class {
|
|
713
733
|
socket = null;
|
|
714
734
|
url;
|
|
735
|
+
gatewayToken;
|
|
715
736
|
logger;
|
|
716
737
|
nextId = 1;
|
|
717
738
|
pending = /* @__PURE__ */ new Map();
|
|
718
739
|
connected = false;
|
|
719
740
|
initialized = false;
|
|
720
741
|
threadId = null;
|
|
742
|
+
currentThreadCwd = null;
|
|
721
743
|
activeTurnId = null;
|
|
722
744
|
lastTurnStatus = null;
|
|
723
745
|
lastNotificationMethod = null;
|
|
@@ -725,15 +747,20 @@ var AppServerClient = class {
|
|
|
725
747
|
lastError = null;
|
|
726
748
|
lastSuccessfulAppServerAt = null;
|
|
727
749
|
lastSuccessfulAppServerMethod = null;
|
|
728
|
-
constructor(url, logger) {
|
|
750
|
+
constructor(url, logger, gatewayToken) {
|
|
729
751
|
this.url = url;
|
|
730
752
|
this.logger = logger;
|
|
753
|
+
this.gatewayToken = gatewayToken ?? null;
|
|
731
754
|
}
|
|
732
755
|
async connect() {
|
|
733
756
|
if (this.connected && this.socket?.readyState === WebSocket.OPEN) {
|
|
734
757
|
return;
|
|
735
758
|
}
|
|
736
|
-
|
|
759
|
+
const wsOptions = {};
|
|
760
|
+
if (this.gatewayToken) {
|
|
761
|
+
wsOptions.protocols = [`${AUTH_SUBPROTOCOL_PREFIX}${this.gatewayToken}`];
|
|
762
|
+
}
|
|
763
|
+
this.socket = new WebSocket(this.url, wsOptions);
|
|
737
764
|
await new Promise((resolvePromise, rejectPromise) => {
|
|
738
765
|
let settled = false;
|
|
739
766
|
const resolveOnce = () => {
|
|
@@ -796,7 +823,7 @@ var AppServerClient = class {
|
|
|
796
823
|
this.initialized = false;
|
|
797
824
|
this.socket = null;
|
|
798
825
|
}
|
|
799
|
-
async ensureThread(explicitThreadId,
|
|
826
|
+
async ensureThread(explicitThreadId, savedThread, cwd, ephemeral) {
|
|
800
827
|
if (explicitThreadId) {
|
|
801
828
|
try {
|
|
802
829
|
const resumeResponse = await this.request("thread/resume", {
|
|
@@ -819,22 +846,38 @@ var AppServerClient = class {
|
|
|
819
846
|
if (loadedThreadId) {
|
|
820
847
|
return loadedThreadId;
|
|
821
848
|
}
|
|
822
|
-
if (
|
|
823
|
-
|
|
824
|
-
const resumeResponse = await this.request("thread/resume", {
|
|
825
|
-
threadId: resumeThreadId,
|
|
826
|
-
persistExtendedHistory: false
|
|
827
|
-
});
|
|
828
|
-
const resumedThreadId = resumeResponse?.thread?.id ?? resumeThreadId;
|
|
829
|
-
await this.refreshThreadState(resumedThreadId);
|
|
849
|
+
if (savedThread?.threadId) {
|
|
850
|
+
if (savedThread.cwd && !threadCwdMatches(cwd, savedThread.cwd)) {
|
|
830
851
|
this.logger(
|
|
831
|
-
`
|
|
832
|
-
);
|
|
833
|
-
return resumedThreadId;
|
|
834
|
-
} catch (error) {
|
|
835
|
-
this.logger(
|
|
836
|
-
`saved thread resume failed for ${resumeThreadId}; starting a fresh thread (${String(error)})`
|
|
852
|
+
`saved thread ${savedThread.threadId} cwd ${savedThread.cwd} does not match ${cwd}; skipping saved thread`
|
|
837
853
|
);
|
|
854
|
+
} else {
|
|
855
|
+
try {
|
|
856
|
+
const resumeResponse = await this.request("thread/resume", {
|
|
857
|
+
threadId: savedThread.threadId,
|
|
858
|
+
persistExtendedHistory: false
|
|
859
|
+
});
|
|
860
|
+
const resumedThreadId = resumeResponse?.thread?.id ?? savedThread.threadId;
|
|
861
|
+
await this.refreshThreadState(resumedThreadId);
|
|
862
|
+
if (!threadCwdMatches(cwd, this.currentThreadCwd)) {
|
|
863
|
+
this.logger(
|
|
864
|
+
`saved thread ${resumedThreadId} cwd ${this.currentThreadCwd ?? "unknown"} does not match ${cwd}; starting a fresh thread`
|
|
865
|
+
);
|
|
866
|
+
this.threadId = null;
|
|
867
|
+
this.currentThreadCwd = null;
|
|
868
|
+
this.activeTurnId = null;
|
|
869
|
+
this.lastTurnStatus = null;
|
|
870
|
+
} else {
|
|
871
|
+
this.logger(
|
|
872
|
+
`resumed saved thread ${resumedThreadId}${this.activeTurnId ? ` (active turn ${this.activeTurnId})` : ""}`
|
|
873
|
+
);
|
|
874
|
+
return resumedThreadId;
|
|
875
|
+
}
|
|
876
|
+
} catch (error) {
|
|
877
|
+
this.logger(
|
|
878
|
+
`saved thread resume failed for ${savedThread.threadId}; starting a fresh thread (${String(error)})`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
838
881
|
}
|
|
839
882
|
}
|
|
840
883
|
const startResponse = await this.request("thread/start", {
|
|
@@ -847,7 +890,9 @@ var AppServerClient = class {
|
|
|
847
890
|
if (!startedThreadId) {
|
|
848
891
|
throw new Error("thread/start did not return a thread id");
|
|
849
892
|
}
|
|
893
|
+
this.syncThreadStateFromThread(startResponse?.thread);
|
|
850
894
|
this.threadId = startedThreadId;
|
|
895
|
+
this.currentThreadCwd = this.currentThreadCwd ?? cwd;
|
|
851
896
|
this.activeTurnId = null;
|
|
852
897
|
this.lastTurnStatus = null;
|
|
853
898
|
this.logger(`started thread ${startedThreadId}`);
|
|
@@ -885,20 +930,13 @@ var AppServerClient = class {
|
|
|
885
930
|
continue;
|
|
886
931
|
}
|
|
887
932
|
}
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
933
|
+
const chosen = chooseLoadedThreadForCwd(cwd, threads);
|
|
934
|
+
if (!chosen) {
|
|
935
|
+
if (threads.length > 0) {
|
|
936
|
+
this.logger(`loaded threads exist but none match cwd ${cwd}`);
|
|
937
|
+
}
|
|
891
938
|
return null;
|
|
892
939
|
}
|
|
893
|
-
candidates.sort((left, right) => {
|
|
894
|
-
const leftActive = left.statusType === "active" ? 1 : 0;
|
|
895
|
-
const rightActive = right.statusType === "active" ? 1 : 0;
|
|
896
|
-
if (leftActive !== rightActive) {
|
|
897
|
-
return rightActive - leftActive;
|
|
898
|
-
}
|
|
899
|
-
return right.updatedAt - left.updatedAt;
|
|
900
|
-
});
|
|
901
|
-
const chosen = candidates[0];
|
|
902
940
|
this.syncThreadStateFromThread(chosen.thread);
|
|
903
941
|
this.logger(
|
|
904
942
|
`attached to loaded thread ${chosen.id}${this.activeTurnId ? ` (active turn ${this.activeTurnId})` : ""}`
|
|
@@ -971,6 +1009,7 @@ var AppServerClient = class {
|
|
|
971
1009
|
if (typeof thread?.id === "string") {
|
|
972
1010
|
this.threadId = thread.id;
|
|
973
1011
|
}
|
|
1012
|
+
this.currentThreadCwd = typeof thread?.cwd === "string" ? thread.cwd : null;
|
|
974
1013
|
let activeTurnId = null;
|
|
975
1014
|
let lastTurnStatus = null;
|
|
976
1015
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
@@ -1019,6 +1058,9 @@ var AppServerClient = class {
|
|
|
1019
1058
|
if (params?.thread?.id) {
|
|
1020
1059
|
this.threadId = params.thread.id;
|
|
1021
1060
|
}
|
|
1061
|
+
if (typeof params?.thread?.cwd === "string") {
|
|
1062
|
+
this.currentThreadCwd = params.thread.cwd;
|
|
1063
|
+
}
|
|
1022
1064
|
this.logger(`thread started ${params?.thread?.id ?? ""}`.trim());
|
|
1023
1065
|
break;
|
|
1024
1066
|
case "thread/status/changed":
|
|
@@ -1074,6 +1116,16 @@ var AppServerClient = class {
|
|
|
1074
1116
|
}
|
|
1075
1117
|
};
|
|
1076
1118
|
function writeHeartbeat(options, client, health) {
|
|
1119
|
+
if (client?.threadId) {
|
|
1120
|
+
const savedThread = readThreadState(options.stateDir);
|
|
1121
|
+
persistThreadState(
|
|
1122
|
+
options.stateDir,
|
|
1123
|
+
client.threadId,
|
|
1124
|
+
options.appServerUrl,
|
|
1125
|
+
options.ephemeral,
|
|
1126
|
+
client.currentThreadCwd ?? savedThread?.cwd ?? null
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1077
1129
|
const payload = {
|
|
1078
1130
|
pid: process.pid,
|
|
1079
1131
|
agent: options.agentName,
|
|
@@ -1083,6 +1135,7 @@ function writeHeartbeat(options, client, health) {
|
|
|
1083
1135
|
connected: client?.connected ?? false,
|
|
1084
1136
|
initialized: client?.initialized ?? false,
|
|
1085
1137
|
threadId: client?.threadId ?? null,
|
|
1138
|
+
threadCwd: client?.currentThreadCwd ?? null,
|
|
1086
1139
|
activeTurnId: client?.activeTurnId ?? null,
|
|
1087
1140
|
lastTurnStatus: client?.lastTurnStatus ?? null,
|
|
1088
1141
|
lastNotificationMethod: client?.lastNotificationMethod ?? null,
|
|
@@ -1219,10 +1272,8 @@ function buildOptions(argv) {
|
|
|
1219
1272
|
runOnce: parsed.runOnce,
|
|
1220
1273
|
waitAfterDispatchSeconds: parsed.waitAfterDispatchSeconds ?? 0,
|
|
1221
1274
|
appServerUrl,
|
|
1222
|
-
connectAppServerUrl:
|
|
1223
|
-
|
|
1224
|
-
readGatewayTokenFile(gatewayTokenFile)
|
|
1225
|
-
) : appServerUrl,
|
|
1275
|
+
connectAppServerUrl: appServerUrl,
|
|
1276
|
+
gatewayToken: gatewayTokenFile ? readGatewayTokenFile(gatewayTokenFile) : null,
|
|
1226
1277
|
gatewayTokenFile,
|
|
1227
1278
|
busyMode: parsed.busyMode ?? "steer",
|
|
1228
1279
|
threadId: parsed.threadId?.trim() || null,
|
|
@@ -1236,7 +1287,7 @@ async function main() {
|
|
|
1236
1287
|
options.messageLookbackMinutes,
|
|
1237
1288
|
options.processExistingMessages
|
|
1238
1289
|
);
|
|
1239
|
-
const
|
|
1290
|
+
const initialSavedThread = readThreadState(options.stateDir);
|
|
1240
1291
|
logStatus("codex app-server bridge ready");
|
|
1241
1292
|
console.log(` repo: ${options.repoRoot}`);
|
|
1242
1293
|
console.log(` comms: ${options.commsDir}`);
|
|
@@ -1252,14 +1303,15 @@ async function main() {
|
|
|
1252
1303
|
console.log(
|
|
1253
1304
|
` lookback: ${options.processExistingMessages ? "existing messages" : `${options.messageLookbackMinutes} minute(s)`}`
|
|
1254
1305
|
);
|
|
1255
|
-
if (options.threadId ||
|
|
1256
|
-
console.log(
|
|
1306
|
+
if (options.threadId || initialSavedThread?.threadId) {
|
|
1307
|
+
console.log(
|
|
1308
|
+
` thread: ${options.threadId ?? initialSavedThread?.threadId}`
|
|
1309
|
+
);
|
|
1257
1310
|
}
|
|
1258
1311
|
if (options.dryRun) {
|
|
1259
1312
|
logStatus("dry-run mode enabled");
|
|
1260
1313
|
}
|
|
1261
1314
|
let client = null;
|
|
1262
|
-
let savedThreadId = savedThread?.threadId ?? null;
|
|
1263
1315
|
const health = {
|
|
1264
1316
|
consecutiveFailureCount: 0
|
|
1265
1317
|
};
|
|
@@ -1267,11 +1319,16 @@ async function main() {
|
|
|
1267
1319
|
try {
|
|
1268
1320
|
if (!options.dryRun) {
|
|
1269
1321
|
if (!client || !client.connected) {
|
|
1270
|
-
client = new AppServerClient(
|
|
1322
|
+
client = new AppServerClient(
|
|
1323
|
+
options.connectAppServerUrl,
|
|
1324
|
+
logStatus,
|
|
1325
|
+
options.gatewayToken
|
|
1326
|
+
);
|
|
1271
1327
|
await client.connect();
|
|
1328
|
+
const savedThread = readThreadState(options.stateDir);
|
|
1272
1329
|
const threadId = await client.ensureThread(
|
|
1273
1330
|
options.threadId,
|
|
1274
|
-
|
|
1331
|
+
savedThread,
|
|
1275
1332
|
options.repoRoot,
|
|
1276
1333
|
options.ephemeral
|
|
1277
1334
|
);
|
|
@@ -1279,9 +1336,9 @@ async function main() {
|
|
|
1279
1336
|
options.stateDir,
|
|
1280
1337
|
threadId,
|
|
1281
1338
|
options.appServerUrl,
|
|
1282
|
-
options.ephemeral
|
|
1339
|
+
options.ephemeral,
|
|
1340
|
+
client.currentThreadCwd ?? options.repoRoot
|
|
1283
1341
|
);
|
|
1284
|
-
savedThreadId = threadId;
|
|
1285
1342
|
writeHeartbeat(options, client, health);
|
|
1286
1343
|
const bootstrapped = await maybeBootstrapHeadlessTurn(
|
|
1287
1344
|
options,
|
|
@@ -1353,6 +1410,7 @@ export {
|
|
|
1353
1410
|
HEADLESS_WARMUP_PROMPT,
|
|
1354
1411
|
buildOptions,
|
|
1355
1412
|
buildUserInput,
|
|
1413
|
+
chooseLoadedThreadForCwd,
|
|
1356
1414
|
isOwnMessageSender,
|
|
1357
1415
|
main,
|
|
1358
1416
|
maybeBootstrapHeadlessTurn,
|
|
@@ -1360,6 +1418,7 @@ export {
|
|
|
1360
1418
|
resolveAddressLabel,
|
|
1361
1419
|
resolveAgentId,
|
|
1362
1420
|
resolveCurrentAgentName,
|
|
1421
|
+
threadCwdMatches,
|
|
1363
1422
|
waitForTurnCompletion
|
|
1364
1423
|
};
|
|
1365
1424
|
//# sourceMappingURL=codex-app-server-bridge.mjs.map
|