0xkobold 0.4.3 → 0.5.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 +226 -383
- package/dist/package.json +13 -4
- package/dist/src/agent/DraconicCapabilityRouter.js +556 -0
- package/dist/src/agent/DraconicCapabilityRouter.js.map +1 -0
- package/dist/src/agent/DraconicErrorClassifier.js +606 -0
- package/dist/src/agent/DraconicErrorClassifier.js.map +1 -0
- package/dist/src/agent/DraconicRunRegistry.js +460 -0
- package/dist/src/agent/DraconicRunRegistry.js.map +1 -0
- package/dist/src/agent/DraconicTokenPredictor.js +500 -0
- package/dist/src/agent/DraconicTokenPredictor.js.map +1 -0
- package/dist/src/agent/embedded-runner.js +3 -2
- package/dist/src/agent/embedded-runner.js.map +1 -1
- package/dist/src/cli/commands/doctor.js +199 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/migrate-from-openclaw.js +39 -0
- package/dist/src/cli/commands/migrate-from-openclaw.js.map +1 -0
- package/dist/src/cli/commands/migrate.js +6 -4
- package/dist/src/cli/commands/migrate.js.map +1 -1
- package/dist/src/cli/program.js +3 -1
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/unified-config.js +195 -0
- package/dist/src/config/unified-config.js.map +1 -0
- package/dist/src/cron/notifications.js +1 -1
- package/dist/src/cron/notifications.js.map +1 -1
- package/dist/src/cron/runner.js +9 -3
- package/dist/src/cron/runner.js.map +1 -1
- package/dist/src/event-bus/index.js +3 -1
- package/dist/src/event-bus/index.js.map +1 -1
- package/dist/src/extensions/community/draconic-messenger-wrapper.js +35 -0
- package/dist/src/extensions/community/draconic-messenger-wrapper.js.map +1 -0
- package/dist/src/extensions/community/draconic-subagents-wrapper.js +115 -0
- package/dist/src/extensions/community/draconic-subagents-wrapper.js.map +1 -0
- package/dist/src/extensions/core/compaction-safeguard-v2.js +95 -0
- package/dist/src/extensions/core/compaction-safeguard-v2.js.map +1 -0
- package/dist/src/extensions/core/compaction-safeguard.js +206 -137
- package/dist/src/extensions/core/compaction-safeguard.js.map +1 -1
- package/dist/src/extensions/core/config-extension.js +122 -0
- package/dist/src/extensions/core/config-extension.js.map +1 -0
- package/dist/src/extensions/core/draconic-hoard-extension.js +194 -0
- package/dist/src/extensions/core/draconic-hoard-extension.js.map +1 -0
- package/dist/src/extensions/core/draconic-lair-extension.js +146 -0
- package/dist/src/extensions/core/draconic-lair-extension.js.map +1 -0
- package/dist/src/extensions/core/draconic-safety-extension.js +402 -0
- package/dist/src/extensions/core/draconic-safety-extension.js.map +1 -0
- package/dist/src/extensions/core/ext-manager.js +40 -0
- package/dist/src/extensions/core/ext-manager.js.map +1 -0
- package/dist/src/extensions/core/gateway-extension.js +413 -624
- package/dist/src/extensions/core/gateway-extension.js.map +1 -1
- package/dist/src/extensions/core/generative-agents-extension.js +704 -0
- package/dist/src/extensions/core/generative-agents-extension.js.map +1 -0
- package/dist/src/extensions/core/memory-bootstrap-extension.js +279 -0
- package/dist/src/extensions/core/memory-bootstrap-extension.js.map +1 -0
- package/dist/src/extensions/core/ollama-extension.js +442 -0
- package/dist/src/extensions/core/ollama-extension.js.map +1 -0
- package/dist/src/extensions/core/ollama-router-extension.js +34 -93
- package/dist/src/extensions/core/ollama-router-extension.js.map +1 -1
- package/dist/src/extensions/core/perennial-memory-extension.js +5 -2
- package/dist/src/extensions/core/perennial-memory-extension.js.map +1 -1
- package/dist/src/extensions/core/persona-loader-extension.js +41 -15
- package/dist/src/extensions/core/persona-loader-extension.js.map +1 -1
- package/dist/src/extensions/core/pi-notify-extension.js +6 -0
- package/dist/src/extensions/core/pi-notify-extension.js.map +1 -1
- package/dist/src/extensions/core/session-bridge-extension.js +8 -72
- package/dist/src/extensions/core/session-bridge-extension.js.map +1 -1
- package/dist/src/extensions/core/tui-integration-extension-fixed.js +3 -0
- package/dist/src/extensions/core/tui-integration-extension-fixed.js.map +1 -0
- package/dist/src/extensions/core/tui-integration-extension.js +984 -0
- package/dist/src/extensions/core/tui-integration-extension.js.map +1 -0
- package/dist/src/gateway/persistence/AgentStore.js +399 -0
- package/dist/src/gateway/persistence/AgentStore.js.map +1 -0
- package/dist/src/gateway/persistence/index.js +9 -0
- package/dist/src/gateway/persistence/index.js.map +1 -0
- package/dist/src/hoard/DraconicHoardSystem.js +465 -0
- package/dist/src/hoard/DraconicHoardSystem.js.map +1 -0
- package/dist/src/index.js +7 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/infra/DraconicConnectionPool.js +492 -0
- package/dist/src/infra/DraconicConnectionPool.js.map +1 -0
- package/dist/src/lair/DraconicLairSystem.js +387 -0
- package/dist/src/lair/DraconicLairSystem.js.map +1 -0
- package/dist/src/llm/ollama.js +143 -99
- package/dist/src/llm/ollama.js.map +1 -1
- package/dist/src/migration/openclaw.js +5 -5
- package/dist/src/migration/openclaw.js.map +1 -1
- package/dist/src/pi-config.js +52 -27
- package/dist/src/pi-config.js.map +1 -1
- package/dist/src/sessions/SessionManager.js +416 -0
- package/dist/src/sessions/SessionManager.js.map +1 -0
- package/dist/src/sessions/SessionStore.js +580 -0
- package/dist/src/sessions/SessionStore.js.map +1 -0
- package/dist/src/sessions/UnifiedSessionBridge.js +394 -0
- package/dist/src/sessions/UnifiedSessionBridge.js.map +1 -0
- package/dist/src/sessions/index.js +15 -0
- package/dist/src/sessions/index.js.map +1 -0
- package/dist/src/sessions/migration/index.js +156 -0
- package/dist/src/sessions/migration/index.js.map +1 -0
- package/dist/src/sessions/types.js +8 -0
- package/dist/src/sessions/types.js.map +1 -0
- package/dist/src/skills/framework.js +1 -1
- package/dist/src/skills/framework.js.map +1 -1
- package/dist/src/tui/commands/final-functions.js +39 -0
- package/dist/src/tui/commands/final-functions.js.map +1 -0
- package/dist/src/tui/commands/natural-language-commands.js +231 -0
- package/dist/src/tui/commands/natural-language-commands.js.map +1 -0
- package/dist/src/tui/commands/orchestration-commands.js +340 -0
- package/dist/src/tui/commands/orchestration-commands.js.map +1 -0
- package/dist/src/tui/commands/orchestration-final-section.js +84 -0
- package/dist/src/tui/commands/orchestration-final-section.js.map +1 -0
- package/dist/src/tui/components/agent-tree-overlay.js +210 -0
- package/dist/src/tui/components/agent-tree-overlay.js.map +1 -0
- package/dist/src/tui/components/agent-tree-panel.js +229 -0
- package/dist/src/tui/components/agent-tree-panel.js.map +1 -0
- package/dist/src/tui/components/sidebar.js +82 -0
- package/dist/src/tui/components/sidebar.js.map +1 -0
- package/dist/src/tui/components/status-bar.js +115 -0
- package/dist/src/tui/components/status-bar.js.map +1 -0
- package/dist/src/tui/components/tree-header.js +85 -0
- package/dist/src/tui/components/tree-header.js.map +1 -0
- package/dist/src/tui/draconic-tui.js +291 -0
- package/dist/src/tui/draconic-tui.js.map +1 -0
- package/dist/src/tui/index.js +93 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/pi-tui-integration.js +260 -0
- package/dist/src/tui/pi-tui-integration.js.map +1 -0
- package/dist/src/utils/nl-patterns.js +145 -0
- package/dist/src/utils/nl-patterns.js.map +1 -0
- package/package.json +13 -4
- package/scripts/migrate-from-openclaw.sh +498 -0
- package/HEARTBEAT.md +0 -121
- package/USAGE.md +0 -191
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🐉 TUI Integration Extension
|
|
3
|
+
*
|
|
4
|
+
* Bridges our Draconic TUI components with @mariozechner/pi-tui
|
|
5
|
+
* - Status bar in footer
|
|
6
|
+
* - /agent-tree overlay
|
|
7
|
+
* - /agent-result display
|
|
8
|
+
*/
|
|
9
|
+
import { Type } from "@sinclair/typebox";
|
|
10
|
+
import { createDraconicTUI } from "../../tui/draconic-tui";
|
|
11
|
+
import { createAgentTreePanel } from "../../tui/components/agent-tree-panel";
|
|
12
|
+
import { eventBus } from "../../event-bus";
|
|
13
|
+
const tui = createDraconicTUI();
|
|
14
|
+
const treePanel = createAgentTreePanel(tui);
|
|
15
|
+
let statusBarInterval = null;
|
|
16
|
+
let showTreePanel = true; // Toggle state
|
|
17
|
+
let mainAgentRegistered = false;
|
|
18
|
+
export default async function register(pi) {
|
|
19
|
+
console.log("[🐉 DraconicTUI] Loading integration...");
|
|
20
|
+
// 🧹 AGGRESSIVE CLEAR: Remove ALL stale agents on every reload
|
|
21
|
+
try {
|
|
22
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
23
|
+
const registry = getDraconicRunRegistry();
|
|
24
|
+
const allRuns = registry.query({}).runs;
|
|
25
|
+
const nonRunning = allRuns.filter(r => r.status !== "running");
|
|
26
|
+
for (const run of nonRunning) {
|
|
27
|
+
registry.delete(run.id);
|
|
28
|
+
}
|
|
29
|
+
if (nonRunning.length > 0 || allRuns.length > 0) {
|
|
30
|
+
console.log(`[🧹 TUI Reload] Cleared ${nonRunning.length}/${allRuns.length} stale agents`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
// Silent fail on initial load
|
|
35
|
+
}
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// 🐉 NATURAL LANGUAGE SUPPORT
|
|
38
|
+
// ============================================================================
|
|
39
|
+
pi.on("input", async (event) => {
|
|
40
|
+
const { parseNaturalLanguage, isNaturalLanguage } = await import("../../tui/commands/natural-language-commands");
|
|
41
|
+
const input = event?.text?.trim();
|
|
42
|
+
if (!input || input.startsWith("/"))
|
|
43
|
+
return { action: "continue" };
|
|
44
|
+
// Check if natural language matches
|
|
45
|
+
const parsed = parseNaturalLanguage(input);
|
|
46
|
+
if (parsed) {
|
|
47
|
+
// Transform the input to a slash command
|
|
48
|
+
const paramsText = Object.entries(parsed.params)
|
|
49
|
+
.map(([k, v]) => typeof v === "string" && v.includes(" ") ? `${k}="${v}"` : `${k}=${v}`)
|
|
50
|
+
.join(" ");
|
|
51
|
+
const command = `/${parsed.tool} ${paramsText}`;
|
|
52
|
+
return {
|
|
53
|
+
action: "transform",
|
|
54
|
+
text: command,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return { action: "continue" };
|
|
58
|
+
});
|
|
59
|
+
// 🐉 Auto-register main agent if not already
|
|
60
|
+
if (!mainAgentRegistered) {
|
|
61
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
62
|
+
const registry = getDraconicRunRegistry();
|
|
63
|
+
// Check if main agent already exists
|
|
64
|
+
const existing = registry.query({ type: "coordinator" }).runs.find(r => r.depth === 0);
|
|
65
|
+
if (!existing) {
|
|
66
|
+
const mainRun = registry.create({
|
|
67
|
+
sessionKey: process.env.DRACONIC_SESSION_KEY || "tui-main",
|
|
68
|
+
name: "main",
|
|
69
|
+
type: "coordinator",
|
|
70
|
+
task: "TUI Main Agent",
|
|
71
|
+
workspace: process.cwd(),
|
|
72
|
+
capabilities: {
|
|
73
|
+
primary: ["coordination", "orchestration"],
|
|
74
|
+
secondary: [],
|
|
75
|
+
},
|
|
76
|
+
parentId: undefined, // Root
|
|
77
|
+
depth: 0,
|
|
78
|
+
isProcessingQueue: false,
|
|
79
|
+
});
|
|
80
|
+
registry.updateStatus(mainRun.id, "running");
|
|
81
|
+
process.env.DRACONIC_RUN_ID = mainRun.id;
|
|
82
|
+
console.log(`[🐉 DraconicTUI] Main agent registered: ${mainRun.id}`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
process.env.DRACONIC_RUN_ID = existing.id;
|
|
86
|
+
console.log(`[🐉 DraconicTUI] Connected to existing main agent: ${existing.id}`);
|
|
87
|
+
}
|
|
88
|
+
mainAgentRegistered = true;
|
|
89
|
+
}
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Register footer status command
|
|
92
|
+
// ============================================================================
|
|
93
|
+
pi.registerTool({
|
|
94
|
+
name: "draconic_tui_status",
|
|
95
|
+
label: "🐉 TUI Status",
|
|
96
|
+
description: "Show Draconic orchestrator status in TUI footer",
|
|
97
|
+
parameters: Type.Object({}),
|
|
98
|
+
async execute() {
|
|
99
|
+
const tree = await tui.getAgentTree();
|
|
100
|
+
if (!tree) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: "🐉 idle" }],
|
|
103
|
+
details: { active: 0, total: 0, task: null },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const { active, total } = countAgents(tree);
|
|
107
|
+
const currentTask = findActiveTask(tree);
|
|
108
|
+
const statusText = active > 0
|
|
109
|
+
? `🐉 ${active} | ${currentTask?.slice(0, 25) || "working..."}`
|
|
110
|
+
: "🐉 idle";
|
|
111
|
+
return {
|
|
112
|
+
content: [{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: statusText,
|
|
115
|
+
}],
|
|
116
|
+
details: { active, total, task: currentTask || null },
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// Register /agent-tree command
|
|
122
|
+
// ============================================================================
|
|
123
|
+
pi.registerTool({
|
|
124
|
+
name: "agent_tree_view",
|
|
125
|
+
label: "🐉 Agent Tree",
|
|
126
|
+
description: "Show agent hierarchy tree",
|
|
127
|
+
parameters: Type.Object({
|
|
128
|
+
runId: Type.Optional(Type.String({ description: "Specific agent run ID (optional)" })),
|
|
129
|
+
}),
|
|
130
|
+
async execute(_id, params) {
|
|
131
|
+
const runId = params.runId;
|
|
132
|
+
const tree = await tui.getAgentTree(runId);
|
|
133
|
+
if (!tree) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: "🐉 No agents in hierarchy\n\nTry:\n /agent-spawn researcher \"analyze something\"" }],
|
|
136
|
+
details: { empty: true },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const formatted = formatTree(tree);
|
|
140
|
+
return {
|
|
141
|
+
content: [{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: `🐉 Agent Tree:\n\n${formatted}`,
|
|
144
|
+
}],
|
|
145
|
+
details: { tree },
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Register /agent-result command
|
|
151
|
+
// ============================================================================
|
|
152
|
+
pi.registerTool({
|
|
153
|
+
name: "agent_result_view",
|
|
154
|
+
label: "🐉 Agent Result",
|
|
155
|
+
description: "View subagent output result",
|
|
156
|
+
parameters: Type.Object({
|
|
157
|
+
runId: Type.String({ description: "Agent run ID" }),
|
|
158
|
+
}),
|
|
159
|
+
async execute(_id, params) {
|
|
160
|
+
const runId = params.runId;
|
|
161
|
+
const artifacts = await tui.getResult(runId);
|
|
162
|
+
if (!artifacts || artifacts.length === 0) {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text: `No results found for ${runId}` }],
|
|
165
|
+
details: null,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const artifact = artifacts[0];
|
|
169
|
+
const preview = artifact.content?.slice(0, 2000) || "No content";
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: `🐉 Result for ${runId}:\n${"─".repeat(50)}\n${preview}\n${artifact.content && artifact.content.length > 2000 ? "\n... (truncated)" : ""}`,
|
|
174
|
+
}],
|
|
175
|
+
details: { artifact },
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// Register /agent-spawn command
|
|
181
|
+
// ============================================================================
|
|
182
|
+
pi.registerTool({
|
|
183
|
+
name: "agent_spawn_tui",
|
|
184
|
+
label: "🐉 Spawn Agent",
|
|
185
|
+
description: "Spawn a subagent with tracking",
|
|
186
|
+
parameters: Type.Object({
|
|
187
|
+
type: Type.String({ description: "Agent type: coordinator, specialist, researcher, planner, reviewer" }),
|
|
188
|
+
task: Type.String({ description: "Task description" }),
|
|
189
|
+
strategy: Type.Optional(Type.String({ description: "Strategy: fast, thorough, auto" })),
|
|
190
|
+
}),
|
|
191
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
192
|
+
const agentType = params.type;
|
|
193
|
+
const task = params.task;
|
|
194
|
+
const strategy = params.strategy || "auto";
|
|
195
|
+
try {
|
|
196
|
+
// Show spawning message
|
|
197
|
+
ctx.ui.notify(`🐉 Spawning ${agentType}...`, "info");
|
|
198
|
+
// Call agent_orchestrate via our TUI
|
|
199
|
+
const result = await tui.spawnSubagent({
|
|
200
|
+
type: agentType,
|
|
201
|
+
task,
|
|
202
|
+
parentId: process.env.DRACONIC_RUN_ID,
|
|
203
|
+
strategy: strategy,
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: `✅ Spawned ${result.runId}\nType: ${agentType}\nTask: ${task.slice(0, 50)}...\nStrategy: ${strategy}`,
|
|
209
|
+
}],
|
|
210
|
+
details: { result },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: `❌ Failed to spawn: ${err}` }],
|
|
216
|
+
details: { error: String(err) },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// Subscribe to events for status updates
|
|
223
|
+
// ============================================================================
|
|
224
|
+
eventBus.on("agent.completed", async (event) => {
|
|
225
|
+
const { runId, type, duration, artifactPath } = event.payload;
|
|
226
|
+
console.log(`[🐉 TUI] ${type} completed (${Math.round(duration / 1000)}s): ${runId.slice(-8)}`);
|
|
227
|
+
if (artifactPath)
|
|
228
|
+
console.log(` 📄 ${artifactPath}`);
|
|
229
|
+
});
|
|
230
|
+
// Start status bar updater
|
|
231
|
+
if (!statusBarInterval) {
|
|
232
|
+
statusBarInterval = setInterval(async () => {
|
|
233
|
+
const tree = await tui.getAgentTree();
|
|
234
|
+
if (tree) {
|
|
235
|
+
const { active } = countAgents(tree);
|
|
236
|
+
process.env.DRACONIC_TUI_ACTIVE_AGENTS = String(active);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
process.env.DRACONIC_TUI_ACTIVE_AGENTS = "0";
|
|
240
|
+
}
|
|
241
|
+
}, 500);
|
|
242
|
+
}
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// Register tree sidebar commands
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// Command to toggle tree panel visibility
|
|
247
|
+
pi.registerTool({
|
|
248
|
+
name: "agent_tree_toggle",
|
|
249
|
+
label: "🐉 Toggle Tree Panel",
|
|
250
|
+
description: "Show/hide the persistent agent tree panel",
|
|
251
|
+
parameters: Type.Object({}),
|
|
252
|
+
async execute() {
|
|
253
|
+
showTreePanel = !showTreePanel;
|
|
254
|
+
return {
|
|
255
|
+
content: [{
|
|
256
|
+
type: "text",
|
|
257
|
+
text: showTreePanel ? "🐉 Tree panel ON" : "🐉 Tree panel OFF",
|
|
258
|
+
}],
|
|
259
|
+
details: { visible: showTreePanel },
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
// Command to get current tree lines (for display)
|
|
264
|
+
pi.registerTool({
|
|
265
|
+
name: "agent_tree_lines",
|
|
266
|
+
label: "🐉 Get Tree Lines",
|
|
267
|
+
description: "Get formatted tree lines for sidebar display",
|
|
268
|
+
parameters: Type.Object({
|
|
269
|
+
width: Type.Optional(Type.Number({ description: "Max width in characters" })),
|
|
270
|
+
}),
|
|
271
|
+
async execute(_id, params) {
|
|
272
|
+
const width = params.width || 50;
|
|
273
|
+
// First, get actual tree from registry
|
|
274
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
275
|
+
const registry = getDraconicRunRegistry();
|
|
276
|
+
const stats = registry.getStats();
|
|
277
|
+
// Get all runs and build tree
|
|
278
|
+
const allRuns = registry.query({}).runs;
|
|
279
|
+
if (allRuns.length === 0) {
|
|
280
|
+
return {
|
|
281
|
+
content: [{
|
|
282
|
+
type: "text",
|
|
283
|
+
text: "🐉 No agents running\n\nTo spawn an agent:\n /agent-spawn researcher \"analyze code\""
|
|
284
|
+
}],
|
|
285
|
+
details: { lines: [], visible: true, stats: { total: 0 } },
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
// Build tree lines
|
|
289
|
+
const lines = [`🐉 Agents (${stats.activeRuns}/${stats.totalRuns})`];
|
|
290
|
+
// Show all root runs
|
|
291
|
+
const rootRuns = allRuns.filter(r => !r.parentId);
|
|
292
|
+
rootRuns.forEach((run, i) => {
|
|
293
|
+
const isLast = i === rootRuns.length - 1;
|
|
294
|
+
const status = run.status === "completed" ? "✅" : run.status === "error" ? "❌" : "🔄";
|
|
295
|
+
const type = run.type?.[0]?.toUpperCase() || "?";
|
|
296
|
+
const task = run.task ? `: ${run.task.slice(0, width - 20)}` : "";
|
|
297
|
+
lines.push(`${isLast ? "└── " : "├── "}${status} [${type}] ${run.id.slice(-8)}${task}`);
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
content: [{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: lines.join("\n"),
|
|
303
|
+
}],
|
|
304
|
+
details: { lines, visible: true, stats },
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// 🐉 SIDEBAR MODE - Persistent Tree Display
|
|
310
|
+
// ============================================================================
|
|
311
|
+
let sidebarActive = true; // 🐉 ON by default
|
|
312
|
+
let sidebarInterval = null;
|
|
313
|
+
pi.registerTool({
|
|
314
|
+
name: "agent_tree_sidebar",
|
|
315
|
+
label: "🐉 Toggle Sidebar",
|
|
316
|
+
description: "Toggle persistent agent tree sidebar",
|
|
317
|
+
parameters: Type.Object({}),
|
|
318
|
+
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
319
|
+
sidebarActive = !sidebarActive;
|
|
320
|
+
if (sidebarActive) {
|
|
321
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
322
|
+
const registry = getDraconicRunRegistry();
|
|
323
|
+
const stats = registry.getStats();
|
|
324
|
+
// Show compact sidebar
|
|
325
|
+
const allRuns = registry.query({}).runs;
|
|
326
|
+
const lines = ["🐉 AGENT TREE ────────────"];
|
|
327
|
+
if (allRuns.length === 0) {
|
|
328
|
+
lines.push("No agents yet");
|
|
329
|
+
lines.push("Spawn: /agent-spawn researcher \"task\"");
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
const rootRuns = allRuns.filter(r => !r.parentId);
|
|
333
|
+
const renderSidebar = (run, prefix, isLast) => {
|
|
334
|
+
const status = run.status === "running" ? "●" : run.status === "completed" ? "✓" : "○";
|
|
335
|
+
const type = run.type?.[0]?.toUpperCase() || "?";
|
|
336
|
+
const task = run.task ? run.task.slice(0, 15) : "";
|
|
337
|
+
lines.push(`${prefix}${isLast ? "└─ " : "├─ "}${status} [${type}] ${run.id.slice(-4)} ${task}`);
|
|
338
|
+
const children = allRuns.filter(r => r.parentId === run.id);
|
|
339
|
+
children.forEach((child, i) => {
|
|
340
|
+
const childIsLast = i === children.length - 1;
|
|
341
|
+
renderSidebar(child, prefix + (isLast ? " " : "│ "), childIsLast);
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
rootRuns.forEach((run, i) => renderSidebar(run, "", i === rootRuns.length - 1));
|
|
345
|
+
}
|
|
346
|
+
lines.push("─────────────────────────────");
|
|
347
|
+
lines.push("[/sidebar to hide]");
|
|
348
|
+
return {
|
|
349
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
350
|
+
details: { active: true, stats },
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
return {
|
|
355
|
+
content: [{ type: "text", text: "🐉 Sidebar hidden" }],
|
|
356
|
+
details: { active: false },
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
// ============================================================================
|
|
362
|
+
// 🐉 REGISTER ALL NEW FEATURES
|
|
363
|
+
// ============================================================================
|
|
364
|
+
registerKeybindings(pi);
|
|
365
|
+
registerAgentControls(pi, tui);
|
|
366
|
+
registerQuickSpawns(pi);
|
|
367
|
+
registerArtifactBrowser(pi);
|
|
368
|
+
setupRealtimeUpdates(pi, treePanel);
|
|
369
|
+
// ============================================================================
|
|
370
|
+
// Session start hook - setup persistent header and footer
|
|
371
|
+
// ============================================================================
|
|
372
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
373
|
+
console.log("[🐉 DraconicTUI] Session started - setting up header/footer");
|
|
374
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
375
|
+
const registry = getDraconicRunRegistry();
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// 🐉 HEADER - Tree view using setWidget (only ACTIVE agents)
|
|
378
|
+
// ============================================================================
|
|
379
|
+
const MAX_HEADER_AGE_MS = 30_000; // Show completed agents for 30s max
|
|
380
|
+
const updateHeader = () => {
|
|
381
|
+
try {
|
|
382
|
+
// 🔧 FIX: Only show ACTIVE agents, not completed
|
|
383
|
+
const activeRuns = registry.query({ status: "running" }).runs;
|
|
384
|
+
const stats = registry.getStats();
|
|
385
|
+
if (activeRuns.length === 0) {
|
|
386
|
+
ctx.ui.setWidget("draconic-tree-header", ["🐉 No agents running"], {
|
|
387
|
+
placement: "aboveEditor",
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const lines = [];
|
|
392
|
+
lines.push(`🐉 Agents: ${stats.activeRuns}/${stats.totalRuns} running`);
|
|
393
|
+
lines.push("─".repeat(40));
|
|
394
|
+
// Show root agents with children (prioritize active)
|
|
395
|
+
const rootRuns = activeRuns
|
|
396
|
+
.filter(r => !r.parentId || r.depth === 0)
|
|
397
|
+
.sort((a, b) => {
|
|
398
|
+
// Sort: running first, then by time
|
|
399
|
+
if (a.status === "running" && b.status !== "running")
|
|
400
|
+
return -1;
|
|
401
|
+
if (b.status === "running" && a.status !== "running")
|
|
402
|
+
return 1;
|
|
403
|
+
return (b.metrics?.lastActivityAt || 0) - (a.metrics?.lastActivityAt || 0);
|
|
404
|
+
})
|
|
405
|
+
.slice(0, 3);
|
|
406
|
+
rootRuns.forEach((run, i) => {
|
|
407
|
+
const status = run.status === "running" ? "●" :
|
|
408
|
+
run.status === "completed" ? "✓" :
|
|
409
|
+
run.status === "error" ? "✗" : "○";
|
|
410
|
+
const type = run.type?.[0]?.toUpperCase() || "?";
|
|
411
|
+
const task = run.task?.slice(0, 25) || "";
|
|
412
|
+
const children = activeRuns.filter(r => r.parentId === run.id);
|
|
413
|
+
const childInfo = children.length > 0 ? ` (+${children.length})` : "";
|
|
414
|
+
lines.push(`${status} [${type}] ${run.id.slice(-6)}${childInfo} ${task}`);
|
|
415
|
+
// Show children
|
|
416
|
+
children.slice(0, 2).forEach((child, ci) => {
|
|
417
|
+
const cStatus = child.status === "running" ? "●" :
|
|
418
|
+
child.status === "completed" ? "✓" :
|
|
419
|
+
child.status === "error" ? "✗" : "○";
|
|
420
|
+
const cType = child.type?.[0]?.toUpperCase() || "?";
|
|
421
|
+
const prefix = ci === Math.min(children.length - 1, 1) ? " └─" : " ├─";
|
|
422
|
+
lines.push(`${prefix} ${cStatus} [${cType}] ${child.id.slice(-6)} ${child.task?.slice(0, 20) || ""}`);
|
|
423
|
+
});
|
|
424
|
+
if (children.length > 2) {
|
|
425
|
+
lines.push(` └─ ... ${children.length - 2} more`);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
ctx.ui.setWidget("draconic-tree-header", lines.slice(0, 8), {
|
|
429
|
+
placement: "aboveEditor",
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
// Silent fail
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
// Initial header
|
|
437
|
+
updateHeader();
|
|
438
|
+
// Update header every 500ms
|
|
439
|
+
const headerInterval = setInterval(updateHeader, 500);
|
|
440
|
+
// ============================================================================
|
|
441
|
+
// 🐉 FOOTER - Compact status
|
|
442
|
+
// ============================================================================
|
|
443
|
+
const updateFooter = async () => {
|
|
444
|
+
try {
|
|
445
|
+
const stats = registry.getStats();
|
|
446
|
+
if (stats.totalRuns === 0) {
|
|
447
|
+
ctx.ui.setStatus("draconic-tree", "🐉 idle");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
// Find most relevant agent to display - ONLY RUNNING
|
|
451
|
+
const activeRuns = registry.query({ status: "running" }).runs;
|
|
452
|
+
const displayRun = activeRuns
|
|
453
|
+
.sort((a, b) => (b.depth || 0) - (a.depth || 0))[0]; // Prefer deeper (subagents)
|
|
454
|
+
if (displayRun && stats.activeRuns > 0) {
|
|
455
|
+
const emoji = displayRun.type === "specialist" ? "👨💻" :
|
|
456
|
+
displayRun.type === "researcher" ? "🔬" :
|
|
457
|
+
displayRun.type === "planner" ? "📋" :
|
|
458
|
+
displayRun.type === "reviewer" ? "👁️" : "🐉";
|
|
459
|
+
const task = displayRun.task?.slice(0, 15) || "";
|
|
460
|
+
const completedCount = stats.totalRuns - stats.activeRuns;
|
|
461
|
+
const completedInfo = completedCount > 0 ? ` (+${completedCount})` : "";
|
|
462
|
+
ctx.ui.setStatus("draconic-tree", `🐉 ${stats.activeRuns} running${completedInfo} ${emoji} ${task}`);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
ctx.ui.setStatus("draconic-tree", "🐉 idle");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (e) {
|
|
469
|
+
// Silent fail
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
// Initial footer
|
|
473
|
+
await updateFooter();
|
|
474
|
+
// Update footer every 500ms
|
|
475
|
+
const footerInterval = setInterval(updateFooter, 500);
|
|
476
|
+
// Cleanup on session shutdown
|
|
477
|
+
pi.on("session_shutdown", async () => {
|
|
478
|
+
clearInterval(headerInterval);
|
|
479
|
+
clearInterval(footerInterval);
|
|
480
|
+
ctx.ui.setWidget("draconic-tree-header", undefined);
|
|
481
|
+
ctx.ui.setStatus("draconic-tree", undefined);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
// ============================================================================
|
|
485
|
+
// Subscribe to events for status updates
|
|
486
|
+
// ============================================================================
|
|
487
|
+
// Also update tree panel on events
|
|
488
|
+
eventBus.on("agent.spawned", () => treePanel.refresh());
|
|
489
|
+
eventBus.on("agent.completed", () => treePanel.refresh());
|
|
490
|
+
console.log("[🐉 DraconicTUI] Integration loaded");
|
|
491
|
+
console.log(" Commands: /agent-spawn, /agent-tree, /agent-result, /sidebar");
|
|
492
|
+
console.log(" Natural: 'spawn a researcher to...', 'analyze...', 'implement...'");
|
|
493
|
+
console.log(" Tools: draconic_tui_status, agent_tree_view, agent_result_view");
|
|
494
|
+
console.log(" Footer: 🐉 agent status (auto-updates)");
|
|
495
|
+
console.log(" Panel: /agent-tree-toggle (show/hide), /agent-tree-lines (get display)");
|
|
496
|
+
console.log(" Sidebar: /sidebar (toggle)");
|
|
497
|
+
}
|
|
498
|
+
// ============================================================================
|
|
499
|
+
// 🐉 KEYBOARD SHORTCUTS - Quick Access
|
|
500
|
+
// ============================================================================
|
|
501
|
+
function registerKeybindings(pi) {
|
|
502
|
+
// Ctrl+Shift+A - Show agent tree
|
|
503
|
+
pi.registerShortcut("ctrl+shift+a", {
|
|
504
|
+
description: "🐉 Show agent tree",
|
|
505
|
+
handler: async (ctx) => {
|
|
506
|
+
ctx.ui.notify("🐉 Opening agent tree...", "info");
|
|
507
|
+
// Simulate command execution
|
|
508
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
509
|
+
const registry = getDraconicRunRegistry();
|
|
510
|
+
const allRuns = registry.query({}).runs;
|
|
511
|
+
if (allRuns.length === 0) {
|
|
512
|
+
ctx.ui.notify("🐉 No agents running", "info");
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
ctx.ui.notify(`🐉 ${allRuns.length} agents`, "info");
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
// Ctrl+Shift+S - Quick spawn specialist
|
|
520
|
+
pi.registerShortcut("ctrl+shift+s", {
|
|
521
|
+
description: "🐉 Quick spawn specialist",
|
|
522
|
+
handler: async (ctx) => {
|
|
523
|
+
ctx.ui.notify("🐉 Use: /spawn-specialist <task>", "info");
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
// Ctrl+Shift+R - Quick spawn researcher
|
|
527
|
+
pi.registerShortcut("ctrl+shift+r", {
|
|
528
|
+
description: "🐉 Quick spawn researcher",
|
|
529
|
+
handler: async (ctx) => {
|
|
530
|
+
ctx.ui.notify("🐉 Use: /spawn-researcher <task>", "info");
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
// Ctrl+Shift+K - Kill last running agent
|
|
534
|
+
pi.registerShortcut("ctrl+shift+k", {
|
|
535
|
+
description: "🐉 Kill last running agent",
|
|
536
|
+
handler: async (ctx) => {
|
|
537
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
538
|
+
const registry = getDraconicRunRegistry();
|
|
539
|
+
const running = registry.query({ status: "running" }).runs[0];
|
|
540
|
+
if (running) {
|
|
541
|
+
registry.updateStatus(running.id, "error");
|
|
542
|
+
ctx.ui.notify(`🐉 Stopped ${running.id.slice(-8)}`, "info");
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
ctx.ui.notify("🐉 No running agents", "info");
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
console.log("[🐉 DraconicTUI] Keybindings registered:");
|
|
550
|
+
console.log(" ctrl+shift+a = Show tree");
|
|
551
|
+
console.log(" ctrl+shift+s = Quick specialist");
|
|
552
|
+
console.log(" ctrl+shift+r = Quick researcher");
|
|
553
|
+
console.log(" ctrl+shift+k = Kill agent");
|
|
554
|
+
}
|
|
555
|
+
// ============================================================================
|
|
556
|
+
// 🐉 AGENT CONTROL COMMANDS
|
|
557
|
+
// ============================================================================
|
|
558
|
+
function registerAgentControls(pi, tui) {
|
|
559
|
+
// /agent-stop <runId>
|
|
560
|
+
pi.registerTool({
|
|
561
|
+
name: "agent_stop",
|
|
562
|
+
label: "🛑 Stop Agent",
|
|
563
|
+
description: "Stop a running agent gracefully",
|
|
564
|
+
parameters: Type.Object({
|
|
565
|
+
runId: Type.String({ description: "Agent run ID (or 'last' for most recent)" }),
|
|
566
|
+
}),
|
|
567
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
568
|
+
const runId = params.runId;
|
|
569
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
570
|
+
const registry = getDraconicRunRegistry();
|
|
571
|
+
let targetId = runId;
|
|
572
|
+
if (runId === "last") {
|
|
573
|
+
const last = registry.query({ status: "running" }).runs[0];
|
|
574
|
+
if (!last) {
|
|
575
|
+
return { content: [{ type: "text", text: "No running agents" }], details: null };
|
|
576
|
+
}
|
|
577
|
+
targetId = last.id;
|
|
578
|
+
}
|
|
579
|
+
const run = registry.get(targetId);
|
|
580
|
+
if (!run) {
|
|
581
|
+
return { content: [{ type: "text", text: `Agent ${targetId} not found` }], details: null };
|
|
582
|
+
}
|
|
583
|
+
if (run.status !== "running") {
|
|
584
|
+
return { content: [{ type: "text", text: `Agent ${targetId} is already ${run.status}` }], details: { status: run.status } };
|
|
585
|
+
}
|
|
586
|
+
registry.updateStatus(targetId, "paused");
|
|
587
|
+
ctx.ui.notify(`🛑 Stopped ${targetId.slice(-8)}`, "info");
|
|
588
|
+
return {
|
|
589
|
+
content: [{ type: "text", text: `🛑 Stopped agent ${targetId}\nType: ${run.type}\nTask: ${run.task?.slice(0, 50)}...` }],
|
|
590
|
+
details: { runId: targetId, status: "paused" },
|
|
591
|
+
};
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
// /agent-resume <runId>
|
|
595
|
+
pi.registerTool({
|
|
596
|
+
name: "agent_resume",
|
|
597
|
+
label: "▶️ Resume Agent",
|
|
598
|
+
description: "Resume a paused agent",
|
|
599
|
+
parameters: Type.Object({
|
|
600
|
+
runId: Type.String({ description: "Agent run ID (or 'last')" }),
|
|
601
|
+
}),
|
|
602
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
603
|
+
const runId = params.runId;
|
|
604
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
605
|
+
const registry = getDraconicRunRegistry();
|
|
606
|
+
let targetId = runId;
|
|
607
|
+
if (runId === "last") {
|
|
608
|
+
const last = registry.query({ status: "paused" }).runs[0];
|
|
609
|
+
if (!last) {
|
|
610
|
+
return { content: [{ type: "text", text: "No paused agents" }], details: null };
|
|
611
|
+
}
|
|
612
|
+
targetId = last.id;
|
|
613
|
+
}
|
|
614
|
+
const run = registry.get(targetId);
|
|
615
|
+
if (!run) {
|
|
616
|
+
return { content: [{ type: "text", text: `Agent ${targetId} not found` }], details: null };
|
|
617
|
+
}
|
|
618
|
+
if (run.status !== "paused") {
|
|
619
|
+
return { content: [{ type: "text", text: `Agent ${targetId} is ${run.status}, not paused` }], details: { status: run.status } };
|
|
620
|
+
}
|
|
621
|
+
registry.updateStatus(targetId, "running");
|
|
622
|
+
ctx.ui.notify(`▶️ Resumed ${targetId.slice(-8)}`, "info");
|
|
623
|
+
return {
|
|
624
|
+
content: [{ type: "text", text: `▶️ Resumed agent ${targetId}` }],
|
|
625
|
+
details: { runId: targetId, status: "running" },
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
// /agent-kill <runId>
|
|
630
|
+
pi.registerTool({
|
|
631
|
+
name: "agent_kill",
|
|
632
|
+
label: "💀 Kill Agent",
|
|
633
|
+
description: "Force kill an agent (use /agent-stop for graceful stop)",
|
|
634
|
+
parameters: Type.Object({
|
|
635
|
+
runId: Type.String({ description: "Agent run ID (or 'last')" }),
|
|
636
|
+
}),
|
|
637
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
638
|
+
const runId = params.runId;
|
|
639
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
640
|
+
const registry = getDraconicRunRegistry();
|
|
641
|
+
let targetId = runId;
|
|
642
|
+
if (runId === "last") {
|
|
643
|
+
const last = registry.query({}).runs[registry.query({}).runs.length - 1];
|
|
644
|
+
if (!last) {
|
|
645
|
+
return { content: [{ type: "text", text: "No agents" }], details: null };
|
|
646
|
+
}
|
|
647
|
+
targetId = last.id;
|
|
648
|
+
}
|
|
649
|
+
const run = registry.get(targetId);
|
|
650
|
+
if (!run) {
|
|
651
|
+
return { content: [{ type: "text", text: `Agent ${targetId} not found` }], details: null };
|
|
652
|
+
}
|
|
653
|
+
registry.updateStatus(targetId, "error");
|
|
654
|
+
eventBus.emit("agent.stopped", { runId: targetId, type: run.type, reason: "killed" });
|
|
655
|
+
ctx.ui.notify(`💀 Killed ${targetId.slice(-8)}`, "warning");
|
|
656
|
+
return {
|
|
657
|
+
content: [{ type: "text", text: `💀 Killed agent ${targetId}` }],
|
|
658
|
+
details: { runId: targetId, killed: true },
|
|
659
|
+
};
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
// /agents - List all with controls
|
|
663
|
+
pi.registerTool({
|
|
664
|
+
name: "agents_list_controls",
|
|
665
|
+
label: "🐉 Agent Controls",
|
|
666
|
+
description: "List all agents with control options",
|
|
667
|
+
parameters: Type.Object({
|
|
668
|
+
filter: Type.Optional(Type.String({ description: "Filter by status: running, completed, error, paused, all" })),
|
|
669
|
+
}),
|
|
670
|
+
async execute(_id, params) {
|
|
671
|
+
const status = params.filter;
|
|
672
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
673
|
+
const registry = getDraconicRunRegistry();
|
|
674
|
+
const allRuns = status
|
|
675
|
+
? registry.query({ status: status }).runs
|
|
676
|
+
: registry.query({}).runs;
|
|
677
|
+
if (allRuns.length === 0) {
|
|
678
|
+
return {
|
|
679
|
+
content: [{ type: "text", text: "🐉 No agents" }],
|
|
680
|
+
details: { count: 0 },
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
const lines = [`🐉 Agents (${allRuns.length})`];
|
|
684
|
+
lines.push("─".repeat(50));
|
|
685
|
+
allRuns.forEach((run, i) => {
|
|
686
|
+
const statusEmoji = run.status === "running" ? "▶️" : run.status === "completed" ? "✅" : run.status === "error" ? "❌" : run.status === "paused" ? "⏸️" : "○";
|
|
687
|
+
const type = run.type?.[0]?.toUpperCase() || "?";
|
|
688
|
+
const task = run.task?.slice(0, 25) || "no task";
|
|
689
|
+
lines.push(`${i + 1}. ${statusEmoji} [${type}] ${run.id.slice(-8)} | ${task}`);
|
|
690
|
+
});
|
|
691
|
+
lines.push("");
|
|
692
|
+
lines.push("Commands:");
|
|
693
|
+
lines.push(" /agent-stop <id|last> - Stop gracefully");
|
|
694
|
+
lines.push(" /agent-resume <id|last> - Resume paused");
|
|
695
|
+
lines.push(" /agent-kill <id|last> - Force kill");
|
|
696
|
+
return {
|
|
697
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
698
|
+
details: { count: allRuns.length, runs: allRuns.map(r => ({ id: r.id, status: r.status, type: r.type })) },
|
|
699
|
+
};
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
console.log("[🐉 DraconicTUI] Agent controls registered:");
|
|
703
|
+
console.log(" /agent-stop, /agent-resume, /agent-kill, /agents");
|
|
704
|
+
}
|
|
705
|
+
// ============================================================================
|
|
706
|
+
// 🐉 QUICK SPAWN PRESETS
|
|
707
|
+
// ============================================================================
|
|
708
|
+
function registerQuickSpawns(pi) {
|
|
709
|
+
const spawnAgent = async (type, task, ctx) => {
|
|
710
|
+
ctx.ui.notify(`🐉 Spawning ${type}...`, "info");
|
|
711
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
712
|
+
const registry = getDraconicRunRegistry();
|
|
713
|
+
const parentId = process.env.DRACONIC_RUN_ID;
|
|
714
|
+
const parent = parentId ? registry.get(parentId) : undefined;
|
|
715
|
+
const run = registry.create({
|
|
716
|
+
sessionKey: process.env.DRACONIC_SESSION_KEY || "tui",
|
|
717
|
+
name: `${type}-${Date.now().toString(36).slice(-4)}`,
|
|
718
|
+
type: type,
|
|
719
|
+
task,
|
|
720
|
+
workspace: process.cwd(),
|
|
721
|
+
capabilities: {
|
|
722
|
+
primary: type === "researcher" ? ["research", "analysis"] :
|
|
723
|
+
type === "specialist" ? ["coding", "implementation"] :
|
|
724
|
+
type === "planner" ? ["planning", "architecture"] :
|
|
725
|
+
type === "reviewer" ? ["review", "quality"] : ["general"],
|
|
726
|
+
secondary: [],
|
|
727
|
+
},
|
|
728
|
+
parentId,
|
|
729
|
+
depth: (parent?.depth ?? -1) + 1,
|
|
730
|
+
isProcessingQueue: false,
|
|
731
|
+
});
|
|
732
|
+
registry.updateStatus(run.id, "running");
|
|
733
|
+
eventBus.emit("agent.spawned", { runId: run.id, parentId, type });
|
|
734
|
+
ctx.ui.notify(`Spawned ${type}: ${run.id.slice(-8)}`, "info");
|
|
735
|
+
return run.id;
|
|
736
|
+
};
|
|
737
|
+
// /specialist <task>
|
|
738
|
+
pi.registerTool({
|
|
739
|
+
name: "spawn_specialist",
|
|
740
|
+
label: "👨💻 Specialist",
|
|
741
|
+
description: "Quick spawn a specialist agent for coding tasks",
|
|
742
|
+
parameters: Type.Object({
|
|
743
|
+
task: Type.String({ description: "Task description" }),
|
|
744
|
+
strategy: Type.Optional(Type.String({ description: "Strategy: fast, thorough, auto" })),
|
|
745
|
+
}),
|
|
746
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
747
|
+
const task = params.task;
|
|
748
|
+
const runId = await spawnAgent("specialist", task, ctx);
|
|
749
|
+
return {
|
|
750
|
+
content: [{ type: "text", text: `👨💻 Spawned specialist\nTask: ${task.slice(0, 50)}...\nID: ${runId}` }],
|
|
751
|
+
details: { runId, type: "specialist", task },
|
|
752
|
+
};
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
// /researcher <task>
|
|
756
|
+
pi.registerTool({
|
|
757
|
+
name: "spawn_researcher",
|
|
758
|
+
label: "🔬 Researcher",
|
|
759
|
+
description: "Quick spawn a researcher agent for analysis",
|
|
760
|
+
parameters: Type.Object({
|
|
761
|
+
task: Type.String({ description: "Research task" }),
|
|
762
|
+
}),
|
|
763
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
764
|
+
const task = params.task;
|
|
765
|
+
const runId = await spawnAgent("researcher", task, ctx);
|
|
766
|
+
return {
|
|
767
|
+
content: [{ type: "text", text: `🔬 Spawned researcher\nTask: ${task.slice(0, 50)}...\nID: ${runId}` }],
|
|
768
|
+
details: { runId, type: "researcher", task },
|
|
769
|
+
};
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
// /planner <task>
|
|
773
|
+
pi.registerTool({
|
|
774
|
+
name: "spawn_planner",
|
|
775
|
+
label: "📋 Planner",
|
|
776
|
+
description: "Quick spawn a planner agent for architecture/design",
|
|
777
|
+
parameters: Type.Object({
|
|
778
|
+
task: Type.String({ description: "Planning task" }),
|
|
779
|
+
}),
|
|
780
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
781
|
+
const task = params.task;
|
|
782
|
+
const runId = await spawnAgent("planner", task, ctx);
|
|
783
|
+
return {
|
|
784
|
+
content: [{ type: "text", text: `📋 Spawned planner\nTask: ${task.slice(0, 50)}...\nID: ${runId}` }],
|
|
785
|
+
details: { runId, type: "planner", task },
|
|
786
|
+
};
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
// /reviewer <task>
|
|
790
|
+
pi.registerTool({
|
|
791
|
+
name: "spawn_reviewer",
|
|
792
|
+
label: "👁️ Reviewer",
|
|
793
|
+
description: "Quick spawn a reviewer agent for code review",
|
|
794
|
+
parameters: Type.Object({
|
|
795
|
+
task: Type.String({ description: "Review task" }),
|
|
796
|
+
}),
|
|
797
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
798
|
+
const task = params.task;
|
|
799
|
+
const runId = await spawnAgent("reviewer", task, ctx);
|
|
800
|
+
return {
|
|
801
|
+
content: [{ type: "text", text: `👁️ Spawned reviewer\nTask: ${task.slice(0, 50)}...\nID: ${runId}` }],
|
|
802
|
+
details: { runId, type: "reviewer", task },
|
|
803
|
+
};
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
console.log("[🐉 DraconicTUI] Quick spawns registered:");
|
|
807
|
+
console.log(" /specialist, /researcher, /planner, /reviewer");
|
|
808
|
+
}
|
|
809
|
+
// ============================================================================
|
|
810
|
+
// 🐉 ARTIFACT BROWSER
|
|
811
|
+
// ============================================================================
|
|
812
|
+
function registerArtifactBrowser(pi) {
|
|
813
|
+
// /artifacts
|
|
814
|
+
pi.registerTool({
|
|
815
|
+
name: "artifacts_list",
|
|
816
|
+
label: "🗂️ Artifacts",
|
|
817
|
+
description: "Browse all agent artifacts (outputs)",
|
|
818
|
+
parameters: Type.Object({
|
|
819
|
+
limit: Type.Optional(Type.Number({ description: "Max artifacts to show", default: 10 })),
|
|
820
|
+
}),
|
|
821
|
+
async execute(_id, params) {
|
|
822
|
+
const limit = params.limit || 10;
|
|
823
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
824
|
+
const registry = getDraconicRunRegistry();
|
|
825
|
+
// Get completed runs with artifacts
|
|
826
|
+
const allRuns = registry.query({ status: "completed" }).runs.slice(0, limit);
|
|
827
|
+
if (allRuns.length === 0) {
|
|
828
|
+
return {
|
|
829
|
+
content: [{ type: "text", text: "🗂️ No artifacts yet\n\nComplete an agent run to generate artifacts." }],
|
|
830
|
+
details: { count: 0 },
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
const lines = [`�️ Artifacts (${allRuns.length})`];
|
|
834
|
+
lines.push("─".repeat(50));
|
|
835
|
+
allRuns.forEach((run, i) => {
|
|
836
|
+
const type = run.type?.[0]?.toUpperCase() || "?";
|
|
837
|
+
const task = run.task?.slice(0, 30) || "no task";
|
|
838
|
+
const duration = run.metrics?.duration
|
|
839
|
+
? Math.round(run.metrics.duration / 1000) + "s"
|
|
840
|
+
: "?";
|
|
841
|
+
lines.push(`${i + 1}. [${type}] ${run.id.slice(-8)} | ${duration} | ${task}`);
|
|
842
|
+
});
|
|
843
|
+
lines.push("");
|
|
844
|
+
lines.push("View: /artifact <runId> or /artifact-latest");
|
|
845
|
+
return {
|
|
846
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
847
|
+
details: { count: allRuns.length, runs: allRuns.map(r => r.id) },
|
|
848
|
+
};
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
// /artifact <runId>
|
|
852
|
+
pi.registerTool({
|
|
853
|
+
name: "artifact_view",
|
|
854
|
+
label: "📄 View Artifact",
|
|
855
|
+
description: "View specific artifact by run ID",
|
|
856
|
+
parameters: Type.Object({
|
|
857
|
+
runId: Type.String({ description: "Run ID (or 'latest')" }),
|
|
858
|
+
}),
|
|
859
|
+
async execute(_id, params) {
|
|
860
|
+
let runId = params.runId;
|
|
861
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
862
|
+
const registry = getDraconicRunRegistry();
|
|
863
|
+
if (runId === "latest") {
|
|
864
|
+
const latest = registry.query({}).runs
|
|
865
|
+
.filter(r => r.artifacts && r.artifacts.length > 0)
|
|
866
|
+
.sort((a, b) => (b.metrics?.lastActivityAt || 0) - (a.metrics?.lastActivityAt || 0))[0];
|
|
867
|
+
if (!latest) {
|
|
868
|
+
return { content: [{ type: "text", text: "No artifacts yet" }], details: null };
|
|
869
|
+
}
|
|
870
|
+
runId = latest.id;
|
|
871
|
+
}
|
|
872
|
+
const run = registry.get(runId);
|
|
873
|
+
if (!run) {
|
|
874
|
+
return { content: [{ type: "text", text: `Run ${runId} not found` }], details: null };
|
|
875
|
+
}
|
|
876
|
+
if (!run.artifacts || run.artifacts.length === 0) {
|
|
877
|
+
return {
|
|
878
|
+
content: [{ type: "text", text: `Run ${runId} has no artifacts\nStatus: ${run.status}\nTask: ${run.task?.slice(0, 50)}...` }],
|
|
879
|
+
details: { runId, status: run.status }
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
const artifact = run.artifacts[0];
|
|
883
|
+
const content = typeof artifact.content === "string"
|
|
884
|
+
? artifact.content
|
|
885
|
+
: JSON.stringify(artifact.content, null, 2);
|
|
886
|
+
const preview = content.slice(0, 2000);
|
|
887
|
+
const lines = [
|
|
888
|
+
`📄 Artifact: ${runId.slice(-8)}`,
|
|
889
|
+
`Type: ${run.type}`,
|
|
890
|
+
`Status: ${run.status}`,
|
|
891
|
+
`Task: ${run.task}`,
|
|
892
|
+
"─".repeat(50),
|
|
893
|
+
preview,
|
|
894
|
+
content.length > 2000 ? "\n... (truncated, use /artifact-result for full)" : "",
|
|
895
|
+
];
|
|
896
|
+
return {
|
|
897
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
898
|
+
details: { runId, artifact, truncated: content.length > 2000 },
|
|
899
|
+
};
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
// /artifact-latest
|
|
903
|
+
pi.registerTool({
|
|
904
|
+
name: "artifact_latest",
|
|
905
|
+
label: "📄 Latest Artifact",
|
|
906
|
+
description: "View most recent artifact",
|
|
907
|
+
parameters: Type.Object({}),
|
|
908
|
+
async execute(_id) {
|
|
909
|
+
const { getDraconicRunRegistry } = await import("../../agent/DraconicRunRegistry");
|
|
910
|
+
const registry = getDraconicRunRegistry();
|
|
911
|
+
const latest = registry.query({}).runs
|
|
912
|
+
.filter(r => r.artifacts && r.artifacts.length > 0)
|
|
913
|
+
.sort((a, b) => (b.metrics?.lastActivityAt || 0) - (a.metrics?.lastActivityAt || 0))[0];
|
|
914
|
+
if (!latest) {
|
|
915
|
+
return { content: [{ type: "text", text: "🗂️ No artifacts yet" }], details: null };
|
|
916
|
+
}
|
|
917
|
+
const artifact = latest.artifacts[0];
|
|
918
|
+
const content = typeof artifact.content === "string"
|
|
919
|
+
? artifact.content
|
|
920
|
+
: JSON.stringify(artifact.content, null, 2);
|
|
921
|
+
return {
|
|
922
|
+
content: [{
|
|
923
|
+
type: "text",
|
|
924
|
+
text: `📄 Latest: ${latest.id.slice(-8)} [${latest.type}]\n\n${content.slice(0, 2000)}${content.length > 2000 ? "\n..." : ""}`,
|
|
925
|
+
}],
|
|
926
|
+
details: { runId: latest.id, artifact },
|
|
927
|
+
};
|
|
928
|
+
},
|
|
929
|
+
});
|
|
930
|
+
console.log("[🐉 DraconicTUI] Artifact browser registered:");
|
|
931
|
+
console.log(" /artifacts, /artifact, /artifact-latest");
|
|
932
|
+
}
|
|
933
|
+
// ============================================================================
|
|
934
|
+
// REAL-TIME TREE UPDATES - Event-driven
|
|
935
|
+
// ============================================================================
|
|
936
|
+
function setupRealtimeUpdates(pi, treePanel) {
|
|
937
|
+
// Subscribe to all agent lifecycle events
|
|
938
|
+
const events = ["agent.spawned", "agent.started", "agent.completed", "agent.error", "agent.stopped", "agent.status_changed"];
|
|
939
|
+
events.forEach(eventName => {
|
|
940
|
+
eventBus.on(eventName, (data) => {
|
|
941
|
+
console.log(`[🐉 DraconicTUI] Event: ${eventName}`, data?.runId?.slice(-8) || "");
|
|
942
|
+
treePanel.refresh();
|
|
943
|
+
});
|
|
944
|
+
});
|
|
945
|
+
console.log("[🐉 DraconicTUI] Real-time updates enabled");
|
|
946
|
+
console.log(" Events:", events.join(", "));
|
|
947
|
+
}
|
|
948
|
+
// Helpers
|
|
949
|
+
function countAgents(tree) {
|
|
950
|
+
let active = tree.status === "running" ? 1 : 0;
|
|
951
|
+
let total = 1;
|
|
952
|
+
for (const child of tree.children || []) {
|
|
953
|
+
const childCounts = countAgents(child);
|
|
954
|
+
active += childCounts.active;
|
|
955
|
+
total += childCounts.total;
|
|
956
|
+
}
|
|
957
|
+
return { active, total };
|
|
958
|
+
}
|
|
959
|
+
function findActiveTask(tree) {
|
|
960
|
+
if (tree.status === "running" && tree.task) {
|
|
961
|
+
return tree.task;
|
|
962
|
+
}
|
|
963
|
+
for (const child of tree.children || []) {
|
|
964
|
+
const task = findActiveTask(child);
|
|
965
|
+
if (task)
|
|
966
|
+
return task;
|
|
967
|
+
}
|
|
968
|
+
return undefined;
|
|
969
|
+
}
|
|
970
|
+
function formatTree(node, indent = "", isLast = true) {
|
|
971
|
+
const prefix = " ".repeat(indent.length / 2);
|
|
972
|
+
const connector = isLast ? "└── " : "├── ";
|
|
973
|
+
const status = node.status === "completed" ? "✅" : node.status === "error" ? "❌" : "🔄";
|
|
974
|
+
const type = node.type?.[0]?.toUpperCase() || "?";
|
|
975
|
+
const task = node.task ? `: ${node.task.slice(0, 30)}` : "";
|
|
976
|
+
let lines = [`${prefix}${connector}${status} [${type}] ${node.runId}${task}`];
|
|
977
|
+
const children = node.children || [];
|
|
978
|
+
children.forEach((child, i) => {
|
|
979
|
+
const childIsLast = i === children.length - 1;
|
|
980
|
+
lines.push(formatTree(child, prefix + (isLast ? " " : "│ "), childIsLast));
|
|
981
|
+
});
|
|
982
|
+
return lines.join("\n");
|
|
983
|
+
}
|
|
984
|
+
//# sourceMappingURL=tui-integration-extension.js.map
|