@poolzin/pool-bot 2026.3.22 → 2026.3.24
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/CHANGELOG.md +111 -0
- package/dist/.buildstamp +1 -1
- package/dist/acp/bindings-store.js +209 -0
- package/dist/acp/control-plane/runtime-cache.js +54 -0
- package/dist/acp/control-plane/runtime-options.js +215 -0
- package/dist/acp/control-plane/session-actor-queue.js +36 -0
- package/dist/acp/policy.js +52 -0
- package/dist/acp/runtime/errors.js +47 -0
- package/dist/acp/runtime/registry.js +86 -0
- package/dist/acp/runtime/types.js +1 -0
- package/dist/acp/translator.js +97 -0
- package/dist/agents/btw.js +280 -0
- package/dist/agents/failover-error.js +145 -47
- package/dist/agents/fast-mode.js +24 -0
- package/dist/agents/live-model-errors.js +23 -0
- package/dist/agents/model-auth-env-vars.js +44 -0
- package/dist/agents/model-auth-markers.js +69 -0
- package/dist/agents/models-config.providers.discovery.js +180 -0
- package/dist/agents/models-config.providers.static.js +480 -0
- package/dist/auto-reply/reply/typing-policy.js +15 -0
- package/dist/browser/browser-profile-manager.js +319 -0
- package/dist/browser/cdp-proxy-bypass.js +129 -0
- package/dist/browser/cdp-timeouts.js +41 -0
- package/dist/browser/chrome-extension-validator.js +406 -0
- package/dist/browser/chrome-mcp-snapshot.js +222 -0
- package/dist/browser/chrome-mcp.js +421 -0
- package/dist/browser/chrome-mcp.snapshot.js +133 -0
- package/dist/browser/errors.js +67 -0
- package/dist/browser/form-fields.js +22 -0
- package/dist/browser/output-atomic.js +44 -0
- package/dist/browser/profile-capabilities.js +47 -0
- package/dist/browser/safe-filename.js +25 -0
- package/dist/browser/snapshot-roles.js +60 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/account-snapshot-fields.js +176 -0
- package/dist/channels/draft-stream-controls.js +89 -0
- package/dist/channels/inbound-debounce-policy.js +28 -0
- package/dist/channels/typing-lifecycle.js +39 -0
- package/dist/cli/program/command-registry.js +52 -0
- package/dist/commands/agent-binding.js +123 -0
- package/dist/commands/agents.commands.bind.js +280 -0
- package/dist/commands/backup-shared.js +186 -0
- package/dist/commands/backup-verify.js +236 -0
- package/dist/commands/backup.js +166 -0
- package/dist/commands/channel-account-context.js +15 -0
- package/dist/commands/channel-account.js +190 -0
- package/dist/commands/gateway-install-token.js +117 -0
- package/dist/commands/oauth-tls-preflight.js +121 -0
- package/dist/commands/ollama-setup.js +402 -0
- package/dist/commands/security-owner-only.js +86 -0
- package/dist/commands/self-hosted-provider-setup.js +207 -0
- package/dist/commands/session-store-targets.js +12 -0
- package/dist/commands/sessions-cleanup.js +97 -0
- package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/cron/cron-filters.js +150 -0
- package/dist/cron/heartbeat-policy.js +26 -0
- package/dist/gateway/device-pairing-security.js +197 -0
- package/dist/gateway/event-deduplication.js +167 -0
- package/dist/gateway/hooks-mapping.js +46 -7
- package/dist/gateway/run-tracker.js +253 -0
- package/dist/gateway/server-methods/nodes.js +14 -0
- package/dist/gateway/websocket-preauth-security.js +188 -0
- package/dist/hooks/module-loader.js +28 -0
- package/dist/infra/agent-command-binding.js +144 -0
- package/dist/infra/backup.js +328 -0
- package/dist/infra/channel-account-context.js +173 -0
- package/dist/infra/errors.js +53 -13
- package/dist/infra/exec-approvals-security.js +217 -0
- package/dist/infra/security/command-analyzer.js +257 -0
- package/dist/infra/session-cleanup.js +143 -0
- package/dist/plugins/loader.js +16 -8
- package/dist/security/external-content.js +51 -1
- package/dist/sessions/session-costs.js +228 -0
- package/dist/shared/param-key.js +16 -0
- package/dist/shared/poll-params.js +58 -0
- package/dist/shared/polls.js +55 -0
- package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
- package/docs/FEATURES.md +523 -0
- package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
- package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
- package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
- package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
- package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
- package/docs/MIKRODASH-ANALYSIS.md +412 -0
- package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
- package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
- package/docs/PHASE-7-SUMMARY.md +144 -0
- package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
- package/docs/PROJECT-FINAL-STATUS.md +237 -0
- package/docs/README.md +116 -0
- package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
- package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
- package/docs/channels/googlechat.md +235 -206
- package/docs/channels/irc.md +332 -0
- package/docs/channels/nostr.md +255 -168
- package/docs/components/command-palette.md +166 -0
- package/docs/components/login-gate.md +219 -0
- package/docs/getting-started/installation.md +191 -0
- package/docs/getting-started/introduction.md +120 -0
- package/docs/improvements/USAGE-GUIDE.md +359 -0
- package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
- package/docs/reference/deadcode-detection.md +72 -0
- package/extensions/acpx/node_modules/.bin/acpx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +4 -4
- package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
- package/extensions/googlechat/node_modules/.bin/tsc +21 -0
- package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
- package/extensions/googlechat/node_modules/.bin/vitest +21 -0
- package/extensions/googlechat/package.json +11 -28
- package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
- package/extensions/googlechat/src/googlechat-channel.ts +120 -0
- package/extensions/googlechat/src/index.ts +14 -0
- package/extensions/irc/node_modules/.bin/tsc +21 -0
- package/extensions/irc/node_modules/.bin/tsserver +21 -0
- package/extensions/irc/node_modules/.bin/vitest +21 -0
- package/extensions/irc/package.json +16 -8
- package/extensions/irc/src/index.ts +14 -0
- package/extensions/irc/src/irc-channel.test.ts +43 -0
- package/extensions/irc/src/irc-channel.ts +191 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
- package/extensions/keyed-async-queue/package.json +20 -0
- package/extensions/keyed-async-queue/src/index.ts +14 -0
- package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
- package/extensions/keyed-async-queue/src/queue.ts +200 -0
- package/extensions/memory-core/node_modules/.bin/tsc +21 -0
- package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
- package/extensions/memory-core/node_modules/.bin/vitest +21 -0
- package/extensions/memory-core/package.json +11 -8
- package/extensions/memory-core/src/index.ts +14 -0
- package/extensions/memory-core/src/memory-manager.test.ts +124 -0
- package/extensions/memory-core/src/memory-manager.ts +186 -0
- package/extensions/nostr/node_modules/.bin/tsc +2 -2
- package/extensions/nostr/node_modules/.bin/tsserver +2 -2
- package/extensions/nostr/node_modules/.bin/vitest +21 -0
- package/extensions/nostr/package.json +15 -24
- package/extensions/nostr/src/index.ts +14 -0
- package/extensions/nostr/src/nostr-channel.test.ts +55 -0
- package/extensions/nostr/src/nostr-channel.ts +228 -0
- package/extensions/page-agent/node_modules/.bin/vitest +2 -2
- package/extensions/test-utils/node_modules/.bin/jiti +21 -0
- package/extensions/test-utils/node_modules/.bin/playwright +21 -0
- package/extensions/test-utils/node_modules/.bin/tsx +21 -0
- package/extensions/test-utils/node_modules/.bin/vite +21 -0
- package/extensions/test-utils/node_modules/.bin/vitest +21 -0
- package/extensions/test-utils/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +2 -2
- package/package.json +2 -1
- package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
- package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
- package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IRC Channel Implementation
|
|
3
|
+
* Supports connecting to IRC servers, joining channels, and sending/receiving messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import IRC from "irc-framework";
|
|
7
|
+
import type { Channel, ChannelMessage, ChannelPresence } from "../../../src/channels/types.js";
|
|
8
|
+
|
|
9
|
+
export type IRCConfig = {
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
nick: string;
|
|
13
|
+
channels: string[];
|
|
14
|
+
password?: string;
|
|
15
|
+
sasl?: {
|
|
16
|
+
account: string;
|
|
17
|
+
password: string;
|
|
18
|
+
};
|
|
19
|
+
tls?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class IRCChannel implements Channel {
|
|
23
|
+
public readonly id = "irc";
|
|
24
|
+
public readonly name = "IRC";
|
|
25
|
+
|
|
26
|
+
private config: IRCConfig;
|
|
27
|
+
private client: IRC.Client;
|
|
28
|
+
private connected = false;
|
|
29
|
+
private messageCallback?: (message: ChannelMessage) => void;
|
|
30
|
+
|
|
31
|
+
constructor(config: IRCConfig) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.client = new IRC.Client({
|
|
34
|
+
host: config.host,
|
|
35
|
+
port: config.port,
|
|
36
|
+
nick: config.nick,
|
|
37
|
+
password: config.password,
|
|
38
|
+
sasl: config.sasl ? {
|
|
39
|
+
account: config.sasl.account,
|
|
40
|
+
password: config.sasl.password,
|
|
41
|
+
} : undefined,
|
|
42
|
+
tls: config.tls,
|
|
43
|
+
auto_reconnect: true,
|
|
44
|
+
auto_reconnect_wait: 10,
|
|
45
|
+
max_reconnect_attempts: 5,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.setupEventHandlers();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private setupEventHandlers(): void {
|
|
52
|
+
this.client.on("connect", () => {
|
|
53
|
+
this.connected = true;
|
|
54
|
+
console.log("[irc] Connected to server");
|
|
55
|
+
|
|
56
|
+
// Join configured channels
|
|
57
|
+
for (const channel of this.config.channels) {
|
|
58
|
+
this.client.join(channel);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.client.on("disconnect", () => {
|
|
63
|
+
this.connected = false;
|
|
64
|
+
console.log("[irc] Disconnected from server");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.client.on("message", (context) => {
|
|
68
|
+
if (this.messageCallback && context.type === "message") {
|
|
69
|
+
this.messageCallback({
|
|
70
|
+
id: `${context.target}-${Date.now()}`,
|
|
71
|
+
text: context.message,
|
|
72
|
+
from: context.nick,
|
|
73
|
+
to: context.target,
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
threadId: undefined,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.client.on("notice", (context) => {
|
|
81
|
+
console.log("[irc] Notice:", context);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.client.on("error", (error) => {
|
|
85
|
+
console.error("[irc] Error:", error);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async connect(): Promise<void> {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
try {
|
|
92
|
+
this.client.connect();
|
|
93
|
+
|
|
94
|
+
const timeout = setTimeout(() => {
|
|
95
|
+
reject(new Error("IRC connection timeout"));
|
|
96
|
+
}, 30000);
|
|
97
|
+
|
|
98
|
+
this.client.once("connect", () => {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
resolve();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.client.once("error", (error) => {
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
reject(new Error(`IRC connection error: ${error.error}`));
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
reject(error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async disconnect(): Promise<void> {
|
|
114
|
+
this.client.quit("Disconnecting");
|
|
115
|
+
this.connected = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async sendMessage(message: ChannelMessage): Promise<void> {
|
|
119
|
+
if (!this.connected) {
|
|
120
|
+
throw new Error("IRC channel not connected");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const target = message.to || this.config.channels[0];
|
|
124
|
+
if (!target) {
|
|
125
|
+
throw new Error("No target channel specified");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.client.say(target, message.text);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async receiveMessages(callback: (message: ChannelMessage) => void): Promise<void> {
|
|
132
|
+
this.messageCallback = callback;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getPresences(): Promise<ChannelPresence[]> {
|
|
136
|
+
const presences: ChannelPresence[] = [];
|
|
137
|
+
|
|
138
|
+
for (const channel of this.config.channels) {
|
|
139
|
+
try {
|
|
140
|
+
const users = await this.getChannelUsers(channel);
|
|
141
|
+
for (const user of users) {
|
|
142
|
+
presences.push({
|
|
143
|
+
channelId: channel,
|
|
144
|
+
userId: user,
|
|
145
|
+
status: "online",
|
|
146
|
+
lastSeen: Date.now(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
// Ignore errors getting users
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return presences;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private getChannelUsers(channel: string): Promise<string[]> {
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
this.client.who(channel, (response) => {
|
|
160
|
+
if (response.users) {
|
|
161
|
+
resolve(response.users.map((u: any) => u.nick));
|
|
162
|
+
} else {
|
|
163
|
+
resolve([]);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getStatus(): "connected" | "disconnected" | "error" {
|
|
170
|
+
return this.connected ? "connected" : "disconnected";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async joinChannel(channel: string): Promise<void> {
|
|
174
|
+
this.client.join(channel);
|
|
175
|
+
if (!this.config.channels.includes(channel)) {
|
|
176
|
+
this.config.channels.push(channel);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async leaveChannel(channel: string): Promise<void> {
|
|
181
|
+
this.client.part(channel);
|
|
182
|
+
this.config.channels = this.config.channels.filter((c) => c !== channel);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create IRC channel from config
|
|
188
|
+
*/
|
|
189
|
+
export function createIRCChannel(config: IRCConfig): IRCChannel {
|
|
190
|
+
return new IRCChannel(config);
|
|
191
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.30_@vitest+browser@4.0.18_vite@7.3.1_@types+node@25.2.3__a102e25684722dd7e16c8e55a5848dc8/node_modules/vitest/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.30_@vitest+browser@4.0.18_vite@7.3.1_@types+node@25.2.3__a102e25684722dd7e16c8e55a5848dc8/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.30_@vitest+browser@4.0.18_vite@7.3.1_@types+node@25.2.3__a102e25684722dd7e16c8e55a5848dc8/node_modules/vitest/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.30_@vitest+browser@4.0.18_vite@7.3.1_@types+node@25.2.3__a102e25684722dd7e16c8e55a5848dc8/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../vitest/vitest.mjs" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@poolbot/keyed-async-queue",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Keyed async queue for concurrent task management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/node": "^20.0.0",
|
|
14
|
+
"typescript": "^5.0.0",
|
|
15
|
+
"vitest": "^1.0.0"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"poolbot": "workspace:*"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyed Async Queue Plugin Entry Point
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { KeyedAsyncQueue, createKeyedAsyncQueue, type QueueTask, type QueueStats } from "./queue.js";
|
|
6
|
+
|
|
7
|
+
// Export plugin metadata
|
|
8
|
+
export const pluginInfo = {
|
|
9
|
+
id: "keyed-async-queue",
|
|
10
|
+
name: "Keyed Async Queue",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
description: "Key-based async queue for concurrent task management",
|
|
13
|
+
author: "Pool Bot Team",
|
|
14
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { KeyedAsyncQueue, createKeyedAsyncQueue } from "./queue";
|
|
3
|
+
|
|
4
|
+
describe("KeyedAsyncQueue", () => {
|
|
5
|
+
let queue: KeyedAsyncQueue;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
queue = createKeyedAsyncQueue();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("enqueues and processes tasks", async () => {
|
|
12
|
+
const result = await queue.enqueue("key1", async () => "result");
|
|
13
|
+
expect(result).toBe("result");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("processes tasks in order for same key", async () => {
|
|
17
|
+
const results: string[] = [];
|
|
18
|
+
|
|
19
|
+
const task1 = queue.enqueue("key1", async () => {
|
|
20
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
21
|
+
results.push("task1");
|
|
22
|
+
return "result1";
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const task2 = queue.enqueue("key1", async () => {
|
|
26
|
+
results.push("task2");
|
|
27
|
+
return "result2";
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await Promise.all([task1, task2]);
|
|
31
|
+
expect(results).toEqual(["task1", "task2"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("processes tasks in parallel for different keys", async () => {
|
|
35
|
+
const results: string[] = [];
|
|
36
|
+
|
|
37
|
+
const task1 = queue.enqueue("key1", async () => {
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
39
|
+
results.push("key1");
|
|
40
|
+
return "result1";
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const task2 = queue.enqueue("key2", async () => {
|
|
44
|
+
results.push("key2");
|
|
45
|
+
return "result2";
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await Promise.all([task1, task2]);
|
|
49
|
+
expect(results).toEqual(["key2", "key1"]); // key2 finishes first
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("respects priority", async () => {
|
|
53
|
+
const results: string[] = [];
|
|
54
|
+
|
|
55
|
+
queue.enqueue("key1", async () => {
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
57
|
+
results.push("low");
|
|
58
|
+
return "low";
|
|
59
|
+
}, 0);
|
|
60
|
+
|
|
61
|
+
queue.enqueue("key1", async () => {
|
|
62
|
+
results.push("high");
|
|
63
|
+
return "high";
|
|
64
|
+
}, 10);
|
|
65
|
+
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
67
|
+
expect(results).toEqual(["high", "low"]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("gets queue length", async () => {
|
|
71
|
+
queue.enqueue("key1", async () => {
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
queue.enqueue("key1", async () => {});
|
|
76
|
+
queue.enqueue("key1", async () => {});
|
|
77
|
+
|
|
78
|
+
expect(queue.getQueueLength("key1")).toBeGreaterThanOrEqual(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("clears queue for key", () => {
|
|
82
|
+
queue.enqueue("key1", async () => {});
|
|
83
|
+
queue.enqueue("key1", async () => {});
|
|
84
|
+
queue.enqueue("key2", async () => {});
|
|
85
|
+
|
|
86
|
+
const cleared = queue.clearQueue("key1");
|
|
87
|
+
expect(cleared).toBeGreaterThanOrEqual(1);
|
|
88
|
+
expect(queue.getQueueLength("key2")).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("clears all queues", () => {
|
|
92
|
+
queue.enqueue("key1", async () => {});
|
|
93
|
+
queue.enqueue("key2", async () => {});
|
|
94
|
+
|
|
95
|
+
const cleared = queue.clearAll();
|
|
96
|
+
expect(cleared).toBeGreaterThanOrEqual(2);
|
|
97
|
+
expect(queue.getTotalQueueLength()).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("gets statistics", async () => {
|
|
101
|
+
queue.enqueue("key1", async () => {
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
103
|
+
});
|
|
104
|
+
queue.enqueue("key1", async () => {});
|
|
105
|
+
queue.enqueue("key2", async () => {});
|
|
106
|
+
|
|
107
|
+
const stats = queue.getStats();
|
|
108
|
+
expect(stats.total).toBeGreaterThanOrEqual(2);
|
|
109
|
+
expect(stats.byKey.key1).toBeGreaterThanOrEqual(1);
|
|
110
|
+
expect(stats.byKey.key2).toBeGreaterThanOrEqual(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("handles task errors", async () => {
|
|
114
|
+
const errorTask = queue.enqueue("key1", async () => {
|
|
115
|
+
throw new Error("Task failed");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await expect(errorTask).rejects.toThrow("Task failed");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("continues processing after error", async () => {
|
|
122
|
+
const results: string[] = [];
|
|
123
|
+
|
|
124
|
+
queue.enqueue("key1", async () => {
|
|
125
|
+
throw new Error("First failed");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await queue.enqueue("key1", async () => {
|
|
129
|
+
results.push("second");
|
|
130
|
+
return "success";
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(results).toEqual(["second"]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyed Async Queue
|
|
3
|
+
* Manages concurrent tasks with key-based ordering
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type QueueTask<T = unknown> = {
|
|
7
|
+
id: string;
|
|
8
|
+
key: string;
|
|
9
|
+
fn: () => Promise<T>;
|
|
10
|
+
priority?: number;
|
|
11
|
+
createdAt: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type QueueStats = {
|
|
15
|
+
total: number;
|
|
16
|
+
byKey: Record<string, number>;
|
|
17
|
+
processing: number;
|
|
18
|
+
queued: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class KeyedAsyncQueue {
|
|
22
|
+
private queues: Map<string, QueueTask[]> = new Map();
|
|
23
|
+
private processing: Map<string, boolean> = new Map();
|
|
24
|
+
private concurrency: number;
|
|
25
|
+
|
|
26
|
+
constructor(concurrency = 5) {
|
|
27
|
+
this.concurrency = concurrency;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Add a task to the queue
|
|
32
|
+
*/
|
|
33
|
+
async enqueue<T>(key: string, fn: () => Promise<T>, priority = 0): Promise<T> {
|
|
34
|
+
const task: QueueTask<T> = {
|
|
35
|
+
id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
36
|
+
key,
|
|
37
|
+
fn,
|
|
38
|
+
priority,
|
|
39
|
+
createdAt: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Get or create queue for this key
|
|
43
|
+
let queue = this.queues.get(key);
|
|
44
|
+
if (!queue) {
|
|
45
|
+
queue = [];
|
|
46
|
+
this.queues.set(key, queue);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Insert by priority (higher priority first)
|
|
50
|
+
const insertIndex = queue.findIndex((t) => t.priority < priority);
|
|
51
|
+
if (insertIndex === -1) {
|
|
52
|
+
queue.push(task);
|
|
53
|
+
} else {
|
|
54
|
+
queue.splice(insertIndex, 0, task);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Process queue
|
|
58
|
+
return this.processQueue(key);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Process queue for a specific key
|
|
63
|
+
*/
|
|
64
|
+
private async processQueue<T>(key: string): Promise<T> {
|
|
65
|
+
// Check if already processing for this key
|
|
66
|
+
if (this.processing.get(key)) {
|
|
67
|
+
// Wait for current processing to finish
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const checkQueue = async () => {
|
|
70
|
+
const queue = this.queues.get(key);
|
|
71
|
+
if (!queue || queue.length === 0) {
|
|
72
|
+
reject(new Error("Task not found in queue"));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const task = queue[0];
|
|
77
|
+
if (!task) {
|
|
78
|
+
reject(new Error("Task not found"));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const result = await task.fn();
|
|
84
|
+
queue.shift();
|
|
85
|
+
resolve(result);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
queue.shift();
|
|
88
|
+
reject(error);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
setTimeout(checkQueue, 10);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Mark as processing
|
|
97
|
+
this.processing.set(key, true);
|
|
98
|
+
|
|
99
|
+
// Get next task
|
|
100
|
+
const queue = this.queues.get(key);
|
|
101
|
+
if (!queue || queue.length === 0) {
|
|
102
|
+
this.processing.set(key, false);
|
|
103
|
+
throw new Error("No tasks in queue");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const task = queue[0];
|
|
107
|
+
if (!task) {
|
|
108
|
+
this.processing.set(key, false);
|
|
109
|
+
throw new Error("Task not found");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await task.fn();
|
|
114
|
+
queue.shift();
|
|
115
|
+
return result;
|
|
116
|
+
} finally {
|
|
117
|
+
this.processing.set(key, false);
|
|
118
|
+
|
|
119
|
+
// Process next task if available
|
|
120
|
+
if (queue.length > 0) {
|
|
121
|
+
this.processQueue(key).catch(console.error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get queue length for a key
|
|
128
|
+
*/
|
|
129
|
+
getQueueLength(key: string): number {
|
|
130
|
+
const queue = this.queues.get(key);
|
|
131
|
+
return queue ? queue.length : 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get total queue length
|
|
136
|
+
*/
|
|
137
|
+
getTotalQueueLength(): number {
|
|
138
|
+
let total = 0;
|
|
139
|
+
for (const queue of this.queues.values()) {
|
|
140
|
+
total += queue.length;
|
|
141
|
+
}
|
|
142
|
+
return total;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clear queue for a key
|
|
147
|
+
*/
|
|
148
|
+
clearQueue(key: string): number {
|
|
149
|
+
const queue = this.queues.get(key);
|
|
150
|
+
if (!queue) {
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const count = queue.length;
|
|
155
|
+
queue.length = 0;
|
|
156
|
+
return count;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Clear all queues
|
|
161
|
+
*/
|
|
162
|
+
clearAll(): number {
|
|
163
|
+
let count = 0;
|
|
164
|
+
for (const queue of this.queues.values()) {
|
|
165
|
+
count += queue.length;
|
|
166
|
+
}
|
|
167
|
+
this.queues.clear();
|
|
168
|
+
this.processing.clear();
|
|
169
|
+
return count;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get statistics
|
|
174
|
+
*/
|
|
175
|
+
getStats(): QueueStats {
|
|
176
|
+
const stats: QueueStats = {
|
|
177
|
+
total: 0,
|
|
178
|
+
byKey: {},
|
|
179
|
+
processing: 0,
|
|
180
|
+
queued: 0,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
for (const [key, queue] of this.queues.entries()) {
|
|
184
|
+
stats.byKey[key] = queue.length;
|
|
185
|
+
stats.total += queue.length;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
stats.queued = stats.total;
|
|
189
|
+
stats.processing = Array.from(this.processing.values()).filter(Boolean).length;
|
|
190
|
+
|
|
191
|
+
return stats;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create keyed async queue
|
|
197
|
+
*/
|
|
198
|
+
export function createKeyedAsyncQueue(concurrency?: number): KeyedAsyncQueue {
|
|
199
|
+
return new KeyedAsyncQueue(concurrency);
|
|
200
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/pool/Documents/GitHub/pool-bot/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|