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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Session Reader — reads session data from .archbyte/sessions/
|
|
2
|
+
import { readFile, readdir } from "fs/promises";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
function sessionsDir(projectRoot) {
|
|
6
|
+
return path.join(projectRoot, ".archbyte", "sessions");
|
|
7
|
+
}
|
|
8
|
+
/** Load session index — reads index.json, or scans session dirs if missing */
|
|
9
|
+
export async function loadSessionIndex(projectRoot) {
|
|
10
|
+
const dir = sessionsDir(projectRoot);
|
|
11
|
+
const indexPath = path.join(dir, "index.json");
|
|
12
|
+
// Try cached index first
|
|
13
|
+
if (existsSync(indexPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = readFileSync(indexPath, "utf-8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Corrupted — rebuild
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Scan session directories
|
|
23
|
+
return rebuildIndex(projectRoot);
|
|
24
|
+
}
|
|
25
|
+
/** Rebuild index by scanning all session dirs */
|
|
26
|
+
async function rebuildIndex(projectRoot) {
|
|
27
|
+
const dir = sessionsDir(projectRoot);
|
|
28
|
+
if (!existsSync(dir))
|
|
29
|
+
return { sessions: [] };
|
|
30
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
31
|
+
const sessions = [];
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.isDirectory())
|
|
34
|
+
continue;
|
|
35
|
+
const sessionPath = path.join(dir, entry.name, "session.json");
|
|
36
|
+
if (!existsSync(sessionPath))
|
|
37
|
+
continue;
|
|
38
|
+
try {
|
|
39
|
+
const raw = await readFile(sessionPath, "utf-8");
|
|
40
|
+
const session = JSON.parse(raw);
|
|
41
|
+
sessions.push({
|
|
42
|
+
sessionId: session.sessionId,
|
|
43
|
+
startedAt: session.startedAt,
|
|
44
|
+
completedAt: session.completedAt,
|
|
45
|
+
status: session.status,
|
|
46
|
+
runCount: session.summary.totalRuns,
|
|
47
|
+
phases: session.summary.phases,
|
|
48
|
+
source: session.source,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Skip corrupted session files
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Sort newest first
|
|
56
|
+
sessions.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
57
|
+
return { sessions };
|
|
58
|
+
}
|
|
59
|
+
/** Load a full session by ID */
|
|
60
|
+
export async function loadSession(projectRoot, sessionId) {
|
|
61
|
+
const sessionPath = path.join(sessionsDir(projectRoot), sessionId, "session.json");
|
|
62
|
+
if (!existsSync(sessionPath))
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
const raw = await readFile(sessionPath, "utf-8");
|
|
66
|
+
return JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Read recent activity log entries (newest first) */
|
|
73
|
+
export async function loadRecentActivity(projectRoot, limit = 50) {
|
|
74
|
+
const logPath = path.join(sessionsDir(projectRoot), "activity.log");
|
|
75
|
+
if (!existsSync(logPath))
|
|
76
|
+
return [];
|
|
77
|
+
try {
|
|
78
|
+
const raw = await readFile(logPath, "utf-8");
|
|
79
|
+
const lines = raw.trim().split("\n").filter(Boolean);
|
|
80
|
+
// Return newest first, limited
|
|
81
|
+
return lines.slice(-limit).reverse();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export interface TranscriptEvent {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
type: 'user' | 'assistant' | 'thinking' | 'tool_use' | 'tool_result' | 'system';
|
|
4
|
+
agentId?: string;
|
|
5
|
+
agentSlug?: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
toolName?: string;
|
|
8
|
+
toolUseId?: string;
|
|
9
|
+
toolInput?: string;
|
|
10
|
+
toolResult?: string;
|
|
11
|
+
toolSuccess?: boolean;
|
|
12
|
+
model?: string;
|
|
13
|
+
usage?: {
|
|
14
|
+
inputTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
cacheReadTokens: number;
|
|
17
|
+
cacheCreationTokens: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface CloudStats {
|
|
21
|
+
totalInputTokens: number;
|
|
22
|
+
totalOutputTokens: number;
|
|
23
|
+
totalCacheReadTokens: number;
|
|
24
|
+
totalCacheCreationTokens: number;
|
|
25
|
+
estimatedCost: {
|
|
26
|
+
input: number;
|
|
27
|
+
output: number;
|
|
28
|
+
cacheRead: number;
|
|
29
|
+
cacheCreation: number;
|
|
30
|
+
total: number;
|
|
31
|
+
};
|
|
32
|
+
models: string[];
|
|
33
|
+
apiCalls: number;
|
|
34
|
+
}
|
|
35
|
+
export interface PayloadFile {
|
|
36
|
+
path: string;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
agentId?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface AgentRun {
|
|
41
|
+
runId: string;
|
|
42
|
+
sessionId: string;
|
|
43
|
+
phase: string;
|
|
44
|
+
passId: string;
|
|
45
|
+
agent: string;
|
|
46
|
+
model: string;
|
|
47
|
+
iteration: number;
|
|
48
|
+
startedAt: string;
|
|
49
|
+
completedAt?: string;
|
|
50
|
+
elapsedSeconds?: number;
|
|
51
|
+
status: "running" | "success" | "error" | "skipped";
|
|
52
|
+
error?: string;
|
|
53
|
+
tokens?: {
|
|
54
|
+
input: number;
|
|
55
|
+
output: number;
|
|
56
|
+
};
|
|
57
|
+
cost?: {
|
|
58
|
+
input: number;
|
|
59
|
+
output: number;
|
|
60
|
+
total: number;
|
|
61
|
+
};
|
|
62
|
+
toolCalls?: Array<{
|
|
63
|
+
name: string;
|
|
64
|
+
durationMs: number;
|
|
65
|
+
success: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
artifacts?: {
|
|
68
|
+
prompt?: string;
|
|
69
|
+
output?: string;
|
|
70
|
+
error?: string;
|
|
71
|
+
};
|
|
72
|
+
agentType?: string;
|
|
73
|
+
transcript?: TranscriptEvent[];
|
|
74
|
+
}
|
|
75
|
+
export interface AgentSession {
|
|
76
|
+
sessionId: string;
|
|
77
|
+
projectName?: string;
|
|
78
|
+
startedAt: string;
|
|
79
|
+
completedAt?: string;
|
|
80
|
+
status: "running" | "success" | "error" | "partial";
|
|
81
|
+
source: string;
|
|
82
|
+
projectMeta?: {
|
|
83
|
+
topic?: string;
|
|
84
|
+
mode?: string;
|
|
85
|
+
model?: string;
|
|
86
|
+
style?: string;
|
|
87
|
+
};
|
|
88
|
+
summary: {
|
|
89
|
+
totalRuns: number;
|
|
90
|
+
successfulRuns: number;
|
|
91
|
+
failedRuns: number;
|
|
92
|
+
skippedRuns: number;
|
|
93
|
+
totalElapsedSeconds: number;
|
|
94
|
+
totalTokens: {
|
|
95
|
+
input: number;
|
|
96
|
+
output: number;
|
|
97
|
+
};
|
|
98
|
+
phases: string[];
|
|
99
|
+
models: string[];
|
|
100
|
+
};
|
|
101
|
+
runs: AgentRun[];
|
|
102
|
+
transcript?: TranscriptEvent[];
|
|
103
|
+
cloudStats?: CloudStats;
|
|
104
|
+
payload?: PayloadFile[];
|
|
105
|
+
}
|
|
106
|
+
export interface SessionIndex {
|
|
107
|
+
sessions: Array<{
|
|
108
|
+
sessionId: string;
|
|
109
|
+
startedAt: string;
|
|
110
|
+
completedAt?: string;
|
|
111
|
+
status: AgentSession["status"];
|
|
112
|
+
runCount: number;
|
|
113
|
+
phases: string[];
|
|
114
|
+
source: string;
|
|
115
|
+
category?: string;
|
|
116
|
+
label?: string;
|
|
117
|
+
touchedDirs?: string[];
|
|
118
|
+
eventCount?: number;
|
|
119
|
+
dirMetrics?: Record<string, {
|
|
120
|
+
reads: number;
|
|
121
|
+
writes: number;
|
|
122
|
+
}>;
|
|
123
|
+
estimatedCost?: number;
|
|
124
|
+
}>;
|
|
125
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentRun, AgentSession } from "./types.js";
|
|
2
|
+
export declare function createSession(projectRoot: string, sessionId: string, opts?: {
|
|
3
|
+
projectName?: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
}): Promise<AgentSession>;
|
|
6
|
+
export declare function writeRun(projectRoot: string, sessionId: string, run: AgentRun): Promise<void>;
|
|
7
|
+
export declare function completeSession(projectRoot: string, sessionId: string, runs: AgentRun[]): Promise<void>;
|
|
8
|
+
export declare function updateSessionIndex(projectRoot: string): Promise<void>;
|
|
9
|
+
export declare function appendActivityLog(projectRoot: string, entry: Record<string, unknown>): Promise<void>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Session Writer — writes session data to .archbyte/sessions/
|
|
2
|
+
import { mkdir, writeFile, appendFile } from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { loadSessionIndex } from "./reader.js";
|
|
5
|
+
function sessionsDir(projectRoot) {
|
|
6
|
+
return path.join(projectRoot, ".archbyte", "sessions");
|
|
7
|
+
}
|
|
8
|
+
export async function createSession(projectRoot, sessionId, opts) {
|
|
9
|
+
const dir = path.join(sessionsDir(projectRoot), sessionId);
|
|
10
|
+
await mkdir(path.join(dir, "runs"), { recursive: true });
|
|
11
|
+
const session = {
|
|
12
|
+
sessionId,
|
|
13
|
+
projectName: opts?.projectName,
|
|
14
|
+
startedAt: new Date().toISOString(),
|
|
15
|
+
status: "running",
|
|
16
|
+
source: opts?.source ?? "archbyte",
|
|
17
|
+
summary: {
|
|
18
|
+
totalRuns: 0,
|
|
19
|
+
successfulRuns: 0,
|
|
20
|
+
failedRuns: 0,
|
|
21
|
+
skippedRuns: 0,
|
|
22
|
+
totalElapsedSeconds: 0,
|
|
23
|
+
totalTokens: { input: 0, output: 0 },
|
|
24
|
+
phases: [],
|
|
25
|
+
models: [],
|
|
26
|
+
},
|
|
27
|
+
runs: [],
|
|
28
|
+
};
|
|
29
|
+
await writeFile(path.join(dir, "session.json"), JSON.stringify(session, null, 2), "utf-8");
|
|
30
|
+
return session;
|
|
31
|
+
}
|
|
32
|
+
export async function writeRun(projectRoot, sessionId, run) {
|
|
33
|
+
const runDir = path.join(sessionsDir(projectRoot), sessionId, "runs", run.runId);
|
|
34
|
+
await mkdir(runDir, { recursive: true });
|
|
35
|
+
await writeFile(path.join(runDir, "meta.json"), JSON.stringify(run, null, 2), "utf-8");
|
|
36
|
+
// Write optional artifacts
|
|
37
|
+
if (run.artifacts?.prompt) {
|
|
38
|
+
await writeFile(path.join(runDir, "prompt.md"), run.artifacts.prompt, "utf-8");
|
|
39
|
+
}
|
|
40
|
+
if (run.artifacts?.output) {
|
|
41
|
+
await writeFile(path.join(runDir, "output.md"), run.artifacts.output, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
if (run.artifacts?.error) {
|
|
44
|
+
await writeFile(path.join(runDir, "error.txt"), run.artifacts.error, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
// Append to activity log
|
|
47
|
+
await appendActivityLog(projectRoot, {
|
|
48
|
+
type: "run",
|
|
49
|
+
sessionId,
|
|
50
|
+
runId: run.runId,
|
|
51
|
+
agent: run.agent,
|
|
52
|
+
status: run.status,
|
|
53
|
+
timestamp: run.completedAt ?? run.startedAt,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function completeSession(projectRoot, sessionId, runs) {
|
|
57
|
+
const dir = path.join(sessionsDir(projectRoot), sessionId);
|
|
58
|
+
const successfulRuns = runs.filter((r) => r.status === "success").length;
|
|
59
|
+
const failedRuns = runs.filter((r) => r.status === "error").length;
|
|
60
|
+
const skippedRuns = runs.filter((r) => r.status === "skipped").length;
|
|
61
|
+
const totalElapsed = runs.reduce((s, r) => s + (r.elapsedSeconds ?? 0), 0);
|
|
62
|
+
const totalTokensIn = runs.reduce((s, r) => s + (r.tokens?.input ?? 0), 0);
|
|
63
|
+
const totalTokensOut = runs.reduce((s, r) => s + (r.tokens?.output ?? 0), 0);
|
|
64
|
+
const phases = [...new Set(runs.map((r) => r.phase))];
|
|
65
|
+
const models = [...new Set(runs.map((r) => r.model))];
|
|
66
|
+
const hasErrors = failedRuns > 0;
|
|
67
|
+
const allFailed = failedRuns === runs.length;
|
|
68
|
+
const session = {
|
|
69
|
+
sessionId,
|
|
70
|
+
startedAt: runs[0]?.startedAt ?? new Date().toISOString(),
|
|
71
|
+
completedAt: new Date().toISOString(),
|
|
72
|
+
status: allFailed ? "error" : hasErrors ? "partial" : "success",
|
|
73
|
+
source: "archbyte",
|
|
74
|
+
summary: {
|
|
75
|
+
totalRuns: runs.length,
|
|
76
|
+
successfulRuns,
|
|
77
|
+
failedRuns,
|
|
78
|
+
skippedRuns,
|
|
79
|
+
totalElapsedSeconds: totalElapsed,
|
|
80
|
+
totalTokens: { input: totalTokensIn, output: totalTokensOut },
|
|
81
|
+
phases,
|
|
82
|
+
models,
|
|
83
|
+
},
|
|
84
|
+
runs,
|
|
85
|
+
};
|
|
86
|
+
await writeFile(path.join(dir, "session.json"), JSON.stringify(session, null, 2), "utf-8");
|
|
87
|
+
// Rebuild index
|
|
88
|
+
await updateSessionIndex(projectRoot);
|
|
89
|
+
}
|
|
90
|
+
export async function updateSessionIndex(projectRoot) {
|
|
91
|
+
const index = await loadSessionIndex(projectRoot);
|
|
92
|
+
await mkdir(sessionsDir(projectRoot), { recursive: true });
|
|
93
|
+
await writeFile(path.join(sessionsDir(projectRoot), "index.json"), JSON.stringify(index, null, 2), "utf-8");
|
|
94
|
+
}
|
|
95
|
+
export async function appendActivityLog(projectRoot, entry) {
|
|
96
|
+
const logPath = path.join(sessionsDir(projectRoot), "activity.log");
|
|
97
|
+
await mkdir(sessionsDir(projectRoot), { recursive: true });
|
|
98
|
+
const line = JSON.stringify({ ...entry, _ts: new Date().toISOString() }) + "\n";
|
|
99
|
+
await appendFile(logPath, line, "utf-8");
|
|
100
|
+
}
|
|
@@ -6,7 +6,7 @@ import type { IncrementalContext } from "./types.js";
|
|
|
6
6
|
* Run the multi-agent pipeline: 3 parallel fast agents → 2 sequential agents.
|
|
7
7
|
* Each agent gets a single chat() call with pre-collected static context.
|
|
8
8
|
*/
|
|
9
|
-
export declare function runPipeline(ctx: StaticContext, provider: LLMProvider, config: ArchByteConfig, onProgress?: (msg: string) => void, incrementalContext?: IncrementalContext, onDebug?: (agentId: string, model: string, system: string, user: string) => void): Promise<StaticAnalysisResult & {
|
|
9
|
+
export declare function runPipeline(ctx: StaticContext, provider: LLMProvider, config: ArchByteConfig, onProgress?: (msg: string) => void, incrementalContext?: IncrementalContext, onDebug?: (agentId: string, model: string, system: string, user: string) => void, projectRoot?: string): Promise<StaticAnalysisResult & {
|
|
10
10
|
tokenUsage?: {
|
|
11
11
|
input: number;
|
|
12
12
|
output: number;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Pipeline — Orchestrator
|
|
2
2
|
// Runs static context collection → parallel LLM agents → sequential LLM agents
|
|
3
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
3
5
|
import { resolveModel } from "../runtime/types.js";
|
|
4
6
|
import { validateAnalysis } from "../static/validator.js";
|
|
5
7
|
import { componentIdentifier } from "./agents/component-identifier.js";
|
|
@@ -92,14 +94,40 @@ function getFallbackData(agentId, inc) {
|
|
|
92
94
|
* Run the multi-agent pipeline: 3 parallel fast agents → 2 sequential agents.
|
|
93
95
|
* Each agent gets a single chat() call with pre-collected static context.
|
|
94
96
|
*/
|
|
95
|
-
export async function runPipeline(ctx, provider, config, onProgress, incrementalContext, onDebug) {
|
|
97
|
+
export async function runPipeline(ctx, provider, config, onProgress, incrementalContext, onDebug, projectRoot) {
|
|
96
98
|
const agentResults = {};
|
|
97
99
|
const agentMeta = [];
|
|
98
100
|
const skippedAgents = [];
|
|
101
|
+
const sessionId = `archbyte-${Date.now()}`;
|
|
102
|
+
const sessionRuns = [];
|
|
99
103
|
// Pass incremental context to agents via priorResults
|
|
100
104
|
if (incrementalContext) {
|
|
101
105
|
agentResults["__incremental__"] = incrementalContext;
|
|
102
106
|
}
|
|
107
|
+
// Helper: record an agent run for session observability
|
|
108
|
+
function recordRun(agent, result, status, error) {
|
|
109
|
+
const model = resolveModel(config.provider, agent.modelTier, config.modelOverrides, config.model);
|
|
110
|
+
const run = {
|
|
111
|
+
runId: `${agent.id}-${Date.now()}`,
|
|
112
|
+
sessionId,
|
|
113
|
+
phase: agent.id === "connection-mapper" || agent.id === "validator" ? "sequential" : "parallel",
|
|
114
|
+
passId: agent.id,
|
|
115
|
+
agent: agent.name,
|
|
116
|
+
model,
|
|
117
|
+
iteration: 1,
|
|
118
|
+
startedAt: result ? new Date(Date.now() - result.duration).toISOString() : new Date().toISOString(),
|
|
119
|
+
completedAt: new Date().toISOString(),
|
|
120
|
+
elapsedSeconds: result ? result.duration / 1000 : 0,
|
|
121
|
+
status,
|
|
122
|
+
error,
|
|
123
|
+
tokens: result?.tokenUsage ? { input: result.tokenUsage.input, output: result.tokenUsage.output } : undefined,
|
|
124
|
+
artifacts: result?.prompt || result?.rawOutput ? {
|
|
125
|
+
...(result.prompt ? { prompt: result.prompt } : {}),
|
|
126
|
+
...(result.rawOutput ? { output: result.rawOutput } : {}),
|
|
127
|
+
} : undefined,
|
|
128
|
+
};
|
|
129
|
+
sessionRuns.push(run);
|
|
130
|
+
}
|
|
103
131
|
// === Phase 1: Parallel agents ===
|
|
104
132
|
onProgress?.(`Phase 1: Running ${PARALLEL_AGENTS.length} agents in parallel...`);
|
|
105
133
|
// Pass agentResults to parallel agents too (contains __incremental__ if set)
|
|
@@ -123,17 +151,21 @@ export async function runPipeline(ctx, provider, config, onProgress, incremental
|
|
|
123
151
|
let authFailed = false;
|
|
124
152
|
for (let i = 0; i < parallelTasks.length; i++) {
|
|
125
153
|
const { agent, skipReason } = parallelTasks[i];
|
|
126
|
-
if (skipReason)
|
|
127
|
-
|
|
154
|
+
if (skipReason) {
|
|
155
|
+
recordRun(agent, null, "skipped");
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
128
158
|
const result = parallelResults[i];
|
|
129
159
|
if (result.status === "fulfilled" && result.value) {
|
|
130
160
|
agentResults[agent.id] = result.value.data;
|
|
131
161
|
agentMeta.push(result.value);
|
|
132
162
|
onProgress?.(` ${agent.name}: done (${(result.value.duration / 1000).toFixed(1)}s)`);
|
|
163
|
+
recordRun(agent, result.value, "success");
|
|
133
164
|
}
|
|
134
165
|
else {
|
|
135
166
|
const reason = result.status === "rejected" ? result.reason : "null response";
|
|
136
167
|
onProgress?.(` ${agent.name}: failed (${reason})`);
|
|
168
|
+
recordRun(agent, null, "error", String(reason));
|
|
137
169
|
if (result.status === "rejected" && isAuthError(result.reason)) {
|
|
138
170
|
authFailed = true;
|
|
139
171
|
}
|
|
@@ -153,6 +185,7 @@ export async function runPipeline(ctx, provider, config, onProgress, incremental
|
|
|
153
185
|
onProgress?.(` ${agent.name}: skipped (${skipReason})`);
|
|
154
186
|
const fallback = getFallbackData(agent.id, incrementalContext);
|
|
155
187
|
agentResults[agent.id] = fallback;
|
|
188
|
+
recordRun(agent, null, "skipped");
|
|
156
189
|
continue;
|
|
157
190
|
}
|
|
158
191
|
try {
|
|
@@ -161,13 +194,16 @@ export async function runPipeline(ctx, provider, config, onProgress, incremental
|
|
|
161
194
|
agentResults[agent.id] = result.data;
|
|
162
195
|
agentMeta.push(result);
|
|
163
196
|
onProgress?.(` ${agent.name}: done (${(result.duration / 1000).toFixed(1)}s)`);
|
|
197
|
+
recordRun(agent, result, "success");
|
|
164
198
|
}
|
|
165
199
|
else {
|
|
166
200
|
onProgress?.(` ${agent.name}: returned null`);
|
|
201
|
+
recordRun(agent, null, "error", "returned null");
|
|
167
202
|
}
|
|
168
203
|
}
|
|
169
204
|
catch (err) {
|
|
170
205
|
onProgress?.(` ${agent.name}: failed (${err instanceof Error ? err.message : String(err)})`);
|
|
206
|
+
recordRun(agent, null, "error", err instanceof Error ? err.message : String(err));
|
|
171
207
|
if (isAuthError(err)) {
|
|
172
208
|
throw new Error(`API key authentication failed. Check your API key with: archbyte config set api-key <your-key>\n` +
|
|
173
209
|
` Current provider: ${config.provider}. Get a valid key from your provider's dashboard.`);
|
|
@@ -204,6 +240,50 @@ export async function runPipeline(ctx, provider, config, onProgress, incremental
|
|
|
204
240
|
console.error();
|
|
205
241
|
onProgress?.(`Token usage: ${totalInput} in / ${totalOutput} out${skippedAgents.length > 0 ? ` (${skippedAgents.length} skipped)` : ""}`);
|
|
206
242
|
}
|
|
243
|
+
// === Write runs to .archbyte/runs/ (best-effort, fire-and-forget) ===
|
|
244
|
+
if (projectRoot) {
|
|
245
|
+
const sessionStartedAt = sessionRuns.length > 0
|
|
246
|
+
? sessionRuns.reduce((earliest, r) => r.startedAt < earliest ? r.startedAt : earliest, sessionRuns[0].startedAt)
|
|
247
|
+
: new Date().toISOString();
|
|
248
|
+
const sessionCompletedAt = new Date().toISOString();
|
|
249
|
+
const writeRuns = async () => {
|
|
250
|
+
const runsDir = path.join(projectRoot, ".archbyte", "runs");
|
|
251
|
+
for (const run of sessionRuns) {
|
|
252
|
+
const agentDir = path.join(runsDir, run.passId);
|
|
253
|
+
await mkdir(agentDir, { recursive: true });
|
|
254
|
+
// meta.json — AgentRun fields minus artifacts
|
|
255
|
+
const { artifacts, ...meta } = run;
|
|
256
|
+
await writeFile(path.join(agentDir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
257
|
+
// prompt.md and output.md from artifacts
|
|
258
|
+
if (artifacts?.prompt) {
|
|
259
|
+
await writeFile(path.join(agentDir, "prompt.md"), artifacts.prompt);
|
|
260
|
+
}
|
|
261
|
+
if (artifacts?.output) {
|
|
262
|
+
await writeFile(path.join(agentDir, "output.md"), artifacts.output);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// session.json — lightweight summary
|
|
266
|
+
const successCount = sessionRuns.filter(r => r.status === "success").length;
|
|
267
|
+
const failedCount = sessionRuns.filter(r => r.status === "error").length;
|
|
268
|
+
const session = {
|
|
269
|
+
sessionId,
|
|
270
|
+
startedAt: sessionStartedAt,
|
|
271
|
+
completedAt: sessionCompletedAt,
|
|
272
|
+
status: failedCount > 0 ? "partial" : "success",
|
|
273
|
+
source: "archbyte",
|
|
274
|
+
mode: "pipeline",
|
|
275
|
+
totalRuns: sessionRuns.length,
|
|
276
|
+
successfulRuns: successCount,
|
|
277
|
+
failedRuns: failedCount,
|
|
278
|
+
totalTokens: { input: totalInput, output: totalOutput },
|
|
279
|
+
totalElapsedSeconds: sessionRuns.reduce((s, r) => s + (r.elapsedSeconds ?? 0), 0),
|
|
280
|
+
};
|
|
281
|
+
await writeFile(path.join(projectRoot, ".archbyte", "session.json"), JSON.stringify(session, null, 2));
|
|
282
|
+
};
|
|
283
|
+
writeRuns().catch((err) => {
|
|
284
|
+
console.error("[pipeline] Failed to write runs:", err);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
207
287
|
return { ...merged, tokenUsage: { input: totalInput, output: totalOutput }, skippedAgents: skippedAgents.length > 0 ? skippedAgents : undefined };
|
|
208
288
|
}
|
|
209
289
|
// Agents that produce large structured output (lists of components/connections) need more tokens
|
|
@@ -248,5 +328,7 @@ async function runAgent(agent, ctx, provider, config, priorResults, onProgress,
|
|
|
248
328
|
data,
|
|
249
329
|
duration: Date.now() - start,
|
|
250
330
|
tokenUsage: { input: response.usage.inputTokens, output: response.usage.outputTokens },
|
|
331
|
+
prompt: system + "\n\n---\n\n" + user,
|
|
332
|
+
rawOutput: text,
|
|
251
333
|
};
|
|
252
334
|
}
|
package/dist/cli/config.js
CHANGED
|
@@ -96,6 +96,14 @@ function showConfig() {
|
|
|
96
96
|
console.log(` ${chalk.bold("model")}: ${config.model}`);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
const sessionsPaths = config.sessionsPaths;
|
|
100
|
+
if (Array.isArray(sessionsPaths) && sessionsPaths.length > 0) {
|
|
101
|
+
console.log();
|
|
102
|
+
console.log(` ${chalk.bold("sessions-path")}:`);
|
|
103
|
+
for (const p of sessionsPaths) {
|
|
104
|
+
console.log(` ${p}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
99
107
|
console.log();
|
|
100
108
|
}
|
|
101
109
|
function setConfig(key, value) {
|
|
@@ -151,9 +159,15 @@ function setConfig(key, value) {
|
|
|
151
159
|
profiles[activeProvider2].model = value;
|
|
152
160
|
break;
|
|
153
161
|
}
|
|
162
|
+
case "sessions-path":
|
|
163
|
+
case "sessionsPath": {
|
|
164
|
+
// External directory paths for session observability (comma-separated)
|
|
165
|
+
config.sessionsPaths = value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
154
168
|
default:
|
|
155
169
|
console.error(chalk.red(`Unknown config key: ${key}`));
|
|
156
|
-
console.error(chalk.gray(" Valid keys: provider, api-key, model"));
|
|
170
|
+
console.error(chalk.gray(" Valid keys: provider, api-key, model, sessions-path"));
|
|
157
171
|
process.exit(1);
|
|
158
172
|
}
|
|
159
173
|
saveConfig(config);
|
|
@@ -182,6 +196,12 @@ function getConfig(key, raw = false) {
|
|
|
182
196
|
case "model":
|
|
183
197
|
console.log(profile?.model ?? config.model ?? "");
|
|
184
198
|
break;
|
|
199
|
+
case "sessions-path":
|
|
200
|
+
case "sessionsPath": {
|
|
201
|
+
const paths = config.sessionsPaths;
|
|
202
|
+
console.log(Array.isArray(paths) ? paths.join(", ") : "");
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
185
205
|
default:
|
|
186
206
|
console.error(chalk.red(`Unknown config key: ${key}`));
|
|
187
207
|
process.exit(1);
|