@hellolynk-ai/openclaw-channel 0.0.0-stage → 0.2.100
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/LICENSE +92 -0
- package/README.md +86 -2
- package/bin/hellolynk-client-setup.mjs +2748 -0
- package/bin/hellolynk-mcp-config-shape.mjs +23 -0
- package/clients/core/hellolynk-agent-configurator.mjs +701 -0
- package/clients/core/hellolynk-client-account.mjs +180 -0
- package/clients/core/hellolynk-client-core.mjs +770 -0
- package/clients/core/hellolynk-client-profile.mjs +191 -0
- package/clients/core/hellolynk-client-update-entry.mjs +31 -0
- package/clients/core/hellolynk-client-update-runtime.mjs +600 -0
- package/clients/core/hellolynk-client-update.mjs +453 -0
- package/clients/core/hellolynk-contact-request-e2ee.mjs +867 -0
- package/clients/core/hellolynk-device-enrollment.mjs +435 -0
- package/clients/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
- package/clients/core/hellolynk-direct-text-e2ee.mjs +1207 -0
- package/clients/core/hellolynk-host-auth.mjs +347 -0
- package/clients/core/hellolynk-host-protected-store.mjs +2721 -0
- package/clients/core/hellolynk-host-transport-client.mjs +946 -0
- package/clients/core/hellolynk-host.mjs +2374 -0
- package/clients/core/hellolynk-human-confirmation-ledger.mjs +256 -0
- package/clients/core/hellolynk-human-relationship-controller.mjs +1234 -0
- package/clients/core/hellolynk-inbox-read-state.mjs +11 -0
- package/clients/core/hellolynk-indexed-message-store.mjs +1558 -0
- package/clients/core/hellolynk-instance-conversation.mjs +185 -0
- package/clients/core/hellolynk-local-contact-requests.mjs +1733 -0
- package/clients/core/hellolynk-local-contacts.mjs +1228 -0
- package/clients/core/hellolynk-local-data-migration.mjs +766 -0
- package/clients/core/hellolynk-local-data-protection.mjs +747 -0
- package/clients/core/hellolynk-local-identity-trust.mjs +274 -0
- package/clients/core/hellolynk-local-messaging.mjs +3043 -0
- package/clients/core/hellolynk-local-peer-transport.mjs +103 -0
- package/clients/core/hellolynk-local-process-lock.mjs +157 -0
- package/clients/core/hellolynk-model-inbox.mjs +230 -0
- package/clients/core/hellolynk-model-notices.cjs +60 -0
- package/clients/core/hellolynk-native-addressing-tools.cjs +231 -0
- package/clients/core/hellolynk-native-conversations.mjs +280 -0
- package/clients/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
- package/clients/core/hellolynk-native-inbox-runtime.mjs +553 -0
- package/clients/core/hellolynk-native-notice.mjs +50 -0
- package/clients/core/hellolynk-native-reply-routes.mjs +96 -0
- package/clients/core/hellolynk-native-worker-lease.mjs +84 -0
- package/clients/core/hellolynk-neutral-host-assembly.mjs +955 -0
- package/clients/core/hellolynk-neutral-host-installation-store.mjs +765 -0
- package/clients/core/hellolynk-neutral-host-launcher.mjs +665 -0
- package/clients/core/hellolynk-neutral-host-process.mjs +4374 -0
- package/clients/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
- package/clients/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
- package/clients/core/hellolynk-platform-credential-store.mjs +773 -0
- package/clients/core/hellolynk-relay-client-cli.mjs +232 -0
- package/clients/core/hellolynk-release-retention.mjs +160 -0
- package/clients/core/hellolynk-runtime-safety.cjs +471 -0
- package/clients/core/hellolynk-workbench-action-grants.mjs +457 -0
- package/clients/core/hellolynk-workbench-connection.mjs +234 -0
- package/clients/platform/windows/configure-agent.ps1 +281 -0
- package/clients/platform/windows/credential-store.ps1 +844 -0
- package/clients/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
- package/clients/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
- package/clients/platform/windows/hellolynk-local-data-key.ps1 +300 -0
- package/clients/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
- package/clients/workbench/contract.mjs +1314 -0
- package/clients/workbench/service.mjs +485 -0
- package/hellolynk-client-pack-manifest.json +854 -0
- package/hellolynk-client-pack-pin.json +4 -0
- package/integrations/openclaw-channel/dist/control-ui/index.js +63 -0
- package/integrations/openclaw-channel/index.mjs +5 -0
- package/integrations/openclaw-channel/openclaw.plugin.json +57 -0
- package/integrations/openclaw-channel/package.json +50 -0
- package/integrations/openclaw-channel/setup-entry.mjs +4 -0
- package/integrations/openclaw-channel/skills/hellolynk-messaging/SKILL.md +9 -0
- package/integrations/openclaw-channel/src/authorized-messaging-tools.mjs +423 -0
- package/integrations/openclaw-channel/src/channel-dispatch.mjs +657 -0
- package/integrations/openclaw-channel/src/channel.mjs +368 -0
- package/integrations/openclaw-channel/src/config-schema.mjs +33 -0
- package/integrations/openclaw-channel/src/control-ui.mjs +411 -0
- package/integrations/openclaw-channel/src/inbound-policy.mjs +44 -0
- package/integrations/openclaw-channel/src/native-origins.mjs +53 -0
- package/integrations/openclaw-channel/src/neutral-host-adapter.mjs +826 -0
- package/integrations/openclaw-channel/src/neutral-read-runtime.mjs +469 -0
- package/integrations/openclaw-channel/src/runtime-primitives.mjs +12 -0
- package/integrations/openclaw-channel/src/transcript-attribution.mjs +140 -0
- package/package.json +69 -4
- package/runtime/connect-activate.mjs +837 -0
- package/runtime/core/hellolynk-agent-configurator.mjs +701 -0
- package/runtime/core/hellolynk-client-account.mjs +180 -0
- package/runtime/core/hellolynk-client-core.mjs +770 -0
- package/runtime/core/hellolynk-client-profile.mjs +191 -0
- package/runtime/core/hellolynk-client-update-entry.mjs +31 -0
- package/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
- package/runtime/core/hellolynk-client-update.mjs +453 -0
- package/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
- package/runtime/core/hellolynk-device-enrollment.mjs +435 -0
- package/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
- package/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
- package/runtime/core/hellolynk-host-auth.mjs +347 -0
- package/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
- package/runtime/core/hellolynk-host-transport-client.mjs +946 -0
- package/runtime/core/hellolynk-host.mjs +2374 -0
- package/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
- package/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
- package/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
- package/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
- package/runtime/core/hellolynk-instance-conversation.mjs +185 -0
- package/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
- package/runtime/core/hellolynk-local-contacts.mjs +1228 -0
- package/runtime/core/hellolynk-local-data-migration.mjs +766 -0
- package/runtime/core/hellolynk-local-data-protection.mjs +747 -0
- package/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
- package/runtime/core/hellolynk-local-messaging.mjs +3043 -0
- package/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
- package/runtime/core/hellolynk-local-process-lock.mjs +157 -0
- package/runtime/core/hellolynk-model-inbox.mjs +230 -0
- package/runtime/core/hellolynk-model-notices.cjs +60 -0
- package/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
- package/runtime/core/hellolynk-native-conversations.mjs +280 -0
- package/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
- package/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
- package/runtime/core/hellolynk-native-notice.mjs +50 -0
- package/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
- package/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
- package/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
- package/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
- package/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
- package/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
- package/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
- package/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
- package/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
- package/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
- package/runtime/core/hellolynk-release-retention.mjs +160 -0
- package/runtime/core/hellolynk-runtime-safety.cjs +471 -0
- package/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
- package/runtime/core/hellolynk-workbench-connection.mjs +234 -0
- package/runtime/neutral-host-bootstrap.mjs +57 -0
- package/runtime/neutral-host-release-entry.mjs +81 -0
- package/runtime/neutral-host-release-loader.mjs +156 -0
- package/runtime/neutral-host-release-manifest.json +302 -0
- package/runtime/neutral-host-release-pin.json +4 -0
- package/runtime/platform/windows/configure-agent.ps1 +281 -0
- package/runtime/platform/windows/credential-store.ps1 +844 -0
- package/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
- package/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
- package/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
- package/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
- package/runtime/workbench/candidate-bootstrap.mjs +54 -0
- package/runtime/workbench/contract.mjs +1314 -0
- package/runtime/workbench/hellolynk-workbench-build.json +215 -0
- package/runtime/workbench/host-capabilities.mjs +80 -0
- package/runtime/workbench/i18n.mjs +1127 -0
- package/runtime/workbench/loopback-surface.mjs +740 -0
- package/runtime/workbench/mcp-app-bundle.mjs +97 -0
- package/runtime/workbench/service.mjs +485 -0
- package/runtime/workbench/state.mjs +421 -0
- package/runtime/workbench/transports.mjs +171 -0
- package/runtime/workbench/ui/app.mjs +6120 -0
- package/runtime/workbench/ui/connection-form.mjs +155 -0
- package/runtime/workbench/ui/hellolynk-mark.png +0 -0
- package/runtime/workbench/ui/http-transport.mjs +247 -0
- package/runtime/workbench/ui/icons/arrow-left.svg +3 -0
- package/runtime/workbench/ui/icons/close.svg +3 -0
- package/runtime/workbench/ui/icons/contacts.svg +3 -0
- package/runtime/workbench/ui/icons/inbox.svg +3 -0
- package/runtime/workbench/ui/icons/refresh.svg +3 -0
- package/runtime/workbench/ui/icons/search.svg +3 -0
- package/runtime/workbench/ui/icons/security.svg +3 -0
- package/runtime/workbench/ui/icons/settings.svg +3 -0
- package/runtime/workbench/ui/index.html +139 -0
- package/runtime/workbench/ui/loopback-entry.mjs +26 -0
- package/runtime/workbench/ui/mcp-app-client.mjs +151 -0
- package/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
- package/runtime/workbench/ui/styles.css +686 -0
- package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChatChannelPlugin,
|
|
3
|
+
defineChannelPluginEntry,
|
|
4
|
+
} from "openclaw/plugin-sdk/channel-core";
|
|
5
|
+
import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-outbound";
|
|
6
|
+
import { getReplyFromConfig } from "openclaw/plugin-sdk/reply-runtime";
|
|
7
|
+
import { getSessionEntry, listSessionEntries, resolveTranscriptSessionKeyBySessionId } from "openclaw/plugin-sdk/session-store-runtime";
|
|
8
|
+
import { readAgentRunTerminalOutcome } from "openclaw/plugin-sdk/channel-inbound";
|
|
9
|
+
import { resolveActiveEmbeddedRunSessionId } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
10
|
+
import { HELLOLYNK_CHANNEL_CONFIG_SCHEMA_DESCRIPTOR } from "./config-schema.mjs";
|
|
11
|
+
import {
|
|
12
|
+
createOpenClawWorkbenchRuntime,
|
|
13
|
+
registerHelloLynkWorkbenchControlUi,
|
|
14
|
+
resolveOpenClawWorkbenchParentOrigin,
|
|
15
|
+
} from "./control-ui.mjs";
|
|
16
|
+
import { createOpenClawAuthorizedMessagingAgentTools, OPENCLAW_AUTHORIZED_MESSAGING_TOOLS } from "./authorized-messaging-tools.mjs";
|
|
17
|
+
import { observeHelloLynkModelCall } from "./channel-dispatch.mjs";
|
|
18
|
+
import { runOpenClawNeutralReadRuntime } from "./neutral-read-runtime.mjs";
|
|
19
|
+
import { createWorkbenchConnection, isWorkbenchConnectionRequired } from "../../../clients/core/hellolynk-workbench-connection.mjs";
|
|
20
|
+
import { readClientAccount } from "../../../clients/core/hellolynk-client-account.mjs";
|
|
21
|
+
import { startInstalledClientUpdate } from "../../../clients/core/hellolynk-client-update-entry.mjs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { launchPinnedNeutralHost } from "../../../clients/core/hellolynk-neutral-host-launcher.mjs";
|
|
25
|
+
import {
|
|
26
|
+
createOpenClawNeutralHostAdapter,
|
|
27
|
+
createOpenClawProtectedBindingProvider,
|
|
28
|
+
isOpenClawNeutralHostUnavailable,
|
|
29
|
+
} from "./neutral-host-adapter.mjs";
|
|
30
|
+
|
|
31
|
+
const CHANNEL_ID = "hellolynk";
|
|
32
|
+
const DEFAULT_ACCOUNT_ID = "default";
|
|
33
|
+
|
|
34
|
+
// OpenClaw can run this module from a temporary copy of its imports, while each
|
|
35
|
+
// registration names the installed plugin directory. Files outside the module
|
|
36
|
+
// graph (Host helper, package metadata, workbench runtime) come from that root.
|
|
37
|
+
let registeredReleaseRoot = "";
|
|
38
|
+
let registeredAgentRuntime;
|
|
39
|
+
|
|
40
|
+
function openClawReleaseRoot() {
|
|
41
|
+
return registeredReleaseRoot || fileURLToPath(new URL("../../../", import.meta.url));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function channelConfig(cfg) {
|
|
45
|
+
return cfg?.channels?.hellolynk || cfg?.hellolynk || {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function accountConfig(cfg, accountId = DEFAULT_ACCOUNT_ID) {
|
|
49
|
+
const root = channelConfig(cfg);
|
|
50
|
+
const account = root.accounts?.[accountId] || {};
|
|
51
|
+
rejectLegacyFields(root);
|
|
52
|
+
rejectLegacyFields(account);
|
|
53
|
+
const hostMode = account.hostMode ?? root.hostMode;
|
|
54
|
+
const bindingRef = account.bindingRef ?? root.bindingRef;
|
|
55
|
+
const adapterInstanceId = account.adapterInstanceId ?? root.adapterInstanceId;
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
accountId,
|
|
58
|
+
name: account.name || root.name || "HelloLynk 通讯",
|
|
59
|
+
enabled: account.enabled ?? root.enabled ?? false,
|
|
60
|
+
configured: hostMode === "neutral" && bindingRef === "protected-store:openclaw",
|
|
61
|
+
hostMode: hostMode || "",
|
|
62
|
+
bindingRef: bindingRef || "",
|
|
63
|
+
adapterInstanceId: adapterInstanceId || "",
|
|
64
|
+
nativeModel: account.nativeModel ?? root.nativeModel,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function rejectLegacyFields(value) {
|
|
69
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
70
|
+
for (const key of [
|
|
71
|
+
"baseUrl", "principalId", "hellolynkId", "delegateId", "deviceId", "deviceCredential",
|
|
72
|
+
"runtimeSessionId", "runtimeSessionSecret", "pollIntervalMs", "hellolynkAgentId",
|
|
73
|
+
"openclawAgentId", "devTrustedSenderPrincipalIds", "knownTargets", "defaultTo",
|
|
74
|
+
]) {
|
|
75
|
+
if (Object.hasOwn(value, key)) throw codeError("neutral_openclaw_legacy_config_rejected");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function listAccountIds(cfg) {
|
|
80
|
+
const accounts = channelConfig(cfg).accounts;
|
|
81
|
+
const ids = accounts && typeof accounts === "object" ? Object.keys(accounts) : [];
|
|
82
|
+
return ids.length ? ids : [DEFAULT_ACCOUNT_ID];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveDefaultAccountId(cfg) {
|
|
86
|
+
return channelConfig(cfg).defaultAccount || listAccountIds(cfg)[0] || DEFAULT_ACCOUNT_ID;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const failDirectSend = async () => { throw codeError("neutral_openclaw_direct_send_unavailable"); };
|
|
90
|
+
export const hellolynkNeutralOutbound = Object.freeze({
|
|
91
|
+
deliveryMode: "direct",
|
|
92
|
+
resolveTarget: () => ({ ok: false, error: codeError("neutral_openclaw_direct_send_unavailable") }),
|
|
93
|
+
sendText: failDirectSend,
|
|
94
|
+
sendMedia: failDirectSend,
|
|
95
|
+
sendPayload: failDirectSend,
|
|
96
|
+
});
|
|
97
|
+
const message = createChannelMessageAdapterFromOutbound({ id: CHANNEL_ID, outbound: hellolynkNeutralOutbound });
|
|
98
|
+
|
|
99
|
+
async function startHelloLynkGatewayAccount(ctx) {
|
|
100
|
+
const account = accountConfig(ctx.cfg, ctx.account?.accountId || ctx.accountId || DEFAULT_ACCOUNT_ID);
|
|
101
|
+
if (!account.enabled) throw codeError("neutral_openclaw_disabled");
|
|
102
|
+
if (!account.configured) throw codeError("neutral_openclaw_binding_configuration_required");
|
|
103
|
+
const adapter = await createOpenClawAdapter(ctx, account);
|
|
104
|
+
ctx.setStatus?.({
|
|
105
|
+
accountId: account.accountId,
|
|
106
|
+
running: true,
|
|
107
|
+
configured: true,
|
|
108
|
+
enabled: true,
|
|
109
|
+
mode: "neutral_host",
|
|
110
|
+
});
|
|
111
|
+
const readController = new AbortController();
|
|
112
|
+
const abortRead = () => readController.abort();
|
|
113
|
+
ctx.abortSignal?.addEventListener("abort", abortRead, { once: true });
|
|
114
|
+
if (ctx.abortSignal?.aborted) readController.abort();
|
|
115
|
+
const readRuntime = runOpenClawNeutralReadRuntime({
|
|
116
|
+
adapter,
|
|
117
|
+
account: { ...account, delegateId: adapter.binding?.delegateId },
|
|
118
|
+
channelContext: { ...ctx, hellolynkGetReplyFromConfig: getReplyFromConfig, hellolynkGetSessionEntry: getSessionEntry, hellolynkListSessionEntries: listSessionEntries,
|
|
119
|
+
hellolynkResolveSessionKeyById: resolveTranscriptSessionKeyBySessionId,
|
|
120
|
+
hellolynkActiveSessionRun: resolveActiveEmbeddedRunSessionId,
|
|
121
|
+
hellolynkAgentRuntime: registeredAgentRuntime, hellolynkReadRunOutcome: readAgentRunTerminalOutcome },
|
|
122
|
+
abortSignal: readController.signal,
|
|
123
|
+
...(ctx.hellolynkAcquireNativeWorkerLease ? { acquireLease: ctx.hellolynkAcquireNativeWorkerLease } : {}),
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
const reason = await Promise.race([adapter.done, readRuntime]);
|
|
127
|
+
if (adapter.state === "dead" && !ctx.abortSignal?.aborted) throw codeError(reason);
|
|
128
|
+
} finally {
|
|
129
|
+
readController.abort();
|
|
130
|
+
await readRuntime.catch(() => {});
|
|
131
|
+
ctx.abortSignal?.removeEventListener("abort", abortRead);
|
|
132
|
+
await adapter.close();
|
|
133
|
+
ctx.setStatus?.({
|
|
134
|
+
accountId: account.accountId,
|
|
135
|
+
running: false,
|
|
136
|
+
configured: true,
|
|
137
|
+
enabled: true,
|
|
138
|
+
mode: "neutral_host",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function createOpenClawAdapter(ctx, account) {
|
|
144
|
+
if (!ctx?.hellolynkNeutralAdapterFactory) {
|
|
145
|
+
await startInstalledClientUpdate({ client: "openclaw", packageRoot: openClawReleaseRoot() });
|
|
146
|
+
}
|
|
147
|
+
const createAdapter = ctx?.hellolynkNeutralAdapterFactory || createOpenClawNeutralHostAdapter;
|
|
148
|
+
return createAdapter({
|
|
149
|
+
bindingRef: account.bindingRef,
|
|
150
|
+
...(account.adapterInstanceId ? { adapterInstanceId: account.adapterInstanceId } : {}),
|
|
151
|
+
capabilityProvider: ctx?.hellolynkNeutralCapabilityProvider
|
|
152
|
+
|| createOpenClawProtectedBindingProvider({
|
|
153
|
+
helperPath: path.join(openClawReleaseRoot(), "clients", "platform", "windows", "hellolynk-host-protected-store.ps1"),
|
|
154
|
+
}),
|
|
155
|
+
hostLauncher: ctx?.hellolynkNeutralHostLauncher
|
|
156
|
+
|| ((binding) => launchPinnedNeutralHost(binding)),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The workbench retains this facade for its lifetime. Its underlying attachment
|
|
161
|
+
// can be retired without replaying the operation that discovered the failure.
|
|
162
|
+
export function createRecoveringOpenClawAdapter(factory) {
|
|
163
|
+
let pending = null;
|
|
164
|
+
let current = null;
|
|
165
|
+
let epoch = 0;
|
|
166
|
+
let closing = null;
|
|
167
|
+
const closes = new WeakMap();
|
|
168
|
+
const retiring = new Set();
|
|
169
|
+
let accountRevision = readClientAccount("openclaw").revision;
|
|
170
|
+
let accountReset = null;
|
|
171
|
+
const closeAdapter = (adapter) => {
|
|
172
|
+
if (!closes.has(adapter)) {
|
|
173
|
+
const operation = Promise.resolve().then(() => adapter.close?.());
|
|
174
|
+
closes.set(adapter, operation);
|
|
175
|
+
retiring.add(operation);
|
|
176
|
+
void operation.then(() => retiring.delete(operation), () => retiring.delete(operation));
|
|
177
|
+
}
|
|
178
|
+
return closes.get(adapter);
|
|
179
|
+
};
|
|
180
|
+
const retire = async (adapter, flight) => {
|
|
181
|
+
if (pending === flight) { pending = null; current = null; }
|
|
182
|
+
await closeAdapter(adapter).catch(() => {});
|
|
183
|
+
};
|
|
184
|
+
const resolve = async (allowCachedRecovery = true) => {
|
|
185
|
+
const nextRevision = readClientAccount("openclaw").revision;
|
|
186
|
+
if (!accountReset && nextRevision !== accountRevision) {
|
|
187
|
+
const previousFlight = pending;
|
|
188
|
+
pending = null; current = null; accountRevision = nextRevision;
|
|
189
|
+
accountReset = Promise.resolve().then(async () => {
|
|
190
|
+
const previous = await previousFlight?.catch(() => null);
|
|
191
|
+
if (previous) await closeAdapter(previous).catch(() => {});
|
|
192
|
+
}).finally(() => { accountReset = null; });
|
|
193
|
+
}
|
|
194
|
+
if (accountReset) await accountReset;
|
|
195
|
+
const selectedAccount = accountRevision;
|
|
196
|
+
if (selectedAccount !== readClientAccount("openclaw").revision) throw codeError("workbench_account_changed");
|
|
197
|
+
if (closing) await closing;
|
|
198
|
+
if (!pending && retiring.size) await Promise.allSettled([...retiring]);
|
|
199
|
+
const generation = epoch;
|
|
200
|
+
const cachedAdapter = current;
|
|
201
|
+
const flight = pending ||= Promise.resolve().then(factory);
|
|
202
|
+
let adapter;
|
|
203
|
+
try { adapter = await flight; }
|
|
204
|
+
catch (error) { if (pending === flight) pending = null; throw error; }
|
|
205
|
+
if (epoch !== generation || selectedAccount !== readClientAccount("openclaw").revision) {
|
|
206
|
+
await closeAdapter(adapter).catch(() => {});
|
|
207
|
+
throw codeError("neutral_host_adapter_closed");
|
|
208
|
+
}
|
|
209
|
+
if (adapter.state === "dead" || adapter.state === "closed") {
|
|
210
|
+
await retire(adapter, flight);
|
|
211
|
+
// A dead cached attachment may be replaced once. A fresh dead result must
|
|
212
|
+
// reject instead of continuously creating attachments in this operation.
|
|
213
|
+
if (allowCachedRecovery && adapter === cachedAdapter) return resolve(false);
|
|
214
|
+
throw codeError("neutral_host_adapter_unavailable");
|
|
215
|
+
}
|
|
216
|
+
if (pending === flight) current = adapter;
|
|
217
|
+
return { adapter, flight };
|
|
218
|
+
};
|
|
219
|
+
const invoke = async (method, args, minimumArity = 0) => {
|
|
220
|
+
const { adapter, flight } = await resolve();
|
|
221
|
+
if (typeof adapter[method] !== "function" || adapter[method].length < minimumArity) {
|
|
222
|
+
throw codeError("neutral_openclaw_adapter_invalid");
|
|
223
|
+
}
|
|
224
|
+
try { return await adapter[method](...args); }
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (adapter.state === "dead" || isOpenClawNeutralHostUnavailable(error)) await retire(adapter, flight);
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const methods = Object.fromEntries([
|
|
231
|
+
"readBundle", "readWorkbenchSnapshot", "readWorkbenchChanges", "markRead", "deleteRead", "saveContact", "deleteContact",
|
|
232
|
+
"setAutonomousSendAuthorization", "listAuthorizedContacts", "activateAuthorizedInbox", "listAuthorizedInboxMessages", "modelInbox",
|
|
233
|
+
"readAuthorizedInboxMessage", "listAuthorizedContactRequests", "readAuthorizedContactRequest", "listPendingMessages", "claimMessage", "claimBatch", "inboxBatch",
|
|
234
|
+
"sendAuthorizedMessage", "replyAuthorizedMessage", "issueRetryGrant", "retryOutgoing",
|
|
235
|
+
"deleteOutgoing", "requestHumanSend", "getHumanSendStatus",
|
|
236
|
+
].map((method) => [method, (...args) => invoke(method, args)]));
|
|
237
|
+
return Object.freeze({
|
|
238
|
+
...methods,
|
|
239
|
+
async ready() { await resolve(); },
|
|
240
|
+
get binding() { return current?.binding; },
|
|
241
|
+
async issueWorkbenchActionGrant(command) { return invoke("issueWorkbenchActionGrant", [command], 1); },
|
|
242
|
+
async executeWorkbenchCommand(command, context) { return invoke("executeWorkbenchCommand", [command, context], 2); },
|
|
243
|
+
async close() {
|
|
244
|
+
if (closing) return closing;
|
|
245
|
+
epoch += 1;
|
|
246
|
+
const flight = pending;
|
|
247
|
+
pending = null;
|
|
248
|
+
current = null;
|
|
249
|
+
const operation = (async () => {
|
|
250
|
+
const adapter = await flight?.catch(() => null);
|
|
251
|
+
if (adapter) await closeAdapter(adapter);
|
|
252
|
+
await Promise.allSettled([...retiring]);
|
|
253
|
+
})();
|
|
254
|
+
closing = operation;
|
|
255
|
+
try { await operation; }
|
|
256
|
+
finally { if (closing === operation) closing = null; }
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const hellolynkChannelPlugin = createChatChannelPlugin({
|
|
262
|
+
base: {
|
|
263
|
+
id: CHANNEL_ID,
|
|
264
|
+
meta: {
|
|
265
|
+
id: CHANNEL_ID,
|
|
266
|
+
label: "HelloLynk 通讯",
|
|
267
|
+
selectionLabel: "HelloLynk 通讯",
|
|
268
|
+
detailLabel: "HelloLynk 通讯 · 本机 Host",
|
|
269
|
+
docsPath: "/channels/hellolynk",
|
|
270
|
+
blurb: "HelloLynk Messaging attached to the local neutral HelloLynk Host.",
|
|
271
|
+
},
|
|
272
|
+
capabilities: { chatTypes: ["direct"], media: false, threads: false, blockStreaming: true },
|
|
273
|
+
reload: { configPrefixes: ["channels.hellolynk"] },
|
|
274
|
+
configSchema: HELLOLYNK_CHANNEL_CONFIG_SCHEMA_DESCRIPTOR,
|
|
275
|
+
config: {
|
|
276
|
+
listAccountIds,
|
|
277
|
+
resolveAccount: (cfg, accountId) => accountConfig(cfg, accountId),
|
|
278
|
+
defaultAccountId: resolveDefaultAccountId,
|
|
279
|
+
isConfigured: (account) => account.configured,
|
|
280
|
+
resolveAllowFrom: () => [],
|
|
281
|
+
resolveDefaultTo: () => undefined,
|
|
282
|
+
},
|
|
283
|
+
messaging: {
|
|
284
|
+
targetPrefixes: ["hellolynk"],
|
|
285
|
+
normalizeTarget: () => null,
|
|
286
|
+
inferTargetChatType: () => "direct",
|
|
287
|
+
},
|
|
288
|
+
directory: { listPeers: async () => [] },
|
|
289
|
+
message,
|
|
290
|
+
actions: { describeMessageTool: () => ({ actions: [] }) },
|
|
291
|
+
gateway: { startAccount: startHelloLynkGatewayAccount },
|
|
292
|
+
},
|
|
293
|
+
outbound: hellolynkNeutralOutbound,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Discovery and the active Gateway expose the same Host-backed tools.
|
|
297
|
+
function registerMessagingTools(api, account) {
|
|
298
|
+
let recoveringAdapter;
|
|
299
|
+
const adapterProvider = async () => {
|
|
300
|
+
recoveringAdapter ||= createRecoveringOpenClawAdapter(() => (
|
|
301
|
+
typeof api.hellolynkNeutralAdapterProvider === "function"
|
|
302
|
+
? api.hellolynkNeutralAdapterProvider()
|
|
303
|
+
: createOpenClawAdapter(api, account)
|
|
304
|
+
));
|
|
305
|
+
await recoveringAdapter.ready().catch((error) => { if (!isWorkbenchConnectionRequired(error)) throw error; });
|
|
306
|
+
return recoveringAdapter;
|
|
307
|
+
};
|
|
308
|
+
api.registerTool((toolContext) => createOpenClawAuthorizedMessagingAgentTools({ adapterProvider, toolContext }),
|
|
309
|
+
{ names: OPENCLAW_AUTHORIZED_MESSAGING_TOOLS.map((tool) => tool.name) });
|
|
310
|
+
return adapterProvider;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const channelEntry = defineChannelPluginEntry({
|
|
314
|
+
id: CHANNEL_ID,
|
|
315
|
+
name: "HelloLynk 通讯",
|
|
316
|
+
description: "HelloLynk Messaging: linked replies return to their exact originating conversation with explicit source attribution; new mail uses a dedicated conversation.",
|
|
317
|
+
plugin: hellolynkChannelPlugin,
|
|
318
|
+
registerFull: (api) => {
|
|
319
|
+
if (api.registrationMode !== "full" && api.registrationMode !== "tool-discovery") return;
|
|
320
|
+
const account = accountConfig(api.config, resolveDefaultAccountId(api.config));
|
|
321
|
+
if (!account.enabled) return;
|
|
322
|
+
if (api.registrationMode === "full" && typeof api.registerService !== "function") {
|
|
323
|
+
throw codeError("openclaw_workbench_service_api_unavailable");
|
|
324
|
+
}
|
|
325
|
+
if (api.registrationMode === "full") registeredAgentRuntime = api.runtime?.agent;
|
|
326
|
+
const adapterProvider = registerMessagingTools(api, account);
|
|
327
|
+
if (api.registrationMode === "full") {
|
|
328
|
+
if (typeof api.on === "function") {
|
|
329
|
+
api.on("model_call_started", (event) => observeHelloLynkModelCall(event, "started"));
|
|
330
|
+
api.on("model_call_ended", (event) => observeHelloLynkModelCall(event, "ended"));
|
|
331
|
+
api.on("llm_output", (event) => observeHelloLynkModelCall(event, "llm_output"));
|
|
332
|
+
}
|
|
333
|
+
const workbenchRuntime = createOpenClawWorkbenchRuntime({
|
|
334
|
+
adapterProvider,
|
|
335
|
+
releaseRoot: openClawReleaseRoot(),
|
|
336
|
+
connectAccount: createWorkbenchConnection({ client: "openclaw", releaseRoot: openClawReleaseRoot(),
|
|
337
|
+
helperPath: path.join(openClawReleaseRoot(), "clients", "platform", "windows", "hellolynk-host-protected-store.ps1"),
|
|
338
|
+
}),
|
|
339
|
+
trustedParentOrigin: resolveOpenClawWorkbenchParentOrigin(api.config),
|
|
340
|
+
});
|
|
341
|
+
registerHelloLynkWorkbenchControlUi(api, {
|
|
342
|
+
workbenchProvider: workbenchRuntime.workbenchProvider,
|
|
343
|
+
});
|
|
344
|
+
api.registerService(workbenchRuntime.service);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
registerCapabilities: (api) => {
|
|
348
|
+
if (api.registrationMode !== "discovery") return;
|
|
349
|
+
const account = accountConfig(api.config, resolveDefaultAccountId(api.config));
|
|
350
|
+
if (account.enabled) registerMessagingTools(api, account);
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Every registration mode receives api.rootDir, before the gateway can start an
|
|
355
|
+
// account from this registry.
|
|
356
|
+
export const hellolynkChannelEntry = {
|
|
357
|
+
...channelEntry,
|
|
358
|
+
register(api) {
|
|
359
|
+
if (path.isAbsolute(String(api?.rootDir || ""))) registeredReleaseRoot = path.resolve(api.rootDir, "..", "..");
|
|
360
|
+
return channelEntry.register(api);
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
function codeError(code) {
|
|
365
|
+
const error = new Error(code);
|
|
366
|
+
error.code = code;
|
|
367
|
+
return error;
|
|
368
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const BINDING_REFERENCE = Object.freeze({
|
|
2
|
+
type: "string",
|
|
3
|
+
const: "protected-store:openclaw",
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const NEUTRAL_PROPERTIES = Object.freeze({
|
|
7
|
+
enabled: { type: "boolean" },
|
|
8
|
+
hostMode: { type: "string", const: "neutral" },
|
|
9
|
+
bindingRef: BINDING_REFERENCE,
|
|
10
|
+
nativeModel: {
|
|
11
|
+
type: "object",
|
|
12
|
+
description: "Requests a model and thinking level for HelloLynk turns. OpenClaw may select another model; nativeModelSelection.hostObservation is session-level and is not correlated to this turn.",
|
|
13
|
+
additionalProperties: false,
|
|
14
|
+
required: ["provider", "model"],
|
|
15
|
+
properties: {
|
|
16
|
+
provider: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]{0,79}$" },
|
|
17
|
+
model: { type: "string", minLength: 1, maxLength: 240, pattern: "^[^\\s\\x00-\\x1f\\x7f]+$" },
|
|
18
|
+
thinkingLevel: { type: "string", pattern: "^[a-z][a-z0-9_-]{0,31}$" },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const HELLOLYNK_CHANNEL_CONFIG_SCHEMA = Object.freeze({
|
|
24
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
required: ["enabled", "hostMode", "bindingRef"],
|
|
28
|
+
properties: NEUTRAL_PROPERTIES,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const HELLOLYNK_CHANNEL_CONFIG_SCHEMA_DESCRIPTOR = Object.freeze({
|
|
32
|
+
schema: HELLOLYNK_CHANNEL_CONFIG_SCHEMA,
|
|
33
|
+
});
|