@xopcai/xopc 0.0.83 → 0.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.zh-CN.md +3 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
- package/dist/extensions/feishu/src/plugin.d.ts +2 -0
- package/dist/extensions/feishu/src/plugin.js +10 -0
- package/dist/extensions/feishu/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/workflow-progress.d.ts +27 -0
- package/dist/extensions/feishu/src/workflow-progress.js +99 -0
- package/dist/extensions/feishu/src/workflow-progress.js.map +1 -0
- package/dist/extensions/telegram/src/plugin.d.ts +2 -0
- package/dist/extensions/telegram/src/plugin.js +11 -1
- package/dist/extensions/telegram/src/plugin.js.map +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +2 -2
- package/dist/extensions/telegram/src/workflow-progress.d.ts +24 -0
- package/dist/extensions/telegram/src/workflow-progress.js +73 -0
- package/dist/extensions/telegram/src/workflow-progress.js.map +1 -0
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +158 -0
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -0
- package/dist/extensions/weixin/src/api/api.js +2 -2
- package/dist/extensions/weixin/src/auth/accounts.js +1 -1
- package/dist/extensions/weixin/src/cdn/upload.js +1 -1
- package/dist/extensions/weixin/src/media/data-url.js +1 -1
- package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
- package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
- package/dist/extensions/weixin/src/plugin.d.ts +2 -0
- package/dist/extensions/weixin/src/plugin.js +11 -1
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
- package/dist/extensions/weixin/src/workflow-progress.d.ts +26 -0
- package/dist/extensions/weixin/src/workflow-progress.js +99 -0
- package/dist/extensions/weixin/src/workflow-progress.js.map +1 -0
- package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +222 -0
- package/dist/gateway/static/root/assets/apps-page-D7v7649T.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-nCaMb0a7.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-C1gZBcJV.js +8 -0
- package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
- package/dist/gateway/static/root/assets/cron-api-CoYK0hlm.js +1 -0
- package/dist/gateway/static/root/assets/cron-page-DeGo-Vjc.js +1 -0
- package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +45 -0
- package/dist/gateway/static/root/assets/{dist-BpQxde0t.js → dist-DaK4dsss.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CY27wj_p.js → extension-debug-page-BZngZWbO.js} +1 -1
- package/dist/gateway/static/root/assets/extension-page-D6JSyV27.js +1 -0
- package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +1 -0
- package/dist/gateway/static/root/assets/fetch-B2MYHbWg.js +1 -0
- package/dist/gateway/static/root/assets/{field-primitives-fa_hiQcX.js → field-primitives-Zzl22MvN.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-config-api-BtIcpG0O.js +1 -0
- package/dist/gateway/static/root/assets/index-D4vM3-P7.js +4700 -0
- package/dist/gateway/static/root/assets/index-ew_2L2We.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-_d4UJ-qQ.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-5N4aF2Wk.js +1 -0
- package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +3 -0
- package/dist/gateway/static/root/assets/share-preview-page-D4EG_vM1.js +2 -0
- package/dist/gateway/static/root/assets/skills-page-sPAXhh8w.js +2 -0
- package/dist/gateway/static/root/assets/theme-store-DryYl3qD.js +1 -0
- package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +3 -0
- package/dist/gateway/static/root/assets/utils-CYO9eTCM.js +1 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-Ds51havm.js +1 -0
- package/dist/gateway/static/root/index.html +7 -6
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +7 -7
- package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
- package/dist/src/agent/context/workspace-seed.js +3 -3
- package/dist/src/agent/embedded/map-stream-events.js +6 -0
- package/dist/src/agent/embedded/map-stream-events.js.map +1 -1
- package/dist/src/agent/embedded/subscribe-session.js +24 -0
- package/dist/src/agent/embedded/subscribe-session.js.map +1 -1
- package/dist/src/agent/embedded/types.d.ts +19 -0
- package/dist/src/agent/goals/goal-locale.js +2 -2
- package/dist/src/agent/goals/goal-run-store.js +4 -4
- package/dist/src/agent/goals/persistent-goal-service.js +1 -1
- package/dist/src/agent/goals/post-turn.js +2 -2
- package/dist/src/agent/image/load-image-media.js +2 -2
- package/dist/src/agent/ipc/bus.js +1 -1
- package/dist/src/agent/ipc/inbox.js +2 -2
- package/dist/src/agent/ipc/socket.js +1 -1
- package/dist/src/agent/memory/builtin-memory-store.js +1 -1
- package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
- package/dist/src/agent/memory/dreaming/events.js +1 -1
- package/dist/src/agent/memory/dreaming/last-run.js +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
- package/dist/src/agent/memory/dreaming/preview.js +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
- package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
- package/dist/src/agent/memory/dreaming/utils.js +1 -1
- package/dist/src/agent/memory/plugin-discovery.js +1 -1
- package/dist/src/agent/models/manager.js +1 -1
- package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
- package/dist/src/agent/reply/post-compaction-context.js +1 -1
- package/dist/src/agent/reply/startup-context.d.ts +3 -0
- package/dist/src/agent/reply/startup-context.js +25 -2
- package/dist/src/agent/reply/startup-context.js.map +1 -1
- package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
- package/dist/src/agent/sandbox/path-policy.js +2 -2
- package/dist/src/agent/service/build-direct-message-content.js +1 -1
- package/dist/src/agent/service.d.ts +1 -0
- package/dist/src/agent/service.js +10 -4
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/config.js +1 -1
- package/dist/src/agent/skills/hub-hash.js +2 -2
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/skills/hub-pull.js +3 -3
- package/dist/src/agent/skills/index.js +1 -1
- package/dist/src/agent/skills/managed-store.js +1 -1
- package/dist/src/agent/skills/scanner.js +1 -1
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/skills/skill-manager.js +1 -1
- package/dist/src/agent/tools/create-share-tool.d.ts +27 -0
- package/dist/src/agent/tools/create-share-tool.js +237 -0
- package/dist/src/agent/tools/create-share-tool.js.map +1 -0
- package/dist/src/agent/tools/dreaming-tool.js +1 -1
- package/dist/src/agent/tools/factory.js +35 -1
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/image-generate-tool.js +1 -1
- package/dist/src/agent/tools/index.d.ts +2 -0
- package/dist/src/agent/tools/index.js +3 -1
- package/dist/src/agent/tools/send-media.js +1 -1
- package/dist/src/agent/tools/skill-manage-tool.js +1 -1
- package/dist/src/agent/tools/workflow-tool.d.ts +41 -0
- package/dist/src/agent/tools/workflow-tool.js +271 -0
- package/dist/src/agent/tools/workflow-tool.js.map +1 -0
- package/dist/src/agent/tools/write.js +1 -1
- package/dist/src/agent/workflow/builtins/audit-repo.d.ts +9 -0
- package/dist/src/agent/workflow/builtins/audit-repo.js +115 -0
- package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -0
- package/dist/src/agent/workflow/builtins/index.d.ts +15 -0
- package/dist/src/agent/workflow/builtins/index.js +28 -0
- package/dist/src/agent/workflow/builtins/index.js.map +1 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +9 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js +113 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -0
- package/dist/src/agent/workflow/builtins/research.d.ts +9 -0
- package/dist/src/agent/workflow/builtins/research.js +129 -0
- package/dist/src/agent/workflow/builtins/research.js.map +1 -0
- package/dist/src/agent/workflow/catalog.d.ts +51 -0
- package/dist/src/agent/workflow/catalog.js +155 -0
- package/dist/src/agent/workflow/catalog.js.map +1 -0
- package/dist/src/agent/workflow/channel-capability.d.ts +76 -0
- package/dist/src/agent/workflow/channel-capability.js +1 -0
- package/dist/src/agent/workflow/index.d.ts +11 -0
- package/dist/src/agent/workflow/index.js +10 -0
- package/dist/src/agent/workflow/last-run-memory.d.ts +42 -0
- package/dist/src/agent/workflow/last-run-memory.js +60 -0
- package/dist/src/agent/workflow/last-run-memory.js.map +1 -0
- package/dist/src/agent/workflow/parser.d.ts +20 -0
- package/dist/src/agent/workflow/parser.js +137 -0
- package/dist/src/agent/workflow/parser.js.map +1 -0
- package/dist/src/agent/workflow/progress-broker.d.ts +80 -0
- package/dist/src/agent/workflow/progress-broker.js +263 -0
- package/dist/src/agent/workflow/progress-broker.js.map +1 -0
- package/dist/src/agent/workflow/runtime.d.ts +31 -0
- package/dist/src/agent/workflow/runtime.js +301 -0
- package/dist/src/agent/workflow/runtime.js.map +1 -0
- package/dist/src/agent/workflow/snapshot.d.ts +18 -0
- package/dist/src/agent/workflow/snapshot.js +144 -0
- package/dist/src/agent/workflow/snapshot.js.map +1 -0
- package/dist/src/agent/workflow/structured-output-tool.d.ts +33 -0
- package/dist/src/agent/workflow/structured-output-tool.js +58 -0
- package/dist/src/agent/workflow/structured-output-tool.js.map +1 -0
- package/dist/src/agent/workflow/subagent-runner.d.ts +42 -0
- package/dist/src/agent/workflow/subagent-runner.js +104 -0
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -0
- package/dist/src/agent/workflow/types.d.ts +137 -0
- package/dist/src/agent/workflow/types.js +1 -0
- package/dist/src/auth/credentials.js +3 -3
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +1 -1
- package/dist/src/browser/cache-dir-policy.js +1 -1
- package/dist/src/browser/cdp-local-launcher.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +4 -4
- package/dist/src/browser/providers/cloakbrowser.js +4 -4
- package/dist/src/browser/providers/playwright-doctor.js +1 -1
- package/dist/src/browser/stealth.js +1 -1
- package/dist/src/channels/attachments/inbound-persist.js +1 -1
- package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
- package/dist/src/channels/outbound/persist-store.js +1 -1
- package/dist/src/channels/pairing/allow-from-file.js +1 -1
- package/dist/src/channels/pairing/pairing-store.js +2 -2
- package/dist/src/chat-commands/builtins/config.js +2 -2
- package/dist/src/chat-commands/builtins/model.js +40 -23
- package/dist/src/chat-commands/builtins/model.js.map +1 -1
- package/dist/src/chat-commands/builtins/system.js +30 -15
- package/dist/src/chat-commands/builtins/system.js.map +1 -1
- package/dist/src/chat-commands/builtins/workflow.d.ts +18 -0
- package/dist/src/chat-commands/builtins/workflow.js +167 -0
- package/dist/src/chat-commands/builtins/workflow.js.map +1 -0
- package/dist/src/chat-commands/context.js +1 -1
- package/dist/src/chat-commands/format-output.d.ts +28 -0
- package/dist/src/chat-commands/format-output.js +45 -0
- package/dist/src/chat-commands/format-output.js.map +1 -0
- package/dist/src/chat-commands/index.d.ts +1 -0
- package/dist/src/chat-commands/index.js +3 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/cli/command-catalog.js +110 -8
- package/dist/src/cli/command-catalog.js.map +1 -1
- package/dist/src/cli/command-loaders.js +2 -0
- package/dist/src/cli/command-loaders.js.map +1 -1
- package/dist/src/cli/command-manifest.js +9 -1
- package/dist/src/cli/command-manifest.js.map +1 -1
- package/dist/src/cli/commands/config.js +71 -20
- package/dist/src/cli/commands/config.js.map +1 -1
- package/dist/src/cli/commands/cron-cli.d.ts +2 -0
- package/dist/src/cli/commands/cron-cli.js +15 -0
- package/dist/src/cli/commands/cron-cli.js.map +1 -0
- package/dist/src/cli/commands/cron.d.ts +4 -1
- package/dist/src/cli/commands/cron.js +76 -41
- package/dist/src/cli/commands/cron.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/channel-config.js +1 -1
- package/dist/src/cli/commands/doctor/checks/channel-config.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/config-health.js +2 -2
- package/dist/src/cli/commands/doctor/checks/config-health.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/cron-health.js +1 -1
- package/dist/src/cli/commands/doctor/checks/cron-health.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/gateway-health.js +2 -2
- package/dist/src/cli/commands/doctor/checks/gateway-health.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/gateway-service.js +2 -2
- package/dist/src/cli/commands/doctor/checks/gateway-service.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
- package/dist/src/cli/commands/doctor/checks/state-integrity.js +2 -2
- package/dist/src/cli/commands/doctor/checks/state-integrity.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/workspace-status.js +4 -4
- package/dist/src/cli/commands/doctor/checks/workspace-status.js.map +1 -1
- package/dist/src/cli/commands/extension-dev.js +1 -1
- package/dist/src/cli/commands/extension-marketplace.js +1 -1
- package/dist/src/cli/commands/extension-pack.js +1 -1
- package/dist/src/cli/commands/gateway/index.d.ts +1 -1
- package/dist/src/cli/commands/gateway/index.js +2 -2
- package/dist/src/cli/commands/gateway/lifecycle.js +10 -4
- package/dist/src/cli/commands/gateway/lifecycle.js.map +1 -1
- package/dist/src/cli/commands/gateway/service.d.ts +4 -0
- package/dist/src/cli/commands/gateway/service.js +17 -2
- package/dist/src/cli/commands/gateway/service.js.map +1 -1
- package/dist/src/cli/commands/gateway/shared.js +1 -1
- package/dist/src/cli/commands/gateway/subcommands.js +1 -4
- package/dist/src/cli/commands/gateway/subcommands.js.map +1 -1
- package/dist/src/cli/commands/image.js +1 -1
- package/dist/src/cli/commands/init.js +31 -4
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/models.d.ts +4 -1
- package/dist/src/cli/commands/models.js +86 -74
- package/dist/src/cli/commands/models.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +4 -2
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/profile.d.ts +3 -5
- package/dist/src/cli/commands/profile.js +31 -31
- package/dist/src/cli/commands/profile.js.map +1 -1
- package/dist/src/cli/commands/setup.js +6 -1
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/dist/src/cli/commands/tunnel.js +2 -2
- package/dist/src/cli/gateway-run-argv.js +15 -5
- package/dist/src/cli/gateway-run-argv.js.map +1 -1
- package/dist/src/cli/utils/gateway-client.js +1 -1
- package/dist/src/cli/utils/init-workspace-core.js +2 -2
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/gateway-bind.js +1 -1
- package/dist/src/config/index.js +5 -5
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +2 -2
- package/dist/src/config/paths-state.js +1 -1
- package/dist/src/config/profile.js +2 -2
- package/dist/src/config/public-url.d.ts +28 -0
- package/dist/src/config/public-url.js +103 -0
- package/dist/src/config/public-url.js.map +1 -0
- package/dist/src/config/schema.d.ts +82 -0
- package/dist/src/config/schema.js +130 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/workspace-path.js +1 -1
- package/dist/src/cron/executor.js +2 -2
- package/dist/src/cron/persistence.js +1 -1
- package/dist/src/cron/run-log-store.js +1 -1
- package/dist/src/daemon/constants.js +1 -1
- package/dist/src/daemon/install-plan.js +3 -3
- package/dist/src/daemon/install-plan.js.map +1 -1
- package/dist/src/daemon/launchd.js +2 -2
- package/dist/src/daemon/schtasks.js +38 -1
- package/dist/src/daemon/schtasks.js.map +1 -1
- package/dist/src/daemon/systemd.js +2 -2
- package/dist/src/extensions/bundle-mcp.js +1 -1
- package/dist/src/extensions/discover-extensions.js +1 -1
- package/dist/src/extensions/health.js +1 -1
- package/dist/src/extensions/loader.js +1 -1
- package/dist/src/extensions/lockfile.js +2 -2
- package/dist/src/gateway/agents-admin.js +2 -2
- package/dist/src/gateway/file-path-classifier.js +2 -2
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +33 -2
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/extension-store.js +2 -2
- package/dist/src/gateway/hono/lib/static-ui.js +2 -2
- package/dist/src/gateway/hono/oauth.js +1 -1
- package/dist/src/gateway/hono/routes/agents.js +1 -1
- package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
- package/dist/src/gateway/hono/routes/dreaming.js +1 -1
- package/dist/src/gateway/hono/routes/host-fs.js +2 -2
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/hono/routes/shares.js +631 -34
- package/dist/src/gateway/hono/routes/shares.js.map +1 -1
- package/dist/src/gateway/hono/routes/site-shares.d.ts +3 -0
- package/dist/src/gateway/hono/routes/site-shares.js +228 -0
- package/dist/src/gateway/hono/routes/site-shares.js.map +1 -0
- package/dist/src/gateway/hono/routes/tunnel.js +97 -8
- package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
- package/dist/src/gateway/hono/routes/workspace.js +5 -5
- package/dist/src/gateway/hono/sse.js +2 -2
- package/dist/src/gateway/host.d.ts +3 -1
- package/dist/src/gateway/host.js +3 -1
- package/dist/src/gateway/host.js.map +1 -1
- package/dist/src/gateway/lock.js +3 -3
- package/dist/src/gateway/ports.d.ts +6 -0
- package/dist/src/gateway/ports.js +38 -2
- package/dist/src/gateway/ports.js.map +1 -1
- package/dist/src/gateway/public-url.d.ts +8 -0
- package/dist/src/gateway/public-url.js +10 -0
- package/dist/src/gateway/public-url.js.map +1 -0
- package/dist/src/gateway/security/origin-check.d.ts +9 -1
- package/dist/src/gateway/security/origin-check.js +4 -0
- package/dist/src/gateway/security/origin-check.js.map +1 -1
- package/dist/src/gateway/server.js +15 -0
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service/agent-runner.js +2 -2
- package/dist/src/gateway/service/marketplace-service.js +2 -2
- package/dist/src/gateway/service/run-gateway-agent.js +2 -2
- package/dist/src/gateway/service.js +3 -2
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/workspace-fs-file-list.js +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/i18n/goals-bundle.js +1 -1
- package/dist/src/i18n/index.d.ts +1 -0
- package/dist/src/i18n/index.js +2 -1
- package/dist/src/i18n/locales/share-tool.en.js +15 -0
- package/dist/src/i18n/locales/share-tool.en.js.map +1 -0
- package/dist/src/i18n/locales/share-tool.zh.js +15 -0
- package/dist/src/i18n/locales/share-tool.zh.js.map +1 -0
- package/dist/src/i18n/share-tool-bundle.d.ts +20 -0
- package/dist/src/i18n/share-tool-bundle.js +56 -0
- package/dist/src/i18n/share-tool-bundle.js.map +1 -0
- package/dist/src/infra/gateway-processes.js +1 -0
- package/dist/src/infra/gateway-processes.js.map +1 -1
- package/dist/src/infra/restart.js +2 -2
- package/dist/src/infra/update-check.js +1 -1
- package/dist/src/infra/update-lock.js +3 -3
- package/dist/src/infra/update-runner.js +1 -1
- package/dist/src/infra/update-startup.js +2 -2
- package/dist/src/infra/write-file-atomic.js +2 -2
- package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
- package/dist/src/providers/index.js +2 -2
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/session/config-store.js +2 -2
- package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
- package/dist/src/session/parity/sessions-json-file.js +1 -1
- package/dist/src/session/parity/transcript-file-lock.js +2 -2
- package/dist/src/session/parity/transcript-paths.js +1 -1
- package/dist/src/session/search-index-cache.js +1 -1
- package/dist/src/session/search-index.js +1 -1
- package/dist/src/session/session-title.js +3 -2
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/session/store.js +5 -5
- package/dist/src/share/share-auto.d.ts +74 -0
- package/dist/src/share/share-auto.js +247 -0
- package/dist/src/share/share-auto.js.map +1 -0
- package/dist/src/share/share-config.js +63 -4
- package/dist/src/share/share-config.js.map +1 -1
- package/dist/src/share/share-landing.d.ts +28 -2
- package/dist/src/share/share-landing.js +155 -34
- package/dist/src/share/share-landing.js.map +1 -1
- package/dist/src/share/share-store.d.ts +48 -4
- package/dist/src/share/share-store.js +322 -51
- package/dist/src/share/share-store.js.map +1 -1
- package/dist/src/share/share-thumbnail.d.ts +35 -0
- package/dist/src/share/share-thumbnail.js +277 -0
- package/dist/src/share/share-thumbnail.js.map +1 -0
- package/dist/src/share/share-types.d.ts +68 -10
- package/dist/src/share/share-types.js +18 -1
- package/dist/src/share/share-types.js.map +1 -1
- package/dist/src/share/share-url.js +1 -1
- package/dist/src/share/share-zip.d.ts +35 -0
- package/dist/src/share/share-zip.js +303 -0
- package/dist/src/share/share-zip.js.map +1 -0
- package/dist/src/share/site-proxy.d.ts +35 -0
- package/dist/src/share/site-proxy.js +234 -0
- package/dist/src/share/site-proxy.js.map +1 -0
- package/dist/src/share/site-share-config.d.ts +11 -0
- package/dist/src/share/site-share-config.js +103 -0
- package/dist/src/share/site-share-config.js.map +1 -0
- package/dist/src/share/site-share-router.d.ts +23 -0
- package/dist/src/share/site-share-router.js +147 -0
- package/dist/src/share/site-share-router.js.map +1 -0
- package/dist/src/share/site-share-store.d.ts +53 -0
- package/dist/src/share/site-share-store.js +400 -0
- package/dist/src/share/site-share-store.js.map +1 -0
- package/dist/src/share/site-share-types.d.ts +103 -0
- package/dist/src/share/site-share-types.js +41 -0
- package/dist/src/share/site-share-types.js.map +1 -0
- package/dist/src/share/site-static-serve.d.ts +10 -0
- package/dist/src/share/site-static-serve.js +145 -0
- package/dist/src/share/site-static-serve.js.map +1 -0
- package/dist/src/tui/clipboard-image.js +3 -3
- package/dist/src/tui/theme-manager.js +1 -1
- package/dist/src/tui/tui-commands.js +18 -0
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-keybindings-file.js +1 -1
- package/dist/src/tui/tui-scoped-models.js +2 -2
- package/dist/src/tui/tui-settings.js +1 -1
- package/dist/src/tui/tui-workflow-slash.d.ts +32 -0
- package/dist/src/tui/tui-workflow-slash.js +63 -0
- package/dist/src/tui/tui-workflow-slash.js.map +1 -0
- package/dist/src/tui/tui.js +2 -2
- package/dist/src/tunnel/enable-lan-pairing.js +1 -1
- package/dist/src/tunnel/frpc-binary.js +3 -3
- package/dist/src/tunnel/frpc-config.js +1 -1
- package/dist/src/tunnel/frpc-extract.js +1 -1
- package/dist/src/tunnel/index.js +2 -2
- package/dist/src/tunnel/pair-context.d.ts +7 -1
- package/dist/src/tunnel/pair-context.js +25 -9
- package/dist/src/tunnel/pair-context.js.map +1 -1
- package/dist/src/tunnel/pair-url.d.ts +14 -1
- package/dist/src/tunnel/pair-url.js +14 -1
- package/dist/src/tunnel/pair-url.js.map +1 -1
- package/dist/src/tunnel/tunnel-service.js +2 -2
- package/dist/src/tunnel/tunnel-state.js +1 -1
- package/dist/src/utils/logger/audit.js +1 -1
- package/dist/src/utils/logger/log-store.js +1 -1
- package/dist/src/utils/logger/rotation.js +1 -1
- package/dist/src/voice/tts/audio.js +1 -1
- package/dist/src/voice/tts/providers/edge-speech.js +2 -2
- package/package.json +3 -2
- package/dist/gateway/static/root/assets/agents-CrpYTHJS.js +0 -222
- package/dist/gateway/static/root/assets/apps-page-1mcKh5Rh.js +0 -1
- package/dist/gateway/static/root/assets/button-KafIU8dx.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-zd6QNKPx.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-uRAuhiUo.js +0 -8
- package/dist/gateway/static/root/assets/cron-api-O2Q_ruV6.js +0 -1
- package/dist/gateway/static/root/assets/cron-page-By09AQD-.js +0 -1
- package/dist/gateway/static/root/assets/dist-C57OMHW8.js +0 -48
- package/dist/gateway/static/root/assets/extension-page-C-Ed5ZmP.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-raLux7E7.js +0 -1
- package/dist/gateway/static/root/assets/fetch-2iRFmd3n.js +0 -3
- package/dist/gateway/static/root/assets/heartbeat-config-api-BVl5VHvL.js +0 -1
- package/dist/gateway/static/root/assets/index-BuFldCsB.css +0 -1
- package/dist/gateway/static/root/assets/index-Y-iqo-gL.js +0 -4693
- package/dist/gateway/static/root/assets/logs-page-BdH2n7ZW.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-Vpchzdp-.js +0 -1
- package/dist/gateway/static/root/assets/settings-form-section-Kk1yAGBl.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-KBm0u6Dz.js +0 -3
- package/dist/gateway/static/root/assets/skills-page-BjeXXaOn.js +0 -2
- package/dist/gateway/static/root/assets/theme-store-D01dJt95.js +0 -1
- package/dist/gateway/static/root/assets/utils-DpTxN4AF.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-CwO8Cf01.js +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { init_session_key, parseSessionKey } from "../../../src/routing/session-key.js";
|
|
2
|
+
import { createLogger } from "../../../src/utils/logger/index.js";
|
|
3
|
+
import { init_logger } from "../../../src/utils/logger.js";
|
|
4
|
+
//#region extensions/telegram/src/workflow-progress.ts
|
|
5
|
+
init_session_key();
|
|
6
|
+
init_logger();
|
|
7
|
+
const log = createLogger("TelegramWorkflowProgress");
|
|
8
|
+
/** Telegram bot API: 4096 char hard cap; leave 96 chars of headroom for re-renders. */
|
|
9
|
+
const TELEGRAM_TEXT_MAX = 4e3;
|
|
10
|
+
/** Telegram bot edit rate ≈ 1/s per chat; broker default 5 s leaves ample headroom. */
|
|
11
|
+
const DEFAULT_THROTTLE_MS = 5e3;
|
|
12
|
+
function createTelegramWorkflowProgressCapability(accountManager) {
|
|
13
|
+
return {
|
|
14
|
+
channelId: "telegram",
|
|
15
|
+
supportsEdit: true,
|
|
16
|
+
defaultThrottleMs: DEFAULT_THROTTLE_MS,
|
|
17
|
+
defaultMode: "edit",
|
|
18
|
+
async postProgress(input) {
|
|
19
|
+
const target = resolveTarget(input.sessionKey);
|
|
20
|
+
if (!target) throw new Error(`telegram workflow progress: cannot route sessionKey "${input.sessionKey}"`);
|
|
21
|
+
const bot = accountManager.getBot(target.accountId);
|
|
22
|
+
if (!bot) throw new Error(`telegram workflow progress: no bot for accountId "${target.accountId}" (sessionKey "${input.sessionKey}")`);
|
|
23
|
+
const text = clampForTelegram(input.text);
|
|
24
|
+
if (input.previousMessageId && !input.isFinal) try {
|
|
25
|
+
await bot.api.editMessageText(target.chatId, Number(input.previousMessageId), text);
|
|
26
|
+
return { messageId: input.previousMessageId };
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (isMessageNotModified(err)) return { messageId: input.previousMessageId };
|
|
29
|
+
if (isEditTargetGone(err)) log.debug({
|
|
30
|
+
sessionKey: input.sessionKey,
|
|
31
|
+
previousMessageId: input.previousMessageId
|
|
32
|
+
}, "edit target gone; falling back to sendMessage");
|
|
33
|
+
else throw err;
|
|
34
|
+
}
|
|
35
|
+
const sent = await bot.api.sendMessage(target.chatId, text, {
|
|
36
|
+
message_thread_id: target.threadId ? Number(target.threadId) : void 0,
|
|
37
|
+
disable_notification: !input.isFinal
|
|
38
|
+
});
|
|
39
|
+
return { messageId: String(sent.message_id) };
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function resolveTarget(sessionKey) {
|
|
44
|
+
const parsed = parseSessionKey(sessionKey);
|
|
45
|
+
if (!parsed) return null;
|
|
46
|
+
if (parsed.source !== "telegram") return null;
|
|
47
|
+
if (!parsed.peerId) return null;
|
|
48
|
+
return {
|
|
49
|
+
accountId: parsed.accountId,
|
|
50
|
+
chatId: parsed.peerId,
|
|
51
|
+
threadId: parsed.threadId
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function clampForTelegram(text) {
|
|
55
|
+
if (text.length <= TELEGRAM_TEXT_MAX) return text;
|
|
56
|
+
return `${text.slice(0, TELEGRAM_TEXT_MAX - 1)}…`;
|
|
57
|
+
}
|
|
58
|
+
function isMessageNotModified(err) {
|
|
59
|
+
return errorMessage(err).toLowerCase().includes("message is not modified");
|
|
60
|
+
}
|
|
61
|
+
function isEditTargetGone(err) {
|
|
62
|
+
const msg = errorMessage(err).toLowerCase();
|
|
63
|
+
return msg.includes("message to edit not found") || msg.includes("message can't be edited") || msg.includes("chat not found");
|
|
64
|
+
}
|
|
65
|
+
function errorMessage(err) {
|
|
66
|
+
if (!err) return "";
|
|
67
|
+
if (err instanceof Error) return err.message;
|
|
68
|
+
return String(err);
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { createTelegramWorkflowProgressCapability };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=workflow-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/telegram/src/workflow-progress.ts"],"sourcesContent":["/**\n * Telegram capability for the {@link WorkflowProgressBroker}.\n *\n * Strategy: send the first progress update as a fresh message, then edit it in\n * place on every subsequent update. The broker enforces per-channel throttle\n * (default 5 s) and bypasses it for key events (phase change / new error /\n * tool_end), so users always see milestones promptly without us hammering\n * Telegram's edit rate limit.\n *\n * Failure handling is intentionally narrow:\n * - `message is not modified` from `editMessageText` → swallow (it's a\n * no-op the broker shouldn't retry).\n * - Lost / inaccessible message during edit → fall back to a new send so\n * the user always sees the latest snapshot.\n * - 429 / network → bubble up, broker logs and drops the update; the next\n * scheduled (or key) tick will retry.\n *\n * We deliberately use plain text (no parse_mode) — the snapshot renderer\n * emits ASCII-friendly output and we don't want HTML/markdown surprises with\n * `<` in agent labels or shell-style payloads.\n */\n\nimport type { ChannelProgressCapability, WorkflowProgressPostInput } from '@xopcai/xopc/agent/workflow/index.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport type { TelegramAccountManager } from './account-manager.js';\n\nconst log = createLogger('TelegramWorkflowProgress');\n\n/** Telegram bot API: 4096 char hard cap; leave 96 chars of headroom for re-renders. */\nconst TELEGRAM_TEXT_MAX = 4000;\n\n/** Telegram bot edit rate ≈ 1/s per chat; broker default 5 s leaves ample headroom. */\nconst DEFAULT_THROTTLE_MS = 5_000;\n\nexport function createTelegramWorkflowProgressCapability(\n accountManager: TelegramAccountManager,\n): ChannelProgressCapability {\n return {\n channelId: 'telegram',\n supportsEdit: true,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'edit',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`telegram workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n const bot = accountManager.getBot(target.accountId);\n if (!bot) {\n throw new Error(\n `telegram workflow progress: no bot for accountId \"${target.accountId}\" (sessionKey \"${input.sessionKey}\")`,\n );\n }\n\n const text = clampForTelegram(input.text);\n\n // Edit-in-place when we have a previous message id and we're not on the\n // final send (final could be substantially longer; sending fresh keeps\n // the conclusion visible even when scrolled past the streaming bubble).\n if (input.previousMessageId && !input.isFinal) {\n try {\n await bot.api.editMessageText(\n target.chatId,\n Number(input.previousMessageId),\n text,\n );\n return { messageId: input.previousMessageId };\n } catch (err) {\n if (isMessageNotModified(err)) {\n // Snapshot rendered identically to the last edit (e.g. only\n // running spinner ticked) — silently keep the same message id.\n return { messageId: input.previousMessageId };\n }\n if (isEditTargetGone(err)) {\n log.debug(\n { sessionKey: input.sessionKey, previousMessageId: input.previousMessageId },\n 'edit target gone; falling back to sendMessage',\n );\n // fall through to the send path below\n } else {\n throw err;\n }\n }\n }\n\n const sent = await bot.api.sendMessage(target.chatId, text, {\n message_thread_id: target.threadId ? Number(target.threadId) : undefined,\n disable_notification: !input.isFinal,\n });\n return { messageId: String(sent.message_id) };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n chatId: string;\n threadId?: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'telegram') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId,\n chatId: parsed.peerId,\n threadId: parsed.threadId,\n };\n}\n\nfunction clampForTelegram(text: string): string {\n if (text.length <= TELEGRAM_TEXT_MAX) return text;\n return `${text.slice(0, TELEGRAM_TEXT_MAX - 1)}…`;\n}\n\nfunction isMessageNotModified(err: unknown): boolean {\n const msg = errorMessage(err).toLowerCase();\n return msg.includes('message is not modified');\n}\n\nfunction isEditTargetGone(err: unknown): boolean {\n const msg = errorMessage(err).toLowerCase();\n return (\n msg.includes('message to edit not found') ||\n msg.includes(\"message can't be edited\") ||\n msg.includes('chat not found')\n );\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":";;;;kBAuBsE;aACV;AAI5D,MAAM,MAAM,aAAa,2BAA2B;;AAGpD,MAAM,oBAAoB;;AAG1B,MAAM,sBAAsB;AAE5B,SAAgB,yCACd,gBAC2B;AAC3B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,wDAAwD,MAAM,WAAW,GAAG;GAE9F,MAAM,MAAM,eAAe,OAAO,OAAO,UAAU;AACnD,OAAI,CAAC,IACH,OAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,iBAAiB,MAAM,WAAW,IACzG;GAGH,MAAM,OAAO,iBAAiB,MAAM,KAAK;AAKzC,OAAI,MAAM,qBAAqB,CAAC,MAAM,QACpC,KAAI;AACF,UAAM,IAAI,IAAI,gBACZ,OAAO,QACP,OAAO,MAAM,kBAAkB,EAC/B,KACD;AACD,WAAO,EAAE,WAAW,MAAM,mBAAmB;YACtC,KAAK;AACZ,QAAI,qBAAqB,IAAI,CAG3B,QAAO,EAAE,WAAW,MAAM,mBAAmB;AAE/C,QAAI,iBAAiB,IAAI,CACvB,KAAI,MACF;KAAE,YAAY,MAAM;KAAY,mBAAmB,MAAM;KAAmB,EAC5E,gDACD;QAGD,OAAM;;GAKZ,MAAM,OAAO,MAAM,IAAI,IAAI,YAAY,OAAO,QAAQ,MAAM;IAC1D,mBAAmB,OAAO,WAAW,OAAO,OAAO,SAAS,GAAG,KAAA;IAC/D,sBAAsB,CAAC,MAAM;IAC9B,CAAC;AACF,UAAO,EAAE,WAAW,OAAO,KAAK,WAAW,EAAE;;EAEhD;;AASH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,WAAY,QAAO;AACzC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf,UAAU,OAAO;EAClB;;AAGH,SAAS,iBAAiB,MAAsB;AAC9C,KAAI,KAAK,UAAU,kBAAmB,QAAO;AAC7C,QAAO,GAAG,KAAK,MAAM,GAAG,oBAAoB,EAAE,CAAC;;AAGjD,SAAS,qBAAqB,KAAuB;AAEnD,QADY,aAAa,IAAI,CAAC,aACpB,CAAC,SAAS,0BAA0B;;AAGhD,SAAS,iBAAiB,KAAuB;CAC/C,MAAM,MAAM,aAAa,IAAI,CAAC,aAAa;AAC3C,QACE,IAAI,SAAS,4BAA4B,IACzC,IAAI,SAAS,0BAA0B,IACvC,IAAI,SAAS,iBAAiB;;AAIlC,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "telegram",
|
|
3
3
|
"name": "Telegram Channel",
|
|
4
4
|
"description": "Bundled Telegram Bot channel (private workspace sources; ships inside @xopcai/xopc dist/)",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.85",
|
|
6
6
|
"kind": "channel",
|
|
7
7
|
"main": "src/index.ts",
|
|
8
8
|
"channels": ["telegram"],
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { getContextToken } from "../messaging/inbound.js";
|
|
2
|
+
import { sendMessageWeixin } from "../messaging/send.js";
|
|
3
|
+
import { ensureWeixinContextTokenForOutbound } from "../messaging/context-token-init.js";
|
|
4
|
+
import { createWeixinWorkflowProgressCapability } from "../workflow-progress.js";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
//#region extensions/weixin/src/__tests__/workflow-progress.test.ts
|
|
7
|
+
vi.mock("../auth/accounts.js", () => ({ resolveWeixinAccount: () => ({
|
|
8
|
+
accountId: "default",
|
|
9
|
+
enabled: true,
|
|
10
|
+
configured: true,
|
|
11
|
+
token: "tok-x",
|
|
12
|
+
baseUrl: "https://example/weixin",
|
|
13
|
+
routeTag: void 0
|
|
14
|
+
}) }));
|
|
15
|
+
vi.mock("../messaging/inbound.js", () => ({
|
|
16
|
+
getContextToken: vi.fn(),
|
|
17
|
+
restoreContextTokens: vi.fn()
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("../messaging/context-token-init.js", () => ({ ensureWeixinContextTokenForOutbound: vi.fn() }));
|
|
20
|
+
vi.mock("../messaging/send.js", async () => {
|
|
21
|
+
return {
|
|
22
|
+
...await vi.importActual("../messaging/send.js"),
|
|
23
|
+
sendMessageWeixin: vi.fn().mockResolvedValue({ messageId: "weixin-1" })
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
const getCtxMock = vi.mocked(getContextToken);
|
|
27
|
+
const ensureMock = vi.mocked(ensureWeixinContextTokenForOutbound);
|
|
28
|
+
const sendMock = vi.mocked(sendMessageWeixin);
|
|
29
|
+
const SESSION = "main:weixin:default:dm:ilink_user_abc";
|
|
30
|
+
function mkCap() {
|
|
31
|
+
return createWeixinWorkflowProgressCapability({ getConfig: () => ({}) });
|
|
32
|
+
}
|
|
33
|
+
describe("weixin workflow progress capability", () => {
|
|
34
|
+
it("declares final-only defaults (no editMessage on WeChat)", () => {
|
|
35
|
+
const cap = mkCap();
|
|
36
|
+
expect(cap.channelId).toBe("weixin");
|
|
37
|
+
expect(cap.supportsEdit).toBe(false);
|
|
38
|
+
expect(cap.defaultMode).toBe("final-only");
|
|
39
|
+
expect(cap.defaultThrottleMs).toBeGreaterThanOrEqual(3e4);
|
|
40
|
+
});
|
|
41
|
+
it("sends the final message when a cached context token exists", async () => {
|
|
42
|
+
getCtxMock.mockReturnValue("tok-cached");
|
|
43
|
+
sendMock.mockClear();
|
|
44
|
+
ensureMock.mockClear();
|
|
45
|
+
const r = await mkCap().postProgress({
|
|
46
|
+
sessionKey: SESSION,
|
|
47
|
+
text: "done",
|
|
48
|
+
isFinal: true
|
|
49
|
+
});
|
|
50
|
+
expect(ensureMock).not.toHaveBeenCalled();
|
|
51
|
+
expect(sendMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
52
|
+
to: "ilink_user_abc",
|
|
53
|
+
text: "done",
|
|
54
|
+
opts: expect.objectContaining({ contextToken: "tok-cached" })
|
|
55
|
+
}));
|
|
56
|
+
expect(r.messageId).toBe("weixin-1");
|
|
57
|
+
});
|
|
58
|
+
it("hydrates a context token on demand when cache is empty", async () => {
|
|
59
|
+
getCtxMock.mockReturnValue(void 0);
|
|
60
|
+
ensureMock.mockResolvedValue("tok-hydrated");
|
|
61
|
+
sendMock.mockClear();
|
|
62
|
+
await mkCap().postProgress({
|
|
63
|
+
sessionKey: SESSION,
|
|
64
|
+
text: "done",
|
|
65
|
+
isFinal: true
|
|
66
|
+
});
|
|
67
|
+
expect(ensureMock).toHaveBeenCalled();
|
|
68
|
+
const sendArg = sendMock.mock.calls[0][0];
|
|
69
|
+
expect(sendArg.opts.contextToken).toBe("tok-hydrated");
|
|
70
|
+
});
|
|
71
|
+
it("returns an empty messageId without sending when no context token can be found", async () => {
|
|
72
|
+
getCtxMock.mockReturnValue(void 0);
|
|
73
|
+
ensureMock.mockResolvedValue(void 0);
|
|
74
|
+
sendMock.mockClear();
|
|
75
|
+
const r = await mkCap().postProgress({
|
|
76
|
+
sessionKey: SESSION,
|
|
77
|
+
text: "done",
|
|
78
|
+
isFinal: true
|
|
79
|
+
});
|
|
80
|
+
expect(sendMock).not.toHaveBeenCalled();
|
|
81
|
+
expect(r.messageId).toBe("");
|
|
82
|
+
});
|
|
83
|
+
it("throws on unroutable sessionKey", async () => {
|
|
84
|
+
await expect(mkCap().postProgress({
|
|
85
|
+
sessionKey: "main:telegram:default:dm:123",
|
|
86
|
+
text: "x",
|
|
87
|
+
isFinal: true
|
|
88
|
+
})).rejects.toThrow(/cannot route/);
|
|
89
|
+
});
|
|
90
|
+
it("clamps oversized text to WeChat limit", async () => {
|
|
91
|
+
getCtxMock.mockReturnValue("tok");
|
|
92
|
+
sendMock.mockClear();
|
|
93
|
+
await mkCap().postProgress({
|
|
94
|
+
sessionKey: SESSION,
|
|
95
|
+
text: "x".repeat(1e4),
|
|
96
|
+
isFinal: true
|
|
97
|
+
});
|
|
98
|
+
const text = sendMock.mock.calls[0][0].text;
|
|
99
|
+
expect(text.length).toBeLessThanOrEqual(4e3);
|
|
100
|
+
expect(text.endsWith("…")).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
describe("append-mode message decoration", () => {
|
|
103
|
+
it("prefixes mid-run append messages with \"工作流进展\" header", async () => {
|
|
104
|
+
getCtxMock.mockReturnValue("tok");
|
|
105
|
+
sendMock.mockClear();
|
|
106
|
+
await mkCap().postProgress({
|
|
107
|
+
sessionKey: SESSION,
|
|
108
|
+
text: "Inventory done · Review running",
|
|
109
|
+
isFinal: false,
|
|
110
|
+
mode: "append"
|
|
111
|
+
});
|
|
112
|
+
const text = sendMock.mock.calls[0][0].text;
|
|
113
|
+
expect(text.startsWith("▾ 工作流进展\n")).toBe(true);
|
|
114
|
+
expect(text).toContain("Inventory done · Review running");
|
|
115
|
+
});
|
|
116
|
+
it("prefixes final append message with \"工作流完成\" header", async () => {
|
|
117
|
+
getCtxMock.mockReturnValue("tok");
|
|
118
|
+
sendMock.mockClear();
|
|
119
|
+
await mkCap().postProgress({
|
|
120
|
+
sessionKey: SESSION,
|
|
121
|
+
text: "Top findings (3): …",
|
|
122
|
+
isFinal: true,
|
|
123
|
+
mode: "append"
|
|
124
|
+
});
|
|
125
|
+
const text = sendMock.mock.calls[0][0].text;
|
|
126
|
+
expect(text.startsWith("✓ 工作流完成\n")).toBe(true);
|
|
127
|
+
expect(text).toContain("Top findings (3): …");
|
|
128
|
+
});
|
|
129
|
+
it("does NOT add a header in final-only mode (existing behaviour preserved)", async () => {
|
|
130
|
+
getCtxMock.mockReturnValue("tok");
|
|
131
|
+
sendMock.mockClear();
|
|
132
|
+
await mkCap().postProgress({
|
|
133
|
+
sessionKey: SESSION,
|
|
134
|
+
text: "Top findings (3): …",
|
|
135
|
+
isFinal: true,
|
|
136
|
+
mode: "final-only"
|
|
137
|
+
});
|
|
138
|
+
const text = sendMock.mock.calls[0][0].text;
|
|
139
|
+
expect(text).not.toContain("工作流");
|
|
140
|
+
expect(text).toBe("Top findings (3): …");
|
|
141
|
+
});
|
|
142
|
+
it("does NOT add a header when mode is omitted (back-compat)", async () => {
|
|
143
|
+
getCtxMock.mockReturnValue("tok");
|
|
144
|
+
sendMock.mockClear();
|
|
145
|
+
await mkCap().postProgress({
|
|
146
|
+
sessionKey: SESSION,
|
|
147
|
+
text: "plain",
|
|
148
|
+
isFinal: true
|
|
149
|
+
});
|
|
150
|
+
const text = sendMock.mock.calls[0][0].text;
|
|
151
|
+
expect(text).toBe("plain");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
//#endregion
|
|
156
|
+
export {};
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=workflow-progress.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-progress.test.js","names":[],"sources":["../../../../../extensions/weixin/src/__tests__/workflow-progress.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\nimport { createWeixinWorkflowProgressCapability } from '../workflow-progress.js';\n\nvi.mock('../auth/accounts.js', () => ({\n resolveWeixinAccount: () => ({\n accountId: 'default',\n enabled: true,\n configured: true,\n token: 'tok-x',\n baseUrl: 'https://example/weixin',\n routeTag: undefined,\n }),\n}));\n\nvi.mock('../messaging/inbound.js', () => ({\n getContextToken: vi.fn(),\n restoreContextTokens: vi.fn(),\n}));\n\nvi.mock('../messaging/context-token-init.js', () => ({\n ensureWeixinContextTokenForOutbound: vi.fn(),\n}));\n\nvi.mock('../messaging/send.js', async () => {\n const actual = await vi.importActual<typeof import('../messaging/send.js')>('../messaging/send.js');\n return {\n ...actual,\n sendMessageWeixin: vi.fn().mockResolvedValue({ messageId: 'weixin-1' }),\n };\n});\n\nimport { getContextToken } from '../messaging/inbound.js';\nimport { ensureWeixinContextTokenForOutbound } from '../messaging/context-token-init.js';\nimport { sendMessageWeixin } from '../messaging/send.js';\n\nconst getCtxMock = vi.mocked(getContextToken);\nconst ensureMock = vi.mocked(ensureWeixinContextTokenForOutbound);\nconst sendMock = vi.mocked(sendMessageWeixin);\n\nconst SESSION = 'main:weixin:default:dm:ilink_user_abc';\n\nfunction mkCap() {\n return createWeixinWorkflowProgressCapability({\n getConfig: () => ({}) as never,\n });\n}\n\ndescribe('weixin workflow progress capability', () => {\n it('declares final-only defaults (no editMessage on WeChat)', () => {\n const cap = mkCap();\n expect(cap.channelId).toBe('weixin');\n expect(cap.supportsEdit).toBe(false);\n expect(cap.defaultMode).toBe('final-only');\n expect(cap.defaultThrottleMs).toBeGreaterThanOrEqual(30_000);\n });\n\n it('sends the final message when a cached context token exists', async () => {\n getCtxMock.mockReturnValue('tok-cached');\n sendMock.mockClear();\n ensureMock.mockClear();\n const cap = mkCap();\n const r = await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(ensureMock).not.toHaveBeenCalled();\n expect(sendMock).toHaveBeenCalledWith(\n expect.objectContaining({\n to: 'ilink_user_abc',\n text: 'done',\n opts: expect.objectContaining({ contextToken: 'tok-cached' }),\n }),\n );\n expect(r.messageId).toBe('weixin-1');\n });\n\n it('hydrates a context token on demand when cache is empty', async () => {\n getCtxMock.mockReturnValue(undefined);\n ensureMock.mockResolvedValue('tok-hydrated');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(ensureMock).toHaveBeenCalled();\n const sendArg = sendMock.mock.calls[0][0];\n expect(sendArg.opts.contextToken).toBe('tok-hydrated');\n });\n\n it('returns an empty messageId without sending when no context token can be found', async () => {\n getCtxMock.mockReturnValue(undefined);\n ensureMock.mockResolvedValue(undefined);\n sendMock.mockClear();\n const cap = mkCap();\n const r = await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(sendMock).not.toHaveBeenCalled();\n expect(r.messageId).toBe('');\n });\n\n it('throws on unroutable sessionKey', async () => {\n const cap = mkCap();\n await expect(\n cap.postProgress({\n sessionKey: 'main:telegram:default:dm:123',\n text: 'x',\n isFinal: true,\n }),\n ).rejects.toThrow(/cannot route/);\n });\n\n it('clamps oversized text to WeChat limit', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'x'.repeat(10_000), isFinal: true });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.length).toBeLessThanOrEqual(4_000);\n expect(text.endsWith('…')).toBe(true);\n });\n\n describe('append-mode message decoration', () => {\n it('prefixes mid-run append messages with \"工作流进展\" header', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Inventory done · Review running',\n isFinal: false,\n mode: 'append',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.startsWith('▾ 工作流进展\\n')).toBe(true);\n expect(text).toContain('Inventory done · Review running');\n });\n\n it('prefixes final append message with \"工作流完成\" header', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Top findings (3): …',\n isFinal: true,\n mode: 'append',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.startsWith('✓ 工作流完成\\n')).toBe(true);\n expect(text).toContain('Top findings (3): …');\n });\n\n it('does NOT add a header in final-only mode (existing behaviour preserved)', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Top findings (3): …',\n isFinal: true,\n mode: 'final-only',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text).not.toContain('工作流');\n expect(text).toBe('Top findings (3): …');\n });\n\n it('does NOT add a header when mode is omitted (back-compat)', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'plain', isFinal: true });\n const text = sendMock.mock.calls[0][0].text;\n expect(text).toBe('plain');\n });\n });\n});\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { logger } from "../util/logger.js";
|
|
2
2
|
import { redactBody, redactUrl } from "../util/redact.js";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import fsSync from "node:fs";
|
|
5
3
|
import path from "node:path";
|
|
4
|
+
import fsSync from "node:fs";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { Agent, fetch as fetch$1 } from "undici";
|
|
8
8
|
//#region extensions/weixin/src/api/api.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { logger } from "../util/logger.js";
|
|
2
2
|
import { normalizeWeixinAccountId } from "./weixin-account-id.js";
|
|
3
3
|
import { resolveWeixinRootDir } from "../storage/state-dir.js";
|
|
4
|
-
import fsSync from "node:fs";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import fsSync from "node:fs";
|
|
6
6
|
//#region extensions/weixin/src/auth/accounts.ts
|
|
7
7
|
const DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
8
8
|
const CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
|
|
@@ -6,9 +6,9 @@ import { getUploadUrl } from "../api/api.js";
|
|
|
6
6
|
import { getExtensionFromContentTypeOrUrl } from "../media/mime.js";
|
|
7
7
|
import { aesEcbPaddedSize } from "./aes-ecb.js";
|
|
8
8
|
import { uploadBufferToCdn } from "./cdn-upload.js";
|
|
9
|
+
import path from "node:path";
|
|
9
10
|
import crypto from "node:crypto";
|
|
10
11
|
import fs from "node:fs/promises";
|
|
11
|
-
import path from "node:path";
|
|
12
12
|
//#region extensions/weixin/src/cdn/upload.ts
|
|
13
13
|
/**
|
|
14
14
|
* Download a remote media URL (image, video, file) to a local temp file in destDir.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getExtensionFromMime } from "./mime.js";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { randomBytes } from "node:crypto";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
5
|
//#region extensions/weixin/src/media/data-url.ts
|
|
6
6
|
/**
|
|
7
7
|
* Parse `data:mime;base64,...` into buffer (aligned with Telegram `parseDataUrl`).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logger } from "../util/logger.js";
|
|
2
2
|
import { resolveWeixinRootDir } from "../storage/state-dir.js";
|
|
3
|
-
import fsSync from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import fsSync from "node:fs";
|
|
5
5
|
//#region extensions/weixin/src/messaging/debug-mode.ts
|
|
6
6
|
/**
|
|
7
7
|
* Per-bot debug mode toggle, persisted to disk so it survives gateway restarts.
|
|
@@ -3,8 +3,8 @@ import { generateId } from "../util/random.js";
|
|
|
3
3
|
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
|
-
import fsSync from "node:fs";
|
|
7
6
|
import path from "node:path";
|
|
7
|
+
import fsSync 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
|
|
@@ -11,9 +11,9 @@ import { downloadMediaFromItem } from "../media/media-download.js";
|
|
|
11
11
|
import { isDebugMode } from "./debug-mode.js";
|
|
12
12
|
import { sendMessageWeixin } from "./send.js";
|
|
13
13
|
import { handleSlashCommand } from "./slash-commands.js";
|
|
14
|
+
import path from "node:path";
|
|
14
15
|
import { randomBytes } from "node:crypto";
|
|
15
16
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
-
import path from "node:path";
|
|
17
17
|
//#region extensions/weixin/src/messaging/process-message.ts
|
|
18
18
|
function extractTextBody(itemList) {
|
|
19
19
|
if (!itemList?.length) return "";
|
|
@@ -39,6 +39,8 @@ export declare class WeixinChannelPlugin implements ChannelPlugin<ResolvedWeixin
|
|
|
39
39
|
private bus;
|
|
40
40
|
private cfg;
|
|
41
41
|
private abortControllers;
|
|
42
|
+
/** Unregister fn for the workflow-progress capability registered against the global broker. */
|
|
43
|
+
private workflowProgressUnregister;
|
|
42
44
|
config: {
|
|
43
45
|
listAccountIds: (cfg: Config) => string[];
|
|
44
46
|
resolveAccount: (cfg: Config, accountId?: string | null) => ResolvedWeixinAccount;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { WeixinConfigSchema, init_config_schema } from "./config-schema.js";
|
|
1
2
|
import { createLogger } from "../../../src/utils/logger/index.js";
|
|
2
3
|
import { init_logger } from "../../../src/utils/logger.js";
|
|
3
|
-
import {
|
|
4
|
+
import { getWorkflowProgressBroker } from "../../../src/agent/workflow/progress-broker.js";
|
|
5
|
+
import "../../../src/agent/workflow/index.js";
|
|
4
6
|
import { evaluateAccess, resolveDmPolicy } from "../../../src/channels/security.js";
|
|
5
7
|
import { createStandardPairingAdapter } from "../../../src/channels/pairing/pairing-store-adapter.js";
|
|
6
8
|
import { restoreContextTokens } from "./messaging/inbound.js";
|
|
@@ -11,6 +13,7 @@ import { createWeixinOutboundHandlers, weixinTextChunker } from "./outbound-send
|
|
|
11
13
|
import { normalizeWeixinCronDeliveryToResolved } from "./delivery-to.js";
|
|
12
14
|
import { weixinConfigSurface } from "./config-surface.js";
|
|
13
15
|
import { weixinOnboardAdapter } from "./adapters/onboard-cli.js";
|
|
16
|
+
import { createWeixinWorkflowProgressCapability } from "./workflow-progress.js";
|
|
14
17
|
import { isDeepStrictEqual } from "node:util";
|
|
15
18
|
//#region extensions/weixin/src/plugin.ts
|
|
16
19
|
/**
|
|
@@ -81,6 +84,8 @@ var WeixinChannelPlugin = class {
|
|
|
81
84
|
bus;
|
|
82
85
|
cfg;
|
|
83
86
|
abortControllers = /* @__PURE__ */ new Map();
|
|
87
|
+
/** Unregister fn for the workflow-progress capability registered against the global broker. */
|
|
88
|
+
workflowProgressUnregister = null;
|
|
84
89
|
config = {
|
|
85
90
|
listAccountIds: (cfg) => listWeixinAccountIds(cfg),
|
|
86
91
|
resolveAccount: (cfg, accountId) => resolveWeixinAccount(cfg, accountId),
|
|
@@ -130,6 +135,7 @@ var WeixinChannelPlugin = class {
|
|
|
130
135
|
async init(options) {
|
|
131
136
|
this.bus = options.bus;
|
|
132
137
|
this.cfg = options.config;
|
|
138
|
+
this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }));
|
|
133
139
|
log.debug("Weixin plugin initialized");
|
|
134
140
|
}
|
|
135
141
|
async start(options) {
|
|
@@ -168,6 +174,10 @@ var WeixinChannelPlugin = class {
|
|
|
168
174
|
this.abortControllers.delete(id);
|
|
169
175
|
}
|
|
170
176
|
}
|
|
177
|
+
if (!accountId) {
|
|
178
|
+
this.workflowProgressUnregister?.();
|
|
179
|
+
this.workflowProgressUnregister = null;
|
|
180
|
+
}
|
|
171
181
|
}
|
|
172
182
|
channelIsRunning(cfg) {
|
|
173
183
|
return listWeixinAccountIds(cfg).some((id) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/weixin/src/plugin.ts"],"sourcesContent":["/**\n * Weixin (WeChat ilink) channel — long-poll getUpdates, QR login, direct messages only.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type {\n ChannelCapabilities,\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityContext,\n ChannelStreamingAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport { evaluateAccess, resolveDmPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { restoreContextTokens } from './messaging/inbound.js';\nimport { monitorWeixinProvider } from './monitor/monitor.js';\nimport type { ChannelCliLoginAdapter, ChannelCronDeliveryAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport {\n listWeixinAccountIds,\n resolveWeixinAccount,\n type ResolvedWeixinAccount,\n} from './auth/accounts.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { createWeixinOutboundHandlers, weixinTextChunker } from './outbound-send.js';\nimport { normalizeWeixinCronDeliveryToResolved } from './delivery-to.js';\nimport { weixinConfigSurface } from './config-surface.js';\nimport { WeixinConfigSchema } from './config-schema.js';\nimport { weixinOnboardAdapter } from './adapters/onboard-cli.js';\n\nconst log = createLogger('WeixinPlugin');\n\nexport class WeixinChannelPlugin implements ChannelPlugin<ResolvedWeixinAccount> {\n readonly id = 'weixin' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.weixin'],\n };\n\n readonly meta = {\n id: 'weixin',\n label: 'Weixin',\n selectionLabel: 'Weixin (ilink)',\n docsPath: '/channels/weixin',\n blurb: 'WeChat via Tencent ilink bot API (QR login, direct chat).',\n order: 3,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct'] as ChatType[],\n reactions: false,\n threads: false,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: true,\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = WeixinConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly cronDelivery: ChannelCronDeliveryAdapter = {\n async normalizeDeliveryTarget(to, sessionStore) {\n const { chatId, accountId } = await normalizeWeixinCronDeliveryToResolved(to, sessionStore);\n return { chatId, accountId };\n },\n };\n\n readonly onboard = weixinOnboardAdapter;\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { runWeixinQrLoginCli } = await import('./cli/qr-login.js');\n return runWeixinQrLoginCli({\n configPath: params.configPath,\n verbose: params.verbose,\n timeoutMs: params.timeoutMs,\n account: params.accountId,\n writeConfig: params.writeConfig,\n });\n },\n };\n\n readonly configSurface = weixinConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('weixin');\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 0 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 3000,\n },\n },\n };\n\n private bus!: ChannelPluginInitOptions['bus'];\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n\n config = {\n listAccountIds: (cfg: Config) => listWeixinAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveWeixinAccount(cfg, accountId),\n isConfigured: async (account: ResolvedWeixinAccount) => account.configured,\n describeAccount: (account: ResolvedWeixinAccount, _cfg: Config) => ({\n accountId: account.accountId,\n channelId: 'weixin',\n name: account.name,\n enabled: account.enabled,\n configured: account.configured,\n status: undefined,\n }),\n };\n\n security = {\n resolveDmPolicy: ({ account }: { account: ResolvedWeixinAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedWeixinAccount, _cfg: Config) => {\n const allowFrom = [...(account.allowFrom ?? []), ...readFrameworkAllowFromList(account.accountId)];\n return evaluateAccess({\n context: {\n channel: 'weixin',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: account.dmPolicy,\n allowFrom,\n });\n },\n };\n\n outbound = {\n deliveryMode: 'direct' as const,\n chunker: weixinTextChunker,\n chunkerMode: 'text' as const,\n textChunkLimit: 4000,\n ...createWeixinOutboundHandlers(),\n };\n\n streaming: ChannelStreamingAdapter = {\n startStream: () => null,\n };\n\n /** Channel plugin hints (cron targets, media paths). */\n agentPrompt = {\n augmentSystemPrompt: (): string =>\n [\n 'Weixin (ilink): direct chat only. To send an image or file, use the message tool with action send and set media to a local absolute path or an HTTPS URL; relative paths may fail.',\n 'For cron or scheduled delivery to a Weixin contact, set delivery.to to the user Weixin id (ending in @im.wechat) and delivery.accountId to the bot account id, or outbound may pick the wrong account.',\n 'When using MEDIA: to attach a file, put the MEDIA: line alone on its own line, not inline with other text.',\n ].join('\\n'),\n };\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n log.debug('Weixin plugin initialized');\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const ids = options?.accountId\n ? [options.accountId]\n : listWeixinAccountIds(this.cfg);\n\n for (const accountId of ids) {\n const account = resolveWeixinAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured || !account.token) continue;\n\n if (this.abortControllers.has(accountId)) continue;\n\n restoreContextTokens(account.accountId);\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n void monitorWeixinProvider({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n }).catch((err) => {\n if ((err as { name?: string; message?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Weixin monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Weixin monitor exited with error');\n });\n\n log.info({ accountId }, 'Weixin monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n return listWeixinAccountIds(cfg).some((id) => {\n const a = resolveWeixinAccount(cfg, id);\n return a.configured && a.enabled !== false && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prevWx = this.cfg.channels?.weixin as unknown;\n const nextWx = cfg.channels?.weixin as { enabled?: boolean } | undefined;\n const channelOff = !nextWx || nextWx.enabled !== true;\n\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n return;\n }\n\n this.cfg = cfg;\n\n if (isDeepStrictEqual(prevWx, nextWx) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n\n /**\n * Restart long-poll monitors after credentials were written to disk without a `channels.weixin` JSON\n * delta (e.g. only token files / account index updated). Gateway calls this after QR login completes.\n *\n * Pass `bus` explicitly: if Weixin was disabled at gateway boot, `init()` was skipped and `this.bus` was never set.\n */\n async reloadMonitorsWithConfig(cfg: Config, bus: MessageBus): Promise<void> {\n this.bus = bus;\n this.cfg = cfg;\n await this.stop();\n await this.start();\n }\n}\n\nexport const weixinPlugin = new WeixinChannelPlugin();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/weixin/src/plugin.ts"],"sourcesContent":["/**\n * Weixin (WeChat ilink) channel — long-poll getUpdates, QR login, direct messages only.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type {\n ChannelCapabilities,\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityContext,\n ChannelStreamingAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport { evaluateAccess, resolveDmPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { restoreContextTokens } from './messaging/inbound.js';\nimport { monitorWeixinProvider } from './monitor/monitor.js';\nimport type { ChannelCliLoginAdapter, ChannelCronDeliveryAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport {\n listWeixinAccountIds,\n resolveWeixinAccount,\n type ResolvedWeixinAccount,\n} from './auth/accounts.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { createWeixinOutboundHandlers, weixinTextChunker } from './outbound-send.js';\nimport { normalizeWeixinCronDeliveryToResolved } from './delivery-to.js';\nimport { weixinConfigSurface } from './config-surface.js';\nimport { WeixinConfigSchema } from './config-schema.js';\nimport { weixinOnboardAdapter } from './adapters/onboard-cli.js';\nimport { getWorkflowProgressBroker } from '@xopcai/xopc/agent/workflow/index.js';\nimport { createWeixinWorkflowProgressCapability } from './workflow-progress.js';\n\nconst log = createLogger('WeixinPlugin');\n\nexport class WeixinChannelPlugin implements ChannelPlugin<ResolvedWeixinAccount> {\n readonly id = 'weixin' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.weixin'],\n };\n\n readonly meta = {\n id: 'weixin',\n label: 'Weixin',\n selectionLabel: 'Weixin (ilink)',\n docsPath: '/channels/weixin',\n blurb: 'WeChat via Tencent ilink bot API (QR login, direct chat).',\n order: 3,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct'] as ChatType[],\n reactions: false,\n threads: false,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: true,\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = WeixinConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly cronDelivery: ChannelCronDeliveryAdapter = {\n async normalizeDeliveryTarget(to, sessionStore) {\n const { chatId, accountId } = await normalizeWeixinCronDeliveryToResolved(to, sessionStore);\n return { chatId, accountId };\n },\n };\n\n readonly onboard = weixinOnboardAdapter;\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { runWeixinQrLoginCli } = await import('./cli/qr-login.js');\n return runWeixinQrLoginCli({\n configPath: params.configPath,\n verbose: params.verbose,\n timeoutMs: params.timeoutMs,\n account: params.accountId,\n writeConfig: params.writeConfig,\n });\n },\n };\n\n readonly configSurface = weixinConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('weixin');\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 0 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 3000,\n },\n },\n };\n\n private bus!: ChannelPluginInitOptions['bus'];\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n\n config = {\n listAccountIds: (cfg: Config) => listWeixinAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveWeixinAccount(cfg, accountId),\n isConfigured: async (account: ResolvedWeixinAccount) => account.configured,\n describeAccount: (account: ResolvedWeixinAccount, _cfg: Config) => ({\n accountId: account.accountId,\n channelId: 'weixin',\n name: account.name,\n enabled: account.enabled,\n configured: account.configured,\n status: undefined,\n }),\n };\n\n security = {\n resolveDmPolicy: ({ account }: { account: ResolvedWeixinAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedWeixinAccount, _cfg: Config) => {\n const allowFrom = [...(account.allowFrom ?? []), ...readFrameworkAllowFromList(account.accountId)];\n return evaluateAccess({\n context: {\n channel: 'weixin',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: account.dmPolicy,\n allowFrom,\n });\n },\n };\n\n outbound = {\n deliveryMode: 'direct' as const,\n chunker: weixinTextChunker,\n chunkerMode: 'text' as const,\n textChunkLimit: 4000,\n ...createWeixinOutboundHandlers(),\n };\n\n streaming: ChannelStreamingAdapter = {\n startStream: () => null,\n };\n\n /** Channel plugin hints (cron targets, media paths). */\n agentPrompt = {\n augmentSystemPrompt: (): string =>\n [\n 'Weixin (ilink): direct chat only. To send an image or file, use the message tool with action send and set media to a local absolute path or an HTTPS URL; relative paths may fail.',\n 'For cron or scheduled delivery to a Weixin contact, set delivery.to to the user Weixin id (ending in @im.wechat) and delivery.accountId to the bot account id, or outbound may pick the wrong account.',\n 'When using MEDIA: to attach a file, put the MEDIA: line alone on its own line, not inline with other text.',\n ].join('\\n'),\n };\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n\n // Workflow progress broker capability — WeChat has no editMessage, so the\n // capability runs in `final-only` mode: the broker silently drops every\n // mid-run snapshot and only invokes us once when `tool_end` lands.\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n\n log.debug('Weixin plugin initialized');\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const ids = options?.accountId\n ? [options.accountId]\n : listWeixinAccountIds(this.cfg);\n\n for (const accountId of ids) {\n const account = resolveWeixinAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured || !account.token) continue;\n\n if (this.abortControllers.has(accountId)) continue;\n\n restoreContextTokens(account.accountId);\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n void monitorWeixinProvider({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n }).catch((err) => {\n if ((err as { name?: string; message?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Weixin monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Weixin monitor exited with error');\n });\n\n log.info({ accountId }, 'Weixin monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n // Only unregister the broker capability on a full stop. Per-account stops\n // still leave the cap usable for other accounts.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n return listWeixinAccountIds(cfg).some((id) => {\n const a = resolveWeixinAccount(cfg, id);\n return a.configured && a.enabled !== false && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prevWx = this.cfg.channels?.weixin as unknown;\n const nextWx = cfg.channels?.weixin as { enabled?: boolean } | undefined;\n const channelOff = !nextWx || nextWx.enabled !== true;\n\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n return;\n }\n\n this.cfg = cfg;\n\n if (isDeepStrictEqual(prevWx, nextWx) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n\n /**\n * Restart long-poll monitors after credentials were written to disk without a `channels.weixin` JSON\n * delta (e.g. only token files / account index updated). Gateway calls this after QR login completes.\n *\n * Pass `bus` explicitly: if Weixin was disabled at gateway boot, `init()` was skipped and `this.bus` was never set.\n */\n async reloadMonitorsWithConfig(cfg: Config, bus: MessageBus): Promise<void> {\n this.bus = bus;\n this.cfg = cfg;\n await this.stop();\n await this.start();\n }\n}\n\nexport const weixinPlugin = new WeixinChannelPlugin();\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveWeixinRootDir } from "./state-dir.js";
|
|
2
|
-
import fsSync from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import fsSync from "node:fs";
|
|
4
4
|
//#region extensions/weixin/src/storage/sync-buf.ts
|
|
5
5
|
function resolveAccountsDir() {
|
|
6
6
|
return path.join(resolveWeixinRootDir(), "accounts");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat capability for the workflow progress broker.
|
|
3
|
+
*
|
|
4
|
+
* WeChat (ilink/personal account) does not expose an "edit message" API for
|
|
5
|
+
* bot replies, so live edit-in-place — what Telegram and Feishu do — is
|
|
6
|
+
* structurally impossible. Trying to fake it with a `append` mode would
|
|
7
|
+
* spam the chat with one message per tick, which violates the social
|
|
8
|
+
* properties of the surface ("Don't @ me twenty times").
|
|
9
|
+
*
|
|
10
|
+
* Strategy: **final-only**. The broker silently drops every mid-run update;
|
|
11
|
+
* when `tool_end` fires, the broker calls us once with `isFinal: true` and
|
|
12
|
+
* the full final snapshot rendered as text. The user sees a single
|
|
13
|
+
* tasteful summary at the end.
|
|
14
|
+
*
|
|
15
|
+
* Routing: sessionKey `main:weixin:<accountId>:dm:<ilinkUserId>`. We need a
|
|
16
|
+
* valid `contextToken` for the recipient — which requires the user to have
|
|
17
|
+
* recently messaged the bot (the token is harvested from inbound). If
|
|
18
|
+
* missing, we throw and the broker logs; the run still completes, just
|
|
19
|
+
* without a WeChat notification. This is consistent with how other WeChat
|
|
20
|
+
* outbound paths behave.
|
|
21
|
+
*/
|
|
22
|
+
import type { ChannelProgressCapability } from '@xopcai/xopc/agent/workflow/index.js';
|
|
23
|
+
import type { Config } from '@xopcai/xopc/config/schema.js';
|
|
24
|
+
export declare function createWeixinWorkflowProgressCapability(opts: {
|
|
25
|
+
getConfig: () => Config | undefined;
|
|
26
|
+
}): ChannelProgressCapability;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { init_session_key, parseSessionKey } from "../../../src/routing/session-key.js";
|
|
2
|
+
import { createLogger } from "../../../src/utils/logger/index.js";
|
|
3
|
+
import { init_logger } from "../../../src/utils/logger.js";
|
|
4
|
+
import { getContextToken } from "./messaging/inbound.js";
|
|
5
|
+
import { resolveWeixinAccount } from "./auth/accounts.js";
|
|
6
|
+
import { sendMessageWeixin } from "./messaging/send.js";
|
|
7
|
+
import { ensureWeixinContextTokenForOutbound } from "./messaging/context-token-init.js";
|
|
8
|
+
//#region extensions/weixin/src/workflow-progress.ts
|
|
9
|
+
init_session_key();
|
|
10
|
+
init_logger();
|
|
11
|
+
const log = createLogger("WeixinWorkflowProgress");
|
|
12
|
+
const WEIXIN_TEXT_MAX = 4e3;
|
|
13
|
+
/**
|
|
14
|
+
* Conservative throttle — only enforced when the user overrides the default
|
|
15
|
+
* `final-only` mode to `append`. Set so back-to-back workflows can't spam.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_THROTTLE_MS = 6e4;
|
|
18
|
+
function createWeixinWorkflowProgressCapability(opts) {
|
|
19
|
+
return {
|
|
20
|
+
channelId: "weixin",
|
|
21
|
+
supportsEdit: false,
|
|
22
|
+
defaultThrottleMs: DEFAULT_THROTTLE_MS,
|
|
23
|
+
defaultMode: "final-only",
|
|
24
|
+
async postProgress(input) {
|
|
25
|
+
const cfg = opts.getConfig();
|
|
26
|
+
if (!cfg) throw new Error("weixin workflow progress: no config loaded");
|
|
27
|
+
const target = resolveTarget(input.sessionKey);
|
|
28
|
+
if (!target) throw new Error(`weixin workflow progress: cannot route sessionKey "${input.sessionKey}"`);
|
|
29
|
+
let account;
|
|
30
|
+
try {
|
|
31
|
+
account = resolveWeixinAccount(cfg, target.accountId);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error(`weixin workflow progress: cannot resolve account "${target.accountId}": ${errorMessage(err)}`);
|
|
34
|
+
}
|
|
35
|
+
if (!account.configured || !account.token) throw new Error(`weixin workflow progress: account "${target.accountId}" not configured / logged in`);
|
|
36
|
+
let ctxTok = getContextToken(account.accountId, target.to)?.trim();
|
|
37
|
+
if (!ctxTok) ctxTok = (await ensureWeixinContextTokenForOutbound(account.accountId, target.to, account))?.trim();
|
|
38
|
+
if (!ctxTok) {
|
|
39
|
+
log.debug({
|
|
40
|
+
sessionKey: input.sessionKey,
|
|
41
|
+
accountId: account.accountId
|
|
42
|
+
}, "no context token for recipient; skipping workflow progress send");
|
|
43
|
+
return { messageId: "" };
|
|
44
|
+
}
|
|
45
|
+
const text = clampForWeixin(decorateForWeixin(input.text, input.mode, input.isFinal));
|
|
46
|
+
return { messageId: (await sendMessageWeixin({
|
|
47
|
+
to: target.to,
|
|
48
|
+
text,
|
|
49
|
+
opts: {
|
|
50
|
+
baseUrl: account.baseUrl,
|
|
51
|
+
token: account.token,
|
|
52
|
+
routeTag: account.routeTag,
|
|
53
|
+
contextToken: ctxTok
|
|
54
|
+
}
|
|
55
|
+
})).messageId };
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function resolveTarget(sessionKey) {
|
|
60
|
+
const parsed = parseSessionKey(sessionKey);
|
|
61
|
+
if (!parsed) return null;
|
|
62
|
+
if (parsed.source !== "weixin") return null;
|
|
63
|
+
if (!parsed.peerId) return null;
|
|
64
|
+
return {
|
|
65
|
+
accountId: parsed.accountId || "default",
|
|
66
|
+
to: parsed.peerId
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function clampForWeixin(text) {
|
|
70
|
+
if (text.length <= WEIXIN_TEXT_MAX) return text;
|
|
71
|
+
return `${text.slice(0, WEIXIN_TEXT_MAX - 1)}…`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Add a one-line header on append-mode messages so the user can tell mid-run
|
|
75
|
+
* snapshots apart from the final summary — WeChat has no editMessage, so they
|
|
76
|
+
* pile up as separate messages and look identical without a marker.
|
|
77
|
+
*
|
|
78
|
+
* - `final-only` mode: no header (default; one summary message per run).
|
|
79
|
+
* - `append` mode + mid-run: "▾ 工作流进展" header so the user knows more
|
|
80
|
+
* updates are coming.
|
|
81
|
+
* - `append` mode + final: "✓ 工作流完成" header to mark the conclusion.
|
|
82
|
+
*
|
|
83
|
+
* `edit` mode is never reached on WeChat (`supportsEdit: false`), so we don't
|
|
84
|
+
* branch on it. Unknown / missing mode falls through to no header — safe
|
|
85
|
+
* default for hand-rolled callers and tests.
|
|
86
|
+
*/
|
|
87
|
+
function decorateForWeixin(text, mode, isFinal) {
|
|
88
|
+
if (mode !== "append") return text;
|
|
89
|
+
return `${isFinal ? "✓ 工作流完成" : "▾ 工作流进展"}\n${text}`;
|
|
90
|
+
}
|
|
91
|
+
function errorMessage(err) {
|
|
92
|
+
if (!err) return "";
|
|
93
|
+
if (err instanceof Error) return err.message;
|
|
94
|
+
return String(err);
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { createWeixinWorkflowProgressCapability };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=workflow-progress.js.map
|
|
@@ -0,0 +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>:dm:<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"}
|