@poolzin/pool-bot 2026.3.16 → 2026.3.18

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 (88) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/agents/tools/web-fetch.js +1 -1
  3. package/dist/build-info.json +3 -3
  4. package/dist/commands/skills-openclaw.command.js +123 -0
  5. package/dist/config/paths.js +7 -0
  6. package/dist/infra/net/fetch-guard.js +191 -146
  7. package/dist/media/fetch.js +83 -112
  8. package/dist/media/inbound-path-policy.js +90 -97
  9. package/dist/media/read-response-with-limit.js +49 -26
  10. package/dist/media-understanding/attachments.js +1 -1
  11. package/dist/plugin-sdk/audio.js +7 -0
  12. package/dist/plugin-sdk/bluebubbles.js +7 -0
  13. package/dist/plugin-sdk/browser.js +7 -0
  14. package/dist/plugin-sdk/canvas.js +7 -0
  15. package/dist/plugin-sdk/cron.js +7 -0
  16. package/dist/plugin-sdk/discord-actions.js +6 -0
  17. package/dist/plugin-sdk/discord.js +7 -0
  18. package/dist/plugin-sdk/image.js +7 -0
  19. package/dist/plugin-sdk/imessage.js +6 -0
  20. package/dist/plugin-sdk/keyed-async-queue.js +35 -0
  21. package/dist/plugin-sdk/media.js +8 -0
  22. package/dist/plugin-sdk/memory.js +7 -0
  23. package/dist/plugin-sdk/pdf.js +7 -0
  24. package/dist/plugin-sdk/sessions.js +7 -0
  25. package/dist/plugin-sdk/signal.js +6 -0
  26. package/dist/plugin-sdk/slack-actions.js +7 -0
  27. package/dist/plugin-sdk/slack.js +7 -0
  28. package/dist/plugin-sdk/telegram-actions.js +6 -0
  29. package/dist/plugin-sdk/telegram.js +6 -0
  30. package/dist/plugin-sdk/test-utils.js +110 -0
  31. package/dist/plugin-sdk/tts.js +7 -0
  32. package/dist/plugin-sdk/whatsapp.js +6 -0
  33. package/dist/providers/github-copilot-auth.js +53 -76
  34. package/dist/providers/github-copilot-models.js +63 -35
  35. package/dist/providers/github-copilot-token.js +46 -89
  36. package/dist/security/audit-findings.js +165 -0
  37. package/dist/security/audit.js +141 -572
  38. package/dist/skills/openclaw-skill-loader.js +191 -0
  39. package/dist/slack/monitor/media.js +2 -1
  40. package/docs/branding-evaluation-2026-03-12.md +285 -0
  41. package/docs/improvements/OPENCLAW-IMPLEMENTATION.md +45 -0
  42. package/docs/skills/openclaw-integration.md +295 -0
  43. package/docs/testing/TEST-PLAN-2026-03-13.md +338 -0
  44. package/docs/version-2026.3.16-evaluation.md +190 -0
  45. package/extensions/acpx/package.json +19 -0
  46. package/extensions/acpx/poolbot.plugin.json +9 -0
  47. package/extensions/acpx/src/index.ts +34 -0
  48. package/extensions/bluebubbles/src/runtime.ts +1 -0
  49. package/extensions/diffs/package.json +15 -0
  50. package/extensions/diffs/poolbot.plugin.json +10 -0
  51. package/extensions/diffs/src/index.ts +106 -0
  52. package/extensions/discord/src/runtime.ts +1 -0
  53. package/extensions/feishu/src/runtime.ts +1 -0
  54. package/extensions/github-copilot/package.json +28 -0
  55. package/extensions/github-copilot/poolbot.plugin.json +29 -0
  56. package/extensions/github-copilot/src/index.ts +126 -0
  57. package/extensions/github-copilot/tsconfig.json +10 -0
  58. package/extensions/googlechat/src/runtime.ts +1 -0
  59. package/extensions/imessage/src/runtime.ts +1 -0
  60. package/extensions/irc/src/runtime.ts +1 -0
  61. package/extensions/line/src/runtime.ts +1 -0
  62. package/extensions/matrix/src/runtime.ts +1 -0
  63. package/extensions/mattermost/src/mattermost/monitor-helpers.ts +10 -1
  64. package/extensions/mattermost/src/runtime.ts +6 -3
  65. package/extensions/msteams/src/runtime.ts +1 -0
  66. package/extensions/nextcloud-talk/src/runtime.ts +1 -0
  67. package/extensions/nostr/src/runtime.ts +5 -2
  68. package/extensions/ollama/package.json +20 -0
  69. package/extensions/ollama/poolbot.plugin.json +14 -0
  70. package/extensions/ollama/src/index.ts +95 -0
  71. package/extensions/sglang/package.json +18 -0
  72. package/extensions/sglang/poolbot.plugin.json +13 -0
  73. package/extensions/sglang/src/index.ts +62 -0
  74. package/extensions/signal/src/runtime.ts +1 -0
  75. package/extensions/slack/src/runtime.ts +1 -0
  76. package/extensions/telegram/src/runtime.ts +1 -0
  77. package/extensions/test-utils/package.json +17 -0
  78. package/extensions/test-utils/poolbot.plugin.json +16 -0
  79. package/extensions/test-utils/src/index.ts +220 -0
  80. package/extensions/tlon/src/runtime.ts +1 -0
  81. package/extensions/twitch/src/runtime.ts +1 -0
  82. package/extensions/vllm/package.json +19 -0
  83. package/extensions/vllm/poolbot.plugin.json +13 -0
  84. package/extensions/vllm/src/index.ts +90 -0
  85. package/extensions/whatsapp/src/runtime.ts +1 -0
  86. package/extensions/zalo/src/runtime.ts +1 -0
  87. package/extensions/zalouser/src/runtime.ts +1 -0
  88. package/package.json +77 -3
@@ -1,14 +1,17 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
- let runtime: PluginRuntime | null = null;
3
+ // MattermostPluginRuntime wrapper interface to avoid lint resolution issues
4
+ interface MattermostPluginRuntime extends PluginRuntime {}
5
+
6
+ let runtime: MattermostPluginRuntime | null = null;
4
7
 
5
8
  export function setMattermostRuntime(next: PluginRuntime) {
6
- runtime = next;
9
+ runtime = next as MattermostPluginRuntime;
7
10
  }
8
11
 
9
12
  export function getMattermostRuntime(): PluginRuntime {
10
13
  if (!runtime) {
11
14
  throw new Error("Mattermost runtime not initialized");
12
15
  }
13
- return runtime;
16
+ return runtime as PluginRuntime;
14
17
  }
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setMSTeamsRuntime(next: PluginRuntime) {
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setNextcloudTalkRuntime(next: PluginRuntime) {
@@ -1,6 +1,9 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
- let runtime: PluginRuntime | null = null;
3
+ // NostrPluginRuntime wrapper interface to avoid lint resolution issues
4
+ interface NostrPluginRuntime extends PluginRuntime {}
5
+
6
+ let runtime: NostrPluginRuntime | null = null;
4
7
 
5
8
  export function setNostrRuntime(next: PluginRuntime): void {
6
9
  runtime = next;
@@ -10,5 +13,5 @@ export function getNostrRuntime(): PluginRuntime {
10
13
  if (!runtime) {
11
14
  throw new Error("Nostr runtime not initialized");
12
15
  }
13
- return runtime;
16
+ return runtime as PluginRuntime;
14
17
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@poolbot/ollama",
3
+ "version": "2026.3.17",
4
+ "description": "Ollama provider for Pool Bot - local LLM inference",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "dependencies": {
8
+ "poolbot": "workspace:*",
9
+ "ollama": "^0.6.3"
10
+ },
11
+ "poolbot": {
12
+ "extensions": ["./src/index.ts"],
13
+ "provider": {
14
+ "id": "ollama",
15
+ "name": "Ollama",
16
+ "description": "Local LLM inference with Ollama",
17
+ "models": ["llama3", "mistral", "codellama", "phi", "gemma"]
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "ollama",
3
+ "name": "Ollama Provider",
4
+ "version": "2026.3.17",
5
+ "description": "Local LLM inference with Ollama",
6
+ "main": "dist/index.js",
7
+ "capabilities": ["provider", "local-llm"],
8
+ "commands": ["ollama.status", "ollama.models", "ollama.pull"],
9
+ "config": {
10
+ "host": "http://127.0.0.1",
11
+ "port": 11434,
12
+ "defaultModel": "llama3"
13
+ }
14
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pool Bot Ollama Provider Extension
3
+ *
4
+ * Provides local LLM inference via Ollama
5
+ */
6
+
7
+ export default async function createPlugin(ctx: any): Promise<any> {
8
+ const config = ctx.config as {
9
+ host?: string;
10
+ port?: number;
11
+ defaultModel?: string;
12
+ };
13
+
14
+ const host = config.host ?? "http://127.0.0.1";
15
+ const port = config.port ?? 11434;
16
+ const baseUrl = `${host}:${port}`;
17
+
18
+ ctx.logger.info(`Ollama Provider initialized (${baseUrl})`);
19
+
20
+ ctx.cli
21
+ .command("ollama.status")
22
+ .description("Check Ollama server status")
23
+ .action(async () => {
24
+ try {
25
+ const res = await fetch(`${baseUrl}/api/tags`);
26
+ if (res.ok) {
27
+ const data = await res.json() as { models: Array<{ name: string }> };
28
+ console.log("✅ Ollama Status");
29
+ console.log(` URL: ${baseUrl}`);
30
+ console.log(` Models: ${data.models.length}`);
31
+ data.models.slice(0, 5).forEach((m) => console.log(` - ${m.name}`));
32
+ } else {
33
+ console.log("❌ Ollama not running");
34
+ }
35
+ } catch (error) {
36
+ console.log("❌ Ollama connection failed");
37
+ console.log(` Error: ${error instanceof Error ? error.message : error}`);
38
+ }
39
+ });
40
+
41
+ ctx.cli
42
+ .command("ollama.models")
43
+ .description("List available Ollama models")
44
+ .action(async () => {
45
+ try {
46
+ const res = await fetch(`${baseUrl}/api/tags`);
47
+ if (!res.ok) throw new Error("Ollama not running");
48
+
49
+ const data = await res.json() as { models: Array<{ name: string; size: number }> };
50
+ console.log("📦 Ollama Models\n");
51
+
52
+ for (const model of data.models) {
53
+ const sizeGB = (model.size / 1e9).toFixed(2);
54
+ console.log(`🤖 ${model.name}`);
55
+ console.log(` Size: ${sizeGB} GB`);
56
+ }
57
+ } catch (error) {
58
+ console.error("Failed to list models:", error instanceof Error ? error.message : error);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ ctx.cli
64
+ .command("ollama.pull")
65
+ .description("Pull an Ollama model")
66
+ .argument("<model>", "Model name (e.g., llama3, mistral)")
67
+ .action(async (modelName: string) => {
68
+ console.log(`📥 Pulling ${modelName}...`);
69
+
70
+ try {
71
+ const res = await fetch(`${baseUrl}/api/pull`, {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify({ name: modelName }),
75
+ });
76
+
77
+ if (res.ok) {
78
+ console.log(`✅ Model ${modelName} pulled successfully`);
79
+ } else {
80
+ const error = await res.text();
81
+ console.error(`❌ Failed to pull: ${error}`);
82
+ process.exit(1);
83
+ }
84
+ } catch (error) {
85
+ console.error("Failed to pull model:", error instanceof Error ? error.message : error);
86
+ process.exit(1);
87
+ }
88
+ });
89
+
90
+ return {
91
+ name: "ollama",
92
+ version: "2026.3.17",
93
+ commands: ["ollama.status", "ollama.models", "ollama.pull"],
94
+ };
95
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@poolbot/sglang",
3
+ "version": "2026.3.17",
4
+ "description": "SGLang provider for Pool Bot - optimized LLM inference",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "dependencies": {
8
+ "poolbot": "workspace:*"
9
+ },
10
+ "poolbot": {
11
+ "extensions": ["./src/index.ts"],
12
+ "provider": {
13
+ "id": "sglang",
14
+ "name": "SGLang",
15
+ "description": "SGLang runtime for optimized LLM inference"
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "sglang",
3
+ "name": "SGLang Provider",
4
+ "version": "2026.3.17",
5
+ "description": "SGLang runtime for optimized LLM inference",
6
+ "main": "dist/index.js",
7
+ "capabilities": ["provider", "optimized-inference"],
8
+ "commands": ["sglang.status", "sglang.models"],
9
+ "config": {
10
+ "host": "http://127.0.0.1",
11
+ "port": 30000
12
+ }
13
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Pool Bot SGLang Provider Extension
3
+ *
4
+ * Provides SGLang runtime for optimized LLM inference
5
+ */
6
+
7
+ export default async function createPlugin(ctx: any): Promise<any> {
8
+ const config = ctx.config as {
9
+ host?: string;
10
+ port?: number;
11
+ };
12
+
13
+ const host = config.host ?? "http://127.0.0.1";
14
+ const port = config.port ?? 30000;
15
+ const baseUrl = `${host}:${port}`;
16
+
17
+ ctx.logger.info(`SGLang Provider initialized (${baseUrl})`);
18
+
19
+ ctx.cli
20
+ .command("sglang.status")
21
+ .description("Check SGLang server status")
22
+ .action(async () => {
23
+ try {
24
+ const res = await fetch(`${baseUrl}/health`);
25
+ if (res.ok) {
26
+ console.log("✅ SGLang Status");
27
+ console.log(` URL: ${baseUrl}`);
28
+ console.log(` Status: Running`);
29
+ } else {
30
+ console.log("❌ SGLang not running");
31
+ }
32
+ } catch (error) {
33
+ console.log("❌ SGLang connection failed");
34
+ console.log(` Error: ${error instanceof Error ? error.message : error}`);
35
+ }
36
+ });
37
+
38
+ ctx.cli
39
+ .command("sglang.models")
40
+ .description("List available SGLang models")
41
+ .action(async () => {
42
+ try {
43
+ const res = await fetch(`${baseUrl}/v1/models`);
44
+ if (!res.ok) throw new Error("SGLang not running");
45
+
46
+ const data = await res.json() as { data: Array<{ id: string }> };
47
+ console.log("📦 SGLang Models\n");
48
+
49
+ for (const model of data.data) {
50
+ console.log(`🤖 ${model.id}`);
51
+ }
52
+ } catch (error) {
53
+ console.error("Failed to list models:", error instanceof Error ? error.message : error);
54
+ }
55
+ });
56
+
57
+ return {
58
+ name: "sglang",
59
+ version: "2026.3.17",
60
+ commands: ["sglang.status", "sglang.models"],
61
+ };
62
+ }
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setSignalRuntime(next: PluginRuntime) {
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setSlackRuntime(next: PluginRuntime) {
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setTelegramRuntime(next: PluginRuntime) {
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@poolbot/test-utils",
3
+ "version": "2026.3.17",
4
+ "description": "Pool Bot test utilities and helpers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "dependencies": {
8
+ "poolbot": "workspace:*"
9
+ },
10
+ "devDependencies": {
11
+ "vitest": "^4.0.18"
12
+ },
13
+ "poolbot": {
14
+ "extensions": ["./src/index.ts"],
15
+ "testUtils": true
16
+ }
17
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "test-utils",
3
+ "name": "Test Utilities",
4
+ "version": "2026.3.17",
5
+ "description": "Testing utilities and helpers for Pool Bot development",
6
+ "main": "dist/index.js",
7
+ "capabilities": ["testing", "mocking"],
8
+ "commands": ["test.mock-config"],
9
+ "exports": {
10
+ "createMockConfig": "./dist/index.js",
11
+ "createMockRuntime": "./dist/index.js",
12
+ "waitForCondition": "./dist/index.js",
13
+ "createTempFile": "./dist/index.js",
14
+ "cleanupTempFiles": "./dist/index.js"
15
+ }
16
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Pool Bot Test Utilities
3
+ *
4
+ * Provides testing utilities and helpers for Pool Bot development
5
+ */
6
+
7
+ import type { PoolBotConfig } from "poolbot/config/config.js";
8
+ import type { PluginRuntime } from "poolbot/plugins/runtime/types.js";
9
+
10
+ /**
11
+ * Create a mock Pool Bot config for testing
12
+ */
13
+ export function createMockConfig(overrides?: Partial<PoolBotConfig>): PoolBotConfig {
14
+ return {
15
+ models: {
16
+ primary: "gpt-4o",
17
+ },
18
+ agents: {
19
+ list: [],
20
+ },
21
+ channels: {},
22
+ gateway: {
23
+ port: 18789,
24
+ host: "127.0.0.1",
25
+ },
26
+ ...overrides,
27
+ } as PoolBotConfig;
28
+ }
29
+
30
+ /**
31
+ * Create a mock Plugin Runtime for testing
32
+ */
33
+ export function createMockRuntime(): PluginRuntime {
34
+ return {
35
+ version: "test",
36
+ config: {
37
+ loadConfig: async () => createMockConfig(),
38
+ writeConfigFile: async () => {},
39
+ },
40
+ system: {
41
+ enqueueSystemEvent: async () => {},
42
+ runCommandWithTimeout: async () => ({ stdout: "", stderr: "", exitCode: 0 }),
43
+ formatNativeDependencyHint: () => "",
44
+ },
45
+ media: {
46
+ loadWebMedia: async () => ({ buffer: Buffer.from([]) }),
47
+ detectMime: async () => "application/octet-stream",
48
+ mediaKindFromMime: () => "unknown" as any,
49
+ isVoiceCompatibleAudio: () => false,
50
+ getImageMetadata: async () => ({ width: 0, height: 0 }),
51
+ resizeToJpeg: async () => Buffer.from([]),
52
+ },
53
+ tts: {
54
+ textToSpeechTelephony: async () => ({ audioPath: "" }),
55
+ },
56
+ tools: {
57
+ createMemoryGetTool: () => ({} as any),
58
+ createMemorySearchTool: () => ({} as any),
59
+ registerMemoryCli: () => {},
60
+ },
61
+ channel: {
62
+ text: {
63
+ chunkByNewline: () => [],
64
+ chunkMarkdownText: () => [],
65
+ chunkMarkdownTextWithMode: () => [],
66
+ chunkText: () => [],
67
+ chunkTextWithMode: () => [],
68
+ resolveChunkMode: () => "markdown" as const,
69
+ resolveTextChunkLimit: () => 4000,
70
+ hasControlCommand: () => false,
71
+ resolveMarkdownTableMode: () => "convert" as const,
72
+ convertMarkdownTables: () => "",
73
+ },
74
+ reply: {
75
+ dispatchReplyWithBufferedBlockDispatcher: async () => {},
76
+ createReplyDispatcherWithTyping: () => ({} as any),
77
+ resolveEffectiveMessagesConfig: () => ({} as any),
78
+ resolveHumanDelayConfig: () => ({} as any),
79
+ dispatchReplyFromConfig: async () => {},
80
+ finalizeInboundContext: async () => ({} as any),
81
+ formatAgentEnvelope: () => "",
82
+ formatInboundEnvelope: () => "",
83
+ resolveEnvelopeFormatOptions: () => ({} as any),
84
+ },
85
+ routing: {
86
+ resolveAgentRoute: async () => null,
87
+ },
88
+ pairing: {
89
+ buildPairingReply: () => "",
90
+ readAllowFromStore: async () => [],
91
+ upsertPairingRequest: async () => {},
92
+ },
93
+ identity: {
94
+ resolveEffectiveMessagesConfig: () => ({} as any),
95
+ resolveHumanDelayConfig: () => ({} as any),
96
+ },
97
+ lifecycle: {
98
+ enqueueSystemEvent: async () => {},
99
+ },
100
+ gateway: {
101
+ call: async () => ({} as any),
102
+ },
103
+ security: {
104
+ checkDmPolicy: async () => "allow" as const,
105
+ },
106
+ mentions: {
107
+ buildMentionRegexes: () => [],
108
+ matchesMentionPatterns: () => false,
109
+ matchesMentionWithExplicit: () => false,
110
+ },
111
+ reactions: {
112
+ shouldAckReaction: () => false,
113
+ removeAckReactionAfterReply: async () => {},
114
+ },
115
+ group: {
116
+ resolveChannelGroupPolicy: () => "mention" as const,
117
+ resolveChannelGroupRequireMention: () => true,
118
+ },
119
+ debounce: {
120
+ createInboundDebouncer: () => ({} as any),
121
+ resolveInboundDebounceMs: () => 0,
122
+ },
123
+ gating: {
124
+ resolveCommandAuthorizedFromAuthorizers: async () => false,
125
+ },
126
+ },
127
+ logging: {
128
+ debug: () => {},
129
+ info: () => {},
130
+ warn: () => {},
131
+ error: () => {},
132
+ },
133
+ state: {
134
+ getStateDir: () => "/tmp/poolbot-test",
135
+ },
136
+ logger: {
137
+ debug: () => {},
138
+ info: () => {},
139
+ warn: () => {},
140
+ error: () => {},
141
+ },
142
+ } as PluginRuntime;
143
+ }
144
+
145
+ /**
146
+ * Test helper: wait for a condition to be true
147
+ */
148
+ export async function waitForCondition(
149
+ condition: () => boolean | Promise<boolean>,
150
+ timeoutMs: number = 5000,
151
+ intervalMs: number = 100,
152
+ ): Promise<void> {
153
+ const start = Date.now();
154
+
155
+ while (Date.now() - start < timeoutMs) {
156
+ const result = await condition();
157
+ if (result) return;
158
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
159
+ }
160
+
161
+ throw new Error(`Condition not met within ${timeoutMs}ms`);
162
+ }
163
+
164
+ /**
165
+ * Test helper: create a temporary file
166
+ */
167
+ export async function createTempFile(content: string, extension: string = ".txt"): Promise<string> {
168
+ const fs = await import("node:fs/promises");
169
+ const path = await import("node:path");
170
+ const os = await import("node:os");
171
+
172
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-test-"));
173
+ const filePath = path.join(tmpDir, `test${extension}`);
174
+ await fs.writeFile(filePath, content, "utf-8");
175
+
176
+ return filePath;
177
+ }
178
+
179
+ /**
180
+ * Test helper: cleanup temporary files
181
+ */
182
+ export async function cleanupTempFiles(...paths: string[]): Promise<void> {
183
+ const fs = await import("node:fs/promises");
184
+
185
+ for (const p of paths) {
186
+ try {
187
+ await fs.rm(p, { recursive: true, force: true });
188
+ } catch {
189
+ // Ignore cleanup errors
190
+ }
191
+ }
192
+ }
193
+
194
+ // Export CLI command for test utilities
195
+ export default async function createPlugin(ctx: any): Promise<any> {
196
+ ctx.logger.info("Test Utils initialized");
197
+
198
+ ctx.cli
199
+ .command("test.mock-config")
200
+ .description("Generate a mock config for testing")
201
+ .option("-o, --output <file>", "Output file path")
202
+ .action(async (options?: { output?: string }) => {
203
+ const config = createMockConfig();
204
+ const json = JSON.stringify(config, null, 2);
205
+
206
+ if (options?.output) {
207
+ const fs = await import("node:fs/promises");
208
+ await fs.writeFile(options.output, json, "utf-8");
209
+ console.log(`✅ Mock config written to ${options.output}`);
210
+ } else {
211
+ console.log(json);
212
+ }
213
+ });
214
+
215
+ return {
216
+ name: "test-utils",
217
+ version: "2026.3.17",
218
+ commands: ["test.mock-config"],
219
+ };
220
+ }
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setTlonRuntime(next: PluginRuntime) {
@@ -1,5 +1,6 @@
1
1
  import type { PluginRuntime } from "poolbot/plugin-sdk";
2
2
 
3
+ // oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents
3
4
  let runtime: PluginRuntime | null = null;
4
5
 
5
6
  export function setTwitchRuntime(next: PluginRuntime) {
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@poolbot/vllm",
3
+ "version": "2026.3.17",
4
+ "description": "vLLM provider for Pool Bot - high-throughput LLM inference",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "dependencies": {
8
+ "poolbot": "workspace:*"
9
+ },
10
+ "poolbot": {
11
+ "extensions": ["./src/index.ts"],
12
+ "provider": {
13
+ "id": "vllm",
14
+ "name": "vLLM",
15
+ "description": "vLLM for high-throughput LLM inference",
16
+ "models": ["llama", "mistral", "qwen", "yi"]
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "vllm",
3
+ "name": "vLLM Provider",
4
+ "version": "2026.3.17",
5
+ "description": "vLLM for high-throughput LLM inference",
6
+ "main": "dist/index.js",
7
+ "capabilities": ["provider", "high-throughput"],
8
+ "commands": ["vllm.status", "vllm.models", "vllm.generate"],
9
+ "config": {
10
+ "host": "http://127.0.0.1",
11
+ "port": 8000
12
+ }
13
+ }