@xmanrui/dsh-im 4.2.1 → 4.4.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.en.md +4 -2
- package/README.md +4 -2
- package/lib/client.js +1267 -787
- package/lib/index.js +237 -235
- package/package.json +1 -1
- package/plugin-src/client/access-policy-settings.js +351 -0
- package/plugin-src/client/channel-card-meta.js +2 -1
- package/plugin-src/client/channels/dingtalk/api.js +5 -0
- package/plugin-src/client/channels/dingtalk/index.js +1 -0
- package/plugin-src/client/channels/feishu/api.js +5 -0
- package/plugin-src/client/channels/feishu/index.js +1 -0
- package/plugin-src/client/channels/qq/api.js +5 -0
- package/plugin-src/client/channels/qq/index.js +1 -0
- package/plugin-src/client/channels/shared/token-api.js +5 -0
- package/plugin-src/client/channels/shared/token-channel.js +1 -0
- package/plugin-src/client/channels/telegram/api.js +2 -17
- package/plugin-src/client/channels/telegram/index.js +0 -112
- package/plugin-src/client/channels/telegram/styles.js +0 -28
- package/plugin-src/client/channels/wecom/api.js +5 -0
- package/plugin-src/client/channels/wecom/index.js +1 -0
- package/plugin-src/client/channels/weixin/api.js +5 -0
- package/plugin-src/client/channels/weixin/index.js +1 -0
- package/plugin-src/client/channels/whatsapp/api.js +4 -10
- package/plugin-src/client/channels/whatsapp/index.js +1 -125
- package/plugin-src/client/channels/whatsapp/styles.js +0 -25
- package/plugin-src/client/context-enhancement.js +207 -102
- package/plugin-src/client/delivery-settings.js +61 -9
- package/plugin-src/client/i18n.js +71 -3
- package/plugin-src/client/index.js +1 -0
- package/plugin-src/client/styles.js +59 -8
- package/plugin-src/host/channels/dingtalk/production.mjs +12 -1
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +39 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +19 -2
- package/plugin-src/host/channels/qq/production.mjs +12 -1
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/access-policy-production.mjs +137 -0
- package/plugin-src/host/channels/shared/access-policy-rpc.mjs +17 -0
- package/plugin-src/host/channels/shared/production.mjs +15 -1
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/slack/production.mjs +12 -1
- package/plugin-src/host/channels/slack/rpc.mjs +10 -0
- package/plugin-src/host/channels/telegram/rpc.mjs +2 -49
- package/plugin-src/host/channels/wecom/production.mjs +12 -1
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +12 -1
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +16 -2
- package/plugin-src/host/channels/whatsapp/rpc.mjs +10 -15
- package/scripts/verify-package.mjs +4 -3
- package/src/channels/dingtalk/dingtalk-bridge.mjs +61 -9
- package/src/channels/dingtalk/dingtalk-runtime.mjs +4 -0
- package/src/channels/discord/discord-runtime.mjs +29 -1
- package/src/channels/feishu/bridge.mjs +286 -120
- package/src/channels/feishu/feishu-runtime.mjs +4 -0
- package/src/channels/feishu/message-utils.mjs +5 -0
- package/src/channels/qq/qq-bridge.mjs +59 -17
- package/src/channels/qq/qq-runtime.mjs +25 -3
- package/src/channels/shared/access-policy.mjs +210 -0
- package/src/channels/shared/bot-workspace-store.mjs +176 -33
- package/src/channels/shared/command-permission.mjs +32 -0
- package/src/channels/shared/compact-command.mjs +5 -2
- package/src/channels/shared/context-enhancement.mjs +73 -20
- package/src/channels/shared/i18n-en/shared-c.mjs +2 -0
- package/src/channels/shared/inbound-access.mjs +43 -0
- package/src/channels/shared/text-harness-bridge.mjs +47 -4
- package/src/channels/shared/workspace-command.mjs +10 -1
- package/src/channels/slack/slack-runtime.mjs +4 -0
- package/src/channels/telegram/telegram-runtime.mjs +5 -13
- package/src/channels/wecom/wecom-bridge.mjs +51 -4
- package/src/channels/wecom/wecom-runtime.mjs +4 -0
- package/src/channels/weixin/weixin-bridge.mjs +59 -16
- package/src/channels/weixin/weixin-runtime.mjs +4 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +42 -20
|
@@ -228,10 +228,15 @@ const CSS = String.raw`
|
|
|
228
228
|
.dim-contextHeaderTooltip { width: min(340px, calc(100vw - 72px)); }
|
|
229
229
|
.dim-contextClose { width: 30px; height: 30px; flex: none; display: grid; place-items: center; padding: 0; border: 0; border-radius: 6px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; cursor: pointer; }
|
|
230
230
|
.dim-contextClose:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
231
|
-
.dim-
|
|
232
|
-
.dim-
|
|
231
|
+
.dim-contextTabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3px; margin-top: 14px; padding: 3px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 8px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
|
|
232
|
+
.dim-contextTab { min-width: 0; min-height: 34px; padding: 5px 12px; border: 0; border-radius: 6px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-weight: 500; cursor: pointer; transition: color .15s ease, background .15s ease, box-shadow .15s ease; }
|
|
233
|
+
.dim-contextTab:hover:not(:disabled):not([aria-selected="true"]) { color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
234
|
+
.dim-contextTab[aria-selected="true"] { color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 3px rgb(31 35 41 / 12%); }
|
|
235
|
+
.dim-contextTabPanel[hidden] { display: none; }
|
|
236
|
+
.dim-contextSection { min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
237
|
+
.dim-contextScope { margin-top: 10px; padding: 12px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 9px; background: var(--dsw-alias-bg-layer-1, #fff); }
|
|
238
|
+
.dim-contextScopeBlock { margin-top: 12px; }
|
|
233
239
|
.dim-contextLegend { position: relative; display: inline-flex; align-items: center; gap: 6px; }
|
|
234
|
-
.dim-contextSwitches { display: grid; grid-template-columns: 1fr; gap: 2px; }
|
|
235
240
|
.dim-contextSwitchRow { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 34px; cursor: pointer; }
|
|
236
241
|
.dim-contextSwitchLabel { min-width: 0; display: inline-flex; align-items: baseline; gap: 5px; flex-wrap: wrap; }
|
|
237
242
|
.dim-contextUnavailable { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 16px; font-weight: 400; }
|
|
@@ -274,7 +279,7 @@ const CSS = String.raw`
|
|
|
274
279
|
.dim-contextEntry:focus-visible, .dim-contextDialog button:focus-visible, .dim-contextDialog input:focus-visible, .dim-contextDialog textarea:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
275
280
|
.dim-contextEntry:disabled, .dim-contextDialog button:disabled, .dim-contextDialog input:disabled, .dim-contextDialog textarea:disabled { opacity: .55; cursor: not-allowed; }
|
|
276
281
|
@media (pointer: coarse) {
|
|
277
|
-
.dim-contextEntry, .dim-contextClose, .dim-contextFooter button, .dim-contextTextActions button, .dim-contextField, .dim-contextSwitchRow { min-height: 44px; }
|
|
282
|
+
.dim-contextEntry, .dim-contextTab, .dim-contextClose, .dim-contextFooter button, .dim-contextTextActions button, .dim-contextField, .dim-contextSwitchRow { min-height: 44px; }
|
|
278
283
|
.dim-contextClose, .dim-contextTextActions button { min-width: 44px; }
|
|
279
284
|
.dim-contextGuidance textarea { font-size: 16px; }
|
|
280
285
|
}
|
|
@@ -364,9 +369,18 @@ const CSS = String.raw`
|
|
|
364
369
|
.dim-botSettingsButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
365
370
|
.dim-botSettingsTooltip { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: max-content; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
|
366
371
|
.dim-botSettingsAction:hover .dim-botSettingsTooltip, .dim-botSettingsAction:focus-within .dim-botSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
367
|
-
.dim-deliveryPage { min-width: 0; display: grid;
|
|
372
|
+
.dim-deliveryPage { min-width: 0; display: grid; }
|
|
368
373
|
.dim-deliveryHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
|
369
|
-
.dim-
|
|
374
|
+
.dim-botSettingsTabsBar { min-width: 0; margin-top: 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, #dfe1e5); }
|
|
375
|
+
.dim-botSettingsTabs { min-width: 0; display: flex; align-items: flex-end; gap: 24px; overflow-x: auto; scrollbar-width: none; }
|
|
376
|
+
.dim-botSettingsTabs::-webkit-scrollbar { display: none; }
|
|
377
|
+
.dim-botSettingsTab { position: relative; min-height: 38px; flex: none; display: inline-flex; align-items: center; justify-content: center; padding: 6px 2px 9px; border: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 13px; line-height: 20px; font-weight: 560; white-space: nowrap; cursor: pointer; transition: color .15s ease; }
|
|
378
|
+
.dim-botSettingsTab::after { content: ''; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px 2px 0 0; background: transparent; transform: scaleX(.45); transition: background .15s ease, transform .15s ease; }
|
|
379
|
+
.dim-botSettingsTab:hover:not([aria-selected="true"]) { color: var(--dsw-alias-label-primary, #1f2329); }
|
|
380
|
+
.dim-botSettingsTab[aria-selected="true"] { color: var(--dsw-alias-state-business-primary, #3370ff); font-weight: 650; }
|
|
381
|
+
.dim-botSettingsTab[aria-selected="true"]::after { background: var(--dsw-alias-state-business-primary, #3370ff); transform: scaleX(1); }
|
|
382
|
+
.dim-botSettingsTab:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: -2px; border-radius: 5px; }
|
|
383
|
+
.dim-botSettingsTabPanel { min-width: 0; display: grid; gap: 14px; padding-top: 14px; }
|
|
370
384
|
.dim-deliveryDocsLink { min-height: 30px; flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); font-size: 12px; line-height: 18px; text-decoration: none; white-space: nowrap; }
|
|
371
385
|
.dim-deliveryDocsLink:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, transparent); }
|
|
372
386
|
.dim-deliveryDocsLink:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
@@ -378,6 +392,7 @@ const CSS = String.raw`
|
|
|
378
392
|
.dim-deliveryButton[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); }
|
|
379
393
|
.dim-deliveryBack { flex: none; border-color: transparent; background: transparent; }
|
|
380
394
|
.dim-deliveryIdentity, .dim-deliveryTargets { min-width: 0; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); }
|
|
395
|
+
.dim-deliveryIdentityHeading { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
381
396
|
.dim-deliveryBotName, .dim-deliverySectionHeading h3 { min-width: 0; overflow: hidden; margin: 0; font-size: 15px; line-height: 22px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
|
|
382
397
|
.dim-deliveryIdentity > div:first-child p, .dim-deliverySectionHeading p { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
|
|
383
398
|
.dim-deliveryBotId { min-width: 0; display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; align-items: center; gap: 10px; margin-top: 12px; padding: 10px 12px; border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
|
|
@@ -420,6 +435,38 @@ const CSS = String.raw`
|
|
|
420
435
|
.dim-targetField input[readonly] { color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
|
|
421
436
|
.dim-targetFormError { margin: 10px 0 0; font-size: 12px; line-height: 18px; }
|
|
422
437
|
.dim-targetFormActions { display: flex; justify-content: flex-end; gap: 7px; margin-top: 12px; }
|
|
438
|
+
.dim-accessPage { min-width: 0; display: grid; gap: 14px; }
|
|
439
|
+
.dim-accessScene { position: relative; min-width: 0; margin: 0; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); }
|
|
440
|
+
.dim-accessScene > legend { padding: 0 6px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; line-height: 22px; font-weight: 650; }
|
|
441
|
+
.dim-accessLegendContent { display: inline-flex; align-items: center; gap: 6px; }
|
|
442
|
+
.dim-panel .dim-accessLegendHelp { position: static; }
|
|
443
|
+
.dim-accessLegendHelp .dim-channelTooltip { top: 20px; right: auto; left: 16px; width: min(320px, calc(100% - 32px)); max-width: none; }
|
|
444
|
+
.dim-accessControls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
445
|
+
.dim-accessControls[data-mode="allowlist"] { grid-template-columns: minmax(0, 1fr); }
|
|
446
|
+
.dim-accessField { min-width: 0; display: grid; align-content: start; gap: 5px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 16px; }
|
|
447
|
+
.dim-accessField input, .dim-accessField select { width: 100%; min-width: 0; height: 36px; padding: 0 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; }
|
|
448
|
+
.dim-accessField input:focus, .dim-accessField select:focus { outline: 2px solid color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 28%, transparent); border-color: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
449
|
+
.dim-accessUsers { min-width: 0; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
450
|
+
.dim-accessUsersHeading { position: relative; min-width: 0; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
451
|
+
.dim-accessUsersHeading > div { min-width: 0; }
|
|
452
|
+
.dim-accessUsersTitle { display: inline-flex; align-items: center; gap: 6px; }
|
|
453
|
+
.dim-accessUsersHeading strong { color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; line-height: 20px; font-weight: 620; }
|
|
454
|
+
.dim-accessUsersHeading p { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; }
|
|
455
|
+
.dim-panel .dim-accessUsersHelp { position: static; }
|
|
456
|
+
.dim-accessUsersHelp .dim-channelTooltip { top: calc(100% + 7px); right: auto; left: 0; width: min(320px, 100%); max-width: none; }
|
|
457
|
+
.dim-accessAddUser { width: 32px; height: 32px; min-height: 32px; flex: 0 0 32px; padding: 0; font-size: 20px; line-height: 1; }
|
|
458
|
+
.dim-accessUsersEmpty { margin-top: 10px; padding: 15px 12px; border: 1px dashed var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: 18px; text-align: center; }
|
|
459
|
+
.dim-accessUserList { display: grid; gap: 9px; margin: 10px 0 0; padding: 0; list-style: none; }
|
|
460
|
+
.dim-accessUserRow { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(145px, 180px) max-content; align-items: end; gap: 10px; padding: 11px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
|
|
461
|
+
.dim-accessDeleteUser { margin-bottom: 1px; }
|
|
462
|
+
.dim-accessUnsupported { padding: 18px 14px; border: 1px dashed var(--dsw-alias-border-l2, #dfe1e5); border-radius: 9px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f7f8fa); text-align: center; }
|
|
463
|
+
.dim-accessUnsupported strong { color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; line-height: 20px; }
|
|
464
|
+
.dim-accessUnsupported p { margin: 4px 0 0; font-size: 12px; line-height: 18px; }
|
|
465
|
+
.dim-accessState { padding: 11px 13px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 24%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 9px; color: var(--dsw-alias-state-warn-primary, #d97706); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 7%, var(--dsw-alias-bg-layer-1, #fff)); font-size: 12px; line-height: 18px; }
|
|
466
|
+
.dim-accessFeedback { margin: 0; padding: 10px 12px; border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font-size: 12px; line-height: 18px; }
|
|
467
|
+
.dim-accessFeedback[data-tone="success"] { color: var(--dsw-alias-state-success-primary, #20a162); }
|
|
468
|
+
.dim-accessFeedback[data-tone="error"] { color: var(--dsw-alias-state-error-primary, #d54941); }
|
|
469
|
+
.dim-accessActions { display: flex; justify-content: flex-end; }
|
|
423
470
|
.dim-panel .dim-botCard .dim-cardFooter { margin-top: 0; }
|
|
424
471
|
.dim-panel .ddt-headingCopy { display: none; }
|
|
425
472
|
.dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); }
|
|
@@ -443,6 +490,9 @@ const CSS = String.raw`
|
|
|
443
490
|
.dim-targetActions { justify-content: flex-start; }
|
|
444
491
|
.dim-targetFormGrid { grid-template-columns: minmax(0, 1fr); }
|
|
445
492
|
.dim-targetSuggestionHeading { align-items: stretch; flex-direction: column; }
|
|
493
|
+
.dim-accessControls { grid-template-columns: minmax(0, 1fr); }
|
|
494
|
+
.dim-accessUserRow { grid-template-columns: minmax(0, 1fr); }
|
|
495
|
+
.dim-accessDeleteUser { justify-self: start; }
|
|
446
496
|
}
|
|
447
497
|
@media (max-width: 840px) {
|
|
448
498
|
.dim-title { align-items: flex-start; }
|
|
@@ -466,11 +516,12 @@ const CSS = String.raw`
|
|
|
466
516
|
.dim-rail { grid-template-columns: minmax(0, 1fr); }
|
|
467
517
|
.dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; }
|
|
468
518
|
.dim-loopbackRecoveryAction { width: 100%; }
|
|
469
|
-
.dim-
|
|
470
|
-
.dim-
|
|
519
|
+
.dim-deliverySectionHeading { align-items: stretch; flex-direction: column; }
|
|
520
|
+
.dim-botSettingsTabs { gap: 18px; }
|
|
471
521
|
.dim-deliveryBotId { grid-template-columns: minmax(0, 1fr) max-content; }
|
|
472
522
|
.dim-deliveryBotId > span { grid-column: 1 / -1; }
|
|
473
523
|
.dim-targetActions .dim-deliveryButton { flex: 1 1 auto; }
|
|
524
|
+
.dim-accessActions .dim-deliveryButton { width: 100%; }
|
|
474
525
|
.dim-directoryPickerBackdrop { padding: 10px; }
|
|
475
526
|
.dim-directoryPicker { height: calc(100vh - 20px); min-height: 0; border-radius: 14px; }
|
|
476
527
|
.dim-directoryPickerHeader { padding: 18px 17px 14px; }
|
|
@@ -20,6 +20,10 @@ import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
|
20
20
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
21
21
|
import { harnessConnection } from '../../harness-connection.mjs';
|
|
22
22
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
23
|
+
import {
|
|
24
|
+
accessPolicyProvider,
|
|
25
|
+
initialAccessPolicyFor,
|
|
26
|
+
} from '../shared/access-policy-production.mjs';
|
|
23
27
|
|
|
24
28
|
function pluginPaths(config) {
|
|
25
29
|
const dshHome = resolve(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'));
|
|
@@ -60,6 +64,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
60
64
|
}
|
|
61
65
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
|
|
62
66
|
defaultAgentPreset: config.agentPreset,
|
|
67
|
+
initialAccessPolicy: initialAccessPolicyFor('dingtalk', bot),
|
|
63
68
|
})));
|
|
64
69
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
65
70
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
@@ -101,7 +106,10 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
101
106
|
logger,
|
|
102
107
|
createRuntime: async ({ botId, config: botConfig, clientSecret }) => {
|
|
103
108
|
const state = await stateFor(botId);
|
|
104
|
-
await workspaces.ensure(botId, {
|
|
109
|
+
await workspaces.ensure(botId, {
|
|
110
|
+
defaultAgentPreset: config.agentPreset,
|
|
111
|
+
initialAccessPolicy: initialAccessPolicyFor('dingtalk', botConfig),
|
|
112
|
+
});
|
|
105
113
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
106
114
|
botId, workspaces, state, agentPresetCatalog,
|
|
107
115
|
});
|
|
@@ -111,6 +119,9 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
111
119
|
harness: workspaceScope.harness,
|
|
112
120
|
state: workspaceScope.state,
|
|
113
121
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
122
|
+
accessPolicy: accessPolicyProvider(workspaces, botId, {
|
|
123
|
+
channel: 'dingtalk', config: botConfig,
|
|
124
|
+
}),
|
|
114
125
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
115
126
|
maxMessageChars: config.maxMessageChars ?? 4_000,
|
|
116
127
|
connectTimeoutMs: config.connectTimeoutMs ?? 15_000,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
2
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
3
|
+
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
3
4
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
4
5
|
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
5
6
|
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
|
@@ -20,6 +21,7 @@ export const DINGTALK_ENDPOINTS = Object.freeze({
|
|
|
20
21
|
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
|
21
22
|
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
22
23
|
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
|
24
|
+
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
23
25
|
approveSender: 'bot.sender.approve',
|
|
24
26
|
revokeSender: 'bot.sender.revoke',
|
|
25
27
|
});
|
|
@@ -100,6 +102,10 @@ function payloadFailure(endpoint, payload) {
|
|
|
100
102
|
return validContextEnhancementPayload(payload)
|
|
101
103
|
? null : '请提交有效的上下文增强设置。';
|
|
102
104
|
}
|
|
105
|
+
if (endpoint === DINGTALK_ENDPOINTS.setAccessPolicy) {
|
|
106
|
+
return validAccessPolicyPayload(payload)
|
|
107
|
+
? null : '请提交有效的访问设置。';
|
|
108
|
+
}
|
|
103
109
|
if (endpoint === DINGTALK_ENDPOINTS.approveSender) {
|
|
104
110
|
return exactKeys(payload, ['botId', 'requestId', 'confirm'])
|
|
105
111
|
&& validId(payload.botId)
|
|
@@ -262,6 +268,11 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
262
268
|
value = await controller.updateContextEnhancement(
|
|
263
269
|
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
|
264
270
|
);
|
|
271
|
+
} else if (endpoint === DINGTALK_ENDPOINTS.setAccessPolicy) {
|
|
272
|
+
if (typeof controller.updateAccessPolicy !== 'function') throw new Error('Access policy update is unavailable');
|
|
273
|
+
value = await controller.updateAccessPolicy(
|
|
274
|
+
payload.botId, payload.policy, (status) => publicStatus(status, cachedEncode),
|
|
275
|
+
);
|
|
265
276
|
} else if (endpoint === DINGTALK_ENDPOINTS.setAgentPreset) {
|
|
266
277
|
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
|
267
278
|
value = await publicStatus(
|
|
@@ -24,8 +24,39 @@ import {
|
|
|
24
24
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
25
25
|
import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
|
|
26
26
|
import { createDeliveryAdapter } from '../../delivery-adapter.mjs';
|
|
27
|
+
import {
|
|
28
|
+
accessPolicyProvider,
|
|
29
|
+
initialAccessPolicyFor,
|
|
30
|
+
} from '../shared/access-policy-production.mjs';
|
|
31
|
+
|
|
32
|
+
// The WebSocket agent built here is only used for the Feishu long connection,
|
|
33
|
+
// whose endpoint is open.feishu.cn (Feishu) or open.larksuite.com (Lark).
|
|
34
|
+
// Honor NO_PROXY/no_proxy for those hosts so users with blanket proxy
|
|
35
|
+
// environments exported in their shell (Clash/V2Ray etc.) keep a direct
|
|
36
|
+
// connection to Feishu instead of routing the WSS handshake through the proxy.
|
|
37
|
+
const LONG_CONNECTION_HOSTS = ['open.feishu.cn', 'open.larksuite.com'];
|
|
38
|
+
|
|
39
|
+
function hostExcludedByNoProxyEntry(host, entry) {
|
|
40
|
+
if (entry === '*') return true;
|
|
41
|
+
const bare = entry.startsWith('.') ? entry.slice(1) : entry;
|
|
42
|
+
if (!bare) return false;
|
|
43
|
+
return host === bare || host.endsWith(`.${bare}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function longConnectionExcludedByNoProxy(env) {
|
|
47
|
+
for (const key of ['no_proxy', 'NO_PROXY']) {
|
|
48
|
+
const value = env?.[key];
|
|
49
|
+
if (typeof value !== 'string' || !value.trim()) continue;
|
|
50
|
+
const entries = value.split(',').map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
51
|
+
if (LONG_CONNECTION_HOSTS.some((host) => entries.some((entry) => hostExcludedByNoProxyEntry(host, entry)))) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
27
57
|
|
|
28
58
|
function webSocketProxyUrl(env) {
|
|
59
|
+
if (longConnectionExcludedByNoProxy(env)) return undefined;
|
|
29
60
|
for (const key of ['https_proxy', 'HTTPS_PROXY', 'http_proxy', 'HTTP_PROXY']) {
|
|
30
61
|
const value = env?.[key];
|
|
31
62
|
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
@@ -93,6 +124,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
93
124
|
}
|
|
94
125
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.id, {
|
|
95
126
|
defaultAgentPreset: config.agentPreset,
|
|
127
|
+
initialAccessPolicy: initialAccessPolicyFor('feishu', bot),
|
|
96
128
|
})));
|
|
97
129
|
const observedConfigStore = typeof configStore.removeBot === 'function'
|
|
98
130
|
? observeBotWorkspaceRemovals(configStore, {
|
|
@@ -154,7 +186,10 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
154
186
|
createRuntime: async ({ botId, config: botConfig, appSecret, repair }) => {
|
|
155
187
|
const state = await stateFor(botConfig);
|
|
156
188
|
const id = botId ?? botConfig.id ?? botConfig.appId;
|
|
157
|
-
await workspaces.ensure(id, {
|
|
189
|
+
await workspaces.ensure(id, {
|
|
190
|
+
defaultAgentPreset: config.agentPreset,
|
|
191
|
+
initialAccessPolicy: initialAccessPolicyFor('feishu', botConfig),
|
|
192
|
+
});
|
|
158
193
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
159
194
|
botId: id, workspaces, state, agentPresetCatalog,
|
|
160
195
|
});
|
|
@@ -171,6 +206,9 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
171
206
|
harness: workspaceScope.harness,
|
|
172
207
|
state: workspaceScope.state,
|
|
173
208
|
contextEnhancement: { botId: id, getSettings: () => workspaces.contextEnhancementFor(id) },
|
|
209
|
+
accessPolicy: accessPolicyProvider(workspaces, id, {
|
|
210
|
+
channel: 'feishu', config: botConfig,
|
|
211
|
+
}),
|
|
174
212
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
175
213
|
slashCommands: config.slashCommands !== false,
|
|
176
214
|
...(wsAgent ? { wsAgent } : {}),
|
|
@@ -5,21 +5,27 @@ import {
|
|
|
5
5
|
} from '../../../../src/channels/shared/agent-preset.mjs';
|
|
6
6
|
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
|
7
7
|
import { publicMessageFailure } from '../../../../src/channels/shared/message-failure.mjs';
|
|
8
|
+
import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
|
|
8
9
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
9
10
|
import { publicWorkspaceError, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
10
11
|
import { validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
|
11
12
|
import { validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
13
|
+
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
12
14
|
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
|
13
15
|
import {
|
|
14
16
|
isFeishuGroupResponseMode,
|
|
15
17
|
normalizeFeishuGroupResponseMode,
|
|
16
18
|
} from '../../../../src/channels/feishu/group-response-mode.mjs';
|
|
17
19
|
import {
|
|
18
|
-
FEISHU_ENDPOINTS,
|
|
20
|
+
FEISHU_ENDPOINTS as FEISHU_CLIENT_ENDPOINTS,
|
|
19
21
|
FEISHU_RPC_CHANNEL,
|
|
20
22
|
} from '../../../client/channels/feishu/api.js';
|
|
21
23
|
|
|
22
|
-
export
|
|
24
|
+
export const FEISHU_ENDPOINTS = Object.freeze({
|
|
25
|
+
...FEISHU_CLIENT_ENDPOINTS,
|
|
26
|
+
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
27
|
+
});
|
|
28
|
+
export { FEISHU_RPC_CHANNEL };
|
|
23
29
|
export const FEISHU_MULTI_ENDPOINTS = Object.freeze({
|
|
24
30
|
reconnectBot: 'bot.reconnect',
|
|
25
31
|
disconnectBot: 'bot.disconnect',
|
|
@@ -279,6 +285,7 @@ function publicBotEntry(entry) {
|
|
|
279
285
|
configured: source.configured === true,
|
|
280
286
|
agentPreset: normalizeAgentPresetId(source.agentPreset),
|
|
281
287
|
contextEnhancement: normalizeContextEnhancementConfig(source.contextEnhancement),
|
|
288
|
+
accessPolicy: normalizeAccessPolicy(source.accessPolicy),
|
|
282
289
|
groupResponseMode: normalizeFeishuGroupResponseMode(source.groupResponseMode),
|
|
283
290
|
groupMessagePermissionGranted: source.groupMessagePermissionGranted === true,
|
|
284
291
|
bot: publicBot(source.bot),
|
|
@@ -422,6 +429,10 @@ function validPayload(endpoint, payload) {
|
|
|
422
429
|
return validContextEnhancementPayload(payload)
|
|
423
430
|
? null : '请提交有效的上下文增强设置。';
|
|
424
431
|
}
|
|
432
|
+
if (endpoint === FEISHU_ENDPOINTS.setAccessPolicy) {
|
|
433
|
+
return validAccessPolicyPayload(payload)
|
|
434
|
+
? null : '请提交有效的访问设置。';
|
|
435
|
+
}
|
|
425
436
|
if (endpoint === FEISHU_ENDPOINTS.setGroupResponseMode) {
|
|
426
437
|
return hasOnlyKeys(payload, new Set(['botId', 'groupResponseMode']))
|
|
427
438
|
&& safeOpaqueId(payload.botId)
|
|
@@ -679,6 +690,12 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
|
|
|
679
690
|
payload.botId, payload.config,
|
|
680
691
|
(status) => toPublicFeishuStatus(status, { encodeQr: cachedEncodeQr }),
|
|
681
692
|
);
|
|
693
|
+
} else if (endpoint === FEISHU_ENDPOINTS.setAccessPolicy) {
|
|
694
|
+
if (typeof controller.updateAccessPolicy !== 'function') throw new Error('Access policy update is unavailable');
|
|
695
|
+
value = await controller.updateAccessPolicy(
|
|
696
|
+
payload.botId, payload.policy,
|
|
697
|
+
(status) => toPublicFeishuStatus(status, { encodeQr: cachedEncodeQr }),
|
|
698
|
+
);
|
|
682
699
|
} else if (endpoint === FEISHU_ENDPOINTS.setAgentPreset) {
|
|
683
700
|
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
|
684
701
|
value = await toPublicFeishuStatus(
|
|
@@ -20,6 +20,10 @@ import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
|
20
20
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
21
21
|
import { harnessConnection } from '../../harness-connection.mjs';
|
|
22
22
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
23
|
+
import {
|
|
24
|
+
accessPolicyProvider,
|
|
25
|
+
initialAccessPolicyFor,
|
|
26
|
+
} from '../shared/access-policy-production.mjs';
|
|
23
27
|
|
|
24
28
|
function pluginPaths(config) {
|
|
25
29
|
const dshHome = resolve(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'));
|
|
@@ -54,6 +58,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
54
58
|
await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
|
|
55
59
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
|
|
56
60
|
defaultAgentPreset: config.agentPreset,
|
|
61
|
+
initialAccessPolicy: initialAccessPolicyFor('qq', bot),
|
|
57
62
|
})));
|
|
58
63
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
59
64
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
@@ -92,7 +97,10 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
92
97
|
logger,
|
|
93
98
|
createRuntime: async ({ botId, config: botConfig, appSecret }) => {
|
|
94
99
|
const state = await stateFor(botId);
|
|
95
|
-
await workspaces.ensure(botId, {
|
|
100
|
+
await workspaces.ensure(botId, {
|
|
101
|
+
defaultAgentPreset: config.agentPreset,
|
|
102
|
+
initialAccessPolicy: initialAccessPolicyFor('qq', botConfig),
|
|
103
|
+
});
|
|
96
104
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
97
105
|
botId, workspaces, state, agentPresetCatalog,
|
|
98
106
|
});
|
|
@@ -102,6 +110,9 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
102
110
|
harness: workspaceScope.harness,
|
|
103
111
|
state: workspaceScope.state,
|
|
104
112
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
113
|
+
accessPolicy: accessPolicyProvider(workspaces, botId, {
|
|
114
|
+
channel: 'qq', config: botConfig,
|
|
115
|
+
}),
|
|
105
116
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
106
117
|
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
|
107
118
|
logger: {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
publicConnectionTestResult,
|
|
5
5
|
} from '../../../../src/channels/shared/connection-test.mjs';
|
|
6
6
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
7
|
+
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
7
8
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
8
9
|
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
9
10
|
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
|
@@ -20,6 +21,7 @@ export const QQ_ENDPOINTS = Object.freeze({
|
|
|
20
21
|
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
|
21
22
|
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
22
23
|
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
|
24
|
+
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
23
25
|
});
|
|
24
26
|
export const QQ_RPC_ENDPOINTS = Object.freeze(Object.values(QQ_ENDPOINTS));
|
|
25
27
|
|
|
@@ -82,6 +84,10 @@ function payloadFailure(endpoint, payload) {
|
|
|
82
84
|
return validContextEnhancementPayload(payload)
|
|
83
85
|
? null : '请提交有效的上下文增强设置。';
|
|
84
86
|
}
|
|
87
|
+
if (endpoint === QQ_ENDPOINTS.setAccessPolicy) {
|
|
88
|
+
return validAccessPolicyPayload(payload)
|
|
89
|
+
? null : '请提交有效的访问设置。';
|
|
90
|
+
}
|
|
85
91
|
return 'Unknown QQ endpoint.';
|
|
86
92
|
}
|
|
87
93
|
|
|
@@ -182,6 +188,11 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
|
182
188
|
value = await controller.updateContextEnhancement(
|
|
183
189
|
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
|
184
190
|
);
|
|
191
|
+
} else if (endpoint === QQ_ENDPOINTS.setAccessPolicy) {
|
|
192
|
+
if (typeof controller.updateAccessPolicy !== 'function') throw new Error('Access policy update is unavailable');
|
|
193
|
+
value = await controller.updateAccessPolicy(
|
|
194
|
+
payload.botId, payload.policy, (status) => publicStatus(status, cachedEncode),
|
|
195
|
+
);
|
|
185
196
|
} else if (endpoint === QQ_ENDPOINTS.setAgentPreset) {
|
|
186
197
|
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
|
187
198
|
value = await publicStatus(
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAccessPolicy,
|
|
3
|
+
createAccessPolicyScope,
|
|
4
|
+
} from '../../../../src/channels/shared/access-policy.mjs';
|
|
5
|
+
|
|
6
|
+
function policyUsers(users) {
|
|
7
|
+
return users.map((id) => ({ id, canExecuteCommands: true }));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function openScope(allowlistUsers = []) {
|
|
11
|
+
return createAccessPolicyScope({
|
|
12
|
+
mode: 'open',
|
|
13
|
+
open: {
|
|
14
|
+
defaultCanExecuteCommands: true,
|
|
15
|
+
commandPermissionOverrides: [],
|
|
16
|
+
},
|
|
17
|
+
allowlist: { users: policyUsers(allowlistUsers) },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function allowlistScope(users = []) {
|
|
22
|
+
return createAccessPolicyScope({
|
|
23
|
+
mode: 'allowlist',
|
|
24
|
+
open: {
|
|
25
|
+
defaultCanExecuteCommands: false,
|
|
26
|
+
commandPermissionOverrides: [],
|
|
27
|
+
},
|
|
28
|
+
allowlist: { users: policyUsers(users) },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cleanIds(values) {
|
|
33
|
+
return [...new Set((Array.isArray(values) ? values : [values])
|
|
34
|
+
.filter((value) => typeof value === 'string' || typeof value === 'number'
|
|
35
|
+
|| typeof value === 'bigint')
|
|
36
|
+
.map((value) => String(value).trim())
|
|
37
|
+
.filter(Boolean))];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function whatsappNumberJids(values) {
|
|
41
|
+
return cleanIds(values).map((value) => `${value.replace(/^\+/, '')}@s.whatsapp.net`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the one-time, backwards-compatible seed for a bot whose workspace
|
|
46
|
+
* document does not yet contain an access policy.
|
|
47
|
+
*/
|
|
48
|
+
export function initialAccessPolicyFor(channel, config = {}) {
|
|
49
|
+
const key = String(channel ?? '').trim().toLowerCase();
|
|
50
|
+
if (key === 'weixin') {
|
|
51
|
+
return createAccessPolicy({
|
|
52
|
+
direct: allowlistScope(),
|
|
53
|
+
group: allowlistScope(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (key === 'feishu') {
|
|
57
|
+
const owners = cleanIds(config.ownerOpenIds ?? config.ownerOpenId);
|
|
58
|
+
const scope = owners.includes('*') ? openScope() : allowlistScope();
|
|
59
|
+
return createAccessPolicy({ direct: scope, group: scope });
|
|
60
|
+
}
|
|
61
|
+
if (key === 'qq') {
|
|
62
|
+
const owners = cleanIds(config.ownerUserOpenid);
|
|
63
|
+
return createAccessPolicy({
|
|
64
|
+
direct: owners.includes('*') ? openScope() : allowlistScope(),
|
|
65
|
+
group: openScope(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (key === 'telegram') {
|
|
69
|
+
const users = cleanIds(config.allowedUsers);
|
|
70
|
+
if ((config.accessMode ?? 'compatible') === 'private-allowlist') {
|
|
71
|
+
return createAccessPolicy({
|
|
72
|
+
direct: allowlistScope(users),
|
|
73
|
+
group: allowlistScope(),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return createAccessPolicy({
|
|
77
|
+
direct: openScope(users),
|
|
78
|
+
group: openScope(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (key === 'whatsapp') {
|
|
82
|
+
const mode = config.accessMode ?? 'self-only';
|
|
83
|
+
const allowed = whatsappNumberJids(config.allowedNumbers);
|
|
84
|
+
if (mode === 'open') {
|
|
85
|
+
return createAccessPolicy({ direct: openScope(allowed), group: openScope() });
|
|
86
|
+
}
|
|
87
|
+
return createAccessPolicy({
|
|
88
|
+
direct: allowlistScope(mode === 'private-allowlist'
|
|
89
|
+
? allowed
|
|
90
|
+
: []),
|
|
91
|
+
group: allowlistScope(),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (['dingtalk', 'wecom', 'slack', 'discord'].includes(key)) {
|
|
95
|
+
return createAccessPolicy({ direct: openScope(), group: openScope() });
|
|
96
|
+
}
|
|
97
|
+
throw new TypeError(`Unsupported access-policy channel: ${channel}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function privilegedSenderIdsFor(channel, config = {}) {
|
|
101
|
+
const key = String(channel ?? '').trim().toLowerCase();
|
|
102
|
+
if (key === 'weixin') return cleanIds(config.ownerUserId);
|
|
103
|
+
if (key === 'feishu') {
|
|
104
|
+
return cleanIds(config.ownerOpenIds ?? config.ownerOpenId).filter((id) => id !== '*');
|
|
105
|
+
}
|
|
106
|
+
if (key === 'dingtalk') {
|
|
107
|
+
const approved = Array.isArray(config.approvedSenders) ? config.approvedSenders : [];
|
|
108
|
+
return cleanIds(approved.map((entry) => entry?.staffId));
|
|
109
|
+
}
|
|
110
|
+
if (key === 'qq') return cleanIds(config.ownerUserOpenid).filter((id) => id !== '*');
|
|
111
|
+
if (key === 'whatsapp') return cleanIds(config.accountJid);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function accessPolicyProvider(workspaces, botId, { channel, config, equals } = {}) {
|
|
116
|
+
if (!workspaces || typeof workspaces.accessPolicyFor !== 'function') {
|
|
117
|
+
throw new TypeError('A workspace store with access policies is required');
|
|
118
|
+
}
|
|
119
|
+
const privilegedSenderIds = new Set(privilegedSenderIdsFor(channel, config));
|
|
120
|
+
const sameSender = typeof equals === 'function' ? equals : (left, right) => left === right;
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
botId,
|
|
123
|
+
getSettings: () => workspaces.accessPolicyFor(botId),
|
|
124
|
+
isPrivileged(senderIds, conversationType) {
|
|
125
|
+
if (!['direct', 'group'].includes(conversationType)) return false;
|
|
126
|
+
const candidates = Array.isArray(senderIds) ? senderIds : [senderIds];
|
|
127
|
+
try {
|
|
128
|
+
return candidates.some((senderId) => typeof senderId === 'string'
|
|
129
|
+
&& [...privilegedSenderIds].some((privilegedId) => (
|
|
130
|
+
sameSender(senderId.trim(), privilegedId) === true
|
|
131
|
+
)));
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { validateAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
|
|
2
|
+
|
|
3
|
+
export const SET_ACCESS_POLICY_ENDPOINT = 'bot.access-policy.set';
|
|
4
|
+
|
|
5
|
+
export function validAccessPolicyPayload(payload) {
|
|
6
|
+
try {
|
|
7
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
8
|
+
|| Reflect.ownKeys(payload).length !== 2
|
|
9
|
+
|| !Object.hasOwn(payload, 'botId') || !Object.hasOwn(payload, 'policy')
|
|
10
|
+
|| typeof payload.botId !== 'string'
|
|
11
|
+
|| !/^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)) return false;
|
|
12
|
+
validateAccessPolicy(payload.policy);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
createDeliveryAdapter,
|
|
18
18
|
supportsDeliveryChannel,
|
|
19
19
|
} from '../../delivery-adapter.mjs';
|
|
20
|
+
import {
|
|
21
|
+
accessPolicyProvider,
|
|
22
|
+
initialAccessPolicyFor,
|
|
23
|
+
} from './access-policy-production.mjs';
|
|
20
24
|
|
|
21
25
|
export function pluginPaths(config, channel) {
|
|
22
26
|
const dshHome = resolve(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'));
|
|
@@ -46,6 +50,11 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
46
50
|
throw new TypeError(`dsh-im ${channel} runtimeOptions must return an object`);
|
|
47
51
|
}
|
|
48
52
|
const createSupervisor = internals.createConnectionSupervisor ?? createTokenConnectionSupervisor;
|
|
53
|
+
const seedAccessPolicy = typeof definitions.initialAccessPolicyForBot === 'function'
|
|
54
|
+
? definitions.initialAccessPolicyForBot
|
|
55
|
+
// Telegram is the only token channel with a legacy access model. Other
|
|
56
|
+
// current token channels preserve their fully-open baseline.
|
|
57
|
+
: (bot) => initialAccessPolicyFor(channel === 'telegram' ? 'telegram' : 'discord', bot);
|
|
49
58
|
const logger = typeof ctx.logger === 'function'
|
|
50
59
|
? ctx.logger(`dsh-im:${channel}`) : (ctx.logger ?? console);
|
|
51
60
|
const agentPresetCatalog = () => listAgentPresetCatalog(ctx);
|
|
@@ -59,6 +68,7 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
59
68
|
await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
|
|
60
69
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
|
|
61
70
|
defaultAgentPreset: config.agentPreset,
|
|
71
|
+
initialAccessPolicy: seedAccessPolicy(bot),
|
|
62
72
|
})));
|
|
63
73
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
64
74
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
@@ -96,7 +106,10 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
96
106
|
...(internals.inspectToken ? { inspectToken: internals.inspectToken } : {}),
|
|
97
107
|
createRuntime: async ({ botId, config: botConfig, token }) => {
|
|
98
108
|
const state = await stateFor(botId);
|
|
99
|
-
await workspaces.ensure(botId, {
|
|
109
|
+
await workspaces.ensure(botId, {
|
|
110
|
+
defaultAgentPreset: config.agentPreset,
|
|
111
|
+
initialAccessPolicy: seedAccessPolicy(botConfig),
|
|
112
|
+
});
|
|
100
113
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
101
114
|
botId, workspaces, state, agentPresetCatalog,
|
|
102
115
|
});
|
|
@@ -107,6 +120,7 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
107
120
|
harness: workspaceScope.harness,
|
|
108
121
|
state: workspaceScope.state,
|
|
109
122
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
123
|
+
accessPolicy: accessPolicyProvider(workspaces, botId, { channel, config: botConfig }),
|
|
110
124
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
111
125
|
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
|
112
126
|
logger: {
|