botmux 3.13.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +25 -1
- package/dist/.runtime-build-id +1 -1
- package/dist/adapters/adopt-route.d.ts +58 -0
- package/dist/adapters/adopt-route.d.ts.map +1 -1
- package/dist/adapters/adopt-route.js +136 -0
- package/dist/adapters/adopt-route.js.map +1 -1
- package/dist/adapters/backend/capabilities.js +1 -1
- package/dist/adapters/backend/capabilities.js.map +1 -1
- package/dist/adapters/backend/herdr-backend.d.ts +1 -1
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +2 -1
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/riff-backend.d.ts +97 -0
- package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
- package/dist/adapters/backend/riff-backend.js +380 -66
- package/dist/adapters/backend/riff-backend.js.map +1 -1
- package/dist/adapters/backend/sandbox.d.ts +8 -0
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +16 -2
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +12 -13
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +113 -53
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +34 -3
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +72 -9
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +7 -0
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts +2 -3
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +6 -13
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.d.ts +2 -1
- package/dist/adapters/backend/zellij-observe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.js +6 -3
- package/dist/adapters/backend/zellij-observe-backend.js.map +1 -1
- package/dist/adapters/backend/zmx-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zmx-backend.js +40 -18
- package/dist/adapters/backend/zmx-backend.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +12 -1
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +3 -2
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/cursor.d.ts.map +1 -1
- package/dist/adapters/cli/cursor.js +13 -1
- package/dist/adapters/cli/cursor.js.map +1 -1
- package/dist/adapters/cli/dsh.d.ts +4 -0
- package/dist/adapters/cli/dsh.d.ts.map +1 -0
- package/dist/adapters/cli/dsh.js +100 -0
- package/dist/adapters/cli/dsh.js.map +1 -0
- package/dist/adapters/cli/fs-policy.d.ts.map +1 -1
- package/dist/adapters/cli/fs-policy.js +5 -0
- package/dist/adapters/cli/fs-policy.js.map +1 -1
- package/dist/adapters/cli/opencode.d.ts +52 -1
- package/dist/adapters/cli/opencode.d.ts.map +1 -1
- package/dist/adapters/cli/opencode.js +117 -76
- package/dist/adapters/cli/opencode.js.map +1 -1
- package/dist/adapters/cli/opencode2.d.ts +4 -0
- package/dist/adapters/cli/opencode2.d.ts.map +1 -0
- package/dist/adapters/cli/opencode2.js +137 -0
- package/dist/adapters/cli/opencode2.js.map +1 -0
- package/dist/adapters/cli/pi.d.ts +14 -1
- package/dist/adapters/cli/pi.d.ts.map +1 -1
- package/dist/adapters/cli/pi.js +21 -1
- package/dist/adapters/cli/pi.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +1 -1
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +5 -2
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/registry.d.ts +7 -1
- package/dist/adapters/cli/registry.d.ts.map +1 -1
- package/dist/adapters/cli/registry.js +19 -6
- package/dist/adapters/cli/registry.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +41 -23
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +28 -3
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/adapters/hook-command.d.ts +8 -0
- package/dist/adapters/hook-command.d.ts.map +1 -1
- package/dist/adapters/hook-command.js +11 -0
- package/dist/adapters/hook-command.js.map +1 -1
- package/dist/adapters/hook-installer.d.ts +16 -1
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +232 -2
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/bot-registry.d.ts +138 -8
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +76 -4
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/pm2-start-transaction.d.ts +14 -0
- package/dist/cli/pm2-start-transaction.d.ts.map +1 -1
- package/dist/cli/pm2-start-transaction.js +25 -9
- package/dist/cli/pm2-start-transaction.js.map +1 -1
- package/dist/cli/restart-failure-notification.d.ts +54 -0
- package/dist/cli/restart-failure-notification.d.ts.map +1 -0
- package/dist/cli/restart-failure-notification.js +142 -0
- package/dist/cli/restart-failure-notification.js.map +1 -0
- package/dist/cli/restart-failure-owner.d.ts +20 -0
- package/dist/cli/restart-failure-owner.d.ts.map +1 -0
- package/dist/cli/restart-failure-owner.js +27 -0
- package/dist/cli/restart-failure-owner.js.map +1 -0
- package/dist/cli/restart-shutdown-preflight.d.ts +24 -0
- package/dist/cli/restart-shutdown-preflight.d.ts.map +1 -0
- package/dist/cli/restart-shutdown-preflight.js +105 -0
- package/dist/cli/restart-shutdown-preflight.js.map +1 -0
- package/dist/cli/send-dispatch.d.ts +26 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -1
- package/dist/cli/send-dispatch.js +30 -0
- package/dist/cli/send-dispatch.js.map +1 -1
- package/dist/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +673 -402
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-runner.js +38 -15
- package/dist/codex-app-runner.js.map +1 -1
- package/dist/codex-rpc-lifecycle.d.ts +14 -2
- package/dist/codex-rpc-lifecycle.d.ts.map +1 -1
- package/dist/codex-rpc-lifecycle.js +13 -3
- package/dist/codex-rpc-lifecycle.js.map +1 -1
- package/dist/core/ask-broker.d.ts +3 -0
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +29 -7
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/ask-hook/registry.d.ts.map +1 -1
- package/dist/core/ask-hook/registry.js +4 -0
- package/dist/core/ask-hook/registry.js.map +1 -1
- package/dist/core/ask-types.d.ts +5 -1
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/browser-restart.d.ts +112 -0
- package/dist/core/browser-restart.d.ts.map +1 -0
- package/dist/core/browser-restart.js +233 -0
- package/dist/core/browser-restart.js.map +1 -0
- package/dist/core/command-discovery.d.ts.map +1 -1
- package/dist/core/command-discovery.js +4 -1
- package/dist/core/command-discovery.js.map +1 -1
- package/dist/core/command-handler.d.ts +19 -11
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +237 -43
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/current-turn-provenance.d.ts.map +1 -1
- package/dist/core/current-turn-provenance.js +7 -27
- package/dist/core/current-turn-provenance.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +359 -11
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dispatch-registry.d.ts.map +1 -1
- package/dist/core/dispatch-registry.js +6 -0
- package/dist/core/dispatch-registry.js.map +1 -1
- package/dist/core/dispatch-report-binding.d.ts +31 -0
- package/dist/core/dispatch-report-binding.d.ts.map +1 -0
- package/dist/core/dispatch-report-binding.js +88 -0
- package/dist/core/dispatch-report-binding.js.map +1 -0
- package/dist/core/doc-comment-prompt.d.ts +2 -0
- package/dist/core/doc-comment-prompt.d.ts.map +1 -1
- package/dist/core/doc-comment-prompt.js +6 -0
- package/dist/core/doc-comment-prompt.js.map +1 -1
- package/dist/core/host-overload-alert.d.ts +25 -1
- package/dist/core/host-overload-alert.d.ts.map +1 -1
- package/dist/core/host-overload-alert.js +69 -8
- package/dist/core/host-overload-alert.js.map +1 -1
- package/dist/core/local-terminal-opener.js +1 -1
- package/dist/core/local-terminal-opener.js.map +1 -1
- package/dist/core/managed-origin-capability.d.ts +7 -6
- package/dist/core/managed-origin-capability.d.ts.map +1 -1
- package/dist/core/managed-origin-capability.js +32 -1
- package/dist/core/managed-origin-capability.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +3 -2
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/plugins/materializer.d.ts.map +1 -1
- package/dist/core/plugins/materializer.js +1 -0
- package/dist/core/plugins/materializer.js.map +1 -1
- package/dist/core/producer-quiescence.d.ts +70 -0
- package/dist/core/producer-quiescence.d.ts.map +1 -0
- package/dist/core/producer-quiescence.js +90 -0
- package/dist/core/producer-quiescence.js.map +1 -0
- package/dist/core/raw-command-write-options.d.ts +10 -0
- package/dist/core/raw-command-write-options.d.ts.map +1 -0
- package/dist/core/raw-command-write-options.js +23 -0
- package/dist/core/raw-command-write-options.js.map +1 -0
- package/dist/core/raw-command-writer.d.ts +4 -0
- package/dist/core/raw-command-writer.d.ts.map +1 -1
- package/dist/core/raw-command-writer.js +15 -6
- package/dist/core/raw-command-writer.js.map +1 -1
- package/dist/core/reply-target.d.ts +11 -0
- package/dist/core/reply-target.d.ts.map +1 -1
- package/dist/core/reply-target.js +22 -7
- package/dist/core/reply-target.js.map +1 -1
- package/dist/core/report-session-relay.d.ts +53 -0
- package/dist/core/report-session-relay.d.ts.map +1 -0
- package/dist/core/report-session-relay.js +116 -0
- package/dist/core/report-session-relay.js.map +1 -0
- package/dist/core/session-discovery.d.ts +5 -0
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +13 -0
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-group-birth.d.ts +7 -0
- package/dist/core/session-group-birth.d.ts.map +1 -0
- package/dist/core/session-group-birth.js +145 -0
- package/dist/core/session-group-birth.js.map +1 -0
- package/dist/core/session-manager.d.ts +16 -21
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +140 -21
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/skills/frontmatter.d.ts.map +1 -1
- package/dist/core/skills/frontmatter.js +6 -1
- package/dist/core/skills/frontmatter.js.map +1 -1
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +18 -3
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +18 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +63 -11
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +262 -21
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +33 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +904 -147
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +20 -0
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +86 -1
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +9 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +5 -1
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/federation-spoke-api.d.ts.map +1 -1
- package/dist/dashboard/federation-spoke-api.js +27 -3
- package/dist/dashboard/federation-spoke-api.js.map +1 -1
- package/dist/dashboard/feedback-analytics-api.d.ts +6 -0
- package/dist/dashboard/feedback-analytics-api.d.ts.map +1 -0
- package/dist/dashboard/feedback-analytics-api.js +39 -0
- package/dist/dashboard/feedback-analytics-api.js.map +1 -0
- package/dist/dashboard/session-action-timeout.d.ts +3 -0
- package/dist/dashboard/session-action-timeout.d.ts.map +1 -0
- package/dist/dashboard/session-action-timeout.js +12 -0
- package/dist/dashboard/session-action-timeout.js.map +1 -0
- package/dist/dashboard/vc-consumer-profiles-api.d.ts +9 -3
- package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
- package/dist/dashboard/vc-consumer-profiles-api.js +2 -1
- package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +48 -5
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +11 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +368 -36
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +8 -0
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
- package/dist/dashboard/web/dashboard-routes.js +1 -0
- package/dist/dashboard/web/dashboard-routes.js.map +1 -1
- package/dist/dashboard/web/feedback-page.d.ts +3 -0
- package/dist/dashboard/web/feedback-page.d.ts.map +1 -0
- package/dist/dashboard/web/feedback-page.js +34 -0
- package/dist/dashboard/web/feedback-page.js.map +1 -0
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +7 -2
- package/dist/dashboard/web/groups-page.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +122 -22
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/skills-page.d.ts.map +1 -1
- package/dist/dashboard/web/skills-page.js +4 -2
- package/dist/dashboard/web/skills-page.js.map +1 -1
- package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
- package/dist/dashboard/web/team-federation-page.js +26 -2
- package/dist/dashboard/web/team-federation-page.js.map +1 -1
- package/dist/dashboard/web/team-federation.d.ts +19 -0
- package/dist/dashboard/web/team-federation.d.ts.map +1 -1
- package/dist/dashboard/web/team-federation.js +3 -0
- package/dist/dashboard/web/team-federation.js.map +1 -1
- package/dist/dashboard/web/update-action.d.ts +8 -0
- package/dist/dashboard/web/update-action.d.ts.map +1 -1
- package/dist/dashboard/web/update-action.js +10 -0
- package/dist/dashboard/web/update-action.js.map +1 -1
- package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
- package/dist/dashboard/web/vc-consumer-profiles-section.js +58 -9
- package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-65SMZ4QY.js +10 -0
- package/dist/dashboard-web/chunks/chunk-G5XGBCM6.js +1 -0
- package/dist/dashboard-web/chunks/chunk-IKN6WURK.js +1 -0
- package/dist/dashboard-web/chunks/{chunk-6JTZJRQJ.js → chunk-VMWNR3SV.js} +15 -15
- package/dist/dashboard-web/chunks/{chunk-CU4SUKK3.js → chunk-Z7B3E3US.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-5QJB24I2.js → chunk-Z7DO42GJ.js} +1 -1
- package/dist/dashboard-web/chunks/connectors-page-CPH6VVFY.js +1 -0
- package/dist/dashboard-web/chunks/feedback-page-J2FKQ3LK.js +1 -0
- package/dist/dashboard-web/chunks/groups-page-VDOOLS4M.js +8 -0
- package/dist/dashboard-web/chunks/{insights-page-EVGGJI5L.js → insights-page-A2LWTKJD.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-HRGR2ENP.js → monitor-room-2YR752E3.js} +1 -1
- package/dist/dashboard-web/chunks/monitoring-page-BGYSKZGM.js +2 -0
- package/dist/dashboard-web/chunks/office-page-ZZHTEAE4.js +1 -0
- package/dist/dashboard-web/chunks/overview-page-7NYPKQSB.js +1 -0
- package/dist/dashboard-web/chunks/roles-page-YUC4YLJG.js +1 -0
- package/dist/dashboard-web/chunks/schedules-page-2L2NKTDJ.js +1 -0
- package/dist/dashboard-web/chunks/{sessions-page-DXBCDWCS.js → sessions-page-DYZZMKFL.js} +1 -1
- package/dist/dashboard-web/chunks/settings-page-LIULUSMH.js +2 -0
- package/dist/dashboard-web/chunks/{skills-page-2QAVB2SA.js → skills-page-DQPBBI3T.js} +2 -2
- package/dist/dashboard-web/chunks/team-federation-page-LQKLDV4K.js +1 -0
- package/dist/dashboard-web/chunks/{v3-page-AY35LKCX.js → v3-page-RVKQIMAP.js} +1 -1
- package/dist/dashboard-web/chunks/whiteboards-page-S5K55HRZ.js +1 -0
- package/dist/dashboard-web/style.css +35 -0
- package/dist/dashboard.js +210 -13
- package/dist/dashboard.js.map +1 -1
- package/dist/desktop/shared/shell-path-probes.d.ts +1 -1
- package/dist/desktop/shared/shell-path-probes.js +2 -2
- package/dist/desktop/shared/shell-path-probes.js.map +1 -1
- package/dist/dsh-runner.d.ts +3 -0
- package/dist/dsh-runner.d.ts.map +1 -0
- package/dist/dsh-runner.js +673 -0
- package/dist/dsh-runner.js.map +1 -0
- package/dist/global-config.d.ts +24 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +35 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +39 -31
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +39 -31
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/ask-card.d.ts +3 -1
- package/dist/im/lark/ask-card.d.ts.map +1 -1
- package/dist/im/lark/ask-card.js +61 -5
- package/dist/im/lark/ask-card.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +13 -6
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +2 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +145 -5
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +18 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +41 -28
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/md-card.d.ts +18 -0
- package/dist/im/lark/md-card.d.ts.map +1 -1
- package/dist/im/lark/md-card.js +21 -0
- package/dist/im/lark/md-card.js.map +1 -1
- package/dist/im/lark/relay-target-routing.d.ts.map +1 -1
- package/dist/im/lark/relay-target-routing.js +3 -2
- package/dist/im/lark/relay-target-routing.js.map +1 -1
- package/dist/im/lark/reply-mode-command.d.ts.map +1 -1
- package/dist/im/lark/reply-mode-command.js +19 -8
- package/dist/im/lark/reply-mode-command.js.map +1 -1
- package/dist/im/lark/skill-feedback-card.d.ts +15 -0
- package/dist/im/lark/skill-feedback-card.d.ts.map +1 -0
- package/dist/im/lark/skill-feedback-card.js +149 -0
- package/dist/im/lark/skill-feedback-card.js.map +1 -0
- package/dist/services/bot-config-store.d.ts +10 -1
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +103 -3
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/bridge-fallback-gate.d.ts +29 -7
- package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
- package/dist/services/bridge-fallback-gate.js +58 -16
- package/dist/services/bridge-fallback-gate.js.map +1 -1
- package/dist/services/codex-reasoning-effort.d.ts +9 -0
- package/dist/services/codex-reasoning-effort.d.ts.map +1 -0
- package/dist/services/codex-reasoning-effort.js +23 -0
- package/dist/services/codex-reasoning-effort.js.map +1 -0
- package/dist/services/codex-service-tier.d.ts +1 -1
- package/dist/services/codex-service-tier.d.ts.map +1 -1
- package/dist/services/codex-transcript.d.ts +9 -0
- package/dist/services/codex-transcript.d.ts.map +1 -1
- package/dist/services/codex-transcript.js +13 -6
- package/dist/services/codex-transcript.js.map +1 -1
- package/dist/services/feed-group-tagger.d.ts +15 -0
- package/dist/services/feed-group-tagger.d.ts.map +1 -0
- package/dist/services/feed-group-tagger.js +348 -0
- package/dist/services/feed-group-tagger.js.map +1 -0
- package/dist/services/feedback-analytics.d.ts +82 -0
- package/dist/services/feedback-analytics.d.ts.map +1 -0
- package/dist/services/feedback-analytics.js +95 -0
- package/dist/services/feedback-analytics.js.map +1 -0
- package/dist/services/feedback-outbox.d.ts +24 -0
- package/dist/services/feedback-outbox.d.ts.map +1 -0
- package/dist/services/feedback-outbox.js +12 -0
- package/dist/services/feedback-outbox.js.map +1 -0
- package/dist/services/feedback-policy-resolver.d.ts +56 -0
- package/dist/services/feedback-policy-resolver.d.ts.map +1 -0
- package/dist/services/feedback-policy-resolver.js +156 -0
- package/dist/services/feedback-policy-resolver.js.map +1 -0
- package/dist/services/feedback-policy.d.ts +41 -0
- package/dist/services/feedback-policy.d.ts.map +1 -0
- package/dist/services/feedback-policy.js +102 -0
- package/dist/services/feedback-policy.js.map +1 -0
- package/dist/services/feedback-webhook-dispatcher.d.ts +76 -0
- package/dist/services/feedback-webhook-dispatcher.d.ts.map +1 -0
- package/dist/services/feedback-webhook-dispatcher.js +237 -0
- package/dist/services/feedback-webhook-dispatcher.js.map +1 -0
- package/dist/services/grant-prefs-store.d.ts +3 -0
- package/dist/services/grant-prefs-store.d.ts.map +1 -1
- package/dist/services/grant-prefs-store.js +30 -1
- package/dist/services/grant-prefs-store.js.map +1 -1
- package/dist/services/groups-store.d.ts +14 -0
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +23 -0
- package/dist/services/groups-store.js.map +1 -1
- package/dist/services/local-cli-opener.d.ts +1 -1
- package/dist/services/local-cli-opener.d.ts.map +1 -1
- package/dist/services/local-cli-opener.js +2 -0
- package/dist/services/local-cli-opener.js.map +1 -1
- package/dist/services/peer-cross-ref-store.d.ts +8 -0
- package/dist/services/peer-cross-ref-store.d.ts.map +1 -0
- package/dist/services/peer-cross-ref-store.js +95 -0
- package/dist/services/peer-cross-ref-store.js.map +1 -0
- package/dist/services/pi-transcript.d.ts.map +1 -1
- package/dist/services/pi-transcript.js +10 -6
- package/dist/services/pi-transcript.js.map +1 -1
- package/dist/services/prompt-context-store.d.ts +29 -0
- package/dist/services/prompt-context-store.d.ts.map +1 -0
- package/dist/services/prompt-context-store.js +205 -0
- package/dist/services/prompt-context-store.js.map +1 -0
- package/dist/services/session-group-avatar.d.ts +6 -0
- package/dist/services/session-group-avatar.d.ts.map +1 -0
- package/dist/services/session-group-avatar.js +96 -0
- package/dist/services/session-group-avatar.js.map +1 -0
- package/dist/services/session-group-title.d.ts +47 -0
- package/dist/services/session-group-title.d.ts.map +1 -0
- package/dist/services/session-group-title.js +226 -0
- package/dist/services/session-group-title.js.map +1 -0
- package/dist/services/session-groups-store.d.ts +30 -0
- package/dist/services/session-groups-store.d.ts.map +1 -0
- package/dist/services/session-groups-store.js +132 -0
- package/dist/services/session-groups-store.js.map +1 -0
- package/dist/services/session-store.d.ts +58 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +174 -0
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/skill-feedback-store.d.ts +252 -0
- package/dist/services/skill-feedback-store.d.ts.map +1 -0
- package/dist/services/skill-feedback-store.js +795 -0
- package/dist/services/skill-feedback-store.js.map +1 -0
- package/dist/services/skill-registry-store.d.ts +23 -0
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +93 -7
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/structured-bridge-clis.d.ts +13 -4
- package/dist/services/structured-bridge-clis.d.ts.map +1 -1
- package/dist/services/structured-bridge-clis.js +13 -3
- package/dist/services/structured-bridge-clis.js.map +1 -1
- package/dist/services/team-store.d.ts +4 -0
- package/dist/services/team-store.d.ts.map +1 -1
- package/dist/services/team-store.js +16 -0
- package/dist/services/team-store.js.map +1 -1
- package/dist/services/traex-transcript.d.ts +32 -4
- package/dist/services/traex-transcript.d.ts.map +1 -1
- package/dist/services/traex-transcript.js +116 -8
- package/dist/services/traex-transcript.js.map +1 -1
- package/dist/services/trigger-types.d.ts +1 -1
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +2 -2
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/services/turn-completion-events.d.ts +57 -0
- package/dist/services/turn-completion-events.d.ts.map +1 -0
- package/dist/services/turn-completion-events.js +179 -0
- package/dist/services/turn-completion-events.js.map +1 -0
- package/dist/services/vc-meeting-consumer-isolation.d.ts +21 -12
- package/dist/services/vc-meeting-consumer-isolation.d.ts.map +1 -1
- package/dist/services/vc-meeting-consumer-isolation.js +25 -19
- package/dist/services/vc-meeting-consumer-isolation.js.map +1 -1
- package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts +8 -2
- package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts.map +1 -1
- package/dist/services/vc-meeting-consumer-profile-bootstrap.js +18 -10
- package/dist/services/vc-meeting-consumer-profile-bootstrap.js.map +1 -1
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +6 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/setup/cli-availability.d.ts +2 -1
- package/dist/setup/cli-availability.d.ts.map +1 -1
- package/dist/setup/cli-availability.js +2 -1
- package/dist/setup/cli-availability.js.map +1 -1
- package/dist/setup/cli-selection.d.ts +8 -0
- package/dist/setup/cli-selection.d.ts.map +1 -1
- package/dist/setup/cli-selection.js +31 -0
- package/dist/setup/cli-selection.js.map +1 -1
- package/dist/setup/ensure-fonts.d.ts +5 -0
- package/dist/setup/ensure-fonts.d.ts.map +1 -1
- package/dist/setup/ensure-fonts.js +26 -1
- package/dist/setup/ensure-fonts.js.map +1 -1
- package/dist/setup/lark-scopes.json +3 -1
- package/dist/setup/open-platform-automation.d.ts +1 -1
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +15 -3
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/skills/definitions.d.ts +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +9 -2
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +11 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/bot-routing.d.ts +1 -1
- package/dist/utils/bot-routing.d.ts.map +1 -1
- package/dist/utils/bot-routing.js.map +1 -1
- package/dist/utils/user-token.d.ts +38 -0
- package/dist/utils/user-token.d.ts.map +1 -1
- package/dist/utils/user-token.js +76 -1
- package/dist/utils/user-token.js.map +1 -1
- package/dist/utils/web-terminal-scroll.d.ts +27 -0
- package/dist/utils/web-terminal-scroll.d.ts.map +1 -0
- package/dist/utils/web-terminal-scroll.js +60 -0
- package/dist/utils/web-terminal-scroll.js.map +1 -0
- package/dist/vc-agent/polling-source.d.ts +70 -0
- package/dist/vc-agent/polling-source.d.ts.map +1 -1
- package/dist/vc-agent/polling-source.js +115 -0
- package/dist/vc-agent/polling-source.js.map +1 -1
- package/dist/worker.js +469 -86
- package/dist/worker.js.map +1 -1
- package/dist/workflows/definition.d.ts +22 -22
- package/dist/workflows/events/payloads.d.ts +8 -8
- package/dist/workflows/events/schema.d.ts +20 -20
- package/dist/workflows/migration/v2-run-archive-schema.d.ts +20 -20
- package/package.json +5 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-NQJFJ45Z.js +0 -10
- package/dist/dashboard-web/chunks/chunk-KEEYQ3WF.js +0 -1
- package/dist/dashboard-web/chunks/connectors-page-HADHZXXZ.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-5TYF46RS.js +0 -8
- package/dist/dashboard-web/chunks/monitoring-page-LBU7TTTW.js +0 -2
- package/dist/dashboard-web/chunks/office-page-DF6RRBMN.js +0 -1
- package/dist/dashboard-web/chunks/overview-page-ZB6QH2EX.js +0 -1
- package/dist/dashboard-web/chunks/roles-page-ZBZGHIIF.js +0 -1
- package/dist/dashboard-web/chunks/schedules-page-NQVQC3QY.js +0 -1
- package/dist/dashboard-web/chunks/settings-page-E7NN4NVQ.js +0 -2
- package/dist/dashboard-web/chunks/team-federation-page-VGWL53AE.js +0 -1
- package/dist/dashboard-web/chunks/whiteboards-page-BNWUU4KG.js +0 -1
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { normalizeFeedbackPolicy } from './feedback-policy.js';
|
|
5
|
+
import { effectiveWebhookDestinations } from './feedback-outbox.js';
|
|
6
|
+
/** True when a node:sqlite error is the recoverable write-lock contention that
|
|
7
|
+
* a burst of concurrent cold-start opens against a shared dataDir produces. */
|
|
8
|
+
function isSqliteBusyError(error) {
|
|
9
|
+
const code = error?.code;
|
|
10
|
+
if (code === 'ERR_SQLITE_ERROR' || code === 'SQLITE_BUSY' || code === 'SQLITE_LOCKED') {
|
|
11
|
+
// ERR_SQLITE_ERROR is generic; fall through to the message check for it.
|
|
12
|
+
if (code !== 'ERR_SQLITE_ERROR')
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
const message = String(error?.message ?? error).toLowerCase();
|
|
16
|
+
return message.includes('database is locked') || message.includes('database table is locked')
|
|
17
|
+
|| message.includes('sqlite_busy') || message.includes('sqlite_locked');
|
|
18
|
+
}
|
|
19
|
+
/** Synchronous backoff sleep (the store constructor / migration path is sync).
|
|
20
|
+
* Uses Atomics.wait on a throwaway buffer so it yields the CPU instead of
|
|
21
|
+
* spinning. Backoff grows with the attempt, capped, with jitter to de-sync
|
|
22
|
+
* concurrent cold-start racers. */
|
|
23
|
+
function sleepShort(attempt) {
|
|
24
|
+
const base = Math.min(20 * attempt, 120);
|
|
25
|
+
const jitter = Math.floor((Number(process.hrtime.bigint() % 17n))); // 0..16ms, no Math.random needed
|
|
26
|
+
try {
|
|
27
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, base + jitter);
|
|
28
|
+
}
|
|
29
|
+
catch { /* Atomics.wait unavailable (should not happen in Node); fall through */ }
|
|
30
|
+
}
|
|
31
|
+
function id(prefix) {
|
|
32
|
+
return `${prefix}_${randomUUID().replaceAll('-', '')}`;
|
|
33
|
+
}
|
|
34
|
+
function contentHash(content) {
|
|
35
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
36
|
+
}
|
|
37
|
+
function stableId(prefix, ...parts) {
|
|
38
|
+
return `${prefix}_${createHash('sha256').update(JSON.stringify(parts)).digest('hex')}`;
|
|
39
|
+
}
|
|
40
|
+
/** Persist only the structural card tail needed to rebuild feedback state. */
|
|
41
|
+
function feedbackCardTemplate(baseCard) {
|
|
42
|
+
if (!baseCard)
|
|
43
|
+
return undefined;
|
|
44
|
+
const card = structuredClone(baseCard);
|
|
45
|
+
const elements = Array.isArray(card.body?.elements) ? card.body.elements : [];
|
|
46
|
+
const feedbackIndex = elements.findIndex((element) => element?.element_id === 'botmux_feedback');
|
|
47
|
+
if (feedbackIndex < 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
// The platform already owns the answer body. Keeping only feedback and the
|
|
50
|
+
// stable footer prevents analytics storage from becoming an answer archive.
|
|
51
|
+
card.body.elements = elements.slice(feedbackIndex);
|
|
52
|
+
return card;
|
|
53
|
+
}
|
|
54
|
+
const SCHEMA_VERSION = 7;
|
|
55
|
+
const DELIVERY_V7_SCHEMA = `
|
|
56
|
+
ALTER TABLE deliveries ADD COLUMN correlation_discriminator TEXT NOT NULL DEFAULT '';
|
|
57
|
+
DROP INDEX deliveries_correlation_identity;
|
|
58
|
+
CREATE UNIQUE INDEX deliveries_correlation_identity
|
|
59
|
+
ON deliveries(bot_app_id, session_id, turn_id, IFNULL(native_session_id,''), IFNULL(dispatch_attempt,0), correlation_discriminator, response_id)
|
|
60
|
+
WHERE bot_app_id IS NOT NULL AND session_id IS NOT NULL AND turn_id IS NOT NULL;
|
|
61
|
+
`;
|
|
62
|
+
const ANALYTICS_V6_SCHEMA = `
|
|
63
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_created ON deliveries(created_at, delivery_id);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_bot_created ON deliveries(bot_app_id, created_at, delivery_id);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_chat_created ON deliveries(chat_id, created_at, delivery_id);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_topic_created ON deliveries(topic_root_id, created_at, delivery_id);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_status_completed ON deliveries(status, completed_at, delivery_id);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_model_created ON deliveries(model, created_at, delivery_id);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_cli_created ON deliveries(cli_id, created_at, delivery_id);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_skill_created ON deliveries(skill_name, skill_version, created_at, delivery_id);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS deliveries_analytics_workflow_task_created ON deliveries(workflow_id, task_id, created_at, delivery_id);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS feedback_revisions_analytics_latest ON feedback_revisions(delivery_id, operator_subject_id, revision DESC);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS feedback_revisions_analytics_semantic_created ON feedback_revisions(semantic, created_at, delivery_id);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS feedback_revisions_analytics_verdict_created ON feedback_revisions(result, created_at, delivery_id);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS feedback_revisions_analytics_reason_created ON feedback_revisions(reason_key, created_at, delivery_id);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS feedback_outbox_analytics_status_created ON feedback_outbox(status, created_at, outbox_id);
|
|
77
|
+
`;
|
|
78
|
+
const OUTBOX_V5_SCHEMA = `
|
|
79
|
+
ALTER TABLE deliveries ADD COLUMN webhook_destinations_json TEXT;
|
|
80
|
+
CREATE TABLE feedback_events (
|
|
81
|
+
event_id TEXT PRIMARY KEY,
|
|
82
|
+
event_type TEXT NOT NULL CHECK(event_type IN ('turn.completed','feedback.revised')),
|
|
83
|
+
version INTEGER NOT NULL CHECK(version=1), aggregate_id TEXT NOT NULL,
|
|
84
|
+
payload_json TEXT NOT NULL, created_at TEXT NOT NULL
|
|
85
|
+
);
|
|
86
|
+
CREATE TABLE feedback_outbox (
|
|
87
|
+
outbox_id TEXT PRIMARY KEY,
|
|
88
|
+
event_id TEXT NOT NULL REFERENCES feedback_events(event_id),
|
|
89
|
+
destination_id TEXT NOT NULL, destination_json TEXT NOT NULL,
|
|
90
|
+
status TEXT NOT NULL CHECK(status IN ('pending','inflight','delivered','failed')),
|
|
91
|
+
attempts INTEGER NOT NULL DEFAULT 0, next_attempt_at INTEGER NOT NULL,
|
|
92
|
+
claimed_at INTEGER, claim_token TEXT, last_http_status INTEGER, last_error TEXT,
|
|
93
|
+
delivered_at TEXT, created_at TEXT NOT NULL,
|
|
94
|
+
UNIQUE(event_id,destination_id)
|
|
95
|
+
);
|
|
96
|
+
CREATE INDEX feedback_outbox_due ON feedback_outbox(status,next_attempt_at);
|
|
97
|
+
`;
|
|
98
|
+
const COMPLETION_V4_SCHEMA = `
|
|
99
|
+
CREATE TABLE turn_terminals (
|
|
100
|
+
bot_app_id TEXT NOT NULL, session_id TEXT NOT NULL, turn_id TEXT NOT NULL,
|
|
101
|
+
dispatch_attempt INTEGER NOT NULL DEFAULT 0,
|
|
102
|
+
status TEXT NOT NULL CHECK(status IN ('completed','failed','cancelled','ambiguous')),
|
|
103
|
+
completed_at TEXT NOT NULL, duration_ms INTEGER, usage_json TEXT,
|
|
104
|
+
PRIMARY KEY(bot_app_id,session_id,turn_id,dispatch_attempt)
|
|
105
|
+
);
|
|
106
|
+
CREATE TABLE turn_completion_events (
|
|
107
|
+
event_id TEXT PRIMARY KEY,
|
|
108
|
+
delivery_id TEXT NOT NULL UNIQUE REFERENCES deliveries(delivery_id),
|
|
109
|
+
event_type TEXT NOT NULL CHECK(event_type='turn.completed'),
|
|
110
|
+
version INTEGER NOT NULL CHECK(version=1), payload_json TEXT NOT NULL, created_at TEXT NOT NULL
|
|
111
|
+
);
|
|
112
|
+
`;
|
|
113
|
+
const DELIVERY_V3_COLUMNS = `
|
|
114
|
+
ALTER TABLE deliveries ADD COLUMN bot_app_id TEXT;
|
|
115
|
+
ALTER TABLE deliveries ADD COLUMN session_id TEXT;
|
|
116
|
+
ALTER TABLE deliveries ADD COLUMN turn_id TEXT;
|
|
117
|
+
ALTER TABLE deliveries ADD COLUMN native_session_id TEXT;
|
|
118
|
+
ALTER TABLE deliveries ADD COLUMN dispatch_attempt INTEGER;
|
|
119
|
+
ALTER TABLE deliveries ADD COLUMN chat_id TEXT;
|
|
120
|
+
ALTER TABLE deliveries ADD COLUMN topic_root_id TEXT;
|
|
121
|
+
ALTER TABLE deliveries ADD COLUMN scope TEXT;
|
|
122
|
+
ALTER TABLE deliveries ADD COLUMN workflow_id TEXT;
|
|
123
|
+
ALTER TABLE deliveries ADD COLUMN task_id TEXT;
|
|
124
|
+
ALTER TABLE deliveries ADD COLUMN parent_task_id TEXT;
|
|
125
|
+
ALTER TABLE deliveries ADD COLUMN cli_id TEXT;
|
|
126
|
+
ALTER TABLE deliveries ADD COLUMN cli_version TEXT;
|
|
127
|
+
ALTER TABLE deliveries ADD COLUMN model TEXT;
|
|
128
|
+
ALTER TABLE deliveries ADD COLUMN reasoning_effort TEXT;
|
|
129
|
+
ALTER TABLE deliveries ADD COLUMN skill_name TEXT;
|
|
130
|
+
ALTER TABLE deliveries ADD COLUMN skill_version TEXT;
|
|
131
|
+
ALTER TABLE deliveries ADD COLUMN card_mode TEXT;
|
|
132
|
+
ALTER TABLE deliveries ADD COLUMN status TEXT;
|
|
133
|
+
ALTER TABLE deliveries ADD COLUMN duration_ms INTEGER;
|
|
134
|
+
ALTER TABLE deliveries ADD COLUMN usage_json TEXT;
|
|
135
|
+
ALTER TABLE deliveries ADD COLUMN completed_at TEXT;
|
|
136
|
+
CREATE UNIQUE INDEX deliveries_correlation_identity
|
|
137
|
+
ON deliveries(bot_app_id, session_id, turn_id, IFNULL(native_session_id,''), IFNULL(dispatch_attempt,0), response_id)
|
|
138
|
+
WHERE bot_app_id IS NOT NULL AND session_id IS NOT NULL AND turn_id IS NOT NULL;
|
|
139
|
+
`;
|
|
140
|
+
/** Fresh-build DDL for a brand-new DB (version 0 → 7). No BEGIN/COMMIT/PRAGMA:
|
|
141
|
+
* migrateStep() owns the transaction and the user_version bump. Base tables use
|
|
142
|
+
* IF NOT EXISTS so a loser that somehow re-enters is a no-op on them; the later
|
|
143
|
+
* ${...} fragments (bare ALTER/CREATE) are guarded by migrateStep re-reading
|
|
144
|
+
* user_version under the write lock, so the loser never reaches this SQL. */
|
|
145
|
+
const FRESH_V7_SCHEMA = `
|
|
146
|
+
CREATE TABLE IF NOT EXISTS interactions (
|
|
147
|
+
interaction_id TEXT PRIMARY KEY,
|
|
148
|
+
context_json TEXT,
|
|
149
|
+
created_at TEXT NOT NULL
|
|
150
|
+
);
|
|
151
|
+
CREATE TABLE IF NOT EXISTS skill_runs (
|
|
152
|
+
skill_run_id TEXT PRIMARY KEY,
|
|
153
|
+
interaction_id TEXT NOT NULL REFERENCES interactions(interaction_id),
|
|
154
|
+
skill_ref TEXT NOT NULL,
|
|
155
|
+
context_json TEXT,
|
|
156
|
+
created_at TEXT NOT NULL
|
|
157
|
+
);
|
|
158
|
+
CREATE TABLE IF NOT EXISTS responses (
|
|
159
|
+
response_id TEXT PRIMARY KEY,
|
|
160
|
+
interaction_id TEXT NOT NULL REFERENCES interactions(interaction_id),
|
|
161
|
+
skill_run_id TEXT REFERENCES skill_runs(skill_run_id),
|
|
162
|
+
content_hash TEXT NOT NULL,
|
|
163
|
+
content_ref TEXT,
|
|
164
|
+
created_at TEXT NOT NULL
|
|
165
|
+
);
|
|
166
|
+
CREATE UNIQUE INDEX IF NOT EXISTS responses_identity
|
|
167
|
+
ON responses(interaction_id, content_hash);
|
|
168
|
+
CREATE TABLE IF NOT EXISTS deliveries (
|
|
169
|
+
delivery_id TEXT PRIMARY KEY,
|
|
170
|
+
response_id TEXT NOT NULL REFERENCES responses(response_id),
|
|
171
|
+
platform TEXT NOT NULL,
|
|
172
|
+
platform_message_id TEXT NOT NULL,
|
|
173
|
+
platform_app_id TEXT NOT NULL,
|
|
174
|
+
level TEXT NOT NULL DEFAULT 'L1' CHECK(level IN ('L0','L1','L2')),
|
|
175
|
+
policy_snapshot_json TEXT,
|
|
176
|
+
base_card_json TEXT,
|
|
177
|
+
requester_subject_id TEXT,
|
|
178
|
+
context_json TEXT,
|
|
179
|
+
created_at TEXT NOT NULL,
|
|
180
|
+
UNIQUE(platform, platform_app_id, platform_message_id)
|
|
181
|
+
);
|
|
182
|
+
CREATE TABLE IF NOT EXISTS feedback_revisions (
|
|
183
|
+
feedback_id TEXT PRIMARY KEY,
|
|
184
|
+
delivery_id TEXT NOT NULL REFERENCES deliveries(delivery_id),
|
|
185
|
+
operator_subject_id TEXT NOT NULL,
|
|
186
|
+
revision INTEGER NOT NULL,
|
|
187
|
+
result TEXT NOT NULL,
|
|
188
|
+
semantic TEXT,
|
|
189
|
+
reason_key TEXT,
|
|
190
|
+
comment_text TEXT,
|
|
191
|
+
callback_key TEXT NOT NULL UNIQUE,
|
|
192
|
+
supersedes_feedback_id TEXT REFERENCES feedback_revisions(feedback_id),
|
|
193
|
+
created_at TEXT NOT NULL,
|
|
194
|
+
UNIQUE(delivery_id, operator_subject_id, revision)
|
|
195
|
+
);
|
|
196
|
+
${DELIVERY_V3_COLUMNS}
|
|
197
|
+
${COMPLETION_V4_SCHEMA}
|
|
198
|
+
${OUTBOX_V5_SCHEMA}
|
|
199
|
+
${ANALYTICS_V6_SCHEMA}
|
|
200
|
+
${DELIVERY_V7_SCHEMA}
|
|
201
|
+
`;
|
|
202
|
+
/** Legacy v1→v2 columns (no BEGIN/COMMIT/PRAGMA — migrateStep owns those). */
|
|
203
|
+
const MIGRATE_V1_TO_V2 = `
|
|
204
|
+
ALTER TABLE deliveries ADD COLUMN policy_snapshot_json TEXT;
|
|
205
|
+
ALTER TABLE deliveries ADD COLUMN base_card_json TEXT;
|
|
206
|
+
ALTER TABLE deliveries ADD COLUMN requester_subject_id TEXT;
|
|
207
|
+
ALTER TABLE feedback_revisions ADD COLUMN comment_text TEXT;
|
|
208
|
+
`;
|
|
209
|
+
/** Legacy v4→v5: semantic column + outbox tables + backfill events. */
|
|
210
|
+
const MIGRATE_V4_TO_V5 = `
|
|
211
|
+
ALTER TABLE feedback_revisions ADD COLUMN semantic TEXT;
|
|
212
|
+
${OUTBOX_V5_SCHEMA}
|
|
213
|
+
INSERT OR IGNORE INTO feedback_events(event_id,event_type,version,aggregate_id,payload_json,created_at)
|
|
214
|
+
SELECT event_id,event_type,version,delivery_id,payload_json,created_at FROM turn_completion_events;
|
|
215
|
+
`;
|
|
216
|
+
export class SkillFeedbackStore {
|
|
217
|
+
path;
|
|
218
|
+
db;
|
|
219
|
+
constructor(dataDir, db) {
|
|
220
|
+
mkdirSync(dataDir, { recursive: true });
|
|
221
|
+
this.path = join(dataDir, 'botmux-feedback.sqlite');
|
|
222
|
+
this.db = db;
|
|
223
|
+
// Connection init ordering matters under a shared dataDir: busy_timeout MUST
|
|
224
|
+
// be set FIRST so every subsequent lock acquisition (including the WAL-mode
|
|
225
|
+
// switch, which takes a write lock to rewrite the DB header on a fresh file)
|
|
226
|
+
// is protected. journal_mode=WAL is issued separately and wrapped in a
|
|
227
|
+
// bounded busy retry, because on a brand-new file many cold-start processes
|
|
228
|
+
// race for that first write lock and would otherwise fail open with
|
|
229
|
+
// `database is locked` before ever reaching the migration path.
|
|
230
|
+
this.db.exec('PRAGMA busy_timeout=5000;');
|
|
231
|
+
this.db.exec('PRAGMA foreign_keys=ON;');
|
|
232
|
+
this.enableWalWithRetry();
|
|
233
|
+
const version = Number(this.db.prepare('PRAGMA user_version').get()?.user_version ?? 0);
|
|
234
|
+
if (version > SCHEMA_VERSION) {
|
|
235
|
+
throw new Error(`skill_feedback_schema_newer:${version}`);
|
|
236
|
+
}
|
|
237
|
+
// Fast path: an already-current DB must NOT enter the migration loop — that
|
|
238
|
+
// would take (and re-take) the write lock only to no-op, turning every plain
|
|
239
|
+
// `botmux send` open into a writer and amplifying the very lock contention
|
|
240
|
+
// we are fixing. Only a stale reader falls through to guarded migration.
|
|
241
|
+
if (version < SCHEMA_VERSION)
|
|
242
|
+
this.runMigrations();
|
|
243
|
+
this.validateSchemaV1();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Cross-process-safe migration. Multiple OS processes (each PM2 bot daemon
|
|
247
|
+
* plus every `botmux send` subprocess) legitimately open the SAME
|
|
248
|
+
* botmux-feedback.sqlite under a shared dataDir at cold start. The version is
|
|
249
|
+
* therefore re-read INSIDE each step's BEGIN IMMEDIATE write lock: SQLite
|
|
250
|
+
* serializes the writers, so a loser that raced in at version 0 sees the
|
|
251
|
+
* winner's committed version and skips — instead of re-running bare
|
|
252
|
+
* `ALTER TABLE ADD COLUMN` (no IF NOT EXISTS) and throwing `duplicate column`.
|
|
253
|
+
* `BEGIN IMMEDIATE` acquisition is retried on `database is locked` beyond the
|
|
254
|
+
* 5s busy_timeout so a burst of concurrent cold starts settles rather than
|
|
255
|
+
* failing the open (which would poison the module-level store cache).
|
|
256
|
+
*/
|
|
257
|
+
runMigrations() {
|
|
258
|
+
// Each step: [applicableFrom, applicableToInclusive, targetVersion, sql].
|
|
259
|
+
// Guarded so it only runs when the version RE-READ under the write lock is
|
|
260
|
+
// still within [from,to]; the fresh-build step (from 0) creates the full v7
|
|
261
|
+
// schema, later steps are incremental ALTER/CREATE for legacy DBs.
|
|
262
|
+
const steps = [
|
|
263
|
+
{ from: 0, to: 0, target: 7, sql: FRESH_V7_SCHEMA },
|
|
264
|
+
{ from: 1, to: 1, target: 2, sql: MIGRATE_V1_TO_V2 },
|
|
265
|
+
{ from: 1, to: 2, target: 3, sql: DELIVERY_V3_COLUMNS },
|
|
266
|
+
{ from: 1, to: 3, target: 4, sql: COMPLETION_V4_SCHEMA },
|
|
267
|
+
{ from: 1, to: 4, target: 5, sql: MIGRATE_V4_TO_V5 },
|
|
268
|
+
{ from: 1, to: 5, target: 6, sql: ANALYTICS_V6_SCHEMA },
|
|
269
|
+
{ from: 1, to: 6, target: 7, sql: DELIVERY_V7_SCHEMA },
|
|
270
|
+
];
|
|
271
|
+
for (const step of steps) {
|
|
272
|
+
// Stop as soon as the DB is fully migrated: a loser that lost every race
|
|
273
|
+
// sees SCHEMA_VERSION on its first guarded step and exits, instead of
|
|
274
|
+
// taking the write lock once per remaining step only to no-op each time.
|
|
275
|
+
if (this.migrateStep(step.from, step.to, step.target, step.sql) >= SCHEMA_VERSION)
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Apply one migration step atomically and idempotently across processes.
|
|
281
|
+
* Re-reads user_version after acquiring the write lock and only mutates when
|
|
282
|
+
* still in the step's applicable range, so a process that lost the race is a
|
|
283
|
+
* no-op instead of replaying DDL the winner already committed. Returns the
|
|
284
|
+
* version observed under the lock so the caller can stop once fully migrated.
|
|
285
|
+
*/
|
|
286
|
+
migrateStep(from, to, target, sql) {
|
|
287
|
+
let observed = 0;
|
|
288
|
+
this.withImmediateWrite(() => {
|
|
289
|
+
observed = Number(this.db.prepare('PRAGMA user_version').get()?.user_version ?? 0);
|
|
290
|
+
if (observed < from || observed > to)
|
|
291
|
+
return; // another process already advanced past this step
|
|
292
|
+
this.db.exec(sql);
|
|
293
|
+
this.db.exec(`PRAGMA user_version=${target};`);
|
|
294
|
+
observed = target;
|
|
295
|
+
});
|
|
296
|
+
return observed;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Switch to WAL with bounded retry. `PRAGMA journal_mode=WAL` takes a write
|
|
300
|
+
* lock to rewrite the DB header on a fresh file; under a cold-start stampede
|
|
301
|
+
* it may either throw `database is locked` OR silently return the prior mode
|
|
302
|
+
* (e.g. "delete") without switching. Both are retried — we verify the query
|
|
303
|
+
* result actually reports "wal" rather than trusting a non-throwing call.
|
|
304
|
+
*/
|
|
305
|
+
enableWalWithRetry() {
|
|
306
|
+
const maxAttempts = 10;
|
|
307
|
+
for (let attempt = 1;; attempt++) {
|
|
308
|
+
try {
|
|
309
|
+
const mode = String(this.db.prepare('PRAGMA journal_mode=WAL').get()?.journal_mode ?? '').toLowerCase();
|
|
310
|
+
if (mode === 'wal')
|
|
311
|
+
return;
|
|
312
|
+
if (attempt < maxAttempts) {
|
|
313
|
+
sleepShort(attempt);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`skill_feedback_wal_mode_not_set:${mode || 'unknown'}`);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
if (isSqliteBusyError(error) && attempt < maxAttempts) {
|
|
320
|
+
sleepShort(attempt);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Run `fn` inside a BEGIN IMMEDIATE / COMMIT, retrying the transaction when
|
|
329
|
+
* the write lock cannot be acquired (`database is locked` / `SQLITE_BUSY`)
|
|
330
|
+
* even after busy_timeout — expected when many bot daemons cold-start against
|
|
331
|
+
* a shared dataDir at once. Non-lock errors roll back and rethrow.
|
|
332
|
+
*/
|
|
333
|
+
withImmediateWrite(fn) {
|
|
334
|
+
const maxAttempts = 10;
|
|
335
|
+
for (let attempt = 1;; attempt++) {
|
|
336
|
+
try {
|
|
337
|
+
this.db.exec('BEGIN IMMEDIATE;');
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (isSqliteBusyError(error) && attempt < maxAttempts) {
|
|
341
|
+
sleepShort(attempt);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
fn();
|
|
348
|
+
this.db.exec('COMMIT;');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
try {
|
|
353
|
+
this.db.exec('ROLLBACK;');
|
|
354
|
+
}
|
|
355
|
+
catch { /* connection may already be unwound */ }
|
|
356
|
+
if (isSqliteBusyError(error) && attempt < maxAttempts) {
|
|
357
|
+
sleepShort(attempt);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
static async open(dataDir) {
|
|
365
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
366
|
+
const path = join(dataDir, 'botmux-feedback.sqlite');
|
|
367
|
+
mkdirSync(dataDir, { recursive: true });
|
|
368
|
+
const db = new DatabaseSync(path);
|
|
369
|
+
try {
|
|
370
|
+
return new SkillFeedbackStore(dataDir, db);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
db.close();
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
close() { this.db.close(); }
|
|
378
|
+
validateSchemaV1() {
|
|
379
|
+
const required = ['interactions', 'skill_runs', 'responses', 'deliveries', 'feedback_revisions', 'turn_terminals', 'turn_completion_events', 'feedback_events', 'feedback_outbox'];
|
|
380
|
+
for (const table of required) {
|
|
381
|
+
const row = this.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(table);
|
|
382
|
+
if (!row)
|
|
383
|
+
throw new Error(`skill_feedback_schema_invalid:missing_${table}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
pragmas() {
|
|
387
|
+
return {
|
|
388
|
+
journalMode: String(this.db.prepare('PRAGMA journal_mode').get()?.journal_mode ?? '').toLowerCase(),
|
|
389
|
+
foreignKeys: Number(this.db.prepare('PRAGMA foreign_keys').get()?.foreign_keys ?? 0),
|
|
390
|
+
busyTimeout: Number(this.db.prepare('PRAGMA busy_timeout').get()?.timeout ?? 0),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
debugCounts() {
|
|
394
|
+
return {
|
|
395
|
+
responses: Number(this.db.prepare('SELECT COUNT(*) AS count FROM responses').get().count),
|
|
396
|
+
deliveries: Number(this.db.prepare('SELECT COUNT(*) AS count FROM deliveries').get().count),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
schemaVersion() {
|
|
400
|
+
return Number(this.db.prepare('PRAGMA user_version').get()?.user_version ?? 0);
|
|
401
|
+
}
|
|
402
|
+
integrityCheck() {
|
|
403
|
+
return {
|
|
404
|
+
integrity: String(this.db.prepare('PRAGMA integrity_check').get()?.integrity_check ?? ''),
|
|
405
|
+
foreignKeys: this.db.prepare('PRAGMA foreign_key_check').all(),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
recordTurnDelivery(input) {
|
|
409
|
+
const hash = contentHash(input.content);
|
|
410
|
+
const attempt = input.dispatchAttempt ?? 0;
|
|
411
|
+
const discriminator = input.correlationDiscriminator ?? '';
|
|
412
|
+
const deliveryId = discriminator
|
|
413
|
+
? stableId('del', input.botAppId, input.sessionId, input.turnId, input.nativeSessionId ?? '', String(attempt), discriminator)
|
|
414
|
+
: stableId('del', input.botAppId, input.sessionId, input.turnId, input.nativeSessionId ?? '', String(attempt));
|
|
415
|
+
const interactionId = stableId('int', input.botAppId, input.sessionId, input.turnId);
|
|
416
|
+
const responseId = stableId('resp', interactionId, hash);
|
|
417
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
418
|
+
const baseCard = feedbackCardTemplate(input.baseCard);
|
|
419
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
420
|
+
try {
|
|
421
|
+
const messageOwner = this.db.prepare('SELECT delivery_id FROM deliveries WHERE platform=? AND platform_app_id=? AND platform_message_id=?').get(input.platform, input.platformAppId, input.platformMessageId);
|
|
422
|
+
if (messageOwner && messageOwner.delivery_id !== deliveryId)
|
|
423
|
+
throw new Error('turn_delivery_platform_message_rebind');
|
|
424
|
+
const correlationOwner = this.db.prepare(`SELECT platform_message_id FROM deliveries
|
|
425
|
+
WHERE bot_app_id=? AND session_id=? AND turn_id=? AND IFNULL(native_session_id,'')=?
|
|
426
|
+
AND IFNULL(dispatch_attempt,0)=? AND correlation_discriminator=?`).get(input.botAppId, input.sessionId, input.turnId, input.nativeSessionId ?? '', attempt, discriminator);
|
|
427
|
+
if (correlationOwner && correlationOwner.platform_message_id !== input.platformMessageId) {
|
|
428
|
+
throw new Error('turn_delivery_correlation_rebind');
|
|
429
|
+
}
|
|
430
|
+
this.db.prepare('INSERT OR IGNORE INTO interactions(interaction_id,context_json,created_at) VALUES(?,?,?)')
|
|
431
|
+
.run(interactionId, null, createdAt);
|
|
432
|
+
this.db.prepare(`INSERT OR IGNORE INTO responses(response_id,interaction_id,skill_run_id,content_hash,content_ref,created_at)
|
|
433
|
+
VALUES(?,?,?,?,?,?)`).run(responseId, interactionId, null, hash, input.contentRef ?? null, createdAt);
|
|
434
|
+
this.db.prepare(`INSERT OR IGNORE INTO deliveries(
|
|
435
|
+
delivery_id,response_id,platform,platform_message_id,platform_app_id,level,policy_snapshot_json,base_card_json,
|
|
436
|
+
requester_subject_id,context_json,created_at,bot_app_id,session_id,turn_id,native_session_id,dispatch_attempt,
|
|
437
|
+
chat_id,topic_root_id,scope,workflow_id,task_id,parent_task_id,cli_id,cli_version,model,reasoning_effort,
|
|
438
|
+
skill_name,skill_version,card_mode,status,duration_ms,usage_json,completed_at,webhook_destinations_json,correlation_discriminator
|
|
439
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(deliveryId, responseId, input.platform, input.platformMessageId, input.platformAppId, 'L1', input.policy ? JSON.stringify(input.policy) : null, baseCard ? JSON.stringify(baseCard) : null, input.requesterSubjectId ?? null, input.context ? JSON.stringify(input.context) : null, createdAt, input.botAppId, input.sessionId, input.turnId, input.nativeSessionId ?? null, input.dispatchAttempt ?? null, input.chatId ?? null, input.topicRootId ?? null, input.scope ?? null, input.workflowId ?? null, input.taskId ?? null, input.parentTaskId ?? null, input.cliId ?? null, input.cliVersion ?? null, input.model ?? null, input.reasoningEffort ?? null, input.skillName ?? null, input.skillVersion ?? null, input.cardMode, input.status, input.durationMs ?? null, input.usage ? JSON.stringify(input.usage) : null, input.completedAt ?? null, input.webhookDestinations ? JSON.stringify(input.webhookDestinations) : null, discriminator);
|
|
440
|
+
const row = this.db.prepare('SELECT * FROM deliveries WHERE delivery_id=?').get(deliveryId);
|
|
441
|
+
if (row.response_id !== responseId) {
|
|
442
|
+
this.db.prepare(`DELETE FROM responses WHERE response_id=? AND NOT EXISTS (
|
|
443
|
+
SELECT 1 FROM deliveries WHERE response_id=responses.response_id
|
|
444
|
+
)`).run(responseId);
|
|
445
|
+
}
|
|
446
|
+
this.reconcileTurnCompletion(deliveryId);
|
|
447
|
+
const completedRow = this.db.prepare('SELECT * FROM deliveries WHERE delivery_id=?').get(deliveryId);
|
|
448
|
+
this.db.exec('COMMIT');
|
|
449
|
+
return this.mapDelivery(completedRow);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
this.db.exec('ROLLBACK');
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
recordTurnTerminal(input) {
|
|
457
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
458
|
+
try {
|
|
459
|
+
const payload = this.applyTurnTerminal(input);
|
|
460
|
+
this.db.exec('COMMIT');
|
|
461
|
+
return payload;
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
this.db.exec('ROLLBACK');
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Nonblocking variant for the daemon's per-turn hot path. Acquiring the write
|
|
470
|
+
* lock with `DatabaseSync` + busy_timeout>0 SYNCHRONOUSLY blocks the whole
|
|
471
|
+
* Node event loop until the lock frees (measured multi-second stalls when a
|
|
472
|
+
* `botmux send` subprocess held the lock). Here we temporarily drop
|
|
473
|
+
* busy_timeout to 0 so `BEGIN IMMEDIATE` fails FAST on contention, return a
|
|
474
|
+
* discriminable {busy:true} instead of throwing, and restore the timeout in
|
|
475
|
+
* finally. The set/try/restore never crosses an await, and no Store method
|
|
476
|
+
* holds a transaction across an await, so no same-process caller can observe
|
|
477
|
+
* the borrowed 0 timeout. The daemon retries busy turns on a timer (which
|
|
478
|
+
* yields the loop between attempts) instead of blocking inline.
|
|
479
|
+
*/
|
|
480
|
+
tryRecordTurnTerminal(input) {
|
|
481
|
+
this.db.exec('PRAGMA busy_timeout=0;');
|
|
482
|
+
try {
|
|
483
|
+
try {
|
|
484
|
+
this.db.exec('BEGIN IMMEDIATE;');
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
if (isSqliteBusyError(error))
|
|
488
|
+
return { done: false, busy: true };
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
const payload = this.applyTurnTerminal(input);
|
|
493
|
+
this.db.exec('COMMIT');
|
|
494
|
+
return { done: true, payload };
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
try {
|
|
498
|
+
this.db.exec('ROLLBACK;');
|
|
499
|
+
}
|
|
500
|
+
catch { /* already unwound */ }
|
|
501
|
+
if (isSqliteBusyError(error))
|
|
502
|
+
return { done: false, busy: true };
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
this.db.exec('PRAGMA busy_timeout=5000;');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/** Shared turn-terminal transaction body (caller owns BEGIN/COMMIT/ROLLBACK). */
|
|
511
|
+
applyTurnTerminal(input) {
|
|
512
|
+
const attempt = input.dispatchAttempt ?? 0;
|
|
513
|
+
const completedAt = input.completedAt ?? new Date().toISOString();
|
|
514
|
+
const prior = this.db.prepare(`SELECT status FROM turn_terminals WHERE bot_app_id=? AND session_id=? AND turn_id=?
|
|
515
|
+
AND dispatch_attempt=?`).get(input.botAppId, input.sessionId, input.turnId, attempt);
|
|
516
|
+
if (prior && prior.status !== input.status)
|
|
517
|
+
throw new Error('turn_terminal_status_conflict');
|
|
518
|
+
this.db.prepare(`INSERT OR IGNORE INTO turn_terminals(
|
|
519
|
+
bot_app_id,session_id,turn_id,dispatch_attempt,status,completed_at,duration_ms,usage_json
|
|
520
|
+
) VALUES(?,?,?,?,?,?,?,?)`).run(input.botAppId, input.sessionId, input.turnId, attempt, input.status, completedAt, input.durationMs ?? null, input.usage ? JSON.stringify(input.usage) : null);
|
|
521
|
+
const deliveries = this.db.prepare(`SELECT delivery_id FROM deliveries WHERE bot_app_id=? AND session_id=? AND turn_id=?
|
|
522
|
+
AND IFNULL(dispatch_attempt,0)=? ORDER BY created_at,delivery_id`).all(input.botAppId, input.sessionId, input.turnId, attempt);
|
|
523
|
+
let payload;
|
|
524
|
+
for (const delivery of deliveries)
|
|
525
|
+
payload = this.reconcileTurnCompletion(delivery.delivery_id) ?? payload;
|
|
526
|
+
return payload;
|
|
527
|
+
}
|
|
528
|
+
listTurnCompletionEvents() {
|
|
529
|
+
return this.db.prepare('SELECT * FROM turn_completion_events ORDER BY created_at,event_id').all().map(row => ({
|
|
530
|
+
eventId: row.event_id, eventType: row.event_type, version: row.version, deliveryId: row.delivery_id,
|
|
531
|
+
createdAt: row.created_at, payload: JSON.parse(row.payload_json),
|
|
532
|
+
}));
|
|
533
|
+
}
|
|
534
|
+
insertFeedbackEvent(event, aggregateId, destinations) {
|
|
535
|
+
const envelope = 'data' in event ? event : {
|
|
536
|
+
eventId: event.eventId, type: event.type, version: 1, time: event.time,
|
|
537
|
+
data: Object.fromEntries(Object.entries(event).filter(([key]) => !['eventId', 'type', 'version', 'time'].includes(key))),
|
|
538
|
+
};
|
|
539
|
+
this.db.prepare(`INSERT OR IGNORE INTO feedback_events(event_id,event_type,version,aggregate_id,payload_json,created_at) VALUES(?,?,?,?,?,?)`)
|
|
540
|
+
.run(envelope.eventId, envelope.type, 1, aggregateId, JSON.stringify(envelope), envelope.time);
|
|
541
|
+
for (const destination of effectiveWebhookDestinations(envelope.type, [destinations])) {
|
|
542
|
+
this.db.prepare(`INSERT OR IGNORE INTO feedback_outbox(outbox_id,event_id,destination_id,destination_json,status,attempts,next_attempt_at,created_at) VALUES(?,?,?,?,'pending',0,?,?)`)
|
|
543
|
+
.run(stableId('out', envelope.eventId, destination.id), envelope.eventId, destination.id, JSON.stringify(destination), Date.parse(envelope.time) || Date.now(), envelope.time);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
listFeedbackEvents() {
|
|
547
|
+
return this.db.prepare('SELECT payload_json FROM feedback_events ORDER BY created_at,event_id').all()
|
|
548
|
+
.map(row => JSON.parse(row.payload_json));
|
|
549
|
+
}
|
|
550
|
+
listFeedbackOutbox() {
|
|
551
|
+
return this.db.prepare(`SELECT o.*,e.payload_json FROM feedback_outbox o JOIN feedback_events e ON e.event_id=o.event_id ORDER BY o.created_at,o.outbox_id`).all()
|
|
552
|
+
.map(row => ({ outboxId: row.outbox_id, eventId: row.event_id, destinationId: row.destination_id, destination: JSON.parse(row.destination_json), status: row.status, attempts: row.attempts, nextAttemptAt: row.next_attempt_at, claimToken: row.claim_token ?? undefined, claimedAt: row.claimed_at ?? undefined, lastHttpStatus: row.last_http_status ?? undefined, lastError: row.last_error ?? undefined, event: JSON.parse(row.payload_json) }));
|
|
553
|
+
}
|
|
554
|
+
claimFeedbackOutbox(input) {
|
|
555
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
556
|
+
try {
|
|
557
|
+
const ids = this.db.prepare(`SELECT outbox_id FROM feedback_outbox WHERE status='pending' AND next_attempt_at<=? ORDER BY next_attempt_at,outbox_id LIMIT ?`).all(input.now, Math.max(1, Math.min(input.limit, 100))).map(row => row.outbox_id);
|
|
558
|
+
for (const outboxId of ids)
|
|
559
|
+
this.db.prepare(`UPDATE feedback_outbox SET status='inflight',attempts=attempts+1,claimed_at=?,claim_token=? WHERE outbox_id=? AND status='pending'`).run(input.now, input.claimToken, outboxId);
|
|
560
|
+
this.db.exec('COMMIT');
|
|
561
|
+
const wanted = new Set(ids);
|
|
562
|
+
return this.listFeedbackOutbox().filter(row => wanted.has(row.outboxId) && row.claimToken === input.claimToken);
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
this.db.exec('ROLLBACK');
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
settleFeedbackOutboxDelivered(outboxId, claimToken, httpStatus, deliveredAt) {
|
|
570
|
+
return this.db.prepare(`UPDATE feedback_outbox SET status='delivered',last_http_status=?,delivered_at=?,claim_token=NULL,claimed_at=NULL WHERE outbox_id=? AND status='inflight' AND claim_token=?`).run(httpStatus, deliveredAt, outboxId, claimToken).changes === 1;
|
|
571
|
+
}
|
|
572
|
+
rescheduleFeedbackOutbox(outboxId, claimToken, input) {
|
|
573
|
+
return this.db.prepare(`UPDATE feedback_outbox SET status=?,next_attempt_at=?,last_error=?,last_http_status=?,claim_token=NULL,claimed_at=NULL WHERE outbox_id=? AND status='inflight' AND claim_token=?`).run(input.permanent ? 'failed' : 'pending', input.nextAttemptAt, input.error.slice(0, 500), input.httpStatus ?? null, outboxId, claimToken).changes === 1;
|
|
574
|
+
}
|
|
575
|
+
resetExpiredFeedbackOutboxClaims(now, staleAfterMs) {
|
|
576
|
+
return Number(this.db.prepare(`UPDATE feedback_outbox SET status='pending',claim_token=NULL,claimed_at=NULL WHERE status='inflight' AND claimed_at<=?`).run(now - staleAfterMs).changes);
|
|
577
|
+
}
|
|
578
|
+
reconcileTurnCompletion(deliveryId) {
|
|
579
|
+
const row = this.db.prepare(`SELECT d.*,r.content_hash,r.content_ref,t.status AS terminal_status,
|
|
580
|
+
t.completed_at AS terminal_completed_at,t.duration_ms AS terminal_duration_ms,t.usage_json AS terminal_usage_json
|
|
581
|
+
FROM deliveries d JOIN responses r ON r.response_id=d.response_id
|
|
582
|
+
JOIN turn_terminals t ON t.bot_app_id=d.bot_app_id AND t.session_id=d.session_id AND t.turn_id=d.turn_id
|
|
583
|
+
AND t.dispatch_attempt=IFNULL(d.dispatch_attempt,0)
|
|
584
|
+
WHERE d.delivery_id=?`).get(deliveryId);
|
|
585
|
+
if (!row)
|
|
586
|
+
return undefined;
|
|
587
|
+
const existing = this.db.prepare('SELECT payload_json FROM turn_completion_events WHERE delivery_id=?').get(deliveryId);
|
|
588
|
+
if (existing)
|
|
589
|
+
return JSON.parse(existing.payload_json);
|
|
590
|
+
const eventId = stableId('evt', deliveryId, row.terminal_status);
|
|
591
|
+
const payload = {
|
|
592
|
+
type: 'turn.completed', version: 1, eventId, time: row.terminal_completed_at,
|
|
593
|
+
status: row.terminal_status, deliveryId, contentHash: row.content_hash,
|
|
594
|
+
platform: row.platform, platformMessageId: row.platform_message_id, platformAppId: row.platform_app_id,
|
|
595
|
+
botAppId: row.bot_app_id, sessionId: row.session_id, turnId: row.turn_id,
|
|
596
|
+
...(row.content_ref ? { contentRef: row.content_ref } : {}),
|
|
597
|
+
...(row.native_session_id ? { nativeSessionId: row.native_session_id } : {}),
|
|
598
|
+
...(row.dispatch_attempt !== null ? { dispatchAttempt: row.dispatch_attempt } : {}),
|
|
599
|
+
...(row.chat_id ? { chatId: row.chat_id } : {}), ...(row.topic_root_id ? { topicRootId: row.topic_root_id } : {}),
|
|
600
|
+
...(row.terminal_duration_ms !== null || row.duration_ms !== null ? { durationMs: row.terminal_duration_ms ?? row.duration_ms } : {}),
|
|
601
|
+
...(row.terminal_usage_json || row.usage_json ? { usage: JSON.parse(row.terminal_usage_json ?? row.usage_json) } : {}),
|
|
602
|
+
...(row.cli_id ? { cliId: row.cli_id } : {}), ...(row.cli_version ? { cliVersion: row.cli_version } : {}),
|
|
603
|
+
...(row.model ? { model: row.model } : {}), ...(row.reasoning_effort ? { reasoningEffort: row.reasoning_effort } : {}),
|
|
604
|
+
...(row.skill_name ? { skillName: row.skill_name } : {}), ...(row.skill_version ? { skillVersion: row.skill_version } : {}),
|
|
605
|
+
...(row.workflow_id ? { workflowId: row.workflow_id } : {}), ...(row.task_id ? { taskId: row.task_id } : {}),
|
|
606
|
+
...(row.parent_task_id ? { parentTaskId: row.parent_task_id } : {}),
|
|
607
|
+
};
|
|
608
|
+
// COALESCE so a terminal that carries no usage/duration (the turn-terminal
|
|
609
|
+
// path only records status) does not clobber the usage/duration_ms the
|
|
610
|
+
// delivery captured at send time. status/completed_at are authoritative from
|
|
611
|
+
// the terminal and intentionally overwrite.
|
|
612
|
+
this.db.prepare('UPDATE deliveries SET status=?,duration_ms=COALESCE(?,duration_ms),usage_json=COALESCE(?,usage_json),completed_at=? WHERE delivery_id=?').run(row.terminal_status, row.terminal_duration_ms, row.terminal_usage_json, row.terminal_completed_at, deliveryId);
|
|
613
|
+
this.db.prepare(`INSERT INTO turn_completion_events(event_id,delivery_id,event_type,version,payload_json,created_at)
|
|
614
|
+
VALUES(?,?, 'turn.completed',1,?,?)`).run(eventId, deliveryId, JSON.stringify(payload), row.terminal_completed_at);
|
|
615
|
+
this.insertFeedbackEvent(payload, deliveryId, row.webhook_destinations_json ? JSON.parse(row.webhook_destinations_json) : []);
|
|
616
|
+
return payload;
|
|
617
|
+
}
|
|
618
|
+
createResponse(input) {
|
|
619
|
+
const now = new Date().toISOString();
|
|
620
|
+
this.db.prepare(`INSERT OR IGNORE INTO interactions(interaction_id, context_json, created_at) VALUES (?, ?, ?)`)
|
|
621
|
+
.run(input.interactionId, input.context ? JSON.stringify(input.context) : null, now);
|
|
622
|
+
const hash = contentHash(input.content);
|
|
623
|
+
const responseId = stableId('resp', input.interactionId, hash);
|
|
624
|
+
this.db.prepare(`INSERT INTO responses(response_id, interaction_id, skill_run_id, content_hash, content_ref, created_at)
|
|
625
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
626
|
+
ON CONFLICT(interaction_id, content_hash) DO NOTHING`)
|
|
627
|
+
.run(responseId, input.interactionId, input.skillRunId ?? null, hash, input.contentRef ?? null, now);
|
|
628
|
+
const row = this.db.prepare('SELECT * FROM responses WHERE interaction_id=? AND content_hash=?')
|
|
629
|
+
.get(input.interactionId, hash);
|
|
630
|
+
return this.mapResponse(row);
|
|
631
|
+
}
|
|
632
|
+
getResponse(responseId) {
|
|
633
|
+
const row = this.db.prepare('SELECT * FROM responses WHERE response_id=?').get(responseId);
|
|
634
|
+
return row ? this.mapResponse(row) : undefined;
|
|
635
|
+
}
|
|
636
|
+
createDelivery(input) {
|
|
637
|
+
const baseCard = feedbackCardTemplate(input.baseCard);
|
|
638
|
+
const row = {
|
|
639
|
+
delivery_id: stableId('del', input.platform, input.platformAppId, input.platformMessageId), response_id: input.responseId, platform: input.platform,
|
|
640
|
+
platform_message_id: input.platformMessageId, platform_app_id: input.platformAppId, level: input.level ?? 'L1',
|
|
641
|
+
policy_snapshot_json: input.policy ? JSON.stringify(input.policy) : null,
|
|
642
|
+
base_card_json: baseCard ? JSON.stringify(baseCard) : null,
|
|
643
|
+
requester_subject_id: input.requesterSubjectId ?? null,
|
|
644
|
+
context_json: input.context ? JSON.stringify(input.context) : null, created_at: new Date().toISOString(),
|
|
645
|
+
bot_app_id: null, session_id: null, turn_id: null, native_session_id: null, dispatch_attempt: null,
|
|
646
|
+
chat_id: null, topic_root_id: null, scope: null, workflow_id: null, task_id: null, parent_task_id: null,
|
|
647
|
+
cli_id: null, cli_version: null, model: null, reasoning_effort: null, skill_name: null, skill_version: null,
|
|
648
|
+
card_mode: null, status: null, duration_ms: null, usage_json: null, completed_at: null,
|
|
649
|
+
webhook_destinations_json: null, correlation_discriminator: '',
|
|
650
|
+
};
|
|
651
|
+
const winner = this.db.prepare(`INSERT INTO deliveries(delivery_id,response_id,platform,platform_message_id,platform_app_id,level,policy_snapshot_json,base_card_json,requester_subject_id,context_json,created_at)
|
|
652
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
|
653
|
+
ON CONFLICT(platform,platform_app_id,platform_message_id) DO UPDATE SET platform_message_id=excluded.platform_message_id
|
|
654
|
+
RETURNING *`)
|
|
655
|
+
.get(row.delivery_id, row.response_id, row.platform, row.platform_message_id, row.platform_app_id, row.level, row.policy_snapshot_json, row.base_card_json, row.requester_subject_id, row.context_json, row.created_at);
|
|
656
|
+
if (winner.response_id !== input.responseId) {
|
|
657
|
+
this.db.prepare(`DELETE FROM responses WHERE response_id=? AND NOT EXISTS (
|
|
658
|
+
SELECT 1 FROM deliveries WHERE response_id=responses.response_id
|
|
659
|
+
)`).run(input.responseId);
|
|
660
|
+
}
|
|
661
|
+
return this.mapDelivery(winner);
|
|
662
|
+
}
|
|
663
|
+
findDeliveryByPlatformMessage(platform, platformAppId, platformMessageId) {
|
|
664
|
+
const row = this.db.prepare('SELECT * FROM deliveries WHERE platform=? AND platform_app_id=? AND platform_message_id=?')
|
|
665
|
+
.get(platform, platformAppId, platformMessageId);
|
|
666
|
+
return row ? this.mapDelivery(row) : undefined;
|
|
667
|
+
}
|
|
668
|
+
recordFeedback(input) {
|
|
669
|
+
const delivery = this.findDeliveryByPlatformMessage(input.platform, input.platformAppId, input.platformMessageId);
|
|
670
|
+
if (!delivery)
|
|
671
|
+
throw new Error('feedback_delivery_not_found');
|
|
672
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
673
|
+
try {
|
|
674
|
+
const duplicate = this.db.prepare('SELECT * FROM feedback_revisions WHERE callback_key=?').get(input.callbackKey);
|
|
675
|
+
if (duplicate) {
|
|
676
|
+
if (duplicate.delivery_id !== delivery.deliveryId || duplicate.operator_subject_id !== input.operatorSubjectId) {
|
|
677
|
+
throw new Error('feedback_callback_key_conflict');
|
|
678
|
+
}
|
|
679
|
+
this.db.exec('COMMIT');
|
|
680
|
+
const feedback = this.mapFeedback(duplicate);
|
|
681
|
+
return { status: 'duplicate', feedback, feedbackId: feedback.feedbackId };
|
|
682
|
+
}
|
|
683
|
+
const previous = this.db.prepare(`SELECT * FROM feedback_revisions WHERE delivery_id=? AND operator_subject_id=? ORDER BY revision DESC LIMIT 1`)
|
|
684
|
+
.get(delivery.deliveryId, input.operatorSubjectId);
|
|
685
|
+
if (previous && previous.result === input.result && (previous.reason_key ?? undefined) === input.reasonKey && (previous.comment_text ?? undefined) === input.comment) {
|
|
686
|
+
this.db.exec('COMMIT');
|
|
687
|
+
return { status: 'duplicate', feedback: this.mapFeedback(previous), feedbackId: previous.feedback_id };
|
|
688
|
+
}
|
|
689
|
+
const row = {
|
|
690
|
+
feedback_id: id('fb'), delivery_id: delivery.deliveryId, operator_subject_id: input.operatorSubjectId,
|
|
691
|
+
revision: (previous?.revision ?? 0) + 1, result: input.result, semantic: input.semantic ?? null, reason_key: input.reasonKey ?? null,
|
|
692
|
+
comment_text: input.comment ?? null,
|
|
693
|
+
callback_key: input.callbackKey, supersedes_feedback_id: previous?.feedback_id ?? null, created_at: new Date().toISOString(),
|
|
694
|
+
};
|
|
695
|
+
this.db.prepare(`INSERT INTO feedback_revisions(feedback_id,delivery_id,operator_subject_id,revision,result,semantic,reason_key,comment_text,callback_key,supersedes_feedback_id,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)`)
|
|
696
|
+
.run(row.feedback_id, row.delivery_id, row.operator_subject_id, row.revision, row.result, row.semantic, row.reason_key, row.comment_text, row.callback_key, row.supersedes_feedback_id, row.created_at);
|
|
697
|
+
const eventId = stableId('evt', 'feedback.revised', row.feedback_id);
|
|
698
|
+
const event = {
|
|
699
|
+
eventId, type: 'feedback.revised', version: 1, time: row.created_at,
|
|
700
|
+
data: {
|
|
701
|
+
feedbackId: row.feedback_id, deliveryId: row.delivery_id, revision: row.revision,
|
|
702
|
+
...(row.supersedes_feedback_id ? { supersedesFeedbackId: row.supersedes_feedback_id } : {}),
|
|
703
|
+
verdictKey: row.result, ...(row.semantic ? { semantic: row.semantic } : {}),
|
|
704
|
+
...(row.reason_key ? { reasonKey: row.reason_key } : {}),
|
|
705
|
+
...(row.comment_text ? { comment: row.comment_text } : {}),
|
|
706
|
+
operatorSubjectId: row.operator_subject_id,
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
this.insertFeedbackEvent(event, row.feedback_id, input.webhookDestinations ?? delivery.webhookDestinations ?? []);
|
|
710
|
+
this.db.exec('COMMIT');
|
|
711
|
+
return { status: previous ? 'revised' : 'accepted', feedback: this.mapFeedback(row) };
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
this.db.exec('ROLLBACK');
|
|
715
|
+
const duplicate = this.db.prepare('SELECT * FROM feedback_revisions WHERE callback_key=?').get(input.callbackKey);
|
|
716
|
+
if (duplicate) {
|
|
717
|
+
if (duplicate.delivery_id !== delivery.deliveryId || duplicate.operator_subject_id !== input.operatorSubjectId) {
|
|
718
|
+
throw new Error('feedback_callback_key_conflict');
|
|
719
|
+
}
|
|
720
|
+
const feedback = this.mapFeedback(duplicate);
|
|
721
|
+
return { status: 'duplicate', feedback, feedbackId: feedback.feedbackId };
|
|
722
|
+
}
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
listFeedbackRevisions(deliveryId, operatorSubjectId) {
|
|
727
|
+
return this.db.prepare(`SELECT * FROM feedback_revisions WHERE delivery_id=? AND operator_subject_id=? ORDER BY revision`).all(deliveryId, operatorSubjectId)
|
|
728
|
+
.map(row => this.mapFeedback(row));
|
|
729
|
+
}
|
|
730
|
+
getLatestFeedback(deliveryId, operatorSubjectId) {
|
|
731
|
+
const row = this.db.prepare('SELECT * FROM feedback_revisions WHERE delivery_id=? AND operator_subject_id=? ORDER BY revision DESC LIMIT 1')
|
|
732
|
+
.get(deliveryId, operatorSubjectId);
|
|
733
|
+
return row ? this.mapFeedback(row) : undefined;
|
|
734
|
+
}
|
|
735
|
+
mapResponse(row) {
|
|
736
|
+
return { responseId: row.response_id, interactionId: row.interaction_id, skillRunId: row.skill_run_id ?? undefined, contentHash: row.content_hash, contentRef: row.content_ref ?? undefined, createdAt: row.created_at };
|
|
737
|
+
}
|
|
738
|
+
mapDelivery(row) {
|
|
739
|
+
const response = this.db.prepare('SELECT content_hash,content_ref FROM responses WHERE response_id=?')
|
|
740
|
+
.get(row.response_id);
|
|
741
|
+
return {
|
|
742
|
+
deliveryId: row.delivery_id, responseId: row.response_id,
|
|
743
|
+
botAppId: row.bot_app_id ?? undefined, sessionId: row.session_id ?? undefined, turnId: row.turn_id ?? undefined,
|
|
744
|
+
nativeSessionId: row.native_session_id ?? undefined, platform: row.platform, platformAppId: row.platform_app_id,
|
|
745
|
+
platformMessageId: row.platform_message_id, dispatchAttempt: row.dispatch_attempt ?? undefined,
|
|
746
|
+
contentHash: response?.content_hash, contentRef: response?.content_ref ?? undefined,
|
|
747
|
+
chatId: row.chat_id ?? undefined, topicRootId: row.topic_root_id ?? undefined,
|
|
748
|
+
scope: (row.scope ?? undefined),
|
|
749
|
+
workflowId: row.workflow_id ?? undefined, taskId: row.task_id ?? undefined, parentTaskId: row.parent_task_id ?? undefined,
|
|
750
|
+
cliId: row.cli_id ?? undefined, cliVersion: row.cli_version ?? undefined, model: row.model ?? undefined,
|
|
751
|
+
reasoningEffort: row.reasoning_effort ?? undefined, skillName: row.skill_name ?? undefined,
|
|
752
|
+
skillVersion: row.skill_version ?? undefined, cardMode: (row.card_mode ?? undefined),
|
|
753
|
+
status: (row.status ?? undefined), durationMs: row.duration_ms ?? undefined,
|
|
754
|
+
usage: row.usage_json ? JSON.parse(row.usage_json) : undefined,
|
|
755
|
+
completedAt: row.completed_at ?? undefined, level: row.level,
|
|
756
|
+
policy: row.policy_snapshot_json ? normalizeFeedbackPolicy(JSON.parse(row.policy_snapshot_json)) : undefined,
|
|
757
|
+
baseCard: row.base_card_json ? JSON.parse(row.base_card_json) : undefined,
|
|
758
|
+
requesterSubjectId: row.requester_subject_id ?? undefined,
|
|
759
|
+
webhookDestinations: row.webhook_destinations_json ? JSON.parse(row.webhook_destinations_json) : undefined,
|
|
760
|
+
correlationDiscriminator: row.correlation_discriminator || undefined,
|
|
761
|
+
context: row.context_json ? JSON.parse(row.context_json) : undefined, createdAt: row.created_at,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
mapFeedback(row) {
|
|
765
|
+
return { feedbackId: row.feedback_id, deliveryId: row.delivery_id, operatorSubjectId: row.operator_subject_id, revision: row.revision, result: row.result, semantic: row.semantic ?? undefined, reasonKey: row.reason_key ?? undefined, comment: row.comment_text ?? undefined, callbackKey: row.callback_key, supersedesFeedbackId: row.supersedes_feedback_id ?? undefined, createdAt: row.created_at };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const stores = new Map();
|
|
769
|
+
export function getSkillFeedbackStore(dataDir) {
|
|
770
|
+
let store = stores.get(dataDir);
|
|
771
|
+
if (!store) {
|
|
772
|
+
// Evict on rejection so a transient first-open failure (disk full, a
|
|
773
|
+
// concurrent cold-start lock that outlasted retries, corruption later
|
|
774
|
+
// repaired) is retried on the next call instead of poisoning the cache and
|
|
775
|
+
// silently disabling all feedback persistence for the rest of the process.
|
|
776
|
+
store = SkillFeedbackStore.open(dataDir).catch(error => {
|
|
777
|
+
if (stores.get(dataDir) === store)
|
|
778
|
+
stores.delete(dataDir);
|
|
779
|
+
throw error;
|
|
780
|
+
});
|
|
781
|
+
stores.set(dataDir, store);
|
|
782
|
+
}
|
|
783
|
+
return store;
|
|
784
|
+
}
|
|
785
|
+
export async function __testOnly_closeSkillFeedbackStores() {
|
|
786
|
+
const pending = [...stores.values()];
|
|
787
|
+
stores.clear();
|
|
788
|
+
for (const store of pending) {
|
|
789
|
+
try {
|
|
790
|
+
(await store).close();
|
|
791
|
+
}
|
|
792
|
+
catch { /* test cleanup only */ }
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
//# sourceMappingURL=skill-feedback-store.js.map
|