@shapeshift-labs/frontier-loom-ui 0.1.7 → 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 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
- if (!stat?.isDirectory())
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
- for (const queueDir of queueDirs) {
1028
- if (!queueDir.isDirectory())
1029
- continue;
1030
- const dir = path.join(root, queueDir.name);
1031
- const manifestFile = await preferredQueueManifestFile(dir);
1032
- if (manifestFile) {
1033
- const manifestStat = await fs.stat(manifestFile).catch(() => undefined);
1034
- generatedAt = Math.max(generatedAt, manifestStat?.mtimeMs ?? 0);
1035
- const manifest = await readQueueCapacityManifest(cwd, manifestFile);
1036
- if (manifest)
1037
- manifests.push(manifest);
1038
- }
1039
- for (const taskFile of await queueTaskFiles(dir)) {
1040
- const fileStat = await fs.stat(taskFile).catch(() => undefined);
1041
- generatedAt = Math.max(generatedAt, fileStat?.mtimeMs ?? 0);
1042
- paths.push(path.relative(cwd, taskFile));
1043
- const tasks = await readQueueTaskFile(taskFile);
1044
- for (const task of tasks) {
1045
- const id = textValue(task.id ?? task.taskId ?? task.title, '');
1046
- if (!id)
1047
- continue;
1048
- entriesById.set(id, normalizeQueueBacklogEntry(cwd, queueDir.name, taskFile, task));
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) {