@poolzin/pool-bot 2026.3.25 → 2026.3.26
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/dist/agents/model-fallback.js +5 -4
- package/dist/agents/tools/common.js +16 -201
- package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
- package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
- package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
- package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
- package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
- package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
- package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
- package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
- package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
- package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
- package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
- package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
- package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
- package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
- package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
- package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
- package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
- package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
- package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
- package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
- package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
- package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
- package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
- package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
- package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
- package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
- package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
- package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
- package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
- package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
- package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
- package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
- package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
- package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
- package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
- package/dist/canvas-host/a2ui/index.html +1 -307
- package/dist/channels/channels/directory-config.js +185 -0
- package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
- package/dist/channels/channels/discord/handle-action.js +165 -0
- package/dist/channels/channels/discord.js +413 -0
- package/dist/channels/channels/dock.js +436 -0
- package/dist/channels/channels/index.js +51 -0
- package/dist/channels/channels/plugins/outbound/discord.js +101 -0
- package/dist/channels/channels/whatsapp.js +17 -0
- package/dist/channels/plugins/types.js +1 -1
- package/dist/channels/run-state-machine.js +7 -0
- package/dist/commands/models/auth.js +47 -1
- package/dist/commands-subagents/action-agents.js +44 -0
- package/dist/commands-subagents/action-focus.js +64 -0
- package/dist/commands-subagents/action-help.js +4 -0
- package/dist/commands-subagents/action-info.js +45 -0
- package/dist/commands-subagents/action-kill.js +60 -0
- package/dist/commands-subagents/action-list.js +44 -0
- package/dist/commands-subagents/action-log.js +29 -0
- package/dist/commands-subagents/action-send.js +119 -0
- package/dist/commands-subagents/action-spawn.js +52 -0
- package/dist/commands-subagents/action-unfocus.js +30 -0
- package/dist/commands-subagents/shared.js +303 -0
- package/dist/config/config.js +1 -8
- package/dist/config/types.secrets.js +61 -0
- package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
- package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
- package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
- package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
- package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
- package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
- package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
- package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/method-scopes.js +9 -1
- package/dist/gateway/node-pending-work.js +142 -0
- package/dist/gateway/protocol/index.js +5 -1
- package/dist/gateway/protocol/schema/nodes.js +18 -0
- package/dist/gateway/server-methods/nodes-pending.js +96 -0
- package/dist/gateway/server-methods-list.js +4 -0
- package/dist/gateway/server-methods.js +2 -0
- package/dist/imessage/channel.js +253 -0
- package/dist/imessage/monitor/echo-cache.js +70 -0
- package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
- package/dist/imessage/monitor/reflection-guard.js +50 -0
- package/dist/imessage/monitor/sanitize-outbound.js +25 -0
- package/dist/imessage/monitor/self-chat-cache.js +75 -0
- package/dist/imessage/runtime.js +3 -0
- package/dist/infra/exec-approval-reply.js +7 -0
- package/dist/infra/tmp-openclaw-dir.js +84 -0
- package/dist/pairing/pairing-challenge.js +15 -0
- package/dist/plugin-sdk/account-id.d.ts +1 -0
- package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
- package/dist/plugin-sdk/allow-from.d.ts +27 -0
- package/dist/plugin-sdk/command-auth.d.ts +25 -0
- package/dist/plugin-sdk/command-auth.js +3 -1
- package/dist/plugin-sdk/config-paths.d.ts +6 -0
- package/dist/plugin-sdk/file-lock.d.ts +16 -0
- package/dist/plugin-sdk/index.d.ts +428 -0
- package/dist/plugin-sdk/index.js +237 -103
- package/dist/plugin-sdk/json-store.d.ts +5 -0
- package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
- package/dist/plugin-sdk/onboarding.d.ts +11 -0
- package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
- package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
- package/dist/plugin-sdk/status-helpers.d.ts +25 -0
- package/dist/plugin-sdk/temp-path.d.ts +12 -0
- package/dist/plugin-sdk/text-chunking.d.ts +1 -0
- package/dist/plugin-sdk/tool-send.d.ts +4 -0
- package/dist/plugin-sdk/webhook-path.d.ts +6 -0
- package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
- package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
- package/dist/plugin-sdk-internal/accounts.js +6 -0
- package/dist/plugin-sdk-internal/discord.js +23 -0
- package/dist/plugin-sdk-internal/imessage.js +13 -0
- package/dist/plugin-sdk-internal/setup.js +9 -0
- package/dist/plugin-sdk-internal/signal.js +13 -0
- package/dist/plugin-sdk-internal/slack.js +22 -0
- package/dist/plugin-sdk-internal/telegram.js +32 -0
- package/dist/plugin-sdk-internal/whatsapp.js +29 -0
- package/dist/routing/session-key.js +4 -185
- package/dist/shared/pid-alive.js +2 -61
- package/dist/shared/process-scoped-map.js +5 -7
- package/dist/signal/channel.js +264 -0
- package/dist/signal/monitor/access-policy.js +60 -0
- package/dist/signal/runtime.js +3 -0
- package/dist/slack/account-inspect.js +135 -0
- package/dist/slack/blocks-input.js +7 -38
- package/dist/slack/channel.js +394 -0
- package/dist/slack/interactive-replies.js +28 -0
- package/dist/slack/monitor/channel-type.js +31 -0
- package/dist/slack/monitor/dm-auth.js +49 -0
- package/dist/slack/monitor/events/interactions.modal.js +137 -0
- package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
- package/dist/slack/monitor/events/system-event-context.js +29 -0
- package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
- package/dist/slack/monitor/external-arg-menu-store.js +46 -0
- package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
- package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
- package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
- package/dist/slack/monitor/reconnect-policy.js +78 -0
- package/dist/slack/monitor/slash-commands.runtime.js +1 -0
- package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
- package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
- package/dist/slack/resolve-allowlist-common.js +36 -0
- package/dist/slack/runtime.js +3 -0
- package/dist/slack/sent-thread-cache.js +61 -0
- package/dist/slack/truncate.js +10 -0
- package/dist/telegram/account-inspect.js +175 -0
- package/dist/telegram/allow-from.js +10 -0
- package/dist/telegram/api-fetch.js +18 -0
- package/dist/telegram/approval-buttons.js +30 -0
- package/dist/telegram/audit-membership-runtime.js +61 -0
- package/dist/telegram/bot/delivery.replies.js +508 -0
- package/dist/telegram/bot/delivery.resolve-media.js +227 -0
- package/dist/telegram/bot/delivery.send.js +132 -0
- package/dist/telegram/bot/reply-threading.js +46 -0
- package/dist/telegram/bot-message-context.body.js +186 -0
- package/dist/telegram/bot-message-context.session.js +207 -0
- package/dist/telegram/bot-message-context.types.js +1 -0
- package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
- package/dist/telegram/bot.media.e2e-harness.js +81 -0
- package/dist/telegram/bot.media.test-utils.js +81 -0
- package/dist/telegram/channel-actions.js +225 -0
- package/dist/telegram/channel.js +515 -0
- package/dist/telegram/conversation-route.js +107 -0
- package/dist/telegram/delivery.js +2 -0
- package/dist/telegram/delivery.replies.js +508 -0
- package/dist/telegram/dm-access.js +86 -0
- package/dist/telegram/draft-stream.test-helpers.js +62 -0
- package/dist/telegram/exec-approvals-handler.js +281 -0
- package/dist/telegram/exec-approvals.js +62 -0
- package/dist/telegram/forum-service-message.js +22 -0
- package/dist/telegram/group-config-helpers.js +10 -0
- package/dist/telegram/lane-delivery-state.js +19 -0
- package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
- package/dist/telegram/lane-delivery.js +2 -0
- package/dist/telegram/normalize.js +37 -0
- package/dist/telegram/onboarding.js +192 -0
- package/dist/telegram/outbound-adapter.js +100 -0
- package/dist/telegram/polling-session.js +275 -0
- package/dist/telegram/runtime.js +3 -0
- package/dist/telegram/sendchataction-401-backoff.js +71 -0
- package/dist/telegram/sequential-key.js +46 -0
- package/dist/telegram/status-issues.js +105 -0
- package/dist/telegram/target-writeback.js +165 -0
- package/dist/telegram/thread-bindings.js +560 -0
- package/dist/utils.js +10 -276
- package/dist/wizard/prompts.js +5 -5
- package/extensions/feishu/src/policy.ts +1 -1
- package/extensions/firecrawl/index.test.ts +82 -0
- package/extensions/firecrawl/index.ts +20 -0
- package/extensions/firecrawl/openclaw.plugin.json +8 -0
- package/extensions/firecrawl/package.json +12 -0
- package/extensions/firecrawl/src/config.ts +159 -0
- package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
- package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
- package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
- package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
- package/package.json +1 -1
- package/dist/.buildstamp +0 -1
- package/dist/acp/bindings-store.js +0 -209
- package/dist/acp/control-plane/runtime-cache.js +0 -54
- package/dist/acp/control-plane/runtime-options.js +0 -215
- package/dist/acp/control-plane/session-actor-queue.js +0 -36
- package/dist/acp/index.js +0 -2
- package/dist/acp/runtime/errors.js +0 -47
- package/dist/acp/runtime/registry.js +0 -86
- package/dist/acp/secret-file.js +0 -22
- package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
- package/dist/agents/bash-process-registry.test-helpers.js +0 -29
- package/dist/agents/bash-tools.exec-approval-request.js +0 -20
- package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
- package/dist/agents/bash-tools.exec-host-node.js +0 -235
- package/dist/agents/checkpoint-manager.js +0 -290
- package/dist/agents/claude-cli-runner.js +0 -3
- package/dist/agents/error-classifier.js +0 -251
- package/dist/agents/live-model-filter.js +0 -84
- package/dist/agents/nvidia-models.js +0 -228
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
- package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
- package/dist/agents/provider/config-loader.js +0 -76
- package/dist/agents/provider/index.js +0 -15
- package/dist/agents/provider/models-dev.js +0 -129
- package/dist/agents/provider/session-binding.js +0 -376
- package/dist/agents/queued-file-writer.js +0 -22
- package/dist/agents/skills/bundled-context.js +0 -23
- package/dist/agents/skills/security.js +0 -211
- package/dist/agents/skills/tools-dir.js +0 -9
- package/dist/agents/skills-install-download.js +0 -290
- package/dist/agents/skills-install-output.js +0 -30
- package/dist/agents/skills-install.download-test-utils.js +0 -36
- package/dist/agents/skills.test-helpers.js +0 -13
- package/dist/agents/subagent-announce-reliability.js +0 -160
- package/dist/agents/subagent-registry.mocks.shared.js +0 -12
- package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
- package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
- package/dist/agents/test-helpers/fast-core-tools.js +0 -8
- package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
- package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
- package/dist/agents/tool-display-common.js +0 -915
- package/dist/agents/tool-policy-shared.js +0 -108
- package/dist/agents/tool-policy.conformance.js +0 -14
- package/dist/agents/tool-result-truncation.js +0 -299
- package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
- package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
- package/dist/agents/tools/discord-actions-presence.js +0 -78
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
- package/dist/discord/discord-improvements.js +0 -167
- package/dist/discord/index.js +0 -2
- package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
- package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
- package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
- package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
- package/dist/whatsapp/normalize.js +0 -66
- package/dist/whatsapp/resolve-outbound-target.js +0 -42
- /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
- /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { logVerbose } from "../../globals.js";
|
|
2
|
+
import { listSkillCommandsForAgents } from "../skill-commands.js";
|
|
3
|
+
import { buildCommandsMessage, buildCommandsMessagePaginated, buildHelpMessage, } from "../status.js";
|
|
4
|
+
import { buildContextReply } from "./commands-context-report.js";
|
|
5
|
+
import { buildExportSessionReply } from "./commands-export-session.js";
|
|
6
|
+
import { buildStatusReply } from "./commands-status.js";
|
|
7
|
+
export const handleHelpCommand = async (params, allowTextCommands) => {
|
|
8
|
+
if (!allowTextCommands) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (params.command.commandBodyNormalized !== "/help") {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (!params.command.isAuthorizedSender) {
|
|
15
|
+
logVerbose(`Ignoring /help from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
16
|
+
return { shouldContinue: false };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
shouldContinue: false,
|
|
20
|
+
reply: { text: buildHelpMessage(params.cfg) },
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export const handleCommandsListCommand = async (params, allowTextCommands) => {
|
|
24
|
+
if (!allowTextCommands) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (params.command.commandBodyNormalized !== "/commands") {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (!params.command.isAuthorizedSender) {
|
|
31
|
+
logVerbose(`Ignoring /commands from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
32
|
+
return { shouldContinue: false };
|
|
33
|
+
}
|
|
34
|
+
const skillCommands = params.skillCommands ??
|
|
35
|
+
listSkillCommandsForAgents({
|
|
36
|
+
cfg: params.cfg,
|
|
37
|
+
agentIds: params.agentId ? [params.agentId] : undefined,
|
|
38
|
+
});
|
|
39
|
+
const surface = params.ctx.Surface;
|
|
40
|
+
if (surface === "telegram") {
|
|
41
|
+
const result = buildCommandsMessagePaginated(params.cfg, skillCommands, {
|
|
42
|
+
page: 1,
|
|
43
|
+
surface,
|
|
44
|
+
});
|
|
45
|
+
if (result.totalPages > 1) {
|
|
46
|
+
return {
|
|
47
|
+
shouldContinue: false,
|
|
48
|
+
reply: {
|
|
49
|
+
text: result.text,
|
|
50
|
+
channelData: {
|
|
51
|
+
telegram: {
|
|
52
|
+
buttons: buildCommandsPaginationKeyboard(result.currentPage, result.totalPages, params.agentId),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
shouldContinue: false,
|
|
60
|
+
reply: { text: result.text },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
shouldContinue: false,
|
|
65
|
+
reply: { text: buildCommandsMessage(params.cfg, skillCommands, { surface }) },
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export function buildCommandsPaginationKeyboard(currentPage, totalPages, agentId) {
|
|
69
|
+
const buttons = [];
|
|
70
|
+
const suffix = agentId ? `:${agentId}` : "";
|
|
71
|
+
if (currentPage > 1) {
|
|
72
|
+
buttons.push({
|
|
73
|
+
text: "◀ Prev",
|
|
74
|
+
callback_data: `commands_page_${currentPage - 1}${suffix}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
buttons.push({
|
|
78
|
+
text: `${currentPage}/${totalPages}`,
|
|
79
|
+
callback_data: `commands_page_noop${suffix}`,
|
|
80
|
+
});
|
|
81
|
+
if (currentPage < totalPages) {
|
|
82
|
+
buttons.push({
|
|
83
|
+
text: "Next ▶",
|
|
84
|
+
callback_data: `commands_page_${currentPage + 1}${suffix}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return [buttons];
|
|
88
|
+
}
|
|
89
|
+
export const handleStatusCommand = async (params, allowTextCommands) => {
|
|
90
|
+
if (!allowTextCommands) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const statusRequested = params.directives.hasStatusDirective || params.command.commandBodyNormalized === "/status";
|
|
94
|
+
if (!statusRequested) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (!params.command.isAuthorizedSender) {
|
|
98
|
+
logVerbose(`Ignoring /status from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
99
|
+
return { shouldContinue: false };
|
|
100
|
+
}
|
|
101
|
+
const reply = await buildStatusReply({
|
|
102
|
+
cfg: params.cfg,
|
|
103
|
+
command: params.command,
|
|
104
|
+
sessionEntry: params.sessionEntry,
|
|
105
|
+
sessionKey: params.sessionKey,
|
|
106
|
+
parentSessionKey: params.ctx.ParentSessionKey,
|
|
107
|
+
sessionScope: params.sessionScope,
|
|
108
|
+
provider: params.provider,
|
|
109
|
+
model: params.model,
|
|
110
|
+
contextTokens: params.contextTokens,
|
|
111
|
+
resolvedThinkLevel: params.resolvedThinkLevel,
|
|
112
|
+
resolvedVerboseLevel: params.resolvedVerboseLevel,
|
|
113
|
+
resolvedReasoningLevel: params.resolvedReasoningLevel,
|
|
114
|
+
resolvedElevatedLevel: params.resolvedElevatedLevel,
|
|
115
|
+
resolveDefaultThinkingLevel: params.resolveDefaultThinkingLevel,
|
|
116
|
+
isGroup: params.isGroup,
|
|
117
|
+
defaultGroupActivation: params.defaultGroupActivation,
|
|
118
|
+
mediaDecisions: params.ctx.MediaUnderstandingDecisions,
|
|
119
|
+
});
|
|
120
|
+
return { shouldContinue: false, reply };
|
|
121
|
+
};
|
|
122
|
+
export const handleContextCommand = async (params, allowTextCommands) => {
|
|
123
|
+
if (!allowTextCommands) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const normalized = params.command.commandBodyNormalized;
|
|
127
|
+
if (normalized !== "/context" && !normalized.startsWith("/context ")) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if (!params.command.isAuthorizedSender) {
|
|
131
|
+
logVerbose(`Ignoring /context from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
132
|
+
return { shouldContinue: false };
|
|
133
|
+
}
|
|
134
|
+
return { shouldContinue: false, reply: await buildContextReply(params) };
|
|
135
|
+
};
|
|
136
|
+
export const handleExportSessionCommand = async (params, allowTextCommands) => {
|
|
137
|
+
if (!allowTextCommands) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const normalized = params.command.commandBodyNormalized;
|
|
141
|
+
if (normalized !== "/export-session" &&
|
|
142
|
+
!normalized.startsWith("/export-session ") &&
|
|
143
|
+
normalized !== "/export" &&
|
|
144
|
+
!normalized.startsWith("/export ")) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (!params.command.isAuthorizedSender) {
|
|
148
|
+
logVerbose(`Ignoring /export-session from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
149
|
+
return { shouldContinue: false };
|
|
150
|
+
}
|
|
151
|
+
return { shouldContinue: false, reply: await buildExportSessionReply(params) };
|
|
152
|
+
};
|
|
153
|
+
export const handleWhoamiCommand = async (params, allowTextCommands) => {
|
|
154
|
+
if (!allowTextCommands) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (params.command.commandBodyNormalized !== "/whoami") {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (!params.command.isAuthorizedSender) {
|
|
161
|
+
logVerbose(`Ignoring /whoami from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
162
|
+
return { shouldContinue: false };
|
|
163
|
+
}
|
|
164
|
+
const senderId = params.ctx.SenderId ?? "";
|
|
165
|
+
const senderUsername = params.ctx.SenderUsername ?? "";
|
|
166
|
+
const lines = ["🧭 Identity", `Channel: ${params.command.channel}`];
|
|
167
|
+
if (senderId) {
|
|
168
|
+
lines.push(`User id: ${senderId}`);
|
|
169
|
+
}
|
|
170
|
+
if (senderUsername) {
|
|
171
|
+
const handle = senderUsername.startsWith("@") ? senderUsername : `@${senderUsername}`;
|
|
172
|
+
lines.push(`Username: ${handle}`);
|
|
173
|
+
}
|
|
174
|
+
if (params.ctx.ChatType === "group" && params.ctx.From) {
|
|
175
|
+
lines.push(`Chat: ${params.ctx.From}`);
|
|
176
|
+
}
|
|
177
|
+
if (params.ctx.MessageThreadId != null) {
|
|
178
|
+
lines.push(`Thread: ${params.ctx.MessageThreadId}`);
|
|
179
|
+
}
|
|
180
|
+
if (senderId) {
|
|
181
|
+
lines.push(`AllowFrom: ${senderId}`);
|
|
182
|
+
}
|
|
183
|
+
return { shouldContinue: false, reply: { text: lines.join("\n") } };
|
|
184
|
+
};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js";
|
|
2
|
+
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
|
3
|
+
import { resolveModelAuthLabel } from "../../agents/model-auth-label.js";
|
|
4
|
+
import { loadModelCatalog } from "../../agents/model-catalog.js";
|
|
5
|
+
import { buildAllowedModelSet, buildModelAliasIndex, normalizeProviderId, resolveConfiguredModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js";
|
|
6
|
+
import { buildModelsKeyboard, buildProviderKeyboard, calculateTotalPages, getModelsPageSize, } from "../../telegram/model-buttons.js";
|
|
7
|
+
const PAGE_SIZE_DEFAULT = 20;
|
|
8
|
+
const PAGE_SIZE_MAX = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Build provider/model data from config and catalog.
|
|
11
|
+
* Exported for reuse by callback handlers.
|
|
12
|
+
*/
|
|
13
|
+
export async function buildModelsProviderData(cfg) {
|
|
14
|
+
const resolvedDefault = resolveConfiguredModelRef({
|
|
15
|
+
cfg,
|
|
16
|
+
defaultProvider: DEFAULT_PROVIDER,
|
|
17
|
+
defaultModel: DEFAULT_MODEL,
|
|
18
|
+
});
|
|
19
|
+
const catalog = await loadModelCatalog({ config: cfg });
|
|
20
|
+
const allowed = buildAllowedModelSet({
|
|
21
|
+
cfg,
|
|
22
|
+
catalog,
|
|
23
|
+
defaultProvider: resolvedDefault.provider,
|
|
24
|
+
defaultModel: resolvedDefault.model,
|
|
25
|
+
});
|
|
26
|
+
const aliasIndex = buildModelAliasIndex({
|
|
27
|
+
cfg,
|
|
28
|
+
defaultProvider: resolvedDefault.provider,
|
|
29
|
+
});
|
|
30
|
+
const byProvider = new Map();
|
|
31
|
+
const add = (p, m) => {
|
|
32
|
+
const key = normalizeProviderId(p);
|
|
33
|
+
const set = byProvider.get(key) ?? new Set();
|
|
34
|
+
set.add(m);
|
|
35
|
+
byProvider.set(key, set);
|
|
36
|
+
};
|
|
37
|
+
const addRawModelRef = (raw) => {
|
|
38
|
+
const trimmed = raw?.trim();
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const resolved = resolveModelRefFromString({
|
|
43
|
+
raw: trimmed,
|
|
44
|
+
defaultProvider: resolvedDefault.provider,
|
|
45
|
+
aliasIndex,
|
|
46
|
+
});
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
add(resolved.ref.provider, resolved.ref.model);
|
|
51
|
+
};
|
|
52
|
+
const addModelConfigEntries = () => {
|
|
53
|
+
const modelConfig = cfg.agents?.defaults?.model;
|
|
54
|
+
if (typeof modelConfig === "string") {
|
|
55
|
+
addRawModelRef(modelConfig);
|
|
56
|
+
}
|
|
57
|
+
else if (modelConfig && typeof modelConfig === "object") {
|
|
58
|
+
addRawModelRef(modelConfig.primary);
|
|
59
|
+
for (const fallback of modelConfig.fallbacks ?? []) {
|
|
60
|
+
addRawModelRef(fallback);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const imageConfig = cfg.agents?.defaults?.imageModel;
|
|
64
|
+
if (typeof imageConfig === "string") {
|
|
65
|
+
addRawModelRef(imageConfig);
|
|
66
|
+
}
|
|
67
|
+
else if (imageConfig && typeof imageConfig === "object") {
|
|
68
|
+
addRawModelRef(imageConfig.primary);
|
|
69
|
+
for (const fallback of imageConfig.fallbacks ?? []) {
|
|
70
|
+
addRawModelRef(fallback);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
for (const entry of allowed.allowedCatalog) {
|
|
75
|
+
add(entry.provider, entry.id);
|
|
76
|
+
}
|
|
77
|
+
// Include config-only allowlist keys that aren't in the curated catalog.
|
|
78
|
+
for (const raw of Object.keys(cfg.agents?.defaults?.models ?? {})) {
|
|
79
|
+
addRawModelRef(raw);
|
|
80
|
+
}
|
|
81
|
+
// Ensure configured defaults/fallbacks/image models show up even when the
|
|
82
|
+
// curated catalog doesn't know about them (custom providers, dev builds, etc.).
|
|
83
|
+
add(resolvedDefault.provider, resolvedDefault.model);
|
|
84
|
+
addModelConfigEntries();
|
|
85
|
+
const providers = [...byProvider.keys()].toSorted();
|
|
86
|
+
return { byProvider, providers, resolvedDefault };
|
|
87
|
+
}
|
|
88
|
+
function formatProviderLine(params) {
|
|
89
|
+
return `- ${params.provider} (${params.count})`;
|
|
90
|
+
}
|
|
91
|
+
function parseModelsArgs(raw) {
|
|
92
|
+
const trimmed = raw.trim();
|
|
93
|
+
if (!trimmed) {
|
|
94
|
+
return { page: 1, pageSize: PAGE_SIZE_DEFAULT, all: false };
|
|
95
|
+
}
|
|
96
|
+
const tokens = trimmed.split(/\s+/g).filter(Boolean);
|
|
97
|
+
const provider = tokens[0]?.trim();
|
|
98
|
+
let page = 1;
|
|
99
|
+
let all = false;
|
|
100
|
+
for (const token of tokens.slice(1)) {
|
|
101
|
+
const lower = token.toLowerCase();
|
|
102
|
+
if (lower === "all" || lower === "--all") {
|
|
103
|
+
all = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (lower.startsWith("page=")) {
|
|
107
|
+
const value = Number.parseInt(lower.slice("page=".length), 10);
|
|
108
|
+
if (Number.isFinite(value) && value > 0) {
|
|
109
|
+
page = value;
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (/^[0-9]+$/.test(lower)) {
|
|
114
|
+
const value = Number.parseInt(lower, 10);
|
|
115
|
+
if (Number.isFinite(value) && value > 0) {
|
|
116
|
+
page = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let pageSize = PAGE_SIZE_DEFAULT;
|
|
121
|
+
for (const token of tokens) {
|
|
122
|
+
const lower = token.toLowerCase();
|
|
123
|
+
if (lower.startsWith("limit=") || lower.startsWith("size=")) {
|
|
124
|
+
const rawValue = lower.slice(lower.indexOf("=") + 1);
|
|
125
|
+
const value = Number.parseInt(rawValue, 10);
|
|
126
|
+
if (Number.isFinite(value) && value > 0) {
|
|
127
|
+
pageSize = Math.min(PAGE_SIZE_MAX, value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
provider: provider ? normalizeProviderId(provider) : undefined,
|
|
133
|
+
page,
|
|
134
|
+
pageSize,
|
|
135
|
+
all,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function resolveProviderLabel(params) {
|
|
139
|
+
const authLabel = resolveModelAuthLabel({
|
|
140
|
+
provider: params.provider,
|
|
141
|
+
cfg: params.cfg,
|
|
142
|
+
sessionEntry: params.sessionEntry,
|
|
143
|
+
agentDir: params.agentDir,
|
|
144
|
+
});
|
|
145
|
+
if (!authLabel || authLabel === "unknown") {
|
|
146
|
+
return params.provider;
|
|
147
|
+
}
|
|
148
|
+
return `${params.provider} \u00b7 \uD83D\uDD11 ${authLabel}`;
|
|
149
|
+
}
|
|
150
|
+
export function formatModelsAvailableHeader(params) {
|
|
151
|
+
const providerLabel = resolveProviderLabel({
|
|
152
|
+
provider: params.provider,
|
|
153
|
+
cfg: params.cfg,
|
|
154
|
+
agentDir: params.agentDir,
|
|
155
|
+
sessionEntry: params.sessionEntry,
|
|
156
|
+
});
|
|
157
|
+
return `Models (${providerLabel}) \u2014 ${params.total} available`;
|
|
158
|
+
}
|
|
159
|
+
export async function resolveModelsCommandReply(params) {
|
|
160
|
+
const body = params.commandBodyNormalized.trim();
|
|
161
|
+
if (!body.startsWith("/models")) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const argText = body.replace(/^\/models\b/i, "").trim();
|
|
165
|
+
const { provider, page, pageSize, all } = parseModelsArgs(argText);
|
|
166
|
+
const { byProvider, providers } = await buildModelsProviderData(params.cfg);
|
|
167
|
+
const isTelegram = params.surface === "telegram";
|
|
168
|
+
// Provider list (no provider specified)
|
|
169
|
+
if (!provider) {
|
|
170
|
+
// For Telegram: show buttons if there are providers
|
|
171
|
+
if (isTelegram && providers.length > 0) {
|
|
172
|
+
const providerInfos = providers.map((p) => ({
|
|
173
|
+
id: p,
|
|
174
|
+
count: byProvider.get(p)?.size ?? 0,
|
|
175
|
+
}));
|
|
176
|
+
const buttons = buildProviderKeyboard(providerInfos);
|
|
177
|
+
const text = "Select a provider:";
|
|
178
|
+
return {
|
|
179
|
+
text,
|
|
180
|
+
channelData: { telegram: { buttons } },
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// Text fallback for non-Telegram surfaces
|
|
184
|
+
const lines = [
|
|
185
|
+
"Providers:",
|
|
186
|
+
...providers.map((p) => formatProviderLine({ provider: p, count: byProvider.get(p)?.size ?? 0 })),
|
|
187
|
+
"",
|
|
188
|
+
"Use: /models <provider>",
|
|
189
|
+
"Switch: /model <provider/model>",
|
|
190
|
+
];
|
|
191
|
+
return { text: lines.join("\n") };
|
|
192
|
+
}
|
|
193
|
+
if (!byProvider.has(provider)) {
|
|
194
|
+
const lines = [
|
|
195
|
+
`Unknown provider: ${provider}`,
|
|
196
|
+
"",
|
|
197
|
+
"Available providers:",
|
|
198
|
+
...providers.map((p) => `- ${p}`),
|
|
199
|
+
"",
|
|
200
|
+
"Use: /models <provider>",
|
|
201
|
+
];
|
|
202
|
+
return { text: lines.join("\n") };
|
|
203
|
+
}
|
|
204
|
+
const models = [...(byProvider.get(provider) ?? new Set())].toSorted();
|
|
205
|
+
const total = models.length;
|
|
206
|
+
const providerLabel = resolveProviderLabel({
|
|
207
|
+
provider,
|
|
208
|
+
cfg: params.cfg,
|
|
209
|
+
agentDir: params.agentDir,
|
|
210
|
+
sessionEntry: params.sessionEntry,
|
|
211
|
+
});
|
|
212
|
+
if (total === 0) {
|
|
213
|
+
const lines = [
|
|
214
|
+
`Models (${providerLabel}) \u2014 none`,
|
|
215
|
+
"",
|
|
216
|
+
"Browse: /models",
|
|
217
|
+
"Switch: /model <provider/model>",
|
|
218
|
+
];
|
|
219
|
+
return { text: lines.join("\n") };
|
|
220
|
+
}
|
|
221
|
+
// For Telegram: use button-based model list with inline keyboard pagination
|
|
222
|
+
if (isTelegram) {
|
|
223
|
+
const telegramPageSize = getModelsPageSize();
|
|
224
|
+
const totalPages = calculateTotalPages(total, telegramPageSize);
|
|
225
|
+
const safePage = Math.max(1, Math.min(page, totalPages));
|
|
226
|
+
const buttons = buildModelsKeyboard({
|
|
227
|
+
provider,
|
|
228
|
+
models,
|
|
229
|
+
currentModel: params.currentModel,
|
|
230
|
+
currentPage: safePage,
|
|
231
|
+
totalPages,
|
|
232
|
+
pageSize: telegramPageSize,
|
|
233
|
+
});
|
|
234
|
+
const text = formatModelsAvailableHeader({
|
|
235
|
+
provider,
|
|
236
|
+
total,
|
|
237
|
+
cfg: params.cfg,
|
|
238
|
+
agentDir: params.agentDir,
|
|
239
|
+
sessionEntry: params.sessionEntry,
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
text,
|
|
243
|
+
channelData: { telegram: { buttons } },
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// Text fallback for non-Telegram surfaces
|
|
247
|
+
const effectivePageSize = all ? total : pageSize;
|
|
248
|
+
const pageCount = effectivePageSize > 0 ? Math.ceil(total / effectivePageSize) : 1;
|
|
249
|
+
const safePage = all ? 1 : Math.max(1, Math.min(page, pageCount));
|
|
250
|
+
if (!all && page !== safePage) {
|
|
251
|
+
const lines = [
|
|
252
|
+
`Page out of range: ${page} (valid: 1-${pageCount})`,
|
|
253
|
+
"",
|
|
254
|
+
`Try: /models ${provider} ${safePage}`,
|
|
255
|
+
`All: /models ${provider} all`,
|
|
256
|
+
];
|
|
257
|
+
return { text: lines.join("\n") };
|
|
258
|
+
}
|
|
259
|
+
const startIndex = (safePage - 1) * effectivePageSize;
|
|
260
|
+
const endIndexExclusive = Math.min(total, startIndex + effectivePageSize);
|
|
261
|
+
const pageModels = models.slice(startIndex, endIndexExclusive);
|
|
262
|
+
const header = `Models (${providerLabel}) \u2014 showing ${startIndex + 1}-${endIndexExclusive} of ${total} (page ${safePage}/${pageCount})`;
|
|
263
|
+
const lines = [header];
|
|
264
|
+
for (const id of pageModels) {
|
|
265
|
+
lines.push(`- ${provider}/${id}`);
|
|
266
|
+
}
|
|
267
|
+
lines.push("", "Switch: /model <provider/model>");
|
|
268
|
+
if (!all && safePage < pageCount) {
|
|
269
|
+
lines.push(`More: /models ${provider} ${safePage + 1}`);
|
|
270
|
+
}
|
|
271
|
+
if (!all) {
|
|
272
|
+
lines.push(`All: /models ${provider} all`);
|
|
273
|
+
}
|
|
274
|
+
const payload = { text: lines.join("\n") };
|
|
275
|
+
return payload;
|
|
276
|
+
}
|
|
277
|
+
export const handleModelsCommand = async (params, allowTextCommands) => {
|
|
278
|
+
if (!allowTextCommands) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const modelsAgentId = params.agentId ??
|
|
282
|
+
resolveSessionAgentId({
|
|
283
|
+
sessionKey: params.sessionKey,
|
|
284
|
+
config: params.cfg,
|
|
285
|
+
});
|
|
286
|
+
const modelsAgentDir = resolveAgentDir(params.cfg, modelsAgentId);
|
|
287
|
+
const reply = await resolveModelsCommandReply({
|
|
288
|
+
cfg: params.cfg,
|
|
289
|
+
commandBodyNormalized: params.command.commandBodyNormalized,
|
|
290
|
+
surface: params.ctx.Surface,
|
|
291
|
+
currentModel: params.model ? `${params.provider}/${params.model}` : undefined,
|
|
292
|
+
agentDir: modelsAgentDir,
|
|
293
|
+
sessionEntry: params.sessionEntry,
|
|
294
|
+
});
|
|
295
|
+
if (!reply) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return { reply, shouldContinue: false };
|
|
299
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles commands registered by plugins, bypassing the LLM agent.
|
|
5
|
+
* This handler is called before built-in command handlers.
|
|
6
|
+
*/
|
|
7
|
+
import { matchPluginCommand, executePluginCommand } from "../../plugins/commands.js";
|
|
8
|
+
/**
|
|
9
|
+
* Handle plugin-registered commands.
|
|
10
|
+
* Returns a result if a plugin command was matched and executed,
|
|
11
|
+
* or null to continue to the next handler.
|
|
12
|
+
*/
|
|
13
|
+
export const handlePluginCommand = async (params, allowTextCommands) => {
|
|
14
|
+
const { command, cfg } = params;
|
|
15
|
+
if (!allowTextCommands)
|
|
16
|
+
return null;
|
|
17
|
+
// Try to match a plugin command
|
|
18
|
+
const match = matchPluginCommand(command.commandBodyNormalized);
|
|
19
|
+
if (!match)
|
|
20
|
+
return null;
|
|
21
|
+
// Execute the plugin command (always returns a result)
|
|
22
|
+
const result = await executePluginCommand({
|
|
23
|
+
command: match.command,
|
|
24
|
+
args: match.args,
|
|
25
|
+
senderId: command.senderId,
|
|
26
|
+
channel: command.channel,
|
|
27
|
+
isAuthorizedSender: command.isAuthorizedSender,
|
|
28
|
+
commandBody: command.commandBodyNormalized,
|
|
29
|
+
config: cfg,
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
shouldContinue: false,
|
|
33
|
+
reply: result,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
|
|
2
|
+
import { logVerbose } from "../../globals.js";
|
|
3
|
+
const PTT_COMMANDS = {
|
|
4
|
+
start: "talk.ptt.start",
|
|
5
|
+
stop: "talk.ptt.stop",
|
|
6
|
+
once: "talk.ptt.once",
|
|
7
|
+
cancel: "talk.ptt.cancel",
|
|
8
|
+
};
|
|
9
|
+
function normalizeNodeKey(value) {
|
|
10
|
+
return value
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
13
|
+
.replace(/^-+/, "")
|
|
14
|
+
.replace(/-+$/, "");
|
|
15
|
+
}
|
|
16
|
+
function isIOSNode(node) {
|
|
17
|
+
const platform = node.platform?.toLowerCase() ?? "";
|
|
18
|
+
const family = node.deviceFamily?.toLowerCase() ?? "";
|
|
19
|
+
return (platform.startsWith("ios") ||
|
|
20
|
+
family.includes("iphone") ||
|
|
21
|
+
family.includes("ipad") ||
|
|
22
|
+
family.includes("ios"));
|
|
23
|
+
}
|
|
24
|
+
async function loadNodes(cfg) {
|
|
25
|
+
try {
|
|
26
|
+
const res = await callGateway({
|
|
27
|
+
method: "node.list",
|
|
28
|
+
params: {},
|
|
29
|
+
config: cfg,
|
|
30
|
+
});
|
|
31
|
+
return Array.isArray(res.nodes) ? res.nodes : [];
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
const res = await callGateway({
|
|
35
|
+
method: "node.pair.list",
|
|
36
|
+
params: {},
|
|
37
|
+
config: cfg,
|
|
38
|
+
});
|
|
39
|
+
return Array.isArray(res.paired) ? res.paired : [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function describeNodes(nodes) {
|
|
43
|
+
return nodes
|
|
44
|
+
.map((node) => node.displayName || node.remoteIp || node.nodeId)
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join(", ");
|
|
47
|
+
}
|
|
48
|
+
function resolveNodeId(nodes, query) {
|
|
49
|
+
const trimmed = String(query ?? "").trim();
|
|
50
|
+
if (trimmed) {
|
|
51
|
+
const qNorm = normalizeNodeKey(trimmed);
|
|
52
|
+
const matches = nodes.filter((node) => {
|
|
53
|
+
if (node.nodeId === trimmed) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (typeof node.remoteIp === "string" && node.remoteIp === trimmed) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const name = typeof node.displayName === "string" ? node.displayName : "";
|
|
60
|
+
if (name && normalizeNodeKey(name) === qNorm) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (trimmed.length >= 6 && node.nodeId.startsWith(trimmed)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
});
|
|
68
|
+
if (matches.length === 1) {
|
|
69
|
+
return matches[0].nodeId;
|
|
70
|
+
}
|
|
71
|
+
const known = describeNodes(nodes);
|
|
72
|
+
if (matches.length === 0) {
|
|
73
|
+
throw new Error(`unknown node: ${trimmed}${known ? ` (known: ${known})` : ""}`);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`ambiguous node: ${trimmed} (matches: ${matches
|
|
76
|
+
.map((node) => node.displayName || node.remoteIp || node.nodeId)
|
|
77
|
+
.join(", ")})`);
|
|
78
|
+
}
|
|
79
|
+
const iosNodes = nodes.filter(isIOSNode);
|
|
80
|
+
const iosConnected = iosNodes.filter((node) => node.connected);
|
|
81
|
+
const iosCandidates = iosConnected.length > 0 ? iosConnected : iosNodes;
|
|
82
|
+
if (iosCandidates.length === 1) {
|
|
83
|
+
return iosCandidates[0].nodeId;
|
|
84
|
+
}
|
|
85
|
+
if (iosCandidates.length > 1) {
|
|
86
|
+
throw new Error(`multiple iOS nodes found (${describeNodes(iosCandidates)}); specify node=<id>`);
|
|
87
|
+
}
|
|
88
|
+
const connected = nodes.filter((node) => node.connected);
|
|
89
|
+
const fallback = connected.length > 0 ? connected : nodes;
|
|
90
|
+
if (fallback.length === 1) {
|
|
91
|
+
return fallback[0].nodeId;
|
|
92
|
+
}
|
|
93
|
+
const known = describeNodes(nodes);
|
|
94
|
+
throw new Error(`node required${known ? ` (known: ${known})` : ""}`);
|
|
95
|
+
}
|
|
96
|
+
function parsePTTArgs(commandBody) {
|
|
97
|
+
const tokens = commandBody.trim().split(/\s+/).slice(1);
|
|
98
|
+
let action;
|
|
99
|
+
let node;
|
|
100
|
+
for (const token of tokens) {
|
|
101
|
+
if (!token) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (token.toLowerCase().startsWith("node=")) {
|
|
105
|
+
node = token.slice("node=".length);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!action) {
|
|
109
|
+
action = token;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { action, node };
|
|
113
|
+
}
|
|
114
|
+
function buildPTTHelpText() {
|
|
115
|
+
return [
|
|
116
|
+
"Usage: /ptt <start|stop|once|cancel> [node=<id>]",
|
|
117
|
+
"Example: /ptt once node=iphone",
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
|
120
|
+
export const handlePTTCommand = async (params, allowTextCommands) => {
|
|
121
|
+
if (!allowTextCommands) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const { command, cfg } = params;
|
|
125
|
+
const normalized = command.commandBodyNormalized.trim();
|
|
126
|
+
if (!normalized.startsWith("/ptt")) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (!command.isAuthorizedSender) {
|
|
130
|
+
logVerbose(`Ignoring /ptt from unauthorized sender: ${command.senderId || "<unknown>"}`);
|
|
131
|
+
return { shouldContinue: false, reply: { text: "PTT requires an authorized sender." } };
|
|
132
|
+
}
|
|
133
|
+
const parsed = parsePTTArgs(normalized);
|
|
134
|
+
const actionKey = parsed.action?.trim().toLowerCase() ?? "";
|
|
135
|
+
const commandId = PTT_COMMANDS[actionKey];
|
|
136
|
+
if (!commandId) {
|
|
137
|
+
return { shouldContinue: false, reply: { text: buildPTTHelpText() } };
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const nodes = await loadNodes(cfg);
|
|
141
|
+
const nodeId = resolveNodeId(nodes, parsed.node);
|
|
142
|
+
const invokeParams = {
|
|
143
|
+
nodeId,
|
|
144
|
+
command: commandId,
|
|
145
|
+
params: {},
|
|
146
|
+
idempotencyKey: randomIdempotencyKey(),
|
|
147
|
+
timeoutMs: 15_000,
|
|
148
|
+
};
|
|
149
|
+
const res = await callGateway({
|
|
150
|
+
method: "node.invoke",
|
|
151
|
+
params: invokeParams,
|
|
152
|
+
config: cfg,
|
|
153
|
+
});
|
|
154
|
+
const payload = res.payload && typeof res.payload === "object" ? res.payload : {};
|
|
155
|
+
const lines = [`PTT ${actionKey} → ${nodeId}`];
|
|
156
|
+
if (typeof payload.status === "string") {
|
|
157
|
+
lines.push(`status: ${payload.status}`);
|
|
158
|
+
}
|
|
159
|
+
if (typeof payload.captureId === "string") {
|
|
160
|
+
lines.push(`captureId: ${payload.captureId}`);
|
|
161
|
+
}
|
|
162
|
+
if (typeof payload.transcript === "string" && payload.transcript.trim()) {
|
|
163
|
+
lines.push(`transcript: ${payload.transcript}`);
|
|
164
|
+
}
|
|
165
|
+
return { shouldContinue: false, reply: { text: lines.join("\n") } };
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
169
|
+
return { shouldContinue: false, reply: { text: `PTT failed: ${message}` } };
|
|
170
|
+
}
|
|
171
|
+
};
|