@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.
Files changed (159) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/acp/bindings-store.js +209 -0
  4. package/dist/acp/control-plane/runtime-cache.js +54 -0
  5. package/dist/acp/control-plane/runtime-options.js +215 -0
  6. package/dist/acp/control-plane/session-actor-queue.js +36 -0
  7. package/dist/acp/policy.js +52 -0
  8. package/dist/acp/runtime/errors.js +47 -0
  9. package/dist/acp/runtime/registry.js +86 -0
  10. package/dist/acp/runtime/types.js +1 -0
  11. package/dist/acp/translator.js +97 -0
  12. package/dist/agents/btw.js +280 -0
  13. package/dist/agents/failover-error.js +145 -47
  14. package/dist/agents/fast-mode.js +24 -0
  15. package/dist/agents/live-model-errors.js +23 -0
  16. package/dist/agents/model-auth-env-vars.js +44 -0
  17. package/dist/agents/model-auth-markers.js +69 -0
  18. package/dist/agents/models-config.providers.discovery.js +180 -0
  19. package/dist/agents/models-config.providers.static.js +480 -0
  20. package/dist/auto-reply/reply/typing-policy.js +15 -0
  21. package/dist/browser/browser-profile-manager.js +319 -0
  22. package/dist/browser/cdp-proxy-bypass.js +129 -0
  23. package/dist/browser/cdp-timeouts.js +41 -0
  24. package/dist/browser/chrome-extension-validator.js +406 -0
  25. package/dist/browser/chrome-mcp-snapshot.js +222 -0
  26. package/dist/browser/chrome-mcp.js +421 -0
  27. package/dist/browser/chrome-mcp.snapshot.js +133 -0
  28. package/dist/browser/errors.js +67 -0
  29. package/dist/browser/form-fields.js +22 -0
  30. package/dist/browser/output-atomic.js +44 -0
  31. package/dist/browser/profile-capabilities.js +47 -0
  32. package/dist/browser/safe-filename.js +25 -0
  33. package/dist/browser/snapshot-roles.js +60 -0
  34. package/dist/build-info.json +3 -3
  35. package/dist/channels/account-snapshot-fields.js +176 -0
  36. package/dist/channels/draft-stream-controls.js +89 -0
  37. package/dist/channels/inbound-debounce-policy.js +28 -0
  38. package/dist/channels/typing-lifecycle.js +39 -0
  39. package/dist/cli/program/command-registry.js +52 -0
  40. package/dist/commands/agent-binding.js +123 -0
  41. package/dist/commands/agents.commands.bind.js +280 -0
  42. package/dist/commands/backup-shared.js +186 -0
  43. package/dist/commands/backup-verify.js +236 -0
  44. package/dist/commands/backup.js +166 -0
  45. package/dist/commands/channel-account-context.js +15 -0
  46. package/dist/commands/channel-account.js +190 -0
  47. package/dist/commands/gateway-install-token.js +117 -0
  48. package/dist/commands/oauth-tls-preflight.js +121 -0
  49. package/dist/commands/ollama-setup.js +402 -0
  50. package/dist/commands/security-owner-only.js +86 -0
  51. package/dist/commands/self-hosted-provider-setup.js +207 -0
  52. package/dist/commands/session-store-targets.js +12 -0
  53. package/dist/commands/sessions-cleanup.js +97 -0
  54. package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
  55. package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
  56. package/dist/control-ui/index.html +1 -1
  57. package/dist/cron/cron-filters.js +150 -0
  58. package/dist/cron/heartbeat-policy.js +26 -0
  59. package/dist/gateway/device-pairing-security.js +197 -0
  60. package/dist/gateway/event-deduplication.js +167 -0
  61. package/dist/gateway/hooks-mapping.js +46 -7
  62. package/dist/gateway/run-tracker.js +253 -0
  63. package/dist/gateway/server-methods/nodes.js +14 -0
  64. package/dist/gateway/websocket-preauth-security.js +188 -0
  65. package/dist/hooks/module-loader.js +28 -0
  66. package/dist/infra/agent-command-binding.js +144 -0
  67. package/dist/infra/backup.js +328 -0
  68. package/dist/infra/channel-account-context.js +173 -0
  69. package/dist/infra/errors.js +53 -13
  70. package/dist/infra/exec-approvals-security.js +217 -0
  71. package/dist/infra/security/command-analyzer.js +257 -0
  72. package/dist/infra/session-cleanup.js +143 -0
  73. package/dist/plugins/loader.js +16 -8
  74. package/dist/security/external-content.js +51 -1
  75. package/dist/sessions/session-costs.js +228 -0
  76. package/dist/shared/param-key.js +16 -0
  77. package/dist/shared/poll-params.js +58 -0
  78. package/dist/shared/polls.js +55 -0
  79. package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
  80. package/docs/FEATURES.md +523 -0
  81. package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
  82. package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
  83. package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
  84. package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
  85. package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
  86. package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
  87. package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
  88. package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
  89. package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
  90. package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
  91. package/docs/MIKRODASH-ANALYSIS.md +412 -0
  92. package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
  93. package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
  94. package/docs/PHASE-7-SUMMARY.md +144 -0
  95. package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
  96. package/docs/PROJECT-FINAL-STATUS.md +237 -0
  97. package/docs/README.md +116 -0
  98. package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
  99. package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
  100. package/docs/channels/googlechat.md +235 -206
  101. package/docs/channels/irc.md +332 -0
  102. package/docs/channels/nostr.md +255 -168
  103. package/docs/components/command-palette.md +166 -0
  104. package/docs/components/login-gate.md +219 -0
  105. package/docs/getting-started/installation.md +191 -0
  106. package/docs/getting-started/introduction.md +120 -0
  107. package/docs/improvements/USAGE-GUIDE.md +359 -0
  108. package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
  109. package/docs/reference/deadcode-detection.md +72 -0
  110. package/extensions/acpx/node_modules/.bin/acpx +21 -0
  111. package/extensions/agency-agents/node_modules/.bin/vite +4 -4
  112. package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
  113. package/extensions/googlechat/node_modules/.bin/tsc +21 -0
  114. package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
  115. package/extensions/googlechat/node_modules/.bin/vitest +21 -0
  116. package/extensions/googlechat/package.json +11 -28
  117. package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
  118. package/extensions/googlechat/src/googlechat-channel.ts +120 -0
  119. package/extensions/googlechat/src/index.ts +14 -0
  120. package/extensions/irc/node_modules/.bin/tsc +21 -0
  121. package/extensions/irc/node_modules/.bin/tsserver +21 -0
  122. package/extensions/irc/node_modules/.bin/vitest +21 -0
  123. package/extensions/irc/package.json +16 -8
  124. package/extensions/irc/src/index.ts +14 -0
  125. package/extensions/irc/src/irc-channel.test.ts +43 -0
  126. package/extensions/irc/src/irc-channel.ts +191 -0
  127. package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
  128. package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
  129. package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
  130. package/extensions/keyed-async-queue/package.json +20 -0
  131. package/extensions/keyed-async-queue/src/index.ts +14 -0
  132. package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
  133. package/extensions/keyed-async-queue/src/queue.ts +200 -0
  134. package/extensions/memory-core/node_modules/.bin/tsc +21 -0
  135. package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
  136. package/extensions/memory-core/node_modules/.bin/vitest +21 -0
  137. package/extensions/memory-core/package.json +11 -8
  138. package/extensions/memory-core/src/index.ts +14 -0
  139. package/extensions/memory-core/src/memory-manager.test.ts +124 -0
  140. package/extensions/memory-core/src/memory-manager.ts +186 -0
  141. package/extensions/nostr/node_modules/.bin/tsc +2 -2
  142. package/extensions/nostr/node_modules/.bin/tsserver +2 -2
  143. package/extensions/nostr/node_modules/.bin/vitest +21 -0
  144. package/extensions/nostr/package.json +15 -24
  145. package/extensions/nostr/src/index.ts +14 -0
  146. package/extensions/nostr/src/nostr-channel.test.ts +55 -0
  147. package/extensions/nostr/src/nostr-channel.ts +228 -0
  148. package/extensions/page-agent/node_modules/.bin/vitest +2 -2
  149. package/extensions/test-utils/node_modules/.bin/jiti +21 -0
  150. package/extensions/test-utils/node_modules/.bin/playwright +21 -0
  151. package/extensions/test-utils/node_modules/.bin/tsx +21 -0
  152. package/extensions/test-utils/node_modules/.bin/vite +21 -0
  153. package/extensions/test-utils/node_modules/.bin/vitest +21 -0
  154. package/extensions/test-utils/node_modules/.bin/yaml +21 -0
  155. package/extensions/xyops/node_modules/.bin/vitest +2 -2
  156. package/package.json +2 -1
  157. package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
  158. package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
  159. package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
@@ -0,0 +1,47 @@
1
+ export function getBrowserProfileCapabilities(profile) {
2
+ if (profile.driver === "extension") {
3
+ return {
4
+ mode: "local-extension-relay",
5
+ isRemote: false,
6
+ requiresRelay: true,
7
+ requiresAttachedTab: true,
8
+ usesPersistentPlaywright: false,
9
+ supportsPerTabWs: false,
10
+ supportsJsonTabEndpoints: true,
11
+ supportsReset: true,
12
+ supportsManagedTabLimit: false,
13
+ };
14
+ }
15
+ return {
16
+ mode: "local-managed",
17
+ isRemote: !profile.cdpIsLoopback,
18
+ requiresRelay: false,
19
+ requiresAttachedTab: false,
20
+ usesPersistentPlaywright: false,
21
+ supportsPerTabWs: profile.cdpIsLoopback,
22
+ supportsJsonTabEndpoints: profile.cdpIsLoopback,
23
+ supportsReset: profile.cdpIsLoopback,
24
+ supportsManagedTabLimit: profile.cdpIsLoopback,
25
+ };
26
+ }
27
+ export function resolveDefaultSnapshotFormat(params) {
28
+ if (params.explicitFormat) {
29
+ return params.explicitFormat;
30
+ }
31
+ if (params.mode === "efficient") {
32
+ return "ai";
33
+ }
34
+ const capabilities = getBrowserProfileCapabilities(params.profile);
35
+ if (capabilities.mode === "local-extension-relay") {
36
+ return "aria";
37
+ }
38
+ return params.hasPlaywright ? "ai" : "aria";
39
+ }
40
+ export function shouldUsePlaywrightForScreenshot(params) {
41
+ const capabilities = getBrowserProfileCapabilities(params.profile);
42
+ return (capabilities.requiresRelay || !params.wsUrl || Boolean(params.ref) || Boolean(params.element));
43
+ }
44
+ export function shouldUsePlaywrightForAriaSnapshot(params) {
45
+ const capabilities = getBrowserProfileCapabilities(params.profile);
46
+ return capabilities.requiresRelay || !params.wsUrl;
47
+ }
@@ -0,0 +1,25 @@
1
+ import path from "node:path";
2
+ export function sanitizeUntrustedFileName(fileName, fallbackName) {
3
+ const trimmed = String(fileName ?? "").trim();
4
+ if (!trimmed) {
5
+ return fallbackName;
6
+ }
7
+ let base = path.posix.basename(trimmed);
8
+ base = path.win32.basename(base);
9
+ let cleaned = "";
10
+ for (let i = 0; i < base.length; i++) {
11
+ const code = base.charCodeAt(i);
12
+ if (code < 0x20 || code === 0x7f) {
13
+ continue;
14
+ }
15
+ cleaned += base[i];
16
+ }
17
+ base = cleaned.trim();
18
+ if (!base || base === "." || base === "..") {
19
+ return fallbackName;
20
+ }
21
+ if (base.length > 200) {
22
+ base = base.slice(0, 200);
23
+ }
24
+ return base;
25
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared ARIA role classification sets used by both the Playwright and Chrome MCP
3
+ * snapshot paths. Keep these in sync — divergence causes the two drivers to produce
4
+ * different snapshot output for the same page.
5
+ */
6
+ /** Roles that represent user-interactive elements and always get a ref. */
7
+ export const INTERACTIVE_ROLES = new Set([
8
+ "button",
9
+ "checkbox",
10
+ "combobox",
11
+ "link",
12
+ "listbox",
13
+ "menuitem",
14
+ "menuitemcheckbox",
15
+ "menuitemradio",
16
+ "option",
17
+ "radio",
18
+ "searchbox",
19
+ "slider",
20
+ "spinbutton",
21
+ "switch",
22
+ "tab",
23
+ "textbox",
24
+ "treeitem",
25
+ ]);
26
+ /** Roles that carry meaningful content and get a ref when named. */
27
+ export const CONTENT_ROLES = new Set([
28
+ "article",
29
+ "cell",
30
+ "columnheader",
31
+ "gridcell",
32
+ "heading",
33
+ "listitem",
34
+ "main",
35
+ "navigation",
36
+ "region",
37
+ "rowheader",
38
+ ]);
39
+ /** Structural/container roles — typically skipped in compact mode. */
40
+ export const STRUCTURAL_ROLES = new Set([
41
+ "application",
42
+ "directory",
43
+ "document",
44
+ "generic",
45
+ "grid",
46
+ "group",
47
+ "ignored",
48
+ "list",
49
+ "menu",
50
+ "menubar",
51
+ "none",
52
+ "presentation",
53
+ "row",
54
+ "rowgroup",
55
+ "table",
56
+ "tablist",
57
+ "toolbar",
58
+ "tree",
59
+ "treegrid",
60
+ ]);
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.3.22",
3
- "commit": "d73f24d737d21800609b762183d8554526df0ee7",
4
- "builtAt": "2026-03-13T23:59:05.337Z"
2
+ "version": "2026.3.24",
3
+ "commit": "30d8108895641ae634c05fb74351706c65b83308",
4
+ "builtAt": "2026-03-16T18:27:37.980Z"
5
5
  }
@@ -0,0 +1,176 @@
1
+ // Read-only status commands project a safe subset of account fields into snapshots
2
+ // so renderers can preserve "configured but unavailable" state without touching
3
+ // strict runtime-only credential helpers.
4
+ const CREDENTIAL_STATUS_KEYS = [
5
+ "tokenStatus",
6
+ "botTokenStatus",
7
+ "appTokenStatus",
8
+ "signingSecretStatus",
9
+ "userTokenStatus",
10
+ ];
11
+ function asRecord(value) {
12
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
13
+ return null;
14
+ }
15
+ return value;
16
+ }
17
+ function readTrimmedString(record, key) {
18
+ const value = record[key];
19
+ if (typeof value !== "string") {
20
+ return undefined;
21
+ }
22
+ const trimmed = value.trim();
23
+ return trimmed.length > 0 ? trimmed : undefined;
24
+ }
25
+ function readBoolean(record, key) {
26
+ return typeof record[key] === "boolean" ? record[key] : undefined;
27
+ }
28
+ function readNumber(record, key) {
29
+ const value = record[key];
30
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
31
+ }
32
+ function readStringArray(record, key) {
33
+ const value = record[key];
34
+ if (!Array.isArray(value)) {
35
+ return undefined;
36
+ }
37
+ const normalized = value
38
+ .map((entry) => (typeof entry === "string" || typeof entry === "number" ? String(entry) : ""))
39
+ .map((entry) => entry.trim())
40
+ .filter(Boolean);
41
+ return normalized.length > 0 ? normalized : undefined;
42
+ }
43
+ function readCredentialStatus(record, key) {
44
+ const value = record[key];
45
+ return value === "available" || value === "configured_unavailable" || value === "missing"
46
+ ? value
47
+ : undefined;
48
+ }
49
+ export function resolveConfiguredFromCredentialStatuses(account) {
50
+ const record = asRecord(account);
51
+ if (!record) {
52
+ return undefined;
53
+ }
54
+ let sawCredentialStatus = false;
55
+ for (const key of CREDENTIAL_STATUS_KEYS) {
56
+ const status = readCredentialStatus(record, key);
57
+ if (!status) {
58
+ continue;
59
+ }
60
+ sawCredentialStatus = true;
61
+ if (status !== "missing") {
62
+ return true;
63
+ }
64
+ }
65
+ return sawCredentialStatus ? false : undefined;
66
+ }
67
+ export function resolveConfiguredFromRequiredCredentialStatuses(account, requiredKeys) {
68
+ const record = asRecord(account);
69
+ if (!record) {
70
+ return undefined;
71
+ }
72
+ let sawCredentialStatus = false;
73
+ for (const key of requiredKeys) {
74
+ const status = readCredentialStatus(record, key);
75
+ if (!status) {
76
+ continue;
77
+ }
78
+ sawCredentialStatus = true;
79
+ if (status === "missing") {
80
+ return false;
81
+ }
82
+ }
83
+ return sawCredentialStatus ? true : undefined;
84
+ }
85
+ export function hasConfiguredUnavailableCredentialStatus(account) {
86
+ const record = asRecord(account);
87
+ if (!record) {
88
+ return false;
89
+ }
90
+ return CREDENTIAL_STATUS_KEYS.some((key) => readCredentialStatus(record, key) === "configured_unavailable");
91
+ }
92
+ export function hasResolvedCredentialValue(account) {
93
+ const record = asRecord(account);
94
+ if (!record) {
95
+ return false;
96
+ }
97
+ return (["token", "botToken", "appToken", "signingSecret", "userToken"].some((key) => {
98
+ const value = record[key];
99
+ return typeof value === "string" && value.trim().length > 0;
100
+ }) || CREDENTIAL_STATUS_KEYS.some((key) => readCredentialStatus(record, key) === "available"));
101
+ }
102
+ export function projectCredentialSnapshotFields(account) {
103
+ const record = asRecord(account);
104
+ if (!record) {
105
+ return {};
106
+ }
107
+ return {
108
+ ...(readTrimmedString(record, "tokenSource")
109
+ ? { tokenSource: readTrimmedString(record, "tokenSource") }
110
+ : {}),
111
+ ...(readTrimmedString(record, "botTokenSource")
112
+ ? { botTokenSource: readTrimmedString(record, "botTokenSource") }
113
+ : {}),
114
+ ...(readTrimmedString(record, "appTokenSource")
115
+ ? { appTokenSource: readTrimmedString(record, "appTokenSource") }
116
+ : {}),
117
+ ...(readTrimmedString(record, "signingSecretSource")
118
+ ? { signingSecretSource: readTrimmedString(record, "signingSecretSource") }
119
+ : {}),
120
+ ...(readCredentialStatus(record, "tokenStatus")
121
+ ? { tokenStatus: readCredentialStatus(record, "tokenStatus") }
122
+ : {}),
123
+ ...(readCredentialStatus(record, "botTokenStatus")
124
+ ? { botTokenStatus: readCredentialStatus(record, "botTokenStatus") }
125
+ : {}),
126
+ ...(readCredentialStatus(record, "appTokenStatus")
127
+ ? { appTokenStatus: readCredentialStatus(record, "appTokenStatus") }
128
+ : {}),
129
+ ...(readCredentialStatus(record, "signingSecretStatus")
130
+ ? { signingSecretStatus: readCredentialStatus(record, "signingSecretStatus") }
131
+ : {}),
132
+ ...(readCredentialStatus(record, "userTokenStatus")
133
+ ? { userTokenStatus: readCredentialStatus(record, "userTokenStatus") }
134
+ : {}),
135
+ };
136
+ }
137
+ export function projectSafeChannelAccountSnapshotFields(account) {
138
+ const record = asRecord(account);
139
+ if (!record) {
140
+ return {};
141
+ }
142
+ return {
143
+ ...(readTrimmedString(record, "name") ? { name: readTrimmedString(record, "name") } : {}),
144
+ ...(readBoolean(record, "linked") !== undefined
145
+ ? { linked: readBoolean(record, "linked") }
146
+ : {}),
147
+ ...(readBoolean(record, "running") !== undefined
148
+ ? { running: readBoolean(record, "running") }
149
+ : {}),
150
+ ...(readBoolean(record, "connected") !== undefined
151
+ ? { connected: readBoolean(record, "connected") }
152
+ : {}),
153
+ ...(readNumber(record, "reconnectAttempts") !== undefined
154
+ ? { reconnectAttempts: readNumber(record, "reconnectAttempts") }
155
+ : {}),
156
+ ...(readTrimmedString(record, "mode") ? { mode: readTrimmedString(record, "mode") } : {}),
157
+ ...(readTrimmedString(record, "dmPolicy")
158
+ ? { dmPolicy: readTrimmedString(record, "dmPolicy") }
159
+ : {}),
160
+ ...(readStringArray(record, "allowFrom")
161
+ ? { allowFrom: readStringArray(record, "allowFrom") }
162
+ : {}),
163
+ ...projectCredentialSnapshotFields(account),
164
+ ...(readTrimmedString(record, "baseUrl")
165
+ ? { baseUrl: readTrimmedString(record, "baseUrl") }
166
+ : {}),
167
+ ...(readBoolean(record, "allowUnmentionedGroups") !== undefined
168
+ ? { allowUnmentionedGroups: readBoolean(record, "allowUnmentionedGroups") }
169
+ : {}),
170
+ ...(readTrimmedString(record, "cliPath")
171
+ ? { cliPath: readTrimmedString(record, "cliPath") }
172
+ : {}),
173
+ ...(readTrimmedString(record, "dbPath") ? { dbPath: readTrimmedString(record, "dbPath") } : {}),
174
+ ...(readNumber(record, "port") !== undefined ? { port: readNumber(record, "port") } : {}),
175
+ };
176
+ }
@@ -0,0 +1,89 @@
1
+ import { createDraftStreamLoop } from "./draft-stream-loop.js";
2
+ export function createFinalizableDraftStreamControls(params) {
3
+ const loop = createDraftStreamLoop({
4
+ throttleMs: params.throttleMs,
5
+ isStopped: params.isStopped,
6
+ sendOrEditStreamMessage: params.sendOrEditStreamMessage,
7
+ });
8
+ const update = (text) => {
9
+ if (params.isStopped() || params.isFinal()) {
10
+ return;
11
+ }
12
+ loop.update(text);
13
+ };
14
+ const stop = async () => {
15
+ params.markFinal();
16
+ await loop.flush();
17
+ };
18
+ const stopForClear = async () => {
19
+ params.markStopped();
20
+ loop.stop();
21
+ await loop.waitForInFlight();
22
+ };
23
+ return {
24
+ loop,
25
+ update,
26
+ stop,
27
+ stopForClear,
28
+ };
29
+ }
30
+ export function createFinalizableDraftStreamControlsForState(params) {
31
+ return createFinalizableDraftStreamControls({
32
+ throttleMs: params.throttleMs,
33
+ isStopped: () => params.state.stopped,
34
+ isFinal: () => params.state.final,
35
+ markStopped: () => {
36
+ params.state.stopped = true;
37
+ },
38
+ markFinal: () => {
39
+ params.state.final = true;
40
+ },
41
+ sendOrEditStreamMessage: params.sendOrEditStreamMessage,
42
+ });
43
+ }
44
+ export async function takeMessageIdAfterStop(params) {
45
+ await params.stopForClear();
46
+ const messageId = params.readMessageId();
47
+ params.clearMessageId();
48
+ return messageId;
49
+ }
50
+ export async function clearFinalizableDraftMessage(params) {
51
+ const messageId = await takeMessageIdAfterStop({
52
+ stopForClear: params.stopForClear,
53
+ readMessageId: params.readMessageId,
54
+ clearMessageId: params.clearMessageId,
55
+ });
56
+ if (!params.isValidMessageId(messageId)) {
57
+ return;
58
+ }
59
+ try {
60
+ await params.deleteMessage(messageId);
61
+ params.onDeleteSuccess?.(messageId);
62
+ }
63
+ catch (err) {
64
+ params.warn?.(`${params.warnPrefix}: ${err instanceof Error ? err.message : String(err)}`);
65
+ }
66
+ }
67
+ export function createFinalizableDraftLifecycle(params) {
68
+ const controls = createFinalizableDraftStreamControlsForState({
69
+ throttleMs: params.throttleMs,
70
+ state: params.state,
71
+ sendOrEditStreamMessage: params.sendOrEditStreamMessage,
72
+ });
73
+ const clear = async () => {
74
+ await clearFinalizableDraftMessage({
75
+ stopForClear: controls.stopForClear,
76
+ readMessageId: params.readMessageId,
77
+ clearMessageId: params.clearMessageId,
78
+ isValidMessageId: params.isValidMessageId,
79
+ deleteMessage: params.deleteMessage,
80
+ onDeleteSuccess: params.onDeleteSuccess,
81
+ warn: params.warn,
82
+ warnPrefix: params.warnPrefix,
83
+ });
84
+ };
85
+ return {
86
+ ...controls,
87
+ clear,
88
+ };
89
+ }
@@ -0,0 +1,28 @@
1
+ import { hasControlCommand } from "../auto-reply/command-detection.js";
2
+ import { createInboundDebouncer, resolveInboundDebounceMs, } from "../auto-reply/inbound-debounce.js";
3
+ export function shouldDebounceTextInbound(params) {
4
+ if (params.allowDebounce === false) {
5
+ return false;
6
+ }
7
+ if (params.hasMedia) {
8
+ return false;
9
+ }
10
+ const text = params.text?.trim() ?? "";
11
+ if (!text) {
12
+ return false;
13
+ }
14
+ return !hasControlCommand(text, params.cfg, params.commandOptions);
15
+ }
16
+ export function createChannelInboundDebouncer(params) {
17
+ const debounceMs = resolveInboundDebounceMs({
18
+ cfg: params.cfg,
19
+ channel: params.channel,
20
+ overrideMs: params.debounceMsOverride,
21
+ });
22
+ const { cfg: _cfg, channel: _channel, debounceMsOverride: _override, ...rest } = params;
23
+ const debouncer = createInboundDebouncer({
24
+ debounceMs,
25
+ ...rest,
26
+ });
27
+ return { debounceMs, debouncer };
28
+ }
@@ -0,0 +1,39 @@
1
+ export function createTypingKeepaliveLoop(params) {
2
+ let timer;
3
+ let tickInFlight = false;
4
+ const tick = async () => {
5
+ if (tickInFlight) {
6
+ return;
7
+ }
8
+ tickInFlight = true;
9
+ try {
10
+ await params.onTick();
11
+ }
12
+ finally {
13
+ tickInFlight = false;
14
+ }
15
+ };
16
+ const start = () => {
17
+ if (params.intervalMs <= 0 || timer) {
18
+ return;
19
+ }
20
+ timer = setInterval(() => {
21
+ void tick();
22
+ }, params.intervalMs);
23
+ };
24
+ const stop = () => {
25
+ if (!timer) {
26
+ return;
27
+ }
28
+ clearInterval(timer);
29
+ timer = undefined;
30
+ tickInFlight = false;
31
+ };
32
+ const isRunning = () => timer !== undefined;
33
+ return {
34
+ tick,
35
+ start,
36
+ stop,
37
+ isRunning,
38
+ };
39
+ }
@@ -92,6 +92,19 @@ const coreEntries = [
92
92
  mod.registerMaintenanceCommands(program);
93
93
  },
94
94
  },
95
+ {
96
+ commands: [
97
+ {
98
+ name: "sessions-cleanup",
99
+ description: "Clean up old sessions to stay within disk budget",
100
+ hasSubcommands: true,
101
+ },
102
+ ],
103
+ register: async ({ program }) => {
104
+ const mod = await import("../../commands/sessions-cleanup.js");
105
+ mod.registerSessionCleanupCommand(program);
106
+ },
107
+ },
95
108
  {
96
109
  commands: [
97
110
  {
@@ -138,6 +151,19 @@ const coreEntries = [
138
151
  });
139
152
  },
140
153
  },
154
+ {
155
+ commands: [
156
+ {
157
+ name: "agent-binding",
158
+ description: "Manage agent command bindings (bind/unbind commands to agents)",
159
+ hasSubcommands: true,
160
+ },
161
+ ],
162
+ register: async ({ program }) => {
163
+ const mod = await import("../../commands/agent-binding.js");
164
+ mod.registerAgentCommandBindingCommand(program);
165
+ },
166
+ },
141
167
  {
142
168
  commands: [
143
169
  {
@@ -161,6 +187,32 @@ const coreEntries = [
161
187
  mod.registerStatusHealthSessionsCommands(program);
162
188
  },
163
189
  },
190
+ {
191
+ commands: [
192
+ {
193
+ name: "backup",
194
+ description: "Manage Pool Bot backups (create, restore, list, delete)",
195
+ hasSubcommands: true,
196
+ },
197
+ ],
198
+ register: async ({ program }) => {
199
+ const mod = await import("../../commands/backup.js");
200
+ mod.registerBackupCommand(program);
201
+ },
202
+ },
203
+ {
204
+ commands: [
205
+ {
206
+ name: "channel-account",
207
+ description: "Manage channel accounts (multi-account support)",
208
+ hasSubcommands: true,
209
+ },
210
+ ],
211
+ register: async ({ program }) => {
212
+ const mod = await import("../../commands/channel-account.js");
213
+ mod.registerChannelAccountCommand(program);
214
+ },
215
+ },
164
216
  {
165
217
  commands: [
166
218
  {
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Agent Command Binding CLI
3
+ *
4
+ * Bind specific commands to agents.
5
+ */
6
+ import { bindCommandsToAgent, unbindCommandsFromAgent, getAgentBindings, getAllBindings, addCommandsToAgent, removeCommandsFromAgent, toggleBindingMode, isCommandAllowedForAgent, } from "../infra/agent-command-binding.js";
7
+ export function registerAgentCommandBindingCommand(program) {
8
+ const bindingCmd = program.command("agent-binding").description("Manage agent command bindings");
9
+ // List all bindings
10
+ bindingCmd
11
+ .command("list")
12
+ .description("List all agent command bindings")
13
+ .action(async () => {
14
+ console.log("🎱 Pool Bot - Agent Command Bindings\n");
15
+ const bindings = await getAllBindings();
16
+ if (bindings.length === 0) {
17
+ console.log("No bindings found.\n");
18
+ return;
19
+ }
20
+ console.log("┌──────────────┬──────────────┬──────────────┬──────────────┐");
21
+ console.log("│ Agent ID │ Mode │ Commands │ Updated │");
22
+ console.log("├──────────────┼──────────────┼──────────────┼──────────────┤");
23
+ bindings.forEach((binding) => {
24
+ const mode = binding.allowlist ? "Allowlist" : "Denylist";
25
+ const commandCount = binding.commands.length;
26
+ const updated = new Date(binding.updatedAt).toLocaleDateString();
27
+ console.log(`│ ${binding.agentId.padEnd(12)} │ ${mode.padEnd(12)} │ ${String(commandCount).padEnd(12)} │ ${updated.padEnd(12)} │`);
28
+ });
29
+ console.log("└──────────────┴──────────────┴──────────────┴──────────────┘\n");
30
+ console.log(`Total: ${bindings.length} binding(s)\n`);
31
+ });
32
+ // Show binding for specific agent
33
+ bindingCmd
34
+ .command("show <agentId>")
35
+ .description("Show bindings for a specific agent")
36
+ .action(async (agentId) => {
37
+ console.log(`🎱 Pool Bot - Agent Binding for ${agentId}\n`);
38
+ const binding = await getAgentBindings(agentId);
39
+ if (!binding) {
40
+ console.log(`No binding found for agent ${agentId}\n`);
41
+ return;
42
+ }
43
+ console.log(`Agent ID: ${binding.agentId}`);
44
+ console.log(`Mode: ${binding.allowlist ? "Allowlist" : "Denylist"}`);
45
+ console.log(`Commands (${binding.commands.length}):`);
46
+ binding.commands.forEach((cmd) => console.log(` - ${cmd}`));
47
+ console.log(`Created: ${new Date(binding.createdAt).toLocaleString()}`);
48
+ console.log(`Updated: ${new Date(binding.updatedAt).toLocaleString()}\n`);
49
+ });
50
+ // Bind commands
51
+ bindingCmd
52
+ .command("bind <agentId> <commands...>")
53
+ .description("Bind commands to an agent (allowlist mode)")
54
+ .option("--denylist", "Use denylist mode instead of allowlist")
55
+ .action(async (agentId, commands, options) => {
56
+ console.log("🎱 Pool Bot - Binding Commands\n");
57
+ const binding = await bindCommandsToAgent({
58
+ agentId,
59
+ commands,
60
+ allowlist: !options.denylist,
61
+ });
62
+ console.log(`✅ Commands bound to agent ${agentId}\n`);
63
+ console.log(`Mode: ${binding.allowlist ? "Allowlist" : "Denylist"}`);
64
+ console.log(`Commands: ${binding.commands.join(", ")}\n`);
65
+ });
66
+ // Add commands
67
+ bindingCmd
68
+ .command("add <agentId> <commands...>")
69
+ .description("Add commands to an agent's binding")
70
+ .action(async (agentId, commands) => {
71
+ console.log("🎱 Pool Bot - Adding Commands\n");
72
+ const binding = await addCommandsToAgent({ agentId, commands });
73
+ console.log(`✅ Commands added to agent ${agentId}\n`);
74
+ console.log(`Total commands: ${binding.commands.length}\n`);
75
+ });
76
+ // Remove commands
77
+ bindingCmd
78
+ .command("remove <agentId> <commands...>")
79
+ .description("Remove commands from an agent's binding")
80
+ .action(async (agentId, commands) => {
81
+ console.log("🎱 Pool Bot - Removing Commands\n");
82
+ const binding = await removeCommandsFromAgent({ agentId, commands });
83
+ console.log(`✅ Commands removed from agent ${agentId}\n`);
84
+ console.log(`Remaining commands: ${binding.commands.length}\n`);
85
+ });
86
+ // Unbind
87
+ bindingCmd
88
+ .command("unbind <agentId>")
89
+ .description("Remove all bindings from an agent")
90
+ .action(async (agentId) => {
91
+ console.log("🎱 Pool Bot - Unbinding Commands\n");
92
+ const success = await unbindCommandsFromAgent(agentId);
93
+ if (success) {
94
+ console.log(`✅ Bindings removed for agent ${agentId}\n`);
95
+ }
96
+ else {
97
+ console.log(`No bindings found for agent ${agentId}\n`);
98
+ }
99
+ });
100
+ // Toggle mode
101
+ bindingCmd
102
+ .command("toggle <agentId>")
103
+ .description("Toggle between allowlist and denylist mode")
104
+ .action(async (agentId) => {
105
+ console.log("🎱 Pool Bot - Toggling Binding Mode\n");
106
+ const binding = await toggleBindingMode(agentId);
107
+ console.log(`✅ Mode toggled for agent ${agentId}\n`);
108
+ console.log(`New mode: ${binding.allowlist ? "Allowlist" : "Denylist"}\n`);
109
+ });
110
+ // Test command
111
+ bindingCmd
112
+ .command("test <agentId> <command>")
113
+ .description("Test if a command is allowed for an agent")
114
+ .action(async (agentId, command) => {
115
+ console.log("🎱 Pool Bot - Testing Command\n");
116
+ const result = await isCommandAllowedForAgent({ agentId, command });
117
+ console.log(`Agent: ${agentId}`);
118
+ console.log(`Command: ${command}`);
119
+ console.log(`Allowed: ${result.allowed ? "Yes ✅" : "No ❌"}`);
120
+ console.log(`Reason: ${result.reason}\n`);
121
+ });
122
+ return bindingCmd;
123
+ }