@soimy/dingtalk 3.6.11 → 3.7.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/dist/index.d.ts +1 -1
- package/dist/index.js +11684 -10583
- package/dist/src/{ack-reaction-service.d.ts → ack-reaction/ack-reaction-service.d.ts} +1 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +3 -3
- package/dist/src/card/ask-user-question-context.d.ts +1 -1
- package/dist/src/card/ask-user-question-store.d.ts +1 -1
- package/dist/src/card/ask-user-question.d.ts +1 -1
- package/dist/src/card/card-action-handler.d.ts +2 -2
- package/dist/src/{card-draft-controller.d.ts → card/card-draft-controller.d.ts} +5 -1
- package/dist/src/card/card-run-registry.d.ts +2 -2
- package/dist/src/{card-service.d.ts → card/card-service.d.ts} +1 -1
- package/dist/src/card/card-stop-handler.d.ts +1 -1
- package/dist/src/card/card-streaming-mode.d.ts +1 -1
- package/dist/src/card/card-task-progress.d.ts +60 -0
- package/dist/src/channel.d.ts +6 -6
- package/dist/src/command/card-stop-command.d.ts +1 -1
- package/dist/src/{feedback-learning-service.d.ts → command/feedback-learning-service.d.ts} +1 -1
- package/dist/src/command/inbound-command-dispatch-service.d.ts +2 -2
- package/dist/src/{learning-command-service.d.ts → command/learning-command-service.d.ts} +1 -1
- package/dist/src/gateway/channel-gateway.d.ts +1 -1
- package/dist/src/{connection-manager.d.ts → gateway/connection-manager.d.ts} +1 -1
- package/dist/src/{docs-service.d.ts → gateway/docs-service.d.ts} +1 -1
- package/dist/src/{inbound-handler.d.ts → gateway/inbound-handler.d.ts} +1 -1
- package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +1 -1
- package/dist/src/messaging/attachment-text-extractor.d.ts +1 -1
- package/dist/src/messaging/btw-deliver.d.ts +1 -1
- package/dist/src/messaging/channel-outbound.d.ts +1 -1
- package/dist/src/{media-utils.d.ts → messaging/media-utils.d.ts} +1 -1
- package/dist/src/{message-context-store.d.ts → messaging/message-context-store.d.ts} +7 -1
- package/dist/src/{message-utils.d.ts → messaging/message-utils.d.ts} +1 -1
- package/dist/src/messaging/quoted-context.d.ts +2 -2
- package/dist/src/messaging/quoted-file-service.d.ts +1 -1
- package/dist/src/messaging/quoted-ref.d.ts +2 -2
- package/dist/src/{reply-strategy-card.d.ts → messaging/reply-strategy-card.d.ts} +1 -1
- package/dist/src/{reply-strategy-types.d.ts → messaging/reply-strategy-types.d.ts} +10 -1
- package/dist/src/{reply-strategy-with-reaction.d.ts → messaging/reply-strategy-with-reaction.d.ts} +1 -1
- package/dist/src/{reply-strategy.d.ts → messaging/reply-strategy.d.ts} +1 -1
- package/dist/src/{send-service.d.ts → messaging/send-service.d.ts} +1 -1
- package/dist/src/platform/channel-status.d.ts +1 -1
- package/dist/src/platform/config.d.ts +110 -0
- package/dist/src/platform/runtime-events.d.ts +67 -0
- package/dist/src/{types.d.ts → platform/types.d.ts} +74 -1
- package/dist/src/{persistence-store.d.ts → shared/persistence-store.d.ts} +1 -1
- package/dist/src/{utils.d.ts → shared/utils.d.ts} +1 -1
- package/dist/src/targeting/agent-name-matcher.d.ts +1 -1
- package/dist/src/targeting/agent-routing.d.ts +3 -3
- package/dist/src/{session-routing.d.ts → targeting/session-routing.d.ts} +1 -1
- package/index.ts +193 -69
- package/openclaw.plugin.json +158 -2
- package/package.json +16 -16
- package/src/{ack-reaction-classifier.ts → ack-reaction/ack-reaction-classifier.ts} +85 -13
- package/src/{ack-reaction-service.ts → ack-reaction/ack-reaction-service.ts} +13 -7
- package/src/ack-reaction/dynamic-ack-reaction-controller.ts +46 -31
- package/src/ack-reaction/dynamic-ack-reaction-progress.ts +4 -1
- package/src/card/ask-user-question-context.ts +1 -1
- package/src/card/ask-user-question-store.ts +2 -2
- package/src/card/ask-user-question.ts +11 -7
- package/src/card/card-action-handler.ts +2 -2
- package/src/{card-callback-service.ts → card/card-callback-service.ts} +7 -9
- package/src/card/card-draft-controller.ts +770 -0
- package/src/card/card-markdown-image-reroute.ts +8 -2
- package/src/card/card-run-registry.ts +17 -7
- package/src/{card-service.ts → card/card-service.ts} +91 -61
- package/src/card/card-stop-handler.ts +12 -8
- package/src/card/card-streaming-mode.ts +1 -1
- package/src/card/card-task-progress.ts +330 -0
- package/src/card/draft-stream-loop.ts +119 -0
- package/src/card/reasoning-answer-split.ts +125 -121
- package/src/card/reasoning-block-assembler.ts +122 -123
- package/src/{run-usage-store.ts → card/run-usage-store.ts} +18 -6
- package/src/card/statusline-renderer.ts +14 -5
- package/src/card/task-model-metadata.ts +8 -3
- package/src/channel.ts +14 -16
- package/src/command/card-stop-command.ts +2 -2
- package/src/{feedback-learning-service.ts → command/feedback-learning-service.ts} +36 -16
- package/src/{feedback-learning-store.ts → command/feedback-learning-store.ts} +133 -81
- package/src/command/inbound-command-dispatch-service.ts +12 -6
- package/src/{learning-command-service.ts → command/learning-command-service.ts} +36 -29
- package/src/{session-command-service.ts → command/session-command-service.ts} +3 -1
- package/src/gateway/channel-gateway.ts +106 -28
- package/src/{connection-manager.ts → gateway/connection-manager.ts} +45 -30
- package/src/{docs-service.ts → gateway/docs-service.ts} +5 -5
- package/src/{inbound-handler.ts → gateway/inbound-handler.ts} +139 -84
- package/src/gateway/inbound-session-queue-dispatcher.ts +18 -14
- package/src/{session-lock.ts → gateway/session-lock.ts} +14 -14
- package/src/messaging/attachment-text-extractor.ts +21 -7
- package/src/messaging/btw-deliver.ts +2 -2
- package/src/messaging/channel-actions.ts +15 -7
- package/src/messaging/channel-outbound.ts +16 -11
- package/src/{media-utils.ts → messaging/media-utils.ts} +276 -72
- package/src/{message-context-store.ts → messaging/message-context-store.ts} +11 -2
- package/src/{message-utils.ts → messaging/message-utils.ts} +46 -11
- package/src/messaging/quoted-context.ts +2 -2
- package/src/messaging/quoted-file-service.ts +311 -294
- package/src/messaging/quoted-ref.ts +11 -7
- package/src/{reply-strategy-card.ts → messaging/reply-strategy-card.ts} +178 -101
- package/src/{reply-strategy-markdown.ts → messaging/reply-strategy-markdown.ts} +8 -2
- package/src/{reply-strategy-types.ts → messaging/reply-strategy-types.ts} +11 -1
- package/src/{reply-strategy-with-reaction.ts → messaging/reply-strategy-with-reaction.ts} +6 -1
- package/src/{reply-strategy.ts → messaging/reply-strategy.ts} +2 -2
- package/src/{send-service.ts → messaging/send-service.ts} +100 -65
- package/src/{auth.ts → platform/auth.ts} +2 -2
- package/src/platform/channel-status.ts +3 -3
- package/src/{config-schema.ts → platform/config-schema.ts} +81 -4
- package/src/{config.ts → platform/config.ts} +167 -19
- package/src/{device-registration.ts → platform/device-registration.ts} +5 -4
- package/src/{onboarding.ts → platform/onboarding.ts} +1 -4
- package/src/{plugin-sdk-channel-actions-augment.ts → platform/plugin-sdk-channel-actions-augment.ts} +6 -5
- package/src/platform/runtime-events.ts +202 -0
- package/src/{secret-input.ts → platform/secret-input.ts} +117 -11
- package/src/{types.ts → platform/types.ts} +76 -1
- package/src/{http-client.ts → shared/http-client.ts} +1 -3
- package/src/{persistence-store.ts → shared/persistence-store.ts} +6 -6
- package/src/{utils.ts → shared/utils.ts} +30 -13
- package/src/targeting/agent-name-matcher.ts +1 -1
- package/src/targeting/agent-routing.ts +7 -7
- package/src/targeting/group-members-store.ts +1 -1
- package/src/{session-peer-store.ts → targeting/session-peer-store.ts} +9 -4
- package/src/{session-routing.ts → targeting/session-routing.ts} +1 -1
- package/src/targeting/target-directory-adapter.ts +3 -3
- package/src/targeting/target-directory-store.ts +1 -1
- package/src/targeting/target-input.ts +1 -1
- package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -35
- package/dist/src/config.d.ts +0 -59
- package/src/ack-reaction/dynamic-ack-reaction-events.ts +0 -123
- package/src/card-draft-controller.ts +0 -637
- package/src/draft-stream-loop.ts +0 -119
- /package/dist/src/{ack-reaction-classifier.d.ts → ack-reaction/ack-reaction-classifier.d.ts} +0 -0
- /package/dist/src/{card-callback-service.d.ts → card/card-callback-service.d.ts} +0 -0
- /package/dist/src/{draft-stream-loop.d.ts → card/draft-stream-loop.d.ts} +0 -0
- /package/dist/src/{run-usage-store.d.ts → card/run-usage-store.d.ts} +0 -0
- /package/dist/src/{feedback-learning-store.d.ts → command/feedback-learning-store.d.ts} +0 -0
- /package/dist/src/{session-command-service.d.ts → command/session-command-service.d.ts} +0 -0
- /package/dist/src/{session-lock.d.ts → gateway/session-lock.d.ts} +0 -0
- /package/dist/src/{proactive-risk-registry.d.ts → messaging/proactive-risk-registry.d.ts} +0 -0
- /package/dist/src/{reply-strategy-markdown.d.ts → messaging/reply-strategy-markdown.d.ts} +0 -0
- /package/dist/src/{access-control.d.ts → platform/access-control.d.ts} +0 -0
- /package/dist/src/{auth.d.ts → platform/auth.d.ts} +0 -0
- /package/dist/src/{config-schema.d.ts → platform/config-schema.d.ts} +0 -0
- /package/dist/src/{device-registration.d.ts → platform/device-registration.d.ts} +0 -0
- /package/dist/src/{logger-context.d.ts → platform/logger-context.d.ts} +0 -0
- /package/dist/src/{onboarding.d.ts → platform/onboarding.d.ts} +0 -0
- /package/dist/src/{plugin-sdk-channel-actions-augment.d.ts → platform/plugin-sdk-channel-actions-augment.d.ts} +0 -0
- /package/dist/src/{runtime.d.ts → platform/runtime.d.ts} +0 -0
- /package/dist/src/{secret-input.d.ts → platform/secret-input.d.ts} +0 -0
- /package/dist/src/{session-state.d.ts → platform/session-state.d.ts} +0 -0
- /package/dist/src/{signature.d.ts → platform/signature.d.ts} +0 -0
- /package/dist/src/{dedup.d.ts → shared/dedup.d.ts} +0 -0
- /package/dist/src/{http-client.d.ts → shared/http-client.d.ts} +0 -0
- /package/dist/src/{path-utils.d.ts → shared/path-utils.d.ts} +0 -0
- /package/dist/src/{peer-id-registry.d.ts → targeting/peer-id-registry.d.ts} +0 -0
- /package/dist/src/{session-peer-store.d.ts → targeting/session-peer-store.d.ts} +0 -0
- /package/src/{proactive-risk-registry.ts → messaging/proactive-risk-registry.ts} +0 -0
- /package/src/{access-control.ts → platform/access-control.ts} +0 -0
- /package/src/{logger-context.ts → platform/logger-context.ts} +0 -0
- /package/src/{runtime.ts → platform/runtime.ts} +0 -0
- /package/src/{session-state.ts → platform/session-state.ts} +0 -0
- /package/src/{signature.ts → platform/signature.ts} +0 -0
- /package/src/{dedup.ts → shared/dedup.ts} +0 -0
- /package/src/{path-utils.ts → shared/path-utils.ts} +0 -0
- /package/src/{peer-id-registry.ts → targeting/peer-id-registry.ts} +0 -0
|
@@ -4,8 +4,12 @@ import {
|
|
|
4
4
|
hasConfiguredSecretInput,
|
|
5
5
|
resolveDingTalkSecretConfig,
|
|
6
6
|
} from "./secret-input";
|
|
7
|
-
import type {
|
|
8
|
-
|
|
7
|
+
import type {
|
|
8
|
+
DingTalkChannelConfig,
|
|
9
|
+
DingTalkConfig,
|
|
10
|
+
DingTalkGatewayCapabilitiesConfig,
|
|
11
|
+
} from "./types";
|
|
12
|
+
export { resolveRelativePath, resolveUserPath } from "../shared/path-utils";
|
|
9
13
|
const DEFAULT_LEARNING_NOTE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
10
14
|
export type RuntimeDingTalkConfig = Omit<DingTalkConfig, "clientSecret"> & { clientSecret: string };
|
|
11
15
|
|
|
@@ -15,16 +19,22 @@ function normalizeLearningConfig(
|
|
|
15
19
|
): DingTalkConfig {
|
|
16
20
|
return {
|
|
17
21
|
...config,
|
|
18
|
-
learningEnabled: options.applyDefaults
|
|
22
|
+
learningEnabled: options.applyDefaults
|
|
23
|
+
? (config.learningEnabled ?? false)
|
|
24
|
+
: config.learningEnabled,
|
|
19
25
|
learningAutoApply: options.applyDefaults
|
|
20
|
-
? config.learningAutoApply ?? false
|
|
26
|
+
? (config.learningAutoApply ?? false)
|
|
21
27
|
: config.learningAutoApply,
|
|
22
28
|
learningNoteTtlMs: options.applyDefaults
|
|
23
|
-
? config.learningNoteTtlMs ?? DEFAULT_LEARNING_NOTE_TTL_MS
|
|
29
|
+
? (config.learningNoteTtlMs ?? DEFAULT_LEARNING_NOTE_TTL_MS)
|
|
24
30
|
: config.learningNoteTtlMs,
|
|
25
31
|
cardStreamingMode: options.applyDefaults
|
|
26
32
|
? (config.cardStreamingMode ?? (config.cardRealTimeStream === true ? "all" : "off"))
|
|
27
33
|
: config.cardStreamingMode,
|
|
34
|
+
// Derived, internal-only: defaults below turn an omitted mode into "off",
|
|
35
|
+
// which would otherwise be indistinguishable from an explicit "off".
|
|
36
|
+
cardStreamingModeConfigured:
|
|
37
|
+
config.cardStreamingModeConfigured ?? config.cardStreamingMode !== undefined,
|
|
28
38
|
};
|
|
29
39
|
}
|
|
30
40
|
|
|
@@ -53,6 +63,32 @@ function stripRemovedLegacyFields(config: DingTalkConfig): DingTalkConfig {
|
|
|
53
63
|
return rest as DingTalkConfig;
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Merge channel-level and account-level `gatewayCapabilities` gates by sub-key.
|
|
68
|
+
*
|
|
69
|
+
* `mergeAccountWithDefaults` is a shallow merge, so without this helper an
|
|
70
|
+
* account-level object would replace the whole channel-level `gatewayCapabilities` and
|
|
71
|
+
* silently drop a channel-level allowlist (fail-open). Merging `tools` /
|
|
72
|
+
* `docs` / `send` separately keeps channel-level restrictions in force unless
|
|
73
|
+
* the account explicitly overrides that exact sub-key.
|
|
74
|
+
*/
|
|
75
|
+
export function mergeGatewayCapabilitiesConfig(
|
|
76
|
+
channelLevel: DingTalkGatewayCapabilitiesConfig | undefined,
|
|
77
|
+
accountLevel: DingTalkGatewayCapabilitiesConfig | undefined,
|
|
78
|
+
): DingTalkGatewayCapabilitiesConfig | undefined {
|
|
79
|
+
if (!channelLevel) {
|
|
80
|
+
return accountLevel;
|
|
81
|
+
}
|
|
82
|
+
if (!accountLevel) {
|
|
83
|
+
return channelLevel;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
tools: { ...channelLevel.tools, ...accountLevel.tools },
|
|
87
|
+
docs: { ...channelLevel.docs, ...accountLevel.docs },
|
|
88
|
+
send: { ...channelLevel.send, ...accountLevel.send },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
56
92
|
/**
|
|
57
93
|
* Merge channel-level defaults into an account-specific config.
|
|
58
94
|
* Account-level values take precedence; `accounts` key is excluded to avoid recursion.
|
|
@@ -61,8 +97,10 @@ export function mergeAccountWithDefaults(
|
|
|
61
97
|
channelCfg: DingTalkConfig,
|
|
62
98
|
accountCfg: DingTalkConfig,
|
|
63
99
|
): DingTalkConfig {
|
|
64
|
-
const { accounts: _accounts, ...defaultCandidate } =
|
|
65
|
-
|
|
100
|
+
const { accounts: _accounts, ...defaultCandidate } = channelCfg as DingTalkConfig & {
|
|
101
|
+
accounts?: unknown;
|
|
102
|
+
verboseRealtimeStream?: unknown;
|
|
103
|
+
};
|
|
66
104
|
const defaults = stripRemovedLegacyFields(defaultCandidate as DingTalkConfig);
|
|
67
105
|
const normalizedAccountCfg = stripRemovedLegacyFields(
|
|
68
106
|
normalizeLearningConfig(accountCfg, { applyDefaults: false }),
|
|
@@ -73,13 +111,23 @@ export function mergeAccountWithDefaults(
|
|
|
73
111
|
Object.assign(overrides, { [key]: value });
|
|
74
112
|
}
|
|
75
113
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
114
|
+
const merged: DingTalkConfig = {
|
|
115
|
+
...defaults,
|
|
116
|
+
...overrides,
|
|
117
|
+
};
|
|
118
|
+
// Explicitness is inherited: the account opt-out only counts when the account
|
|
119
|
+
// (or the channel) actually wrote `cardStreamingMode` in its config.
|
|
120
|
+
merged.cardStreamingModeConfigured =
|
|
121
|
+
normalizedAccountCfg.cardStreamingModeConfigured === true ||
|
|
122
|
+
channelCfg.cardStreamingMode !== undefined;
|
|
123
|
+
const gatewayCapabilities = mergeGatewayCapabilitiesConfig(
|
|
124
|
+
defaults.gatewayCapabilities,
|
|
125
|
+
overrides.gatewayCapabilities,
|
|
82
126
|
);
|
|
127
|
+
if (gatewayCapabilities) {
|
|
128
|
+
merged.gatewayCapabilities = gatewayCapabilities;
|
|
129
|
+
}
|
|
130
|
+
return normalizeLearningConfig(merged, { applyDefaults: true });
|
|
83
131
|
}
|
|
84
132
|
|
|
85
133
|
/**
|
|
@@ -113,6 +161,107 @@ export function isConfigured(cfg: OpenClawConfig, accountId?: string): boolean {
|
|
|
113
161
|
return Boolean(config.clientId && hasConfiguredSecretInput(config.clientSecret));
|
|
114
162
|
}
|
|
115
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Resolved Gateway RPC capability settings (Issue #608, 问题 3).
|
|
166
|
+
* All capabilities default to enabled; allowlists default to unrestricted.
|
|
167
|
+
*/
|
|
168
|
+
export interface ResolvedGatewayCapabilities {
|
|
169
|
+
/** `dingtalk.docs.*` / `dingtalk-connector.docs.*` RPCs enabled (default: true) */
|
|
170
|
+
docsEnabled: boolean;
|
|
171
|
+
/** `dingtalk-connector.sendToUser/sendToGroup/send` RPCs enabled (default: true) */
|
|
172
|
+
proactiveSendEnabled: boolean;
|
|
173
|
+
/** When set, docs RPCs only accept these spaceId values */
|
|
174
|
+
allowedSpaceIds?: string[];
|
|
175
|
+
/** When set, proactive-send RPCs only accept these `user:*` / `group:*` targets */
|
|
176
|
+
allowedTargets?: string[];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Denial reason returned when `gatewayCapabilities.tools.docs` is explicitly false. */
|
|
180
|
+
export const DOCS_GATE_DISABLED_REASON =
|
|
181
|
+
"dingtalk docs Gateway RPC is disabled by config (gatewayCapabilities.tools.docs = false)";
|
|
182
|
+
|
|
183
|
+
/** Denial reason returned when `gatewayCapabilities.tools.proactiveSend` is explicitly false. */
|
|
184
|
+
export const PROACTIVE_SEND_GATE_DISABLED_REASON =
|
|
185
|
+
"dingtalk proactive-send Gateway RPC is disabled by config (gatewayCapabilities.tools.proactiveSend = false)";
|
|
186
|
+
|
|
187
|
+
const DEFAULT_GATEWAY_CAPABILITIES: ResolvedGatewayCapabilities = Object.freeze({
|
|
188
|
+
docsEnabled: true,
|
|
189
|
+
proactiveSendEnabled: true,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resolve Gateway RPC capability configuration for an account.
|
|
194
|
+
* Account-level `gatewayCapabilities` is merged with channel-level defaults by sub-key
|
|
195
|
+
* (see `mergeGatewayCapabilitiesConfig`); both default to all capabilities enabled.
|
|
196
|
+
*/
|
|
197
|
+
export function resolveGatewayCapabilityConfig(
|
|
198
|
+
cfg: OpenClawConfig,
|
|
199
|
+
accountId?: string,
|
|
200
|
+
): ResolvedGatewayCapabilities {
|
|
201
|
+
const config = getConfig(cfg, accountId);
|
|
202
|
+
const gatewayCapabilities = config.gatewayCapabilities;
|
|
203
|
+
if (!gatewayCapabilities) {
|
|
204
|
+
return DEFAULT_GATEWAY_CAPABILITIES;
|
|
205
|
+
}
|
|
206
|
+
const tools = gatewayCapabilities.tools ?? {};
|
|
207
|
+
const docs = gatewayCapabilities.docs ?? {};
|
|
208
|
+
const send = gatewayCapabilities.send ?? {};
|
|
209
|
+
return {
|
|
210
|
+
docsEnabled: tools.docs !== false,
|
|
211
|
+
proactiveSendEnabled: tools.proactiveSend !== false,
|
|
212
|
+
allowedSpaceIds: docs.allowedSpaceIds,
|
|
213
|
+
allowedTargets: send.allowedTargets,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check a docs RPC request against the configured capability gates.
|
|
219
|
+
* Returns null when allowed, or a human-readable denial reason.
|
|
220
|
+
*
|
|
221
|
+
* A configured allowlist is fail-closed: an empty list (possible only when
|
|
222
|
+
* config validation was bypassed) denies every docs RPC, and a request without
|
|
223
|
+
* a spaceId is denied as well because its doc space cannot be verified.
|
|
224
|
+
*/
|
|
225
|
+
export function checkDocsGatewayCapability(
|
|
226
|
+
caps: ResolvedGatewayCapabilities,
|
|
227
|
+
spaceId: string | undefined,
|
|
228
|
+
): string | null {
|
|
229
|
+
if (!caps.docsEnabled) {
|
|
230
|
+
return DOCS_GATE_DISABLED_REASON;
|
|
231
|
+
}
|
|
232
|
+
if (caps.allowedSpaceIds) {
|
|
233
|
+
if (!spaceId) {
|
|
234
|
+
return "docs RPC denied: this request carries no spaceId while gatewayCapabilities.docs.allowedSpaceIds is configured (dingtalk.docs.append never carries a spaceId)";
|
|
235
|
+
}
|
|
236
|
+
if (!caps.allowedSpaceIds.includes(spaceId)) {
|
|
237
|
+
return "spaceId is not in gatewayCapabilities.docs.allowedSpaceIds allowlist";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check a proactive-send RPC request against the configured capability gates.
|
|
245
|
+
* Returns null when allowed, or a human-readable denial reason.
|
|
246
|
+
*
|
|
247
|
+
* A configured allowlist is fail-closed: an empty list (possible only when
|
|
248
|
+
* config validation was bypassed) denies every proactive-send RPC.
|
|
249
|
+
*/
|
|
250
|
+
export function checkProactiveSendGatewayCapability(
|
|
251
|
+
caps: ResolvedGatewayCapabilities,
|
|
252
|
+
target: string,
|
|
253
|
+
): string | null {
|
|
254
|
+
if (!caps.proactiveSendEnabled) {
|
|
255
|
+
return PROACTIVE_SEND_GATE_DISABLED_REASON;
|
|
256
|
+
}
|
|
257
|
+
if (caps.allowedTargets) {
|
|
258
|
+
if (!caps.allowedTargets.includes(target)) {
|
|
259
|
+
return "target is not in gatewayCapabilities.send.allowedTargets allowlist";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
116
265
|
export async function resolveRuntimeConfig(
|
|
117
266
|
config: DingTalkConfig,
|
|
118
267
|
log?: { warn?: (message: string, data?: unknown) => void },
|
|
@@ -154,7 +303,10 @@ function hasOwn(obj: unknown, key: string): boolean {
|
|
|
154
303
|
return typeof obj === "object" && obj !== null && Object.prototype.hasOwnProperty.call(obj, key);
|
|
155
304
|
}
|
|
156
305
|
|
|
157
|
-
function resolveAgentIdentityEmoji(
|
|
306
|
+
function resolveAgentIdentityEmoji(
|
|
307
|
+
cfg: OpenClawConfig,
|
|
308
|
+
agentId?: string | null,
|
|
309
|
+
): string | undefined {
|
|
158
310
|
const targetAgentId = String(agentId || "").trim();
|
|
159
311
|
if (!targetAgentId) {
|
|
160
312
|
return undefined;
|
|
@@ -234,7 +386,6 @@ export function stripTargetPrefix(target: string): { targetId: string; isExplici
|
|
|
234
386
|
|
|
235
387
|
const DEFAULT_ACCOUNT_ID = "default";
|
|
236
388
|
|
|
237
|
-
|
|
238
389
|
/**
|
|
239
390
|
* List all DingTalk account IDs from config
|
|
240
391
|
*/
|
|
@@ -326,10 +477,7 @@ export function resolveDingTalkAccount(
|
|
|
326
477
|
|
|
327
478
|
const accountConfig = dingtalk?.accounts?.[id];
|
|
328
479
|
if (accountConfig) {
|
|
329
|
-
const merged = mergeAccountWithDefaults(
|
|
330
|
-
dingtalk as DingTalkConfig,
|
|
331
|
-
accountConfig,
|
|
332
|
-
);
|
|
480
|
+
const merged = mergeAccountWithDefaults(dingtalk as DingTalkConfig, accountConfig);
|
|
333
481
|
const publicMerged = stripRemovedLegacyFields(merged);
|
|
334
482
|
return {
|
|
335
483
|
...publicMerged,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import httpClient from "
|
|
1
|
+
import httpClient from "../shared/http-client.js";
|
|
2
2
|
|
|
3
3
|
// ── Constants ──────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
@@ -52,7 +52,9 @@ async function apiPost(
|
|
|
52
52
|
const errcode = data.errcode;
|
|
53
53
|
if (errcode !== undefined && errcode !== 0) {
|
|
54
54
|
const errmsg = asString(data.errmsg) || "unknown error";
|
|
55
|
-
throw new RegistrationError(
|
|
55
|
+
throw new RegistrationError(
|
|
56
|
+
`API error [${path}]: ${errmsg} (errcode=${typeof errcode === "number" ? errcode : asString(errcode)})`,
|
|
57
|
+
);
|
|
56
58
|
}
|
|
57
59
|
return data;
|
|
58
60
|
}
|
|
@@ -137,8 +139,7 @@ export async function beginDeviceRegistration(): Promise<DeviceRegistrationSessi
|
|
|
137
139
|
// Suppress unhandled rejection when abort fires outside Promise.race
|
|
138
140
|
abortPromise?.catch(() => {});
|
|
139
141
|
|
|
140
|
-
const sleep = () =>
|
|
141
|
-
new Promise((resolve) => setTimeout(resolve, interval * 1000));
|
|
142
|
+
const sleep = () => new Promise((resolve) => setTimeout(resolve, interval * 1000));
|
|
142
143
|
|
|
143
144
|
try {
|
|
144
145
|
while (Date.now() < deadline) {
|
|
@@ -7,10 +7,7 @@ import type {
|
|
|
7
7
|
} from "openclaw/plugin-sdk/setup";
|
|
8
8
|
import { DEFAULT_ACCOUNT_ID, formatDocsLink, normalizeAccountId } from "openclaw/plugin-sdk/setup";
|
|
9
9
|
import { listDingTalkAccountIds, resolveDingTalkAccount } from "./config.js";
|
|
10
|
-
import {
|
|
11
|
-
beginDeviceRegistration,
|
|
12
|
-
RegistrationError,
|
|
13
|
-
} from "./device-registration.js";
|
|
10
|
+
import { beginDeviceRegistration, RegistrationError } from "./device-registration.js";
|
|
14
11
|
import {
|
|
15
12
|
hasConfiguredSecretInput,
|
|
16
13
|
normalizeSecretInputString,
|
package/src/{plugin-sdk-channel-actions-augment.ts → platform/plugin-sdk-channel-actions-augment.ts}
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";
|
|
2
2
|
|
|
3
|
-
type JsonResultReturn =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
type JsonResultReturn =
|
|
4
|
+
NonNullable<ChannelMessageActionAdapter["handleAction"]> extends (
|
|
5
|
+
...args: unknown[]
|
|
6
|
+
) => Promise<infer TResult>
|
|
7
|
+
? TResult
|
|
8
|
+
: never;
|
|
8
9
|
|
|
9
10
|
declare module "openclaw/plugin-sdk/channel-actions" {
|
|
10
11
|
export function jsonResult(payload: unknown): JsonResultReturn;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-domain runtime agent-event infrastructure.
|
|
3
|
+
*
|
|
4
|
+
* Owns the generic OpenClaw `events.onAgentEvent` surface that several
|
|
5
|
+
* domains consume: event typing, reference-counted fan-out, field accessors,
|
|
6
|
+
* and run correlation. Domain owners (ack reactions, AI card task progress)
|
|
7
|
+
* build their own behavior on top of these primitives instead of importing
|
|
8
|
+
* each other's private modules.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { getErrorMessage } from "../shared/utils";
|
|
12
|
+
|
|
13
|
+
export type RuntimeEventsLogger = {
|
|
14
|
+
debug?: (msg: string) => void;
|
|
15
|
+
info?: (msg: string) => void;
|
|
16
|
+
warn?: (msg: string) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type RuntimeAgentEvent = {
|
|
20
|
+
stream?: string;
|
|
21
|
+
runId?: string;
|
|
22
|
+
sessionKey?: string;
|
|
23
|
+
data?: {
|
|
24
|
+
phase?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
args?: unknown;
|
|
27
|
+
runId?: string;
|
|
28
|
+
sessionKey?: string;
|
|
29
|
+
toolCallId?: string;
|
|
30
|
+
meta?: {
|
|
31
|
+
runId?: string;
|
|
32
|
+
sessionKey?: string;
|
|
33
|
+
} | null;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type RuntimeEventsSurface = {
|
|
38
|
+
onAgentEvent?: (listener: (event: unknown) => void) => () => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fan one upstream `onAgentEvent` subscription out to multiple local consumers.
|
|
43
|
+
*
|
|
44
|
+
* The upstream subscription is reference-counted: it is created with the first
|
|
45
|
+
* local listener and released with the last one. Each listener is isolated so a
|
|
46
|
+
* throwing consumer can neither starve the remaining consumers nor bubble back
|
|
47
|
+
* into the host event emitter.
|
|
48
|
+
*/
|
|
49
|
+
export function createRuntimeEventsFanout(
|
|
50
|
+
upstream: RuntimeEventsSurface | undefined,
|
|
51
|
+
options: { log?: RuntimeEventsLogger } = {},
|
|
52
|
+
): RuntimeEventsSurface {
|
|
53
|
+
const listeners = new Set<(event: unknown) => void>();
|
|
54
|
+
let unsubscribeUpstream: (() => void) | undefined;
|
|
55
|
+
|
|
56
|
+
const ensureUpstreamSubscription = () => {
|
|
57
|
+
if (unsubscribeUpstream || !upstream?.onAgentEvent) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
unsubscribeUpstream = upstream.onAgentEvent((event: unknown) => {
|
|
61
|
+
// Dispatch against a snapshot so a listener that unsubscribes another
|
|
62
|
+
// listener during this event still receives the current event.
|
|
63
|
+
const currentListeners = Array.from(listeners);
|
|
64
|
+
for (const listener of currentListeners) {
|
|
65
|
+
try {
|
|
66
|
+
listener(event);
|
|
67
|
+
} catch (error: unknown) {
|
|
68
|
+
options.log?.warn?.(
|
|
69
|
+
`[DingTalk][RuntimeEvents] Listener failed: ${getErrorMessage(error)}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
onAgentEvent(listener: (event: unknown) => void) {
|
|
78
|
+
listeners.add(listener);
|
|
79
|
+
ensureUpstreamSubscription();
|
|
80
|
+
return () => {
|
|
81
|
+
listeners.delete(listener);
|
|
82
|
+
if (listeners.size === 0 && unsubscribeUpstream) {
|
|
83
|
+
unsubscribeUpstream();
|
|
84
|
+
unsubscribeUpstream = undefined;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function firstTrimmedString(...values: unknown[]): string | undefined {
|
|
92
|
+
for (const value of values) {
|
|
93
|
+
if (typeof value === "string" && value.trim()) {
|
|
94
|
+
return value.trim();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getEventRunId(event: RuntimeAgentEvent | undefined): string | undefined {
|
|
101
|
+
return firstTrimmedString(event?.runId, event?.data?.runId, event?.data?.meta?.runId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getEventSessionKey(event: RuntimeAgentEvent | undefined): string | undefined {
|
|
105
|
+
return firstTrimmedString(
|
|
106
|
+
event?.sessionKey,
|
|
107
|
+
event?.data?.sessionKey,
|
|
108
|
+
event?.data?.meta?.sessionKey,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function describeEvent(event: RuntimeAgentEvent | undefined): string {
|
|
113
|
+
const stream = firstTrimmedString(event?.stream) || "-";
|
|
114
|
+
const phase = firstTrimmedString(event?.data?.phase) || "-";
|
|
115
|
+
const toolName = firstTrimmedString(event?.data?.name) || "-";
|
|
116
|
+
const toolCallId = firstTrimmedString(event?.data?.toolCallId) || "-";
|
|
117
|
+
return (
|
|
118
|
+
`stream=${stream} phase=${phase} runId=${getEventRunId(event) || "-"} ` +
|
|
119
|
+
`sessionKey=${getEventSessionKey(event) || "-"} toolCallId=${toolCallId} toolName=${toolName}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build a predicate that decides whether an agent event belongs to one reply.
|
|
125
|
+
*
|
|
126
|
+
* Correlation prefers an already captured `runId`; otherwise it accepts events
|
|
127
|
+
* whose `sessionKey` matches, and finally falls back to optimistically
|
|
128
|
+
* capturing the first session-less lifecycle start inside a short window.
|
|
129
|
+
*
|
|
130
|
+
* `consumer` labels the debug logs so two consumers sharing the same session
|
|
131
|
+
* (ack reactions and card task progress) stay distinguishable while triaging.
|
|
132
|
+
*/
|
|
133
|
+
export function createAgentEventCorrelator(params: {
|
|
134
|
+
/** Short label identifying the consuming domain in debug logs. */
|
|
135
|
+
consumer: string;
|
|
136
|
+
sessionKey: string;
|
|
137
|
+
enabled: boolean;
|
|
138
|
+
createdAt: number;
|
|
139
|
+
optimisticCaptureWindowMs: number;
|
|
140
|
+
log?: RuntimeEventsLogger;
|
|
141
|
+
}) {
|
|
142
|
+
const logPrefix = `[DingTalk][AgentEventCorrelation][${params.consumer}]`;
|
|
143
|
+
let activeRunId: string | undefined;
|
|
144
|
+
let correlationUnavailableLogged = false;
|
|
145
|
+
let optimisticCaptureCount = 0;
|
|
146
|
+
|
|
147
|
+
return (event: RuntimeAgentEvent | undefined): boolean => {
|
|
148
|
+
const eventRunId = getEventRunId(event);
|
|
149
|
+
const eventSessionKey = getEventSessionKey(event);
|
|
150
|
+
const eventStream = firstTrimmedString(event?.stream) || "";
|
|
151
|
+
const eventPhase = firstTrimmedString(event?.data?.phase) || "";
|
|
152
|
+
|
|
153
|
+
if (activeRunId) {
|
|
154
|
+
const matched = eventRunId === activeRunId;
|
|
155
|
+
params.log?.debug?.(
|
|
156
|
+
`${logPrefix} correlation by runId matched=${matched} activeRunId=${activeRunId} ` +
|
|
157
|
+
`eventRunId=${eventRunId || "-"} eventSessionKey=${eventSessionKey || "-"}`,
|
|
158
|
+
);
|
|
159
|
+
return matched;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (eventSessionKey === params.sessionKey) {
|
|
163
|
+
if (eventRunId) {
|
|
164
|
+
activeRunId = eventRunId;
|
|
165
|
+
params.log?.debug?.(
|
|
166
|
+
`${logPrefix} captured active runId=${activeRunId} from sessionKey=${params.sessionKey}`,
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
params.log?.debug?.(
|
|
170
|
+
`${logPrefix} correlated by sessionKey=${params.sessionKey} without runId`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
optimisticCaptureCount === 0 &&
|
|
178
|
+
eventStream === "lifecycle" &&
|
|
179
|
+
eventPhase === "start" &&
|
|
180
|
+
eventRunId &&
|
|
181
|
+
!eventSessionKey &&
|
|
182
|
+
Date.now() - params.createdAt <= params.optimisticCaptureWindowMs
|
|
183
|
+
) {
|
|
184
|
+
optimisticCaptureCount += 1;
|
|
185
|
+
activeRunId = eventRunId;
|
|
186
|
+
params.log?.debug?.(
|
|
187
|
+
`${logPrefix} optimistically captured active runId=${activeRunId} ` +
|
|
188
|
+
`from first lifecycle event without sessionKey windowMs=${params.optimisticCaptureWindowMs}`,
|
|
189
|
+
);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!correlationUnavailableLogged && params.enabled) {
|
|
194
|
+
correlationUnavailableLogged = true;
|
|
195
|
+
params.log?.debug?.(
|
|
196
|
+
`${logPrefix} ignored uncorrelated agent events; ` +
|
|
197
|
+
`reason=${getErrorMessage(eventRunId || eventSessionKey || "waiting for sessionKey/runId match")}`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import {
|
|
3
|
+
coerceSecretRef,
|
|
3
4
|
hasConfiguredSecretInput as hasConfiguredOpenClawSecretInput,
|
|
4
5
|
resolveConfiguredSecretInputString,
|
|
5
6
|
} from "openclaw/plugin-sdk/secret-input-runtime";
|
|
7
|
+
import { canResolveEnvSecretRefInReadOnlyPath } from "openclaw/plugin-sdk/secret-ref-readonly";
|
|
6
8
|
import { z } from "zod";
|
|
7
9
|
import { getDingTalkRuntime } from "./runtime";
|
|
8
10
|
|
|
@@ -28,6 +30,16 @@ type SecretInputLog = {
|
|
|
28
30
|
const SECRET_INPUT_PROVIDER_PATTERN = /^[^:>]+$/;
|
|
29
31
|
const SECRET_INPUT_ID_PATTERN = /^[^>]+$/;
|
|
30
32
|
|
|
33
|
+
const SECRET_INPUT_PATH = "channels.dingtalk.clientSecret";
|
|
34
|
+
|
|
35
|
+
function normalizeOptionalSecretString(value: unknown): string | undefined {
|
|
36
|
+
if (typeof value !== "string") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
return trimmed || undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
function buildSecretInputFailure(
|
|
32
44
|
value: SecretInputRef,
|
|
33
45
|
reason: string,
|
|
@@ -40,6 +52,105 @@ function buildSecretInputFailure(
|
|
|
40
52
|
};
|
|
41
53
|
}
|
|
42
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Resolve one `env` SecretInput reference without handing the whole ambient
|
|
57
|
+
* environment to the host resolver.
|
|
58
|
+
*
|
|
59
|
+
* Mirrors the official channel pattern (`openclaw/plugin-sdk/secret-ref-readonly`):
|
|
60
|
+
* inspect the reference, require the host to authorize this provider/id for a
|
|
61
|
+
* read-only path, and only then read the single `process.env[id]`. The SDK's
|
|
62
|
+
* own `resolveReadOnlyEnvSecretRef` collapses "not authorized" and "authorized
|
|
63
|
+
* but the variable is unset" into one `blocked` status, which would tell an
|
|
64
|
+
* operator with a correct allowlist to change it; re-deriving the same checks
|
|
65
|
+
* here keeps the failure reason actionable. Both branches fail closed.
|
|
66
|
+
*/
|
|
67
|
+
function resolveEnvSecretInputRef(
|
|
68
|
+
value: SecretInputRef,
|
|
69
|
+
hostConfig: OpenClawConfig,
|
|
70
|
+
): { value?: string; failure?: SecretInputResolutionFailure } {
|
|
71
|
+
const refLabel = `${value.source}:${value.provider}:${value.id}`;
|
|
72
|
+
const unresolved = (detail?: string) =>
|
|
73
|
+
buildSecretInputFailure(
|
|
74
|
+
value,
|
|
75
|
+
`${SECRET_INPUT_PATH} SecretRef is unresolved (${refLabel}).${detail ? ` ${detail}` : ""}`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const ref = coerceSecretRef(value, hostConfig.secrets?.defaults);
|
|
79
|
+
if (!ref) {
|
|
80
|
+
return { failure: unresolved("SecretRef shape is not coercible.") };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (ref.source !== "env") {
|
|
84
|
+
return {
|
|
85
|
+
failure: unresolved(
|
|
86
|
+
`Source "${ref.source}" is not usable from the DingTalk clientSecret env path.`,
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const providerConfig = hostConfig.secrets?.providers?.[ref.provider];
|
|
92
|
+
if (providerConfig && providerConfig.source !== "env") {
|
|
93
|
+
return {
|
|
94
|
+
failure: unresolved(
|
|
95
|
+
`Secret provider "${ref.provider}" has source "${providerConfig.source}" but the ref requests "env".`,
|
|
96
|
+
),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
!canResolveEnvSecretRefInReadOnlyPath({
|
|
102
|
+
cfg: hostConfig,
|
|
103
|
+
provider: ref.provider,
|
|
104
|
+
id: ref.id,
|
|
105
|
+
})
|
|
106
|
+
) {
|
|
107
|
+
return {
|
|
108
|
+
failure: unresolved(
|
|
109
|
+
`Environment variable "${value.id}" is not authorized for a read-only path: ` +
|
|
110
|
+
`set secrets.providers.${value.provider}.source to "env" and include ` +
|
|
111
|
+
`"${value.id}" in its allowlist.`,
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const envValue = normalizeOptionalSecretString(process.env[ref.id]);
|
|
117
|
+
if (envValue) {
|
|
118
|
+
return { value: envValue };
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
failure: unresolved(`Environment variable "${value.id}" is authorized but unset or empty.`),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a `file` SecretInput reference with a scoped resolver call.
|
|
127
|
+
*
|
|
128
|
+
* The explicit empty `env` means this branch never reads ambient credentials.
|
|
129
|
+
* It does not stop the host from expanding a configured provider path (for
|
|
130
|
+
* example a leading `~`) against its own environment.
|
|
131
|
+
*/
|
|
132
|
+
async function resolveFileSecretInputRef(
|
|
133
|
+
value: SecretInputRef,
|
|
134
|
+
hostConfig: OpenClawConfig,
|
|
135
|
+
): Promise<{ value?: string; failure?: SecretInputResolutionFailure }> {
|
|
136
|
+
const resolved = await resolveConfiguredSecretInputString({
|
|
137
|
+
config: hostConfig,
|
|
138
|
+
env: {},
|
|
139
|
+
value,
|
|
140
|
+
path: SECRET_INPUT_PATH,
|
|
141
|
+
unresolvedReasonStyle: "detailed",
|
|
142
|
+
});
|
|
143
|
+
if (resolved.value) {
|
|
144
|
+
return { value: resolved.value };
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
failure: buildSecretInputFailure(
|
|
148
|
+
value,
|
|
149
|
+
resolved.unresolvedRefReason || "secret reference is unresolved",
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
43
154
|
export function formatSecretInputResolutionFailure(failure: SecretInputResolutionFailure): string {
|
|
44
155
|
return `${failure.source}:${failure.provider}:${failure.id} - ${failure.reason}`;
|
|
45
156
|
}
|
|
@@ -133,20 +244,15 @@ export async function resolveSecretInputStringWithFailure(
|
|
|
133
244
|
}
|
|
134
245
|
}
|
|
135
246
|
try {
|
|
136
|
-
const resolved =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
path: "channels.dingtalk.clientSecret",
|
|
141
|
-
unresolvedReasonStyle: "detailed",
|
|
142
|
-
});
|
|
247
|
+
const resolved =
|
|
248
|
+
value.source === "env"
|
|
249
|
+
? resolveEnvSecretInputRef(value, hostConfig)
|
|
250
|
+
: await resolveFileSecretInputRef(value, hostConfig);
|
|
143
251
|
if (resolved.value) {
|
|
144
252
|
return { value: resolved.value };
|
|
145
253
|
}
|
|
146
|
-
const failure =
|
|
147
|
-
value,
|
|
148
|
-
resolved.unresolvedRefReason || "secret reference is unresolved",
|
|
149
|
-
);
|
|
254
|
+
const failure =
|
|
255
|
+
resolved.failure ?? buildSecretInputFailure(value, "secret reference is unresolved");
|
|
150
256
|
log?.warn?.("[DingTalk][SecretInput] Failed to resolve secret", {
|
|
151
257
|
provider: value.provider,
|
|
152
258
|
id: value.id,
|