@drisp/cli 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{WorkflowInstallWizard-2MC5A7W4.js → WorkflowInstallWizard-X754ND4V.js} +2 -2
- package/dist/athena-gateway.js +5 -3888
- package/dist/chunk-2OJ3GGIP.js +104 -0
- package/dist/{chunk-5VK2ZMVV.js → chunk-A54HGVML.js} +96 -95
- package/dist/{chunk-4CRZXLIP.js → chunk-BTY7MYYT.js} +135 -135
- package/dist/{chunk-PJUDHH4R.js → chunk-K53YMYTG.js} +1049 -812
- package/dist/chunk-MRAM6EYI.js +76 -0
- package/dist/chunk-SHLHZL5F.js +4124 -0
- package/dist/chunk-ZVOGOZNT.js +395 -0
- package/dist/cli.js +1131 -888
- package/dist/dashboard-daemon.js +9 -107
- package/dist/supervisor.js +692 -0
- package/package.json +1 -1
- package/dist/chunk-M44KEGM7.js +0 -173
|
@@ -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-BTY7MYYT.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,254 +11781,92 @@ async function runExec(options) {
|
|
|
11480
11781
|
return result;
|
|
11481
11782
|
}
|
|
11482
11783
|
|
|
11483
|
-
// src/app/
|
|
11784
|
+
// src/app/bootstrap/harnessOverride.ts
|
|
11785
|
+
function normalizeHarnessOverride(value) {
|
|
11786
|
+
switch (value) {
|
|
11787
|
+
case "claude":
|
|
11788
|
+
case "claude-code":
|
|
11789
|
+
return "claude-code";
|
|
11790
|
+
case "codex":
|
|
11791
|
+
case "openai-codex":
|
|
11792
|
+
return "openai-codex";
|
|
11793
|
+
case "opencode":
|
|
11794
|
+
return "opencode";
|
|
11795
|
+
default:
|
|
11796
|
+
return void 0;
|
|
11797
|
+
}
|
|
11798
|
+
}
|
|
11799
|
+
|
|
11800
|
+
// src/app/dashboard/runStreamClient.ts
|
|
11484
11801
|
import { WebSocket as WebSocket2 } from "ws";
|
|
11802
|
+
var DEFAULT_RECONNECT_DELAYS_MS = [250, 1e3, 2e3, 5e3, 15e3];
|
|
11485
11803
|
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;
|
|
11804
|
+
var DEFAULT_WATCHDOG_MS = 9e4;
|
|
11805
|
+
var DEFAULT_MAX_QUEUE_SIZE = 5e3;
|
|
11806
|
+
function createRunStreamClient(opts) {
|
|
11498
11807
|
const log = opts.log ?? (() => {
|
|
11499
11808
|
});
|
|
11500
11809
|
const now = opts.now ?? (() => Date.now());
|
|
11501
|
-
const
|
|
11502
|
-
const
|
|
11503
|
-
const
|
|
11810
|
+
const reconnectDelays = opts.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
|
|
11811
|
+
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
11812
|
+
const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
|
|
11813
|
+
const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
11814
|
+
const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket2(url));
|
|
11815
|
+
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
11816
|
+
const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
11817
|
+
let nextSeq = 1;
|
|
11818
|
+
const queue = [];
|
|
11504
11819
|
let ws = null;
|
|
11505
|
-
let
|
|
11506
|
-
let
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
}
|
|
11518
|
-
droppedSinceClose = 0;
|
|
11519
|
-
try {
|
|
11520
|
-
ws.send(JSON.stringify(frame));
|
|
11521
|
-
} catch (err) {
|
|
11522
|
-
log(
|
|
11523
|
-
"warn",
|
|
11524
|
-
`instance socket send failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11525
|
-
);
|
|
11820
|
+
let connectAttempt = 0;
|
|
11821
|
+
let stopped = false;
|
|
11822
|
+
let serverTerminated = false;
|
|
11823
|
+
let heartbeatTimer = null;
|
|
11824
|
+
let watchdogTimer = null;
|
|
11825
|
+
let reconnectTimer = null;
|
|
11826
|
+
let resumeResolved = false;
|
|
11827
|
+
let firstConnect = null;
|
|
11828
|
+
const terminationWaiters = [];
|
|
11829
|
+
function trimQueueUpTo(lastAckedSeq) {
|
|
11830
|
+
while (queue.length > 0 && queue[0].seq <= lastAckedSeq) {
|
|
11831
|
+
queue.shift();
|
|
11526
11832
|
}
|
|
11527
11833
|
}
|
|
11528
|
-
function
|
|
11529
|
-
|
|
11530
|
-
|
|
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
|
-
try {
|
|
11546
|
-
handler(reason);
|
|
11547
|
-
} catch {
|
|
11548
|
-
}
|
|
11834
|
+
function trimQueueUpToExclusive(expectedSeq) {
|
|
11835
|
+
while (queue.length > 0 && queue[0].seq < expectedSeq) {
|
|
11836
|
+
queue.shift();
|
|
11549
11837
|
}
|
|
11550
11838
|
}
|
|
11551
|
-
function
|
|
11552
|
-
if (
|
|
11553
|
-
|
|
11554
|
-
|
|
11839
|
+
function clearTimers() {
|
|
11840
|
+
if (heartbeatTimer) {
|
|
11841
|
+
clearTimer(heartbeatTimer);
|
|
11842
|
+
heartbeatTimer = null;
|
|
11843
|
+
}
|
|
11844
|
+
if (watchdogTimer) {
|
|
11845
|
+
clearTimer(watchdogTimer);
|
|
11846
|
+
watchdogTimer = null;
|
|
11847
|
+
}
|
|
11848
|
+
if (reconnectTimer) {
|
|
11849
|
+
clearTimer(reconnectTimer);
|
|
11850
|
+
reconnectTimer = null;
|
|
11555
11851
|
}
|
|
11556
|
-
|
|
11852
|
+
}
|
|
11853
|
+
function startHeartbeat() {
|
|
11854
|
+
if (heartbeatMs <= 0) return;
|
|
11855
|
+
const tick = () => {
|
|
11856
|
+
if (!ws || ws.readyState !== ws.OPEN) return;
|
|
11557
11857
|
try {
|
|
11558
|
-
|
|
11858
|
+
ws.send(JSON.stringify({ type: "ping", ts: now() }));
|
|
11559
11859
|
} catch (err) {
|
|
11560
11860
|
log(
|
|
11561
11861
|
"warn",
|
|
11562
|
-
`
|
|
11862
|
+
`run-stream ping failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11563
11863
|
);
|
|
11564
11864
|
}
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
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
|
-
function createRunStreamClient(opts) {
|
|
11668
|
-
const log = opts.log ?? (() => {
|
|
11669
|
-
});
|
|
11670
|
-
const now = opts.now ?? (() => Date.now());
|
|
11671
|
-
const reconnectDelays = opts.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
|
|
11672
|
-
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS2;
|
|
11673
|
-
const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
|
|
11674
|
-
const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
11675
|
-
const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket3(url));
|
|
11676
|
-
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
11677
|
-
const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
11678
|
-
let nextSeq = 1;
|
|
11679
|
-
const queue = [];
|
|
11680
|
-
let ws = null;
|
|
11681
|
-
let connectAttempt = 0;
|
|
11682
|
-
let stopped = false;
|
|
11683
|
-
let serverTerminated = false;
|
|
11684
|
-
let heartbeatTimer = null;
|
|
11685
|
-
let watchdogTimer = null;
|
|
11686
|
-
let reconnectTimer = null;
|
|
11687
|
-
let resumeResolved = false;
|
|
11688
|
-
let firstConnect = null;
|
|
11689
|
-
const terminationWaiters = [];
|
|
11690
|
-
function trimQueueUpTo(lastAckedSeq) {
|
|
11691
|
-
while (queue.length > 0 && queue[0].seq <= lastAckedSeq) {
|
|
11692
|
-
queue.shift();
|
|
11693
|
-
}
|
|
11694
|
-
}
|
|
11695
|
-
function trimQueueUpToExclusive(expectedSeq) {
|
|
11696
|
-
while (queue.length > 0 && queue[0].seq < expectedSeq) {
|
|
11697
|
-
queue.shift();
|
|
11698
|
-
}
|
|
11699
|
-
}
|
|
11700
|
-
function clearTimers() {
|
|
11701
|
-
if (heartbeatTimer) {
|
|
11702
|
-
clearTimer(heartbeatTimer);
|
|
11703
|
-
heartbeatTimer = null;
|
|
11704
|
-
}
|
|
11705
|
-
if (watchdogTimer) {
|
|
11706
|
-
clearTimer(watchdogTimer);
|
|
11707
|
-
watchdogTimer = null;
|
|
11708
|
-
}
|
|
11709
|
-
if (reconnectTimer) {
|
|
11710
|
-
clearTimer(reconnectTimer);
|
|
11711
|
-
reconnectTimer = null;
|
|
11712
|
-
}
|
|
11713
|
-
}
|
|
11714
|
-
function startHeartbeat() {
|
|
11715
|
-
if (heartbeatMs <= 0) return;
|
|
11716
|
-
const tick = () => {
|
|
11717
|
-
if (!ws || ws.readyState !== ws.OPEN) return;
|
|
11718
|
-
try {
|
|
11719
|
-
ws.send(JSON.stringify({ type: "ping", ts: now() }));
|
|
11720
|
-
} catch (err) {
|
|
11721
|
-
log(
|
|
11722
|
-
"warn",
|
|
11723
|
-
`run-stream ping failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11724
|
-
);
|
|
11725
|
-
}
|
|
11726
|
-
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11727
|
-
heartbeatTimer.unref?.();
|
|
11728
|
-
};
|
|
11729
|
-
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11730
|
-
heartbeatTimer.unref?.();
|
|
11865
|
+
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11866
|
+
heartbeatTimer.unref();
|
|
11867
|
+
};
|
|
11868
|
+
heartbeatTimer = setTimer(tick, heartbeatMs);
|
|
11869
|
+
heartbeatTimer.unref();
|
|
11731
11870
|
}
|
|
11732
11871
|
function bumpWatchdog() {
|
|
11733
11872
|
if (watchdogMs <= 0) return;
|
|
@@ -11742,7 +11881,7 @@ function createRunStreamClient(opts) {
|
|
|
11742
11881
|
} catch {
|
|
11743
11882
|
}
|
|
11744
11883
|
}, watchdogMs);
|
|
11745
|
-
watchdogTimer.unref
|
|
11884
|
+
watchdogTimer.unref();
|
|
11746
11885
|
}
|
|
11747
11886
|
function flushQueue() {
|
|
11748
11887
|
if (!ws || ws.readyState !== ws.OPEN || !resumeResolved) return;
|
|
@@ -11829,7 +11968,7 @@ function createRunStreamClient(opts) {
|
|
|
11829
11968
|
reconnectTimer = null;
|
|
11830
11969
|
void openSocket();
|
|
11831
11970
|
}, delayMs);
|
|
11832
|
-
reconnectTimer.unref
|
|
11971
|
+
reconnectTimer.unref();
|
|
11833
11972
|
}
|
|
11834
11973
|
async function openSocket() {
|
|
11835
11974
|
if (stopped || serverTerminated) return;
|
|
@@ -11855,7 +11994,7 @@ function createRunStreamClient(opts) {
|
|
|
11855
11994
|
if (next !== ws) return;
|
|
11856
11995
|
ws = null;
|
|
11857
11996
|
clearTimers();
|
|
11858
|
-
const reason = reasonBuf
|
|
11997
|
+
const reason = reasonBuf.toString() || "closed";
|
|
11859
11998
|
if (code === 1e3 && reason === "run_terminated") {
|
|
11860
11999
|
notifyTerminated();
|
|
11861
12000
|
return;
|
|
@@ -11940,6 +12079,7 @@ function createRunStreamClient(opts) {
|
|
|
11940
12079
|
}
|
|
11941
12080
|
|
|
11942
12081
|
// src/app/dashboard/remoteRunExecutor.ts
|
|
12082
|
+
var DEFAULT_MARKETPLACE_SLUG = "lespaceman/athena-workflow-marketplace";
|
|
11943
12083
|
function parseRunSpec(value) {
|
|
11944
12084
|
if (typeof value !== "object" || value === null) return null;
|
|
11945
12085
|
const obj = value;
|
|
@@ -11949,11 +12089,19 @@ function parseRunSpec(value) {
|
|
|
11949
12089
|
const workflow = obj["workflow"];
|
|
11950
12090
|
const callbackWsUrl = obj["callbackWsUrl"];
|
|
11951
12091
|
const callbackToken = obj["callbackToken"];
|
|
12092
|
+
const workflowObj = typeof workflow === "object" && workflow !== null ? workflow : null;
|
|
11952
12093
|
return {
|
|
11953
12094
|
prompt,
|
|
12095
|
+
athenaSessionId: typeof obj["athenaSessionId"] === "string" && obj["athenaSessionId"].length > 0 ? obj["athenaSessionId"] : void 0,
|
|
12096
|
+
adapterResumeSessionId: typeof obj["adapterResumeSessionId"] === "string" && obj["adapterResumeSessionId"].length > 0 ? obj["adapterResumeSessionId"] : void 0,
|
|
11954
12097
|
sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
|
|
11955
12098
|
projectDir: typeof obj["projectDir"] === "string" && obj["projectDir"].length > 0 ? obj["projectDir"] : void 0,
|
|
11956
|
-
workflow:
|
|
12099
|
+
workflow: workflowObj && typeof workflowObj["ref"] === "string" ? {
|
|
12100
|
+
ref: workflowObj["ref"],
|
|
12101
|
+
...typeof workflowObj["source"] === "string" ? { source: workflowObj["source"] } : {},
|
|
12102
|
+
...typeof workflowObj["version"] === "string" ? { version: workflowObj["version"] } : {}
|
|
12103
|
+
} : void 0,
|
|
12104
|
+
harness: normalizeHarnessOverride(obj["harness"]),
|
|
11957
12105
|
env: typeof env === "object" && env !== null ? Object.fromEntries(
|
|
11958
12106
|
Object.entries(env).filter(
|
|
11959
12107
|
(entry) => typeof entry[1] === "string"
|
|
@@ -11969,6 +12117,47 @@ function workflowNameFromRef(ref) {
|
|
|
11969
12117
|
const [name] = ref.split("@", 1);
|
|
11970
12118
|
return name && name.length > 0 ? name : void 0;
|
|
11971
12119
|
}
|
|
12120
|
+
function isMissingWorkflowError(err, workflowName) {
|
|
12121
|
+
return err instanceof Error && err.message.includes(`Workflow "${workflowName}" not found`);
|
|
12122
|
+
}
|
|
12123
|
+
function configuredWorkflowSources(readGlobalConfigFn) {
|
|
12124
|
+
const sources = readGlobalConfigFn().workflowMarketplaceSources;
|
|
12125
|
+
return sources && sources.length > 0 ? sources : [DEFAULT_MARKETPLACE_SLUG];
|
|
12126
|
+
}
|
|
12127
|
+
function workflowInstallRef(spec) {
|
|
12128
|
+
const ref = spec.workflow?.ref;
|
|
12129
|
+
if (!ref) return void 0;
|
|
12130
|
+
const version = spec.workflow?.version;
|
|
12131
|
+
if (version && !ref.includes("@")) {
|
|
12132
|
+
return `${ref}@${version}`;
|
|
12133
|
+
}
|
|
12134
|
+
return ref;
|
|
12135
|
+
}
|
|
12136
|
+
function workflowInstallSources(spec, readGlobalConfigFn) {
|
|
12137
|
+
const source = spec.workflow?.source?.trim();
|
|
12138
|
+
if (source && source !== "marketplace") {
|
|
12139
|
+
return [source];
|
|
12140
|
+
}
|
|
12141
|
+
return configuredWorkflowSources(readGlobalConfigFn);
|
|
12142
|
+
}
|
|
12143
|
+
function ensureRemoteWorkflowInstalled(input) {
|
|
12144
|
+
const ref = workflowInstallRef(input.spec);
|
|
12145
|
+
const workflowName = workflowNameFromRef(ref);
|
|
12146
|
+
if (!workflowName) return void 0;
|
|
12147
|
+
try {
|
|
12148
|
+
input.resolveWorkflowFn(workflowName);
|
|
12149
|
+
return workflowName;
|
|
12150
|
+
} catch (err) {
|
|
12151
|
+
if (!isMissingWorkflowError(err, workflowName)) {
|
|
12152
|
+
throw err;
|
|
12153
|
+
}
|
|
12154
|
+
}
|
|
12155
|
+
const resolved = input.resolveWorkflowInstallFn(
|
|
12156
|
+
ref,
|
|
12157
|
+
workflowInstallSources(input.spec, input.readGlobalConfigFn)
|
|
12158
|
+
);
|
|
12159
|
+
return input.installWorkflowFromSourceFn(resolved);
|
|
12160
|
+
}
|
|
11972
12161
|
function eventKind(event) {
|
|
11973
12162
|
if (event.type === "exec.completed") {
|
|
11974
12163
|
const data = event.data;
|
|
@@ -11988,22 +12177,18 @@ function eventPayload(event) {
|
|
|
11988
12177
|
}
|
|
11989
12178
|
return event.data ?? null;
|
|
11990
12179
|
}
|
|
11991
|
-
function
|
|
11992
|
-
if (!env || Object.keys(env).length === 0) return
|
|
11993
|
-
const
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
process.env[key] = value;
|
|
12004
|
-
}
|
|
12005
|
-
}
|
|
12006
|
-
});
|
|
12180
|
+
function mergeRunSpecEnvIntoWorkflow(workflow, env) {
|
|
12181
|
+
if (!env || Object.keys(env).length === 0) return workflow;
|
|
12182
|
+
const mergedEnv = { ...workflow?.env ?? {}, ...env };
|
|
12183
|
+
if (workflow) {
|
|
12184
|
+
return { ...workflow, env: mergedEnv };
|
|
12185
|
+
}
|
|
12186
|
+
return {
|
|
12187
|
+
name: "dashboard-remote",
|
|
12188
|
+
plugins: [],
|
|
12189
|
+
promptTemplate: "{input}",
|
|
12190
|
+
env: mergedEnv
|
|
12191
|
+
};
|
|
12007
12192
|
}
|
|
12008
12193
|
async function executeRemoteAssignment({
|
|
12009
12194
|
frame,
|
|
@@ -12012,14 +12197,21 @@ async function executeRemoteAssignment({
|
|
|
12012
12197
|
log = () => {
|
|
12013
12198
|
},
|
|
12014
12199
|
runExecFn = runExec,
|
|
12200
|
+
decisionInbox,
|
|
12015
12201
|
bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
|
|
12016
12202
|
now = Date.now,
|
|
12017
12203
|
abortSignal,
|
|
12018
12204
|
createRunStreamClientFn = createRunStreamClient,
|
|
12205
|
+
resolveWorkflowFn = resolveWorkflow,
|
|
12206
|
+
resolveWorkflowInstallFn = resolveWorkflowInstall,
|
|
12207
|
+
installWorkflowFromSourceFn = installWorkflowFromSource,
|
|
12208
|
+
readGlobalConfigFn = readGlobalConfig,
|
|
12019
12209
|
runStreamConnectTimeoutMs = 5e3
|
|
12020
12210
|
}) {
|
|
12021
|
-
|
|
12022
|
-
|
|
12211
|
+
const lastTerminalFailureMessage = { current: null };
|
|
12212
|
+
const deferredFailedCompletion = {
|
|
12213
|
+
current: null
|
|
12214
|
+
};
|
|
12023
12215
|
const spec = parseRunSpec(frame.runSpec);
|
|
12024
12216
|
let runStream = null;
|
|
12025
12217
|
if (spec?.callbackWsUrl && spec.callbackToken) {
|
|
@@ -12031,7 +12223,7 @@ async function executeRemoteAssignment({
|
|
|
12031
12223
|
});
|
|
12032
12224
|
const timeoutPromise = new Promise((resolve) => {
|
|
12033
12225
|
const t = setTimeout(() => resolve("timeout"), runStreamConnectTimeoutMs);
|
|
12034
|
-
t.unref
|
|
12226
|
+
t.unref();
|
|
12035
12227
|
});
|
|
12036
12228
|
try {
|
|
12037
12229
|
const result = await Promise.race([
|
|
@@ -12057,7 +12249,7 @@ async function executeRemoteAssignment({
|
|
|
12057
12249
|
let legacySeq = 0;
|
|
12058
12250
|
const send = (kind, payload, ts = now()) => {
|
|
12059
12251
|
if (kind === "error" && typeof payload === "object" && payload !== null && typeof payload.message === "string") {
|
|
12060
|
-
lastTerminalFailureMessage = payload.message;
|
|
12252
|
+
lastTerminalFailureMessage.current = payload.message;
|
|
12061
12253
|
}
|
|
12062
12254
|
if (runStream) {
|
|
12063
12255
|
runStream.sendEvent({ ts, kind, payload });
|
|
@@ -12081,11 +12273,19 @@ async function executeRemoteAssignment({
|
|
|
12081
12273
|
const projectDir = spec.projectDir ?? fallbackProjectDir;
|
|
12082
12274
|
let runtimeConfig;
|
|
12083
12275
|
try {
|
|
12276
|
+
const workflowOverride = ensureRemoteWorkflowInstalled({
|
|
12277
|
+
spec,
|
|
12278
|
+
resolveWorkflowFn,
|
|
12279
|
+
resolveWorkflowInstallFn,
|
|
12280
|
+
installWorkflowFromSourceFn,
|
|
12281
|
+
readGlobalConfigFn
|
|
12282
|
+
});
|
|
12084
12283
|
runtimeConfig = bootstrapRuntimeConfigFn({
|
|
12085
12284
|
projectDir,
|
|
12086
12285
|
showSetup: false,
|
|
12087
12286
|
isolationPreset: "minimal",
|
|
12088
|
-
|
|
12287
|
+
harnessOverride: spec.harness,
|
|
12288
|
+
workflowOverride
|
|
12089
12289
|
});
|
|
12090
12290
|
} catch (err) {
|
|
12091
12291
|
send("error", {
|
|
@@ -12109,7 +12309,7 @@ async function executeRemoteAssignment({
|
|
|
12109
12309
|
const event = JSON.parse(line);
|
|
12110
12310
|
const data = event.data;
|
|
12111
12311
|
if (event.type === "exec.completed" && data?.success === false) {
|
|
12112
|
-
deferredFailedCompletion = event;
|
|
12312
|
+
deferredFailedCompletion.current = event;
|
|
12113
12313
|
continue;
|
|
12114
12314
|
}
|
|
12115
12315
|
send(eventKind(event), eventPayload(event), now());
|
|
@@ -12134,45 +12334,37 @@ async function executeRemoteAssignment({
|
|
|
12134
12334
|
}
|
|
12135
12335
|
};
|
|
12136
12336
|
try {
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
message: result.failure?.message ?? eventPayload(deferredFailedCompletion).message ?? "remote execution failed"
|
|
12169
|
-
},
|
|
12170
|
-
typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now()
|
|
12171
|
-
);
|
|
12172
|
-
return;
|
|
12173
|
-
}
|
|
12174
|
-
if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
|
|
12175
|
-
send("error", {
|
|
12337
|
+
const workflow = mergeRunSpecEnvIntoWorkflow(
|
|
12338
|
+
runtimeConfig.workflow,
|
|
12339
|
+
spec.env
|
|
12340
|
+
);
|
|
12341
|
+
const result = await runExecFn({
|
|
12342
|
+
prompt: spec.prompt,
|
|
12343
|
+
projectDir,
|
|
12344
|
+
harness: runtimeConfig.harness,
|
|
12345
|
+
athenaSessionId: spec.athenaSessionId ?? spec.sessionId ?? `athena-${frame.runId}`,
|
|
12346
|
+
adapterResumeSessionId: spec.adapterResumeSessionId,
|
|
12347
|
+
isolationConfig: runtimeConfig.isolationConfig,
|
|
12348
|
+
pluginMcpConfig: runtimeConfig.pluginMcpConfig,
|
|
12349
|
+
workflow,
|
|
12350
|
+
workflowPlan: runtimeConfig.workflowPlan,
|
|
12351
|
+
dashboardOrigin: "dashboard",
|
|
12352
|
+
json: true,
|
|
12353
|
+
verbose: false,
|
|
12354
|
+
ephemeral: false,
|
|
12355
|
+
timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
|
|
12356
|
+
signal: abortSignal,
|
|
12357
|
+
stdout,
|
|
12358
|
+
stderr,
|
|
12359
|
+
...decisionInbox ? { dashboardDecisionInbox: decisionInbox } : {}
|
|
12360
|
+
});
|
|
12361
|
+
const failedCompletion = deferredFailedCompletion.current;
|
|
12362
|
+
if (failedCompletion) {
|
|
12363
|
+
const data = typeof failedCompletion.data === "object" && failedCompletion.data !== null ? failedCompletion.data : {};
|
|
12364
|
+
send(
|
|
12365
|
+
"error",
|
|
12366
|
+
{
|
|
12367
|
+
...data,
|
|
12176
12368
|
success: result.success,
|
|
12177
12369
|
exitCode: result.exitCode,
|
|
12178
12370
|
athenaSessionId: result.athenaSessionId,
|
|
@@ -12180,10 +12372,24 @@ async function executeRemoteAssignment({
|
|
|
12180
12372
|
finalMessage: result.finalMessage,
|
|
12181
12373
|
tokens: result.tokens,
|
|
12182
12374
|
durationMs: result.durationMs,
|
|
12183
|
-
message: result.failure.message
|
|
12184
|
-
}
|
|
12185
|
-
|
|
12186
|
-
|
|
12375
|
+
message: result.failure?.message ?? eventPayload(failedCompletion).message ?? "remote execution failed"
|
|
12376
|
+
},
|
|
12377
|
+
typeof failedCompletion.ts === "number" ? failedCompletion.ts : now()
|
|
12378
|
+
);
|
|
12379
|
+
return;
|
|
12380
|
+
}
|
|
12381
|
+
if (result.failure && result.failure.message !== lastTerminalFailureMessage.current) {
|
|
12382
|
+
send("error", {
|
|
12383
|
+
success: result.success,
|
|
12384
|
+
exitCode: result.exitCode,
|
|
12385
|
+
athenaSessionId: result.athenaSessionId,
|
|
12386
|
+
adapterSessionId: result.adapterSessionId,
|
|
12387
|
+
finalMessage: result.finalMessage,
|
|
12388
|
+
tokens: result.tokens,
|
|
12389
|
+
durationMs: result.durationMs,
|
|
12390
|
+
message: result.failure.message
|
|
12391
|
+
});
|
|
12392
|
+
}
|
|
12187
12393
|
} catch (err) {
|
|
12188
12394
|
send("error", {
|
|
12189
12395
|
message: err instanceof Error ? err.message : String(err)
|
|
@@ -12193,7 +12399,7 @@ async function executeRemoteAssignment({
|
|
|
12193
12399
|
if (runStream) {
|
|
12194
12400
|
const drainTimeout = new Promise((resolve) => {
|
|
12195
12401
|
const t = setTimeout(() => resolve(), 1e4);
|
|
12196
|
-
t.unref
|
|
12402
|
+
t.unref();
|
|
12197
12403
|
});
|
|
12198
12404
|
await Promise.race([runStream.whenTerminated(), drainTimeout]);
|
|
12199
12405
|
await runStream.close("done");
|
|
@@ -12201,131 +12407,304 @@ async function executeRemoteAssignment({
|
|
|
12201
12407
|
}
|
|
12202
12408
|
}
|
|
12203
12409
|
|
|
12204
|
-
// src/
|
|
12205
|
-
import
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
import path18 from "path";
|
|
12209
|
-
function attachmentMirrorPath(env = process.env) {
|
|
12210
|
-
const home = env["HOME"] ?? os11.homedir();
|
|
12211
|
-
return path18.join(home, ".config", "athena", "attachments.json");
|
|
12410
|
+
// src/app/dashboard/dashboardDecisionInbox.ts
|
|
12411
|
+
import Database4 from "better-sqlite3";
|
|
12412
|
+
function dashboardDecisionInboxPath() {
|
|
12413
|
+
return `${ensureDaemonStateDir().dir}/dashboard-decision-inbox.db`;
|
|
12212
12414
|
}
|
|
12213
|
-
function
|
|
12214
|
-
const
|
|
12215
|
-
|
|
12216
|
-
try {
|
|
12217
|
-
raw = fs20.readFileSync(file, "utf-8");
|
|
12218
|
-
} catch (err) {
|
|
12219
|
-
if (err.code === "ENOENT") return null;
|
|
12220
|
-
throw err;
|
|
12221
|
-
}
|
|
12222
|
-
let parsed;
|
|
12223
|
-
try {
|
|
12224
|
-
parsed = JSON.parse(raw);
|
|
12225
|
-
} catch (err) {
|
|
12226
|
-
throw new Error(
|
|
12227
|
-
`attachment mirror ${file} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
12228
|
-
);
|
|
12229
|
-
}
|
|
12230
|
-
try {
|
|
12231
|
-
return parseAttachmentMirror(parsed);
|
|
12232
|
-
} catch (err) {
|
|
12233
|
-
throw new Error(
|
|
12234
|
-
`attachment mirror ${file} is invalid: ${err instanceof Error ? err.message : String(err)}`
|
|
12235
|
-
);
|
|
12236
|
-
}
|
|
12415
|
+
function hasLegacyUniqueConstraint(db) {
|
|
12416
|
+
const rows = db.prepare(`PRAGMA index_list('dashboard_decision_inbox')`).all();
|
|
12417
|
+
return rows.some((row) => row.unique === 1 && row.origin === "u");
|
|
12237
12418
|
}
|
|
12238
|
-
function
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12419
|
+
function migrateLegacyUniqueConstraint(db) {
|
|
12420
|
+
if (!hasLegacyUniqueConstraint(db)) return;
|
|
12421
|
+
db.exec(`
|
|
12422
|
+
ALTER TABLE dashboard_decision_inbox
|
|
12423
|
+
RENAME TO dashboard_decision_inbox_legacy;
|
|
12424
|
+
|
|
12425
|
+
CREATE TABLE dashboard_decision_inbox (
|
|
12426
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12427
|
+
athena_session_id TEXT NOT NULL,
|
|
12428
|
+
request_id TEXT NOT NULL,
|
|
12429
|
+
decision_json TEXT NOT NULL,
|
|
12430
|
+
received_at INTEGER NOT NULL,
|
|
12431
|
+
consumed_at INTEGER
|
|
12432
|
+
);
|
|
12433
|
+
|
|
12434
|
+
INSERT INTO dashboard_decision_inbox (
|
|
12435
|
+
id,
|
|
12436
|
+
athena_session_id,
|
|
12437
|
+
request_id,
|
|
12438
|
+
decision_json,
|
|
12439
|
+
received_at,
|
|
12440
|
+
consumed_at
|
|
12441
|
+
)
|
|
12442
|
+
SELECT
|
|
12443
|
+
id,
|
|
12444
|
+
athena_session_id,
|
|
12445
|
+
request_id,
|
|
12446
|
+
decision_json,
|
|
12447
|
+
received_at,
|
|
12448
|
+
consumed_at
|
|
12449
|
+
FROM dashboard_decision_inbox_legacy;
|
|
12450
|
+
|
|
12451
|
+
DROP TABLE dashboard_decision_inbox_legacy;
|
|
12452
|
+
`);
|
|
12453
|
+
}
|
|
12454
|
+
function initInboxSchema(db) {
|
|
12455
|
+
db.exec(`
|
|
12456
|
+
PRAGMA journal_mode = WAL;
|
|
12457
|
+
|
|
12458
|
+
CREATE TABLE IF NOT EXISTS dashboard_decision_inbox (
|
|
12459
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12460
|
+
athena_session_id TEXT NOT NULL,
|
|
12461
|
+
request_id TEXT NOT NULL,
|
|
12462
|
+
decision_json TEXT NOT NULL,
|
|
12463
|
+
received_at INTEGER NOT NULL,
|
|
12464
|
+
consumed_at INTEGER
|
|
12465
|
+
);
|
|
12466
|
+
`);
|
|
12467
|
+
migrateLegacyUniqueConstraint(db);
|
|
12468
|
+
db.exec(`
|
|
12469
|
+
|
|
12470
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_dashboard_decision_unconsumed
|
|
12471
|
+
ON dashboard_decision_inbox(athena_session_id, request_id)
|
|
12472
|
+
WHERE consumed_at IS NULL;
|
|
12473
|
+
|
|
12474
|
+
CREATE INDEX IF NOT EXISTS idx_dashboard_decision_pending
|
|
12475
|
+
ON dashboard_decision_inbox(athena_session_id, consumed_at, id);
|
|
12476
|
+
`);
|
|
12477
|
+
}
|
|
12478
|
+
function createDashboardDecisionInbox(options = {}) {
|
|
12479
|
+
const db = new Database4(options.dbPath ?? dashboardDecisionInboxPath());
|
|
12480
|
+
initInboxSchema(db);
|
|
12481
|
+
const upsertUnconsumed = db.prepare(`
|
|
12482
|
+
INSERT INTO dashboard_decision_inbox (
|
|
12483
|
+
athena_session_id,
|
|
12484
|
+
request_id,
|
|
12485
|
+
decision_json,
|
|
12486
|
+
received_at
|
|
12487
|
+
)
|
|
12488
|
+
VALUES (?, ?, ?, ?)
|
|
12489
|
+
ON CONFLICT(athena_session_id, request_id) WHERE consumed_at IS NULL
|
|
12490
|
+
DO UPDATE SET
|
|
12491
|
+
decision_json = excluded.decision_json,
|
|
12492
|
+
received_at = excluded.received_at
|
|
12493
|
+
`);
|
|
12494
|
+
const selectPending = db.prepare(`
|
|
12495
|
+
SELECT id, athena_session_id, request_id, decision_json, received_at
|
|
12496
|
+
FROM dashboard_decision_inbox
|
|
12497
|
+
WHERE athena_session_id = ? AND consumed_at IS NULL
|
|
12498
|
+
ORDER BY id ASC
|
|
12499
|
+
LIMIT ?
|
|
12500
|
+
`);
|
|
12501
|
+
const consume = db.prepare(`
|
|
12502
|
+
UPDATE dashboard_decision_inbox
|
|
12503
|
+
SET consumed_at = ?
|
|
12504
|
+
WHERE id = ?
|
|
12505
|
+
`);
|
|
12506
|
+
return {
|
|
12507
|
+
enqueue(input) {
|
|
12508
|
+
upsertUnconsumed.run(
|
|
12509
|
+
input.athenaSessionId,
|
|
12510
|
+
input.requestId,
|
|
12511
|
+
JSON.stringify(input.decision),
|
|
12512
|
+
input.receivedAt
|
|
12513
|
+
);
|
|
12514
|
+
},
|
|
12515
|
+
pendingForSession(input) {
|
|
12516
|
+
const rows = selectPending.all(
|
|
12517
|
+
input.athenaSessionId,
|
|
12518
|
+
input.limit
|
|
12519
|
+
);
|
|
12520
|
+
return rows.map((row) => ({
|
|
12521
|
+
id: row.id,
|
|
12522
|
+
athenaSessionId: row.athena_session_id,
|
|
12523
|
+
requestId: row.request_id,
|
|
12524
|
+
decision: JSON.parse(row.decision_json),
|
|
12525
|
+
receivedAt: row.received_at
|
|
12526
|
+
}));
|
|
12527
|
+
},
|
|
12528
|
+
markConsumed(input) {
|
|
12529
|
+
consume.run(Date.now(), input.id);
|
|
12530
|
+
},
|
|
12531
|
+
close() {
|
|
12532
|
+
db.close();
|
|
12533
|
+
}
|
|
12534
|
+
};
|
|
12535
|
+
}
|
|
12536
|
+
|
|
12537
|
+
// src/app/dashboard/dashboardPairedExecution.ts
|
|
12538
|
+
var DEFAULT_MAX_CONCURRENT_RUNS = 1;
|
|
12539
|
+
var DEFAULT_RUN_HISTORY_LIMIT = 100;
|
|
12540
|
+
function createDashboardPairedExecution(options) {
|
|
12541
|
+
const client = options.client;
|
|
12542
|
+
const executor = options.executor;
|
|
12543
|
+
const projectDir = options.projectDir;
|
|
12544
|
+
const decisionInbox = options.decisionInbox;
|
|
12545
|
+
const log = options.log ?? (() => {
|
|
12546
|
+
});
|
|
12547
|
+
const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
|
|
12548
|
+
const runHistoryLimit = options.runHistoryLimit ?? DEFAULT_RUN_HISTORY_LIMIT;
|
|
12549
|
+
const now = options.now ?? (() => Date.now());
|
|
12550
|
+
let completedRuns = 0;
|
|
12551
|
+
const active = /* @__PURE__ */ new Map();
|
|
12552
|
+
const activeByRunner = /* @__PURE__ */ new Map();
|
|
12553
|
+
const runHistory = [];
|
|
12554
|
+
function recordRun(record) {
|
|
12555
|
+
runHistory.push(record);
|
|
12556
|
+
while (runHistory.length > runHistoryLimit) {
|
|
12557
|
+
runHistory.shift();
|
|
12257
12558
|
}
|
|
12258
|
-
throw err;
|
|
12259
12559
|
}
|
|
12260
|
-
|
|
12560
|
+
function rejectAssignment(runId, reason) {
|
|
12261
12561
|
try {
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12562
|
+
client.sendRunEvent({
|
|
12563
|
+
runId,
|
|
12564
|
+
seq: 0,
|
|
12565
|
+
ts: now(),
|
|
12566
|
+
kind: "rejected",
|
|
12567
|
+
payload: { reason }
|
|
12568
|
+
});
|
|
12569
|
+
} catch (err) {
|
|
12570
|
+
log(
|
|
12571
|
+
"warn",
|
|
12572
|
+
`runtime daemon: failed to send rejected for ${runId}: ${err instanceof Error ? err.message : String(err)}`
|
|
12573
|
+
);
|
|
12265
12574
|
}
|
|
12575
|
+
recordRun({
|
|
12576
|
+
runId,
|
|
12577
|
+
startedAt: now(),
|
|
12578
|
+
endedAt: now(),
|
|
12579
|
+
status: "rejected",
|
|
12580
|
+
error: reason
|
|
12581
|
+
});
|
|
12582
|
+
log("warn", `run ${runId} rejected: ${reason}`);
|
|
12266
12583
|
}
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
}
|
|
12275
|
-
}
|
|
12276
|
-
function parseAttachmentMirror(raw) {
|
|
12277
|
-
if (typeof raw !== "object" || raw === null) {
|
|
12278
|
-
throw new Error("root must be an object");
|
|
12279
|
-
}
|
|
12280
|
-
const obj = raw;
|
|
12281
|
-
if (typeof obj["instanceId"] !== "string" || obj["instanceId"].length === 0) {
|
|
12282
|
-
throw new Error("instanceId must be a non-empty string");
|
|
12283
|
-
}
|
|
12284
|
-
if (typeof obj["fetchedAt"] !== "number") {
|
|
12285
|
-
throw new Error("fetchedAt must be a number");
|
|
12286
|
-
}
|
|
12287
|
-
if (!Array.isArray(obj["attachments"])) {
|
|
12288
|
-
throw new Error("attachments must be an array");
|
|
12584
|
+
function handleDecision(frame) {
|
|
12585
|
+
decisionInbox.enqueue({
|
|
12586
|
+
athenaSessionId: frame.athenaSessionId,
|
|
12587
|
+
requestId: frame.requestId,
|
|
12588
|
+
decision: frame.decision,
|
|
12589
|
+
receivedAt: now()
|
|
12590
|
+
});
|
|
12289
12591
|
}
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12592
|
+
function handleCancel(frame) {
|
|
12593
|
+
const entry = active.get(frame.runId);
|
|
12594
|
+
if (!entry) return;
|
|
12595
|
+
entry.record.status = "cancelled";
|
|
12596
|
+
entry.controller.abort();
|
|
12597
|
+
}
|
|
12598
|
+
function handleAssignment(frame) {
|
|
12599
|
+
if (active.has(frame.runId)) {
|
|
12600
|
+
rejectAssignment(
|
|
12601
|
+
frame.runId,
|
|
12602
|
+
`duplicate active assignment ${frame.runId}`
|
|
12298
12603
|
);
|
|
12604
|
+
return;
|
|
12299
12605
|
}
|
|
12300
|
-
const
|
|
12301
|
-
|
|
12302
|
-
if (
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12606
|
+
const runnerKey = frame.runnerId;
|
|
12607
|
+
const bucket = activeByRunner.get(runnerKey) ?? /* @__PURE__ */ new Set();
|
|
12608
|
+
if (bucket.size >= maxConcurrentRuns) {
|
|
12609
|
+
rejectAssignment(
|
|
12610
|
+
frame.runId,
|
|
12611
|
+
`runtime daemon at concurrency cap (${maxConcurrentRuns}) for runner ${runnerKey ?? "<legacy>"}`
|
|
12612
|
+
);
|
|
12613
|
+
return;
|
|
12307
12614
|
}
|
|
12308
|
-
|
|
12309
|
-
|
|
12615
|
+
const controller = new AbortController();
|
|
12616
|
+
const record = {
|
|
12617
|
+
runId: frame.runId,
|
|
12618
|
+
startedAt: now(),
|
|
12619
|
+
status: "running"
|
|
12620
|
+
};
|
|
12621
|
+
recordRun(record);
|
|
12622
|
+
bucket.add(frame.runId);
|
|
12623
|
+
activeByRunner.set(runnerKey, bucket);
|
|
12624
|
+
const promise = executor({
|
|
12625
|
+
frame,
|
|
12626
|
+
client,
|
|
12627
|
+
projectDir,
|
|
12628
|
+
log,
|
|
12629
|
+
abortSignal: controller.signal,
|
|
12630
|
+
decisionInbox
|
|
12631
|
+
}).then(() => {
|
|
12632
|
+
if (record.status === "running") record.status = "completed";
|
|
12633
|
+
}).catch((err) => {
|
|
12634
|
+
if (record.status === "running") {
|
|
12635
|
+
record.status = "failed";
|
|
12636
|
+
}
|
|
12637
|
+
record.error = err instanceof Error ? err.message : String(err);
|
|
12638
|
+
log(
|
|
12639
|
+
"error",
|
|
12640
|
+
`run ${frame.runId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12641
|
+
);
|
|
12642
|
+
}).finally(() => {
|
|
12643
|
+
record.endedAt = now();
|
|
12644
|
+
completedRuns += 1;
|
|
12645
|
+
active.delete(frame.runId);
|
|
12646
|
+
const remaining = activeByRunner.get(runnerKey);
|
|
12647
|
+
if (remaining) {
|
|
12648
|
+
remaining.delete(frame.runId);
|
|
12649
|
+
if (remaining.size === 0) activeByRunner.delete(runnerKey);
|
|
12650
|
+
}
|
|
12651
|
+
});
|
|
12652
|
+
active.set(frame.runId, { controller, promise, record, runnerKey });
|
|
12653
|
+
}
|
|
12310
12654
|
return {
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12655
|
+
handleFrame(frame) {
|
|
12656
|
+
if (frame.type === "dashboard_decision") {
|
|
12657
|
+
handleDecision(frame);
|
|
12658
|
+
return true;
|
|
12659
|
+
}
|
|
12660
|
+
if (frame.type === "cancel") {
|
|
12661
|
+
handleCancel(frame);
|
|
12662
|
+
return true;
|
|
12663
|
+
}
|
|
12664
|
+
if (frame.type === "job_assignment") {
|
|
12665
|
+
handleAssignment(frame);
|
|
12666
|
+
return true;
|
|
12667
|
+
}
|
|
12668
|
+
return false;
|
|
12669
|
+
},
|
|
12670
|
+
snapshot() {
|
|
12671
|
+
return {
|
|
12672
|
+
activeRuns: active.size,
|
|
12673
|
+
completedRuns
|
|
12674
|
+
};
|
|
12675
|
+
},
|
|
12676
|
+
listRuns(opts = {}) {
|
|
12677
|
+
let out = runHistory.slice();
|
|
12678
|
+
if (typeof opts.limit === "number" && opts.limit > 0) {
|
|
12679
|
+
out = out.slice(-opts.limit);
|
|
12680
|
+
}
|
|
12681
|
+
if (opts.active) {
|
|
12682
|
+
out = out.filter((r) => r.status === "running");
|
|
12683
|
+
}
|
|
12684
|
+
return out;
|
|
12685
|
+
},
|
|
12686
|
+
async stop() {
|
|
12687
|
+
for (const run of active.values()) {
|
|
12688
|
+
run.controller.abort();
|
|
12689
|
+
}
|
|
12690
|
+
await Promise.allSettled([...active.values()].map((run) => run.promise));
|
|
12691
|
+
}
|
|
12314
12692
|
};
|
|
12315
12693
|
}
|
|
12316
12694
|
|
|
12317
12695
|
// src/app/dashboard/runtimeDaemon.ts
|
|
12318
12696
|
var DEFAULT_RECONNECT_DELAYS_MS2 = [1e3, 2e3, 5e3, 1e4, 3e4];
|
|
12319
|
-
var
|
|
12697
|
+
var DEFAULT_MAX_CONCURRENT_RUNS2 = 1;
|
|
12320
12698
|
var DEFAULT_REFRESH_LEAD_SEC = 60;
|
|
12321
12699
|
var DEFAULT_REFRESH_FAILURE_LIMIT = 5;
|
|
12322
12700
|
var DEFAULT_REFRESH_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
12323
12701
|
var DEFAULT_REFRESH_COOLDOWN_MS = 5 * 6e4;
|
|
12324
|
-
var
|
|
12702
|
+
var DEFAULT_RUN_HISTORY_LIMIT2 = 100;
|
|
12703
|
+
var DEFAULT_FEED_DRAIN_INTERVAL_MS = 1e3;
|
|
12325
12704
|
function delay(ms) {
|
|
12326
12705
|
return new Promise((resolve) => {
|
|
12327
12706
|
const timer = setTimeout(resolve, ms);
|
|
12328
|
-
timer.unref
|
|
12707
|
+
timer.unref();
|
|
12329
12708
|
});
|
|
12330
12709
|
}
|
|
12331
12710
|
async function runDashboardRuntimeDaemon(options = {}) {
|
|
@@ -12342,33 +12721,52 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12342
12721
|
const log = options.log ?? (() => {
|
|
12343
12722
|
});
|
|
12344
12723
|
const reconnectDelays = options.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS2;
|
|
12345
|
-
const maxConcurrentRuns = options.maxConcurrentRuns ??
|
|
12724
|
+
const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS2;
|
|
12346
12725
|
const refreshLeadSec = options.refreshLeadSec ?? DEFAULT_REFRESH_LEAD_SEC;
|
|
12347
12726
|
const refreshFailureLimit = options.refreshFailureLimit ?? DEFAULT_REFRESH_FAILURE_LIMIT;
|
|
12348
12727
|
const refreshFailureWindowMs = options.refreshFailureWindowMs ?? DEFAULT_REFRESH_FAILURE_WINDOW_MS;
|
|
12349
12728
|
const refreshCooldownMs = options.refreshCooldownMs ?? DEFAULT_REFRESH_COOLDOWN_MS;
|
|
12350
|
-
const runHistoryLimit = options.runHistoryLimit ??
|
|
12729
|
+
const runHistoryLimit = options.runHistoryLimit ?? DEFAULT_RUN_HISTORY_LIMIT2;
|
|
12351
12730
|
const now = options.now ?? (() => Date.now());
|
|
12352
12731
|
const writeMirror = options.writeMirror ?? writeAttachmentMirror;
|
|
12732
|
+
const feedOutbox = options.feedOutbox ?? createDashboardFeedOutbox();
|
|
12733
|
+
const decisionInbox = options.decisionInbox ?? createDashboardDecisionInbox();
|
|
12734
|
+
const feedDrainIntervalMs = options.feedDrainIntervalMs ?? DEFAULT_FEED_DRAIN_INTERVAL_MS;
|
|
12353
12735
|
const startedAt = now();
|
|
12354
12736
|
let stopped = false;
|
|
12355
12737
|
let reconnectAttempt = 0;
|
|
12356
12738
|
let client = null;
|
|
12739
|
+
let lastSocketClient = null;
|
|
12357
12740
|
let currentInstanceId;
|
|
12358
12741
|
let currentDashboardUrl;
|
|
12359
12742
|
let lastFrameAt;
|
|
12360
|
-
let completedRuns = 0;
|
|
12361
12743
|
let refreshTimer = null;
|
|
12744
|
+
let feedDrainTimer = null;
|
|
12362
12745
|
const refreshFailures = [];
|
|
12363
12746
|
let cooldownUntil = 0;
|
|
12364
|
-
const
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12747
|
+
const executionClient = {
|
|
12748
|
+
sendRunEvent(event) {
|
|
12749
|
+
const current = client ?? lastSocketClient;
|
|
12750
|
+
if (!current) {
|
|
12751
|
+
log(
|
|
12752
|
+
"warn",
|
|
12753
|
+
`instance socket dropped run_event (socket not connected): runId=${event.runId} kind=${event.kind}`
|
|
12754
|
+
);
|
|
12755
|
+
return;
|
|
12756
|
+
}
|
|
12757
|
+
current.sendRunEvent(event);
|
|
12370
12758
|
}
|
|
12371
|
-
}
|
|
12759
|
+
};
|
|
12760
|
+
const pairedExecution = createDashboardPairedExecution({
|
|
12761
|
+
client: executionClient,
|
|
12762
|
+
executor,
|
|
12763
|
+
projectDir,
|
|
12764
|
+
decisionInbox,
|
|
12765
|
+
log,
|
|
12766
|
+
maxConcurrentRuns,
|
|
12767
|
+
now,
|
|
12768
|
+
runHistoryLimit
|
|
12769
|
+
});
|
|
12372
12770
|
function nextReconnectDelay() {
|
|
12373
12771
|
if (reconnectDelays.length === 0) return 0;
|
|
12374
12772
|
const delayMs = reconnectDelays[Math.min(reconnectAttempt, reconnectDelays.length - 1)] ?? 0;
|
|
@@ -12381,6 +12779,35 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12381
12779
|
refreshTimer = null;
|
|
12382
12780
|
}
|
|
12383
12781
|
}
|
|
12782
|
+
function clearFeedDrainTimer() {
|
|
12783
|
+
if (feedDrainTimer) {
|
|
12784
|
+
clearInterval(feedDrainTimer);
|
|
12785
|
+
feedDrainTimer = null;
|
|
12786
|
+
}
|
|
12787
|
+
}
|
|
12788
|
+
function drainFeedOutbox() {
|
|
12789
|
+
const current = client;
|
|
12790
|
+
if (!current) return;
|
|
12791
|
+
const rows = feedOutbox.pendingBatch({ limit: 100, now: now() });
|
|
12792
|
+
for (const row of rows) {
|
|
12793
|
+
current.sendFeedEvent({
|
|
12794
|
+
deliverySeq: row.deliverySeq,
|
|
12795
|
+
envelope: row.envelope
|
|
12796
|
+
});
|
|
12797
|
+
const retryDelayMs = Math.min(3e4, (row.attempt + 1) * 1e3);
|
|
12798
|
+
feedOutbox.markAttempted({
|
|
12799
|
+
deliverySeq: row.deliverySeq,
|
|
12800
|
+
nextAttemptAt: now() + retryDelayMs
|
|
12801
|
+
});
|
|
12802
|
+
}
|
|
12803
|
+
}
|
|
12804
|
+
function startFeedDrainTimer() {
|
|
12805
|
+
clearFeedDrainTimer();
|
|
12806
|
+
const timer = setInterval(drainFeedOutbox, feedDrainIntervalMs);
|
|
12807
|
+
timer.unref();
|
|
12808
|
+
feedDrainTimer = timer;
|
|
12809
|
+
drainFeedOutbox();
|
|
12810
|
+
}
|
|
12384
12811
|
function scheduleRefresh(expiresInSec) {
|
|
12385
12812
|
clearRefreshTimer();
|
|
12386
12813
|
if (!Number.isFinite(expiresInSec) || expiresInSec <= refreshLeadSec) {
|
|
@@ -12390,7 +12817,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12390
12817
|
const timer = setTimeout(() => {
|
|
12391
12818
|
void proactiveRefresh();
|
|
12392
12819
|
}, ms);
|
|
12393
|
-
timer.unref
|
|
12820
|
+
timer.unref();
|
|
12394
12821
|
refreshTimer = timer;
|
|
12395
12822
|
}
|
|
12396
12823
|
async function proactiveRefresh() {
|
|
@@ -12425,30 +12852,6 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12425
12852
|
);
|
|
12426
12853
|
}
|
|
12427
12854
|
}
|
|
12428
|
-
function rejectAssignment(client_, runId, reason) {
|
|
12429
|
-
try {
|
|
12430
|
-
client_.sendRunEvent({
|
|
12431
|
-
runId,
|
|
12432
|
-
seq: 0,
|
|
12433
|
-
ts: now(),
|
|
12434
|
-
kind: "rejected",
|
|
12435
|
-
payload: { reason }
|
|
12436
|
-
});
|
|
12437
|
-
} catch (err) {
|
|
12438
|
-
log(
|
|
12439
|
-
"warn",
|
|
12440
|
-
`runtime daemon: failed to send rejected for ${runId}: ${err instanceof Error ? err.message : String(err)}`
|
|
12441
|
-
);
|
|
12442
|
-
}
|
|
12443
|
-
recordRun({
|
|
12444
|
-
runId,
|
|
12445
|
-
startedAt: now(),
|
|
12446
|
-
endedAt: now(),
|
|
12447
|
-
status: "rejected",
|
|
12448
|
-
error: reason
|
|
12449
|
-
});
|
|
12450
|
-
log("warn", `run ${runId} rejected: ${reason}`);
|
|
12451
|
-
}
|
|
12452
12855
|
async function connectOnce() {
|
|
12453
12856
|
const config = readConfig2();
|
|
12454
12857
|
if (!config) {
|
|
@@ -12503,54 +12906,14 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12503
12906
|
}
|
|
12504
12907
|
return;
|
|
12505
12908
|
}
|
|
12506
|
-
if (frame.type === "
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
}
|
|
12512
|
-
return;
|
|
12513
|
-
}
|
|
12514
|
-
if (frame.type !== "job_assignment") return;
|
|
12515
|
-
if (active.has(frame.runId)) return;
|
|
12516
|
-
if (active.size >= maxConcurrentRuns) {
|
|
12517
|
-
rejectAssignment(
|
|
12518
|
-
next,
|
|
12519
|
-
frame.runId,
|
|
12520
|
-
`runtime daemon at concurrency cap (${maxConcurrentRuns})`
|
|
12521
|
-
);
|
|
12909
|
+
if (frame.type === "feed_ack") {
|
|
12910
|
+
feedOutbox.markAcked({
|
|
12911
|
+
...typeof frame.deliverySeq === "number" ? { deliverySeq: frame.deliverySeq } : {},
|
|
12912
|
+
...typeof frame.eventId === "string" ? { eventId: frame.eventId } : {}
|
|
12913
|
+
});
|
|
12522
12914
|
return;
|
|
12523
12915
|
}
|
|
12524
|
-
|
|
12525
|
-
const record = {
|
|
12526
|
-
runId: frame.runId,
|
|
12527
|
-
startedAt: now(),
|
|
12528
|
-
status: "running"
|
|
12529
|
-
};
|
|
12530
|
-
recordRun(record);
|
|
12531
|
-
const promise = executor({
|
|
12532
|
-
frame,
|
|
12533
|
-
client: next,
|
|
12534
|
-
projectDir,
|
|
12535
|
-
log,
|
|
12536
|
-
abortSignal: controller.signal
|
|
12537
|
-
}).then(() => {
|
|
12538
|
-
if (record.status === "running") record.status = "completed";
|
|
12539
|
-
}).catch((err) => {
|
|
12540
|
-
if (record.status === "running") {
|
|
12541
|
-
record.status = "failed";
|
|
12542
|
-
}
|
|
12543
|
-
record.error = err instanceof Error ? err.message : String(err);
|
|
12544
|
-
log(
|
|
12545
|
-
"error",
|
|
12546
|
-
`run ${frame.runId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12547
|
-
);
|
|
12548
|
-
}).finally(() => {
|
|
12549
|
-
record.endedAt = now();
|
|
12550
|
-
completedRuns += 1;
|
|
12551
|
-
active.delete(frame.runId);
|
|
12552
|
-
});
|
|
12553
|
-
active.set(frame.runId, { controller, promise, record });
|
|
12916
|
+
pairedExecution.handleFrame(frame);
|
|
12554
12917
|
});
|
|
12555
12918
|
next.onClose((reason) => {
|
|
12556
12919
|
if (stopped || client !== next) return;
|
|
@@ -12558,14 +12921,17 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12558
12921
|
client = null;
|
|
12559
12922
|
currentInstanceId = void 0;
|
|
12560
12923
|
clearRefreshTimer();
|
|
12924
|
+
clearFeedDrainTimer();
|
|
12561
12925
|
void reconnectLoop();
|
|
12562
12926
|
});
|
|
12563
12927
|
await next.connect();
|
|
12564
12928
|
client = next;
|
|
12929
|
+
lastSocketClient = next;
|
|
12565
12930
|
currentInstanceId = token.instanceId;
|
|
12566
12931
|
currentDashboardUrl = config.dashboardUrl;
|
|
12567
12932
|
reconnectAttempt = 0;
|
|
12568
12933
|
scheduleRefresh(token.expiresInSec);
|
|
12934
|
+
startFeedDrainTimer();
|
|
12569
12935
|
log("info", `dashboard runtime daemon connected as ${token.instanceId}`);
|
|
12570
12936
|
}
|
|
12571
12937
|
async function reconnectLoop() {
|
|
@@ -12587,6 +12953,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12587
12953
|
await connectOnce();
|
|
12588
12954
|
return {
|
|
12589
12955
|
snapshot() {
|
|
12956
|
+
const executionSnapshot = pairedExecution.snapshot();
|
|
12590
12957
|
const refreshState = refreshFailures.length > 0 || cooldownUntil > now() ? {
|
|
12591
12958
|
recentFailures: refreshFailures.length,
|
|
12592
12959
|
...cooldownUntil > now() ? { cooldownUntilMs: cooldownUntil } : {}
|
|
@@ -12595,158 +12962,30 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
12595
12962
|
startedAt,
|
|
12596
12963
|
socketConnected: client !== null,
|
|
12597
12964
|
...lastFrameAt !== void 0 ? { lastFrameAt } : {},
|
|
12598
|
-
activeRuns:
|
|
12599
|
-
completedRuns,
|
|
12965
|
+
activeRuns: executionSnapshot.activeRuns,
|
|
12966
|
+
completedRuns: executionSnapshot.completedRuns,
|
|
12600
12967
|
...currentInstanceId ? { instanceId: currentInstanceId } : {},
|
|
12601
12968
|
...currentDashboardUrl ? { dashboardUrl: currentDashboardUrl } : {},
|
|
12602
12969
|
...refreshState ? { refreshState } : {}
|
|
12603
12970
|
};
|
|
12604
12971
|
},
|
|
12605
12972
|
listRuns(opts = {}) {
|
|
12606
|
-
|
|
12607
|
-
if (typeof opts.limit === "number" && opts.limit > 0) {
|
|
12608
|
-
out = out.slice(-opts.limit);
|
|
12609
|
-
}
|
|
12610
|
-
if (opts.active) {
|
|
12611
|
-
out = out.filter((r) => r.status === "running");
|
|
12612
|
-
}
|
|
12613
|
-
return out;
|
|
12973
|
+
return pairedExecution.listRuns(opts);
|
|
12614
12974
|
},
|
|
12615
12975
|
async stop(reason = "stopped") {
|
|
12616
12976
|
stopped = true;
|
|
12617
12977
|
clearRefreshTimer();
|
|
12618
|
-
|
|
12619
|
-
run.controller.abort();
|
|
12620
|
-
}
|
|
12978
|
+
clearFeedDrainTimer();
|
|
12621
12979
|
const current = client;
|
|
12622
12980
|
client = null;
|
|
12623
12981
|
current?.close(reason);
|
|
12624
|
-
await
|
|
12982
|
+
await pairedExecution.stop();
|
|
12625
12983
|
}
|
|
12626
12984
|
};
|
|
12627
12985
|
}
|
|
12628
12986
|
|
|
12629
|
-
// src/infra/daemon/stateDir.ts
|
|
12630
|
-
import fs21 from "fs";
|
|
12631
|
-
import os12 from "os";
|
|
12632
|
-
import path19 from "path";
|
|
12633
|
-
function daemonStatePaths(env = process.env) {
|
|
12634
|
-
const xdg = env["XDG_STATE_HOME"];
|
|
12635
|
-
const home = env["HOME"] ?? os12.homedir();
|
|
12636
|
-
const base = xdg && xdg.length > 0 ? xdg : path19.join(home, ".local", "state");
|
|
12637
|
-
const dir = path19.join(base, "drisp");
|
|
12638
|
-
return {
|
|
12639
|
-
dir,
|
|
12640
|
-
pidPath: path19.join(dir, "dashboard-daemon.pid"),
|
|
12641
|
-
logPath: path19.join(dir, "dashboard-daemon.log"),
|
|
12642
|
-
socketPath: path19.join(dir, "dashboard-daemon.sock")
|
|
12643
|
-
};
|
|
12644
|
-
}
|
|
12645
|
-
function ensureDaemonStateDir(env = process.env) {
|
|
12646
|
-
const paths = daemonStatePaths(env);
|
|
12647
|
-
fs21.mkdirSync(paths.dir, { recursive: true, mode: 448 });
|
|
12648
|
-
if (process.platform !== "win32") {
|
|
12649
|
-
try {
|
|
12650
|
-
fs21.chmodSync(paths.dir, 448);
|
|
12651
|
-
} catch {
|
|
12652
|
-
}
|
|
12653
|
-
}
|
|
12654
|
-
return paths;
|
|
12655
|
-
}
|
|
12656
|
-
|
|
12657
|
-
// src/infra/daemon/pidLock.ts
|
|
12658
|
-
import fs22 from "fs";
|
|
12659
|
-
function acquirePidLock(pidPath) {
|
|
12660
|
-
const ownPid = process.pid;
|
|
12661
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
12662
|
-
try {
|
|
12663
|
-
const fd = fs22.openSync(pidPath, "wx", 384);
|
|
12664
|
-
try {
|
|
12665
|
-
fs22.writeSync(fd, `${ownPid}
|
|
12666
|
-
`);
|
|
12667
|
-
fs22.fsyncSync(fd);
|
|
12668
|
-
} finally {
|
|
12669
|
-
fs22.closeSync(fd);
|
|
12670
|
-
}
|
|
12671
|
-
return makeHandle(pidPath, ownPid);
|
|
12672
|
-
} catch (err) {
|
|
12673
|
-
if (err.code !== "EEXIST") throw err;
|
|
12674
|
-
}
|
|
12675
|
-
const existing = readPidLock(pidPath);
|
|
12676
|
-
if (existing.state === "held") {
|
|
12677
|
-
throw new Error(
|
|
12678
|
-
`dashboard daemon is already running as pid ${existing.pid} (lock at ${pidPath}). Use "drisp dashboard daemon stop" to terminate it.`
|
|
12679
|
-
);
|
|
12680
|
-
}
|
|
12681
|
-
if (existing.state === "stale") {
|
|
12682
|
-
try {
|
|
12683
|
-
fs22.unlinkSync(pidPath);
|
|
12684
|
-
} catch (err) {
|
|
12685
|
-
if (err.code !== "ENOENT") throw err;
|
|
12686
|
-
}
|
|
12687
|
-
continue;
|
|
12688
|
-
}
|
|
12689
|
-
}
|
|
12690
|
-
throw new Error(
|
|
12691
|
-
`dashboard daemon: failed to acquire pid lock at ${pidPath} after retry`
|
|
12692
|
-
);
|
|
12693
|
-
}
|
|
12694
|
-
function readPidLock(pidPath) {
|
|
12695
|
-
let raw;
|
|
12696
|
-
try {
|
|
12697
|
-
raw = fs22.readFileSync(pidPath, "utf-8");
|
|
12698
|
-
} catch (err) {
|
|
12699
|
-
if (err.code === "ENOENT") {
|
|
12700
|
-
return { state: "absent" };
|
|
12701
|
-
}
|
|
12702
|
-
throw err;
|
|
12703
|
-
}
|
|
12704
|
-
const pid = Number.parseInt(raw.trim(), 10);
|
|
12705
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
12706
|
-
return { state: "stale", pid: 0 };
|
|
12707
|
-
}
|
|
12708
|
-
if (!isProcessAlive(pid)) {
|
|
12709
|
-
return { state: "stale", pid };
|
|
12710
|
-
}
|
|
12711
|
-
return { state: "held", pid };
|
|
12712
|
-
}
|
|
12713
|
-
function makeHandle(pidPath, pid) {
|
|
12714
|
-
let released = false;
|
|
12715
|
-
return {
|
|
12716
|
-
pid,
|
|
12717
|
-
release() {
|
|
12718
|
-
if (released) return;
|
|
12719
|
-
released = true;
|
|
12720
|
-
try {
|
|
12721
|
-
const raw = fs22.readFileSync(pidPath, "utf-8").trim();
|
|
12722
|
-
if (raw === String(pid)) {
|
|
12723
|
-
fs22.unlinkSync(pidPath);
|
|
12724
|
-
}
|
|
12725
|
-
} catch (err) {
|
|
12726
|
-
if (err.code !== "ENOENT") {
|
|
12727
|
-
}
|
|
12728
|
-
}
|
|
12729
|
-
}
|
|
12730
|
-
};
|
|
12731
|
-
}
|
|
12732
|
-
function isProcessAlive(pid) {
|
|
12733
|
-
if (process.platform === "win32") {
|
|
12734
|
-
return true;
|
|
12735
|
-
}
|
|
12736
|
-
try {
|
|
12737
|
-
process.kill(pid, 0);
|
|
12738
|
-
return true;
|
|
12739
|
-
} catch (err) {
|
|
12740
|
-
const code = err.code;
|
|
12741
|
-
if (code === "EPERM") {
|
|
12742
|
-
return true;
|
|
12743
|
-
}
|
|
12744
|
-
return false;
|
|
12745
|
-
}
|
|
12746
|
-
}
|
|
12747
|
-
|
|
12748
12987
|
// src/infra/daemon/udsIpc.ts
|
|
12749
|
-
import
|
|
12988
|
+
import fs21 from "fs";
|
|
12750
12989
|
import net2 from "net";
|
|
12751
12990
|
|
|
12752
12991
|
// src/infra/daemon/udsFrameCodec.ts
|
|
@@ -12791,7 +13030,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
12791
13030
|
});
|
|
12792
13031
|
if (process.platform !== "win32") {
|
|
12793
13032
|
try {
|
|
12794
|
-
|
|
13033
|
+
fs21.chmodSync(socketPath, 384);
|
|
12795
13034
|
} catch {
|
|
12796
13035
|
}
|
|
12797
13036
|
}
|
|
@@ -12801,7 +13040,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
12801
13040
|
server.close(() => resolve());
|
|
12802
13041
|
});
|
|
12803
13042
|
try {
|
|
12804
|
-
|
|
13043
|
+
fs21.unlinkSync(socketPath);
|
|
12805
13044
|
} catch (err) {
|
|
12806
13045
|
if (err.code !== "ENOENT") {
|
|
12807
13046
|
}
|
|
@@ -12867,7 +13106,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
|
|
|
12867
13106
|
socket.destroy();
|
|
12868
13107
|
reject(new Error(`uds request timed out after ${timeoutMs}ms`));
|
|
12869
13108
|
}, timeoutMs);
|
|
12870
|
-
timer.unref
|
|
13109
|
+
timer.unref();
|
|
12871
13110
|
const finish = (action) => {
|
|
12872
13111
|
if (settled) return;
|
|
12873
13112
|
settled = true;
|
|
@@ -12919,7 +13158,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
|
|
|
12919
13158
|
async function unlinkStaleSocket(socketPath) {
|
|
12920
13159
|
let stat;
|
|
12921
13160
|
try {
|
|
12922
|
-
stat =
|
|
13161
|
+
stat = fs21.statSync(socketPath);
|
|
12923
13162
|
} catch (err) {
|
|
12924
13163
|
if (err.code === "ENOENT") return;
|
|
12925
13164
|
throw err;
|
|
@@ -12946,7 +13185,7 @@ async function unlinkStaleSocket(socketPath) {
|
|
|
12946
13185
|
`uds path ${socketPath} is in use by another process; aborting`
|
|
12947
13186
|
);
|
|
12948
13187
|
}
|
|
12949
|
-
|
|
13188
|
+
fs21.unlinkSync(socketPath);
|
|
12950
13189
|
}
|
|
12951
13190
|
|
|
12952
13191
|
export {
|
|
@@ -12964,6 +13203,10 @@ export {
|
|
|
12964
13203
|
ingestRuntimeEvent,
|
|
12965
13204
|
ingestRuntimeDecision,
|
|
12966
13205
|
generateId,
|
|
13206
|
+
daemonStatePaths,
|
|
13207
|
+
ensureDaemonStateDir,
|
|
13208
|
+
createDashboardFeedPublisher,
|
|
13209
|
+
createDashboardDecisionInbox,
|
|
12967
13210
|
createSessionStore,
|
|
12968
13211
|
sessionsDir,
|
|
12969
13212
|
listSessions,
|
|
@@ -13011,15 +13254,9 @@ export {
|
|
|
13011
13254
|
bootstrapRuntimeConfig,
|
|
13012
13255
|
EXEC_EXIT_CODE,
|
|
13013
13256
|
runExec,
|
|
13014
|
-
|
|
13015
|
-
writeAttachmentMirror,
|
|
13016
|
-
removeAttachmentMirror,
|
|
13257
|
+
normalizeHarnessOverride,
|
|
13017
13258
|
runDashboardRuntimeDaemon,
|
|
13018
|
-
daemonStatePaths,
|
|
13019
|
-
ensureDaemonStateDir,
|
|
13020
|
-
acquirePidLock,
|
|
13021
|
-
readPidLock,
|
|
13022
13259
|
startUdsServer,
|
|
13023
13260
|
sendUdsRequest
|
|
13024
13261
|
};
|
|
13025
|
-
//# sourceMappingURL=chunk-
|
|
13262
|
+
//# sourceMappingURL=chunk-K53YMYTG.js.map
|