@jingyi0605/codingns 0.4.0 → 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/bin/codingns.mjs +425 -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-6jHZV9Mh.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 +62 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +58 -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 +66 -3
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +173 -1
- 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 +2 -0
- package/dist/server/modules/butler/assistant-automation-service.js +46 -0
- package/dist/server/modules/butler/assistant-automation-service.js.map +1 -1
- 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 +16 -2
- package/dist/server/modules/butler/assistant-sandbox-service.js +137 -4
- package/dist/server/modules/butler/assistant-sandbox-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 +4 -1
- package/dist/server/modules/butler/butler-control-session-service.js +20 -1
- package/dist/server/modules/butler/butler-control-session-service.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 +32 -4
- package/dist/server/modules/butler/butler-follow-up-service.js +436 -331
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +1 -1
- 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-session-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-session-service.js +15 -1
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-workspace-context.d.ts +1 -1
- package/dist/server/modules/butler/butler-workspace-context.js +54 -28
- package/dist/server/modules/butler/butler-workspace-context.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/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/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 +17 -0
- package/dist/server/modules/sessions/session-history-service.js +150 -1
- 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.js +34 -18
- 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-permission-request-service.d.ts +1 -0
- package/dist/server/modules/sessions/session-permission-request-service.js +200 -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/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-types.d.ts +3 -0
- package/dist/server/modules/tasks/task-types.js +3 -0
- package/dist/server/modules/tasks/task-types.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/routes/assistant.js +30 -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/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 +10 -0
- package/dist/server/server/create-server.js +82 -12
- 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-task-repository.d.ts +2 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js +8 -2
- package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -1
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +1 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +27 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -1
- 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-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/sqlite/client.js +297 -2
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +122 -4
- package/dist/server/types/domain.d.ts +82 -1
- package/dist/server/ws/workbench-ws-hub.js +99 -75
- 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 +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +11 -1
- 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 +11 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +132 -21
- 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 +2 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +53 -1
- 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 +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +10 -1
- 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 +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +30 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.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 +145 -58
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/services.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/services.js +7 -0
- package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +0 -33
- package/dist/public/assets/index-CSVhg7I8.js +0 -123
- package/dist/public/assets/index-Ce1VX19m.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",
|
|
@@ -43,9 +43,10 @@ export class ButlerFollowUpService {
|
|
|
43
43
|
followUpCodexHomeDir;
|
|
44
44
|
sourceCodexHomeDir;
|
|
45
45
|
sessionMessageOriginRepository;
|
|
46
|
+
providerUsageLimitGuardService;
|
|
46
47
|
permissionRequestSweepAtByTaskId = new Map();
|
|
47
48
|
activeExecutionStateByTaskId = new Map();
|
|
48
|
-
constructor(butlerProfileService, butlerProjectService, butlerSessionService, butlerFollowUpTaskRepository, sessionHistoryService, sessionIndexRepository, sessionLiveRuntimeService, workspaceService, providerAdapterRegistry, instructionAdapter, followUpCodexHomeDir = null, sourceCodexHomeDir = null, sessionMessageOriginRepository = null) {
|
|
49
|
+
constructor(butlerProfileService, butlerProjectService, butlerSessionService, butlerFollowUpTaskRepository, sessionHistoryService, sessionIndexRepository, sessionLiveRuntimeService, workspaceService, providerAdapterRegistry, instructionAdapter, followUpCodexHomeDir = null, sourceCodexHomeDir = null, sessionMessageOriginRepository = null, providerUsageLimitGuardService = new SessionProviderUsageLimitGuardService(sessionHistoryService)) {
|
|
49
50
|
this.butlerProfileService = butlerProfileService;
|
|
50
51
|
this.butlerProjectService = butlerProjectService;
|
|
51
52
|
this.butlerSessionService = butlerSessionService;
|
|
@@ -59,6 +60,7 @@ export class ButlerFollowUpService {
|
|
|
59
60
|
this.followUpCodexHomeDir = followUpCodexHomeDir;
|
|
60
61
|
this.sourceCodexHomeDir = sourceCodexHomeDir;
|
|
61
62
|
this.sessionMessageOriginRepository = sessionMessageOriginRepository;
|
|
63
|
+
this.providerUsageLimitGuardService = providerUsageLimitGuardService;
|
|
62
64
|
}
|
|
63
65
|
listTasks(filters = {}) {
|
|
64
66
|
this.butlerProfileService.ensureInitialized();
|
|
@@ -85,9 +87,41 @@ export class ButlerFollowUpService {
|
|
|
85
87
|
const index = this.sessionIndexRepository.findIndexRecordBySessionId(task.sessionId);
|
|
86
88
|
return mapTaskView(task, project.workspaceId, project.name, index?.title ?? null);
|
|
87
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
|
+
}
|
|
88
121
|
async createTask(input, userId) {
|
|
89
122
|
this.butlerProfileService.ensureInitialized();
|
|
90
123
|
const project = this.butlerProjectService.getById(input.projectId);
|
|
124
|
+
const providerId = normalizeFollowUpProviderId(input.providerId);
|
|
91
125
|
const objective = normalizeObjective(input.objective);
|
|
92
126
|
const completionCriteria = normalizeCompletionCriteria(input.completionCriteria, objective);
|
|
93
127
|
const maxAutoContinueCount = normalizeMaxAutoContinueCount(input.maxAutoContinueCount);
|
|
@@ -102,15 +136,38 @@ export class ButlerFollowUpService {
|
|
|
102
136
|
});
|
|
103
137
|
}
|
|
104
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);
|
|
105
159
|
const timestamp = nowIso();
|
|
106
160
|
const initialSummary = snapshot.runningState === "starting" || snapshot.runningState === "running"
|
|
107
|
-
?
|
|
108
|
-
:
|
|
161
|
+
? `已创建跟进助手会话,先等待当前运行结束,再由该会话决定是否继续推进。默认最多自动推进 ${maxAutoContinueCount} 轮。`
|
|
162
|
+
: `已创建跟进助手会话,准备由该会话检查当前进展并决定是否继续推进。默认最多自动推进 ${maxAutoContinueCount} 轮。`;
|
|
109
163
|
const task = this.butlerFollowUpTaskRepository.create({
|
|
110
164
|
id: createId(),
|
|
111
165
|
projectId: project.id,
|
|
112
166
|
butlerSessionId: input.butlerSessionId,
|
|
113
167
|
sessionId: snapshot.sessionId,
|
|
168
|
+
providerId,
|
|
169
|
+
assistantButlerSessionId: assistantSession.id,
|
|
170
|
+
assistantSessionId: assistantSession.sessionId,
|
|
114
171
|
createdByUserId: userId,
|
|
115
172
|
objective,
|
|
116
173
|
completionCriteria,
|
|
@@ -136,6 +193,143 @@ export class ButlerFollowUpService {
|
|
|
136
193
|
const processed = await this.processTask(task.id);
|
|
137
194
|
return mapTaskView(processed, project.workspaceId, project.name, session.title ?? null);
|
|
138
195
|
}
|
|
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
|
+
}
|
|
139
333
|
async cancelTask(taskId, userId) {
|
|
140
334
|
this.butlerProfileService.ensureInitialized();
|
|
141
335
|
const task = this.butlerFollowUpTaskRepository.findById(taskId);
|
|
@@ -181,7 +375,7 @@ export class ButlerFollowUpService {
|
|
|
181
375
|
autoContinueCount: task.autoContinueCount,
|
|
182
376
|
createdAt: timestamp
|
|
183
377
|
});
|
|
184
|
-
await this.stopActiveTaskAutomation(execution);
|
|
378
|
+
await this.stopActiveTaskAutomation(task, execution);
|
|
185
379
|
const project = this.butlerProjectService.getById(updated.projectId);
|
|
186
380
|
const index = this.sessionIndexRepository.findIndexRecordBySessionId(updated.sessionId);
|
|
187
381
|
return mapTaskView(updated, project.workspaceId, project.name, index?.title ?? null);
|
|
@@ -236,7 +430,6 @@ export class ButlerFollowUpService {
|
|
|
236
430
|
}
|
|
237
431
|
const execution = this.beginTaskExecution(task.id);
|
|
238
432
|
try {
|
|
239
|
-
const profile = this.butlerProfileService.ensureInitialized();
|
|
240
433
|
const project = this.butlerProjectService.getById(task.projectId);
|
|
241
434
|
const inspection = await this.inspectTask(task);
|
|
242
435
|
this.ensureTaskExecutionActive(task.id, execution);
|
|
@@ -282,139 +475,52 @@ export class ButlerFollowUpService {
|
|
|
282
475
|
createdAt: referenceAt
|
|
283
476
|
});
|
|
284
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
|
+
}
|
|
285
502
|
try {
|
|
286
|
-
const
|
|
503
|
+
const progressBeforeDispatch = snapshotTaskProgress(task);
|
|
504
|
+
await this.requestAssistantEvaluation(project, task, inspection, runningState, execution);
|
|
287
505
|
this.ensureTaskExecutionActive(task.id, execution);
|
|
288
|
-
|
|
289
|
-
case "completed":
|
|
290
|
-
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
291
|
-
...baseUpdate,
|
|
292
|
-
status: "completed",
|
|
293
|
-
waitingReason: null,
|
|
294
|
-
nextCheckAt: null,
|
|
295
|
-
completedAt: referenceAt,
|
|
296
|
-
lastAutomationAt: referenceAt,
|
|
297
|
-
lastAutomationSummary: evaluation.summary
|
|
298
|
-
}, {
|
|
299
|
-
kind: "completed",
|
|
300
|
-
status: "completed",
|
|
301
|
-
summary: evaluation.summary,
|
|
302
|
-
waitingReason: null,
|
|
303
|
-
continuePrompt: null,
|
|
304
|
-
observedRunningState: runningState,
|
|
305
|
-
autoContinueCount: task.autoContinueCount,
|
|
306
|
-
createdAt: referenceAt
|
|
307
|
-
});
|
|
308
|
-
case "waiting_user":
|
|
309
|
-
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
310
|
-
...baseUpdate,
|
|
311
|
-
status: "waiting_user",
|
|
312
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
313
|
-
nextCheckAt: null,
|
|
314
|
-
completedAt: null,
|
|
315
|
-
lastAutomationAt: referenceAt,
|
|
316
|
-
lastAutomationSummary: evaluation.summary
|
|
317
|
-
}, {
|
|
318
|
-
kind: "waiting_user",
|
|
319
|
-
status: "waiting_user",
|
|
320
|
-
summary: evaluation.summary,
|
|
321
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
322
|
-
continuePrompt: null,
|
|
323
|
-
observedRunningState: runningState,
|
|
324
|
-
autoContinueCount: task.autoContinueCount,
|
|
325
|
-
createdAt: referenceAt
|
|
326
|
-
});
|
|
327
|
-
case "failed":
|
|
328
|
-
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
329
|
-
...baseUpdate,
|
|
330
|
-
status: "failed",
|
|
331
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
332
|
-
nextCheckAt: null,
|
|
333
|
-
completedAt: null,
|
|
334
|
-
lastAutomationAt: referenceAt,
|
|
335
|
-
lastAutomationSummary: evaluation.summary
|
|
336
|
-
}, {
|
|
337
|
-
kind: "failed",
|
|
338
|
-
status: "failed",
|
|
339
|
-
summary: evaluation.summary,
|
|
340
|
-
waitingReason: evaluation.waitingReason ?? evaluation.summary,
|
|
341
|
-
continuePrompt: null,
|
|
342
|
-
observedRunningState: runningState,
|
|
343
|
-
autoContinueCount: task.autoContinueCount,
|
|
344
|
-
createdAt: referenceAt
|
|
345
|
-
});
|
|
346
|
-
case "continue":
|
|
347
|
-
if (!evaluation.continuePrompt) {
|
|
348
|
-
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
349
|
-
...baseUpdate,
|
|
350
|
-
status: "failed",
|
|
351
|
-
waitingReason: "后台评估助手没有返回可继续推进的指令。",
|
|
352
|
-
nextCheckAt: null,
|
|
353
|
-
completedAt: null,
|
|
354
|
-
lastAutomationAt: referenceAt,
|
|
355
|
-
lastAutomationSummary: evaluation.summary
|
|
356
|
-
}, {
|
|
357
|
-
kind: "failed",
|
|
358
|
-
status: "failed",
|
|
359
|
-
summary: evaluation.summary,
|
|
360
|
-
waitingReason: "后台评估助手没有返回可继续推进的指令。",
|
|
361
|
-
continuePrompt: null,
|
|
362
|
-
observedRunningState: runningState,
|
|
363
|
-
autoContinueCount: task.autoContinueCount,
|
|
364
|
-
createdAt: referenceAt
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
this.ensureTaskExecutionActive(task.id, execution);
|
|
368
|
-
const sendResult = await this.sendContinuePrompt(task, evaluation.continuePrompt, referenceAt);
|
|
369
|
-
this.ensureTaskExecutionActive(task.id, execution);
|
|
370
|
-
this.butlerSessionService.captureSessionSnapshot(task.projectId, task.butlerSessionId, task.createdByUserId, { sourceKind: "manual" });
|
|
371
|
-
const nextAutoContinueCount = task.autoContinueCount + 1;
|
|
372
|
-
const nextSummary = sendResult.delivery === "queued"
|
|
373
|
-
? buildQueuedFollowUpSummary(evaluation.summary, sendResult.queueItem)
|
|
374
|
-
: evaluation.summary;
|
|
375
|
-
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
376
|
-
...baseUpdate,
|
|
377
|
-
status: "active",
|
|
378
|
-
waitingReason: null,
|
|
379
|
-
nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
|
|
380
|
-
lastAutomationAt: referenceAt,
|
|
381
|
-
autoContinueCount: nextAutoContinueCount,
|
|
382
|
-
lastAutomationSummary: nextSummary
|
|
383
|
-
}, {
|
|
384
|
-
kind: sendResult.delivery === "queued" ? "queued" : "continue",
|
|
385
|
-
status: "active",
|
|
386
|
-
summary: nextSummary,
|
|
387
|
-
waitingReason: null,
|
|
388
|
-
continuePrompt: evaluation.continuePrompt,
|
|
389
|
-
observedRunningState: runningState,
|
|
390
|
-
autoContinueCount: nextAutoContinueCount,
|
|
391
|
-
createdAt: referenceAt
|
|
392
|
-
});
|
|
393
|
-
default:
|
|
394
|
-
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
395
|
-
...baseUpdate,
|
|
396
|
-
status: "failed",
|
|
397
|
-
waitingReason: "后台评估助手返回了不支持的决策。",
|
|
398
|
-
nextCheckAt: null,
|
|
399
|
-
completedAt: null,
|
|
400
|
-
lastAutomationAt: referenceAt,
|
|
401
|
-
lastAutomationSummary: "后台评估助手返回了不支持的决策。"
|
|
402
|
-
}, {
|
|
403
|
-
kind: "failed",
|
|
404
|
-
status: "failed",
|
|
405
|
-
summary: "后台评估助手返回了不支持的决策。",
|
|
406
|
-
waitingReason: "后台评估助手返回了不支持的决策。",
|
|
407
|
-
continuePrompt: null,
|
|
408
|
-
observedRunningState: runningState,
|
|
409
|
-
autoContinueCount: task.autoContinueCount,
|
|
410
|
-
createdAt: referenceAt
|
|
411
|
-
});
|
|
412
|
-
}
|
|
506
|
+
return this.requireAssistantDecisionPersisted(task.id, progressBeforeDispatch);
|
|
413
507
|
}
|
|
414
508
|
catch (error) {
|
|
415
509
|
if (error instanceof FollowUpTaskCancelledError) {
|
|
416
510
|
return this.butlerFollowUpTaskRepository.findById(task.id) ?? task;
|
|
417
511
|
}
|
|
512
|
+
const providerUsageLimit = resolveProviderUsageLimitFromError(error, task.providerId, referenceAt);
|
|
513
|
+
if (providerUsageLimit) {
|
|
514
|
+
return this.persistIfExecutionActive(task.id, execution, {
|
|
515
|
+
...baseUpdate,
|
|
516
|
+
status: "active",
|
|
517
|
+
waitingReason: null,
|
|
518
|
+
nextCheckAt: resolveProviderUsageLimitNextCheckAt(providerUsageLimit, referenceAt, task.checkIntervalSeconds),
|
|
519
|
+
completedAt: null,
|
|
520
|
+
lastAutomationAt: referenceAt,
|
|
521
|
+
lastAutomationSummary: buildFollowUpUsageLimitSummary(providerUsageLimit, "跟进助手会话当前被 provider 额度限制暂时挡住。")
|
|
522
|
+
});
|
|
523
|
+
}
|
|
418
524
|
if (isDeferredFollowUpSendError(error)) {
|
|
419
525
|
return this.persistIfExecutionActive(task.id, execution, {
|
|
420
526
|
...baseUpdate,
|
|
@@ -423,11 +529,11 @@ export class ButlerFollowUpService {
|
|
|
423
529
|
nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
|
|
424
530
|
completedAt: null,
|
|
425
531
|
lastAutomationAt: referenceAt,
|
|
426
|
-
lastAutomationSummary: "
|
|
532
|
+
lastAutomationSummary: "跟进助手会话当前仍在运行,本轮继续等待下一次检查。"
|
|
427
533
|
});
|
|
428
534
|
}
|
|
429
535
|
const detail = error instanceof Error ? error.message : String(error);
|
|
430
|
-
const summary =
|
|
536
|
+
const summary = `跟进助手执行失败:${detail}`;
|
|
431
537
|
return this.persistWithRoundIfExecutionActive(task.id, execution, {
|
|
432
538
|
...baseUpdate,
|
|
433
539
|
status: "failed",
|
|
@@ -464,6 +570,16 @@ export class ButlerFollowUpService {
|
|
|
464
570
|
}
|
|
465
571
|
return this.butlerFollowUpTaskRepository.update(normalizedTask) ?? normalizedTask;
|
|
466
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
|
+
}
|
|
467
583
|
persistWithRound(task, round) {
|
|
468
584
|
const normalizedRounds = normalizeFollowUpRounds(task.rounds);
|
|
469
585
|
return this.persist({
|
|
@@ -533,7 +649,7 @@ export class ButlerFollowUpService {
|
|
|
533
649
|
&& normalizeNullableIso(session.lastMessageAt) === normalizeNullableIso(task.lastObservedMessageAt)
|
|
534
650
|
&& session.messageCount === task.lastObservedMessageCount);
|
|
535
651
|
}
|
|
536
|
-
async sendContinuePrompt(task, continuePrompt, referenceAt) {
|
|
652
|
+
async sendContinuePrompt(task, providerId, continuePrompt, referenceAt) {
|
|
537
653
|
const clientRequestId = buildFollowUpClientRequestId(task.id, referenceAt);
|
|
538
654
|
try {
|
|
539
655
|
const result = await this.sessionLiveRuntimeService.sendLiveMessage({
|
|
@@ -554,6 +670,13 @@ export class ButlerFollowUpService {
|
|
|
554
670
|
};
|
|
555
671
|
}
|
|
556
672
|
catch (error) {
|
|
673
|
+
const providerUsageLimit = resolveProviderUsageLimitFromError(error, providerId, referenceAt);
|
|
674
|
+
if (providerUsageLimit) {
|
|
675
|
+
return {
|
|
676
|
+
delivery: "cooldown",
|
|
677
|
+
providerUsageLimit
|
|
678
|
+
};
|
|
679
|
+
}
|
|
557
680
|
if (!isDeferredFollowUpSendError(error)) {
|
|
558
681
|
throw error;
|
|
559
682
|
}
|
|
@@ -589,32 +712,36 @@ export class ButlerFollowUpService {
|
|
|
589
712
|
});
|
|
590
713
|
}
|
|
591
714
|
async inspectTask(task) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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);
|
|
595
722
|
const sortedMessages = (envelope?.messages ?? [])
|
|
596
723
|
.slice()
|
|
597
724
|
.sort((left, right) => left.sequence - right.sequence);
|
|
725
|
+
const providerUsageLimit = resolveInspectionProviderUsageLimit(session.provider, session.lastErrorDetail, latestAssistantText, session.lastMessageAt);
|
|
598
726
|
return {
|
|
727
|
+
providerId: session.provider,
|
|
599
728
|
runningState: normalizeRunningState(runtime.runningState),
|
|
600
729
|
messageAt: session.lastMessageAt,
|
|
601
730
|
messageCount: session.messageCount,
|
|
602
731
|
sessionTitle: session.title ?? null,
|
|
603
|
-
|
|
732
|
+
providerUsageLimit,
|
|
733
|
+
latestAssistantText,
|
|
604
734
|
transcriptLines: sortedMessages.map((message) => renderHistoryLine(message.sequence, message.role, message.kind ?? "text", message.timestamp, message.content))
|
|
605
735
|
};
|
|
606
736
|
}
|
|
607
|
-
async
|
|
608
|
-
const evaluatorWorkspacePath = path.join(profile.workspacePath, FOLLOW_UP_EVALUATOR_DIRNAME);
|
|
609
|
-
ensureButlerWorkspaceIsolation(evaluatorWorkspacePath);
|
|
610
|
-
this.writeEvaluationInstructionFiles(evaluatorWorkspacePath, profile.providerId);
|
|
611
|
-
this.syncCodexInstructionConfig(profile.providerId, evaluatorWorkspacePath);
|
|
612
|
-
const workspace = this.workspaceService.importWorkspace(evaluatorWorkspacePath, "代码助手");
|
|
737
|
+
async requestAssistantEvaluation(project, task, inspection, runningState, execution) {
|
|
613
738
|
const instruction = this.instructionAdapter.buildInstruction({
|
|
614
|
-
|
|
739
|
+
taskId: task.id,
|
|
740
|
+
providerId: task.providerId,
|
|
615
741
|
project,
|
|
616
742
|
sessionId: task.sessionId,
|
|
617
743
|
butlerSessionId: task.butlerSessionId,
|
|
744
|
+
assistantSessionId: task.assistantSessionId,
|
|
618
745
|
sessionTitle: inspection.sessionTitle,
|
|
619
746
|
objective: task.objective,
|
|
620
747
|
completionCriteria: task.completionCriteria,
|
|
@@ -627,34 +754,35 @@ export class ButlerFollowUpService {
|
|
|
627
754
|
latestAssistantText: inspection.latestAssistantText,
|
|
628
755
|
transcriptLines: inspection.transcriptLines
|
|
629
756
|
});
|
|
630
|
-
|
|
631
|
-
const launch = await adapter.startPatrolSession({
|
|
632
|
-
workspaceId: workspace.id,
|
|
633
|
-
userId: task.createdByUserId,
|
|
634
|
-
providerId: profile.providerId,
|
|
635
|
-
prompt: instruction.prompt,
|
|
636
|
-
model: resolveFollowUpModel(profile.providerId, this.sourceCodexHomeDir),
|
|
637
|
-
reasoningLevel: "low",
|
|
638
|
-
permissionMode: "default",
|
|
639
|
-
instructionFilePath: resolveFollowUpInstructionFilePath(profile.providerId, evaluatorWorkspacePath)
|
|
640
|
-
});
|
|
641
|
-
execution.evaluatorSessionId = launch.sessionId;
|
|
757
|
+
execution.assistantSessionId = task.assistantSessionId;
|
|
642
758
|
try {
|
|
643
|
-
await
|
|
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);
|
|
644
774
|
this.ensureTaskExecutionActive(task.id, execution);
|
|
645
|
-
const result = await adapter.readPatrolResult(launch.sessionId);
|
|
646
|
-
return parseEvaluationResult(result);
|
|
647
775
|
}
|
|
648
776
|
finally {
|
|
649
|
-
if (execution.
|
|
650
|
-
execution.
|
|
777
|
+
if (execution.assistantSessionId === task.assistantSessionId) {
|
|
778
|
+
execution.assistantSessionId = null;
|
|
651
779
|
}
|
|
652
780
|
}
|
|
653
781
|
}
|
|
654
782
|
beginTaskExecution(taskId) {
|
|
655
783
|
const execution = {
|
|
656
784
|
cancelled: false,
|
|
657
|
-
|
|
785
|
+
assistantSessionId: null
|
|
658
786
|
};
|
|
659
787
|
this.activeExecutionStateByTaskId.set(taskId, execution);
|
|
660
788
|
return execution;
|
|
@@ -675,61 +803,33 @@ export class ButlerFollowUpService {
|
|
|
675
803
|
const current = this.activeExecutionStateByTaskId.get(taskId);
|
|
676
804
|
return Boolean(current && current === execution && !execution.cancelled);
|
|
677
805
|
}
|
|
678
|
-
async stopActiveTaskAutomation(execution) {
|
|
679
|
-
if (!execution?.
|
|
806
|
+
async stopActiveTaskAutomation(task, execution) {
|
|
807
|
+
if (!execution?.assistantSessionId) {
|
|
680
808
|
return;
|
|
681
809
|
}
|
|
682
|
-
const profile = this.butlerProfileService.ensureInitialized();
|
|
683
|
-
const adapter = this.providerAdapterRegistry.get(profile.providerId);
|
|
684
810
|
try {
|
|
685
|
-
await
|
|
811
|
+
await this.sessionLiveRuntimeService.interruptSession(execution.assistantSessionId, task.createdByUserId);
|
|
686
812
|
}
|
|
687
813
|
catch (error) {
|
|
688
|
-
console.warn("[butler-follow-up] interrupt
|
|
689
|
-
sessionId: execution.
|
|
814
|
+
console.warn("[butler-follow-up] interrupt assistant follow-up session failed", {
|
|
815
|
+
sessionId: execution.assistantSessionId,
|
|
690
816
|
error: error instanceof Error ? error.message : String(error)
|
|
691
817
|
});
|
|
692
818
|
}
|
|
693
819
|
finally {
|
|
694
|
-
execution.
|
|
820
|
+
execution.assistantSessionId = null;
|
|
695
821
|
}
|
|
696
822
|
}
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
"如果没有 spec,就先从目标和最近消息里归纳一句当前核心任务,后续只能围绕这个核心任务判断,不准无限扩展。",
|
|
706
|
-
"除非目标本身要求,否则不要把重构、补测试、补体验优化之类建议项升级成必须开发的工作。",
|
|
707
|
-
"禁止照搬最后一句回复做草率判断,必须结合用户目标、当前运行态和最近消息一起判断。",
|
|
708
|
-
"如果能继续推进,就直接给出下一条要发给开发会话的中文指令,不要空谈。",
|
|
709
|
-
"如果确实需要用户决定,要把缺口说清楚,但不要替用户做不存在的决定。",
|
|
710
|
-
"输出语言必须是中文,先给结论,再给结构化 JSON。"
|
|
711
|
-
].join("\n");
|
|
712
|
-
writeFileIfChanged(path.join(workspacePath, "AGENTS.md"), `${content}\n`);
|
|
713
|
-
if (providerId === "claude-code") {
|
|
714
|
-
writeFileIfChanged(path.join(workspacePath, "CLAUDE.md"), `${content}\n`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
syncCodexInstructionConfig(providerId, workspacePath) {
|
|
718
|
-
if (providerId !== "codex" || !this.followUpCodexHomeDir?.trim()) {
|
|
719
|
-
return;
|
|
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);
|
|
720
831
|
}
|
|
721
|
-
|
|
722
|
-
const sourceHomeDir = resolveSourceCodexHomeDir(this.sourceCodexHomeDir, targetHomeDir);
|
|
723
|
-
const sourceConfigPath = path.join(sourceHomeDir, "config.toml");
|
|
724
|
-
const sourceConfigContent = sourceHomeDir !== targetHomeDir && fs.existsSync(sourceConfigPath) && fs.statSync(sourceConfigPath).isFile()
|
|
725
|
-
? fs.readFileSync(sourceConfigPath, "utf8")
|
|
726
|
-
: "";
|
|
727
|
-
const instructionFilePath = path.join(workspacePath, "AGENTS.md");
|
|
728
|
-
fs.mkdirSync(targetHomeDir, { recursive: true });
|
|
729
|
-
removeFileIfExists(path.join(targetHomeDir, "AGENTS.md"));
|
|
730
|
-
removeFileIfExists(path.join(targetHomeDir, "AGENTS.override.md"));
|
|
731
|
-
syncOptionalFile(path.join(sourceHomeDir, "auth.json"), path.join(targetHomeDir, "auth.json"));
|
|
732
|
-
writeFileIfChanged(path.join(targetHomeDir, "config.toml"), `${composeCodexConfigContent(sourceConfigContent, instructionFilePath)}\n`);
|
|
832
|
+
throw new Error(`BUTLER_FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT:${sessionId}`);
|
|
733
833
|
}
|
|
734
834
|
}
|
|
735
835
|
function mapTaskView(task, workspaceId, projectName, sessionTitle) {
|
|
@@ -741,6 +841,9 @@ function mapTaskView(task, workspaceId, projectName, sessionTitle) {
|
|
|
741
841
|
workspaceId,
|
|
742
842
|
butlerSessionId: task.butlerSessionId,
|
|
743
843
|
sessionId: task.sessionId,
|
|
844
|
+
providerId: task.providerId,
|
|
845
|
+
assistantButlerSessionId: task.assistantButlerSessionId,
|
|
846
|
+
assistantSessionId: task.assistantSessionId,
|
|
744
847
|
sessionTitle,
|
|
745
848
|
objective: task.objective,
|
|
746
849
|
completionCriteria: task.completionCriteria,
|
|
@@ -818,6 +921,24 @@ function normalizeObjective(value) {
|
|
|
818
921
|
}
|
|
819
922
|
return normalized;
|
|
820
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
|
+
}
|
|
821
942
|
function normalizeCompletionCriteria(value, objective) {
|
|
822
943
|
const normalized = value?.trim();
|
|
823
944
|
return normalized && normalized.length > 0
|
|
@@ -843,6 +964,16 @@ function buildFollowUpClientRequestId(taskId, referenceAt) {
|
|
|
843
964
|
function buildQueuedFollowUpSummary(summary, queueItem) {
|
|
844
965
|
return `${summary} 已转入消息队列,等待当前会话空闲后自动补发(队列项 ${queueItem.orderIndex})。`;
|
|
845
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
|
+
}
|
|
846
977
|
function isDeferredFollowUpSendError(error) {
|
|
847
978
|
if (error instanceof AppError) {
|
|
848
979
|
return (error.errorCode === "ACTIVE_RUN_EXISTS"
|
|
@@ -864,6 +995,14 @@ function isDeferredFollowUpSendError(error) {
|
|
|
864
995
|
|| error.message === "SERVER_TIMEOUT"
|
|
865
996
|
|| error.message.includes("当前会话正在运行"));
|
|
866
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
|
+
}
|
|
867
1006
|
function isSyntheticMessageId(messageId) {
|
|
868
1007
|
return typeof messageId === "string" && messageId.startsWith("synthetic-");
|
|
869
1008
|
}
|
|
@@ -891,6 +1030,24 @@ function normalizeNullableIso(value) {
|
|
|
891
1030
|
const normalized = value?.trim();
|
|
892
1031
|
return normalized && normalized.length > 0 ? normalized : null;
|
|
893
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
|
+
}
|
|
894
1051
|
function resolveLatestAssistantText(envelope) {
|
|
895
1052
|
if (!envelope || envelope.messages.length === 0) {
|
|
896
1053
|
return null;
|
|
@@ -912,143 +1069,91 @@ function truncateText(value, maxLength) {
|
|
|
912
1069
|
}
|
|
913
1070
|
return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
914
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
|
+
}
|
|
915
1106
|
function resolveFollowUpModel(providerId, sourceCodexHomeDir) {
|
|
916
1107
|
if (providerId !== "codex") {
|
|
917
1108
|
return "haiku";
|
|
918
1109
|
}
|
|
919
1110
|
return resolveButlerCodexBackgroundModel("gpt-5.1-codex-mini", sourceCodexHomeDir);
|
|
920
1111
|
}
|
|
921
|
-
function
|
|
922
|
-
return path.join(workspacePath, providerId === "claude-code" ? "CLAUDE.md" : "AGENTS.md");
|
|
923
|
-
}
|
|
924
|
-
function parseEvaluationResult(result) {
|
|
925
|
-
const rawJson = result.structured.rawJson ?? extractJsonFromText(result.latestAssistantMessage);
|
|
926
|
-
if (!rawJson) {
|
|
927
|
-
throw new Error("后台评估助手没有返回结构化 JSON");
|
|
928
|
-
}
|
|
929
|
-
let parsed;
|
|
930
|
-
try {
|
|
931
|
-
parsed = JSON.parse(rawJson);
|
|
932
|
-
}
|
|
933
|
-
catch (error) {
|
|
934
|
-
throw new Error(`后台评估助手返回的 JSON 无法解析:${error instanceof Error ? error.message : String(error)}`);
|
|
935
|
-
}
|
|
936
|
-
const decision = normalizeDecision(parsed.decision);
|
|
937
|
-
if (!decision) {
|
|
938
|
-
throw new Error("后台评估助手返回的 decision 不合法");
|
|
939
|
-
}
|
|
940
|
-
const summary = normalizeNonEmptyString(parsed.summary) ?? result.structured.summary ?? "后台评估助手未提供摘要";
|
|
941
|
-
const waitingReason = normalizeNullableString(parsed.waitingReason);
|
|
942
|
-
const continuePrompt = normalizeNullableString(parsed.continuePrompt);
|
|
943
|
-
const riskLevel = normalizeRiskLevel(parsed.riskLevel);
|
|
1112
|
+
function snapshotTaskProgress(task) {
|
|
944
1113
|
return {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
riskLevel
|
|
1114
|
+
roundCount: normalizeFollowUpRounds(task.rounds).length,
|
|
1115
|
+
updatedAt: task.updatedAt,
|
|
1116
|
+
lastAutomationAt: task.lastAutomationAt,
|
|
1117
|
+
autoContinueCount: task.autoContinueCount
|
|
950
1118
|
};
|
|
951
1119
|
}
|
|
952
|
-
function
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
return value;
|
|
959
|
-
default:
|
|
960
|
-
return null;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
function normalizeRiskLevel(value) {
|
|
964
|
-
switch (value) {
|
|
965
|
-
case "low":
|
|
966
|
-
case "medium":
|
|
967
|
-
case "high":
|
|
968
|
-
return value;
|
|
969
|
-
default:
|
|
970
|
-
return null;
|
|
971
|
-
}
|
|
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);
|
|
972
1126
|
}
|
|
973
|
-
function
|
|
1127
|
+
function requireNonEmptyFollowUpText(value, field, detail) {
|
|
974
1128
|
if (typeof value !== "string") {
|
|
975
|
-
|
|
1129
|
+
throw new AppError({
|
|
1130
|
+
statusCode: 400,
|
|
1131
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
|
|
1132
|
+
detail
|
|
1133
|
+
});
|
|
976
1134
|
}
|
|
977
1135
|
const normalized = value.trim();
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
return normalizeNonEmptyString(value);
|
|
985
|
-
}
|
|
986
|
-
function extractJsonFromText(value) {
|
|
987
|
-
if (!value) {
|
|
988
|
-
return null;
|
|
989
|
-
}
|
|
990
|
-
const matched = value.match(/```json\s*([\s\S]*?)```/i);
|
|
991
|
-
const raw = matched?.[1]?.trim();
|
|
992
|
-
return raw || null;
|
|
993
|
-
}
|
|
994
|
-
function resolveSourceCodexHomeDir(sourceCodexHomeDir, targetHomeDir) {
|
|
995
|
-
const configuredSource = sourceCodexHomeDir?.trim();
|
|
996
|
-
if (configuredSource) {
|
|
997
|
-
const resolvedConfiguredSource = path.resolve(configuredSource);
|
|
998
|
-
if (resolvedConfiguredSource !== targetHomeDir) {
|
|
999
|
-
return resolvedConfiguredSource;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
const fallbackHomeDir = path.resolve(path.join(os.homedir(), ".codex"));
|
|
1003
|
-
if (fallbackHomeDir !== targetHomeDir) {
|
|
1004
|
-
return fallbackHomeDir;
|
|
1005
|
-
}
|
|
1006
|
-
return targetHomeDir;
|
|
1007
|
-
}
|
|
1008
|
-
function composeCodexConfigContent(sourceConfigContent, instructionFilePath) {
|
|
1009
|
-
const normalizedSource = sourceConfigContent
|
|
1010
|
-
.split(/\r?\n/)
|
|
1011
|
-
.filter((line) => {
|
|
1012
|
-
const trimmed = line.trim();
|
|
1013
|
-
return trimmed.length > 0 && !trimmed.startsWith("model_instructions_file");
|
|
1014
|
-
})
|
|
1015
|
-
.join("\n")
|
|
1016
|
-
.trim();
|
|
1017
|
-
return [
|
|
1018
|
-
"# 代码助手跟进评估专用 Codex 配置(系统自动生成)",
|
|
1019
|
-
normalizedSource,
|
|
1020
|
-
`model_instructions_file = ${toTomlString(path.resolve(instructionFilePath))}`
|
|
1021
|
-
]
|
|
1022
|
-
.filter((part) => part.trim().length > 0)
|
|
1023
|
-
.join("\n\n");
|
|
1024
|
-
}
|
|
1025
|
-
function toTomlString(value) {
|
|
1026
|
-
return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
1027
|
-
}
|
|
1028
|
-
function writeFileIfChanged(filePath, content) {
|
|
1029
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1030
|
-
if (fs.existsSync(filePath) && fs.readFileSync(filePath, "utf8") === content) {
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
fs.writeFileSync(filePath, content, "utf8");
|
|
1034
|
-
}
|
|
1035
|
-
function removeFileIfExists(filePath) {
|
|
1036
|
-
if (!fs.existsSync(filePath)) {
|
|
1037
|
-
return;
|
|
1136
|
+
if (!normalized) {
|
|
1137
|
+
throw new AppError({
|
|
1138
|
+
statusCode: 400,
|
|
1139
|
+
errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
|
|
1140
|
+
detail
|
|
1141
|
+
});
|
|
1038
1142
|
}
|
|
1039
|
-
if (
|
|
1040
|
-
|
|
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
|
+
});
|
|
1041
1149
|
}
|
|
1150
|
+
return normalized;
|
|
1042
1151
|
}
|
|
1043
|
-
function
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
1049
|
-
if (fs.existsSync(targetPath) && fs.readFileSync(targetPath).equals(fs.readFileSync(sourcePath))) {
|
|
1050
|
-
return;
|
|
1152
|
+
function normalizeNullableText(value) {
|
|
1153
|
+
if (typeof value !== "string") {
|
|
1154
|
+
return null;
|
|
1051
1155
|
}
|
|
1052
|
-
|
|
1156
|
+
const normalized = value.trim();
|
|
1157
|
+
return normalized || null;
|
|
1053
1158
|
}
|
|
1054
1159
|
//# sourceMappingURL=butler-follow-up-service.js.map
|