@taicho-ai/cli 0.1.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/README.md +4 -0
- package/package.json +46 -0
- package/src/index.tsx +334 -0
- package/src/ui/AgentBlock.tsx +183 -0
- package/src/ui/App.tsx +1404 -0
- package/src/ui/ArtifactBrowser.tsx +451 -0
- package/src/ui/ChatInput.tsx +146 -0
- package/src/ui/OperationView.tsx +185 -0
- package/src/ui/OrgBrowser.tsx +489 -0
- package/src/ui/PlanPanel.tsx +83 -0
- package/src/ui/ProposalCard.tsx +96 -0
- package/src/ui/QuestionCard.tsx +61 -0
- package/src/ui/SquadPanes.tsx +155 -0
- package/src/ui/StatusBar.tsx +72 -0
- package/src/ui/WorkflowBrowser.tsx +141 -0
- package/src/ui/banner.ts +10 -0
- package/src/ui/browser-model.ts +170 -0
- package/src/ui/editor-handoff.ts +51 -0
- package/src/ui/input-history.ts +55 -0
- package/src/ui/input-keys.ts +51 -0
- package/src/ui/input.ts +18 -0
- package/src/ui/markdown-stream.ts +31 -0
- package/src/ui/markdown.ts +45 -0
- package/src/ui/org-browser-model.ts +43 -0
- package/src/ui/slash.ts +270 -0
- package/src/ui/text-buffer.ts +92 -0
- package/src/ui/transcript-hydrate.ts +51 -0
- package/src/ui/transcript-style.ts +124 -0
- package/src/ui/workflow-browser-model.ts +65 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/** Plan 13 — the operation view (drill-in). Opening a focused block gives it the full screen — this
|
|
2
|
+
* is where "see the whole thing" lives, so the squad surface can stay two lines.
|
|
3
|
+
*
|
|
4
|
+
* ← esc / <name>
|
|
5
|
+
* <name> · ✓ done · 19s · 4.5k tok · via <parent>
|
|
6
|
+
*
|
|
7
|
+
* BRIEF <the goal/brief this agent was given>
|
|
8
|
+
* OUTPUT <the agent's FULL, untrimmed output — scrollable>
|
|
9
|
+
* ↑↓ scroll · N more lines
|
|
10
|
+
* TOOLS <tools it ran, e.g. use_skill(...) · save_artifact>
|
|
11
|
+
* ARTIFACT <artifact@vN> → handed to <consumers>
|
|
12
|
+
*
|
|
13
|
+
* Data source: reuses the per-run evidence the /trace inspector already reads — the run's input.json
|
|
14
|
+
* (brief), transcript.jsonl / final.md (full output), trace.artifacts (artifact + consumers). Owns
|
|
15
|
+
* the keyboard while open via the cardKeyRef pattern (same as TraceInspector). */
|
|
16
|
+
import { useState, useEffect, useRef } from "react";
|
|
17
|
+
import { Box, Text, useInput } from "ink";
|
|
18
|
+
import { readTrace } from "@taicho-ai/framework/store/trace";
|
|
19
|
+
import { readRunTranscript, type RunTranscriptEvent } from "@taicho-ai/framework/store/run-transcript";
|
|
20
|
+
import { readArtifact } from "@taicho-ai/framework/store/artifacts";
|
|
21
|
+
import { artifactHandle } from "@taicho-ai/contracts/artifact";
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { paths } from "@taicho-ai/framework/store/files";
|
|
25
|
+
import type { CardKeyHandler } from "./ProposalCard";
|
|
26
|
+
|
|
27
|
+
interface OperationData {
|
|
28
|
+
runId: string;
|
|
29
|
+
agent: string;
|
|
30
|
+
brief: string;
|
|
31
|
+
output: string;
|
|
32
|
+
tools: string[];
|
|
33
|
+
artifact?: string;
|
|
34
|
+
tokens: number;
|
|
35
|
+
durationMs: number;
|
|
36
|
+
outcome: string;
|
|
37
|
+
parentAgent?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readFinalOutput(ws: string, runId: string): string {
|
|
41
|
+
const file = join(paths.runRecordDir(ws, runId), "final.md");
|
|
42
|
+
if (!existsSync(file)) return "";
|
|
43
|
+
try { return readFileSync(file, "utf8").trim(); }
|
|
44
|
+
catch { return ""; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readBrief(ws: string, runId: string): string {
|
|
48
|
+
const file = join(paths.runRecordDir(ws, runId), "input.json");
|
|
49
|
+
if (!existsSync(file)) return "";
|
|
50
|
+
try {
|
|
51
|
+
const data = JSON.parse(readFileSync(file, "utf8")) as { brief?: { goal?: string } };
|
|
52
|
+
return data.brief?.goal ?? "";
|
|
53
|
+
}
|
|
54
|
+
catch { return ""; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractTools(events: RunTranscriptEvent[]): string[] {
|
|
58
|
+
const tools: string[] = [];
|
|
59
|
+
for (const e of events) {
|
|
60
|
+
if (e.kind === "tool_start") {
|
|
61
|
+
const data = e.data as { tool?: string; argsPreview?: string } | undefined;
|
|
62
|
+
if (data?.tool) {
|
|
63
|
+
const args = data.argsPreview ? `(${data.argsPreview})` : "";
|
|
64
|
+
tools.push(`${data.tool}${args}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return tools;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function loadOperationData(ws: string, runId: string): OperationData | null {
|
|
72
|
+
const trace = readTrace(ws, runId);
|
|
73
|
+
if (!trace) return null;
|
|
74
|
+
|
|
75
|
+
const events = readRunTranscript(ws, runId);
|
|
76
|
+
const output = readFinalOutput(ws, runId);
|
|
77
|
+
const brief = readBrief(ws, runId);
|
|
78
|
+
|
|
79
|
+
let artifact: string | undefined;
|
|
80
|
+
if (trace.artifacts.length > 0) {
|
|
81
|
+
const handle = trace.artifacts[0];
|
|
82
|
+
artifact = typeof handle === "string" ? handle : artifactHandle(handle);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Duration is not stored on the trace; compute from transcript events if available
|
|
86
|
+
let durationMs = 0;
|
|
87
|
+
if (events.length >= 2) {
|
|
88
|
+
const first = Date.parse(events[0].ts);
|
|
89
|
+
const last = Date.parse(events[events.length - 1].ts);
|
|
90
|
+
if (!isNaN(first) && !isNaN(last)) durationMs = last - first;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
runId,
|
|
95
|
+
agent: trace.agent,
|
|
96
|
+
brief,
|
|
97
|
+
output,
|
|
98
|
+
tools: extractTools(events),
|
|
99
|
+
artifact,
|
|
100
|
+
tokens: trace.tokens ?? 0,
|
|
101
|
+
durationMs,
|
|
102
|
+
outcome: trace.outcome ?? "unknown",
|
|
103
|
+
parentAgent: undefined, // parentAgent is not stored on the trace
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const MAX_OUTPUT_LINES = 50;
|
|
108
|
+
|
|
109
|
+
export function OperationView({
|
|
110
|
+
ws,
|
|
111
|
+
runId,
|
|
112
|
+
width,
|
|
113
|
+
keyHandlerRef,
|
|
114
|
+
onClose,
|
|
115
|
+
}: {
|
|
116
|
+
ws: string;
|
|
117
|
+
runId: string;
|
|
118
|
+
width: number;
|
|
119
|
+
keyHandlerRef: React.MutableRefObject<CardKeyHandler | null>;
|
|
120
|
+
onClose: () => void;
|
|
121
|
+
}) {
|
|
122
|
+
const [scroll, setScroll] = useState(0);
|
|
123
|
+
const data = loadOperationData(ws, runId);
|
|
124
|
+
|
|
125
|
+
const outputLines = data?.output.split("\n") ?? [];
|
|
126
|
+
const visibleLines = Math.min(MAX_OUTPUT_LINES, outputLines.length);
|
|
127
|
+
const maxScroll = Math.max(0, outputLines.length - visibleLines);
|
|
128
|
+
|
|
129
|
+
const handler = (input: string, key: { escape: boolean; upArrow: boolean; downArrow: boolean; return: boolean; tab?: boolean }) => {
|
|
130
|
+
if (key.escape) { onClose(); return; }
|
|
131
|
+
if (key.upArrow) { setScroll((s) => Math.max(0, s - 1)); return; }
|
|
132
|
+
if (key.downArrow) { setScroll((s) => Math.min(maxScroll, s + 1)); return; }
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
keyHandlerRef.current = handler;
|
|
137
|
+
return () => { if (keyHandlerRef.current === handler) keyHandlerRef.current = null; };
|
|
138
|
+
}, [keyHandlerRef, maxScroll]);
|
|
139
|
+
|
|
140
|
+
if (!data) {
|
|
141
|
+
return (
|
|
142
|
+
<Box flexDirection="column" borderStyle="round" borderColor="gray" paddingX={1}>
|
|
143
|
+
<Text dimColor>no data for run {runId}</Text>
|
|
144
|
+
<Text dimColor>esc to close</Text>
|
|
145
|
+
</Box>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const secs = (data.durationMs / 1000).toFixed(1);
|
|
150
|
+
const outcomeIcon = data.outcome === "completed" ? "✓" : data.outcome === "failed" ? "✗" : "·";
|
|
151
|
+
const outcomeColor = data.outcome === "completed" ? "green" : data.outcome === "failed" ? "red" : "gray";
|
|
152
|
+
const via = data.parentAgent ? ` via ${data.parentAgent}` : "";
|
|
153
|
+
const headerLine = `${data.agent} · ${outcomeIcon} ${data.outcome} · ${secs}s · ${data.tokens} tok${via}`;
|
|
154
|
+
|
|
155
|
+
const visibleOutput = outputLines.slice(scroll, scroll + visibleLines);
|
|
156
|
+
const moreLines = outputLines.length - visibleLines;
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1} width={width}>
|
|
160
|
+
<Text dimColor>← esc / {data.agent}</Text>
|
|
161
|
+
<Text color={outcomeColor}>{headerLine}</Text>
|
|
162
|
+
<Text> </Text>
|
|
163
|
+
<Text color="cyan" bold>BRIEF</Text>
|
|
164
|
+
<Text dimColor>{data.brief || "(no brief)"}</Text>
|
|
165
|
+
<Text> </Text>
|
|
166
|
+
<Text color="cyan" bold>OUTPUT</Text>
|
|
167
|
+
{visibleOutput.map((line, i) => (
|
|
168
|
+
<Text key={i}>{line || " "}</Text>
|
|
169
|
+
))}
|
|
170
|
+
{moreLines > 0 && <Text dimColor> ↑↓ scroll · {moreLines} more lines</Text>}
|
|
171
|
+
<Text> </Text>
|
|
172
|
+
<Text color="cyan" bold>TOOLS</Text>
|
|
173
|
+
<Text dimColor>{data.tools.length > 0 ? data.tools.join(" · ") : "(no tools)"}</Text>
|
|
174
|
+
{data.artifact && (
|
|
175
|
+
<>
|
|
176
|
+
<Text> </Text>
|
|
177
|
+
<Text color="cyan" bold>ARTIFACT</Text>
|
|
178
|
+
<Text>{data.artifact}</Text>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
<Text> </Text>
|
|
182
|
+
<Text dimColor>esc to close</Text>
|
|
183
|
+
</Box>
|
|
184
|
+
);
|
|
185
|
+
}
|