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.
Files changed (258) hide show
  1. package/.agents/skills/nextjs-best-practices/SKILL.md +208 -0
  2. package/.agents/skills/sql-optimization-patterns/SKILL.md +509 -0
  3. package/HEARTBEAT.md +45 -0
  4. package/README.md +197 -0
  5. package/USAGE.md +191 -0
  6. package/dist/package.json +77 -0
  7. package/dist/src/agent/pi-adapter.js +307 -0
  8. package/dist/src/agent/pi-adapter.js.map +1 -0
  9. package/dist/src/agent/tool-adapter.js +86 -0
  10. package/dist/src/agent/tool-adapter.js.map +1 -0
  11. package/dist/src/approval/queue.js +114 -0
  12. package/dist/src/approval/queue.js.map +1 -0
  13. package/dist/src/ascii-kobold.js +76 -0
  14. package/dist/src/ascii-kobold.js.map +1 -0
  15. package/dist/src/cli/client.js +217 -0
  16. package/dist/src/cli/client.js.map +1 -0
  17. package/dist/src/cli/commands/agent.js +272 -0
  18. package/dist/src/cli/commands/agent.js.map +1 -0
  19. package/dist/src/cli/commands/chat.js +234 -0
  20. package/dist/src/cli/commands/chat.js.map +1 -0
  21. package/dist/src/cli/commands/config.js +202 -0
  22. package/dist/src/cli/commands/config.js.map +1 -0
  23. package/dist/src/cli/commands/daemon.js +203 -0
  24. package/dist/src/cli/commands/daemon.js.map +1 -0
  25. package/dist/src/cli/commands/gateway.js +184 -0
  26. package/dist/src/cli/commands/gateway.js.map +1 -0
  27. package/dist/src/cli/commands/init.js +175 -0
  28. package/dist/src/cli/commands/init.js.map +1 -0
  29. package/dist/src/cli/commands/kobold.js +21 -0
  30. package/dist/src/cli/commands/kobold.js.map +1 -0
  31. package/dist/src/cli/commands/logs.js +27 -0
  32. package/dist/src/cli/commands/logs.js.map +1 -0
  33. package/dist/src/cli/commands/mode.js +121 -0
  34. package/dist/src/cli/commands/mode.js.map +1 -0
  35. package/dist/src/cli/commands/persona.js +261 -0
  36. package/dist/src/cli/commands/persona.js.map +1 -0
  37. package/dist/src/cli/commands/start.js +66 -0
  38. package/dist/src/cli/commands/start.js.map +1 -0
  39. package/dist/src/cli/commands/status.js +117 -0
  40. package/dist/src/cli/commands/status.js.map +1 -0
  41. package/dist/src/cli/commands/stop.js +27 -0
  42. package/dist/src/cli/commands/stop.js.map +1 -0
  43. package/dist/src/cli/commands/system.js +128 -0
  44. package/dist/src/cli/commands/system.js.map +1 -0
  45. package/dist/src/cli/commands/tui.js +103 -0
  46. package/dist/src/cli/commands/tui.js.map +1 -0
  47. package/dist/src/cli/commands/update.js +133 -0
  48. package/dist/src/cli/commands/update.js.map +1 -0
  49. package/dist/src/cli/extensions/discord.js +113 -0
  50. package/dist/src/cli/extensions/discord.js.map +1 -0
  51. package/dist/src/cli/extensions/env.js +91 -0
  52. package/dist/src/cli/extensions/env.js.map +1 -0
  53. package/dist/src/cli/extensions/heartbeat.js +78 -0
  54. package/dist/src/cli/extensions/heartbeat.js.map +1 -0
  55. package/dist/src/cli/index.js +24 -0
  56. package/dist/src/cli/index.js.map +1 -0
  57. package/dist/src/cli/program.js +70 -0
  58. package/dist/src/cli/program.js.map +1 -0
  59. package/dist/src/cli/repl.js +184 -0
  60. package/dist/src/cli/repl.js.map +1 -0
  61. package/dist/src/cli/shared/discord-service.js +102 -0
  62. package/dist/src/cli/shared/discord-service.js.map +1 -0
  63. package/dist/src/config/index.js +10 -0
  64. package/dist/src/config/index.js.map +1 -0
  65. package/dist/src/config/loader.js +401 -0
  66. package/dist/src/config/loader.js.map +1 -0
  67. package/dist/src/config/paths.js +84 -0
  68. package/dist/src/config/paths.js.map +1 -0
  69. package/dist/src/config/types.js +8 -0
  70. package/dist/src/config/types.js.map +1 -0
  71. package/dist/src/context-detector.js +60 -0
  72. package/dist/src/context-detector.js.map +1 -0
  73. package/dist/src/discord/index.js +376 -0
  74. package/dist/src/discord/index.js.map +1 -0
  75. package/dist/src/event-bus/index.js +97 -0
  76. package/dist/src/event-bus/index.js.map +1 -0
  77. package/dist/src/extensions/command-args.js +68 -0
  78. package/dist/src/extensions/command-args.js.map +1 -0
  79. package/dist/src/extensions/core/agent-registry-extension.js +541 -0
  80. package/dist/src/extensions/core/agent-registry-extension.js.map +1 -0
  81. package/dist/src/extensions/core/agent-worker.js +148 -0
  82. package/dist/src/extensions/core/agent-worker.js.map +1 -0
  83. package/dist/src/extensions/core/compaction-safeguard.js +154 -0
  84. package/dist/src/extensions/core/compaction-safeguard.js.map +1 -0
  85. package/dist/src/extensions/core/confirm-destructive.js +43 -0
  86. package/dist/src/extensions/core/confirm-destructive.js.map +1 -0
  87. package/dist/src/extensions/core/context-aware-extension.js +124 -0
  88. package/dist/src/extensions/core/context-aware-extension.js.map +1 -0
  89. package/dist/src/extensions/core/context-pruning/extension.js +124 -0
  90. package/dist/src/extensions/core/context-pruning/extension.js.map +1 -0
  91. package/dist/src/extensions/core/context-pruning/pruner.js +312 -0
  92. package/dist/src/extensions/core/context-pruning/pruner.js.map +1 -0
  93. package/dist/src/extensions/core/context-pruning/runtime.js +48 -0
  94. package/dist/src/extensions/core/context-pruning/runtime.js.map +1 -0
  95. package/dist/src/extensions/core/context-pruning/settings.js +105 -0
  96. package/dist/src/extensions/core/context-pruning/settings.js.map +1 -0
  97. package/dist/src/extensions/core/dirty-repo-guard.js +42 -0
  98. package/dist/src/extensions/core/dirty-repo-guard.js.map +1 -0
  99. package/dist/src/extensions/core/discord-channel-extension.js +205 -0
  100. package/dist/src/extensions/core/discord-channel-extension.js.map +1 -0
  101. package/dist/src/extensions/core/discord-extension.js +142 -0
  102. package/dist/src/extensions/core/discord-extension.js.map +1 -0
  103. package/dist/src/extensions/core/env-loader-extension.js +157 -0
  104. package/dist/src/extensions/core/env-loader-extension.js.map +1 -0
  105. package/dist/src/extensions/core/fileops-extension.js +699 -0
  106. package/dist/src/extensions/core/fileops-extension.js.map +1 -0
  107. package/dist/src/extensions/core/gateway-extension.js +730 -0
  108. package/dist/src/extensions/core/gateway-extension.js.map +1 -0
  109. package/dist/src/extensions/core/git-checkpoint.js +46 -0
  110. package/dist/src/extensions/core/git-checkpoint.js.map +1 -0
  111. package/dist/src/extensions/core/handoff-extension.js +206 -0
  112. package/dist/src/extensions/core/handoff-extension.js.map +1 -0
  113. package/dist/src/extensions/core/heartbeat-extension.js +373 -0
  114. package/dist/src/extensions/core/heartbeat-extension.js.map +1 -0
  115. package/dist/src/extensions/core/mcp-extension.js +413 -0
  116. package/dist/src/extensions/core/mcp-extension.js.map +1 -0
  117. package/dist/src/extensions/core/mode-manager-extension.js +562 -0
  118. package/dist/src/extensions/core/mode-manager-extension.js.map +1 -0
  119. package/dist/src/extensions/core/multi-channel-extension.js +435 -0
  120. package/dist/src/extensions/core/multi-channel-extension.js.map +1 -0
  121. package/dist/src/extensions/core/ollama-provider-extension.js +66 -0
  122. package/dist/src/extensions/core/ollama-provider-extension.js.map +1 -0
  123. package/dist/src/extensions/core/onboarding-extension.js +122 -0
  124. package/dist/src/extensions/core/onboarding-extension.js.map +1 -0
  125. package/dist/src/extensions/core/persona-loader-extension.js +139 -0
  126. package/dist/src/extensions/core/persona-loader-extension.js.map +1 -0
  127. package/dist/src/extensions/core/pi-notify-extension.js +70 -0
  128. package/dist/src/extensions/core/pi-notify-extension.js.map +1 -0
  129. package/dist/src/extensions/core/protected-paths.js +24 -0
  130. package/dist/src/extensions/core/protected-paths.js.map +1 -0
  131. package/dist/src/extensions/core/questionnaire-extension.js +242 -0
  132. package/dist/src/extensions/core/questionnaire-extension.js.map +1 -0
  133. package/dist/src/extensions/core/self-update-extension.js +181 -0
  134. package/dist/src/extensions/core/self-update-extension.js.map +1 -0
  135. package/dist/src/extensions/core/session-bridge-extension.js +78 -0
  136. package/dist/src/extensions/core/session-bridge-extension.js.map +1 -0
  137. package/dist/src/extensions/core/session-manager-extension.js +319 -0
  138. package/dist/src/extensions/core/session-manager-extension.js.map +1 -0
  139. package/dist/src/extensions/core/session-name-extension.js +88 -0
  140. package/dist/src/extensions/core/session-name-extension.js.map +1 -0
  141. package/dist/src/extensions/core/session-pruning-extension.js +480 -0
  142. package/dist/src/extensions/core/session-pruning-extension.js.map +1 -0
  143. package/dist/src/extensions/core/task-manager-extension.js +661 -0
  144. package/dist/src/extensions/core/task-manager-extension.js.map +1 -0
  145. package/dist/src/extensions/core/update-extension.js +438 -0
  146. package/dist/src/extensions/core/update-extension.js.map +1 -0
  147. package/dist/src/extensions/core/websearch-extension.js +463 -0
  148. package/dist/src/extensions/core/websearch-extension.js.map +1 -0
  149. package/dist/src/extensions/index.js +5 -0
  150. package/dist/src/extensions/index.js.map +1 -0
  151. package/dist/src/extensions/loader.js +80 -0
  152. package/dist/src/extensions/loader.js.map +1 -0
  153. package/dist/src/gateway/index.js +353 -0
  154. package/dist/src/gateway/index.js.map +1 -0
  155. package/dist/src/index.js +150 -0
  156. package/dist/src/index.js.map +1 -0
  157. package/dist/src/llm/anthropic.js +86 -0
  158. package/dist/src/llm/anthropic.js.map +1 -0
  159. package/dist/src/llm/index.js +9 -0
  160. package/dist/src/llm/index.js.map +1 -0
  161. package/dist/src/llm/ollama.js +113 -0
  162. package/dist/src/llm/ollama.js.map +1 -0
  163. package/dist/src/llm/router.js +145 -0
  164. package/dist/src/llm/router.js.map +1 -0
  165. package/dist/src/llm/types.js +7 -0
  166. package/dist/src/llm/types.js.map +1 -0
  167. package/dist/src/memory/index.js +5 -0
  168. package/dist/src/memory/index.js.map +1 -0
  169. package/dist/src/memory/store.js +91 -0
  170. package/dist/src/memory/store.js.map +1 -0
  171. package/dist/src/pi-config.js +80 -0
  172. package/dist/src/pi-config.js.map +1 -0
  173. package/dist/src/skills/builtin/file.js +184 -0
  174. package/dist/src/skills/builtin/file.js.map +1 -0
  175. package/dist/src/skills/builtin/shell.js +100 -0
  176. package/dist/src/skills/builtin/shell.js.map +1 -0
  177. package/dist/src/skills/builtin/subagent.js +62 -0
  178. package/dist/src/skills/builtin/subagent.js.map +1 -0
  179. package/dist/src/skills/hello.js +42 -0
  180. package/dist/src/skills/hello.js.map +1 -0
  181. package/dist/src/skills/index.js +11 -0
  182. package/dist/src/skills/index.js.map +1 -0
  183. package/dist/src/skills/loader.js +382 -0
  184. package/dist/src/skills/loader.js.map +1 -0
  185. package/dist/src/skills/types.js +8 -0
  186. package/dist/src/skills/types.js.map +1 -0
  187. package/dist/src/utils/working-dir.js +71 -0
  188. package/dist/src/utils/working-dir.js.map +1 -0
  189. package/package.json +77 -0
  190. package/skills/1password/SKILL.md +70 -0
  191. package/skills/1password/references/cli-examples.md +29 -0
  192. package/skills/1password/references/get-started.md +17 -0
  193. package/skills/apple-notes/SKILL.md +77 -0
  194. package/skills/apple-reminders/SKILL.md +118 -0
  195. package/skills/bear-notes/SKILL.md +107 -0
  196. package/skills/blogwatcher/SKILL.md +69 -0
  197. package/skills/blucli/SKILL.md +47 -0
  198. package/skills/bluebubbles/SKILL.md +131 -0
  199. package/skills/camsnap/SKILL.md +45 -0
  200. package/skills/canvas/SKILL.md +198 -0
  201. package/skills/clawhub/SKILL.md +77 -0
  202. package/skills/coding-agent/SKILL.md +284 -0
  203. package/skills/discord/SKILL.md +197 -0
  204. package/skills/eightctl/SKILL.md +50 -0
  205. package/skills/food-order/SKILL.md +48 -0
  206. package/skills/gemini/SKILL.md +43 -0
  207. package/skills/gh-issues/SKILL.md +865 -0
  208. package/skills/gifgrep/SKILL.md +79 -0
  209. package/skills/github/SKILL.md +163 -0
  210. package/skills/gog/SKILL.md +116 -0
  211. package/skills/goplaces/SKILL.md +52 -0
  212. package/skills/healthcheck/SKILL.md +245 -0
  213. package/skills/himalaya/SKILL.md +257 -0
  214. package/skills/himalaya/references/configuration.md +184 -0
  215. package/skills/himalaya/references/message-composition.md +199 -0
  216. package/skills/imsg/SKILL.md +122 -0
  217. package/skills/mcporter/SKILL.md +61 -0
  218. package/skills/model-usage/SKILL.md +69 -0
  219. package/skills/model-usage/references/codexbar-cli.md +33 -0
  220. package/skills/model-usage/scripts/model_usage.py +310 -0
  221. package/skills/nano-banana-pro/SKILL.md +58 -0
  222. package/skills/nano-banana-pro/scripts/generate_image.py +184 -0
  223. package/skills/nano-pdf/SKILL.md +38 -0
  224. package/skills/notion/SKILL.md +172 -0
  225. package/skills/obsidian/SKILL.md +81 -0
  226. package/skills/openai-image-gen/SKILL.md +89 -0
  227. package/skills/openai-image-gen/scripts/gen.py +240 -0
  228. package/skills/openai-whisper/SKILL.md +38 -0
  229. package/skills/openai-whisper-api/SKILL.md +52 -0
  230. package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
  231. package/skills/openhue/SKILL.md +112 -0
  232. package/skills/oracle/SKILL.md +125 -0
  233. package/skills/ordercli/SKILL.md +78 -0
  234. package/skills/peekaboo/SKILL.md +190 -0
  235. package/skills/sag/SKILL.md +87 -0
  236. package/skills/session-logs/SKILL.md +115 -0
  237. package/skills/sherpa-onnx-tts/SKILL.md +103 -0
  238. package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
  239. package/skills/skill-creator/SKILL.md +370 -0
  240. package/skills/skill-creator/license.txt +202 -0
  241. package/skills/skill-creator/scripts/init_skill.py +378 -0
  242. package/skills/skill-creator/scripts/package_skill.py +111 -0
  243. package/skills/skill-creator/scripts/quick_validate.py +101 -0
  244. package/skills/slack/SKILL.md +144 -0
  245. package/skills/songsee/SKILL.md +49 -0
  246. package/skills/sonoscli/SKILL.md +46 -0
  247. package/skills/spotify-player/SKILL.md +64 -0
  248. package/skills/summarize/SKILL.md +87 -0
  249. package/skills/things-mac/SKILL.md +86 -0
  250. package/skills/tmux/SKILL.md +153 -0
  251. package/skills/tmux/scripts/find-sessions.sh +112 -0
  252. package/skills/tmux/scripts/wait-for-text.sh +83 -0
  253. package/skills/trello/SKILL.md +95 -0
  254. package/skills/video-frames/SKILL.md +46 -0
  255. package/skills/video-frames/scripts/frame.sh +81 -0
  256. package/skills/voice-call/SKILL.md +45 -0
  257. package/skills/wacli/SKILL.md +72 -0
  258. 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