@xopcai/xopc 0.0.87 → 0.0.89
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 +8 -1
- package/README.zh-CN.md +8 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-B6PJB07W.js +222 -0
- package/dist/gateway/static/root/assets/apps-page-BOr0B1wv.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-BelUKggl.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-BSHqqCF1.js → channels-status-swr-DaHGkRF1.js} +1 -1
- package/dist/gateway/static/root/assets/cron-api-CjOg-BIj.js +1 -0
- package/dist/gateway/static/root/assets/cron-page-DhoZmZXb.js +1 -0
- package/dist/gateway/static/root/assets/{dist-Cmjp2APP.js → dist-6LecgDx5.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CFa9z_1N.js → extension-debug-page-CtuKJ9tE.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BI8eaTPq.js → extension-page-ykzjOkR5.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-Ce2qrdpO.js +1 -0
- package/dist/gateway/static/root/assets/{fetch-DRqwef_Q.js → fetch-C9FFJjuH.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-BiNHBo2Y.js → field-primitives-BFcrNeTU.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-ZRb8qhuz.js → heartbeat-config-api-CEg4Vr9R.js} +1 -1
- package/dist/gateway/static/root/assets/{index-Cu7bKuUi.js → index-CZfy9oxs.js} +85 -85
- package/dist/gateway/static/root/assets/index-CiN1cQiQ.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-BwWLfqvd.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-DV5WN8uk.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-DiqqVs6m.js → settings-form-section-BqdzA28u.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-CfOBRbPX.js +3 -0
- package/dist/gateway/static/root/assets/{share-preview-page-n1Gprylk.js → share-preview-page-Di5Bzh4g.js} +1 -1
- package/dist/gateway/static/root/assets/skills-page-D0H5Kaxg.js +2 -0
- package/dist/gateway/static/root/assets/{theme-store-CZOh1nT3.js → theme-store-CNqbmTNV.js} +1 -1
- package/dist/gateway/static/root/assets/url-aYn-Rj1C.js +7 -0
- package/dist/gateway/static/root/assets/{utils-CkWBfxs4.js → utils-BWm2tG2w.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-X2UfnHeq.js +1 -0
- package/dist/gateway/static/root/assets/workflows-page-BOPpO3NG.js +27 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +2 -0
- package/dist/src/agent/agent-manager.js +1 -0
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/child-agent-factory.d.ts +15 -0
- package/dist/src/agent/child-agent-factory.js +35 -2
- package/dist/src/agent/child-agent-factory.js.map +1 -1
- package/dist/src/agent/client-error-format.d.ts +20 -0
- package/dist/src/agent/client-error-format.js +97 -0
- package/dist/src/agent/client-error-format.js.map +1 -0
- package/dist/src/agent/embedded/run-turn.js +23 -4
- package/dist/src/agent/embedded/run-turn.js.map +1 -1
- package/dist/src/agent/goals/goal-locale.d.ts +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.js +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
- package/dist/src/agent/orchestration/llm-turn-retry.d.ts +2 -0
- package/dist/src/agent/orchestration/llm-turn-retry.js +9 -1
- package/dist/src/agent/orchestration/llm-turn-retry.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +19 -3
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service/webchat-tts.d.ts +1 -2
- 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.js +2 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/service.types.d.ts +3 -1
- package/dist/src/agent/tools/cronjob-tool.js +2 -1
- package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
- package/dist/src/agent/tools/factory.d.ts +3 -0
- package/dist/src/agent/tools/factory.js +2 -23
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/workflow-tool.d.ts +6 -28
- package/dist/src/agent/tools/workflow-tool.js +61 -213
- package/dist/src/agent/tools/workflow-tool.js.map +1 -1
- package/dist/src/agent/workflow/agent-progress.d.ts +5 -0
- package/dist/src/agent/workflow/agent-progress.js +65 -0
- package/dist/src/agent/workflow/agent-progress.js.map +1 -0
- package/dist/src/agent/workflow/builtins/audit-repo.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/audit-repo.js +14 -0
- package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.js +14 -0
- package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
- package/dist/src/agent/workflow/builtins/implementation-plan.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/implementation-plan.js +175 -0
- package/dist/src/agent/workflow/builtins/implementation-plan.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 +1 -1
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js +14 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.js +14 -0
- package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
- package/dist/src/agent/workflow/builtins/release-check.d.ts +11 -0
- package/dist/src/agent/workflow/builtins/release-check.js +165 -0
- package/dist/src/agent/workflow/builtins/release-check.js.map +1 -0
- package/dist/src/agent/workflow/builtins/research.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/research.js +14 -0
- package/dist/src/agent/workflow/builtins/research.js.map +1 -1
- package/dist/src/agent/workflow/index.d.ts +2 -1
- package/dist/src/agent/workflow/index.js +3 -2
- package/dist/src/agent/workflow/meta-locale.d.ts +12 -0
- package/dist/src/agent/workflow/meta-locale.js +62 -0
- package/dist/src/agent/workflow/meta-locale.js.map +1 -0
- package/dist/src/agent/workflow/parser.js +3 -0
- package/dist/src/agent/workflow/parser.js.map +1 -1
- package/dist/src/agent/workflow/runtime.d.ts +2 -2
- package/dist/src/agent/workflow/runtime.js +21 -14
- package/dist/src/agent/workflow/runtime.js.map +1 -1
- package/dist/src/agent/workflow/snapshot.js +2 -12
- package/dist/src/agent/workflow/snapshot.js.map +1 -1
- package/dist/src/agent/workflow/step-labels.d.ts +8 -0
- package/dist/src/agent/workflow/step-labels.js +48 -0
- package/dist/src/agent/workflow/step-labels.js.map +1 -0
- package/dist/src/agent/workflow/subagent-runner.js +46 -1
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
- package/dist/src/agent/workflow/types.d.ts +74 -1
- package/dist/src/agent/workflow/workflow-child-tools.d.ts +4 -0
- package/dist/src/agent/workflow/workflow-child-tools.js +21 -0
- package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -0
- package/dist/src/auth/credentials.d.ts +19 -2
- package/dist/src/auth/credentials.js +47 -13
- package/dist/src/auth/credentials.js.map +1 -1
- package/dist/src/auth/oauth/types.d.ts +16 -0
- package/dist/src/cli/commands/auth.js +6 -0
- package/dist/src/cli/commands/auth.js.map +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/onboard/model.js +6 -0
- package/dist/src/cli/commands/onboard/model.js.map +1 -1
- package/dist/src/config/agent-typed-models.d.ts +18 -0
- package/dist/src/config/agent-typed-models.js +53 -0
- package/dist/src/config/agent-typed-models.js.map +1 -0
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/schema.d.ts +52 -0
- package/dist/src/config/schema.js +39 -3
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/voice.d.ts +3 -28
- package/dist/src/config/voice.js +27 -261
- package/dist/src/config/voice.js.map +1 -1
- package/dist/src/cron/executor.d.ts +2 -0
- package/dist/src/cron/executor.js +59 -5
- package/dist/src/cron/executor.js.map +1 -1
- package/dist/src/cron/job-content.js +2 -1
- package/dist/src/cron/job-content.js.map +1 -1
- package/dist/src/cron/types.d.ts +21 -1
- package/dist/src/cron/validation.d.ts +76 -0
- package/dist/src/cron/validation.js +26 -1
- package/dist/src/cron/validation.js.map +1 -1
- package/dist/src/gateway/agents-admin.d.ts +9 -0
- package/dist/src/gateway/agents-admin.js +16 -0
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/config-tools-web.js +3 -2
- package/dist/src/gateway/config-tools-web.js.map +1 -1
- package/dist/src/gateway/gateway-workflow-host.types.d.ts +17 -0
- package/dist/src/gateway/gateway-workflow-host.types.js +1 -0
- package/dist/src/gateway/hono/lib/agent-model.d.ts +7 -0
- package/dist/src/gateway/hono/lib/agent-model.js +36 -1
- package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +28 -5
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/lib/mask-secret-length.d.ts +6 -0
- package/dist/src/gateway/hono/lib/mask-secret-length.js +16 -0
- package/dist/src/gateway/hono/lib/mask-secret-length.js.map +1 -0
- package/dist/src/gateway/hono/lib/safe-providers-config.d.ts +1 -1
- package/dist/src/gateway/hono/lib/safe-providers-config.js +2 -1
- package/dist/src/gateway/hono/lib/safe-providers-config.js.map +1 -1
- package/dist/src/gateway/hono/lib/safe-voice-config.js +2 -1
- package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
- package/dist/src/gateway/hono/oauth-async.js +40 -15
- package/dist/src/gateway/hono/oauth-async.js.map +1 -1
- package/dist/src/gateway/hono/oauth.js +31 -6
- package/dist/src/gateway/hono/oauth.js.map +1 -1
- package/dist/src/gateway/hono/routes/agents.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/agents.js +8 -2
- package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/gateway.js +3 -2
- package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +7 -2
- package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +59 -0
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- 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 +84 -15
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/voice.js +75 -0
- package/dist/src/gateway/hono/routes/voice.js.map +1 -1
- package/dist/src/gateway/hono/routes/workflows.d.ts +3 -0
- package/dist/src/gateway/hono/routes/workflows.js +226 -0
- package/dist/src/gateway/hono/routes/workflows.js.map +1 -0
- package/dist/src/gateway/service/run-gateway-agent.js +2 -20
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service.d.ts +8 -0
- package/dist/src/gateway/service.js +28 -2
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/providers/index.d.ts +8 -0
- package/dist/src/providers/index.js +51 -12
- package/dist/src/providers/index.js.map +1 -1
- package/dist/src/share/site-share-config.d.ts +3 -2
- package/dist/src/share/site-share-config.js.map +1 -1
- package/dist/src/tui/tui-agent-events.js +2 -1
- package/dist/src/tui/tui-agent-events.js.map +1 -1
- package/dist/src/voice/metadata/builtin.d.ts +2 -0
- package/dist/src/voice/metadata/builtin.js +420 -0
- package/dist/src/voice/metadata/builtin.js.map +1 -0
- package/dist/src/voice/metadata/index.d.ts +4 -0
- package/dist/src/voice/metadata/index.js +3 -0
- package/dist/src/voice/metadata/registry.d.ts +5 -0
- package/dist/src/voice/metadata/registry.js +34 -0
- package/dist/src/voice/metadata/registry.js.map +1 -0
- package/dist/src/voice/metadata/types.d.ts +41 -0
- package/dist/src/voice/metadata/types.js +1 -0
- package/dist/src/voice/stt/list-providers.d.ts +3 -3
- package/dist/src/voice/stt/list-providers.js +41 -6
- package/dist/src/voice/stt/list-providers.js.map +1 -1
- package/dist/src/voice/tts/list-providers.d.ts +3 -3
- package/dist/src/voice/tts/list-providers.js +41 -6
- package/dist/src/voice/tts/list-providers.js.map +1 -1
- package/dist/src/workflows/domain/command.d.ts +19 -0
- package/dist/src/workflows/domain/command.js +1 -0
- package/dist/src/workflows/domain/definition-utils.d.ts +14 -0
- package/dist/src/workflows/domain/definition-utils.js +50 -0
- package/dist/src/workflows/domain/definition-utils.js.map +1 -0
- package/dist/src/workflows/domain/definition.d.ts +62 -0
- package/dist/src/workflows/domain/definition.js +1 -0
- package/dist/src/workflows/domain/event.d.ts +67 -0
- package/dist/src/workflows/domain/event.js +1 -0
- package/dist/src/workflows/domain/index.d.ts +7 -0
- package/dist/src/workflows/domain/index.js +4 -0
- package/dist/src/workflows/domain/result.d.ts +65 -0
- package/dist/src/workflows/domain/result.js +1 -0
- package/dist/src/workflows/domain/run.d.ts +177 -0
- package/dist/src/workflows/domain/run.js +14 -0
- package/dist/src/workflows/domain/run.js.map +1 -0
- package/dist/src/workflows/domain/validation.d.ts +19 -0
- package/dist/src/workflows/domain/validation.js +66 -0
- package/dist/src/workflows/domain/validation.js.map +1 -0
- package/dist/src/workflows/engine/index.d.ts +2 -0
- package/dist/src/workflows/engine/index.js +3 -0
- package/dist/src/workflows/engine/projector.d.ts +3 -0
- package/dist/src/workflows/engine/projector.js +205 -0
- package/dist/src/workflows/engine/projector.js.map +1 -0
- package/dist/src/workflows/engine/workflow-engine.d.ts +32 -0
- package/dist/src/workflows/engine/workflow-engine.js +189 -0
- package/dist/src/workflows/engine/workflow-engine.js.map +1 -0
- package/dist/src/workflows/index.d.ts +10 -0
- package/dist/src/workflows/index.js +18 -0
- package/dist/src/workflows/runtime/index.d.ts +1 -0
- package/dist/src/workflows/runtime/index.js +4 -0
- package/dist/src/workflows/runtime/script-runtime.d.ts +3 -0
- package/dist/src/workflows/runtime/script-runtime.js +3 -0
- package/dist/src/workflows/service/run-view-to-snapshot.d.ts +4 -0
- package/dist/src/workflows/service/run-view-to-snapshot.js +61 -0
- package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -0
- package/dist/src/workflows/service/workflow-run-service.d.ts +36 -0
- package/dist/src/workflows/service/workflow-run-service.js +279 -0
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -0
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +47 -0
- package/dist/src/workflows/service/workflow-run-service.types.js +1 -0
- package/dist/src/workflows/service/workflow-session-bridge.d.ts +29 -0
- package/dist/src/workflows/service/workflow-session-bridge.js +177 -0
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -0
- package/dist/src/workflows/service/workflow-session-key.d.ts +3 -0
- package/dist/src/workflows/service/workflow-session-key.js +21 -0
- package/dist/src/workflows/service/workflow-session-key.js.map +1 -0
- package/dist/src/workflows/store/event-store.d.ts +17 -0
- package/dist/src/workflows/store/event-store.js +83 -0
- package/dist/src/workflows/store/event-store.js.map +1 -0
- package/dist/src/workflows/store/paths.d.ts +7 -0
- package/dist/src/workflows/store/paths.js +26 -0
- package/dist/src/workflows/store/paths.js.map +1 -0
- package/dist/src/workflows/store/run-store.d.ts +13 -0
- package/dist/src/workflows/store/run-store.js +69 -0
- package/dist/src/workflows/store/run-store.js.map +1 -0
- package/package.json +5 -5
- package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +0 -222
- package/dist/gateway/static/root/assets/apps-page-Dg8R-Szf.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-yohw9YSu.js +0 -1
- package/dist/gateway/static/root/assets/cron-api-0h_QT8U3.js +0 -1
- package/dist/gateway/static/root/assets/cron-page-BkfKFfFk.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +0 -1
- package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-BFZ8GgCv.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-CD7AfB-2.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +0 -3
- package/dist/gateway/static/root/assets/skills-page-CcN_gj--.js +0 -2
- package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +0 -3
- package/dist/gateway/static/root/assets/voice-api-key-field-O6awz9hi.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.js","names":[],"sources":["../../../../../src/gateway/hono/routes/voice.ts"],"sourcesContent":["/**\n * Voice routes — POST /api/voice/transcribe\n *\n * Single endpoint that:\n * 1. Runs STT (Whisper preferred for low latency, Alibaba fallback)\n * 2. Optionally runs LLM refine on the raw transcript\n * 3. Returns { raw, refined?, language }\n *\n * LLM refine is auto-applied when a model is resolvable; gracefully degrades\n * to raw-only when no LLM is configured.\n */\n\nimport type { Hono } from 'hono';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport { isSTTAvailable, transcribe } from '../../../voice/stt/index.js';\nimport { listTtsProvidersForApi } from '../../../voice/tts/list-providers.js';\nimport { listSttProvidersForApi } from '../../../voice/stt/list-providers.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { resolveSttProviderConfigSlice } from '../../../voice/stt/config-slice.js';\nimport { resolveTtsProviderConfigSlice } from '../../../voice/tts/config-slice.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createLogger('Gateway:Voice');\n\nfunction readVoiceApiKeyFromConfigFileOnly(\n cfg: Config,\n kind: 'stt' | 'tts',\n providerId: string,\n): string | undefined {\n const id = providerId.trim();\n if (!id) return undefined;\n const slice =\n kind === 'stt'\n ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio)\n : resolveTtsProviderConfigSlice(id, cfg.messages?.tts);\n const key = slice.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\nconst REFINE_TIMEOUT_MS = 15_000;\nconst MAX_AUDIO_BYTES = 25 * 1024 * 1024; // 25 MB\n\nconst REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。\n\n规则:\n1. 修正明显的语音识别错误\n2. 添加正确的标点符号\n3. 去除口语赘词(嗯、啊、那个、就是说、然后就是)\n4. 保持原意不变,不要扩写或改变语义\n5. 如果原文已经很好,原样输出\n6. 只输出整理后的文字,不要解释`;\n\nfunction resolveRefineModel(config: Config | undefined): ReturnType<typeof resolveModel> | null {\n const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();\n if (envRef) {\n try {\n return resolveModel(envRef);\n } catch { /* fall through */ }\n }\n for (const candidate of ['openai/gpt-4o-mini', 'google/gemini-2.0-flash']) {\n try {\n return resolveModel(candidate);\n } catch { /* next */ }\n }\n try {\n return resolveModel(getDefaultModelSync(config));\n } catch {\n return null;\n }\n}\n\nasync function refineTranscript(\n raw: string,\n config: Config | undefined,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n if (!raw.trim()) return undefined;\n\n const model = resolveRefineModel(config);\n if (!model) {\n log.debug('No LLM model available for voice refine; returning raw only');\n return undefined;\n }\n\n try {\n const userMsg: UserMessage = {\n role: 'user',\n content: `${REFINE_SYSTEM_PROMPT}\\n\\n原文:${raw}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(REFINE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(raw.length * 3, 4096),\n temperature: 0.2,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const refined = out.trim();\n if (!refined || refined === raw.trim()) return undefined;\n return refined;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: msg }, 'Voice refine failed; returning raw only');\n return undefined;\n }\n}\n\nexport function registerVoiceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n /**\n * GET /api/voice/providers\n *\n * Lists registered SpeechProviderPlugin ids with configured state for the\n * current gateway config (OpenClaw `tts.providers` equivalent).\n */\n authenticated.get('/api/voice/providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listTtsProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/stt-providers\n *\n * Lists registered MediaUnderstandingProvider ids with configured state for\n * the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).\n */\n authenticated.get('/api/voice/stt-providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listSttProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.\n * Body: `{ kind: 'stt' | 'tts', provider: string }`\n */\n authenticated.post('/api/voice/reveal-api-key', strictRateLimitMiddleware, async (c) => {\n let body: { kind?: unknown; provider?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n const kind = body.kind === 'stt' || body.kind === 'tts' ? body.kind : null;\n const provider = typeof body.provider === 'string' ? body.provider.trim() : '';\n if (!kind) {\n return c.json({ ok: false, error: { message: 'kind must be \"stt\" or \"tts\"' } }, 400);\n }\n if (!provider) {\n return c.json({ ok: false, error: { message: 'provider is required' } }, 400);\n }\n\n const cfg = service.currentConfig as Config;\n const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);\n return c.json({\n ok: true,\n payload: {\n kind,\n provider,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n });\n\n /**\n * POST /api/voice/transcribe\n *\n * Body: { audio: string (base64), mimeType: string, language?: string }\n * Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }\n */\n authenticated.post('/api/voice/transcribe', async (c) => {\n let body: { audio?: string; mimeType?: string; language?: string } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const { audio, mimeType, language } = body;\n if (!audio || typeof audio !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: audio (base64)' } }, 400);\n }\n if (!mimeType || typeof mimeType !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: mimeType' } }, 400);\n }\n\n // Decode base64 audio\n let audioBuffer: Buffer;\n try {\n audioBuffer = Buffer.from(audio, 'base64');\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid base64 audio data' } }, 400);\n }\n\n if (audioBuffer.length === 0) {\n return c.json({ ok: false, error: { message: 'Empty audio data' } }, 400);\n }\n if (audioBuffer.length > MAX_AUDIO_BYTES) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 400);\n }\n\n // Resolve STT config from app config\n const config = service.currentConfig as Config;\n const sttConfigRaw = config.tools?.media?.audio;\n const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);\n\n if (!isSTTAvailable(sttConfig)) {\n return c.json({\n ok: false,\n error: { message: 'STT is not configured. Enable STT in gateway config (tools.media.audio).' },\n }, 503);\n }\n\n // Run STT\n let raw: string;\n let detectedLanguage: string | undefined;\n try {\n const result = await transcribe(audioBuffer, sttConfig, {\n language: language || (sttConfig.provider === 'alibaba' ? 'zh' : undefined),\n });\n raw = result.text;\n detectedLanguage = result.language ?? language;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg }, 'Voice transcription failed');\n return c.json({ ok: false, error: { message: `Transcription failed: ${msg}` } }, 502);\n }\n\n if (!raw.trim()) {\n return c.json({\n ok: true,\n payload: { raw: '', language: detectedLanguage },\n });\n }\n\n // Run LLM refine (auto, best-effort)\n const refined = await refineTranscript(raw, config);\n\n return c.json({\n ok: true,\n payload: {\n raw,\n ...(refined ? { refined } : {}),\n ...(detectedLanguage ? { language: detectedLanguage } : {}),\n },\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"voice.js","names":[],"sources":["../../../../../src/gateway/hono/routes/voice.ts"],"sourcesContent":["/**\n * Voice routes — POST /api/voice/transcribe\n *\n * Single endpoint that:\n * 1. Runs STT (Whisper preferred for low latency, Alibaba fallback)\n * 2. Optionally runs LLM refine on the raw transcript\n * 3. Returns { raw, refined?, language }\n *\n * LLM refine is auto-applied when a model is resolvable; gracefully degrades\n * to raw-only when no LLM is configured.\n */\n\nimport type { Hono } from 'hono';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../../providers/index.js';\nimport { isSTTAvailable, transcribe } from '../../../voice/stt/index.js';\nimport { isTTSAvailable, mergeTtsConfigFromAppConfig, speak } from '../../../voice/tts/index.js';\nimport { listTtsProvidersForApi } from '../../../voice/tts/list-providers.js';\nimport { listSttProvidersForApi } from '../../../voice/stt/list-providers.js';\nimport { mergeSttConfigFromAppConfig } from '../../../channels/attachments/voice-stt-webchat.js';\nimport { resolveSttProviderConfigSlice } from '../../../voice/stt/config-slice.js';\nimport { resolveTtsProviderConfigSlice } from '../../../voice/tts/config-slice.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst log = createLogger('Gateway:Voice');\n\nfunction readVoiceApiKeyFromConfigFileOnly(\n cfg: Config,\n kind: 'stt' | 'tts',\n providerId: string,\n): string | undefined {\n const id = providerId.trim();\n if (!id) return undefined;\n const slice =\n kind === 'stt'\n ? resolveSttProviderConfigSlice(id, cfg.tools?.media?.audio)\n : resolveTtsProviderConfigSlice(id, cfg.messages?.tts);\n const key = slice.apiKey;\n return typeof key === 'string' && key.trim() ? key.trim() : undefined;\n}\n\nconst REFINE_TIMEOUT_MS = 15_000;\nconst MAX_AUDIO_BYTES = 25 * 1024 * 1024; // 25 MB\n\nconst REFINE_SYSTEM_PROMPT = `你是语音转文字后处理助手。将语音转写原文整理为高质量文本输入。\n\n规则:\n1. 修正明显的语音识别错误\n2. 添加正确的标点符号\n3. 去除口语赘词(嗯、啊、那个、就是说、然后就是)\n4. 保持原意不变,不要扩写或改变语义\n5. 如果原文已经很好,原样输出\n6. 只输出整理后的文字,不要解释`;\n\nfunction resolveRefineModel(config: Config | undefined): ReturnType<typeof resolveModel> | null {\n const envRef = process.env.XOPC_VOICE_REFINE_MODEL?.trim();\n if (envRef) {\n try {\n return resolveModel(envRef);\n } catch { /* fall through */ }\n }\n for (const candidate of ['openai/gpt-4o-mini', 'google/gemini-2.0-flash']) {\n try {\n return resolveModel(candidate);\n } catch { /* next */ }\n }\n try {\n return resolveModel(getDefaultModelSync(config));\n } catch {\n return null;\n }\n}\n\nasync function refineTranscript(\n raw: string,\n config: Config | undefined,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n if (!raw.trim()) return undefined;\n\n const model = resolveRefineModel(config);\n if (!model) {\n log.debug('No LLM model available for voice refine; returning raw only');\n return undefined;\n }\n\n try {\n const userMsg: UserMessage = {\n role: 'user',\n content: `${REFINE_SYSTEM_PROMPT}\\n\\n原文:${raw}`,\n timestamp: Date.now(),\n };\n\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(REFINE_TIMEOUT_MS)\n : undefined;\n const mergedSignal =\n signal && timeoutSignal && typeof AbortSignal.any === 'function'\n ? AbortSignal.any([signal, timeoutSignal])\n : signal ?? timeoutSignal;\n\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: Math.min(raw.length * 3, 4096),\n temperature: 0.2,\n signal: mergedSignal as AbortSignal,\n },\n );\n\n let out = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n out += String((c as { text?: string }).text || '');\n }\n }\n }\n\n const refined = out.trim();\n if (!refined || refined === raw.trim()) return undefined;\n return refined;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.warn({ errorMessage: msg }, 'Voice refine failed; returning raw only');\n return undefined;\n }\n}\n\nexport function registerVoiceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n /**\n * GET /api/voice/providers\n *\n * Lists registered SpeechProviderPlugin ids with configured state for the\n * current gateway config (OpenClaw `tts.providers` equivalent).\n */\n authenticated.get('/api/voice/providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listTtsProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * GET /api/voice/stt-providers\n *\n * Lists registered MediaUnderstandingProvider ids with configured state for\n * the current gateway config (OpenClaw `tools.media.audio.providers` equivalent).\n */\n authenticated.get('/api/voice/stt-providers', (c) => {\n const config = service.currentConfig as Config;\n const payload = listSttProvidersForApi(config);\n return c.json({ ok: true, payload });\n });\n\n /**\n * POST /api/voice/reveal-api-key — return plaintext voice provider apiKey from config file only.\n * Body: `{ kind: 'stt' | 'tts', provider: string }`\n */\n authenticated.post('/api/voice/reveal-api-key', strictRateLimitMiddleware, async (c) => {\n let body: { kind?: unknown; provider?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n const kind = body.kind === 'stt' || body.kind === 'tts' ? body.kind : null;\n const provider = typeof body.provider === 'string' ? body.provider.trim() : '';\n if (!kind) {\n return c.json({ ok: false, error: { message: 'kind must be \"stt\" or \"tts\"' } }, 400);\n }\n if (!provider) {\n return c.json({ ok: false, error: { message: 'provider is required' } }, 400);\n }\n\n const cfg = service.currentConfig as Config;\n const apiKey = readVoiceApiKeyFromConfigFileOnly(cfg, kind, provider);\n return c.json({\n ok: true,\n payload: {\n kind,\n provider,\n apiKey: apiKey ?? null,\n source: apiKey ? ('config' as const) : ('none' as const),\n },\n });\n });\n\n /**\n * POST /api/voice/tts-test\n *\n * Body: { text: string, provider?: string, model?: string, voice?: string }\n * Response: { ok: true, payload: { audio: string, format: string, provider: string } }\n */\n authenticated.post('/api/voice/tts-test', strictRateLimitMiddleware, async (c) => {\n let body: { text?: unknown; provider?: unknown; model?: unknown; voice?: unknown } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ ok: false, error: { message: 'text is required' } }, 400);\n }\n if (text.length > 1000) {\n return c.json({ ok: false, error: { message: 'text exceeds 1000 characters' } }, 400);\n }\n\n const config = service.currentConfig as Config;\n const baseTtsConfig = mergeTtsConfigFromAppConfig(config.messages?.tts);\n const provider = typeof body.provider === 'string' && body.provider.trim() ? body.provider.trim() : baseTtsConfig.provider;\n const ttsConfig = {\n ...baseTtsConfig,\n enabled: true,\n provider,\n fallback: { enabled: false, order: [provider] },\n };\n\n if (!isTTSAvailable(ttsConfig)) {\n return c.json({\n ok: false,\n error: { message: `TTS provider \"${provider}\" is not configured.` },\n }, 503);\n }\n\n try {\n const result = await speak(text, ttsConfig, {\n appConfig: config,\n parseDirectives: false,\n tts: {\n ...(typeof body.model === 'string' && body.model.trim() ? { model: body.model.trim() } : {}),\n ...(typeof body.voice === 'string' && body.voice.trim() ? { voice: body.voice.trim() } : {}),\n },\n });\n return c.json({\n ok: true,\n payload: {\n audio: result.audio.toString('base64'),\n format: result.format,\n provider: result.provider,\n ...(result.duration !== undefined ? { duration: result.duration } : {}),\n },\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg, provider }, 'Voice TTS test failed');\n return c.json({ ok: false, error: { message: `TTS test failed: ${msg}` } }, 502);\n }\n });\n\n /**\n * POST /api/voice/transcribe\n *\n * Body: { audio: string (base64), mimeType: string, language?: string }\n * Response: { ok: true, payload: { raw: string, refined?: string, language?: string } }\n */\n authenticated.post('/api/voice/transcribe', async (c) => {\n let body: { audio?: string; mimeType?: string; language?: string } = {};\n try {\n body = (await c.req.json()) as typeof body;\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON body' } }, 400);\n }\n\n const { audio, mimeType, language } = body;\n if (!audio || typeof audio !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: audio (base64)' } }, 400);\n }\n if (!mimeType || typeof mimeType !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing required field: mimeType' } }, 400);\n }\n\n // Decode base64 audio\n let audioBuffer: Buffer;\n try {\n audioBuffer = Buffer.from(audio, 'base64');\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid base64 audio data' } }, 400);\n }\n\n if (audioBuffer.length === 0) {\n return c.json({ ok: false, error: { message: 'Empty audio data' } }, 400);\n }\n if (audioBuffer.length > MAX_AUDIO_BYTES) {\n return c.json({ ok: false, error: { message: 'Audio data exceeds 25 MB limit' } }, 400);\n }\n\n // Resolve STT config from app config\n const config = service.currentConfig as Config;\n const sttConfigRaw = config.tools?.media?.audio;\n const sttConfig = mergeSttConfigFromAppConfig(sttConfigRaw, config.tools?.media);\n\n if (!isSTTAvailable(sttConfig)) {\n return c.json({\n ok: false,\n error: { message: 'STT is not configured. Enable STT in gateway config (tools.media.audio).' },\n }, 503);\n }\n\n // Run STT\n let raw: string;\n let detectedLanguage: string | undefined;\n try {\n const result = await transcribe(audioBuffer, sttConfig, {\n language: language || (sttConfig.provider === 'alibaba' ? 'zh' : undefined),\n });\n raw = result.text;\n detectedLanguage = result.language ?? language;\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n log.error({ errorMessage: msg }, 'Voice transcription failed');\n return c.json({ ok: false, error: { message: `Transcription failed: ${msg}` } }, 502);\n }\n\n if (!raw.trim()) {\n return c.json({\n ok: true,\n payload: { raw: '', language: detectedLanguage },\n });\n }\n\n // Run LLM refine (auto, best-effort)\n const refined = await refineTranscript(raw, config);\n\n return c.json({\n ok: true,\n payload: {\n raw,\n ...(refined ? { refined } : {}),\n ...(detectedLanguage ? { language: detectedLanguage } : {}),\n },\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;gBAgBgF;aAQxB;AAGxD,MAAM,MAAM,aAAa,gBAAgB;AAEzC,SAAS,kCACP,KACA,MACA,YACoB;CACpB,MAAM,KAAK,WAAW,MAAM;AAC5B,KAAI,CAAC,GAAI,QAAO,KAAA;CAKhB,MAAM,OAHJ,SAAS,QACL,8BAA8B,IAAI,IAAI,OAAO,OAAO,MAAM,GAC1D,8BAA8B,IAAI,IAAI,UAAU,IAAI,EACxC;AAClB,QAAO,OAAO,QAAQ,YAAY,IAAI,MAAM,GAAG,IAAI,MAAM,GAAG,KAAA;;AAG9D,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB,KAAK,OAAO;AAEpC,MAAM,uBAAuB;;;;;;;;;AAU7B,SAAS,mBAAmB,QAAoE;CAC9F,MAAM,SAAS,QAAQ,IAAI,yBAAyB,MAAM;AAC1D,KAAI,OACF,KAAI;AACF,SAAO,aAAa,OAAO;SACrB;AAEV,MAAK,MAAM,aAAa,CAAC,sBAAsB,0BAA0B,CACvE,KAAI;AACF,SAAO,aAAa,UAAU;SACxB;AAEV,KAAI;AACF,SAAO,aAAa,oBAAoB,OAAO,CAAC;SAC1C;AACN,SAAO;;;AAIX,eAAe,iBACb,KACA,QACA,QAC6B;AAC7B,KAAI,CAAC,IAAI,MAAM,CAAE,QAAO,KAAA;CAExB,MAAM,QAAQ,mBAAmB,OAAO;AACxC,KAAI,CAAC,OAAO;AACV,MAAI,MAAM,8DAA8D;AACxE;;AAGF,KAAI;EACF,MAAM,UAAuB;GAC3B,MAAM;GACN,SAAS,GAAG,qBAAqB,SAAS;GAC1C,WAAW,KAAK,KAAK;GACtB;EAED,MAAM,gBACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,YAAY,aACjE,YAAY,QAAQ,kBAAkB,GACtC,KAAA;EACN,MAAM,eACJ,UAAU,iBAAiB,OAAO,YAAY,QAAQ,aAClD,YAAY,IAAI,CAAC,QAAQ,cAAc,CAAC,GACxC,UAAU;EAEhB,MAAM,SAAS,MAAM,SACnB,OACA,EAAE,UAAU,CAAC,QAAQ,EAAE,EACvB;GACE,WAAW,KAAK,IAAI,IAAI,SAAS,GAAG,KAAK;GACzC,aAAa;GACb,QAAQ;GACT,CACF;EAED,IAAI,MAAM;AACV,MAAI,MAAM,QAAQ,OAAO,QAAQ;QAC1B,MAAM,KAAK,OAAO,QACrB,KAAI,KAAK,OAAO,MAAM,YAAa,EAAwB,SAAS,OAClE,QAAO,OAAQ,EAAwB,QAAQ,GAAG;;EAKxD,MAAM,UAAU,IAAI,MAAM;AAC1B,MAAI,CAAC,WAAW,YAAY,IAAI,MAAM,CAAE,QAAO,KAAA;AAC/C,SAAO;UACA,OAAO;EACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,MAAI,KAAK,EAAE,cAAc,KAAK,EAAE,0CAA0C;AAC1E;;;AAIJ,SAAgB,oBAAoB,eAAqB,MAAoC;CAC3F,MAAM,EAAE,SAAS,8BAA8B;;;;;;;AAQ/C,eAAc,IAAI,yBAAyB,MAAM;EAC/C,MAAM,SAAS,QAAQ;EACvB,MAAM,UAAU,uBAAuB,OAAO;AAC9C,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM;GAAS,CAAC;GACpC;;;;;;;AAQF,eAAc,IAAI,6BAA6B,MAAM;EACnD,MAAM,SAAS,QAAQ;EACvB,MAAM,UAAU,uBAAuB,OAAO;AAC9C,SAAO,EAAE,KAAK;GAAE,IAAI;GAAM;GAAS,CAAC;GACpC;;;;;AAMF,eAAc,KAAK,6BAA6B,2BAA2B,OAAO,MAAM;EACtF,IAAI,OAA+C,EAAE;AACrD,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;EAE5E,MAAM,OAAO,KAAK,SAAS,SAAS,KAAK,SAAS,QAAQ,KAAK,OAAO;EACtE,MAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,SAAS,MAAM,GAAG;AAC5E,MAAI,CAAC,KACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,mCAA+B;GAAE,EAAE,IAAI;AAEtF,MAAI,CAAC,SACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,wBAAwB;GAAE,EAAE,IAAI;EAG/E,MAAM,MAAM,QAAQ;EACpB,MAAM,SAAS,kCAAkC,KAAK,MAAM,SAAS;AACrE,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP;IACA;IACA,QAAQ,UAAU;IAClB,QAAQ,SAAU,WAAsB;IACzC;GACF,CAAC;GACF;;;;;;;AAQF,eAAc,KAAK,uBAAuB,2BAA2B,OAAO,MAAM;EAChF,IAAI,OAAiF,EAAE;AACvF,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;EAG5E,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,KAAK,MAAM,GAAG;AAChE,MAAI,CAAC,KACH,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oBAAoB;GAAE,EAAE,IAAI;AAE3E,MAAI,KAAK,SAAS,IAChB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,gCAAgC;GAAE,EAAE,IAAI;EAGvF,MAAM,SAAS,QAAQ;EACvB,MAAM,gBAAgB,4BAA4B,OAAO,UAAU,IAAI;EACvE,MAAM,WAAW,OAAO,KAAK,aAAa,YAAY,KAAK,SAAS,MAAM,GAAG,KAAK,SAAS,MAAM,GAAG,cAAc;EAClH,MAAM,YAAY;GAChB,GAAG;GACH,SAAS;GACT;GACA,UAAU;IAAE,SAAS;IAAO,OAAO,CAAC,SAAS;IAAE;GAChD;AAED,MAAI,CAAC,eAAe,UAAU,CAC5B,QAAO,EAAE,KAAK;GACZ,IAAI;GACJ,OAAO,EAAE,SAAS,iBAAiB,SAAS,uBAAuB;GACpE,EAAE,IAAI;AAGT,MAAI;GACF,MAAM,SAAS,MAAM,MAAM,MAAM,WAAW;IAC1C,WAAW;IACX,iBAAiB;IACjB,KAAK;KACH,GAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,EAAE,OAAO,KAAK,MAAM,MAAM,EAAE,GAAG,EAAE;KAC3F,GAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,EAAE,OAAO,KAAK,MAAM,MAAM,EAAE,GAAG,EAAE;KAC5F;IACF,CAAC;AACF,UAAO,EAAE,KAAK;IACZ,IAAI;IACJ,SAAS;KACP,OAAO,OAAO,MAAM,SAAS,SAAS;KACtC,QAAQ,OAAO;KACf,UAAU,OAAO;KACjB,GAAI,OAAO,aAAa,KAAA,IAAY,EAAE,UAAU,OAAO,UAAU,GAAG,EAAE;KACvE;IACF,CAAC;WACK,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,OAAI,MAAM;IAAE,cAAc;IAAK;IAAU,EAAE,wBAAwB;AACnE,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,oBAAoB,OAAO;IAAE,EAAE,IAAI;;GAElF;;;;;;;AAQF,eAAc,KAAK,yBAAyB,OAAO,MAAM;EACvD,IAAI,OAAiE,EAAE;AACvE,MAAI;AACF,UAAQ,MAAM,EAAE,IAAI,MAAM;UACpB;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,qBAAqB;IAAE,EAAE,IAAI;;EAG5E,MAAM,EAAE,OAAO,UAAU,aAAa;AACtC,MAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,0CAA0C;GAAE,EAAE,IAAI;AAEjG,MAAI,CAAC,YAAY,OAAO,aAAa,SACnC,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oCAAoC;GAAE,EAAE,IAAI;EAI3F,IAAI;AACJ,MAAI;AACF,iBAAc,OAAO,KAAK,OAAO,SAAS;UACpC;AACN,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,6BAA6B;IAAE,EAAE,IAAI;;AAGpF,MAAI,YAAY,WAAW,EACzB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,oBAAoB;GAAE,EAAE,IAAI;AAE3E,MAAI,YAAY,SAAS,gBACvB,QAAO,EAAE,KAAK;GAAE,IAAI;GAAO,OAAO,EAAE,SAAS,kCAAkC;GAAE,EAAE,IAAI;EAIzF,MAAM,SAAS,QAAQ;EACvB,MAAM,eAAe,OAAO,OAAO,OAAO;EAC1C,MAAM,YAAY,4BAA4B,cAAc,OAAO,OAAO,MAAM;AAEhF,MAAI,CAAC,eAAe,UAAU,CAC5B,QAAO,EAAE,KAAK;GACZ,IAAI;GACJ,OAAO,EAAE,SAAS,4EAA4E;GAC/F,EAAE,IAAI;EAIT,IAAI;EACJ,IAAI;AACJ,MAAI;GACF,MAAM,SAAS,MAAM,WAAW,aAAa,WAAW,EACtD,UAAU,aAAa,UAAU,aAAa,YAAY,OAAO,KAAA,IAClE,CAAC;AACF,SAAM,OAAO;AACb,sBAAmB,OAAO,YAAY;WAC/B,OAAO;GACd,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAClE,OAAI,MAAM,EAAE,cAAc,KAAK,EAAE,6BAA6B;AAC9D,UAAO,EAAE,KAAK;IAAE,IAAI;IAAO,OAAO,EAAE,SAAS,yBAAyB,OAAO;IAAE,EAAE,IAAI;;AAGvF,MAAI,CAAC,IAAI,MAAM,CACb,QAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IAAE,KAAK;IAAI,UAAU;IAAkB;GACjD,CAAC;EAIJ,MAAM,UAAU,MAAM,iBAAiB,KAAK,OAAO;AAEnD,SAAO,EAAE,KAAK;GACZ,IAAI;GACJ,SAAS;IACP;IACA,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;IAC9B,GAAI,mBAAmB,EAAE,UAAU,kBAAkB,GAAG,EAAE;IAC3D;GACF,CAAC;GACF"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { init_agent_scope, resolveDefaultAgentId } from "../../../agent/agent-scope.js";
|
|
2
|
+
import { createWorkflowCatalog } from "../../../agent/workflow/catalog.js";
|
|
3
|
+
import { buildWorkflowDefinition } from "../../../workflows/domain/definition-utils.js";
|
|
4
|
+
import { validateWorkflowDefinitionInput } from "../../../workflows/domain/validation.js";
|
|
5
|
+
import "../../../workflows/domain/index.js";
|
|
6
|
+
//#region src/gateway/hono/routes/workflows.ts
|
|
7
|
+
init_agent_scope();
|
|
8
|
+
function registerWorkflowRoutes(authenticated, deps) {
|
|
9
|
+
const { service } = deps;
|
|
10
|
+
const workflowRunService = service.createWorkflowRunService();
|
|
11
|
+
authenticated.get("/api/workflows/definitions", (c) => {
|
|
12
|
+
const catalog = createWorkflowCatalog();
|
|
13
|
+
const definitions = catalog.list().map((entry) => {
|
|
14
|
+
try {
|
|
15
|
+
return toWorkflowDefinition(catalog.load(entry.name));
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}).filter((definition) => Boolean(definition));
|
|
20
|
+
return c.json({ definitions });
|
|
21
|
+
});
|
|
22
|
+
authenticated.get("/api/workflows/definitions/:id", (c) => {
|
|
23
|
+
const id = c.req.param("id");
|
|
24
|
+
const catalog = createWorkflowCatalog();
|
|
25
|
+
try {
|
|
26
|
+
const definition = toWorkflowDefinition(catalog.load(id));
|
|
27
|
+
return c.json({ definition });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return c.json({ error: err instanceof Error ? err.message : "Workflow definition not found" }, 404);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
authenticated.post("/api/workflows/definitions/validate", async (c) => {
|
|
33
|
+
const body = await readJsonBody(c.req.raw);
|
|
34
|
+
const result = validateWorkflowDefinitionInput({
|
|
35
|
+
name: body.name,
|
|
36
|
+
script: body.script
|
|
37
|
+
});
|
|
38
|
+
return c.json(result);
|
|
39
|
+
});
|
|
40
|
+
authenticated.post("/api/workflows/definitions", async (c) => {
|
|
41
|
+
const body = await readJsonBody(c.req.raw);
|
|
42
|
+
const validation = validateWorkflowDefinitionInput({
|
|
43
|
+
name: body.name,
|
|
44
|
+
script: body.script
|
|
45
|
+
});
|
|
46
|
+
if (!validation.valid) return c.json({
|
|
47
|
+
error: validation.errors[0]?.message ?? "Invalid workflow definition",
|
|
48
|
+
validation
|
|
49
|
+
}, 400);
|
|
50
|
+
const name = body.name?.trim() ?? "";
|
|
51
|
+
const script = body.script ?? "";
|
|
52
|
+
const catalog = createWorkflowCatalog();
|
|
53
|
+
try {
|
|
54
|
+
catalog.save(name, script);
|
|
55
|
+
const definition = toWorkflowDefinition(catalog.load(name));
|
|
56
|
+
return c.json({ definition }, 201);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return c.json({ error: err instanceof Error ? err.message : "Failed to save workflow" }, 400);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
authenticated.delete("/api/workflows/definitions/:id", (c) => {
|
|
62
|
+
const id = c.req.param("id").trim();
|
|
63
|
+
if (!id) return c.json({ error: "id is required" }, 400);
|
|
64
|
+
const catalog = createWorkflowCatalog();
|
|
65
|
+
try {
|
|
66
|
+
if (!catalog.remove(id)) return c.json({ error: "User workflow not found or cannot delete built-in workflow" }, 404);
|
|
67
|
+
return c.json({ removed: true });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return c.json({ error: err instanceof Error ? err.message : "Failed to delete workflow" }, 400);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
authenticated.get("/api/workflows/stats", async (c) => {
|
|
73
|
+
const agentId = getAgentId(c.req.query("agentId"), service.currentConfig);
|
|
74
|
+
const runs = await workflowRunService.createRunStore(agentId).listRunSummaries(500);
|
|
75
|
+
return c.json({ stats: buildWorkflowStats(runs) });
|
|
76
|
+
});
|
|
77
|
+
authenticated.post("/api/workflows/runs", async (c) => {
|
|
78
|
+
const body = await readJsonBody(c.req.raw);
|
|
79
|
+
const definitionId = body.definitionId?.trim();
|
|
80
|
+
if (!definitionId) return c.json({ error: "definitionId is required" }, 400);
|
|
81
|
+
const agentId = getAgentId(body.agentId ?? c.req.query("agentId"), service.currentConfig);
|
|
82
|
+
const parentSessionKey = body.parentSessionKey?.trim() || void 0;
|
|
83
|
+
const result = await workflowRunService.startWorkflowRun({
|
|
84
|
+
agentId,
|
|
85
|
+
definitionId,
|
|
86
|
+
input: body.input,
|
|
87
|
+
inputEnvelope: body.inputEnvelope,
|
|
88
|
+
goal: body.goal,
|
|
89
|
+
parentSessionKey,
|
|
90
|
+
source: normalizeWorkflowRunSource(body.source),
|
|
91
|
+
concurrency: normalizePositiveInteger(body.concurrency),
|
|
92
|
+
maxSubagents: normalizePositiveInteger(body.maxSubagents),
|
|
93
|
+
tokenBudget: body.tokenBudget,
|
|
94
|
+
idempotencyKey: body.idempotencyKey
|
|
95
|
+
});
|
|
96
|
+
if (result.ok === false) return c.json({
|
|
97
|
+
error: result.message,
|
|
98
|
+
code: result.code
|
|
99
|
+
}, result.httpStatus);
|
|
100
|
+
return c.json({
|
|
101
|
+
runId: result.runId,
|
|
102
|
+
sessionKey: result.sessionKey
|
|
103
|
+
}, 202);
|
|
104
|
+
});
|
|
105
|
+
authenticated.get("/api/workflows/runs", async (c) => {
|
|
106
|
+
const agentId = getAgentId(c.req.query("agentId"), service.currentConfig);
|
|
107
|
+
const rawLimit = c.req.query("limit");
|
|
108
|
+
const limit = rawLimit ? Number.parseInt(rawLimit, 10) : 50;
|
|
109
|
+
const runs = await workflowRunService.createRunStore(agentId).listRunSummaries(Number.isFinite(limit) ? limit : 50);
|
|
110
|
+
return c.json({ runs });
|
|
111
|
+
});
|
|
112
|
+
authenticated.post("/api/workflows/runs/:runId/cancel", async (c) => {
|
|
113
|
+
const runId = c.req.param("runId");
|
|
114
|
+
const agentId = getAgentId(c.req.query("agentId"), service.currentConfig);
|
|
115
|
+
const result = await workflowRunService.cancelWorkflowRun({
|
|
116
|
+
agentId,
|
|
117
|
+
runId,
|
|
118
|
+
reason: "Cancelled by user"
|
|
119
|
+
});
|
|
120
|
+
if (result.ok === false) return c.json({
|
|
121
|
+
error: result.message,
|
|
122
|
+
code: result.code
|
|
123
|
+
}, result.httpStatus);
|
|
124
|
+
return c.json({
|
|
125
|
+
cancelled: result.cancelled,
|
|
126
|
+
alreadyFinished: result.alreadyFinished
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
authenticated.get("/api/workflows/runs/:runId", async (c) => {
|
|
130
|
+
const agentId = getAgentId(c.req.query("agentId"), service.currentConfig);
|
|
131
|
+
const runId = c.req.param("runId");
|
|
132
|
+
const view = await workflowRunService.createRunStore(agentId).readRunView(runId);
|
|
133
|
+
if (!view) return c.json({ error: "Workflow run not found" }, 404);
|
|
134
|
+
return c.json({ view });
|
|
135
|
+
});
|
|
136
|
+
authenticated.post("/api/workflows/runs/:runId/rebuild", async (c) => {
|
|
137
|
+
const agentId = getAgentId(c.req.query("agentId"), service.currentConfig);
|
|
138
|
+
const runId = c.req.param("runId");
|
|
139
|
+
const view = await workflowRunService.createRunStore(agentId).rebuildRunView(runId);
|
|
140
|
+
if (!view) return c.json({ error: "Workflow run not found" }, 404);
|
|
141
|
+
service.emit("workflow.run.updated", {
|
|
142
|
+
runId,
|
|
143
|
+
view
|
|
144
|
+
});
|
|
145
|
+
return c.json({ view });
|
|
146
|
+
});
|
|
147
|
+
authenticated.post("/api/workflows/runs/:runId/retry", async (c) => {
|
|
148
|
+
const agentId = getAgentId(c.req.query("agentId"), service.currentConfig);
|
|
149
|
+
const runId = c.req.param("runId");
|
|
150
|
+
const result = await workflowRunService.retryWorkflowRun({
|
|
151
|
+
agentId,
|
|
152
|
+
runId
|
|
153
|
+
});
|
|
154
|
+
if (result.ok === false) return c.json({
|
|
155
|
+
error: result.message,
|
|
156
|
+
code: result.code
|
|
157
|
+
}, result.httpStatus);
|
|
158
|
+
return c.json({
|
|
159
|
+
runId: result.runId,
|
|
160
|
+
sessionKey: result.sessionKey
|
|
161
|
+
}, 202);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function getAgentId(rawAgentId, config) {
|
|
165
|
+
const trimmed = rawAgentId?.trim();
|
|
166
|
+
if (trimmed) return trimmed;
|
|
167
|
+
return resolveDefaultAgentId(config);
|
|
168
|
+
}
|
|
169
|
+
function toWorkflowDefinition(loaded) {
|
|
170
|
+
return buildWorkflowDefinition({
|
|
171
|
+
name: loaded.name,
|
|
172
|
+
source: loaded.source,
|
|
173
|
+
script: loaded.script,
|
|
174
|
+
meta: loaded.meta
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function buildWorkflowStats(runs) {
|
|
178
|
+
const activeStatuses = new Set(["queued", "running"]);
|
|
179
|
+
const succeededStatuses = new Set(["succeeded"]);
|
|
180
|
+
const failedStatuses = new Set([
|
|
181
|
+
"failed",
|
|
182
|
+
"timeout",
|
|
183
|
+
"cancelled"
|
|
184
|
+
]);
|
|
185
|
+
let durationTotal = 0;
|
|
186
|
+
let durationCount = 0;
|
|
187
|
+
const definitionCounts = /* @__PURE__ */ new Map();
|
|
188
|
+
for (const run of runs) {
|
|
189
|
+
definitionCounts.set(run.definitionId, (definitionCounts.get(run.definitionId) ?? 0) + 1);
|
|
190
|
+
if (run.metrics.durationMs != null && Number.isFinite(run.metrics.durationMs)) {
|
|
191
|
+
durationTotal += run.metrics.durationMs;
|
|
192
|
+
durationCount += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const topDefinitions = [...definitionCounts.entries()].sort((left, right) => right[1] - left[1]).slice(0, 5).map(([definitionId, count]) => ({
|
|
196
|
+
definitionId,
|
|
197
|
+
count
|
|
198
|
+
}));
|
|
199
|
+
return {
|
|
200
|
+
totalRuns: runs.length,
|
|
201
|
+
activeRuns: runs.filter((run) => activeStatuses.has(run.status)).length,
|
|
202
|
+
succeededRuns: runs.filter((run) => succeededStatuses.has(run.status)).length,
|
|
203
|
+
failedRuns: runs.filter((run) => failedStatuses.has(run.status)).length,
|
|
204
|
+
averageDurationMs: durationCount > 0 ? Math.round(durationTotal / durationCount) : null,
|
|
205
|
+
topDefinitions
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
async function readJsonBody(request) {
|
|
209
|
+
try {
|
|
210
|
+
return await request.json();
|
|
211
|
+
} catch {
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function normalizeWorkflowRunSource(source) {
|
|
216
|
+
if (!source) return { kind: "webui" };
|
|
217
|
+
return source;
|
|
218
|
+
}
|
|
219
|
+
function normalizePositiveInteger(value) {
|
|
220
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) return;
|
|
221
|
+
return Math.floor(value);
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { registerWorkflowRoutes };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=workflows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflows.js","names":[],"sources":["../../../../../src/gateway/hono/routes/workflows.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport { resolveDefaultAgentId } from '../../../agent/agent-scope.js';\nimport { createWorkflowCatalog } from '../../../agent/workflow/catalog.js';\nimport type {\n WorkflowDefinition,\n WorkflowRunInputEnvelope,\n WorkflowRunSource,\n WorkflowRunSummary,\n} from '../../../workflows/domain/index.js';\nimport { buildWorkflowDefinition, validateWorkflowDefinitionInput } from '../../../workflows/domain/index.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\ninterface StartWorkflowRunRequestBody {\n definitionId?: string;\n input?: unknown;\n inputEnvelope?: WorkflowRunInputEnvelope;\n goal?: string;\n agentId?: string;\n parentSessionKey?: string;\n source?: WorkflowRunSource;\n concurrency?: number;\n maxSubagents?: number;\n tokenBudget?: number | null;\n idempotencyKey?: string;\n}\n\ninterface SaveWorkflowDefinitionRequestBody {\n name?: string;\n script?: string;\n}\n\nexport function registerWorkflowRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n const workflowRunService = service.createWorkflowRunService();\n\n authenticated.get('/api/workflows/definitions', (c) => {\n const catalog = createWorkflowCatalog();\n const definitions = catalog.list().map((entry) => {\n try {\n return toWorkflowDefinition(catalog.load(entry.name));\n } catch {\n return null;\n }\n }).filter((definition): definition is WorkflowDefinition => Boolean(definition));\n\n return c.json({ definitions });\n });\n\n authenticated.get('/api/workflows/definitions/:id', (c) => {\n const id = c.req.param('id');\n const catalog = createWorkflowCatalog();\n try {\n const definition = toWorkflowDefinition(catalog.load(id));\n return c.json({ definition });\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : 'Workflow definition not found' }, 404);\n }\n });\n\n authenticated.post('/api/workflows/definitions/validate', async (c) => {\n const body = await readJsonBody<SaveWorkflowDefinitionRequestBody>(c.req.raw);\n const result = validateWorkflowDefinitionInput({\n name: body.name,\n script: body.script,\n });\n return c.json(result);\n });\n\n authenticated.post('/api/workflows/definitions', async (c) => {\n const body = await readJsonBody<SaveWorkflowDefinitionRequestBody>(c.req.raw);\n const validation = validateWorkflowDefinitionInput({\n name: body.name,\n script: body.script,\n });\n if (!validation.valid) {\n return c.json({ error: validation.errors[0]?.message ?? 'Invalid workflow definition', validation }, 400);\n }\n\n const name = body.name?.trim() ?? '';\n const script = body.script ?? '';\n const catalog = createWorkflowCatalog();\n try {\n catalog.save(name, script);\n const definition = toWorkflowDefinition(catalog.load(name));\n return c.json({ definition }, 201);\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : 'Failed to save workflow' }, 400);\n }\n });\n\n authenticated.delete('/api/workflows/definitions/:id', (c) => {\n const id = c.req.param('id').trim();\n if (!id) {\n return c.json({ error: 'id is required' }, 400);\n }\n\n const catalog = createWorkflowCatalog();\n try {\n const removed = catalog.remove(id);\n if (!removed) {\n return c.json({ error: 'User workflow not found or cannot delete built-in workflow' }, 404);\n }\n return c.json({ removed: true });\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : 'Failed to delete workflow' }, 400);\n }\n });\n\n authenticated.get('/api/workflows/stats', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runStore = workflowRunService.createRunStore(agentId);\n const runs = await runStore.listRunSummaries(500);\n return c.json({ stats: buildWorkflowStats(runs) });\n });\n\n authenticated.post('/api/workflows/runs', async (c) => {\n const body = await readJsonBody<StartWorkflowRunRequestBody>(c.req.raw);\n const definitionId = body.definitionId?.trim();\n if (!definitionId) {\n return c.json({ error: 'definitionId is required' }, 400);\n }\n\n const agentId = getAgentId(body.agentId ?? c.req.query('agentId'), service.currentConfig);\n const parentSessionKey = body.parentSessionKey?.trim() || undefined;\n const result = await workflowRunService.startWorkflowRun({\n agentId,\n definitionId,\n input: body.input,\n inputEnvelope: body.inputEnvelope,\n goal: body.goal,\n parentSessionKey,\n source: normalizeWorkflowRunSource(body.source),\n concurrency: normalizePositiveInteger(body.concurrency),\n maxSubagents: normalizePositiveInteger(body.maxSubagents),\n tokenBudget: body.tokenBudget,\n idempotencyKey: body.idempotencyKey,\n });\n\n if (result.ok === false) {\n return c.json({ error: result.message, code: result.code }, result.httpStatus);\n }\n\n return c.json({ runId: result.runId, sessionKey: result.sessionKey }, 202);\n });\n\n authenticated.get('/api/workflows/runs', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const rawLimit = c.req.query('limit');\n const limit = rawLimit ? Number.parseInt(rawLimit, 10) : 50;\n const runStore = workflowRunService.createRunStore(agentId);\n const runs = await runStore.listRunSummaries(Number.isFinite(limit) ? limit : 50);\n return c.json({ runs });\n });\n\n authenticated.post('/api/workflows/runs/:runId/cancel', async (c) => {\n const runId = c.req.param('runId');\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const result = await workflowRunService.cancelWorkflowRun({\n agentId,\n runId,\n reason: 'Cancelled by user',\n });\n if (result.ok === false) {\n return c.json({ error: result.message, code: result.code }, result.httpStatus);\n }\n return c.json({\n cancelled: result.cancelled,\n alreadyFinished: result.alreadyFinished,\n });\n });\n\n authenticated.get('/api/workflows/runs/:runId', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runId = c.req.param('runId');\n const runStore = workflowRunService.createRunStore(agentId);\n const view = await runStore.readRunView(runId);\n if (!view) {\n return c.json({ error: 'Workflow run not found' }, 404);\n }\n return c.json({ view });\n });\n\n authenticated.post('/api/workflows/runs/:runId/rebuild', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runId = c.req.param('runId');\n const runStore = workflowRunService.createRunStore(agentId);\n const view = await runStore.rebuildRunView(runId);\n if (!view) {\n return c.json({ error: 'Workflow run not found' }, 404);\n }\n service.emit('workflow.run.updated', { runId, view });\n return c.json({ view });\n });\n\n authenticated.post('/api/workflows/runs/:runId/retry', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runId = c.req.param('runId');\n const result = await workflowRunService.retryWorkflowRun({ agentId, runId });\n if (result.ok === false) {\n return c.json({ error: result.message, code: result.code }, result.httpStatus);\n }\n\n return c.json({ runId: result.runId, sessionKey: result.sessionKey }, 202);\n });\n}\n\nfunction getAgentId(rawAgentId: string | undefined, config: AuthenticatedRouteDeps['service']['currentConfig']): string {\n const trimmed = rawAgentId?.trim();\n if (trimmed) {\n return trimmed;\n }\n return resolveDefaultAgentId(config);\n}\n\nfunction toWorkflowDefinition(loaded: ReturnType<ReturnType<typeof createWorkflowCatalog>['load']>): WorkflowDefinition {\n return buildWorkflowDefinition({\n name: loaded.name,\n source: loaded.source,\n script: loaded.script,\n meta: loaded.meta,\n });\n}\n\nfunction buildWorkflowStats(runs: WorkflowRunSummary[]): {\n totalRuns: number;\n activeRuns: number;\n succeededRuns: number;\n failedRuns: number;\n averageDurationMs: number | null;\n topDefinitions: Array<{ definitionId: string; count: number }>;\n} {\n const activeStatuses = new Set(['queued', 'running']);\n const succeededStatuses = new Set(['succeeded']);\n const failedStatuses = new Set(['failed', 'timeout', 'cancelled']);\n\n let durationTotal = 0;\n let durationCount = 0;\n const definitionCounts = new Map<string, number>();\n\n for (const run of runs) {\n definitionCounts.set(run.definitionId, (definitionCounts.get(run.definitionId) ?? 0) + 1);\n if (run.metrics.durationMs != null && Number.isFinite(run.metrics.durationMs)) {\n durationTotal += run.metrics.durationMs;\n durationCount += 1;\n }\n }\n\n const topDefinitions = [...definitionCounts.entries()]\n .sort((left, right) => right[1] - left[1])\n .slice(0, 5)\n .map(([definitionId, count]) => ({ definitionId, count }));\n\n return {\n totalRuns: runs.length,\n activeRuns: runs.filter((run) => activeStatuses.has(run.status)).length,\n succeededRuns: runs.filter((run) => succeededStatuses.has(run.status)).length,\n failedRuns: runs.filter((run) => failedStatuses.has(run.status)).length,\n averageDurationMs: durationCount > 0 ? Math.round(durationTotal / durationCount) : null,\n topDefinitions,\n };\n}\n\nasync function readJsonBody<T>(request: Request): Promise<T> {\n try {\n return await request.json() as T;\n } catch {\n return {} as T;\n }\n}\n\nfunction normalizeWorkflowRunSource(source: WorkflowRunSource | undefined): WorkflowRunSource {\n if (!source) {\n return { kind: 'webui' };\n }\n return source;\n}\n\nfunction normalizePositiveInteger(value: number | undefined): number | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {\n return undefined;\n }\n return Math.floor(value);\n}\n\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { inboundCorrelationMetadataFromAsyncLogContext } from "../../utils/logger/context.js";
|
|
2
2
|
import { createLogger } from "../../utils/logger/index.js";
|
|
3
3
|
import { init_logger } from "../../utils/logger.js";
|
|
4
|
+
import { formatAgentRunErrorForClient } from "../../agent/client-error-format.js";
|
|
4
5
|
import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
|
|
5
6
|
import { shouldSkipWebchatInboundByAbortCutoff } from "../../session/abort-cutoff.js";
|
|
6
7
|
import "../chat-limits.js";
|
|
@@ -10,25 +11,6 @@ import crypto from "crypto";
|
|
|
10
11
|
init_logger();
|
|
11
12
|
const log = createLogger("GatewayService");
|
|
12
13
|
/**
|
|
13
|
-
* Match upstream pi-ai "No API key found for <provider>" errors and replace
|
|
14
|
-
* with a short, actionable message the web UI can render as a setup card.
|
|
15
|
-
* Falls back to `Error: <raw>` for anything else.
|
|
16
|
-
*/
|
|
17
|
-
const API_KEY_MISSING_RE = /^No API key found for (\S+)/i;
|
|
18
|
-
function formatAgentErrorForClient(rawError) {
|
|
19
|
-
const match = API_KEY_MISSING_RE.exec(rawError);
|
|
20
|
-
if (match) {
|
|
21
|
-
const provider = match[1].replace(/\.$/, "");
|
|
22
|
-
return JSON.stringify({
|
|
23
|
-
kind: "provider_setup_required",
|
|
24
|
-
provider,
|
|
25
|
-
deepLink: "/settings/providers",
|
|
26
|
-
message: rawError
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
return `Error: ${rawError}`;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
14
|
* @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.
|
|
33
15
|
*/
|
|
34
16
|
async function* runGatewayAgent(deps, message, channel, chatId, attachments, thinking, runOptions) {
|
|
@@ -114,7 +96,7 @@ async function* runGatewayAgent(deps, message, channel, chatId, attachments, thi
|
|
|
114
96
|
streamError = error instanceof Error ? error.message : "Unknown error";
|
|
115
97
|
const errorEvent = {
|
|
116
98
|
type: "error",
|
|
117
|
-
content:
|
|
99
|
+
content: formatAgentRunErrorForClient(streamError)
|
|
118
100
|
};
|
|
119
101
|
runRelay.publish(runId, errorEvent);
|
|
120
102
|
emit("agent.stream", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-gateway-agent.js","names":[],"sources":["../../../../src/gateway/service/run-gateway-agent.ts"],"sourcesContent":["import crypto from 'crypto';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport {\n createLogger,\n inboundCorrelationMetadataFromAsyncLogContext,\n} from '../../utils/logger.js';\nimport { shouldSkipWebchatInboundByAbortCutoff } from '../../session/abort-cutoff.js';\n\nimport type { AgentRunRelay } from '../agent-run-relay.js';\nimport { MAX_CHAT_ATTACHMENTS } from '../chat-limits.js';\nconst log = createLogger('GatewayService');\n\n/**\n * Match upstream pi-ai \"No API key found for <provider>\" errors and replace\n * with a short, actionable message the web UI can render as a setup card.\n * Falls back to `Error: <raw>` for anything else.\n */\nconst API_KEY_MISSING_RE = /^No API key found for (\\S+)/i;\n\nfunction formatAgentErrorForClient(rawError: string): string {\n const match = API_KEY_MISSING_RE.exec(rawError);\n if (match) {\n const provider = match[1].replace(/\\.$/, '');\n return JSON.stringify({\n kind: 'provider_setup_required',\n provider,\n deepLink: '/settings/providers',\n message: rawError,\n });\n }\n return `Error: ${rawError}`;\n}\n\nexport type RunGatewayAgentYield = {\n type: string;\n content?: string;\n status?: string;\n runId?: string;\n};\n\nexport type RunGatewayAgentDeps = {\n config: Config;\n agentService: AgentService;\n bus: MessageBus;\n runRelay: AgentRunRelay;\n runAbortControllers: Map<string, AbortController>;\n activeWebchatRunBySession: Map<string, string>;\n sessionIndex: SessionIndex;\n emit: (type: string, payload: unknown) => void;\n};\n\n/**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\nexport async function *runGatewayAgent(\n deps: RunGatewayAgentDeps,\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n): AsyncGenerator<RunGatewayAgentYield, { status: string; summary: string }, unknown> {\n const cappedAttachments =\n attachments && attachments.length > MAX_CHAT_ATTACHMENTS\n ? attachments.slice(0, MAX_CHAT_ATTACHMENTS)\n : attachments;\n if (attachments && cappedAttachments && attachments.length > cappedAttachments.length) {\n log.debug(\n { dropped: attachments.length - cappedAttachments.length, max: MAX_CHAT_ATTACHMENTS },\n 'Attachments capped for webchat',\n );\n }\n\n const runId = crypto.randomUUID();\n const {\n config,\n agentService,\n bus,\n runRelay,\n runAbortControllers,\n activeWebchatRunBySession,\n sessionIndex: sessionIndexFromDeps,\n emit,\n } = deps;\n const sessionIndex = sessionIndexFromDeps;\n\n let webchatSessionKey: string | undefined;\n let webchatStaleSkip = false;\n if (channel === 'webchat') {\n const resolved = resolveWebchatSessionKey({ cfg: config, chatId, newSession: false });\n if (resolved.ok === false) {\n throw new Error(resolved.error);\n }\n webchatSessionKey = resolved.sessionKey;\n const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);\n webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);\n if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== undefined) {\n await sessionIndex\n .updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: undefined })\n .catch(() => {});\n }\n runRelay.ensureRun(runId, webchatSessionKey);\n runAbortControllers.set(runId, new AbortController());\n }\n\n const statusEvent = { type: 'status', status: 'accepted', runId };\n if (channel === 'webchat') runRelay.publish(runId, statusEvent);\n yield statusEvent;\n\n try {\n if (channel === 'webchat' && webchatSessionKey) {\n if (webchatStaleSkip) {\n runRelay.complete(runId);\n runAbortControllers.delete(runId);\n return {\n status: 'skipped',\n summary: 'Stale inbound after abort (clientCreatedAtMs before cutoff)',\n };\n }\n\n const sessionKey = webchatSessionKey;\n\n const timezone = agentService.resolveUserTimezoneForSession(sessionKey);\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n const runAbort = runAbortControllers.get(runId);\n if (!runAbort) {\n throw new Error('run abort controller missing for webchat');\n }\n const mergedSignal = runOptions?.signal\n ? AbortSignal.any([runOptions.signal, runAbort.signal])\n : runAbort.signal;\n\n agentService.beginInboundTurn(sessionKey);\n activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n emit('agent.stream', { sessionKey, event: statusEvent });\n const eventStream = agentService.turnDispatcher.processDirectStreaming(\n stampedMessage,\n sessionKey,\n prepared,\n thinking,\n { signal: mergedSignal },\n );\n\n for await (const event of eventStream) {\n runRelay.publish(runId, event);\n emit('agent.stream', { sessionKey, event });\n yield event as RunGatewayAgentYield;\n }\n\n runRelay.complete(runId);\n try {\n const metaAfter = await sessionIndex.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n emit('session.updated', { key: sessionKey, name: metaAfter.name });\n }\n } catch {\n /* ignore */\n }\n return {\n status: mergedSignal.aborted ? 'aborted' : 'ok',\n summary: mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully',\n };\n } catch (error) {\n log.error({ error }, 'Agent processing failed');\n streamError = error instanceof Error ? error.message : 'Unknown error';\n const errorContent = formatAgentErrorForClient(streamError);\n const errorEvent = { type: 'error', content: errorContent };\n runRelay.publish(runId, errorEvent);\n emit('agent.stream', { sessionKey, event: errorEvent });\n runRelay.complete(runId);\n yield errorEvent;\n return { status: 'error', summary: streamError };\n } finally {\n activeWebchatRunBySession.delete(sessionKey);\n runAbortControllers.delete(runId);\n const assistantPlainText = agentService.getLastAssistantPlainText(sessionKey);\n const streamOutcome = agentService.persistentGoals.takeStreamOutcome(sessionKey);\n try {\n await agentService.outboundCoordinator.emitSessionTurnComplete({\n sessionKey,\n channel: 'webchat',\n chatId: sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n skipPersistentGoalPostTurn: streamOutcome?.skipPersistentGoalPostTurn ?? false,\n outboundMetadata: {},\n });\n } catch (goalErr) {\n log.warn(\n { err: goalErr, sessionKey },\n `Session turn complete failed: ${goalErr instanceof Error ? goalErr.message : String(goalErr)}`,\n );\n }\n agentService.endInboundTurn(sessionKey);\n }\n }\n\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n yield { type: 'token', content: 'Processing...\\n' };\n await new Promise((resolve) => setTimeout(resolve, 1000));\n yield { type: 'token', content: 'Done\\n' };\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n log.error({ error }, 'Agent run failed');\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run-gateway-agent.js","names":[],"sources":["../../../../src/gateway/service/run-gateway-agent.ts"],"sourcesContent":["import crypto from 'crypto';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport {\n createLogger,\n inboundCorrelationMetadataFromAsyncLogContext,\n} from '../../utils/logger.js';\nimport { shouldSkipWebchatInboundByAbortCutoff } from '../../session/abort-cutoff.js';\n\nimport { formatAgentRunErrorForClient } from '../../agent/client-error-format.js';\n\nimport type { AgentRunRelay } from '../agent-run-relay.js';\nimport { MAX_CHAT_ATTACHMENTS } from '../chat-limits.js';\nconst log = createLogger('GatewayService');\n\nexport type RunGatewayAgentYield = {\n type: string;\n content?: string;\n status?: string;\n runId?: string;\n};\n\nexport type RunGatewayAgentDeps = {\n config: Config;\n agentService: AgentService;\n bus: MessageBus;\n runRelay: AgentRunRelay;\n runAbortControllers: Map<string, AbortController>;\n activeWebchatRunBySession: Map<string, string>;\n sessionIndex: SessionIndex;\n emit: (type: string, payload: unknown) => void;\n};\n\n/**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\nexport async function *runGatewayAgent(\n deps: RunGatewayAgentDeps,\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n): AsyncGenerator<RunGatewayAgentYield, { status: string; summary: string }, unknown> {\n const cappedAttachments =\n attachments && attachments.length > MAX_CHAT_ATTACHMENTS\n ? attachments.slice(0, MAX_CHAT_ATTACHMENTS)\n : attachments;\n if (attachments && cappedAttachments && attachments.length > cappedAttachments.length) {\n log.debug(\n { dropped: attachments.length - cappedAttachments.length, max: MAX_CHAT_ATTACHMENTS },\n 'Attachments capped for webchat',\n );\n }\n\n const runId = crypto.randomUUID();\n const {\n config,\n agentService,\n bus,\n runRelay,\n runAbortControllers,\n activeWebchatRunBySession,\n sessionIndex: sessionIndexFromDeps,\n emit,\n } = deps;\n const sessionIndex = sessionIndexFromDeps;\n\n let webchatSessionKey: string | undefined;\n let webchatStaleSkip = false;\n if (channel === 'webchat') {\n const resolved = resolveWebchatSessionKey({ cfg: config, chatId, newSession: false });\n if (resolved.ok === false) {\n throw new Error(resolved.error);\n }\n webchatSessionKey = resolved.sessionKey;\n const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);\n webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);\n if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== undefined) {\n await sessionIndex\n .updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: undefined })\n .catch(() => {});\n }\n runRelay.ensureRun(runId, webchatSessionKey);\n runAbortControllers.set(runId, new AbortController());\n }\n\n const statusEvent = { type: 'status', status: 'accepted', runId };\n if (channel === 'webchat') runRelay.publish(runId, statusEvent);\n yield statusEvent;\n\n try {\n if (channel === 'webchat' && webchatSessionKey) {\n if (webchatStaleSkip) {\n runRelay.complete(runId);\n runAbortControllers.delete(runId);\n return {\n status: 'skipped',\n summary: 'Stale inbound after abort (clientCreatedAtMs before cutoff)',\n };\n }\n\n const sessionKey = webchatSessionKey;\n\n const timezone = agentService.resolveUserTimezoneForSession(sessionKey);\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n const runAbort = runAbortControllers.get(runId);\n if (!runAbort) {\n throw new Error('run abort controller missing for webchat');\n }\n const mergedSignal = runOptions?.signal\n ? AbortSignal.any([runOptions.signal, runAbort.signal])\n : runAbort.signal;\n\n agentService.beginInboundTurn(sessionKey);\n activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n emit('agent.stream', { sessionKey, event: statusEvent });\n const eventStream = agentService.turnDispatcher.processDirectStreaming(\n stampedMessage,\n sessionKey,\n prepared,\n thinking,\n { signal: mergedSignal },\n );\n\n for await (const event of eventStream) {\n runRelay.publish(runId, event);\n emit('agent.stream', { sessionKey, event });\n yield event as RunGatewayAgentYield;\n }\n\n runRelay.complete(runId);\n try {\n const metaAfter = await sessionIndex.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n emit('session.updated', { key: sessionKey, name: metaAfter.name });\n }\n } catch {\n /* ignore */\n }\n return {\n status: mergedSignal.aborted ? 'aborted' : 'ok',\n summary: mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully',\n };\n } catch (error) {\n log.error({ error }, 'Agent processing failed');\n streamError = error instanceof Error ? error.message : 'Unknown error';\n const errorContent = formatAgentRunErrorForClient(streamError);\n const errorEvent = { type: 'error', content: errorContent };\n runRelay.publish(runId, errorEvent);\n emit('agent.stream', { sessionKey, event: errorEvent });\n runRelay.complete(runId);\n yield errorEvent;\n return { status: 'error', summary: streamError };\n } finally {\n activeWebchatRunBySession.delete(sessionKey);\n runAbortControllers.delete(runId);\n const assistantPlainText = agentService.getLastAssistantPlainText(sessionKey);\n const streamOutcome = agentService.persistentGoals.takeStreamOutcome(sessionKey);\n try {\n await agentService.outboundCoordinator.emitSessionTurnComplete({\n sessionKey,\n channel: 'webchat',\n chatId: sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n skipPersistentGoalPostTurn: streamOutcome?.skipPersistentGoalPostTurn ?? false,\n outboundMetadata: {},\n });\n } catch (goalErr) {\n log.warn(\n { err: goalErr, sessionKey },\n `Session turn complete failed: ${goalErr instanceof Error ? goalErr.message : String(goalErr)}`,\n );\n }\n agentService.endInboundTurn(sessionKey);\n }\n }\n\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n yield { type: 'token', content: 'Processing...\\n' };\n await new Promise((resolve) => setTimeout(resolve, 1000));\n yield { type: 'token', content: 'Done\\n' };\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n log.error({ error }, 'Agent run failed');\n throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AgentService } from '../agent/service.js';
|
|
2
|
+
import { MessageBus } from '../infra/bus/index.js';
|
|
2
3
|
import { CronService } from '../cron/index.js';
|
|
4
|
+
import { WorkflowRunService } from '../workflows/service/workflow-run-service.js';
|
|
3
5
|
import { ExtensionLoader } from '../extensions/index.js';
|
|
4
6
|
import { SessionIndex } from '../session/index.js';
|
|
5
7
|
import type { Config } from '../config/schema.js';
|
|
@@ -42,6 +44,8 @@ export declare class GatewayService {
|
|
|
42
44
|
private lastChannelConnectDeferSource;
|
|
43
45
|
private readonly readiness;
|
|
44
46
|
private startupTrace;
|
|
47
|
+
private workflowSessionBridge;
|
|
48
|
+
private workflowRunServiceInstance;
|
|
45
49
|
/**
|
|
46
50
|
* Webchat agent invocation surface (`runAgent`, `abortAgentRun`, `steer*`,
|
|
47
51
|
* `submitClarifyResponse`, clarify-bridge dispatch). Owns the
|
|
@@ -209,10 +213,14 @@ export declare class GatewayService {
|
|
|
209
213
|
*/
|
|
210
214
|
invokeGatewayMethod(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
211
215
|
get currentConfig(): Config;
|
|
216
|
+
get currentWorkspacePath(): string;
|
|
217
|
+
get messageBusInstance(): MessageBus;
|
|
212
218
|
/** Effective HTTP listen port (CLI `--port` override or config default). */
|
|
213
219
|
getEffectiveListenPort(): number;
|
|
214
220
|
get cronServiceInstance(): CronService;
|
|
215
221
|
get sessionIndexInstance(): SessionIndex;
|
|
222
|
+
/** Shared workflow run orchestrator + session bridge (one instance per gateway). */
|
|
223
|
+
createWorkflowRunService(): WorkflowRunService;
|
|
216
224
|
/** Process a message directly through the agent (for CLI mode). */
|
|
217
225
|
processDirect(content: string, sessionKey?: string): Promise<string>;
|
|
218
226
|
subscribe(sessionId: string, listener: (event: ServiceEvent) => Promise<void> | void): () => void;
|
|
@@ -30,6 +30,9 @@ import "../config/index.js";
|
|
|
30
30
|
import { CronService } from "../cron/service.js";
|
|
31
31
|
import "../cron/index.js";
|
|
32
32
|
import { assertGatewayAuthConfigured, extractToken, resolveGatewayAuth, validateToken } from "./auth.js";
|
|
33
|
+
import { buildWorkflowChildTools } from "../agent/workflow/workflow-child-tools.js";
|
|
34
|
+
import { WorkflowRunService } from "../workflows/service/workflow-run-service.js";
|
|
35
|
+
import { WorkflowSessionBridge } from "../workflows/service/workflow-session-bridge.js";
|
|
33
36
|
import { HeartbeatService, heartbeatRunnerConfigFromConfig } from "./heartbeat/service.js";
|
|
34
37
|
import "./heartbeat/index.js";
|
|
35
38
|
import { wireTunnelEventsToGateway } from "../tunnel/gateway-lifecycle.js";
|
|
@@ -90,6 +93,8 @@ var GatewayService = class {
|
|
|
90
93
|
lastChannelConnectDeferSource = "off";
|
|
91
94
|
readiness = new GatewayReadiness();
|
|
92
95
|
startupTrace = null;
|
|
96
|
+
workflowSessionBridge = null;
|
|
97
|
+
workflowRunServiceInstance = null;
|
|
93
98
|
/**
|
|
94
99
|
* Webchat agent invocation surface (`runAgent`, `abortAgentRun`, `steer*`,
|
|
95
100
|
* `submitClarifyResponse`, clarify-bridge dispatch). Owns the
|
|
@@ -222,6 +227,7 @@ var GatewayService = class {
|
|
|
222
227
|
},
|
|
223
228
|
extensionRegistry: this.extensionLoader?.getRegistry(),
|
|
224
229
|
getCronService: () => cronRef.service,
|
|
230
|
+
getWorkflowRunService: () => this.createWorkflowRunService(),
|
|
225
231
|
gatewayClarify: { requestClarification: (sessionKey, request) => this.agentRunner.requestClarification({
|
|
226
232
|
sessionKey,
|
|
227
233
|
request,
|
|
@@ -240,7 +246,8 @@ var GatewayService = class {
|
|
|
240
246
|
messageBus: this.bus,
|
|
241
247
|
heartbeatService: this.ensureHeartbeatService(),
|
|
242
248
|
sessionStore: this.sessionIndex.getStore(),
|
|
243
|
-
getDefaultCronAgentId: () => getDefaultAgentId(this.config)
|
|
249
|
+
getDefaultCronAgentId: () => getDefaultAgentId(this.config),
|
|
250
|
+
workflowRunService: this.createWorkflowRunService()
|
|
244
251
|
});
|
|
245
252
|
cronRef.service = this.cronService;
|
|
246
253
|
this._agentService.persistentGoals.setWebchatContinuationScheduler((sessionKey, message) => {
|
|
@@ -453,7 +460,8 @@ var GatewayService = class {
|
|
|
453
460
|
messageBus: this.bus,
|
|
454
461
|
heartbeatService: this.ensureHeartbeatService(),
|
|
455
462
|
sessionStore: this.sessionIndex.getStore(),
|
|
456
|
-
getDefaultCronAgentId: () => getDefaultAgentId(this.config)
|
|
463
|
+
getDefaultCronAgentId: () => getDefaultAgentId(this.config),
|
|
464
|
+
workflowRunService: this.createWorkflowRunService()
|
|
457
465
|
});
|
|
458
466
|
this.sessionIndex.on("sessionUpdated", (data) => {
|
|
459
467
|
this.emit("session.updated", {
|
|
@@ -904,6 +912,12 @@ var GatewayService = class {
|
|
|
904
912
|
get currentConfig() {
|
|
905
913
|
return this.config;
|
|
906
914
|
}
|
|
915
|
+
get currentWorkspacePath() {
|
|
916
|
+
return this.workspacePath;
|
|
917
|
+
}
|
|
918
|
+
get messageBusInstance() {
|
|
919
|
+
return this.bus;
|
|
920
|
+
}
|
|
907
921
|
/** Effective HTTP listen port (CLI `--port` override or config default). */
|
|
908
922
|
getEffectiveListenPort() {
|
|
909
923
|
return resolveEffectiveGatewayPort(this.config, this.serviceConfig.listenPort);
|
|
@@ -914,6 +928,18 @@ var GatewayService = class {
|
|
|
914
928
|
get sessionIndexInstance() {
|
|
915
929
|
return this.sessionIndex;
|
|
916
930
|
}
|
|
931
|
+
/** Shared workflow run orchestrator + session bridge (one instance per gateway). */
|
|
932
|
+
createWorkflowRunService() {
|
|
933
|
+
if (!this.workflowRunServiceInstance) {
|
|
934
|
+
this.workflowSessionBridge = new WorkflowSessionBridge(this);
|
|
935
|
+
this.workflowRunServiceInstance = new WorkflowRunService({
|
|
936
|
+
service: this,
|
|
937
|
+
sessionBridge: this.workflowSessionBridge,
|
|
938
|
+
buildChildTools: buildWorkflowChildTools
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
return this.workflowRunServiceInstance;
|
|
942
|
+
}
|
|
917
943
|
/** Process a message directly through the agent (for CLI mode). */
|
|
918
944
|
async processDirect(content, sessionKey = "agent:main:main") {
|
|
919
945
|
return this.agentService.turnDispatcher.processDirect(content, sessionKey);
|