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.
Files changed (130) hide show
  1. package/README.md +226 -383
  2. package/dist/package.json +13 -4
  3. package/dist/src/agent/DraconicCapabilityRouter.js +556 -0
  4. package/dist/src/agent/DraconicCapabilityRouter.js.map +1 -0
  5. package/dist/src/agent/DraconicErrorClassifier.js +606 -0
  6. package/dist/src/agent/DraconicErrorClassifier.js.map +1 -0
  7. package/dist/src/agent/DraconicRunRegistry.js +460 -0
  8. package/dist/src/agent/DraconicRunRegistry.js.map +1 -0
  9. package/dist/src/agent/DraconicTokenPredictor.js +500 -0
  10. package/dist/src/agent/DraconicTokenPredictor.js.map +1 -0
  11. package/dist/src/agent/embedded-runner.js +3 -2
  12. package/dist/src/agent/embedded-runner.js.map +1 -1
  13. package/dist/src/cli/commands/doctor.js +199 -0
  14. package/dist/src/cli/commands/doctor.js.map +1 -0
  15. package/dist/src/cli/commands/migrate-from-openclaw.js +39 -0
  16. package/dist/src/cli/commands/migrate-from-openclaw.js.map +1 -0
  17. package/dist/src/cli/commands/migrate.js +6 -4
  18. package/dist/src/cli/commands/migrate.js.map +1 -1
  19. package/dist/src/cli/program.js +3 -1
  20. package/dist/src/cli/program.js.map +1 -1
  21. package/dist/src/config/unified-config.js +195 -0
  22. package/dist/src/config/unified-config.js.map +1 -0
  23. package/dist/src/cron/notifications.js +1 -1
  24. package/dist/src/cron/notifications.js.map +1 -1
  25. package/dist/src/cron/runner.js +9 -3
  26. package/dist/src/cron/runner.js.map +1 -1
  27. package/dist/src/event-bus/index.js +3 -1
  28. package/dist/src/event-bus/index.js.map +1 -1
  29. package/dist/src/extensions/community/draconic-messenger-wrapper.js +35 -0
  30. package/dist/src/extensions/community/draconic-messenger-wrapper.js.map +1 -0
  31. package/dist/src/extensions/community/draconic-subagents-wrapper.js +115 -0
  32. package/dist/src/extensions/community/draconic-subagents-wrapper.js.map +1 -0
  33. package/dist/src/extensions/core/compaction-safeguard-v2.js +95 -0
  34. package/dist/src/extensions/core/compaction-safeguard-v2.js.map +1 -0
  35. package/dist/src/extensions/core/compaction-safeguard.js +206 -137
  36. package/dist/src/extensions/core/compaction-safeguard.js.map +1 -1
  37. package/dist/src/extensions/core/config-extension.js +122 -0
  38. package/dist/src/extensions/core/config-extension.js.map +1 -0
  39. package/dist/src/extensions/core/draconic-hoard-extension.js +194 -0
  40. package/dist/src/extensions/core/draconic-hoard-extension.js.map +1 -0
  41. package/dist/src/extensions/core/draconic-lair-extension.js +146 -0
  42. package/dist/src/extensions/core/draconic-lair-extension.js.map +1 -0
  43. package/dist/src/extensions/core/draconic-safety-extension.js +402 -0
  44. package/dist/src/extensions/core/draconic-safety-extension.js.map +1 -0
  45. package/dist/src/extensions/core/ext-manager.js +40 -0
  46. package/dist/src/extensions/core/ext-manager.js.map +1 -0
  47. package/dist/src/extensions/core/gateway-extension.js +413 -624
  48. package/dist/src/extensions/core/gateway-extension.js.map +1 -1
  49. package/dist/src/extensions/core/generative-agents-extension.js +704 -0
  50. package/dist/src/extensions/core/generative-agents-extension.js.map +1 -0
  51. package/dist/src/extensions/core/memory-bootstrap-extension.js +279 -0
  52. package/dist/src/extensions/core/memory-bootstrap-extension.js.map +1 -0
  53. package/dist/src/extensions/core/ollama-extension.js +442 -0
  54. package/dist/src/extensions/core/ollama-extension.js.map +1 -0
  55. package/dist/src/extensions/core/ollama-router-extension.js +34 -93
  56. package/dist/src/extensions/core/ollama-router-extension.js.map +1 -1
  57. package/dist/src/extensions/core/perennial-memory-extension.js +5 -2
  58. package/dist/src/extensions/core/perennial-memory-extension.js.map +1 -1
  59. package/dist/src/extensions/core/persona-loader-extension.js +41 -15
  60. package/dist/src/extensions/core/persona-loader-extension.js.map +1 -1
  61. package/dist/src/extensions/core/pi-notify-extension.js +6 -0
  62. package/dist/src/extensions/core/pi-notify-extension.js.map +1 -1
  63. package/dist/src/extensions/core/session-bridge-extension.js +8 -72
  64. package/dist/src/extensions/core/session-bridge-extension.js.map +1 -1
  65. package/dist/src/extensions/core/tui-integration-extension-fixed.js +3 -0
  66. package/dist/src/extensions/core/tui-integration-extension-fixed.js.map +1 -0
  67. package/dist/src/extensions/core/tui-integration-extension.js +984 -0
  68. package/dist/src/extensions/core/tui-integration-extension.js.map +1 -0
  69. package/dist/src/gateway/persistence/AgentStore.js +399 -0
  70. package/dist/src/gateway/persistence/AgentStore.js.map +1 -0
  71. package/dist/src/gateway/persistence/index.js +9 -0
  72. package/dist/src/gateway/persistence/index.js.map +1 -0
  73. package/dist/src/hoard/DraconicHoardSystem.js +465 -0
  74. package/dist/src/hoard/DraconicHoardSystem.js.map +1 -0
  75. package/dist/src/index.js +7 -5
  76. package/dist/src/index.js.map +1 -1
  77. package/dist/src/infra/DraconicConnectionPool.js +492 -0
  78. package/dist/src/infra/DraconicConnectionPool.js.map +1 -0
  79. package/dist/src/lair/DraconicLairSystem.js +387 -0
  80. package/dist/src/lair/DraconicLairSystem.js.map +1 -0
  81. package/dist/src/llm/ollama.js +143 -99
  82. package/dist/src/llm/ollama.js.map +1 -1
  83. package/dist/src/migration/openclaw.js +5 -5
  84. package/dist/src/migration/openclaw.js.map +1 -1
  85. package/dist/src/pi-config.js +52 -27
  86. package/dist/src/pi-config.js.map +1 -1
  87. package/dist/src/sessions/SessionManager.js +416 -0
  88. package/dist/src/sessions/SessionManager.js.map +1 -0
  89. package/dist/src/sessions/SessionStore.js +580 -0
  90. package/dist/src/sessions/SessionStore.js.map +1 -0
  91. package/dist/src/sessions/UnifiedSessionBridge.js +394 -0
  92. package/dist/src/sessions/UnifiedSessionBridge.js.map +1 -0
  93. package/dist/src/sessions/index.js +15 -0
  94. package/dist/src/sessions/index.js.map +1 -0
  95. package/dist/src/sessions/migration/index.js +156 -0
  96. package/dist/src/sessions/migration/index.js.map +1 -0
  97. package/dist/src/sessions/types.js +8 -0
  98. package/dist/src/sessions/types.js.map +1 -0
  99. package/dist/src/skills/framework.js +1 -1
  100. package/dist/src/skills/framework.js.map +1 -1
  101. package/dist/src/tui/commands/final-functions.js +39 -0
  102. package/dist/src/tui/commands/final-functions.js.map +1 -0
  103. package/dist/src/tui/commands/natural-language-commands.js +231 -0
  104. package/dist/src/tui/commands/natural-language-commands.js.map +1 -0
  105. package/dist/src/tui/commands/orchestration-commands.js +340 -0
  106. package/dist/src/tui/commands/orchestration-commands.js.map +1 -0
  107. package/dist/src/tui/commands/orchestration-final-section.js +84 -0
  108. package/dist/src/tui/commands/orchestration-final-section.js.map +1 -0
  109. package/dist/src/tui/components/agent-tree-overlay.js +210 -0
  110. package/dist/src/tui/components/agent-tree-overlay.js.map +1 -0
  111. package/dist/src/tui/components/agent-tree-panel.js +229 -0
  112. package/dist/src/tui/components/agent-tree-panel.js.map +1 -0
  113. package/dist/src/tui/components/sidebar.js +82 -0
  114. package/dist/src/tui/components/sidebar.js.map +1 -0
  115. package/dist/src/tui/components/status-bar.js +115 -0
  116. package/dist/src/tui/components/status-bar.js.map +1 -0
  117. package/dist/src/tui/components/tree-header.js +85 -0
  118. package/dist/src/tui/components/tree-header.js.map +1 -0
  119. package/dist/src/tui/draconic-tui.js +291 -0
  120. package/dist/src/tui/draconic-tui.js.map +1 -0
  121. package/dist/src/tui/index.js +93 -0
  122. package/dist/src/tui/index.js.map +1 -0
  123. package/dist/src/tui/pi-tui-integration.js +260 -0
  124. package/dist/src/tui/pi-tui-integration.js.map +1 -0
  125. package/dist/src/utils/nl-patterns.js +145 -0
  126. package/dist/src/utils/nl-patterns.js.map +1 -0
  127. package/package.json +13 -4
  128. package/scripts/migrate-from-openclaw.sh +498 -0
  129. package/HEARTBEAT.md +0 -121
  130. 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