0xkobold 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -383
- package/dist/package.json +13 -4
- package/dist/src/agent/DraconicCapabilityRouter.js +556 -0
- package/dist/src/agent/DraconicCapabilityRouter.js.map +1 -0
- package/dist/src/agent/DraconicErrorClassifier.js +606 -0
- package/dist/src/agent/DraconicErrorClassifier.js.map +1 -0
- package/dist/src/agent/DraconicRunRegistry.js +460 -0
- package/dist/src/agent/DraconicRunRegistry.js.map +1 -0
- package/dist/src/agent/DraconicTokenPredictor.js +500 -0
- package/dist/src/agent/DraconicTokenPredictor.js.map +1 -0
- package/dist/src/agent/embedded-runner.js +3 -2
- package/dist/src/agent/embedded-runner.js.map +1 -1
- package/dist/src/cli/commands/doctor.js +199 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/migrate-from-openclaw.js +39 -0
- package/dist/src/cli/commands/migrate-from-openclaw.js.map +1 -0
- package/dist/src/cli/commands/migrate.js +6 -4
- package/dist/src/cli/commands/migrate.js.map +1 -1
- package/dist/src/cli/program.js +3 -1
- package/dist/src/cli/program.js.map +1 -1
- package/dist/src/config/unified-config.js +195 -0
- package/dist/src/config/unified-config.js.map +1 -0
- package/dist/src/cron/notifications.js +1 -1
- package/dist/src/cron/notifications.js.map +1 -1
- package/dist/src/cron/runner.js +9 -3
- package/dist/src/cron/runner.js.map +1 -1
- package/dist/src/event-bus/index.js +3 -1
- package/dist/src/event-bus/index.js.map +1 -1
- package/dist/src/extensions/community/draconic-messenger-wrapper.js +35 -0
- package/dist/src/extensions/community/draconic-messenger-wrapper.js.map +1 -0
- package/dist/src/extensions/community/draconic-subagents-wrapper.js +115 -0
- package/dist/src/extensions/community/draconic-subagents-wrapper.js.map +1 -0
- package/dist/src/extensions/core/compaction-safeguard-v2.js +95 -0
- package/dist/src/extensions/core/compaction-safeguard-v2.js.map +1 -0
- package/dist/src/extensions/core/compaction-safeguard.js +206 -137
- package/dist/src/extensions/core/compaction-safeguard.js.map +1 -1
- package/dist/src/extensions/core/config-extension.js +122 -0
- package/dist/src/extensions/core/config-extension.js.map +1 -0
- package/dist/src/extensions/core/draconic-hoard-extension.js +194 -0
- package/dist/src/extensions/core/draconic-hoard-extension.js.map +1 -0
- package/dist/src/extensions/core/draconic-lair-extension.js +146 -0
- package/dist/src/extensions/core/draconic-lair-extension.js.map +1 -0
- package/dist/src/extensions/core/draconic-safety-extension.js +402 -0
- package/dist/src/extensions/core/draconic-safety-extension.js.map +1 -0
- package/dist/src/extensions/core/ext-manager.js +40 -0
- package/dist/src/extensions/core/ext-manager.js.map +1 -0
- package/dist/src/extensions/core/gateway-extension.js +413 -624
- package/dist/src/extensions/core/gateway-extension.js.map +1 -1
- package/dist/src/extensions/core/generative-agents-extension.js +704 -0
- package/dist/src/extensions/core/generative-agents-extension.js.map +1 -0
- package/dist/src/extensions/core/memory-bootstrap-extension.js +279 -0
- package/dist/src/extensions/core/memory-bootstrap-extension.js.map +1 -0
- package/dist/src/extensions/core/ollama-extension.js +442 -0
- package/dist/src/extensions/core/ollama-extension.js.map +1 -0
- package/dist/src/extensions/core/ollama-router-extension.js +34 -93
- package/dist/src/extensions/core/ollama-router-extension.js.map +1 -1
- package/dist/src/extensions/core/perennial-memory-extension.js +5 -2
- package/dist/src/extensions/core/perennial-memory-extension.js.map +1 -1
- package/dist/src/extensions/core/persona-loader-extension.js +41 -15
- package/dist/src/extensions/core/persona-loader-extension.js.map +1 -1
- package/dist/src/extensions/core/pi-notify-extension.js +6 -0
- package/dist/src/extensions/core/pi-notify-extension.js.map +1 -1
- package/dist/src/extensions/core/session-bridge-extension.js +8 -72
- package/dist/src/extensions/core/session-bridge-extension.js.map +1 -1
- package/dist/src/extensions/core/tui-integration-extension-fixed.js +3 -0
- package/dist/src/extensions/core/tui-integration-extension-fixed.js.map +1 -0
- package/dist/src/extensions/core/tui-integration-extension.js +984 -0
- package/dist/src/extensions/core/tui-integration-extension.js.map +1 -0
- package/dist/src/gateway/persistence/AgentStore.js +399 -0
- package/dist/src/gateway/persistence/AgentStore.js.map +1 -0
- package/dist/src/gateway/persistence/index.js +9 -0
- package/dist/src/gateway/persistence/index.js.map +1 -0
- package/dist/src/hoard/DraconicHoardSystem.js +465 -0
- package/dist/src/hoard/DraconicHoardSystem.js.map +1 -0
- package/dist/src/index.js +7 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/infra/DraconicConnectionPool.js +492 -0
- package/dist/src/infra/DraconicConnectionPool.js.map +1 -0
- package/dist/src/lair/DraconicLairSystem.js +387 -0
- package/dist/src/lair/DraconicLairSystem.js.map +1 -0
- package/dist/src/llm/ollama.js +143 -99
- package/dist/src/llm/ollama.js.map +1 -1
- package/dist/src/migration/openclaw.js +5 -5
- package/dist/src/migration/openclaw.js.map +1 -1
- package/dist/src/pi-config.js +52 -27
- package/dist/src/pi-config.js.map +1 -1
- package/dist/src/sessions/SessionManager.js +416 -0
- package/dist/src/sessions/SessionManager.js.map +1 -0
- package/dist/src/sessions/SessionStore.js +580 -0
- package/dist/src/sessions/SessionStore.js.map +1 -0
- package/dist/src/sessions/UnifiedSessionBridge.js +394 -0
- package/dist/src/sessions/UnifiedSessionBridge.js.map +1 -0
- package/dist/src/sessions/index.js +15 -0
- package/dist/src/sessions/index.js.map +1 -0
- package/dist/src/sessions/migration/index.js +156 -0
- package/dist/src/sessions/migration/index.js.map +1 -0
- package/dist/src/sessions/types.js +8 -0
- package/dist/src/sessions/types.js.map +1 -0
- package/dist/src/skills/framework.js +1 -1
- package/dist/src/skills/framework.js.map +1 -1
- package/dist/src/tui/commands/final-functions.js +39 -0
- package/dist/src/tui/commands/final-functions.js.map +1 -0
- package/dist/src/tui/commands/natural-language-commands.js +231 -0
- package/dist/src/tui/commands/natural-language-commands.js.map +1 -0
- package/dist/src/tui/commands/orchestration-commands.js +340 -0
- package/dist/src/tui/commands/orchestration-commands.js.map +1 -0
- package/dist/src/tui/commands/orchestration-final-section.js +84 -0
- package/dist/src/tui/commands/orchestration-final-section.js.map +1 -0
- package/dist/src/tui/components/agent-tree-overlay.js +210 -0
- package/dist/src/tui/components/agent-tree-overlay.js.map +1 -0
- package/dist/src/tui/components/agent-tree-panel.js +229 -0
- package/dist/src/tui/components/agent-tree-panel.js.map +1 -0
- package/dist/src/tui/components/sidebar.js +82 -0
- package/dist/src/tui/components/sidebar.js.map +1 -0
- package/dist/src/tui/components/status-bar.js +115 -0
- package/dist/src/tui/components/status-bar.js.map +1 -0
- package/dist/src/tui/components/tree-header.js +85 -0
- package/dist/src/tui/components/tree-header.js.map +1 -0
- package/dist/src/tui/draconic-tui.js +291 -0
- package/dist/src/tui/draconic-tui.js.map +1 -0
- package/dist/src/tui/index.js +93 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/pi-tui-integration.js +260 -0
- package/dist/src/tui/pi-tui-integration.js.map +1 -0
- package/dist/src/utils/nl-patterns.js +145 -0
- package/dist/src/utils/nl-patterns.js.map +1 -0
- package/package.json +13 -4
- package/scripts/migrate-from-openclaw.sh +498 -0
- package/HEARTBEAT.md +0 -121
- package/USAGE.md +0 -191
|
@@ -1,730 +1,519 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gateway Extension
|
|
2
|
+
* 🐉 Draconic Gateway Extension
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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 {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
//
|
|
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: "
|
|
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 || "
|
|
102
|
+
model: params.model || "default",
|
|
200
103
|
children: [],
|
|
201
|
-
workspace,
|
|
202
|
-
tokens: { input:
|
|
203
|
-
|
|
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
|
-
//
|
|
207
|
-
if (
|
|
208
|
-
parent.
|
|
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
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
290
|
-
|
|
136
|
+
/**
|
|
137
|
+
* 🐉 Enhanced API call with connection pooling
|
|
138
|
+
*/
|
|
139
|
+
async function draconicAPICall(provider, model, endpoint, payload) {
|
|
291
140
|
try {
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
381
|
-
let res;
|
|
382
|
-
switch (req.method) {
|
|
248
|
+
switch (frame.method) {
|
|
383
249
|
case "agent.spawn": {
|
|
384
|
-
|
|
385
|
-
const { task, parentId,
|
|
386
|
-
|
|
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:
|
|
434
|
-
payload:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
449
|
-
},
|
|
450
|
-
};
|
|
270
|
+
payload: { tree },
|
|
271
|
+
}));
|
|
451
272
|
break;
|
|
452
273
|
}
|
|
453
|
-
case "agent.
|
|
454
|
-
const {
|
|
455
|
-
const agent = agents.get(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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 "
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
331
|
+
ws.send(JSON.stringify({
|
|
532
332
|
type: "res",
|
|
533
333
|
id: frame.id,
|
|
534
334
|
ok: false,
|
|
535
|
-
error: `Unknown method: ${
|
|
536
|
-
};
|
|
335
|
+
error: `Unknown method: ${frame.method}`,
|
|
336
|
+
}));
|
|
537
337
|
}
|
|
538
|
-
ws.send(JSON.stringify(res));
|
|
539
338
|
}
|
|
540
339
|
}
|
|
541
|
-
catch (
|
|
340
|
+
catch (error) {
|
|
341
|
+
console.error("[🐉 Gateway] Message error:", error);
|
|
542
342
|
ws.send(JSON.stringify({
|
|
543
343
|
type: "error",
|
|
544
|
-
error:
|
|
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
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
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]
|
|
575
|
-
console.log(`[Gateway]
|
|
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:
|
|
581
|
-
content: [{ type:
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
588
|
-
|
|
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(
|
|
423
|
+
console.log("[🐉 Gateway] Not running");
|
|
591
424
|
return;
|
|
592
425
|
}
|
|
593
|
-
//
|
|
594
|
-
|
|
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(
|
|
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:
|
|
610
|
-
content: [{ type:
|
|
611
|
-
|
|
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
|
-
//
|
|
617
|
-
function
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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(
|
|
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
|