@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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/infra/daemon/logFile.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
5
|
+
var DEFAULT_MAX_FILES = 5;
|
|
6
|
+
function openDaemonLog(logPath, options = {}) {
|
|
7
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
8
|
+
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
9
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
10
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true, mode: 448 });
|
|
11
|
+
let fd = fs.openSync(logPath, "a", 384);
|
|
12
|
+
if (process.platform !== "win32") {
|
|
13
|
+
try {
|
|
14
|
+
fs.chmodSync(logPath, 384);
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function rotate() {
|
|
19
|
+
try {
|
|
20
|
+
fs.closeSync(fd);
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
for (let i = maxFiles - 1; i >= 1; i -= 1) {
|
|
24
|
+
const src = `${logPath}.${i}`;
|
|
25
|
+
const dst = `${logPath}.${i + 1}`;
|
|
26
|
+
try {
|
|
27
|
+
fs.renameSync(src, dst);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err.code !== "ENOENT") {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
fs.renameSync(logPath, `${logPath}.1`);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err.code !== "ENOENT") {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
fd = fs.openSync(logPath, "a", 384);
|
|
40
|
+
if (process.platform !== "win32") {
|
|
41
|
+
try {
|
|
42
|
+
fs.chmodSync(logPath, 384);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function write(level, message) {
|
|
48
|
+
const line = `${now().toISOString()} ${level.toUpperCase()} ${redactSecrets(message)}
|
|
49
|
+
`;
|
|
50
|
+
const buf = Buffer.from(line, "utf-8");
|
|
51
|
+
let stat = null;
|
|
52
|
+
try {
|
|
53
|
+
stat = fs.fstatSync(fd);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
if (stat && stat.size + buf.length > maxBytes) {
|
|
57
|
+
rotate();
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
fs.writeSync(fd, buf);
|
|
61
|
+
} catch {
|
|
62
|
+
try {
|
|
63
|
+
fs.closeSync(fd);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
fd = fs.openSync(logPath, "a", 384);
|
|
68
|
+
fs.writeSync(fd, buf);
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function close() {
|
|
74
|
+
try {
|
|
75
|
+
fs.closeSync(fd);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { write, close, path: logPath };
|
|
80
|
+
}
|
|
81
|
+
var SECRET_PATTERNS = [
|
|
82
|
+
[/Bearer\s+[A-Za-z0-9._\-+/=]+/g, "Bearer ***"],
|
|
83
|
+
[
|
|
84
|
+
/(["']?(?:access|refresh)_?token["']?\s*[:=]\s*)["']?[A-Za-z0-9._\-+/=]+/gi,
|
|
85
|
+
'$1"***"'
|
|
86
|
+
],
|
|
87
|
+
[/(Sec-WebSocket-Protocol:\s*)\S+/gi, "$1***"],
|
|
88
|
+
[
|
|
89
|
+
/eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g,
|
|
90
|
+
"***.***.***"
|
|
91
|
+
]
|
|
92
|
+
];
|
|
93
|
+
function redactSecrets(message) {
|
|
94
|
+
let out = message;
|
|
95
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
96
|
+
out = out.replace(pattern, replacement);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
openDaemonLog
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=chunk-2OJ3GGIP.js.map
|
|
@@ -934,6 +934,31 @@ function hasProjectWorkflow(projectDir) {
|
|
|
934
934
|
return hasActiveWorkflow(projectConfigPath(projectDir));
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
+
// src/infra/plugins/mcpOptions.ts
|
|
938
|
+
import fs6 from "fs";
|
|
939
|
+
import path5 from "path";
|
|
940
|
+
function collectMcpServersWithOptions(pluginDirs) {
|
|
941
|
+
const result = [];
|
|
942
|
+
const seen = /* @__PURE__ */ new Set();
|
|
943
|
+
for (const dir of pluginDirs) {
|
|
944
|
+
const mcpPath = path5.join(dir, ".mcp.json");
|
|
945
|
+
if (!fs6.existsSync(mcpPath)) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const config = JSON.parse(fs6.readFileSync(mcpPath, "utf-8"));
|
|
949
|
+
for (const [serverName, serverConfig] of Object.entries(
|
|
950
|
+
config.mcpServers ?? {}
|
|
951
|
+
)) {
|
|
952
|
+
if (seen.has(serverName) || !Array.isArray(serverConfig.options) || serverConfig.options.length === 0) {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
seen.add(serverName);
|
|
956
|
+
result.push({ serverName, options: serverConfig.options });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return result;
|
|
960
|
+
}
|
|
961
|
+
|
|
937
962
|
// src/core/workflows/types.ts
|
|
938
963
|
function pluginSpecRef(spec) {
|
|
939
964
|
return typeof spec === "string" ? spec : spec.ref;
|
|
@@ -1027,17 +1052,17 @@ function refreshPinnedWorkflowPlugins(workflow) {
|
|
|
1027
1052
|
}
|
|
1028
1053
|
|
|
1029
1054
|
// src/core/workflows/registry.ts
|
|
1030
|
-
import
|
|
1055
|
+
import fs10 from "fs";
|
|
1031
1056
|
import os5 from "os";
|
|
1032
|
-
import
|
|
1057
|
+
import path8 from "path";
|
|
1033
1058
|
|
|
1034
1059
|
// src/core/workflows/builtins/index.ts
|
|
1035
|
-
import
|
|
1060
|
+
import fs8 from "fs";
|
|
1036
1061
|
import os4 from "os";
|
|
1037
|
-
import
|
|
1062
|
+
import path6 from "path";
|
|
1038
1063
|
|
|
1039
1064
|
// src/core/workflows/loopManager.ts
|
|
1040
|
-
import
|
|
1065
|
+
import fs7 from "fs";
|
|
1041
1066
|
|
|
1042
1067
|
// src/core/workflows/templateVars.ts
|
|
1043
1068
|
function substituteVariables(text, ctx) {
|
|
@@ -1068,7 +1093,7 @@ function createLoopManager(trackerPath, config) {
|
|
|
1068
1093
|
const blockedMarker = config.blockedMarker ?? DEFAULT_BLOCKED_MARKER;
|
|
1069
1094
|
function readTracker() {
|
|
1070
1095
|
try {
|
|
1071
|
-
return
|
|
1096
|
+
return fs7.readFileSync(trackerPath, "utf-8");
|
|
1072
1097
|
} catch {
|
|
1073
1098
|
return "";
|
|
1074
1099
|
}
|
|
@@ -1180,17 +1205,17 @@ If you are blocked and cannot make further progress:
|
|
|
1180
1205
|
3. Explain what needs to happen to unblock the task whenever possible
|
|
1181
1206
|
`;
|
|
1182
1207
|
function ensureSystemPromptFile() {
|
|
1183
|
-
const dir =
|
|
1208
|
+
const dir = path6.join(
|
|
1184
1209
|
os4.homedir(),
|
|
1185
1210
|
".config",
|
|
1186
1211
|
"athena",
|
|
1187
1212
|
"builtins",
|
|
1188
1213
|
"default"
|
|
1189
1214
|
);
|
|
1190
|
-
const filePath =
|
|
1191
|
-
if (!
|
|
1192
|
-
|
|
1193
|
-
|
|
1215
|
+
const filePath = path6.join(dir, "system_prompt.md");
|
|
1216
|
+
if (!fs8.existsSync(filePath) || fs8.readFileSync(filePath, "utf-8") !== SYSTEM_PROMPT) {
|
|
1217
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1218
|
+
fs8.writeFileSync(filePath, SYSTEM_PROMPT, "utf-8");
|
|
1194
1219
|
}
|
|
1195
1220
|
return filePath;
|
|
1196
1221
|
}
|
|
@@ -1217,19 +1242,19 @@ function listBuiltinWorkflows() {
|
|
|
1217
1242
|
}
|
|
1218
1243
|
|
|
1219
1244
|
// src/core/workflows/sourceMetadata.ts
|
|
1220
|
-
import
|
|
1221
|
-
import
|
|
1245
|
+
import fs9 from "fs";
|
|
1246
|
+
import path7 from "path";
|
|
1222
1247
|
function legacyLocalToNew(legacyPath, legacyRepoDir) {
|
|
1223
1248
|
const repoDir = legacyRepoDir ?? findMarketplaceRepoDir(legacyPath);
|
|
1224
1249
|
if (!repoDir) {
|
|
1225
1250
|
return { kind: "filesystem", path: legacyPath };
|
|
1226
1251
|
}
|
|
1227
1252
|
try {
|
|
1228
|
-
const canonicalRepoDir =
|
|
1253
|
+
const canonicalRepoDir = fs9.realpathSync(repoDir);
|
|
1229
1254
|
const listings = listMarketplaceWorkflowsFromRepo(canonicalRepoDir);
|
|
1230
|
-
const absolutePath =
|
|
1255
|
+
const absolutePath = fs9.realpathSync(legacyPath);
|
|
1231
1256
|
const match = listings.find(
|
|
1232
|
-
(l) =>
|
|
1257
|
+
(l) => fs9.realpathSync(l.workflowPath) === absolutePath
|
|
1233
1258
|
);
|
|
1234
1259
|
if (match) {
|
|
1235
1260
|
return {
|
|
@@ -1244,11 +1269,11 @@ function legacyLocalToNew(legacyPath, legacyRepoDir) {
|
|
|
1244
1269
|
return { kind: "filesystem", path: legacyPath };
|
|
1245
1270
|
}
|
|
1246
1271
|
function readWorkflowSourceMetadata(workflowDir) {
|
|
1247
|
-
const sourceFile =
|
|
1248
|
-
if (!
|
|
1272
|
+
const sourceFile = path7.join(workflowDir, "source.json");
|
|
1273
|
+
if (!fs9.existsSync(sourceFile)) return void 0;
|
|
1249
1274
|
let raw;
|
|
1250
1275
|
try {
|
|
1251
|
-
raw = JSON.parse(
|
|
1276
|
+
raw = JSON.parse(fs9.readFileSync(sourceFile, "utf-8"));
|
|
1252
1277
|
} catch {
|
|
1253
1278
|
throw new Error(`Invalid source.json: ${sourceFile} is not valid JSON`);
|
|
1254
1279
|
}
|
|
@@ -1290,9 +1315,9 @@ function readWorkflowSourceMetadata(workflowDir) {
|
|
|
1290
1315
|
throw new Error(`Invalid source.json: ${sourceFile} kind is not supported`);
|
|
1291
1316
|
}
|
|
1292
1317
|
function writeWorkflowSourceMetadata(workflowDir, metadata) {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1318
|
+
fs9.mkdirSync(workflowDir, { recursive: true });
|
|
1319
|
+
fs9.writeFileSync(
|
|
1320
|
+
path7.join(workflowDir, "source.json"),
|
|
1296
1321
|
JSON.stringify({ v: 2, ...metadata }),
|
|
1297
1322
|
"utf-8"
|
|
1298
1323
|
);
|
|
@@ -1300,19 +1325,19 @@ function writeWorkflowSourceMetadata(workflowDir, metadata) {
|
|
|
1300
1325
|
|
|
1301
1326
|
// src/core/workflows/registry.ts
|
|
1302
1327
|
function registryDir() {
|
|
1303
|
-
return
|
|
1328
|
+
return path8.join(os5.homedir(), ".config", "athena", "workflows");
|
|
1304
1329
|
}
|
|
1305
1330
|
function ensurePathWithinRoot(rootDir, targetPath, label) {
|
|
1306
|
-
const relative =
|
|
1307
|
-
if (relative === "" || !relative.startsWith("..") && !
|
|
1331
|
+
const relative = path8.relative(rootDir, targetPath);
|
|
1332
|
+
if (relative === "" || !relative.startsWith("..") && !path8.isAbsolute(relative)) {
|
|
1308
1333
|
return;
|
|
1309
1334
|
}
|
|
1310
1335
|
throw new Error(`${label} resolves outside the workflow root: ${targetPath}`);
|
|
1311
1336
|
}
|
|
1312
1337
|
function resolveWorkflow(name) {
|
|
1313
|
-
const workflowDir =
|
|
1314
|
-
const workflowPath =
|
|
1315
|
-
if (!
|
|
1338
|
+
const workflowDir = path8.join(registryDir(), name);
|
|
1339
|
+
const workflowPath = path8.join(workflowDir, "workflow.json");
|
|
1340
|
+
if (!fs10.existsSync(workflowPath)) {
|
|
1316
1341
|
const builtin = resolveBuiltinWorkflow(name);
|
|
1317
1342
|
if (builtin) {
|
|
1318
1343
|
return builtin;
|
|
@@ -1322,7 +1347,7 @@ function resolveWorkflow(name) {
|
|
|
1322
1347
|
);
|
|
1323
1348
|
}
|
|
1324
1349
|
const source = readWorkflowSourceMetadata(workflowDir);
|
|
1325
|
-
const raw = JSON.parse(
|
|
1350
|
+
const raw = JSON.parse(fs10.readFileSync(workflowPath, "utf-8"));
|
|
1326
1351
|
if (!Array.isArray(raw["plugins"])) {
|
|
1327
1352
|
throw new Error(
|
|
1328
1353
|
`Invalid workflow.json: "plugins" must be an array (got ${typeof raw["plugins"]})`
|
|
@@ -1362,15 +1387,15 @@ function resolveWorkflow(name) {
|
|
|
1362
1387
|
);
|
|
1363
1388
|
}
|
|
1364
1389
|
const workflowFile = raw["workflowFile"];
|
|
1365
|
-
if (typeof workflowFile === "string" && !
|
|
1366
|
-
const workflowFilePath =
|
|
1367
|
-
if (!
|
|
1390
|
+
if (typeof workflowFile === "string" && !path8.isAbsolute(workflowFile)) {
|
|
1391
|
+
const workflowFilePath = path8.resolve(workflowDir, workflowFile);
|
|
1392
|
+
if (!fs10.existsSync(workflowFilePath)) {
|
|
1368
1393
|
throw new Error(
|
|
1369
1394
|
`Invalid workflow.json: workflowFile "${workflowFile}" not found at ${workflowFilePath}`
|
|
1370
1395
|
);
|
|
1371
1396
|
}
|
|
1372
1397
|
raw["workflowFile"] = workflowFilePath;
|
|
1373
|
-
} else if (typeof workflowFile === "string" && !
|
|
1398
|
+
} else if (typeof workflowFile === "string" && !fs10.existsSync(workflowFile)) {
|
|
1374
1399
|
throw new Error(
|
|
1375
1400
|
`Invalid workflow.json: workflowFile "${workflowFile}" not found`
|
|
1376
1401
|
);
|
|
@@ -1381,29 +1406,29 @@ function resolveWorkflow(name) {
|
|
|
1381
1406
|
};
|
|
1382
1407
|
}
|
|
1383
1408
|
function readWorkflowSource(sourcePath) {
|
|
1384
|
-
const content =
|
|
1409
|
+
const content = fs10.readFileSync(sourcePath, "utf-8");
|
|
1385
1410
|
return { content, workflow: JSON.parse(content) };
|
|
1386
1411
|
}
|
|
1387
1412
|
function copyWorkflowFiles(sourcePath, destDir) {
|
|
1388
1413
|
const { content, workflow } = readWorkflowSource(sourcePath);
|
|
1389
|
-
const absoluteSourcePath =
|
|
1390
|
-
const sourceDir =
|
|
1391
|
-
const absoluteDestDir =
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1414
|
+
const absoluteSourcePath = path8.resolve(sourcePath);
|
|
1415
|
+
const sourceDir = path8.dirname(absoluteSourcePath);
|
|
1416
|
+
const absoluteDestDir = path8.resolve(destDir);
|
|
1417
|
+
fs10.mkdirSync(absoluteDestDir, { recursive: true });
|
|
1418
|
+
fs10.writeFileSync(
|
|
1419
|
+
path8.join(absoluteDestDir, "workflow.json"),
|
|
1395
1420
|
content,
|
|
1396
1421
|
"utf-8"
|
|
1397
1422
|
);
|
|
1398
1423
|
const copyRelativeAsset = (assetPath) => {
|
|
1399
|
-
if (!assetPath ||
|
|
1400
|
-
const sourceAssetPath =
|
|
1424
|
+
if (!assetPath || path8.isAbsolute(assetPath)) return;
|
|
1425
|
+
const sourceAssetPath = path8.resolve(sourceDir, assetPath);
|
|
1401
1426
|
ensurePathWithinRoot(sourceDir, sourceAssetPath, "Workflow asset");
|
|
1402
|
-
if (!
|
|
1403
|
-
const destAssetPath =
|
|
1427
|
+
if (!fs10.existsSync(sourceAssetPath)) return;
|
|
1428
|
+
const destAssetPath = path8.resolve(absoluteDestDir, assetPath);
|
|
1404
1429
|
ensurePathWithinRoot(absoluteDestDir, destAssetPath, "Workflow asset");
|
|
1405
|
-
|
|
1406
|
-
|
|
1430
|
+
fs10.mkdirSync(path8.dirname(destAssetPath), { recursive: true });
|
|
1431
|
+
fs10.copyFileSync(sourceAssetPath, destAssetPath);
|
|
1407
1432
|
};
|
|
1408
1433
|
const rawWorkflow = workflow;
|
|
1409
1434
|
const promptAsset = rawWorkflow["workflowFile"];
|
|
@@ -1440,7 +1465,7 @@ function installWorkflowFromSource(source, name) {
|
|
|
1440
1465
|
'Workflow has no "name" field. Provide --name to specify one.'
|
|
1441
1466
|
);
|
|
1442
1467
|
}
|
|
1443
|
-
const destDir =
|
|
1468
|
+
const destDir = path8.join(registryDir(), workflowName);
|
|
1444
1469
|
copyWorkflowFiles(source.workflowPath, destDir);
|
|
1445
1470
|
const metadata = toStoredMetadata(source);
|
|
1446
1471
|
writeWorkflowSourceMetadata(destDir, metadata);
|
|
@@ -1482,7 +1507,7 @@ function reResolveFromMetadata(metadata) {
|
|
|
1482
1507
|
return { kind: "filesystem", workflowPath: metadata.path };
|
|
1483
1508
|
}
|
|
1484
1509
|
function updateWorkflow(name) {
|
|
1485
|
-
const workflowDir =
|
|
1510
|
+
const workflowDir = path8.join(registryDir(), name);
|
|
1486
1511
|
const metadata = readWorkflowSourceMetadata(workflowDir);
|
|
1487
1512
|
if (!metadata) {
|
|
1488
1513
|
throw new Error(
|
|
@@ -1496,8 +1521,8 @@ function updateWorkflow(name) {
|
|
|
1496
1521
|
}
|
|
1497
1522
|
function listWorkflows() {
|
|
1498
1523
|
const dir = registryDir();
|
|
1499
|
-
const installed =
|
|
1500
|
-
(entry) => entry.isDirectory() &&
|
|
1524
|
+
const installed = fs10.existsSync(dir) ? fs10.readdirSync(dir, { withFileTypes: true }).filter(
|
|
1525
|
+
(entry) => entry.isDirectory() && fs10.existsSync(path8.join(dir, entry.name, "workflow.json"))
|
|
1501
1526
|
).map((entry) => entry.name) : [];
|
|
1502
1527
|
const builtins = listBuiltinWorkflows().filter(
|
|
1503
1528
|
(name) => !installed.includes(name)
|
|
@@ -1505,11 +1530,11 @@ function listWorkflows() {
|
|
|
1505
1530
|
return [...builtins, ...installed];
|
|
1506
1531
|
}
|
|
1507
1532
|
function removeWorkflow(name) {
|
|
1508
|
-
const dir =
|
|
1509
|
-
if (!
|
|
1533
|
+
const dir = path8.join(registryDir(), name);
|
|
1534
|
+
if (!fs10.existsSync(dir)) {
|
|
1510
1535
|
throw new Error(`Workflow "${name}" not found.`);
|
|
1511
1536
|
}
|
|
1512
|
-
|
|
1537
|
+
fs10.rmSync(dir, { recursive: true, force: true });
|
|
1513
1538
|
}
|
|
1514
1539
|
|
|
1515
1540
|
// src/core/workflows/applyWorkflow.ts
|
|
@@ -1543,8 +1568,8 @@ function compileWorkflowPlan(input) {
|
|
|
1543
1568
|
}
|
|
1544
1569
|
|
|
1545
1570
|
// src/core/workflows/sessionPlan.ts
|
|
1546
|
-
import
|
|
1547
|
-
import
|
|
1571
|
+
import fs11 from "fs";
|
|
1572
|
+
import path9 from "path";
|
|
1548
1573
|
|
|
1549
1574
|
// src/core/workflows/stateMachine.md
|
|
1550
1575
|
var stateMachine_default = "# Stateless Session Protocol\n\nYou run in a stateless loop. Each session is a fresh process with no memory of prior sessions. **The tracker file is your only continuity** \u2014 read it, work, write it. Assume interruption: the runner may kill a long session, your context may collapse under tool output, you may hit token limits mid-task. Anything not in the tracker is gone.\n\n## First action, every session\n\n1. Read the tracker at the configured path (default: `.athena/<session_id>/tracker.md`). The runner provides the session ID \u2014 do not invent one.\n2. If the tracker contains `<!-- TRACKER_SKELETON -->` \u2192 this is session 1, run [**Orient**](#orient-session-1).\n3. Otherwise \u2192 this is a continuation, run [**Execute**](#execute-session-2) from where the tracker says, not from the start of the flow.\n\nReading first prevents two failure modes that waste whole sessions: redoing work already done, or contradicting decisions a prior session made.\n\n## Tracker contract\n\nThe tracker must always answer four questions:\n\n1. What are we trying to accomplish?\n2. What has been done?\n3. What's left?\n4. What should the next session do first?\n\nA future session has no other context. If something isn't here, it doesn't exist. Section headings may vary by workflow, but these four answers must be explicit and easy to find.\n\n### Terminal markers\n\nDefault markers (workflows may override \u2014 use the markers configured for the active workflow):\n\n- `<!-- WORKFLOW_COMPLETE -->` \u2014 all work done and verified\n- `<!-- WORKFLOW_BLOCKED -->` or `<!-- WORKFLOW_BLOCKED: reason -->` \u2014 cannot proceed without external intervention\n\nRules:\n\n- Only the last non-empty line of the tracker is authoritative. Marker-like text in notes, examples, or quoted instructions earlier in the file is ignored.\n- The runner trusts markers unconditionally. A premature marker ends the loop with no automatic recovery \u2014 write one only when its criteria are fully met.\n- Include a concrete reason after `WORKFLOW_BLOCKED:` whenever possible; the bare form is still valid.\n\n## Phases\n\n### Orient (session 1)\n\n1. **Replace the skeleton immediately**, before any domain work. Even a three-line tracker (goal + \"orienting\") protects you if the session dies during setup.\n2. Run the workflow's orientation steps. These vary by domain \u2014 a test-writing workflow explores the product in a browser; a migration workflow audits the schema. The workflow defines what orientation means.\n3. Refine the tracker into a granular plan. Each task a concrete, verifiable unit of work, including verification steps (running checks, reviewing output) \u2014 not just implementation. Vague tasks (\"write tests\") cannot be meaningfully resumed by a future session that has no idea what they mean here.\n4. Record concrete observations \u2014 what you actually saw, not what you assumed. Wrong assumptions burn entire future sessions on rework.\n5. **Single-turn requests still go through this phase.** If the entire request is satisfied in one turn, write a minimal tracker (what was asked, what was done, the outcome) and append `<!-- WORKFLOW_COMPLETE -->`. Leaving the skeleton in place causes the runner to classify the session as a failure.\n\n### Execute (session 2+)\n\n- Work from where the tracker says, in the workflow's prescribed sequence. Not every session covers every step.\n- If the workflow defines a skill table, **load the relevant skill before each activity**. Skills carry the implementation detail (scaffolding steps, locator rules, anti-patterns, code templates) that this protocol intentionally doesn't repeat.\n- Delegate heavy exploration or generation to subagents via the Task tool. Pass file paths, conventions, and concrete output expectations; tell them which skill to load. Respect the workflow's **delegation constraints** \u2014 some operations must run in the main agent because their output is proof, or because the main agent needs to interpret results in context.\n- Run quality gates in order. Do not skip \u2014 they exist because skipping cascades into rework. On a failing verdict, address the issues and re-run before proceeding. Respect the workflow's **retry limits**: repeated failure usually signals a deeper issue another retry won't fix.\n\n### End\n\n1. Tracker reflects all progress, discoveries, and blockers.\n2. Tracker says clearly what the next session should do first.\n3. If all work is verified: append the completion marker.\n4. If an unrecoverable blocker prevents progress: append the blocked marker, with a reason if you have one.\n\n## When to write the tracker\n\nWrite on **concrete triggers**, not on a vague sense of \"meaningful progress.\" The right cadence sits between every-tool-call (noisy log, wastes tokens) and end-of-session (everything lost if you die mid-task).\n\n- **Discrete unit done** \u2014 file written, fix applied, test run, gate passed. Reflect the new reality before starting the next unit.\n- **Insight learned** \u2014 API quirk, config field that turned out to matter, dead end ruled out, decision between two approaches. Insights are tracker-worthy even when no code changed; rediscovering them costs the next session a full re-exploration. The tracker is a knowledge ledger, not just a task log.\n- **About to do something risky or long-running** \u2014 subagent dispatch, long build, flaky external call, large refactor. Write _first_, then act. If the operation kills your session, only what's on disk survives.\n- **Plan changed** \u2014 task resequenced, new task surfaced, planned task no longer needed. Stale plans poison continuation sessions.\n- **You haven't written in a while** \u2014 if you can't remember the last update, you've gone too long. A short defensive update (\"doing X, last completed Y, next is Z\") beats nothing.\n\nEach update covers: what changed (work or knowledge), what's now next, and any caveat the next session needs. Don't transcribe tool calls \u2014 the tracker is a contract with your future self, not a replay log.\n\nThe cost of one extra tracker update is a few tokens. The cost of dying without one is a whole wasted session. Bias toward writing.\n\n## Task UI projection\n\nThe tracker is the durable source of truth. Your harness's task tools are a session-scoped UI projection of the same plan, shown to the user in their CLI widget. They do not survive process exit.\n\n{{TASK_TOOL_INSTRUCTIONS}}\n\n- **Session 1, after orientation:** project the tracker's task plan into the task tools.\n- **Session 2+, after reading the tracker:** recreate the projection from the tracker; do not assume task IDs from prior sessions still exist.\n- **During work:** update both \u2014 the task tools for immediate UI feedback, the tracker for persistence \u2014 in the same working phase.\n\n## Session bounding\n\nEach fresh session starts with a clean context window and a compact tracker \u2014 effectively self-compaction. As you work, context fills with tool outputs and intermediate state. The longer you run, the more attention is spread across tokens that are no longer relevant, degrading precision on the work that matters now.\n\nWork a bounded chunk per session. Ending early and letting the next session pick up from a clean tracker is almost always better than pushing through with a heavy context. Natural checkpoints:\n\n- After a quality gate\n- After crossing multiple phases (explored \u2192 planned \u2192 wrote specs) \u2014 stop before pushing into the next\n- When your context is visibly heavy with tool output from earlier work\n\n## Quick reference\n\n- [ ] Read the tracker before doing anything else\n- [ ] Replace the skeleton immediately, even for single-turn requests\n- [ ] Update on concrete triggers \u2014 unit done, insight learned, risky op pending, plan changed\n- [ ] Project the tracker plan into task tools at session start; keep both in sync as work lands\n- [ ] Load the workflow's skill before each activity\n- [ ] Run quality gates in order; respect delegation constraints and retry limits\n- [ ] Write the completion marker only when all work is verified\n- [ ] Checkpoint and end before context goes stale\n";
|
|
@@ -1587,10 +1612,10 @@ function readWorkflowOverride(projectDir, workflow, sessionId, trackerPath, harn
|
|
|
1587
1612
|
if (!workflow?.workflowFile) {
|
|
1588
1613
|
return { workflowOverride: void 0, warnings: [] };
|
|
1589
1614
|
}
|
|
1590
|
-
const resolvedPath =
|
|
1615
|
+
const resolvedPath = path9.isAbsolute(workflow.workflowFile) ? workflow.workflowFile : path9.resolve(projectDir, workflow.workflowFile);
|
|
1591
1616
|
let workflowContent;
|
|
1592
1617
|
try {
|
|
1593
|
-
workflowContent =
|
|
1618
|
+
workflowContent = fs11.readFileSync(resolvedPath, "utf-8");
|
|
1594
1619
|
} catch {
|
|
1595
1620
|
return {
|
|
1596
1621
|
workflowOverride: void 0,
|
|
@@ -1604,9 +1629,9 @@ function readWorkflowOverride(projectDir, workflow, sessionId, trackerPath, harn
|
|
|
1604
1629
|
sessionId,
|
|
1605
1630
|
trackerPath: trackerPath ?? void 0
|
|
1606
1631
|
});
|
|
1607
|
-
const workflowDir =
|
|
1608
|
-
const composedPath =
|
|
1609
|
-
|
|
1632
|
+
const workflowDir = path9.dirname(resolvedPath);
|
|
1633
|
+
const composedPath = path9.join(workflowDir, ".composed-system-prompt.md");
|
|
1634
|
+
fs11.writeFileSync(composedPath, composed, "utf-8");
|
|
1610
1635
|
return {
|
|
1611
1636
|
workflowOverride: {
|
|
1612
1637
|
appendSystemPromptFile: composedPath,
|
|
@@ -1633,7 +1658,7 @@ function resolveTrackerPath(input) {
|
|
|
1633
1658
|
return null;
|
|
1634
1659
|
}
|
|
1635
1660
|
const promptPath = input.sessionId ? rawPath.replaceAll("{sessionId}", input.sessionId) : rawPath;
|
|
1636
|
-
const absolutePath =
|
|
1661
|
+
const absolutePath = path9.isAbsolute(promptPath) ? promptPath : path9.resolve(input.projectDir, promptPath);
|
|
1637
1662
|
return {
|
|
1638
1663
|
absolutePath,
|
|
1639
1664
|
promptPath
|
|
@@ -1679,7 +1704,7 @@ function shouldContinueWorkflowRun(state) {
|
|
|
1679
1704
|
return null;
|
|
1680
1705
|
}
|
|
1681
1706
|
const loopState = loopManager.getState();
|
|
1682
|
-
if (!
|
|
1707
|
+
if (!fs11.existsSync(loopManager.trackerPath)) {
|
|
1683
1708
|
cleanupWorkflowRun(state);
|
|
1684
1709
|
return {
|
|
1685
1710
|
reason: "missing_tracker",
|
|
@@ -1725,8 +1750,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
1725
1750
|
|
|
1726
1751
|
// src/core/workflows/workflowRunner.ts
|
|
1727
1752
|
import crypto from "crypto";
|
|
1728
|
-
import
|
|
1729
|
-
import
|
|
1753
|
+
import fs12 from "fs";
|
|
1754
|
+
import path10 from "path";
|
|
1730
1755
|
var NULL_TOKENS = {
|
|
1731
1756
|
input: null,
|
|
1732
1757
|
output: null,
|
|
@@ -1783,9 +1808,9 @@ function mergeTokens(base, next) {
|
|
|
1783
1808
|
};
|
|
1784
1809
|
}
|
|
1785
1810
|
function defaultCreateTracker(trackerPath, content) {
|
|
1786
|
-
|
|
1811
|
+
fs12.mkdirSync(path10.dirname(trackerPath), { recursive: true });
|
|
1787
1812
|
try {
|
|
1788
|
-
|
|
1813
|
+
fs12.writeFileSync(trackerPath, content, { encoding: "utf-8", flag: "wx" });
|
|
1789
1814
|
} catch (e) {
|
|
1790
1815
|
if (e.code !== "EEXIST") throw e;
|
|
1791
1816
|
}
|
|
@@ -1898,7 +1923,8 @@ function createWorkflowRunner(input) {
|
|
|
1898
1923
|
} else if (loopStop.reason === "max_iterations") {
|
|
1899
1924
|
status = "exhausted";
|
|
1900
1925
|
} else if (loopStop.reason === "skeleton_not_replaced") {
|
|
1901
|
-
status = "
|
|
1926
|
+
status = "failed";
|
|
1927
|
+
stopReason = "tracker skeleton was never replaced \u2014 Claude did not bootstrap the tracker";
|
|
1902
1928
|
} else {
|
|
1903
1929
|
status = "failed";
|
|
1904
1930
|
stopReason = `Loop stopped: ${loopStop.reason}`;
|
|
@@ -2041,31 +2067,6 @@ function useWorkflowSessionController(base, input) {
|
|
|
2041
2067
|
};
|
|
2042
2068
|
}
|
|
2043
2069
|
|
|
2044
|
-
// src/infra/plugins/mcpOptions.ts
|
|
2045
|
-
import fs12 from "fs";
|
|
2046
|
-
import path10 from "path";
|
|
2047
|
-
function collectMcpServersWithOptions(pluginDirs) {
|
|
2048
|
-
const result = [];
|
|
2049
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2050
|
-
for (const dir of pluginDirs) {
|
|
2051
|
-
const mcpPath = path10.join(dir, ".mcp.json");
|
|
2052
|
-
if (!fs12.existsSync(mcpPath)) {
|
|
2053
|
-
continue;
|
|
2054
|
-
}
|
|
2055
|
-
const config = JSON.parse(fs12.readFileSync(mcpPath, "utf-8"));
|
|
2056
|
-
for (const [serverName, serverConfig] of Object.entries(
|
|
2057
|
-
config.mcpServers ?? {}
|
|
2058
|
-
)) {
|
|
2059
|
-
if (seen.has(serverName) || !Array.isArray(serverConfig.options) || serverConfig.options.length === 0) {
|
|
2060
|
-
continue;
|
|
2061
|
-
}
|
|
2062
|
-
seen.add(serverName);
|
|
2063
|
-
result.push({ serverName, options: serverConfig.options });
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
return result;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
2070
|
export {
|
|
2070
2071
|
createWorkflowRunner,
|
|
2071
2072
|
useWorkflowSessionController,
|
|
@@ -2084,6 +2085,7 @@ export {
|
|
|
2084
2085
|
writeGlobalConfig,
|
|
2085
2086
|
writeProjectConfig,
|
|
2086
2087
|
hasProjectWorkflow,
|
|
2088
|
+
collectMcpServersWithOptions,
|
|
2087
2089
|
installWorkflowPlugins,
|
|
2088
2090
|
resolveWorkflowPlugins,
|
|
2089
2091
|
listBuiltinWorkflows,
|
|
@@ -2092,7 +2094,6 @@ export {
|
|
|
2092
2094
|
updateWorkflow,
|
|
2093
2095
|
listWorkflows,
|
|
2094
2096
|
removeWorkflow,
|
|
2095
|
-
compileWorkflowPlan
|
|
2096
|
-
collectMcpServersWithOptions
|
|
2097
|
+
compileWorkflowPlan
|
|
2097
2098
|
};
|
|
2098
|
-
//# sourceMappingURL=chunk-
|
|
2099
|
+
//# sourceMappingURL=chunk-A54HGVML.js.map
|