hydra-os-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.
Files changed (64) hide show
  1. package/README.md +274 -0
  2. package/dist/app.d.ts +12 -0
  3. package/dist/app.js +127 -0
  4. package/dist/cli.d.ts +13 -0
  5. package/dist/cli.js +177 -0
  6. package/dist/clients/api.d.ts +115 -0
  7. package/dist/clients/api.js +123 -0
  8. package/dist/clients/qdrant.d.ts +39 -0
  9. package/dist/clients/qdrant.js +34 -0
  10. package/dist/clients/temporal.d.ts +37 -0
  11. package/dist/clients/temporal.js +32 -0
  12. package/dist/commands/agent.d.ts +4 -0
  13. package/dist/commands/agent.js +103 -0
  14. package/dist/commands/artifact.d.ts +4 -0
  15. package/dist/commands/artifact.js +42 -0
  16. package/dist/commands/config.d.ts +4 -0
  17. package/dist/commands/config.js +80 -0
  18. package/dist/commands/core.d.ts +4 -0
  19. package/dist/commands/core.js +79 -0
  20. package/dist/commands/index.d.ts +6 -0
  21. package/dist/commands/index.js +20 -0
  22. package/dist/commands/memory.d.ts +4 -0
  23. package/dist/commands/memory.js +24 -0
  24. package/dist/commands/registry.d.ts +23 -0
  25. package/dist/commands/registry.js +23 -0
  26. package/dist/commands/session.d.ts +4 -0
  27. package/dist/commands/session.js +15 -0
  28. package/dist/commands/workflow.d.ts +5 -0
  29. package/dist/commands/workflow.js +301 -0
  30. package/dist/config.d.ts +152 -0
  31. package/dist/config.js +91 -0
  32. package/dist/screens/help.d.ts +5 -0
  33. package/dist/screens/help.js +14 -0
  34. package/dist/screens/main.d.ts +5 -0
  35. package/dist/screens/main.js +5 -0
  36. package/dist/screens/workflow-detail.d.ts +9 -0
  37. package/dist/screens/workflow-detail.js +11 -0
  38. package/dist/screens/workflow-list.d.ts +5 -0
  39. package/dist/screens/workflow-list.js +10 -0
  40. package/dist/sse.d.ts +16 -0
  41. package/dist/sse.js +197 -0
  42. package/dist/store.d.ts +100 -0
  43. package/dist/store.js +64 -0
  44. package/dist/widgets/agent-panel.d.ts +15 -0
  45. package/dist/widgets/agent-panel.js +23 -0
  46. package/dist/widgets/approval-modal.d.ts +16 -0
  47. package/dist/widgets/approval-modal.js +24 -0
  48. package/dist/widgets/artifact-tree.d.ts +14 -0
  49. package/dist/widgets/artifact-tree.js +9 -0
  50. package/dist/widgets/chat-panel.d.ts +10 -0
  51. package/dist/widgets/chat-panel.js +29 -0
  52. package/dist/widgets/header.d.ts +11 -0
  53. package/dist/widgets/header.js +14 -0
  54. package/dist/widgets/health-check.d.ts +15 -0
  55. package/dist/widgets/health-check.js +19 -0
  56. package/dist/widgets/input-bar.d.ts +9 -0
  57. package/dist/widgets/input-bar.js +37 -0
  58. package/dist/widgets/memory-panel.d.ts +11 -0
  59. package/dist/widgets/memory-panel.js +9 -0
  60. package/dist/widgets/status-bar.d.ts +13 -0
  61. package/dist/widgets/status-bar.js +24 -0
  62. package/dist/widgets/timeline.d.ts +26 -0
  63. package/dist/widgets/timeline.js +19 -0
  64. package/package.json +64 -0
package/dist/config.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Configuration loading and validation for the Hydra TUI.
3
+ *
4
+ * Config file: ~/.hydra/config.yaml
5
+ * Overridable via environment variables.
6
+ */
7
+ import { z } from "zod";
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+ import { parse as parseYaml } from "yaml";
12
+ export const TuiConfigSchema = z.object({
13
+ temporal: z.object({
14
+ address: z.string().default("localhost:7233"),
15
+ namespace: z.string().default("default"),
16
+ }).default({}),
17
+ api: z.object({
18
+ url: z.string().default("http://localhost:7070"),
19
+ }).default({}),
20
+ qdrant: z.object({
21
+ url: z.string().default("http://localhost:6333"),
22
+ }).default({}),
23
+ tui: z.object({
24
+ theme: z.enum(["dark", "light", "solarized", "monokai", "hydra"]).default("dark"),
25
+ layout: z.enum(["split", "full"]).default("split"),
26
+ rightPanel: z.boolean().default(true),
27
+ vimMode: z.boolean().default(false),
28
+ syntaxHighlighting: z.boolean().default(true),
29
+ maxHistory: z.number().default(1000),
30
+ spinnerStyle: z.enum(["dots", "line", "arc", "bounce"]).default("dots"),
31
+ }).default({}),
32
+ workflow: z.object({
33
+ defaultModel: z.string().default("claude-sonnet-4-5-20250929"),
34
+ autoApproveReads: z.boolean().default(true),
35
+ qualityThreshold: z.number().default(28),
36
+ costWarning: z.number().default(5.0),
37
+ }).default({}),
38
+ notifications: z.object({
39
+ sound: z.boolean().default(true),
40
+ desktop: z.boolean().default(true),
41
+ idleAlert: z.number().default(300),
42
+ }).default({}),
43
+ });
44
+ function deepMerge(target, source) {
45
+ const result = { ...target };
46
+ for (const key of Object.keys(source)) {
47
+ const sv = source[key];
48
+ const tv = result[key];
49
+ if (sv && typeof sv === "object" && !Array.isArray(sv) && tv && typeof tv === "object" && !Array.isArray(tv)) {
50
+ result[key] = deepMerge(tv, sv);
51
+ }
52
+ else {
53
+ result[key] = sv;
54
+ }
55
+ }
56
+ return result;
57
+ }
58
+ function loadYamlConfig() {
59
+ try {
60
+ const configPath = join(homedir(), ".hydra", "config.yaml");
61
+ const raw = readFileSync(configPath, "utf-8");
62
+ const parsed = parseYaml(raw);
63
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
64
+ }
65
+ catch {
66
+ // No config file or parse error — use defaults
67
+ return {};
68
+ }
69
+ }
70
+ export function getEnvOverrides() {
71
+ const overrides = {};
72
+ if (process.env.TEMPORAL_ADDRESS) {
73
+ overrides.temporal = { address: process.env.TEMPORAL_ADDRESS };
74
+ }
75
+ if (process.env.HYDRA_API_URL) {
76
+ overrides.api = { url: process.env.HYDRA_API_URL };
77
+ }
78
+ if (process.env.QDRANT_URL) {
79
+ overrides.qdrant = { url: process.env.QDRANT_URL };
80
+ }
81
+ if (process.env.HYDRA_THEME) {
82
+ overrides.tui = { theme: process.env.HYDRA_THEME };
83
+ }
84
+ return overrides;
85
+ }
86
+ export function loadConfig() {
87
+ const fileConfig = loadYamlConfig();
88
+ const envOverrides = getEnvOverrides();
89
+ const merged = deepMerge(fileConfig, envOverrides);
90
+ return TuiConfigSchema.parse(merged);
91
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Help screen.
3
+ * Displays all available commands grouped by category.
4
+ */
5
+ export declare function HelpScreen(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Help screen.
4
+ * Displays all available commands grouped by category.
5
+ */
6
+ import { Box, Text } from "ink";
7
+ import { getAllCommands } from "../commands/index.js";
8
+ export function HelpScreen() {
9
+ const commands = getAllCommands();
10
+ const categories = [...new Set(commands.map((c) => c.category))];
11
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Hydra TUI Commands" }), _jsx(Text, {}), categories.map((cat) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: cat.toUpperCase() }), commands
12
+ .filter((c) => c.category === cat)
13
+ .map((cmd) => (_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: "green", children: `/${cmd.name}`.padEnd(16) }), _jsx(Text, { children: cmd.description })] }, cmd.name)))] }, cat))), _jsx(Text, {}), _jsx(Text, { dimColor: true, children: "Keyboard: Ctrl+P toggle panels | Ctrl+D exit | F1 help | F5 refresh" })] }));
14
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Main screen with split layout.
3
+ * This is the default screen shown on TUI launch.
4
+ */
5
+ export { App as MainScreen } from "../app.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Main screen with split layout.
3
+ * This is the default screen shown on TUI launch.
4
+ */
5
+ export { App as MainScreen } from "../app.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Workflow detail / timeline view screen.
3
+ * Shows full workflow timeline, agent outputs, quality scores, artifacts.
4
+ */
5
+ interface WorkflowDetailScreenProps {
6
+ workflowId: string;
7
+ }
8
+ export declare function WorkflowDetailScreen({ workflowId }: WorkflowDetailScreenProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,11 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Workflow detail / timeline view screen.
4
+ * Shows full workflow timeline, agent outputs, quality scores, artifacts.
5
+ */
6
+ import { Box, Text } from "ink";
7
+ import { Timeline } from "../widgets/timeline.js";
8
+ export function WorkflowDetailScreen({ workflowId }) {
9
+ // TODO: fetch workflow data from API/Temporal
10
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { bold: true, children: ["Workflow Detail: ", workflowId] }), _jsx(Timeline, { workflowId: workflowId, workflowStatus: "LOADING", startedAgo: "...", totalCost: "...", steps: [] })] }));
11
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Workflow list view screen.
3
+ * Shows all workflows with status, phase, cost, elapsed time.
4
+ */
5
+ export declare function WorkflowListScreen(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Workflow list view screen.
4
+ * Shows all workflows with status, phase, cost, elapsed time.
5
+ */
6
+ import { Box, Text } from "ink";
7
+ export function WorkflowListScreen() {
8
+ // TODO: fetch workflows from API
9
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Workflows" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "No workflows found. Use /start to create one." }) })] }));
10
+ }
package/dist/sse.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SSE (Server-Sent Events) streaming for the Hydra TUI.
3
+ * Fetch-based parser since Node 18+ has no native EventSource.
4
+ */
5
+ import type { HydraStore } from "./store.js";
6
+ export interface SseEvent {
7
+ event: string;
8
+ data: string;
9
+ id?: string;
10
+ }
11
+ /**
12
+ * Subscribe to a workflow's SSE stream and push updates to the store.
13
+ * Reconnects with exponential backoff on connection failure (max 5 retries).
14
+ * Returns an AbortController for cleanup.
15
+ */
16
+ export declare function subscribeToWorkflow(apiUrl: string, workflowId: string, store: HydraStore): AbortController;
package/dist/sse.js ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * SSE (Server-Sent Events) streaming for the Hydra TUI.
3
+ * Fetch-based parser since Node 18+ has no native EventSource.
4
+ */
5
+ const MAX_RECONNECT_ATTEMPTS = 5;
6
+ const BASE_RECONNECT_DELAY_MS = 1000;
7
+ function parseSseLine(line, state) {
8
+ if (line.startsWith(":"))
9
+ return; // Comment / heartbeat
10
+ const colonIdx = line.indexOf(":");
11
+ // Per SSE spec: line with no colon uses entire line as field name with empty value
12
+ const field = colonIdx === -1 ? line : line.slice(0, colonIdx);
13
+ const value = colonIdx === -1 ? "" : line.slice(colonIdx + 1).trimStart();
14
+ switch (field) {
15
+ case "event":
16
+ state.event = value;
17
+ break;
18
+ case "data":
19
+ state.data += (state.data ? "\n" : "") + value;
20
+ break;
21
+ case "id":
22
+ state.id = value;
23
+ break;
24
+ }
25
+ }
26
+ /**
27
+ * Async generator that yields parsed SSE events from a fetch stream.
28
+ */
29
+ async function* sseStream(url, signal) {
30
+ const res = await fetch(url, {
31
+ headers: { Accept: "text/event-stream" },
32
+ signal,
33
+ });
34
+ if (!res.ok) {
35
+ throw new Error(`SSE connection failed: ${res.status} ${res.statusText}`);
36
+ }
37
+ const reader = res.body?.getReader();
38
+ if (!reader)
39
+ throw new Error("No response body");
40
+ const decoder = new TextDecoder();
41
+ let buffer = "";
42
+ const state = { event: "", data: "", id: undefined };
43
+ function resetState() {
44
+ state.event = "";
45
+ state.data = "";
46
+ state.id = undefined;
47
+ }
48
+ try {
49
+ while (true) {
50
+ const { done, value } = await reader.read();
51
+ if (done)
52
+ break;
53
+ buffer += decoder.decode(value, { stream: true });
54
+ const lines = buffer.split("\n");
55
+ // Keep the last incomplete line in buffer
56
+ buffer = lines.pop() ?? "";
57
+ for (const line of lines) {
58
+ if (line === "") {
59
+ // Empty line = event boundary
60
+ if (state.data) {
61
+ yield {
62
+ event: state.event || "message",
63
+ data: state.data.trimEnd(),
64
+ id: state.id,
65
+ };
66
+ }
67
+ resetState();
68
+ continue;
69
+ }
70
+ parseSseLine(line, state);
71
+ }
72
+ }
73
+ // Flush any remaining data in buffer after stream ends
74
+ if (buffer.trim()) {
75
+ parseSseLine(buffer, state);
76
+ }
77
+ if (state.data) {
78
+ yield {
79
+ event: state.event || "message",
80
+ data: state.data.trimEnd(),
81
+ id: state.id,
82
+ };
83
+ }
84
+ }
85
+ finally {
86
+ reader.releaseLock();
87
+ }
88
+ }
89
+ function processSseEvent(event, workflowId, store) {
90
+ if (event.event === "heartbeat")
91
+ return;
92
+ try {
93
+ const data = JSON.parse(event.data);
94
+ if (event.event === "workflow.progress") {
95
+ const step = data.current_step ?? data.phase ?? "unknown";
96
+ const status = data.status ?? "";
97
+ store.addMessage({
98
+ role: "agent",
99
+ content: `[${step}] ${status}${data.message ? `: ${data.message}` : ""}`,
100
+ agent: step,
101
+ });
102
+ // Update agents from step data
103
+ if (Array.isArray(data.steps)) {
104
+ store.setAgents(data.steps.map((st) => ({
105
+ id: String(st.role ?? ""),
106
+ role_id: String(st.role ?? ""),
107
+ queue: String(st.role ?? ""),
108
+ status: String(st.status === "completed" ? "idle" : st.status === "running" ? "active" : "idle"),
109
+ model: "",
110
+ active_sessions: st.status === "running" ? 1 : 0,
111
+ total_tokens_used: Number(st.tokens ?? 0),
112
+ current_workflow: st.status === "running" ? workflowId : null,
113
+ skill_packs: [],
114
+ })));
115
+ }
116
+ }
117
+ else if (event.event === "workflow.completed") {
118
+ store.addMessage({
119
+ role: "system",
120
+ content: `Workflow completed: ${data.status ?? "done"}`,
121
+ });
122
+ store.removeSseController(workflowId);
123
+ }
124
+ else if (event.event === "error") {
125
+ store.addMessage({
126
+ role: "error",
127
+ content: `Stream error: ${data.message ?? JSON.stringify(data)}`,
128
+ });
129
+ }
130
+ else {
131
+ store.addMessage({
132
+ role: "agent",
133
+ content: `[${event.event}] ${JSON.stringify(data).slice(0, 200)}`,
134
+ agent: "stream",
135
+ });
136
+ }
137
+ }
138
+ catch {
139
+ // Non-JSON data
140
+ store.addMessage({
141
+ role: "agent",
142
+ content: `[${event.event}] ${event.data.slice(0, 200)}`,
143
+ agent: "stream",
144
+ });
145
+ }
146
+ }
147
+ /**
148
+ * Subscribe to a workflow's SSE stream and push updates to the store.
149
+ * Reconnects with exponential backoff on connection failure (max 5 retries).
150
+ * Returns an AbortController for cleanup.
151
+ */
152
+ export function subscribeToWorkflow(apiUrl, workflowId, store) {
153
+ const controller = new AbortController();
154
+ const url = `${apiUrl.replace(/\/$/, "")}/stream/workflows/${workflowId}`;
155
+ (async () => {
156
+ let attempts = 0;
157
+ while (!controller.signal.aborted) {
158
+ try {
159
+ attempts++;
160
+ for await (const event of sseStream(url, controller.signal)) {
161
+ processSseEvent(event, workflowId, store);
162
+ // Reset attempts on successful event — connection is healthy
163
+ attempts = 0;
164
+ }
165
+ // Stream ended normally (server closed connection)
166
+ break;
167
+ }
168
+ catch (err) {
169
+ if (controller.signal.aborted)
170
+ return; // Normal cleanup
171
+ if (attempts >= MAX_RECONNECT_ATTEMPTS) {
172
+ const msg = err instanceof Error ? err.message : String(err);
173
+ store.addMessage({
174
+ role: "error",
175
+ content: `SSE stream failed after ${MAX_RECONNECT_ATTEMPTS} attempts: ${msg}`,
176
+ });
177
+ store.removeSseController(workflowId);
178
+ return;
179
+ }
180
+ const delay = BASE_RECONNECT_DELAY_MS * Math.pow(2, attempts - 1);
181
+ store.addMessage({
182
+ role: "system",
183
+ content: `Stream disconnected, reconnecting in ${(delay / 1000).toFixed(0)}s (attempt ${attempts}/${MAX_RECONNECT_ATTEMPTS})...`,
184
+ });
185
+ await new Promise((resolve) => {
186
+ const timer = setTimeout(resolve, delay);
187
+ // Allow abort to cancel the wait
188
+ controller.signal.addEventListener("abort", () => {
189
+ clearTimeout(timer);
190
+ resolve();
191
+ }, { once: true });
192
+ });
193
+ }
194
+ }
195
+ })();
196
+ return controller;
197
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Central Zustand store for the Hydra TUI.
3
+ * All widgets read from it, all commands write to it.
4
+ */
5
+ export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
6
+ export interface ChatMessage {
7
+ id: string;
8
+ role: "user" | "agent" | "system" | "error";
9
+ content: string;
10
+ timestamp: Date;
11
+ agent?: string;
12
+ }
13
+ export interface WorkflowDetail {
14
+ id: string;
15
+ workflow_type: string;
16
+ status: string;
17
+ current_step: string | null;
18
+ steps: Array<{
19
+ role: string;
20
+ status: string;
21
+ started_at?: string;
22
+ completed_at?: string;
23
+ cost?: number;
24
+ tokens?: number;
25
+ score?: number;
26
+ }>;
27
+ input: Record<string, unknown>;
28
+ output: Record<string, unknown> | null;
29
+ artifacts: Array<{
30
+ path: string;
31
+ type: string;
32
+ name?: string;
33
+ }>;
34
+ created_at: string;
35
+ updated_at: string;
36
+ metrics: Record<string, unknown> | null;
37
+ }
38
+ export interface WorkflowSummary {
39
+ workflow_id: string;
40
+ workflow_type: string;
41
+ task_description: string | null;
42
+ status: string;
43
+ domain: string | null;
44
+ technologies: string[] | null;
45
+ complexity: string | null;
46
+ created_at: string;
47
+ completed_at: string | null;
48
+ duration_minutes: number | null;
49
+ current_step: string | null;
50
+ completed_steps: string[] | null;
51
+ }
52
+ export interface InboxItem {
53
+ id: string;
54
+ type: string;
55
+ workflow_id: string;
56
+ workflow_type: string;
57
+ title: string;
58
+ summary: string;
59
+ priority: string;
60
+ created_at: string;
61
+ agent_role: string | null;
62
+ approval_type: string;
63
+ status: string;
64
+ }
65
+ export interface AgentInfo {
66
+ id: string;
67
+ role_id: string;
68
+ queue: string;
69
+ status: string;
70
+ model: string;
71
+ active_sessions: number;
72
+ total_tokens_used: number;
73
+ current_workflow: string | null;
74
+ skill_packs: string[];
75
+ }
76
+ export interface HydraStore {
77
+ messages: ChatMessage[];
78
+ maxHistory: number;
79
+ activeWorkflowId: string | null;
80
+ activeWorkflow: WorkflowDetail | null;
81
+ workflows: WorkflowSummary[];
82
+ inbox: InboxItem[];
83
+ agents: AgentInfo[];
84
+ apiUrl: string;
85
+ apiStatus: ConnectionStatus;
86
+ sseAbortControllers: Map<string, AbortController>;
87
+ addMessage: (msg: Omit<ChatMessage, "id" | "timestamp">) => void;
88
+ clearMessages: () => void;
89
+ setActiveWorkflowId: (id: string | null) => void;
90
+ setActiveWorkflow: (wf: WorkflowDetail | null) => void;
91
+ setWorkflows: (wfs: WorkflowSummary[]) => void;
92
+ setInbox: (items: InboxItem[]) => void;
93
+ setAgents: (agents: AgentInfo[]) => void;
94
+ setApiUrl: (url: string) => void;
95
+ setApiStatus: (status: ConnectionStatus) => void;
96
+ addSseController: (key: string, controller: AbortController) => void;
97
+ removeSseController: (key: string) => void;
98
+ abortAllSse: () => void;
99
+ }
100
+ export declare const useStore: import("zustand").UseBoundStore<import("zustand").StoreApi<HydraStore>>;
package/dist/store.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Central Zustand store for the Hydra TUI.
3
+ * All widgets read from it, all commands write to it.
4
+ */
5
+ import { create } from "zustand";
6
+ let messageCounter = 0;
7
+ export const useStore = create((set, get) => ({
8
+ messages: [],
9
+ maxHistory: 1000,
10
+ activeWorkflowId: null,
11
+ activeWorkflow: null,
12
+ workflows: [],
13
+ inbox: [],
14
+ agents: [],
15
+ apiUrl: "http://localhost:7070",
16
+ apiStatus: "disconnected",
17
+ sseAbortControllers: new Map(),
18
+ addMessage: (msg) => set((state) => {
19
+ const next = [
20
+ ...state.messages,
21
+ {
22
+ ...msg,
23
+ id: `msg-${Date.now()}-${++messageCounter}`,
24
+ timestamp: new Date(),
25
+ },
26
+ ];
27
+ return {
28
+ messages: next.length > state.maxHistory
29
+ ? next.slice(-state.maxHistory)
30
+ : next,
31
+ };
32
+ }),
33
+ clearMessages: () => set({ messages: [] }),
34
+ setActiveWorkflowId: (id) => set({ activeWorkflowId: id }),
35
+ setActiveWorkflow: (wf) => set({ activeWorkflow: wf }),
36
+ setWorkflows: (wfs) => set({ workflows: wfs }),
37
+ setInbox: (items) => set({ inbox: items }),
38
+ setAgents: (agents) => set({ agents }),
39
+ setApiUrl: (url) => set({ apiUrl: url }),
40
+ setApiStatus: (status) => set({ apiStatus: status }),
41
+ addSseController: (key, controller) => set((state) => {
42
+ const next = new Map(state.sseAbortControllers);
43
+ // Abort existing controller for this key
44
+ const existing = next.get(key);
45
+ if (existing)
46
+ existing.abort();
47
+ next.set(key, controller);
48
+ return { sseAbortControllers: next };
49
+ }),
50
+ removeSseController: (key) => set((state) => {
51
+ const next = new Map(state.sseAbortControllers);
52
+ const existing = next.get(key);
53
+ if (existing)
54
+ existing.abort();
55
+ next.delete(key);
56
+ return { sseAbortControllers: next };
57
+ }),
58
+ abortAllSse: () => set((state) => {
59
+ for (const controller of state.sseAbortControllers.values()) {
60
+ controller.abort();
61
+ }
62
+ return { sseAbortControllers: new Map() };
63
+ }),
64
+ }));
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Agent panel widget.
3
+ * Shows live status of each agent role in the active workflow.
4
+ */
5
+ interface AgentStatus {
6
+ role: string;
7
+ status: "done" | "running" | "waiting" | "pending" | "failed";
8
+ duration?: string;
9
+ }
10
+ interface AgentPanelProps {
11
+ workflowId?: string;
12
+ agents?: AgentStatus[];
13
+ }
14
+ export declare function AgentPanel({ workflowId, agents }: AgentPanelProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Agent panel widget.
4
+ * Shows live status of each agent role in the active workflow.
5
+ */
6
+ import { Box, Text } from "ink";
7
+ const STATUS_ICONS = {
8
+ done: "✓",
9
+ running: "⏳",
10
+ waiting: "⏸",
11
+ pending: "○",
12
+ failed: "✗",
13
+ };
14
+ const STATUS_COLORS = {
15
+ done: "green",
16
+ running: "cyan",
17
+ waiting: "yellow",
18
+ pending: "gray",
19
+ failed: "red",
20
+ };
21
+ export function AgentPanel({ workflowId, agents = [] }) {
22
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Agents" }), !workflowId ? (_jsx(Text, { dimColor: true, children: "No active workflow" })) : agents.length === 0 ? (_jsx(Text, { dimColor: true, children: "Waiting for agents..." })) : (agents.map((agent) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: STATUS_COLORS[agent.status], children: STATUS_ICONS[agent.status] }), _jsx(Text, { children: agent.role.padEnd(14) }), _jsx(Text, { dimColor: true, children: agent.status })] }, agent.role))))] }));
23
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Approval modal widget.
3
+ * Displays approval requests from workflows with approve/deny/edit options.
4
+ */
5
+ interface ApprovalModalProps {
6
+ agent: string;
7
+ action: string;
8
+ command: string;
9
+ risk: "LOW" | "MEDIUM" | "HIGH";
10
+ active?: boolean;
11
+ onApprove: () => void;
12
+ onDeny: () => void;
13
+ onEdit?: () => void;
14
+ }
15
+ export declare function ApprovalModal({ agent, action, command, risk, active, onApprove, onDeny, onEdit, }: ApprovalModalProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Approval modal widget.
4
+ * Displays approval requests from workflows with approve/deny/edit options.
5
+ */
6
+ import { Box, Text, useInput } from "ink";
7
+ const RISK_COLORS = {
8
+ LOW: "green",
9
+ MEDIUM: "yellow",
10
+ HIGH: "red",
11
+ };
12
+ export function ApprovalModal({ agent, action, command, risk, active = true, onApprove, onDeny, onEdit, }) {
13
+ useInput((input) => {
14
+ if (!active)
15
+ return;
16
+ if (input === "y")
17
+ onApprove();
18
+ if (input === "n")
19
+ onDeny();
20
+ if (input === "e" && onEdit)
21
+ onEdit();
22
+ }, { isActive: active });
23
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: "yellow", padding: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "APPROVAL REQUIRED" }), _jsx(Text, {}), _jsxs(Text, { children: ["Agent: ", _jsx(Text, { bold: true, children: agent })] }), _jsxs(Text, { children: ["Action: ", action] }), _jsx(Text, {}), _jsx(Box, { borderStyle: "single", paddingX: 1, children: _jsx(Text, { children: command }) }), _jsx(Text, {}), _jsxs(Text, { children: ["Risk: ", _jsx(Text, { color: RISK_COLORS[risk], children: risk })] }), _jsx(Text, {}), _jsxs(Text, { children: [_jsx(Text, { color: "green", children: "[y]" }), " Approve", " ", _jsx(Text, { color: "red", children: "[n]" }), " Deny", " ", onEdit && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "blue", children: "[e]" }), " Edit"] }))] })] }));
24
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Artifact tree widget.
3
+ * Displays a tree view of artifacts produced by the active workflow.
4
+ */
5
+ interface Artifact {
6
+ path: string;
7
+ type: "file" | "directory";
8
+ }
9
+ interface ArtifactTreeProps {
10
+ workflowId?: string;
11
+ artifacts?: Artifact[];
12
+ }
13
+ export declare function ArtifactTree({ workflowId, artifacts }: ArtifactTreeProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Artifact tree widget.
4
+ * Displays a tree view of artifacts produced by the active workflow.
5
+ */
6
+ import { Box, Text } from "ink";
7
+ export function ArtifactTree({ workflowId, artifacts = [] }) {
8
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Artifacts" }), !workflowId ? (_jsx(Text, { dimColor: true, children: "No active workflow" })) : artifacts.length === 0 ? (_jsx(Text, { dimColor: true, children: "No artifacts yet" })) : (artifacts.map((artifact) => (_jsxs(Text, { children: [artifact.type === "directory" ? "📁 " : "📄 ", artifact.path] }, artifact.path))))] }));
9
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Chat panel widget.
3
+ * Displays streaming output from agents, approval prompts, and user messages.
4
+ */
5
+ import type { ChatMessage } from "../store.js";
6
+ interface ChatPanelProps {
7
+ messages?: ChatMessage[];
8
+ }
9
+ export declare function ChatPanel({ messages }: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};