@xopcai/xopc 0.0.85 → 0.0.87
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
- package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
- package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
- package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
- package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +1 -0
- package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
- package/dist/extensions/weixin/src/api/api.js +2 -2
- package/dist/extensions/weixin/src/api/api.js.map +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js +12 -12
- package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
- package/dist/extensions/weixin/src/delivery-to.js +2 -2
- package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
- package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
- package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
- package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
- package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
- package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
- package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
- package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
- package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
- package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
- package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
- package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
- package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
- package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
- package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
- package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
- package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
- package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
- package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
- package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-scope.d.ts +4 -0
- package/dist/src/agent/agent-scope.js +53 -10
- package/dist/src/agent/agent-scope.js.map +1 -1
- package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
- package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
- package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
- package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
- package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
- package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
- package/dist/src/agent/fallback/candidates.js +2 -2
- package/dist/src/agent/fallback/candidates.js.map +1 -1
- package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
- package/dist/src/agent/goals/persistent-goal-service.js +0 -1
- package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
- package/dist/src/agent/image/generation/normalization.js +2 -12
- package/dist/src/agent/image/generation/normalization.js.map +1 -1
- package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
- package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
- package/dist/src/agent/image/generation/runtime.d.ts +2 -2
- package/dist/src/agent/image/generation/runtime.js.map +1 -1
- package/dist/src/agent/image/generation/types.d.ts +0 -18
- package/dist/src/agent/image/image-helpers.js +6 -1
- package/dist/src/agent/image/image-helpers.js.map +1 -1
- package/dist/src/agent/image/index.d.ts +1 -1
- package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
- package/dist/src/agent/inbound/inbound-loop.js +41 -10
- package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
- package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
- package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
- package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
- package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport.js +2 -1
- package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
- package/dist/src/agent/media-generation/runtime-shared.js +2 -9
- package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.d.ts +6 -0
- package/dist/src/agent/messaging/command-handler.js +5 -0
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/prompt/safety.d.ts +0 -7
- package/dist/src/agent/prompt/safety.js +1 -20
- package/dist/src/agent/prompt/safety.js.map +1 -1
- package/dist/src/agent/service/build-direct-message-content.js +1 -1
- package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
- package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
- package/dist/src/agent/service/direct-turn-helpers.js +6 -1
- package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
- package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
- package/dist/src/agent/service/process-direct-one-shot.js +15 -2
- package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
- package/dist/src/agent/service/process-direct-streaming.js +34 -4
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service/webchat-tts.js +1 -1
- package/dist/src/agent/service/webchat-tts.js.map +1 -1
- package/dist/src/agent/service.d.ts +8 -0
- package/dist/src/agent/service.js +21 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/tools/create-share-tool.js +27 -20
- package/dist/src/agent/tools/create-share-tool.js.map +1 -1
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/agent/tools/index.d.ts +0 -1
- package/dist/src/agent/tools/index.js +4 -5
- package/dist/src/agent/tools/shell.js +0 -13
- package/dist/src/agent/tools/shell.js.map +1 -1
- package/dist/src/agent/tools/workflow-tool.js +10 -4
- package/dist/src/agent/tools/workflow-tool.js.map +1 -1
- package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
- package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
- package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
- package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
- package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
- package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
- package/dist/src/agent/workflow/builtins/index.js +11 -1
- package/dist/src/agent/workflow/builtins/index.js.map +1 -1
- package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
- package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
- package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
- package/dist/src/agent/workflow/builtins/research.js +37 -6
- package/dist/src/agent/workflow/builtins/research.js.map +1 -1
- package/dist/src/agent/workflow/catalog.d.ts +5 -0
- package/dist/src/agent/workflow/catalog.js +6 -2
- package/dist/src/agent/workflow/catalog.js.map +1 -1
- package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
- package/dist/src/agent/workflow/index.d.ts +1 -1
- package/dist/src/agent/workflow/lint.d.ts +38 -0
- package/dist/src/agent/workflow/lint.js +74 -0
- package/dist/src/agent/workflow/lint.js.map +1 -0
- package/dist/src/agent/workflow/parser.js +13 -1
- package/dist/src/agent/workflow/parser.js.map +1 -1
- package/dist/src/agent/workflow/runtime.d.ts +3 -0
- package/dist/src/agent/workflow/runtime.js +76 -3
- package/dist/src/agent/workflow/runtime.js.map +1 -1
- package/dist/src/agent/workflow/types.d.ts +11 -1
- package/dist/src/browser/index.js +4 -4
- package/dist/src/browser/manager.d.ts +1 -3
- package/dist/src/browser/manager.js +0 -6
- package/dist/src/browser/manager.js.map +1 -1
- package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
- package/dist/src/browser/providers/browser-ext-install.js +38 -85
- package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
- package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
- package/dist/src/browser/providers/cloakbrowser.js +2 -55
- package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
- package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
- package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
- package/dist/src/channels/pairing/allow-from-file.js +9 -9
- package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
- package/dist/src/channels/pairing/pairing-store.js +6 -6
- package/dist/src/channels/pairing/pairing-store.js.map +1 -1
- package/dist/src/chat-commands/builtins/session.js +1 -1
- package/dist/src/chat-commands/builtins/session.js.map +1 -1
- package/dist/src/chat-commands/builtins/tts.js +2 -2
- package/dist/src/chat-commands/builtins/tts.js.map +1 -1
- package/dist/src/chat-commands/builtins/workflow.js +7 -2
- package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
- package/dist/src/chat-commands/context.d.ts +3 -0
- package/dist/src/chat-commands/context.js +21 -3
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/chat-commands/session-key.d.ts +4 -37
- package/dist/src/chat-commands/session-key.js +49 -85
- package/dist/src/chat-commands/session-key.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +2 -0
- package/dist/src/cli/commands/agent/interactive.js +2 -2
- package/dist/src/cli/commands/agent/interactive.js.map +1 -1
- package/dist/src/cli/commands/agent/sessions.js +2 -2
- package/dist/src/cli/commands/agent/sessions.js.map +1 -1
- package/dist/src/cli/commands/agent.js +4 -5
- package/dist/src/cli/commands/agent.js.map +1 -1
- package/dist/src/cli/commands/channels.js +1 -5
- package/dist/src/cli/commands/channels.js.map +1 -1
- package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
- package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
- package/dist/src/cli/commands/gateway/logs.js +50 -17
- package/dist/src/cli/commands/gateway/logs.js.map +1 -1
- package/dist/src/cli/commands/image.js +22 -21
- package/dist/src/cli/commands/image.js.map +1 -1
- package/dist/src/cli/commands/session/utils.js +2 -2
- package/dist/src/cli/commands/session/utils.js.map +1 -1
- package/dist/src/cli/commands/update.js +26 -46
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/cli/utils/session.d.ts +0 -5
- package/dist/src/cli/utils/session.js +1 -6
- package/dist/src/cli/utils/session.js.map +1 -1
- package/dist/src/commands/agents.config.js +1 -1
- package/dist/src/commands/agents.config.js.map +1 -1
- package/dist/src/config/agent-profile.js +5 -27
- package/dist/src/config/agent-profile.js.map +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/model-input.js +2 -5
- package/dist/src/config/model-input.js.map +1 -1
- package/dist/src/config/schema.d.ts +201 -217
- package/dist/src/config/schema.js +54 -39
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/workspace-path-helpers.d.ts +1 -2
- package/dist/src/config/workspace-path-helpers.js.map +1 -1
- package/dist/src/daemon/install-plan.js +25 -1
- package/dist/src/daemon/install-plan.js.map +1 -1
- package/dist/src/daemon/launchd.d.ts +8 -0
- package/dist/src/daemon/launchd.js +5 -12
- package/dist/src/daemon/launchd.js.map +1 -1
- package/dist/src/daemon/schtasks.d.ts +25 -0
- package/dist/src/daemon/schtasks.js +166 -46
- package/dist/src/daemon/schtasks.js.map +1 -1
- package/dist/src/daemon/service.js +5 -4
- package/dist/src/daemon/service.js.map +1 -1
- package/dist/src/daemon/systemd.d.ts +6 -0
- package/dist/src/daemon/systemd.js +18 -3
- package/dist/src/daemon/systemd.js.map +1 -1
- package/dist/src/extensions/activation-context.js +0 -1
- package/dist/src/extensions/activation-context.js.map +1 -1
- package/dist/src/extensions/normalize-manifest.js +0 -1
- package/dist/src/extensions/normalize-manifest.js.map +1 -1
- package/dist/src/extensions/types/manifest.d.ts +0 -2
- package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
- package/dist/src/gateway/agent-builtin-tools.js +1 -0
- package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +10 -2
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/heartbeat/service.js.map +1 -1
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
- package/dist/src/gateway/hono/lib/agent-model.js +24 -35
- package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
- package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
- package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
- package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
- package/dist/src/gateway/hono/routes/goals.js +1 -1
- package/dist/src/gateway/hono/routes/goals.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +28 -7
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/shares.js +14 -12
- package/dist/src/gateway/hono/routes/shares.js.map +1 -1
- package/dist/src/gateway/hono/routes/tunnel.js +1 -1
- package/dist/src/gateway/hono/routes/update.js +4 -2
- package/dist/src/gateway/hono/routes/update.js.map +1 -1
- package/dist/src/gateway/hono/sse.js +16 -33
- package/dist/src/gateway/hono/sse.js.map +1 -1
- package/dist/src/gateway/lock.js +10 -10
- package/dist/src/gateway/lock.js.map +1 -1
- package/dist/src/gateway/ports.js +6 -6
- package/dist/src/gateway/ports.js.map +1 -1
- package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
- package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
- package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
- package/dist/src/gateway/service/run-gateway-agent.js +27 -11
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service/sessions-api.d.ts +3 -0
- package/dist/src/gateway/service/sessions-api.js +8 -0
- package/dist/src/gateway/service/sessions-api.js.map +1 -1
- package/dist/src/gateway/service.d.ts +0 -2
- package/dist/src/gateway/service.js +2 -7
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/session-reset-service.d.ts +20 -0
- package/dist/src/gateway/session-reset-service.js +54 -0
- package/dist/src/gateway/session-reset-service.js.map +1 -0
- package/dist/src/gateway/startup-readiness.d.ts +1 -1
- package/dist/src/gateway/startup-readiness.js +1 -0
- package/dist/src/gateway/startup-readiness.js.map +1 -1
- package/dist/src/infra/gateway-processes.js +2 -2
- package/dist/src/infra/gateway-processes.js.map +1 -1
- package/dist/src/infra/run-command.d.ts +16 -0
- package/dist/src/infra/run-command.js +67 -0
- package/dist/src/infra/run-command.js.map +1 -0
- package/dist/src/infra/update-global.d.ts +45 -0
- package/dist/src/infra/update-global.js +224 -0
- package/dist/src/infra/update-global.js.map +1 -0
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/mcp/channel-shared.js +2 -1
- package/dist/src/mcp/channel-shared.js.map +1 -1
- package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
- package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
- package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
- package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
- package/dist/src/providers/auth-runtime/types.d.ts +6 -12
- package/dist/src/routing/agent-session-key.d.ts +58 -0
- package/dist/src/routing/agent-session-key.js +164 -0
- package/dist/src/routing/agent-session-key.js.map +1 -0
- package/dist/src/routing/index.d.ts +1 -1
- package/dist/src/routing/index.js +4 -2
- package/dist/src/routing/index.js.map +1 -1
- package/dist/src/routing/resolve-route.d.ts +15 -0
- package/dist/src/routing/resolve-route.js +41 -20
- package/dist/src/routing/resolve-route.js.map +1 -1
- package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
- package/dist/src/routing/resolve-tui-session-key.js +54 -0
- package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
- package/dist/src/routing/session-key-utils.d.ts +24 -0
- package/dist/src/routing/session-key-utils.js +92 -0
- package/dist/src/routing/session-key-utils.js.map +1 -0
- package/dist/src/routing/session-key.d.ts +19 -49
- package/dist/src/routing/session-key.js +143 -116
- package/dist/src/routing/session-key.js.map +1 -1
- package/dist/src/session/index.d.ts +6 -0
- package/dist/src/session/index.js +7 -1
- package/dist/src/session/init-session-turn.d.ts +30 -0
- package/dist/src/session/init-session-turn.js +102 -0
- package/dist/src/session/init-session-turn.js.map +1 -0
- package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
- package/dist/src/session/lifecycle-timestamps.js +16 -0
- package/dist/src/session/lifecycle-timestamps.js.map +1 -0
- package/dist/src/session/manager.d.ts +7 -1
- package/dist/src/session/manager.js +8 -1
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/parity/transcript-paths.js +2 -2
- package/dist/src/session/parity/transcript-paths.js.map +1 -1
- package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
- package/dist/src/session/reset-policy.d.ts +32 -0
- package/dist/src/session/reset-policy.js +65 -0
- package/dist/src/session/reset-policy.js.map +1 -0
- package/dist/src/session/reset-triggers.d.ts +20 -0
- package/dist/src/session/reset-triggers.js +63 -0
- package/dist/src/session/reset-triggers.js.map +1 -0
- package/dist/src/session/reset-type.d.ts +12 -0
- package/dist/src/session/reset-type.js +25 -0
- package/dist/src/session/reset-type.js.map +1 -0
- package/dist/src/session/resolve-session.d.ts +30 -0
- package/dist/src/session/resolve-session.js +93 -0
- package/dist/src/session/resolve-session.js.map +1 -0
- package/dist/src/session/session-title.js +3 -2
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/session/store.d.ts +11 -4
- package/dist/src/session/store.js +57 -6
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/transcript-events.js +2 -1
- package/dist/src/session/transcript-events.js.map +1 -1
- package/dist/src/share/share-url.d.ts +33 -0
- package/dist/src/share/share-url.js +56 -14
- package/dist/src/share/share-url.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.js +4 -9
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/components/chat-log.js +3 -3
- package/dist/src/tui/components/chat-log.js.map +1 -1
- package/dist/src/tui/theme.d.ts +0 -2
- package/dist/src/tui/theme.js +1 -3
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/tui-commands.d.ts +3 -0
- package/dist/src/tui/tui-commands.js +45 -10
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-keybindings-file.js +1 -21
- package/dist/src/tui/tui-keybindings-file.js.map +1 -1
- package/dist/src/tui/tui-session-actions.d.ts +28 -0
- package/dist/src/tui/tui-session-actions.js +88 -0
- package/dist/src/tui/tui-session-actions.js.map +1 -0
- package/dist/src/tui/tui.js +52 -47
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/utils/string-coerce.d.ts +2 -0
- package/dist/src/utils/string-coerce.js +10 -1
- package/dist/src/utils/string-coerce.js.map +1 -1
- package/dist/src/voice/stt/config-slice.d.ts +2 -5
- package/dist/src/voice/stt/config-slice.js +5 -26
- package/dist/src/voice/stt/config-slice.js.map +1 -1
- package/dist/src/voice/stt/types.d.ts +1 -18
- package/dist/src/voice/stt/types.js +4 -2
- package/dist/src/voice/stt/types.js.map +1 -1
- package/dist/src/voice/tts/config-slice.d.ts +3 -7
- package/dist/src/voice/tts/config-slice.js +7 -38
- package/dist/src/voice/tts/config-slice.js.map +1 -1
- package/dist/src/voice/tts/merge-config.js +2 -48
- package/dist/src/voice/tts/merge-config.js.map +1 -1
- package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
- package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
- package/dist/src/voice/tts/types.d.ts +1 -29
- package/dist/src/voice/tts/types.js +19 -17
- package/dist/src/voice/tts/types.js.map +1 -1
- package/package.json +1 -4
- package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
- package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
- package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
- package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
- package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
- package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
- package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
- package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { MessageItemType } from "../api/types.js";
|
|
|
4
4
|
import { canonicalWeixinPeerId, normalizeWeixinAccountId } from "../auth/weixin-account-id.js";
|
|
5
5
|
import { resolveWeixinRootDir } from "../storage/state-dir.js";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import
|
|
7
|
+
import fs from "node:fs";
|
|
8
8
|
//#region extensions/weixin/src/messaging/inbound.ts
|
|
9
9
|
/**
|
|
10
10
|
* contextToken is issued per-message by the Weixin getupdates API and must
|
|
@@ -66,8 +66,8 @@ function persistContextTokens(accountId) {
|
|
|
66
66
|
const filePath = canonicalContextTokenFilePath(accountId);
|
|
67
67
|
try {
|
|
68
68
|
const dir = path.dirname(filePath);
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
70
|
+
fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), "utf-8");
|
|
71
71
|
} catch (err) {
|
|
72
72
|
logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);
|
|
73
73
|
}
|
|
@@ -81,8 +81,8 @@ function restoreContextTokens(accountId) {
|
|
|
81
81
|
const norm = normalizeWeixinAccountId(accountId);
|
|
82
82
|
const filePath = canonicalContextTokenFilePath(accountId);
|
|
83
83
|
try {
|
|
84
|
-
if (
|
|
85
|
-
const raw =
|
|
84
|
+
if (fs.existsSync(filePath)) {
|
|
85
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
86
86
|
const tokens = JSON.parse(raw);
|
|
87
87
|
for (const [userId, val] of Object.entries(tokens)) {
|
|
88
88
|
const entry = parsePersistedPeerVal(val);
|
|
@@ -104,7 +104,7 @@ function clearContextTokensForAccount(accountId) {
|
|
|
104
104
|
for (const k of [...contextTokenStore.keys()]) if (k.startsWith(prefix)) contextTokenStore.delete(k);
|
|
105
105
|
try {
|
|
106
106
|
const filePath = canonicalContextTokenFilePath(accountId);
|
|
107
|
-
if (
|
|
107
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
108
108
|
} catch (err) {
|
|
109
109
|
logger.warn(`clearContextTokensForAccount: failed to remove context tokens file: ${String(err)}`);
|
|
110
110
|
}
|
|
@@ -114,9 +114,9 @@ function tryHydratePeerEntryFromDisk(accountId, userId) {
|
|
|
114
114
|
const peer = normalizeIlinkUserIdForContext(userId);
|
|
115
115
|
const norm = normalizeWeixinAccountId(accountId);
|
|
116
116
|
const filePath = canonicalContextTokenFilePath(accountId);
|
|
117
|
-
if (!
|
|
117
|
+
if (!fs.existsSync(filePath)) return void 0;
|
|
118
118
|
try {
|
|
119
|
-
const raw =
|
|
119
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
120
120
|
const tokens = JSON.parse(raw);
|
|
121
121
|
for (const [jsonKey, val] of Object.entries(tokens)) {
|
|
122
122
|
const entry = parsePersistedPeerVal(val);
|
|
@@ -198,11 +198,11 @@ function findContextTokenEntriesByPeer(peerUserId) {
|
|
|
198
198
|
function tryHydrateAnyAccountContextTokenFromDisk(peerUserId) {
|
|
199
199
|
const peer = normalizeIlinkUserIdForContext(peerUserId);
|
|
200
200
|
const accountsDir = path.join(resolveWeixinRootDir(), "accounts");
|
|
201
|
-
if (!
|
|
201
|
+
if (!fs.existsSync(accountsDir)) return void 0;
|
|
202
202
|
const hits = [];
|
|
203
203
|
let names;
|
|
204
204
|
try {
|
|
205
|
-
names =
|
|
205
|
+
names = fs.readdirSync(accountsDir);
|
|
206
206
|
} catch {
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
@@ -212,7 +212,7 @@ function tryHydrateAnyAccountContextTokenFromDisk(peerUserId) {
|
|
|
212
212
|
const accountFromFile = name.slice(0, -20);
|
|
213
213
|
const filePath = path.join(accountsDir, name);
|
|
214
214
|
try {
|
|
215
|
-
const raw =
|
|
215
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
216
216
|
const tokens = JSON.parse(raw);
|
|
217
217
|
for (const [jsonKey, val] of Object.entries(tokens)) {
|
|
218
218
|
const entry = parsePersistedPeerVal(val);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.js","names":["fs"],"sources":["../../../../../extensions/weixin/src/messaging/inbound.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { logger } from \"../util/logger.js\";\nimport { generateId } from \"../util/random.js\";\nimport type { WeixinMessage, MessageItem } from \"../api/types.js\";\nimport { MessageItemType } from \"../api/types.js\";\nimport { canonicalWeixinPeerId, normalizeWeixinAccountId } from \"../auth/weixin-account-id.js\";\nimport { resolveWeixinRootDir } from \"../storage/state-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Context token store (in-process cache + disk persistence)\n// ---------------------------------------------------------------------------\n\ntype WeixinContextTokenEntry = { token: string; sendTo?: string };\n\n/**\n * contextToken is issued per-message by the Weixin getupdates API and must\n * be echoed verbatim in every outbound send. The in-memory map is the primary\n * lookup; a disk-backed file per account ensures tokens survive gateway restarts.\n *\n * `sendTo` is the last inbound `from_user_id` verbatim. ilink often requires `to_user_id`\n * in sendmessage to match that string exactly (session/cron peer ids are sanitized).\n */\nconst contextTokenStore = new Map<string, WeixinContextTokenEntry>();\n\nfunction parsePersistedPeerVal(val: unknown): WeixinContextTokenEntry | null {\n if (typeof val === \"string\" && val.trim()) {\n return { token: val.trim() };\n }\n if (val && typeof val === \"object\" && \"token\" in val) {\n const o = val as { token: unknown; sendTo?: unknown };\n if (typeof o.token !== \"string\" || !o.token.trim()) return null;\n const sendTo = typeof o.sendTo === \"string\" && o.sendTo.trim() ? o.sendTo.trim() : undefined;\n return { token: o.token.trim(), ...(sendTo ? { sendTo } : {}) };\n }\n return null;\n}\n\n/**\n * Session keys use {@link canonicalWeixinPeerId} (same as `buildSessionKey`); cron `delivery.to`\n * and outbound `ctx.to` use that shape. ilink `from_user_id` is often `…@im.wechat` while the\n * session peer is `…-im-wechat`. Normalize so context_token cache keys match lookups.\n */\nfunction normalizeIlinkUserIdForContext(userId: string): string {\n return canonicalWeixinPeerId(userId);\n}\n\n/** ilink may use a shorter openid in one path and a suffixed id in another; treat as same peer when unambiguous. */\nfunction ilinkPeerKeysLikelySame(storedKey: string, queryPeer: string): boolean {\n const a = normalizeIlinkUserIdForContext(storedKey);\n const b = normalizeIlinkUserIdForContext(queryPeer);\n if (a === b) return true;\n if (a.length < 12 || b.length < 12) return false;\n return a.startsWith(b) || b.startsWith(a);\n}\n\nfunction contextTokenKey(accountId: string, userId: string): string {\n return `${normalizeWeixinAccountId(accountId)}:${normalizeIlinkUserIdForContext(userId)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Disk persistence helpers\n// ---------------------------------------------------------------------------\n\nfunction canonicalContextTokenFilePath(accountId: string): string {\n const norm = normalizeWeixinAccountId(accountId);\n return path.join(resolveWeixinRootDir(), \"accounts\", `${norm}.context-tokens.json`);\n}\n\n/** Persist all context tokens for a given account to disk. */\nfunction persistContextTokens(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n const tokens: Record<string, string | { token: string; sendTo?: string }> = {};\n for (const [k, v] of contextTokenStore) {\n if (k.startsWith(prefix)) {\n const peerSuffix = k.slice(prefix.length);\n if (v.sendTo?.trim()) {\n tokens[peerSuffix] = { token: v.token, sendTo: v.sendTo.trim() };\n } else {\n tokens[peerSuffix] = v.token;\n }\n }\n }\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), \"utf-8\");\n } catch (err) {\n logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);\n }\n}\n\n/**\n * Restore persisted context tokens for an account into the in-memory map.\n * Called once during gateway startAccount to survive restarts.\n */\nexport function restoreContextTokens(accountId: string): void {\n let count = 0;\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [userId, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (entry?.token) {\n contextTokenStore.set(contextTokenKey(norm, userId), entry);\n count++;\n }\n }\n }\n } catch (err) {\n logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);\n }\n if (count > 0) {\n logger.info(`restoreContextTokens: restored ${count} tokens for account=${norm}`);\n }\n}\n\n/** Remove all context tokens for a given account (memory + disk). */\nexport function clearContextTokensForAccount(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n for (const k of [...contextTokenStore.keys()]) {\n if (k.startsWith(prefix)) {\n contextTokenStore.delete(k);\n }\n }\n try {\n const filePath = canonicalContextTokenFilePath(accountId);\n if (fs.existsSync(filePath)) fs.unlinkSync(filePath);\n } catch (err) {\n logger.warn(`clearContextTokensForAccount: failed to remove context tokens file: ${String(err)}`);\n }\n logger.info(`clearContextTokensForAccount: cleared tokens for account=${norm}`);\n}\n\nfunction tryHydratePeerEntryFromDisk(accountId: string, userId: string): WeixinContextTokenEntry | undefined {\n const peer = normalizeIlinkUserIdForContext(userId);\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n if (!fs.existsSync(filePath)) return undefined;\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n contextTokenStore.set(contextTokenKey(norm, peer), entry);\n persistContextTokens(accountId);\n return entry;\n }\n } catch (err) {\n logger.debug(`tryHydratePeerEntryFromDisk: ${filePath}: ${String(err)}`);\n }\n return undefined;\n}\n\nfunction getWeixinContextPeerEntry(\n accountId: string,\n userId: string,\n): WeixinContextTokenEntry | undefined {\n const k = contextTokenKey(accountId, userId);\n let e = contextTokenStore.get(k);\n if (e?.token) {\n return e;\n }\n const hydrated = tryHydratePeerEntryFromDisk(accountId, userId);\n if (hydrated) return hydrated;\n return contextTokenStore.get(k);\n}\n\n/** Last inbound `from_user_id` for API `to_user_id` (verbatim), when known. */\nexport function getWeixinOutboundSendUserId(accountId: string, userId: string): string | undefined {\n return getWeixinContextPeerEntry(accountId, userId)?.sendTo?.trim() || undefined;\n}\n\n/** Store a context token for a given account+user pair (memory + disk). */\nexport function setContextToken(\n accountId: string,\n userId: string,\n token: string,\n options?: { sendToUserId?: string },\n): void {\n const k = contextTokenKey(accountId, userId);\n const prev = contextTokenStore.get(k);\n const sendTo =\n options && options.sendToUserId !== undefined\n ? options.sendToUserId.trim() || undefined\n : prev?.sendTo;\n const next: WeixinContextTokenEntry = { token, ...(sendTo ? { sendTo } : {}) };\n logger.debug(`setContextToken: key=${k} sendTo=${sendTo ? \"yes\" : \"no\"}`);\n contextTokenStore.set(k, next);\n persistContextTokens(accountId);\n}\n\n/** Retrieve the cached context token for a given account+user pair. */\nexport function getContextToken(accountId: string, userId: string): string | undefined {\n const k = contextTokenKey(accountId, userId);\n const e = getWeixinContextPeerEntry(accountId, userId);\n logger.debug(\n `getContextToken: key=${k} found=${e?.token !== undefined} storeSize=${contextTokenStore.size}`,\n );\n return e?.token;\n}\n\n/**\n * Find all accountIds that have an active contextToken for the given userId.\n * Used to infer the sending bot account from the recipient address when\n * accountId is not explicitly provided (e.g. cron delivery).\n *\n * Returns all matching accountIds (not just the first) so the caller can\n * detect ambiguity when multiple accounts have sessions with the same user.\n */\nexport function findAccountIdsByContextToken(\n accountIds: string[],\n userId: string,\n): string[] {\n return accountIds.filter((id) => contextTokenStore.has(contextTokenKey(id, userId)));\n}\n\n/** In-memory entries whose peer id matches (exact or ilink prefix/suffix variant). */\nexport function findContextTokenEntriesByPeer(\n peerUserId: string,\n): Array<{ accountId: string; token: string; sendTo?: string }> {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const out: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n for (const [k, entry] of contextTokenStore) {\n if (!entry?.token?.trim()) continue;\n const idx = k.indexOf(':');\n if (idx <= 0 || idx >= k.length - 1) continue;\n const accountId = k.slice(0, idx);\n const storedPeer = k.slice(idx + 1);\n if (!ilinkPeerKeysLikelySame(storedPeer, peer)) continue;\n if (accountId) {\n out.push({\n accountId,\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n }\n return out;\n}\n\n/**\n * When the preferred account id does not match the token filename, scan every\n * `accounts/*.context-tokens.json` for this peer (case-insensitive user key).\n */\nexport function tryHydrateAnyAccountContextTokenFromDisk(\n peerUserId: string,\n): { accountId: string; token: string; sendTo?: string } | undefined {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const accountsDir = path.join(resolveWeixinRootDir(), 'accounts');\n if (!fs.existsSync(accountsDir)) return undefined;\n const hits: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n let names: string[];\n try {\n names = fs.readdirSync(accountsDir);\n } catch {\n return undefined;\n }\n const suffix = '.context-tokens.json';\n for (const name of names) {\n if (!name.endsWith(suffix)) continue;\n const accountFromFile = name.slice(0, -suffix.length);\n const filePath = path.join(accountsDir, name);\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n hits.push({\n accountId: normalizeWeixinAccountId(accountFromFile),\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n } catch {\n /* skip corrupt file */\n }\n }\n if (hits.length === 0) return undefined;\n const byAccount = new Map<string, { token: string; sendTo?: string }>();\n for (const h of hits) {\n if (!byAccount.has(h.accountId)) byAccount.set(h.accountId, { token: h.token, sendTo: h.sendTo });\n }\n if (byAccount.size > 1) return undefined;\n const [accountId, hit] = [...byAccount.entries()][0]!;\n setContextToken(accountId, peer, hit.token, hit.sendTo ? { sendToUserId: hit.sendTo } : undefined);\n return { accountId, token: hit.token, sendTo: hit.sendTo };\n}\n\n// ---------------------------------------------------------------------------\n// Message ID generation\n// ---------------------------------------------------------------------------\n\nfunction generateMessageSid(): string {\n return generateId(\"weixin\");\n}\n\n/** Inbound context for the core message pipeline (matches MsgContext shape). */\nexport type WeixinMsgContext = {\n Body: string;\n From: string;\n To: string;\n AccountId: string;\n OriginatingChannel: \"weixin\";\n OriginatingTo: string;\n MessageSid: string;\n Timestamp?: number;\n Provider: \"weixin\";\n ChatType: \"direct\";\n /** Set by monitor after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */\n SessionKey?: string;\n context_token?: string;\n MediaUrl?: string;\n MediaPath?: string;\n MediaType?: string;\n /** Raw message body for framework command authorization. */\n CommandBody?: string;\n /** Whether the sender is authorized to execute slash commands. */\n CommandAuthorized?: boolean;\n};\n\n/** Returns true if the message item is a media type (image, video, file, or voice). */\nexport function isMediaItem(item: MessageItem): boolean {\n return (\n item.type === MessageItemType.IMAGE ||\n item.type === MessageItemType.VIDEO ||\n item.type === MessageItemType.FILE ||\n item.type === MessageItemType.VOICE\n );\n}\n\nfunction bodyFromItemList(itemList?: MessageItem[]): string {\n if (!itemList?.length) return \"\";\n for (const item of itemList) {\n if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {\n const text = String(item.text_item.text);\n const ref = item.ref_msg;\n if (!ref) return text;\n // Quoted media is passed as MediaPath; only include the current text as body.\n if (ref.message_item && isMediaItem(ref.message_item)) return text;\n // Build quoted context from both title and message_item content.\n const parts: string[] = [];\n if (ref.title) parts.push(ref.title);\n if (ref.message_item) {\n const refBody = bodyFromItemList([ref.message_item]);\n if (refBody) parts.push(refBody);\n }\n if (!parts.length) return text;\n return `[引用: ${parts.join(\" | \")}]\\n${text}`;\n }\n // 语音转文字:如果语音消息有 text 字段,直接使用文字内容\n if (item.type === MessageItemType.VOICE && item.voice_item?.text) {\n return item.voice_item.text;\n }\n }\n return \"\";\n}\n\nexport type WeixinInboundMediaOpts = {\n /** Local path to decrypted image file. */\n decryptedPicPath?: string;\n /** Local path to transcoded/raw voice file (.wav or .silk). */\n decryptedVoicePath?: string;\n /** MIME type for the voice file (e.g. \"audio/wav\" or \"audio/silk\"). */\n voiceMediaType?: string;\n /** Local path to decrypted file attachment. */\n decryptedFilePath?: string;\n /** MIME type for the file attachment (guessed from file_name). */\n fileMediaType?: string;\n /** Local path to decrypted video file. */\n decryptedVideoPath?: string;\n};\n\n/**\n * Convert a WeixinMessage from getUpdates to the inbound MsgContext for the core pipeline.\n * Media: only pass MediaPath (local file, after CDN download + decrypt).\n * We never pass MediaUrl — the upstream CDN URL is encrypted/auth-only.\n * Priority when multiple media types present: image > video > file > voice.\n */\nexport function weixinMessageToMsgContext(\n msg: WeixinMessage,\n accountId: string,\n opts?: WeixinInboundMediaOpts,\n): WeixinMsgContext {\n const from_user_id = msg.from_user_id ?? \"\";\n const ctx: WeixinMsgContext = {\n Body: bodyFromItemList(msg.item_list),\n From: from_user_id,\n To: from_user_id,\n AccountId: accountId,\n OriginatingChannel: \"weixin\",\n OriginatingTo: from_user_id,\n MessageSid: generateMessageSid(),\n Timestamp: msg.create_time_ms,\n Provider: \"weixin\",\n ChatType: \"direct\",\n };\n if (msg.context_token) {\n ctx.context_token = msg.context_token;\n }\n\n if (opts?.decryptedPicPath) {\n ctx.MediaPath = opts.decryptedPicPath;\n ctx.MediaType = \"image/*\";\n } else if (opts?.decryptedVideoPath) {\n ctx.MediaPath = opts.decryptedVideoPath;\n ctx.MediaType = \"video/mp4\";\n } else if (opts?.decryptedFilePath) {\n ctx.MediaPath = opts.decryptedFilePath;\n ctx.MediaType = opts.fileMediaType ?? \"application/octet-stream\";\n } else if (opts?.decryptedVoicePath) {\n ctx.MediaPath = opts.decryptedVoicePath;\n ctx.MediaType = opts.voiceMediaType ?? \"audio/wav\";\n }\n\n return ctx;\n}\n\n/** Extract the context_token from an inbound WeixinMsgContext. */\nexport function getContextTokenFromMsgContext(ctx: WeixinMsgContext): string | undefined {\n return ctx.context_token;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAwBA,MAAM,oCAAoB,IAAI,KAAsC;AAEpE,SAAS,sBAAsB,KAA8C;AAC3E,KAAI,OAAO,QAAQ,YAAY,IAAI,MAAM,CACvC,QAAO,EAAE,OAAO,IAAI,MAAM,EAAE;AAE9B,KAAI,OAAO,OAAO,QAAQ,YAAY,WAAW,KAAK;EACpD,MAAM,IAAI;AACV,MAAI,OAAO,EAAE,UAAU,YAAY,CAAC,EAAE,MAAM,MAAM,CAAE,QAAO;EAC3D,MAAM,SAAS,OAAO,EAAE,WAAW,YAAY,EAAE,OAAO,MAAM,GAAG,EAAE,OAAO,MAAM,GAAG,KAAA;AACnF,SAAO;GAAE,OAAO,EAAE,MAAM,MAAM;GAAE,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;GAAG;;AAEjE,QAAO;;;;;;;AAQT,SAAS,+BAA+B,QAAwB;AAC9D,QAAO,sBAAsB,OAAO;;;AAItC,SAAS,wBAAwB,WAAmB,WAA4B;CAC9E,MAAM,IAAI,+BAA+B,UAAU;CACnD,MAAM,IAAI,+BAA+B,UAAU;AACnD,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,EAAE,SAAS,MAAM,EAAE,SAAS,GAAI,QAAO;AAC3C,QAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE;;AAG3C,SAAS,gBAAgB,WAAmB,QAAwB;AAClE,QAAO,GAAG,yBAAyB,UAAU,CAAC,GAAG,+BAA+B,OAAO;;AAOzF,SAAS,8BAA8B,WAA2B;CAChE,MAAM,OAAO,yBAAyB,UAAU;AAChD,QAAO,KAAK,KAAK,sBAAsB,EAAE,YAAY,GAAG,KAAK,sBAAsB;;;AAIrF,SAAS,qBAAqB,WAAyB;CAErD,MAAM,SAAS,GADF,yBAAyB,UAChB,CAAC;CACvB,MAAM,SAAsE,EAAE;AAC9E,MAAK,MAAM,CAAC,GAAG,MAAM,kBACnB,KAAI,EAAE,WAAW,OAAO,EAAE;EACxB,MAAM,aAAa,EAAE,MAAM,OAAO,OAAO;AACzC,MAAI,EAAE,QAAQ,MAAM,CAClB,QAAO,cAAc;GAAE,OAAO,EAAE;GAAO,QAAQ,EAAE,OAAO,MAAM;GAAE;MAEhE,QAAO,cAAc,EAAE;;CAI7B,MAAM,WAAW,8BAA8B,UAAU;AACzD,KAAI;EACF,MAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,SAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACtC,SAAG,cAAc,UAAU,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ;UAC7D,KAAK;AACZ,SAAO,KAAK,yCAAyC,SAAS,IAAI,OAAO,IAAI,GAAG;;;;;;;AAQpF,SAAgB,qBAAqB,WAAyB;CAC5D,IAAI,QAAQ;CACZ,MAAM,OAAO,yBAAyB,UAAU;CAChD,MAAM,WAAW,8BAA8B,UAAU;AACzD,KAAI;AACF,MAAIA,OAAG,WAAW,SAAS,EAAE;GAC3B,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;GAC9C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAK,MAAM,CAAC,QAAQ,QAAQ,OAAO,QAAQ,OAAO,EAAE;IAClD,MAAM,QAAQ,sBAAsB,IAAI;AACxC,QAAI,OAAO,OAAO;AAChB,uBAAkB,IAAI,gBAAgB,MAAM,OAAO,EAAE,MAAM;AAC3D;;;;UAIC,KAAK;AACZ,SAAO,KAAK,wCAAwC,SAAS,IAAI,OAAO,IAAI,GAAG;;AAEjF,KAAI,QAAQ,EACV,QAAO,KAAK,kCAAkC,MAAM,sBAAsB,OAAO;;;AAKrF,SAAgB,6BAA6B,WAAyB;CACpE,MAAM,OAAO,yBAAyB,UAAU;CAChD,MAAM,SAAS,GAAG,KAAK;AACvB,MAAK,MAAM,KAAK,CAAC,GAAG,kBAAkB,MAAM,CAAC,CAC3C,KAAI,EAAE,WAAW,OAAO,CACtB,mBAAkB,OAAO,EAAE;AAG/B,KAAI;EACF,MAAM,WAAW,8BAA8B,UAAU;AACzD,MAAIA,OAAG,WAAW,SAAS,CAAE,QAAG,WAAW,SAAS;UAC7C,KAAK;AACZ,SAAO,KAAK,uEAAuE,OAAO,IAAI,GAAG;;AAEnG,QAAO,KAAK,4DAA4D,OAAO;;AAGjF,SAAS,4BAA4B,WAAmB,QAAqD;CAC3G,MAAM,OAAO,+BAA+B,OAAO;CACnD,MAAM,OAAO,yBAAyB,UAAU;CAChD,MAAM,WAAW,8BAA8B,UAAU;AACzD,KAAI,CAACA,OAAG,WAAW,SAAS,CAAE,QAAO,KAAA;AACrC,KAAI;EACF,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,OAAK,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,OAAO,EAAE;GACnD,MAAM,QAAQ,sBAAsB,IAAI;AACxC,OAAI,CAAC,OAAO,MAAO;AACnB,OAAI,CAAC,wBAAwB,SAAS,KAAK,CAAE;AAC7C,qBAAkB,IAAI,gBAAgB,MAAM,KAAK,EAAE,MAAM;AACzD,wBAAqB,UAAU;AAC/B,UAAO;;UAEF,KAAK;AACZ,SAAO,MAAM,gCAAgC,SAAS,IAAI,OAAO,IAAI,GAAG;;;AAK5E,SAAS,0BACP,WACA,QACqC;CACrC,MAAM,IAAI,gBAAgB,WAAW,OAAO;CAC5C,IAAI,IAAI,kBAAkB,IAAI,EAAE;AAChC,KAAI,GAAG,MACL,QAAO;CAET,MAAM,WAAW,4BAA4B,WAAW,OAAO;AAC/D,KAAI,SAAU,QAAO;AACrB,QAAO,kBAAkB,IAAI,EAAE;;;AAIjC,SAAgB,4BAA4B,WAAmB,QAAoC;AACjG,QAAO,0BAA0B,WAAW,OAAO,EAAE,QAAQ,MAAM,IAAI,KAAA;;;AAIzE,SAAgB,gBACd,WACA,QACA,OACA,SACM;CACN,MAAM,IAAI,gBAAgB,WAAW,OAAO;CAC5C,MAAM,OAAO,kBAAkB,IAAI,EAAE;CACrC,MAAM,SACJ,WAAW,QAAQ,iBAAiB,KAAA,IAChC,QAAQ,aAAa,MAAM,IAAI,KAAA,IAC/B,MAAM;CACZ,MAAM,OAAgC;EAAE;EAAO,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAAG;AAC9E,QAAO,MAAM,wBAAwB,EAAE,UAAU,SAAS,QAAQ,OAAO;AACzE,mBAAkB,IAAI,GAAG,KAAK;AAC9B,sBAAqB,UAAU;;;AAIjC,SAAgB,gBAAgB,WAAmB,QAAoC;CACrF,MAAM,IAAI,gBAAgB,WAAW,OAAO;CAC5C,MAAM,IAAI,0BAA0B,WAAW,OAAO;AACtD,QAAO,MACL,wBAAwB,EAAE,SAAS,GAAG,UAAU,KAAA,EAAU,aAAa,kBAAkB,OAC1F;AACD,QAAO,GAAG;;;;;;;;;;AAWZ,SAAgB,6BACd,YACA,QACU;AACV,QAAO,WAAW,QAAQ,OAAO,kBAAkB,IAAI,gBAAgB,IAAI,OAAO,CAAC,CAAC;;;AAItF,SAAgB,8BACd,YAC8D;CAC9D,MAAM,OAAO,+BAA+B,WAAW;CACvD,MAAM,MAAoE,EAAE;AAC5E,MAAK,MAAM,CAAC,GAAG,UAAU,mBAAmB;AAC1C,MAAI,CAAC,OAAO,OAAO,MAAM,CAAE;EAC3B,MAAM,MAAM,EAAE,QAAQ,IAAI;AAC1B,MAAI,OAAO,KAAK,OAAO,EAAE,SAAS,EAAG;EACrC,MAAM,YAAY,EAAE,MAAM,GAAG,IAAI;AAEjC,MAAI,CAAC,wBADc,EAAE,MAAM,MAAM,EACM,EAAE,KAAK,CAAE;AAChD,MAAI,UACF,KAAI,KAAK;GACP;GACA,OAAO,MAAM;GACb,GAAI,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,OAAO,MAAM,EAAE,GAAG,EAAE;GAChE,CAAC;;AAGN,QAAO;;;;;;AAOT,SAAgB,yCACd,YACmE;CACnE,MAAM,OAAO,+BAA+B,WAAW;CACvD,MAAM,cAAc,KAAK,KAAK,sBAAsB,EAAE,WAAW;AACjE,KAAI,CAACA,OAAG,WAAW,YAAY,CAAE,QAAO,KAAA;CACxC,MAAM,OAAqE,EAAE;CAC7E,IAAI;AACJ,KAAI;AACF,UAAQA,OAAG,YAAY,YAAY;SAC7B;AACN;;CAEF,MAAM,SAAS;AACf,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAK,SAAS,OAAO,CAAE;EAC5B,MAAM,kBAAkB,KAAK,MAAM,GAAG,IAAe;EACrD,MAAM,WAAW,KAAK,KAAK,aAAa,KAAK;AAC7C,MAAI;GACF,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;GAC9C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAK,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,OAAO,EAAE;IACnD,MAAM,QAAQ,sBAAsB,IAAI;AACxC,QAAI,CAAC,OAAO,MAAO;AACnB,QAAI,CAAC,wBAAwB,SAAS,KAAK,CAAE;AAC7C,SAAK,KAAK;KACR,WAAW,yBAAyB,gBAAgB;KACpD,OAAO,MAAM;KACb,GAAI,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,OAAO,MAAM,EAAE,GAAG,EAAE;KAChE,CAAC;;UAEE;;AAIV,KAAI,KAAK,WAAW,EAAG,QAAO,KAAA;CAC9B,MAAM,4BAAY,IAAI,KAAiD;AACvE,MAAK,MAAM,KAAK,KACd,KAAI,CAAC,UAAU,IAAI,EAAE,UAAU,CAAE,WAAU,IAAI,EAAE,WAAW;EAAE,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAQ,CAAC;AAEnG,KAAI,UAAU,OAAO,EAAG,QAAO,KAAA;CAC/B,MAAM,CAAC,WAAW,OAAO,CAAC,GAAG,UAAU,SAAS,CAAC,CAAC;AAClD,iBAAgB,WAAW,MAAM,IAAI,OAAO,IAAI,SAAS,EAAE,cAAc,IAAI,QAAQ,GAAG,KAAA,EAAU;AAClG,QAAO;EAAE;EAAW,OAAO,IAAI;EAAO,QAAQ,IAAI;EAAQ;;AAO5D,SAAS,qBAA6B;AACpC,QAAO,WAAW,SAAS;;;AA4B7B,SAAgB,YAAY,MAA4B;AACtD,QACE,KAAK,SAAS,gBAAgB,SAC9B,KAAK,SAAS,gBAAgB,SAC9B,KAAK,SAAS,gBAAgB,QAC9B,KAAK,SAAS,gBAAgB;;AAIlC,SAAS,iBAAiB,UAAkC;AAC1D,KAAI,CAAC,UAAU,OAAQ,QAAO;AAC9B,MAAK,MAAM,QAAQ,UAAU;AAC3B,MAAI,KAAK,SAAS,gBAAgB,QAAQ,KAAK,WAAW,QAAQ,MAAM;GACtE,MAAM,OAAO,OAAO,KAAK,UAAU,KAAK;GACxC,MAAM,MAAM,KAAK;AACjB,OAAI,CAAC,IAAK,QAAO;AAEjB,OAAI,IAAI,gBAAgB,YAAY,IAAI,aAAa,CAAE,QAAO;GAE9D,MAAM,QAAkB,EAAE;AAC1B,OAAI,IAAI,MAAO,OAAM,KAAK,IAAI,MAAM;AACpC,OAAI,IAAI,cAAc;IACpB,MAAM,UAAU,iBAAiB,CAAC,IAAI,aAAa,CAAC;AACpD,QAAI,QAAS,OAAM,KAAK,QAAQ;;AAElC,OAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,UAAO,QAAQ,MAAM,KAAK,MAAM,CAAC,KAAK;;AAGxC,MAAI,KAAK,SAAS,gBAAgB,SAAS,KAAK,YAAY,KAC1D,QAAO,KAAK,WAAW;;AAG3B,QAAO;;;;;;;;AAwBT,SAAgB,0BACd,KACA,WACA,MACkB;CAClB,MAAM,eAAe,IAAI,gBAAgB;CACzC,MAAM,MAAwB;EAC5B,MAAM,iBAAiB,IAAI,UAAU;EACrC,MAAM;EACN,IAAI;EACJ,WAAW;EACX,oBAAoB;EACpB,eAAe;EACf,YAAY,oBAAoB;EAChC,WAAW,IAAI;EACf,UAAU;EACV,UAAU;EACX;AACD,KAAI,IAAI,cACN,KAAI,gBAAgB,IAAI;AAG1B,KAAI,MAAM,kBAAkB;AAC1B,MAAI,YAAY,KAAK;AACrB,MAAI,YAAY;YACP,MAAM,oBAAoB;AACnC,MAAI,YAAY,KAAK;AACrB,MAAI,YAAY;YACP,MAAM,mBAAmB;AAClC,MAAI,YAAY,KAAK;AACrB,MAAI,YAAY,KAAK,iBAAiB;YAC7B,MAAM,oBAAoB;AACnC,MAAI,YAAY,KAAK;AACrB,MAAI,YAAY,KAAK,kBAAkB;;AAGzC,QAAO;;;AAIT,SAAgB,8BAA8B,KAA2C;AACvF,QAAO,IAAI"}
|
|
1
|
+
{"version":3,"file":"inbound.js","names":[],"sources":["../../../../../extensions/weixin/src/messaging/inbound.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { logger } from \"../util/logger.js\";\nimport { generateId } from \"../util/random.js\";\nimport type { WeixinMessage, MessageItem } from \"../api/types.js\";\nimport { MessageItemType } from \"../api/types.js\";\nimport { canonicalWeixinPeerId, normalizeWeixinAccountId } from \"../auth/weixin-account-id.js\";\nimport { resolveWeixinRootDir } from \"../storage/state-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Context token store (in-process cache + disk persistence)\n// ---------------------------------------------------------------------------\n\ntype WeixinContextTokenEntry = { token: string; sendTo?: string };\n\n/**\n * contextToken is issued per-message by the Weixin getupdates API and must\n * be echoed verbatim in every outbound send. The in-memory map is the primary\n * lookup; a disk-backed file per account ensures tokens survive gateway restarts.\n *\n * `sendTo` is the last inbound `from_user_id` verbatim. ilink often requires `to_user_id`\n * in sendmessage to match that string exactly (session/cron peer ids are sanitized).\n */\nconst contextTokenStore = new Map<string, WeixinContextTokenEntry>();\n\nfunction parsePersistedPeerVal(val: unknown): WeixinContextTokenEntry | null {\n if (typeof val === \"string\" && val.trim()) {\n return { token: val.trim() };\n }\n if (val && typeof val === \"object\" && \"token\" in val) {\n const o = val as { token: unknown; sendTo?: unknown };\n if (typeof o.token !== \"string\" || !o.token.trim()) return null;\n const sendTo = typeof o.sendTo === \"string\" && o.sendTo.trim() ? o.sendTo.trim() : undefined;\n return { token: o.token.trim(), ...(sendTo ? { sendTo } : {}) };\n }\n return null;\n}\n\n/**\n * Session keys use {@link canonicalWeixinPeerId} (same as `buildSessionKey`); cron `delivery.to`\n * and outbound `ctx.to` use that shape. ilink `from_user_id` is often `…@im.wechat` while the\n * session peer is `…-im-wechat`. Normalize so context_token cache keys match lookups.\n */\nfunction normalizeIlinkUserIdForContext(userId: string): string {\n return canonicalWeixinPeerId(userId);\n}\n\n/** ilink may use a shorter openid in one path and a suffixed id in another; treat as same peer when unambiguous. */\nfunction ilinkPeerKeysLikelySame(storedKey: string, queryPeer: string): boolean {\n const a = normalizeIlinkUserIdForContext(storedKey);\n const b = normalizeIlinkUserIdForContext(queryPeer);\n if (a === b) return true;\n if (a.length < 12 || b.length < 12) return false;\n return a.startsWith(b) || b.startsWith(a);\n}\n\nfunction contextTokenKey(accountId: string, userId: string): string {\n return `${normalizeWeixinAccountId(accountId)}:${normalizeIlinkUserIdForContext(userId)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Disk persistence helpers\n// ---------------------------------------------------------------------------\n\nfunction canonicalContextTokenFilePath(accountId: string): string {\n const norm = normalizeWeixinAccountId(accountId);\n return path.join(resolveWeixinRootDir(), \"accounts\", `${norm}.context-tokens.json`);\n}\n\n/** Persist all context tokens for a given account to disk. */\nfunction persistContextTokens(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n const tokens: Record<string, string | { token: string; sendTo?: string }> = {};\n for (const [k, v] of contextTokenStore) {\n if (k.startsWith(prefix)) {\n const peerSuffix = k.slice(prefix.length);\n if (v.sendTo?.trim()) {\n tokens[peerSuffix] = { token: v.token, sendTo: v.sendTo.trim() };\n } else {\n tokens[peerSuffix] = v.token;\n }\n }\n }\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), \"utf-8\");\n } catch (err) {\n logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);\n }\n}\n\n/**\n * Restore persisted context tokens for an account into the in-memory map.\n * Called once during gateway startAccount to survive restarts.\n */\nexport function restoreContextTokens(accountId: string): void {\n let count = 0;\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [userId, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (entry?.token) {\n contextTokenStore.set(contextTokenKey(norm, userId), entry);\n count++;\n }\n }\n }\n } catch (err) {\n logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);\n }\n if (count > 0) {\n logger.info(`restoreContextTokens: restored ${count} tokens for account=${norm}`);\n }\n}\n\n/** Remove all context tokens for a given account (memory + disk). */\nexport function clearContextTokensForAccount(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n for (const k of [...contextTokenStore.keys()]) {\n if (k.startsWith(prefix)) {\n contextTokenStore.delete(k);\n }\n }\n try {\n const filePath = canonicalContextTokenFilePath(accountId);\n if (fs.existsSync(filePath)) fs.unlinkSync(filePath);\n } catch (err) {\n logger.warn(`clearContextTokensForAccount: failed to remove context tokens file: ${String(err)}`);\n }\n logger.info(`clearContextTokensForAccount: cleared tokens for account=${norm}`);\n}\n\nfunction tryHydratePeerEntryFromDisk(accountId: string, userId: string): WeixinContextTokenEntry | undefined {\n const peer = normalizeIlinkUserIdForContext(userId);\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n if (!fs.existsSync(filePath)) return undefined;\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n contextTokenStore.set(contextTokenKey(norm, peer), entry);\n persistContextTokens(accountId);\n return entry;\n }\n } catch (err) {\n logger.debug(`tryHydratePeerEntryFromDisk: ${filePath}: ${String(err)}`);\n }\n return undefined;\n}\n\nfunction getWeixinContextPeerEntry(\n accountId: string,\n userId: string,\n): WeixinContextTokenEntry | undefined {\n const k = contextTokenKey(accountId, userId);\n let e = contextTokenStore.get(k);\n if (e?.token) {\n return e;\n }\n const hydrated = tryHydratePeerEntryFromDisk(accountId, userId);\n if (hydrated) return hydrated;\n return contextTokenStore.get(k);\n}\n\n/** Last inbound `from_user_id` for API `to_user_id` (verbatim), when known. */\nexport function getWeixinOutboundSendUserId(accountId: string, userId: string): string | undefined {\n return getWeixinContextPeerEntry(accountId, userId)?.sendTo?.trim() || undefined;\n}\n\n/** Store a context token for a given account+user pair (memory + disk). */\nexport function setContextToken(\n accountId: string,\n userId: string,\n token: string,\n options?: { sendToUserId?: string },\n): void {\n const k = contextTokenKey(accountId, userId);\n const prev = contextTokenStore.get(k);\n const sendTo =\n options && options.sendToUserId !== undefined\n ? options.sendToUserId.trim() || undefined\n : prev?.sendTo;\n const next: WeixinContextTokenEntry = { token, ...(sendTo ? { sendTo } : {}) };\n logger.debug(`setContextToken: key=${k} sendTo=${sendTo ? \"yes\" : \"no\"}`);\n contextTokenStore.set(k, next);\n persistContextTokens(accountId);\n}\n\n/** Retrieve the cached context token for a given account+user pair. */\nexport function getContextToken(accountId: string, userId: string): string | undefined {\n const k = contextTokenKey(accountId, userId);\n const e = getWeixinContextPeerEntry(accountId, userId);\n logger.debug(\n `getContextToken: key=${k} found=${e?.token !== undefined} storeSize=${contextTokenStore.size}`,\n );\n return e?.token;\n}\n\n/**\n * Find all accountIds that have an active contextToken for the given userId.\n * Used to infer the sending bot account from the recipient address when\n * accountId is not explicitly provided (e.g. cron delivery).\n *\n * Returns all matching accountIds (not just the first) so the caller can\n * detect ambiguity when multiple accounts have sessions with the same user.\n */\nexport function findAccountIdsByContextToken(\n accountIds: string[],\n userId: string,\n): string[] {\n return accountIds.filter((id) => contextTokenStore.has(contextTokenKey(id, userId)));\n}\n\n/** In-memory entries whose peer id matches (exact or ilink prefix/suffix variant). */\nexport function findContextTokenEntriesByPeer(\n peerUserId: string,\n): Array<{ accountId: string; token: string; sendTo?: string }> {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const out: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n for (const [k, entry] of contextTokenStore) {\n if (!entry?.token?.trim()) continue;\n const idx = k.indexOf(':');\n if (idx <= 0 || idx >= k.length - 1) continue;\n const accountId = k.slice(0, idx);\n const storedPeer = k.slice(idx + 1);\n if (!ilinkPeerKeysLikelySame(storedPeer, peer)) continue;\n if (accountId) {\n out.push({\n accountId,\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n }\n return out;\n}\n\n/**\n * When the preferred account id does not match the token filename, scan every\n * `accounts/*.context-tokens.json` for this peer (case-insensitive user key).\n */\nexport function tryHydrateAnyAccountContextTokenFromDisk(\n peerUserId: string,\n): { accountId: string; token: string; sendTo?: string } | undefined {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const accountsDir = path.join(resolveWeixinRootDir(), 'accounts');\n if (!fs.existsSync(accountsDir)) return undefined;\n const hits: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n let names: string[];\n try {\n names = fs.readdirSync(accountsDir);\n } catch {\n return undefined;\n }\n const suffix = '.context-tokens.json';\n for (const name of names) {\n if (!name.endsWith(suffix)) continue;\n const accountFromFile = name.slice(0, -suffix.length);\n const filePath = path.join(accountsDir, name);\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n hits.push({\n accountId: normalizeWeixinAccountId(accountFromFile),\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n } catch {\n /* skip corrupt file */\n }\n }\n if (hits.length === 0) return undefined;\n const byAccount = new Map<string, { token: string; sendTo?: string }>();\n for (const h of hits) {\n if (!byAccount.has(h.accountId)) byAccount.set(h.accountId, { token: h.token, sendTo: h.sendTo });\n }\n if (byAccount.size > 1) return undefined;\n const [accountId, hit] = [...byAccount.entries()][0]!;\n setContextToken(accountId, peer, hit.token, hit.sendTo ? { sendToUserId: hit.sendTo } : undefined);\n return { accountId, token: hit.token, sendTo: hit.sendTo };\n}\n\n// ---------------------------------------------------------------------------\n// Message ID generation\n// ---------------------------------------------------------------------------\n\nfunction generateMessageSid(): string {\n return generateId(\"weixin\");\n}\n\n/** Inbound context for the core message pipeline (matches MsgContext shape). */\nexport type WeixinMsgContext = {\n Body: string;\n From: string;\n To: string;\n AccountId: string;\n OriginatingChannel: \"weixin\";\n OriginatingTo: string;\n MessageSid: string;\n Timestamp?: number;\n Provider: \"weixin\";\n ChatType: \"direct\";\n /** Set by monitor after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */\n SessionKey?: string;\n context_token?: string;\n MediaUrl?: string;\n MediaPath?: string;\n MediaType?: string;\n /** Raw message body for framework command authorization. */\n CommandBody?: string;\n /** Whether the sender is authorized to execute slash commands. */\n CommandAuthorized?: boolean;\n};\n\n/** Returns true if the message item is a media type (image, video, file, or voice). */\nexport function isMediaItem(item: MessageItem): boolean {\n return (\n item.type === MessageItemType.IMAGE ||\n item.type === MessageItemType.VIDEO ||\n item.type === MessageItemType.FILE ||\n item.type === MessageItemType.VOICE\n );\n}\n\nfunction bodyFromItemList(itemList?: MessageItem[]): string {\n if (!itemList?.length) return \"\";\n for (const item of itemList) {\n if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {\n const text = String(item.text_item.text);\n const ref = item.ref_msg;\n if (!ref) return text;\n // Quoted media is passed as MediaPath; only include the current text as body.\n if (ref.message_item && isMediaItem(ref.message_item)) return text;\n // Build quoted context from both title and message_item content.\n const parts: string[] = [];\n if (ref.title) parts.push(ref.title);\n if (ref.message_item) {\n const refBody = bodyFromItemList([ref.message_item]);\n if (refBody) parts.push(refBody);\n }\n if (!parts.length) return text;\n return `[引用: ${parts.join(\" | \")}]\\n${text}`;\n }\n // 语音转文字:如果语音消息有 text 字段,直接使用文字内容\n if (item.type === MessageItemType.VOICE && item.voice_item?.text) {\n return item.voice_item.text;\n }\n }\n return \"\";\n}\n\nexport type WeixinInboundMediaOpts = {\n /** Local path to decrypted image file. */\n decryptedPicPath?: string;\n /** Local path to transcoded/raw voice file (.wav or .silk). */\n decryptedVoicePath?: string;\n /** MIME type for the voice file (e.g. \"audio/wav\" or \"audio/silk\"). */\n voiceMediaType?: string;\n /** Local path to decrypted file attachment. */\n decryptedFilePath?: string;\n /** MIME type for the file attachment (guessed from file_name). */\n fileMediaType?: string;\n /** Local path to decrypted video file. */\n decryptedVideoPath?: string;\n};\n\n/**\n * Convert a WeixinMessage from getUpdates to the inbound MsgContext for the core pipeline.\n * Media: only pass MediaPath (local file, after CDN download + decrypt).\n * We never pass MediaUrl — the upstream CDN URL is encrypted/auth-only.\n * Priority when multiple media types present: image > video > file > voice.\n */\nexport function weixinMessageToMsgContext(\n msg: WeixinMessage,\n accountId: string,\n opts?: WeixinInboundMediaOpts,\n): WeixinMsgContext {\n const from_user_id = msg.from_user_id ?? \"\";\n const ctx: WeixinMsgContext = {\n Body: bodyFromItemList(msg.item_list),\n From: from_user_id,\n To: from_user_id,\n AccountId: accountId,\n OriginatingChannel: \"weixin\",\n OriginatingTo: from_user_id,\n MessageSid: generateMessageSid(),\n Timestamp: msg.create_time_ms,\n Provider: \"weixin\",\n ChatType: \"direct\",\n };\n if (msg.context_token) {\n ctx.context_token = msg.context_token;\n }\n\n if (opts?.decryptedPicPath) {\n ctx.MediaPath = opts.decryptedPicPath;\n ctx.MediaType = \"image/*\";\n } else if (opts?.decryptedVideoPath) {\n ctx.MediaPath = opts.decryptedVideoPath;\n ctx.MediaType = \"video/mp4\";\n } else if (opts?.decryptedFilePath) {\n ctx.MediaPath = opts.decryptedFilePath;\n ctx.MediaType = opts.fileMediaType ?? \"application/octet-stream\";\n } else if (opts?.decryptedVoicePath) {\n ctx.MediaPath = opts.decryptedVoicePath;\n ctx.MediaType = opts.voiceMediaType ?? \"audio/wav\";\n }\n\n return ctx;\n}\n\n/** Extract the context_token from an inbound WeixinMsgContext. */\nexport function getContextTokenFromMsgContext(ctx: WeixinMsgContext): string | undefined {\n return ctx.context_token;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveWeixinRootDir } from "./state-dir.js";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import fs from "node:fs";
|
|
4
4
|
//#region extensions/weixin/src/storage/sync-buf.ts
|
|
5
5
|
function resolveAccountsDir() {
|
|
6
6
|
return path.join(resolveWeixinRootDir(), "accounts");
|
|
@@ -10,7 +10,7 @@ function getSyncBufFilePath(accountId) {
|
|
|
10
10
|
}
|
|
11
11
|
function readSyncBufFile(filePath) {
|
|
12
12
|
try {
|
|
13
|
-
const raw =
|
|
13
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
14
14
|
const data = JSON.parse(raw);
|
|
15
15
|
if (typeof data.get_updates_buf === "string") return data.get_updates_buf;
|
|
16
16
|
} catch {}
|
|
@@ -20,8 +20,8 @@ function loadGetUpdatesBuf(filePath) {
|
|
|
20
20
|
}
|
|
21
21
|
function saveGetUpdatesBuf(filePath, getUpdatesBuf) {
|
|
22
22
|
const dir = path.dirname(filePath);
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
fs.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), "utf-8");
|
|
25
25
|
}
|
|
26
26
|
//#endregion
|
|
27
27
|
export { getSyncBufFilePath, loadGetUpdatesBuf, saveGetUpdatesBuf };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-buf.js","names":[
|
|
1
|
+
{"version":3,"file":"sync-buf.js","names":[],"sources":["../../../../../extensions/weixin/src/storage/sync-buf.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { resolveWeixinRootDir } from './state-dir.js';\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinRootDir(), 'accounts');\n}\n\nexport function getSyncBufFilePath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.sync.json`);\n}\n\nexport type SyncBufData = {\n get_updates_buf: string;\n};\n\nfunction readSyncBufFile(filePath: string): string | undefined {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const data = JSON.parse(raw) as { get_updates_buf?: string };\n if (typeof data.get_updates_buf === 'string') {\n return data.get_updates_buf;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nexport function loadGetUpdatesBuf(filePath: string): string | undefined {\n return readSyncBufFile(filePath);\n}\n\nexport function saveGetUpdatesBuf(filePath: string, getUpdatesBuf: string): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), 'utf-8');\n}\n"],"mappings":";;;;AAKA,SAAS,qBAA6B;AACpC,QAAO,KAAK,KAAK,sBAAsB,EAAE,WAAW;;AAGtD,SAAgB,mBAAmB,WAA2B;AAC5D,QAAO,KAAK,KAAK,oBAAoB,EAAE,GAAG,UAAU,YAAY;;AAOlE,SAAS,gBAAgB,UAAsC;AAC7D,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,MAAI,OAAO,KAAK,oBAAoB,SAClC,QAAO,KAAK;SAER;;AAMV,SAAgB,kBAAkB,UAAsC;AACtE,QAAO,gBAAgB,SAAS;;AAGlC,SAAgB,kBAAkB,UAAkB,eAA6B;CAC/E,MAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,IAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACtC,IAAG,cAAc,UAAU,KAAK,UAAU,EAAE,iBAAiB,eAAe,EAAE,MAAM,EAAE,EAAE,QAAQ"}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* the full final snapshot rendered as text. The user sees a single
|
|
13
13
|
* tasteful summary at the end.
|
|
14
14
|
*
|
|
15
|
-
* Routing: sessionKey `main:weixin:<accountId>:
|
|
15
|
+
* Routing: sessionKey `agent:main:weixin:<accountId>:direct:<ilinkUserId>`. We need a
|
|
16
16
|
* valid `contextToken` for the recipient — which requires the user to have
|
|
17
17
|
* recently messaged the bot (the token is harvested from inbound). If
|
|
18
18
|
* missing, we throw and the broker logs; the run still completes, just
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/weixin/src/workflow-progress.ts"],"sourcesContent":["/**\n * WeChat capability for the workflow progress broker.\n *\n * WeChat (ilink/personal account) does not expose an \"edit message\" API for\n * bot replies, so live edit-in-place — what Telegram and Feishu do — is\n * structurally impossible. Trying to fake it with a `append` mode would\n * spam the chat with one message per tick, which violates the social\n * properties of the surface (\"Don't @ me twenty times\").\n *\n * Strategy: **final-only**. The broker silently drops every mid-run update;\n * when `tool_end` fires, the broker calls us once with `isFinal: true` and\n * the full final snapshot rendered as text. The user sees a single\n * tasteful summary at the end.\n *\n * Routing: sessionKey `main:weixin:<accountId>:
|
|
1
|
+
{"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/weixin/src/workflow-progress.ts"],"sourcesContent":["/**\n * WeChat capability for the workflow progress broker.\n *\n * WeChat (ilink/personal account) does not expose an \"edit message\" API for\n * bot replies, so live edit-in-place — what Telegram and Feishu do — is\n * structurally impossible. Trying to fake it with a `append` mode would\n * spam the chat with one message per tick, which violates the social\n * properties of the surface (\"Don't @ me twenty times\").\n *\n * Strategy: **final-only**. The broker silently drops every mid-run update;\n * when `tool_end` fires, the broker calls us once with `isFinal: true` and\n * the full final snapshot rendered as text. The user sees a single\n * tasteful summary at the end.\n *\n * Routing: sessionKey `agent:main:weixin:<accountId>:direct:<ilinkUserId>`. We need a\n * valid `contextToken` for the recipient — which requires the user to have\n * recently messaged the bot (the token is harvested from inbound). If\n * missing, we throw and the broker logs; the run still completes, just\n * without a WeChat notification. This is consistent with how other WeChat\n * outbound paths behave.\n */\n\nimport type {\n ChannelProgressCapability,\n WorkflowProgressPostInput,\n} from '@xopcai/xopc/agent/workflow/index.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveWeixinAccount } from './auth/accounts.js';\nimport { ensureWeixinContextTokenForOutbound } from './messaging/context-token-init.js';\nimport { getContextToken } from './messaging/inbound.js';\nimport { sendMessageWeixin } from './messaging/send.js';\n\nconst log = createLogger('WeixinWorkflowProgress');\n\nconst WEIXIN_TEXT_MAX = 4_000;\n/**\n * Conservative throttle — only enforced when the user overrides the default\n * `final-only` mode to `append`. Set so back-to-back workflows can't spam.\n */\nconst DEFAULT_THROTTLE_MS = 60_000;\n\nexport function createWeixinWorkflowProgressCapability(opts: {\n getConfig: () => Config | undefined;\n}): ChannelProgressCapability {\n return {\n channelId: 'weixin',\n supportsEdit: false,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'final-only',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const cfg = opts.getConfig();\n if (!cfg) {\n throw new Error('weixin workflow progress: no config loaded');\n }\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`weixin workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n\n let account;\n try {\n account = resolveWeixinAccount(cfg, target.accountId);\n } catch (err) {\n throw new Error(\n `weixin workflow progress: cannot resolve account \"${target.accountId}\": ${errorMessage(err)}`,\n );\n }\n if (!account.configured || !account.token) {\n throw new Error(\n `weixin workflow progress: account \"${target.accountId}\" not configured / logged in`,\n );\n }\n\n let ctxTok = getContextToken(account.accountId, target.to)?.trim();\n if (!ctxTok) {\n ctxTok = (await ensureWeixinContextTokenForOutbound(account.accountId, target.to, account))?.trim();\n }\n if (!ctxTok) {\n // No usable context token — without one WeChat refuses outbound. Better\n // to drop the progress notice than to spam an error; the parent agent\n // still surfaces the result through its normal reply path.\n log.debug(\n { sessionKey: input.sessionKey, accountId: account.accountId },\n 'no context token for recipient; skipping workflow progress send',\n );\n return { messageId: '' };\n }\n\n const text = clampForWeixin(decorateForWeixin(input.text, input.mode, input.isFinal));\n const r = await sendMessageWeixin({\n to: target.to,\n text,\n opts: {\n baseUrl: account.baseUrl,\n token: account.token,\n routeTag: account.routeTag,\n contextToken: ctxTok,\n },\n });\n return { messageId: r.messageId };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n to: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'weixin') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId || 'default',\n to: parsed.peerId,\n };\n}\n\nfunction clampForWeixin(text: string): string {\n if (text.length <= WEIXIN_TEXT_MAX) return text;\n return `${text.slice(0, WEIXIN_TEXT_MAX - 1)}…`;\n}\n\n/**\n * Add a one-line header on append-mode messages so the user can tell mid-run\n * snapshots apart from the final summary — WeChat has no editMessage, so they\n * pile up as separate messages and look identical without a marker.\n *\n * - `final-only` mode: no header (default; one summary message per run).\n * - `append` mode + mid-run: \"▾ 工作流进展\" header so the user knows more\n * updates are coming.\n * - `append` mode + final: \"✓ 工作流完成\" header to mark the conclusion.\n *\n * `edit` mode is never reached on WeChat (`supportsEdit: false`), so we don't\n * branch on it. Unknown / missing mode falls through to no header — safe\n * default for hand-rolled callers and tests.\n */\nfunction decorateForWeixin(\n text: string,\n mode: string | undefined,\n isFinal: boolean,\n): string {\n if (mode !== 'append') return text;\n const header = isFinal ? '✓ 工作流完成' : '▾ 工作流进展';\n return `${header}\\n${text}`;\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return '';\n if (err instanceof Error) return err.message;\n return String(err);\n}\n"],"mappings":";;;;;;;;kBA2BsE;aACV;AAO5D,MAAM,MAAM,aAAa,yBAAyB;AAElD,MAAM,kBAAkB;;;;;AAKxB,MAAM,sBAAsB;AAE5B,SAAgB,uCAAuC,MAEzB;AAC5B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,MAAM,KAAK,WAAW;AAC5B,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD,MAAM,WAAW,GAAG;GAG5F,IAAI;AACJ,OAAI;AACF,cAAU,qBAAqB,KAAK,OAAO,UAAU;YAC9C,KAAK;AACZ,UAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,KAAK,aAAa,IAAI,GAC7F;;AAEH,OAAI,CAAC,QAAQ,cAAc,CAAC,QAAQ,MAClC,OAAM,IAAI,MACR,sCAAsC,OAAO,UAAU,8BACxD;GAGH,IAAI,SAAS,gBAAgB,QAAQ,WAAW,OAAO,GAAG,EAAE,MAAM;AAClE,OAAI,CAAC,OACH,WAAU,MAAM,oCAAoC,QAAQ,WAAW,OAAO,IAAI,QAAQ,GAAG,MAAM;AAErG,OAAI,CAAC,QAAQ;AAIX,QAAI,MACF;KAAE,YAAY,MAAM;KAAY,WAAW,QAAQ;KAAW,EAC9D,kEACD;AACD,WAAO,EAAE,WAAW,IAAI;;GAG1B,MAAM,OAAO,eAAe,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,CAAC;AAWrF,UAAO,EAAE,YAAW,MAVJ,kBAAkB;IAChC,IAAI,OAAO;IACX;IACA,MAAM;KACJ,SAAS,QAAQ;KACjB,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,cAAc;KACf;IACF,CAAC,EACoB,WAAW;;EAEpC;;AAQH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO,aAAa;EAC/B,IAAI,OAAO;EACZ;;AAGH,SAAS,eAAe,MAAsB;AAC5C,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,GAAG,KAAK,MAAM,GAAG,kBAAkB,EAAE,CAAC;;;;;;;;;;;;;;;;AAiB/C,SAAS,kBACP,MACA,MACA,SACQ;AACR,KAAI,SAAS,SAAU,QAAO;AAE9B,QAAO,GADQ,UAAU,YAAY,UACpB,IAAI;;AAGvB,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}
|