@shapeshift-labs/frontier-loom-ui 0.1.8 → 0.1.9
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/server.js +177 -25
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -26,6 +26,7 @@ const LIFETIME_DASHBOARD_MAX_AUTONOMOUS_DECISION_FILES = 400;
|
|
|
26
26
|
const LIFETIME_DASHBOARD_MAX_DRAIN_RUNS = 6;
|
|
27
27
|
const LIFETIME_DASHBOARD_MAX_ACTIVE_PID_RUNS = 32;
|
|
28
28
|
const LIFETIME_DASHBOARD_MAX_QUEUE_TASKS = 500;
|
|
29
|
+
const LIFETIME_DASHBOARD_MAX_QUEUE_STATE_FILES = 200;
|
|
29
30
|
const LIFETIME_DASHBOARD_SOURCE_TIMEOUT_MS = 8000;
|
|
30
31
|
const LIFETIME_DASHBOARD_MAX_SUBSTRATE_FILES = 800;
|
|
31
32
|
const LIFETIME_DASHBOARD_SUBSTRATE_MAX_BYTES = 4 * 1024 * 1024;
|
|
@@ -1017,47 +1018,71 @@ function isResolvedCoordinatorReviewRecord(job) {
|
|
|
1017
1018
|
async function readLifetimeQueueBacklog(cwd) {
|
|
1018
1019
|
const root = path.join(cwd, '.loom', 'queues');
|
|
1019
1020
|
const stat = await fs.stat(root).catch(() => undefined);
|
|
1020
|
-
|
|
1021
|
-
return { entries: [], manifests: [], sourceCount: 0, paths: [], generatedAt: 0 };
|
|
1021
|
+
const agentRunsRoot = path.join(cwd, 'agent-runs');
|
|
1022
1022
|
const queueDirs = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
1023
1023
|
const entriesById = new Map();
|
|
1024
1024
|
const manifests = [];
|
|
1025
|
+
const queueStates = [];
|
|
1025
1026
|
const paths = [];
|
|
1026
1027
|
let generatedAt = 0;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1028
|
+
if (stat?.isDirectory()) {
|
|
1029
|
+
for (const queueDir of queueDirs) {
|
|
1030
|
+
if (!queueDir.isDirectory())
|
|
1031
|
+
continue;
|
|
1032
|
+
const dir = path.join(root, queueDir.name);
|
|
1033
|
+
const manifestFile = await preferredQueueManifestFile(dir);
|
|
1034
|
+
if (manifestFile) {
|
|
1035
|
+
const manifestStat = await fs.stat(manifestFile).catch(() => undefined);
|
|
1036
|
+
generatedAt = Math.max(generatedAt, manifestStat?.mtimeMs ?? 0);
|
|
1037
|
+
const manifest = await readQueueCapacityManifest(cwd, manifestFile);
|
|
1038
|
+
if (manifest)
|
|
1039
|
+
manifests.push(manifest);
|
|
1040
|
+
}
|
|
1041
|
+
for (const taskFile of await queueTaskFiles(dir)) {
|
|
1042
|
+
const fileStat = await fs.stat(taskFile).catch(() => undefined);
|
|
1043
|
+
generatedAt = Math.max(generatedAt, fileStat?.mtimeMs ?? 0);
|
|
1044
|
+
paths.push(path.relative(cwd, taskFile));
|
|
1045
|
+
const tasks = await readQueueTaskFile(taskFile);
|
|
1046
|
+
for (const task of tasks) {
|
|
1047
|
+
const id = textValue(task.id ?? task.taskId ?? task.title, '');
|
|
1048
|
+
if (!id)
|
|
1049
|
+
continue;
|
|
1050
|
+
entriesById.set(id, normalizeQueueBacklogEntry(cwd, queueDir.name, taskFile, task));
|
|
1051
|
+
if (entriesById.size >= LIFETIME_DASHBOARD_MAX_QUEUE_TASKS)
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1049
1054
|
if (entriesById.size >= LIFETIME_DASHBOARD_MAX_QUEUE_TASKS)
|
|
1050
1055
|
break;
|
|
1051
1056
|
}
|
|
1052
1057
|
if (entriesById.size >= LIFETIME_DASHBOARD_MAX_QUEUE_TASKS)
|
|
1053
1058
|
break;
|
|
1054
1059
|
}
|
|
1060
|
+
}
|
|
1061
|
+
const queueStateFiles = await findLifetimeQueueStateFiles(agentRunsRoot);
|
|
1062
|
+
for (const file of queueStateFiles) {
|
|
1063
|
+
const fileStat = await fs.stat(file).catch(() => undefined);
|
|
1064
|
+
generatedAt = Math.max(generatedAt, fileStat?.mtimeMs ?? 0);
|
|
1065
|
+
const relative = path.relative(cwd, file);
|
|
1066
|
+
paths.push(relative);
|
|
1067
|
+
const state = recordValue(await readJsonFile(file));
|
|
1068
|
+
const summary = queueRuntimeStateSummary(relative, state, fileStat?.mtimeMs ?? 0);
|
|
1069
|
+
if (summary)
|
|
1070
|
+
queueStates.push(summary);
|
|
1071
|
+
for (const entry of normalizeQueueStateBacklogEntries(cwd, file, state)) {
|
|
1072
|
+
const id = textValue(entry.id ?? entry.taskId, '');
|
|
1073
|
+
if (!id)
|
|
1074
|
+
continue;
|
|
1075
|
+
entriesById.set(id, entry);
|
|
1076
|
+
if (entriesById.size >= LIFETIME_DASHBOARD_MAX_QUEUE_TASKS)
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1055
1079
|
if (entriesById.size >= LIFETIME_DASHBOARD_MAX_QUEUE_TASKS)
|
|
1056
1080
|
break;
|
|
1057
1081
|
}
|
|
1058
1082
|
return {
|
|
1059
1083
|
entries: Array.from(entriesById.values()),
|
|
1060
1084
|
manifests,
|
|
1085
|
+
queueStates,
|
|
1061
1086
|
sourceCount: paths.length,
|
|
1062
1087
|
paths,
|
|
1063
1088
|
generatedAt
|
|
@@ -1081,6 +1106,34 @@ async function queueTaskFiles(dir) {
|
|
|
1081
1106
|
.sort((left, right) => left.mtimeMs - right.mtimeMs || left.preferred - right.preferred || left.file.localeCompare(right.file))
|
|
1082
1107
|
.map((candidate) => candidate.file);
|
|
1083
1108
|
}
|
|
1109
|
+
async function findLifetimeQueueStateFiles(root) {
|
|
1110
|
+
const stat = await fs.stat(root).catch(() => undefined);
|
|
1111
|
+
if (!stat?.isDirectory())
|
|
1112
|
+
return [];
|
|
1113
|
+
const out = [];
|
|
1114
|
+
const skipDirs = new Set(['.git', 'node_modules', 'dist', 'coverage', 'evidence', 'streams', 'artifact-store']);
|
|
1115
|
+
async function walk(current, depth) {
|
|
1116
|
+
if (out.length >= LIFETIME_DASHBOARD_MAX_QUEUE_STATE_FILES || depth > LIFETIME_DASHBOARD_SCAN_MAX_DEPTH)
|
|
1117
|
+
return;
|
|
1118
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1119
|
+
for (const entry of entries) {
|
|
1120
|
+
if (out.length >= LIFETIME_DASHBOARD_MAX_QUEUE_STATE_FILES)
|
|
1121
|
+
return;
|
|
1122
|
+
const absolute = path.join(current, entry.name);
|
|
1123
|
+
if (entry.isDirectory()) {
|
|
1124
|
+
if (!skipDirs.has(entry.name))
|
|
1125
|
+
await walk(absolute, depth + 1);
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (!entry.isFile() || entry.name !== 'queue-state.json')
|
|
1129
|
+
continue;
|
|
1130
|
+
const fileStat = await fs.stat(absolute).catch(() => undefined);
|
|
1131
|
+
out.push({ file: absolute, mtimeMs: fileStat?.mtimeMs ?? 0 });
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
await walk(root, 0);
|
|
1135
|
+
return out.sort((left, right) => right.mtimeMs - left.mtimeMs || left.file.localeCompare(right.file)).map((entry) => entry.file);
|
|
1136
|
+
}
|
|
1084
1137
|
async function preferredQueueManifestFile(dir) {
|
|
1085
1138
|
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1086
1139
|
const candidates = [];
|
|
@@ -1169,6 +1222,88 @@ function normalizeQueueBacklogEntry(cwd, queueId, file, task) {
|
|
|
1169
1222
|
sourceQueue: queueId
|
|
1170
1223
|
};
|
|
1171
1224
|
}
|
|
1225
|
+
function queueRuntimeStateSummary(pathLabel, state, generatedAt) {
|
|
1226
|
+
const jobs = recordArray(state.jobs);
|
|
1227
|
+
if (!jobs.length && !textValue(state.id, ''))
|
|
1228
|
+
return undefined;
|
|
1229
|
+
return {
|
|
1230
|
+
path: pathLabel,
|
|
1231
|
+
queueId: textValue(state.id, pathLabel),
|
|
1232
|
+
jobCount: jobs.length,
|
|
1233
|
+
queuedCount: queueJobStatusCount(jobs, ['queued', 'pending', 'ready', 'scheduled', 'delayed']),
|
|
1234
|
+
leasedCount: queueJobStatusCount(jobs, ['leased', 'running', 'active']),
|
|
1235
|
+
completedCount: queueJobStatusCount(jobs, ['completed', 'deduped']),
|
|
1236
|
+
failedCount: queueJobStatusCount(jobs, ['failed']),
|
|
1237
|
+
deadCount: queueJobStatusCount(jobs, ['dead', 'cancelled']),
|
|
1238
|
+
terminalOutcomeCount: recordArray(state.terminalOutcomes).length,
|
|
1239
|
+
eventCount: recordArray(state.events).length,
|
|
1240
|
+
generatedAt
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function normalizeQueueStateBacklogEntries(cwd, file, state) {
|
|
1244
|
+
const queueId = textValue(state.id, path.basename(path.dirname(file)));
|
|
1245
|
+
return recordArray(state.jobs)
|
|
1246
|
+
.filter((job) => !isTerminalQueueStatus(job.status))
|
|
1247
|
+
.map((job) => normalizeQueueStateBacklogEntry(cwd, queueId, file, job))
|
|
1248
|
+
.filter((entry) => Boolean(textValue(entry.id ?? entry.taskId, '')));
|
|
1249
|
+
}
|
|
1250
|
+
function normalizeQueueStateBacklogEntry(cwd, queueId, file, job) {
|
|
1251
|
+
const metadata = recordValue(job.metadata);
|
|
1252
|
+
const payload = recordValue(job.payload);
|
|
1253
|
+
const lease = recordValue(job.lease);
|
|
1254
|
+
const rawId = textValue(metadata.swarmJobId ?? metadata.taskId ?? payload.id ?? payload.taskId ?? job.id, '');
|
|
1255
|
+
const id = rawId.startsWith('swarm-job:') ? rawId.slice('swarm-job:'.length) : rawId;
|
|
1256
|
+
const queueStatus = textValue(job.status, 'queued');
|
|
1257
|
+
const sourceRefs = stringArray(payload.sourceRefs);
|
|
1258
|
+
const targetRefs = stringArray(payload.targetRefs);
|
|
1259
|
+
const allowedWrites = stringArray(payload.allowedWrites);
|
|
1260
|
+
const files = uniquePaths([...targetRefs, ...allowedWrites, ...sourceRefs]).slice(0, 40);
|
|
1261
|
+
const status = queueBacklogStatus(queueStatus);
|
|
1262
|
+
return {
|
|
1263
|
+
id,
|
|
1264
|
+
taskId: id,
|
|
1265
|
+
title: textValue(payload.title ?? payload.objective ?? metadata.taskId ?? id, id),
|
|
1266
|
+
objective: textValue(payload.objective ?? payload.summary, ''),
|
|
1267
|
+
status,
|
|
1268
|
+
queueStatus,
|
|
1269
|
+
ready: status === 'todo',
|
|
1270
|
+
lane: textValue(metadata.lane ?? payload.lane, queueId),
|
|
1271
|
+
group: textValue(metadata.lane ?? payload.groupId ?? payload.epicId, queueId),
|
|
1272
|
+
epicId: textValue(payload.epicId, ''),
|
|
1273
|
+
priority: numberValue(job.priority ?? payload.priority),
|
|
1274
|
+
changedPaths: files,
|
|
1275
|
+
changedPathCount: files.length,
|
|
1276
|
+
sourceRefs,
|
|
1277
|
+
targetRefs,
|
|
1278
|
+
allowedWrites,
|
|
1279
|
+
acceptance: stringArray(payload.acceptance),
|
|
1280
|
+
verification: recordArray(payload.verification),
|
|
1281
|
+
tags: uniquePaths([...stringArray(job.tags), ...stringArray(payload.tags)]),
|
|
1282
|
+
sourceLabel: path.relative(cwd, file),
|
|
1283
|
+
sourceQueue: queueId,
|
|
1284
|
+
queueJobId: textValue(job.id, ''),
|
|
1285
|
+
queueRunId: textValue(metadata.runId, ''),
|
|
1286
|
+
queuePlanId: textValue(metadata.planId, ''),
|
|
1287
|
+
leaseOwner: textValue(lease.owner, '')
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function queueBacklogStatus(value) {
|
|
1291
|
+
const status = normalized(value);
|
|
1292
|
+
if (['leased', 'running', 'active'].includes(status))
|
|
1293
|
+
return 'running';
|
|
1294
|
+
if (['failed', 'dead'].includes(status))
|
|
1295
|
+
return 'failed';
|
|
1296
|
+
if (['completed', 'deduped', 'cancelled'].includes(status))
|
|
1297
|
+
return 'completed';
|
|
1298
|
+
return 'todo';
|
|
1299
|
+
}
|
|
1300
|
+
function isTerminalQueueStatus(value) {
|
|
1301
|
+
return ['completed', 'deduped', 'dead', 'cancelled'].includes(normalized(value));
|
|
1302
|
+
}
|
|
1303
|
+
function queueJobStatusCount(jobs, statuses) {
|
|
1304
|
+
const allowed = new Set(statuses);
|
|
1305
|
+
return jobs.filter((job) => allowed.has(normalized(job.status))).length;
|
|
1306
|
+
}
|
|
1172
1307
|
async function readLifetimeDashboardResetCutoff(root) {
|
|
1173
1308
|
const reset = recordValue(await readJsonFile(path.join(root, LIFETIME_DASHBOARD_RESET_FILE)));
|
|
1174
1309
|
return numberValue(reset.resetAt ?? reset.generatedAt);
|
|
@@ -1578,6 +1713,7 @@ async function combineLifetimeDashboardSnapshots(options, discoveredSources, sna
|
|
|
1578
1713
|
dirtyAutoDrainSkipCount: autoDrainDelays.filter((record) => record.skippedReason === 'dirty-worktree').length,
|
|
1579
1714
|
substrateRecordCount: numberValue(substrateGraph.nodeCount),
|
|
1580
1715
|
substrateSourceCount: substrate.sourceCount,
|
|
1716
|
+
...queueRuntimeStateRollup(queueBacklog),
|
|
1581
1717
|
...(graph ? { graph } : {})
|
|
1582
1718
|
};
|
|
1583
1719
|
const queueOverlay = lifetimeQueueBacklogOverlay(queueBacklog, jobs);
|
|
@@ -1602,6 +1738,7 @@ async function combineLifetimeDashboardSnapshots(options, discoveredSources, sna
|
|
|
1602
1738
|
loadedSourceCount: visibleSnapshots.length,
|
|
1603
1739
|
suppressedCollectionSourceCount: snapshots.length - visibleSnapshots.length,
|
|
1604
1740
|
queueSourceCount: queueBacklog.sourceCount,
|
|
1741
|
+
queueStateSourceCount: queueBacklog.queueStates.length,
|
|
1605
1742
|
coordinationDelayCount: autoDrainDelays.length,
|
|
1606
1743
|
substrateSourceCount: substrate.sourceCount,
|
|
1607
1744
|
...(substrate.sourceCount ? { substrateFiles: substrate.sourceFiles } : {}),
|
|
@@ -1643,6 +1780,7 @@ async function combineLifetimeDashboardSnapshots(options, discoveredSources, sna
|
|
|
1643
1780
|
...(substrate.sourceCount ? { substrate } : {}),
|
|
1644
1781
|
sources: discoveredSources.slice(0, LIFETIME_DASHBOARD_MAX_SOURCES),
|
|
1645
1782
|
manifests: queueBacklog.manifests,
|
|
1783
|
+
queueStates: queueBacklog.queueStates,
|
|
1646
1784
|
queueSources: queueBacklog.paths
|
|
1647
1785
|
}
|
|
1648
1786
|
}
|
|
@@ -2303,6 +2441,7 @@ function mergeLifetimeActiveRunSnapshot(lifetime, active) {
|
|
|
2303
2441
|
capacity: lifetimeCapacitySummary({
|
|
2304
2442
|
entries: recordArray(recordValue(lifetime.backlog).entries),
|
|
2305
2443
|
manifests: recordArray(recordValue(recordValue(lifetime.raw).lifetime).manifests),
|
|
2444
|
+
queueStates: recordArray(recordValue(recordValue(lifetime.raw).lifetime).queueStates),
|
|
2306
2445
|
sourceCount: numberValue(recordValue(lifetime.backlog).entryCount),
|
|
2307
2446
|
paths: stringArray(recordValue(recordValue(lifetime.raw).lifetime).queueSources),
|
|
2308
2447
|
generatedAt: numberValue(lifetime.generatedAt)
|
|
@@ -3143,7 +3282,20 @@ function lifetimeCapacitySummary(queueBacklog, jobs, openQueueEntries = queueBac
|
|
|
3143
3282
|
totalTaskCount: queueBacklog.entries.length,
|
|
3144
3283
|
completedTaskCount: jobs.filter((job) => textValue(job.status, '') === 'completed').length,
|
|
3145
3284
|
lanes,
|
|
3146
|
-
queueSources: queueBacklog.paths
|
|
3285
|
+
queueSources: queueBacklog.paths,
|
|
3286
|
+
...queueRuntimeStateRollup(queueBacklog)
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
function queueRuntimeStateRollup(queueBacklog) {
|
|
3290
|
+
return {
|
|
3291
|
+
durableQueueStateCount: queueBacklog.queueStates.length,
|
|
3292
|
+
durableQueueJobCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.jobCount, 0),
|
|
3293
|
+
durableQueueQueuedCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.queuedCount, 0),
|
|
3294
|
+
durableQueueLeasedCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.leasedCount, 0),
|
|
3295
|
+
durableQueueCompletedCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.completedCount, 0),
|
|
3296
|
+
durableQueueFailedCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.failedCount + state.deadCount, 0),
|
|
3297
|
+
durableQueueTerminalOutcomeCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.terminalOutcomeCount, 0),
|
|
3298
|
+
durableQueueEventCount: queueBacklog.queueStates.reduce((sum, state) => sum + state.eventCount, 0)
|
|
3147
3299
|
};
|
|
3148
3300
|
}
|
|
3149
3301
|
function capacityLaneRow(lane, queuedEntries, laneJobs) {
|