@phuetz/code-buddy 1.1.0 → 1.2.0
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/README.md +160 -165
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +1 -1
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.js +11 -2
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +464 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
* Supports NIP-04 encrypted direct messages.
|
|
6
6
|
*/
|
|
7
7
|
import { createECDH, createHash, randomBytes } from 'crypto';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
9
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
10
|
+
import { schnorr } from '@noble/curves/secp256k1';
|
|
8
11
|
import { logger } from '../../utils/logger.js';
|
|
9
|
-
import { BaseChannel } from '../core.js';
|
|
12
|
+
import { BaseChannel, } from '../core.js';
|
|
13
|
+
import { ReconnectionManager } from '../reconnection-manager.js';
|
|
10
14
|
export class NostrAdapter {
|
|
11
15
|
static BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
|
12
16
|
static BECH32_GENERATOR = [
|
|
@@ -78,6 +82,15 @@ export class NostrAdapter {
|
|
|
78
82
|
getConfig() {
|
|
79
83
|
return { ...this.config, relays: [...this.config.relays] };
|
|
80
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the configured secret as raw 32 bytes: 64-char hex as-is, `nsec...`
|
|
87
|
+
* bech32 decoded to its 32-byte payload, otherwise a deterministic sha256 of
|
|
88
|
+
* the arbitrary secret. No configured key => a fresh random 32 bytes.
|
|
89
|
+
* Public so {@link NostrChannel} signs under the *same* identity (esp. nsec).
|
|
90
|
+
*/
|
|
91
|
+
getSecretKeyBytes() {
|
|
92
|
+
return this.resolvePrivateKeyBytes();
|
|
93
|
+
}
|
|
81
94
|
resolvePrivateKeyBytes() {
|
|
82
95
|
const raw = this.config.privateKey;
|
|
83
96
|
if (!raw) {
|
|
@@ -201,9 +214,40 @@ export class NostrAdapter {
|
|
|
201
214
|
return chk;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Real Nostr relay channel (NIP-01).
|
|
219
|
+
*
|
|
220
|
+
* Opens persistent WebSocket connections to each configured relay, subscribes
|
|
221
|
+
* to kind-1 (text note) events via a `REQ` frame, and surfaces inbound events
|
|
222
|
+
* as `'message'` events on the channel. Dropped sockets are recovered with the
|
|
223
|
+
* shared {@link ReconnectionManager} (exponential backoff + jitter), the same
|
|
224
|
+
* idiom used by the discord/slack/imessage adapters.
|
|
225
|
+
*
|
|
226
|
+
* Connection semantics are *optimistic*: `connect()` marks the channel
|
|
227
|
+
* connected and returns immediately, opening sockets in the background. This
|
|
228
|
+
* mirrors how a real Nostr client behaves (any one of N relays may be down)
|
|
229
|
+
* and lets the channel come up even when a relay is unreachable.
|
|
230
|
+
*
|
|
231
|
+
* `NostrAdapter` (above) is retained unchanged as the key / bech32 / relay-list
|
|
232
|
+
* utility — its `start()`/`sendDirectMessage()` are legacy in-process stubs
|
|
233
|
+
* kept only for the existing unit tests. The real transport lives here.
|
|
234
|
+
*/
|
|
204
235
|
export class NostrChannel extends BaseChannel {
|
|
205
236
|
adapter;
|
|
206
|
-
|
|
237
|
+
relays = [];
|
|
238
|
+
subId;
|
|
239
|
+
filters;
|
|
240
|
+
maxRetries;
|
|
241
|
+
reconnectDelayMs;
|
|
242
|
+
/** Set while an intentional disconnect is in progress so `close` skips reconnect. */
|
|
243
|
+
shuttingDown = false;
|
|
244
|
+
/** Injectable socket factory (defaults to the Node global `WebSocket`). */
|
|
245
|
+
socketFactory;
|
|
246
|
+
/** Outstanding publishes keyed by event id, resolved on the relay's `OK` ack. */
|
|
247
|
+
pendingPublishes = new Map();
|
|
248
|
+
/** How long to wait for a relay `["OK", id, ...]` ack before timing out. */
|
|
249
|
+
publishTimeoutMs;
|
|
250
|
+
constructor(config, socketFactory) {
|
|
207
251
|
super('nostr', {
|
|
208
252
|
type: 'nostr',
|
|
209
253
|
enabled: config.enabled,
|
|
@@ -215,33 +259,120 @@ export class NostrChannel extends BaseChannel {
|
|
|
215
259
|
rateLimit: config.rateLimit,
|
|
216
260
|
options: config.options,
|
|
217
261
|
});
|
|
262
|
+
const relayUrls = [
|
|
263
|
+
...(Array.isArray(config.relays) ? config.relays : []),
|
|
264
|
+
...(config.relay ? [config.relay] : []),
|
|
265
|
+
].filter((u) => typeof u === 'string' && u.length > 0);
|
|
218
266
|
this.adapter = new NostrAdapter({
|
|
219
267
|
privateKey: config.privateKey,
|
|
220
|
-
relays:
|
|
268
|
+
relays: relayUrls,
|
|
221
269
|
});
|
|
270
|
+
this.filters = config.filters ?? {};
|
|
271
|
+
this.maxRetries = config.maxRetries ?? 10;
|
|
272
|
+
this.reconnectDelayMs = config.reconnectDelayMs ?? 1000;
|
|
273
|
+
this.publishTimeoutMs = config.publishTimeoutMs ?? 8000;
|
|
274
|
+
this.subId = `cb-${randomBytes(6).toString('hex')}`;
|
|
275
|
+
this.socketFactory =
|
|
276
|
+
socketFactory ??
|
|
277
|
+
((url) => new globalThis.WebSocket(url));
|
|
278
|
+
this.relays = relayUrls.map((url) => ({
|
|
279
|
+
url,
|
|
280
|
+
socket: null,
|
|
281
|
+
reconnection: new ReconnectionManager(`nostr:${url}`, {
|
|
282
|
+
maxRetries: this.maxRetries,
|
|
283
|
+
initialDelayMs: this.reconnectDelayMs,
|
|
284
|
+
maxDelayMs: 60000,
|
|
285
|
+
}),
|
|
286
|
+
}));
|
|
222
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Optimistically connect: mark the channel up, emit `'connected'`, then open
|
|
290
|
+
* every relay socket in the background. Resolves immediately — it never waits
|
|
291
|
+
* on a socket `'open'`, so an unreachable relay can't block startup.
|
|
292
|
+
*/
|
|
223
293
|
async connect() {
|
|
224
|
-
|
|
294
|
+
this.shuttingDown = false;
|
|
225
295
|
this.status.connected = true;
|
|
226
296
|
this.status.authenticated = true;
|
|
227
297
|
this.status.lastActivity = new Date();
|
|
228
298
|
this.emit('connected', this.type);
|
|
299
|
+
for (const relay of this.relays) {
|
|
300
|
+
this.openRelay(relay);
|
|
301
|
+
}
|
|
229
302
|
}
|
|
230
303
|
async disconnect() {
|
|
231
304
|
if (!this.status.connected)
|
|
232
305
|
return;
|
|
233
|
-
|
|
306
|
+
this.shuttingDown = true;
|
|
307
|
+
for (const relay of this.relays) {
|
|
308
|
+
relay.reconnection.cancel();
|
|
309
|
+
this.closeSocket(relay);
|
|
310
|
+
}
|
|
311
|
+
// Fail any in-flight publishes so awaiting `send()` calls can't hang.
|
|
312
|
+
for (const pending of this.pendingPublishes.values()) {
|
|
313
|
+
pending.resolve({ accepted: false, message: 'channel disconnected', relayUrl: '' });
|
|
314
|
+
}
|
|
315
|
+
this.pendingPublishes.clear();
|
|
234
316
|
this.status.connected = false;
|
|
317
|
+
this.status.authenticated = false;
|
|
235
318
|
this.status.lastActivity = new Date();
|
|
236
319
|
this.emit('disconnected', this.type);
|
|
237
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Build, BIP-340 sign, and publish a Nostr kind-1 event for the outbound
|
|
323
|
+
* message.
|
|
324
|
+
*
|
|
325
|
+
* When a secret key is configured we derive the x-only `pubkey` and the
|
|
326
|
+
* Schnorr `sig` via `@noble/curves` (audited secp256k1/BIP-340), compute the
|
|
327
|
+
* canonical sha256 `id`, publish `["EVENT", event]` to every connected relay,
|
|
328
|
+
* and resolve from the relay's `["OK", id, accepted, msg]` acknowledgement.
|
|
329
|
+
*
|
|
330
|
+
* HONEST GATE: if NO secret key is configured we do NOT sign with a throwaway
|
|
331
|
+
* key — we return the same clear error as before. Live publishing is gated
|
|
332
|
+
* only on the operator providing their nsec/hex key + a reachable relay.
|
|
333
|
+
*/
|
|
238
334
|
async send(message) {
|
|
239
335
|
try {
|
|
240
|
-
|
|
336
|
+
// Honesty guard: never sign with the random fallback key. A configured
|
|
337
|
+
// key is required to produce a publishable, attributable event.
|
|
338
|
+
if (!this.adapter.getConfig().privateKey) {
|
|
339
|
+
const unsigned = this.buildUnsignedEvent(message.content);
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
messageId: unsigned.id,
|
|
343
|
+
error: 'Nostr send requires a configured secret key / signer (no key configured)',
|
|
344
|
+
timestamp: new Date(),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const event = this.buildSignedEvent(message.content);
|
|
241
348
|
this.status.lastActivity = new Date();
|
|
349
|
+
const sockets = this.relays
|
|
350
|
+
.map((r) => r.socket)
|
|
351
|
+
.filter((s) => s !== null && s.readyState === 1 /* OPEN */);
|
|
352
|
+
if (sockets.length === 0) {
|
|
353
|
+
return {
|
|
354
|
+
success: false,
|
|
355
|
+
messageId: event.id,
|
|
356
|
+
error: 'Nostr send failed: no relay socket is open',
|
|
357
|
+
timestamp: new Date(),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const ack = await this.publishToRelays(event, sockets);
|
|
361
|
+
if (ack.accepted) {
|
|
362
|
+
logger.debug('NostrChannel: relay accepted published event', {
|
|
363
|
+
id: event.id,
|
|
364
|
+
relay: ack.relayUrl,
|
|
365
|
+
});
|
|
366
|
+
return {
|
|
367
|
+
success: true,
|
|
368
|
+
messageId: event.id,
|
|
369
|
+
timestamp: new Date(),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
242
372
|
return {
|
|
243
|
-
success:
|
|
244
|
-
messageId:
|
|
373
|
+
success: false,
|
|
374
|
+
messageId: event.id,
|
|
375
|
+
error: ack.message || 'Nostr relay rejected the event',
|
|
245
376
|
timestamp: new Date(),
|
|
246
377
|
};
|
|
247
378
|
}
|
|
@@ -253,6 +384,263 @@ export class NostrChannel extends BaseChannel {
|
|
|
253
384
|
};
|
|
254
385
|
}
|
|
255
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Publish a signed event to every open relay socket and resolve from the
|
|
389
|
+
* FIRST `["OK", id, ...]` acknowledgement (or a timeout). The pending entry is
|
|
390
|
+
* keyed by event id and resolved by {@link handleRelayData}'s `OK` branch.
|
|
391
|
+
*/
|
|
392
|
+
publishToRelays(event, sockets) {
|
|
393
|
+
return new Promise((resolve) => {
|
|
394
|
+
const timer = setTimeout(() => {
|
|
395
|
+
this.pendingPublishes.delete(event.id);
|
|
396
|
+
resolve({ accepted: false, message: 'timed out waiting for relay OK', relayUrl: '' });
|
|
397
|
+
}, this.publishTimeoutMs);
|
|
398
|
+
// Don't keep the event loop alive solely for this ack timer.
|
|
399
|
+
if (typeof timer.unref === 'function') {
|
|
400
|
+
timer.unref();
|
|
401
|
+
}
|
|
402
|
+
this.pendingPublishes.set(event.id, {
|
|
403
|
+
resolve: (ok) => {
|
|
404
|
+
clearTimeout(timer);
|
|
405
|
+
this.pendingPublishes.delete(event.id);
|
|
406
|
+
resolve(ok);
|
|
407
|
+
},
|
|
408
|
+
timer,
|
|
409
|
+
});
|
|
410
|
+
const frame = JSON.stringify(['EVENT', event]);
|
|
411
|
+
for (const socket of sockets) {
|
|
412
|
+
try {
|
|
413
|
+
socket.send(frame);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
logger.debug('NostrChannel: failed to send EVENT frame', {
|
|
417
|
+
error: error instanceof Error ? error.message : String(error),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
// ============================================================================
|
|
424
|
+
// Relay socket lifecycle
|
|
425
|
+
// ============================================================================
|
|
426
|
+
openRelay(relay) {
|
|
427
|
+
let socket;
|
|
428
|
+
try {
|
|
429
|
+
socket = this.socketFactory(relay.url);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
logger.warn('NostrChannel: failed to construct relay socket', {
|
|
433
|
+
url: relay.url,
|
|
434
|
+
error: error instanceof Error ? error.message : String(error),
|
|
435
|
+
});
|
|
436
|
+
this.scheduleRelayReconnect(relay);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
relay.socket = socket;
|
|
440
|
+
socket.addEventListener('open', () => {
|
|
441
|
+
logger.debug('NostrChannel: relay open', { url: relay.url });
|
|
442
|
+
relay.reconnection.onConnected();
|
|
443
|
+
this.status.connected = true;
|
|
444
|
+
this.status.lastActivity = new Date();
|
|
445
|
+
this.sendSubscription(socket);
|
|
446
|
+
});
|
|
447
|
+
socket.addEventListener('message', (event) => {
|
|
448
|
+
const data = event.data;
|
|
449
|
+
this.handleRelayData(relay, data);
|
|
450
|
+
});
|
|
451
|
+
socket.addEventListener('error', (event) => {
|
|
452
|
+
const err = this.errorFromEvent(event);
|
|
453
|
+
logger.debug('NostrChannel: relay socket error', { url: relay.url, error: err.message });
|
|
454
|
+
// BaseChannel is an EventEmitter — only emit 'error' when someone listens,
|
|
455
|
+
// otherwise EventEmitter throws and crashes the process.
|
|
456
|
+
if (this.listenerCount('error') > 0) {
|
|
457
|
+
this.emit('error', this.type, err);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
socket.addEventListener('close', () => {
|
|
461
|
+
relay.socket = null;
|
|
462
|
+
if (this.shuttingDown)
|
|
463
|
+
return;
|
|
464
|
+
logger.debug('NostrChannel: relay closed, scheduling reconnect', { url: relay.url });
|
|
465
|
+
this.scheduleRelayReconnect(relay);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
scheduleRelayReconnect(relay) {
|
|
469
|
+
if (this.shuttingDown)
|
|
470
|
+
return;
|
|
471
|
+
relay.reconnection.scheduleReconnect(async () => {
|
|
472
|
+
if (this.shuttingDown)
|
|
473
|
+
return;
|
|
474
|
+
this.openRelay(relay);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
sendSubscription(socket) {
|
|
478
|
+
const filter = { kinds: [1], ...this.filters };
|
|
479
|
+
const req = JSON.stringify(['REQ', this.subId, filter]);
|
|
480
|
+
try {
|
|
481
|
+
socket.send(req);
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
logger.debug('NostrChannel: failed to send REQ', {
|
|
485
|
+
error: error instanceof Error ? error.message : String(error),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
handleRelayData(relay, data) {
|
|
490
|
+
let frame;
|
|
491
|
+
try {
|
|
492
|
+
const text = typeof data === 'string' ? data : String(data);
|
|
493
|
+
frame = JSON.parse(text);
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
logger.debug('NostrChannel: ignoring non-JSON relay frame', { url: relay.url });
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (!Array.isArray(frame) || frame.length === 0)
|
|
500
|
+
return;
|
|
501
|
+
const kind = frame[0];
|
|
502
|
+
if (kind === 'EVENT') {
|
|
503
|
+
// ["EVENT", <subId>, <event>]
|
|
504
|
+
const event = frame[2];
|
|
505
|
+
if (event && typeof event === 'object') {
|
|
506
|
+
this.emitInbound(event, relay.url);
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (kind === 'EOSE') {
|
|
511
|
+
// End of stored events — nothing to do.
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (kind === 'NOTICE') {
|
|
515
|
+
logger.info('NostrChannel: relay NOTICE', { url: relay.url, message: String(frame[1] ?? '') });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (kind === 'OK') {
|
|
519
|
+
// Publish acknowledgement: ["OK", <eventId>, <ok>, <message>]
|
|
520
|
+
const eventId = typeof frame[1] === 'string' ? frame[1] : '';
|
|
521
|
+
const accepted = frame[2] === true;
|
|
522
|
+
const message = typeof frame[3] === 'string' ? frame[3] : '';
|
|
523
|
+
logger.debug('NostrChannel: relay OK', { url: relay.url, eventId, accepted, message });
|
|
524
|
+
const pending = this.pendingPublishes.get(eventId);
|
|
525
|
+
if (pending) {
|
|
526
|
+
pending.resolve({ accepted, message, relayUrl: relay.url });
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
emitInbound(event, relayUrl) {
|
|
532
|
+
const content = typeof event.content === 'string' ? event.content : '';
|
|
533
|
+
const pubkey = typeof event.pubkey === 'string' ? event.pubkey : 'unknown';
|
|
534
|
+
const id = typeof event.id === 'string' ? event.id : `nostr-${Date.now()}`;
|
|
535
|
+
const createdAt = typeof event.created_at === 'number' ? event.created_at : Math.floor(Date.now() / 1000);
|
|
536
|
+
const inbound = {
|
|
537
|
+
id,
|
|
538
|
+
channel: {
|
|
539
|
+
id: relayUrl,
|
|
540
|
+
type: 'nostr',
|
|
541
|
+
name: relayUrl,
|
|
542
|
+
isDM: false,
|
|
543
|
+
},
|
|
544
|
+
sender: {
|
|
545
|
+
id: pubkey,
|
|
546
|
+
username: pubkey,
|
|
547
|
+
},
|
|
548
|
+
content,
|
|
549
|
+
contentType: 'text',
|
|
550
|
+
timestamp: new Date(createdAt * 1000),
|
|
551
|
+
raw: event,
|
|
552
|
+
};
|
|
553
|
+
this.status.lastActivity = new Date();
|
|
554
|
+
const parsed = this.parseCommand(inbound);
|
|
555
|
+
this.emit('message', parsed);
|
|
556
|
+
if (parsed.isCommand) {
|
|
557
|
+
this.emit('command', parsed);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Build the canonical unsigned Nostr event for `content`. The `id` is the
|
|
562
|
+
* sha256 of the NIP-01 serialization `[0, pubkey, created_at, kind, tags,
|
|
563
|
+
* content]`. Used for the honest no-key error path (still surfaces a real id).
|
|
564
|
+
*/
|
|
565
|
+
buildUnsignedEvent(content) {
|
|
566
|
+
const pubkey = this.getPublicKeyHex();
|
|
567
|
+
const created_at = Math.floor(Date.now() / 1000);
|
|
568
|
+
const kind = 1;
|
|
569
|
+
const tags = [];
|
|
570
|
+
const serialized = JSON.stringify([0, pubkey, created_at, kind, tags, content]);
|
|
571
|
+
const id = bytesToHex(sha256(new TextEncoder().encode(serialized)));
|
|
572
|
+
return { id, pubkey, created_at, kind, tags, content };
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Build a fully BIP-340 signed Nostr kind-1 event for `content`.
|
|
576
|
+
*
|
|
577
|
+
* `pubkey` and `sig` are derived from the same secp256k1 secret via
|
|
578
|
+
* `@noble/curves`' Schnorr primitives: `schnorr.getPublicKey` yields the
|
|
579
|
+
* x-only pubkey (correct BIP-340 even-y normalization), `id` is the sha256 of
|
|
580
|
+
* the NIP-01 serialization, and `sig = schnorr.sign(id, sk)`. Nostr signs the
|
|
581
|
+
* 32-byte id directly (no extra pre-hash), which is exactly noble's raw-message
|
|
582
|
+
* contract — so `schnorr.verify(sig, id, pubkey)` round-trips.
|
|
583
|
+
*/
|
|
584
|
+
buildSignedEvent(content) {
|
|
585
|
+
const sk = this.resolvePrivateKeyBytes();
|
|
586
|
+
const pubkey = bytesToHex(schnorr.getPublicKey(sk));
|
|
587
|
+
const created_at = Math.floor(Date.now() / 1000);
|
|
588
|
+
const kind = 1;
|
|
589
|
+
const tags = [];
|
|
590
|
+
const serialized = JSON.stringify([0, pubkey, created_at, kind, tags, content]);
|
|
591
|
+
const id = bytesToHex(sha256(new TextEncoder().encode(serialized)));
|
|
592
|
+
const sig = bytesToHex(schnorr.sign(id, sk));
|
|
593
|
+
return { id, pubkey, created_at, kind, tags, content, sig };
|
|
594
|
+
}
|
|
595
|
+
/** x-only 32-byte public key as lowercase hex (Nostr event `pubkey` field). */
|
|
596
|
+
getPublicKeyHex() {
|
|
597
|
+
return bytesToHex(schnorr.getPublicKey(this.resolvePrivateKeyBytes()));
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Resolve the signing secret as raw 32 bytes. Delegates to the adapter's
|
|
601
|
+
* canonical resolver so hex AND `nsec...` bech32 keys decode to the SAME
|
|
602
|
+
* identity the operator configured (a duplicated resolver here previously
|
|
603
|
+
* dropped the nsec branch and silently signed under a sha256-derived key).
|
|
604
|
+
*/
|
|
605
|
+
resolvePrivateKeyBytes() {
|
|
606
|
+
return this.adapter.getSecretKeyBytes();
|
|
607
|
+
}
|
|
608
|
+
closeSocket(relay) {
|
|
609
|
+
const socket = relay.socket;
|
|
610
|
+
relay.socket = null;
|
|
611
|
+
if (!socket)
|
|
612
|
+
return;
|
|
613
|
+
try {
|
|
614
|
+
// Prefer terminate(): the socket may still be CONNECTING against a dead
|
|
615
|
+
// relay, where close() would hang waiting for a handshake that won't come.
|
|
616
|
+
if (typeof socket.terminate === 'function') {
|
|
617
|
+
socket.terminate();
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
socket.close();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
logger.debug('NostrChannel: error closing relay socket', {
|
|
625
|
+
url: relay.url,
|
|
626
|
+
error: error instanceof Error ? error.message : String(error),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
errorFromEvent(event) {
|
|
631
|
+
if (event instanceof Error)
|
|
632
|
+
return event;
|
|
633
|
+
const maybe = event;
|
|
634
|
+
if (maybe?.error instanceof Error)
|
|
635
|
+
return maybe.error;
|
|
636
|
+
if (typeof maybe?.message === 'string')
|
|
637
|
+
return new Error(maybe.message);
|
|
638
|
+
return new Error('Nostr relay socket error');
|
|
639
|
+
}
|
|
640
|
+
/** Expose the configured relay URLs (post construction). */
|
|
641
|
+
getRelayUrls() {
|
|
642
|
+
return this.relays.map((r) => r.url);
|
|
643
|
+
}
|
|
256
644
|
}
|
|
257
645
|
export default NostrAdapter;
|
|
258
646
|
//# sourceMappingURL=index.js.map
|
|
@@ -99,6 +99,9 @@ export class NtfyChannel extends BaseChannel {
|
|
|
99
99
|
tags: config.tags,
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
// REST adapter — outbound messages are one-shot HTTP POSTs to an ntfy topic;
|
|
103
|
+
// there is no persistent subscription stream held open here, so reconnection
|
|
104
|
+
// (ReconnectionManager) is N/A.
|
|
102
105
|
async connect() {
|
|
103
106
|
await this.adapter.start();
|
|
104
107
|
this.status.connected = true;
|
|
@@ -97,6 +97,9 @@ export class QQChannel extends BaseChannel {
|
|
|
97
97
|
autoEscape: config.autoEscape,
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
+
// REST adapter — outbound messages are one-shot HTTP POSTs to a OneBot v11
|
|
101
|
+
// gateway; there is no persistent connection held open here, so reconnection
|
|
102
|
+
// (ReconnectionManager) is N/A.
|
|
100
103
|
async connect() {
|
|
101
104
|
await this.adapter.start();
|
|
102
105
|
this.status.connected = true;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-Platform Slash-Command Parity Manifest
|
|
3
|
+
*
|
|
4
|
+
* Defines the expected slash commands per messaging platform and
|
|
5
|
+
* compares them against the actual registered commands per adapter.
|
|
6
|
+
* Returns a machine-readable readiness report for each platform.
|
|
7
|
+
*/
|
|
8
|
+
import type { ChannelType, BaseChannel } from './core.js';
|
|
9
|
+
import { ChannelManager } from './core.js';
|
|
10
|
+
/**
|
|
11
|
+
* Describes a single expected slash command on a platform.
|
|
12
|
+
*/
|
|
13
|
+
export interface SlashCommandSpec {
|
|
14
|
+
/** Command name (without leading /) */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Human-readable description */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Whether this command is required for parity (default: true) */
|
|
19
|
+
required?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Per-platform parity result for a single command.
|
|
23
|
+
*/
|
|
24
|
+
export interface SlashCommandParityEntry {
|
|
25
|
+
/** Command name */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Whether this command is present on the platform adapter */
|
|
28
|
+
present: boolean;
|
|
29
|
+
/** Whether this command is required */
|
|
30
|
+
required: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Per-platform parity report.
|
|
34
|
+
*/
|
|
35
|
+
export interface PlatformSlashParityReport {
|
|
36
|
+
/** Platform identifier */
|
|
37
|
+
platform: ChannelType;
|
|
38
|
+
/** Whether the platform adapter is registered in the channel manager */
|
|
39
|
+
adapterRegistered: boolean;
|
|
40
|
+
/** Total expected commands */
|
|
41
|
+
expectedCount: number;
|
|
42
|
+
/** Number of commands present on the adapter */
|
|
43
|
+
presentCount: number;
|
|
44
|
+
/** Number of missing required commands */
|
|
45
|
+
missingRequiredCount: number;
|
|
46
|
+
/** Number of missing optional commands */
|
|
47
|
+
missingOptionalCount: number;
|
|
48
|
+
/** Per-command parity entries */
|
|
49
|
+
commands: SlashCommandParityEntry[];
|
|
50
|
+
/** Overall parity status */
|
|
51
|
+
status: 'full' | 'partial' | 'none' | 'no-adapter';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Full parity manifest across all platforms.
|
|
55
|
+
*/
|
|
56
|
+
export interface SlashParityManifest {
|
|
57
|
+
/** Generation timestamp */
|
|
58
|
+
generatedAt: string;
|
|
59
|
+
/** Overall parity status */
|
|
60
|
+
ok: boolean;
|
|
61
|
+
/** Total platforms checked */
|
|
62
|
+
totalPlatforms: number;
|
|
63
|
+
/** Number of platforms with full parity */
|
|
64
|
+
fullParityCount: number;
|
|
65
|
+
/** Number of platforms with partial parity */
|
|
66
|
+
partialParityCount: number;
|
|
67
|
+
/** Number of platforms with no parity (missing adapter) */
|
|
68
|
+
noAdapterCount: number;
|
|
69
|
+
/** Per-platform reports */
|
|
70
|
+
platforms: PlatformSlashParityReport[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The canonical set of slash commands expected on each platform.
|
|
74
|
+
*
|
|
75
|
+
* These represent the Code Buddy bot commands that users should be
|
|
76
|
+
* able to invoke from each messaging platform. The manifest is
|
|
77
|
+
* intentionally conservative — platforms may support additional
|
|
78
|
+
* commands beyond what is listed here.
|
|
79
|
+
*/
|
|
80
|
+
export declare const EXPECTED_SLASH_COMMANDS: Record<string, SlashCommandSpec[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Interface for adapters that expose their registered commands.
|
|
83
|
+
*
|
|
84
|
+
* Channel adapters can optionally implement this interface to
|
|
85
|
+
* allow the parity checker to extract their actual registered
|
|
86
|
+
* commands. Otherwise, fallback heuristics are used.
|
|
87
|
+
*/
|
|
88
|
+
export interface SlashCommandProvider {
|
|
89
|
+
/** Return the list of registered slash command names (without leading /) */
|
|
90
|
+
getRegisteredCommands(): string[];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Extract the actual registered commands from a channel adapter.
|
|
94
|
+
*
|
|
95
|
+
* Uses the SlashCommandProvider interface if available, otherwise
|
|
96
|
+
* returns an empty array (no commands can be detected).
|
|
97
|
+
*/
|
|
98
|
+
export declare function extractActualCommands(channel: BaseChannel): string[];
|
|
99
|
+
/**
|
|
100
|
+
* Build a parity report for a single platform.
|
|
101
|
+
*
|
|
102
|
+
* @param platform - The platform channel type
|
|
103
|
+
* @param expectedCommands - The expected slash commands
|
|
104
|
+
* @param actualCommands - The actual registered commands
|
|
105
|
+
* @param adapterRegistered - Whether the adapter is in the ChannelManager
|
|
106
|
+
*/
|
|
107
|
+
export declare function buildPlatformParityReport(platform: ChannelType, expectedCommands: SlashCommandSpec[], actualCommands: string[], adapterRegistered: boolean): PlatformSlashParityReport;
|
|
108
|
+
/**
|
|
109
|
+
* Build the full slash-command parity manifest across all tracked platforms.
|
|
110
|
+
*
|
|
111
|
+
* @param manager - Optional ChannelManager instance (defaults to singleton)
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildSlashParityManifest(manager?: ChannelManager): SlashParityManifest;
|
|
114
|
+
/**
|
|
115
|
+
* Render the parity manifest as a human-readable text report.
|
|
116
|
+
*/
|
|
117
|
+
export declare function renderSlashParityManifest(manifest: SlashParityManifest): string;
|