@jingyi0605/codingns 0.3.6 → 0.5.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.md +3 -0
- package/bin/codingns.mjs +913 -1
- package/dist/public/assets/AdaptiveButlerPage-B153lk5H.css +1 -0
- package/dist/public/assets/AdaptiveButlerPage-R-XZw7pd.js +3 -0
- package/dist/public/assets/App-DUAg5urj.css +1 -0
- package/dist/public/assets/App-DkvE5EyM.js +30 -0
- package/dist/public/assets/BootstrapPage-Vu5oEJ8z.js +1 -0
- package/dist/public/assets/ConversationPage-Cjpg6g0J.js +2 -0
- package/dist/public/assets/DesktopDetachPreviewPage-BgeEqbc5.js +1 -0
- package/dist/public/assets/DesktopWindowPage-1WelvxdH.js +2 -0
- package/dist/public/assets/FileContextPanel-D_ghXJuW.js +1 -0
- package/dist/public/assets/GitSidebar-D9f9Jxwr.js +6 -0
- package/dist/public/assets/MobileCreateSessionSheet-DLq5qPkx.js +1 -0
- package/dist/public/assets/MobileSheet-DLg-gX1t.js +1 -0
- package/dist/public/assets/MobileTopHeaderFrame-DArgZI7L.js +1 -0
- package/dist/public/assets/MobileWorkspaceSwitcherHeader-0ywJKfBQ.js +1 -0
- package/dist/public/assets/ServerSettingsModal-izoYMx9U.js +1 -0
- package/dist/public/assets/SessionIndexPage-C5aG8FIv.js +1 -0
- package/dist/public/assets/SettingsPage-HJIC-P-4.js +1 -0
- package/dist/public/assets/TerminalManagerPanel-DpyUTo9k.js +1 -0
- package/dist/public/assets/{TerminalPage-D00S4KM6.js → TerminalPage-CtKXIU0h.js} +19 -19
- package/dist/public/assets/TerminalRuntimeFallbackModal-CRhOQOsT.js +1 -0
- package/dist/public/assets/ToolFilesPage-DcYPsS-e.js +1 -0
- package/dist/public/assets/ToolGitPage-CsPl89ty.js +1 -0
- package/dist/public/assets/ToolProcessesPage-D0dvR8xK.js +1 -0
- package/dist/public/assets/ToolsHomePage-4fP-KRiv.js +1 -0
- package/dist/public/assets/WorkbenchLandingPage-kvlfyxRo.js +1 -0
- package/dist/public/assets/WorkbenchLayout-ByFw4eeu.js +3 -0
- package/dist/public/assets/WorkbenchModal-Ctob14VR.js +1 -0
- package/dist/public/assets/WorkbenchShellRoute-BUITtdAg.css +1 -0
- package/dist/public/assets/WorkbenchShellRoute-Kw7JEZI3.js +1 -0
- package/dist/public/assets/WorkspaceDebugDetailPage-Com5kEXJ.js +1 -0
- package/dist/public/assets/WorkspaceDetailPage-D0Lrx4Uz.js +1 -0
- package/dist/public/assets/WorkspaceHomePage-wR8d3aP9.js +1 -0
- package/dist/public/assets/butler-records-events-DgWCG364.js +1 -0
- package/dist/public/assets/default-session-permission-mode-CcGwR4Kk.js +1 -0
- package/dist/public/assets/event-DvH9tcej.js +1 -0
- package/dist/public/assets/file-tree-icon-UFVoVzhM.js +31 -0
- package/dist/public/assets/index-Byp9hJ0c.js +42 -0
- package/dist/public/assets/index-_52jxu4a.css +1 -0
- package/dist/public/assets/preferences-service-KIYeE2gk.js +1 -0
- package/dist/public/assets/session-runtime-machine-0KNSSPp5.js +17 -0
- package/dist/public/assets/styles-BWPBZvze.css +1 -0
- package/dist/public/assets/styles-CSUx5LGe.js +1 -0
- package/dist/public/assets/terminal-runtime-meta-AWXJpN4r.js +1 -0
- package/dist/public/assets/useRegisteredDebugTemplates-DBDRdptr.js +1 -0
- package/dist/public/assets/window-BWqRixxq.js +1 -0
- package/dist/public/index.html +2 -2
- package/dist/server/middlewares/auth-guard.d.ts +4 -0
- package/dist/server/middlewares/auth-guard.js +42 -4
- package/dist/server/middlewares/auth-guard.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +234 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +365 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +262 -2
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +737 -3
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
- package/dist/server/modules/auth/auth-controller.d.ts +11 -1
- package/dist/server/modules/auth/auth-controller.js +61 -2
- package/dist/server/modules/auth/auth-controller.js.map +1 -1
- package/dist/server/modules/auth/auth-device-display-name.d.ts +10 -0
- package/dist/server/modules/auth/auth-device-display-name.js +190 -0
- package/dist/server/modules/auth/auth-device-display-name.js.map +1 -0
- package/dist/server/modules/auth/auth-service.d.ts +80 -5
- package/dist/server/modules/auth/auth-service.js +333 -23
- package/dist/server/modules/auth/auth-service.js.map +1 -1
- package/dist/server/modules/butler/assistant-automation-service.d.ts +112 -0
- package/dist/server/modules/butler/assistant-automation-service.js +832 -0
- package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
- package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
- package/dist/server/modules/butler/assistant-sandbox-cleanup-scheduler.d.ts +32 -0
- package/dist/server/modules/butler/assistant-sandbox-cleanup-scheduler.js +93 -0
- package/dist/server/modules/butler/assistant-sandbox-cleanup-scheduler.js.map +1 -0
- package/dist/server/modules/butler/assistant-sandbox-service.d.ts +69 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js +399 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
- package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-action-context-service.js +8 -2
- package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
- package/dist/server/modules/butler/butler-auth-service.js +7 -2
- package/dist/server/modules/butler/butler-auth-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-session-service.d.ts +11 -1
- package/dist/server/modules/butler/butler-control-session-service.js +173 -40
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
- package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
- package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
- package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
- package/dist/server/modules/butler/butler-controller.d.ts +42 -2
- package/dist/server/modules/butler/butler-controller.js +79 -12
- package/dist/server/modules/butler/butler-controller.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.d.ts +2 -1
- package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js +27 -25
- package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-service.d.ts +41 -5
- package/dist/server/modules/butler/butler-follow-up-service.js +568 -371
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
- package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-service.js +1 -0
- package/dist/server/modules/butler/butler-inbox-service.js.map +1 -1
- package/dist/server/modules/butler/butler-profile-service.js +2 -5
- package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
- package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-project-service.js +7 -1
- package/dist/server/modules/butler/butler-project-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-service.d.ts +5 -1
- package/dist/server/modules/butler/butler-session-service.js +26 -1
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
- package/dist/server/modules/butler/butler-workspace-context.d.ts +4 -1
- package/dist/server/modules/butler/butler-workspace-context.js +204 -58
- package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
- package/dist/server/modules/butler/patrol-execution-service.js +2 -1
- package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
- package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
- package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
- package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
- package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
- package/dist/server/modules/butler/verification-run-service.js +188 -34
- package/dist/server/modules/butler/verification-run-service.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-controller.js +1 -1
- package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-service.d.ts +7 -2
- package/dist/server/modules/debug-target/debug-target-service.js +563 -100
- package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-client.d.ts +1 -0
- package/dist/server/modules/git/git-command-helper-client.js +19 -26
- package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
- package/dist/server/modules/git/git-command-runner.js +19 -1
- package/dist/server/modules/git/git-command-runner.js.map +1 -1
- package/dist/server/modules/preferences/profile-service.d.ts +3 -1
- package/dist/server/modules/preferences/profile-service.js +74 -3
- package/dist/server/modules/preferences/profile-service.js.map +1 -1
- package/dist/server/modules/provider/provider-controller.d.ts +1 -1
- package/dist/server/modules/provider/provider-controller.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +5 -3
- package/dist/server/modules/provider/provider-discovery-helper-client.js +129 -43
- package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-process.js +44 -0
- package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-runtime.js +83 -3
- package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-identity-service.d.ts +10 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-identity-service.js +48 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-identity-service.js.map +1 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.d.ts +48 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.js +11 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.js.map +1 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.d.ts +74 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.js +302 -0
- package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.js.map +1 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-controller.d.ts +33 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-controller.js +57 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-controller.js.map +1 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-edge-proof.d.ts +9 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-edge-proof.js +25 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-edge-proof.js.map +1 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.d.ts +18 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.js +230 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.js.map +1 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.d.ts +41 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js +443 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js.map +1 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-service.d.ts +111 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-service.js +771 -0
- package/dist/server/modules/relay-tunnel/relay-tunnel-service.js.map +1 -0
- package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
- package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +2 -1
- package/dist/server/modules/sessions/codex-app-server-helper-client.js +78 -0
- package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/codex-app-server-helper-process.js +84 -2
- package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
- package/dist/server/modules/sessions/provider-session-delete-cli.d.ts +15 -0
- package/dist/server/modules/sessions/provider-session-delete-cli.js +148 -0
- package/dist/server/modules/sessions/provider-session-delete-cli.js.map +1 -0
- package/dist/server/modules/sessions/session-controller.d.ts +4 -1
- package/dist/server/modules/sessions/session-controller.js +4 -0
- package/dist/server/modules/sessions/session-controller.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +24 -1
- package/dist/server/modules/sessions/session-history-service.js +401 -42
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-router-service.d.ts +25 -0
- package/dist/server/modules/sessions/session-live-runtime-router-service.js +42 -0
- package/dist/server/modules/sessions/session-live-runtime-router-service.js.map +1 -0
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +130 -28
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-message-attachment-service.d.ts +1 -0
- package/dist/server/modules/sessions/session-message-attachment-service.js +22 -0
- package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
- package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
- package/dist/server/modules/sessions/session-permission-request-service.d.ts +1 -0
- package/dist/server/modules/sessions/session-permission-request-service.js +367 -5
- package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
- package/dist/server/modules/sessions/session-provider-error-mapper.js +32 -0
- package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
- package/dist/server/modules/sessions/session-provider-usage-guard-service.d.ts +37 -0
- package/dist/server/modules/sessions/session-provider-usage-guard-service.js +179 -0
- package/dist/server/modules/sessions/session-provider-usage-guard-service.js.map +1 -0
- package/dist/server/modules/sessions/session-provider-usage-limit.d.ts +17 -0
- package/dist/server/modules/sessions/session-provider-usage-limit.js +465 -0
- package/dist/server/modules/sessions/session-provider-usage-limit.js.map +1 -0
- package/dist/server/modules/skills/assistant-runtime-skill-catalog.d.ts +8 -0
- package/dist/server/modules/skills/assistant-runtime-skill-catalog.js +26 -0
- package/dist/server/modules/skills/assistant-runtime-skill-catalog.js.map +1 -0
- package/dist/server/modules/skills/assistant-runtime-skill-cleanup.d.ts +9 -0
- package/dist/server/modules/skills/assistant-runtime-skill-cleanup.js +55 -0
- package/dist/server/modules/skills/assistant-runtime-skill-cleanup.js.map +1 -0
- package/dist/server/modules/skills/builtin-skill-service.js +1 -6
- package/dist/server/modules/skills/builtin-skill-service.js.map +1 -1
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +19 -12
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +9 -3
- package/dist/server/modules/skills/skill-controller.d.ts +2 -2
- package/dist/server/modules/skills/skill-controller.js +9 -1
- package/dist/server/modules/skills/skill-controller.js.map +1 -1
- package/dist/server/modules/skills/skill-manager-service.d.ts +26 -1
- package/dist/server/modules/skills/skill-manager-service.js +346 -90
- package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
- package/dist/server/modules/skills/skill-name-policy.d.ts +2 -0
- package/dist/server/modules/skills/skill-name-policy.js +10 -0
- package/dist/server/modules/skills/skill-name-policy.js.map +1 -0
- package/dist/server/modules/tailscale/tailscale-service.d.ts +2 -0
- package/dist/server/modules/tailscale/tailscale-service.js +21 -8
- package/dist/server/modules/tailscale/tailscale-service.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-client.d.ts +5 -2
- package/dist/server/modules/tasks/task-helper-client.js +118 -38
- package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-process.js +94 -3
- package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +6 -0
- package/dist/server/modules/tasks/task-types.js +7 -1
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/terminal/command-template-service.d.ts +9 -0
- package/dist/server/modules/terminal/command-template-service.js +87 -5
- package/dist/server/modules/terminal/command-template-service.js.map +1 -1
- package/dist/server/modules/terminal/template-reverse-proxy-service.js +71 -3
- package/dist/server/modules/terminal/template-reverse-proxy-service.js.map +1 -1
- package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
- package/dist/server/modules/terminal/terminal-controller.js +41 -0
- package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
- package/dist/server/modules/workbench/workbench-service.js +4 -3
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
- package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
- package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +2 -0
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +32 -3
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-manager.d.ts +9 -1
- package/dist/server/modules/worktree/worktree-manager.js +9 -1
- package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
- package/dist/server/routes/assistant.js +49 -0
- package/dist/server/routes/assistant.js.map +1 -1
- package/dist/server/routes/auth.js +4 -0
- package/dist/server/routes/auth.js.map +1 -1
- package/dist/server/routes/butler.js +5 -0
- package/dist/server/routes/butler.js.map +1 -1
- package/dist/server/routes/sessions.js +1 -0
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/system.d.ts +2 -1
- package/dist/server/routes/system.js +13 -1
- package/dist/server/routes/system.js.map +1 -1
- package/dist/server/server/create-server.d.ts +18 -0
- package/dist/server/server/create-server.js +113 -20
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/shared/utils/tokens.d.ts +3 -1
- package/dist/server/shared/utils/tokens.js +9 -2
- package/dist/server/shared/utils/tokens.js.map +1 -1
- package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +17 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js +179 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +18 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +191 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
- package/dist/server/storage/repositories/auth-device-repository.d.ts +22 -0
- package/dist/server/storage/repositories/auth-device-repository.js +97 -0
- package/dist/server/storage/repositories/auth-device-repository.js.map +1 -0
- package/dist/server/storage/repositories/auth-device-session-repository.d.ts +17 -0
- package/dist/server/storage/repositories/auth-device-session-repository.js +82 -0
- package/dist/server/storage/repositories/auth-device-session-repository.js.map +1 -0
- package/dist/server/storage/repositories/auth-login-event-repository.d.ts +9 -0
- package/dist/server/storage/repositories/auth-login-event-repository.js +53 -0
- package/dist/server/storage/repositories/auth-login-event-repository.js.map +1 -0
- package/dist/server/storage/repositories/auth-token-repository.d.ts +4 -0
- package/dist/server/storage/repositories/auth-token-repository.js +58 -5
- package/dist/server/storage/repositories/auth-token-repository.js.map +1 -1
- package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
- package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
- package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-follow-up-task-repository.js +21 -3
- package/dist/server/storage/repositories/butler-follow-up-task-repository.js.map +1 -1
- package/dist/server/storage/repositories/instance-relay-tunnel-identity-repository.d.ts +8 -0
- package/dist/server/storage/repositories/instance-relay-tunnel-identity-repository.js +52 -0
- package/dist/server/storage/repositories/instance-relay-tunnel-identity-repository.js.map +1 -0
- package/dist/server/storage/repositories/instance-relay-tunnel-repository.d.ts +10 -0
- package/dist/server/storage/repositories/instance-relay-tunnel-repository.js +153 -0
- package/dist/server/storage/repositories/instance-relay-tunnel-repository.js.map +1 -0
- package/dist/server/storage/repositories/instance-tailscale-repository.js +6 -3
- package/dist/server/storage/repositories/instance-tailscale-repository.js.map +1 -1
- package/dist/server/storage/repositories/managed-skill-repository.d.ts +2 -1
- package/dist/server/storage/repositories/managed-skill-repository.js +14 -4
- package/dist/server/storage/repositories/managed-skill-repository.js.map +1 -1
- package/dist/server/storage/repositories/session-message-attachment-repository.d.ts +2 -0
- package/dist/server/storage/repositories/session-message-attachment-repository.js +24 -0
- package/dist/server/storage/repositories/session-message-attachment-repository.js.map +1 -1
- package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
- package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
- package/dist/server/storage/sqlite/client.js +534 -2
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +228 -4
- package/dist/server/types/domain.d.ts +170 -2
- package/dist/server/ws/workbench-ws-hub.d.ts +14 -8
- package/dist/server/ws/workbench-ws-hub.js +369 -209
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/dist/server/ws/ws-auth-guard.js +1 -4
- package/dist/server/ws/ws-auth-guard.js.map +1 -1
- package/dist/server/ws/ws-server.d.ts +1 -1
- package/dist/server/ws/ws-server.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/codex-resume-history.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/codex-resume-history.js +80 -0
- package/node_modules/@codingns/session-sync-core/dist/codex-resume-history.js.map +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +5 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +122 -4
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +17 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +437 -51
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +7 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +240 -27
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +5 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +108 -2
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +3 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +101 -8
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +5 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +153 -60
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/services.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/services.js +24 -8
- package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +6 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +0 -33
- package/dist/public/assets/index-BlOinYqR.js +0 -122
- package/dist/public/assets/index-Dg_7g6lA.css +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import { AppError } from "../../shared/errors/app-error.js";
|
|
5
2
|
import { createId } from "../../shared/utils/id.js";
|
|
6
3
|
import { nowIso } from "../../shared/utils/time.js";
|
|
7
|
-
import { ensureButlerWorkspaceIsolation } from "./butler-profile-service.js";
|
|
8
4
|
import { resolveButlerCodexBackgroundModel } from "./butler-codex-model-policy.js";
|
|
5
|
+
import { normalizeProviderUsageLimit } from "../sessions/session-provider-usage-limit.js";
|
|
6
|
+
import { resolveProviderUsageLimitBlockedUntil, resolveProviderUsageLimitFromError, SessionProviderUsageLimitGuardService } from "../sessions/session-provider-usage-guard-service.js";
|
|
9
7
|
const DEFAULT_CHECK_INTERVAL_SECONDS = 300;
|
|
10
8
|
const MIN_CHECK_INTERVAL_SECONDS = 60;
|
|
11
9
|
const MAX_CHECK_INTERVAL_SECONDS = 3600;
|
|
@@ -15,6 +13,8 @@ const MAX_MAX_AUTO_CONTINUE_COUNT = 20;
|
|
|
15
13
|
const FOLLOW_UP_EVALUATOR_DIRNAME = ".butler-follow-up-evaluator";
|
|
16
14
|
const RECENT_HISTORY_LIMIT = 40;
|
|
17
15
|
const FOLLOW_UP_PERMISSION_CHECK_INTERVAL_MS = 10_000;
|
|
16
|
+
const FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT_MS = 20 * 60_000;
|
|
17
|
+
const FOLLOW_UP_ASSISTANT_WAIT_POLL_INTERVAL_MS = 2_000;
|
|
18
18
|
const FOLLOW_UP_AUTO_APPROVE_ACTION_PREFERENCE = [
|
|
19
19
|
"acceptForSession",
|
|
20
20
|
"allow_session",
|
|
@@ -23,6 +23,12 @@ const FOLLOW_UP_AUTO_APPROVE_ACTION_PREFERENCE = [
|
|
|
23
23
|
"once",
|
|
24
24
|
"allow"
|
|
25
25
|
];
|
|
26
|
+
class FollowUpTaskCancelledError extends Error {
|
|
27
|
+
constructor() {
|
|
28
|
+
super("FOLLOW_UP_TASK_CANCELLED");
|
|
29
|
+
this.name = "FollowUpTaskCancelledError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
26
32
|
export class ButlerFollowUpService {
|
|
27
33
|
butlerProfileService;
|
|
28
34
|
butlerProjectService;
|
|
@@ -37,8 +43,10 @@ export class ButlerFollowUpService {
|
|
|
37
43
|
followUpCodexHomeDir;
|
|
38
44
|
sourceCodexHomeDir;
|
|
39
45
|
sessionMessageOriginRepository;
|
|
46
|
+
providerUsageLimitGuardService;
|
|
40
47
|
permissionRequestSweepAtByTaskId = new Map();
|
|
41
|
-
|
|
48
|
+
activeExecutionStateByTaskId = new Map();
|
|
49
|
+
constructor(butlerProfileService, butlerProjectService, butlerSessionService, butlerFollowUpTaskRepository, sessionHistoryService, sessionIndexRepository, sessionLiveRuntimeService, workspaceService, providerAdapterRegistry, instructionAdapter, followUpCodexHomeDir = null, sourceCodexHomeDir = null, sessionMessageOriginRepository = null, providerUsageLimitGuardService = new SessionProviderUsageLimitGuardService(sessionHistoryService)) {
|
|
42
50
|
this.butlerProfileService = butlerProfileService;
|
|
43
51
|
this.butlerProjectService = butlerProjectService;
|
|
44
52
|
this.butlerSessionService = butlerSessionService;
|
|
@@ -52,6 +60,7 @@ export class ButlerFollowUpService {
|
|
|
52
60
|
this.followUpCodexHomeDir = followUpCodexHomeDir;
|
|
53
61
|
this.sourceCodexHomeDir = sourceCodexHomeDir;
|
|
54
62
|
this.sessionMessageOriginRepository = sessionMessageOriginRepository;
|
|
63
|
+
this.providerUsageLimitGuardService = providerUsageLimitGuardService;
|
|
55
64
|
}
|
|
56
65
|
listTasks(filters = {}) {
|
|
57
66
|
this.butlerProfileService.ensureInitialized();
|
|
@@ -78,9 +87,41 @@ export class ButlerFollowUpService {
|
|
|
78
87
|
const index = this.sessionIndexRepository.findIndexRecordBySessionId(task.sessionId);
|
|
79
88
|
return mapTaskView(task, project.workspaceId, project.name, index?.title ?? null);
|
|
80
89
|
}
|
|
90
|
+
toTaskView(task) {
|
|
91
|
+
const project = this.butlerProjectService.getById(task.projectId);
|
|
92
|
+
const index = this.sessionIndexRepository.findIndexRecordBySessionId(task.sessionId);
|
|
93
|
+
return mapTaskView(task, project.workspaceId, project.name, index?.title ?? null);
|
|
94
|
+
}
|
|
95
|
+
requireTaskForAssistantUpdate(taskId, userId) {
|
|
96
|
+
this.butlerProfileService.ensureInitialized();
|
|
97
|
+
const task = this.butlerFollowUpTaskRepository.findById(taskId);
|
|
98
|
+
if (!task) {
|
|
99
|
+
throw new AppError({
|
|
100
|
+
statusCode: 404,
|
|
101
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_NOT_FOUND",
|
|
102
|
+
detail: "未找到对应的跟进任务"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (task.createdByUserId !== userId) {
|
|
106
|
+
throw new AppError({
|
|
107
|
+
statusCode: 403,
|
|
108
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_FORBIDDEN",
|
|
109
|
+
detail: "你没有权限更新这个跟进任务"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (task.status !== "active") {
|
|
113
|
+
throw new AppError({
|
|
114
|
+
statusCode: 409,
|
|
115
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_NOT_ACTIVE",
|
|
116
|
+
detail: "当前跟进任务已经不处于可回写状态"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return task;
|
|
120
|
+
}
|
|
81
121
|
async createTask(input, userId) {
|
|
82
122
|
this.butlerProfileService.ensureInitialized();
|
|
83
123
|
const project = this.butlerProjectService.getById(input.projectId);
|
|
124
|
+
const providerId = normalizeFollowUpProviderId(input.providerId);
|
|
84
125
|
const objective = normalizeObjective(input.objective);
|
|
85
126
|
const completionCriteria = normalizeCompletionCriteria(input.completionCriteria, objective);
|
|
86
127
|
const maxAutoContinueCount = normalizeMaxAutoContinueCount(input.maxAutoContinueCount);
|
|
@@ -95,15 +136,38 @@ export class ButlerFollowUpService {
|
|
|
95
136
|
});
|
|
96
137
|
}
|
|
97
138
|
const session = this.sessionHistoryService.getSession(snapshot.sessionId, userId);
|
|
139
|
+
const inspection = await this.inspectSession(snapshot.sessionId, userId);
|
|
140
|
+
const assistantSession = await this.butlerSessionService.startSession(project.id, {
|
|
141
|
+
providerId,
|
|
142
|
+
role: "adhoc",
|
|
143
|
+
ownershipMode: "managed",
|
|
144
|
+
content: buildFollowUpBootstrapPrompt({
|
|
145
|
+
project,
|
|
146
|
+
sourceButlerSessionId: input.butlerSessionId,
|
|
147
|
+
sourceSessionId: snapshot.sessionId,
|
|
148
|
+
sourceSessionTitle: inspection.sessionTitle,
|
|
149
|
+
objective,
|
|
150
|
+
completionCriteria,
|
|
151
|
+
maxAutoContinueCount,
|
|
152
|
+
latestAssistantText: inspection.latestAssistantText,
|
|
153
|
+
transcriptLines: inspection.transcriptLines
|
|
154
|
+
}),
|
|
155
|
+
model: resolveFollowUpModel(providerId, this.sourceCodexHomeDir),
|
|
156
|
+
reasoningLevel: "low",
|
|
157
|
+
permissionMode: "default"
|
|
158
|
+
}, userId);
|
|
98
159
|
const timestamp = nowIso();
|
|
99
160
|
const initialSummary = snapshot.runningState === "starting" || snapshot.runningState === "running"
|
|
100
|
-
?
|
|
101
|
-
:
|
|
161
|
+
? `已创建跟进助手会话,先等待当前运行结束,再由该会话决定是否继续推进。默认最多自动推进 ${maxAutoContinueCount} 轮。`
|
|
162
|
+
: `已创建跟进助手会话,准备由该会话检查当前进展并决定是否继续推进。默认最多自动推进 ${maxAutoContinueCount} 轮。`;
|
|
102
163
|
const task = this.butlerFollowUpTaskRepository.create({
|
|
103
164
|
id: createId(),
|
|
104
165
|
projectId: project.id,
|
|
105
166
|
butlerSessionId: input.butlerSessionId,
|
|
106
167
|
sessionId: snapshot.sessionId,
|
|
168
|
+
providerId,
|
|
169
|
+
assistantButlerSessionId: assistantSession.id,
|
|
170
|
+
assistantSessionId: assistantSession.sessionId,
|
|
107
171
|
createdByUserId: userId,
|
|
108
172
|
objective,
|
|
109
173
|
completionCriteria,
|
|
@@ -129,7 +193,144 @@ export class ButlerFollowUpService {
|
|
|
129
193
|
const processed = await this.processTask(task.id);
|
|
130
194
|
return mapTaskView(processed, project.workspaceId, project.name, session.title ?? null);
|
|
131
195
|
}
|
|
132
|
-
|
|
196
|
+
async continueTask(taskId, input, userId) {
|
|
197
|
+
const summary = requireNonEmptyFollowUpText(input.summary, "summary", "继续推进必须提供 summary");
|
|
198
|
+
const continuePrompt = requireNonEmptyFollowUpText(input.continuePrompt, "continuePrompt", "继续推进必须提供 continuePrompt");
|
|
199
|
+
const task = this.requireTaskForAssistantUpdate(taskId, userId);
|
|
200
|
+
if (hasReachedAutoContinueLimit(task)) {
|
|
201
|
+
throw new AppError({
|
|
202
|
+
statusCode: 409,
|
|
203
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_LIMIT_REACHED",
|
|
204
|
+
detail: "当前跟进任务已经达到自动推进上限,不能继续自动推进"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
this.butlerSessionService.captureSessionSnapshot(task.projectId, task.butlerSessionId, task.createdByUserId, { sourceKind: "manual" });
|
|
208
|
+
const inspection = await this.inspectTask(task);
|
|
209
|
+
const timestamp = nowIso();
|
|
210
|
+
const runningState = normalizeRunningState(inspection.runningState);
|
|
211
|
+
const nextAutoContinueCount = task.autoContinueCount + 1;
|
|
212
|
+
const updated = this.persistWithRound({
|
|
213
|
+
...task,
|
|
214
|
+
status: "active",
|
|
215
|
+
lastCheckedAt: timestamp,
|
|
216
|
+
lastObservedRunningState: runningState,
|
|
217
|
+
lastObservedMessageAt: inspection.messageAt,
|
|
218
|
+
lastObservedMessageCount: inspection.messageCount,
|
|
219
|
+
waitingReason: null,
|
|
220
|
+
nextCheckAt: shiftSeconds(timestamp, task.checkIntervalSeconds),
|
|
221
|
+
lastAutomationSummary: summary,
|
|
222
|
+
lastAutomationAt: timestamp,
|
|
223
|
+
autoContinueCount: nextAutoContinueCount,
|
|
224
|
+
updatedAt: timestamp,
|
|
225
|
+
completedAt: null
|
|
226
|
+
}, {
|
|
227
|
+
kind: "continue",
|
|
228
|
+
status: "active",
|
|
229
|
+
summary,
|
|
230
|
+
waitingReason: null,
|
|
231
|
+
continuePrompt,
|
|
232
|
+
observedRunningState: runningState,
|
|
233
|
+
autoContinueCount: nextAutoContinueCount,
|
|
234
|
+
createdAt: timestamp
|
|
235
|
+
});
|
|
236
|
+
return this.toTaskView(updated);
|
|
237
|
+
}
|
|
238
|
+
async markTaskWaitingUser(taskId, input, userId) {
|
|
239
|
+
const summary = requireNonEmptyFollowUpText(input.summary, "summary", "等待用户必须提供 summary");
|
|
240
|
+
const waitingReason = requireNonEmptyFollowUpText(input.waitingReason, "waitingReason", "等待用户必须提供 waitingReason");
|
|
241
|
+
const task = this.requireTaskForAssistantUpdate(taskId, userId);
|
|
242
|
+
const inspection = await this.inspectTask(task);
|
|
243
|
+
const timestamp = nowIso();
|
|
244
|
+
const runningState = normalizeRunningState(inspection.runningState);
|
|
245
|
+
const updated = this.persistWithRound({
|
|
246
|
+
...task,
|
|
247
|
+
status: "waiting_user",
|
|
248
|
+
lastCheckedAt: timestamp,
|
|
249
|
+
lastObservedRunningState: runningState,
|
|
250
|
+
lastObservedMessageAt: inspection.messageAt,
|
|
251
|
+
lastObservedMessageCount: inspection.messageCount,
|
|
252
|
+
waitingReason,
|
|
253
|
+
nextCheckAt: null,
|
|
254
|
+
lastAutomationSummary: summary,
|
|
255
|
+
lastAutomationAt: timestamp,
|
|
256
|
+
updatedAt: timestamp,
|
|
257
|
+
completedAt: null
|
|
258
|
+
}, {
|
|
259
|
+
kind: "waiting_user",
|
|
260
|
+
status: "waiting_user",
|
|
261
|
+
summary,
|
|
262
|
+
waitingReason,
|
|
263
|
+
continuePrompt: null,
|
|
264
|
+
observedRunningState: runningState,
|
|
265
|
+
autoContinueCount: task.autoContinueCount,
|
|
266
|
+
createdAt: timestamp
|
|
267
|
+
});
|
|
268
|
+
return this.toTaskView(updated);
|
|
269
|
+
}
|
|
270
|
+
async completeTask(taskId, input, userId) {
|
|
271
|
+
const summary = requireNonEmptyFollowUpText(input.summary, "summary", "完成任务必须提供 summary");
|
|
272
|
+
const task = this.requireTaskForAssistantUpdate(taskId, userId);
|
|
273
|
+
const inspection = await this.inspectTask(task);
|
|
274
|
+
const timestamp = nowIso();
|
|
275
|
+
const runningState = normalizeRunningState(inspection.runningState);
|
|
276
|
+
const updated = this.persistWithRound({
|
|
277
|
+
...task,
|
|
278
|
+
status: "completed",
|
|
279
|
+
lastCheckedAt: timestamp,
|
|
280
|
+
lastObservedRunningState: runningState,
|
|
281
|
+
lastObservedMessageAt: inspection.messageAt,
|
|
282
|
+
lastObservedMessageCount: inspection.messageCount,
|
|
283
|
+
waitingReason: null,
|
|
284
|
+
nextCheckAt: null,
|
|
285
|
+
lastAutomationSummary: summary,
|
|
286
|
+
lastAutomationAt: timestamp,
|
|
287
|
+
updatedAt: timestamp,
|
|
288
|
+
completedAt: timestamp
|
|
289
|
+
}, {
|
|
290
|
+
kind: "completed",
|
|
291
|
+
status: "completed",
|
|
292
|
+
summary,
|
|
293
|
+
waitingReason: null,
|
|
294
|
+
continuePrompt: null,
|
|
295
|
+
observedRunningState: runningState,
|
|
296
|
+
autoContinueCount: task.autoContinueCount,
|
|
297
|
+
createdAt: timestamp
|
|
298
|
+
});
|
|
299
|
+
return this.toTaskView(updated);
|
|
300
|
+
}
|
|
301
|
+
async failTask(taskId, input, userId) {
|
|
302
|
+
const summary = requireNonEmptyFollowUpText(input.summary, "summary", "标记失败必须提供 summary");
|
|
303
|
+
const reason = normalizeNullableText(input.reason) ?? summary;
|
|
304
|
+
const task = this.requireTaskForAssistantUpdate(taskId, userId);
|
|
305
|
+
const inspection = await this.inspectTask(task);
|
|
306
|
+
const timestamp = nowIso();
|
|
307
|
+
const runningState = normalizeRunningState(inspection.runningState);
|
|
308
|
+
const updated = this.persistWithRound({
|
|
309
|
+
...task,
|
|
310
|
+
status: "failed",
|
|
311
|
+
lastCheckedAt: timestamp,
|
|
312
|
+
lastObservedRunningState: runningState,
|
|
313
|
+
lastObservedMessageAt: inspection.messageAt,
|
|
314
|
+
lastObservedMessageCount: inspection.messageCount,
|
|
315
|
+
waitingReason: reason,
|
|
316
|
+
nextCheckAt: null,
|
|
317
|
+
lastAutomationSummary: summary,
|
|
318
|
+
lastAutomationAt: timestamp,
|
|
319
|
+
updatedAt: timestamp,
|
|
320
|
+
completedAt: null
|
|
321
|
+
}, {
|
|
322
|
+
kind: "failed",
|
|
323
|
+
status: "failed",
|
|
324
|
+
summary,
|
|
325
|
+
waitingReason: reason,
|
|
326
|
+
continuePrompt: null,
|
|
327
|
+
observedRunningState: runningState,
|
|
328
|
+
autoContinueCount: task.autoContinueCount,
|
|
329
|
+
createdAt: timestamp
|
|
330
|
+
});
|
|
331
|
+
return this.toTaskView(updated);
|
|
332
|
+
}
|
|
333
|
+
async cancelTask(taskId, userId) {
|
|
133
334
|
this.butlerProfileService.ensureInitialized();
|
|
134
335
|
const task = this.butlerFollowUpTaskRepository.findById(taskId);
|
|
135
336
|
if (!task) {
|
|
@@ -153,6 +354,7 @@ export class ButlerFollowUpService {
|
|
|
153
354
|
detail: "当前跟进任务已经结束,不能再次停止"
|
|
154
355
|
});
|
|
155
356
|
}
|
|
357
|
+
const execution = this.markTaskExecutionCancelled(task.id);
|
|
156
358
|
const timestamp = nowIso();
|
|
157
359
|
const updated = this.persistWithRound({
|
|
158
360
|
...task,
|
|
@@ -173,6 +375,7 @@ export class ButlerFollowUpService {
|
|
|
173
375
|
autoContinueCount: task.autoContinueCount,
|
|
174
376
|
createdAt: timestamp
|
|
175
377
|
});
|
|
378
|
+
await this.stopActiveTaskAutomation(task, execution);
|
|
176
379
|
const project = this.butlerProjectService.getById(updated.projectId);
|
|
177
380
|
const index = this.sessionIndexRepository.findIndexRecordBySessionId(updated.sessionId);
|
|
178
381
|
return mapTaskView(updated, project.workspaceId, project.name, index?.title ?? null);
|
|
@@ -225,209 +428,136 @@ export class ButlerFollowUpService {
|
|
|
225
428
|
if (task.status !== "active") {
|
|
226
429
|
return task;
|
|
227
430
|
}
|
|
228
|
-
const
|
|
229
|
-
const project = this.butlerProjectService.getById(task.projectId);
|
|
230
|
-
const inspection = await this.inspectTask(task);
|
|
231
|
-
const runningState = normalizeRunningState(inspection.runningState);
|
|
232
|
-
const baseUpdate = {
|
|
233
|
-
...task,
|
|
234
|
-
lastCheckedAt: referenceAt,
|
|
235
|
-
lastObservedRunningState: runningState,
|
|
236
|
-
lastObservedMessageAt: inspection.messageAt,
|
|
237
|
-
lastObservedMessageCount: inspection.messageCount,
|
|
238
|
-
updatedAt: referenceAt
|
|
239
|
-
};
|
|
240
|
-
if (runningState === "starting" || runningState === "running") {
|
|
241
|
-
return this.persist({
|
|
242
|
-
...baseUpdate,
|
|
243
|
-
status: "active",
|
|
244
|
-
waitingReason: null,
|
|
245
|
-
nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
|
|
246
|
-
lastAutomationSummary: hasReachedAutoContinueLimit(task)
|
|
247
|
-
? `会话仍在运行,但已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),本轮结束后将停止自动续接。`
|
|
248
|
-
: "会话仍在运行,助手继续观察当前进度。"
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
if (hasReachedAutoContinueLimit(task)) {
|
|
252
|
-
const waitingReason = `已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),如需继续,请手动重新发起跟进。`;
|
|
253
|
-
const summary = `自动跟进已按预设上限停止。结束条件:${task.completionCriteria}`;
|
|
254
|
-
return this.persistWithRound({
|
|
255
|
-
...baseUpdate,
|
|
256
|
-
status: "waiting_user",
|
|
257
|
-
waitingReason,
|
|
258
|
-
nextCheckAt: null,
|
|
259
|
-
completedAt: null,
|
|
260
|
-
lastAutomationAt: referenceAt,
|
|
261
|
-
lastAutomationSummary: summary
|
|
262
|
-
}, {
|
|
263
|
-
kind: "limit_reached",
|
|
264
|
-
status: "waiting_user",
|
|
265
|
-
summary,
|
|
266
|
-
waitingReason,
|
|
267
|
-
continuePrompt: null,
|
|
268
|
-
observedRunningState: runningState,
|
|
269
|
-
autoContinueCount: task.autoContinueCount,
|
|
270
|
-
createdAt: referenceAt
|
|
271
|
-
});
|
|
272
|
-
}
|
|
431
|
+
const execution = this.beginTaskExecution(task.id);
|
|
273
432
|
try {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
433
|
+
const project = this.butlerProjectService.getById(task.projectId);
|
|
434
|
+
const inspection = await this.inspectTask(task);
|
|
435
|
+
this.ensureTaskExecutionActive(task.id, execution);
|
|
436
|
+
const runningState = normalizeRunningState(inspection.runningState);
|
|
437
|
+
const baseUpdate = {
|
|
438
|
+
...task,
|
|
439
|
+
lastCheckedAt: referenceAt,
|
|
440
|
+
lastObservedRunningState: runningState,
|
|
441
|
+
lastObservedMessageAt: inspection.messageAt,
|
|
442
|
+
lastObservedMessageCount: inspection.messageCount,
|
|
443
|
+
updatedAt: referenceAt
|
|
444
|
+
};
|
|
445
|
+
if (runningState === "starting" || runningState === "running") {
|
|
446
|
+
return this.persistIfExecutionActive(task.id, execution, {
|
|
447
|
+
...baseUpdate,
|
|
448
|
+
status: "active",
|
|
449
|
+
waitingReason: null,
|
|
450
|
+
nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
|
|
451
|
+
lastAutomationSummary: hasReachedAutoContinueLimit(task)
|
|
452
|
+
? `会话仍在运行,但已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),本轮结束后将停止自动续接。`
|
|
453
|
+
: "会话仍在运行,助手继续观察当前进度。"
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (hasReachedAutoContinueLimit(task)) {
|
|
457
|
+
const waitingReason = `已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),如需继续,请手动重新发起跟进。`;
|
|
458
|
+
const summary = `自动跟进已按预设上限停止。结束条件:${task.completionCriteria}`;
|
|
459
|
+
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
460
|
+
...baseUpdate,
|
|
461
|
+
status: "waiting_user",
|
|
462
|
+
waitingReason,
|
|
463
|
+
nextCheckAt: null,
|
|
464
|
+
completedAt: null,
|
|
465
|
+
lastAutomationAt: referenceAt,
|
|
466
|
+
lastAutomationSummary: summary
|
|
467
|
+
}, {
|
|
468
|
+
kind: "limit_reached",
|
|
469
|
+
status: "waiting_user",
|
|
470
|
+
summary,
|
|
471
|
+
waitingReason,
|
|
472
|
+
continuePrompt: null,
|
|
473
|
+
observedRunningState: runningState,
|
|
474
|
+
autoContinueCount: task.autoContinueCount,
|
|
475
|
+
createdAt: referenceAt
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
const usageLimitBlock = await this.providerUsageLimitGuardService.resolveBlockingInspection([
|
|
479
|
+
{
|
|
480
|
+
sessionId: task.sessionId,
|
|
481
|
+
userId: task.createdByUserId,
|
|
482
|
+
sourceLabel: "跟进目标会话"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
sessionId: task.assistantSessionId,
|
|
486
|
+
userId: task.createdByUserId,
|
|
487
|
+
sourceLabel: "跟进助手会话"
|
|
488
|
+
}
|
|
489
|
+
], referenceAt);
|
|
490
|
+
if (usageLimitBlock) {
|
|
491
|
+
const nextCheckAt = usageLimitBlock.blockedUntil;
|
|
492
|
+
return this.persistIfExecutionActive(task.id, execution, {
|
|
493
|
+
...baseUpdate,
|
|
494
|
+
status: "active",
|
|
495
|
+
waitingReason: null,
|
|
496
|
+
nextCheckAt,
|
|
497
|
+
completedAt: null,
|
|
498
|
+
lastAutomationAt: referenceAt,
|
|
499
|
+
lastAutomationSummary: buildFollowUpUsageLimitSummary(usageLimitBlock.inspection.providerUsageLimit, `${usageLimitBlock.inspection.sourceLabel ?? "当前会话"}被 provider 套餐限额暂时挡住。`)
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const progressBeforeDispatch = snapshotTaskProgress(task);
|
|
504
|
+
await this.requestAssistantEvaluation(project, task, inspection, runningState, execution);
|
|
505
|
+
this.ensureTaskExecutionActive(task.id, execution);
|
|
506
|
+
return this.requireAssistantDecisionPersisted(task.id, progressBeforeDispatch);
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
if (error instanceof FollowUpTaskCancelledError) {
|
|
510
|
+
return this.butlerFollowUpTaskRepository.findById(task.id) ?? task;
|
|
511
|
+
}
|
|
512
|
+
const providerUsageLimit = resolveProviderUsageLimitFromError(error, task.providerId, referenceAt);
|
|
513
|
+
if (providerUsageLimit) {
|
|
514
|
+
return this.persistIfExecutionActive(task.id, execution, {
|
|
278
515
|
...baseUpdate,
|
|
279
|
-
status: "
|
|
280
|
-
waitingReason: null,
|
|
281
|
-
nextCheckAt: null,
|
|
282
|
-
completedAt: referenceAt,
|
|
283
|
-
lastAutomationAt: referenceAt,
|
|
284
|
-
lastAutomationSummary: evaluation.summary
|
|
285
|
-
}, {
|
|
286
|
-
kind: "completed",
|
|
287
|
-
status: "completed",
|
|
288
|
-
summary: evaluation.summary,
|
|
516
|
+
status: "active",
|
|
289
517
|
waitingReason: null,
|
|
290
|
-
|
|
291
|
-
observedRunningState: runningState,
|
|
292
|
-
autoContinueCount: task.autoContinueCount,
|
|
293
|
-
createdAt: referenceAt
|
|
294
|
-
});
|
|
295
|
-
case "waiting_user":
|
|
296
|
-
return this.persistWithRound({
|
|
297
|
-
...baseUpdate,
|
|
298
|
-
status: "waiting_user",
|
|
299
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
300
|
-
nextCheckAt: null,
|
|
301
|
-
completedAt: null,
|
|
302
|
-
lastAutomationAt: referenceAt,
|
|
303
|
-
lastAutomationSummary: evaluation.summary
|
|
304
|
-
}, {
|
|
305
|
-
kind: "waiting_user",
|
|
306
|
-
status: "waiting_user",
|
|
307
|
-
summary: evaluation.summary,
|
|
308
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
309
|
-
continuePrompt: null,
|
|
310
|
-
observedRunningState: runningState,
|
|
311
|
-
autoContinueCount: task.autoContinueCount,
|
|
312
|
-
createdAt: referenceAt
|
|
313
|
-
});
|
|
314
|
-
case "failed":
|
|
315
|
-
return this.persistWithRound({
|
|
316
|
-
...baseUpdate,
|
|
317
|
-
status: "failed",
|
|
318
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
319
|
-
nextCheckAt: null,
|
|
518
|
+
nextCheckAt: resolveProviderUsageLimitNextCheckAt(providerUsageLimit, referenceAt, task.checkIntervalSeconds),
|
|
320
519
|
completedAt: null,
|
|
321
520
|
lastAutomationAt: referenceAt,
|
|
322
|
-
lastAutomationSummary:
|
|
323
|
-
}, {
|
|
324
|
-
kind: "failed",
|
|
325
|
-
status: "failed",
|
|
326
|
-
summary: evaluation.summary,
|
|
327
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
328
|
-
continuePrompt: null,
|
|
329
|
-
observedRunningState: runningState,
|
|
330
|
-
autoContinueCount: task.autoContinueCount,
|
|
331
|
-
createdAt: referenceAt
|
|
521
|
+
lastAutomationSummary: buildFollowUpUsageLimitSummary(providerUsageLimit, "跟进助手会话当前被 provider 额度限制暂时挡住。")
|
|
332
522
|
});
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
...baseUpdate,
|
|
337
|
-
status: "failed",
|
|
338
|
-
waitingReason: "后台评估助手没有返回可继续推进的指令。",
|
|
339
|
-
nextCheckAt: null,
|
|
340
|
-
completedAt: null,
|
|
341
|
-
lastAutomationAt: referenceAt,
|
|
342
|
-
lastAutomationSummary: evaluation.summary
|
|
343
|
-
}, {
|
|
344
|
-
kind: "failed",
|
|
345
|
-
status: "failed",
|
|
346
|
-
summary: evaluation.summary,
|
|
347
|
-
waitingReason: "后台评估助手没有返回可继续推进的指令。",
|
|
348
|
-
continuePrompt: null,
|
|
349
|
-
observedRunningState: runningState,
|
|
350
|
-
autoContinueCount: task.autoContinueCount,
|
|
351
|
-
createdAt: referenceAt
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
const sendResult = await this.sendContinuePrompt(task, evaluation.continuePrompt, referenceAt);
|
|
355
|
-
this.butlerSessionService.captureSessionSnapshot(task.projectId, task.butlerSessionId, task.createdByUserId, { sourceKind: "manual" });
|
|
356
|
-
const nextAutoContinueCount = task.autoContinueCount + 1;
|
|
357
|
-
const nextSummary = sendResult.delivery === "queued"
|
|
358
|
-
? buildQueuedFollowUpSummary(evaluation.summary, sendResult.queueItem)
|
|
359
|
-
: evaluation.summary;
|
|
360
|
-
return this.persistWithRound({
|
|
523
|
+
}
|
|
524
|
+
if (isDeferredFollowUpSendError(error)) {
|
|
525
|
+
return this.persistIfExecutionActive(task.id, execution, {
|
|
361
526
|
...baseUpdate,
|
|
362
527
|
status: "active",
|
|
363
528
|
waitingReason: null,
|
|
364
529
|
nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
|
|
365
|
-
lastAutomationAt: referenceAt,
|
|
366
|
-
autoContinueCount: nextAutoContinueCount,
|
|
367
|
-
lastAutomationSummary: nextSummary
|
|
368
|
-
}, {
|
|
369
|
-
kind: sendResult.delivery === "queued" ? "queued" : "continue",
|
|
370
|
-
status: "active",
|
|
371
|
-
summary: nextSummary,
|
|
372
|
-
waitingReason: null,
|
|
373
|
-
continuePrompt: evaluation.continuePrompt,
|
|
374
|
-
observedRunningState: runningState,
|
|
375
|
-
autoContinueCount: nextAutoContinueCount,
|
|
376
|
-
createdAt: referenceAt
|
|
377
|
-
});
|
|
378
|
-
default:
|
|
379
|
-
return this.persistWithRound({
|
|
380
|
-
...baseUpdate,
|
|
381
|
-
status: "failed",
|
|
382
|
-
waitingReason: "后台评估助手返回了不支持的决策。",
|
|
383
|
-
nextCheckAt: null,
|
|
384
530
|
completedAt: null,
|
|
385
531
|
lastAutomationAt: referenceAt,
|
|
386
|
-
lastAutomationSummary: "
|
|
387
|
-
}, {
|
|
388
|
-
kind: "failed",
|
|
389
|
-
status: "failed",
|
|
390
|
-
summary: "后台评估助手返回了不支持的决策。",
|
|
391
|
-
waitingReason: "后台评估助手返回了不支持的决策。",
|
|
392
|
-
continuePrompt: null,
|
|
393
|
-
observedRunningState: runningState,
|
|
394
|
-
autoContinueCount: task.autoContinueCount,
|
|
395
|
-
createdAt: referenceAt
|
|
532
|
+
lastAutomationSummary: "跟进助手会话当前仍在运行,本轮继续等待下一次检查。"
|
|
396
533
|
});
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return this.persist({
|
|
534
|
+
}
|
|
535
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
536
|
+
const summary = `跟进助手执行失败:${detail}`;
|
|
537
|
+
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
402
538
|
...baseUpdate,
|
|
403
|
-
status: "
|
|
404
|
-
waitingReason:
|
|
405
|
-
nextCheckAt:
|
|
539
|
+
status: "failed",
|
|
540
|
+
waitingReason: detail,
|
|
541
|
+
nextCheckAt: null,
|
|
406
542
|
completedAt: null,
|
|
407
543
|
lastAutomationAt: referenceAt,
|
|
408
|
-
lastAutomationSummary:
|
|
544
|
+
lastAutomationSummary: summary
|
|
545
|
+
}, {
|
|
546
|
+
kind: "failed",
|
|
547
|
+
status: "failed",
|
|
548
|
+
summary,
|
|
549
|
+
waitingReason: detail,
|
|
550
|
+
continuePrompt: null,
|
|
551
|
+
observedRunningState: runningState,
|
|
552
|
+
autoContinueCount: task.autoContinueCount,
|
|
553
|
+
createdAt: referenceAt
|
|
409
554
|
});
|
|
410
555
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
waitingReason: detail,
|
|
417
|
-
nextCheckAt: null,
|
|
418
|
-
completedAt: null,
|
|
419
|
-
lastAutomationAt: referenceAt,
|
|
420
|
-
lastAutomationSummary: summary
|
|
421
|
-
}, {
|
|
422
|
-
kind: "failed",
|
|
423
|
-
status: "failed",
|
|
424
|
-
summary,
|
|
425
|
-
waitingReason: detail,
|
|
426
|
-
continuePrompt: null,
|
|
427
|
-
observedRunningState: runningState,
|
|
428
|
-
autoContinueCount: task.autoContinueCount,
|
|
429
|
-
createdAt: referenceAt
|
|
430
|
-
});
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
if (this.activeExecutionStateByTaskId.get(task.id) === execution) {
|
|
559
|
+
this.activeExecutionStateByTaskId.delete(task.id);
|
|
560
|
+
}
|
|
431
561
|
}
|
|
432
562
|
}
|
|
433
563
|
persist(task) {
|
|
@@ -440,6 +570,16 @@ export class ButlerFollowUpService {
|
|
|
440
570
|
}
|
|
441
571
|
return this.butlerFollowUpTaskRepository.update(normalizedTask) ?? normalizedTask;
|
|
442
572
|
}
|
|
573
|
+
requireAssistantDecisionPersisted(taskId, before) {
|
|
574
|
+
const updated = this.butlerFollowUpTaskRepository.findById(taskId);
|
|
575
|
+
if (!updated) {
|
|
576
|
+
throw new Error("跟进任务在回写结果前已丢失");
|
|
577
|
+
}
|
|
578
|
+
if (!hasTaskProgressAdvanced(updated, before)) {
|
|
579
|
+
throw new Error("跟进助手没有通过 follow-ups 命令回写本轮结果");
|
|
580
|
+
}
|
|
581
|
+
return updated;
|
|
582
|
+
}
|
|
443
583
|
persistWithRound(task, round) {
|
|
444
584
|
const normalizedRounds = normalizeFollowUpRounds(task.rounds);
|
|
445
585
|
return this.persist({
|
|
@@ -447,6 +587,18 @@ export class ButlerFollowUpService {
|
|
|
447
587
|
rounds: [...normalizedRounds, createFollowUpRound(normalizedRounds, round)]
|
|
448
588
|
});
|
|
449
589
|
}
|
|
590
|
+
persistIfExecutionActive(taskId, execution, task) {
|
|
591
|
+
if (!this.isTaskExecutionActive(taskId, execution)) {
|
|
592
|
+
return this.butlerFollowUpTaskRepository.findById(taskId) ?? task;
|
|
593
|
+
}
|
|
594
|
+
return this.persist(task);
|
|
595
|
+
}
|
|
596
|
+
persistWithRoundIfExecutionActive(taskId, execution, task, round) {
|
|
597
|
+
if (!this.isTaskExecutionActive(taskId, execution)) {
|
|
598
|
+
return this.butlerFollowUpTaskRepository.findById(taskId) ?? task;
|
|
599
|
+
}
|
|
600
|
+
return this.persistWithRound(task, round);
|
|
601
|
+
}
|
|
450
602
|
async autoApprovePendingPermissionRequestsIfDue(task, referenceAt) {
|
|
451
603
|
const parsedReferenceAt = Date.parse(referenceAt);
|
|
452
604
|
const referenceAtMs = Number.isFinite(parsedReferenceAt) ? parsedReferenceAt : Date.now();
|
|
@@ -497,7 +649,7 @@ export class ButlerFollowUpService {
|
|
|
497
649
|
&& normalizeNullableIso(session.lastMessageAt) === normalizeNullableIso(task.lastObservedMessageAt)
|
|
498
650
|
&& session.messageCount === task.lastObservedMessageCount);
|
|
499
651
|
}
|
|
500
|
-
async sendContinuePrompt(task, continuePrompt, referenceAt) {
|
|
652
|
+
async sendContinuePrompt(task, providerId, continuePrompt, referenceAt) {
|
|
501
653
|
const clientRequestId = buildFollowUpClientRequestId(task.id, referenceAt);
|
|
502
654
|
try {
|
|
503
655
|
const result = await this.sessionLiveRuntimeService.sendLiveMessage({
|
|
@@ -518,6 +670,13 @@ export class ButlerFollowUpService {
|
|
|
518
670
|
};
|
|
519
671
|
}
|
|
520
672
|
catch (error) {
|
|
673
|
+
const providerUsageLimit = resolveProviderUsageLimitFromError(error, providerId, referenceAt);
|
|
674
|
+
if (providerUsageLimit) {
|
|
675
|
+
return {
|
|
676
|
+
delivery: "cooldown",
|
|
677
|
+
providerUsageLimit
|
|
678
|
+
};
|
|
679
|
+
}
|
|
521
680
|
if (!isDeferredFollowUpSendError(error)) {
|
|
522
681
|
throw error;
|
|
523
682
|
}
|
|
@@ -553,32 +712,36 @@ export class ButlerFollowUpService {
|
|
|
553
712
|
});
|
|
554
713
|
}
|
|
555
714
|
async inspectTask(task) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
715
|
+
return this.inspectSession(task.sessionId, task.createdByUserId);
|
|
716
|
+
}
|
|
717
|
+
async inspectSession(sessionId, userId) {
|
|
718
|
+
const session = this.sessionHistoryService.getSession(sessionId, userId);
|
|
719
|
+
const runtime = await this.sessionLiveRuntimeService.getSessionRuntime(sessionId, userId);
|
|
720
|
+
const envelope = await this.sessionHistoryService.readRecentHistoryEnvelope(sessionId, RECENT_HISTORY_LIMIT);
|
|
721
|
+
const latestAssistantText = resolveLatestAssistantText(envelope);
|
|
559
722
|
const sortedMessages = (envelope?.messages ?? [])
|
|
560
723
|
.slice()
|
|
561
724
|
.sort((left, right) => left.sequence - right.sequence);
|
|
725
|
+
const providerUsageLimit = resolveInspectionProviderUsageLimit(session.provider, session.lastErrorDetail, latestAssistantText, session.lastMessageAt);
|
|
562
726
|
return {
|
|
727
|
+
providerId: session.provider,
|
|
563
728
|
runningState: normalizeRunningState(runtime.runningState),
|
|
564
729
|
messageAt: session.lastMessageAt,
|
|
565
730
|
messageCount: session.messageCount,
|
|
566
731
|
sessionTitle: session.title ?? null,
|
|
567
|
-
|
|
732
|
+
providerUsageLimit,
|
|
733
|
+
latestAssistantText,
|
|
568
734
|
transcriptLines: sortedMessages.map((message) => renderHistoryLine(message.sequence, message.role, message.kind ?? "text", message.timestamp, message.content))
|
|
569
735
|
};
|
|
570
736
|
}
|
|
571
|
-
async
|
|
572
|
-
const evaluatorWorkspacePath = path.join(profile.workspacePath, FOLLOW_UP_EVALUATOR_DIRNAME);
|
|
573
|
-
ensureButlerWorkspaceIsolation(evaluatorWorkspacePath);
|
|
574
|
-
this.writeEvaluationInstructionFiles(evaluatorWorkspacePath, profile.providerId);
|
|
575
|
-
this.syncCodexInstructionConfig(profile.providerId, evaluatorWorkspacePath);
|
|
576
|
-
const workspace = this.workspaceService.importWorkspace(evaluatorWorkspacePath, "代码助手");
|
|
737
|
+
async requestAssistantEvaluation(project, task, inspection, runningState, execution) {
|
|
577
738
|
const instruction = this.instructionAdapter.buildInstruction({
|
|
578
|
-
|
|
739
|
+
taskId: task.id,
|
|
740
|
+
providerId: task.providerId,
|
|
579
741
|
project,
|
|
580
742
|
sessionId: task.sessionId,
|
|
581
743
|
butlerSessionId: task.butlerSessionId,
|
|
744
|
+
assistantSessionId: task.assistantSessionId,
|
|
582
745
|
sessionTitle: inspection.sessionTitle,
|
|
583
746
|
objective: task.objective,
|
|
584
747
|
completionCriteria: task.completionCriteria,
|
|
@@ -591,56 +754,82 @@ export class ButlerFollowUpService {
|
|
|
591
754
|
latestAssistantText: inspection.latestAssistantText,
|
|
592
755
|
transcriptLines: inspection.transcriptLines
|
|
593
756
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (
|
|
757
|
+
execution.assistantSessionId = task.assistantSessionId;
|
|
758
|
+
try {
|
|
759
|
+
await this.waitForAssistantSessionTerminal(task.assistantSessionId, task.createdByUserId);
|
|
760
|
+
this.ensureTaskExecutionActive(task.id, execution);
|
|
761
|
+
await this.sessionLiveRuntimeService.sendLiveMessage({
|
|
762
|
+
sessionId: task.assistantSessionId,
|
|
763
|
+
userId: task.createdByUserId,
|
|
764
|
+
content: instruction.prompt,
|
|
765
|
+
clientRequestId: null,
|
|
766
|
+
runtimeOptions: {
|
|
767
|
+
model: resolveFollowUpModel(task.providerId, this.sourceCodexHomeDir),
|
|
768
|
+
reasoningLevel: "low",
|
|
769
|
+
permissionMode: "default",
|
|
770
|
+
attachments: []
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
await this.waitForAssistantSessionTerminal(task.assistantSessionId, task.createdByUserId);
|
|
774
|
+
this.ensureTaskExecutionActive(task.id, execution);
|
|
775
|
+
}
|
|
776
|
+
finally {
|
|
777
|
+
if (execution.assistantSessionId === task.assistantSessionId) {
|
|
778
|
+
execution.assistantSessionId = null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
beginTaskExecution(taskId) {
|
|
783
|
+
const execution = {
|
|
784
|
+
cancelled: false,
|
|
785
|
+
assistantSessionId: null
|
|
786
|
+
};
|
|
787
|
+
this.activeExecutionStateByTaskId.set(taskId, execution);
|
|
788
|
+
return execution;
|
|
789
|
+
}
|
|
790
|
+
markTaskExecutionCancelled(taskId) {
|
|
791
|
+
const execution = this.activeExecutionStateByTaskId.get(taskId) ?? null;
|
|
792
|
+
if (execution) {
|
|
793
|
+
execution.cancelled = true;
|
|
794
|
+
}
|
|
795
|
+
return execution;
|
|
796
|
+
}
|
|
797
|
+
ensureTaskExecutionActive(taskId, execution) {
|
|
798
|
+
if (!this.isTaskExecutionActive(taskId, execution)) {
|
|
799
|
+
throw new FollowUpTaskCancelledError();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
isTaskExecutionActive(taskId, execution) {
|
|
803
|
+
const current = this.activeExecutionStateByTaskId.get(taskId);
|
|
804
|
+
return Boolean(current && current === execution && !execution.cancelled);
|
|
805
|
+
}
|
|
806
|
+
async stopActiveTaskAutomation(task, execution) {
|
|
807
|
+
if (!execution?.assistantSessionId) {
|
|
630
808
|
return;
|
|
631
809
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
810
|
+
try {
|
|
811
|
+
await this.sessionLiveRuntimeService.interruptSession(execution.assistantSessionId, task.createdByUserId);
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
console.warn("[butler-follow-up] interrupt assistant follow-up session failed", {
|
|
815
|
+
sessionId: execution.assistantSessionId,
|
|
816
|
+
error: error instanceof Error ? error.message : String(error)
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
finally {
|
|
820
|
+
execution.assistantSessionId = null;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async waitForAssistantSessionTerminal(sessionId, userId) {
|
|
824
|
+
const startedAt = Date.now();
|
|
825
|
+
while (Date.now() - startedAt < FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT_MS) {
|
|
826
|
+
const runtime = await this.sessionLiveRuntimeService.getSessionRuntime(sessionId, userId);
|
|
827
|
+
if (isAssistantTerminalRuntimeState(runtime.runningState)) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
await delay(FOLLOW_UP_ASSISTANT_WAIT_POLL_INTERVAL_MS);
|
|
831
|
+
}
|
|
832
|
+
throw new Error(`BUTLER_FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT:${sessionId}`);
|
|
644
833
|
}
|
|
645
834
|
}
|
|
646
835
|
function mapTaskView(task, workspaceId, projectName, sessionTitle) {
|
|
@@ -652,6 +841,9 @@ function mapTaskView(task, workspaceId, projectName, sessionTitle) {
|
|
|
652
841
|
workspaceId,
|
|
653
842
|
butlerSessionId: task.butlerSessionId,
|
|
654
843
|
sessionId: task.sessionId,
|
|
844
|
+
providerId: task.providerId,
|
|
845
|
+
assistantButlerSessionId: task.assistantButlerSessionId,
|
|
846
|
+
assistantSessionId: task.assistantSessionId,
|
|
655
847
|
sessionTitle,
|
|
656
848
|
objective: task.objective,
|
|
657
849
|
completionCriteria: task.completionCriteria,
|
|
@@ -729,6 +921,24 @@ function normalizeObjective(value) {
|
|
|
729
921
|
}
|
|
730
922
|
return normalized;
|
|
731
923
|
}
|
|
924
|
+
function normalizeFollowUpProviderId(value) {
|
|
925
|
+
switch (value) {
|
|
926
|
+
case undefined:
|
|
927
|
+
case null:
|
|
928
|
+
case "":
|
|
929
|
+
return "codex";
|
|
930
|
+
case "codex":
|
|
931
|
+
case "claude-code":
|
|
932
|
+
return value;
|
|
933
|
+
default:
|
|
934
|
+
throw new AppError({
|
|
935
|
+
statusCode: 400,
|
|
936
|
+
errorCode: "INVALID_INPUT",
|
|
937
|
+
detail: "会话跟进只允许选择 Codex 或 Claude Code",
|
|
938
|
+
field: "providerId"
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
732
942
|
function normalizeCompletionCriteria(value, objective) {
|
|
733
943
|
const normalized = value?.trim();
|
|
734
944
|
return normalized && normalized.length > 0
|
|
@@ -754,6 +964,16 @@ function buildFollowUpClientRequestId(taskId, referenceAt) {
|
|
|
754
964
|
function buildQueuedFollowUpSummary(summary, queueItem) {
|
|
755
965
|
return `${summary} 已转入消息队列,等待当前会话空闲后自动补发(队列项 ${queueItem.orderIndex})。`;
|
|
756
966
|
}
|
|
967
|
+
function buildFollowUpUsageLimitSummary(providerUsageLimit, prefix) {
|
|
968
|
+
return `${prefix} ${providerUsageLimit.summary}`;
|
|
969
|
+
}
|
|
970
|
+
function resolveProviderUsageLimitNextCheckAt(providerUsageLimit, referenceAt, fallbackSeconds) {
|
|
971
|
+
const blockedUntil = resolveProviderUsageLimitBlockedUntil(providerUsageLimit, referenceAt);
|
|
972
|
+
if (blockedUntil && Date.parse(blockedUntil) > Date.parse(referenceAt)) {
|
|
973
|
+
return blockedUntil;
|
|
974
|
+
}
|
|
975
|
+
return shiftSeconds(referenceAt, fallbackSeconds);
|
|
976
|
+
}
|
|
757
977
|
function isDeferredFollowUpSendError(error) {
|
|
758
978
|
if (error instanceof AppError) {
|
|
759
979
|
return (error.errorCode === "ACTIVE_RUN_EXISTS"
|
|
@@ -775,6 +995,14 @@ function isDeferredFollowUpSendError(error) {
|
|
|
775
995
|
|| error.message === "SERVER_TIMEOUT"
|
|
776
996
|
|| error.message.includes("当前会话正在运行"));
|
|
777
997
|
}
|
|
998
|
+
function isAssistantTerminalRuntimeState(state) {
|
|
999
|
+
return state === "idle" || state === "completed" || state === "failed" || state === "interrupted";
|
|
1000
|
+
}
|
|
1001
|
+
function delay(timeoutMs) {
|
|
1002
|
+
return new Promise((resolve) => {
|
|
1003
|
+
setTimeout(resolve, timeoutMs);
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
778
1006
|
function isSyntheticMessageId(messageId) {
|
|
779
1007
|
return typeof messageId === "string" && messageId.startsWith("synthetic-");
|
|
780
1008
|
}
|
|
@@ -802,6 +1030,24 @@ function normalizeNullableIso(value) {
|
|
|
802
1030
|
const normalized = value?.trim();
|
|
803
1031
|
return normalized && normalized.length > 0 ? normalized : null;
|
|
804
1032
|
}
|
|
1033
|
+
function resolveInspectionProviderUsageLimit(providerId, lastErrorDetail, latestAssistantText, referenceAt) {
|
|
1034
|
+
const normalizedReferenceAt = normalizeNullableIso(referenceAt) ?? undefined;
|
|
1035
|
+
const fromErrorDetail = normalizeProviderUsageLimit({
|
|
1036
|
+
providerId,
|
|
1037
|
+
text: lastErrorDetail,
|
|
1038
|
+
referenceAt: normalizedReferenceAt,
|
|
1039
|
+
source: "error_detail"
|
|
1040
|
+
});
|
|
1041
|
+
if (fromErrorDetail) {
|
|
1042
|
+
return fromErrorDetail;
|
|
1043
|
+
}
|
|
1044
|
+
return normalizeProviderUsageLimit({
|
|
1045
|
+
providerId,
|
|
1046
|
+
text: latestAssistantText,
|
|
1047
|
+
referenceAt: normalizedReferenceAt,
|
|
1048
|
+
source: "message"
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
805
1051
|
function resolveLatestAssistantText(envelope) {
|
|
806
1052
|
if (!envelope || envelope.messages.length === 0) {
|
|
807
1053
|
return null;
|
|
@@ -823,140 +1069,91 @@ function truncateText(value, maxLength) {
|
|
|
823
1069
|
}
|
|
824
1070
|
return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
825
1071
|
}
|
|
1072
|
+
function buildFollowUpBootstrapPrompt(input) {
|
|
1073
|
+
const transcript = input.transcriptLines.length > 0
|
|
1074
|
+
? input.transcriptLines.slice(-12).join("\n")
|
|
1075
|
+
: "- 暂时没有可用消息,请先按会话现状建立上下文。";
|
|
1076
|
+
return [
|
|
1077
|
+
"你现在是这条开发会话的专用跟进助手,会长期复用当前助手会话推进,不再切回隐藏评估器。",
|
|
1078
|
+
"你的职责只有三件事:",
|
|
1079
|
+
"1. 用 codingns assistant CLI 复核目标项目和目标会话的最新状态。",
|
|
1080
|
+
"2. 判断当前是否真的还需要继续跟进,还是应该等待用户决定,或者已经可以结束。",
|
|
1081
|
+
"3. 用 codingns assistant sessions send 和 codingns assistant follow-ups.* 自己完成发消息与任务回写,不要等后台代发或猜结果。",
|
|
1082
|
+
"",
|
|
1083
|
+
"硬约束:",
|
|
1084
|
+
"- 不要直接改当前仓库代码,这条会话只负责跟进判断和向目标开发会话发消息。",
|
|
1085
|
+
"- 如果决定继续,必须显式使用 `codingns assistant sessions send` 把中文跟进消息发到目标开发会话。",
|
|
1086
|
+
"- 每一轮正式结论都必须用 `codingns assistant follow-ups continue|waiting-user|complete|fail` 之一回写到跟进任务。",
|
|
1087
|
+
"- 如果信息不足或需要用户决策,要明确说明缺口,不要假装已经发消息。",
|
|
1088
|
+
"- 跟进边界只围绕当前目标和结束条件,不准顺手扩范围。",
|
|
1089
|
+
"",
|
|
1090
|
+
`项目名称:${input.project.name}`,
|
|
1091
|
+
`项目路径:${input.project.repoRoot}`,
|
|
1092
|
+
`目标 Butler 会话 ID:${input.sourceButlerSessionId}`,
|
|
1093
|
+
`目标真实会话 ID:${input.sourceSessionId}`,
|
|
1094
|
+
`目标会话标题:${input.sourceSessionTitle ?? "未命名会话"}`,
|
|
1095
|
+
`跟进目标:${input.objective}`,
|
|
1096
|
+
`结束条件:${input.completionCriteria}`,
|
|
1097
|
+
`最多自动推进轮数:${input.maxAutoContinueCount}`,
|
|
1098
|
+
`最近一条助手消息:${input.latestAssistantText?.trim() || "无"}`,
|
|
1099
|
+
"",
|
|
1100
|
+
"最近消息摘录:",
|
|
1101
|
+
transcript,
|
|
1102
|
+
"",
|
|
1103
|
+
"这条消息只用来建立上下文。请先整理当前理解,后续我会继续给你发送正式的跟进检查请求。"
|
|
1104
|
+
].join("\n");
|
|
1105
|
+
}
|
|
826
1106
|
function resolveFollowUpModel(providerId, sourceCodexHomeDir) {
|
|
827
1107
|
if (providerId !== "codex") {
|
|
828
1108
|
return "haiku";
|
|
829
1109
|
}
|
|
830
1110
|
return resolveButlerCodexBackgroundModel("gpt-5.1-codex-mini", sourceCodexHomeDir);
|
|
831
1111
|
}
|
|
832
|
-
function
|
|
833
|
-
const rawJson = result.structured.rawJson ?? extractJsonFromText(result.latestAssistantMessage);
|
|
834
|
-
if (!rawJson) {
|
|
835
|
-
throw new Error("后台评估助手没有返回结构化 JSON");
|
|
836
|
-
}
|
|
837
|
-
let parsed;
|
|
838
|
-
try {
|
|
839
|
-
parsed = JSON.parse(rawJson);
|
|
840
|
-
}
|
|
841
|
-
catch (error) {
|
|
842
|
-
throw new Error(`后台评估助手返回的 JSON 无法解析:${error instanceof Error ? error.message : String(error)}`);
|
|
843
|
-
}
|
|
844
|
-
const decision = normalizeDecision(parsed.decision);
|
|
845
|
-
if (!decision) {
|
|
846
|
-
throw new Error("后台评估助手返回的 decision 不合法");
|
|
847
|
-
}
|
|
848
|
-
const summary = normalizeNonEmptyString(parsed.summary) ?? result.structured.summary ?? "后台评估助手未提供摘要";
|
|
849
|
-
const waitingReason = normalizeNullableString(parsed.waitingReason);
|
|
850
|
-
const continuePrompt = normalizeNullableString(parsed.continuePrompt);
|
|
851
|
-
const riskLevel = normalizeRiskLevel(parsed.riskLevel);
|
|
1112
|
+
function snapshotTaskProgress(task) {
|
|
852
1113
|
return {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
riskLevel
|
|
1114
|
+
roundCount: normalizeFollowUpRounds(task.rounds).length,
|
|
1115
|
+
updatedAt: task.updatedAt,
|
|
1116
|
+
lastAutomationAt: task.lastAutomationAt,
|
|
1117
|
+
autoContinueCount: task.autoContinueCount
|
|
858
1118
|
};
|
|
859
1119
|
}
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
return value;
|
|
867
|
-
default:
|
|
868
|
-
return null;
|
|
869
|
-
}
|
|
1120
|
+
function hasTaskProgressAdvanced(task, before) {
|
|
1121
|
+
const roundCount = normalizeFollowUpRounds(task.rounds).length;
|
|
1122
|
+
return (roundCount > before.roundCount
|
|
1123
|
+
|| task.updatedAt !== before.updatedAt
|
|
1124
|
+
|| task.lastAutomationAt !== before.lastAutomationAt
|
|
1125
|
+
|| task.autoContinueCount !== before.autoContinueCount);
|
|
870
1126
|
}
|
|
871
|
-
function
|
|
872
|
-
switch (value) {
|
|
873
|
-
case "low":
|
|
874
|
-
case "medium":
|
|
875
|
-
case "high":
|
|
876
|
-
return value;
|
|
877
|
-
default:
|
|
878
|
-
return null;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
function normalizeNonEmptyString(value) {
|
|
1127
|
+
function requireNonEmptyFollowUpText(value, field, detail) {
|
|
882
1128
|
if (typeof value !== "string") {
|
|
883
|
-
|
|
1129
|
+
throw new AppError({
|
|
1130
|
+
statusCode: 400,
|
|
1131
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
|
|
1132
|
+
detail
|
|
1133
|
+
});
|
|
884
1134
|
}
|
|
885
1135
|
const normalized = value.trim();
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
return normalizeNonEmptyString(value);
|
|
893
|
-
}
|
|
894
|
-
function extractJsonFromText(value) {
|
|
895
|
-
if (!value) {
|
|
896
|
-
return null;
|
|
897
|
-
}
|
|
898
|
-
const matched = value.match(/```json\s*([\s\S]*?)```/i);
|
|
899
|
-
const raw = matched?.[1]?.trim();
|
|
900
|
-
return raw || null;
|
|
901
|
-
}
|
|
902
|
-
function resolveSourceCodexHomeDir(sourceCodexHomeDir, targetHomeDir) {
|
|
903
|
-
const configuredSource = sourceCodexHomeDir?.trim();
|
|
904
|
-
if (configuredSource) {
|
|
905
|
-
const resolvedConfiguredSource = path.resolve(configuredSource);
|
|
906
|
-
if (resolvedConfiguredSource !== targetHomeDir) {
|
|
907
|
-
return resolvedConfiguredSource;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
const fallbackHomeDir = path.resolve(path.join(os.homedir(), ".codex"));
|
|
911
|
-
if (fallbackHomeDir !== targetHomeDir) {
|
|
912
|
-
return fallbackHomeDir;
|
|
913
|
-
}
|
|
914
|
-
return targetHomeDir;
|
|
915
|
-
}
|
|
916
|
-
function composeCodexConfigContent(sourceConfigContent, instructionFilePath) {
|
|
917
|
-
const normalizedSource = sourceConfigContent
|
|
918
|
-
.split(/\r?\n/)
|
|
919
|
-
.filter((line) => {
|
|
920
|
-
const trimmed = line.trim();
|
|
921
|
-
return trimmed.length > 0 && !trimmed.startsWith("model_instructions_file");
|
|
922
|
-
})
|
|
923
|
-
.join("\n")
|
|
924
|
-
.trim();
|
|
925
|
-
return [
|
|
926
|
-
"# 代码助手跟进评估专用 Codex 配置(系统自动生成)",
|
|
927
|
-
normalizedSource,
|
|
928
|
-
`model_instructions_file = ${toTomlString(path.resolve(instructionFilePath))}`
|
|
929
|
-
]
|
|
930
|
-
.filter((part) => part.trim().length > 0)
|
|
931
|
-
.join("\n\n");
|
|
932
|
-
}
|
|
933
|
-
function toTomlString(value) {
|
|
934
|
-
return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
935
|
-
}
|
|
936
|
-
function writeFileIfChanged(filePath, content) {
|
|
937
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
938
|
-
if (fs.existsSync(filePath) && fs.readFileSync(filePath, "utf8") === content) {
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
fs.writeFileSync(filePath, content, "utf8");
|
|
942
|
-
}
|
|
943
|
-
function removeFileIfExists(filePath) {
|
|
944
|
-
if (!fs.existsSync(filePath)) {
|
|
945
|
-
return;
|
|
1136
|
+
if (!normalized) {
|
|
1137
|
+
throw new AppError({
|
|
1138
|
+
statusCode: 400,
|
|
1139
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
|
|
1140
|
+
detail
|
|
1141
|
+
});
|
|
946
1142
|
}
|
|
947
|
-
if (
|
|
948
|
-
|
|
1143
|
+
if (normalized.length > 4000) {
|
|
1144
|
+
throw new AppError({
|
|
1145
|
+
statusCode: 400,
|
|
1146
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
|
|
1147
|
+
detail: `${field} 长度不能超过 4000 个字符`
|
|
1148
|
+
});
|
|
949
1149
|
}
|
|
1150
|
+
return normalized;
|
|
950
1151
|
}
|
|
951
|
-
function
|
|
952
|
-
if (
|
|
953
|
-
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
957
|
-
if (fs.existsSync(targetPath) && fs.readFileSync(targetPath).equals(fs.readFileSync(sourcePath))) {
|
|
958
|
-
return;
|
|
1152
|
+
function normalizeNullableText(value) {
|
|
1153
|
+
if (typeof value !== "string") {
|
|
1154
|
+
return null;
|
|
959
1155
|
}
|
|
960
|
-
|
|
1156
|
+
const normalized = value.trim();
|
|
1157
|
+
return normalized || null;
|
|
961
1158
|
}
|
|
962
1159
|
//# sourceMappingURL=butler-follow-up-service.js.map
|