@drisp/cli 0.4.4 → 0.4.7
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/dist/{WorkflowInstallWizard-2MC5A7W4.js → WorkflowInstallWizard-X754ND4V.js} +2 -2
- package/dist/athena-gateway.js +5 -3853
- package/dist/chunk-2OJ3GGIP.js +104 -0
- package/dist/{chunk-5VK2ZMVV.js → chunk-A54HGVML.js} +96 -95
- package/dist/chunk-D4W7RB25.js +76 -0
- package/dist/chunk-SKWAGQXF.js +4124 -0
- package/dist/{chunk-JAPBSL7D.js → chunk-TQKNZNDP.js} +1115 -770
- package/dist/{chunk-4CRZXLIP.js → chunk-WRHKXH5M.js} +136 -136
- package/dist/chunk-ZVOGOZNT.js +395 -0
- package/dist/cli.js +1458 -1110
- package/dist/dashboard-daemon.js +9 -107
- package/dist/supervisor.js +692 -0
- package/package.json +1 -1
- package/dist/chunk-6TJHAUNB.js +0 -161
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
isValidHookEventEnvelope,
|
|
14
14
|
resolveHookSocketPath
|
|
15
15
|
} from "./chunk-BTKQ67RE.js";
|
|
16
|
+
import {
|
|
17
|
+
createInstanceSocketClient,
|
|
18
|
+
writeAttachmentMirror
|
|
19
|
+
} from "./chunk-ZVOGOZNT.js";
|
|
16
20
|
import {
|
|
17
21
|
TransportUnreachableError,
|
|
18
22
|
createUdsClientTransport,
|
|
@@ -23,16 +27,46 @@ import {
|
|
|
23
27
|
traceGatewayFrame,
|
|
24
28
|
trackGatewayTransportReconnect,
|
|
25
29
|
writeGatewayTrace
|
|
26
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-WRHKXH5M.js";
|
|
27
31
|
import {
|
|
28
32
|
compileWorkflowPlan,
|
|
29
33
|
createWorkflowRunner,
|
|
34
|
+
installWorkflowFromSource,
|
|
30
35
|
readConfig,
|
|
31
36
|
readGlobalConfig,
|
|
32
37
|
resolveActiveWorkflow,
|
|
33
38
|
resolveWorkflow,
|
|
39
|
+
resolveWorkflowInstall,
|
|
34
40
|
resolveWorkflowPlugins
|
|
35
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-A54HGVML.js";
|
|
42
|
+
|
|
43
|
+
// src/infra/daemon/stateDir.ts
|
|
44
|
+
import fs from "fs";
|
|
45
|
+
import os from "os";
|
|
46
|
+
import path from "path";
|
|
47
|
+
function daemonStatePaths(env = process.env) {
|
|
48
|
+
const xdg = env["XDG_STATE_HOME"];
|
|
49
|
+
const home = env["HOME"] ?? os.homedir();
|
|
50
|
+
const base = xdg && xdg.length > 0 ? xdg : path.join(home, ".local", "state");
|
|
51
|
+
const dir = path.join(base, "drisp");
|
|
52
|
+
return {
|
|
53
|
+
dir,
|
|
54
|
+
pidPath: path.join(dir, "dashboard-daemon.pid"),
|
|
55
|
+
logPath: path.join(dir, "dashboard-daemon.log"),
|
|
56
|
+
socketPath: path.join(dir, "dashboard-daemon.sock")
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function ensureDaemonStateDir(env = process.env) {
|
|
60
|
+
const paths = daemonStatePaths(env);
|
|
61
|
+
fs.mkdirSync(paths.dir, { recursive: true, mode: 448 });
|
|
62
|
+
if (process.platform !== "win32") {
|
|
63
|
+
try {
|
|
64
|
+
fs.chmodSync(paths.dir, 448);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return paths;
|
|
69
|
+
}
|
|
36
70
|
|
|
37
71
|
// src/shared/utils/processRegistry.ts
|
|
38
72
|
var ProcessRegistry = class {
|
|
@@ -146,12 +180,12 @@ var processRegistry = new ProcessRegistry();
|
|
|
146
180
|
|
|
147
181
|
// src/harnesses/claude/runtime/server.ts
|
|
148
182
|
import * as net from "net";
|
|
149
|
-
import * as
|
|
150
|
-
import * as
|
|
183
|
+
import * as fs3 from "fs";
|
|
184
|
+
import * as path3 from "path";
|
|
151
185
|
|
|
152
186
|
// src/harnesses/claude/runtime/cleanupStaleSockets.ts
|
|
153
|
-
import * as
|
|
154
|
-
import * as
|
|
187
|
+
import * as fs2 from "fs";
|
|
188
|
+
import * as path2 from "path";
|
|
155
189
|
var SOCK_PATTERN = /^ink-(\d+)\.sock$/;
|
|
156
190
|
function isPidAlive(pid) {
|
|
157
191
|
try {
|
|
@@ -165,7 +199,7 @@ function isPidAlive(pid) {
|
|
|
165
199
|
function cleanupStaleSockets(sockDir) {
|
|
166
200
|
let entries;
|
|
167
201
|
try {
|
|
168
|
-
entries =
|
|
202
|
+
entries = fs2.readdirSync(sockDir);
|
|
169
203
|
} catch {
|
|
170
204
|
return [];
|
|
171
205
|
}
|
|
@@ -176,7 +210,7 @@ function cleanupStaleSockets(sockDir) {
|
|
|
176
210
|
const pid = parseInt(match[1], 10);
|
|
177
211
|
if (isPidAlive(pid)) continue;
|
|
178
212
|
try {
|
|
179
|
-
|
|
213
|
+
fs2.unlinkSync(path2.join(sockDir, entry));
|
|
180
214
|
removed.push(entry);
|
|
181
215
|
} catch {
|
|
182
216
|
}
|
|
@@ -355,7 +389,15 @@ var RULES = {
|
|
|
355
389
|
},
|
|
356
390
|
unknown: DEFAULT_HINTS
|
|
357
391
|
};
|
|
358
|
-
|
|
392
|
+
var ASK_USER_QUESTION_HINTS = {
|
|
393
|
+
expectsDecision: true,
|
|
394
|
+
defaultTimeoutMs: null,
|
|
395
|
+
canBlock: true
|
|
396
|
+
};
|
|
397
|
+
function getInteractionHints(kind, toolName) {
|
|
398
|
+
if (kind === "tool.pre" && toolName === "AskUserQuestion") {
|
|
399
|
+
return ASK_USER_QUESTION_HINTS;
|
|
400
|
+
}
|
|
359
401
|
const maybeRule = RULES[kind];
|
|
360
402
|
return maybeRule ?? DEFAULT_HINTS;
|
|
361
403
|
}
|
|
@@ -664,7 +706,7 @@ function mapEnvelopeToRuntimeEvent(envelope) {
|
|
|
664
706
|
agentId: translated.agentId,
|
|
665
707
|
agentType: translated.agentType,
|
|
666
708
|
context,
|
|
667
|
-
interaction: getInteractionHints(translated.kind),
|
|
709
|
+
interaction: getInteractionHints(translated.kind, translated.toolName),
|
|
668
710
|
payload: safePayload,
|
|
669
711
|
display: buildClaudeDisplay(translated.kind, translated.data)
|
|
670
712
|
};
|
|
@@ -754,6 +796,13 @@ var BoundedLineParser = class {
|
|
|
754
796
|
}
|
|
755
797
|
};
|
|
756
798
|
|
|
799
|
+
// src/harnesses/claude/runtime/timeoutResolution.ts
|
|
800
|
+
function resolveAdapterTimeoutMs(interaction, fallbackMs) {
|
|
801
|
+
const ms = interaction.defaultTimeoutMs;
|
|
802
|
+
if (ms === null) return null;
|
|
803
|
+
return ms ?? fallbackMs;
|
|
804
|
+
}
|
|
805
|
+
|
|
757
806
|
// src/harnesses/claude/runtime/streamJsonToolParser.ts
|
|
758
807
|
function extractResultText(content) {
|
|
759
808
|
if (typeof content === "string") return content;
|
|
@@ -943,7 +992,7 @@ function createServer2(opts) {
|
|
|
943
992
|
return startPromise;
|
|
944
993
|
}
|
|
945
994
|
socketPath = resolveHookSocketPath(instanceId);
|
|
946
|
-
const socketDir =
|
|
995
|
+
const socketDir = path3.dirname(socketPath);
|
|
947
996
|
lastError = null;
|
|
948
997
|
const simulatedFailure = getSimulatedStartupFailure();
|
|
949
998
|
if (simulatedFailure) {
|
|
@@ -969,7 +1018,7 @@ function createServer2(opts) {
|
|
|
969
1018
|
return Promise.resolve();
|
|
970
1019
|
}
|
|
971
1020
|
try {
|
|
972
|
-
|
|
1021
|
+
fs3.mkdirSync(socketDir, { recursive: true });
|
|
973
1022
|
} catch (error) {
|
|
974
1023
|
status = "stopped";
|
|
975
1024
|
lastError = makeStartupError(
|
|
@@ -983,7 +1032,7 @@ function createServer2(opts) {
|
|
|
983
1032
|
}
|
|
984
1033
|
cleanupStaleSockets(socketDir);
|
|
985
1034
|
try {
|
|
986
|
-
|
|
1035
|
+
fs3.unlinkSync(socketPath);
|
|
987
1036
|
} catch {
|
|
988
1037
|
}
|
|
989
1038
|
server = net.createServer((socket) => {
|
|
@@ -1002,8 +1051,11 @@ function createServer2(opts) {
|
|
|
1002
1051
|
sessionId = envelope.session_id;
|
|
1003
1052
|
}
|
|
1004
1053
|
const runtimeEvent = mapEnvelopeToRuntimeEvent(envelope);
|
|
1005
|
-
const timeoutMs =
|
|
1006
|
-
|
|
1054
|
+
const timeoutMs = resolveAdapterTimeoutMs(
|
|
1055
|
+
runtimeEvent.interaction,
|
|
1056
|
+
PENDING_TTL_MS
|
|
1057
|
+
);
|
|
1058
|
+
const timer = timeoutMs === null ? void 0 : setTimeout(() => {
|
|
1007
1059
|
const timeoutDecision = {
|
|
1008
1060
|
type: "passthrough",
|
|
1009
1061
|
source: "timeout"
|
|
@@ -1068,7 +1120,7 @@ function createServer2(opts) {
|
|
|
1068
1120
|
try {
|
|
1069
1121
|
currentServer.listen(socketPath, () => {
|
|
1070
1122
|
try {
|
|
1071
|
-
|
|
1123
|
+
fs3.chmodSync(socketPath, 384);
|
|
1072
1124
|
} catch {
|
|
1073
1125
|
}
|
|
1074
1126
|
});
|
|
@@ -1099,7 +1151,7 @@ function createServer2(opts) {
|
|
|
1099
1151
|
status = "stopped";
|
|
1100
1152
|
lastError = null;
|
|
1101
1153
|
try {
|
|
1102
|
-
|
|
1154
|
+
fs3.unlinkSync(socketPath);
|
|
1103
1155
|
} catch {
|
|
1104
1156
|
}
|
|
1105
1157
|
},
|
|
@@ -1151,20 +1203,20 @@ function createClaudeHookRuntime(opts) {
|
|
|
1151
1203
|
}
|
|
1152
1204
|
|
|
1153
1205
|
// src/harnesses/claude/config/readSettingsModel.ts
|
|
1154
|
-
import
|
|
1155
|
-
import
|
|
1156
|
-
import
|
|
1206
|
+
import fs4 from "fs";
|
|
1207
|
+
import os2 from "os";
|
|
1208
|
+
import path4 from "path";
|
|
1157
1209
|
function readClaudeSettingsModel(projectDir) {
|
|
1158
|
-
const home =
|
|
1210
|
+
const home = os2.homedir();
|
|
1159
1211
|
const paths = [
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1212
|
+
path4.join(projectDir, ".claude", "settings.local.json"),
|
|
1213
|
+
path4.join(projectDir, ".claude", "settings.json"),
|
|
1214
|
+
path4.join(home, ".claude", "settings.local.json"),
|
|
1215
|
+
path4.join(home, ".claude", "settings.json")
|
|
1164
1216
|
];
|
|
1165
1217
|
for (const p of paths) {
|
|
1166
1218
|
try {
|
|
1167
|
-
const raw = JSON.parse(
|
|
1219
|
+
const raw = JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
1168
1220
|
if (raw.model) return raw.model;
|
|
1169
1221
|
} catch {
|
|
1170
1222
|
}
|
|
@@ -1186,16 +1238,16 @@ function resolveClaudeModel({
|
|
|
1186
1238
|
|
|
1187
1239
|
// src/harnesses/claude/system/verifyHarness.ts
|
|
1188
1240
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1189
|
-
import
|
|
1190
|
-
import
|
|
1241
|
+
import fs10 from "fs";
|
|
1242
|
+
import path9 from "path";
|
|
1191
1243
|
|
|
1192
1244
|
// src/harnesses/claude/system/detectVersion.ts
|
|
1193
1245
|
import { execFileSync } from "child_process";
|
|
1194
1246
|
|
|
1195
1247
|
// src/harnesses/claude/system/resolveBinary.ts
|
|
1196
|
-
import * as
|
|
1197
|
-
import * as
|
|
1198
|
-
import * as
|
|
1248
|
+
import * as fs5 from "fs";
|
|
1249
|
+
import * as os3 from "os";
|
|
1250
|
+
import * as path5 from "path";
|
|
1199
1251
|
var CLAUDE_BINARY_ENV_VARS = [
|
|
1200
1252
|
"ATHENA_CLAUDE_PATH",
|
|
1201
1253
|
"CLAUDE_BINARY",
|
|
@@ -1203,7 +1255,7 @@ var CLAUDE_BINARY_ENV_VARS = [
|
|
|
1203
1255
|
];
|
|
1204
1256
|
function isExecutablePath(candidatePath) {
|
|
1205
1257
|
try {
|
|
1206
|
-
|
|
1258
|
+
fs5.accessSync(candidatePath, fs5.constants.X_OK);
|
|
1207
1259
|
return true;
|
|
1208
1260
|
} catch {
|
|
1209
1261
|
return false;
|
|
@@ -1211,7 +1263,7 @@ function isExecutablePath(candidatePath) {
|
|
|
1211
1263
|
}
|
|
1212
1264
|
function macosFallbacks(homeDir) {
|
|
1213
1265
|
return [
|
|
1214
|
-
|
|
1266
|
+
path5.join(homeDir, ".local", "bin", "claude"),
|
|
1215
1267
|
"/opt/homebrew/bin/claude",
|
|
1216
1268
|
"/usr/local/bin/claude",
|
|
1217
1269
|
"/usr/bin/claude"
|
|
@@ -1219,16 +1271,16 @@ function macosFallbacks(homeDir) {
|
|
|
1219
1271
|
}
|
|
1220
1272
|
function linuxFallbacks(homeDir) {
|
|
1221
1273
|
return [
|
|
1222
|
-
|
|
1274
|
+
path5.join(homeDir, ".local", "bin", "claude"),
|
|
1223
1275
|
"/usr/local/bin/claude",
|
|
1224
1276
|
"/usr/bin/claude"
|
|
1225
1277
|
];
|
|
1226
1278
|
}
|
|
1227
1279
|
function collectCandidatePaths(pathValue, platform, homeDir) {
|
|
1228
1280
|
const candidates = [];
|
|
1229
|
-
for (const entry of (pathValue ?? "").split(
|
|
1281
|
+
for (const entry of (pathValue ?? "").split(path5.delimiter)) {
|
|
1230
1282
|
if (!entry) continue;
|
|
1231
|
-
candidates.push(
|
|
1283
|
+
candidates.push(path5.join(entry, "claude"));
|
|
1232
1284
|
}
|
|
1233
1285
|
if (platform === "darwin") {
|
|
1234
1286
|
candidates.push(...macosFallbacks(homeDir));
|
|
@@ -1240,7 +1292,7 @@ function collectCandidatePaths(pathValue, platform, homeDir) {
|
|
|
1240
1292
|
function resolveClaudeBinary(options = {}) {
|
|
1241
1293
|
const env = options.env ?? process.env;
|
|
1242
1294
|
const platform = options.platform ?? process.platform;
|
|
1243
|
-
const homeDir = options.homeDir ??
|
|
1295
|
+
const homeDir = options.homeDir ?? os3.homedir();
|
|
1244
1296
|
const pathValue = options.pathValue ?? env["PATH"];
|
|
1245
1297
|
const isExecutable = options.isExecutable ?? isExecutablePath;
|
|
1246
1298
|
for (const envVar of CLAUDE_BINARY_ENV_VARS) {
|
|
@@ -1278,9 +1330,9 @@ function detectClaudeVersion() {
|
|
|
1278
1330
|
}
|
|
1279
1331
|
|
|
1280
1332
|
// src/harnesses/claude/hooks/generateHookSettings.ts
|
|
1281
|
-
import * as
|
|
1282
|
-
import * as
|
|
1283
|
-
import * as
|
|
1333
|
+
import * as fs6 from "fs";
|
|
1334
|
+
import * as path6 from "path";
|
|
1335
|
+
import * as os4 from "os";
|
|
1284
1336
|
import { fileURLToPath } from "url";
|
|
1285
1337
|
var TOOL_HOOK_EVENTS = [
|
|
1286
1338
|
"PreToolUse",
|
|
@@ -1319,17 +1371,17 @@ function formatHookForwarderCommand(nodePath, scriptPath) {
|
|
|
1319
1371
|
return `${quoteShellArg(nodePath)} ${quoteShellArg(scriptPath)}`;
|
|
1320
1372
|
}
|
|
1321
1373
|
function resolveHookForwarderPath(entryUrl) {
|
|
1322
|
-
let currentDir =
|
|
1323
|
-
const siblingPath =
|
|
1324
|
-
if (
|
|
1374
|
+
let currentDir = path6.dirname(fileURLToPath(entryUrl));
|
|
1375
|
+
const siblingPath = path6.join(currentDir, "hook-forwarder.js");
|
|
1376
|
+
if (fs6.existsSync(siblingPath)) {
|
|
1325
1377
|
return siblingPath;
|
|
1326
1378
|
}
|
|
1327
1379
|
for (; ; ) {
|
|
1328
|
-
const candidatePath =
|
|
1329
|
-
if (
|
|
1380
|
+
const candidatePath = path6.join(currentDir, "dist", "hook-forwarder.js");
|
|
1381
|
+
if (fs6.existsSync(candidatePath)) {
|
|
1330
1382
|
return candidatePath;
|
|
1331
1383
|
}
|
|
1332
|
-
const parentDir =
|
|
1384
|
+
const parentDir = path6.dirname(currentDir);
|
|
1333
1385
|
if (parentDir === currentDir) {
|
|
1334
1386
|
return null;
|
|
1335
1387
|
}
|
|
@@ -1386,10 +1438,10 @@ function generateHookSettings(tempDir, authOverlay) {
|
|
|
1386
1438
|
if (authOverlay?.apiKeyHelper) {
|
|
1387
1439
|
settings.apiKeyHelper = authOverlay.apiKeyHelper;
|
|
1388
1440
|
}
|
|
1389
|
-
const dir = tempDir ??
|
|
1441
|
+
const dir = tempDir ?? os4.tmpdir();
|
|
1390
1442
|
const filename = `athena-hooks-${process.pid}-${Date.now()}.json`;
|
|
1391
|
-
const settingsPath =
|
|
1392
|
-
|
|
1443
|
+
const settingsPath = path6.join(dir, filename);
|
|
1444
|
+
fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), {
|
|
1393
1445
|
encoding: "utf8",
|
|
1394
1446
|
mode: 384
|
|
1395
1447
|
});
|
|
@@ -1404,8 +1456,8 @@ function generateHookSettings(tempDir, authOverlay) {
|
|
|
1404
1456
|
settingsPath,
|
|
1405
1457
|
cleanup: () => {
|
|
1406
1458
|
try {
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1459
|
+
if (fs6.existsSync(settingsPath)) {
|
|
1460
|
+
fs6.unlinkSync(settingsPath);
|
|
1409
1461
|
}
|
|
1410
1462
|
} catch {
|
|
1411
1463
|
}
|
|
@@ -1429,13 +1481,13 @@ function registerCleanupOnExit(cleanup) {
|
|
|
1429
1481
|
}
|
|
1430
1482
|
|
|
1431
1483
|
// src/harnesses/claude/auth/runtimeAuth.ts
|
|
1432
|
-
import
|
|
1484
|
+
import os7 from "os";
|
|
1433
1485
|
|
|
1434
1486
|
// src/harnesses/claude/system/doctorProbes.ts
|
|
1435
1487
|
import { spawn, spawnSync } from "child_process";
|
|
1436
|
-
import * as
|
|
1437
|
-
import * as
|
|
1438
|
-
import * as
|
|
1488
|
+
import * as fs7 from "fs";
|
|
1489
|
+
import * as os5 from "os";
|
|
1490
|
+
import * as path7 from "path";
|
|
1439
1491
|
var DOCTOR_PROMPT = "Reply with exactly: ATHENA_DOCTOR_OK";
|
|
1440
1492
|
var DOCTOR_EXPECTED = "ATHENA_DOCTOR_OK";
|
|
1441
1493
|
var DOCTOR_TIMEOUT_MS = 3e4;
|
|
@@ -1553,15 +1605,15 @@ function scanSettingsFiles(homeDir, cwd, env, platform, readFileFn) {
|
|
|
1553
1605
|
} else if (platform === "win32") {
|
|
1554
1606
|
const programFiles = env["PROGRAMFILES"] ?? "C:\\Program Files";
|
|
1555
1607
|
candidates.push(
|
|
1556
|
-
|
|
1608
|
+
path7.join(programFiles, "ClaudeCode", "managed-settings.json")
|
|
1557
1609
|
);
|
|
1558
1610
|
} else {
|
|
1559
1611
|
candidates.push("/etc/claude-code/managed-settings.json");
|
|
1560
1612
|
}
|
|
1561
|
-
const configDir = env["CLAUDE_CONFIG_DIR"] ??
|
|
1562
|
-
candidates.push(
|
|
1563
|
-
candidates.push(
|
|
1564
|
-
candidates.push(
|
|
1613
|
+
const configDir = env["CLAUDE_CONFIG_DIR"] ?? path7.join(homeDir, ".claude");
|
|
1614
|
+
candidates.push(path7.join(configDir, "settings.json"));
|
|
1615
|
+
candidates.push(path7.join(cwd, ".claude", "settings.json"));
|
|
1616
|
+
candidates.push(path7.join(cwd, ".claude", "settings.local.json"));
|
|
1565
1617
|
const scan = {
|
|
1566
1618
|
apiKeyHelper: null,
|
|
1567
1619
|
envApiKey: null,
|
|
@@ -1642,9 +1694,9 @@ function lookupCredential(options = {}) {
|
|
|
1642
1694
|
function lookupAllCredentials(options = {}) {
|
|
1643
1695
|
const env = options.env ?? process.env;
|
|
1644
1696
|
const platform = options.platform ?? process.platform;
|
|
1645
|
-
const homeDir = options.homeDir ??
|
|
1697
|
+
const homeDir = options.homeDir ?? os5.homedir();
|
|
1646
1698
|
const cwd = options.cwd ?? process.cwd();
|
|
1647
|
-
const readFileFn = options.readFileFn ?? ((p) =>
|
|
1699
|
+
const readFileFn = options.readFileFn ?? ((p) => fs7.readFileSync(p, "utf8"));
|
|
1648
1700
|
const found = [];
|
|
1649
1701
|
const seen = /* @__PURE__ */ new Set();
|
|
1650
1702
|
const push = (cred) => {
|
|
@@ -1713,9 +1765,9 @@ function lookupAllCredentials(options = {}) {
|
|
|
1713
1765
|
}
|
|
1714
1766
|
}
|
|
1715
1767
|
const dotenvCandidates = [
|
|
1716
|
-
{ source: "dotenv:.env", path:
|
|
1717
|
-
{ source: "dotenv:.env.local", path:
|
|
1718
|
-
{ source: "dotenv:~/.env", path:
|
|
1768
|
+
{ source: "dotenv:.env", path: path7.join(cwd, ".env") },
|
|
1769
|
+
{ source: "dotenv:.env.local", path: path7.join(cwd, ".env.local") },
|
|
1770
|
+
{ source: "dotenv:~/.env", path: path7.join(homeDir, ".env") }
|
|
1719
1771
|
];
|
|
1720
1772
|
for (const candidate of dotenvCandidates) {
|
|
1721
1773
|
try {
|
|
@@ -1788,8 +1840,8 @@ function lookupAllCredentials(options = {}) {
|
|
|
1788
1840
|
const raw = lookup("Claude Code-credentials");
|
|
1789
1841
|
if (raw) pushBlobCredentials("keychain:Claude Code-credentials", raw);
|
|
1790
1842
|
} else {
|
|
1791
|
-
const credentialsPath =
|
|
1792
|
-
env["CLAUDE_CONFIG_DIR"] ??
|
|
1843
|
+
const credentialsPath = path7.join(
|
|
1844
|
+
env["CLAUDE_CONFIG_DIR"] ?? path7.join(homeDir, ".claude"),
|
|
1793
1845
|
".credentials.json"
|
|
1794
1846
|
);
|
|
1795
1847
|
try {
|
|
@@ -1802,7 +1854,7 @@ function lookupAllCredentials(options = {}) {
|
|
|
1802
1854
|
}
|
|
1803
1855
|
const configHome = env["CLAUDE_CONFIG_DIR"] ?? homeDir;
|
|
1804
1856
|
try {
|
|
1805
|
-
const raw = readFileFn(
|
|
1857
|
+
const raw = readFileFn(path7.join(configHome, ".claude.json"));
|
|
1806
1858
|
if (raw.includes(SK_ANT_API_PREFIX)) {
|
|
1807
1859
|
const apiKey2 = extractApiKeyFromParsed(safeJsonParse(raw));
|
|
1808
1860
|
if (apiKey2) {
|
|
@@ -1816,19 +1868,19 @@ function lookupAllCredentials(options = {}) {
|
|
|
1816
1868
|
function shellQuoteSingle(value) {
|
|
1817
1869
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1818
1870
|
}
|
|
1819
|
-
function buildApiKeyHelperSettings(value, tempDir =
|
|
1871
|
+
function buildApiKeyHelperSettings(value, tempDir = os5.tmpdir()) {
|
|
1820
1872
|
const filename = `athena-doctor-helper-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
|
|
1821
|
-
const settingsPath =
|
|
1873
|
+
const settingsPath = path7.join(tempDir, filename);
|
|
1822
1874
|
const settings = {
|
|
1823
1875
|
apiKeyHelper: `printf %s ${shellQuoteSingle(value)}`
|
|
1824
1876
|
};
|
|
1825
|
-
|
|
1877
|
+
fs7.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
1826
1878
|
return {
|
|
1827
1879
|
settingsPath,
|
|
1828
1880
|
cleanup: () => {
|
|
1829
1881
|
try {
|
|
1830
|
-
if (
|
|
1831
|
-
|
|
1882
|
+
if (fs7.existsSync(settingsPath)) {
|
|
1883
|
+
fs7.unlinkSync(settingsPath);
|
|
1832
1884
|
}
|
|
1833
1885
|
} catch {
|
|
1834
1886
|
}
|
|
@@ -1851,7 +1903,7 @@ function formatProbeCommand(probe, claudeBinary, aliases) {
|
|
|
1851
1903
|
parts.push(`${key}=${shellQuoteArg(maskCredentialValue(value))}`);
|
|
1852
1904
|
}
|
|
1853
1905
|
}
|
|
1854
|
-
parts.push(
|
|
1906
|
+
parts.push(path7.basename(claudeBinary));
|
|
1855
1907
|
const aliasFor = (arg) => {
|
|
1856
1908
|
if (!aliases) return null;
|
|
1857
1909
|
for (const [name, fullPath] of aliases) {
|
|
@@ -2206,12 +2258,12 @@ function probeSkipReason(probe, opts) {
|
|
|
2206
2258
|
}
|
|
2207
2259
|
|
|
2208
2260
|
// src/harnesses/claude/auth/portableAuth.ts
|
|
2209
|
-
import
|
|
2210
|
-
import
|
|
2261
|
+
import fs9 from "fs";
|
|
2262
|
+
import os6 from "os";
|
|
2211
2263
|
|
|
2212
2264
|
// src/harnesses/claude/auth/settingsSurfaces.ts
|
|
2213
|
-
import
|
|
2214
|
-
import
|
|
2265
|
+
import fs8 from "fs";
|
|
2266
|
+
import path8 from "path";
|
|
2215
2267
|
var PORTABLE_PROVIDER_ENV_VARS = [
|
|
2216
2268
|
"ANTHROPIC_API_KEY",
|
|
2217
2269
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -2230,7 +2282,7 @@ var PORTABLE_PROVIDER_ENV_VARS = [
|
|
|
2230
2282
|
"CLAUDE_CODE_SKIP_FOUNDRY_AUTH"
|
|
2231
2283
|
];
|
|
2232
2284
|
function resolveClaudeSettingsDir(homeDir, env) {
|
|
2233
|
-
return env["CLAUDE_CONFIG_DIR"] ??
|
|
2285
|
+
return env["CLAUDE_CONFIG_DIR"] ?? path8.join(homeDir, ".claude");
|
|
2234
2286
|
}
|
|
2235
2287
|
function managedSettingsDir(platform, env) {
|
|
2236
2288
|
if (platform === "darwin") {
|
|
@@ -2238,27 +2290,27 @@ function managedSettingsDir(platform, env) {
|
|
|
2238
2290
|
}
|
|
2239
2291
|
if (platform === "win32") {
|
|
2240
2292
|
const programFiles = env["PROGRAMFILES"] ?? "C:\\Program Files";
|
|
2241
|
-
return
|
|
2293
|
+
return path8.join(programFiles, "ClaudeCode");
|
|
2242
2294
|
}
|
|
2243
2295
|
return "/etc/claude-code";
|
|
2244
2296
|
}
|
|
2245
|
-
function listManagedSettingsPaths(platform, env, readdirSync2 =
|
|
2297
|
+
function listManagedSettingsPaths(platform, env, readdirSync2 = fs8.readdirSync) {
|
|
2246
2298
|
const baseDir = managedSettingsDir(platform, env);
|
|
2247
|
-
const paths = [
|
|
2248
|
-
const dropInDir =
|
|
2299
|
+
const paths = [path8.join(baseDir, "managed-settings.json")];
|
|
2300
|
+
const dropInDir = path8.join(baseDir, "managed-settings.d");
|
|
2249
2301
|
try {
|
|
2250
|
-
const dropIns = readdirSync2(dropInDir).filter((name) => name.endsWith(".json")).sort().map((name) =>
|
|
2302
|
+
const dropIns = readdirSync2(dropInDir).filter((name) => name.endsWith(".json")).sort().map((name) => path8.join(dropInDir, name));
|
|
2251
2303
|
paths.push(...dropIns);
|
|
2252
2304
|
} catch {
|
|
2253
2305
|
}
|
|
2254
2306
|
return paths;
|
|
2255
2307
|
}
|
|
2256
|
-
function resolveClaudeSettingsSurfacePaths(homeDir, cwd, platform, env, readdirSync2 =
|
|
2308
|
+
function resolveClaudeSettingsSurfacePaths(homeDir, cwd, platform, env, readdirSync2 = fs8.readdirSync) {
|
|
2257
2309
|
return {
|
|
2258
2310
|
managed: listManagedSettingsPaths(platform, env, readdirSync2),
|
|
2259
|
-
user:
|
|
2260
|
-
project:
|
|
2261
|
-
local:
|
|
2311
|
+
user: path8.join(resolveClaudeSettingsDir(homeDir, env), "settings.json"),
|
|
2312
|
+
project: path8.join(cwd, ".claude", "settings.json"),
|
|
2313
|
+
local: path8.join(cwd, ".claude", "settings.local.json")
|
|
2262
2314
|
};
|
|
2263
2315
|
}
|
|
2264
2316
|
|
|
@@ -2272,10 +2324,10 @@ function readJsonObject(filePath, readFileFn) {
|
|
|
2272
2324
|
}
|
|
2273
2325
|
function resolvePortableAuthSettings(options = {}) {
|
|
2274
2326
|
const cwd = options.cwd ?? process.cwd();
|
|
2275
|
-
const homeDir = options.homeDir ??
|
|
2327
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
2276
2328
|
const platform = options.platform ?? process.platform;
|
|
2277
2329
|
const env = options.env ?? process.env;
|
|
2278
|
-
const readFileFn = options.readFileFn ?? ((filePath) =>
|
|
2330
|
+
const readFileFn = options.readFileFn ?? ((filePath) => fs9.readFileSync(filePath, "utf8"));
|
|
2279
2331
|
const settingsPaths = resolveClaudeSettingsSurfacePaths(
|
|
2280
2332
|
homeDir,
|
|
2281
2333
|
cwd,
|
|
@@ -2335,7 +2387,7 @@ function resolveRuntimeAuthOverlay(options = {}) {
|
|
|
2335
2387
|
const credential = lookupCredential({
|
|
2336
2388
|
...options,
|
|
2337
2389
|
cwd: options.cwd ?? process.cwd(),
|
|
2338
|
-
homeDir: options.homeDir ??
|
|
2390
|
+
homeDir: options.homeDir ?? os7.homedir(),
|
|
2339
2391
|
platform: options.platform ?? process.platform,
|
|
2340
2392
|
env: options.env ?? process.env
|
|
2341
2393
|
});
|
|
@@ -2479,16 +2531,16 @@ function runClaudeAuthStatus(claudeBinary) {
|
|
|
2479
2531
|
}
|
|
2480
2532
|
function canExecute(candidatePath) {
|
|
2481
2533
|
try {
|
|
2482
|
-
|
|
2534
|
+
fs10.accessSync(candidatePath, fs10.constants.X_OK);
|
|
2483
2535
|
return true;
|
|
2484
2536
|
} catch {
|
|
2485
2537
|
return false;
|
|
2486
2538
|
}
|
|
2487
2539
|
}
|
|
2488
2540
|
function resolveExecutableOnPath(commandName, pathValue, fileExists) {
|
|
2489
|
-
for (const entry of pathValue.split(
|
|
2541
|
+
for (const entry of pathValue.split(path9.delimiter)) {
|
|
2490
2542
|
if (!entry) continue;
|
|
2491
|
-
const candidate =
|
|
2543
|
+
const candidate = path9.join(entry, commandName);
|
|
2492
2544
|
if (fileExists(candidate)) {
|
|
2493
2545
|
return candidate;
|
|
2494
2546
|
}
|
|
@@ -2565,7 +2617,7 @@ function verifyClaudeHarness(options = {}) {
|
|
|
2565
2617
|
}
|
|
2566
2618
|
if (hookForwarder.source === "bundled") {
|
|
2567
2619
|
const nodeOk = fileExists(hookForwarder.executable);
|
|
2568
|
-
const scriptOk = !!hookForwarder.scriptPath &&
|
|
2620
|
+
const scriptOk = !!hookForwarder.scriptPath && fs10.existsSync(hookForwarder.scriptPath);
|
|
2569
2621
|
checks.push({
|
|
2570
2622
|
label: "Hook forwarder",
|
|
2571
2623
|
status: nodeOk && scriptOk ? "pass" : "fail",
|
|
@@ -2730,8 +2782,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
2730
2782
|
|
|
2731
2783
|
// src/harnesses/claude/process/spawn.ts
|
|
2732
2784
|
import { spawn as spawn2 } from "child_process";
|
|
2733
|
-
import
|
|
2734
|
-
import
|
|
2785
|
+
import fs11 from "fs";
|
|
2786
|
+
import path10 from "path";
|
|
2735
2787
|
|
|
2736
2788
|
// src/harnesses/claude/config/isolation.ts
|
|
2737
2789
|
var DISALLOWED_FIRST_PARTY_MCPS = [
|
|
@@ -2979,11 +3031,11 @@ function makePreflightError(failureCode, message) {
|
|
|
2979
3031
|
}
|
|
2980
3032
|
function resolveExecutableOnPath2(commandName) {
|
|
2981
3033
|
const pathValue = process.env["PATH"] ?? "";
|
|
2982
|
-
for (const entry of pathValue.split(
|
|
3034
|
+
for (const entry of pathValue.split(path10.delimiter)) {
|
|
2983
3035
|
if (!entry) continue;
|
|
2984
|
-
const candidate =
|
|
3036
|
+
const candidate = path10.join(entry, commandName);
|
|
2985
3037
|
try {
|
|
2986
|
-
|
|
3038
|
+
fs11.accessSync(candidate, fs11.constants.X_OK);
|
|
2987
3039
|
return candidate;
|
|
2988
3040
|
} catch {
|
|
2989
3041
|
}
|
|
@@ -3008,7 +3060,7 @@ function runSpawnPreflight(hookSocketPath) {
|
|
|
3008
3060
|
);
|
|
3009
3061
|
}
|
|
3010
3062
|
const hookForwarder = resolveHookForwarderCommand();
|
|
3011
|
-
if (hookForwarder.source === "bundled" && (!
|
|
3063
|
+
if (hookForwarder.source === "bundled" && (!fs11.existsSync(hookForwarder.executable) || !hookForwarder.scriptPath || !fs11.existsSync(hookForwarder.scriptPath))) {
|
|
3012
3064
|
throw makePreflightError(
|
|
3013
3065
|
"hook_forwarder_missing",
|
|
3014
3066
|
"Athena hook forwarder bundle is missing. Rebuild or reinstall Athena before retrying."
|
|
@@ -5518,9 +5570,9 @@ function buildCodexPluginInstallMessage(plugins) {
|
|
|
5518
5570
|
}
|
|
5519
5571
|
|
|
5520
5572
|
// src/harnesses/codex/runtime/agentConfig.ts
|
|
5521
|
-
import
|
|
5522
|
-
import
|
|
5523
|
-
import
|
|
5573
|
+
import fs12 from "fs";
|
|
5574
|
+
import path11 from "path";
|
|
5575
|
+
import os8 from "os";
|
|
5524
5576
|
|
|
5525
5577
|
// src/shared/utils/yamlFrontmatter.ts
|
|
5526
5578
|
function parseSimpleYaml(lines) {
|
|
@@ -5679,7 +5731,7 @@ function discoverAgents(agentRoots) {
|
|
|
5679
5731
|
for (const root of agentRoots) {
|
|
5680
5732
|
let entries;
|
|
5681
5733
|
try {
|
|
5682
|
-
entries =
|
|
5734
|
+
entries = fs12.readdirSync(root, { withFileTypes: true });
|
|
5683
5735
|
} catch {
|
|
5684
5736
|
continue;
|
|
5685
5737
|
}
|
|
@@ -5687,9 +5739,9 @@ function discoverAgents(agentRoots) {
|
|
|
5687
5739
|
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
5688
5740
|
continue;
|
|
5689
5741
|
}
|
|
5690
|
-
const filePath =
|
|
5742
|
+
const filePath = path11.join(root, entry.name);
|
|
5691
5743
|
try {
|
|
5692
|
-
const content =
|
|
5744
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
5693
5745
|
const agent = parseAgentMd(filePath, content);
|
|
5694
5746
|
agents.push({ filePath, agent });
|
|
5695
5747
|
} catch (err) {
|
|
@@ -5745,8 +5797,8 @@ function resolveCodexAgentConfig(input) {
|
|
|
5745
5797
|
errors: allErrors
|
|
5746
5798
|
};
|
|
5747
5799
|
}
|
|
5748
|
-
const tempDir =
|
|
5749
|
-
|
|
5800
|
+
const tempDir = path11.join(os8.tmpdir(), `athena-agents-${sessionId}`);
|
|
5801
|
+
fs12.mkdirSync(tempDir, { recursive: true });
|
|
5750
5802
|
const edits = [
|
|
5751
5803
|
{
|
|
5752
5804
|
keyPath: "features.multi_agent",
|
|
@@ -5767,8 +5819,8 @@ function resolveCodexAgentConfig(input) {
|
|
|
5767
5819
|
const agentNames = [];
|
|
5768
5820
|
for (const { agent } of uniqueAgents) {
|
|
5769
5821
|
const toml = generateAgentToml(agent);
|
|
5770
|
-
const tomlPath =
|
|
5771
|
-
|
|
5822
|
+
const tomlPath = path11.join(tempDir, `${agent.name}.toml`);
|
|
5823
|
+
fs12.writeFileSync(tomlPath, toml, "utf-8");
|
|
5772
5824
|
edits.push({
|
|
5773
5825
|
keyPath: `agents.${agent.name}`,
|
|
5774
5826
|
value: {
|
|
@@ -5807,7 +5859,7 @@ function cleanupAgentConfig(tempDir) {
|
|
|
5807
5859
|
return;
|
|
5808
5860
|
}
|
|
5809
5861
|
try {
|
|
5810
|
-
|
|
5862
|
+
fs12.rmSync(tempDir, { recursive: true, force: true });
|
|
5811
5863
|
} catch {
|
|
5812
5864
|
}
|
|
5813
5865
|
}
|
|
@@ -6422,7 +6474,7 @@ function createCodexRuntime(opts) {
|
|
|
6422
6474
|
}
|
|
6423
6475
|
|
|
6424
6476
|
// src/harnesses/codex/session/sessionAssets.ts
|
|
6425
|
-
import
|
|
6477
|
+
import fs13 from "fs";
|
|
6426
6478
|
function asRecord4(value) {
|
|
6427
6479
|
if (typeof value === "object" && value !== null) {
|
|
6428
6480
|
return value;
|
|
@@ -6491,7 +6543,7 @@ function readMcpServers(configPath) {
|
|
|
6491
6543
|
}
|
|
6492
6544
|
let raw;
|
|
6493
6545
|
try {
|
|
6494
|
-
raw =
|
|
6546
|
+
raw = fs13.readFileSync(configPath, "utf-8");
|
|
6495
6547
|
} catch {
|
|
6496
6548
|
return {};
|
|
6497
6549
|
}
|
|
@@ -6985,9 +7037,9 @@ function listHarnessCapabilities() {
|
|
|
6985
7037
|
}
|
|
6986
7038
|
|
|
6987
7039
|
// src/infra/plugins/register.ts
|
|
6988
|
-
import
|
|
6989
|
-
import
|
|
6990
|
-
import
|
|
7040
|
+
import fs15 from "fs";
|
|
7041
|
+
import os9 from "os";
|
|
7042
|
+
import path13 from "path";
|
|
6991
7043
|
|
|
6992
7044
|
// src/app/commands/registry.ts
|
|
6993
7045
|
var commands = /* @__PURE__ */ new Map();
|
|
@@ -7018,8 +7070,8 @@ function getAll() {
|
|
|
7018
7070
|
}
|
|
7019
7071
|
|
|
7020
7072
|
// src/infra/plugins/loader.ts
|
|
7021
|
-
import
|
|
7022
|
-
import
|
|
7073
|
+
import fs14 from "fs";
|
|
7074
|
+
import path12 from "path";
|
|
7023
7075
|
|
|
7024
7076
|
// src/infra/plugins/frontmatter.ts
|
|
7025
7077
|
function parseFrontmatter(content) {
|
|
@@ -7036,27 +7088,27 @@ function parseFrontmatter(content) {
|
|
|
7036
7088
|
|
|
7037
7089
|
// src/infra/plugins/loader.ts
|
|
7038
7090
|
function loadPlugin(pluginDir) {
|
|
7039
|
-
if (!
|
|
7091
|
+
if (!fs14.existsSync(pluginDir)) {
|
|
7040
7092
|
throw new Error(`Plugin directory not found: ${pluginDir}`);
|
|
7041
7093
|
}
|
|
7042
|
-
const manifestPath =
|
|
7043
|
-
if (!
|
|
7094
|
+
const manifestPath = path12.join(pluginDir, ".claude-plugin", "plugin.json");
|
|
7095
|
+
if (!fs14.existsSync(manifestPath)) {
|
|
7044
7096
|
throw new Error(`Plugin manifest not found: ${manifestPath}`);
|
|
7045
7097
|
}
|
|
7046
|
-
JSON.parse(
|
|
7047
|
-
const skillsDir =
|
|
7048
|
-
if (!
|
|
7098
|
+
JSON.parse(fs14.readFileSync(manifestPath, "utf-8"));
|
|
7099
|
+
const skillsDir = path12.join(pluginDir, "skills");
|
|
7100
|
+
if (!fs14.existsSync(skillsDir)) {
|
|
7049
7101
|
return [];
|
|
7050
7102
|
}
|
|
7051
|
-
const mcpConfigPath =
|
|
7052
|
-
const hasMcpConfig =
|
|
7053
|
-
const entries =
|
|
7103
|
+
const mcpConfigPath = path12.join(pluginDir, ".mcp.json");
|
|
7104
|
+
const hasMcpConfig = fs14.existsSync(mcpConfigPath);
|
|
7105
|
+
const entries = fs14.readdirSync(skillsDir, { withFileTypes: true });
|
|
7054
7106
|
const commands2 = [];
|
|
7055
7107
|
for (const entry of entries) {
|
|
7056
7108
|
if (!entry.isDirectory()) continue;
|
|
7057
|
-
const skillPath =
|
|
7058
|
-
if (!
|
|
7059
|
-
const content =
|
|
7109
|
+
const skillPath = path12.join(skillsDir, entry.name, "SKILL.md");
|
|
7110
|
+
if (!fs14.existsSync(skillPath)) continue;
|
|
7111
|
+
const content = fs14.readFileSync(skillPath, "utf-8");
|
|
7060
7112
|
const parsed = parseFrontmatter(content);
|
|
7061
7113
|
if (!parsed.frontmatter["user-invocable"]) continue;
|
|
7062
7114
|
commands2.push(
|
|
@@ -7095,11 +7147,11 @@ function skillToCommand(frontmatter, body, mcpConfigPath) {
|
|
|
7095
7147
|
function buildPluginMcpConfig(pluginDirs, mcpServerOptions) {
|
|
7096
7148
|
const mergedServers = {};
|
|
7097
7149
|
for (const dir of pluginDirs) {
|
|
7098
|
-
const mcpPath =
|
|
7099
|
-
if (!
|
|
7150
|
+
const mcpPath = path13.join(dir, ".mcp.json");
|
|
7151
|
+
if (!fs15.existsSync(mcpPath)) {
|
|
7100
7152
|
continue;
|
|
7101
7153
|
}
|
|
7102
|
-
const config = JSON.parse(
|
|
7154
|
+
const config = JSON.parse(fs15.readFileSync(mcpPath, "utf-8"));
|
|
7103
7155
|
for (const [serverName, serverConfig] of Object.entries(
|
|
7104
7156
|
config.mcpServers ?? {}
|
|
7105
7157
|
)) {
|
|
@@ -7122,8 +7174,8 @@ function buildPluginMcpConfig(pluginDirs, mcpServerOptions) {
|
|
|
7122
7174
|
if (Object.keys(mergedServers).length === 0) {
|
|
7123
7175
|
return void 0;
|
|
7124
7176
|
}
|
|
7125
|
-
const mcpConfig =
|
|
7126
|
-
|
|
7177
|
+
const mcpConfig = path13.join(os9.tmpdir(), `athena-mcp-${process.pid}.json`);
|
|
7178
|
+
fs15.writeFileSync(mcpConfig, JSON.stringify({ mcpServers: mergedServers }));
|
|
7127
7179
|
return mcpConfig;
|
|
7128
7180
|
}
|
|
7129
7181
|
function registerPlugins(pluginDirs, mcpServerOptions, includeMcpConfig = true) {
|
|
@@ -7295,7 +7347,7 @@ var EXEC_EXIT_CODE = {
|
|
|
7295
7347
|
|
|
7296
7348
|
// src/app/exec/runner.ts
|
|
7297
7349
|
import crypto2 from "crypto";
|
|
7298
|
-
import
|
|
7350
|
+
import path18 from "path";
|
|
7299
7351
|
|
|
7300
7352
|
// src/core/feed/todo.ts
|
|
7301
7353
|
function isSubagentTool(toolName) {
|
|
@@ -7657,27 +7709,27 @@ function generateNeutralTitle(event, g) {
|
|
|
7657
7709
|
}
|
|
7658
7710
|
|
|
7659
7711
|
// src/core/feed/transcript.ts
|
|
7660
|
-
import * as
|
|
7712
|
+
import * as fs16 from "fs";
|
|
7661
7713
|
function createTranscriptReader() {
|
|
7662
7714
|
const offsets = /* @__PURE__ */ new Map();
|
|
7663
7715
|
function readNewAssistantMessages(transcriptPath) {
|
|
7664
7716
|
const offset = offsets.get(transcriptPath) ?? 0;
|
|
7665
7717
|
let fileSize;
|
|
7666
7718
|
try {
|
|
7667
|
-
fileSize =
|
|
7719
|
+
fileSize = fs16.statSync(transcriptPath).size;
|
|
7668
7720
|
if (fileSize <= offset) return [];
|
|
7669
7721
|
} catch {
|
|
7670
7722
|
return [];
|
|
7671
7723
|
}
|
|
7672
7724
|
let fd;
|
|
7673
7725
|
try {
|
|
7674
|
-
fd =
|
|
7726
|
+
fd = fs16.openSync(transcriptPath, "r");
|
|
7675
7727
|
} catch {
|
|
7676
7728
|
return [];
|
|
7677
7729
|
}
|
|
7678
7730
|
try {
|
|
7679
7731
|
const buf = Buffer.alloc(fileSize - offset);
|
|
7680
|
-
const bytesRead =
|
|
7732
|
+
const bytesRead = fs16.readSync(fd, buf, 0, buf.length, offset);
|
|
7681
7733
|
let lastNewline = -1;
|
|
7682
7734
|
for (let i = bytesRead - 1; i >= 0; i--) {
|
|
7683
7735
|
if (buf[i] === 10) {
|
|
@@ -7721,7 +7773,7 @@ function createTranscriptReader() {
|
|
|
7721
7773
|
}
|
|
7722
7774
|
return messages;
|
|
7723
7775
|
} finally {
|
|
7724
|
-
|
|
7776
|
+
fs16.closeSync(fd);
|
|
7725
7777
|
}
|
|
7726
7778
|
}
|
|
7727
7779
|
function getOffset(transcriptPath) {
|
|
@@ -9461,8 +9513,8 @@ function persistOrDegrade(store, action, label, onFailure) {
|
|
|
9461
9513
|
}
|
|
9462
9514
|
|
|
9463
9515
|
// src/infra/sessions/store.ts
|
|
9464
|
-
import
|
|
9465
|
-
import
|
|
9516
|
+
import fs17 from "fs";
|
|
9517
|
+
import path14 from "path";
|
|
9466
9518
|
import Database from "better-sqlite3";
|
|
9467
9519
|
|
|
9468
9520
|
// src/infra/sessions/schema.ts
|
|
@@ -9731,7 +9783,7 @@ function rowToAthenaSession(row, adapterSessionIds, firstPrompt) {
|
|
|
9731
9783
|
// src/infra/sessions/store.ts
|
|
9732
9784
|
function createSessionStore(opts) {
|
|
9733
9785
|
if (opts.dbPath !== ":memory:") {
|
|
9734
|
-
|
|
9786
|
+
fs17.mkdirSync(path14.dirname(opts.dbPath), { recursive: true });
|
|
9735
9787
|
}
|
|
9736
9788
|
const db = new Database(opts.dbPath);
|
|
9737
9789
|
initSchema(db);
|
|
@@ -10006,18 +10058,18 @@ function createSessionStore(opts) {
|
|
|
10006
10058
|
}
|
|
10007
10059
|
|
|
10008
10060
|
// src/infra/sessions/registry.ts
|
|
10009
|
-
import
|
|
10010
|
-
import
|
|
10011
|
-
import
|
|
10061
|
+
import fs18 from "fs";
|
|
10062
|
+
import os10 from "os";
|
|
10063
|
+
import path15 from "path";
|
|
10012
10064
|
import Database2 from "better-sqlite3";
|
|
10013
10065
|
function sessionsDir() {
|
|
10014
|
-
return
|
|
10066
|
+
return path15.join(os10.homedir(), ".config", "athena", "sessions");
|
|
10015
10067
|
}
|
|
10016
10068
|
function sessionDbPath(sessionId) {
|
|
10017
|
-
return
|
|
10069
|
+
return path15.join(sessionsDir(), sessionId, "session.db");
|
|
10018
10070
|
}
|
|
10019
10071
|
function readSessionFromDb(dbPath) {
|
|
10020
|
-
if (!
|
|
10072
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
10021
10073
|
let db;
|
|
10022
10074
|
try {
|
|
10023
10075
|
db = new Database2(dbPath, { readonly: true });
|
|
@@ -10050,12 +10102,12 @@ function readSessionFromDb(dbPath) {
|
|
|
10050
10102
|
}
|
|
10051
10103
|
function listSessions(projectDir) {
|
|
10052
10104
|
const dir = sessionsDir();
|
|
10053
|
-
if (!
|
|
10054
|
-
const entries =
|
|
10105
|
+
if (!fs18.existsSync(dir)) return [];
|
|
10106
|
+
const entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
10055
10107
|
const sessions = [];
|
|
10056
10108
|
for (const entry of entries) {
|
|
10057
10109
|
if (!entry.isDirectory()) continue;
|
|
10058
|
-
const dbPath =
|
|
10110
|
+
const dbPath = path15.join(dir, entry.name, "session.db");
|
|
10059
10111
|
const session = readSessionFromDb(dbPath);
|
|
10060
10112
|
if (session && (session.eventCount ?? 0) > 0) {
|
|
10061
10113
|
if (!projectDir || session.projectDir === projectDir) {
|
|
@@ -10502,9 +10554,9 @@ function defaultLoadToken(tokenPath) {
|
|
|
10502
10554
|
}
|
|
10503
10555
|
|
|
10504
10556
|
// src/infra/config/gatewayClient.ts
|
|
10505
|
-
import
|
|
10506
|
-
import
|
|
10507
|
-
import
|
|
10557
|
+
import fs19 from "fs";
|
|
10558
|
+
import os11 from "os";
|
|
10559
|
+
import path16 from "path";
|
|
10508
10560
|
|
|
10509
10561
|
// src/shared/gateway-protocol/endpoint.ts
|
|
10510
10562
|
function parseRuntimeEndpoint(value) {
|
|
@@ -10559,17 +10611,17 @@ function isRecord(value) {
|
|
|
10559
10611
|
|
|
10560
10612
|
// src/infra/config/gatewayClient.ts
|
|
10561
10613
|
function resolveGatewayClientConfigPath(env = process.env) {
|
|
10562
|
-
const home = env["HOME"] ??
|
|
10563
|
-
return
|
|
10614
|
+
const home = env["HOME"] ?? os11.homedir();
|
|
10615
|
+
return path16.join(home, ".config", "athena", "gateway.json");
|
|
10564
10616
|
}
|
|
10565
10617
|
function readGatewayClientConfig(env = process.env) {
|
|
10566
10618
|
const configPath = resolveGatewayClientConfigPath(env);
|
|
10567
|
-
if (!
|
|
10619
|
+
if (!fs19.existsSync(configPath)) {
|
|
10568
10620
|
return { mode: "local" };
|
|
10569
10621
|
}
|
|
10570
10622
|
let parsed;
|
|
10571
10623
|
try {
|
|
10572
|
-
parsed = JSON.parse(
|
|
10624
|
+
parsed = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
|
|
10573
10625
|
} catch (err) {
|
|
10574
10626
|
throw new Error(
|
|
10575
10627
|
`gateway client config ${configPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -10586,16 +10638,16 @@ function readGatewayClientConfig(env = process.env) {
|
|
|
10586
10638
|
function writeGatewayClientConfig(config, env = process.env) {
|
|
10587
10639
|
const parsed = parseRuntimeEndpoint(config);
|
|
10588
10640
|
const configPath = resolveGatewayClientConfigPath(env);
|
|
10589
|
-
const dir =
|
|
10590
|
-
|
|
10591
|
-
|
|
10641
|
+
const dir = path16.dirname(configPath);
|
|
10642
|
+
fs19.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10643
|
+
fs19.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
|
|
10592
10644
|
encoding: "utf-8",
|
|
10593
10645
|
mode: 384
|
|
10594
10646
|
});
|
|
10595
10647
|
if (process.platform !== "win32") {
|
|
10596
10648
|
try {
|
|
10597
|
-
|
|
10598
|
-
|
|
10649
|
+
fs19.chmodSync(dir, 448);
|
|
10650
|
+
fs19.chmodSync(configPath, 384);
|
|
10599
10651
|
} catch {
|
|
10600
10652
|
}
|
|
10601
10653
|
}
|
|
@@ -10731,6 +10783,19 @@ var SessionBridge = class {
|
|
|
10731
10783
|
);
|
|
10732
10784
|
return res;
|
|
10733
10785
|
}
|
|
10786
|
+
async sendRunEvent(event) {
|
|
10787
|
+
const client = await this.requireConnectedClient();
|
|
10788
|
+
const req = {
|
|
10789
|
+
runtimeId: this.opts.runtimeId,
|
|
10790
|
+
location: event.location,
|
|
10791
|
+
runId: event.runId,
|
|
10792
|
+
seq: event.seq,
|
|
10793
|
+
ts: event.ts,
|
|
10794
|
+
kind: event.kind,
|
|
10795
|
+
...event.payload !== void 0 ? { payload: event.payload } : {}
|
|
10796
|
+
};
|
|
10797
|
+
return client.request("session.run.event", req);
|
|
10798
|
+
}
|
|
10734
10799
|
async relayPermission(req) {
|
|
10735
10800
|
writeGatewayTrace(
|
|
10736
10801
|
`sessionBridge relayPermission tool=${req.toolName} runtimeId=${this.opts.runtimeId}`
|
|
@@ -10743,7 +10808,7 @@ var SessionBridge = class {
|
|
|
10743
10808
|
inputPreview: req.inputPreview,
|
|
10744
10809
|
...req.ttlMs !== void 0 ? { ttlMs: req.ttlMs } : {}
|
|
10745
10810
|
};
|
|
10746
|
-
const overallTimeoutMs = req.ttlMs ?? RELAY_REQUEST_TIMEOUT_MS;
|
|
10811
|
+
const overallTimeoutMs = req.ttlMs === null ? null : req.ttlMs ?? RELAY_REQUEST_TIMEOUT_MS;
|
|
10747
10812
|
return this.requestWithReconnect("relay.permission.request", payload, overallTimeoutMs);
|
|
10748
10813
|
}
|
|
10749
10814
|
async relayQuestion(req) {
|
|
@@ -10754,7 +10819,7 @@ var SessionBridge = class {
|
|
|
10754
10819
|
questions: req.questions,
|
|
10755
10820
|
...req.ttlMs !== void 0 ? { ttlMs: req.ttlMs } : {}
|
|
10756
10821
|
};
|
|
10757
|
-
const overallTimeoutMs = req.ttlMs ?? RELAY_REQUEST_TIMEOUT_MS;
|
|
10822
|
+
const overallTimeoutMs = req.ttlMs === null ? null : req.ttlMs ?? RELAY_REQUEST_TIMEOUT_MS;
|
|
10758
10823
|
return this.requestWithReconnect("relay.question.request", payload, overallTimeoutMs);
|
|
10759
10824
|
}
|
|
10760
10825
|
/**
|
|
@@ -10766,19 +10831,21 @@ var SessionBridge = class {
|
|
|
10766
10831
|
* caller-provided overall timeout so a long outage still surfaces.
|
|
10767
10832
|
*/
|
|
10768
10833
|
async requestWithReconnect(kind, payload, overallTimeoutMs) {
|
|
10769
|
-
const deadline = Date.now() + overallTimeoutMs;
|
|
10834
|
+
const deadline = overallTimeoutMs === null ? null : Date.now() + overallTimeoutMs;
|
|
10770
10835
|
while (true) {
|
|
10771
10836
|
const client = await this.requireConnectedClient();
|
|
10772
|
-
const remaining = deadline - Date.now();
|
|
10773
|
-
if (remaining <= 0) {
|
|
10837
|
+
const remaining = deadline === null ? null : deadline - Date.now();
|
|
10838
|
+
if (remaining !== null && remaining <= 0) {
|
|
10774
10839
|
throw new GatewayProtocolError(`request ${kind} timed out`);
|
|
10775
10840
|
}
|
|
10776
10841
|
try {
|
|
10777
10842
|
return await client.request(kind, payload, {
|
|
10778
|
-
|
|
10843
|
+
// When unbounded, pass the largest safe setTimeout value so the
|
|
10844
|
+
// inner request never self-cancels in any human time scale.
|
|
10845
|
+
timeoutMs: remaining ?? 2147483647
|
|
10779
10846
|
});
|
|
10780
10847
|
} catch (err) {
|
|
10781
|
-
if (err instanceof GatewayProtocolError && err.code === "connection_closed" && !this.stopped && Date.now() < deadline) {
|
|
10848
|
+
if (err instanceof GatewayProtocolError && err.code === "connection_closed" && !this.stopped && (deadline === null || Date.now() < deadline)) {
|
|
10782
10849
|
writeGatewayTrace(
|
|
10783
10850
|
`sessionBridge relay retry kind=${kind} runtimeId=${this.opts.runtimeId}`
|
|
10784
10851
|
);
|
|
@@ -10913,7 +10980,8 @@ var SessionBridge = class {
|
|
|
10913
10980
|
const req = {
|
|
10914
10981
|
runtimeId: this.opts.runtimeId,
|
|
10915
10982
|
defaultAgentId: this.opts.defaultAgentId,
|
|
10916
|
-
pid: this.opts.pid ?? process.pid
|
|
10983
|
+
pid: this.opts.pid ?? process.pid,
|
|
10984
|
+
...this.opts.attachmentId !== void 0 ? { attachmentId: this.opts.attachmentId } : {}
|
|
10917
10985
|
};
|
|
10918
10986
|
try {
|
|
10919
10987
|
return await client.request("session.register", req);
|
|
@@ -10967,7 +11035,8 @@ async function startSessionBridge(opts) {
|
|
|
10967
11035
|
if (signal?.aborted) return null;
|
|
10968
11036
|
const bridge = new SessionBridge({
|
|
10969
11037
|
runtimeId: opts.runtimeId,
|
|
10970
|
-
defaultAgentId: opts.defaultAgentId
|
|
11038
|
+
defaultAgentId: opts.defaultAgentId,
|
|
11039
|
+
...opts.attachmentId !== void 0 ? { attachmentId: opts.attachmentId } : {}
|
|
10971
11040
|
});
|
|
10972
11041
|
writeGatewayTrace(`startSessionBridge attempt runtimeId=${opts.runtimeId}`);
|
|
10973
11042
|
try {
|
|
@@ -11007,6 +11076,194 @@ function sleepOrAbort(ms, signal) {
|
|
|
11007
11076
|
});
|
|
11008
11077
|
}
|
|
11009
11078
|
|
|
11079
|
+
// src/app/dashboard/dashboardFeedPublisher.ts
|
|
11080
|
+
import Database3 from "better-sqlite3";
|
|
11081
|
+
function dashboardFeedOutboxPath() {
|
|
11082
|
+
return `${ensureDaemonStateDir().dir}/dashboard-feed-outbox.db`;
|
|
11083
|
+
}
|
|
11084
|
+
function initOutboxSchema(db) {
|
|
11085
|
+
db.exec(`
|
|
11086
|
+
PRAGMA journal_mode = WAL;
|
|
11087
|
+
|
|
11088
|
+
CREATE TABLE IF NOT EXISTS dashboard_feed_outbox (
|
|
11089
|
+
delivery_seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11090
|
+
instance_id TEXT NOT NULL,
|
|
11091
|
+
athena_session_id TEXT NOT NULL,
|
|
11092
|
+
run_id TEXT NOT NULL,
|
|
11093
|
+
origin TEXT NOT NULL CHECK(origin IN ('local', 'dashboard')),
|
|
11094
|
+
event_id TEXT NOT NULL,
|
|
11095
|
+
emitted_at INTEGER NOT NULL,
|
|
11096
|
+
feed_event_json TEXT NOT NULL,
|
|
11097
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
11098
|
+
next_attempt_at INTEGER NOT NULL,
|
|
11099
|
+
last_error TEXT,
|
|
11100
|
+
acked_at INTEGER,
|
|
11101
|
+
UNIQUE(instance_id, event_id)
|
|
11102
|
+
);
|
|
11103
|
+
|
|
11104
|
+
CREATE INDEX IF NOT EXISTS idx_dashboard_feed_outbox_pending
|
|
11105
|
+
ON dashboard_feed_outbox(acked_at, next_attempt_at, delivery_seq);
|
|
11106
|
+
`);
|
|
11107
|
+
}
|
|
11108
|
+
function makeEnvelope(input) {
|
|
11109
|
+
return {
|
|
11110
|
+
instanceId: input.instanceId,
|
|
11111
|
+
athenaSessionId: input.athenaSessionId,
|
|
11112
|
+
runId: input.feedEvent.run_id,
|
|
11113
|
+
origin: input.origin,
|
|
11114
|
+
eventId: `${input.athenaSessionId}:${input.feedEvent.event_id}`,
|
|
11115
|
+
feedSeq: input.deliverySeq,
|
|
11116
|
+
emittedAt: input.emittedAt,
|
|
11117
|
+
feedEvent: input.feedEvent
|
|
11118
|
+
};
|
|
11119
|
+
}
|
|
11120
|
+
function createDashboardFeedOutbox(options = {}) {
|
|
11121
|
+
const db = new Database3(options.dbPath ?? dashboardFeedOutboxPath());
|
|
11122
|
+
initOutboxSchema(db);
|
|
11123
|
+
const insert = db.prepare(`
|
|
11124
|
+
INSERT OR IGNORE INTO dashboard_feed_outbox (
|
|
11125
|
+
instance_id,
|
|
11126
|
+
athena_session_id,
|
|
11127
|
+
run_id,
|
|
11128
|
+
origin,
|
|
11129
|
+
event_id,
|
|
11130
|
+
emitted_at,
|
|
11131
|
+
feed_event_json,
|
|
11132
|
+
next_attempt_at
|
|
11133
|
+
)
|
|
11134
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
11135
|
+
`);
|
|
11136
|
+
const selectPending = db.prepare(`
|
|
11137
|
+
SELECT
|
|
11138
|
+
delivery_seq,
|
|
11139
|
+
instance_id,
|
|
11140
|
+
athena_session_id,
|
|
11141
|
+
run_id,
|
|
11142
|
+
origin,
|
|
11143
|
+
event_id,
|
|
11144
|
+
emitted_at,
|
|
11145
|
+
feed_event_json,
|
|
11146
|
+
attempt,
|
|
11147
|
+
next_attempt_at,
|
|
11148
|
+
last_error
|
|
11149
|
+
FROM dashboard_feed_outbox
|
|
11150
|
+
WHERE acked_at IS NULL AND next_attempt_at <= ?
|
|
11151
|
+
ORDER BY delivery_seq ASC
|
|
11152
|
+
LIMIT ?
|
|
11153
|
+
`);
|
|
11154
|
+
const updateAttempt = db.prepare(`
|
|
11155
|
+
UPDATE dashboard_feed_outbox
|
|
11156
|
+
SET attempt = attempt + 1,
|
|
11157
|
+
next_attempt_at = ?,
|
|
11158
|
+
last_error = ?
|
|
11159
|
+
WHERE delivery_seq = ?
|
|
11160
|
+
`);
|
|
11161
|
+
const ackBySeq = db.prepare(`
|
|
11162
|
+
UPDATE dashboard_feed_outbox
|
|
11163
|
+
SET acked_at = ?
|
|
11164
|
+
WHERE delivery_seq = ?
|
|
11165
|
+
`);
|
|
11166
|
+
const ackByEventId = db.prepare(`
|
|
11167
|
+
UPDATE dashboard_feed_outbox
|
|
11168
|
+
SET acked_at = ?
|
|
11169
|
+
WHERE event_id = ?
|
|
11170
|
+
`);
|
|
11171
|
+
const enqueueTx = db.transaction(
|
|
11172
|
+
(input) => {
|
|
11173
|
+
for (const feedEvent of input.feedEvents) {
|
|
11174
|
+
const eventId = `${input.athenaSessionId}:${feedEvent.event_id}`;
|
|
11175
|
+
insert.run(
|
|
11176
|
+
input.instanceId,
|
|
11177
|
+
input.athenaSessionId,
|
|
11178
|
+
feedEvent.run_id,
|
|
11179
|
+
input.origin,
|
|
11180
|
+
eventId,
|
|
11181
|
+
input.emittedAt,
|
|
11182
|
+
JSON.stringify(feedEvent),
|
|
11183
|
+
input.emittedAt
|
|
11184
|
+
);
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11187
|
+
);
|
|
11188
|
+
return {
|
|
11189
|
+
enqueue(input) {
|
|
11190
|
+
if (input.feedEvents.length === 0) return;
|
|
11191
|
+
enqueueTx(input);
|
|
11192
|
+
},
|
|
11193
|
+
pendingBatch(input) {
|
|
11194
|
+
const rows = selectPending.all(input.now, input.limit);
|
|
11195
|
+
return rows.map((row) => {
|
|
11196
|
+
const feedEvent = JSON.parse(row.feed_event_json);
|
|
11197
|
+
return {
|
|
11198
|
+
deliverySeq: row.delivery_seq,
|
|
11199
|
+
envelope: makeEnvelope({
|
|
11200
|
+
instanceId: row.instance_id,
|
|
11201
|
+
athenaSessionId: row.athena_session_id,
|
|
11202
|
+
origin: row.origin,
|
|
11203
|
+
feedEvent,
|
|
11204
|
+
deliverySeq: row.delivery_seq,
|
|
11205
|
+
emittedAt: row.emitted_at
|
|
11206
|
+
}),
|
|
11207
|
+
attempt: row.attempt,
|
|
11208
|
+
nextAttemptAt: row.next_attempt_at,
|
|
11209
|
+
...row.last_error ? { lastError: row.last_error } : {}
|
|
11210
|
+
};
|
|
11211
|
+
});
|
|
11212
|
+
},
|
|
11213
|
+
markAttempted(input) {
|
|
11214
|
+
updateAttempt.run(
|
|
11215
|
+
input.nextAttemptAt,
|
|
11216
|
+
input.lastError ?? null,
|
|
11217
|
+
input.deliverySeq
|
|
11218
|
+
);
|
|
11219
|
+
},
|
|
11220
|
+
markAcked(input) {
|
|
11221
|
+
const now = Date.now();
|
|
11222
|
+
if (typeof input.deliverySeq === "number") {
|
|
11223
|
+
ackBySeq.run(now, input.deliverySeq);
|
|
11224
|
+
}
|
|
11225
|
+
if (input.eventId) {
|
|
11226
|
+
ackByEventId.run(now, input.eventId);
|
|
11227
|
+
}
|
|
11228
|
+
},
|
|
11229
|
+
close() {
|
|
11230
|
+
db.close();
|
|
11231
|
+
}
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
function createDashboardFeedPublisher(options = {}) {
|
|
11235
|
+
const readConfig2 = options.readConfig ?? (() => readDashboardClientConfig());
|
|
11236
|
+
const now = options.now ?? (() => Date.now());
|
|
11237
|
+
const onError = options.onError ?? (() => {
|
|
11238
|
+
});
|
|
11239
|
+
let ownedOutbox = null;
|
|
11240
|
+
function getOutbox() {
|
|
11241
|
+
if (options.outbox) return options.outbox;
|
|
11242
|
+
ownedOutbox ??= createDashboardFeedOutbox();
|
|
11243
|
+
return ownedOutbox;
|
|
11244
|
+
}
|
|
11245
|
+
return {
|
|
11246
|
+
publish(input) {
|
|
11247
|
+
if (input.feedEvents.length === 0) return;
|
|
11248
|
+
try {
|
|
11249
|
+
const config = readConfig2();
|
|
11250
|
+
if (!config) return;
|
|
11251
|
+
getOutbox().enqueue({
|
|
11252
|
+
instanceId: config.instanceId,
|
|
11253
|
+
athenaSessionId: input.athenaSessionId,
|
|
11254
|
+
origin: input.origin,
|
|
11255
|
+
feedEvents: input.feedEvents,
|
|
11256
|
+
emittedAt: now()
|
|
11257
|
+
});
|
|
11258
|
+
} catch (err) {
|
|
11259
|
+
onError(
|
|
11260
|
+
`dashboard feed publish failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11261
|
+
);
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
};
|
|
11265
|
+
}
|
|
11266
|
+
|
|
11010
11267
|
// src/app/exec/finalMessage.ts
|
|
11011
11268
|
function findLastMappedAgentMessage(feedEvents) {
|
|
11012
11269
|
for (let i = feedEvents.length - 1; i >= 0; i--) {
|
|
@@ -11057,8 +11314,8 @@ function exitCodeFromFailure(failure) {
|
|
|
11057
11314
|
}
|
|
11058
11315
|
|
|
11059
11316
|
// src/app/exec/output.ts
|
|
11060
|
-
import
|
|
11061
|
-
import
|
|
11317
|
+
import fs20 from "fs/promises";
|
|
11318
|
+
import path17 from "path";
|
|
11062
11319
|
|
|
11063
11320
|
// src/app/exec/jsonl.ts
|
|
11064
11321
|
function createExecJsonlEvent(type, data, ts = Date.now()) {
|
|
@@ -11097,9 +11354,9 @@ function createExecOutputWriter(options) {
|
|
|
11097
11354
|
writeLine(options.stdout, message);
|
|
11098
11355
|
},
|
|
11099
11356
|
async writeLastMessage(filePath, message) {
|
|
11100
|
-
const absPath =
|
|
11101
|
-
await
|
|
11102
|
-
await
|
|
11357
|
+
const absPath = path17.resolve(filePath);
|
|
11358
|
+
await fs20.mkdir(path17.dirname(absPath), { recursive: true });
|
|
11359
|
+
await fs20.writeFile(absPath, message, "utf-8");
|
|
11103
11360
|
}
|
|
11104
11361
|
};
|
|
11105
11362
|
}
|
|
@@ -11155,6 +11412,8 @@ async function runExec(options) {
|
|
|
11155
11412
|
const runtimeFactory = options.runtimeFactory ?? createRuntime;
|
|
11156
11413
|
const sessionStoreFactory = options.sessionStoreFactory ?? createSessionStore;
|
|
11157
11414
|
const athenaSessionId = options.athenaSessionId ?? crypto2.randomUUID();
|
|
11415
|
+
const dashboardFeedPublisher = options.dashboardFeedPublisher ?? createDashboardFeedPublisher();
|
|
11416
|
+
const dashboardOrigin = options.dashboardOrigin ?? "local";
|
|
11158
11417
|
const output = createExecOutputWriter({
|
|
11159
11418
|
json,
|
|
11160
11419
|
verbose,
|
|
@@ -11174,7 +11433,7 @@ async function runExec(options) {
|
|
|
11174
11433
|
store = sessionStoreFactory({
|
|
11175
11434
|
sessionId: athenaSessionId,
|
|
11176
11435
|
projectDir: options.projectDir,
|
|
11177
|
-
dbPath: options.ephemeral ? ":memory:" :
|
|
11436
|
+
dbPath: options.ephemeral ? ":memory:" : path18.join(sessionsDir(), athenaSessionId, "session.db")
|
|
11178
11437
|
});
|
|
11179
11438
|
} catch (error) {
|
|
11180
11439
|
const message = `Failed to initialize session store: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -11264,7 +11523,33 @@ async function runExec(options) {
|
|
|
11264
11523
|
} : {},
|
|
11265
11524
|
...options.signal ? { signal: options.signal } : {}
|
|
11266
11525
|
};
|
|
11526
|
+
const dashboardDecisionInbox = options.dashboardDecisionInbox;
|
|
11527
|
+
const applyPendingDashboardDecisions = () => {
|
|
11528
|
+
if (!dashboardDecisionInbox) return;
|
|
11529
|
+
const rows = dashboardDecisionInbox.pendingForSession({
|
|
11530
|
+
athenaSessionId,
|
|
11531
|
+
limit: 25
|
|
11532
|
+
});
|
|
11533
|
+
for (const row of rows) {
|
|
11534
|
+
try {
|
|
11535
|
+
runtime.sendDecision(row.requestId, row.decision);
|
|
11536
|
+
dashboardDecisionInbox.markConsumed({ id: row.id });
|
|
11537
|
+
} catch (error) {
|
|
11538
|
+
output.warn(
|
|
11539
|
+
`dashboard decision failed: ${error instanceof Error ? error.message : String(error)}`
|
|
11540
|
+
);
|
|
11541
|
+
}
|
|
11542
|
+
}
|
|
11543
|
+
};
|
|
11267
11544
|
const linkedAdapterSessions = /* @__PURE__ */ new Set();
|
|
11545
|
+
function publishFeedEvents(feedEvents) {
|
|
11546
|
+
if (feedEvents.length === 0) return;
|
|
11547
|
+
dashboardFeedPublisher.publish({
|
|
11548
|
+
origin: dashboardOrigin,
|
|
11549
|
+
athenaSessionId,
|
|
11550
|
+
feedEvents
|
|
11551
|
+
});
|
|
11552
|
+
}
|
|
11268
11553
|
const unsubscribeEvent = runtime.onEvent((runtimeEvent) => {
|
|
11269
11554
|
adapterSessionId = runtimeEvent.sessionId;
|
|
11270
11555
|
if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
|
|
@@ -11299,6 +11584,7 @@ async function runExec(options) {
|
|
|
11299
11584
|
mappedFinalMessage = event.data.message;
|
|
11300
11585
|
}
|
|
11301
11586
|
}
|
|
11587
|
+
publishFeedEvents(feedEvents);
|
|
11302
11588
|
});
|
|
11303
11589
|
const unsubscribeDecision = runtime.onDecision(
|
|
11304
11590
|
(eventId, decision) => {
|
|
@@ -11306,14 +11592,18 @@ async function runExec(options) {
|
|
|
11306
11592
|
eventId,
|
|
11307
11593
|
decision
|
|
11308
11594
|
});
|
|
11309
|
-
ingestRuntimeDecision(eventId, decision, {
|
|
11595
|
+
const feedEvent = ingestRuntimeDecision(eventId, decision, {
|
|
11310
11596
|
mapper,
|
|
11311
11597
|
store,
|
|
11312
11598
|
onPersistFailure: (message) => output.warn(message)
|
|
11313
11599
|
});
|
|
11600
|
+
if (feedEvent) {
|
|
11601
|
+
publishFeedEvents([feedEvent]);
|
|
11602
|
+
}
|
|
11314
11603
|
}
|
|
11315
11604
|
);
|
|
11316
11605
|
let timeoutTimer;
|
|
11606
|
+
let dashboardDecisionTimer;
|
|
11317
11607
|
if (typeof options.timeoutMs === "number" && options.timeoutMs > 0) {
|
|
11318
11608
|
timeoutTimer = setTimeout(() => {
|
|
11319
11609
|
latch.register({
|
|
@@ -11333,6 +11623,14 @@ async function runExec(options) {
|
|
|
11333
11623
|
output.emitJsonEvent("runtime.started", {
|
|
11334
11624
|
status: runtime.getStatus()
|
|
11335
11625
|
});
|
|
11626
|
+
if (dashboardDecisionInbox) {
|
|
11627
|
+
applyPendingDashboardDecisions();
|
|
11628
|
+
dashboardDecisionTimer = setInterval(
|
|
11629
|
+
applyPendingDashboardDecisions,
|
|
11630
|
+
options.dashboardDecisionPollIntervalMs ?? 1e3
|
|
11631
|
+
);
|
|
11632
|
+
dashboardDecisionTimer.unref();
|
|
11633
|
+
}
|
|
11336
11634
|
const workflow = options.workflow;
|
|
11337
11635
|
output.emitJsonEvent("run.started", {
|
|
11338
11636
|
workflow: workflow?.name ?? null,
|
|
@@ -11418,6 +11716,9 @@ async function runExec(options) {
|
|
|
11418
11716
|
if (timeoutTimer) {
|
|
11419
11717
|
clearTimeout(timeoutTimer);
|
|
11420
11718
|
}
|
|
11719
|
+
if (dashboardDecisionTimer) {
|
|
11720
|
+
clearInterval(dashboardDecisionTimer);
|
|
11721
|
+
}
|
|
11421
11722
|
await sessionController.kill();
|
|
11422
11723
|
unsubscribeEvent();
|
|
11423
11724
|
unsubscribeDecision();
|
|
@@ -11480,276 +11781,97 @@ async function runExec(options) {
|
|
|
11480
11781
|
return result;
|
|
11481
11782
|
}
|
|
11482
11783
|
|
|
11483
|
-
// src/app/dashboard/
|
|
11784
|
+
// src/app/dashboard/runStreamClient.ts
|
|
11484
11785
|
import { WebSocket as WebSocket2 } from "ws";
|
|
11786
|
+
var DEFAULT_RECONNECT_DELAYS_MS = [250, 1e3, 2e3, 5e3, 15e3];
|
|
11485
11787
|
var DEFAULT_HEARTBEAT_MS = 3e4;
|
|
11486
|
-
var
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
11490
|
-
url.pathname = `/api/instances/${encodeURIComponent(instanceId)}/socket`;
|
|
11491
|
-
url.search = "";
|
|
11492
|
-
url.hash = "";
|
|
11493
|
-
return url.toString();
|
|
11494
|
-
}
|
|
11495
|
-
function createInstanceSocketClient(opts) {
|
|
11496
|
-
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
11497
|
-
const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
11788
|
+
var DEFAULT_WATCHDOG_MS = 9e4;
|
|
11789
|
+
var DEFAULT_MAX_QUEUE_SIZE = 5e3;
|
|
11790
|
+
function createRunStreamClient(opts) {
|
|
11498
11791
|
const log = opts.log ?? (() => {
|
|
11499
11792
|
});
|
|
11500
11793
|
const now = opts.now ?? (() => Date.now());
|
|
11501
|
-
const
|
|
11502
|
-
const
|
|
11503
|
-
const
|
|
11794
|
+
const reconnectDelays = opts.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
|
|
11795
|
+
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
11796
|
+
const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
|
|
11797
|
+
const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
11798
|
+
const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket2(url));
|
|
11799
|
+
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
11800
|
+
const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
11801
|
+
let nextSeq = 1;
|
|
11802
|
+
const queue = [];
|
|
11504
11803
|
let ws = null;
|
|
11505
|
-
let
|
|
11506
|
-
let
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11804
|
+
let connectAttempt = 0;
|
|
11805
|
+
let stopped = false;
|
|
11806
|
+
let serverTerminated = false;
|
|
11807
|
+
let heartbeatTimer = null;
|
|
11808
|
+
let watchdogTimer = null;
|
|
11809
|
+
let reconnectTimer = null;
|
|
11810
|
+
let resumeResolved = false;
|
|
11811
|
+
let firstConnect = null;
|
|
11812
|
+
const terminationWaiters = [];
|
|
11813
|
+
function trimQueueUpTo(lastAckedSeq) {
|
|
11814
|
+
while (queue.length > 0 && queue[0].seq <= lastAckedSeq) {
|
|
11815
|
+
queue.shift();
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
function trimQueueUpToExclusive(expectedSeq) {
|
|
11819
|
+
while (queue.length > 0 && queue[0].seq < expectedSeq) {
|
|
11820
|
+
queue.shift();
|
|
11821
|
+
}
|
|
11822
|
+
}
|
|
11823
|
+
function clearTimers() {
|
|
11824
|
+
if (heartbeatTimer) {
|
|
11825
|
+
clearTimer(heartbeatTimer);
|
|
11826
|
+
heartbeatTimer = null;
|
|
11827
|
+
}
|
|
11828
|
+
if (watchdogTimer) {
|
|
11829
|
+
clearTimer(watchdogTimer);
|
|
11830
|
+
watchdogTimer = null;
|
|
11831
|
+
}
|
|
11832
|
+
if (reconnectTimer) {
|
|
11833
|
+
clearTimer(reconnectTimer);
|
|
11834
|
+
reconnectTimer = null;
|
|
11835
|
+
}
|
|
11836
|
+
}
|
|
11837
|
+
function startHeartbeat() {
|
|
11838
|
+
if (heartbeatMs <= 0) return;
|
|
11839
|
+
const tick = () => {
|
|
11840
|
+
if (!ws || ws.readyState !== ws.OPEN) return;
|
|
11841
|
+
try {
|
|
11842
|
+
ws.send(JSON.stringify({ type: "ping", ts: now() }));
|
|
11843
|
+
} catch (err) {
|
|
11511
11844
|
log(
|
|
11512
11845
|
"warn",
|
|
11513
|
-
`
|
|
11846
|
+
`run-stream ping failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11514
11847
|
);
|
|
11515
11848
|
}
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11849
|
+
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11850
|
+
heartbeatTimer.unref();
|
|
11851
|
+
};
|
|
11852
|
+
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11853
|
+
heartbeatTimer.unref();
|
|
11854
|
+
}
|
|
11855
|
+
function bumpWatchdog() {
|
|
11856
|
+
if (watchdogMs <= 0) return;
|
|
11857
|
+
if (watchdogTimer) clearTimer(watchdogTimer);
|
|
11858
|
+
watchdogTimer = setTimer(() => {
|
|
11522
11859
|
log(
|
|
11523
11860
|
"warn",
|
|
11524
|
-
`
|
|
11861
|
+
`run-stream watchdog: no server frames for ${watchdogMs}ms \u2014 recycling socket`
|
|
11525
11862
|
);
|
|
11526
|
-
}
|
|
11527
|
-
}
|
|
11528
|
-
function startHeartbeat() {
|
|
11529
|
-
stopHeartbeat();
|
|
11530
|
-
const interval = setInterval(() => {
|
|
11531
|
-
send({ type: "ping", ts: now() });
|
|
11532
|
-
}, heartbeatMs);
|
|
11533
|
-
interval.unref();
|
|
11534
|
-
heartbeat = interval;
|
|
11535
|
-
}
|
|
11536
|
-
function stopHeartbeat() {
|
|
11537
|
-
if (heartbeat) {
|
|
11538
|
-
clearInterval(heartbeat);
|
|
11539
|
-
heartbeat = null;
|
|
11540
|
-
}
|
|
11541
|
-
}
|
|
11542
|
-
function emitClose(reason) {
|
|
11543
|
-
stopHeartbeat();
|
|
11544
|
-
for (const handler of [...closeHandlers]) {
|
|
11545
11863
|
try {
|
|
11546
|
-
|
|
11864
|
+
ws?.terminate();
|
|
11547
11865
|
} catch {
|
|
11548
11866
|
}
|
|
11549
|
-
}
|
|
11867
|
+
}, watchdogMs);
|
|
11868
|
+
watchdogTimer.unref();
|
|
11550
11869
|
}
|
|
11551
|
-
function
|
|
11552
|
-
if (
|
|
11553
|
-
|
|
11554
|
-
log("info", `instance socket: assignment accepted runId=${parsed.runId}`);
|
|
11555
|
-
}
|
|
11556
|
-
for (const handler of [...frameHandlers]) {
|
|
11870
|
+
function flushQueue() {
|
|
11871
|
+
if (!ws || ws.readyState !== ws.OPEN || !resumeResolved) return;
|
|
11872
|
+
for (const frame of queue) {
|
|
11557
11873
|
try {
|
|
11558
|
-
|
|
11559
|
-
} catch (err) {
|
|
11560
|
-
log(
|
|
11561
|
-
"warn",
|
|
11562
|
-
`instance socket frame handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
11563
|
-
);
|
|
11564
|
-
}
|
|
11565
|
-
}
|
|
11566
|
-
}
|
|
11567
|
-
async function connect2() {
|
|
11568
|
-
if (ws) throw new Error("instance socket already connected");
|
|
11569
|
-
const url = instanceSocketUrl(opts.dashboardUrl, opts.instanceId);
|
|
11570
|
-
const next = makeWebSocket(url, opts.accessToken);
|
|
11571
|
-
try {
|
|
11572
|
-
await new Promise((resolve, reject) => {
|
|
11573
|
-
let settled = false;
|
|
11574
|
-
const cleanup = () => {
|
|
11575
|
-
next.off("open", onOpen);
|
|
11576
|
-
next.off("error", onError);
|
|
11577
|
-
clearTimeout(timer);
|
|
11578
|
-
};
|
|
11579
|
-
const onOpen = () => {
|
|
11580
|
-
if (settled) return;
|
|
11581
|
-
settled = true;
|
|
11582
|
-
cleanup();
|
|
11583
|
-
resolve();
|
|
11584
|
-
};
|
|
11585
|
-
const onError = (err) => {
|
|
11586
|
-
if (settled) return;
|
|
11587
|
-
settled = true;
|
|
11588
|
-
cleanup();
|
|
11589
|
-
reject(new Error(`instance socket connect failed: ${err.message}`));
|
|
11590
|
-
};
|
|
11591
|
-
const timer = setTimeout(() => {
|
|
11592
|
-
if (settled) return;
|
|
11593
|
-
settled = true;
|
|
11594
|
-
cleanup();
|
|
11595
|
-
reject(
|
|
11596
|
-
new Error(
|
|
11597
|
-
`instance socket connect failed: timed out after ${connectTimeoutMs}ms`
|
|
11598
|
-
)
|
|
11599
|
-
);
|
|
11600
|
-
}, connectTimeoutMs);
|
|
11601
|
-
next.once("open", onOpen);
|
|
11602
|
-
next.once("error", onError);
|
|
11603
|
-
});
|
|
11604
|
-
} catch (err) {
|
|
11605
|
-
next.on("error", () => {
|
|
11606
|
-
});
|
|
11607
|
-
try {
|
|
11608
|
-
next.terminate();
|
|
11609
|
-
} catch {
|
|
11610
|
-
}
|
|
11611
|
-
throw err;
|
|
11612
|
-
}
|
|
11613
|
-
ws = next;
|
|
11614
|
-
startHeartbeat();
|
|
11615
|
-
next.on("message", (data) => {
|
|
11616
|
-
let parsed;
|
|
11617
|
-
try {
|
|
11618
|
-
parsed = JSON.parse(String(data));
|
|
11619
|
-
} catch (err) {
|
|
11620
|
-
log(
|
|
11621
|
-
"warn",
|
|
11622
|
-
`instance socket frame parse failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11623
|
-
);
|
|
11624
|
-
return;
|
|
11625
|
-
}
|
|
11626
|
-
handleFrame(parsed);
|
|
11627
|
-
});
|
|
11628
|
-
next.on("close", (_code, reasonBuf) => {
|
|
11629
|
-
if (next !== ws) return;
|
|
11630
|
-
ws = null;
|
|
11631
|
-
const reason = reasonBuf.toString() || "closed";
|
|
11632
|
-
emitClose(reason);
|
|
11633
|
-
});
|
|
11634
|
-
next.on("error", (err) => {
|
|
11635
|
-
log("warn", `instance socket error: ${err.message}`);
|
|
11636
|
-
});
|
|
11637
|
-
}
|
|
11638
|
-
function close(reason) {
|
|
11639
|
-
stopHeartbeat();
|
|
11640
|
-
if (ws) {
|
|
11641
|
-
try {
|
|
11642
|
-
ws.close(1e3, reason ?? "client closed");
|
|
11643
|
-
} catch {
|
|
11644
|
-
ws.terminate();
|
|
11645
|
-
}
|
|
11646
|
-
}
|
|
11647
|
-
ws = null;
|
|
11648
|
-
}
|
|
11649
|
-
function onFrame(handler) {
|
|
11650
|
-
frameHandlers.add(handler);
|
|
11651
|
-
}
|
|
11652
|
-
function onClose(handler) {
|
|
11653
|
-
closeHandlers.add(handler);
|
|
11654
|
-
}
|
|
11655
|
-
function sendRunEvent(event) {
|
|
11656
|
-
send({ type: "run_event", ...event });
|
|
11657
|
-
}
|
|
11658
|
-
return { connect: connect2, close, onFrame, onClose, sendRunEvent };
|
|
11659
|
-
}
|
|
11660
|
-
|
|
11661
|
-
// src/app/dashboard/runStreamClient.ts
|
|
11662
|
-
import { WebSocket as WebSocket3 } from "ws";
|
|
11663
|
-
var DEFAULT_RECONNECT_DELAYS_MS = [250, 1e3, 2e3, 5e3, 15e3];
|
|
11664
|
-
var DEFAULT_HEARTBEAT_MS2 = 3e4;
|
|
11665
|
-
var DEFAULT_WATCHDOG_MS = 9e4;
|
|
11666
|
-
var DEFAULT_MAX_QUEUE_SIZE = 5e3;
|
|
11667
|
-
var TERMINAL_KINDS = /* @__PURE__ */ new Set(["completion", "error"]);
|
|
11668
|
-
function createRunStreamClient(opts) {
|
|
11669
|
-
const log = opts.log ?? (() => {
|
|
11670
|
-
});
|
|
11671
|
-
const now = opts.now ?? (() => Date.now());
|
|
11672
|
-
const reconnectDelays = opts.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
|
|
11673
|
-
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS2;
|
|
11674
|
-
const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
|
|
11675
|
-
const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
11676
|
-
const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket3(url));
|
|
11677
|
-
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
11678
|
-
const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
11679
|
-
let nextSeq = 1;
|
|
11680
|
-
const queue = [];
|
|
11681
|
-
let ws = null;
|
|
11682
|
-
let connectAttempt = 0;
|
|
11683
|
-
let stopped = false;
|
|
11684
|
-
let serverTerminated = false;
|
|
11685
|
-
let heartbeatTimer = null;
|
|
11686
|
-
let watchdogTimer = null;
|
|
11687
|
-
let reconnectTimer = null;
|
|
11688
|
-
let resumeResolved = false;
|
|
11689
|
-
let firstConnect = null;
|
|
11690
|
-
const terminationWaiters = [];
|
|
11691
|
-
function trimQueueUpTo(lastAckedSeq) {
|
|
11692
|
-
while (queue.length > 0 && queue[0].seq <= lastAckedSeq) {
|
|
11693
|
-
queue.shift();
|
|
11694
|
-
}
|
|
11695
|
-
}
|
|
11696
|
-
function trimQueueUpToExclusive(expectedSeq) {
|
|
11697
|
-
while (queue.length > 0 && queue[0].seq < expectedSeq) {
|
|
11698
|
-
queue.shift();
|
|
11699
|
-
}
|
|
11700
|
-
}
|
|
11701
|
-
function clearTimers() {
|
|
11702
|
-
if (heartbeatTimer) {
|
|
11703
|
-
clearTimer(heartbeatTimer);
|
|
11704
|
-
heartbeatTimer = null;
|
|
11705
|
-
}
|
|
11706
|
-
if (watchdogTimer) {
|
|
11707
|
-
clearTimer(watchdogTimer);
|
|
11708
|
-
watchdogTimer = null;
|
|
11709
|
-
}
|
|
11710
|
-
if (reconnectTimer) {
|
|
11711
|
-
clearTimer(reconnectTimer);
|
|
11712
|
-
reconnectTimer = null;
|
|
11713
|
-
}
|
|
11714
|
-
}
|
|
11715
|
-
function startHeartbeat() {
|
|
11716
|
-
if (heartbeatMs <= 0) return;
|
|
11717
|
-
const tick = () => {
|
|
11718
|
-
if (!ws || ws.readyState !== ws.OPEN) return;
|
|
11719
|
-
try {
|
|
11720
|
-
ws.send(JSON.stringify({ type: "ping", ts: now() }));
|
|
11721
|
-
} catch (err) {
|
|
11722
|
-
log(
|
|
11723
|
-
"warn",
|
|
11724
|
-
`run-stream ping failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11725
|
-
);
|
|
11726
|
-
}
|
|
11727
|
-
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11728
|
-
heartbeatTimer.unref?.();
|
|
11729
|
-
};
|
|
11730
|
-
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11731
|
-
heartbeatTimer.unref?.();
|
|
11732
|
-
}
|
|
11733
|
-
function bumpWatchdog() {
|
|
11734
|
-
if (watchdogMs <= 0) return;
|
|
11735
|
-
if (watchdogTimer) clearTimer(watchdogTimer);
|
|
11736
|
-
watchdogTimer = setTimer(() => {
|
|
11737
|
-
log(
|
|
11738
|
-
"warn",
|
|
11739
|
-
`run-stream watchdog: no server frames for ${watchdogMs}ms \u2014 recycling socket`
|
|
11740
|
-
);
|
|
11741
|
-
try {
|
|
11742
|
-
ws?.terminate();
|
|
11743
|
-
} catch {
|
|
11744
|
-
}
|
|
11745
|
-
}, watchdogMs);
|
|
11746
|
-
watchdogTimer.unref?.();
|
|
11747
|
-
}
|
|
11748
|
-
function flushQueue() {
|
|
11749
|
-
if (!ws || ws.readyState !== ws.OPEN || !resumeResolved) return;
|
|
11750
|
-
for (const frame of queue) {
|
|
11751
|
-
try {
|
|
11752
|
-
ws.send(JSON.stringify(frame));
|
|
11874
|
+
ws.send(JSON.stringify(frame));
|
|
11753
11875
|
} catch (err) {
|
|
11754
11876
|
log(
|
|
11755
11877
|
"warn",
|
|
@@ -11830,7 +11952,7 @@ function createRunStreamClient(opts) {
|
|
|
11830
11952
|
reconnectTimer = null;
|
|
11831
11953
|
void openSocket();
|
|
11832
11954
|
}, delayMs);
|
|
11833
|
-
reconnectTimer.unref
|
|
11955
|
+
reconnectTimer.unref();
|
|
11834
11956
|
}
|
|
11835
11957
|
async function openSocket() {
|
|
11836
11958
|
if (stopped || serverTerminated) return;
|
|
@@ -11856,7 +11978,7 @@ function createRunStreamClient(opts) {
|
|
|
11856
11978
|
if (next !== ws) return;
|
|
11857
11979
|
ws = null;
|
|
11858
11980
|
clearTimers();
|
|
11859
|
-
const reason = reasonBuf
|
|
11981
|
+
const reason = reasonBuf.toString() || "closed";
|
|
11860
11982
|
if (code === 1e3 && reason === "run_terminated") {
|
|
11861
11983
|
notifyTerminated();
|
|
11862
11984
|
return;
|
|
@@ -11889,9 +12011,8 @@ function createRunStreamClient(opts) {
|
|
|
11889
12011
|
});
|
|
11890
12012
|
},
|
|
11891
12013
|
sendEvent(input) {
|
|
11892
|
-
const seq = nextSeq++;
|
|
11893
12014
|
const frame = {
|
|
11894
|
-
seq
|
|
12015
|
+
seq: nextSeq++,
|
|
11895
12016
|
ts: input.ts,
|
|
11896
12017
|
kind: input.kind,
|
|
11897
12018
|
payload: input.payload ?? null
|
|
@@ -11914,9 +12035,6 @@ function createRunStreamClient(opts) {
|
|
|
11914
12035
|
);
|
|
11915
12036
|
}
|
|
11916
12037
|
}
|
|
11917
|
-
if (TERMINAL_KINDS.has(frame.kind)) {
|
|
11918
|
-
}
|
|
11919
|
-
return seq;
|
|
11920
12038
|
},
|
|
11921
12039
|
whenTerminated() {
|
|
11922
12040
|
if (serverTerminated) return Promise.resolve();
|
|
@@ -11945,6 +12063,7 @@ function createRunStreamClient(opts) {
|
|
|
11945
12063
|
}
|
|
11946
12064
|
|
|
11947
12065
|
// src/app/dashboard/remoteRunExecutor.ts
|
|
12066
|
+
var DEFAULT_MARKETPLACE_SLUG = "lespaceman/athena-workflow-marketplace";
|
|
11948
12067
|
function parseRunSpec(value) {
|
|
11949
12068
|
if (typeof value !== "object" || value === null) return null;
|
|
11950
12069
|
const obj = value;
|
|
@@ -11954,11 +12073,18 @@ function parseRunSpec(value) {
|
|
|
11954
12073
|
const workflow = obj["workflow"];
|
|
11955
12074
|
const callbackWsUrl = obj["callbackWsUrl"];
|
|
11956
12075
|
const callbackToken = obj["callbackToken"];
|
|
12076
|
+
const workflowObj = typeof workflow === "object" && workflow !== null ? workflow : null;
|
|
11957
12077
|
return {
|
|
11958
12078
|
prompt,
|
|
12079
|
+
athenaSessionId: typeof obj["athenaSessionId"] === "string" && obj["athenaSessionId"].length > 0 ? obj["athenaSessionId"] : void 0,
|
|
12080
|
+
adapterResumeSessionId: typeof obj["adapterResumeSessionId"] === "string" && obj["adapterResumeSessionId"].length > 0 ? obj["adapterResumeSessionId"] : void 0,
|
|
11959
12081
|
sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
|
|
11960
12082
|
projectDir: typeof obj["projectDir"] === "string" && obj["projectDir"].length > 0 ? obj["projectDir"] : void 0,
|
|
11961
|
-
workflow:
|
|
12083
|
+
workflow: workflowObj && typeof workflowObj["ref"] === "string" ? {
|
|
12084
|
+
ref: workflowObj["ref"],
|
|
12085
|
+
...typeof workflowObj["source"] === "string" ? { source: workflowObj["source"] } : {},
|
|
12086
|
+
...typeof workflowObj["version"] === "string" ? { version: workflowObj["version"] } : {}
|
|
12087
|
+
} : void 0,
|
|
11962
12088
|
env: typeof env === "object" && env !== null ? Object.fromEntries(
|
|
11963
12089
|
Object.entries(env).filter(
|
|
11964
12090
|
(entry) => typeof entry[1] === "string"
|
|
@@ -11974,6 +12100,47 @@ function workflowNameFromRef(ref) {
|
|
|
11974
12100
|
const [name] = ref.split("@", 1);
|
|
11975
12101
|
return name && name.length > 0 ? name : void 0;
|
|
11976
12102
|
}
|
|
12103
|
+
function isMissingWorkflowError(err, workflowName) {
|
|
12104
|
+
return err instanceof Error && err.message.includes(`Workflow "${workflowName}" not found`);
|
|
12105
|
+
}
|
|
12106
|
+
function configuredWorkflowSources(readGlobalConfigFn) {
|
|
12107
|
+
const sources = readGlobalConfigFn().workflowMarketplaceSources;
|
|
12108
|
+
return sources && sources.length > 0 ? sources : [DEFAULT_MARKETPLACE_SLUG];
|
|
12109
|
+
}
|
|
12110
|
+
function workflowInstallRef(spec) {
|
|
12111
|
+
const ref = spec.workflow?.ref;
|
|
12112
|
+
if (!ref) return void 0;
|
|
12113
|
+
const version = spec.workflow?.version;
|
|
12114
|
+
if (version && !ref.includes("@")) {
|
|
12115
|
+
return `${ref}@${version}`;
|
|
12116
|
+
}
|
|
12117
|
+
return ref;
|
|
12118
|
+
}
|
|
12119
|
+
function workflowInstallSources(spec, readGlobalConfigFn) {
|
|
12120
|
+
const source = spec.workflow?.source?.trim();
|
|
12121
|
+
if (source && source !== "marketplace") {
|
|
12122
|
+
return [source];
|
|
12123
|
+
}
|
|
12124
|
+
return configuredWorkflowSources(readGlobalConfigFn);
|
|
12125
|
+
}
|
|
12126
|
+
function ensureRemoteWorkflowInstalled(input) {
|
|
12127
|
+
const ref = workflowInstallRef(input.spec);
|
|
12128
|
+
const workflowName = workflowNameFromRef(ref);
|
|
12129
|
+
if (!workflowName) return void 0;
|
|
12130
|
+
try {
|
|
12131
|
+
input.resolveWorkflowFn(workflowName);
|
|
12132
|
+
return workflowName;
|
|
12133
|
+
} catch (err) {
|
|
12134
|
+
if (!isMissingWorkflowError(err, workflowName)) {
|
|
12135
|
+
throw err;
|
|
12136
|
+
}
|
|
12137
|
+
}
|
|
12138
|
+
const resolved = input.resolveWorkflowInstallFn(
|
|
12139
|
+
ref,
|
|
12140
|
+
workflowInstallSources(input.spec, input.readGlobalConfigFn)
|
|
12141
|
+
);
|
|
12142
|
+
return input.installWorkflowFromSourceFn(resolved);
|
|
12143
|
+
}
|
|
11977
12144
|
function eventKind(event) {
|
|
11978
12145
|
if (event.type === "exec.completed") {
|
|
11979
12146
|
const data = event.data;
|
|
@@ -11993,22 +12160,18 @@ function eventPayload(event) {
|
|
|
11993
12160
|
}
|
|
11994
12161
|
return event.data ?? null;
|
|
11995
12162
|
}
|
|
11996
|
-
function
|
|
11997
|
-
if (!env || Object.keys(env).length === 0) return
|
|
11998
|
-
const
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
process.env[key] = value;
|
|
12009
|
-
}
|
|
12010
|
-
}
|
|
12011
|
-
});
|
|
12163
|
+
function mergeRunSpecEnvIntoWorkflow(workflow, env) {
|
|
12164
|
+
if (!env || Object.keys(env).length === 0) return workflow;
|
|
12165
|
+
const mergedEnv = { ...workflow?.env ?? {}, ...env };
|
|
12166
|
+
if (workflow) {
|
|
12167
|
+
return { ...workflow, env: mergedEnv };
|
|
12168
|
+
}
|
|
12169
|
+
return {
|
|
12170
|
+
name: "dashboard-remote",
|
|
12171
|
+
plugins: [],
|
|
12172
|
+
promptTemplate: "{input}",
|
|
12173
|
+
env: mergedEnv
|
|
12174
|
+
};
|
|
12012
12175
|
}
|
|
12013
12176
|
async function executeRemoteAssignment({
|
|
12014
12177
|
frame,
|
|
@@ -12017,14 +12180,21 @@ async function executeRemoteAssignment({
|
|
|
12017
12180
|
log = () => {
|
|
12018
12181
|
},
|
|
12019
12182
|
runExecFn = runExec,
|
|
12183
|
+
decisionInbox,
|
|
12020
12184
|
bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
|
|
12021
12185
|
now = Date.now,
|
|
12022
12186
|
abortSignal,
|
|
12023
12187
|
createRunStreamClientFn = createRunStreamClient,
|
|
12188
|
+
resolveWorkflowFn = resolveWorkflow,
|
|
12189
|
+
resolveWorkflowInstallFn = resolveWorkflowInstall,
|
|
12190
|
+
installWorkflowFromSourceFn = installWorkflowFromSource,
|
|
12191
|
+
readGlobalConfigFn = readGlobalConfig,
|
|
12024
12192
|
runStreamConnectTimeoutMs = 5e3
|
|
12025
12193
|
}) {
|
|
12026
|
-
|
|
12027
|
-
|
|
12194
|
+
const lastTerminalFailureMessage = { current: null };
|
|
12195
|
+
const deferredFailedCompletion = {
|
|
12196
|
+
current: null
|
|
12197
|
+
};
|
|
12028
12198
|
const spec = parseRunSpec(frame.runSpec);
|
|
12029
12199
|
let runStream = null;
|
|
12030
12200
|
if (spec?.callbackWsUrl && spec.callbackToken) {
|
|
@@ -12036,7 +12206,7 @@ async function executeRemoteAssignment({
|
|
|
12036
12206
|
});
|
|
12037
12207
|
const timeoutPromise = new Promise((resolve) => {
|
|
12038
12208
|
const t = setTimeout(() => resolve("timeout"), runStreamConnectTimeoutMs);
|
|
12039
|
-
t.unref
|
|
12209
|
+
t.unref();
|
|
12040
12210
|
});
|
|
12041
12211
|
try {
|
|
12042
12212
|
const result = await Promise.race([
|
|
@@ -12062,7 +12232,7 @@ async function executeRemoteAssignment({
|
|
|
12062
12232
|
let legacySeq = 0;
|
|
12063
12233
|
const send = (kind, payload, ts = now()) => {
|
|
12064
12234
|
if (kind === "error" && typeof payload === "object" && payload !== null && typeof payload.message === "string") {
|
|
12065
|
-
lastTerminalFailureMessage = payload.message;
|
|
12235
|
+
lastTerminalFailureMessage.current = payload.message;
|
|
12066
12236
|
}
|
|
12067
12237
|
if (runStream) {
|
|
12068
12238
|
runStream.sendEvent({ ts, kind, payload });
|
|
@@ -12078,100 +12248,101 @@ async function executeRemoteAssignment({
|
|
|
12078
12248
|
});
|
|
12079
12249
|
};
|
|
12080
12250
|
send("progress", { message: "assignment received" });
|
|
12081
|
-
const closeRunStream = async (reason) => {
|
|
12082
|
-
if (!runStream) return;
|
|
12083
|
-
const drainTimeout = new Promise((resolve) => {
|
|
12084
|
-
const t = setTimeout(() => resolve(), 1e4);
|
|
12085
|
-
t.unref?.();
|
|
12086
|
-
});
|
|
12087
|
-
await Promise.race([runStream.whenTerminated(), drainTimeout]);
|
|
12088
|
-
await runStream.close(reason);
|
|
12089
|
-
runStream = null;
|
|
12090
|
-
};
|
|
12091
|
-
if (!spec) {
|
|
12092
|
-
send("error", {
|
|
12093
|
-
message: "remote assignment missing prompt"
|
|
12094
|
-
});
|
|
12095
|
-
await closeRunStream("done");
|
|
12096
|
-
return;
|
|
12097
|
-
}
|
|
12098
|
-
const projectDir = spec.projectDir ?? fallbackProjectDir;
|
|
12099
|
-
let runtimeConfig;
|
|
12100
12251
|
try {
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12252
|
+
if (!spec) {
|
|
12253
|
+
send("error", { message: "remote assignment missing prompt" });
|
|
12254
|
+
return;
|
|
12255
|
+
}
|
|
12256
|
+
const projectDir = spec.projectDir ?? fallbackProjectDir;
|
|
12257
|
+
let runtimeConfig;
|
|
12258
|
+
try {
|
|
12259
|
+
const workflowOverride = ensureRemoteWorkflowInstalled({
|
|
12260
|
+
spec,
|
|
12261
|
+
resolveWorkflowFn,
|
|
12262
|
+
resolveWorkflowInstallFn,
|
|
12263
|
+
installWorkflowFromSourceFn,
|
|
12264
|
+
readGlobalConfigFn
|
|
12265
|
+
});
|
|
12266
|
+
runtimeConfig = bootstrapRuntimeConfigFn({
|
|
12267
|
+
projectDir,
|
|
12268
|
+
showSetup: false,
|
|
12269
|
+
isolationPreset: "minimal",
|
|
12270
|
+
workflowOverride
|
|
12271
|
+
});
|
|
12272
|
+
} catch (err) {
|
|
12273
|
+
send("error", {
|
|
12274
|
+
message: err instanceof Error ? err.message : String(err)
|
|
12275
|
+
});
|
|
12276
|
+
return;
|
|
12277
|
+
}
|
|
12278
|
+
for (const warning of runtimeConfig.warnings) {
|
|
12279
|
+
send("warning", { message: warning });
|
|
12280
|
+
}
|
|
12281
|
+
let buffered = "";
|
|
12282
|
+
const stdout = {
|
|
12283
|
+
write(chunk) {
|
|
12284
|
+
buffered += chunk;
|
|
12285
|
+
let newline = buffered.indexOf("\n");
|
|
12286
|
+
while (newline >= 0) {
|
|
12287
|
+
const line = buffered.slice(0, newline).trim();
|
|
12288
|
+
buffered = buffered.slice(newline + 1);
|
|
12289
|
+
if (line.length > 0) {
|
|
12290
|
+
try {
|
|
12291
|
+
const event = JSON.parse(line);
|
|
12292
|
+
const data = event.data;
|
|
12293
|
+
if (event.type === "exec.completed" && data?.success === false) {
|
|
12294
|
+
deferredFailedCompletion.current = event;
|
|
12295
|
+
continue;
|
|
12296
|
+
}
|
|
12297
|
+
send(eventKind(event), eventPayload(event), now());
|
|
12298
|
+
} catch (err) {
|
|
12299
|
+
send("progress", { line });
|
|
12300
|
+
log(
|
|
12301
|
+
"warn",
|
|
12302
|
+
`remote run emitted malformed JSONL: ${err instanceof Error ? err.message : String(err)}`
|
|
12303
|
+
);
|
|
12132
12304
|
}
|
|
12133
|
-
send(eventKind(event), eventPayload(event), now());
|
|
12134
|
-
} catch (err) {
|
|
12135
|
-
send("progress", { line });
|
|
12136
|
-
log(
|
|
12137
|
-
"warn",
|
|
12138
|
-
`remote run emitted malformed JSONL: ${err instanceof Error ? err.message : String(err)}`
|
|
12139
|
-
);
|
|
12140
12305
|
}
|
|
12306
|
+
newline = buffered.indexOf("\n");
|
|
12141
12307
|
}
|
|
12142
|
-
|
|
12308
|
+
return true;
|
|
12143
12309
|
}
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12310
|
+
};
|
|
12311
|
+
const stderr = {
|
|
12312
|
+
write(chunk) {
|
|
12313
|
+
const text = chunk.trim();
|
|
12314
|
+
if (text.length > 0) send("stderr", { text });
|
|
12315
|
+
return true;
|
|
12316
|
+
}
|
|
12317
|
+
};
|
|
12318
|
+
try {
|
|
12319
|
+
const workflow = mergeRunSpecEnvIntoWorkflow(
|
|
12320
|
+
runtimeConfig.workflow,
|
|
12321
|
+
spec.env
|
|
12322
|
+
);
|
|
12156
12323
|
const result = await runExecFn({
|
|
12157
12324
|
prompt: spec.prompt,
|
|
12158
12325
|
projectDir,
|
|
12159
12326
|
harness: runtimeConfig.harness,
|
|
12160
|
-
athenaSessionId: spec.sessionId ?? `athena-${frame.runId}`,
|
|
12327
|
+
athenaSessionId: spec.athenaSessionId ?? spec.sessionId ?? `athena-${frame.runId}`,
|
|
12328
|
+
adapterResumeSessionId: spec.adapterResumeSessionId,
|
|
12161
12329
|
isolationConfig: runtimeConfig.isolationConfig,
|
|
12162
12330
|
pluginMcpConfig: runtimeConfig.pluginMcpConfig,
|
|
12163
|
-
workflow
|
|
12331
|
+
workflow,
|
|
12164
12332
|
workflowPlan: runtimeConfig.workflowPlan,
|
|
12333
|
+
dashboardOrigin: "dashboard",
|
|
12165
12334
|
json: true,
|
|
12166
12335
|
verbose: false,
|
|
12167
12336
|
ephemeral: false,
|
|
12168
12337
|
timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
|
|
12169
12338
|
signal: abortSignal,
|
|
12170
12339
|
stdout,
|
|
12171
|
-
stderr
|
|
12340
|
+
stderr,
|
|
12341
|
+
...decisionInbox ? { dashboardDecisionInbox: decisionInbox } : {}
|
|
12172
12342
|
});
|
|
12173
|
-
|
|
12174
|
-
|
|
12343
|
+
const failedCompletion = deferredFailedCompletion.current;
|
|
12344
|
+
if (failedCompletion) {
|
|
12345
|
+
const data = typeof failedCompletion.data === "object" && failedCompletion.data !== null ? failedCompletion.data : {};
|
|
12175
12346
|
send(
|
|
12176
12347
|
"error",
|
|
12177
12348
|
{
|
|
@@ -12183,13 +12354,13 @@ async function executeRemoteAssignment({
|
|
|
12183
12354
|
finalMessage: result.finalMessage,
|
|
12184
12355
|
tokens: result.tokens,
|
|
12185
12356
|
durationMs: result.durationMs,
|
|
12186
|
-
message: result.failure?.message ?? eventPayload(
|
|
12357
|
+
message: result.failure?.message ?? eventPayload(failedCompletion).message ?? "remote execution failed"
|
|
12187
12358
|
},
|
|
12188
|
-
typeof
|
|
12359
|
+
typeof failedCompletion.ts === "number" ? failedCompletion.ts : now()
|
|
12189
12360
|
);
|
|
12190
12361
|
return;
|
|
12191
12362
|
}
|
|
12192
|
-
if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
|
|
12363
|
+
if (result.failure && result.failure.message !== lastTerminalFailureMessage.current) {
|
|
12193
12364
|
send("error", {
|
|
12194
12365
|
success: result.success,
|
|
12195
12366
|
exitCode: result.exitCode,
|
|
@@ -12201,28 +12372,321 @@ async function executeRemoteAssignment({
|
|
|
12201
12372
|
message: result.failure.message
|
|
12202
12373
|
});
|
|
12203
12374
|
}
|
|
12375
|
+
} catch (err) {
|
|
12376
|
+
send("error", {
|
|
12377
|
+
message: err instanceof Error ? err.message : String(err)
|
|
12378
|
+
});
|
|
12379
|
+
}
|
|
12380
|
+
} finally {
|
|
12381
|
+
if (runStream) {
|
|
12382
|
+
const drainTimeout = new Promise((resolve) => {
|
|
12383
|
+
const t = setTimeout(() => resolve(), 1e4);
|
|
12384
|
+
t.unref();
|
|
12385
|
+
});
|
|
12386
|
+
await Promise.race([runStream.whenTerminated(), drainTimeout]);
|
|
12387
|
+
await runStream.close("done");
|
|
12388
|
+
}
|
|
12389
|
+
}
|
|
12390
|
+
}
|
|
12391
|
+
|
|
12392
|
+
// src/app/dashboard/dashboardDecisionInbox.ts
|
|
12393
|
+
import Database4 from "better-sqlite3";
|
|
12394
|
+
function dashboardDecisionInboxPath() {
|
|
12395
|
+
return `${ensureDaemonStateDir().dir}/dashboard-decision-inbox.db`;
|
|
12396
|
+
}
|
|
12397
|
+
function hasLegacyUniqueConstraint(db) {
|
|
12398
|
+
const rows = db.prepare(`PRAGMA index_list('dashboard_decision_inbox')`).all();
|
|
12399
|
+
return rows.some((row) => row.unique === 1 && row.origin === "u");
|
|
12400
|
+
}
|
|
12401
|
+
function migrateLegacyUniqueConstraint(db) {
|
|
12402
|
+
if (!hasLegacyUniqueConstraint(db)) return;
|
|
12403
|
+
db.exec(`
|
|
12404
|
+
ALTER TABLE dashboard_decision_inbox
|
|
12405
|
+
RENAME TO dashboard_decision_inbox_legacy;
|
|
12406
|
+
|
|
12407
|
+
CREATE TABLE dashboard_decision_inbox (
|
|
12408
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12409
|
+
athena_session_id TEXT NOT NULL,
|
|
12410
|
+
request_id TEXT NOT NULL,
|
|
12411
|
+
decision_json TEXT NOT NULL,
|
|
12412
|
+
received_at INTEGER NOT NULL,
|
|
12413
|
+
consumed_at INTEGER
|
|
12414
|
+
);
|
|
12415
|
+
|
|
12416
|
+
INSERT INTO dashboard_decision_inbox (
|
|
12417
|
+
id,
|
|
12418
|
+
athena_session_id,
|
|
12419
|
+
request_id,
|
|
12420
|
+
decision_json,
|
|
12421
|
+
received_at,
|
|
12422
|
+
consumed_at
|
|
12423
|
+
)
|
|
12424
|
+
SELECT
|
|
12425
|
+
id,
|
|
12426
|
+
athena_session_id,
|
|
12427
|
+
request_id,
|
|
12428
|
+
decision_json,
|
|
12429
|
+
received_at,
|
|
12430
|
+
consumed_at
|
|
12431
|
+
FROM dashboard_decision_inbox_legacy;
|
|
12432
|
+
|
|
12433
|
+
DROP TABLE dashboard_decision_inbox_legacy;
|
|
12434
|
+
`);
|
|
12435
|
+
}
|
|
12436
|
+
function initInboxSchema(db) {
|
|
12437
|
+
db.exec(`
|
|
12438
|
+
PRAGMA journal_mode = WAL;
|
|
12439
|
+
|
|
12440
|
+
CREATE TABLE IF NOT EXISTS dashboard_decision_inbox (
|
|
12441
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12442
|
+
athena_session_id TEXT NOT NULL,
|
|
12443
|
+
request_id TEXT NOT NULL,
|
|
12444
|
+
decision_json TEXT NOT NULL,
|
|
12445
|
+
received_at INTEGER NOT NULL,
|
|
12446
|
+
consumed_at INTEGER
|
|
12447
|
+
);
|
|
12448
|
+
`);
|
|
12449
|
+
migrateLegacyUniqueConstraint(db);
|
|
12450
|
+
db.exec(`
|
|
12451
|
+
|
|
12452
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_dashboard_decision_unconsumed
|
|
12453
|
+
ON dashboard_decision_inbox(athena_session_id, request_id)
|
|
12454
|
+
WHERE consumed_at IS NULL;
|
|
12455
|
+
|
|
12456
|
+
CREATE INDEX IF NOT EXISTS idx_dashboard_decision_pending
|
|
12457
|
+
ON dashboard_decision_inbox(athena_session_id, consumed_at, id);
|
|
12458
|
+
`);
|
|
12459
|
+
}
|
|
12460
|
+
function createDashboardDecisionInbox(options = {}) {
|
|
12461
|
+
const db = new Database4(options.dbPath ?? dashboardDecisionInboxPath());
|
|
12462
|
+
initInboxSchema(db);
|
|
12463
|
+
const upsertUnconsumed = db.prepare(`
|
|
12464
|
+
INSERT INTO dashboard_decision_inbox (
|
|
12465
|
+
athena_session_id,
|
|
12466
|
+
request_id,
|
|
12467
|
+
decision_json,
|
|
12468
|
+
received_at
|
|
12469
|
+
)
|
|
12470
|
+
VALUES (?, ?, ?, ?)
|
|
12471
|
+
ON CONFLICT(athena_session_id, request_id) WHERE consumed_at IS NULL
|
|
12472
|
+
DO UPDATE SET
|
|
12473
|
+
decision_json = excluded.decision_json,
|
|
12474
|
+
received_at = excluded.received_at
|
|
12475
|
+
`);
|
|
12476
|
+
const selectPending = db.prepare(`
|
|
12477
|
+
SELECT id, athena_session_id, request_id, decision_json, received_at
|
|
12478
|
+
FROM dashboard_decision_inbox
|
|
12479
|
+
WHERE athena_session_id = ? AND consumed_at IS NULL
|
|
12480
|
+
ORDER BY id ASC
|
|
12481
|
+
LIMIT ?
|
|
12482
|
+
`);
|
|
12483
|
+
const consume = db.prepare(`
|
|
12484
|
+
UPDATE dashboard_decision_inbox
|
|
12485
|
+
SET consumed_at = ?
|
|
12486
|
+
WHERE id = ?
|
|
12487
|
+
`);
|
|
12488
|
+
return {
|
|
12489
|
+
enqueue(input) {
|
|
12490
|
+
upsertUnconsumed.run(
|
|
12491
|
+
input.athenaSessionId,
|
|
12492
|
+
input.requestId,
|
|
12493
|
+
JSON.stringify(input.decision),
|
|
12494
|
+
input.receivedAt
|
|
12495
|
+
);
|
|
12496
|
+
},
|
|
12497
|
+
pendingForSession(input) {
|
|
12498
|
+
const rows = selectPending.all(
|
|
12499
|
+
input.athenaSessionId,
|
|
12500
|
+
input.limit
|
|
12501
|
+
);
|
|
12502
|
+
return rows.map((row) => ({
|
|
12503
|
+
id: row.id,
|
|
12504
|
+
athenaSessionId: row.athena_session_id,
|
|
12505
|
+
requestId: row.request_id,
|
|
12506
|
+
decision: JSON.parse(row.decision_json),
|
|
12507
|
+
receivedAt: row.received_at
|
|
12508
|
+
}));
|
|
12509
|
+
},
|
|
12510
|
+
markConsumed(input) {
|
|
12511
|
+
consume.run(Date.now(), input.id);
|
|
12512
|
+
},
|
|
12513
|
+
close() {
|
|
12514
|
+
db.close();
|
|
12515
|
+
}
|
|
12516
|
+
};
|
|
12517
|
+
}
|
|
12518
|
+
|
|
12519
|
+
// src/app/dashboard/dashboardPairedExecution.ts
|
|
12520
|
+
var DEFAULT_MAX_CONCURRENT_RUNS = 1;
|
|
12521
|
+
var DEFAULT_RUN_HISTORY_LIMIT = 100;
|
|
12522
|
+
function createDashboardPairedExecution(options) {
|
|
12523
|
+
const client = options.client;
|
|
12524
|
+
const executor = options.executor;
|
|
12525
|
+
const projectDir = options.projectDir;
|
|
12526
|
+
const decisionInbox = options.decisionInbox;
|
|
12527
|
+
const log = options.log ?? (() => {
|
|
12528
|
+
});
|
|
12529
|
+
const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
|
|
12530
|
+
const runHistoryLimit = options.runHistoryLimit ?? DEFAULT_RUN_HISTORY_LIMIT;
|
|
12531
|
+
const now = options.now ?? (() => Date.now());
|
|
12532
|
+
let completedRuns = 0;
|
|
12533
|
+
const active = /* @__PURE__ */ new Map();
|
|
12534
|
+
const activeByRunner = /* @__PURE__ */ new Map();
|
|
12535
|
+
const runHistory = [];
|
|
12536
|
+
function recordRun(record) {
|
|
12537
|
+
runHistory.push(record);
|
|
12538
|
+
while (runHistory.length > runHistoryLimit) {
|
|
12539
|
+
runHistory.shift();
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
function rejectAssignment(runId, reason) {
|
|
12543
|
+
try {
|
|
12544
|
+
client.sendRunEvent({
|
|
12545
|
+
runId,
|
|
12546
|
+
seq: 0,
|
|
12547
|
+
ts: now(),
|
|
12548
|
+
kind: "rejected",
|
|
12549
|
+
payload: { reason }
|
|
12550
|
+
});
|
|
12551
|
+
} catch (err) {
|
|
12552
|
+
log(
|
|
12553
|
+
"warn",
|
|
12554
|
+
`runtime daemon: failed to send rejected for ${runId}: ${err instanceof Error ? err.message : String(err)}`
|
|
12555
|
+
);
|
|
12556
|
+
}
|
|
12557
|
+
recordRun({
|
|
12558
|
+
runId,
|
|
12559
|
+
startedAt: now(),
|
|
12560
|
+
endedAt: now(),
|
|
12561
|
+
status: "rejected",
|
|
12562
|
+
error: reason
|
|
12204
12563
|
});
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12564
|
+
log("warn", `run ${runId} rejected: ${reason}`);
|
|
12565
|
+
}
|
|
12566
|
+
function handleDecision(frame) {
|
|
12567
|
+
decisionInbox.enqueue({
|
|
12568
|
+
athenaSessionId: frame.athenaSessionId,
|
|
12569
|
+
requestId: frame.requestId,
|
|
12570
|
+
decision: frame.decision,
|
|
12571
|
+
receivedAt: now()
|
|
12208
12572
|
});
|
|
12209
|
-
} finally {
|
|
12210
|
-
await closeRunStream("done");
|
|
12211
12573
|
}
|
|
12574
|
+
function handleCancel(frame) {
|
|
12575
|
+
const entry = active.get(frame.runId);
|
|
12576
|
+
if (!entry) return;
|
|
12577
|
+
entry.record.status = "cancelled";
|
|
12578
|
+
entry.controller.abort();
|
|
12579
|
+
}
|
|
12580
|
+
function handleAssignment(frame) {
|
|
12581
|
+
if (active.has(frame.runId)) {
|
|
12582
|
+
rejectAssignment(
|
|
12583
|
+
frame.runId,
|
|
12584
|
+
`duplicate active assignment ${frame.runId}`
|
|
12585
|
+
);
|
|
12586
|
+
return;
|
|
12587
|
+
}
|
|
12588
|
+
const runnerKey = frame.runnerId;
|
|
12589
|
+
const bucket = activeByRunner.get(runnerKey) ?? /* @__PURE__ */ new Set();
|
|
12590
|
+
if (bucket.size >= maxConcurrentRuns) {
|
|
12591
|
+
rejectAssignment(
|
|
12592
|
+
frame.runId,
|
|
12593
|
+
`runtime daemon at concurrency cap (${maxConcurrentRuns}) for runner ${runnerKey ?? "<legacy>"}`
|
|
12594
|
+
);
|
|
12595
|
+
return;
|
|
12596
|
+
}
|
|
12597
|
+
const controller = new AbortController();
|
|
12598
|
+
const record = {
|
|
12599
|
+
runId: frame.runId,
|
|
12600
|
+
startedAt: now(),
|
|
12601
|
+
status: "running"
|
|
12602
|
+
};
|
|
12603
|
+
recordRun(record);
|
|
12604
|
+
bucket.add(frame.runId);
|
|
12605
|
+
activeByRunner.set(runnerKey, bucket);
|
|
12606
|
+
const promise = executor({
|
|
12607
|
+
frame,
|
|
12608
|
+
client,
|
|
12609
|
+
projectDir,
|
|
12610
|
+
log,
|
|
12611
|
+
abortSignal: controller.signal,
|
|
12612
|
+
decisionInbox
|
|
12613
|
+
}).then(() => {
|
|
12614
|
+
if (record.status === "running") record.status = "completed";
|
|
12615
|
+
}).catch((err) => {
|
|
12616
|
+
if (record.status === "running") {
|
|
12617
|
+
record.status = "failed";
|
|
12618
|
+
}
|
|
12619
|
+
record.error = err instanceof Error ? err.message : String(err);
|
|
12620
|
+
log(
|
|
12621
|
+
"error",
|
|
12622
|
+
`run ${frame.runId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12623
|
+
);
|
|
12624
|
+
}).finally(() => {
|
|
12625
|
+
record.endedAt = now();
|
|
12626
|
+
completedRuns += 1;
|
|
12627
|
+
active.delete(frame.runId);
|
|
12628
|
+
const remaining = activeByRunner.get(runnerKey);
|
|
12629
|
+
if (remaining) {
|
|
12630
|
+
remaining.delete(frame.runId);
|
|
12631
|
+
if (remaining.size === 0) activeByRunner.delete(runnerKey);
|
|
12632
|
+
}
|
|
12633
|
+
});
|
|
12634
|
+
active.set(frame.runId, { controller, promise, record, runnerKey });
|
|
12635
|
+
}
|
|
12636
|
+
return {
|
|
12637
|
+
handleFrame(frame) {
|
|
12638
|
+
if (frame.type === "dashboard_decision") {
|
|
12639
|
+
handleDecision(frame);
|
|
12640
|
+
return true;
|
|
12641
|
+
}
|
|
12642
|
+
if (frame.type === "cancel") {
|
|
12643
|
+
handleCancel(frame);
|
|
12644
|
+
return true;
|
|
12645
|
+
}
|
|
12646
|
+
if (frame.type === "job_assignment") {
|
|
12647
|
+
handleAssignment(frame);
|
|
12648
|
+
return true;
|
|
12649
|
+
}
|
|
12650
|
+
return false;
|
|
12651
|
+
},
|
|
12652
|
+
snapshot() {
|
|
12653
|
+
return {
|
|
12654
|
+
activeRuns: active.size,
|
|
12655
|
+
completedRuns
|
|
12656
|
+
};
|
|
12657
|
+
},
|
|
12658
|
+
listRuns(opts = {}) {
|
|
12659
|
+
let out = runHistory.slice();
|
|
12660
|
+
if (typeof opts.limit === "number" && opts.limit > 0) {
|
|
12661
|
+
out = out.slice(-opts.limit);
|
|
12662
|
+
}
|
|
12663
|
+
if (opts.active) {
|
|
12664
|
+
out = out.filter((r) => r.status === "running");
|
|
12665
|
+
}
|
|
12666
|
+
return out;
|
|
12667
|
+
},
|
|
12668
|
+
async stop() {
|
|
12669
|
+
for (const run of active.values()) {
|
|
12670
|
+
run.controller.abort();
|
|
12671
|
+
}
|
|
12672
|
+
await Promise.allSettled([...active.values()].map((run) => run.promise));
|
|
12673
|
+
}
|
|
12674
|
+
};
|
|
12212
12675
|
}
|
|
12213
12676
|
|
|
12214
12677
|
// src/app/dashboard/runtimeDaemon.ts
|
|
12215
12678
|
var DEFAULT_RECONNECT_DELAYS_MS2 = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
12216
|
-
var
|
|
12679
|
+
var DEFAULT_MAX_CONCURRENT_RUNS2 = 1;
|
|
12217
12680
|
var DEFAULT_REFRESH_LEAD_SEC = 60;
|
|
12218
12681
|
var DEFAULT_REFRESH_FAILURE_LIMIT = 5;
|
|
12219
12682
|
var DEFAULT_REFRESH_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
12220
12683
|
var DEFAULT_REFRESH_COOLDOWN_MS = 5 * 6e4;
|
|
12221
|
-
var
|
|
12684
|
+
var DEFAULT_RUN_HISTORY_LIMIT2 = 100;
|
|
12685
|
+
var DEFAULT_FEED_DRAIN_INTERVAL_MS = 1e3;
|
|
12222
12686
|
function delay(ms) {
|
|
12223
12687
|
return new Promise((resolve) => {
|
|
12224
12688
|
const timer = setTimeout(resolve, ms);
|
|
12225
|
-
timer.unref
|
|
12689
|
+
timer.unref();
|
|
12226
12690
|
});
|
|
12227
12691
|
}
|
|
12228
12692
|
async function runDashboardRuntimeDaemon(options = {}) {
|
|
@@ -12239,32 +12703,52 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12239
12703
|
const log = options.log ?? (() => {
|
|
12240
12704
|
});
|
|
12241
12705
|
const reconnectDelays = options.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS2;
|
|
12242
|
-
const maxConcurrentRuns = options.maxConcurrentRuns ??
|
|
12706
|
+
const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS2;
|
|
12243
12707
|
const refreshLeadSec = options.refreshLeadSec ?? DEFAULT_REFRESH_LEAD_SEC;
|
|
12244
12708
|
const refreshFailureLimit = options.refreshFailureLimit ?? DEFAULT_REFRESH_FAILURE_LIMIT;
|
|
12245
12709
|
const refreshFailureWindowMs = options.refreshFailureWindowMs ?? DEFAULT_REFRESH_FAILURE_WINDOW_MS;
|
|
12246
12710
|
const refreshCooldownMs = options.refreshCooldownMs ?? DEFAULT_REFRESH_COOLDOWN_MS;
|
|
12247
|
-
const runHistoryLimit = options.runHistoryLimit ??
|
|
12711
|
+
const runHistoryLimit = options.runHistoryLimit ?? DEFAULT_RUN_HISTORY_LIMIT2;
|
|
12248
12712
|
const now = options.now ?? (() => Date.now());
|
|
12713
|
+
const writeMirror = options.writeMirror ?? writeAttachmentMirror;
|
|
12714
|
+
const feedOutbox = options.feedOutbox ?? createDashboardFeedOutbox();
|
|
12715
|
+
const decisionInbox = options.decisionInbox ?? createDashboardDecisionInbox();
|
|
12716
|
+
const feedDrainIntervalMs = options.feedDrainIntervalMs ?? DEFAULT_FEED_DRAIN_INTERVAL_MS;
|
|
12249
12717
|
const startedAt = now();
|
|
12250
12718
|
let stopped = false;
|
|
12251
12719
|
let reconnectAttempt = 0;
|
|
12252
12720
|
let client = null;
|
|
12721
|
+
let lastSocketClient = null;
|
|
12253
12722
|
let currentInstanceId;
|
|
12254
12723
|
let currentDashboardUrl;
|
|
12255
12724
|
let lastFrameAt;
|
|
12256
|
-
let completedRuns = 0;
|
|
12257
12725
|
let refreshTimer = null;
|
|
12726
|
+
let feedDrainTimer = null;
|
|
12258
12727
|
const refreshFailures = [];
|
|
12259
12728
|
let cooldownUntil = 0;
|
|
12260
|
-
const
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12729
|
+
const executionClient = {
|
|
12730
|
+
sendRunEvent(event) {
|
|
12731
|
+
const current = client ?? lastSocketClient;
|
|
12732
|
+
if (!current) {
|
|
12733
|
+
log(
|
|
12734
|
+
"warn",
|
|
12735
|
+
`instance socket dropped run_event (socket not connected): runId=${event.runId} kind=${event.kind}`
|
|
12736
|
+
);
|
|
12737
|
+
return;
|
|
12738
|
+
}
|
|
12739
|
+
current.sendRunEvent(event);
|
|
12266
12740
|
}
|
|
12267
|
-
}
|
|
12741
|
+
};
|
|
12742
|
+
const pairedExecution = createDashboardPairedExecution({
|
|
12743
|
+
client: executionClient,
|
|
12744
|
+
executor,
|
|
12745
|
+
projectDir,
|
|
12746
|
+
decisionInbox,
|
|
12747
|
+
log,
|
|
12748
|
+
maxConcurrentRuns,
|
|
12749
|
+
now,
|
|
12750
|
+
runHistoryLimit
|
|
12751
|
+
});
|
|
12268
12752
|
function nextReconnectDelay() {
|
|
12269
12753
|
if (reconnectDelays.length === 0) return 0;
|
|
12270
12754
|
const delayMs = reconnectDelays[Math.min(reconnectAttempt, reconnectDelays.length - 1)] ?? 0;
|
|
@@ -12277,6 +12761,35 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12277
12761
|
refreshTimer = null;
|
|
12278
12762
|
}
|
|
12279
12763
|
}
|
|
12764
|
+
function clearFeedDrainTimer() {
|
|
12765
|
+
if (feedDrainTimer) {
|
|
12766
|
+
clearInterval(feedDrainTimer);
|
|
12767
|
+
feedDrainTimer = null;
|
|
12768
|
+
}
|
|
12769
|
+
}
|
|
12770
|
+
function drainFeedOutbox() {
|
|
12771
|
+
const current = client;
|
|
12772
|
+
if (!current) return;
|
|
12773
|
+
const rows = feedOutbox.pendingBatch({ limit: 100, now: now() });
|
|
12774
|
+
for (const row of rows) {
|
|
12775
|
+
current.sendFeedEvent({
|
|
12776
|
+
deliverySeq: row.deliverySeq,
|
|
12777
|
+
envelope: row.envelope
|
|
12778
|
+
});
|
|
12779
|
+
const retryDelayMs = Math.min(3e4, (row.attempt + 1) * 1e3);
|
|
12780
|
+
feedOutbox.markAttempted({
|
|
12781
|
+
deliverySeq: row.deliverySeq,
|
|
12782
|
+
nextAttemptAt: now() + retryDelayMs
|
|
12783
|
+
});
|
|
12784
|
+
}
|
|
12785
|
+
}
|
|
12786
|
+
function startFeedDrainTimer() {
|
|
12787
|
+
clearFeedDrainTimer();
|
|
12788
|
+
const timer = setInterval(drainFeedOutbox, feedDrainIntervalMs);
|
|
12789
|
+
timer.unref();
|
|
12790
|
+
feedDrainTimer = timer;
|
|
12791
|
+
drainFeedOutbox();
|
|
12792
|
+
}
|
|
12280
12793
|
function scheduleRefresh(expiresInSec) {
|
|
12281
12794
|
clearRefreshTimer();
|
|
12282
12795
|
if (!Number.isFinite(expiresInSec) || expiresInSec <= refreshLeadSec) {
|
|
@@ -12286,7 +12799,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12286
12799
|
const timer = setTimeout(() => {
|
|
12287
12800
|
void proactiveRefresh();
|
|
12288
12801
|
}, ms);
|
|
12289
|
-
timer.unref
|
|
12802
|
+
timer.unref();
|
|
12290
12803
|
refreshTimer = timer;
|
|
12291
12804
|
}
|
|
12292
12805
|
async function proactiveRefresh() {
|
|
@@ -12321,30 +12834,6 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12321
12834
|
);
|
|
12322
12835
|
}
|
|
12323
12836
|
}
|
|
12324
|
-
function rejectAssignment(client_, runId, reason) {
|
|
12325
|
-
try {
|
|
12326
|
-
client_.sendRunEvent({
|
|
12327
|
-
runId,
|
|
12328
|
-
seq: 0,
|
|
12329
|
-
ts: now(),
|
|
12330
|
-
kind: "rejected",
|
|
12331
|
-
payload: { reason }
|
|
12332
|
-
});
|
|
12333
|
-
} catch (err) {
|
|
12334
|
-
log(
|
|
12335
|
-
"warn",
|
|
12336
|
-
`runtime daemon: failed to send rejected for ${runId}: ${err instanceof Error ? err.message : String(err)}`
|
|
12337
|
-
);
|
|
12338
|
-
}
|
|
12339
|
-
recordRun({
|
|
12340
|
-
runId,
|
|
12341
|
-
startedAt: now(),
|
|
12342
|
-
endedAt: now(),
|
|
12343
|
-
status: "rejected",
|
|
12344
|
-
error: reason
|
|
12345
|
-
});
|
|
12346
|
-
log("warn", `run ${runId} rejected: ${reason}`);
|
|
12347
|
-
}
|
|
12348
12837
|
async function connectOnce() {
|
|
12349
12838
|
const config = readConfig2();
|
|
12350
12839
|
if (!config) {
|
|
@@ -12379,54 +12868,34 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12379
12868
|
});
|
|
12380
12869
|
next.onFrame((frame) => {
|
|
12381
12870
|
lastFrameAt = now();
|
|
12382
|
-
if (frame.type === "
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12871
|
+
if (frame.type === "attachments.changed") {
|
|
12872
|
+
try {
|
|
12873
|
+
writeMirror({
|
|
12874
|
+
instanceId: token.instanceId,
|
|
12875
|
+
fetchedAt: now(),
|
|
12876
|
+
attachments: frame.attachments.map((a) => ({
|
|
12877
|
+
runnerId: a.runnerId,
|
|
12878
|
+
...a.name !== void 0 ? { name: a.name } : {},
|
|
12879
|
+
...a.executionTarget !== void 0 ? { executionTarget: a.executionTarget } : {},
|
|
12880
|
+
...a.remoteInstanceId !== void 0 ? { remoteInstanceId: a.remoteInstanceId } : {}
|
|
12881
|
+
}))
|
|
12882
|
+
});
|
|
12883
|
+
} catch (err) {
|
|
12884
|
+
log(
|
|
12885
|
+
"warn",
|
|
12886
|
+
`runtime daemon: failed to write attachment mirror: ${err instanceof Error ? err.message : String(err)}`
|
|
12887
|
+
);
|
|
12387
12888
|
}
|
|
12388
12889
|
return;
|
|
12389
12890
|
}
|
|
12390
|
-
if (frame.type
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
frame.runId,
|
|
12396
|
-
`runtime daemon at concurrency cap (${maxConcurrentRuns})`
|
|
12397
|
-
);
|
|
12891
|
+
if (frame.type === "feed_ack") {
|
|
12892
|
+
feedOutbox.markAcked({
|
|
12893
|
+
...typeof frame.deliverySeq === "number" ? { deliverySeq: frame.deliverySeq } : {},
|
|
12894
|
+
...typeof frame.eventId === "string" ? { eventId: frame.eventId } : {}
|
|
12895
|
+
});
|
|
12398
12896
|
return;
|
|
12399
12897
|
}
|
|
12400
|
-
|
|
12401
|
-
const record = {
|
|
12402
|
-
runId: frame.runId,
|
|
12403
|
-
startedAt: now(),
|
|
12404
|
-
status: "running"
|
|
12405
|
-
};
|
|
12406
|
-
recordRun(record);
|
|
12407
|
-
const promise = executor({
|
|
12408
|
-
frame,
|
|
12409
|
-
client: next,
|
|
12410
|
-
projectDir,
|
|
12411
|
-
log,
|
|
12412
|
-
abortSignal: controller.signal
|
|
12413
|
-
}).then(() => {
|
|
12414
|
-
if (record.status === "running") record.status = "completed";
|
|
12415
|
-
}).catch((err) => {
|
|
12416
|
-
if (record.status === "running") {
|
|
12417
|
-
record.status = "failed";
|
|
12418
|
-
}
|
|
12419
|
-
record.error = err instanceof Error ? err.message : String(err);
|
|
12420
|
-
log(
|
|
12421
|
-
"error",
|
|
12422
|
-
`run ${frame.runId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12423
|
-
);
|
|
12424
|
-
}).finally(() => {
|
|
12425
|
-
record.endedAt = now();
|
|
12426
|
-
completedRuns += 1;
|
|
12427
|
-
active.delete(frame.runId);
|
|
12428
|
-
});
|
|
12429
|
-
active.set(frame.runId, { controller, promise, record });
|
|
12898
|
+
pairedExecution.handleFrame(frame);
|
|
12430
12899
|
});
|
|
12431
12900
|
next.onClose((reason) => {
|
|
12432
12901
|
if (stopped || client !== next) return;
|
|
@@ -12434,14 +12903,17 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12434
12903
|
client = null;
|
|
12435
12904
|
currentInstanceId = void 0;
|
|
12436
12905
|
clearRefreshTimer();
|
|
12906
|
+
clearFeedDrainTimer();
|
|
12437
12907
|
void reconnectLoop();
|
|
12438
12908
|
});
|
|
12439
12909
|
await next.connect();
|
|
12440
12910
|
client = next;
|
|
12911
|
+
lastSocketClient = next;
|
|
12441
12912
|
currentInstanceId = token.instanceId;
|
|
12442
12913
|
currentDashboardUrl = config.dashboardUrl;
|
|
12443
12914
|
reconnectAttempt = 0;
|
|
12444
12915
|
scheduleRefresh(token.expiresInSec);
|
|
12916
|
+
startFeedDrainTimer();
|
|
12445
12917
|
log("info", `dashboard runtime daemon connected as ${token.instanceId}`);
|
|
12446
12918
|
}
|
|
12447
12919
|
async function reconnectLoop() {
|
|
@@ -12463,6 +12935,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12463
12935
|
await connectOnce();
|
|
12464
12936
|
return {
|
|
12465
12937
|
snapshot() {
|
|
12938
|
+
const executionSnapshot = pairedExecution.snapshot();
|
|
12466
12939
|
const refreshState = refreshFailures.length > 0 || cooldownUntil > now() ? {
|
|
12467
12940
|
recentFailures: refreshFailures.length,
|
|
12468
12941
|
...cooldownUntil > now() ? { cooldownUntilMs: cooldownUntil } : {}
|
|
@@ -12471,158 +12944,30 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12471
12944
|
startedAt,
|
|
12472
12945
|
socketConnected: client !== null,
|
|
12473
12946
|
...lastFrameAt !== void 0 ? { lastFrameAt } : {},
|
|
12474
|
-
activeRuns:
|
|
12475
|
-
completedRuns,
|
|
12947
|
+
activeRuns: executionSnapshot.activeRuns,
|
|
12948
|
+
completedRuns: executionSnapshot.completedRuns,
|
|
12476
12949
|
...currentInstanceId ? { instanceId: currentInstanceId } : {},
|
|
12477
12950
|
...currentDashboardUrl ? { dashboardUrl: currentDashboardUrl } : {},
|
|
12478
12951
|
...refreshState ? { refreshState } : {}
|
|
12479
12952
|
};
|
|
12480
12953
|
},
|
|
12481
12954
|
listRuns(opts = {}) {
|
|
12482
|
-
|
|
12483
|
-
if (typeof opts.limit === "number" && opts.limit > 0) {
|
|
12484
|
-
out = out.slice(-opts.limit);
|
|
12485
|
-
}
|
|
12486
|
-
if (opts.active) {
|
|
12487
|
-
out = out.filter((r) => r.status === "running");
|
|
12488
|
-
}
|
|
12489
|
-
return out;
|
|
12955
|
+
return pairedExecution.listRuns(opts);
|
|
12490
12956
|
},
|
|
12491
12957
|
async stop(reason = "stopped") {
|
|
12492
12958
|
stopped = true;
|
|
12493
12959
|
clearRefreshTimer();
|
|
12494
|
-
|
|
12495
|
-
run.controller.abort();
|
|
12496
|
-
}
|
|
12960
|
+
clearFeedDrainTimer();
|
|
12497
12961
|
const current = client;
|
|
12498
12962
|
client = null;
|
|
12499
12963
|
current?.close(reason);
|
|
12500
|
-
await
|
|
12501
|
-
}
|
|
12502
|
-
};
|
|
12503
|
-
}
|
|
12504
|
-
|
|
12505
|
-
// src/infra/daemon/stateDir.ts
|
|
12506
|
-
import fs20 from "fs";
|
|
12507
|
-
import os11 from "os";
|
|
12508
|
-
import path18 from "path";
|
|
12509
|
-
function daemonStatePaths(env = process.env) {
|
|
12510
|
-
const xdg = env["XDG_STATE_HOME"];
|
|
12511
|
-
const home = env["HOME"] ?? os11.homedir();
|
|
12512
|
-
const base = xdg && xdg.length > 0 ? xdg : path18.join(home, ".local", "state");
|
|
12513
|
-
const dir = path18.join(base, "drisp");
|
|
12514
|
-
return {
|
|
12515
|
-
dir,
|
|
12516
|
-
pidPath: path18.join(dir, "dashboard-daemon.pid"),
|
|
12517
|
-
logPath: path18.join(dir, "dashboard-daemon.log"),
|
|
12518
|
-
socketPath: path18.join(dir, "dashboard-daemon.sock")
|
|
12519
|
-
};
|
|
12520
|
-
}
|
|
12521
|
-
function ensureDaemonStateDir(env = process.env) {
|
|
12522
|
-
const paths = daemonStatePaths(env);
|
|
12523
|
-
fs20.mkdirSync(paths.dir, { recursive: true, mode: 448 });
|
|
12524
|
-
if (process.platform !== "win32") {
|
|
12525
|
-
try {
|
|
12526
|
-
fs20.chmodSync(paths.dir, 448);
|
|
12527
|
-
} catch {
|
|
12528
|
-
}
|
|
12529
|
-
}
|
|
12530
|
-
return paths;
|
|
12531
|
-
}
|
|
12532
|
-
|
|
12533
|
-
// src/infra/daemon/pidLock.ts
|
|
12534
|
-
import fs21 from "fs";
|
|
12535
|
-
function acquirePidLock(pidPath) {
|
|
12536
|
-
const ownPid = process.pid;
|
|
12537
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
12538
|
-
try {
|
|
12539
|
-
const fd = fs21.openSync(pidPath, "wx", 384);
|
|
12540
|
-
try {
|
|
12541
|
-
fs21.writeSync(fd, `${ownPid}
|
|
12542
|
-
`);
|
|
12543
|
-
fs21.fsyncSync(fd);
|
|
12544
|
-
} finally {
|
|
12545
|
-
fs21.closeSync(fd);
|
|
12546
|
-
}
|
|
12547
|
-
return makeHandle(pidPath, ownPid);
|
|
12548
|
-
} catch (err) {
|
|
12549
|
-
if (err.code !== "EEXIST") throw err;
|
|
12550
|
-
}
|
|
12551
|
-
const existing = readPidLock(pidPath);
|
|
12552
|
-
if (existing.state === "held") {
|
|
12553
|
-
throw new Error(
|
|
12554
|
-
`dashboard daemon is already running as pid ${existing.pid} (lock at ${pidPath}). Use "drisp dashboard daemon stop" to terminate it.`
|
|
12555
|
-
);
|
|
12556
|
-
}
|
|
12557
|
-
if (existing.state === "stale") {
|
|
12558
|
-
try {
|
|
12559
|
-
fs21.unlinkSync(pidPath);
|
|
12560
|
-
} catch (err) {
|
|
12561
|
-
if (err.code !== "ENOENT") throw err;
|
|
12562
|
-
}
|
|
12563
|
-
continue;
|
|
12564
|
-
}
|
|
12565
|
-
}
|
|
12566
|
-
throw new Error(
|
|
12567
|
-
`dashboard daemon: failed to acquire pid lock at ${pidPath} after retry`
|
|
12568
|
-
);
|
|
12569
|
-
}
|
|
12570
|
-
function readPidLock(pidPath) {
|
|
12571
|
-
let raw;
|
|
12572
|
-
try {
|
|
12573
|
-
raw = fs21.readFileSync(pidPath, "utf-8");
|
|
12574
|
-
} catch (err) {
|
|
12575
|
-
if (err.code === "ENOENT") {
|
|
12576
|
-
return { state: "absent" };
|
|
12577
|
-
}
|
|
12578
|
-
throw err;
|
|
12579
|
-
}
|
|
12580
|
-
const pid = Number.parseInt(raw.trim(), 10);
|
|
12581
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
12582
|
-
return { state: "stale", pid: 0 };
|
|
12583
|
-
}
|
|
12584
|
-
if (!isProcessAlive(pid)) {
|
|
12585
|
-
return { state: "stale", pid };
|
|
12586
|
-
}
|
|
12587
|
-
return { state: "held", pid };
|
|
12588
|
-
}
|
|
12589
|
-
function makeHandle(pidPath, pid) {
|
|
12590
|
-
let released = false;
|
|
12591
|
-
return {
|
|
12592
|
-
pid,
|
|
12593
|
-
release() {
|
|
12594
|
-
if (released) return;
|
|
12595
|
-
released = true;
|
|
12596
|
-
try {
|
|
12597
|
-
const raw = fs21.readFileSync(pidPath, "utf-8").trim();
|
|
12598
|
-
if (raw === String(pid)) {
|
|
12599
|
-
fs21.unlinkSync(pidPath);
|
|
12600
|
-
}
|
|
12601
|
-
} catch (err) {
|
|
12602
|
-
if (err.code !== "ENOENT") {
|
|
12603
|
-
}
|
|
12604
|
-
}
|
|
12964
|
+
await pairedExecution.stop();
|
|
12605
12965
|
}
|
|
12606
12966
|
};
|
|
12607
12967
|
}
|
|
12608
|
-
function isProcessAlive(pid) {
|
|
12609
|
-
if (process.platform === "win32") {
|
|
12610
|
-
return true;
|
|
12611
|
-
}
|
|
12612
|
-
try {
|
|
12613
|
-
process.kill(pid, 0);
|
|
12614
|
-
return true;
|
|
12615
|
-
} catch (err) {
|
|
12616
|
-
const code = err.code;
|
|
12617
|
-
if (code === "EPERM") {
|
|
12618
|
-
return true;
|
|
12619
|
-
}
|
|
12620
|
-
return false;
|
|
12621
|
-
}
|
|
12622
|
-
}
|
|
12623
12968
|
|
|
12624
12969
|
// src/infra/daemon/udsIpc.ts
|
|
12625
|
-
import
|
|
12970
|
+
import fs21 from "fs";
|
|
12626
12971
|
import net2 from "net";
|
|
12627
12972
|
|
|
12628
12973
|
// src/infra/daemon/udsFrameCodec.ts
|
|
@@ -12667,7 +13012,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
12667
13012
|
});
|
|
12668
13013
|
if (process.platform !== "win32") {
|
|
12669
13014
|
try {
|
|
12670
|
-
|
|
13015
|
+
fs21.chmodSync(socketPath, 384);
|
|
12671
13016
|
} catch {
|
|
12672
13017
|
}
|
|
12673
13018
|
}
|
|
@@ -12677,7 +13022,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
12677
13022
|
server.close(() => resolve());
|
|
12678
13023
|
});
|
|
12679
13024
|
try {
|
|
12680
|
-
|
|
13025
|
+
fs21.unlinkSync(socketPath);
|
|
12681
13026
|
} catch (err) {
|
|
12682
13027
|
if (err.code !== "ENOENT") {
|
|
12683
13028
|
}
|
|
@@ -12743,7 +13088,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
|
|
|
12743
13088
|
socket.destroy();
|
|
12744
13089
|
reject(new Error(`uds request timed out after ${timeoutMs}ms`));
|
|
12745
13090
|
}, timeoutMs);
|
|
12746
|
-
timer.unref
|
|
13091
|
+
timer.unref();
|
|
12747
13092
|
const finish = (action) => {
|
|
12748
13093
|
if (settled) return;
|
|
12749
13094
|
settled = true;
|
|
@@ -12795,7 +13140,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
|
|
|
12795
13140
|
async function unlinkStaleSocket(socketPath) {
|
|
12796
13141
|
let stat;
|
|
12797
13142
|
try {
|
|
12798
|
-
stat =
|
|
13143
|
+
stat = fs21.statSync(socketPath);
|
|
12799
13144
|
} catch (err) {
|
|
12800
13145
|
if (err.code === "ENOENT") return;
|
|
12801
13146
|
throw err;
|
|
@@ -12822,7 +13167,7 @@ async function unlinkStaleSocket(socketPath) {
|
|
|
12822
13167
|
`uds path ${socketPath} is in use by another process; aborting`
|
|
12823
13168
|
);
|
|
12824
13169
|
}
|
|
12825
|
-
|
|
13170
|
+
fs21.unlinkSync(socketPath);
|
|
12826
13171
|
}
|
|
12827
13172
|
|
|
12828
13173
|
export {
|
|
@@ -12840,6 +13185,10 @@ export {
|
|
|
12840
13185
|
ingestRuntimeEvent,
|
|
12841
13186
|
ingestRuntimeDecision,
|
|
12842
13187
|
generateId,
|
|
13188
|
+
daemonStatePaths,
|
|
13189
|
+
ensureDaemonStateDir,
|
|
13190
|
+
createDashboardFeedPublisher,
|
|
13191
|
+
createDashboardDecisionInbox,
|
|
12843
13192
|
createSessionStore,
|
|
12844
13193
|
sessionsDir,
|
|
12845
13194
|
listSessions,
|
|
@@ -12888,11 +13237,7 @@ export {
|
|
|
12888
13237
|
EXEC_EXIT_CODE,
|
|
12889
13238
|
runExec,
|
|
12890
13239
|
runDashboardRuntimeDaemon,
|
|
12891
|
-
daemonStatePaths,
|
|
12892
|
-
ensureDaemonStateDir,
|
|
12893
|
-
acquirePidLock,
|
|
12894
|
-
readPidLock,
|
|
12895
13240
|
startUdsServer,
|
|
12896
13241
|
sendUdsRequest
|
|
12897
13242
|
};
|
|
12898
|
-
//# sourceMappingURL=chunk-
|
|
13243
|
+
//# sourceMappingURL=chunk-TQKNZNDP.js.map
|