@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
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Nextcloud Talk Channel Adapter
|
|
3
3
|
*
|
|
4
|
-
* Connects to Nextcloud Talk for messaging
|
|
5
|
-
*
|
|
4
|
+
* Connects to Nextcloud Talk (Spreed) for messaging via the real Spreed chat
|
|
5
|
+
* REST API using an HTTP long-poll receive loop (`lookIntoFuture=1`), with
|
|
6
|
+
* auto-reconnect through the shared ReconnectionManager (same idiom as
|
|
7
|
+
* imessage/irc/discord/slack).
|
|
6
8
|
*/
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
7
10
|
import { logger } from '../../utils/logger.js';
|
|
8
|
-
import { BaseChannel } from '../core.js';
|
|
11
|
+
import { BaseChannel, } from '../core.js';
|
|
12
|
+
import { ReconnectionManager } from '../reconnection-manager.js';
|
|
9
13
|
export class NextcloudTalkAdapter {
|
|
10
14
|
config;
|
|
11
15
|
running = false;
|
|
@@ -64,8 +68,325 @@ export class NextcloudTalkAdapter {
|
|
|
64
68
|
return { ...this.config };
|
|
65
69
|
}
|
|
66
70
|
}
|
|
71
|
+
const SPREED_CHAT_PATH = '/ocs/v2.php/apps/spreed/api/v1/chat';
|
|
72
|
+
/**
|
|
73
|
+
* Real Nextcloud Talk (Spreed) client. Holds one outstanding long-poll request
|
|
74
|
+
* against `chat/<roomToken>?lookIntoFuture=1` at a time; the server returns 200
|
|
75
|
+
* with new messages or 304 on timeout. Drops are recovered via the shared
|
|
76
|
+
* ReconnectionManager. This is a genuinely persistent connection model — the
|
|
77
|
+
* same shape as imessage/irc.
|
|
78
|
+
*/
|
|
79
|
+
export class NextcloudTalkClient extends EventEmitter {
|
|
80
|
+
baseUrl;
|
|
81
|
+
username;
|
|
82
|
+
password;
|
|
83
|
+
bearer;
|
|
84
|
+
roomToken;
|
|
85
|
+
pollTimeoutSecs;
|
|
86
|
+
running = false;
|
|
87
|
+
/** True while an intentional disconnect is in progress — suppresses reconnect. */
|
|
88
|
+
closing = false;
|
|
89
|
+
reconnecting = false;
|
|
90
|
+
lastKnownMessageId = 0;
|
|
91
|
+
/** AbortController for the in-flight long-poll request. */
|
|
92
|
+
pollAbort = null;
|
|
93
|
+
reconnectionManager;
|
|
94
|
+
constructor(config) {
|
|
95
|
+
super();
|
|
96
|
+
this.baseUrl = config.url.replace(/\/+$/, '');
|
|
97
|
+
this.username = config.username;
|
|
98
|
+
this.password = config.appPassword ?? config.password;
|
|
99
|
+
this.bearer = config.bearer ?? config.token;
|
|
100
|
+
this.roomToken = config.roomToken ?? '';
|
|
101
|
+
this.pollTimeoutSecs = config.pollTimeoutSecs ?? 30;
|
|
102
|
+
this.reconnectionManager = new ReconnectionManager('nextcloud-talk', {
|
|
103
|
+
maxRetries: config.maxRetries ?? 10,
|
|
104
|
+
initialDelayMs: config.retryDelayMs ?? 2000,
|
|
105
|
+
maxDelayMs: 60000,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
isRunning() {
|
|
109
|
+
return this.running;
|
|
110
|
+
}
|
|
111
|
+
getRoomToken() {
|
|
112
|
+
return this.roomToken;
|
|
113
|
+
}
|
|
114
|
+
// --------------------------------------------------------------------------
|
|
115
|
+
// Lifecycle
|
|
116
|
+
// --------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Begin the long-poll receive loop. Returns immediately — `connect()` does
|
|
119
|
+
* NOT block on the first poll (a long-poll can hold open up to
|
|
120
|
+
* `pollTimeoutSecs`). The first successful poll (200 or 304) emits
|
|
121
|
+
* 'connected'.
|
|
122
|
+
*/
|
|
123
|
+
start() {
|
|
124
|
+
if (this.running) {
|
|
125
|
+
throw new Error('NextcloudTalkClient is already running');
|
|
126
|
+
}
|
|
127
|
+
this.running = true;
|
|
128
|
+
this.closing = false;
|
|
129
|
+
// Fresh session — clear any inherited backoff state.
|
|
130
|
+
this.reconnectionManager.onConnected();
|
|
131
|
+
logger.debug('NextcloudTalkClient: starting long-poll loop', {
|
|
132
|
+
url: this.baseUrl,
|
|
133
|
+
room: this.roomToken,
|
|
134
|
+
});
|
|
135
|
+
void this.runPollLoop();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Stop the loop, abort the in-flight request, and cancel any pending
|
|
139
|
+
* reconnect. Idempotent.
|
|
140
|
+
*/
|
|
141
|
+
stop() {
|
|
142
|
+
if (!this.running && !this.closing) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.closing = true;
|
|
146
|
+
this.running = false;
|
|
147
|
+
this.reconnecting = false;
|
|
148
|
+
this.reconnectionManager.cancel();
|
|
149
|
+
if (this.pollAbort) {
|
|
150
|
+
this.pollAbort.abort();
|
|
151
|
+
this.pollAbort = null;
|
|
152
|
+
}
|
|
153
|
+
logger.debug('NextcloudTalkClient: stopped');
|
|
154
|
+
}
|
|
155
|
+
// --------------------------------------------------------------------------
|
|
156
|
+
// Receive (long-poll)
|
|
157
|
+
// --------------------------------------------------------------------------
|
|
158
|
+
async runPollLoop() {
|
|
159
|
+
while (this.running && !this.closing) {
|
|
160
|
+
try {
|
|
161
|
+
await this.pollOnce();
|
|
162
|
+
// A successful poll (messages or 304 timeout) means the link is
|
|
163
|
+
// healthy — reset shared backoff so a future drop starts at delay 0.
|
|
164
|
+
this.reconnectionManager.onConnected();
|
|
165
|
+
this.reconnecting = false;
|
|
166
|
+
this.onConnectedOnce();
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
// Intentional teardown — exit silently, never reconnect.
|
|
170
|
+
if (this.closing || !this.running || this.isAbortError(err)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
174
|
+
logger.warn('NextcloudTalkClient: poll error, scheduling reconnect', {
|
|
175
|
+
error: error.message,
|
|
176
|
+
});
|
|
177
|
+
// Surface the error, but only when a listener exists — a bare
|
|
178
|
+
// EventEmitter 'error' with no listener throws and would abort the
|
|
179
|
+
// reconnect we're about to schedule. Scheduling comes first regardless.
|
|
180
|
+
this.scheduleReconnect();
|
|
181
|
+
if (this.listenerCount('error') > 0) {
|
|
182
|
+
this.emit('error', error);
|
|
183
|
+
}
|
|
184
|
+
// Hand control to the ReconnectionManager — it owns the retry cadence.
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Issue one long-poll request. Resolves on 200 (with messages emitted) or 304
|
|
191
|
+
* (timeout, no messages). Throws on network failure / non-2xx-non-304 status
|
|
192
|
+
* (a real drop → triggers reconnect).
|
|
193
|
+
*/
|
|
194
|
+
async pollOnce() {
|
|
195
|
+
const params = new URLSearchParams({
|
|
196
|
+
lookIntoFuture: '1',
|
|
197
|
+
timeout: String(this.pollTimeoutSecs),
|
|
198
|
+
lastKnownMessageId: String(this.lastKnownMessageId),
|
|
199
|
+
setReadMarker: '0',
|
|
200
|
+
});
|
|
201
|
+
const url = `${this.baseUrl}${SPREED_CHAT_PATH}/${encodeURIComponent(this.roomToken)}?${params.toString()}`;
|
|
202
|
+
// Abort the request slightly after the server-side hold timeout so a wedged
|
|
203
|
+
// connection can't pin the loop forever.
|
|
204
|
+
const abort = new AbortController();
|
|
205
|
+
this.pollAbort = abort;
|
|
206
|
+
const watchdog = setTimeout(() => abort.abort(), (this.pollTimeoutSecs + 10) * 1000);
|
|
207
|
+
watchdog.unref?.();
|
|
208
|
+
let response;
|
|
209
|
+
try {
|
|
210
|
+
response = await fetch(url, {
|
|
211
|
+
method: 'GET',
|
|
212
|
+
headers: this.buildHeaders(),
|
|
213
|
+
signal: abort.signal,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
clearTimeout(watchdog);
|
|
218
|
+
if (this.pollAbort === abort) {
|
|
219
|
+
this.pollAbort = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// 304 = long-poll timed out with no new messages — a SUCCESSFUL empty poll,
|
|
223
|
+
// not an error and not a reconnect trigger.
|
|
224
|
+
if (response.status === 304) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
const body = await response.text().catch(() => '');
|
|
229
|
+
throw new Error(`Nextcloud Talk poll failed: ${response.status} ${response.statusText} - ${body}`);
|
|
230
|
+
}
|
|
231
|
+
const lastGiven = response.headers.get('X-Chat-Last-Given');
|
|
232
|
+
const envelope = (await response.json().catch(() => ({})));
|
|
233
|
+
const messages = envelope.ocs?.data ?? [];
|
|
234
|
+
let maxId = this.lastKnownMessageId;
|
|
235
|
+
for (const raw of messages) {
|
|
236
|
+
if (typeof raw.id === 'number' && raw.id > maxId) {
|
|
237
|
+
maxId = raw.id;
|
|
238
|
+
}
|
|
239
|
+
// Skip system messages (joins/leaves/etc.) — they have no user content.
|
|
240
|
+
if (raw.systemMessage) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
this.emit('message', this.toInbound(raw));
|
|
244
|
+
}
|
|
245
|
+
// Advance the cursor from the server-provided header when present, else from
|
|
246
|
+
// the max message id we saw.
|
|
247
|
+
const givenId = lastGiven ? Number.parseInt(lastGiven, 10) : NaN;
|
|
248
|
+
if (Number.isFinite(givenId) && givenId > this.lastKnownMessageId) {
|
|
249
|
+
this.lastKnownMessageId = givenId;
|
|
250
|
+
}
|
|
251
|
+
else if (maxId > this.lastKnownMessageId) {
|
|
252
|
+
this.lastKnownMessageId = maxId;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
toInbound(raw) {
|
|
256
|
+
const sender = raw.actorId ?? 'unknown';
|
|
257
|
+
const content = raw.message ?? '';
|
|
258
|
+
const isCommand = content.startsWith('/');
|
|
259
|
+
const contentType = isCommand ? 'command' : 'text';
|
|
260
|
+
const message = {
|
|
261
|
+
id: String(raw.id),
|
|
262
|
+
channel: {
|
|
263
|
+
id: raw.token ?? this.roomToken,
|
|
264
|
+
type: 'nextcloud-talk',
|
|
265
|
+
name: raw.token ?? this.roomToken,
|
|
266
|
+
isGroup: true,
|
|
267
|
+
},
|
|
268
|
+
sender: {
|
|
269
|
+
id: sender,
|
|
270
|
+
username: sender,
|
|
271
|
+
displayName: raw.actorDisplayName ?? sender,
|
|
272
|
+
isBot: raw.actorType === 'bots',
|
|
273
|
+
},
|
|
274
|
+
content,
|
|
275
|
+
contentType,
|
|
276
|
+
timestamp: raw.timestamp ? new Date(raw.timestamp * 1000) : new Date(),
|
|
277
|
+
raw,
|
|
278
|
+
};
|
|
279
|
+
if (isCommand) {
|
|
280
|
+
const parts = content.slice(1).split(/\s+/);
|
|
281
|
+
message.isCommand = true;
|
|
282
|
+
message.commandName = parts[0];
|
|
283
|
+
message.commandArgs = parts.slice(1);
|
|
284
|
+
}
|
|
285
|
+
return message;
|
|
286
|
+
}
|
|
287
|
+
/** Emit 'connected' exactly once per session (on first successful poll). */
|
|
288
|
+
connectedEmitted = false;
|
|
289
|
+
onConnectedOnce() {
|
|
290
|
+
if (!this.connectedEmitted) {
|
|
291
|
+
this.connectedEmitted = true;
|
|
292
|
+
this.emit('connected');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// --------------------------------------------------------------------------
|
|
296
|
+
// Reconnection
|
|
297
|
+
// --------------------------------------------------------------------------
|
|
298
|
+
/**
|
|
299
|
+
* Recover a dropped connection via the shared ReconnectionManager
|
|
300
|
+
* (exponential backoff + jitter + exhaustion). `scheduleReconnect` is
|
|
301
|
+
* single-shot, so a failed attempt re-drives it (deferred past the manager's
|
|
302
|
+
* internal active-guard) until recovery or exhaustion — same idiom as
|
|
303
|
+
* irc/imessage.
|
|
304
|
+
*/
|
|
305
|
+
scheduleReconnect() {
|
|
306
|
+
if (this.reconnecting || this.closing || !this.running) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (this.reconnectionManager.listenerCount('exhausted') === 0) {
|
|
310
|
+
this.reconnectionManager.on('exhausted', () => {
|
|
311
|
+
this.reconnecting = false;
|
|
312
|
+
this.running = false;
|
|
313
|
+
this.emit('disconnected', new Error('Nextcloud Talk reconnection failed after all retries'));
|
|
314
|
+
logger.error('NextcloudTalkClient: reconnection failed permanently');
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
this.reconnecting = true;
|
|
318
|
+
this.emit('disconnected', new Error('Nextcloud Talk connection dropped'));
|
|
319
|
+
this.reconnectionManager.scheduleReconnect(async () => {
|
|
320
|
+
// One probe poll. Success resumes the steady-state loop; failure re-drives
|
|
321
|
+
// the manager.
|
|
322
|
+
try {
|
|
323
|
+
await this.pollOnce();
|
|
324
|
+
this.reconnecting = false;
|
|
325
|
+
this.reconnectionManager.onConnected();
|
|
326
|
+
this.emit('reconnected');
|
|
327
|
+
this.onConnectedOnce();
|
|
328
|
+
// Resume the steady-state loop.
|
|
329
|
+
void this.runPollLoop();
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (this.isAbortError(error) || this.closing || !this.running) {
|
|
333
|
+
return; // intentional teardown mid-reconnect
|
|
334
|
+
}
|
|
335
|
+
if (this.running && !this.closing) {
|
|
336
|
+
setTimeout(() => {
|
|
337
|
+
if (this.running && this.reconnecting && !this.closing) {
|
|
338
|
+
this.reconnecting = false;
|
|
339
|
+
this.scheduleReconnect();
|
|
340
|
+
}
|
|
341
|
+
}, 0).unref?.();
|
|
342
|
+
}
|
|
343
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// --------------------------------------------------------------------------
|
|
348
|
+
// Send
|
|
349
|
+
// --------------------------------------------------------------------------
|
|
350
|
+
async sendMessage(roomToken, text) {
|
|
351
|
+
const url = `${this.baseUrl}${SPREED_CHAT_PATH}/${encodeURIComponent(roomToken)}`;
|
|
352
|
+
const response = await fetch(url, {
|
|
353
|
+
method: 'POST',
|
|
354
|
+
headers: { ...this.buildHeaders(), 'Content-Type': 'application/json' },
|
|
355
|
+
body: JSON.stringify({ message: text }),
|
|
356
|
+
});
|
|
357
|
+
if (!response.ok) {
|
|
358
|
+
const body = await response.text().catch(() => '');
|
|
359
|
+
throw new Error(`Nextcloud Talk send failed: ${response.status} ${response.statusText} - ${body}`);
|
|
360
|
+
}
|
|
361
|
+
const envelope = (await response.json().catch(() => ({})));
|
|
362
|
+
const id = envelope.ocs?.data?.id;
|
|
363
|
+
return { success: true, messageId: id !== undefined ? String(id) : undefined };
|
|
364
|
+
}
|
|
365
|
+
// --------------------------------------------------------------------------
|
|
366
|
+
// Helpers
|
|
367
|
+
// --------------------------------------------------------------------------
|
|
368
|
+
buildHeaders() {
|
|
369
|
+
const headers = {
|
|
370
|
+
'OCS-APIRequest': 'true',
|
|
371
|
+
Accept: 'application/json',
|
|
372
|
+
};
|
|
373
|
+
if (this.bearer) {
|
|
374
|
+
headers.Authorization = `Bearer ${this.bearer}`;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const basic = Buffer.from(`${this.username}:${this.password}`).toString('base64');
|
|
378
|
+
headers.Authorization = `Basic ${basic}`;
|
|
379
|
+
}
|
|
380
|
+
return headers;
|
|
381
|
+
}
|
|
382
|
+
isAbortError(err) {
|
|
383
|
+
return (err instanceof Error &&
|
|
384
|
+
(err.name === 'AbortError' || /aborted/i.test(err.message)));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
67
387
|
export class NextcloudTalkChannel extends BaseChannel {
|
|
68
|
-
|
|
388
|
+
client = null;
|
|
389
|
+
ncConfig;
|
|
69
390
|
constructor(config) {
|
|
70
391
|
super('nextcloud-talk', {
|
|
71
392
|
type: 'nextcloud-talk',
|
|
@@ -78,30 +399,60 @@ export class NextcloudTalkChannel extends BaseChannel {
|
|
|
78
399
|
rateLimit: config.rateLimit,
|
|
79
400
|
options: config.options,
|
|
80
401
|
});
|
|
81
|
-
this.
|
|
82
|
-
url: config.url,
|
|
83
|
-
username: config.username,
|
|
84
|
-
password: config.password,
|
|
85
|
-
});
|
|
402
|
+
this.ncConfig = config;
|
|
86
403
|
}
|
|
87
404
|
async connect() {
|
|
88
|
-
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
405
|
+
const client = new NextcloudTalkClient(this.ncConfig);
|
|
406
|
+
this.client = client;
|
|
407
|
+
client.on('message', (message) => {
|
|
408
|
+
this.status.lastActivity = new Date();
|
|
409
|
+
this.emit('message', message);
|
|
410
|
+
if (message.isCommand) {
|
|
411
|
+
this.emit('command', message);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// 'connected' fires on the first successful poll — that's when we flip the
|
|
415
|
+
// channel status to connected (a long-poll can hold open for ~30s, so we do
|
|
416
|
+
// NOT block connect() on it).
|
|
417
|
+
client.on('connected', () => {
|
|
418
|
+
this.status.connected = true;
|
|
419
|
+
this.status.authenticated = true;
|
|
420
|
+
this.status.lastActivity = new Date();
|
|
421
|
+
this.emit('connected', this.type);
|
|
422
|
+
});
|
|
423
|
+
client.on('reconnected', () => {
|
|
424
|
+
this.status.connected = true;
|
|
425
|
+
this.status.lastActivity = new Date();
|
|
426
|
+
});
|
|
427
|
+
client.on('disconnected', (err) => {
|
|
428
|
+
this.status.connected = false;
|
|
429
|
+
this.emit('disconnected', this.type, err);
|
|
430
|
+
});
|
|
431
|
+
client.on('error', (err) => {
|
|
432
|
+
this.status.error = err.message;
|
|
433
|
+
this.emit('error', this.type, err);
|
|
434
|
+
});
|
|
435
|
+
client.start();
|
|
93
436
|
}
|
|
94
437
|
async disconnect() {
|
|
438
|
+
if (this.client) {
|
|
439
|
+
this.client.stop();
|
|
440
|
+
this.client.removeAllListeners();
|
|
441
|
+
this.client = null;
|
|
442
|
+
}
|
|
95
443
|
if (!this.status.connected)
|
|
96
444
|
return;
|
|
97
|
-
await this.adapter.stop();
|
|
98
445
|
this.status.connected = false;
|
|
99
446
|
this.status.lastActivity = new Date();
|
|
100
447
|
this.emit('disconnected', this.type);
|
|
101
448
|
}
|
|
102
449
|
async send(message) {
|
|
450
|
+
if (!this.client) {
|
|
451
|
+
return { success: false, error: 'Not connected', timestamp: new Date() };
|
|
452
|
+
}
|
|
453
|
+
const room = message.channelId || this.client.getRoomToken();
|
|
103
454
|
try {
|
|
104
|
-
const result = await this.
|
|
455
|
+
const result = await this.client.sendMessage(room, message.content);
|
|
105
456
|
this.status.lastActivity = new Date();
|
|
106
457
|
return {
|
|
107
458
|
success: result.success,
|
|
@@ -12,7 +12,30 @@ export interface NostrConfig {
|
|
|
12
12
|
export interface NostrChannelConfig extends ChannelConfig {
|
|
13
13
|
privateKey?: string;
|
|
14
14
|
relays: string[];
|
|
15
|
+
/** Single relay convenience alias (merged with `relays`). */
|
|
16
|
+
relay?: string;
|
|
17
|
+
/** Optional NIP-01 filters merged into the subscription REQ. */
|
|
18
|
+
filters?: Record<string, unknown>;
|
|
19
|
+
/** Max reconnection attempts per relay (default 10). */
|
|
20
|
+
maxRetries?: number;
|
|
21
|
+
/** Initial reconnect backoff in ms (default 1000). */
|
|
22
|
+
reconnectDelayMs?: number;
|
|
23
|
+
/** How long `send()` waits for a relay `OK` ack before failing (default 8000). */
|
|
24
|
+
publishTimeoutMs?: number;
|
|
15
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A minimal subset of the WHATWG/`ws` WebSocket surface the relay client uses.
|
|
28
|
+
* Lets us accept the Node global `WebSocket` without pulling in DOM lib types.
|
|
29
|
+
*/
|
|
30
|
+
interface RelaySocket {
|
|
31
|
+
readyState: number;
|
|
32
|
+
send(data: string): void;
|
|
33
|
+
close(code?: number, reason?: string): void;
|
|
34
|
+
terminate?(): void;
|
|
35
|
+
addEventListener(type: string, listener: (event: unknown) => void): void;
|
|
36
|
+
}
|
|
37
|
+
/** Factory so tests / runtimes can inject a socket constructor. */
|
|
38
|
+
export type RelaySocketFactory = (url: string) => RelaySocket;
|
|
16
39
|
export declare class NostrAdapter {
|
|
17
40
|
private static readonly BECH32_CHARSET;
|
|
18
41
|
private static readonly BECH32_GENERATOR;
|
|
@@ -32,6 +55,13 @@ export declare class NostrAdapter {
|
|
|
32
55
|
addRelay(url: string): void;
|
|
33
56
|
removeRelay(url: string): void;
|
|
34
57
|
getConfig(): NostrConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the configured secret as raw 32 bytes: 64-char hex as-is, `nsec...`
|
|
60
|
+
* bech32 decoded to its 32-byte payload, otherwise a deterministic sha256 of
|
|
61
|
+
* the arbitrary secret. No configured key => a fresh random 32 bytes.
|
|
62
|
+
* Public so {@link NostrChannel} signs under the *same* identity (esp. nsec).
|
|
63
|
+
*/
|
|
64
|
+
getSecretKeyBytes(): Buffer;
|
|
35
65
|
private resolvePrivateKeyBytes;
|
|
36
66
|
private encodeBech32;
|
|
37
67
|
private decodeBech32;
|
|
@@ -41,11 +71,101 @@ export declare class NostrAdapter {
|
|
|
41
71
|
private expandBech32Hrp;
|
|
42
72
|
private bech32Polymod;
|
|
43
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Real Nostr relay channel (NIP-01).
|
|
76
|
+
*
|
|
77
|
+
* Opens persistent WebSocket connections to each configured relay, subscribes
|
|
78
|
+
* to kind-1 (text note) events via a `REQ` frame, and surfaces inbound events
|
|
79
|
+
* as `'message'` events on the channel. Dropped sockets are recovered with the
|
|
80
|
+
* shared {@link ReconnectionManager} (exponential backoff + jitter), the same
|
|
81
|
+
* idiom used by the discord/slack/imessage adapters.
|
|
82
|
+
*
|
|
83
|
+
* Connection semantics are *optimistic*: `connect()` marks the channel
|
|
84
|
+
* connected and returns immediately, opening sockets in the background. This
|
|
85
|
+
* mirrors how a real Nostr client behaves (any one of N relays may be down)
|
|
86
|
+
* and lets the channel come up even when a relay is unreachable.
|
|
87
|
+
*
|
|
88
|
+
* `NostrAdapter` (above) is retained unchanged as the key / bech32 / relay-list
|
|
89
|
+
* utility — its `start()`/`sendDirectMessage()` are legacy in-process stubs
|
|
90
|
+
* kept only for the existing unit tests. The real transport lives here.
|
|
91
|
+
*/
|
|
44
92
|
export declare class NostrChannel extends BaseChannel {
|
|
45
93
|
private adapter;
|
|
46
|
-
|
|
94
|
+
private relays;
|
|
95
|
+
private readonly subId;
|
|
96
|
+
private readonly filters;
|
|
97
|
+
private readonly maxRetries;
|
|
98
|
+
private readonly reconnectDelayMs;
|
|
99
|
+
/** Set while an intentional disconnect is in progress so `close` skips reconnect. */
|
|
100
|
+
private shuttingDown;
|
|
101
|
+
/** Injectable socket factory (defaults to the Node global `WebSocket`). */
|
|
102
|
+
private readonly socketFactory;
|
|
103
|
+
/** Outstanding publishes keyed by event id, resolved on the relay's `OK` ack. */
|
|
104
|
+
private readonly pendingPublishes;
|
|
105
|
+
/** How long to wait for a relay `["OK", id, ...]` ack before timing out. */
|
|
106
|
+
private readonly publishTimeoutMs;
|
|
107
|
+
constructor(config: NostrChannelConfig, socketFactory?: RelaySocketFactory);
|
|
108
|
+
/**
|
|
109
|
+
* Optimistically connect: mark the channel up, emit `'connected'`, then open
|
|
110
|
+
* every relay socket in the background. Resolves immediately — it never waits
|
|
111
|
+
* on a socket `'open'`, so an unreachable relay can't block startup.
|
|
112
|
+
*/
|
|
47
113
|
connect(): Promise<void>;
|
|
48
114
|
disconnect(): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Build, BIP-340 sign, and publish a Nostr kind-1 event for the outbound
|
|
117
|
+
* message.
|
|
118
|
+
*
|
|
119
|
+
* When a secret key is configured we derive the x-only `pubkey` and the
|
|
120
|
+
* Schnorr `sig` via `@noble/curves` (audited secp256k1/BIP-340), compute the
|
|
121
|
+
* canonical sha256 `id`, publish `["EVENT", event]` to every connected relay,
|
|
122
|
+
* and resolve from the relay's `["OK", id, accepted, msg]` acknowledgement.
|
|
123
|
+
*
|
|
124
|
+
* HONEST GATE: if NO secret key is configured we do NOT sign with a throwaway
|
|
125
|
+
* key — we return the same clear error as before. Live publishing is gated
|
|
126
|
+
* only on the operator providing their nsec/hex key + a reachable relay.
|
|
127
|
+
*/
|
|
49
128
|
send(message: OutboundMessage): Promise<DeliveryResult>;
|
|
129
|
+
/**
|
|
130
|
+
* Publish a signed event to every open relay socket and resolve from the
|
|
131
|
+
* FIRST `["OK", id, ...]` acknowledgement (or a timeout). The pending entry is
|
|
132
|
+
* keyed by event id and resolved by {@link handleRelayData}'s `OK` branch.
|
|
133
|
+
*/
|
|
134
|
+
private publishToRelays;
|
|
135
|
+
private openRelay;
|
|
136
|
+
private scheduleRelayReconnect;
|
|
137
|
+
private sendSubscription;
|
|
138
|
+
private handleRelayData;
|
|
139
|
+
private emitInbound;
|
|
140
|
+
/**
|
|
141
|
+
* Build the canonical unsigned Nostr event for `content`. The `id` is the
|
|
142
|
+
* sha256 of the NIP-01 serialization `[0, pubkey, created_at, kind, tags,
|
|
143
|
+
* content]`. Used for the honest no-key error path (still surfaces a real id).
|
|
144
|
+
*/
|
|
145
|
+
private buildUnsignedEvent;
|
|
146
|
+
/**
|
|
147
|
+
* Build a fully BIP-340 signed Nostr kind-1 event for `content`.
|
|
148
|
+
*
|
|
149
|
+
* `pubkey` and `sig` are derived from the same secp256k1 secret via
|
|
150
|
+
* `@noble/curves`' Schnorr primitives: `schnorr.getPublicKey` yields the
|
|
151
|
+
* x-only pubkey (correct BIP-340 even-y normalization), `id` is the sha256 of
|
|
152
|
+
* the NIP-01 serialization, and `sig = schnorr.sign(id, sk)`. Nostr signs the
|
|
153
|
+
* 32-byte id directly (no extra pre-hash), which is exactly noble's raw-message
|
|
154
|
+
* contract — so `schnorr.verify(sig, id, pubkey)` round-trips.
|
|
155
|
+
*/
|
|
156
|
+
private buildSignedEvent;
|
|
157
|
+
/** x-only 32-byte public key as lowercase hex (Nostr event `pubkey` field). */
|
|
158
|
+
private getPublicKeyHex;
|
|
159
|
+
/**
|
|
160
|
+
* Resolve the signing secret as raw 32 bytes. Delegates to the adapter's
|
|
161
|
+
* canonical resolver so hex AND `nsec...` bech32 keys decode to the SAME
|
|
162
|
+
* identity the operator configured (a duplicated resolver here previously
|
|
163
|
+
* dropped the nsec branch and silently signed under a sha256-derived key).
|
|
164
|
+
*/
|
|
165
|
+
private resolvePrivateKeyBytes;
|
|
166
|
+
private closeSocket;
|
|
167
|
+
private errorFromEvent;
|
|
168
|
+
/** Expose the configured relay URLs (post construction). */
|
|
169
|
+
getRelayUrls(): string[];
|
|
50
170
|
}
|
|
51
171
|
export default NostrAdapter;
|