botmux 3.10.0 → 3.11.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/dist/.runtime-build-id +1 -1
- package/dist/adapters/backend/herdr-backend.d.ts +4 -3
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +11 -8
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/pty-backend.d.ts +1 -1
- package/dist/adapters/backend/pty-backend.d.ts.map +1 -1
- package/dist/adapters/backend/pty-backend.js +4 -1
- package/dist/adapters/backend/pty-backend.js.map +1 -1
- package/dist/adapters/backend/riff-backend.d.ts +29 -5
- package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
- package/dist/adapters/backend/riff-backend.js +302 -31
- package/dist/adapters/backend/riff-backend.js.map +1 -1
- package/dist/adapters/backend/sandbox.d.ts +6 -0
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +7 -0
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +4 -1
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +37 -3
- 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 +3 -3
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +9 -3
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.d.ts +3 -3
- package/dist/adapters/backend/zellij-observe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-observe-backend.js +12 -7
- package/dist/adapters/backend/zellij-observe-backend.js.map +1 -1
- package/dist/adapters/cli/codex-app.d.ts.map +1 -1
- package/dist/adapters/cli/codex-app.js +11 -2
- package/dist/adapters/cli/codex-app.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +35 -2
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +88 -7
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/runner-input.d.ts +15 -14
- package/dist/adapters/cli/runner-input.d.ts.map +1 -1
- package/dist/adapters/cli/runner-input.js +64 -26
- package/dist/adapters/cli/runner-input.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +20 -5
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/bot-registry.d.ts +24 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +110 -5
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli/fleet-shutdown.d.ts +47 -0
- package/dist/cli/fleet-shutdown.d.ts.map +1 -0
- package/dist/cli/fleet-shutdown.js +387 -0
- package/dist/cli/fleet-shutdown.js.map +1 -0
- package/dist/cli/pm2-descriptor-guard.d.ts +19 -0
- package/dist/cli/pm2-descriptor-guard.d.ts.map +1 -0
- package/dist/cli/pm2-descriptor-guard.js +122 -0
- package/dist/cli/pm2-descriptor-guard.js.map +1 -0
- package/dist/cli/pm2-exact-start.d.ts +16 -0
- package/dist/cli/pm2-exact-start.d.ts.map +1 -0
- package/dist/cli/pm2-exact-start.js +63 -0
- package/dist/cli/pm2-exact-start.js.map +1 -0
- package/dist/cli/pm2-god-admission.d.ts +8 -0
- package/dist/cli/pm2-god-admission.d.ts.map +1 -0
- package/dist/cli/pm2-god-admission.js +25 -0
- package/dist/cli/pm2-god-admission.js.map +1 -0
- package/dist/cli/pm2-jlist.d.ts +13 -0
- package/dist/cli/pm2-jlist.d.ts.map +1 -0
- package/dist/cli/pm2-jlist.js +108 -0
- package/dist/cli/pm2-jlist.js.map +1 -0
- package/dist/cli/pm2-preflight.d.ts +10 -0
- package/dist/cli/pm2-preflight.d.ts.map +1 -0
- package/dist/cli/pm2-preflight.js +23 -0
- package/dist/cli/pm2-preflight.js.map +1 -0
- package/dist/cli/pm2-shutdown-capability.d.ts +27 -0
- package/dist/cli/pm2-shutdown-capability.d.ts.map +1 -0
- package/dist/cli/pm2-shutdown-capability.js +161 -0
- package/dist/cli/pm2-shutdown-capability.js.map +1 -0
- package/dist/cli/pm2-start-transaction.d.ts +55 -0
- package/dist/cli/pm2-start-transaction.d.ts.map +1 -0
- package/dist/cli/pm2-start-transaction.js +220 -0
- package/dist/cli/pm2-start-transaction.js.map +1 -0
- package/dist/cli/send-dispatch.d.ts +12 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -1
- package/dist/cli/send-dispatch.js +31 -4
- package/dist/cli/send-dispatch.js.map +1 -1
- package/dist/cli/session-list-liveness.d.ts +1 -0
- package/dist/cli/session-list-liveness.d.ts.map +1 -1
- package/dist/cli/session-list-liveness.js +6 -0
- package/dist/cli/session-list-liveness.js.map +1 -1
- package/dist/cli/session-picker-layout.d.ts +26 -0
- package/dist/cli/session-picker-layout.d.ts.map +1 -0
- package/dist/cli/session-picker-layout.js +123 -0
- package/dist/cli/session-picker-layout.js.map +1 -0
- package/dist/cli/session-picker-viewport.d.ts +49 -0
- package/dist/cli/session-picker-viewport.d.ts.map +1 -0
- package/dist/cli/session-picker-viewport.js +46 -0
- package/dist/cli/session-picker-viewport.js.map +1 -0
- package/dist/cli/supervisor-shutdown-client.d.ts +31 -0
- package/dist/cli/supervisor-shutdown-client.d.ts.map +1 -0
- package/dist/cli/supervisor-shutdown-client.js +146 -0
- package/dist/cli/supervisor-shutdown-client.js.map +1 -0
- package/dist/cli/terminal-width.d.ts +48 -0
- package/dist/cli/terminal-width.d.ts.map +1 -0
- package/dist/cli/terminal-width.js +119 -0
- package/dist/cli/terminal-width.js.map +1 -0
- package/dist/cli/vc-agent.d.ts.map +1 -1
- package/dist/cli/vc-agent.js +1 -1
- package/dist/cli/vc-agent.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2269 -716
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-runner.js +1630 -125
- package/dist/codex-app-runner.js.map +1 -1
- package/dist/core/ask-api.d.ts +8 -1
- package/dist/core/ask-api.d.ts.map +1 -1
- package/dist/core/ask-api.js +18 -0
- package/dist/core/ask-api.js.map +1 -1
- package/dist/core/ask-broker.d.ts +20 -0
- package/dist/core/ask-broker.d.ts.map +1 -1
- package/dist/core/ask-broker.js +467 -47
- package/dist/core/ask-broker.js.map +1 -1
- package/dist/core/ask-persist-store.d.ts +126 -0
- package/dist/core/ask-persist-store.d.ts.map +1 -0
- package/dist/core/ask-persist-store.js +188 -0
- package/dist/core/ask-persist-store.js.map +1 -0
- package/dist/core/ask-types.d.ts +80 -0
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js +62 -0
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/cli-session-id-publisher.d.ts +20 -0
- package/dist/core/cli-session-id-publisher.d.ts.map +1 -0
- package/dist/core/cli-session-id-publisher.js +21 -0
- package/dist/core/cli-session-id-publisher.js.map +1 -0
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +427 -255
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/cost-calculator.d.ts +13 -0
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +385 -26
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +9 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +842 -288
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/explicit-session-backing-cleanup.d.ts +49 -0
- package/dist/core/explicit-session-backing-cleanup.d.ts.map +1 -0
- package/dist/core/explicit-session-backing-cleanup.js +76 -0
- package/dist/core/explicit-session-backing-cleanup.js.map +1 -0
- package/dist/core/idle-worker-sweeper.d.ts +20 -0
- package/dist/core/idle-worker-sweeper.d.ts.map +1 -1
- package/dist/core/idle-worker-sweeper.js +18 -0
- package/dist/core/idle-worker-sweeper.js.map +1 -1
- package/dist/core/inflight-input-tracker.d.ts +10 -0
- package/dist/core/inflight-input-tracker.d.ts.map +1 -1
- package/dist/core/inflight-input-tracker.js +7 -0
- package/dist/core/inflight-input-tracker.js.map +1 -1
- package/dist/core/managed-origin-attestation.d.ts +51 -0
- package/dist/core/managed-origin-attestation.d.ts.map +1 -0
- package/dist/core/managed-origin-attestation.js +208 -0
- package/dist/core/managed-origin-attestation.js.map +1 -0
- package/dist/core/managed-origin-capability.d.ts +62 -3
- package/dist/core/managed-origin-capability.d.ts.map +1 -1
- package/dist/core/managed-origin-capability.js +419 -9
- package/dist/core/managed-origin-capability.js.map +1 -1
- package/dist/core/pending-repo-journal.d.ts +6 -0
- package/dist/core/pending-repo-journal.d.ts.map +1 -0
- package/dist/core/pending-repo-journal.js +85 -0
- package/dist/core/pending-repo-journal.js.map +1 -0
- package/dist/core/persistent-backend.d.ts +6 -1
- package/dist/core/persistent-backend.d.ts.map +1 -1
- package/dist/core/persistent-backend.js +12 -6
- package/dist/core/persistent-backend.js.map +1 -1
- package/dist/core/process-start-identity.d.ts +3 -0
- package/dist/core/process-start-identity.d.ts.map +1 -0
- package/dist/core/process-start-identity.js +62 -0
- package/dist/core/process-start-identity.js.map +1 -0
- package/dist/core/raw-command-writer.d.ts +34 -0
- package/dist/core/raw-command-writer.d.ts.map +1 -0
- package/dist/core/raw-command-writer.js +48 -0
- package/dist/core/raw-command-writer.js.map +1 -0
- package/dist/core/reply-target.d.ts +85 -22
- package/dist/core/reply-target.d.ts.map +1 -1
- package/dist/core/reply-target.js +267 -32
- package/dist/core/reply-target.js.map +1 -1
- package/dist/core/resource-monitor/runtime.d.ts.map +1 -1
- package/dist/core/resource-monitor/runtime.js +4 -1
- package/dist/core/resource-monitor/runtime.js.map +1 -1
- package/dist/core/restart-report.d.ts +6 -1
- package/dist/core/restart-report.d.ts.map +1 -1
- package/dist/core/restart-report.js +22 -4
- package/dist/core/restart-report.js.map +1 -1
- package/dist/core/riff-shutdown-detach.d.ts +164 -0
- package/dist/core/riff-shutdown-detach.d.ts.map +1 -0
- package/dist/core/riff-shutdown-detach.js +787 -0
- package/dist/core/riff-shutdown-detach.js.map +1 -0
- package/dist/core/riff-worker-shutdown-readiness.d.ts +17 -0
- package/dist/core/riff-worker-shutdown-readiness.d.ts.map +1 -0
- package/dist/core/riff-worker-shutdown-readiness.js +21 -0
- package/dist/core/riff-worker-shutdown-readiness.js.map +1 -0
- package/dist/core/session-manager.d.ts +6 -6
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1227 -594
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-marker.d.ts +1 -1
- package/dist/core/session-marker.d.ts.map +1 -1
- package/dist/core/session-marker.js +2 -2
- package/dist/core/session-marker.js.map +1 -1
- package/dist/core/session-mutation-guard.d.ts +17 -0
- package/dist/core/session-mutation-guard.d.ts.map +1 -0
- package/dist/core/session-mutation-guard.js +33 -0
- package/dist/core/session-mutation-guard.js.map +1 -0
- package/dist/core/shutdown-budgets.d.ts +27 -0
- package/dist/core/shutdown-budgets.d.ts.map +1 -0
- package/dist/core/shutdown-budgets.js +47 -0
- package/dist/core/shutdown-budgets.js.map +1 -0
- package/dist/core/supervisor-shutdown-ipc.d.ts +10 -0
- package/dist/core/supervisor-shutdown-ipc.d.ts.map +1 -0
- package/dist/core/supervisor-shutdown-ipc.js +10 -0
- package/dist/core/supervisor-shutdown-ipc.js.map +1 -0
- package/dist/core/supervisor-shutdown-protocol.d.ts +16 -0
- package/dist/core/supervisor-shutdown-protocol.d.ts.map +1 -0
- package/dist/core/supervisor-shutdown-protocol.js +16 -0
- package/dist/core/supervisor-shutdown-protocol.js.map +1 -0
- package/dist/core/trigger-final-suppression.d.ts +4 -0
- package/dist/core/trigger-final-suppression.d.ts.map +1 -1
- package/dist/core/trigger-final-suppression.js +6 -11
- package/dist/core/trigger-final-suppression.js.map +1 -1
- package/dist/core/trigger-session.d.ts +70 -0
- package/dist/core/trigger-session.d.ts.map +1 -1
- package/dist/core/trigger-session.js +923 -145
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/types.d.ts +94 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +194 -13
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +2597 -547
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +76 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +2255 -773
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +20 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +24 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/feed-groups.d.ts +17 -0
- package/dist/dashboard/feed-groups.d.ts.map +1 -0
- package/dist/dashboard/feed-groups.js +75 -0
- package/dist/dashboard/feed-groups.js.map +1 -0
- package/dist/dashboard/session-card-model.d.ts +1 -1
- package/dist/dashboard/session-card-model.d.ts.map +1 -1
- package/dist/dashboard/session-card-model.js +8 -5
- package/dist/dashboard/session-card-model.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +4 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +139 -46
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +4 -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/connectors-page.d.ts.map +1 -1
- package/dist/dashboard/web/connectors-page.js +40 -3
- package/dist/dashboard/web/connectors-page.js.map +1 -1
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +95 -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 +86 -40
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/kanban-model.js +1 -1
- package/dist/dashboard/web/kanban-model.js.map +1 -1
- package/dist/dashboard/web/overview-page.d.ts.map +1 -1
- package/dist/dashboard/web/overview-page.js +3 -2
- package/dist/dashboard/web/overview-page.js.map +1 -1
- package/dist/dashboard/web/overview.d.ts +3 -0
- package/dist/dashboard/web/overview.d.ts.map +1 -1
- package/dist/dashboard/web/overview.js +2 -0
- package/dist/dashboard/web/overview.js.map +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-kanban.js +2 -0
- package/dist/dashboard/web/sessions-kanban.js.map +1 -1
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +103 -2
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +3 -2
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/ui.d.ts +8 -0
- package/dist/dashboard/web/ui.d.ts.map +1 -1
- package/dist/dashboard/web/ui.js +15 -0
- package/dist/dashboard/web/ui.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-GED4FEMS.js +10 -0
- package/dist/dashboard-web/chunks/chunk-6J5LS5AU.js +51 -0
- package/dist/dashboard-web/chunks/{chunk-ZU3UUKAR.js → chunk-HK7NIBJG.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-3UE7S77S.js → chunk-KRHWZEXU.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-VBXJOJFH.js +1 -0
- package/dist/dashboard-web/chunks/connectors-page-EELM66G3.js +1 -0
- package/dist/dashboard-web/chunks/groups-page-WKUV6E42.js +8 -0
- package/dist/dashboard-web/chunks/{insights-page-T2ZX2RWL.js → insights-page-54UBPPOC.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-YNVBAGR6.js → monitor-room-PORHVKRH.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-LHM55YWN.js → monitoring-page-XGA7AXNL.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-ATXVP7PW.js → office-page-AO3AFG45.js} +1 -1
- package/dist/dashboard-web/chunks/overview-page-F5BUTYR6.js +1 -0
- package/dist/dashboard-web/chunks/{roles-page-56VWM6CD.js → roles-page-M5GIASJU.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-S5K6U4EO.js → schedules-page-O232WQRR.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-YFRPSTIL.js +6 -0
- package/dist/dashboard-web/chunks/{settings-page-QUZDQARR.js → settings-page-OJAZLZQT.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-IFY7AEND.js → skills-page-EFZQPWV4.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-C7FJQPFZ.js → team-federation-page-ZJVAP7HW.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-2LMF3WWR.js → v3-page-GYWTRGO2.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-3XC6QRMY.js → whiteboards-page-MSDAG6P7.js} +1 -1
- package/dist/dashboard-web/style.css +255 -0
- package/dist/dashboard.js +172 -7
- package/dist/dashboard.js.map +1 -1
- package/dist/desktop/renderer/app.js +21 -0
- package/dist/desktop/renderer/app.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +35 -9
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +35 -9
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/ask-card.d.ts +23 -0
- package/dist/im/lark/ask-card.d.ts.map +1 -1
- package/dist/im/lark/ask-card.js +94 -4
- package/dist/im/lark/ask-card.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +6 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +112 -15
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +496 -310
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +7 -0
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +39 -26
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/doc-comment.d.ts +7 -4
- package/dist/im/lark/doc-comment.d.ts.map +1 -1
- package/dist/im/lark/doc-comment.js +19 -6
- package/dist/im/lark/doc-comment.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +16 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +88 -23
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/forward-followup-content.d.ts.map +1 -1
- package/dist/im/lark/forward-followup-content.js +9 -1
- package/dist/im/lark/forward-followup-content.js.map +1 -1
- package/dist/im/lark/grant-command.d.ts.map +1 -1
- package/dist/im/lark/grant-command.js +7 -4
- package/dist/im/lark/grant-command.js.map +1 -1
- package/dist/im/lark/md-card.d.ts +11 -0
- package/dist/im/lark/md-card.d.ts.map +1 -1
- package/dist/im/lark/md-card.js +46 -0
- package/dist/im/lark/md-card.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +23 -2
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +58 -3
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/im/lark/overview-card.d.ts.map +1 -1
- package/dist/im/lark/overview-card.js +2 -1
- package/dist/im/lark/overview-card.js.map +1 -1
- package/dist/services/async-trigger-state.d.ts +6 -1
- package/dist/services/async-trigger-state.d.ts.map +1 -1
- package/dist/services/async-trigger-state.js +21 -0
- package/dist/services/async-trigger-state.js.map +1 -1
- package/dist/services/async-trigger-store.d.ts +23 -1
- package/dist/services/async-trigger-store.d.ts.map +1 -1
- package/dist/services/async-trigger-store.js +96 -13
- package/dist/services/async-trigger-store.js.map +1 -1
- package/dist/services/bridge-fallback-gate.d.ts +40 -0
- package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
- package/dist/services/bridge-fallback-gate.js +124 -24
- package/dist/services/bridge-fallback-gate.js.map +1 -1
- package/dist/services/codex-app-runner-protocol.d.ts +8 -0
- package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
- package/dist/services/codex-app-runner-protocol.js +9 -2
- package/dist/services/codex-app-runner-protocol.js.map +1 -1
- package/dist/services/codex-bridge-queue.d.ts +1 -0
- package/dist/services/codex-bridge-queue.d.ts.map +1 -1
- package/dist/services/codex-bridge-queue.js +1 -0
- package/dist/services/codex-bridge-queue.js.map +1 -1
- package/dist/services/codex-service-tier.d.ts +2 -0
- package/dist/services/codex-service-tier.d.ts.map +1 -1
- package/dist/services/codex-service-tier.js +1 -0
- package/dist/services/codex-service-tier.js.map +1 -1
- package/dist/services/codex-transcript.d.ts +27 -0
- package/dist/services/codex-transcript.d.ts.map +1 -1
- package/dist/services/codex-transcript.js +340 -2
- package/dist/services/codex-transcript.js.map +1 -1
- package/dist/services/grant-policy.d.ts +3 -0
- package/dist/services/grant-policy.d.ts.map +1 -1
- package/dist/services/grant-policy.js +5 -0
- package/dist/services/grant-policy.js.map +1 -1
- package/dist/services/grant-prefs-store.d.ts +6 -3
- package/dist/services/grant-prefs-store.d.ts.map +1 -1
- package/dist/services/grant-prefs-store.js +42 -12
- package/dist/services/grant-prefs-store.js.map +1 -1
- package/dist/services/hook-runner.d.ts +20 -1
- package/dist/services/hook-runner.d.ts.map +1 -1
- package/dist/services/hook-runner.js +17 -13
- package/dist/services/hook-runner.js.map +1 -1
- package/dist/services/idempotency-store.d.ts +134 -0
- package/dist/services/idempotency-store.d.ts.map +1 -0
- package/dist/services/idempotency-store.js +299 -0
- package/dist/services/idempotency-store.js.map +1 -0
- package/dist/services/jsonl-cursor.d.ts +7 -3
- package/dist/services/jsonl-cursor.d.ts.map +1 -1
- package/dist/services/jsonl-cursor.js +55 -31
- package/dist/services/jsonl-cursor.js.map +1 -1
- package/dist/services/open-platform-rename.d.ts.map +1 -1
- package/dist/services/open-platform-rename.js +1 -108
- package/dist/services/open-platform-rename.js.map +1 -1
- package/dist/services/restart-intent-store.d.ts +22 -2
- package/dist/services/restart-intent-store.d.ts.map +1 -1
- package/dist/services/restart-intent-store.js +160 -18
- package/dist/services/restart-intent-store.js.map +1 -1
- package/dist/services/sandbox-store.d.ts +22 -0
- package/dist/services/sandbox-store.d.ts.map +1 -1
- package/dist/services/sandbox-store.js +37 -8
- package/dist/services/sandbox-store.js.map +1 -1
- package/dist/services/send-policy.d.ts +41 -33
- package/dist/services/send-policy.d.ts.map +1 -1
- package/dist/services/send-policy.js +39 -40
- package/dist/services/send-policy.js.map +1 -1
- package/dist/services/session-store.d.ts +66 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +403 -67
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/submit-notification.d.ts +3 -0
- package/dist/services/submit-notification.d.ts.map +1 -0
- package/dist/services/submit-notification.js +15 -0
- package/dist/services/submit-notification.js.map +1 -0
- package/dist/services/traex-transcript.d.ts +25 -2
- package/dist/services/traex-transcript.d.ts.map +1 -1
- package/dist/services/traex-transcript.js +133 -1
- package/dist/services/traex-transcript.js.map +1 -1
- package/dist/services/trigger-types.d.ts +13 -1
- package/dist/services/trigger-types.d.ts.map +1 -1
- package/dist/services/trigger-types.js +39 -0
- package/dist/services/trigger-types.js.map +1 -1
- package/dist/services/voice/index.d.ts +3 -2
- package/dist/services/voice/index.d.ts.map +1 -1
- package/dist/services/voice/index.js +5 -5
- package/dist/services/voice/index.js.map +1 -1
- package/dist/services/voice/openai.d.ts +2 -1
- package/dist/services/voice/openai.d.ts.map +1 -1
- package/dist/services/voice/openai.js +2 -1
- package/dist/services/voice/openai.js.map +1 -1
- package/dist/services/voice/sami.d.ts +5 -2
- package/dist/services/voice/sami.d.ts.map +1 -1
- package/dist/services/voice/sami.js +19 -4
- package/dist/services/voice/sami.js.map +1 -1
- package/dist/setup/bots-store.d.ts.map +1 -1
- package/dist/setup/bots-store.js +10 -4
- package/dist/setup/bots-store.js.map +1 -1
- package/dist/setup/lark-scopes.json +2 -0
- package/dist/setup/open-platform-automation.d.ts +9 -1
- package/dist/setup/open-platform-automation.d.ts.map +1 -1
- package/dist/setup/open-platform-automation.js +44 -15
- package/dist/setup/open-platform-automation.js.map +1 -1
- package/dist/setup/open-platform-visibility.d.ts +42 -0
- package/dist/setup/open-platform-visibility.d.ts.map +1 -0
- package/dist/setup/open-platform-visibility.js +133 -0
- package/dist/setup/open-platform-visibility.js.map +1 -0
- package/dist/setup/verify-permissions.d.ts.map +1 -1
- package/dist/setup/verify-permissions.js +6 -0
- package/dist/setup/verify-permissions.js.map +1 -1
- package/dist/skills/definitions.js +2 -2
- package/dist/types.d.ts +290 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/bot-routing.d.ts +1 -0
- package/dist/utils/bot-routing.d.ts.map +1 -1
- package/dist/utils/bot-routing.js +1 -1
- package/dist/utils/bot-routing.js.map +1 -1
- package/dist/utils/child-env.d.ts +1 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +9 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/codex-app-control.d.ts +438 -0
- package/dist/utils/codex-app-control.d.ts.map +1 -0
- package/dist/utils/codex-app-control.js +2336 -0
- package/dist/utils/codex-app-control.js.map +1 -0
- package/dist/utils/codex-app-dispatch-ledger.d.ts +78 -0
- package/dist/utils/codex-app-dispatch-ledger.d.ts.map +1 -0
- package/dist/utils/codex-app-dispatch-ledger.js +149 -0
- package/dist/utils/codex-app-dispatch-ledger.js.map +1 -0
- package/dist/utils/codex-app-turn-dispatch.d.ts +77 -0
- package/dist/utils/codex-app-turn-dispatch.d.ts.map +1 -0
- package/dist/utils/codex-app-turn-dispatch.js +141 -0
- package/dist/utils/codex-app-turn-dispatch.js.map +1 -0
- package/dist/utils/codex-app-turn-liveness.d.ts +125 -0
- package/dist/utils/codex-app-turn-liveness.d.ts.map +1 -0
- package/dist/utils/codex-app-turn-liveness.js +301 -0
- package/dist/utils/codex-app-turn-liveness.js.map +1 -0
- package/dist/utils/idle-detector.d.ts +12 -0
- package/dist/utils/idle-detector.d.ts.map +1 -1
- package/dist/utils/idle-detector.js +19 -0
- package/dist/utils/idle-detector.js.map +1 -1
- package/dist/utils/input-gate.d.ts +87 -0
- package/dist/utils/input-gate.d.ts.map +1 -1
- package/dist/utils/input-gate.js +86 -0
- package/dist/utils/input-gate.js.map +1 -1
- package/dist/utils/lark-upload.d.ts.map +1 -1
- package/dist/utils/lark-upload.js +52 -1
- package/dist/utils/lark-upload.js.map +1 -1
- package/dist/utils/pending-input-queue.d.ts +15 -0
- package/dist/utils/pending-input-queue.d.ts.map +1 -1
- package/dist/utils/pending-input-queue.js +5 -1
- package/dist/utils/pending-input-queue.js.map +1 -1
- package/dist/utils/user-token.d.ts.map +1 -1
- package/dist/utils/user-token.js +53 -2
- package/dist/utils/user-token.js.map +1 -1
- package/dist/worker-ipc-preload.d.ts +12 -0
- package/dist/worker-ipc-preload.d.ts.map +1 -0
- package/dist/worker-ipc-preload.js +44 -0
- package/dist/worker-ipc-preload.js.map +1 -0
- package/dist/worker.js +2604 -325
- package/dist/worker.js.map +1 -1
- package/dist/workflows/v3/session-relay-client.d.ts +1 -0
- package/dist/workflows/v3/session-relay-client.d.ts.map +1 -1
- package/dist/workflows/v3/session-relay-client.js +12 -3
- package/dist/workflows/v3/session-relay-client.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-LTVOUDIS.js +0 -10
- package/dist/dashboard-web/chunks/chunk-DCS35UMD.js +0 -1
- package/dist/dashboard-web/chunks/chunk-MFQPCVHY.js +0 -51
- package/dist/dashboard-web/chunks/connectors-page-5KWDIMKC.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-FHOKYXMW.js +0 -8
- package/dist/dashboard-web/chunks/overview-page-QYI3KBDD.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-WBP4N7D2.js +0 -6
package/dist/core/ask-broker.js
CHANGED
|
@@ -9,8 +9,38 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { randomUUID } from 'node:crypto';
|
|
11
11
|
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { askKeyFor, dispatchUuidForKey, HANDOFF_RETENTION_MS, } from './ask-persist-store.js';
|
|
13
|
+
import { AskDispatchError } from './ask-types.js';
|
|
14
|
+
/** Origins for which restart-resume + durable handoff are enabled. Only a
|
|
15
|
+
* caller that has a reconnecting claimant after a daemon restart may persist:
|
|
16
|
+
* the hook process survives in the CLI's tmux/persistent-backend session and
|
|
17
|
+
* re-POSTs with the same requestId. An explicit `botmux ask buttons` CLI exits
|
|
18
|
+
* on restart (no claimant) and a PTY-backend hook doesn't survive, so those
|
|
19
|
+
* must NOT persist/handoff — else they leave orphan records (codex P1-4). */
|
|
20
|
+
const RESUMABLE_ORIGINS = new Set(['hook']);
|
|
12
21
|
const pending = new Map();
|
|
13
22
|
let dispatcher = null;
|
|
23
|
+
/** Injected durable store (codex P1-4: no global-dataDir reads from the broker).
|
|
24
|
+
* Wired once at daemon bootstrap via setAskPersistStore; null → persistence is
|
|
25
|
+
* a no-op (unit tests that don't exercise restart-resume需要绑定各自 temp
|
|
26
|
+
* store,绝不回落到全局目录). */
|
|
27
|
+
let persistStore = null;
|
|
28
|
+
/** Effective handoff-retention window. Defaults to the durable-store constant;
|
|
29
|
+
* a test may shrink it via `_setHandoffRetentionForTest` so the absolute-expiry
|
|
30
|
+
* reaper (codex P1-4) can be exercised with real timers instead of a 24h wait
|
|
31
|
+
* or fake-timer gymnastics around the async dispatch loop. */
|
|
32
|
+
let handoffRetentionMs = HANDOFF_RETENTION_MS;
|
|
33
|
+
/** Wire the durable persist store. Called once at daemon bootstrap with the
|
|
34
|
+
* real dir; tests inject a temp store (and only clean their own sentinel dir). */
|
|
35
|
+
export function setAskPersistStore(store) {
|
|
36
|
+
persistStore = store;
|
|
37
|
+
}
|
|
38
|
+
/** Shrink the handoff-retention window — for tests only, so the absolute-expiry
|
|
39
|
+
* reaper (codex P1-4) can be verified with a short real timer. Restored to the
|
|
40
|
+
* default by `_resetForTest`. */
|
|
41
|
+
export function _setHandoffRetentionForTest(ms) {
|
|
42
|
+
handoffRetentionMs = ms;
|
|
43
|
+
}
|
|
14
44
|
/** IM-side canTalk predicate, wired by the daemon at bootstrap. Lets the broker
|
|
15
45
|
* honour the bot's canTalk gate without importing Lark types: whoever may
|
|
16
46
|
* address the bot in this chat may answer its `botmux ask`. Returns false until
|
|
@@ -57,29 +87,82 @@ export function registerAsk(input) {
|
|
|
57
87
|
if (!dispatcher) {
|
|
58
88
|
throw new Error('ask-broker: cardDispatcher not wired — daemon bootstrap bug');
|
|
59
89
|
}
|
|
90
|
+
const originKind = input.originKind ?? 'hook';
|
|
91
|
+
// Resumability is gated by TWO independent facts (codex P1-4):
|
|
92
|
+
// 1. the origin has a reconnecting claimant (only 'hook' re-POSTs after a
|
|
93
|
+
// restart; an explicit `botmux ask buttons` process exits), AND
|
|
94
|
+
// 2. the issuing session's backend actually SURVIVES a daemon restart —
|
|
95
|
+
// computed by the daemon from the authenticated session's frozen backend
|
|
96
|
+
// (tmux/herdr/zellij/zmx), NOT trusted from the client. A PTY-backed hook
|
|
97
|
+
// dies with the daemon, so persisting it would orphan a record no one can
|
|
98
|
+
// ever re-claim. Undefined backend signal → false (fail closed).
|
|
99
|
+
// A caller-supplied requestId is still required (it's the re-attach identity).
|
|
100
|
+
const resumable = RESUMABLE_ORIGINS.has(originKind) &&
|
|
101
|
+
input.requestId !== undefined &&
|
|
102
|
+
input.backendSurvivesRestart === true;
|
|
103
|
+
// Invocation identity: prefer the caller-supplied requestId (hook generates it
|
|
104
|
+
// once and reuses it across reconnect retries). A caller without one (explicit
|
|
105
|
+
// `botmux ask buttons`) gets a synthesized id and is NOT resumable — it has no
|
|
106
|
+
// reconnecting claimant, so persisting/handing off would orphan (codex P1-4).
|
|
107
|
+
const requestId = input.requestId ?? randomUUID();
|
|
108
|
+
// Identity is SCOPED to the authenticated (larkAppId, sessionId): a requestId
|
|
109
|
+
// is an idempotency id, never a bearer secret — a different session reusing it
|
|
110
|
+
// lands on a different key and can't reclaim this ask (codex P1-3).
|
|
111
|
+
const askKey = askKeyFor(input.larkAppId, input.sessionId, originKind, requestId);
|
|
112
|
+
// Same-key handling — covers ALL states of an existing entry with this key
|
|
113
|
+
// (codex P1-1). The scoped key already pins (app/session/origin/request); here
|
|
114
|
+
// we additionally require the FULL immutable identity (chat/root/questions) to
|
|
115
|
+
// match before joining/replaying/re-attaching.
|
|
116
|
+
const existing = findByKey(askKey);
|
|
117
|
+
if (existing) {
|
|
118
|
+
if (sameIdentity(existing, input)) {
|
|
119
|
+
// active (still awaiting a click): add another waiter → both callers get the
|
|
120
|
+
// one result; no second ask, no second card.
|
|
121
|
+
if (!existing.settled && !existing.dormant) {
|
|
122
|
+
logger.info?.(`ask-broker: active replay joined ask ${existing.askId} (key=${askKey})`);
|
|
123
|
+
return new Promise((resolve) => { existing.waiters.push(resolve); });
|
|
124
|
+
}
|
|
125
|
+
// recent-terminal (settled, still retained): return the same terminal result.
|
|
126
|
+
if (existing.settled && existing.terminalResult && !existing.dormant) {
|
|
127
|
+
logger.info?.(`ask-broker: replay returned retained terminal result for ${existing.askId}`);
|
|
128
|
+
return Promise.resolve(existing.terminalResult);
|
|
129
|
+
}
|
|
130
|
+
// dormant (restored after restart): re-attach (handoff or fresh waiter).
|
|
131
|
+
if (existing.dormant) {
|
|
132
|
+
return reattachByRequest(existing);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Same key, DIFFERENT immutable identity (crafted / stale-but-mutated
|
|
137
|
+
// re-POST). FAIL CLOSED (codex P1-2): never fall through to a second ask —
|
|
138
|
+
// that would post an ambiguous second card and a click could resolve
|
|
139
|
+
// either. The caller gets a terminal `invalidated` and no card is sent.
|
|
140
|
+
logger.warn?.(`ask-broker: identity mismatch on key ${askKey} (existing ask ${existing.askId}) — rejecting re-register`);
|
|
141
|
+
return Promise.resolve({
|
|
142
|
+
kind: 'invalidated',
|
|
143
|
+
reason: 'ask identity mismatch (same key, different question set / chat)',
|
|
144
|
+
selected: null, by: null, comment: null, timedOut: false,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
60
148
|
const askId = randomUUID();
|
|
61
149
|
const nonce = randomUUID().slice(0, 8);
|
|
62
150
|
const createdAt = Date.now();
|
|
63
151
|
const deadlineAt = createdAt + input.timeoutMs;
|
|
64
152
|
return new Promise((resolve) => {
|
|
65
153
|
const timeoutHandle = setTimeout(() => {
|
|
66
|
-
settle(askId, {
|
|
67
|
-
kind: 'timedOut',
|
|
68
|
-
selected: null,
|
|
69
|
-
by: null,
|
|
70
|
-
comment: null,
|
|
71
|
-
timedOut: true,
|
|
72
|
-
});
|
|
154
|
+
settle(askId, { kind: 'timedOut', selected: null, by: null, comment: null, timedOut: true });
|
|
73
155
|
}, input.timeoutMs);
|
|
74
|
-
// Don't keep the event loop alive just because an ask is pending.
|
|
75
156
|
timeoutHandle.unref?.();
|
|
76
|
-
// 为每个问题初始化空的勾选集合
|
|
77
157
|
const selections = new Map();
|
|
78
|
-
for (let i = 0; i < input.questions.length; i++)
|
|
158
|
+
for (let i = 0; i < input.questions.length; i++)
|
|
79
159
|
selections.set(i, new Set());
|
|
80
|
-
}
|
|
81
160
|
const ask = {
|
|
82
161
|
askId,
|
|
162
|
+
askKey,
|
|
163
|
+
requestId,
|
|
164
|
+
originKind,
|
|
165
|
+
resumable,
|
|
83
166
|
nonce,
|
|
84
167
|
larkAppId: input.larkAppId,
|
|
85
168
|
chatId: input.chatId,
|
|
@@ -90,33 +173,180 @@ export function registerAsk(input) {
|
|
|
90
173
|
createdAt,
|
|
91
174
|
deadlineAt,
|
|
92
175
|
settled: false,
|
|
93
|
-
resolve,
|
|
176
|
+
waiters: [resolve],
|
|
94
177
|
timeoutHandle,
|
|
95
178
|
selections,
|
|
96
179
|
};
|
|
97
180
|
pending.set(askId, ask);
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
181
|
+
// Persist ONLY resumable origins (codex P1-4). A restart before the card
|
|
182
|
+
// lands still leaves a resumable record; restore/re-attach re-sends.
|
|
183
|
+
if (resumable) {
|
|
184
|
+
persistFromInternal(ask);
|
|
185
|
+
logger.info?.(`ask-broker: registered + persisted ask ${askId} (key=${askKey}, session=${input.sessionId})`);
|
|
186
|
+
}
|
|
187
|
+
void sendCardForAsk(ask);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/** Immutable-identity equality: a reattach/replay must match the ORIGINAL ask on
|
|
191
|
+
* every caller-independent field, not just the (already-scoped) key. Guards
|
|
192
|
+
* against a stale/crafted requestId landing on a mismatched ask (codex P1-3). */
|
|
193
|
+
function sameIdentity(ask, input) {
|
|
194
|
+
return (ask.larkAppId === input.larkAppId &&
|
|
195
|
+
ask.sessionId === input.sessionId &&
|
|
196
|
+
ask.chatId === input.chatId &&
|
|
197
|
+
ask.rootMessageId === input.rootMessageId &&
|
|
198
|
+
ask.originKind === (input.originKind ?? 'hook') &&
|
|
199
|
+
questionsShape(ask.questions) === questionsShape(input.questions));
|
|
200
|
+
}
|
|
201
|
+
/** Stable shape string for question-set equality (prompt + multiSelect + each
|
|
202
|
+
* option's key AND label, order-sensitive). Two question sets that would render
|
|
203
|
+
* a DIFFERENT card to the user — including a relabelled option whose key is
|
|
204
|
+
* unchanged — must not re-attach/replay (codex P1-2). */
|
|
205
|
+
function questionsShape(qs) {
|
|
206
|
+
return JSON.stringify(qs.map((q) => ({
|
|
207
|
+
p: q.prompt,
|
|
208
|
+
m: !!q.multiSelect,
|
|
209
|
+
o: q.options.map((o) => [o.key, o.label]),
|
|
210
|
+
})));
|
|
211
|
+
}
|
|
212
|
+
/** Max card (re-)dispatch attempts on transient failures before giving up and
|
|
213
|
+
* invalidating. Small: a card send is a single Feishu POST; if a handful of
|
|
214
|
+
* attempts across a few seconds all fail transiently, the chat is unreachable
|
|
215
|
+
* and waiting longer won't help. */
|
|
216
|
+
const CARD_DISPATCH_MAX_ATTEMPTS = 4;
|
|
217
|
+
/** Base backoff between transient re-sends (linear: base × attempt, capped). */
|
|
218
|
+
const CARD_DISPATCH_BACKOFF_MS = 500;
|
|
219
|
+
const CARD_DISPATCH_BACKOFF_CAP_MS = 4_000;
|
|
220
|
+
/** Dispatch (or idempotently re-dispatch) the card and record its messageId. The
|
|
221
|
+
* dispatcher gets a stable `dispatchUuid` (derived from the scoped key) so any
|
|
222
|
+
* re-send — a bounded transient retry here, or a restart re-attach — returns the
|
|
223
|
+
* ORIGINAL message_id instead of posting a duplicate (codex P1-1/P1-3).
|
|
224
|
+
*
|
|
225
|
+
* Retry policy (codex P1-3 partial-success): a `retryable` AskDispatchError
|
|
226
|
+
* (5xx/429/network) is retried up to CARD_DISPATCH_MAX_ATTEMPTS with linear
|
|
227
|
+
* backoff, reusing the SAME uuid — so a send that landed server-side before the
|
|
228
|
+
* socket broke converges to one logical card. A non-retryable error
|
|
229
|
+
* (deterministic 4xx / withdrawn) — or a plain untyped throw (fail closed) —
|
|
230
|
+
* invalidates immediately. */
|
|
231
|
+
function sendCardForAsk(ask) {
|
|
232
|
+
void (async () => {
|
|
233
|
+
for (let attempt = 1; attempt <= CARD_DISPATCH_MAX_ATTEMPTS; attempt++) {
|
|
234
|
+
// Bail if the ask settled out from under us (timeout / invalidate / a click
|
|
235
|
+
// on a restored dormant card) — nothing left to dispatch.
|
|
236
|
+
const live = pending.get(ask.askId);
|
|
237
|
+
if (!live || live.settled)
|
|
238
|
+
return;
|
|
239
|
+
try {
|
|
240
|
+
const { messageId } = await dispatcher.send(snapshot(ask));
|
|
241
|
+
const cur = pending.get(ask.askId);
|
|
242
|
+
if (cur && !cur.settled) {
|
|
243
|
+
cur.cardMessageId = messageId;
|
|
244
|
+
if (cur.resumable)
|
|
245
|
+
persistFromInternal(cur);
|
|
246
|
+
}
|
|
247
|
+
return; // sent (or server-deduped to the original) — done
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const retryable = err instanceof AskDispatchError && err.retryable;
|
|
251
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
252
|
+
if (retryable && attempt < CARD_DISPATCH_MAX_ATTEMPTS) {
|
|
253
|
+
const backoff = Math.min(CARD_DISPATCH_BACKOFF_MS * attempt, CARD_DISPATCH_BACKOFF_CAP_MS);
|
|
254
|
+
logger.warn?.(`ask-broker: ${ask.askId} card dispatch attempt ${attempt} failed (transient): ${msg} — retrying in ${backoff}ms`);
|
|
255
|
+
await sleep(backoff);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Deterministic failure, exhausted retries, or an untyped throw (fail
|
|
259
|
+
// closed): invalidate so the blocked CLI unblocks instead of hanging.
|
|
260
|
+
logger.warn?.(`ask-broker: ${ask.askId} card dispatch failed (${retryable ? 'transient, retries exhausted' : 'not retryable'}): ${msg}`);
|
|
261
|
+
settle(ask.askId, {
|
|
262
|
+
kind: 'invalidated', reason: `card dispatch failed: ${msg}`,
|
|
263
|
+
selected: null, by: null, comment: null, timedOut: false,
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
})();
|
|
269
|
+
}
|
|
270
|
+
/** Promise-based sleep whose timer never keeps the process alive (unref). */
|
|
271
|
+
function sleep(ms) {
|
|
272
|
+
return new Promise((resolve) => {
|
|
273
|
+
const h = setTimeout(resolve, ms);
|
|
274
|
+
h.unref?.();
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/** Find an entry by its scoped stable key (any state). */
|
|
278
|
+
function findByKey(askKey) {
|
|
279
|
+
for (const ask of pending.values())
|
|
280
|
+
if (ask.askKey === askKey)
|
|
281
|
+
return ask;
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Re-attach a reconnecting hook to a restored DORMANT ask. Two cases:
|
|
286
|
+
* - answer already stashed (click → reattach): deliver the durable-handoff
|
|
287
|
+
* result immediately, settle, remove the persisted record. No new card/timer.
|
|
288
|
+
* - still awaiting a click: install a waiter + timeout re-armed to the ORIGINAL
|
|
289
|
+
* absolute deadline; re-send the card if it was never confirmed sent.
|
|
290
|
+
*/
|
|
291
|
+
function reattachByRequest(ask) {
|
|
292
|
+
if (ask.answeredResult) {
|
|
293
|
+
const result = ask.answeredResult;
|
|
294
|
+
ask.dormant = false;
|
|
295
|
+
ask.settled = true;
|
|
296
|
+
ask.settledAt = Date.now();
|
|
297
|
+
ask.terminalResult = result;
|
|
298
|
+
clearTimeout(ask.timeoutHandle);
|
|
299
|
+
clearTimeout(ask.handoffExpiryHandle); // claimed → cancel the unclaimed-stash reaper
|
|
300
|
+
persistStore?.remove(ask.askKey); // claimed → durable record no longer needed
|
|
301
|
+
gcSettled();
|
|
302
|
+
logger.info?.(`ask-broker: re-attach delivered stashed answer for ask ${ask.askId} (key=${ask.askKey})`);
|
|
303
|
+
return Promise.resolve(result);
|
|
304
|
+
}
|
|
305
|
+
return new Promise((resolve) => {
|
|
306
|
+
ask.dormant = false;
|
|
307
|
+
ask.waiters.push(resolve);
|
|
308
|
+
clearTimeout(ask.timeoutHandle);
|
|
309
|
+
const remaining = Math.max(0, ask.deadlineAt - Date.now());
|
|
310
|
+
ask.timeoutHandle = setTimeout(() => {
|
|
311
|
+
settle(ask.askId, { kind: 'timedOut', selected: null, by: null, comment: null, timedOut: true });
|
|
312
|
+
}, remaining);
|
|
313
|
+
ask.timeoutHandle.unref?.();
|
|
314
|
+
logger.info?.(`ask-broker: re-attached hook to restored ask ${ask.askId} (key=${ask.askKey}, ` +
|
|
315
|
+
`${Math.round(remaining / 1000)}s left, card=${ask.cardMessageId ? 'live' : 'MISSING→resend'})`);
|
|
316
|
+
if (!ask.cardMessageId)
|
|
317
|
+
sendCardForAsk(ask);
|
|
118
318
|
});
|
|
119
319
|
}
|
|
320
|
+
/** Build the persisted projection from a live internal ask and write it. Only
|
|
321
|
+
* called for resumable asks. */
|
|
322
|
+
function persistFromInternal(ask) {
|
|
323
|
+
if (!persistStore || !ask.resumable)
|
|
324
|
+
return;
|
|
325
|
+
// A settled ask with a stashed answer is a durable handoff that must be KEPT
|
|
326
|
+
// until claimed; a settled ask without one has nothing left to resume.
|
|
327
|
+
if (ask.settled && !ask.answeredResult)
|
|
328
|
+
return;
|
|
329
|
+
const persisted = {
|
|
330
|
+
v: 2,
|
|
331
|
+
askKey: ask.askKey,
|
|
332
|
+
requestId: ask.requestId,
|
|
333
|
+
originKind: ask.originKind,
|
|
334
|
+
askId: ask.askId,
|
|
335
|
+
nonce: ask.nonce,
|
|
336
|
+
larkAppId: ask.larkAppId,
|
|
337
|
+
chatId: ask.chatId,
|
|
338
|
+
rootMessageId: ask.rootMessageId,
|
|
339
|
+
sessionId: ask.sessionId,
|
|
340
|
+
chatType: ask.chatType,
|
|
341
|
+
questions: ask.questions,
|
|
342
|
+
createdAt: ask.createdAt,
|
|
343
|
+
deadlineAt: ask.deadlineAt,
|
|
344
|
+
cardMessageId: ask.cardMessageId,
|
|
345
|
+
selections: ask.questions.map((_, i) => [...(ask.selections.get(i) ?? new Set())]),
|
|
346
|
+
...(ask.answeredResult ? { answeredResult: ask.answeredResult, answeredAt: ask.settledAt ?? Date.now() } : {}),
|
|
347
|
+
};
|
|
348
|
+
persistStore.put(persisted);
|
|
349
|
+
}
|
|
120
350
|
/**
|
|
121
351
|
* 勾选/取消勾选某问题的某个选项(累积模式,不 settle)。
|
|
122
352
|
*
|
|
@@ -159,6 +389,9 @@ export function toggleAsk(args) {
|
|
|
159
389
|
sel.clear();
|
|
160
390
|
sel.add(args.key);
|
|
161
391
|
}
|
|
392
|
+
// Persist the updated checkbox state so a restart mid-multi-select keeps the
|
|
393
|
+
// boxes the user already ticked (best-effort; never blocks the toggle).
|
|
394
|
+
persistFromInternal(ask);
|
|
162
395
|
return 'toggled';
|
|
163
396
|
}
|
|
164
397
|
/**
|
|
@@ -322,6 +555,105 @@ export function invalidateAll(reason) {
|
|
|
322
555
|
}
|
|
323
556
|
return ids.length;
|
|
324
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Restore pending asks from disk after a daemon restart. Each becomes a DORMANT
|
|
560
|
+
* entry: its card is still live in Feishu (we do NOT re-post — cardMessageId is
|
|
561
|
+
* preserved), so a click can settle it, but there is no waiter Promise until the
|
|
562
|
+
* surviving CLI hook reconnects and re-registers (findDormantByKey →
|
|
563
|
+
* reattachDormantAsk installs a fresh resolve). Each dormant ask arms a timer to
|
|
564
|
+
* its ORIGINAL absolute deadline so it can't linger forever if the CLI never
|
|
565
|
+
* comes back. Called once during daemon bootstrap. Returns the count restored.
|
|
566
|
+
*
|
|
567
|
+
* Idempotent-ish: an askKey already present (e.g. re-invoked) is skipped.
|
|
568
|
+
*/
|
|
569
|
+
export function restorePersistedAsks(now = Date.now(), larkAppId) {
|
|
570
|
+
let restored = 0;
|
|
571
|
+
if (!persistStore) {
|
|
572
|
+
logger.info?.('ask-broker: restore sweep skipped — no persist store wired');
|
|
573
|
+
return 0;
|
|
574
|
+
}
|
|
575
|
+
for (const p of persistStore.list(now)) {
|
|
576
|
+
// One bot per daemon process: only restore asks this daemon can actually
|
|
577
|
+
// serve (its own bot's sessions). Another bot's daemon owns the rest.
|
|
578
|
+
if (larkAppId && p.larkAppId !== larkAppId)
|
|
579
|
+
continue;
|
|
580
|
+
// Skip if this key is already live (defensive — boot runs once).
|
|
581
|
+
let dup = false;
|
|
582
|
+
for (const a of pending.values())
|
|
583
|
+
if (a.askKey === p.askKey && !a.settled) {
|
|
584
|
+
dup = true;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
if (dup)
|
|
588
|
+
continue;
|
|
589
|
+
const selections = new Map();
|
|
590
|
+
for (let i = 0; i < p.questions.length; i++) {
|
|
591
|
+
selections.set(i, new Set(p.selections?.[i] ?? []));
|
|
592
|
+
}
|
|
593
|
+
const hasStashedAnswer = p.answeredResult !== undefined;
|
|
594
|
+
// A stashed-answer restore is already terminal (settled), awaiting only the
|
|
595
|
+
// hook's claim — it must NOT arm a deadline timer (there's nothing left to
|
|
596
|
+
// time out). A still-awaiting-click restore arms the ORIGINAL absolute
|
|
597
|
+
// deadline so it can't linger forever if the CLI never returns.
|
|
598
|
+
const remaining = Math.max(0, p.deadlineAt - now);
|
|
599
|
+
const timeoutHandle = hasStashedAnswer
|
|
600
|
+
? undefined
|
|
601
|
+
: setTimeout(() => {
|
|
602
|
+
settle(p.askId, {
|
|
603
|
+
kind: 'timedOut', selected: null, by: null, comment: null, timedOut: true,
|
|
604
|
+
});
|
|
605
|
+
}, remaining);
|
|
606
|
+
timeoutHandle?.unref?.();
|
|
607
|
+
const ask = {
|
|
608
|
+
askId: p.askId,
|
|
609
|
+
askKey: p.askKey,
|
|
610
|
+
requestId: p.requestId,
|
|
611
|
+
originKind: p.originKind,
|
|
612
|
+
resumable: true, // only resumable origins were ever persisted
|
|
613
|
+
nonce: p.nonce,
|
|
614
|
+
larkAppId: p.larkAppId,
|
|
615
|
+
chatId: p.chatId,
|
|
616
|
+
rootMessageId: p.rootMessageId,
|
|
617
|
+
sessionId: p.sessionId,
|
|
618
|
+
chatType: p.chatType,
|
|
619
|
+
questions: p.questions,
|
|
620
|
+
createdAt: p.createdAt,
|
|
621
|
+
deadlineAt: p.deadlineAt,
|
|
622
|
+
cardMessageId: p.cardMessageId,
|
|
623
|
+
// A stashed-answer restore is terminal-but-unclaimed: settled=true so
|
|
624
|
+
// gcSettled/other paths treat it as done, dormant=true so a hook re-POST
|
|
625
|
+
// routes to reattachByRequest to CLAIM it.
|
|
626
|
+
settled: hasStashedAnswer,
|
|
627
|
+
settledAt: hasStashedAnswer ? (p.answeredAt ?? now) : undefined,
|
|
628
|
+
dormant: true,
|
|
629
|
+
waiters: [], // attached when the reconnecting hook re-registers
|
|
630
|
+
// Carry a stashed answer forward (user answered before restart): the
|
|
631
|
+
// reconnecting hook claims it via reattachByRequest.
|
|
632
|
+
...(p.answeredResult ? { answeredResult: p.answeredResult, terminalResult: p.answeredResult } : {}),
|
|
633
|
+
// timeoutHandle is only meaningful for the awaiting-click case; a stashed
|
|
634
|
+
// restore uses a no-op cleared handle so clearTimeout calls stay safe.
|
|
635
|
+
timeoutHandle: timeoutHandle ?? setTimeout(() => { }, 0),
|
|
636
|
+
selections,
|
|
637
|
+
};
|
|
638
|
+
if (timeoutHandle === undefined) {
|
|
639
|
+
clearTimeout(ask.timeoutHandle);
|
|
640
|
+
}
|
|
641
|
+
pending.set(p.askId, ask);
|
|
642
|
+
// Re-arm the absolute handoff-expiry reaper for a restored stash (codex
|
|
643
|
+
// P1-4): a SECOND restart before the hook claims must still reap memory +
|
|
644
|
+
// disk at the ORIGINAL answeredAt + retention, not restart the clock.
|
|
645
|
+
if (hasStashedAnswer)
|
|
646
|
+
armHandoffExpiry(ask);
|
|
647
|
+
restored++;
|
|
648
|
+
logger.info?.(`ask-broker: restored pending ask ${p.askId} (key=${p.askKey}, session=${p.sessionId}, ` +
|
|
649
|
+
`dormant${p.answeredResult ? '+answered' : ''}, ${Math.round((p.deadlineAt - now) / 1000)}s left) — awaiting hook re-attach`);
|
|
650
|
+
}
|
|
651
|
+
// Always log the boot sweep outcome (even 0) so a live restart test can
|
|
652
|
+
// confirm the restore path ran, not just infer it from behaviour.
|
|
653
|
+
logger.info?.(`ask-broker: restore sweep complete — ${restored} pending ask(s) restored` +
|
|
654
|
+
(larkAppId ? ` for ${larkAppId}` : ''));
|
|
655
|
+
return restored;
|
|
656
|
+
}
|
|
325
657
|
/** Internal — settle an ask exactly once and notify the dispatcher's onSettle
|
|
326
658
|
* hook (best-effort, never blocks broker state transitions). The settled
|
|
327
659
|
* entry stays in the map for `SETTLED_RETENTION_MS` so late race-losers get
|
|
@@ -330,48 +662,129 @@ function settle(askId, result) {
|
|
|
330
662
|
const ask = pending.get(askId);
|
|
331
663
|
if (!ask || ask.settled)
|
|
332
664
|
return;
|
|
665
|
+
// Durable handoff (codex P1-1): a dormant ask (restored after a restart, no
|
|
666
|
+
// waiter yet) that receives an ANSWER must NOT drop it into the void. Stash
|
|
667
|
+
// the terminal result and KEEP the persisted record so the reconnecting hook
|
|
668
|
+
// can claim it (reattachDormantAsk delivers + removes). Only answered results
|
|
669
|
+
// are worth stashing — a dormant ask that timed out / was invalidated has no
|
|
670
|
+
// consumer to hand off to, so it settles+cleans normally.
|
|
671
|
+
if (ask.dormant && ask.waiters.length === 0 && result.kind === 'answered') {
|
|
672
|
+
ask.settled = true;
|
|
673
|
+
ask.settledAt = Date.now();
|
|
674
|
+
ask.answeredResult = result;
|
|
675
|
+
ask.terminalResult = result;
|
|
676
|
+
clearTimeout(ask.timeoutHandle);
|
|
677
|
+
persistFromInternal(ask); // re-persist WITH answeredResult + answeredAt (kept until claimed)
|
|
678
|
+
// Arm the absolute handoff-expiry timer (codex P1-4): if the reconnecting
|
|
679
|
+
// hook never claims this stash (CLI gone for good), reap memory + disk
|
|
680
|
+
// TOGETHER at answeredAt + HANDOFF_RETENTION_MS — not just on the next boot
|
|
681
|
+
// list() sweep, which a long-running daemon never performs.
|
|
682
|
+
armHandoffExpiry(ask);
|
|
683
|
+
logger.info?.(`ask-broker: stashed answer for dormant ask ${askId} (key=${ask.askKey}) — awaiting hook claim`);
|
|
684
|
+
// Still notify the card layer so the Feishu card flips to its settled view.
|
|
685
|
+
notifyOnSettle(ask, result);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
333
688
|
ask.settled = true;
|
|
334
689
|
ask.settledAt = Date.now();
|
|
690
|
+
ask.terminalResult = result; // retained for a same-requestId replay in the ambiguous window
|
|
335
691
|
clearTimeout(ask.timeoutHandle);
|
|
692
|
+
// The durable record's job is done the moment the ask leaves the pending
|
|
693
|
+
// state (delivered to live waiters, or a terminal non-answer) — drop it so a
|
|
694
|
+
// later restart doesn't resurrect a settled ask.
|
|
695
|
+
persistStore?.remove(ask.askKey);
|
|
336
696
|
// Reap older settled entries opportunistically — keeps the map bounded
|
|
337
697
|
// without paying for a dedicated GC timer.
|
|
338
698
|
gcSettled();
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
if (dispatcher?.onSettle) {
|
|
699
|
+
// Resolve every waiter (normally one; >1 when a same-requestId active replay
|
|
700
|
+
// joined). A dormant ask has no waiters — the loop simply no-ops.
|
|
701
|
+
const waiters = ask.waiters;
|
|
702
|
+
ask.waiters = [];
|
|
703
|
+
for (const w of waiters) {
|
|
346
704
|
try {
|
|
347
|
-
|
|
348
|
-
logger.warn?.(`ask-broker: ${askId} onSettle failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
349
|
-
});
|
|
705
|
+
w(result);
|
|
350
706
|
}
|
|
351
707
|
catch (err) {
|
|
352
|
-
logger.warn?.(`ask-broker: ${askId}
|
|
708
|
+
logger.warn?.(`ask-broker: ${askId} waiter threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
353
709
|
}
|
|
354
710
|
}
|
|
711
|
+
notifyOnSettle(ask, result);
|
|
712
|
+
}
|
|
713
|
+
/** Notify the IM-side dispatcher's onSettle hook (best-effort — never blocks
|
|
714
|
+
* broker state). Shared by the normal settle path and the dormant-answer stash. */
|
|
715
|
+
function notifyOnSettle(ask, result) {
|
|
716
|
+
if (!dispatcher?.onSettle)
|
|
717
|
+
return;
|
|
718
|
+
try {
|
|
719
|
+
void Promise.resolve(dispatcher.onSettle(snapshot(ask), result)).catch((err) => {
|
|
720
|
+
logger.warn?.(`ask-broker: ${ask.askId} onSettle failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
catch (err) {
|
|
724
|
+
logger.warn?.(`ask-broker: ${ask.askId} onSettle threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
725
|
+
}
|
|
355
726
|
}
|
|
356
727
|
/** Strip broker-internal fields before handing a snapshot to the IM-side
|
|
357
|
-
* dispatcher. Keeps the dispatcher contract narrow. */
|
|
728
|
+
* dispatcher. Keeps the dispatcher contract narrow (PendingAsk only). */
|
|
358
729
|
function snapshot(ask) {
|
|
359
|
-
const {
|
|
730
|
+
const {
|
|
731
|
+
// Runtime-only / broker-internal fields excluded from the IM contract:
|
|
732
|
+
waiters: _w, timeoutHandle: _t, settledAt: _sat, selections: _sel, askKey: _ak, requestId: _rid, originKind: _ok, resumable: _rs, dormant: _dm, answeredResult: _ar, terminalResult: _tr, ...rest } = ask;
|
|
360
733
|
return {
|
|
361
734
|
...rest,
|
|
362
735
|
selections: ask.questions.map((_, i) => [...(ask.selections.get(i) ?? new Set())]),
|
|
736
|
+
// EVERY ask carries a scoped dedupe token (codex P1-1): the broker's bounded
|
|
737
|
+
// retry re-sends the card, and a re-send without a uuid posts a DUPLICATE on
|
|
738
|
+
// the Feishu side. The uuid is the card's dedupe identity for THIS broker
|
|
739
|
+
// lifetime — derived from the scoped askKey, independent of persistence.
|
|
740
|
+
// `resumable` gates only cross-restart persistence, NOT intra-process
|
|
741
|
+
// dispatch idempotency, so explicit / PTY asks (which also retry) dedupe too.
|
|
742
|
+
dispatchUuid: dispatchUuidForKey(ask.askKey),
|
|
363
743
|
};
|
|
364
744
|
}
|
|
365
745
|
/** Drop settled entries that have aged past the retention window. Cheap O(n)
|
|
366
|
-
* walk — n is tiny in practice (≤ a few dozen pending+recent asks).
|
|
746
|
+
* walk — n is tiny in practice (≤ a few dozen pending+recent asks). A dormant
|
|
747
|
+
* ask holding a stashed answer (durable handoff, awaiting the hook's claim) is
|
|
748
|
+
* NEVER reaped from memory here — it lives until claimed or its deadline; on
|
|
749
|
+
* disk it survives regardless, and a restart re-hydrates it. */
|
|
367
750
|
function gcSettled() {
|
|
368
751
|
const cutoff = Date.now() - SETTLED_RETENTION_MS;
|
|
369
752
|
for (const [id, ask] of pending) {
|
|
753
|
+
if (ask.dormant && ask.answeredResult)
|
|
754
|
+
continue; // unclaimed handoff — keep
|
|
370
755
|
if (ask.settled && ask.settledAt !== undefined && ask.settledAt < cutoff) {
|
|
371
756
|
pending.delete(id);
|
|
372
757
|
}
|
|
373
758
|
}
|
|
374
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Arm an absolute-expiry timer for an unclaimed handoff stash (codex P1-4). A
|
|
762
|
+
* stashed answer is kept until the reconnecting hook claims it — but if the CLI
|
|
763
|
+
* is gone for good, nothing would ever remove it from a long-running daemon's
|
|
764
|
+
* memory (gcSettled deliberately skips unclaimed handoffs) OR from disk (the
|
|
765
|
+
* disk reap only runs in list() at boot). This timer closes both: at
|
|
766
|
+
* `answeredAt + HANDOFF_RETENTION_MS` it deletes the in-memory entry AND the
|
|
767
|
+
* persisted record TOGETHER, so memory and disk never diverge.
|
|
768
|
+
*
|
|
769
|
+
* Idempotent: clears any prior handle first (re-arming on a second restore is
|
|
770
|
+
* fine). The timer is unref'd so it never keeps the process alive.
|
|
771
|
+
*/
|
|
772
|
+
function armHandoffExpiry(ask) {
|
|
773
|
+
clearTimeout(ask.handoffExpiryHandle);
|
|
774
|
+
const stashedAt = ask.settledAt ?? Date.now();
|
|
775
|
+
const fireIn = Math.max(0, stashedAt + handoffRetentionMs - Date.now());
|
|
776
|
+
ask.handoffExpiryHandle = setTimeout(() => {
|
|
777
|
+
// Only reap if STILL an unclaimed stash (a claim clears this handle, but
|
|
778
|
+
// guard against a late fire racing a claim).
|
|
779
|
+
const cur = pending.get(ask.askId);
|
|
780
|
+
if (!cur || !cur.dormant || !cur.answeredResult)
|
|
781
|
+
return;
|
|
782
|
+
pending.delete(cur.askId);
|
|
783
|
+
persistStore?.remove(cur.askKey);
|
|
784
|
+
logger.info?.(`ask-broker: handoff stash expired unclaimed for ask ${cur.askId} (key=${cur.askKey}) — reaped memory + disk`);
|
|
785
|
+
}, fireIn);
|
|
786
|
+
ask.handoffExpiryHandle.unref?.();
|
|
787
|
+
}
|
|
375
788
|
// ---- diagnostics for tests ---------------------------------------------------
|
|
376
789
|
/** Count of asks still awaiting a click / timeout — excludes settled entries
|
|
377
790
|
* retained within the race-loser feedback window. For tests and metrics only. */
|
|
@@ -442,10 +855,17 @@ export function _allAskIds() {
|
|
|
442
855
|
/** Reset broker state — for tests only. Does NOT resolve outstanding promises,
|
|
443
856
|
* so tests must not call this while real CLI processes might be waiting. */
|
|
444
857
|
export function _resetForTest() {
|
|
445
|
-
for (const ask of pending.values())
|
|
858
|
+
for (const ask of pending.values()) {
|
|
446
859
|
clearTimeout(ask.timeoutHandle);
|
|
860
|
+
clearTimeout(ask.handoffExpiryHandle);
|
|
861
|
+
}
|
|
447
862
|
pending.clear();
|
|
448
863
|
dispatcher = null;
|
|
449
864
|
canTalkChecker = null;
|
|
865
|
+
handoffRetentionMs = HANDOFF_RETENTION_MS; // restore default retention
|
|
866
|
+
// Detach the injected store — never delete files here (codex P1-4: a helper
|
|
867
|
+
// reaping a shared dataDir could wipe a LIVE pending ask). Tests own their
|
|
868
|
+
// temp dir's cleanup via their own teardown.
|
|
869
|
+
persistStore = null;
|
|
450
870
|
}
|
|
451
871
|
//# sourceMappingURL=ask-broker.js.map
|