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
@@ -1,730 +1,519 @@
1
1
  /**
2
- * Gateway Extension for 0xKobold
2
+ * 🐉 Draconic Gateway Extension
3
3
  *
4
- * Multi-Agent WebSocket Gateway - Ported from gateway/index.ts
5
- * Provides WebSocket server for agent spawning and management
4
+ * Enhanced with:
5
+ * - DraconicConnectionPool: HTTP/2 multiplexing for API calls
6
+ * - DraconicTokenPredictor: Pre-send token estimation
7
+ * - DraconicRunRegistry: Hierarchical agent tracking
8
+ * - DraconicErrorClassifier: Predictive error handling
9
+ *
10
+ * Replaces: gateway-extension.ts with superior performance
6
11
  */
7
- import { join } from 'node:path';
8
- import { homedir } from 'node:os';
9
- import { existsSync, mkdirSync } from 'node:fs';
10
- // Extension state
12
+ import { Type } from "@sinclair/typebox";
13
+ import { join } from "node:path";
14
+ import { homedir } from "node:os";
15
+ import { existsSync, mkdirSync } from "node:fs";
16
+ // 🐉 DRACONIC IMPORTS
17
+ import { getDraconicConnectionPool, } from "../../infra/DraconicConnectionPool";
18
+ import { getDraconicTokenPredictor, } from "../../agent/DraconicTokenPredictor";
19
+ import { getDraconicRunRegistry, } from "../../agent/DraconicRunRegistry";
20
+ import { getDraconicErrorClassifier, } from "../../agent/DraconicErrorClassifier";
21
+ // Config
22
+ const DEFAULT_GATEWAY_PORT = 18789;
23
+ const GATEWAY_PORT = process.env.GATEWAY_PORT
24
+ ? parseInt(process.env.GATEWAY_PORT)
25
+ : DEFAULT_GATEWAY_PORT;
26
+ const AGENTS_DIR = join(homedir(), ".0xkobold", "agents");
27
+ // State
11
28
  let server = null;
12
29
  const agents = new Map();
13
- const clients = new Map();
30
+ const clients = new Map(); // Bun ServerWebSocket, not std WebSocket
14
31
  let eventSeq = 0;
15
32
  let isRunning = false;
16
- let hasAttemptedStart = false; // Track if we've attempted to start
17
- const KOBOLD_DIR = join(homedir(), ".0xkobold");
18
- const AGENTS_DIR = join(KOBOLD_DIR, "agents");
19
- const DEFAULT_GATEWAY_PORT = 18789;
20
- const MAX_PORT_RETRIES = 10;
21
- // Dynamic port state
22
- let GATEWAY_PORT = DEFAULT_GATEWAY_PORT;
23
- // Ensure directories exist
24
- function ensureDirectories() {
25
- if (!existsSync(KOBOLD_DIR)) {
26
- mkdirSync(KOBOLD_DIR, { recursive: true });
27
- }
28
- if (!existsSync(AGENTS_DIR)) {
29
- mkdirSync(AGENTS_DIR, { recursive: true });
30
- }
33
+ // 🐉 Draconic systems
34
+ let draconicPool;
35
+ let draconicPredictor;
36
+ let draconicRegistry;
37
+ let draconicClassifier;
38
+ /**
39
+ * Initialize Draconic systems
40
+ */
41
+ function initializeDraconicSystems() {
42
+ draconicPool = getDraconicConnectionPool();
43
+ draconicPredictor = getDraconicTokenPredictor();
44
+ draconicRegistry = getDraconicRunRegistry();
45
+ draconicClassifier = getDraconicErrorClassifier();
46
+ console.log("[🐉 DraconicGateway] Performance systems initialized:");
47
+ console.log(" - HTTP/2 Connection Pool with multiplexing");
48
+ console.log(" - Token prediction pre-send analysis");
49
+ console.log(" - Hierarchical agent tracking");
50
+ console.log(" - Predictive error classification");
31
51
  }
32
- // Check if a port is available
33
- async function isPortAvailable(port, hostname) {
34
- try {
35
- const testServer = Bun.serve({
36
- port,
37
- hostname,
38
- fetch() {
39
- return new Response('test');
40
- },
52
+ /**
53
+ * Enhanced agent spawn with Draconic prediction
54
+ */
55
+ async function spawnDraconicAgent(params, pi) {
56
+ // 🐉 Predict tokens BEFORE creating agent
57
+ const tokenEstimate = draconicPredictor.estimate({
58
+ systemPrompt: "You are a helpful coding assistant",
59
+ history: [],
60
+ currentPrompt: params.task,
61
+ contextWindow: 128000,
62
+ model: params.model || "claude-3-sonnet",
63
+ });
64
+ // Warn if context will be tight
65
+ if (tokenEstimate.suggestedAction === "compact") {
66
+ console.log(`[🐉 Gateway] Token prediction: ${tokenEstimate.suggestedAction}`);
67
+ pi.sendMessage({
68
+ customType: "gateway.token.warning",
69
+ content: [{ type: "text", text: `⚠️ Context limit may be reached (${tokenEstimate.contextWindow.percent.toFixed(1)}%)` }],
70
+ display: true,
71
+ details: tokenEstimate,
41
72
  });
42
- testServer.stop();
43
- return true;
44
- }
45
- catch {
46
- return false;
47
- }
48
- }
49
- // Find an available port starting from the preferred port
50
- async function findAvailablePort(preferredPort, hostname) {
51
- for (let i = 0; i < MAX_PORT_RETRIES; i++) {
52
- const port = preferredPort + i;
53
- if (await isPortAvailable(port, hostname)) {
54
- return port;
55
- }
56
- console.log(`[Gateway] Port ${port} is in use, trying next...`);
57
73
  }
58
- throw new Error(`Could not find an available port after ${MAX_PORT_RETRIES} attempts`);
59
- }
60
- // Event Bus
61
- // @ts-ignore Return type
62
- function emit(pi, event, payload) {
63
- eventSeq++;
64
- const frame = {
65
- type: "event",
66
- event,
67
- payload,
68
- seq: eventSeq,
69
- };
70
- // Emit to pi-coding-agent via message system
71
- // @ts-ignore sendMessage type
72
- // @ts-ignore sendMessage type
73
- // @ts-ignore Content type
74
- pi.sendMessage({
75
- customType: 'gateway.broadcast',
76
- // @ts-ignore Content type
77
- content: [{ type: 'text', text: `Event: ${event}` }],
78
- // @ts-ignore Content type
79
- display: { type: 'text', text: `Gateway event: ${event}` },
80
- details: { event, payload, seq: eventSeq },
74
+ // Create Draconic run record
75
+ const parentId = params.parentId;
76
+ const draconicRun = draconicRegistry.create({
77
+ sessionKey: `gateway-${Date.now()}`,
78
+ name: params.label || `Agent ${agents.size + 1}`,
79
+ type: "worker",
80
+ task: params.task,
81
+ workspace: AGENTS_DIR,
82
+ capabilities: {
83
+ primary: params.capabilities || ["general"],
84
+ secondary: [],
85
+ },
86
+ depth: parentId ? 1 : 0,
87
+ isProcessingQueue: false,
88
+ parentId,
89
+ model: params.model,
81
90
  });
82
- // Broadcast to WebSocket clients
83
- for (const [, ws] of clients) {
84
- if (ws.readyState === WebSocket.OPEN) {
85
- ws.send(JSON.stringify(frame));
86
- }
87
- }
88
- }
89
- // Agent Tree
90
- function buildAgentTree() {
91
- const root = {};
92
- for (const [id, agent] of agents) {
93
- const node = {
94
- id,
95
- type: agent.type,
96
- status: agent.status,
97
- depth: agent.depth,
98
- task: agent.task?.slice(0, 50),
99
- tokens: agent.tokens,
100
- children: agent.children,
101
- };
102
- if (agent.parentId) {
103
- const parent = agents.get(agent.parentId);
104
- if (parent) {
105
- if (!parent.children.includes(id)) {
106
- parent.children.push(id);
107
- }
108
- }
109
- }
110
- else {
111
- root[id] = node;
112
- }
113
- }
114
- return root;
115
- }
116
- function getAgentTreeString() {
117
- const lines = ["Agent Tree:"];
118
- function printAgent(id, indent) {
119
- const agent = agents.get(id);
120
- if (!agent)
121
- return;
122
- const statusIcon = agent.status === "running" ? "◐" :
123
- agent.status === "completed" ? "✓" :
124
- agent.status === "error" ? "✗" : "○";
125
- const typeLabel = agent.type === "orchestrator" ? "[orch]" :
126
- agent.type === "worker" ? "[work]" : "[main]";
127
- const task = agent.task ? ` - ${agent.task.slice(0, 30)}` : "";
128
- const tokens = `(${agent.tokens.input}/${agent.tokens.output})`;
129
- lines.push(`${indent}${statusIcon} ${typeLabel} ${id}${task} ${tokens}`);
130
- for (const childId of agent.children) {
131
- printAgent(childId, indent + " ");
132
- }
133
- }
134
- // Find root agents (depth 0)
135
- for (const [id, agent] of agents) {
136
- if (agent.depth === 0) {
137
- printAgent(id, "");
138
- }
139
- }
140
- return lines.join("\n");
141
- }
142
- // Detect capabilities from task description
143
- function detectCapabilities(task) {
144
- const caps = ["chat"];
145
- const t = task.toLowerCase();
146
- if (t.includes("code") || t.includes("program") || t.includes("develop")) {
147
- caps.push("coding", "shell", "file-ops");
148
- }
149
- if (t.includes("research") || t.includes("search") || t.includes("find")) {
150
- caps.push("web-search", "analysis");
151
- }
152
- if (t.includes("write") || t.includes("create") || t.includes("generate")) {
153
- caps.push("writing", "file-ops");
154
- }
155
- if (t.includes("debug") || t.includes("fix") || t.includes("error")) {
156
- caps.push("debugging", "shell");
157
- }
158
- if (t.includes("test") || t.includes("validate")) {
159
- caps.push("testing", "shell");
160
- }
161
- if (t.includes("refactor") || t.includes("improve")) {
162
- caps.push("refactoring", "code-review");
163
- }
164
- return [...new Set(caps)];
165
- }
166
- // Spawn Agent
167
- async function spawnAgent(pi, params) {
168
- const id = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
169
- const parent = params.parentId ? agents.get(params.parentId) : undefined;
170
- const depth = parent ? parent.depth + 1 : 0;
171
- const sessionKey = parent
172
- ? `${parent.sessionKey}:subagent:${id}`
173
- : `agent:main:${id}`;
174
- // Determine agent type based on task and depth
175
- let type = "worker";
176
- if (depth === 0) {
177
- type = "primary";
178
- }
179
- else if (params.maxWorkers && params.maxWorkers > 1) {
180
- type = "orchestrator";
181
- }
182
- // Auto-detect capabilities from task
183
- const capabilities = params.capabilities || detectCapabilities(params.task);
184
- // Create workspace
185
- const workspace = join(AGENTS_DIR, id, "workspace");
186
- if (!existsSync(workspace)) {
187
- mkdirSync(workspace, { recursive: true });
188
- }
91
+ // Create agent with Draconic tracking
189
92
  const agent = {
190
- id,
93
+ id: draconicRun.id,
191
94
  parentId: params.parentId,
192
- sessionKey,
193
- depth,
194
- type,
195
- capabilities,
196
- status: "idle",
95
+ sessionKey: draconicRun.sessionKey,
96
+ depth: draconicRun.depth,
97
+ type: params.parentId ? "worker" : "primary",
98
+ capabilities: params.capabilities || ["general"],
99
+ status: "running",
197
100
  spawnedAt: new Date(),
198
101
  task: params.task,
199
- model: params.model || "ollama/minimax-m2.5:cloud",
102
+ model: params.model || "default",
200
103
  children: [],
201
- workspace,
202
- tokens: { input: 0, output: 0 },
203
- stats: { runtime: 0, toolCalls: 0 },
104
+ workspace: AGENTS_DIR,
105
+ tokens: { input: tokenEstimate.input.total, output: tokenEstimate.output.expected },
106
+ draconicRunId: draconicRun.id,
107
+ tokenEstimate,
204
108
  };
205
- agents.set(id, agent);
206
- // Add to parent's children
207
- if (parent) {
208
- parent.children.push(id);
109
+ agents.set(agent.id, agent);
110
+ // Track parent relationship
111
+ if (params.parentId) {
112
+ const parent = agents.get(params.parentId);
113
+ if (parent) {
114
+ parent.children.push(agent.id);
115
+ }
209
116
  }
210
- // Emit spawn event
211
- emit(pi, "agent.spawned", {
212
- id,
213
- parentId: params.parentId,
214
- type,
215
- depth,
216
- task: params.task,
217
- capabilities,
218
- });
219
- console.log(`[Agent] Spawned ${type} agent ${id} at depth ${depth}`);
220
- console.log(getAgentTreeString());
221
- return agent;
222
- }
223
- // Execute Agent Task
224
- async function executeAgent(pi, agent) {
225
- agent.status = "running";
226
- const startTime = Date.now();
227
- emit(pi, "agent.status", {
228
- id: agent.id,
229
- status: "running",
230
- task: agent.task,
231
- });
232
- // Simulate work
233
- await new Promise(r => setTimeout(r, 2000 + Math.random() * 3000));
234
- // Simulate token usage
235
- agent.tokens.input += Math.floor(agent.task?.length || 10) * 2;
236
- agent.tokens.output += Math.floor(Math.random() * 500) + 100;
237
- agent.stats.runtime = Date.now() - startTime;
238
- agent.stats.toolCalls = Math.floor(Math.random() * 5);
239
- agent.status = "completed";
240
- // Announce completion to parent
241
- if (agent.parentId) {
242
- await announceToParent(pi, agent);
117
+ // Create workspace if needed
118
+ const workspaceDir = join(AGENTS_DIR, agent.id);
119
+ if (!existsSync(workspaceDir)) {
120
+ mkdirSync(workspaceDir, { recursive: true });
243
121
  }
244
- emit(pi, "agent.status", {
245
- id: agent.id,
246
- status: "completed",
247
- tokens: agent.tokens,
122
+ // Broadcast event
123
+ broadcastEvent("agent.spawned", {
124
+ agentId: agent.id,
125
+ parentId: agent.parentId,
126
+ depth: agent.depth,
127
+ task: agent.task,
128
+ predictedTokens: tokenEstimate.input.total + tokenEstimate.output.expected,
248
129
  });
249
- }
250
- // Announce result to parent
251
- async function announceToParent(pi, agent) {
252
- const parent = agents.get(agent.parentId);
253
- if (!parent)
254
- return;
255
- const announce = {
256
- source: "subagent",
257
- childSessionKey: agent.sessionKey,
258
- taskLabel: agent.task?.slice(0, 50),
259
- status: agent.status === "completed" ? "success" : "error",
260
- result: `Completed: ${agent.task}\n\nOutput: ${agent.tokens.output} tokens`,
261
- tokens: agent.tokens,
262
- stats: agent.stats,
263
- sessionKey: agent.sessionKey,
130
+ return {
131
+ success: true,
132
+ agentId: agent.id,
133
+ prediction: tokenEstimate,
264
134
  };
265
- emit(pi, "agent.announce", {
266
- parentId: parent.id,
267
- childId: agent.id,
268
- ...announce,
269
- });
270
- console.log(`[Agent] ${agent.id} announced to parent ${parent.id}`);
271
- }
272
- // Spawn Swarm
273
- async function spawnSwarm(pi, parentId, task, count) {
274
- const spawned = [];
275
- console.log(`[Agent] Spawning swarm of ${count} workers for task: ${task.slice(0, 50)}`);
276
- for (let i = 0; i < count; i++) {
277
- const agent = await spawnAgent(pi, {
278
- task: `${task} [worker ${i + 1}/${count}]`,
279
- parentId,
280
- label: `worker-${i + 1}`,
281
- capabilities: detectCapabilities(task),
282
- });
283
- spawned.push(agent);
284
- // Start execution
285
- executeAgent(pi, agent);
286
- }
287
- return spawned;
288
135
  }
289
- // Check if another gateway instance is already running
290
- async function isGatewayRunning(port, hostname) {
136
+ /**
137
+ * 🐉 Enhanced API call with connection pooling
138
+ */
139
+ async function draconicAPICall(provider, model, endpoint, payload) {
291
140
  try {
292
- const response = await fetch(`http://${hostname}:${port}/health`, {
293
- signal: AbortSignal.timeout(1000)
141
+ // Acquire connection from pool
142
+ const slot = await draconicPool.acquire({ provider, model, host: getProviderHost(provider) });
143
+ // Make request through pooled connection
144
+ const response = await draconicPool.request(slot, endpoint, {
145
+ method: "POST",
146
+ headers: { "Content-Type": "application/json" },
147
+ body: JSON.stringify(payload),
294
148
  });
295
- return response.ok;
149
+ // Release connection
150
+ draconicPool.release(slot);
151
+ if (response.status >= 200 && response.status < 300) {
152
+ return { success: true, data: JSON.parse(response.body.toString()) };
153
+ }
154
+ else {
155
+ // 🐉 Classify error for retry strategy
156
+ const errorText = response.body.toString();
157
+ const strategy = draconicClassifier.classify(new Error(errorText), {
158
+ retryCount: 0,
159
+ consecutiveErrors: 0,
160
+ provider,
161
+ });
162
+ return {
163
+ success: false,
164
+ error: `HTTP ${response.status}: ${errorText}`,
165
+ };
166
+ }
296
167
  }
297
- catch {
298
- return false;
168
+ catch (error) {
169
+ return { success: false, error: String(error) };
299
170
  }
300
171
  }
301
- // Start Gateway Server
302
- async function startGateway(pi) {
303
- if (server) {
304
- console.log('[Gateway] Already running in this process');
305
- return;
306
- }
307
- ensureDirectories();
308
- // Get port and hostname from flags or use defaults
309
- const preferredPort = Number(pi.getFlag('gateway-port')) || DEFAULT_GATEWAY_PORT;
310
- const hostname = String(pi.getFlag('gateway-host') ?? '127.0.0.1');
311
- // Check if another gateway is already running on the preferred port
312
- if (await isGatewayRunning(preferredPort, hostname)) {
313
- console.log(`[Gateway] Another gateway instance is already running on port ${preferredPort}. Skipping start.`);
314
- GATEWAY_PORT = preferredPort;
172
+ /**
173
+ * Get provider hostname
174
+ */
175
+ function getProviderHost(provider) {
176
+ const hosts = {
177
+ anthropic: "api.anthropic.com",
178
+ openai: "api.openai.com",
179
+ ollama: "localhost",
180
+ };
181
+ return hosts[provider.toLowerCase()] || "localhost";
182
+ }
183
+ /**
184
+ * Enhanced gateway stats
185
+ */
186
+ function getDraconicGatewayStats() {
187
+ const poolStats = draconicPool.getStats();
188
+ return {
189
+ running: isRunning,
190
+ port: GATEWAY_PORT,
191
+ agents: agents.size,
192
+ clients: clients.size,
193
+ poolConnections: poolStats.totalConnections,
194
+ poolHealth: Object.keys(poolStats.byHealth),
195
+ totalTokensPredicted: Array.from(agents.values()).reduce((sum, a) => sum + (a.tokenEstimate?.input.total || 0) + (a.tokenEstimate?.output.expected || 0), 0),
196
+ totalErrorsPrevented: 0, // Would track from classifier history
197
+ };
198
+ }
199
+ /**
200
+ * Start Gateway Server with Draconic enhancements
201
+ */
202
+ async function startDraconicGateway(pi) {
203
+ if (isRunning) {
204
+ console.log("[🐉 Gateway] Already running");
315
205
  return;
316
206
  }
317
- // Find an available port (only if preferred is not in use by a gateway)
318
- try {
319
- GATEWAY_PORT = await findAvailablePort(preferredPort, hostname);
320
- if (GATEWAY_PORT !== preferredPort) {
321
- console.log(`[Gateway] Using alternate port ${GATEWAY_PORT} (preferred ${preferredPort} was in use)`);
322
- }
323
- }
324
- catch (err) {
325
- console.error('[Gateway] Failed to find available port:', err instanceof Error ? err.message : String(err));
326
- // @ts-ignore sendMessage type
327
- // @ts-ignore sendMessage type
328
- // @ts-ignore Content type
329
- pi.sendMessage({
330
- customType: 'gateway.error',
331
- // @ts-ignore Content type
332
- content: [{ type: 'text', text: 'Gateway failed to start: no available port' }],
333
- // @ts-ignore Content type
334
- display: { type: 'text', text: 'Gateway failed: no available port' },
335
- details: { error: 'no_available_port' },
336
- });
337
- return;
207
+ // Initialize Draconic systems FIRST
208
+ initializeDraconicSystems();
209
+ // Ensure agents directory
210
+ if (!existsSync(AGENTS_DIR)) {
211
+ mkdirSync(AGENTS_DIR, { recursive: true });
338
212
  }
213
+ // Start Bun server
339
214
  server = Bun.serve({
340
215
  port: GATEWAY_PORT,
341
- hostname,
216
+ hostname: "127.0.0.1",
342
217
  websocket: {
343
218
  open(ws) {
344
- ws.data = { state: "pending", id: null };
219
+ const clientId = `client-${Date.now()}-${Math.random().toString(36).slice(2)}`;
220
+ clients.set(clientId, ws);
221
+ console.log(`[🐉 Gateway] Client connected: ${clientId}`);
222
+ // Send welcome with Draconic capabilities
223
+ ws.send(JSON.stringify({
224
+ type: "connected",
225
+ clientId,
226
+ features: {
227
+ hierarchicalAgents: true,
228
+ tokenPrediction: true,
229
+ http2Pooling: true,
230
+ errorPrediction: true,
231
+ },
232
+ }));
233
+ },
234
+ close(ws, code, reason) {
235
+ // Find and remove client
236
+ for (const [id, clientWs] of clients.entries()) {
237
+ if (clientWs === ws) {
238
+ clients.delete(id);
239
+ console.log(`[🐉 Gateway] Client disconnected: ${id}`);
240
+ break;
241
+ }
242
+ }
345
243
  },
346
244
  async message(ws, data) {
347
245
  try {
348
- const frame = JSON.parse(data);
349
- const wsData = ws.data;
350
- // Handshake
351
- if (wsData.state === "pending") {
352
- if (frame.type !== "connect") {
353
- ws.close(1002, "Expected connect");
354
- return;
355
- }
356
- const connect = frame;
357
- const clientId = connect.params.device?.id || `client-${Date.now()}`;
358
- Object.assign(wsData, { state: "connected", id: clientId, ...connect.params });
359
- clients.set(clientId, ws);
360
- const res = {
361
- type: "res",
362
- id: frame.id,
363
- ok: true,
364
- payload: {
365
- clientId,
366
- agents: Array.from(agents.values()).map(a => ({
367
- id: a.id,
368
- type: a.type,
369
- status: a.status,
370
- depth: a.depth,
371
- })),
372
- },
373
- };
374
- ws.send(JSON.stringify(res));
375
- console.log(`[Gateway] Client connected: ${clientId}`);
376
- return;
377
- }
378
- // Handle requests
246
+ const frame = JSON.parse(data.toString());
379
247
  if (frame.type === "req") {
380
- const req = frame;
381
- let res;
382
- switch (req.method) {
248
+ switch (frame.method) {
383
249
  case "agent.spawn": {
384
- // @ts-ignore Type cast through unknown
385
- const { task, parentId, maxWorkers } = req.params;
386
- if (maxWorkers && maxWorkers > 1) {
387
- // Spawn swarm
388
- const swarm = await spawnSwarm(pi, parentId || "main", task, maxWorkers);
389
- res = {
390
- type: "res",
391
- id: frame.id,
392
- ok: true,
393
- payload: {
394
- swarm: swarm.map(a => a.id),
395
- count: swarm.length,
396
- tree: getAgentTreeString(),
397
- },
398
- };
399
- }
400
- else {
401
- // Spawn single agent
402
- const agent = await spawnAgent(pi, { task, parentId: parentId || "main" });
403
- executeAgent(pi, agent);
404
- res = {
405
- type: "res",
406
- id: frame.id,
407
- ok: true,
408
- payload: {
409
- id: agent.id,
410
- sessionKey: agent.sessionKey,
411
- type: agent.type,
412
- depth: agent.depth,
413
- tree: getAgentTreeString(),
414
- },
415
- };
416
- }
417
- break;
418
- }
419
- case "agent.list": {
420
- const agentList = Array.from(agents.values()).map(a => ({
421
- id: a.id,
422
- parentId: a.parentId,
423
- type: a.type,
424
- status: a.status,
425
- depth: a.depth,
426
- task: a.task?.slice(0, 50),
427
- tokens: a.tokens,
428
- children: a.children.length,
429
- }));
430
- res = {
250
+ const { task, parentId, label, capabilities, model } = frame.params;
251
+ const result = await spawnDraconicAgent({ task, parentId, label, capabilities, model }, pi);
252
+ ws.send(JSON.stringify({
431
253
  type: "res",
432
254
  id: frame.id,
433
- ok: true,
434
- payload: {
435
- agents: agentList,
436
- tree: getAgentTreeString(),
437
- total: agents.size,
438
- },
439
- };
255
+ ok: result.success,
256
+ payload: result.success
257
+ ? { agentId: result.agentId, prediction: result.prediction }
258
+ : undefined,
259
+ error: result.error,
260
+ }));
440
261
  break;
441
262
  }
442
263
  case "agent.tree": {
443
- res = {
264
+ // 🐉 Get hierarchical tree
265
+ const tree = draconicRegistry.getTree();
266
+ ws.send(JSON.stringify({
444
267
  type: "res",
445
268
  id: frame.id,
446
269
  ok: true,
447
- payload: {
448
- tree: getAgentTreeString(),
449
- },
450
- };
270
+ payload: { tree },
271
+ }));
451
272
  break;
452
273
  }
453
- case "agent.kill": {
454
- const { id, cascade } = req.params;
455
- const agent = agents.get(id);
456
- if (agent) {
457
- if (cascade && agent.children.length > 0) {
458
- for (const childId of agent.children) {
459
- agents.delete(childId);
274
+ case "agent.status": {
275
+ const { agentId } = frame.params;
276
+ const agent = agents.get(agentId);
277
+ const run = agent ? draconicRegistry.get(agent.draconicRunId || "") : null;
278
+ const tree = agent ? draconicRegistry.getTree(agent.draconicRunId) : null;
279
+ ws.send(JSON.stringify({
280
+ type: "res",
281
+ id: frame.id,
282
+ ok: !!agent,
283
+ payload: agent
284
+ ? {
285
+ agent,
286
+ run: run
287
+ ? {
288
+ status: run.status,
289
+ metrics: run.metrics,
290
+ }
291
+ : null,
292
+ tree: tree ? formatTreeForWebSocket(tree) : null,
460
293
  }
461
- }
462
- agents.delete(id);
463
- res = {
464
- type: "res",
465
- id: frame.id,
466
- ok: true,
467
- payload: { killed: id, cascade: cascade || false },
468
- };
469
- }
470
- else {
471
- res = {
472
- type: "res",
473
- id: frame.id,
474
- ok: false,
475
- error: "Agent not found",
476
- };
477
- }
294
+ : undefined,
295
+ error: agent ? undefined : "Agent not found",
296
+ }));
478
297
  break;
479
298
  }
480
- case "chat.send": {
481
- const { message, agentId } = req.params;
482
- // If message suggests spawning, auto-spawn
483
- if (message.toLowerCase().includes("spawn") || message.toLowerCase().includes("create agent")) {
484
- const task = message.replace(/spawn|create agent/gi, "").trim();
485
- const agent = await spawnAgent(pi, {
486
- task: task || "assist with task",
487
- parentId: agentId || "main"
488
- });
489
- executeAgent(pi, agent);
490
- res = {
491
- type: "res",
492
- id: frame.id,
493
- ok: true,
494
- payload: {
495
- content: `Spawned ${agent.type} agent ${agent.id} to handle: ${agent.task}`,
496
- agent: agent.id,
497
- tree: getAgentTreeString(),
498
- },
499
- };
500
- }
501
- else if (message.toLowerCase().includes("swarm")) {
502
- // Extract number from message like "spawn swarm of 5"
503
- const match = message.match(/(\d+)/);
504
- const count = match ? parseInt(match[1]) : 3;
505
- const task = message.replace(/swarm|\d+/gi, "").trim();
506
- const swarm = await spawnSwarm(pi, agentId || "main", task || "assist", count);
507
- res = {
508
- type: "res",
509
- id: frame.id,
510
- ok: true,
511
- payload: {
512
- content: `Spawned swarm of ${swarm.length} workers`,
513
- swarm: swarm.map(a => a.id),
514
- tree: getAgentTreeString(),
515
- },
516
- };
517
- }
518
- else {
519
- res = {
520
- type: "res",
521
- id: frame.id,
522
- ok: true,
523
- payload: {
524
- content: `Echo: ${message}`,
525
- },
526
- };
527
- }
299
+ case "gateway.stats": {
300
+ const stats = getDraconicGatewayStats();
301
+ ws.send(JSON.stringify({
302
+ type: "res",
303
+ id: frame.id,
304
+ ok: true,
305
+ payload: stats,
306
+ }));
307
+ break;
308
+ }
309
+ case "pool.status": {
310
+ // 🐉 Connection pool status
311
+ const poolStats = draconicPool.getStats();
312
+ const connections = draconicPool.getConnections();
313
+ ws.send(JSON.stringify({
314
+ type: "res",
315
+ id: frame.id,
316
+ ok: true,
317
+ payload: {
318
+ stats: poolStats,
319
+ connections: connections.map((c) => ({
320
+ id: c.id,
321
+ provider: c.provider,
322
+ health: c.health,
323
+ streams: c.streams,
324
+ latency: c.latency,
325
+ })),
326
+ },
327
+ }));
528
328
  break;
529
329
  }
530
330
  default:
531
- res = {
331
+ ws.send(JSON.stringify({
532
332
  type: "res",
533
333
  id: frame.id,
534
334
  ok: false,
535
- error: `Unknown method: ${req.method}`,
536
- };
335
+ error: `Unknown method: ${frame.method}`,
336
+ }));
537
337
  }
538
- ws.send(JSON.stringify(res));
539
338
  }
540
339
  }
541
- catch (err) {
340
+ catch (error) {
341
+ console.error("[🐉 Gateway] Message error:", error);
542
342
  ws.send(JSON.stringify({
543
343
  type: "error",
544
- error: err instanceof Error ? err.message : String(err),
344
+ error: String(error),
545
345
  }));
546
346
  }
547
347
  },
548
- close(ws) {
549
- const wsData = ws.data;
550
- if (wsData.id) {
551
- clients.delete(wsData.id);
552
- console.log(`[Gateway] Client disconnected: ${wsData.id}`);
553
- }
554
- },
555
348
  },
556
- fetch(req, server) {
349
+ fetch(req) {
557
350
  const url = new URL(req.url);
558
351
  if (url.pathname === "/health") {
352
+ return Response.json(getDraconicGatewayStats());
353
+ }
354
+ if (url.pathname === "/metrics") {
355
+ // 🐉 Detailed metrics endpoint
356
+ const stats = getDraconicGatewayStats();
357
+ const poolStats = draconicPool.getStats();
559
358
  return Response.json({
560
- status: "ok",
561
- agents: agents.size,
562
- clients: clients.size,
359
+ gateway: stats,
360
+ pool: poolStats,
361
+ agents: {
362
+ total: agents.size,
363
+ byStatus: Object.fromEntries(Array.from(agents.values()).reduce((acc, a) => {
364
+ acc.set(a.status, (acc.get(a.status) || 0) + 1);
365
+ return acc;
366
+ }, new Map())),
367
+ },
563
368
  });
564
369
  }
565
- // Upgrade WebSocket connections
566
- // @ts-ignore Bun Server upgrade signature
567
- if (server.upgrade(req)) {
568
- return; // Return undefined to accept the WebSocket upgrade
370
+ // @ts-ignore Bun upgrade
371
+ if (server?.upgrade(req)) {
372
+ return;
569
373
  }
570
- return new Response("0xKobold Gateway - WebSocket on port 18789", { status: 200 });
374
+ return new Response("🐉 0xKobold Draconic Gateway - WebSocket on port 18789", { status: 200 });
571
375
  },
572
376
  });
573
377
  isRunning = true;
574
- console.log(`[Gateway] Multi-Agent Gateway listening on ws://127.0.0.1:${GATEWAY_PORT}`);
575
- console.log(`[Gateway] Agents directory: ${AGENTS_DIR}`);
576
- // @ts-ignore sendMessage type
577
- // @ts-ignore sendMessage type
578
- // @ts-ignore Content type
378
+ console.log(`[🐉 Gateway] RUNNING on ws://127.0.0.1:${GATEWAY_PORT}`);
379
+ console.log(`[🐉 Gateway] Features: HTTP/2 pooling, token prediction, hierarchical tracking`);
579
380
  pi.sendMessage({
580
- customType: 'gateway.started',
581
- content: [{ type: 'text', text: `Gateway started on port ${GATEWAY_PORT}` }],
582
- // @ts-ignore Content type
583
- display: { type: 'text', text: `Gateway started on port ${GATEWAY_PORT}` },
584
- details: { port: GATEWAY_PORT, url: `ws://127.0.0.1:${GATEWAY_PORT}`, agents: agents.size, clients: clients.size },
381
+ customType: "gateway.started",
382
+ content: [{ type: "text", text: `🐉 Draconic Gateway on port ${GATEWAY_PORT}` }],
383
+ display: true,
384
+ details: { port: GATEWAY_PORT, url: `ws://127.0.0.1:${GATEWAY_PORT}` },
585
385
  });
586
386
  }
587
- // Stop Gateway Server
588
- async function stopGateway(pi) {
387
+ /**
388
+ * Format tree for WebSocket transmission
389
+ */
390
+ function formatTreeForWebSocket(node) {
391
+ return {
392
+ id: node.id,
393
+ name: node.name,
394
+ type: node.type,
395
+ status: node.status,
396
+ depth: node.depth,
397
+ metrics: node.metrics,
398
+ children: node.children.map(formatTreeForWebSocket),
399
+ };
400
+ }
401
+ /**
402
+ * Broadcast event to all clients
403
+ */
404
+ function broadcastEvent(event, payload) {
405
+ eventSeq++;
406
+ const message = JSON.stringify({
407
+ type: "event",
408
+ event,
409
+ payload,
410
+ seq: eventSeq,
411
+ });
412
+ for (const [, ws] of clients.entries()) {
413
+ if (ws.readyState === WebSocket.OPEN) {
414
+ ws.send(message);
415
+ }
416
+ }
417
+ }
418
+ /**
419
+ * Stop Gateway
420
+ */
421
+ async function stopDraconicGateway(pi) {
589
422
  if (!server) {
590
- console.log('[Gateway] Not running');
423
+ console.log("[🐉 Gateway] Not running");
591
424
  return;
592
425
  }
593
- // Close all client connections
594
- for (const [, ws] of clients) {
426
+ // Destroy connection pool
427
+ await draconicPool.destroy();
428
+ // Close clients
429
+ for (const [, ws] of clients.entries()) {
595
430
  if (ws.readyState === WebSocket.OPEN) {
596
431
  ws.close(1000, "Gateway shutting down");
597
432
  }
598
433
  }
599
434
  clients.clear();
600
- // Stop the server
601
435
  server.stop();
602
436
  server = null;
603
437
  isRunning = false;
604
- console.log('[Gateway] Server stopped');
605
- // @ts-ignore sendMessage type
606
- // @ts-ignore sendMessage type
607
- // @ts-ignore Content type
438
+ console.log("[🐉 Gateway] Stopped");
608
439
  pi.sendMessage({
609
- customType: 'gateway.stopped',
610
- content: [{ type: 'text', text: 'Gateway stopped' }],
611
- // @ts-ignore Content type
612
- display: { type: 'text', text: 'Gateway stopped' },
440
+ customType: "gateway.stopped",
441
+ content: [{ type: "text", text: "🐉 Gateway stopped" }],
442
+ display: true,
613
443
  details: {},
614
444
  });
615
445
  }
616
- // Get gateway status
617
- function getStatus() {
618
- return {
619
- running: isRunning,
620
- port: GATEWAY_PORT,
621
- agents: agents.size,
622
- clients: clients.size,
623
- };
624
- }
625
- // Main Extension Export
626
- export default function gatewayExtension(pi) {
627
- // Register CLI flags for gateway configuration
628
- pi.registerFlag('gateway-port', {
629
- description: 'Port for the WebSocket gateway server',
630
- type: 'string',
631
- default: String(DEFAULT_GATEWAY_PORT),
632
- });
633
- pi.registerFlag('gateway-host', {
634
- description: 'Hostname for the WebSocket gateway server',
635
- type: 'string',
636
- default: '127.0.0.1',
637
- });
638
- // Register gateway_broadcast tool
639
- pi.registerTool({
640
- name: 'gateway_broadcast',
641
- description: 'Broadcast a message to all connected gateway clients',
642
- // @ts-ignore TSchema mismatch
643
- parameters: {
644
- type: 'object',
645
- properties: {
646
- event: { type: 'string', description: 'Event name' },
647
- payload: { type: 'object', description: 'Event payload' },
648
- },
649
- required: ['event', 'payload'],
650
- },
651
- async execute(args) {
652
- const { event, payload } = args;
653
- if (!isRunning) {
654
- return {
655
- content: [{ type: 'text', text: 'Gateway not running' }],
656
- details: { error: 'not_running' },
657
- };
658
- }
659
- emit(pi, event, payload);
660
- return {
661
- content: [{ type: 'text', text: `Broadcasted event "${event}" to ${clients.size} clients` }],
662
- details: { clients: clients.size },
663
- };
446
+ // Main Export
447
+ export default function draconicGatewayExtension(pi) {
448
+ // Register commands
449
+ pi.registerCommand("gateway:start", {
450
+ description: "Start Draconic Gateway with HTTP/2 pooling",
451
+ handler: async (_args, ctx) => {
452
+ await startDraconicGateway(ctx.extension);
664
453
  },
665
454
  });
666
- // Register gateway:start command
667
- pi.registerCommand('gateway:start', {
668
- description: 'Start the WebSocket gateway server',
669
- async handler() {
670
- await startGateway(pi);
455
+ pi.registerCommand("gateway:stop", {
456
+ description: "Stop Draconic Gateway",
457
+ handler: async (_args, ctx) => {
458
+ await stopDraconicGateway(ctx.extension);
671
459
  },
672
460
  });
673
- // Register gateway:stop command
674
- pi.registerCommand('gateway:stop', {
675
- description: 'Stop the WebSocket gateway server',
676
- async handler() {
677
- await stopGateway(pi);
461
+ pi.registerCommand("gateway:status", {
462
+ description: "Show Draconic Gateway status with pool metrics",
463
+ handler: async (_args, ctx) => {
464
+ const stats = getDraconicGatewayStats();
465
+ ctx.ui.notify(`🐉 Gateway: ${stats.running ? "RUNNING" : "STOPPED"}\n` +
466
+ `Agents: ${stats.agents} | Clients: ${stats.clients}\n` +
467
+ `Pool: ${stats.poolConnections} connections`, stats.running ? "info" : "warning");
678
468
  },
679
469
  });
680
- // Register gateway:status command
681
- pi.registerCommand('gateway:status', {
682
- description: 'Get gateway server status',
683
- async handler() {
684
- const status = getStatus();
685
- // @ts-ignore sendMessage type
686
- // @ts-ignore sendMessage type
687
- // @ts-ignore Content type
688
- pi.sendMessage({
689
- customType: 'gateway.status',
690
- content: [{ type: 'text', text: `Gateway status: ${JSON.stringify(status)}` }],
691
- // @ts-ignore Content type
692
- display: { type: 'text', text: `Gateway: ${status.running ? '🟢' : '🔴'} ${status.agents} agents, ${status.clients} clients` },
693
- details: status,
694
- });
695
- console.log('[Gateway] Status:', status);
470
+ pi.registerCommand("gateway:pool", {
471
+ description: "Show connection pool status",
472
+ handler: async (_args, ctx) => {
473
+ const stats = draconicPool.getStats();
474
+ ctx.ui.notify(`🌊 Connection Pool:\n` +
475
+ `Connections: ${stats.totalConnections}\n` +
476
+ `Active streams: ${stats.activeStreams}\n` +
477
+ `Avg latency: ${stats.averageLatency.toFixed(0)}ms\n` +
478
+ `Error rate: ${(stats.errorRate * 100).toFixed(1)}%`, "info");
696
479
  },
697
480
  });
698
- // Cleanup on shutdown
699
- // @ts-ignore Event type
700
- pi.on('shutdown', async () => {
701
- await stopGateway(pi);
481
+ // Tools
482
+ pi.registerTool({
483
+ name: "draconic_gateway_spawn",
484
+ label: "🐉 Spawn Draconic Agent",
485
+ description: "Spawn agent with token prediction",
486
+ parameters: Type.Object({
487
+ task: Type.String(),
488
+ parentId: Type.Optional(Type.String()),
489
+ model: Type.Optional(Type.String()),
490
+ }),
491
+ async execute(_id, params) {
492
+ const result = await spawnDraconicAgent(params, pi);
493
+ return {
494
+ content: [{ type: "text", text: result.success ? `🐉 Spawned: ${result.agentId}` : `❌ Failed: ${result.error}` }],
495
+ details: result,
496
+ };
497
+ },
702
498
  });
703
- // Auto-start gateway when session starts (runtime is ready)
704
- // Only start in the main process, NOT in subagent sessions
705
- pi.on('session_start', async () => {
706
- // Skip if already attempted (prevents duplicate starts in subagents)
707
- if (hasAttemptedStart) {
708
- console.log('[Gateway] Gateway already started or attempted, skipping...');
709
- return;
710
- }
711
- hasAttemptedStart = true;
712
- // Check if we're in a subagent by looking for parent session indicator
713
- const isSubagent = process.env.KOBOLD_SUBAGENT === 'true' || process.env.PI_SESSION_PARENT;
714
- if (isSubagent) {
715
- console.log('[Gateway] Running in subagent, skipping gateway auto-start');
716
- return;
717
- }
718
- // Skip if this isn't the main TUI process
719
- const args = process.argv.slice(2);
720
- const hasCommandFlag = args.includes('--command') || args.includes('-c');
721
- if (hasCommandFlag) {
722
- console.log('[Gateway] Running with --command flag, skipping gateway auto-start');
723
- return;
724
- }
725
- console.log('[Gateway] Session started. Auto-starting gateway...');
726
- await startGateway(pi);
499
+ pi.registerTool({
500
+ name: "draconic_gateway_stats",
501
+ label: "🐉 Gateway Stats",
502
+ description: "Get Draconic gateway statistics",
503
+ parameters: Type.Object({}),
504
+ async execute() {
505
+ const stats = getDraconicGatewayStats();
506
+ return {
507
+ content: [{
508
+ type: "text",
509
+ text: `🐉 Gateway Stats:\nRunning: ${stats.running}\nAgents: ${stats.agents}\nPool: ${stats.poolConnections}\nTokens predicted: ${stats.totalTokensPredicted}`,
510
+ }],
511
+ details: stats,
512
+ };
513
+ },
727
514
  });
728
- console.log('[Gateway] Extension loaded. Waiting for session start...');
515
+ console.log("[🐉 DraconicGateway] Extension loaded");
516
+ console.log(" Commands: /gateway:start, /gateway:stop, /gateway:status, /gateway:pool");
517
+ console.log(" Tools: draconic_gateway_spawn, draconic_gateway_stats");
729
518
  }
730
519
  //# sourceMappingURL=gateway-extension.js.map