archbyte 0.6.0 → 0.7.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/agents/observability/adapters/archbyte.d.ts +3 -0
- package/dist/agents/observability/adapters/archbyte.js +104 -0
- package/dist/agents/observability/adapters/claude-transcripts.d.ts +31 -0
- package/dist/agents/observability/adapters/claude-transcripts.js +692 -0
- package/dist/agents/observability/reader.d.ts +7 -0
- package/dist/agents/observability/reader.js +86 -0
- package/dist/agents/observability/types.d.ts +125 -0
- package/dist/agents/observability/types.js +3 -0
- package/dist/agents/observability/writer.d.ts +9 -0
- package/dist/agents/observability/writer.js +100 -0
- package/dist/agents/pipeline/index.d.ts +1 -1
- package/dist/agents/pipeline/index.js +85 -3
- package/dist/agents/pipeline/types.d.ts +2 -0
- package/dist/cli/config.js +21 -1
- package/dist/server/src/index.js +301 -13
- package/package.json +1 -1
- package/ui/dist/assets/index-CkJhpZMm.js +85 -0
- package/ui/dist/assets/{index-BQouokNH.css → index-bMoto6NK.css} +1 -1
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-CWGPRsWP.js +0 -72
package/dist/server/src/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { watch } from "chokidar";
|
|
3
3
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
4
|
-
import { existsSync, readFileSync, statSync } from "fs";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { homedir } from "os";
|
|
7
8
|
import { createServer } from "http";
|
|
8
9
|
import { execSync, spawn, spawnSync } from "child_process";
|
|
9
10
|
// Get UI assets path
|
|
@@ -1223,18 +1224,6 @@ function createHttpServer() {
|
|
|
1223
1224
|
res.end(JSON.stringify({ running: runningWorkflows.has("__generate__") }));
|
|
1224
1225
|
return;
|
|
1225
1226
|
}
|
|
1226
|
-
// API: Reload — re-read architecture, reconcile pending changes
|
|
1227
|
-
if (url === "/api/reload" && req.method === "POST") {
|
|
1228
|
-
currentArchitecture = await loadArchitecture();
|
|
1229
|
-
reconcilePendingWithGit();
|
|
1230
|
-
broadcastUpdate();
|
|
1231
|
-
if (pendingSourceChanges.size > 0) {
|
|
1232
|
-
broadcastPendingChanges();
|
|
1233
|
-
}
|
|
1234
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1235
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
1227
|
// API: Run analyze (static or LLM) + generate
|
|
1239
1228
|
if (url === "/api/analyze" && req.method === "POST") {
|
|
1240
1229
|
if (isAnalyzing) {
|
|
@@ -1696,6 +1685,38 @@ function createHttpServer() {
|
|
|
1696
1685
|
});
|
|
1697
1686
|
return;
|
|
1698
1687
|
}
|
|
1688
|
+
// API: Sessions — list all sessions
|
|
1689
|
+
if (url === "/api/sessions" && req.method === "GET") {
|
|
1690
|
+
try {
|
|
1691
|
+
const sessionIndex = await getSessionsIndex();
|
|
1692
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1693
|
+
res.end(JSON.stringify(sessionIndex));
|
|
1694
|
+
}
|
|
1695
|
+
catch (error) {
|
|
1696
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1697
|
+
res.end(JSON.stringify({ error: String(error) }));
|
|
1698
|
+
}
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
// API: Sessions — get single session by ID
|
|
1702
|
+
if (url.startsWith("/api/sessions/") && req.method === "GET") {
|
|
1703
|
+
const sessionId = decodeURIComponent(url.replace("/api/sessions/", "").split("?")[0]);
|
|
1704
|
+
try {
|
|
1705
|
+
const session = await loadSingleSession(sessionId);
|
|
1706
|
+
if (!session) {
|
|
1707
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1708
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1712
|
+
res.end(JSON.stringify(session));
|
|
1713
|
+
}
|
|
1714
|
+
catch (error) {
|
|
1715
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1716
|
+
res.end(JSON.stringify({ error: String(error) }));
|
|
1717
|
+
}
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1699
1720
|
// Serve static UI files
|
|
1700
1721
|
const MIME_TYPES = {
|
|
1701
1722
|
".html": "text/html",
|
|
@@ -2138,6 +2159,272 @@ function loadLicenseInfo() {
|
|
|
2138
2159
|
return defaults;
|
|
2139
2160
|
}
|
|
2140
2161
|
}
|
|
2162
|
+
// Session helpers — load sessions from archbyte + claude-transcripts
|
|
2163
|
+
let sessionWatcher = null;
|
|
2164
|
+
// Cached session index + route map for fast single-session lookups
|
|
2165
|
+
let _cachedSessionIndex = null;
|
|
2166
|
+
const _sessionRouteMap = new Map();
|
|
2167
|
+
async function getSessionsIndex() {
|
|
2168
|
+
if (_cachedSessionIndex)
|
|
2169
|
+
return _cachedSessionIndex;
|
|
2170
|
+
_cachedSessionIndex = await loadSessionsIndex();
|
|
2171
|
+
return _cachedSessionIndex;
|
|
2172
|
+
}
|
|
2173
|
+
// Ignored directories when scanning for session dirs
|
|
2174
|
+
const SCAN_IGNORE = new Set(["node_modules", ".git", ".hg", "dist", "build", ".next", "__pycache__", "vendor", ".venv", "venv"]);
|
|
2175
|
+
const SESSION_SCAN_MAX_DEPTH = 4;
|
|
2176
|
+
/** Recursively find named hidden directories (e.g. ".archbyte") */
|
|
2177
|
+
function findSessionDirs(root, targetNames, depth = 0) {
|
|
2178
|
+
if (depth > SESSION_SCAN_MAX_DEPTH)
|
|
2179
|
+
return [];
|
|
2180
|
+
const results = [];
|
|
2181
|
+
try {
|
|
2182
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
2183
|
+
for (const entry of entries) {
|
|
2184
|
+
if (!entry.isDirectory())
|
|
2185
|
+
continue;
|
|
2186
|
+
if (targetNames.includes(entry.name)) {
|
|
2187
|
+
results.push(path.join(root, entry.name));
|
|
2188
|
+
}
|
|
2189
|
+
else if (!SCAN_IGNORE.has(entry.name) && !entry.name.startsWith(".")) {
|
|
2190
|
+
results.push(...findSessionDirs(path.join(root, entry.name), targetNames, depth + 1));
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
catch {
|
|
2195
|
+
// Permission error or unreadable dir — skip
|
|
2196
|
+
}
|
|
2197
|
+
return results;
|
|
2198
|
+
}
|
|
2199
|
+
// Cache discovered dirs (refreshed on watcher events)
|
|
2200
|
+
let _archbyteDirs = null;
|
|
2201
|
+
let _claudeTranscriptDir = undefined; // undefined = not checked yet
|
|
2202
|
+
/** Load sessionsPaths from archbyte global config (~/.archbyte/config.json) */
|
|
2203
|
+
function getConfigSessionsPaths() {
|
|
2204
|
+
try {
|
|
2205
|
+
const configPath = path.join(homedir(), ".archbyte", "config.json");
|
|
2206
|
+
if (!existsSync(configPath))
|
|
2207
|
+
return [];
|
|
2208
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
2209
|
+
const cfg = JSON.parse(raw);
|
|
2210
|
+
return Array.isArray(cfg.sessionsPaths) ? cfg.sessionsPaths : [];
|
|
2211
|
+
}
|
|
2212
|
+
catch {
|
|
2213
|
+
return [];
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
/** Discover all archbyte session dirs from workspace + external paths */
|
|
2217
|
+
function discoverArchbyteDirs() {
|
|
2218
|
+
if (_archbyteDirs)
|
|
2219
|
+
return _archbyteDirs;
|
|
2220
|
+
const allArchbyte = [];
|
|
2221
|
+
// Scan within project tree
|
|
2222
|
+
const found = findSessionDirs(config.workspaceRoot, [".archbyte"]);
|
|
2223
|
+
for (const dir of found) {
|
|
2224
|
+
if (path.basename(dir) === ".archbyte")
|
|
2225
|
+
allArchbyte.push(dir);
|
|
2226
|
+
}
|
|
2227
|
+
// Also scan configured external paths
|
|
2228
|
+
for (const extPath of getConfigSessionsPaths()) {
|
|
2229
|
+
const resolved = path.resolve(extPath);
|
|
2230
|
+
if (!existsSync(resolved))
|
|
2231
|
+
continue;
|
|
2232
|
+
if (path.basename(resolved) === ".archbyte") {
|
|
2233
|
+
allArchbyte.push(resolved);
|
|
2234
|
+
}
|
|
2235
|
+
else {
|
|
2236
|
+
const extFound = findSessionDirs(resolved, [".archbyte"]);
|
|
2237
|
+
for (const dir of extFound) {
|
|
2238
|
+
if (path.basename(dir) === ".archbyte")
|
|
2239
|
+
allArchbyte.push(dir);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
_archbyteDirs = [...new Set(allArchbyte)];
|
|
2244
|
+
return _archbyteDirs;
|
|
2245
|
+
}
|
|
2246
|
+
function getArchbyteDirs() {
|
|
2247
|
+
return discoverArchbyteDirs();
|
|
2248
|
+
}
|
|
2249
|
+
function getClaudeTranscriptDir() {
|
|
2250
|
+
if (_claudeTranscriptDir !== undefined)
|
|
2251
|
+
return _claudeTranscriptDir;
|
|
2252
|
+
const encoded = config.workspaceRoot.replace(/^\//, "").replace(/\//g, "-");
|
|
2253
|
+
const dir = path.join(homedir(), ".claude", "projects", `-${encoded}`);
|
|
2254
|
+
_claudeTranscriptDir = existsSync(dir) ? dir : null;
|
|
2255
|
+
return _claudeTranscriptDir;
|
|
2256
|
+
}
|
|
2257
|
+
async function loadSessionsIndex() {
|
|
2258
|
+
const index = { sessions: [] };
|
|
2259
|
+
// Clear route map — will be repopulated below
|
|
2260
|
+
_sessionRouteMap.clear();
|
|
2261
|
+
const archbyteDirs = discoverArchbyteDirs();
|
|
2262
|
+
// Import archbyte sessions
|
|
2263
|
+
if (archbyteDirs.length > 0) {
|
|
2264
|
+
try {
|
|
2265
|
+
const { importArchbyte } = await import("../../agents/observability/adapters/archbyte.js");
|
|
2266
|
+
for (const abDir of archbyteDirs) {
|
|
2267
|
+
const abSessions = await importArchbyte(abDir);
|
|
2268
|
+
const relativePath = path.relative(config.workspaceRoot, abDir);
|
|
2269
|
+
const sourceLabel = relativePath === ".archbyte" ? "archbyte" : `archbyte (${path.dirname(relativePath)})`;
|
|
2270
|
+
for (const abs of abSessions) {
|
|
2271
|
+
index.sessions.push({
|
|
2272
|
+
sessionId: abs.sessionId,
|
|
2273
|
+
startedAt: abs.startedAt,
|
|
2274
|
+
completedAt: abs.completedAt,
|
|
2275
|
+
status: abs.status,
|
|
2276
|
+
runCount: abs.summary.totalRuns,
|
|
2277
|
+
phases: abs.summary.phases,
|
|
2278
|
+
source: sourceLabel,
|
|
2279
|
+
});
|
|
2280
|
+
_sessionRouteMap.set(abs.sessionId, { source: "archbyte", dir: abDir });
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
catch (err) {
|
|
2285
|
+
console.error("[sessions] archbyte import error:", err);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
// Import Claude transcript sessions
|
|
2289
|
+
const ctDir = getClaudeTranscriptDir();
|
|
2290
|
+
if (ctDir) {
|
|
2291
|
+
try {
|
|
2292
|
+
const { scanTranscriptIndex } = await import("../../agents/observability/adapters/claude-transcripts.js");
|
|
2293
|
+
const ctEntries = await scanTranscriptIndex(ctDir);
|
|
2294
|
+
for (const e of ctEntries) {
|
|
2295
|
+
index.sessions.push({
|
|
2296
|
+
sessionId: e.sessionId,
|
|
2297
|
+
startedAt: e.startedAt,
|
|
2298
|
+
completedAt: e.completedAt,
|
|
2299
|
+
status: e.status,
|
|
2300
|
+
runCount: e.runCount,
|
|
2301
|
+
phases: e.phases,
|
|
2302
|
+
source: "claude-transcript",
|
|
2303
|
+
category: e.category,
|
|
2304
|
+
label: e.label,
|
|
2305
|
+
touchedDirs: e.touchedDirs,
|
|
2306
|
+
eventCount: e.eventCount,
|
|
2307
|
+
dirMetrics: e.dirMetrics,
|
|
2308
|
+
estimatedCost: e.estimatedCost,
|
|
2309
|
+
});
|
|
2310
|
+
_sessionRouteMap.set(e.sessionId, { source: "claude-transcript", dir: ctDir });
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch (err) {
|
|
2314
|
+
console.error("[sessions] claude-transcripts index error:", err);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
// Sort newest first
|
|
2318
|
+
index.sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
2319
|
+
return index;
|
|
2320
|
+
}
|
|
2321
|
+
async function loadSingleSession(sessionId) {
|
|
2322
|
+
// Fast path: use route map if available (populated after first index build)
|
|
2323
|
+
const route = _sessionRouteMap.get(sessionId);
|
|
2324
|
+
if (route) {
|
|
2325
|
+
try {
|
|
2326
|
+
if (route.source === "claude-transcript") {
|
|
2327
|
+
const { importClaudeTranscript } = await import("../../agents/observability/adapters/claude-transcripts.js");
|
|
2328
|
+
return await importClaudeTranscript(route.dir, sessionId);
|
|
2329
|
+
}
|
|
2330
|
+
else if (route.source === "archbyte") {
|
|
2331
|
+
const { importArchbyte } = await import("../../agents/observability/adapters/archbyte.js");
|
|
2332
|
+
const sessions = await importArchbyte(route.dir);
|
|
2333
|
+
const found = sessions.find((s) => s.sessionId === sessionId);
|
|
2334
|
+
if (found)
|
|
2335
|
+
return found;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
catch (err) {
|
|
2339
|
+
console.error(`[sessions] routed lookup failed for ${sessionId}:`, err);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
// Fallback: full scan (cold start or route miss)
|
|
2343
|
+
const archbyteDirs = discoverArchbyteDirs();
|
|
2344
|
+
if (archbyteDirs.length > 0) {
|
|
2345
|
+
try {
|
|
2346
|
+
const { importArchbyte } = await import("../../agents/observability/adapters/archbyte.js");
|
|
2347
|
+
for (const abDir of archbyteDirs) {
|
|
2348
|
+
const sessions = await importArchbyte(abDir);
|
|
2349
|
+
const found = sessions.find((s) => s.sessionId === sessionId);
|
|
2350
|
+
if (found)
|
|
2351
|
+
return found;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
catch (err) {
|
|
2355
|
+
console.error("[sessions] archbyte single session error:", err);
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
const ctDir2 = getClaudeTranscriptDir();
|
|
2359
|
+
if (ctDir2) {
|
|
2360
|
+
try {
|
|
2361
|
+
const { importClaudeTranscript } = await import("../../agents/observability/adapters/claude-transcripts.js");
|
|
2362
|
+
return await importClaudeTranscript(ctDir2, sessionId);
|
|
2363
|
+
}
|
|
2364
|
+
catch (err) {
|
|
2365
|
+
console.error("[sessions] claude-transcripts session error:", err);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
return null;
|
|
2369
|
+
}
|
|
2370
|
+
/** Build a fingerprint of the session index for diffing */
|
|
2371
|
+
function sessionIndexFingerprint(index) {
|
|
2372
|
+
// Fast fingerprint: sorted sessionId + status + runCount + completedAt
|
|
2373
|
+
return index.sessions
|
|
2374
|
+
.map(s => `${s.sessionId}:${s.status}:${s.runCount}:${s.completedAt || ""}`)
|
|
2375
|
+
.sort()
|
|
2376
|
+
.join("|");
|
|
2377
|
+
}
|
|
2378
|
+
function setupSessionWatcher() {
|
|
2379
|
+
const watchPaths = [];
|
|
2380
|
+
// Watch all discovered session directories
|
|
2381
|
+
for (const abDir of getArchbyteDirs()) {
|
|
2382
|
+
watchPaths.push(abDir);
|
|
2383
|
+
}
|
|
2384
|
+
const ctDir3 = getClaudeTranscriptDir();
|
|
2385
|
+
if (ctDir3)
|
|
2386
|
+
watchPaths.push(ctDir3);
|
|
2387
|
+
if (watchPaths.length === 0)
|
|
2388
|
+
return;
|
|
2389
|
+
let debounce = null;
|
|
2390
|
+
sessionWatcher = watch(watchPaths, {
|
|
2391
|
+
ignoreInitial: true,
|
|
2392
|
+
depth: 4,
|
|
2393
|
+
ignored: /(^|[\/\\])\../,
|
|
2394
|
+
});
|
|
2395
|
+
sessionWatcher.on("all", (_event, changedPath) => {
|
|
2396
|
+
// Targeted cache invalidation for .jsonl transcript files
|
|
2397
|
+
if (changedPath && changedPath.endsWith(".jsonl")) {
|
|
2398
|
+
import("../../agents/observability/adapters/claude-transcripts.js")
|
|
2399
|
+
.then(mod => mod.invalidateIndexCache(changedPath))
|
|
2400
|
+
.catch(() => { });
|
|
2401
|
+
}
|
|
2402
|
+
if (debounce)
|
|
2403
|
+
clearTimeout(debounce);
|
|
2404
|
+
debounce = setTimeout(async () => {
|
|
2405
|
+
try {
|
|
2406
|
+
// Snapshot old fingerprint before rebuild
|
|
2407
|
+
const oldFingerprint = _cachedSessionIndex
|
|
2408
|
+
? sessionIndexFingerprint(_cachedSessionIndex)
|
|
2409
|
+
: "";
|
|
2410
|
+
// Invalidate caches so new dirs are discovered
|
|
2411
|
+
_archbyteDirs = null;
|
|
2412
|
+
_claudeTranscriptDir = undefined;
|
|
2413
|
+
_cachedSessionIndex = null;
|
|
2414
|
+
const index = await loadSessionsIndex();
|
|
2415
|
+
_cachedSessionIndex = index;
|
|
2416
|
+
// Only broadcast if index actually changed
|
|
2417
|
+
const newFingerprint = sessionIndexFingerprint(index);
|
|
2418
|
+
if (newFingerprint !== oldFingerprint) {
|
|
2419
|
+
broadcastOpsEvent({ type: "session:update", sessions: index });
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
catch {
|
|
2423
|
+
// Non-fatal
|
|
2424
|
+
}
|
|
2425
|
+
}, 1000);
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2141
2428
|
// Export start function
|
|
2142
2429
|
export async function startServer(cfg) {
|
|
2143
2430
|
config = cfg;
|
|
@@ -2156,6 +2443,7 @@ export async function startServer(cfg) {
|
|
|
2156
2443
|
reconcilePendingWithGit();
|
|
2157
2444
|
setupSourceWatcher();
|
|
2158
2445
|
setupGitWatcher();
|
|
2446
|
+
setupSessionWatcher();
|
|
2159
2447
|
console.error(`[archbyte] Serving ${config.name}`);
|
|
2160
2448
|
console.error(`[archbyte] Diagram: ${config.diagramPath}`);
|
|
2161
2449
|
// Listen for 'q' keypress to quit gracefully
|