Baseline: distribution workspace before observability redesign
This commit is contained in:
219
.agents/scripts/observe.mjs
Normal file
219
.agents/scripts/observe.mjs
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env node
|
||||
import { createServer } from "node:http";
|
||||
import {
|
||||
existsSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
statSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const workspace = path.resolve(scriptDir, "../..");
|
||||
const agentsDir = path.join(workspace, ".agents");
|
||||
const runsDir = path.join(agentsDir, "runs");
|
||||
const checkpointPath = path.join(agentsDir, "checkpoint.json");
|
||||
const staticDir = path.join(agentsDir, "observability");
|
||||
const port = parsePort(process.argv) || 4317;
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
||||
|
||||
if (url.pathname === "/api/status") {
|
||||
const tail = parseTail(url.searchParams.get("tail"));
|
||||
sendJson(res, buildStatus(tail));
|
||||
return;
|
||||
}
|
||||
|
||||
serveStatic(res, url.pathname);
|
||||
});
|
||||
|
||||
server.listen(port, "127.0.0.1", () => {
|
||||
console.log(`Agent observability dashboard: http://127.0.0.1:${port}`);
|
||||
});
|
||||
|
||||
server.on("error", (error) => {
|
||||
console.error(`Could not start observability dashboard on 127.0.0.1:${port}`);
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
function parsePort(argv) {
|
||||
const index = argv.findIndex((arg) => arg === "--port" || arg === "-p");
|
||||
if (index === -1) return null;
|
||||
const value = Number(argv[index + 1]);
|
||||
if (!Number.isInteger(value) || value < 1 || value > 65535) return null;
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseTail(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed < 0) return 80;
|
||||
return Math.min(parsed, 500);
|
||||
}
|
||||
|
||||
function buildStatus(tailLines) {
|
||||
const checkpoint = readJsonIfExists(checkpointPath);
|
||||
const runs = listRuns();
|
||||
const activeRunId = checkpoint?.active_run_id || findLatestRunId(runs);
|
||||
const activeRun = runs.find((run) => run.id === activeRunId) || null;
|
||||
|
||||
return {
|
||||
generated_at: new Date().toISOString(),
|
||||
workspace,
|
||||
checkpoint,
|
||||
summary: {
|
||||
run_count: runs.length,
|
||||
active_run_id: activeRunId,
|
||||
current_phase: checkpoint?.current_phase || null,
|
||||
active_branch: checkpoint?.active_branch || null,
|
||||
blocker_count: Array.isArray(checkpoint?.blockers) ? checkpoint.blockers.length : 0,
|
||||
next_action: checkpoint?.next_action || null,
|
||||
},
|
||||
runs,
|
||||
active_progress: activeRun ? readProgress(activeRun, tailLines) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function listRuns() {
|
||||
if (!existsSync(runsDir)) return [];
|
||||
|
||||
return readdirSync(runsDir)
|
||||
.map((name) => path.join(runsDir, name))
|
||||
.filter((runPath) => statSync(runPath).isDirectory())
|
||||
.map((runPath) => {
|
||||
const id = path.basename(runPath);
|
||||
const state = readJsonIfExists(path.join(runPath, "state.json"));
|
||||
const agents = normalizeAgents(readJsonIfExists(path.join(runPath, "agents.json")));
|
||||
return {
|
||||
id,
|
||||
path: path.relative(workspace, runPath),
|
||||
state,
|
||||
agents: agents.map((agent) => ({
|
||||
...agent,
|
||||
lease_expired: isExpired(agent.lease_expires_at),
|
||||
})),
|
||||
mtime_ms: statSync(runPath).mtimeMs,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.mtime_ms - a.mtime_ms);
|
||||
}
|
||||
|
||||
function normalizeAgents(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
if (Array.isArray(value.agents)) return value.agents;
|
||||
return Object.entries(value).map(([role, agent]) => ({
|
||||
role,
|
||||
...(typeof agent === "object" && agent ? agent : { status: String(agent) }),
|
||||
}));
|
||||
}
|
||||
|
||||
function findLatestRunId(runs) {
|
||||
if (runs.length === 0) return null;
|
||||
return runs.reduce((latest, run) => (run.mtime_ms > latest.mtime_ms ? run : latest)).id;
|
||||
}
|
||||
|
||||
function readProgress(run, tailLines) {
|
||||
const runPath = path.join(workspace, run.path);
|
||||
const candidateNames = [
|
||||
"progress.log",
|
||||
"implementation-log.md",
|
||||
"validation.md",
|
||||
"opus-proposal.md",
|
||||
"snarky-review.md",
|
||||
];
|
||||
const filePath = candidateNames
|
||||
.map((name) => path.join(runPath, name))
|
||||
.find((candidate) => existsSync(candidate) && statSync(candidate).isFile());
|
||||
|
||||
if (!filePath) {
|
||||
return {
|
||||
run_id: run.id,
|
||||
path: null,
|
||||
lines: [],
|
||||
};
|
||||
}
|
||||
|
||||
const lines = readFileSync(filePath, "utf8").split(/\r?\n/);
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
return {
|
||||
run_id: run.id,
|
||||
path: path.relative(workspace, filePath),
|
||||
lines: lines.slice(Math.max(0, lines.length - tailLines)),
|
||||
};
|
||||
}
|
||||
|
||||
function isExpired(value) {
|
||||
if (!value) return false;
|
||||
const time = Date.parse(value);
|
||||
if (!Number.isFinite(time)) return false;
|
||||
return time < Date.now();
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath) {
|
||||
if (!existsSync(filePath)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
} catch (error) {
|
||||
return {
|
||||
parse_error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function sendJson(res, value) {
|
||||
const body = JSON.stringify(value);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(body),
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
function serveStatic(res, pathname) {
|
||||
const relative = pathname === "/" ? "index.html" : pathname.slice(1);
|
||||
const filePath = path.resolve(staticDir, relative);
|
||||
|
||||
if (filePath !== staticDir && !filePath.startsWith(`${staticDir}${path.sep}`)) {
|
||||
sendNotFound(res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
||||
sendNotFound(res);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = readFileSync(filePath);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": contentType(filePath),
|
||||
"Content-Length": body.length,
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
function sendNotFound(res) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("Not found");
|
||||
}
|
||||
|
||||
function contentType(filePath) {
|
||||
const extension = path.extname(filePath);
|
||||
switch (extension) {
|
||||
case ".html":
|
||||
return "text/html; charset=utf-8";
|
||||
case ".css":
|
||||
return "text/css; charset=utf-8";
|
||||
case ".js":
|
||||
return "text/javascript; charset=utf-8";
|
||||
case ".json":
|
||||
return "application/json";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
192
.agents/scripts/status.mjs
Normal file
192
.agents/scripts/status.mjs
Normal file
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
existsSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
statSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const workspace = process.cwd();
|
||||
const agentsDir = path.join(workspace, ".agents");
|
||||
const runsDir = path.join(agentsDir, "runs");
|
||||
const checkpointPath = path.join(agentsDir, "checkpoint.json");
|
||||
const tailLines = parseTailLines(process.argv);
|
||||
|
||||
main();
|
||||
|
||||
function main() {
|
||||
const checkpoint = readJsonIfExists(checkpointPath);
|
||||
const runs = listRuns();
|
||||
const activeRunId = checkpoint?.active_run_id || findLatestRunId(runs);
|
||||
|
||||
printHeader(checkpoint, runs);
|
||||
|
||||
if (runs.length === 0) {
|
||||
console.log("No runs found under .agents/runs.");
|
||||
console.log("Next: create .agents/runs/<run-id>/ with state.json and agents.json.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const run of runs) {
|
||||
printRun(run, run.id === activeRunId);
|
||||
}
|
||||
|
||||
const activeRun = runs.find((run) => run.id === activeRunId);
|
||||
if (activeRun) {
|
||||
printProgressTail(activeRun);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTailLines(argv) {
|
||||
const tailIndex = argv.findIndex((arg) => arg === "--tail" || arg === "-n");
|
||||
if (tailIndex === -1) return 30;
|
||||
const value = Number(argv[tailIndex + 1]);
|
||||
if (!Number.isFinite(value) || value < 0) return 30;
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function printHeader(checkpoint, runs) {
|
||||
console.log("Agent Orchestration Status");
|
||||
console.log("==========================");
|
||||
console.log(`Workspace: ${workspace}`);
|
||||
console.log(`Runs: ${runs.length}`);
|
||||
console.log(`Active run: ${checkpoint?.active_run_id || "(none)"}`);
|
||||
console.log(`Current phase: ${checkpoint?.current_phase || "(unknown)"}`);
|
||||
console.log(`Active branch: ${checkpoint?.active_branch || "(none)"}`);
|
||||
console.log(`Next action: ${checkpoint?.next_action || "(none recorded)"}`);
|
||||
|
||||
const blockers = Array.isArray(checkpoint?.blockers) ? checkpoint.blockers : [];
|
||||
if (blockers.length > 0) {
|
||||
console.log("Blockers:");
|
||||
for (const blocker of blockers) {
|
||||
console.log(`- ${blocker}`);
|
||||
}
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
function listRuns() {
|
||||
if (!existsSync(runsDir)) return [];
|
||||
|
||||
return readdirSync(runsDir)
|
||||
.map((name) => path.join(runsDir, name))
|
||||
.filter((runPath) => statSync(runPath).isDirectory())
|
||||
.map((runPath) => {
|
||||
const id = path.basename(runPath);
|
||||
return {
|
||||
id,
|
||||
path: runPath,
|
||||
state: readJsonIfExists(path.join(runPath, "state.json")),
|
||||
agents: normalizeAgents(readJsonIfExists(path.join(runPath, "agents.json"))),
|
||||
mtimeMs: statSync(runPath).mtimeMs,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
function normalizeAgents(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
if (Array.isArray(value.agents)) return value.agents;
|
||||
return Object.entries(value).map(([role, agent]) => ({
|
||||
role,
|
||||
...(typeof agent === "object" && agent ? agent : { status: String(agent) }),
|
||||
}));
|
||||
}
|
||||
|
||||
function findLatestRunId(runs) {
|
||||
if (runs.length === 0) return null;
|
||||
return runs.reduce((latest, run) => (run.mtimeMs > latest.mtimeMs ? run : latest)).id;
|
||||
}
|
||||
|
||||
function printRun(run, isActive) {
|
||||
const state = run.state || {};
|
||||
const marker = isActive ? "*" : "-";
|
||||
console.log(`${marker} Run: ${run.id}`);
|
||||
console.log(` Phase: ${state.current_phase || state.phase || "(unknown)"}`);
|
||||
console.log(` Branch: ${state.current_branch || state.branch || "(none)"}`);
|
||||
console.log(` Expected output: ${state.expected_output_artifact || state.expected_output || "(none)"}`);
|
||||
console.log(` Retry count: ${state.retry_count ?? 0}`);
|
||||
console.log(` Next action: ${state.next_action || "(none recorded)"}`);
|
||||
if (state.blocker) console.log(` Blocker: ${state.blocker}`);
|
||||
|
||||
if (run.agents.length === 0) {
|
||||
console.log(" Agents: none recorded");
|
||||
} else {
|
||||
console.log(" Agents:");
|
||||
for (const agent of run.agents) {
|
||||
printAgent(agent);
|
||||
}
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
function printAgent(agent) {
|
||||
const role = agent.role || "(unknown-role)";
|
||||
const status = agent.status || "(unknown)";
|
||||
const heartbeat = agent.heartbeat_at || "(none)";
|
||||
const lease = agent.lease_expires_at || "(none)";
|
||||
const expired = isExpired(agent.lease_expires_at);
|
||||
const thread = agent.thread_id || agent.process_id || agent.tool_call_id || "(none)";
|
||||
|
||||
console.log(` - ${role}: ${status}${expired ? " [lease expired]" : ""}`);
|
||||
console.log(` heartbeat: ${heartbeat}`);
|
||||
console.log(` lease: ${lease}`);
|
||||
console.log(` id: ${thread}`);
|
||||
if (agent.last_error) {
|
||||
console.log(` last_error: ${agent.last_error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printProgressTail(run) {
|
||||
if (tailLines === 0) return;
|
||||
|
||||
const candidateNames = [
|
||||
"progress.log",
|
||||
"implementation-log.md",
|
||||
"validation.md",
|
||||
"opus-proposal.md",
|
||||
"snarky-review.md",
|
||||
];
|
||||
const progressPath = candidateNames
|
||||
.map((name) => path.join(run.path, name))
|
||||
.find((candidate) => existsSync(candidate) && statSync(candidate).isFile());
|
||||
|
||||
console.log("Progress Tail");
|
||||
console.log("=============");
|
||||
|
||||
if (!progressPath) {
|
||||
console.log(`No progress file found for run ${run.id}.`);
|
||||
console.log("Expected one of: progress.log, implementation-log.md, validation.md.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${path.relative(workspace, progressPath)} last ${tailLines} lines:`);
|
||||
const lines = readFileSync(progressPath, "utf8").split(/\r?\n/);
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
||||
lines.pop();
|
||||
}
|
||||
const tail = lines.slice(Math.max(0, lines.length - tailLines));
|
||||
for (const line of tail) {
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
function isExpired(value) {
|
||||
if (!value) return false;
|
||||
const time = Date.parse(value);
|
||||
if (!Number.isFinite(time)) return false;
|
||||
return time < Date.now();
|
||||
}
|
||||
|
||||
function readJsonIfExists(filePath) {
|
||||
if (!existsSync(filePath)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
} catch (error) {
|
||||
return {
|
||||
parse_error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user