0xkobold 0.0.1
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/.agents/skills/nextjs-best-practices/SKILL.md +208 -0
- package/.agents/skills/sql-optimization-patterns/SKILL.md +509 -0
- package/HEARTBEAT.md +45 -0
- package/README.md +197 -0
- package/USAGE.md +191 -0
- package/dist/package.json +77 -0
- package/dist/src/agent/pi-adapter.js +307 -0
- package/dist/src/agent/pi-adapter.js.map +1 -0
- package/dist/src/agent/tool-adapter.js +86 -0
- package/dist/src/agent/tool-adapter.js.map +1 -0
- package/dist/src/approval/queue.js +114 -0
- package/dist/src/approval/queue.js.map +1 -0
- package/dist/src/ascii-kobold.js +76 -0
- package/dist/src/ascii-kobold.js.map +1 -0
- package/dist/src/cli/client.js +217 -0
- package/dist/src/cli/client.js.map +1 -0
- package/dist/src/cli/commands/agent.js +272 -0
- package/dist/src/cli/commands/agent.js.map +1 -0
- package/dist/src/cli/commands/chat.js +234 -0
- package/dist/src/cli/commands/chat.js.map +1 -0
- package/dist/src/cli/commands/config.js +202 -0
- package/dist/src/cli/commands/config.js.map +1 -0
- package/dist/src/cli/commands/daemon.js +203 -0
- package/dist/src/cli/commands/daemon.js.map +1 -0
- package/dist/src/cli/commands/gateway.js +184 -0
- package/dist/src/cli/commands/gateway.js.map +1 -0
- package/dist/src/cli/commands/init.js +175 -0
- package/dist/src/cli/commands/init.js.map +1 -0
- package/dist/src/cli/commands/kobold.js +21 -0
- package/dist/src/cli/commands/kobold.js.map +1 -0
- package/dist/src/cli/commands/logs.js +27 -0
- package/dist/src/cli/commands/logs.js.map +1 -0
- package/dist/src/cli/commands/mode.js +121 -0
- package/dist/src/cli/commands/mode.js.map +1 -0
- package/dist/src/cli/commands/persona.js +261 -0
- package/dist/src/cli/commands/persona.js.map +1 -0
- package/dist/src/cli/commands/start.js +66 -0
- package/dist/src/cli/commands/start.js.map +1 -0
- package/dist/src/cli/commands/status.js +117 -0
- package/dist/src/cli/commands/status.js.map +1 -0
- package/dist/src/cli/commands/stop.js +27 -0
- package/dist/src/cli/commands/stop.js.map +1 -0
- package/dist/src/cli/commands/system.js +128 -0
- package/dist/src/cli/commands/system.js.map +1 -0
- package/dist/src/cli/commands/tui.js +103 -0
- package/dist/src/cli/commands/tui.js.map +1 -0
- package/dist/src/cli/commands/update.js +133 -0
- package/dist/src/cli/commands/update.js.map +1 -0
- package/dist/src/cli/extensions/discord.js +113 -0
- package/dist/src/cli/extensions/discord.js.map +1 -0
- package/dist/src/cli/extensions/env.js +91 -0
- package/dist/src/cli/extensions/env.js.map +1 -0
- package/dist/src/cli/extensions/heartbeat.js +78 -0
- package/dist/src/cli/extensions/heartbeat.js.map +1 -0
- package/dist/src/cli/index.js +24 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/program.js +70 -0
- package/dist/src/cli/program.js.map +1 -0
- package/dist/src/cli/repl.js +184 -0
- package/dist/src/cli/repl.js.map +1 -0
- package/dist/src/cli/shared/discord-service.js +102 -0
- package/dist/src/cli/shared/discord-service.js.map +1 -0
- package/dist/src/config/index.js +10 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/loader.js +401 -0
- package/dist/src/config/loader.js.map +1 -0
- package/dist/src/config/paths.js +84 -0
- package/dist/src/config/paths.js.map +1 -0
- package/dist/src/config/types.js +8 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/context-detector.js +60 -0
- package/dist/src/context-detector.js.map +1 -0
- package/dist/src/discord/index.js +376 -0
- package/dist/src/discord/index.js.map +1 -0
- package/dist/src/event-bus/index.js +97 -0
- package/dist/src/event-bus/index.js.map +1 -0
- package/dist/src/extensions/command-args.js +68 -0
- package/dist/src/extensions/command-args.js.map +1 -0
- package/dist/src/extensions/core/agent-registry-extension.js +541 -0
- package/dist/src/extensions/core/agent-registry-extension.js.map +1 -0
- package/dist/src/extensions/core/agent-worker.js +148 -0
- package/dist/src/extensions/core/agent-worker.js.map +1 -0
- package/dist/src/extensions/core/compaction-safeguard.js +154 -0
- package/dist/src/extensions/core/compaction-safeguard.js.map +1 -0
- package/dist/src/extensions/core/confirm-destructive.js +43 -0
- package/dist/src/extensions/core/confirm-destructive.js.map +1 -0
- package/dist/src/extensions/core/context-aware-extension.js +124 -0
- package/dist/src/extensions/core/context-aware-extension.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/extension.js +124 -0
- package/dist/src/extensions/core/context-pruning/extension.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/pruner.js +312 -0
- package/dist/src/extensions/core/context-pruning/pruner.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/runtime.js +48 -0
- package/dist/src/extensions/core/context-pruning/runtime.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/settings.js +105 -0
- package/dist/src/extensions/core/context-pruning/settings.js.map +1 -0
- package/dist/src/extensions/core/dirty-repo-guard.js +42 -0
- package/dist/src/extensions/core/dirty-repo-guard.js.map +1 -0
- package/dist/src/extensions/core/discord-channel-extension.js +205 -0
- package/dist/src/extensions/core/discord-channel-extension.js.map +1 -0
- package/dist/src/extensions/core/discord-extension.js +142 -0
- package/dist/src/extensions/core/discord-extension.js.map +1 -0
- package/dist/src/extensions/core/env-loader-extension.js +157 -0
- package/dist/src/extensions/core/env-loader-extension.js.map +1 -0
- package/dist/src/extensions/core/fileops-extension.js +699 -0
- package/dist/src/extensions/core/fileops-extension.js.map +1 -0
- package/dist/src/extensions/core/gateway-extension.js +730 -0
- package/dist/src/extensions/core/gateway-extension.js.map +1 -0
- package/dist/src/extensions/core/git-checkpoint.js +46 -0
- package/dist/src/extensions/core/git-checkpoint.js.map +1 -0
- package/dist/src/extensions/core/handoff-extension.js +206 -0
- package/dist/src/extensions/core/handoff-extension.js.map +1 -0
- package/dist/src/extensions/core/heartbeat-extension.js +373 -0
- package/dist/src/extensions/core/heartbeat-extension.js.map +1 -0
- package/dist/src/extensions/core/mcp-extension.js +413 -0
- package/dist/src/extensions/core/mcp-extension.js.map +1 -0
- package/dist/src/extensions/core/mode-manager-extension.js +562 -0
- package/dist/src/extensions/core/mode-manager-extension.js.map +1 -0
- package/dist/src/extensions/core/multi-channel-extension.js +435 -0
- package/dist/src/extensions/core/multi-channel-extension.js.map +1 -0
- package/dist/src/extensions/core/ollama-provider-extension.js +66 -0
- package/dist/src/extensions/core/ollama-provider-extension.js.map +1 -0
- package/dist/src/extensions/core/onboarding-extension.js +122 -0
- package/dist/src/extensions/core/onboarding-extension.js.map +1 -0
- package/dist/src/extensions/core/persona-loader-extension.js +139 -0
- package/dist/src/extensions/core/persona-loader-extension.js.map +1 -0
- package/dist/src/extensions/core/pi-notify-extension.js +70 -0
- package/dist/src/extensions/core/pi-notify-extension.js.map +1 -0
- package/dist/src/extensions/core/protected-paths.js +24 -0
- package/dist/src/extensions/core/protected-paths.js.map +1 -0
- package/dist/src/extensions/core/questionnaire-extension.js +242 -0
- package/dist/src/extensions/core/questionnaire-extension.js.map +1 -0
- package/dist/src/extensions/core/self-update-extension.js +181 -0
- package/dist/src/extensions/core/self-update-extension.js.map +1 -0
- package/dist/src/extensions/core/session-bridge-extension.js +78 -0
- package/dist/src/extensions/core/session-bridge-extension.js.map +1 -0
- package/dist/src/extensions/core/session-manager-extension.js +319 -0
- package/dist/src/extensions/core/session-manager-extension.js.map +1 -0
- package/dist/src/extensions/core/session-name-extension.js +88 -0
- package/dist/src/extensions/core/session-name-extension.js.map +1 -0
- package/dist/src/extensions/core/session-pruning-extension.js +480 -0
- package/dist/src/extensions/core/session-pruning-extension.js.map +1 -0
- package/dist/src/extensions/core/task-manager-extension.js +661 -0
- package/dist/src/extensions/core/task-manager-extension.js.map +1 -0
- package/dist/src/extensions/core/update-extension.js +438 -0
- package/dist/src/extensions/core/update-extension.js.map +1 -0
- package/dist/src/extensions/core/websearch-extension.js +463 -0
- package/dist/src/extensions/core/websearch-extension.js.map +1 -0
- package/dist/src/extensions/index.js +5 -0
- package/dist/src/extensions/index.js.map +1 -0
- package/dist/src/extensions/loader.js +80 -0
- package/dist/src/extensions/loader.js.map +1 -0
- package/dist/src/gateway/index.js +353 -0
- package/dist/src/gateway/index.js.map +1 -0
- package/dist/src/index.js +150 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/llm/anthropic.js +86 -0
- package/dist/src/llm/anthropic.js.map +1 -0
- package/dist/src/llm/index.js +9 -0
- package/dist/src/llm/index.js.map +1 -0
- package/dist/src/llm/ollama.js +113 -0
- package/dist/src/llm/ollama.js.map +1 -0
- package/dist/src/llm/router.js +145 -0
- package/dist/src/llm/router.js.map +1 -0
- package/dist/src/llm/types.js +7 -0
- package/dist/src/llm/types.js.map +1 -0
- package/dist/src/memory/index.js +5 -0
- package/dist/src/memory/index.js.map +1 -0
- package/dist/src/memory/store.js +91 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/pi-config.js +80 -0
- package/dist/src/pi-config.js.map +1 -0
- package/dist/src/skills/builtin/file.js +184 -0
- package/dist/src/skills/builtin/file.js.map +1 -0
- package/dist/src/skills/builtin/shell.js +100 -0
- package/dist/src/skills/builtin/shell.js.map +1 -0
- package/dist/src/skills/builtin/subagent.js +62 -0
- package/dist/src/skills/builtin/subagent.js.map +1 -0
- package/dist/src/skills/hello.js +42 -0
- package/dist/src/skills/hello.js.map +1 -0
- package/dist/src/skills/index.js +11 -0
- package/dist/src/skills/index.js.map +1 -0
- package/dist/src/skills/loader.js +382 -0
- package/dist/src/skills/loader.js.map +1 -0
- package/dist/src/skills/types.js +8 -0
- package/dist/src/skills/types.js.map +1 -0
- package/dist/src/utils/working-dir.js +71 -0
- package/dist/src/utils/working-dir.js.map +1 -0
- package/package.json +77 -0
- package/skills/1password/SKILL.md +70 -0
- package/skills/1password/references/cli-examples.md +29 -0
- package/skills/1password/references/get-started.md +17 -0
- package/skills/apple-notes/SKILL.md +77 -0
- package/skills/apple-reminders/SKILL.md +118 -0
- package/skills/bear-notes/SKILL.md +107 -0
- package/skills/blogwatcher/SKILL.md +69 -0
- package/skills/blucli/SKILL.md +47 -0
- package/skills/bluebubbles/SKILL.md +131 -0
- package/skills/camsnap/SKILL.md +45 -0
- package/skills/canvas/SKILL.md +198 -0
- package/skills/clawhub/SKILL.md +77 -0
- package/skills/coding-agent/SKILL.md +284 -0
- package/skills/discord/SKILL.md +197 -0
- package/skills/eightctl/SKILL.md +50 -0
- package/skills/food-order/SKILL.md +48 -0
- package/skills/gemini/SKILL.md +43 -0
- package/skills/gh-issues/SKILL.md +865 -0
- package/skills/gifgrep/SKILL.md +79 -0
- package/skills/github/SKILL.md +163 -0
- package/skills/gog/SKILL.md +116 -0
- package/skills/goplaces/SKILL.md +52 -0
- package/skills/healthcheck/SKILL.md +245 -0
- package/skills/himalaya/SKILL.md +257 -0
- package/skills/himalaya/references/configuration.md +184 -0
- package/skills/himalaya/references/message-composition.md +199 -0
- package/skills/imsg/SKILL.md +122 -0
- package/skills/mcporter/SKILL.md +61 -0
- package/skills/model-usage/SKILL.md +69 -0
- package/skills/model-usage/references/codexbar-cli.md +33 -0
- package/skills/model-usage/scripts/model_usage.py +310 -0
- package/skills/nano-banana-pro/SKILL.md +58 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +184 -0
- package/skills/nano-pdf/SKILL.md +38 -0
- package/skills/notion/SKILL.md +172 -0
- package/skills/obsidian/SKILL.md +81 -0
- package/skills/openai-image-gen/SKILL.md +89 -0
- package/skills/openai-image-gen/scripts/gen.py +240 -0
- package/skills/openai-whisper/SKILL.md +38 -0
- package/skills/openai-whisper-api/SKILL.md +52 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
- package/skills/openhue/SKILL.md +112 -0
- package/skills/oracle/SKILL.md +125 -0
- package/skills/ordercli/SKILL.md +78 -0
- package/skills/peekaboo/SKILL.md +190 -0
- package/skills/sag/SKILL.md +87 -0
- package/skills/session-logs/SKILL.md +115 -0
- package/skills/sherpa-onnx-tts/SKILL.md +103 -0
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
- package/skills/skill-creator/SKILL.md +370 -0
- package/skills/skill-creator/license.txt +202 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/package_skill.py +111 -0
- package/skills/skill-creator/scripts/quick_validate.py +101 -0
- package/skills/slack/SKILL.md +144 -0
- package/skills/songsee/SKILL.md +49 -0
- package/skills/sonoscli/SKILL.md +46 -0
- package/skills/spotify-player/SKILL.md +64 -0
- package/skills/summarize/SKILL.md +87 -0
- package/skills/things-mac/SKILL.md +86 -0
- package/skills/tmux/SKILL.md +153 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/trello/SKILL.md +95 -0
- package/skills/video-frames/SKILL.md +46 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/voice-call/SKILL.md +45 -0
- package/skills/wacli/SKILL.md +72 -0
- package/skills/weather/SKILL.md +112 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway Extension for 0xKobold
|
|
3
|
+
*
|
|
4
|
+
* Multi-Agent WebSocket Gateway - Ported from gateway/index.ts
|
|
5
|
+
* Provides WebSocket server for agent spawning and management
|
|
6
|
+
*/
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
10
|
+
// Extension state
|
|
11
|
+
let server = null;
|
|
12
|
+
const agents = new Map();
|
|
13
|
+
const clients = new Map();
|
|
14
|
+
let eventSeq = 0;
|
|
15
|
+
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
|
+
}
|
|
31
|
+
}
|
|
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
|
+
},
|
|
41
|
+
});
|
|
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
|
+
}
|
|
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 },
|
|
81
|
+
});
|
|
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
|
+
}
|
|
189
|
+
const agent = {
|
|
190
|
+
id,
|
|
191
|
+
parentId: params.parentId,
|
|
192
|
+
sessionKey,
|
|
193
|
+
depth,
|
|
194
|
+
type,
|
|
195
|
+
capabilities,
|
|
196
|
+
status: "idle",
|
|
197
|
+
spawnedAt: new Date(),
|
|
198
|
+
task: params.task,
|
|
199
|
+
model: params.model || "ollama/minimax-m2.5:cloud",
|
|
200
|
+
children: [],
|
|
201
|
+
workspace,
|
|
202
|
+
tokens: { input: 0, output: 0 },
|
|
203
|
+
stats: { runtime: 0, toolCalls: 0 },
|
|
204
|
+
};
|
|
205
|
+
agents.set(id, agent);
|
|
206
|
+
// Add to parent's children
|
|
207
|
+
if (parent) {
|
|
208
|
+
parent.children.push(id);
|
|
209
|
+
}
|
|
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);
|
|
243
|
+
}
|
|
244
|
+
emit(pi, "agent.status", {
|
|
245
|
+
id: agent.id,
|
|
246
|
+
status: "completed",
|
|
247
|
+
tokens: agent.tokens,
|
|
248
|
+
});
|
|
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,
|
|
264
|
+
};
|
|
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
|
+
}
|
|
289
|
+
// Check if another gateway instance is already running
|
|
290
|
+
async function isGatewayRunning(port, hostname) {
|
|
291
|
+
try {
|
|
292
|
+
const response = await fetch(`http://${hostname}:${port}/health`, {
|
|
293
|
+
signal: AbortSignal.timeout(1000)
|
|
294
|
+
});
|
|
295
|
+
return response.ok;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
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;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
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;
|
|
338
|
+
}
|
|
339
|
+
server = Bun.serve({
|
|
340
|
+
port: GATEWAY_PORT,
|
|
341
|
+
hostname,
|
|
342
|
+
websocket: {
|
|
343
|
+
open(ws) {
|
|
344
|
+
ws.data = { state: "pending", id: null };
|
|
345
|
+
},
|
|
346
|
+
async message(ws, data) {
|
|
347
|
+
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
|
|
379
|
+
if (frame.type === "req") {
|
|
380
|
+
const req = frame;
|
|
381
|
+
let res;
|
|
382
|
+
switch (req.method) {
|
|
383
|
+
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 = {
|
|
431
|
+
type: "res",
|
|
432
|
+
id: frame.id,
|
|
433
|
+
ok: true,
|
|
434
|
+
payload: {
|
|
435
|
+
agents: agentList,
|
|
436
|
+
tree: getAgentTreeString(),
|
|
437
|
+
total: agents.size,
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
case "agent.tree": {
|
|
443
|
+
res = {
|
|
444
|
+
type: "res",
|
|
445
|
+
id: frame.id,
|
|
446
|
+
ok: true,
|
|
447
|
+
payload: {
|
|
448
|
+
tree: getAgentTreeString(),
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
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);
|
|
460
|
+
}
|
|
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
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
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
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
default:
|
|
531
|
+
res = {
|
|
532
|
+
type: "res",
|
|
533
|
+
id: frame.id,
|
|
534
|
+
ok: false,
|
|
535
|
+
error: `Unknown method: ${req.method}`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
ws.send(JSON.stringify(res));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
ws.send(JSON.stringify({
|
|
543
|
+
type: "error",
|
|
544
|
+
error: err instanceof Error ? err.message : String(err),
|
|
545
|
+
}));
|
|
546
|
+
}
|
|
547
|
+
},
|
|
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
|
+
},
|
|
556
|
+
fetch(req, server) {
|
|
557
|
+
const url = new URL(req.url);
|
|
558
|
+
if (url.pathname === "/health") {
|
|
559
|
+
return Response.json({
|
|
560
|
+
status: "ok",
|
|
561
|
+
agents: agents.size,
|
|
562
|
+
clients: clients.size,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
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
|
|
569
|
+
}
|
|
570
|
+
return new Response("0xKobold Gateway - WebSocket on port 18789", { status: 200 });
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
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
|
|
579
|
+
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 },
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
// Stop Gateway Server
|
|
588
|
+
async function stopGateway(pi) {
|
|
589
|
+
if (!server) {
|
|
590
|
+
console.log('[Gateway] Not running');
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
// Close all client connections
|
|
594
|
+
for (const [, ws] of clients) {
|
|
595
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
596
|
+
ws.close(1000, "Gateway shutting down");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
clients.clear();
|
|
600
|
+
// Stop the server
|
|
601
|
+
server.stop();
|
|
602
|
+
server = null;
|
|
603
|
+
isRunning = false;
|
|
604
|
+
console.log('[Gateway] Server stopped');
|
|
605
|
+
// @ts-ignore sendMessage type
|
|
606
|
+
// @ts-ignore sendMessage type
|
|
607
|
+
// @ts-ignore Content type
|
|
608
|
+
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' },
|
|
613
|
+
details: {},
|
|
614
|
+
});
|
|
615
|
+
}
|
|
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
|
+
};
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
// Register gateway:start command
|
|
667
|
+
pi.registerCommand('gateway:start', {
|
|
668
|
+
description: 'Start the WebSocket gateway server',
|
|
669
|
+
async handler() {
|
|
670
|
+
await startGateway(pi);
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
// Register gateway:stop command
|
|
674
|
+
pi.registerCommand('gateway:stop', {
|
|
675
|
+
description: 'Stop the WebSocket gateway server',
|
|
676
|
+
async handler() {
|
|
677
|
+
await stopGateway(pi);
|
|
678
|
+
},
|
|
679
|
+
});
|
|
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);
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
// Cleanup on shutdown
|
|
699
|
+
// @ts-ignore Event type
|
|
700
|
+
pi.on('shutdown', async () => {
|
|
701
|
+
await stopGateway(pi);
|
|
702
|
+
});
|
|
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);
|
|
727
|
+
});
|
|
728
|
+
console.log('[Gateway] Extension loaded. Waiting for session start...');
|
|
729
|
+
}
|
|
730
|
+
//# sourceMappingURL=gateway-extension.js.map
|