yepanywhere 0.5.2 → 0.6.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 +21 -5
- package/bundled/@yep-anywhere/shared/dist/agent-context.d.ts +9 -0
- package/bundled/@yep-anywhere/shared/dist/agent-context.d.ts.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/agent-context.js +18 -0
- package/bundled/@yep-anywhere/shared/dist/agent-context.js.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/app-types.d.ts +104 -1
- package/bundled/@yep-anywhere/shared/dist/app-types.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/app-types.js +4 -0
- package/bundled/@yep-anywhere/shared/dist/app-types.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/binary-framing.d.ts +4 -1
- package/bundled/@yep-anywhere/shared/dist/binary-framing.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/binary-framing.js +8 -3
- package/bundled/@yep-anywhere/shared/dist/binary-framing.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/AssistantEntrySchema.d.ts +4 -0
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/AssistantEntrySchema.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.d.ts +4 -0
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.js +7 -0
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/BaseEntrySchema.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/ProgressEntrySchema.d.ts +4 -0
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/ProgressEntrySchema.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.d.ts +64 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.js +9 -0
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/SystemEntrySchema.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/UserEntrySchema.d.ts +4 -0
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/entry/UserEntrySchema.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/guards.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/guards.js +45 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/guards.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/index.d.ts +144 -2
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/index.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.d.ts +6 -4
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.js +8 -1
- package/bundled/@yep-anywhere/shared/dist/claude-sdk-schema/tool/ToolResultSchemas.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/codex-schema/index.d.ts +3 -2
- package/bundled/@yep-anywhere/shared/dist/codex-schema/index.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/codex-schema/index.js +3 -2
- package/bundled/@yep-anywhere/shared/dist/codex-schema/index.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts +87 -3
- package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js +39 -1
- package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/dag.d.ts +31 -11
- package/bundled/@yep-anywhere/shared/dist/dag.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/dag.js +77 -33
- package/bundled/@yep-anywhere/shared/dist/dag.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/git-status.d.ts +70 -1
- package/bundled/@yep-anywhere/shared/dist/git-status.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/git-status.js +6 -1
- package/bundled/@yep-anywhere/shared/dist/git-status.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/ideMetadata.js +1 -1
- package/bundled/@yep-anywhere/shared/dist/ideMetadata.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/index.d.ts +20 -11
- package/bundled/@yep-anywhere/shared/dist/index.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/index.js +8 -3
- package/bundled/@yep-anywhere/shared/dist/index.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/local-resource.d.ts +1 -0
- package/bundled/@yep-anywhere/shared/dist/local-resource.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/local-resource.js +4 -0
- package/bundled/@yep-anywhere/shared/dist/local-resource.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.d.ts +136 -0
- package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.js +65 -3
- package/bundled/@yep-anywhere/shared/dist/opencode-schema/events.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/opencode-schema/types.d.ts +1 -1
- package/bundled/@yep-anywhere/shared/dist/opencode-schema/types.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/project-queue.d.ts +153 -0
- package/bundled/@yep-anywhere/shared/dist/project-queue.d.ts.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/project-queue.js +8 -0
- package/bundled/@yep-anywhere/shared/dist/project-queue.js.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/relay-protocol.d.ts +26 -2
- package/bundled/@yep-anywhere/shared/dist/relay-protocol.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/relay-protocol.js +33 -0
- package/bundled/@yep-anywhere/shared/dist/relay-protocol.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/relay.d.ts +45 -5
- package/bundled/@yep-anywhere/shared/dist/relay.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/relay.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/safe-restart.d.ts +25 -0
- package/bundled/@yep-anywhere/shared/dist/safe-restart.d.ts.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/safe-restart.js +2 -0
- package/bundled/@yep-anywhere/shared/dist/safe-restart.js.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/session/SessionView.d.ts +2 -2
- package/bundled/@yep-anywhere/shared/dist/session/SessionView.js +3 -3
- package/bundled/@yep-anywhere/shared/dist/session/SessionView.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/session/UnifiedSession.d.ts +10 -0
- package/bundled/@yep-anywhere/shared/dist/session/UnifiedSession.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/session-liveness.d.ts +18 -0
- package/bundled/@yep-anywhere/shared/dist/session-liveness.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/types.d.ts +253 -8
- package/bundled/@yep-anywhere/shared/dist/types.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/types.js +48 -5
- package/bundled/@yep-anywhere/shared/dist/types.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/upload.d.ts +34 -2
- package/bundled/@yep-anywhere/shared/dist/upload.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/user-message-metadata.d.ts +47 -0
- package/bundled/@yep-anywhere/shared/dist/user-message-metadata.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/user-message-metadata.js +59 -1
- package/bundled/@yep-anywhere/shared/dist/user-message-metadata.js.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/workstreams.d.ts +54 -0
- package/bundled/@yep-anywhere/shared/dist/workstreams.d.ts.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/workstreams.js +8 -0
- package/bundled/@yep-anywhere/shared/dist/workstreams.js.map +1 -0
- package/bundled/@yep-anywhere/shared/package.json +1 -1
- package/client-dist/assets/alegreya-sans-latin-400-italic-BQIUBrX-.woff2 +0 -0
- package/client-dist/assets/alegreya-sans-latin-400-normal-BxeZdXas.woff2 +0 -0
- package/client-dist/assets/alegreya-sans-latin-500-normal-uodOEoAl.woff2 +0 -0
- package/client-dist/assets/alegreya-sans-latin-700-normal-DFYOGfC8.woff2 +0 -0
- package/client-dist/assets/de-CHMDbVD5.js +6 -0
- package/client-dist/assets/es-CK8MPF_C.js +6 -0
- package/client-dist/assets/fira-mono-latin-400-normal-DVTTRLHv.woff2 +0 -0
- package/client-dist/assets/fira-mono-latin-ext-400-normal-B2gPvaNr.woff2 +0 -0
- package/client-dist/assets/fira-mono-symbols2-400-normal-C6JptOil.woff2 +0 -0
- package/client-dist/assets/fr-49PXQiUL.js +6 -0
- package/client-dist/assets/index-B-09fVut.js +557 -0
- package/client-dist/assets/index-uJnfvD1f.css +1 -0
- package/client-dist/assets/inter-latin-400-italic-D1l0FflY.woff2 +0 -0
- package/client-dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/client-dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/client-dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/client-dist/assets/ja-CrCGPmoH.js +6 -0
- package/client-dist/assets/zh-CN-AFJxv5ke.js +6 -0
- package/client-dist/index.html +3 -3
- package/client-dist/sw.js +96 -9
- package/dist/app.d.ts +25 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +350 -35
- package/dist/app.js.map +1 -1
- package/dist/augments/augment-generator.d.ts +1 -1
- package/dist/augments/augment-generator.d.ts.map +1 -1
- package/dist/augments/augment-generator.js +2 -2
- package/dist/augments/augment-generator.js.map +1 -1
- package/dist/augments/markdown-augments.d.ts +1 -1
- package/dist/augments/markdown-augments.d.ts.map +1 -1
- package/dist/augments/markdown-augments.js +5 -5
- package/dist/augments/markdown-augments.js.map +1 -1
- package/dist/augments/safe-markdown.d.ts +10 -0
- package/dist/augments/safe-markdown.d.ts.map +1 -1
- package/dist/augments/safe-markdown.js +146 -3
- package/dist/augments/safe-markdown.js.map +1 -1
- package/dist/augments/stream-augmenter.d.ts +3 -0
- package/dist/augments/stream-augmenter.d.ts.map +1 -1
- package/dist/augments/stream-augmenter.js +6 -3
- package/dist/augments/stream-augmenter.js.map +1 -1
- package/dist/augments/stream-coordinator.d.ts +5 -1
- package/dist/augments/stream-coordinator.d.ts.map +1 -1
- package/dist/augments/stream-coordinator.js +6 -4
- package/dist/augments/stream-coordinator.js.map +1 -1
- package/dist/augments/task-list-augments.d.ts +27 -0
- package/dist/augments/task-list-augments.d.ts.map +1 -0
- package/dist/augments/task-list-augments.js +360 -0
- package/dist/augments/task-list-augments.js.map +1 -0
- package/dist/cli.d.ts +1 -17
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -8
- package/dist/cli.js.map +1 -1
- package/dist/codex/correlationDebugLogger.d.ts.map +1 -1
- package/dist/codex/correlationDebugLogger.js +36 -8
- package/dist/codex/correlationDebugLogger.js.map +1 -1
- package/dist/codex/normalization.d.ts +1 -0
- package/dist/codex/normalization.d.ts.map +1 -1
- package/dist/codex/normalization.js +262 -7
- package/dist/codex/normalization.js.map +1 -1
- package/dist/config.d.ts +49 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +55 -14
- package/dist/config.js.map +1 -1
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +3 -0
- package/dist/defaults.js.map +1 -0
- package/dist/device/DeviceBridgeService.js +1 -1
- package/dist/device/DeviceBridgeService.js.map +1 -1
- package/dist/envSettings.d.ts +64 -0
- package/dist/envSettings.d.ts.map +1 -0
- package/dist/envSettings.js +553 -0
- package/dist/envSettings.js.map +1 -0
- package/dist/frontend/static.d.ts.map +1 -1
- package/dist/frontend/static.js +3 -1
- package/dist/frontend/static.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +190 -14
- package/dist/index.js.map +1 -1
- package/dist/indexes/SessionDiscoveryIndex.d.ts +78 -0
- package/dist/indexes/SessionDiscoveryIndex.d.ts.map +1 -0
- package/dist/indexes/SessionDiscoveryIndex.js +223 -0
- package/dist/indexes/SessionDiscoveryIndex.js.map +1 -0
- package/dist/indexes/SessionIndexService.d.ts +114 -1
- package/dist/indexes/SessionIndexService.d.ts.map +1 -1
- package/dist/indexes/SessionIndexService.js +875 -105
- package/dist/indexes/SessionIndexService.js.map +1 -1
- package/dist/indexes/index.d.ts +2 -1
- package/dist/indexes/index.d.ts.map +1 -1
- package/dist/indexes/index.js +1 -0
- package/dist/indexes/index.js.map +1 -1
- package/dist/indexes/types.d.ts +26 -0
- package/dist/indexes/types.d.ts.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +2 -3
- package/dist/logging/logger.js.map +1 -1
- package/dist/maintenance/server.d.ts.map +1 -1
- package/dist/maintenance/server.js +8 -11
- package/dist/maintenance/server.js.map +1 -1
- package/dist/metadata/ProjectMetadataService.d.ts +7 -0
- package/dist/metadata/ProjectMetadataService.d.ts.map +1 -1
- package/dist/metadata/ProjectMetadataService.js +63 -19
- package/dist/metadata/ProjectMetadataService.js.map +1 -1
- package/dist/metadata/SessionMetadataService.d.ts +82 -3
- package/dist/metadata/SessionMetadataService.d.ts.map +1 -1
- package/dist/metadata/SessionMetadataService.js +233 -17
- package/dist/metadata/SessionMetadataService.js.map +1 -1
- package/dist/middleware/file-access.d.ts +66 -0
- package/dist/middleware/file-access.d.ts.map +1 -0
- package/dist/middleware/file-access.js +112 -0
- package/dist/middleware/file-access.js.map +1 -0
- package/dist/projects/codex-scanner.d.ts +28 -1
- package/dist/projects/codex-scanner.d.ts.map +1 -1
- package/dist/projects/codex-scanner.js +149 -39
- package/dist/projects/codex-scanner.js.map +1 -1
- package/dist/projects/gemini-scanner.d.ts.map +1 -1
- package/dist/projects/gemini-scanner.js +59 -6
- package/dist/projects/gemini-scanner.js.map +1 -1
- package/dist/projects/paths.d.ts +15 -1
- package/dist/projects/paths.d.ts.map +1 -1
- package/dist/projects/paths.js +69 -23
- package/dist/projects/paths.js.map +1 -1
- package/dist/projects/scanner.d.ts +5 -0
- package/dist/projects/scanner.d.ts.map +1 -1
- package/dist/projects/scanner.js +136 -28
- package/dist/projects/scanner.js.map +1 -1
- package/dist/push/InactivityPushNotifier.d.ts +61 -0
- package/dist/push/InactivityPushNotifier.d.ts.map +1 -0
- package/dist/push/InactivityPushNotifier.js +305 -0
- package/dist/push/InactivityPushNotifier.js.map +1 -0
- package/dist/push/PushNotifier.d.ts.map +1 -1
- package/dist/push/PushNotifier.js.map +1 -1
- package/dist/push/PushService.d.ts +7 -4
- package/dist/push/PushService.d.ts.map +1 -1
- package/dist/push/PushService.js +27 -7
- package/dist/push/PushService.js.map +1 -1
- package/dist/push/index.d.ts +2 -1
- package/dist/push/index.d.ts.map +1 -1
- package/dist/push/index.js +1 -0
- package/dist/push/index.js.map +1 -1
- package/dist/push/routes.d.ts.map +1 -1
- package/dist/push/routes.js +28 -1
- package/dist/push/routes.js.map +1 -1
- package/dist/push/types.d.ts +21 -3
- package/dist/push/types.d.ts.map +1 -1
- package/dist/push/types.js +2 -0
- package/dist/push/types.js.map +1 -1
- package/dist/routes/dev.d.ts +2 -0
- package/dist/routes/dev.d.ts.map +1 -1
- package/dist/routes/dev.js +25 -0
- package/dist/routes/dev.js.map +1 -1
- package/dist/routes/env-settings.d.ts +20 -0
- package/dist/routes/env-settings.d.ts.map +1 -0
- package/dist/routes/env-settings.js +25 -0
- package/dist/routes/env-settings.js.map +1 -0
- package/dist/routes/files.d.ts +9 -0
- package/dist/routes/files.d.ts.map +1 -1
- package/dist/routes/files.js +98 -10
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/git-status.d.ts.map +1 -1
- package/dist/routes/git-status.js +621 -23
- package/dist/routes/git-status.js.map +1 -1
- package/dist/routes/global-sessions.d.ts +11 -1
- package/dist/routes/global-sessions.d.ts.map +1 -1
- package/dist/routes/global-sessions.js +53 -33
- package/dist/routes/global-sessions.js.map +1 -1
- package/dist/routes/inbox.d.ts +10 -1
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/inbox.js +30 -3
- package/dist/routes/inbox.js.map +1 -1
- package/dist/routes/local-file.d.ts +6 -2
- package/dist/routes/local-file.d.ts.map +1 -1
- package/dist/routes/local-file.js +13 -4
- package/dist/routes/local-file.js.map +1 -1
- package/dist/routes/local-image.d.ts +2 -1
- package/dist/routes/local-image.d.ts.map +1 -1
- package/dist/routes/local-image.js.map +1 -1
- package/dist/routes/local-resource-policy.d.ts +7 -1
- package/dist/routes/local-resource-policy.d.ts.map +1 -1
- package/dist/routes/local-resource-policy.js +0 -0
- package/dist/routes/local-resource-policy.js.map +1 -1
- package/dist/routes/processes.d.ts.map +1 -1
- package/dist/routes/processes.js +49 -11
- package/dist/routes/processes.js.map +1 -1
- package/dist/routes/project-queue-response.d.ts +26 -0
- package/dist/routes/project-queue-response.d.ts.map +1 -0
- package/dist/routes/project-queue-response.js +188 -0
- package/dist/routes/project-queue-response.js.map +1 -0
- package/dist/routes/project-queue.d.ts +5 -0
- package/dist/routes/project-queue.d.ts.map +1 -0
- package/dist/routes/project-queue.js +219 -0
- package/dist/routes/project-queue.js.map +1 -0
- package/dist/routes/projects.d.ts +6 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +116 -28
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/providers.d.ts +5 -0
- package/dist/routes/providers.d.ts.map +1 -1
- package/dist/routes/providers.js +52 -33
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/public-shares.d.ts.map +1 -1
- package/dist/routes/public-shares.js +46 -17
- package/dist/routes/public-shares.js.map +1 -1
- package/dist/routes/recents.d.ts +3 -0
- package/dist/routes/recents.d.ts.map +1 -1
- package/dist/routes/recents.js +3 -1
- package/dist/routes/recents.js.map +1 -1
- package/dist/routes/server-admin.d.ts +9 -0
- package/dist/routes/server-admin.d.ts.map +1 -1
- package/dist/routes/server-admin.js +45 -7
- package/dist/routes/server-admin.js.map +1 -1
- package/dist/routes/session-claude-resume-guard.d.ts +16 -0
- package/dist/routes/session-claude-resume-guard.d.ts.map +1 -0
- package/dist/routes/session-claude-resume-guard.js +50 -0
- package/dist/routes/session-claude-resume-guard.js.map +1 -0
- package/dist/routes/session-compact-thresholds.d.ts +22 -0
- package/dist/routes/session-compact-thresholds.d.ts.map +1 -0
- package/dist/routes/session-compact-thresholds.js +35 -0
- package/dist/routes/session-compact-thresholds.js.map +1 -0
- package/dist/routes/session-index.d.ts +7 -0
- package/dist/routes/session-index.d.ts.map +1 -0
- package/dist/routes/session-index.js +9 -0
- package/dist/routes/session-index.js.map +1 -0
- package/dist/routes/session-list-options.d.ts +1 -1
- package/dist/routes/session-list-options.d.ts.map +1 -1
- package/dist/routes/session-list-options.js +1 -1
- package/dist/routes/session-list-options.js.map +1 -1
- package/dist/routes/session-request-helpers.d.ts +31 -0
- package/dist/routes/session-request-helpers.d.ts.map +1 -0
- package/dist/routes/session-request-helpers.js +212 -0
- package/dist/routes/session-request-helpers.js.map +1 -0
- package/dist/routes/sessions.d.ts +14 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +2141 -551
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/settings.d.ts +7 -1
- package/dist/routes/settings.d.ts.map +1 -1
- package/dist/routes/settings.js +771 -25
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/speech.d.ts +24 -2
- package/dist/routes/speech.d.ts.map +1 -1
- package/dist/routes/speech.js +539 -10
- package/dist/routes/speech.js.map +1 -1
- package/dist/routes/supervisor-queue.d.ts +6 -0
- package/dist/routes/supervisor-queue.d.ts.map +1 -0
- package/dist/routes/supervisor-queue.js +35 -0
- package/dist/routes/supervisor-queue.js.map +1 -0
- package/dist/routes/upload.d.ts +2 -0
- package/dist/routes/upload.d.ts.map +1 -1
- package/dist/routes/upload.js +190 -38
- package/dist/routes/upload.js.map +1 -1
- package/dist/routes/version.d.ts +18 -1
- package/dist/routes/version.d.ts.map +1 -1
- package/dist/routes/version.js +22 -1
- package/dist/routes/version.js.map +1 -1
- package/dist/routes/workstreams.d.ts +11 -0
- package/dist/routes/workstreams.d.ts.map +1 -0
- package/dist/routes/workstreams.js +144 -0
- package/dist/routes/workstreams.js.map +1 -0
- package/dist/routes/ws-message-router.d.ts +10 -1
- package/dist/routes/ws-message-router.d.ts.map +1 -1
- package/dist/routes/ws-message-router.js +43 -4
- package/dist/routes/ws-message-router.js.map +1 -1
- package/dist/routes/ws-relay-handlers.d.ts +27 -5
- package/dist/routes/ws-relay-handlers.d.ts.map +1 -1
- package/dist/routes/ws-relay-handlers.js +177 -19
- package/dist/routes/ws-relay-handlers.js.map +1 -1
- package/dist/routes/ws-relay.d.ts +19 -0
- package/dist/routes/ws-relay.d.ts.map +1 -1
- package/dist/routes/ws-relay.js +21 -7
- package/dist/routes/ws-relay.js.map +1 -1
- package/dist/sdk/cli-detection.d.ts +1 -1
- package/dist/sdk/cli-detection.d.ts.map +1 -1
- package/dist/sdk/cli-detection.js +47 -7
- package/dist/sdk/cli-detection.js.map +1 -1
- package/dist/sdk/messageLogger.d.ts.map +1 -1
- package/dist/sdk/messageLogger.js +38 -10
- package/dist/sdk/messageLogger.js.map +1 -1
- package/dist/sdk/messageQueue.d.ts.map +1 -1
- package/dist/sdk/messageQueue.js +26 -0
- package/dist/sdk/messageQueue.js.map +1 -1
- package/dist/sdk/providers/__mocks__/grok.d.ts +1 -1
- package/dist/sdk/providers/__mocks__/grok.d.ts.map +1 -1
- package/dist/sdk/providers/__mocks__/grok.js +1 -1
- package/dist/sdk/providers/__mocks__/grok.js.map +1 -1
- package/dist/sdk/providers/agentctl-session-env.d.ts +8 -0
- package/dist/sdk/providers/agentctl-session-env.d.ts.map +1 -0
- package/dist/sdk/providers/agentctl-session-env.js +52 -0
- package/dist/sdk/providers/agentctl-session-env.js.map +1 -0
- package/dist/sdk/providers/claude-ollama.d.ts +2 -1
- package/dist/sdk/providers/claude-ollama.d.ts.map +1 -1
- package/dist/sdk/providers/claude-ollama.js +1 -0
- package/dist/sdk/providers/claude-ollama.js.map +1 -1
- package/dist/sdk/providers/claude-retention.d.ts +19 -0
- package/dist/sdk/providers/claude-retention.d.ts.map +1 -0
- package/dist/sdk/providers/claude-retention.js +169 -0
- package/dist/sdk/providers/claude-retention.js.map +1 -0
- package/dist/sdk/providers/claude.d.ts +52 -5
- package/dist/sdk/providers/claude.d.ts.map +1 -1
- package/dist/sdk/providers/claude.js +593 -71
- package/dist/sdk/providers/claude.js.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.d.ts +8 -0
- package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/AgentMessageInputContent.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.d.ts +10 -0
- package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/InternalChatMessageMetadataPassthrough.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.d.ts +23 -0
- package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/LegacyAppPathString.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.d.ts +8 -0
- package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/ReasoningEffort.d.ts +1 -1
- package/dist/sdk/providers/codex-protocol/generated/ReasoningEffort.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts +38 -1
- package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/AdditionalFileSystemPermissions.d.ts +3 -3
- package/dist/sdk/providers/codex-protocol/generated/v2/AdditionalFileSystemPermissions.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts +9 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/CommandExecutionRequestApprovalParams.d.ts +6 -2
- package/dist/sdk/providers/codex-protocol/generated/v2/CommandExecutionRequestApprovalParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.d.ts +8 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolFunctionSpec.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.d.ts +7 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceSpec.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.d.ts +5 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolNamespaceTool.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolSpec.d.ts +6 -7
- package/dist/sdk/providers/codex-protocol/generated/v2/DynamicToolSpec.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/FileSystemPath.d.ts +2 -2
- package/dist/sdk/providers/codex-protocol/generated/v2/FileSystemPath.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.d.ts +6 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/PermissionsRequestApprovalParams.d.ts +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/PermissionsRequestApprovalParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.d.ts +15 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SelectedCapabilityRoot.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.d.ts +2 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SortDirection.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.d.ts +2 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/SubAgentActivityKind.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/Thread.d.ts +8 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/Thread.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.d.ts +4 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartParams.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.d.ts +2 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadCompactStartResponse.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts +64 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.d.ts +46 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkResponse.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts +19 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.d.ts +5 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadReadResponse.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.d.ts +17 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeInitialTurnsPageParams.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeParams.d.ts +7 -7
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeResponse.d.ts +13 -2
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadResumeResponse.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.d.ts +11 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.d.ts +12 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackResponse.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadSource.d.ts +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadSource.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartParams.d.ts +13 -9
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartResponse.d.ts +8 -2
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartResponse.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/ToolRequestUserInputParams.d.ts +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ToolRequestUserInputParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnEnvironmentParams.d.ts +2 -2
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnEnvironmentParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnStartParams.d.ts +17 -5
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnStartParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnSteerParams.d.ts +8 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnSteerParams.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.d.ts +7 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/TurnsPage.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/index.d.ts +7 -0
- package/dist/sdk/providers/codex-protocol/index.d.ts.map +1 -1
- package/dist/sdk/providers/codex.d.ts +26 -4
- package/dist/sdk/providers/codex.d.ts.map +1 -1
- package/dist/sdk/providers/codex.js +457 -37
- package/dist/sdk/providers/codex.js.map +1 -1
- package/dist/sdk/providers/env-filter.d.ts.map +1 -1
- package/dist/sdk/providers/env-filter.js +19 -1
- package/dist/sdk/providers/env-filter.js.map +1 -1
- package/dist/sdk/providers/gemini-tools.d.ts +28 -0
- package/dist/sdk/providers/gemini-tools.d.ts.map +1 -0
- package/dist/sdk/providers/gemini-tools.js +57 -0
- package/dist/sdk/providers/gemini-tools.js.map +1 -0
- package/dist/sdk/providers/gemini.d.ts.map +1 -1
- package/dist/sdk/providers/gemini.js +4 -2
- package/dist/sdk/providers/gemini.js.map +1 -1
- package/dist/sdk/providers/grok-acp.d.ts +1 -1
- package/dist/sdk/providers/grok-acp.d.ts.map +1 -1
- package/dist/sdk/providers/grok-acp.js +1 -1
- package/dist/sdk/providers/grok-acp.js.map +1 -1
- package/dist/sdk/providers/index.d.ts +1 -0
- package/dist/sdk/providers/index.d.ts.map +1 -1
- package/dist/sdk/providers/index.js +6 -0
- package/dist/sdk/providers/index.js.map +1 -1
- package/dist/sdk/providers/opencode-tools.d.ts +36 -0
- package/dist/sdk/providers/opencode-tools.d.ts.map +1 -0
- package/dist/sdk/providers/opencode-tools.js +78 -0
- package/dist/sdk/providers/opencode-tools.js.map +1 -0
- package/dist/sdk/providers/opencode.d.ts +53 -5
- package/dist/sdk/providers/opencode.d.ts.map +1 -1
- package/dist/sdk/providers/opencode.js +379 -71
- package/dist/sdk/providers/opencode.js.map +1 -1
- package/dist/sdk/providers/pi-rpc-client.d.ts +90 -0
- package/dist/sdk/providers/pi-rpc-client.d.ts.map +1 -0
- package/dist/sdk/providers/pi-rpc-client.js +168 -0
- package/dist/sdk/providers/pi-rpc-client.js.map +1 -0
- package/dist/sdk/providers/pi-tools.d.ts +38 -0
- package/dist/sdk/providers/pi-tools.d.ts.map +1 -0
- package/dist/sdk/providers/pi-tools.js +214 -0
- package/dist/sdk/providers/pi-tools.js.map +1 -0
- package/dist/sdk/providers/pi.d.ts +84 -0
- package/dist/sdk/providers/pi.d.ts.map +1 -0
- package/dist/sdk/providers/pi.js +686 -0
- package/dist/sdk/providers/pi.js.map +1 -0
- package/dist/sdk/providers/staticSlashCommands.d.ts +4 -0
- package/dist/sdk/providers/staticSlashCommands.d.ts.map +1 -0
- package/dist/sdk/providers/staticSlashCommands.js +14 -0
- package/dist/sdk/providers/staticSlashCommands.js.map +1 -0
- package/dist/sdk/providers/types.d.ts +144 -17
- package/dist/sdk/providers/types.d.ts.map +1 -1
- package/dist/sdk/remote-spawn.d.ts.map +1 -1
- package/dist/sdk/remote-spawn.js.map +1 -1
- package/dist/sdk/slashCommandEmulation.d.ts +9 -0
- package/dist/sdk/slashCommandEmulation.d.ts.map +1 -1
- package/dist/sdk/slashCommandEmulation.js +15 -0
- package/dist/sdk/slashCommandEmulation.js.map +1 -1
- package/dist/sdk/types.d.ts +40 -0
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/security/approvalAuditLog.d.ts +3 -2
- package/dist/security/approvalAuditLog.d.ts.map +1 -1
- package/dist/security/approvalAuditLog.js +62 -2
- package/dist/security/approvalAuditLog.js.map +1 -1
- package/dist/services/BrowserProfileService.d.ts +21 -0
- package/dist/services/BrowserProfileService.d.ts.map +1 -1
- package/dist/services/BrowserProfileService.js +74 -1
- package/dist/services/BrowserProfileService.js.map +1 -1
- package/dist/services/CacheMissBillingMonitor.d.ts +25 -0
- package/dist/services/CacheMissBillingMonitor.d.ts.map +1 -0
- package/dist/services/CacheMissBillingMonitor.js +179 -0
- package/dist/services/CacheMissBillingMonitor.js.map +1 -0
- package/dist/services/CodexUpdateChecker.d.ts.map +1 -1
- package/dist/services/CodexUpdateChecker.js +3 -2
- package/dist/services/CodexUpdateChecker.js.map +1 -1
- package/dist/services/ModelInfoService.d.ts +58 -9
- package/dist/services/ModelInfoService.d.ts.map +1 -1
- package/dist/services/ModelInfoService.js +127 -14
- package/dist/services/ModelInfoService.js.map +1 -1
- package/dist/services/ProjectQueueScheduler.d.ts +91 -0
- package/dist/services/ProjectQueueScheduler.d.ts.map +1 -0
- package/dist/services/ProjectQueueScheduler.js +404 -0
- package/dist/services/ProjectQueueScheduler.js.map +1 -0
- package/dist/services/ProjectQueueService.d.ts +60 -0
- package/dist/services/ProjectQueueService.d.ts.map +1 -0
- package/dist/services/ProjectQueueService.js +804 -0
- package/dist/services/ProjectQueueService.js.map +1 -0
- package/dist/services/RelayClientService.d.ts +5 -0
- package/dist/services/RelayClientService.d.ts.map +1 -1
- package/dist/services/RelayClientService.js +26 -12
- package/dist/services/RelayClientService.js.map +1 -1
- package/dist/services/SafeRestartService.d.ts +48 -0
- package/dist/services/SafeRestartService.d.ts.map +1 -0
- package/dist/services/SafeRestartService.js +173 -0
- package/dist/services/SafeRestartService.js.map +1 -0
- package/dist/services/ServerSettingsService.d.ts +41 -1
- package/dist/services/ServerSettingsService.d.ts.map +1 -1
- package/dist/services/ServerSettingsService.js +110 -1
- package/dist/services/ServerSettingsService.js.map +1 -1
- package/dist/services/SessionQueuePersistenceService.d.ts +55 -0
- package/dist/services/SessionQueuePersistenceService.d.ts.map +1 -0
- package/dist/services/SessionQueuePersistenceService.js +264 -0
- package/dist/services/SessionQueuePersistenceService.js.map +1 -0
- package/dist/services/WorkstreamService.d.ts +92 -0
- package/dist/services/WorkstreamService.d.ts.map +1 -0
- package/dist/services/WorkstreamService.js +627 -0
- package/dist/services/WorkstreamService.js.map +1 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +4 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/projectWorkIdle.d.ts +42 -0
- package/dist/services/projectWorkIdle.d.ts.map +1 -0
- package/dist/services/projectWorkIdle.js +50 -0
- package/dist/services/projectWorkIdle.js.map +1 -0
- package/dist/services/voice/SpeechBackend.d.ts +18 -1
- package/dist/services/voice/SpeechBackend.d.ts.map +1 -1
- package/dist/services/voice/SpeechBackend.js +3 -0
- package/dist/services/voice/SpeechBackend.js.map +1 -1
- package/dist/services/voice/audioRetention.d.ts +20 -0
- package/dist/services/voice/audioRetention.d.ts.map +1 -1
- package/dist/services/voice/audioRetention.js +2 -0
- package/dist/services/voice/audioRetention.js.map +1 -1
- package/dist/services/voice/deepgramBackend.d.ts.map +1 -1
- package/dist/services/voice/deepgramBackend.js +4 -1
- package/dist/services/voice/deepgramBackend.js.map +1 -1
- package/dist/services/voice/localNemoBackend.d.ts +31 -0
- package/dist/services/voice/localNemoBackend.d.ts.map +1 -0
- package/dist/services/voice/localNemoBackend.js +181 -0
- package/dist/services/voice/localNemoBackend.js.map +1 -0
- package/dist/services/voice/localParakeetBackend.d.ts +31 -0
- package/dist/services/voice/localParakeetBackend.d.ts.map +1 -0
- package/dist/services/voice/localParakeetBackend.js +180 -0
- package/dist/services/voice/localParakeetBackend.js.map +1 -0
- package/dist/services/voice/localSttRuntime.d.ts +21 -0
- package/dist/services/voice/localSttRuntime.d.ts.map +1 -0
- package/dist/services/voice/localSttRuntime.js +87 -0
- package/dist/services/voice/localSttRuntime.js.map +1 -0
- package/dist/services/voice/localWhisperBackend.d.ts +2 -1
- package/dist/services/voice/localWhisperBackend.d.ts.map +1 -1
- package/dist/services/voice/localWhisperBackend.js +47 -34
- package/dist/services/voice/localWhisperBackend.js.map +1 -1
- package/dist/services/voice/nemo_worker.py +232 -0
- package/dist/services/voice/parakeet_worker.py +151 -0
- package/dist/services/voice/registry.d.ts +19 -4
- package/dist/services/voice/registry.d.ts.map +1 -1
- package/dist/services/voice/registry.js +72 -22
- package/dist/services/voice/registry.js.map +1 -1
- package/dist/services/voice/serialQueue.d.ts +17 -0
- package/dist/services/voice/serialQueue.d.ts.map +1 -0
- package/dist/services/voice/serialQueue.js +24 -0
- package/dist/services/voice/serialQueue.js.map +1 -0
- package/dist/services/voice/whisper_worker.py +94 -0
- package/dist/services/voice/xaiSttBackend.d.ts +1 -1
- package/dist/services/voice/xaiSttBackend.d.ts.map +1 -1
- package/dist/services/voice/xaiSttBackend.js +15 -3
- package/dist/services/voice/xaiSttBackend.js.map +1 -1
- package/dist/sessions/agent-excerpt.d.ts +29 -0
- package/dist/sessions/agent-excerpt.d.ts.map +1 -0
- package/dist/sessions/agent-excerpt.js +128 -0
- package/dist/sessions/agent-excerpt.js.map +1 -0
- package/dist/sessions/claude-messages.d.ts.map +1 -1
- package/dist/sessions/claude-messages.js +75 -4
- package/dist/sessions/claude-messages.js.map +1 -1
- package/dist/sessions/claude-summary.d.ts +33 -0
- package/dist/sessions/claude-summary.d.ts.map +1 -0
- package/dist/sessions/claude-summary.js +461 -0
- package/dist/sessions/claude-summary.js.map +1 -0
- package/dist/sessions/codex-discovery.d.ts +46 -0
- package/dist/sessions/codex-discovery.d.ts.map +1 -0
- package/dist/sessions/codex-discovery.js +269 -0
- package/dist/sessions/codex-discovery.js.map +1 -0
- package/dist/sessions/codex-reader.d.ts +96 -13
- package/dist/sessions/codex-reader.d.ts.map +1 -1
- package/dist/sessions/codex-reader.js +1069 -141
- package/dist/sessions/codex-reader.js.map +1 -1
- package/dist/sessions/dag.d.ts +25 -4
- package/dist/sessions/dag.d.ts.map +1 -1
- package/dist/sessions/dag.js +37 -4
- package/dist/sessions/dag.js.map +1 -1
- package/dist/sessions/gemini-reader.d.ts +3 -1
- package/dist/sessions/gemini-reader.d.ts.map +1 -1
- package/dist/sessions/gemini-reader.js +17 -8
- package/dist/sessions/gemini-reader.js.map +1 -1
- package/dist/sessions/grok-reader.d.ts +1 -0
- package/dist/sessions/grok-reader.d.ts.map +1 -1
- package/dist/sessions/grok-reader.js +11 -5
- package/dist/sessions/grok-reader.js.map +1 -1
- package/dist/sessions/normalization.d.ts +3 -1
- package/dist/sessions/normalization.d.ts.map +1 -1
- package/dist/sessions/normalization.js +72 -16
- package/dist/sessions/normalization.js.map +1 -1
- package/dist/sessions/null-reader.d.ts +31 -0
- package/dist/sessions/null-reader.d.ts.map +1 -0
- package/dist/sessions/null-reader.js +30 -0
- package/dist/sessions/null-reader.js.map +1 -0
- package/dist/sessions/opencode-db-reader.d.ts +66 -0
- package/dist/sessions/opencode-db-reader.d.ts.map +1 -0
- package/dist/sessions/opencode-db-reader.js +333 -0
- package/dist/sessions/opencode-db-reader.js.map +1 -0
- package/dist/sessions/opencode-reader.d.ts +50 -0
- package/dist/sessions/opencode-reader.d.ts.map +1 -1
- package/dist/sessions/opencode-reader.js +375 -71
- package/dist/sessions/opencode-reader.js.map +1 -1
- package/dist/sessions/pagination.d.ts.map +1 -1
- package/dist/sessions/pagination.js +119 -3
- package/dist/sessions/pagination.js.map +1 -1
- package/dist/sessions/persisted-augments.d.ts +2 -1
- package/dist/sessions/persisted-augments.d.ts.map +1 -1
- package/dist/sessions/persisted-augments.js +2 -2
- package/dist/sessions/persisted-augments.js.map +1 -1
- package/dist/sessions/pi-fork.d.ts +12 -0
- package/dist/sessions/pi-fork.d.ts.map +1 -0
- package/dist/sessions/pi-fork.js +125 -0
- package/dist/sessions/pi-fork.js.map +1 -0
- package/dist/sessions/pi-reader.d.ts +81 -0
- package/dist/sessions/pi-reader.d.ts.map +1 -0
- package/dist/sessions/pi-reader.js +413 -0
- package/dist/sessions/pi-reader.js.map +1 -0
- package/dist/sessions/provider-resolution.d.ts +10 -3
- package/dist/sessions/provider-resolution.d.ts.map +1 -1
- package/dist/sessions/provider-resolution.js +62 -6
- package/dist/sessions/provider-resolution.js.map +1 -1
- package/dist/sessions/reader.d.ts +17 -24
- package/dist/sessions/reader.d.ts.map +1 -1
- package/dist/sessions/reader.js +86 -157
- package/dist/sessions/reader.js.map +1 -1
- package/dist/sessions/recap-overlays.d.ts +38 -0
- package/dist/sessions/recap-overlays.d.ts.map +1 -0
- package/dist/sessions/recap-overlays.js +176 -0
- package/dist/sessions/recap-overlays.js.map +1 -0
- package/dist/sessions/summary-parser-worker-client.d.ts +80 -0
- package/dist/sessions/summary-parser-worker-client.d.ts.map +1 -0
- package/dist/sessions/summary-parser-worker-client.js +539 -0
- package/dist/sessions/summary-parser-worker-client.js.map +1 -0
- package/dist/sessions/summary-parser-worker-entry.d.ts +2 -0
- package/dist/sessions/summary-parser-worker-entry.d.ts.map +1 -0
- package/dist/sessions/summary-parser-worker-entry.js +29 -0
- package/dist/sessions/summary-parser-worker-entry.js.map +1 -0
- package/dist/sessions/summary-parser-worker-protocol.d.ts +105 -0
- package/dist/sessions/summary-parser-worker-protocol.d.ts.map +1 -0
- package/dist/sessions/summary-parser-worker-protocol.js +35 -0
- package/dist/sessions/summary-parser-worker-protocol.js.map +1 -0
- package/dist/sessions/summary-parser-worker-runner.d.ts +3 -0
- package/dist/sessions/summary-parser-worker-runner.d.ts.map +1 -0
- package/dist/sessions/summary-parser-worker-runner.js +158 -0
- package/dist/sessions/summary-parser-worker-runner.js.map +1 -0
- package/dist/sessions/types.d.ts +30 -1
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/startupEnv.d.ts +2 -0
- package/dist/startupEnv.d.ts.map +1 -0
- package/dist/startupEnv.js +97 -0
- package/dist/startupEnv.js.map +1 -0
- package/dist/subscriptions.d.ts.map +1 -1
- package/dist/subscriptions.js +28 -18
- package/dist/subscriptions.js.map +1 -1
- package/dist/supervisor/ExternalSessionTracker.d.ts +5 -1
- package/dist/supervisor/ExternalSessionTracker.d.ts.map +1 -1
- package/dist/supervisor/ExternalSessionTracker.js +98 -6
- package/dist/supervisor/ExternalSessionTracker.js.map +1 -1
- package/dist/supervisor/Process.d.ts +252 -32
- package/dist/supervisor/Process.d.ts.map +1 -1
- package/dist/supervisor/Process.js +1233 -219
- package/dist/supervisor/Process.js.map +1 -1
- package/dist/supervisor/Supervisor.d.ts +201 -5
- package/dist/supervisor/Supervisor.d.ts.map +1 -1
- package/dist/supervisor/Supervisor.js +1074 -55
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/dist/supervisor/WorkerQueue.d.ts +3 -1
- package/dist/supervisor/WorkerQueue.d.ts.map +1 -1
- package/dist/supervisor/WorkerQueue.js +1 -0
- package/dist/supervisor/WorkerQueue.js.map +1 -1
- package/dist/supervisor/composeTimeAnchor.d.ts +36 -0
- package/dist/supervisor/composeTimeAnchor.d.ts.map +1 -0
- package/dist/supervisor/composeTimeAnchor.js +51 -0
- package/dist/supervisor/composeTimeAnchor.js.map +1 -0
- package/dist/supervisor/deferredDeliverySettings.d.ts +23 -0
- package/dist/supervisor/deferredDeliverySettings.d.ts.map +1 -0
- package/dist/supervisor/deferredDeliverySettings.js +40 -0
- package/dist/supervisor/deferredDeliverySettings.js.map +1 -0
- package/dist/supervisor/liveness.d.ts +5 -2
- package/dist/supervisor/liveness.d.ts.map +1 -1
- package/dist/supervisor/liveness.js +40 -2
- package/dist/supervisor/liveness.js.map +1 -1
- package/dist/supervisor/types.d.ts +56 -5
- package/dist/supervisor/types.d.ts.map +1 -1
- package/dist/supervisor/types.js +0 -2
- package/dist/supervisor/types.js.map +1 -1
- package/dist/uploads/AttachmentStagingService.d.ts +94 -0
- package/dist/uploads/AttachmentStagingService.d.ts.map +1 -0
- package/dist/uploads/AttachmentStagingService.js +673 -0
- package/dist/uploads/AttachmentStagingService.js.map +1 -0
- package/dist/uploads/index.d.ts +2 -0
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +1 -0
- package/dist/uploads/index.js.map +1 -1
- package/dist/uploads/manager.d.ts +1 -0
- package/dist/uploads/manager.d.ts.map +1 -1
- package/dist/uploads/manager.js +72 -12
- package/dist/uploads/manager.js.map +1 -1
- package/dist/utils/codexRolloutFiles.d.ts +16 -0
- package/dist/utils/codexRolloutFiles.d.ts.map +1 -0
- package/dist/utils/codexRolloutFiles.js +45 -0
- package/dist/utils/codexRolloutFiles.js.map +1 -0
- package/dist/utils/jsonl.d.ts +8 -0
- package/dist/utils/jsonl.d.ts.map +1 -1
- package/dist/utils/jsonl.js +158 -5
- package/dist/utils/jsonl.js.map +1 -1
- package/dist/utils/publicShareViewerUrl.d.ts.map +1 -1
- package/dist/utils/publicShareViewerUrl.js +2 -15
- package/dist/utils/publicShareViewerUrl.js.map +1 -1
- package/dist/watcher/EventBus.d.ts +42 -4
- package/dist/watcher/EventBus.d.ts.map +1 -1
- package/dist/watcher/EventBus.js.map +1 -1
- package/dist/watcher/FileWatcher.d.ts +47 -0
- package/dist/watcher/FileWatcher.d.ts.map +1 -1
- package/dist/watcher/FileWatcher.js +185 -14
- package/dist/watcher/FileWatcher.js.map +1 -1
- package/dist/watcher/index.d.ts +2 -2
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js.map +1 -1
- package/dist/yaModuleEnv.d.ts +6 -6
- package/dist/yaModuleEnv.d.ts.map +1 -1
- package/dist/yaModuleEnv.js +12 -12
- package/dist/yaModuleEnv.js.map +1 -1
- package/package.json +5 -6
- package/client-dist/assets/de-BuEb60Yc.js +0 -6
- package/client-dist/assets/es-CRlmPKnE.js +0 -6
- package/client-dist/assets/fr-Cglgjc2X.js +0 -6
- package/client-dist/assets/index-BNj7ibmq.js +0 -524
- package/client-dist/assets/index-CAmsUazi.css +0 -1
- package/client-dist/assets/ja-DMb9mFPv.js +0 -6
- package/client-dist/assets/zh-CN-D1tbfamz.js +0 -6
package/dist/routes/sessions.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ALL_PERMISSION_MODES, PROMPT_SUGGESTION_MODES, buildEffectiveAgentContext, clampRecapAfterSeconds, getModelContextWindow, isUrlProjectId, isWorkstreamId, mainWorkstreamId, thinkingOptionToConfig, truncateSessionTitle, } from "../../bundled/@yep-anywhere/shared/dist/index.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { mkdir } from "node:fs/promises";
|
|
3
4
|
import { performance } from "node:perf_hooks";
|
|
4
5
|
import { Hono } from "hono";
|
|
@@ -6,21 +7,36 @@ import { augmentTextBlocks } from "../augments/markdown-augments.js";
|
|
|
6
7
|
import { getLogger } from "../logging/logger.js";
|
|
7
8
|
import { DETACHED_PROJECT_PATH, encodeProjectId } from "../projects/paths.js";
|
|
8
9
|
import { ensureRemoteDirectory } from "../sdk/remote-spawn.js";
|
|
10
|
+
import { parseSlashCommandSubmission } from "../sdk/slashCommandEmulation.js";
|
|
9
11
|
import { getProjectDirFromCwd, syncSessions } from "../sdk/session-sync.js";
|
|
10
12
|
import { appendApprovalAuditLog } from "../security/approvalAuditLog.js";
|
|
11
13
|
import { CodexSessionReader } from "../sessions/codex-reader.js";
|
|
12
14
|
import { cloneClaudeSession, cloneCodexSession } from "../sessions/fork.js";
|
|
13
|
-
import { GeminiSessionReader } from "../sessions/gemini-reader.js";
|
|
14
|
-
import { buildDag } from "../sessions/dag.js";
|
|
15
15
|
import { GrokSessionReader } from "../sessions/grok-reader.js";
|
|
16
|
+
import { extractLastAgentExcerpt } from "../sessions/agent-excerpt.js";
|
|
16
17
|
import { normalizeSession } from "../sessions/normalization.js";
|
|
18
|
+
import { applyRecapOverlayToSession, applyRecapOverlayToSummary, hasEquivalentRecapMessage, hasUnreadProviderContent, latestRecapMessage, mergeRecapMessages, } from "../sessions/recap-overlays.js";
|
|
17
19
|
import { sliceAfterMessageIdWithMatch, sliceAtCompactBoundaries, sliceAtUserTurnBoundary, } from "../sessions/pagination.js";
|
|
20
|
+
import { augmentTaskListSnapshots, pruneTaskListSnapshotsToLatest, } from "../augments/task-list-augments.js";
|
|
18
21
|
import { augmentEditToolUses, augmentPersistedSessionMessages, } from "../sessions/persisted-augments.js";
|
|
19
|
-
import { findSessionSummaryAcrossProviders } from "../sessions/provider-resolution.js";
|
|
20
|
-
import {
|
|
22
|
+
import { findSessionSummaryAcrossProviders, getSessionSources, } from "../sessions/provider-resolution.js";
|
|
23
|
+
import { getProvider } from "../sdk/providers/index.js";
|
|
24
|
+
import { getStaticSlashCommandsForProvider } from "../sdk/providers/staticSlashCommands.js";
|
|
25
|
+
import { ResumeCompactionError } from "../supervisor/Supervisor.js";
|
|
26
|
+
import { buildUserMessageMetadata, normalizeOptionalServiceTier, parseHelperSettings, parseOptionalExecutor, parseOptionalResumeMode, } from "./session-request-helpers.js";
|
|
27
|
+
import { resolveCompactPercent, resolveCompactWindow, } from "./session-compact-thresholds.js";
|
|
28
|
+
import { getClaudeResumeBlockerFromReader, } from "./session-claude-resume-guard.js";
|
|
21
29
|
const SESSION_DETAIL_SLOW_LOG_MS = 250;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
30
|
+
const DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS = 2;
|
|
31
|
+
const LARGE_FULL_HISTORY_MESSAGE_THRESHOLD = 1000;
|
|
32
|
+
async function getSessionSlashCommands(process, provider) {
|
|
33
|
+
if (process?.supportsDynamicCommands) {
|
|
34
|
+
const commands = await process.supportedCommands();
|
|
35
|
+
if (commands)
|
|
36
|
+
return commands;
|
|
37
|
+
}
|
|
38
|
+
return getStaticSlashCommandsForProvider(provider);
|
|
39
|
+
}
|
|
24
40
|
function roundedMs(value) {
|
|
25
41
|
return Math.round(value * 10) / 10;
|
|
26
42
|
}
|
|
@@ -39,242 +55,116 @@ function isQueuedResponse(result) {
|
|
|
39
55
|
function isQueueFullResponse(result) {
|
|
40
56
|
return "error" in result && result.error === "queue_full";
|
|
41
57
|
}
|
|
42
|
-
function getClaudeResumeApiErrorBlocker(messages) {
|
|
43
|
-
const { activeBranch } = buildDag(messages);
|
|
44
|
-
for (let i = activeBranch.length - 1; i >= 0; i--) {
|
|
45
|
-
const raw = activeBranch[i]?.raw;
|
|
46
|
-
if (raw?.type !== "assistant") {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (raw.isApiErrorMessage !== true) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const apiError = raw;
|
|
53
|
-
return {
|
|
54
|
-
error: CLAUDE_RESUME_API_ERROR_MESSAGE,
|
|
55
|
-
recovery: CLAUDE_RESUME_API_ERROR_RECOVERY,
|
|
56
|
-
messageId: raw.message.id,
|
|
57
|
-
apiErrorStatus: apiError.apiErrorStatus,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
async function getClaudeResumeBlockerFromReader(reader, sessionId, projectId) {
|
|
63
|
-
const session = await reader.getSession(sessionId, projectId);
|
|
64
|
-
if (!session) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
if (session.data.provider !== "claude" &&
|
|
68
|
-
session.data.provider !== "claude-ollama") {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
return getClaudeResumeApiErrorBlocker(session.data.session.messages);
|
|
72
|
-
}
|
|
73
|
-
function parseOptionalExecutor(rawExecutor) {
|
|
74
|
-
if (rawExecutor === undefined || rawExecutor === null) {
|
|
75
|
-
return { executor: undefined };
|
|
76
|
-
}
|
|
77
|
-
if (typeof rawExecutor !== "string") {
|
|
78
|
-
return { executor: undefined, error: "executor must be a string" };
|
|
79
|
-
}
|
|
80
|
-
const executor = normalizeSshHostAlias(rawExecutor);
|
|
81
|
-
if (!executor) {
|
|
82
|
-
return { executor: undefined };
|
|
83
|
-
}
|
|
84
|
-
if (!isValidSshHostAlias(executor)) {
|
|
85
|
-
return {
|
|
86
|
-
executor: undefined,
|
|
87
|
-
error: "executor must be a valid SSH host alias",
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
return { executor };
|
|
91
|
-
}
|
|
92
|
-
function normalizeOptionalServiceTier(rawServiceTier) {
|
|
93
|
-
if (typeof rawServiceTier !== "string") {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
const serviceTier = rawServiceTier.trim();
|
|
97
|
-
return /^[A-Za-z0-9_-]{1,64}$/.test(serviceTier)
|
|
98
|
-
? serviceTier
|
|
99
|
-
: undefined;
|
|
100
|
-
}
|
|
101
|
-
function parseOptionalRecapMode(rawMode) {
|
|
102
|
-
if (rawMode === undefined || rawMode === null || rawMode === "") {
|
|
103
|
-
return { recapMode: undefined };
|
|
104
|
-
}
|
|
105
|
-
if (typeof rawMode !== "string" ||
|
|
106
|
-
!RECAP_MODES.includes(rawMode)) {
|
|
107
|
-
return {
|
|
108
|
-
recapMode: undefined,
|
|
109
|
-
error: "recapMode must be one of: off, native, side-session",
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
return { recapMode: rawMode };
|
|
113
|
-
}
|
|
114
|
-
function parseOptionalPromptSuggestionMode(rawMode) {
|
|
115
|
-
if (rawMode === undefined || rawMode === null || rawMode === "") {
|
|
116
|
-
return { promptSuggestionMode: undefined };
|
|
117
|
-
}
|
|
118
|
-
if (typeof rawMode !== "string" ||
|
|
119
|
-
!PROMPT_SUGGESTION_MODES.includes(rawMode)) {
|
|
120
|
-
return {
|
|
121
|
-
promptSuggestionMode: undefined,
|
|
122
|
-
error: "promptSuggestionMode must be one of: off, native",
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
return { promptSuggestionMode: rawMode };
|
|
126
|
-
}
|
|
127
|
-
function parseOptionalHelperSideModel(rawModel) {
|
|
128
|
-
if (rawModel === undefined || rawModel === null || rawModel === "") {
|
|
129
|
-
return { helperSideModel: undefined };
|
|
130
|
-
}
|
|
131
|
-
if (typeof rawModel !== "string") {
|
|
132
|
-
return { helperSideModel: undefined, error: "helperSideModel must be a string" };
|
|
133
|
-
}
|
|
134
|
-
const trimmed = rawModel.trim();
|
|
135
|
-
if (!trimmed) {
|
|
136
|
-
return { helperSideModel: undefined };
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
helperSideModel: trimmed === HELPER_SIDE_MODEL_SAME_AS_MAIN
|
|
140
|
-
? HELPER_SIDE_MODEL_SAME_AS_MAIN
|
|
141
|
-
: trimmed === HELPER_SIDE_MODEL_CHEAPEST
|
|
142
|
-
? HELPER_SIDE_MODEL_CHEAPEST
|
|
143
|
-
: trimmed.slice(0, 200),
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
function parseHelperSettings(body) {
|
|
147
|
-
const recap = parseOptionalRecapMode(body.recapMode);
|
|
148
|
-
if (recap.error) {
|
|
149
|
-
return {
|
|
150
|
-
recapMode: undefined,
|
|
151
|
-
promptSuggestionMode: undefined,
|
|
152
|
-
helperSideModel: undefined,
|
|
153
|
-
error: recap.error,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
const promptSuggestion = parseOptionalPromptSuggestionMode(body.promptSuggestionMode);
|
|
157
|
-
if (promptSuggestion.error) {
|
|
158
|
-
return {
|
|
159
|
-
recapMode: undefined,
|
|
160
|
-
promptSuggestionMode: undefined,
|
|
161
|
-
helperSideModel: undefined,
|
|
162
|
-
error: promptSuggestion.error,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const helperModel = parseOptionalHelperSideModel(body.helperSideModel);
|
|
166
|
-
if (helperModel.error) {
|
|
167
|
-
return {
|
|
168
|
-
recapMode: undefined,
|
|
169
|
-
promptSuggestionMode: undefined,
|
|
170
|
-
helperSideModel: undefined,
|
|
171
|
-
error: helperModel.error,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
recapMode: recap.recapMode,
|
|
176
|
-
promptSuggestionMode: promptSuggestion.promptSuggestionMode,
|
|
177
|
-
helperSideModel: helperModel.helperSideModel,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
58
|
function isCodexProviderName(provider) {
|
|
181
59
|
return provider === "codex" || provider === "codex-oss";
|
|
182
60
|
}
|
|
183
|
-
function parseDeferredPlacement(body) {
|
|
184
|
-
const beforeTempId = typeof body.insertBeforeTempId === "string" &&
|
|
185
|
-
body.insertBeforeTempId.trim()
|
|
186
|
-
? body.insertBeforeTempId.trim()
|
|
187
|
-
: undefined;
|
|
188
|
-
const afterTempId = typeof body.insertAfterTempId === "string" && body.insertAfterTempId.trim()
|
|
189
|
-
? body.insertAfterTempId.trim()
|
|
190
|
-
: undefined;
|
|
191
|
-
const replaceTempId = typeof body.replaceDeferredTempId === "string" &&
|
|
192
|
-
body.replaceDeferredTempId.trim()
|
|
193
|
-
? body.replaceDeferredTempId.trim()
|
|
194
|
-
: undefined;
|
|
195
|
-
if (!beforeTempId && !afterTempId && !replaceTempId) {
|
|
196
|
-
return undefined;
|
|
197
|
-
}
|
|
198
|
-
return {
|
|
199
|
-
...(afterTempId ? { afterTempId } : {}),
|
|
200
|
-
...(beforeTempId ? { beforeTempId } : {}),
|
|
201
|
-
...(replaceTempId ? { replaceTempId } : {}),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
const USER_MESSAGE_DELIVERY_INTENTS = new Set(["direct", "steer", "deferred", "patient"]);
|
|
205
61
|
const AUTO_COMPACT_CONTEXT_PERCENT_THRESHOLD = 85;
|
|
206
62
|
const AUTO_COMPACT_MODEL_PREFIXES = ["gpt-5.3-codex-spark"];
|
|
207
63
|
const AUTO_COMPACT_TARGET_PROVIDERS = new Set([
|
|
208
64
|
"codex",
|
|
209
65
|
"codex-oss",
|
|
210
66
|
]);
|
|
211
|
-
function
|
|
212
|
-
return
|
|
67
|
+
function providerResolutionDeps(deps) {
|
|
68
|
+
return {
|
|
69
|
+
readerFactory: deps.readerFactory,
|
|
70
|
+
sessionIndexService: deps.sessionIndexService,
|
|
71
|
+
codexSessionsDir: deps.codexSessionsDir,
|
|
72
|
+
codexReaderFactory: deps.codexReaderFactory,
|
|
73
|
+
geminiSessionsDir: deps.geminiSessionsDir,
|
|
74
|
+
geminiReaderFactory: deps.geminiReaderFactory,
|
|
75
|
+
geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
|
|
76
|
+
grokSessionsDir: deps.grokSessionsDir,
|
|
77
|
+
grokReaderFactory: deps.grokReaderFactory,
|
|
78
|
+
piSessionsDir: deps.piSessionsDir,
|
|
79
|
+
piReaderFactory: deps.piReaderFactory,
|
|
80
|
+
};
|
|
213
81
|
}
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
82
|
+
function persistedPatientQueueSummary(item) {
|
|
83
|
+
const attachmentCount = (item.message.attachments?.length ?? 0) +
|
|
84
|
+
(item.message.images?.length ?? 0) +
|
|
85
|
+
(item.message.documents?.length ?? 0);
|
|
86
|
+
const tempId = item.message.tempId ?? item.source?.tempId;
|
|
87
|
+
return {
|
|
88
|
+
id: item.id,
|
|
89
|
+
...(tempId ? { tempId } : {}),
|
|
90
|
+
content: item.message.text,
|
|
91
|
+
timestamp: item.queuedAt,
|
|
92
|
+
...(item.message.attachments?.length
|
|
93
|
+
? { attachments: item.message.attachments }
|
|
94
|
+
: {}),
|
|
95
|
+
...(attachmentCount > 0 ? { attachmentCount } : {}),
|
|
96
|
+
...(item.message.metadata ? { metadata: item.message.metadata } : {}),
|
|
97
|
+
kind: "patient",
|
|
98
|
+
status: item.status === "paused-after-restart" ? item.status : "queued",
|
|
99
|
+
sessionId: item.sessionId,
|
|
100
|
+
projectId: item.projectId,
|
|
101
|
+
queuedAt: item.queuedAt,
|
|
102
|
+
createdAt: item.createdAt,
|
|
103
|
+
updatedAt: item.updatedAt,
|
|
104
|
+
};
|
|
218
105
|
}
|
|
219
|
-
function
|
|
220
|
-
return
|
|
221
|
-
USER_MESSAGE_DELIVERY_INTENTS.has(value)
|
|
222
|
-
? value
|
|
223
|
-
: undefined;
|
|
106
|
+
function isApprovalAuditLogEnabled(deps) {
|
|
107
|
+
return (deps.serverSettingsService?.getSetting("approvalAuditLogEnabled") === true);
|
|
224
108
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
return undefined;
|
|
228
|
-
const trimmed = value.trim();
|
|
229
|
-
return trimmed ? trimmed.slice(0, maxLength) : undefined;
|
|
109
|
+
function recoveredPatientQueueSummaries(deps, sessionId) {
|
|
110
|
+
return recoveredPatientQueueItems(deps, sessionId).map(persistedPatientQueueSummary);
|
|
230
111
|
}
|
|
231
|
-
function
|
|
232
|
-
if (!
|
|
233
|
-
return
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
.
|
|
237
|
-
.
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
function
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
112
|
+
function recoveredPatientQueueItems(deps, sessionId) {
|
|
113
|
+
if (!deps.sessionQueuePersistenceService) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return deps.sessionQueuePersistenceService
|
|
117
|
+
.listSession(sessionId)
|
|
118
|
+
.filter((item) => item.kind === "patient" && item.status === "paused-after-restart")
|
|
119
|
+
.sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
|
|
120
|
+
}
|
|
121
|
+
function sessionQueueSummaries(deps, sessionId, process) {
|
|
122
|
+
const recovered = recoveredPatientQueueSummaries(deps, sessionId);
|
|
123
|
+
if (process && typeof process.getDeferredQueueSummary === "function") {
|
|
124
|
+
const messages = [
|
|
125
|
+
...process.getDeferredQueueSummary(),
|
|
126
|
+
...recovered,
|
|
127
|
+
];
|
|
128
|
+
return messages.sort((left, right) => (left.queuedAt ?? left.timestamp).localeCompare(right.queuedAt ?? right.timestamp));
|
|
129
|
+
}
|
|
130
|
+
if (process) {
|
|
131
|
+
return recovered;
|
|
132
|
+
}
|
|
133
|
+
return recovered;
|
|
134
|
+
}
|
|
135
|
+
function recoveredPatientUserMessage(item) {
|
|
136
|
+
const mode = item.message.mode ?? item.mode;
|
|
137
|
+
const metadata = {
|
|
138
|
+
...item.message.metadata,
|
|
139
|
+
deliveryIntent: "patient",
|
|
250
140
|
};
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
: undefined;
|
|
255
|
-
const rawSpeech = isRecord(rawMetadata.speech) ? rawMetadata.speech : {};
|
|
256
|
-
const speechClientTurnId = parseShortString(rawSpeech.clientTurnId, 120);
|
|
257
|
-
const speechTranscriptionIds = parseStringList(rawSpeech.transcriptionIds, 20, 120);
|
|
258
|
-
const speech = speechClientTurnId || speechTranscriptionIds
|
|
259
|
-
? {
|
|
260
|
-
...(speechClientTurnId
|
|
261
|
-
? { clientTurnId: speechClientTurnId }
|
|
262
|
-
: {}),
|
|
263
|
-
...(speechTranscriptionIds
|
|
264
|
-
? { transcriptionIds: speechTranscriptionIds }
|
|
265
|
-
: {}),
|
|
266
|
-
}
|
|
267
|
-
: undefined;
|
|
141
|
+
// Live-queue chip actions (cancel, steer) address entries by tempId, so a
|
|
142
|
+
// recovered entry that was persisted without one gets a stable fallback.
|
|
143
|
+
const tempId = item.message.tempId ?? item.source?.tempId ?? `recovered-${item.id}`;
|
|
268
144
|
return {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
...(speech ? { speech } : {}),
|
|
274
|
-
...(clientTimestamp !== undefined ? { clientTimestamp } : {}),
|
|
275
|
-
serverReceivedAt: new Date(serverTimestamp).toISOString(),
|
|
145
|
+
...item.message,
|
|
146
|
+
tempId,
|
|
147
|
+
...(mode ? { mode } : {}),
|
|
148
|
+
metadata,
|
|
276
149
|
};
|
|
277
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Count live patient queue entries composed after the given recovered entry.
|
|
153
|
+
* Resuming or steering the recovered entry would deliver its older content
|
|
154
|
+
* behind these newer ones, so callers reject while any exist. Regular
|
|
155
|
+
* deferred entries never block recovered work: the regular lane delivers on
|
|
156
|
+
* turn boundaries and may pass patient work by design.
|
|
157
|
+
*/
|
|
158
|
+
function livePatientEntriesNewerThan(process, queuedAt) {
|
|
159
|
+
const live = process?.getDeferredQueueSummary() ?? [];
|
|
160
|
+
return live.filter((entry) => entry.metadata?.deliveryIntent === "patient" &&
|
|
161
|
+
entry.timestamp.localeCompare(queuedAt) > 0).length;
|
|
162
|
+
}
|
|
163
|
+
async function resolveSessionReaderForAgentContent({ deps, project, sessionId, projectId, }) {
|
|
164
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
165
|
+
const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
|
|
166
|
+
return resolved?.source.reader ?? deps.readerFactory(project);
|
|
167
|
+
}
|
|
278
168
|
const RESTART_HANDOFF_MAX_CHARS = 40_000;
|
|
279
169
|
const RESTART_HANDOFF_JSON_MAX_CHARS = 2_000;
|
|
280
170
|
const RESTART_HANDOFF_COMPACT_MAX_CHARS = 10_000;
|
|
@@ -336,7 +226,9 @@ function renderRestartContent(content) {
|
|
|
336
226
|
case "text":
|
|
337
227
|
return typed.text ?? "";
|
|
338
228
|
case "thinking":
|
|
339
|
-
return typed.thinking
|
|
229
|
+
return typed.thinking
|
|
230
|
+
? `[thinking]\n${typed.thinking}`
|
|
231
|
+
: "[thinking]";
|
|
340
232
|
case "tool_use":
|
|
341
233
|
return `[tool_use ${typed.name ?? "unknown"}]\n${stringifyForRestart(typed.input, RESTART_HANDOFF_JSON_MAX_CHARS)}`;
|
|
342
234
|
case "tool_result":
|
|
@@ -372,6 +264,33 @@ function messageContent(message) {
|
|
|
372
264
|
const nested = message.message;
|
|
373
265
|
return nested?.content ?? message.content;
|
|
374
266
|
}
|
|
267
|
+
function messageId(message) {
|
|
268
|
+
if (!message) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
return ((typeof message.uuid === "string" && message.uuid) ||
|
|
272
|
+
(typeof message.id === "string" && message.id) ||
|
|
273
|
+
undefined);
|
|
274
|
+
}
|
|
275
|
+
function findDurableRecapCursor(afterMessageId, recaps) {
|
|
276
|
+
if (!afterMessageId) {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
return recaps.find((recap) => recap.uuid === afterMessageId || recap.id === afterMessageId);
|
|
280
|
+
}
|
|
281
|
+
function sliceAfterDurableRecapCursor(params) {
|
|
282
|
+
const index = params.messages.findIndex((message) => {
|
|
283
|
+
const id = messageId(message);
|
|
284
|
+
return (id === params.cursorId ||
|
|
285
|
+
id === params.recap.uuid ||
|
|
286
|
+
id === params.recap.id ||
|
|
287
|
+
hasEquivalentRecapMessage([message], params.recap));
|
|
288
|
+
});
|
|
289
|
+
if (index < 0) {
|
|
290
|
+
return { messages: params.messages, found: false };
|
|
291
|
+
}
|
|
292
|
+
return { messages: params.messages.slice(index + 1), found: true };
|
|
293
|
+
}
|
|
375
294
|
function messageHasToolResult(message) {
|
|
376
295
|
if (message.toolUseResult !== undefined) {
|
|
377
296
|
return true;
|
|
@@ -382,6 +301,32 @@ function messageHasToolResult(message) {
|
|
|
382
301
|
typeof block === "object" &&
|
|
383
302
|
block.type === "tool_result"));
|
|
384
303
|
}
|
|
304
|
+
function messageTextContent(message) {
|
|
305
|
+
const content = messageContent(message);
|
|
306
|
+
if (typeof content === "string") {
|
|
307
|
+
return content;
|
|
308
|
+
}
|
|
309
|
+
if (!Array.isArray(content)) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
const textBlocks = content
|
|
313
|
+
.map((block) => block &&
|
|
314
|
+
typeof block === "object" &&
|
|
315
|
+
block.type === "text" &&
|
|
316
|
+
typeof block.text === "string"
|
|
317
|
+
? block.text
|
|
318
|
+
: "")
|
|
319
|
+
.filter(Boolean);
|
|
320
|
+
return textBlocks.length > 0 ? textBlocks.join("\n") : undefined;
|
|
321
|
+
}
|
|
322
|
+
function isSlashCommandSkillBodyUserMessage(message) {
|
|
323
|
+
if (message.isMeta !== true) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
return (messageTextContent(message)
|
|
327
|
+
?.trimStart()
|
|
328
|
+
.startsWith("Base directory for this skill:") === true);
|
|
329
|
+
}
|
|
385
330
|
function isRestartInternalCompactCommand(message) {
|
|
386
331
|
if (messageRole(message) !== "user" || messageHasToolResult(message)) {
|
|
387
332
|
return false;
|
|
@@ -751,8 +696,111 @@ function isHumanUserMessage(message) {
|
|
|
751
696
|
message.type;
|
|
752
697
|
return role === "user" && !messageHasToolResult(message);
|
|
753
698
|
}
|
|
699
|
+
function isCompactSummaryUserMessage(message) {
|
|
700
|
+
return message.isCompactSummary === true;
|
|
701
|
+
}
|
|
702
|
+
function isUserAuthoredRequest(message) {
|
|
703
|
+
return (isHumanUserMessage(message) &&
|
|
704
|
+
!isCompactSummaryUserMessage(message) &&
|
|
705
|
+
!isSlashCommandSkillBodyUserMessage(message));
|
|
706
|
+
}
|
|
707
|
+
function resolveForkAfterBoundary(messages, sourceMessageId, sourceIsBusy) {
|
|
708
|
+
const sourceIndex = messages.findIndex((message) => messageId(message) === sourceMessageId);
|
|
709
|
+
const sourceMessage = messages[sourceIndex];
|
|
710
|
+
if (sourceIndex < 0 || !sourceMessage) {
|
|
711
|
+
return { error: "Selected source message was not found", status: 404 };
|
|
712
|
+
}
|
|
713
|
+
if (!isUserAuthoredRequest(sourceMessage)) {
|
|
714
|
+
return {
|
|
715
|
+
error: "sourceMessageId must identify a user-authored request",
|
|
716
|
+
status: 400,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
let nextUserIndex = -1;
|
|
720
|
+
for (let index = sourceIndex + 1; index < messages.length; index += 1) {
|
|
721
|
+
const candidate = messages[index];
|
|
722
|
+
if (candidate && isUserAuthoredRequest(candidate)) {
|
|
723
|
+
nextUserIndex = index;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (nextUserIndex < 0 && sourceIsBusy) {
|
|
728
|
+
return {
|
|
729
|
+
error: "The selected turn is still in progress",
|
|
730
|
+
status: 409,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const searchEnd = nextUserIndex >= 0 ? nextUserIndex - 1 : messages.length - 1;
|
|
734
|
+
let hasAssistantResponse = false;
|
|
735
|
+
for (let index = sourceIndex + 1; index <= searchEnd; index += 1) {
|
|
736
|
+
const candidate = messages[index];
|
|
737
|
+
if (candidate && messageRole(candidate) === "assistant") {
|
|
738
|
+
hasAssistantResponse = true;
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (!hasAssistantResponse) {
|
|
743
|
+
return {
|
|
744
|
+
error: "The selected request has no completed assistant response",
|
|
745
|
+
status: 409,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
let boundary;
|
|
749
|
+
for (let index = searchEnd; index > sourceIndex; index -= 1) {
|
|
750
|
+
const candidate = messages[index];
|
|
751
|
+
const role = candidate ? messageRole(candidate) : undefined;
|
|
752
|
+
if (candidate &&
|
|
753
|
+
messageId(candidate) &&
|
|
754
|
+
(role === "user" || role === "assistant")) {
|
|
755
|
+
boundary = candidate;
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const retainedThroughMessageId = messageId(boundary);
|
|
760
|
+
if (!boundary || !retainedThroughMessageId) {
|
|
761
|
+
return { error: "Completed turn boundary has no message id", status: 409 };
|
|
762
|
+
}
|
|
763
|
+
const placementAfterMessageId = [...messages].reverse().map(messageId).find(Boolean) ??
|
|
764
|
+
retainedThroughMessageId;
|
|
765
|
+
const rendered = renderRestartContent(messageContent(boundary)).trim();
|
|
766
|
+
return {
|
|
767
|
+
placementAfterMessageId,
|
|
768
|
+
retainedThroughMessageId,
|
|
769
|
+
retainedThroughContext: rendered
|
|
770
|
+
? truncateForRestart(rendered, 1200)
|
|
771
|
+
: undefined,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function forkSummaryTitle(summary, fallback) {
|
|
775
|
+
const firstLine = summary
|
|
776
|
+
.split("\n")
|
|
777
|
+
.map((line) => line.trim())
|
|
778
|
+
.find(Boolean);
|
|
779
|
+
const candidate = firstLine
|
|
780
|
+
?.replace(/^#{1,6}\s*/u, "")
|
|
781
|
+
.replace(/^title:\s*/iu, "")
|
|
782
|
+
.trim()
|
|
783
|
+
.replace(/[.!?]+$/u, "");
|
|
784
|
+
return truncateSessionTitle(candidate || fallback || "Forked session");
|
|
785
|
+
}
|
|
786
|
+
function generatedRetitleCandidate(title) {
|
|
787
|
+
const firstLine = title
|
|
788
|
+
.split("\n")
|
|
789
|
+
.map((line) => line.trim())
|
|
790
|
+
.find(Boolean);
|
|
791
|
+
if (!firstLine) {
|
|
792
|
+
return undefined;
|
|
793
|
+
}
|
|
794
|
+
const candidate = normalizeRestartTitleCandidate(firstLine
|
|
795
|
+
.replace(/^title:\s*/iu, "")
|
|
796
|
+
.replace(/^["'“”‘’]+|["'“”‘’]+$/gu, "")
|
|
797
|
+
.replace(/[.!?]+$/u, "")
|
|
798
|
+
.trim());
|
|
799
|
+
return candidate ? truncateSessionTitle(candidate) : undefined;
|
|
800
|
+
}
|
|
754
801
|
function messageTitleCandidate(message) {
|
|
755
|
-
if (!isHumanUserMessage(message) ||
|
|
802
|
+
if (!isHumanUserMessage(message) ||
|
|
803
|
+
isRestartInternalCompactCommand(message)) {
|
|
756
804
|
return undefined;
|
|
757
805
|
}
|
|
758
806
|
const nested = message.message;
|
|
@@ -793,6 +841,18 @@ function deriveRestartTitle(params) {
|
|
|
793
841
|
const title = /^Handoff:/i.test(base) ? base : `Handoff: ${base}`;
|
|
794
842
|
return truncateRestartTitle(title);
|
|
795
843
|
}
|
|
844
|
+
function deriveForkTitle(params) {
|
|
845
|
+
const candidates = [
|
|
846
|
+
params.preferredTitle,
|
|
847
|
+
params.sourceSession.customTitle,
|
|
848
|
+
params.sourceSession.title,
|
|
849
|
+
latestUserTitleCandidate(params.sourceSession.messages),
|
|
850
|
+
];
|
|
851
|
+
const base = candidates.map(normalizeRestartTitleCandidate).find(Boolean) ??
|
|
852
|
+
"forked session";
|
|
853
|
+
const title = /^Fork:/i.test(base) ? base : `Fork: ${base}`;
|
|
854
|
+
return truncateRestartTitle(title);
|
|
855
|
+
}
|
|
796
856
|
function buildRestartHandoff(params) {
|
|
797
857
|
const { handoffTitle, sourceSession, sourceProvider, sourceModel, sourceProcess, compactAttempt, projectPath, reason, omittedCount, transcript, } = params;
|
|
798
858
|
const oldProcessLine = sourceProcess
|
|
@@ -847,12 +907,14 @@ function isRestartReplacementActivity(message) {
|
|
|
847
907
|
function sdkMessagesToClientMessages(sdkMessages) {
|
|
848
908
|
const messages = [];
|
|
849
909
|
for (const msg of sdkMessages) {
|
|
910
|
+
const rawFields = msg;
|
|
850
911
|
if (isCompactBoundaryMessage(msg)) {
|
|
851
912
|
const content = (typeof msg.message?.content === "string"
|
|
852
913
|
? msg.message.content
|
|
853
914
|
: undefined) ??
|
|
854
915
|
(typeof msg.content === "string" ? msg.content : "Context compacted");
|
|
855
916
|
messages.push({
|
|
917
|
+
...rawFields,
|
|
856
918
|
id: msg.uuid ?? `msg-${Date.now()}-${messages.length}`,
|
|
857
919
|
type: "system",
|
|
858
920
|
role: "system",
|
|
@@ -889,6 +951,7 @@ function sdkMessagesToClientMessages(sdkMessages) {
|
|
|
889
951
|
continue;
|
|
890
952
|
}
|
|
891
953
|
messages.push({
|
|
954
|
+
...rawFields,
|
|
892
955
|
id: msg.uuid ?? `msg-${Date.now()}-${messages.length}`,
|
|
893
956
|
type: msg.type,
|
|
894
957
|
role: msg.type,
|
|
@@ -1005,6 +1068,31 @@ function extractContextUsageFromSDKMessages(sdkMessages, model, provider, resolv
|
|
|
1005
1068
|
}
|
|
1006
1069
|
export function createSessionsRoutes(deps) {
|
|
1007
1070
|
const routes = new Hono();
|
|
1071
|
+
const activeForkSummaryJobs = new Map();
|
|
1072
|
+
const emitTranscriptDisplayObjects = (sessionId) => {
|
|
1073
|
+
deps.eventBus?.emit({
|
|
1074
|
+
type: "session-metadata-changed",
|
|
1075
|
+
sessionId,
|
|
1076
|
+
transcriptDisplayObjects: deps.sessionMetadataService?.getTranscriptDisplayObjects(sessionId) ??
|
|
1077
|
+
[],
|
|
1078
|
+
timestamp: new Date().toISOString(),
|
|
1079
|
+
});
|
|
1080
|
+
};
|
|
1081
|
+
const updateForkSummaryChildMetadata = async (childSessionId, parentSessionId, title, archived) => {
|
|
1082
|
+
await deps.sessionMetadataService?.updateMetadata(childSessionId, {
|
|
1083
|
+
title,
|
|
1084
|
+
archived,
|
|
1085
|
+
parentSessionId,
|
|
1086
|
+
});
|
|
1087
|
+
deps.eventBus?.emit({
|
|
1088
|
+
type: "session-metadata-changed",
|
|
1089
|
+
sessionId: childSessionId,
|
|
1090
|
+
title,
|
|
1091
|
+
archived,
|
|
1092
|
+
parentSessionId,
|
|
1093
|
+
timestamp: new Date().toISOString(),
|
|
1094
|
+
});
|
|
1095
|
+
};
|
|
1008
1096
|
const getCodexReader = (projectPath) => deps.codexReaderFactory?.(projectPath) ??
|
|
1009
1097
|
(deps.codexSessionsDir
|
|
1010
1098
|
? new CodexSessionReader({
|
|
@@ -1012,13 +1100,6 @@ export function createSessionsRoutes(deps) {
|
|
|
1012
1100
|
projectPath,
|
|
1013
1101
|
})
|
|
1014
1102
|
: null);
|
|
1015
|
-
const getGrokReader = (projectPath) => deps.grokReaderFactory?.(projectPath) ??
|
|
1016
|
-
(deps.grokSessionsDir
|
|
1017
|
-
? new GrokSessionReader({
|
|
1018
|
-
sessionsDir: deps.grokSessionsDir,
|
|
1019
|
-
projectPath,
|
|
1020
|
-
})
|
|
1021
|
-
: null);
|
|
1022
1103
|
let unscopedGrokReader;
|
|
1023
1104
|
const getUnscopedGrokReader = () => {
|
|
1024
1105
|
if (unscopedGrokReader !== undefined) {
|
|
@@ -1045,8 +1126,78 @@ export function createSessionsRoutes(deps) {
|
|
|
1045
1126
|
const search = new URL(requestUrl).search;
|
|
1046
1127
|
return `/api/projects/${canonicalProjectId}/sessions/${encodeURIComponent(sessionId)}${suffix}${search}`;
|
|
1047
1128
|
};
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1129
|
+
const buildSessionProjectRedirectPath = (canonicalProjectId, sessionId, suffix, requestUrl) => {
|
|
1130
|
+
const search = new URL(requestUrl).search;
|
|
1131
|
+
return `/api/projects/${canonicalProjectId}/sessions/${encodeURIComponent(sessionId)}${suffix}${search}`;
|
|
1132
|
+
};
|
|
1133
|
+
const resolveSessionProjectRouting = async (requestProjectId, sessionId) => {
|
|
1134
|
+
const workingProjectId = deps.sessionMetadataService?.getMetadata(sessionId)?.workingProjectId;
|
|
1135
|
+
if (workingProjectId && workingProjectId !== requestProjectId) {
|
|
1136
|
+
return { redirectProjectId: workingProjectId };
|
|
1137
|
+
}
|
|
1138
|
+
const activeProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
1139
|
+
if (activeProcess &&
|
|
1140
|
+
typeof activeProcess.projectId === "string" &&
|
|
1141
|
+
isUrlProjectId(activeProcess.projectId) &&
|
|
1142
|
+
activeProcess.projectId !== requestProjectId) {
|
|
1143
|
+
return { redirectProjectId: activeProcess.projectId };
|
|
1144
|
+
}
|
|
1145
|
+
const workingProject = await deps.scanner.getOrCreateProject(requestProjectId);
|
|
1146
|
+
if (!workingProject) {
|
|
1147
|
+
return { error: "Project not found", status: 404 };
|
|
1148
|
+
}
|
|
1149
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
1150
|
+
const transcriptProjectId = metadata?.workingProjectId === requestProjectId &&
|
|
1151
|
+
metadata.transcriptProjectId
|
|
1152
|
+
? metadata.transcriptProjectId
|
|
1153
|
+
: requestProjectId;
|
|
1154
|
+
const transcriptProject = transcriptProjectId === requestProjectId
|
|
1155
|
+
? workingProject
|
|
1156
|
+
: await deps.scanner.getOrCreateProject(transcriptProjectId);
|
|
1157
|
+
if (!transcriptProject) {
|
|
1158
|
+
return { error: "Transcript project not found", status: 404 };
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
transcriptProject,
|
|
1162
|
+
transcriptProjectId,
|
|
1163
|
+
workingProject,
|
|
1164
|
+
workingProjectId: requestProjectId,
|
|
1165
|
+
};
|
|
1166
|
+
};
|
|
1167
|
+
const getGlobalInstructions = () => buildEffectiveAgentContext({
|
|
1168
|
+
globalInstructions: deps.serverSettingsService?.getSetting("globalInstructions"),
|
|
1169
|
+
hints: deps.serverSettingsService?.getSetting("agentContextHints"),
|
|
1170
|
+
});
|
|
1171
|
+
const resolveLaunchWorkstreamTarget = (project, rawWorkstreamId) => {
|
|
1172
|
+
if (rawWorkstreamId === undefined) {
|
|
1173
|
+
return { projectPath: project.path };
|
|
1174
|
+
}
|
|
1175
|
+
if (!isWorkstreamId(rawWorkstreamId)) {
|
|
1176
|
+
return { error: "Invalid workstream ID format", status: 400 };
|
|
1177
|
+
}
|
|
1178
|
+
if (deps.serverSettingsService?.getSetting("workstreamsEnabled") !== true) {
|
|
1179
|
+
return { error: "Workstreams are disabled", status: 404 };
|
|
1180
|
+
}
|
|
1181
|
+
if (!deps.workstreamService) {
|
|
1182
|
+
return { error: "Workstreams unavailable", status: 503 };
|
|
1183
|
+
}
|
|
1184
|
+
const workstreamId = rawWorkstreamId;
|
|
1185
|
+
if (workstreamId === mainWorkstreamId(project.id)) {
|
|
1186
|
+
return { projectPath: project.path };
|
|
1187
|
+
}
|
|
1188
|
+
if (!deps.sessionMetadataService) {
|
|
1189
|
+
return { error: "Session metadata unavailable", status: 503 };
|
|
1190
|
+
}
|
|
1191
|
+
const workstream = deps.workstreamService.getWorkstream(project.id, workstreamId);
|
|
1192
|
+
if (workstream?.kind !== "checkout") {
|
|
1193
|
+
return { error: "Workstream not found", status: 404 };
|
|
1194
|
+
}
|
|
1195
|
+
if (workstream.status !== "active") {
|
|
1196
|
+
return { error: "Workstream is not active", status: 409 };
|
|
1197
|
+
}
|
|
1198
|
+
return { projectPath: workstream.path, workstreamId };
|
|
1199
|
+
};
|
|
1200
|
+
const persistLaunchMetadata = async (sessionId, provider, executor, initialPrompt, requestedModel, promptSuggestionMode, recapAfterSeconds, workstreamId) => {
|
|
1050
1201
|
if (!deps.sessionMetadataService) {
|
|
1051
1202
|
return;
|
|
1052
1203
|
}
|
|
@@ -1059,18 +1210,40 @@ export function createSessionsRoutes(deps) {
|
|
|
1059
1210
|
if (initialPrompt?.trim()) {
|
|
1060
1211
|
await deps.sessionMetadataService.setInitialPrompt(sessionId, initialPrompt);
|
|
1061
1212
|
}
|
|
1213
|
+
// Persist the requested YA model id (the launch alias, incl. "default") so
|
|
1214
|
+
// per-model settings still key by it after a server restart, instead of
|
|
1215
|
+
// falling back to the reported model. See topics/provider-abstraction.md.
|
|
1216
|
+
if (requestedModel) {
|
|
1217
|
+
await deps.sessionMetadataService.setRequestedModel(sessionId, requestedModel);
|
|
1218
|
+
}
|
|
1219
|
+
// Persist the resolved prompt-suggestion mode so a later resume (which may
|
|
1220
|
+
// omit it from the request body) recovers the per-session preference
|
|
1221
|
+
// instead of falling back to the provider's native default.
|
|
1222
|
+
if (promptSuggestionMode !== undefined) {
|
|
1223
|
+
await deps.sessionMetadataService.updateMetadata(sessionId, {
|
|
1224
|
+
promptSuggestionMode,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
if (recapAfterSeconds !== undefined) {
|
|
1228
|
+
await deps.sessionMetadataService.updateMetadata(sessionId, {
|
|
1229
|
+
recapAfterSeconds,
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
if (workstreamId !== undefined) {
|
|
1233
|
+
await deps.sessionMetadataService.setWorkstream(sessionId, workstreamId);
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
const loadProviderSession = async (project, sessionId, projectId, preferredProvider, afterMessageId, options) => {
|
|
1237
|
+
for (const source of getSessionSources(project, providerResolutionDeps(deps), preferredProvider)) {
|
|
1238
|
+
const loaded = await source.reader.getSession(sessionId, projectId, afterMessageId, options);
|
|
1239
|
+
if (loaded) {
|
|
1240
|
+
return loaded;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return null;
|
|
1062
1244
|
};
|
|
1063
1245
|
const loadRestartSourceSession = async (project, sessionId, projectId, preferredProvider, process) => {
|
|
1064
|
-
const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, {
|
|
1065
|
-
readerFactory: deps.readerFactory,
|
|
1066
|
-
codexSessionsDir: deps.codexSessionsDir,
|
|
1067
|
-
codexReaderFactory: deps.codexReaderFactory,
|
|
1068
|
-
geminiSessionsDir: deps.geminiSessionsDir,
|
|
1069
|
-
geminiReaderFactory: deps.geminiReaderFactory,
|
|
1070
|
-
geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
|
|
1071
|
-
grokSessionsDir: deps.grokSessionsDir,
|
|
1072
|
-
grokReaderFactory: deps.grokReaderFactory,
|
|
1073
|
-
}, preferredProvider);
|
|
1246
|
+
const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), preferredProvider, { readMode: "head" });
|
|
1074
1247
|
if (resolved) {
|
|
1075
1248
|
const loaded = await resolved.source.reader.getSession(sessionId, projectId, undefined, { includeOrphans: false });
|
|
1076
1249
|
if (loaded) {
|
|
@@ -1092,6 +1265,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1092
1265
|
processId: process.id,
|
|
1093
1266
|
permissionMode: process.permissionMode,
|
|
1094
1267
|
modeVersion: process.modeVersion,
|
|
1268
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
1095
1269
|
},
|
|
1096
1270
|
provider: process.provider,
|
|
1097
1271
|
model: process.resolvedModel ?? process.model,
|
|
@@ -1127,7 +1301,8 @@ export function createSessionsRoutes(deps) {
|
|
|
1127
1301
|
unsubscribe = null;
|
|
1128
1302
|
};
|
|
1129
1303
|
unsubscribe = replacement.subscribe((event) => {
|
|
1130
|
-
if (event.type === "message" &&
|
|
1304
|
+
if (event.type === "message" &&
|
|
1305
|
+
isRestartReplacementActivity(event.message)) {
|
|
1131
1306
|
cleanup();
|
|
1132
1307
|
void deps.supervisor.abortProcess(oldProcess.id).catch((error) => {
|
|
1133
1308
|
console.warn(`[restart] Failed to abort old process ${oldProcess.id}:`, error);
|
|
@@ -1157,11 +1332,19 @@ export function createSessionsRoutes(deps) {
|
|
|
1157
1332
|
if (!isUrlProjectId(projectId)) {
|
|
1158
1333
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1159
1334
|
}
|
|
1160
|
-
const
|
|
1161
|
-
if (
|
|
1162
|
-
return c.
|
|
1335
|
+
const routing = await resolveSessionProjectRouting(projectId, c.req.param("sessionId"));
|
|
1336
|
+
if ("redirectProjectId" in routing) {
|
|
1337
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, c.req.param("sessionId"), "", c.req.url), 307);
|
|
1338
|
+
}
|
|
1339
|
+
if ("error" in routing) {
|
|
1340
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1163
1341
|
}
|
|
1164
|
-
const reader =
|
|
1342
|
+
const reader = await resolveSessionReaderForAgentContent({
|
|
1343
|
+
deps,
|
|
1344
|
+
project: routing.transcriptProject,
|
|
1345
|
+
sessionId: c.req.param("sessionId"),
|
|
1346
|
+
projectId: routing.transcriptProjectId,
|
|
1347
|
+
});
|
|
1165
1348
|
const mappings = await reader.getAgentMappings();
|
|
1166
1349
|
return c.json({ mappings });
|
|
1167
1350
|
});
|
|
@@ -1174,17 +1357,30 @@ export function createSessionsRoutes(deps) {
|
|
|
1174
1357
|
if (!isUrlProjectId(projectId)) {
|
|
1175
1358
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1176
1359
|
}
|
|
1177
|
-
const
|
|
1178
|
-
if (
|
|
1179
|
-
return c.
|
|
1360
|
+
const routing = await resolveSessionProjectRouting(projectId, c.req.param("sessionId"));
|
|
1361
|
+
if ("redirectProjectId" in routing) {
|
|
1362
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, c.req.param("sessionId"), "", c.req.url), 307);
|
|
1180
1363
|
}
|
|
1181
|
-
|
|
1364
|
+
if ("error" in routing) {
|
|
1365
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1366
|
+
}
|
|
1367
|
+
const reader = await resolveSessionReaderForAgentContent({
|
|
1368
|
+
deps,
|
|
1369
|
+
project: routing.transcriptProject,
|
|
1370
|
+
sessionId: c.req.param("sessionId"),
|
|
1371
|
+
projectId: routing.transcriptProjectId,
|
|
1372
|
+
});
|
|
1182
1373
|
const agentSession = await reader.getAgentSession(agentId);
|
|
1183
1374
|
if (!agentSession) {
|
|
1184
1375
|
return c.json({ error: "Agent session not found" }, 404);
|
|
1185
1376
|
}
|
|
1186
1377
|
// Add server-rendered HTML to text blocks for markdown display
|
|
1187
|
-
await augmentTextBlocks(agentSession.messages
|
|
1378
|
+
await augmentTextBlocks(agentSession.messages, {
|
|
1379
|
+
projectFileLinks: {
|
|
1380
|
+
projectId: routing.workingProjectId,
|
|
1381
|
+
projectPath: routing.workingProject.path,
|
|
1382
|
+
},
|
|
1383
|
+
});
|
|
1188
1384
|
return c.json(agentSession);
|
|
1189
1385
|
});
|
|
1190
1386
|
// GET /api/projects/:projectId/sessions/:sessionId/metadata - Get session metadata only (no messages)
|
|
@@ -1196,10 +1392,16 @@ export function createSessionsRoutes(deps) {
|
|
|
1196
1392
|
if (!isUrlProjectId(projectId)) {
|
|
1197
1393
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1198
1394
|
}
|
|
1199
|
-
const
|
|
1200
|
-
if (
|
|
1201
|
-
return c.
|
|
1395
|
+
const routing = await resolveSessionProjectRouting(projectId, sessionId);
|
|
1396
|
+
if ("redirectProjectId" in routing) {
|
|
1397
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "/metadata", c.req.url), 307);
|
|
1398
|
+
}
|
|
1399
|
+
if ("error" in routing) {
|
|
1400
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1202
1401
|
}
|
|
1402
|
+
const project = routing.workingProject;
|
|
1403
|
+
const transcriptProject = routing.transcriptProject;
|
|
1404
|
+
const transcriptProjectId = routing.transcriptProjectId;
|
|
1203
1405
|
// Check if session is actively owned by a process
|
|
1204
1406
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
1205
1407
|
// Check if session is being controlled by an external program
|
|
@@ -1211,6 +1413,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1211
1413
|
processId: process.id,
|
|
1212
1414
|
permissionMode: process.permissionMode,
|
|
1213
1415
|
modeVersion: process.modeVersion,
|
|
1416
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
1214
1417
|
}
|
|
1215
1418
|
: isExternal
|
|
1216
1419
|
? { owner: "external" }
|
|
@@ -1222,23 +1425,18 @@ export function createSessionsRoutes(deps) {
|
|
|
1222
1425
|
const lastSeenAt = lastSeenEntry?.timestamp;
|
|
1223
1426
|
// Get pending input request from active process
|
|
1224
1427
|
const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
|
|
1225
|
-
|
|
1226
|
-
const slashCommands = process?.supportsDynamicCommands
|
|
1227
|
-
? await process.supportedCommands()
|
|
1228
|
-
: null;
|
|
1428
|
+
const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
|
|
1229
1429
|
// Read minimal session info from disk (just for title/timestamps, no messages)
|
|
1230
|
-
const metadataProvider =
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}, metadataProvider ?? process?.provider);
|
|
1241
|
-
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
1430
|
+
const metadataProvider = metadata?.provider ??
|
|
1431
|
+
deps.sessionMetadataService?.getProvider(sessionId);
|
|
1432
|
+
const slashCommands = await getSessionSlashCommands(process, process?.provider ?? metadataProvider ?? project.provider);
|
|
1433
|
+
const deferredMessages = sessionQueueSummaries(deps, sessionId, process);
|
|
1434
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider ?? process?.provider);
|
|
1435
|
+
const rawSessionSummary = sessionSummaryResult?.summary ?? null;
|
|
1436
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1437
|
+
const sessionSummary = rawSessionSummary
|
|
1438
|
+
? applyRecapOverlayToSummary(rawSessionSummary, recapMessages)
|
|
1439
|
+
: null;
|
|
1242
1440
|
if (!sessionSummary && !process) {
|
|
1243
1441
|
const canonicalProjectId = await getGrokNativeProjectId(sessionId, projectId);
|
|
1244
1442
|
if (canonicalProjectId) {
|
|
@@ -1246,14 +1444,13 @@ export function createSessionsRoutes(deps) {
|
|
|
1246
1444
|
}
|
|
1247
1445
|
return c.json({ error: "Session not found" }, 404);
|
|
1248
1446
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
? deps.notificationService.hasUnread(sessionId, sessionSummary.updatedAt)
|
|
1447
|
+
const hasUnread = rawSessionSummary
|
|
1448
|
+
? hasUnreadProviderContent(deps.notificationService, sessionId, rawSessionSummary.updatedAt)
|
|
1252
1449
|
: undefined;
|
|
1253
1450
|
const response = {
|
|
1254
1451
|
session: {
|
|
1255
1452
|
id: sessionId,
|
|
1256
|
-
projectId:
|
|
1453
|
+
projectId: routing.workingProjectId,
|
|
1257
1454
|
title: sessionSummary?.title ?? null,
|
|
1258
1455
|
fullTitle: sessionSummary?.fullTitle ?? null,
|
|
1259
1456
|
createdAt: sessionSummary?.createdAt ?? new Date().toISOString(),
|
|
@@ -1279,16 +1476,142 @@ export function createSessionsRoutes(deps) {
|
|
|
1279
1476
|
heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
|
|
1280
1477
|
heartbeatTurnText: metadata?.heartbeatTurnText,
|
|
1281
1478
|
heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes ?? undefined,
|
|
1479
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
1480
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
1481
|
+
workingProjectId: metadata?.workingProjectId,
|
|
1482
|
+
transcriptProjectId: metadata?.transcriptProjectId,
|
|
1483
|
+
workstreamId: metadata?.workstreamId,
|
|
1484
|
+
transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
|
|
1282
1485
|
lastSeenAt,
|
|
1283
1486
|
hasUnread,
|
|
1284
1487
|
},
|
|
1285
1488
|
ownership,
|
|
1286
1489
|
processState: process?.state.type ?? null,
|
|
1490
|
+
providerRuntimeStatus,
|
|
1287
1491
|
pendingInputRequest,
|
|
1288
1492
|
slashCommands,
|
|
1493
|
+
...(deferredMessages.length > 0 ? { deferredMessages } : {}),
|
|
1289
1494
|
};
|
|
1290
1495
|
return c.json(response);
|
|
1291
1496
|
});
|
|
1497
|
+
// PUT /api/projects/:projectId/sessions/:sessionId/project
|
|
1498
|
+
// Reclassify a session under a different YA project without sending any
|
|
1499
|
+
// provider-visible turn. The provider transcript remains under
|
|
1500
|
+
// transcriptProjectId; relative file links and UI routing use projectId.
|
|
1501
|
+
routes.put("/projects/:projectId/sessions/:sessionId/project", async (c) => {
|
|
1502
|
+
const projectId = c.req.param("projectId");
|
|
1503
|
+
const sessionId = c.req.param("sessionId");
|
|
1504
|
+
if (!isUrlProjectId(projectId)) {
|
|
1505
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1506
|
+
}
|
|
1507
|
+
if (!deps.sessionMetadataService) {
|
|
1508
|
+
return c.json({ error: "Session metadata service unavailable" }, 503);
|
|
1509
|
+
}
|
|
1510
|
+
let body;
|
|
1511
|
+
try {
|
|
1512
|
+
body = await c.req.json();
|
|
1513
|
+
}
|
|
1514
|
+
catch {
|
|
1515
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
1516
|
+
}
|
|
1517
|
+
const targetProjectId = body.projectId;
|
|
1518
|
+
if (typeof targetProjectId !== "string" ||
|
|
1519
|
+
!isUrlProjectId(targetProjectId)) {
|
|
1520
|
+
return c.json({ error: "Invalid target project ID format" }, 400);
|
|
1521
|
+
}
|
|
1522
|
+
const targetProject = await deps.scanner.getOrCreateProject(targetProjectId);
|
|
1523
|
+
if (!targetProject) {
|
|
1524
|
+
return c.json({ error: "Target project not found" }, 404);
|
|
1525
|
+
}
|
|
1526
|
+
const metadata = deps.sessionMetadataService.getMetadata(sessionId);
|
|
1527
|
+
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
1528
|
+
const transcriptProjectId = metadata?.transcriptProjectId ??
|
|
1529
|
+
process?.projectId ??
|
|
1530
|
+
projectId;
|
|
1531
|
+
const transcriptProject = transcriptProjectId === targetProjectId
|
|
1532
|
+
? targetProject
|
|
1533
|
+
: await deps.scanner.getOrCreateProject(transcriptProjectId);
|
|
1534
|
+
if (!transcriptProject) {
|
|
1535
|
+
return c.json({ error: "Transcript project not found" }, 404);
|
|
1536
|
+
}
|
|
1537
|
+
if (!process) {
|
|
1538
|
+
const metadataProvider = metadata?.provider ??
|
|
1539
|
+
deps.sessionMetadataService.getProvider(sessionId);
|
|
1540
|
+
const summary = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
|
|
1541
|
+
if (!summary) {
|
|
1542
|
+
return c.json({ error: "Session not found" }, 404);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const storedWorkingProjectId = targetProjectId === transcriptProjectId ? undefined : targetProjectId;
|
|
1546
|
+
const storedTranscriptProjectId = targetProjectId === transcriptProjectId ? undefined : transcriptProjectId;
|
|
1547
|
+
await deps.sessionMetadataService.setWorkingProject(sessionId, storedWorkingProjectId, storedTranscriptProjectId);
|
|
1548
|
+
deps.eventBus?.emit({
|
|
1549
|
+
type: "session-metadata-changed",
|
|
1550
|
+
sessionId,
|
|
1551
|
+
projectId: targetProjectId,
|
|
1552
|
+
transcriptProjectId: storedTranscriptProjectId ?? null,
|
|
1553
|
+
timestamp: new Date().toISOString(),
|
|
1554
|
+
});
|
|
1555
|
+
return c.json({
|
|
1556
|
+
updated: true,
|
|
1557
|
+
projectId: targetProjectId,
|
|
1558
|
+
transcriptProjectId: storedTranscriptProjectId ?? null,
|
|
1559
|
+
});
|
|
1560
|
+
});
|
|
1561
|
+
// POST /api/projects/:projectId/sessions/:sessionId/refresh-preview
|
|
1562
|
+
// On-demand recompute of the hover-card recent-activity excerpt for a
|
|
1563
|
+
// non-running session (its live state is otherwise only refreshed when the
|
|
1564
|
+
// session is resumed). Fast reverse-scan of the JSONL, then push the result
|
|
1565
|
+
// to lists/hovers via a session-updated event so the preview updates in
|
|
1566
|
+
// place without flicker. See topics/session-hovercard-recent-activity.md.
|
|
1567
|
+
routes.post("/projects/:projectId/sessions/:sessionId/refresh-preview", async (c) => {
|
|
1568
|
+
const projectId = c.req.param("projectId");
|
|
1569
|
+
const sessionId = c.req.param("sessionId");
|
|
1570
|
+
if (!isUrlProjectId(projectId)) {
|
|
1571
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1572
|
+
}
|
|
1573
|
+
const routing = await resolveSessionProjectRouting(projectId, sessionId);
|
|
1574
|
+
if ("redirectProjectId" in routing) {
|
|
1575
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "/refresh-preview", c.req.url), 307);
|
|
1576
|
+
}
|
|
1577
|
+
if ("error" in routing) {
|
|
1578
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1579
|
+
}
|
|
1580
|
+
const transcriptProject = routing.transcriptProject;
|
|
1581
|
+
const transcriptProjectId = routing.transcriptProjectId;
|
|
1582
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
1583
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1584
|
+
let lastAgentText;
|
|
1585
|
+
if (recapMessages.length > 0) {
|
|
1586
|
+
const summaryResult = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider);
|
|
1587
|
+
if (summaryResult?.summary) {
|
|
1588
|
+
lastAgentText = applyRecapOverlayToSummary(summaryResult.summary, recapMessages).lastAgentText;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
// Claude uses the fast reverse-scan when there is no durable overlay to
|
|
1592
|
+
// compare; every other provider goes through the cross-provider load +
|
|
1593
|
+
// normalize, then a shared extractor on the uniform message form.
|
|
1594
|
+
const reader = deps.readerFactory(transcriptProject);
|
|
1595
|
+
lastAgentText ??= reader.getLastAgentExcerpt
|
|
1596
|
+
? await reader.getLastAgentExcerpt(sessionId)
|
|
1597
|
+
: undefined;
|
|
1598
|
+
if (lastAgentText === undefined) {
|
|
1599
|
+
const normalized = await loadRestartSourceSession(transcriptProject, sessionId, transcriptProjectId, metadataProvider);
|
|
1600
|
+
if (normalized) {
|
|
1601
|
+
lastAgentText = extractLastAgentExcerpt(normalized.messages);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (lastAgentText !== undefined) {
|
|
1605
|
+
deps.eventBus?.emit({
|
|
1606
|
+
type: "session-updated",
|
|
1607
|
+
sessionId,
|
|
1608
|
+
projectId: routing.workingProjectId,
|
|
1609
|
+
lastAgentText,
|
|
1610
|
+
timestamp: new Date().toISOString(),
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
return c.json({ lastAgentText: lastAgentText ?? null });
|
|
1614
|
+
});
|
|
1292
1615
|
// GET /api/projects/:projectId/sessions/:sessionId - Get session detail
|
|
1293
1616
|
// Optional query params:
|
|
1294
1617
|
// ?afterMessageId=<id> - incremental forward-fetch (append new messages)
|
|
@@ -1296,12 +1619,15 @@ export function createSessionsRoutes(deps) {
|
|
|
1296
1619
|
// ?beforeMessageId=<id> - cursor for loading older chunks (used with tailCompactions)
|
|
1297
1620
|
// ?tailTurns=<n> - aggressive opt-in client memory cap by recent user turns
|
|
1298
1621
|
// ?tailFrom=<id> - aggressive opt-in client memory cap from a user message id
|
|
1622
|
+
// ?fullHistory=1 - explicit opt-in to the full transcript
|
|
1299
1623
|
routes.get("/projects/:projectId/sessions/:sessionId", async (c) => {
|
|
1300
1624
|
const requestStartMs = performance.now();
|
|
1301
1625
|
const projectId = c.req.param("projectId");
|
|
1302
1626
|
const sessionId = c.req.param("sessionId");
|
|
1303
1627
|
const afterMessageId = c.req.query("afterMessageId");
|
|
1304
1628
|
const publicShare = c.req.query("publicShare") === "1";
|
|
1629
|
+
const fullHistory = c.req.query("fullHistory") === "1";
|
|
1630
|
+
const fullHistoryReason = c.req.query("fullHistoryReason");
|
|
1305
1631
|
const tailCompactionsParam = c.req.query("tailCompactions");
|
|
1306
1632
|
const beforeMessageId = c.req.query("beforeMessageId");
|
|
1307
1633
|
const tailTurnsParam = c.req.query("tailTurns");
|
|
@@ -1312,15 +1638,38 @@ export function createSessionsRoutes(deps) {
|
|
|
1312
1638
|
const tailTurns = tailTurnsParam !== undefined
|
|
1313
1639
|
? Number.parseInt(tailTurnsParam, 10)
|
|
1314
1640
|
: undefined;
|
|
1641
|
+
const requestedTailCompactions = tailCompactions !== undefined &&
|
|
1642
|
+
!Number.isNaN(tailCompactions) &&
|
|
1643
|
+
tailCompactions > 0
|
|
1644
|
+
? tailCompactions
|
|
1645
|
+
: undefined;
|
|
1646
|
+
const requestedTailTurns = tailTurns !== undefined && !Number.isNaN(tailTurns) && tailTurns > 0
|
|
1647
|
+
? tailTurns
|
|
1648
|
+
: undefined;
|
|
1649
|
+
const defaultedToCompactTail = !fullHistory &&
|
|
1650
|
+
!afterMessageId &&
|
|
1651
|
+
!tailFrom &&
|
|
1652
|
+
requestedTailTurns === undefined &&
|
|
1653
|
+
requestedTailCompactions === undefined;
|
|
1654
|
+
const effectiveTailCompactions = requestedTailCompactions ??
|
|
1655
|
+
(defaultedToCompactTail
|
|
1656
|
+
? DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS
|
|
1657
|
+
: undefined);
|
|
1315
1658
|
// Validate projectId format at API boundary
|
|
1316
1659
|
if (!isUrlProjectId(projectId)) {
|
|
1317
1660
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1318
1661
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return c.json({ error: "Project not found" }, 404);
|
|
1662
|
+
const routing = await resolveSessionProjectRouting(projectId, sessionId);
|
|
1663
|
+
if ("redirectProjectId" in routing) {
|
|
1664
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "", c.req.url), 307);
|
|
1323
1665
|
}
|
|
1666
|
+
if ("error" in routing) {
|
|
1667
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1668
|
+
}
|
|
1669
|
+
const project = routing.workingProject;
|
|
1670
|
+
const effectiveProjectId = routing.workingProjectId;
|
|
1671
|
+
const transcriptProject = routing.transcriptProject;
|
|
1672
|
+
const transcriptProjectId = routing.transcriptProjectId;
|
|
1324
1673
|
const projectResolvedMs = performance.now();
|
|
1325
1674
|
// Check if session is actively owned by a process
|
|
1326
1675
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
@@ -1330,70 +1679,30 @@ export function createSessionsRoutes(deps) {
|
|
|
1330
1679
|
// Only mark tools as "aborted" if we owned the session and know it terminated
|
|
1331
1680
|
const wasEverOwned = deps.supervisor.wasEverOwned(sessionId);
|
|
1332
1681
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
1333
|
-
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1682
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1683
|
+
const recapCursor = findDurableRecapCursor(afterMessageId, recapMessages);
|
|
1684
|
+
const providerAfterMessageId = recapCursor ? undefined : afterMessageId;
|
|
1685
|
+
const primaryReaderAfterMessageId = isClaudeSdkProviderName(project.provider) ||
|
|
1686
|
+
isClaudeSdkProviderName(metadataProvider)
|
|
1687
|
+
? undefined
|
|
1688
|
+
: providerAfterMessageId;
|
|
1689
|
+
const loadedSession = await loadProviderSession(transcriptProject, sessionId, transcriptProjectId, metadataProvider ?? process?.provider, primaryReaderAfterMessageId, {
|
|
1336
1690
|
// Only include orphaned tool info if:
|
|
1337
1691
|
// 1. We previously owned this session (not external)
|
|
1338
1692
|
// 2. No active process (tools aren't potentially in progress)
|
|
1339
1693
|
// When we own the session, tools without results might be pending approval
|
|
1340
1694
|
includeOrphans: wasEverOwned && !process,
|
|
1341
1695
|
});
|
|
1342
|
-
// For Claude projects, also check for Codex sessions if primary reader didn't find it
|
|
1343
|
-
// This handles mixed projects that have sessions from multiple providers
|
|
1344
|
-
if (!loadedSession &&
|
|
1345
|
-
project.provider === "claude" &&
|
|
1346
|
-
(deps.codexReaderFactory || deps.codexSessionsDir)) {
|
|
1347
|
-
const codexReader = deps.codexReaderFactory?.(project.path) ??
|
|
1348
|
-
(deps.codexSessionsDir
|
|
1349
|
-
? new CodexSessionReader({
|
|
1350
|
-
sessionsDir: deps.codexSessionsDir,
|
|
1351
|
-
projectPath: project.path,
|
|
1352
|
-
})
|
|
1353
|
-
: null);
|
|
1354
|
-
if (codexReader) {
|
|
1355
|
-
loadedSession = await codexReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
// For Claude/Codex projects, also check for Gemini sessions if still not found
|
|
1359
|
-
// This handles mixed projects that have sessions from multiple providers
|
|
1360
|
-
if (!loadedSession &&
|
|
1361
|
-
(project.provider === "claude" || project.provider === "codex") &&
|
|
1362
|
-
(deps.geminiReaderFactory || deps.geminiSessionsDir)) {
|
|
1363
|
-
const geminiReader = deps.geminiReaderFactory?.(project.path) ??
|
|
1364
|
-
(deps.geminiSessionsDir
|
|
1365
|
-
? new GeminiSessionReader({
|
|
1366
|
-
sessionsDir: deps.geminiSessionsDir,
|
|
1367
|
-
projectPath: project.path,
|
|
1368
|
-
hashToCwd: deps.geminiScanner?.getHashToCwd(),
|
|
1369
|
-
})
|
|
1370
|
-
: null);
|
|
1371
|
-
if (geminiReader) {
|
|
1372
|
-
loadedSession = await geminiReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
// For mixed-provider projects, a restarted YA process may need session
|
|
1376
|
-
// metadata to rediscover an OpenCode-native ses_* transcript.
|
|
1377
|
-
if (!loadedSession && metadataProvider === "opencode") {
|
|
1378
|
-
const opencodeReader = deps.readerFactory({
|
|
1379
|
-
...project,
|
|
1380
|
-
provider: "opencode",
|
|
1381
|
-
});
|
|
1382
|
-
loadedSession = await opencodeReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
|
|
1383
|
-
}
|
|
1384
|
-
if (!loadedSession) {
|
|
1385
|
-
const grokReader = getGrokReader(project.path);
|
|
1386
|
-
if (grokReader) {
|
|
1387
|
-
loadedSession = await grokReader.getSession(sessionId, project.id, afterMessageId, { includeOrphans: wasEverOwned && !process });
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
1696
|
const readEndMs = performance.now();
|
|
1391
1697
|
let session = loadedSession ? normalizeSession(loadedSession) : null;
|
|
1392
1698
|
const normalizedMessageCount = session?.messages.length ?? 0;
|
|
1393
1699
|
const normalizeEndMs = performance.now();
|
|
1700
|
+
if (session && isClaudeSdkProviderName(session.provider)) {
|
|
1701
|
+
augmentTaskListSnapshots(session.messages);
|
|
1702
|
+
}
|
|
1394
1703
|
let incrementalAnchorFound = false;
|
|
1395
|
-
if (session &&
|
|
1396
|
-
const sliced = sliceAfterMessageIdWithMatch(session.messages,
|
|
1704
|
+
if (session && providerAfterMessageId) {
|
|
1705
|
+
const sliced = sliceAfterMessageIdWithMatch(session.messages, providerAfterMessageId);
|
|
1397
1706
|
session = {
|
|
1398
1707
|
...session,
|
|
1399
1708
|
messages: sliced.messages,
|
|
@@ -1407,6 +1716,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1407
1716
|
processId: process.id,
|
|
1408
1717
|
permissionMode: process.permissionMode,
|
|
1409
1718
|
modeVersion: process.modeVersion,
|
|
1719
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
1410
1720
|
}
|
|
1411
1721
|
: isExternal
|
|
1412
1722
|
? { owner: "external" }
|
|
@@ -1414,12 +1724,16 @@ export function createSessionsRoutes(deps) {
|
|
|
1414
1724
|
// Get pending input request from active process (for tool approval prompts)
|
|
1415
1725
|
// This ensures clients get pending requests immediately without waiting for SSE
|
|
1416
1726
|
const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
|
|
1417
|
-
|
|
1727
|
+
const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
|
|
1728
|
+
// Get available slash commands (for "/" button and typed slash menu)
|
|
1418
1729
|
// The init message that normally carries these gets discarded from the SSE buffer
|
|
1419
|
-
// after ~30s, so we attach them to the REST response
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1730
|
+
// after ~30s, so we attach them to the REST response. Providers with known
|
|
1731
|
+
// native built-ins, such as Codex, can expose those while stopped.
|
|
1732
|
+
const slashCommands = await getSessionSlashCommands(process, process?.provider ??
|
|
1733
|
+
session?.provider ??
|
|
1734
|
+
metadataProvider ??
|
|
1735
|
+
project.provider);
|
|
1736
|
+
const deferredMessages = sessionQueueSummaries(deps, sessionId, process);
|
|
1423
1737
|
if (!session) {
|
|
1424
1738
|
// Session file doesn't exist yet - only valid if we own the process
|
|
1425
1739
|
if (process) {
|
|
@@ -1436,22 +1750,23 @@ export function createSessionsRoutes(deps) {
|
|
|
1436
1750
|
: mis
|
|
1437
1751
|
? (m, p) => mis.getContextWindow(m, p)
|
|
1438
1752
|
: undefined);
|
|
1439
|
-
//
|
|
1440
|
-
|
|
1441
|
-
mis.recordContextWindow(process.resolvedModel, sdkContextWindow, process.provider);
|
|
1442
|
-
}
|
|
1753
|
+
// (Durable recording happens at the observation point in Process via
|
|
1754
|
+
// onContextWindowObserved, not as a side effect of this GET.)
|
|
1443
1755
|
// Get metadata even for new sessions (in case it was set before file was written)
|
|
1444
1756
|
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
1757
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1758
|
+
const visibleProcessMessages = mergeRecapMessages(processMessages, recapMessages);
|
|
1445
1759
|
// Get notification data for new sessions too
|
|
1446
1760
|
const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
|
|
1447
|
-
const
|
|
1761
|
+
const latestRecap = latestRecapMessage(recapMessages);
|
|
1762
|
+
const newSessionUpdatedAt = latestRecap?.timestamp ?? new Date().toISOString();
|
|
1448
1763
|
const hasUnread = deps.notificationService
|
|
1449
1764
|
? deps.notificationService.hasUnread(sessionId, newSessionUpdatedAt)
|
|
1450
1765
|
: undefined;
|
|
1451
1766
|
return c.json({
|
|
1452
1767
|
session: {
|
|
1453
1768
|
id: sessionId,
|
|
1454
|
-
projectId,
|
|
1769
|
+
projectId: effectiveProjectId,
|
|
1455
1770
|
title: null,
|
|
1456
1771
|
createdAt: new Date().toISOString(),
|
|
1457
1772
|
updatedAt: newSessionUpdatedAt,
|
|
@@ -1466,16 +1781,24 @@ export function createSessionsRoutes(deps) {
|
|
|
1466
1781
|
heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
|
|
1467
1782
|
heartbeatTurnText: metadata?.heartbeatTurnText,
|
|
1468
1783
|
heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes,
|
|
1784
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
1785
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
1786
|
+
workingProjectId: metadata?.workingProjectId,
|
|
1787
|
+
transcriptProjectId: metadata?.transcriptProjectId,
|
|
1788
|
+
workstreamId: metadata?.workstreamId,
|
|
1789
|
+
transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
|
|
1469
1790
|
lastSeenAt: lastSeenEntry?.timestamp,
|
|
1470
1791
|
hasUnread,
|
|
1471
1792
|
provider: process.provider,
|
|
1472
1793
|
model: process.resolvedModel,
|
|
1473
1794
|
contextUsage,
|
|
1474
1795
|
},
|
|
1475
|
-
messages:
|
|
1796
|
+
messages: visibleProcessMessages,
|
|
1476
1797
|
ownership,
|
|
1798
|
+
providerRuntimeStatus,
|
|
1477
1799
|
pendingInputRequest,
|
|
1478
1800
|
slashCommands,
|
|
1801
|
+
...(deferredMessages.length > 0 ? { deferredMessages } : {}),
|
|
1479
1802
|
});
|
|
1480
1803
|
}
|
|
1481
1804
|
const canonicalProjectId = await getGrokNativeProjectId(sessionId, projectId);
|
|
@@ -1489,37 +1812,32 @@ export function createSessionsRoutes(deps) {
|
|
|
1489
1812
|
// Get notification data (lastSeenAt, hasUnread)
|
|
1490
1813
|
const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
|
|
1491
1814
|
const lastSeenAt = lastSeenEntry?.timestamp;
|
|
1492
|
-
const hasUnread = deps.notificationService
|
|
1493
|
-
? deps.notificationService.hasUnread(sessionId, session.updatedAt)
|
|
1494
|
-
: undefined;
|
|
1495
1815
|
// Apply pagination if requested (BEFORE expensive augmentation). tailTurns
|
|
1496
1816
|
// is an opt-in stronger client memory cap, so it wins over compact tails.
|
|
1497
1817
|
// Skip both when afterMessageId is present since that's an incremental
|
|
1498
1818
|
// forward-fetch use case.
|
|
1499
1819
|
let paginationInfo;
|
|
1500
1820
|
if (!afterMessageId &&
|
|
1501
|
-
(tailFrom ||
|
|
1502
|
-
|
|
1503
|
-
const sliced = sliceAtUserTurnBoundary(session.messages, tailTurns !== undefined && !Number.isNaN(tailTurns) && tailTurns > 0
|
|
1504
|
-
? tailTurns
|
|
1505
|
-
: 20, tailFrom);
|
|
1821
|
+
(tailFrom || requestedTailTurns !== undefined)) {
|
|
1822
|
+
const sliced = sliceAtUserTurnBoundary(session.messages, requestedTailTurns ?? 20, tailFrom);
|
|
1506
1823
|
session = { ...session, messages: sliced.messages };
|
|
1507
1824
|
paginationInfo = sliced.pagination;
|
|
1508
1825
|
}
|
|
1509
|
-
else if (
|
|
1510
|
-
!Number.isNaN(tailCompactions) &&
|
|
1511
|
-
tailCompactions > 0 &&
|
|
1826
|
+
else if (effectiveTailCompactions !== undefined &&
|
|
1512
1827
|
!afterMessageId) {
|
|
1513
|
-
const sliced = sliceAtCompactBoundaries(session.messages,
|
|
1828
|
+
const sliced = sliceAtCompactBoundaries(session.messages, effectiveTailCompactions, beforeMessageId);
|
|
1514
1829
|
session = { ...session, messages: sliced.messages };
|
|
1515
1830
|
paginationInfo = sliced.pagination;
|
|
1516
1831
|
}
|
|
1832
|
+
if (isClaudeSdkProviderName(session.provider)) {
|
|
1833
|
+
pruneTaskListSnapshotsToLatest(session.messages);
|
|
1834
|
+
}
|
|
1517
1835
|
const sliceEndMs = performance.now();
|
|
1518
1836
|
// Codex normalized IDs can drift between stream and JSONL. If an
|
|
1519
1837
|
// incremental request misses its anchor, never return the full historical
|
|
1520
1838
|
// session into a compact-tail client; bound the fallback to the same tail
|
|
1521
1839
|
// window used for initial loads.
|
|
1522
|
-
if (
|
|
1840
|
+
if (providerAfterMessageId && !incrementalAnchorFound) {
|
|
1523
1841
|
const sliced = sliceAtCompactBoundaries(session.messages, 2);
|
|
1524
1842
|
session = { ...session, messages: sliced.messages };
|
|
1525
1843
|
paginationInfo = sliced.pagination;
|
|
@@ -1529,9 +1847,33 @@ export function createSessionsRoutes(deps) {
|
|
|
1529
1847
|
await augmentEditToolUses(session.messages);
|
|
1530
1848
|
}
|
|
1531
1849
|
else {
|
|
1532
|
-
await augmentPersistedSessionMessages(session.messages
|
|
1850
|
+
await augmentPersistedSessionMessages(session.messages, {
|
|
1851
|
+
projectFileLinks: {
|
|
1852
|
+
projectId: effectiveProjectId,
|
|
1853
|
+
projectPath: project.path,
|
|
1854
|
+
},
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
// The overlay reassigns `session` below; hasUnreadProviderContent needs
|
|
1858
|
+
// the pre-overlay timestamp.
|
|
1859
|
+
const preRecapUpdatedAt = session.updatedAt;
|
|
1860
|
+
if (!beforeMessageId) {
|
|
1861
|
+
session = applyRecapOverlayToSession(session, recapMessages);
|
|
1862
|
+
if (recapCursor && afterMessageId) {
|
|
1863
|
+
const sliced = sliceAfterDurableRecapCursor({
|
|
1864
|
+
messages: session.messages,
|
|
1865
|
+
recap: recapCursor,
|
|
1866
|
+
cursorId: afterMessageId,
|
|
1867
|
+
});
|
|
1868
|
+
session = { ...session, messages: sliced.messages };
|
|
1869
|
+
incrementalAnchorFound = sliced.found;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
else {
|
|
1873
|
+
session = applyRecapOverlayToSummary(session, recapMessages);
|
|
1533
1874
|
}
|
|
1534
1875
|
const augmentEndMs = performance.now();
|
|
1876
|
+
const hasUnread = hasUnreadProviderContent(deps.notificationService, sessionId, preRecapUpdatedAt);
|
|
1535
1877
|
// Override context usage with SDK-reported context window from live process
|
|
1536
1878
|
// The reader uses hardcoded defaults; the process captures the real value at runtime
|
|
1537
1879
|
let { contextUsage } = session;
|
|
@@ -1542,29 +1884,80 @@ export function createSessionsRoutes(deps) {
|
|
|
1542
1884
|
percentage: Math.round((contextUsage.inputTokens / cw) * 100),
|
|
1543
1885
|
contextWindow: cw,
|
|
1544
1886
|
};
|
|
1545
|
-
//
|
|
1546
|
-
|
|
1887
|
+
// Durable recording happens at the observation point in Process via
|
|
1888
|
+
// onContextWindowObserved; this block only overrides the displayed value.
|
|
1547
1889
|
}
|
|
1548
1890
|
const { messages: _messages, ...sessionMetadata } = session;
|
|
1549
1891
|
const totalMs = performance.now() - requestStartMs;
|
|
1892
|
+
if (defaultedToCompactTail &&
|
|
1893
|
+
normalizedMessageCount >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
|
|
1894
|
+
getLogger().warn({
|
|
1895
|
+
beforeMessageId: beforeMessageId ?? null,
|
|
1896
|
+
defaultTailCompactions: DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS,
|
|
1897
|
+
event: "session_detail_unbounded_defaulted_to_tail",
|
|
1898
|
+
normalizedMessageCount,
|
|
1899
|
+
projectId: effectiveProjectId,
|
|
1900
|
+
transcriptProjectId,
|
|
1901
|
+
provider: session.provider,
|
|
1902
|
+
publicShare,
|
|
1903
|
+
returnedMessageCount: session.messages.length,
|
|
1904
|
+
sessionId,
|
|
1905
|
+
timings: {
|
|
1906
|
+
augmentMs: roundedMs(augmentEndMs - sliceEndMs),
|
|
1907
|
+
normalizeMs: roundedMs(normalizeEndMs - readEndMs),
|
|
1908
|
+
projectMs: roundedMs(projectResolvedMs - requestStartMs),
|
|
1909
|
+
readMs: roundedMs(readEndMs - projectResolvedMs),
|
|
1910
|
+
routeMs: roundedMs(sliceEndMs - normalizeEndMs),
|
|
1911
|
+
totalMs: roundedMs(totalMs),
|
|
1912
|
+
},
|
|
1913
|
+
totalMessageCount: session.messageCount,
|
|
1914
|
+
}, "SESSION_DETAIL: defaulted unbounded request to compact tail");
|
|
1915
|
+
}
|
|
1916
|
+
if (fullHistory &&
|
|
1917
|
+
session.messages.length >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
|
|
1918
|
+
getLogger().warn({
|
|
1919
|
+
event: "session_detail_full_history_large",
|
|
1920
|
+
fullHistoryReason: fullHistoryReason ?? null,
|
|
1921
|
+
normalizedMessageCount,
|
|
1922
|
+
projectId: effectiveProjectId,
|
|
1923
|
+
transcriptProjectId,
|
|
1924
|
+
provider: session.provider,
|
|
1925
|
+
publicShare,
|
|
1926
|
+
returnedMessageCount: session.messages.length,
|
|
1927
|
+
sessionId,
|
|
1928
|
+
timings: {
|
|
1929
|
+
augmentMs: roundedMs(augmentEndMs - sliceEndMs),
|
|
1930
|
+
normalizeMs: roundedMs(normalizeEndMs - readEndMs),
|
|
1931
|
+
projectMs: roundedMs(projectResolvedMs - requestStartMs),
|
|
1932
|
+
readMs: roundedMs(readEndMs - projectResolvedMs),
|
|
1933
|
+
routeMs: roundedMs(sliceEndMs - normalizeEndMs),
|
|
1934
|
+
totalMs: roundedMs(totalMs),
|
|
1935
|
+
},
|
|
1936
|
+
totalMessageCount: session.messageCount,
|
|
1937
|
+
}, "SESSION_DETAIL: large explicit full-history request");
|
|
1938
|
+
}
|
|
1550
1939
|
if (totalMs >= SESSION_DETAIL_SLOW_LOG_MS) {
|
|
1551
1940
|
getLogger().warn({
|
|
1552
1941
|
afterMessageId: afterMessageId ?? null,
|
|
1553
1942
|
beforeMessageId: beforeMessageId ?? null,
|
|
1554
1943
|
event: "session_detail_slow",
|
|
1944
|
+
defaultedToCompactTail,
|
|
1945
|
+
fullHistory,
|
|
1946
|
+
fullHistoryReason: fullHistoryReason ?? null,
|
|
1555
1947
|
incrementalAnchorFound: afterMessageId
|
|
1556
1948
|
? incrementalAnchorFound
|
|
1557
1949
|
: null,
|
|
1558
1950
|
normalizedMessageCount,
|
|
1559
1951
|
owned: Boolean(process),
|
|
1560
1952
|
processState: process?.state.type ?? null,
|
|
1561
|
-
projectId,
|
|
1953
|
+
projectId: effectiveProjectId,
|
|
1954
|
+
transcriptProjectId,
|
|
1562
1955
|
provider: session.provider,
|
|
1563
1956
|
publicShare,
|
|
1564
1957
|
returnedMessageCount: session.messages.length,
|
|
1565
1958
|
sessionId,
|
|
1566
|
-
tailCompactions:
|
|
1567
|
-
tailTurns:
|
|
1959
|
+
tailCompactions: effectiveTailCompactions ?? null,
|
|
1960
|
+
tailTurns: requestedTailTurns ?? null,
|
|
1568
1961
|
timings: {
|
|
1569
1962
|
augmentMs: roundedMs(augmentEndMs - sliceEndMs),
|
|
1570
1963
|
normalizeMs: roundedMs(normalizeEndMs - readEndMs),
|
|
@@ -1579,6 +1972,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1579
1972
|
return c.json({
|
|
1580
1973
|
session: {
|
|
1581
1974
|
...sessionMetadata,
|
|
1975
|
+
projectId: effectiveProjectId,
|
|
1582
1976
|
ownership,
|
|
1583
1977
|
contextUsage,
|
|
1584
1978
|
customTitle: metadata?.customTitle,
|
|
@@ -1590,6 +1984,12 @@ export function createSessionsRoutes(deps) {
|
|
|
1590
1984
|
heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
|
|
1591
1985
|
heartbeatTurnText: metadata?.heartbeatTurnText,
|
|
1592
1986
|
heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes,
|
|
1987
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
1988
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
1989
|
+
workingProjectId: metadata?.workingProjectId,
|
|
1990
|
+
transcriptProjectId: metadata?.transcriptProjectId,
|
|
1991
|
+
workstreamId: metadata?.workstreamId,
|
|
1992
|
+
transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
|
|
1593
1993
|
// Model comes from the session reader (extracted from JSONL)
|
|
1594
1994
|
model: session.model,
|
|
1595
1995
|
lastSeenAt,
|
|
@@ -1597,9 +1997,11 @@ export function createSessionsRoutes(deps) {
|
|
|
1597
1997
|
},
|
|
1598
1998
|
messages: session.messages,
|
|
1599
1999
|
ownership,
|
|
2000
|
+
providerRuntimeStatus,
|
|
1600
2001
|
pendingInputRequest,
|
|
1601
2002
|
slashCommands,
|
|
1602
2003
|
...(paginationInfo && { pagination: paginationInfo }),
|
|
2004
|
+
...(deferredMessages.length > 0 ? { deferredMessages } : {}),
|
|
1603
2005
|
});
|
|
1604
2006
|
});
|
|
1605
2007
|
// POST /api/projects/:projectId/sessions - Start new session
|
|
@@ -1632,6 +2034,10 @@ export function createSessionsRoutes(deps) {
|
|
|
1632
2034
|
if (helperSettings.error) {
|
|
1633
2035
|
return c.json({ error: helperSettings.error }, 400);
|
|
1634
2036
|
}
|
|
2037
|
+
const workstreamTarget = resolveLaunchWorkstreamTarget(project, body.workstreamId);
|
|
2038
|
+
if ("error" in workstreamTarget) {
|
|
2039
|
+
return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
|
|
2040
|
+
}
|
|
1635
2041
|
const serverTimestamp = Date.now();
|
|
1636
2042
|
const userMessage = {
|
|
1637
2043
|
text: body.message,
|
|
@@ -1644,7 +2050,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1644
2050
|
};
|
|
1645
2051
|
// Convert thinking option to SDK config
|
|
1646
2052
|
const { thinking, effort } = body.thinking
|
|
1647
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2053
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
1648
2054
|
: { thinking: undefined, effort: undefined };
|
|
1649
2055
|
// Convert model option (undefined or "default" means use CLI default)
|
|
1650
2056
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
@@ -1656,7 +2062,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1656
2062
|
model: body.model,
|
|
1657
2063
|
serviceTier,
|
|
1658
2064
|
});
|
|
1659
|
-
const result = await deps.supervisor.startSession(
|
|
2065
|
+
const result = await deps.supervisor.startSession(workstreamTarget.projectPath, userMessage, body.mode, {
|
|
1660
2066
|
model,
|
|
1661
2067
|
serviceTier,
|
|
1662
2068
|
thinking,
|
|
@@ -1666,8 +2072,12 @@ export function createSessionsRoutes(deps) {
|
|
|
1666
2072
|
globalInstructions: getGlobalInstructions(),
|
|
1667
2073
|
permissions: body.permissions,
|
|
1668
2074
|
recapMode: helperSettings.recapMode,
|
|
2075
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1669
2076
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1670
2077
|
helperSideModel: helperSettings.helperSideModel,
|
|
2078
|
+
}, {
|
|
2079
|
+
projectId: project.id,
|
|
2080
|
+
workstreamId: workstreamTarget.workstreamId,
|
|
1671
2081
|
});
|
|
1672
2082
|
// Check if queue is full
|
|
1673
2083
|
if (isQueueFullResponse(result)) {
|
|
@@ -1677,13 +2087,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1677
2087
|
if (isQueuedResponse(result)) {
|
|
1678
2088
|
return c.json({ ...result, serverTimestamp }, 202); // 202 Accepted - queued for processing
|
|
1679
2089
|
}
|
|
1680
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message);
|
|
2090
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds, workstreamTarget.workstreamId);
|
|
1681
2091
|
return c.json({
|
|
1682
2092
|
sessionId: result.sessionId,
|
|
1683
2093
|
processId: result.id,
|
|
1684
2094
|
projectId: result.projectId,
|
|
1685
2095
|
permissionMode: result.permissionMode,
|
|
1686
2096
|
modeVersion: result.modeVersion,
|
|
2097
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1687
2098
|
serverTimestamp,
|
|
1688
2099
|
});
|
|
1689
2100
|
});
|
|
@@ -1715,14 +2126,18 @@ export function createSessionsRoutes(deps) {
|
|
|
1715
2126
|
if (helperSettings.error) {
|
|
1716
2127
|
return c.json({ error: helperSettings.error }, 400);
|
|
1717
2128
|
}
|
|
2129
|
+
const workstreamTarget = resolveLaunchWorkstreamTarget(project, body.workstreamId);
|
|
2130
|
+
if ("error" in workstreamTarget) {
|
|
2131
|
+
return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
|
|
2132
|
+
}
|
|
1718
2133
|
// Convert thinking option to SDK config
|
|
1719
2134
|
const { thinking, effort } = body.thinking
|
|
1720
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2135
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
1721
2136
|
: { thinking: undefined, effort: undefined };
|
|
1722
2137
|
// Convert model option (undefined or "default" means use CLI default)
|
|
1723
2138
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1724
2139
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
1725
|
-
const result = await deps.supervisor.createSession(
|
|
2140
|
+
const result = await deps.supervisor.createSession(workstreamTarget.projectPath, body.mode, {
|
|
1726
2141
|
model,
|
|
1727
2142
|
serviceTier,
|
|
1728
2143
|
thinking,
|
|
@@ -1732,8 +2147,12 @@ export function createSessionsRoutes(deps) {
|
|
|
1732
2147
|
globalInstructions: getGlobalInstructions(),
|
|
1733
2148
|
permissions: body.permissions,
|
|
1734
2149
|
recapMode: helperSettings.recapMode,
|
|
2150
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1735
2151
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1736
2152
|
helperSideModel: helperSettings.helperSideModel,
|
|
2153
|
+
}, {
|
|
2154
|
+
projectId: project.id,
|
|
2155
|
+
workstreamId: workstreamTarget.workstreamId,
|
|
1737
2156
|
});
|
|
1738
2157
|
// Check if queue is full
|
|
1739
2158
|
if (isQueueFullResponse(result)) {
|
|
@@ -1743,13 +2162,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1743
2162
|
if (isQueuedResponse(result)) {
|
|
1744
2163
|
return c.json({ ...result, serverTimestamp: Date.now() }, 202); // 202 Accepted - queued for processing
|
|
1745
2164
|
}
|
|
1746
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor);
|
|
2165
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, undefined, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds, workstreamTarget.workstreamId);
|
|
1747
2166
|
return c.json({
|
|
1748
2167
|
sessionId: result.sessionId,
|
|
1749
2168
|
processId: result.id,
|
|
1750
2169
|
projectId: result.projectId,
|
|
1751
2170
|
permissionMode: result.permissionMode,
|
|
1752
2171
|
modeVersion: result.modeVersion,
|
|
2172
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1753
2173
|
serverTimestamp: Date.now(),
|
|
1754
2174
|
});
|
|
1755
2175
|
});
|
|
@@ -1785,7 +2205,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1785
2205
|
metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
|
|
1786
2206
|
};
|
|
1787
2207
|
const { thinking, effort } = body.thinking
|
|
1788
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2208
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
1789
2209
|
: { thinking: undefined, effort: undefined };
|
|
1790
2210
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1791
2211
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
@@ -1799,6 +2219,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1799
2219
|
globalInstructions: getGlobalInstructions(),
|
|
1800
2220
|
permissions: body.permissions,
|
|
1801
2221
|
recapMode: helperSettings.recapMode,
|
|
2222
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1802
2223
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1803
2224
|
helperSideModel: helperSettings.helperSideModel,
|
|
1804
2225
|
});
|
|
@@ -1808,13 +2229,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1808
2229
|
if (isQueuedResponse(result)) {
|
|
1809
2230
|
return c.json({ ...result, serverTimestamp: Date.now() }, 202);
|
|
1810
2231
|
}
|
|
1811
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message);
|
|
2232
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds);
|
|
1812
2233
|
return c.json({
|
|
1813
2234
|
sessionId: result.sessionId,
|
|
1814
2235
|
processId: result.id,
|
|
1815
2236
|
projectId: result.projectId,
|
|
1816
2237
|
permissionMode: result.permissionMode,
|
|
1817
2238
|
modeVersion: result.modeVersion,
|
|
2239
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1818
2240
|
serverTimestamp,
|
|
1819
2241
|
});
|
|
1820
2242
|
});
|
|
@@ -1837,7 +2259,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1837
2259
|
}
|
|
1838
2260
|
const projectPath = await ensureDetachedProjectPath(executor);
|
|
1839
2261
|
const { thinking, effort } = body.thinking
|
|
1840
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2262
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
1841
2263
|
: { thinking: undefined, effort: undefined };
|
|
1842
2264
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1843
2265
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
@@ -1851,6 +2273,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1851
2273
|
globalInstructions: getGlobalInstructions(),
|
|
1852
2274
|
permissions: body.permissions,
|
|
1853
2275
|
recapMode: helperSettings.recapMode,
|
|
2276
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1854
2277
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1855
2278
|
helperSideModel: helperSettings.helperSideModel,
|
|
1856
2279
|
});
|
|
@@ -1860,13 +2283,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1860
2283
|
if (isQueuedResponse(result)) {
|
|
1861
2284
|
return c.json({ ...result, serverTimestamp: Date.now() }, 202);
|
|
1862
2285
|
}
|
|
1863
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor);
|
|
2286
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, undefined, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds);
|
|
1864
2287
|
return c.json({
|
|
1865
2288
|
sessionId: result.sessionId,
|
|
1866
2289
|
processId: result.id,
|
|
1867
2290
|
projectId: result.projectId,
|
|
1868
2291
|
permissionMode: result.permissionMode,
|
|
1869
2292
|
modeVersion: result.modeVersion,
|
|
2293
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1870
2294
|
serverTimestamp: Date.now(),
|
|
1871
2295
|
});
|
|
1872
2296
|
});
|
|
@@ -1901,6 +2325,11 @@ export function createSessionsRoutes(deps) {
|
|
|
1901
2325
|
if (helperSettings.error) {
|
|
1902
2326
|
return c.json({ error: helperSettings.error }, 400);
|
|
1903
2327
|
}
|
|
2328
|
+
const parsedResumeMode = parseOptionalResumeMode(body.resumeMode);
|
|
2329
|
+
if (parsedResumeMode.error) {
|
|
2330
|
+
return c.json({ error: parsedResumeMode.error }, 400);
|
|
2331
|
+
}
|
|
2332
|
+
const resumeMode = parsedResumeMode.resumeMode ?? "full";
|
|
1904
2333
|
const serverTimestamp = Date.now();
|
|
1905
2334
|
const userMessage = {
|
|
1906
2335
|
text: body.message,
|
|
@@ -1913,10 +2342,16 @@ export function createSessionsRoutes(deps) {
|
|
|
1913
2342
|
};
|
|
1914
2343
|
// Convert thinking option to SDK config
|
|
1915
2344
|
const { thinking, effort } = body.thinking
|
|
1916
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2345
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
1917
2346
|
: { thinking: undefined, effort: undefined };
|
|
1918
|
-
// Convert model option (undefined or "default" means use CLI default)
|
|
1919
|
-
|
|
2347
|
+
// Convert model option (undefined or "default" means use CLI default). When
|
|
2348
|
+
// the client sends no model (e.g. resume after a server restart), recover the
|
|
2349
|
+
// YA model id persisted at launch so the process keeps its requested alias
|
|
2350
|
+
// and per-model settings stay keyed by it. See topics/provider-abstraction.md.
|
|
2351
|
+
const requestedModel = body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId);
|
|
2352
|
+
const model = requestedModel && requestedModel !== "default"
|
|
2353
|
+
? requestedModel
|
|
2354
|
+
: undefined;
|
|
1920
2355
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
1921
2356
|
// Use client-provided executor, falling back to saved executor from metadata.
|
|
1922
2357
|
let executor = parsedBodyExecutor.executor;
|
|
@@ -1945,23 +2380,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1945
2380
|
await deps.sessionMetadataService.setExecutor(sessionId, executor);
|
|
1946
2381
|
}
|
|
1947
2382
|
}
|
|
1948
|
-
const globalInstructions =
|
|
2383
|
+
const globalInstructions = getGlobalInstructions();
|
|
1949
2384
|
// Look up the session's original provider so we resume with the correct one
|
|
1950
2385
|
// (e.g., claude-ollama sessions need the Ollama provider, not default Claude).
|
|
1951
2386
|
// Check metadata first (explicitly saved on creation), then fall back to reader.
|
|
1952
2387
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
1953
2388
|
let providerName = metadataProvider ?? body.provider;
|
|
1954
2389
|
if (!providerName) {
|
|
1955
|
-
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, {
|
|
1956
|
-
readerFactory: deps.readerFactory,
|
|
1957
|
-
codexSessionsDir: deps.codexSessionsDir,
|
|
1958
|
-
codexReaderFactory: deps.codexReaderFactory,
|
|
1959
|
-
geminiSessionsDir: deps.geminiSessionsDir,
|
|
1960
|
-
geminiReaderFactory: deps.geminiReaderFactory,
|
|
1961
|
-
geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
|
|
1962
|
-
grokSessionsDir: deps.grokSessionsDir,
|
|
1963
|
-
grokReaderFactory: deps.grokReaderFactory,
|
|
1964
|
-
}, metadataProvider ?? body.provider);
|
|
2390
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider ?? body.provider, { readMode: "head" });
|
|
1965
2391
|
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
1966
2392
|
providerName =
|
|
1967
2393
|
sessionSummary?.provider ??
|
|
@@ -1969,6 +2395,20 @@ export function createSessionsRoutes(deps) {
|
|
|
1969
2395
|
body.provider ??
|
|
1970
2396
|
project.provider;
|
|
1971
2397
|
}
|
|
2398
|
+
const previousProcess = deps.supervisor.getProcessForSession?.(sessionId);
|
|
2399
|
+
const resumeDiagnostics = {
|
|
2400
|
+
requestedMode: resumeMode,
|
|
2401
|
+
provider: providerName,
|
|
2402
|
+
previousProcess: previousProcess
|
|
2403
|
+
? {
|
|
2404
|
+
processId: previousProcess.id,
|
|
2405
|
+
state: previousProcess.state.type,
|
|
2406
|
+
provider: previousProcess.provider,
|
|
2407
|
+
supportsDynamicCommands: previousProcess.supportsDynamicCommands,
|
|
2408
|
+
}
|
|
2409
|
+
: null,
|
|
2410
|
+
};
|
|
2411
|
+
let resumeSessionAt;
|
|
1972
2412
|
if (isClaudeSdkProviderName(providerName)) {
|
|
1973
2413
|
let blocker = null;
|
|
1974
2414
|
try {
|
|
@@ -1983,7 +2423,21 @@ export function createSessionsRoutes(deps) {
|
|
|
1983
2423
|
error: error instanceof Error ? error.message : String(error),
|
|
1984
2424
|
}, "Failed to check Claude session for resume-blocking API error");
|
|
1985
2425
|
}
|
|
1986
|
-
if (blocker) {
|
|
2426
|
+
if (blocker?.resumeAtMessageId) {
|
|
2427
|
+
// Recoverable: resume up to the last good assistant message,
|
|
2428
|
+
// dropping the API-error tail, instead of forcing a handoff.
|
|
2429
|
+
resumeSessionAt = blocker.resumeAtMessageId;
|
|
2430
|
+
getLogger().info({
|
|
2431
|
+
event: "claude_resume_truncated_after_api_error",
|
|
2432
|
+
sessionId,
|
|
2433
|
+
projectId,
|
|
2434
|
+
providerName,
|
|
2435
|
+
messageId: blocker.messageId,
|
|
2436
|
+
apiErrorStatus: blocker.apiErrorStatus,
|
|
2437
|
+
resumeSessionAt,
|
|
2438
|
+
}, "Resuming Claude session before SDK API-error tail");
|
|
2439
|
+
}
|
|
2440
|
+
else if (blocker) {
|
|
1987
2441
|
getLogger().warn({
|
|
1988
2442
|
event: "claude_resume_blocked_after_api_error",
|
|
1989
2443
|
sessionId,
|
|
@@ -1995,36 +2449,243 @@ export function createSessionsRoutes(deps) {
|
|
|
1995
2449
|
return c.json({
|
|
1996
2450
|
error: blocker.error,
|
|
1997
2451
|
recovery: blocker.recovery,
|
|
2452
|
+
resume: {
|
|
2453
|
+
...resumeDiagnostics,
|
|
2454
|
+
blockedReason: "claude-api-error-tail",
|
|
2455
|
+
},
|
|
1998
2456
|
}, 409);
|
|
1999
2457
|
}
|
|
2000
2458
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2459
|
+
let result;
|
|
2460
|
+
try {
|
|
2461
|
+
result = await deps.supervisor.resumeSession(sessionId, project.path, userMessage, body.mode, {
|
|
2462
|
+
model,
|
|
2463
|
+
serviceTier,
|
|
2464
|
+
thinking,
|
|
2465
|
+
effort,
|
|
2466
|
+
providerName,
|
|
2467
|
+
executor,
|
|
2468
|
+
globalInstructions,
|
|
2469
|
+
permissions: body.permissions,
|
|
2470
|
+
recapMode: helperSettings.recapMode,
|
|
2471
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ??
|
|
2472
|
+
deps.sessionMetadataService?.getRecapAfterSeconds?.(sessionId),
|
|
2473
|
+
// Body value wins; otherwise recover the per-session preference from
|
|
2474
|
+
// metadata so a body-less resume does not default back to native.
|
|
2475
|
+
promptSuggestionMode: helperSettings.promptSuggestionMode ??
|
|
2476
|
+
deps.sessionMetadataService?.getPromptSuggestionMode?.(sessionId),
|
|
2477
|
+
helperSideModel: helperSettings.helperSideModel,
|
|
2478
|
+
resumeMode,
|
|
2479
|
+
resumeSessionAt,
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
catch (error) {
|
|
2483
|
+
if (error instanceof ResumeCompactionError) {
|
|
2484
|
+
getLogger().warn({
|
|
2485
|
+
event: "resume_compaction_failed",
|
|
2486
|
+
sessionId,
|
|
2487
|
+
projectId,
|
|
2488
|
+
providerName,
|
|
2489
|
+
attempt: error.attempt,
|
|
2490
|
+
}, "Compact-first resume failed");
|
|
2491
|
+
return c.json({
|
|
2492
|
+
error: error.message,
|
|
2493
|
+
recovery: error.recovery,
|
|
2494
|
+
resume: {
|
|
2495
|
+
...resumeDiagnostics,
|
|
2496
|
+
compaction: error.attempt,
|
|
2497
|
+
},
|
|
2498
|
+
}, 409);
|
|
2499
|
+
}
|
|
2500
|
+
throw error;
|
|
2501
|
+
}
|
|
2014
2502
|
// Check if queue is full
|
|
2015
2503
|
if (isQueueFullResponse(result)) {
|
|
2016
2504
|
return c.json({ error: "Queue is full", maxQueueSize: result.maxQueueSize }, 503);
|
|
2017
2505
|
}
|
|
2018
2506
|
// Check if request was queued
|
|
2019
2507
|
if (isQueuedResponse(result)) {
|
|
2020
|
-
return c.json({
|
|
2508
|
+
return c.json({
|
|
2509
|
+
...result,
|
|
2510
|
+
serverTimestamp: Date.now(),
|
|
2511
|
+
resume: { ...resumeDiagnostics, outcome: "queued" },
|
|
2512
|
+
}, 202); // 202 Accepted - queued for processing
|
|
2021
2513
|
}
|
|
2022
2514
|
return c.json({
|
|
2023
2515
|
processId: result.id,
|
|
2024
2516
|
permissionMode: result.permissionMode,
|
|
2025
2517
|
modeVersion: result.modeVersion,
|
|
2518
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
2026
2519
|
serverTimestamp,
|
|
2520
|
+
resume: {
|
|
2521
|
+
...resumeDiagnostics,
|
|
2522
|
+
outcome: "started",
|
|
2523
|
+
compaction: resumeMode === "compact-first" ? { status: "completed" } : undefined,
|
|
2524
|
+
},
|
|
2525
|
+
});
|
|
2526
|
+
});
|
|
2527
|
+
// POST /api/projects/:projectId/sessions/:sessionId/reactivate
|
|
2528
|
+
// Spawn a live harness process bound to the session WITHOUT delivering a turn,
|
|
2529
|
+
// so the client can read live process state (model options) before messaging.
|
|
2530
|
+
// Idempotent: returns the existing process if the session is already owned.
|
|
2531
|
+
routes.post("/projects/:projectId/sessions/:sessionId/reactivate", async (c) => {
|
|
2532
|
+
const projectId = c.req.param("projectId");
|
|
2533
|
+
const sessionId = c.req.param("sessionId");
|
|
2534
|
+
if (!isUrlProjectId(projectId)) {
|
|
2535
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2536
|
+
}
|
|
2537
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2538
|
+
if (!project) {
|
|
2539
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2540
|
+
}
|
|
2541
|
+
// Already owned by a live process - return it (idempotent).
|
|
2542
|
+
const existing = deps.supervisor.getProcessForSession(sessionId);
|
|
2543
|
+
if (existing) {
|
|
2544
|
+
return c.json({
|
|
2545
|
+
processId: existing.id,
|
|
2546
|
+
permissionMode: existing.permissionMode,
|
|
2547
|
+
modeVersion: existing.modeVersion,
|
|
2548
|
+
recapAfterSeconds: existing.recapAfterSeconds,
|
|
2549
|
+
serverTimestamp: Date.now(),
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
// Body is optional - allows overriding mode/model/executor.
|
|
2553
|
+
let body = {};
|
|
2554
|
+
try {
|
|
2555
|
+
body = await c.req.json();
|
|
2556
|
+
}
|
|
2557
|
+
catch {
|
|
2558
|
+
// No body - resume with the session's saved settings.
|
|
2559
|
+
}
|
|
2560
|
+
const parsedBodyExecutor = parseOptionalExecutor(body.executor);
|
|
2561
|
+
if (parsedBodyExecutor.error) {
|
|
2562
|
+
return c.json({ error: parsedBodyExecutor.error }, 400);
|
|
2563
|
+
}
|
|
2564
|
+
const helperSettings = parseHelperSettings(body);
|
|
2565
|
+
if (helperSettings.error) {
|
|
2566
|
+
return c.json({ error: helperSettings.error }, 400);
|
|
2567
|
+
}
|
|
2568
|
+
// Resolve provider/model/executor from the YA launch record (persisted on
|
|
2569
|
+
// launch) so reactivation resumes with the correct backend and model.
|
|
2570
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2571
|
+
const providerName = metadata?.provider ??
|
|
2572
|
+
body.provider ??
|
|
2573
|
+
project.provider;
|
|
2574
|
+
const executor = parsedBodyExecutor.executor ?? metadata?.executor;
|
|
2575
|
+
// Prefer an explicit override, else the YA id the session was launched
|
|
2576
|
+
// with; "default" means let the backend pick (pass undefined).
|
|
2577
|
+
const rawModel = body.model && body.model !== "default"
|
|
2578
|
+
? body.model
|
|
2579
|
+
: metadata?.requestedModel;
|
|
2580
|
+
const model = rawModel && rawModel !== "default" ? rawModel : undefined;
|
|
2581
|
+
const { thinking, effort } = body.thinking
|
|
2582
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
2583
|
+
: { thinking: undefined, effort: undefined };
|
|
2584
|
+
let process;
|
|
2585
|
+
try {
|
|
2586
|
+
process = await deps.supervisor.reactivateSession(project.path, sessionId, body.mode, {
|
|
2587
|
+
model,
|
|
2588
|
+
thinking,
|
|
2589
|
+
effort,
|
|
2590
|
+
providerName,
|
|
2591
|
+
executor,
|
|
2592
|
+
globalInstructions: getGlobalInstructions(),
|
|
2593
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ?? metadata?.recapAfterSeconds,
|
|
2594
|
+
recapMode: helperSettings.recapMode ?? metadata?.recapMode,
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
return c.json({
|
|
2599
|
+
error: error instanceof Error
|
|
2600
|
+
? error.message
|
|
2601
|
+
: "Failed to reactivate session",
|
|
2602
|
+
}, 503);
|
|
2603
|
+
}
|
|
2604
|
+
// Keep the launch record current (provider/executor) for this session.
|
|
2605
|
+
await persistLaunchMetadata(sessionId, providerName, executor);
|
|
2606
|
+
return c.json({
|
|
2607
|
+
processId: process.id,
|
|
2608
|
+
permissionMode: process.permissionMode,
|
|
2609
|
+
modeVersion: process.modeVersion,
|
|
2610
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
2611
|
+
serverTimestamp: Date.now(),
|
|
2612
|
+
});
|
|
2613
|
+
});
|
|
2614
|
+
// POST /api/projects/:projectId/sessions/:sessionId/recap
|
|
2615
|
+
// Away-recap trigger, keyed by session (not process) so it survives a server
|
|
2616
|
+
// restart that killed the process. A live process recaps directly in its own
|
|
2617
|
+
// mode; a cold fork-mode session is revived — without ever preempting a live
|
|
2618
|
+
// worker — and recapped from its on-disk transcript. See topics/fork-recap.md.
|
|
2619
|
+
routes.post("/projects/:projectId/sessions/:sessionId/recap", async (c) => {
|
|
2620
|
+
const projectId = c.req.param("projectId");
|
|
2621
|
+
const sessionId = c.req.param("sessionId");
|
|
2622
|
+
if (!isUrlProjectId(projectId)) {
|
|
2623
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2624
|
+
}
|
|
2625
|
+
let sinceMs = null;
|
|
2626
|
+
try {
|
|
2627
|
+
const body = await c.req.json();
|
|
2628
|
+
if (typeof body.hiddenSinceMs === "number" &&
|
|
2629
|
+
Number.isFinite(body.hiddenSinceMs)) {
|
|
2630
|
+
sinceMs = body.hiddenSinceMs;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
catch {
|
|
2634
|
+
// Empty body is accepted.
|
|
2635
|
+
}
|
|
2636
|
+
// Live process: recap directly in whatever mode it runs.
|
|
2637
|
+
const live = deps.supervisor.getProcessForSession(sessionId);
|
|
2638
|
+
if (live && !live.isTerminated) {
|
|
2639
|
+
const result = await deps.supervisor.requestRecap(live.id, { sinceMs });
|
|
2640
|
+
return c.json(result);
|
|
2641
|
+
}
|
|
2642
|
+
// Cold session: only fork mode can recap from the on-disk transcript —
|
|
2643
|
+
// side-session/native recaps need in-memory recent text a revived process
|
|
2644
|
+
// lacks. Other modes simply skip until the session is live again.
|
|
2645
|
+
const recapMode = deps.sessionMetadataService?.getRecapMode(sessionId);
|
|
2646
|
+
if (recapMode !== "fork") {
|
|
2647
|
+
return c.json({
|
|
2648
|
+
supported: true,
|
|
2649
|
+
emitted: false,
|
|
2650
|
+
reason: "recap requires a live process for this recap mode",
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2654
|
+
if (!project) {
|
|
2655
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2656
|
+
}
|
|
2657
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2658
|
+
const providerName = metadata?.provider ?? project.provider;
|
|
2659
|
+
const rawModel = metadata?.requestedModel;
|
|
2660
|
+
const model = rawModel && rawModel !== "default" ? rawModel : undefined;
|
|
2661
|
+
let process;
|
|
2662
|
+
try {
|
|
2663
|
+
process = await deps.supervisor.reactivateSession(project.path, sessionId, undefined, {
|
|
2664
|
+
model,
|
|
2665
|
+
providerName,
|
|
2666
|
+
executor: metadata?.executor,
|
|
2667
|
+
globalInstructions: getGlobalInstructions(),
|
|
2668
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
2669
|
+
recapMode: "fork",
|
|
2670
|
+
}, { preempt: false });
|
|
2671
|
+
}
|
|
2672
|
+
catch (error) {
|
|
2673
|
+
// At capacity with no idle worker to drop (background recaps never
|
|
2674
|
+
// preempt), or reactivation failed — skip the recap rather than error.
|
|
2675
|
+
const atCapacity = error instanceof Error && error.message.includes("worker capacity");
|
|
2676
|
+
return c.json({
|
|
2677
|
+
supported: true,
|
|
2678
|
+
emitted: false,
|
|
2679
|
+
reason: atCapacity
|
|
2680
|
+
? "recap skipped: at worker capacity"
|
|
2681
|
+
: "recap skipped: session could not be revived",
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
const result = await deps.supervisor.requestRecap(process.id, {
|
|
2685
|
+
sinceMs,
|
|
2686
|
+
revived: true,
|
|
2027
2687
|
});
|
|
2688
|
+
return c.json(result);
|
|
2028
2689
|
});
|
|
2029
2690
|
// POST /api/projects/:projectId/sessions/:sessionId/restart
|
|
2030
2691
|
// Start a fresh session from a bounded handoff, then terminate the old YA-owned process.
|
|
@@ -2061,10 +2722,18 @@ export function createSessionsRoutes(deps) {
|
|
|
2061
2722
|
}
|
|
2062
2723
|
executor = parsedSavedExecutor.executor;
|
|
2063
2724
|
}
|
|
2725
|
+
if (body.restartMode !== undefined &&
|
|
2726
|
+
body.restartMode !== "handoff" &&
|
|
2727
|
+
body.restartMode !== "fork") {
|
|
2728
|
+
return c.json({ error: 'restartMode must be "handoff" or "fork"' }, 400);
|
|
2729
|
+
}
|
|
2730
|
+
const restartMode = body.restartMode ?? "handoff";
|
|
2064
2731
|
const oldProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
2065
2732
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
2066
2733
|
const preferredSourceProvider = metadataProvider ?? oldProcess?.provider ?? project.provider;
|
|
2067
|
-
|
|
2734
|
+
// Fork copies the transcript as-is; pre-restart compaction is a
|
|
2735
|
+
// handoff-only mitigation.
|
|
2736
|
+
const compactAttempt = restartMode === "fork" ? undefined : await tryRestartCompact(oldProcess);
|
|
2068
2737
|
const oldProcessInterrupted = await interruptOldProcessForHandoff(oldProcess);
|
|
2069
2738
|
const sourceSession = await loadRestartSourceSession(project, sessionId, projectId, preferredSourceProvider, oldProcess);
|
|
2070
2739
|
if (!sourceSession) {
|
|
@@ -2073,6 +2742,105 @@ export function createSessionsRoutes(deps) {
|
|
|
2073
2742
|
const sourceProvider = sourceSession.provider ?? preferredSourceProvider;
|
|
2074
2743
|
const providerName = body.provider ?? sourceProvider;
|
|
2075
2744
|
const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2745
|
+
const { thinking, effort } = body.thinking
|
|
2746
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
2747
|
+
: { thinking: undefined, effort: undefined };
|
|
2748
|
+
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
2749
|
+
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
2750
|
+
if (restartMode === "fork") {
|
|
2751
|
+
if (body.provider && body.provider !== sourceProvider) {
|
|
2752
|
+
return c.json({
|
|
2753
|
+
error: "Fork keeps the source provider; omit provider or match the source session",
|
|
2754
|
+
}, 400);
|
|
2755
|
+
}
|
|
2756
|
+
if (!deps.supervisor.supportsForkSession(sourceProvider)) {
|
|
2757
|
+
return c.json({ error: `${sourceProvider} does not support transcript fork` }, 400);
|
|
2758
|
+
}
|
|
2759
|
+
const forkTitle = deriveForkTitle({
|
|
2760
|
+
preferredTitle: originalMetadata?.customTitle,
|
|
2761
|
+
sourceSession,
|
|
2762
|
+
});
|
|
2763
|
+
let fork;
|
|
2764
|
+
try {
|
|
2765
|
+
fork = await deps.supervisor.forkSession({
|
|
2766
|
+
sessionId,
|
|
2767
|
+
projectPath: project.path,
|
|
2768
|
+
providerName: sourceProvider,
|
|
2769
|
+
upToMessageId: body.forkUpToMessageId,
|
|
2770
|
+
title: forkTitle,
|
|
2771
|
+
});
|
|
2772
|
+
}
|
|
2773
|
+
catch (error) {
|
|
2774
|
+
getLogger().warn({
|
|
2775
|
+
event: "restart_fork_failed",
|
|
2776
|
+
sessionId,
|
|
2777
|
+
projectId,
|
|
2778
|
+
sourceProvider,
|
|
2779
|
+
upToMessageId: body.forkUpToMessageId,
|
|
2780
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2781
|
+
}, "Transcript fork failed");
|
|
2782
|
+
return c.json({
|
|
2783
|
+
error: error instanceof Error ? error.message : "Transcript fork failed",
|
|
2784
|
+
}, 500);
|
|
2785
|
+
}
|
|
2786
|
+
const forkMessage = body.reason?.trim() || "Continue from this fork point.";
|
|
2787
|
+
const result = await deps.supervisor.resumeSession(fork.sessionId, project.path, { text: forkMessage, mode: body.mode }, body.mode, {
|
|
2788
|
+
model,
|
|
2789
|
+
serviceTier,
|
|
2790
|
+
thinking,
|
|
2791
|
+
effort,
|
|
2792
|
+
providerName: sourceProvider,
|
|
2793
|
+
executor,
|
|
2794
|
+
globalInstructions: getGlobalInstructions(),
|
|
2795
|
+
permissions: body.permissions,
|
|
2796
|
+
recapMode: helperSettings.recapMode,
|
|
2797
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ??
|
|
2798
|
+
originalMetadata?.recapAfterSeconds,
|
|
2799
|
+
// Inherit the source session's preference unless the body overrides.
|
|
2800
|
+
promptSuggestionMode: helperSettings.promptSuggestionMode ??
|
|
2801
|
+
originalMetadata?.promptSuggestionMode,
|
|
2802
|
+
helperSideModel: helperSettings.helperSideModel,
|
|
2803
|
+
});
|
|
2804
|
+
if (isQueueFullResponse(result)) {
|
|
2805
|
+
return c.json({ error: "Queue is full", maxQueueSize: result.maxQueueSize }, 503);
|
|
2806
|
+
}
|
|
2807
|
+
if (isQueuedResponse(result)) {
|
|
2808
|
+
deps.supervisor.cancelQueuedRequest(result.queueId);
|
|
2809
|
+
return c.json({
|
|
2810
|
+
error: "Fork could not start immediately; old process was left running",
|
|
2811
|
+
}, 503);
|
|
2812
|
+
}
|
|
2813
|
+
await persistLaunchMetadata(result.sessionId, sourceProvider, executor, undefined, body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId), result.promptSuggestionMode, result.recapAfterSeconds);
|
|
2814
|
+
if (deps.sessionMetadataService) {
|
|
2815
|
+
await deps.sessionMetadataService.updateMetadata(result.sessionId, {
|
|
2816
|
+
title: forkTitle,
|
|
2817
|
+
});
|
|
2818
|
+
deps.eventBus?.emit({
|
|
2819
|
+
type: "session-metadata-changed",
|
|
2820
|
+
sessionId: result.sessionId,
|
|
2821
|
+
title: forkTitle,
|
|
2822
|
+
timestamp: new Date().toISOString(),
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
const oldProcessAbortDeferred = abortOldProcessAfterReplacementActivity(oldProcess, result);
|
|
2826
|
+
return c.json({
|
|
2827
|
+
sessionId: result.sessionId,
|
|
2828
|
+
processId: result.id,
|
|
2829
|
+
projectId: result.projectId,
|
|
2830
|
+
provider: result.provider,
|
|
2831
|
+
model: result.resolvedModel ?? result.model,
|
|
2832
|
+
title: forkTitle,
|
|
2833
|
+
permissionMode: result.permissionMode,
|
|
2834
|
+
modeVersion: result.modeVersion,
|
|
2835
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
2836
|
+
restartedFrom: sessionId,
|
|
2837
|
+
forkUpToMessageId: body.forkUpToMessageId,
|
|
2838
|
+
oldProcessId: oldProcess?.id,
|
|
2839
|
+
oldProcessInterrupted,
|
|
2840
|
+
oldProcessAbortDeferred,
|
|
2841
|
+
oldProcessAborted: false,
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2076
2844
|
const handoffTitle = deriveRestartTitle({
|
|
2077
2845
|
preferredTitle: originalMetadata?.customTitle,
|
|
2078
2846
|
sourceSession,
|
|
@@ -2090,11 +2858,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2090
2858
|
omittedCount,
|
|
2091
2859
|
transcript,
|
|
2092
2860
|
});
|
|
2093
|
-
const { thinking, effort } = body.thinking
|
|
2094
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2095
|
-
: { thinking: undefined, effort: undefined };
|
|
2096
|
-
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
2097
|
-
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
2098
2861
|
const result = await deps.supervisor.startSession(project.path, {
|
|
2099
2862
|
text: handoff,
|
|
2100
2863
|
mode: body.mode,
|
|
@@ -2109,7 +2872,11 @@ export function createSessionsRoutes(deps) {
|
|
|
2109
2872
|
globalInstructions: getGlobalInstructions(),
|
|
2110
2873
|
permissions: body.permissions,
|
|
2111
2874
|
recapMode: helperSettings.recapMode,
|
|
2112
|
-
|
|
2875
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ??
|
|
2876
|
+
originalMetadata?.recapAfterSeconds,
|
|
2877
|
+
// Inherit the source session's preference unless the body overrides.
|
|
2878
|
+
promptSuggestionMode: helperSettings.promptSuggestionMode ??
|
|
2879
|
+
originalMetadata?.promptSuggestionMode,
|
|
2113
2880
|
helperSideModel: helperSettings.helperSideModel,
|
|
2114
2881
|
});
|
|
2115
2882
|
if (isQueueFullResponse(result)) {
|
|
@@ -2121,7 +2888,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2121
2888
|
error: "Restart could not start immediately; old process was left running",
|
|
2122
2889
|
}, 503);
|
|
2123
2890
|
}
|
|
2124
|
-
await persistLaunchMetadata(result.sessionId, providerName, executor);
|
|
2891
|
+
await persistLaunchMetadata(result.sessionId, providerName, executor, undefined, body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId), result.promptSuggestionMode, result.recapAfterSeconds);
|
|
2125
2892
|
if (deps.sessionMetadataService) {
|
|
2126
2893
|
await deps.sessionMetadataService.updateMetadata(result.sessionId, {
|
|
2127
2894
|
title: handoffTitle,
|
|
@@ -2143,6 +2910,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2143
2910
|
title: handoffTitle,
|
|
2144
2911
|
permissionMode: result.permissionMode,
|
|
2145
2912
|
modeVersion: result.modeVersion,
|
|
2913
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
2146
2914
|
restartedFrom: sessionId,
|
|
2147
2915
|
oldProcessId: oldProcess?.id,
|
|
2148
2916
|
oldProcessInterrupted,
|
|
@@ -2150,43 +2918,617 @@ export function createSessionsRoutes(deps) {
|
|
|
2150
2918
|
oldProcessAborted: false,
|
|
2151
2919
|
});
|
|
2152
2920
|
});
|
|
2153
|
-
// POST /api/sessions/:sessionId/
|
|
2154
|
-
|
|
2921
|
+
// POST /api/projects/:projectId/sessions/:sessionId/fork
|
|
2922
|
+
// Fork the provider transcript into a new resumable session without
|
|
2923
|
+
// starting a process or sending any message ("fork from here" / rewind).
|
|
2924
|
+
// The forked session opens cold; the next user send resumes it normally.
|
|
2925
|
+
routes.post("/projects/:projectId/sessions/:sessionId/fork", async (c) => {
|
|
2926
|
+
const projectId = c.req.param("projectId");
|
|
2155
2927
|
const sessionId = c.req.param("sessionId");
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
return c.json({ error: "No active process for session" }, 404);
|
|
2928
|
+
if (!isUrlProjectId(projectId)) {
|
|
2929
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2159
2930
|
}
|
|
2160
|
-
|
|
2931
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2932
|
+
if (!project) {
|
|
2933
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2934
|
+
}
|
|
2935
|
+
let body = {};
|
|
2161
2936
|
try {
|
|
2162
2937
|
body = await c.req.json();
|
|
2163
2938
|
}
|
|
2164
2939
|
catch {
|
|
2165
|
-
|
|
2940
|
+
// Body is optional; full-transcript fork.
|
|
2166
2941
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2942
|
+
const upToMessageId = typeof body.upToMessageId === "string" && body.upToMessageId
|
|
2943
|
+
? body.upToMessageId
|
|
2944
|
+
: undefined;
|
|
2945
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
2946
|
+
const sourceProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
2947
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), sourceProcess?.provider ?? metadataProvider, { readMode: "head" });
|
|
2948
|
+
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
2949
|
+
const providerName = sourceProcess?.provider ??
|
|
2950
|
+
metadataProvider ??
|
|
2951
|
+
sessionSummary?.provider ??
|
|
2952
|
+
project.provider;
|
|
2953
|
+
if (!deps.supervisor.supportsForkSession(providerName)) {
|
|
2954
|
+
return c.json({ error: `${providerName} does not support transcript fork` }, 400);
|
|
2169
2955
|
}
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2956
|
+
const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2957
|
+
let baseTitle = normalizeRestartTitleCandidate(originalMetadata?.customTitle);
|
|
2958
|
+
if (!baseTitle) {
|
|
2959
|
+
try {
|
|
2960
|
+
const summary = await deps
|
|
2961
|
+
.readerFactory(project)
|
|
2962
|
+
.getSessionSummary(sessionId, projectId, { readMode: "head" });
|
|
2963
|
+
baseTitle = normalizeRestartTitleCandidate(summary?.title);
|
|
2964
|
+
}
|
|
2965
|
+
catch {
|
|
2966
|
+
// Title is cosmetic; fall through to the provider default.
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
const forkTitle = baseTitle
|
|
2970
|
+
? truncateSessionTitle(/^Fork:/i.test(baseTitle) ? baseTitle : `Fork: ${baseTitle}`)
|
|
2971
|
+
: undefined;
|
|
2972
|
+
let fork;
|
|
2973
|
+
try {
|
|
2974
|
+
fork = await deps.supervisor.forkSession({
|
|
2975
|
+
sessionId,
|
|
2976
|
+
projectPath: project.path,
|
|
2977
|
+
providerName,
|
|
2978
|
+
upToMessageId,
|
|
2979
|
+
title: forkTitle,
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
catch (error) {
|
|
2983
|
+
getLogger().warn({
|
|
2984
|
+
event: "session_fork_failed",
|
|
2985
|
+
sessionId,
|
|
2986
|
+
projectId,
|
|
2987
|
+
providerName,
|
|
2988
|
+
upToMessageId,
|
|
2989
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2990
|
+
}, "Transcript fork failed");
|
|
2182
2991
|
return c.json({
|
|
2183
|
-
error: "
|
|
2184
|
-
|
|
2185
|
-
}, 410); // 410 Gone
|
|
2992
|
+
error: error instanceof Error ? error.message : "Transcript fork failed",
|
|
2993
|
+
}, 500);
|
|
2186
2994
|
}
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2189
|
-
|
|
2995
|
+
const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId)).executor;
|
|
2996
|
+
await persistLaunchMetadata(fork.sessionId, providerName, savedExecutor, undefined, deps.sessionMetadataService?.getRequestedModel(sessionId), originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
|
|
2997
|
+
if (deps.sessionMetadataService) {
|
|
2998
|
+
await deps.sessionMetadataService.updateMetadata(fork.sessionId, {
|
|
2999
|
+
title: forkTitle,
|
|
3000
|
+
parentSessionId: sessionId,
|
|
3001
|
+
});
|
|
3002
|
+
deps.eventBus?.emit({
|
|
3003
|
+
type: "session-metadata-changed",
|
|
3004
|
+
sessionId: fork.sessionId,
|
|
3005
|
+
title: forkTitle,
|
|
3006
|
+
parentSessionId: sessionId,
|
|
3007
|
+
timestamp: new Date().toISOString(),
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
return c.json({
|
|
3011
|
+
sessionId: fork.sessionId,
|
|
3012
|
+
projectId,
|
|
3013
|
+
provider: providerName,
|
|
3014
|
+
title: forkTitle,
|
|
3015
|
+
forkedFrom: sessionId,
|
|
3016
|
+
upToMessageId,
|
|
3017
|
+
});
|
|
3018
|
+
});
|
|
3019
|
+
// POST /api/projects/:projectId/sessions/:sessionId/retitle
|
|
3020
|
+
// Generate a proposed session title through an archived helper fork. This
|
|
3021
|
+
// never updates the source session title; the client must explicitly accept
|
|
3022
|
+
// the returned proposal through the ordinary metadata update route.
|
|
3023
|
+
routes.post("/projects/:projectId/sessions/:sessionId/retitle", async (c) => {
|
|
3024
|
+
const projectId = c.req.param("projectId");
|
|
3025
|
+
const sessionId = c.req.param("sessionId");
|
|
3026
|
+
if (!isUrlProjectId(projectId)) {
|
|
3027
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3028
|
+
}
|
|
3029
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
3030
|
+
if (!project) {
|
|
3031
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
3032
|
+
}
|
|
3033
|
+
if (!deps.sessionMetadataService) {
|
|
3034
|
+
return c.json({ error: "Session metadata service not available" }, 503);
|
|
3035
|
+
}
|
|
3036
|
+
let body = {};
|
|
3037
|
+
try {
|
|
3038
|
+
body = await c.req.json();
|
|
3039
|
+
}
|
|
3040
|
+
catch {
|
|
3041
|
+
// Body is optional.
|
|
3042
|
+
}
|
|
3043
|
+
const currentTitle = typeof body.currentTitle === "string"
|
|
3044
|
+
? body.currentTitle.trim().slice(0, 200)
|
|
3045
|
+
: undefined;
|
|
3046
|
+
let lengthTarget;
|
|
3047
|
+
if (body.lengthTarget !== undefined) {
|
|
3048
|
+
if (typeof body.lengthTarget !== "number" ||
|
|
3049
|
+
!Number.isInteger(body.lengthTarget) ||
|
|
3050
|
+
body.lengthTarget < 20 ||
|
|
3051
|
+
body.lengthTarget > 132) {
|
|
3052
|
+
return c.json({ error: "lengthTarget must be an integer between 20 and 132" }, 400);
|
|
3053
|
+
}
|
|
3054
|
+
lengthTarget = body.lengthTarget;
|
|
3055
|
+
}
|
|
3056
|
+
const metadataProvider = deps.sessionMetadataService.getProvider(sessionId);
|
|
3057
|
+
let sourceProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
3058
|
+
const liveSourceProcess = sourceProcess && !sourceProcess.isTerminated ? sourceProcess : undefined;
|
|
3059
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), liveSourceProcess?.provider ?? metadataProvider, { readMode: "head" });
|
|
3060
|
+
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
3061
|
+
const providerName = liveSourceProcess?.provider ??
|
|
3062
|
+
metadataProvider ??
|
|
3063
|
+
sessionSummary?.provider ??
|
|
3064
|
+
sourceProcess?.provider ??
|
|
3065
|
+
project.provider;
|
|
3066
|
+
if (!deps.supervisor.supportsForkSession(providerName)) {
|
|
3067
|
+
return c.json({ error: `${providerName} does not support transcript fork` }, 400);
|
|
3068
|
+
}
|
|
3069
|
+
const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService.getExecutor(sessionId)).executor;
|
|
3070
|
+
let requestedModel = deps.sessionMetadataService.getRequestedModel(sessionId) ??
|
|
3071
|
+
liveSourceProcess?.model;
|
|
3072
|
+
const sourceMetadata = deps.sessionMetadataService.getMetadata(sessionId);
|
|
3073
|
+
const promptSuggestionMode = sourceMetadata?.promptSuggestionMode;
|
|
3074
|
+
const recapAfterSeconds = sourceMetadata?.recapAfterSeconds;
|
|
3075
|
+
const abortController = new AbortController();
|
|
3076
|
+
const abortFromRequest = () => abortController.abort();
|
|
3077
|
+
if (c.req.raw.signal.aborted) {
|
|
3078
|
+
abortController.abort();
|
|
3079
|
+
}
|
|
3080
|
+
else {
|
|
3081
|
+
c.req.raw.signal.addEventListener("abort", abortFromRequest, {
|
|
3082
|
+
once: true,
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
let generatorSessionId;
|
|
3086
|
+
try {
|
|
3087
|
+
if (!liveSourceProcess) {
|
|
3088
|
+
sourceProcess = await deps.supervisor.reactivateSession(project.path, sessionId, undefined, {
|
|
3089
|
+
model: requestedModel && requestedModel !== "default"
|
|
3090
|
+
? requestedModel
|
|
3091
|
+
: undefined,
|
|
3092
|
+
providerName,
|
|
3093
|
+
executor: savedExecutor,
|
|
3094
|
+
globalInstructions: getGlobalInstructions(),
|
|
3095
|
+
promptSuggestionMode,
|
|
3096
|
+
recapAfterSeconds,
|
|
3097
|
+
});
|
|
3098
|
+
requestedModel = requestedModel ?? sourceProcess.model;
|
|
3099
|
+
}
|
|
3100
|
+
const generator = await deps.supervisor.forkSession({
|
|
3101
|
+
sessionId,
|
|
3102
|
+
projectPath: project.path,
|
|
3103
|
+
providerName,
|
|
3104
|
+
title: "Retitle generator",
|
|
3105
|
+
});
|
|
3106
|
+
generatorSessionId = generator.sessionId;
|
|
3107
|
+
await updateForkSummaryChildMetadata(generator.sessionId, sessionId, "Retitle generator", true);
|
|
3108
|
+
await persistLaunchMetadata(generator.sessionId, providerName, savedExecutor, undefined, requestedModel, promptSuggestionMode, recapAfterSeconds);
|
|
3109
|
+
if (abortController.signal.aborted) {
|
|
3110
|
+
throw new DOMException("Retitle cancelled", "AbortError");
|
|
3111
|
+
}
|
|
3112
|
+
const generated = await deps.supervisor.generateSummary(providerName, {
|
|
3113
|
+
purpose: "session-retitle",
|
|
3114
|
+
strategy: "fork",
|
|
3115
|
+
generatorSessionId: generator.sessionId,
|
|
3116
|
+
cwd: project.path,
|
|
3117
|
+
currentTitle,
|
|
3118
|
+
lengthTarget,
|
|
3119
|
+
signal: abortController.signal,
|
|
3120
|
+
});
|
|
3121
|
+
const title = generatedRetitleCandidate(generated.text);
|
|
3122
|
+
if (!title) {
|
|
3123
|
+
throw new Error("Retitle generation returned empty title");
|
|
3124
|
+
}
|
|
3125
|
+
return c.json({ title, generatorSessionId: generator.sessionId });
|
|
3126
|
+
}
|
|
3127
|
+
catch (error) {
|
|
3128
|
+
const cancelled = abortController.signal.aborted ||
|
|
3129
|
+
(error instanceof DOMException && error.name === "AbortError");
|
|
3130
|
+
const message = error instanceof Error ? error.message : "Retitle generation failed";
|
|
3131
|
+
getLogger().warn({
|
|
3132
|
+
event: cancelled
|
|
3133
|
+
? "session_retitle_cancelled"
|
|
3134
|
+
: "session_retitle_failed",
|
|
3135
|
+
sessionId,
|
|
3136
|
+
projectId,
|
|
3137
|
+
providerName,
|
|
3138
|
+
generatorSessionId,
|
|
3139
|
+
error: message,
|
|
3140
|
+
}, cancelled ? "Session retitle cancelled" : "Session retitle failed");
|
|
3141
|
+
return c.json({ error: message }, cancelled ? 400 : 500);
|
|
3142
|
+
}
|
|
3143
|
+
finally {
|
|
3144
|
+
c.req.raw.signal.removeEventListener("abort", abortFromRequest);
|
|
3145
|
+
abortController.abort();
|
|
3146
|
+
}
|
|
3147
|
+
});
|
|
3148
|
+
// POST /api/projects/:projectId/sessions/:sessionId/fork-summary
|
|
3149
|
+
// Start a server-owned whole-context fork-after-summary job. The response
|
|
3150
|
+
// returns after durable job creation; generation continues independently of
|
|
3151
|
+
// the requesting client connection.
|
|
3152
|
+
routes.post("/projects/:projectId/sessions/:sessionId/fork-summary", async (c) => {
|
|
3153
|
+
const projectId = c.req.param("projectId");
|
|
3154
|
+
const sessionId = c.req.param("sessionId");
|
|
3155
|
+
if (!isUrlProjectId(projectId)) {
|
|
3156
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3157
|
+
}
|
|
3158
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
3159
|
+
if (!project) {
|
|
3160
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
3161
|
+
}
|
|
3162
|
+
let body;
|
|
3163
|
+
try {
|
|
3164
|
+
body = await c.req.json();
|
|
3165
|
+
}
|
|
3166
|
+
catch {
|
|
3167
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3168
|
+
}
|
|
3169
|
+
const sourceMessageId = typeof body.sourceMessageId === "string" && body.sourceMessageId.trim()
|
|
3170
|
+
? body.sourceMessageId.trim()
|
|
3171
|
+
: undefined;
|
|
3172
|
+
if (!sourceMessageId) {
|
|
3173
|
+
return c.json({ error: "sourceMessageId is required" }, 400);
|
|
3174
|
+
}
|
|
3175
|
+
if (body.mode !== undefined &&
|
|
3176
|
+
(typeof body.mode !== "string" ||
|
|
3177
|
+
!ALL_PERMISSION_MODES.includes(body.mode))) {
|
|
3178
|
+
return c.json({ error: "Invalid permission mode" }, 400);
|
|
3179
|
+
}
|
|
3180
|
+
if (body.autoOpenWhenReady !== undefined &&
|
|
3181
|
+
typeof body.autoOpenWhenReady !== "boolean") {
|
|
3182
|
+
return c.json({ error: "autoOpenWhenReady must be a boolean" }, 400);
|
|
3183
|
+
}
|
|
3184
|
+
const mode = body.mode;
|
|
3185
|
+
const instructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3186
|
+
const autoOpenWhenReady = body.autoOpenWhenReady === true;
|
|
3187
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
3188
|
+
const sourceProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
3189
|
+
const providerName = metadataProvider ?? sourceProcess?.provider ?? project.provider;
|
|
3190
|
+
if (!deps.supervisor.supportsForkSession(providerName)) {
|
|
3191
|
+
return c.json({ error: `${providerName} does not support transcript fork` }, 400);
|
|
3192
|
+
}
|
|
3193
|
+
if (!deps.sessionMetadataService) {
|
|
3194
|
+
return c.json({ error: "Session metadata service not available" }, 503);
|
|
3195
|
+
}
|
|
3196
|
+
if (activeForkSummaryJobs.has(sessionId)) {
|
|
3197
|
+
return c.json({ error: "A fork summary is already generating for this session" }, 409);
|
|
3198
|
+
}
|
|
3199
|
+
const sourceSession = sourceProcess
|
|
3200
|
+
? {
|
|
3201
|
+
messages: sdkMessagesToClientMessages(sourceProcess.getMessageHistory()),
|
|
3202
|
+
}
|
|
3203
|
+
: await loadRestartSourceSession(project, sessionId, projectId, providerName);
|
|
3204
|
+
if (!sourceSession) {
|
|
3205
|
+
return c.json({ error: "Source session not found" }, 404);
|
|
3206
|
+
}
|
|
3207
|
+
const boundary = resolveForkAfterBoundary(sourceSession.messages, sourceMessageId, sourceProcess?.state.type === "in-turn" ||
|
|
3208
|
+
sourceProcess?.state.type === "waiting-input");
|
|
3209
|
+
if ("error" in boundary) {
|
|
3210
|
+
return c.json({ error: boundary.error }, boundary.status);
|
|
3211
|
+
}
|
|
3212
|
+
const displayObject = {
|
|
3213
|
+
id: randomUUID(),
|
|
3214
|
+
kind: "fork-summary",
|
|
3215
|
+
createdAt: new Date().toISOString(),
|
|
3216
|
+
placementAfterMessageId: boundary.placementAfterMessageId,
|
|
3217
|
+
sourceMessageId,
|
|
3218
|
+
retainedThroughMessageId: boundary.retainedThroughMessageId,
|
|
3219
|
+
status: "generating",
|
|
3220
|
+
autoOpenWhenReady: autoOpenWhenReady || undefined,
|
|
3221
|
+
};
|
|
3222
|
+
const abortController = new AbortController();
|
|
3223
|
+
activeForkSummaryJobs.set(sessionId, {
|
|
3224
|
+
objectId: displayObject.id,
|
|
3225
|
+
abortController,
|
|
3226
|
+
});
|
|
3227
|
+
try {
|
|
3228
|
+
await deps.sessionMetadataService.addTranscriptDisplayObject(sessionId, displayObject);
|
|
3229
|
+
}
|
|
3230
|
+
catch (error) {
|
|
3231
|
+
activeForkSummaryJobs.delete(sessionId);
|
|
3232
|
+
throw error;
|
|
3233
|
+
}
|
|
3234
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3235
|
+
const originalMetadata = deps.sessionMetadataService.getMetadata(sessionId);
|
|
3236
|
+
const baseTitle = normalizeRestartTitleCandidate(originalMetadata?.customTitle ?? sourceSession.title);
|
|
3237
|
+
const fallbackTitle = baseTitle
|
|
3238
|
+
? truncateSessionTitle(/^Fork:/i.test(baseTitle) ? baseTitle : `Fork: ${baseTitle}`)
|
|
3239
|
+
: undefined;
|
|
3240
|
+
const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService.getExecutor(sessionId)).executor;
|
|
3241
|
+
const requestedModel = deps.sessionMetadataService.getRequestedModel(sessionId) ??
|
|
3242
|
+
sourceProcess?.model;
|
|
3243
|
+
void (async () => {
|
|
3244
|
+
let generatorSessionId;
|
|
3245
|
+
let targetSessionId;
|
|
3246
|
+
let targetTitle;
|
|
3247
|
+
let targetProcessId;
|
|
3248
|
+
let completed = false;
|
|
3249
|
+
try {
|
|
3250
|
+
const generator = await deps.supervisor.forkSession({
|
|
3251
|
+
sessionId,
|
|
3252
|
+
projectPath: project.path,
|
|
3253
|
+
providerName,
|
|
3254
|
+
title: "Fork summary generator",
|
|
3255
|
+
});
|
|
3256
|
+
generatorSessionId = generator.sessionId;
|
|
3257
|
+
await updateForkSummaryChildMetadata(generator.sessionId, sessionId, "Fork summary generator", true);
|
|
3258
|
+
await persistLaunchMetadata(generator.sessionId, providerName, savedExecutor, undefined, requestedModel, originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
|
|
3259
|
+
if (abortController.signal.aborted) {
|
|
3260
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3261
|
+
}
|
|
3262
|
+
const generated = await deps.supervisor.generateSummary(providerName, {
|
|
3263
|
+
purpose: "fork-after-summary",
|
|
3264
|
+
strategy: "fork",
|
|
3265
|
+
generatorSessionId: generator.sessionId,
|
|
3266
|
+
cwd: project.path,
|
|
3267
|
+
afterTurnMessageId: boundary.retainedThroughMessageId,
|
|
3268
|
+
afterTurnContext: boundary.retainedThroughContext,
|
|
3269
|
+
instructions,
|
|
3270
|
+
signal: abortController.signal,
|
|
3271
|
+
});
|
|
3272
|
+
if (abortController.signal.aborted) {
|
|
3273
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3274
|
+
}
|
|
3275
|
+
const title = forkSummaryTitle(generated.text, fallbackTitle);
|
|
3276
|
+
targetTitle = title;
|
|
3277
|
+
const target = await deps.supervisor.forkSession({
|
|
3278
|
+
sessionId,
|
|
3279
|
+
projectPath: project.path,
|
|
3280
|
+
providerName,
|
|
3281
|
+
upToMessageId: boundary.retainedThroughMessageId,
|
|
3282
|
+
title,
|
|
3283
|
+
});
|
|
3284
|
+
targetSessionId = target.sessionId;
|
|
3285
|
+
await updateForkSummaryChildMetadata(target.sessionId, sessionId, title, true);
|
|
3286
|
+
await persistLaunchMetadata(target.sessionId, providerName, savedExecutor, undefined, requestedModel, originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
|
|
3287
|
+
if (abortController.signal.aborted) {
|
|
3288
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3289
|
+
}
|
|
3290
|
+
const result = await deps.supervisor.resumeSession(target.sessionId, project.path, { text: generated.text, mode }, mode, {
|
|
3291
|
+
providerName,
|
|
3292
|
+
executor: savedExecutor,
|
|
3293
|
+
globalInstructions: getGlobalInstructions(),
|
|
3294
|
+
model: requestedModel,
|
|
3295
|
+
promptSuggestionMode: originalMetadata?.promptSuggestionMode,
|
|
3296
|
+
recapAfterSeconds: originalMetadata?.recapAfterSeconds,
|
|
3297
|
+
});
|
|
3298
|
+
if (isQueueFullResponse(result)) {
|
|
3299
|
+
throw new Error("Queue is full");
|
|
3300
|
+
}
|
|
3301
|
+
if (isQueuedResponse(result)) {
|
|
3302
|
+
deps.supervisor.cancelQueuedRequest(result.queueId);
|
|
3303
|
+
throw new Error("Fork summary could not start immediately");
|
|
3304
|
+
}
|
|
3305
|
+
targetProcessId = result.id;
|
|
3306
|
+
if (abortController.signal.aborted) {
|
|
3307
|
+
await deps.supervisor.abortProcess(result.id);
|
|
3308
|
+
targetProcessId = undefined;
|
|
3309
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3310
|
+
}
|
|
3311
|
+
await persistLaunchMetadata(result.sessionId, providerName, savedExecutor, undefined, requestedModel, result.promptSuggestionMode, result.recapAfterSeconds);
|
|
3312
|
+
await updateForkSummaryChildMetadata(result.sessionId, sessionId, title, false);
|
|
3313
|
+
await deps.sessionMetadataService?.updateTranscriptDisplayObject(sessionId, displayObject.id, (object) => ({
|
|
3314
|
+
...object,
|
|
3315
|
+
status: "ready",
|
|
3316
|
+
targetSessionId: result.sessionId,
|
|
3317
|
+
title,
|
|
3318
|
+
error: undefined,
|
|
3319
|
+
}));
|
|
3320
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3321
|
+
completed = true;
|
|
3322
|
+
}
|
|
3323
|
+
catch (error) {
|
|
3324
|
+
const cancelled = abortController.signal.aborted ||
|
|
3325
|
+
(error instanceof DOMException && error.name === "AbortError");
|
|
3326
|
+
const logCleanupFailure = (stage, cleanupError) => {
|
|
3327
|
+
getLogger().warn({
|
|
3328
|
+
event: "fork_after_summary_cleanup_failed",
|
|
3329
|
+
sessionId,
|
|
3330
|
+
projectId,
|
|
3331
|
+
providerName,
|
|
3332
|
+
stage,
|
|
3333
|
+
error: cleanupError instanceof Error
|
|
3334
|
+
? cleanupError.message
|
|
3335
|
+
: String(cleanupError),
|
|
3336
|
+
}, "Fork-after-summary cleanup failed");
|
|
3337
|
+
};
|
|
3338
|
+
if (generatorSessionId) {
|
|
3339
|
+
try {
|
|
3340
|
+
await updateForkSummaryChildMetadata(generatorSessionId, sessionId, "Fork summary generator", true);
|
|
3341
|
+
}
|
|
3342
|
+
catch (cleanupError) {
|
|
3343
|
+
logCleanupFailure("archive-generator", cleanupError);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
if (targetSessionId && targetTitle) {
|
|
3347
|
+
try {
|
|
3348
|
+
await updateForkSummaryChildMetadata(targetSessionId, sessionId, targetTitle, true);
|
|
3349
|
+
}
|
|
3350
|
+
catch (cleanupError) {
|
|
3351
|
+
logCleanupFailure("archive-target", cleanupError);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
if (!completed && targetProcessId) {
|
|
3355
|
+
try {
|
|
3356
|
+
await deps.supervisor.abortProcess(targetProcessId);
|
|
3357
|
+
}
|
|
3358
|
+
catch (cleanupError) {
|
|
3359
|
+
logCleanupFailure("abort-target-process", cleanupError);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
if (!cancelled) {
|
|
3363
|
+
const message = error instanceof Error
|
|
3364
|
+
? error.message
|
|
3365
|
+
: "Summary generation failed";
|
|
3366
|
+
getLogger().warn({
|
|
3367
|
+
event: "fork_after_summary_failed",
|
|
3368
|
+
sessionId,
|
|
3369
|
+
projectId,
|
|
3370
|
+
providerName,
|
|
3371
|
+
sourceMessageId,
|
|
3372
|
+
error: message,
|
|
3373
|
+
}, "Fork-after-summary job failed");
|
|
3374
|
+
try {
|
|
3375
|
+
await deps.sessionMetadataService?.updateTranscriptDisplayObject(sessionId, displayObject.id, (object) => ({
|
|
3376
|
+
...object,
|
|
3377
|
+
status: "error",
|
|
3378
|
+
error: message,
|
|
3379
|
+
}));
|
|
3380
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3381
|
+
}
|
|
3382
|
+
catch (cleanupError) {
|
|
3383
|
+
logCleanupFailure("persist-error-state", cleanupError);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
finally {
|
|
3388
|
+
const active = activeForkSummaryJobs.get(sessionId);
|
|
3389
|
+
if (active?.objectId === displayObject.id) {
|
|
3390
|
+
activeForkSummaryJobs.delete(sessionId);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
})().catch((error) => {
|
|
3394
|
+
getLogger().error({
|
|
3395
|
+
event: "fork_after_summary_unhandled_failure",
|
|
3396
|
+
sessionId,
|
|
3397
|
+
projectId,
|
|
3398
|
+
providerName,
|
|
3399
|
+
sourceMessageId,
|
|
3400
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3401
|
+
}, "Fork-after-summary job escaped its guarded workflow");
|
|
3402
|
+
});
|
|
3403
|
+
return c.json({ displayObject }, 202);
|
|
3404
|
+
});
|
|
3405
|
+
routes.post("/projects/:projectId/sessions/:sessionId/fork-summary/:objectId/cancel", async (c) => {
|
|
3406
|
+
const projectId = c.req.param("projectId");
|
|
3407
|
+
const sessionId = c.req.param("sessionId");
|
|
3408
|
+
const objectId = c.req.param("objectId");
|
|
3409
|
+
if (!isUrlProjectId(projectId)) {
|
|
3410
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3411
|
+
}
|
|
3412
|
+
const active = activeForkSummaryJobs.get(sessionId);
|
|
3413
|
+
const object = deps.sessionMetadataService
|
|
3414
|
+
?.getTranscriptDisplayObjects(sessionId)
|
|
3415
|
+
.find((candidate) => candidate.id === objectId);
|
|
3416
|
+
if (!object) {
|
|
3417
|
+
return c.json({ error: "Transcript display object not found" }, 404);
|
|
3418
|
+
}
|
|
3419
|
+
if (active?.objectId === objectId && object.status === "generating") {
|
|
3420
|
+
active.abortController.abort();
|
|
3421
|
+
}
|
|
3422
|
+
else if (object.status !== "error") {
|
|
3423
|
+
return c.json({ error: "Fork summary job is not active" }, 409);
|
|
3424
|
+
}
|
|
3425
|
+
await deps.sessionMetadataService?.removeTranscriptDisplayObject(sessionId, objectId);
|
|
3426
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3427
|
+
return c.json({
|
|
3428
|
+
transcriptDisplayObjects: deps.sessionMetadataService?.getTranscriptDisplayObjects(sessionId) ??
|
|
3429
|
+
[],
|
|
3430
|
+
});
|
|
3431
|
+
});
|
|
3432
|
+
routes.patch("/projects/:projectId/sessions/:sessionId/fork-summary/:objectId", async (c) => {
|
|
3433
|
+
const projectId = c.req.param("projectId");
|
|
3434
|
+
const sessionId = c.req.param("sessionId");
|
|
3435
|
+
const objectId = c.req.param("objectId");
|
|
3436
|
+
if (!isUrlProjectId(projectId)) {
|
|
3437
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3438
|
+
}
|
|
3439
|
+
if (!deps.sessionMetadataService) {
|
|
3440
|
+
return c.json({ error: "Session metadata service not available" }, 503);
|
|
3441
|
+
}
|
|
3442
|
+
let body;
|
|
3443
|
+
try {
|
|
3444
|
+
body = await c.req.json();
|
|
3445
|
+
}
|
|
3446
|
+
catch {
|
|
3447
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3448
|
+
}
|
|
3449
|
+
const action = body.action === "opened" || body.action === "clicked"
|
|
3450
|
+
? body.action
|
|
3451
|
+
: undefined;
|
|
3452
|
+
if (body.autoOpenWhenReady !== undefined &&
|
|
3453
|
+
typeof body.autoOpenWhenReady !== "boolean") {
|
|
3454
|
+
return c.json({ error: "autoOpenWhenReady must be a boolean" }, 400);
|
|
3455
|
+
}
|
|
3456
|
+
if (body.action !== undefined && !action) {
|
|
3457
|
+
return c.json({ error: "action must be opened or clicked" }, 400);
|
|
3458
|
+
}
|
|
3459
|
+
const now = new Date().toISOString();
|
|
3460
|
+
const updated = await deps.sessionMetadataService.updateTranscriptDisplayObject(sessionId, objectId, (object) => ({
|
|
3461
|
+
...object,
|
|
3462
|
+
...(typeof body.autoOpenWhenReady === "boolean"
|
|
3463
|
+
? { autoOpenWhenReady: body.autoOpenWhenReady || undefined }
|
|
3464
|
+
: {}),
|
|
3465
|
+
...(action === "opened" ? { openedAt: now } : {}),
|
|
3466
|
+
...(action === "clicked" ? { clickedAt: now } : {}),
|
|
3467
|
+
}));
|
|
3468
|
+
if (!updated) {
|
|
3469
|
+
return c.json({ error: "Transcript display object not found" }, 404);
|
|
3470
|
+
}
|
|
3471
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3472
|
+
return c.json({
|
|
3473
|
+
displayObject: updated,
|
|
3474
|
+
transcriptDisplayObjects: deps.sessionMetadataService.getTranscriptDisplayObjects(sessionId),
|
|
3475
|
+
});
|
|
3476
|
+
});
|
|
3477
|
+
// POST /api/sessions/:sessionId/messages - Queue message
|
|
3478
|
+
routes.post("/sessions/:sessionId/messages", async (c) => {
|
|
3479
|
+
const sessionId = c.req.param("sessionId");
|
|
3480
|
+
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
3481
|
+
if (!process) {
|
|
3482
|
+
return c.json({ error: "No active process for session" }, 404);
|
|
3483
|
+
}
|
|
3484
|
+
let body;
|
|
3485
|
+
try {
|
|
3486
|
+
body = await c.req.json();
|
|
3487
|
+
}
|
|
3488
|
+
catch {
|
|
3489
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3490
|
+
}
|
|
3491
|
+
if (!body.message) {
|
|
3492
|
+
return c.json({ error: "Message is required" }, 400);
|
|
3493
|
+
}
|
|
3494
|
+
const serverTimestamp = Date.now();
|
|
3495
|
+
const userMessage = {
|
|
3496
|
+
text: body.message,
|
|
3497
|
+
images: body.images,
|
|
3498
|
+
documents: body.documents,
|
|
3499
|
+
attachments: body.attachments,
|
|
3500
|
+
mode: body.mode,
|
|
3501
|
+
tempId: body.tempId,
|
|
3502
|
+
metadata: buildUserMessageMetadata(body, serverTimestamp, body.deferred ? "deferred" : "direct"),
|
|
3503
|
+
};
|
|
3504
|
+
// Check if process is terminated
|
|
3505
|
+
if (process.isTerminated) {
|
|
3506
|
+
return c.json({
|
|
3507
|
+
error: "Process terminated",
|
|
3508
|
+
reason: process.terminationReason,
|
|
3509
|
+
}, 410); // 410 Gone
|
|
3510
|
+
}
|
|
3511
|
+
// Provider-native slash commands (e.g. Codex `/compact`) are dispatched
|
|
3512
|
+
// through the provider's own protocol rather than delivered as turn text the
|
|
3513
|
+
// model would never interpret. Claude's `/compact` reports handled:false and
|
|
3514
|
+
// falls through to normal delivery so the SDK handles it as a regular turn
|
|
3515
|
+
// (and any trailing focus instructions reach the SDK verbatim). A deferred
|
|
3516
|
+
// submission keeps normal queue semantics.
|
|
3517
|
+
if (!body.deferred) {
|
|
3518
|
+
const parsed = parseSlashCommandSubmission(body.message);
|
|
3519
|
+
if (parsed) {
|
|
3520
|
+
const providerResult = await process.runProviderCommand(parsed.name, parsed.argument);
|
|
3521
|
+
if (providerResult.handled) {
|
|
3522
|
+
if (providerResult.error) {
|
|
3523
|
+
return c.json({ error: "Failed to run command", reason: providerResult.error }, 409);
|
|
3524
|
+
}
|
|
3525
|
+
return c.json({ queued: true, serverTimestamp });
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
const resolvedModel = body.model && body.model !== "default"
|
|
3530
|
+
? body.model
|
|
3531
|
+
: (process.resolvedModel ?? process.model);
|
|
2190
3532
|
const modelInfoService = deps.modelInfoService;
|
|
2191
3533
|
const resolveContextWindow = modelInfoService
|
|
2192
3534
|
? (model, provider) => modelInfoService.getContextWindow(model, provider)
|
|
@@ -2210,7 +3552,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2210
3552
|
await process.primeSupportedCommandsForMessage(userMessage);
|
|
2211
3553
|
const deferredResult = process.deferMessage(userMessage, {
|
|
2212
3554
|
promoteIfReady: true,
|
|
2213
|
-
placement: parseDeferredPlacement(body),
|
|
2214
3555
|
});
|
|
2215
3556
|
if (!deferredResult.success) {
|
|
2216
3557
|
return c.json({
|
|
@@ -2218,6 +3559,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2218
3559
|
reason: deferredResult.error,
|
|
2219
3560
|
}, 410);
|
|
2220
3561
|
}
|
|
3562
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
2221
3563
|
return c.json({
|
|
2222
3564
|
queued: true,
|
|
2223
3565
|
deferred: deferredResult.deferred,
|
|
@@ -2229,7 +3571,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2229
3571
|
}
|
|
2230
3572
|
// Convert thinking option to SDK config
|
|
2231
3573
|
const { thinking, effort } = body.thinking
|
|
2232
|
-
? thinkingOptionToConfig(body.thinking)
|
|
3574
|
+
? thinkingOptionToConfig(body.thinking, body.showThinking)
|
|
2233
3575
|
: { thinking: undefined, effort: undefined };
|
|
2234
3576
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
2235
3577
|
const metadataExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId));
|
|
@@ -2244,9 +3586,26 @@ export function createSessionsRoutes(deps) {
|
|
|
2244
3586
|
? body.model
|
|
2245
3587
|
: (process.resolvedModel ?? process.model);
|
|
2246
3588
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
3589
|
+
// Per-model preemptive-compaction threshold (task 029). The route holds the
|
|
3590
|
+
// settings; the Supervisor stays settings-agnostic. Key strictly by the YA
|
|
3591
|
+
// model id: the alias the user picked this turn, else the live requested
|
|
3592
|
+
// alias, else the alias persisted at launch (survives restart), else the
|
|
3593
|
+
// provider's reported→YA-id helper for sessions YA didn't start. No family
|
|
3594
|
+
// fallback. See topics/provider-abstraction.md § Per-model settings keying.
|
|
3595
|
+
const yaModelId = body.model ??
|
|
3596
|
+
process.requestedModel ??
|
|
3597
|
+
deps.sessionMetadataService?.getRequestedModel(sessionId) ??
|
|
3598
|
+
getProvider(process.provider)?.yaModelIdForReported?.(process.resolvedModel);
|
|
3599
|
+
const compactAtContextPercent = resolveCompactPercent(deps.serverSettingsService?.getSetting("clientDefaults")
|
|
3600
|
+
?.compactAtContextPercent, yaModelId);
|
|
3601
|
+
// Resolve the effective window here too — process.contextWindow is
|
|
3602
|
+
// unreliable (often undefined) and the base resolver ignores always-1M.
|
|
3603
|
+
const compactAtContextWindow = compactAtContextPercent === undefined
|
|
3604
|
+
? undefined
|
|
3605
|
+
: resolveCompactWindow(process.provider, [yaModelId, process.resolvedModel, process.model], resolveContextWindow);
|
|
2247
3606
|
// Use queueMessageToSession which handles thinking mode changes
|
|
2248
3607
|
// If thinking mode changed, it will restart the process automatically
|
|
2249
|
-
const queueGlobalInstructions =
|
|
3608
|
+
const queueGlobalInstructions = getGlobalInstructions();
|
|
2250
3609
|
const result = await deps.supervisor.queueMessageToSession(sessionId, process.projectPath, userMessage, body.mode, {
|
|
2251
3610
|
model,
|
|
2252
3611
|
serviceTier,
|
|
@@ -2259,6 +3618,8 @@ export function createSessionsRoutes(deps) {
|
|
|
2259
3618
|
undefined,
|
|
2260
3619
|
globalInstructions: queueGlobalInstructions,
|
|
2261
3620
|
permissions: body.permissions,
|
|
3621
|
+
compactAtContextPercent,
|
|
3622
|
+
compactAtContextWindow,
|
|
2262
3623
|
});
|
|
2263
3624
|
if (!result.success) {
|
|
2264
3625
|
return c.json({
|
|
@@ -2274,52 +3635,267 @@ export function createSessionsRoutes(deps) {
|
|
|
2274
3635
|
serverTimestamp,
|
|
2275
3636
|
});
|
|
2276
3637
|
});
|
|
2277
|
-
// DELETE /api/sessions/:sessionId/
|
|
2278
|
-
|
|
3638
|
+
// DELETE /api/sessions/:sessionId/recovered-queue/:queueId - Delete a
|
|
3639
|
+
// restart-paused patient queue entry by durable queue id.
|
|
3640
|
+
routes.delete("/sessions/:sessionId/recovered-queue/:queueId", async (c) => {
|
|
2279
3641
|
const sessionId = c.req.param("sessionId");
|
|
2280
|
-
const
|
|
2281
|
-
const
|
|
3642
|
+
const queueId = c.req.param("queueId");
|
|
3643
|
+
const service = deps.sessionQueuePersistenceService;
|
|
3644
|
+
if (!service) {
|
|
3645
|
+
return c.json({ error: "Session queue persistence unavailable" }, 503);
|
|
3646
|
+
}
|
|
3647
|
+
const item = service
|
|
3648
|
+
.listSession(sessionId)
|
|
3649
|
+
.find((candidate) => candidate.id === queueId);
|
|
3650
|
+
if (item?.kind !== "patient" || item.status !== "paused-after-restart") {
|
|
3651
|
+
return c.json({ error: "Recovered queued message not found" }, 404);
|
|
3652
|
+
}
|
|
3653
|
+
await service.deleteItem(queueId);
|
|
3654
|
+
return c.json({
|
|
3655
|
+
deleted: true,
|
|
3656
|
+
deferredMessages: recoveredPatientQueueSummaries(deps, sessionId),
|
|
3657
|
+
});
|
|
3658
|
+
});
|
|
3659
|
+
// A recovered patient queue entry may need a fresh process before it can
|
|
3660
|
+
// rejoin the live queue. Shared by the recovered-queue resume/steer routes.
|
|
3661
|
+
const ensureProcessForRecoveredItem = async (sessionId, item, existing) => {
|
|
3662
|
+
let process = existing;
|
|
3663
|
+
const mode = item.message.mode ?? item.mode;
|
|
3664
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
3665
|
+
const parsedExecutor = parseOptionalExecutor(item.executor ?? metadata?.executor);
|
|
3666
|
+
if (parsedExecutor.error) {
|
|
3667
|
+
return { error: parsedExecutor.error, status: 400 };
|
|
3668
|
+
}
|
|
3669
|
+
const rawModel = item.model ?? metadata?.requestedModel;
|
|
3670
|
+
const model = rawModel && rawModel !== "default" ? rawModel : undefined;
|
|
2282
3671
|
if (!process) {
|
|
2283
|
-
|
|
3672
|
+
try {
|
|
3673
|
+
process = await deps.supervisor.reactivateSession(item.projectPath, sessionId, mode, {
|
|
3674
|
+
model,
|
|
3675
|
+
serviceTier: item.serviceTier,
|
|
3676
|
+
providerName: item.provider,
|
|
3677
|
+
executor: parsedExecutor.executor,
|
|
3678
|
+
globalInstructions: getGlobalInstructions(),
|
|
3679
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
3680
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
catch (error) {
|
|
3684
|
+
return {
|
|
3685
|
+
error: error instanceof Error
|
|
3686
|
+
? error.message
|
|
3687
|
+
: "Failed to reactivate session",
|
|
3688
|
+
status: 503,
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
await persistLaunchMetadata(sessionId, item.provider, parsedExecutor.executor, undefined, item.model ?? metadata?.requestedModel, metadata?.promptSuggestionMode, metadata?.recapAfterSeconds);
|
|
2284
3692
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
return c.json({ error: "Deferred message not found" }, 404);
|
|
3693
|
+
else if (mode) {
|
|
3694
|
+
process.setPermissionMode(mode);
|
|
2288
3695
|
}
|
|
2289
|
-
return
|
|
3696
|
+
return { process };
|
|
3697
|
+
};
|
|
3698
|
+
// Move recovered entries back into the live patient queue in queuedAt
|
|
3699
|
+
// order. On a mid-group failure the earlier resumes stand; the caller
|
|
3700
|
+
// reports the failed entry.
|
|
3701
|
+
const resumeRecoveredGroup = async (process, group, options) => {
|
|
3702
|
+
let last;
|
|
3703
|
+
let lastMessage;
|
|
3704
|
+
for (const item of group) {
|
|
3705
|
+
const userMessage = recoveredPatientUserMessage(item);
|
|
3706
|
+
await process.primeSupportedCommandsForMessage(userMessage);
|
|
3707
|
+
const result = process.deferMessage(userMessage, {
|
|
3708
|
+
promoteIfReady: options.promoteIfReady,
|
|
3709
|
+
persistedQueueId: item.id,
|
|
3710
|
+
timestamp: item.queuedAt,
|
|
3711
|
+
});
|
|
3712
|
+
if (!result.success) {
|
|
3713
|
+
return {
|
|
3714
|
+
last,
|
|
3715
|
+
lastMessage,
|
|
3716
|
+
error: result.error ?? "Failed to queue message",
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
last = result;
|
|
3720
|
+
lastMessage = userMessage;
|
|
3721
|
+
}
|
|
3722
|
+
return { last, lastMessage };
|
|
3723
|
+
};
|
|
3724
|
+
const reportableProcessState = (process) => process.state.type === "waiting-input" || process.state.type === "in-turn"
|
|
3725
|
+
? process.state.type
|
|
3726
|
+
: "idle";
|
|
3727
|
+
// Shared prologue for the recovered-queue resume/steer routes: resolve the
|
|
3728
|
+
// target and the recovered group composed before it, refuse while newer
|
|
3729
|
+
// live patient entries exist, and ensure a live process. The newer-entries
|
|
3730
|
+
// guard runs again after the ensure — its await is a window for new
|
|
3731
|
+
// arrivals, and recovered context must never jump a newer patient entry.
|
|
3732
|
+
const resolveRecoveredGroupForDelivery = async (sessionId, queueId, refusal) => {
|
|
3733
|
+
if (!deps.sessionQueuePersistenceService) {
|
|
3734
|
+
return {
|
|
3735
|
+
ok: false,
|
|
3736
|
+
status: 503,
|
|
3737
|
+
body: { error: "Session queue persistence unavailable" },
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
const recoveredItems = recoveredPatientQueueItems(deps, sessionId);
|
|
3741
|
+
const targetIndex = recoveredItems.findIndex((candidate) => candidate.id === queueId);
|
|
3742
|
+
const target = recoveredItems[targetIndex];
|
|
3743
|
+
if (!target) {
|
|
3744
|
+
return {
|
|
3745
|
+
ok: false,
|
|
3746
|
+
status: 404,
|
|
3747
|
+
body: { error: "Recovered queued message not found" },
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
const group = recoveredItems.slice(0, targetIndex + 1);
|
|
3751
|
+
let process = deps.supervisor.getProcessForSession(sessionId);
|
|
3752
|
+
if (process?.isTerminated) {
|
|
3753
|
+
process = undefined;
|
|
3754
|
+
}
|
|
3755
|
+
const refuseIfNewerEntries = (candidate) => livePatientEntriesNewerThan(candidate, target.queuedAt) > 0
|
|
3756
|
+
? {
|
|
3757
|
+
ok: false,
|
|
3758
|
+
status: 409,
|
|
3759
|
+
body: {
|
|
3760
|
+
error: refusal,
|
|
3761
|
+
headQueueId: target.id,
|
|
3762
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, candidate),
|
|
3763
|
+
},
|
|
3764
|
+
}
|
|
3765
|
+
: null;
|
|
3766
|
+
const refusedBefore = refuseIfNewerEntries(process);
|
|
3767
|
+
if (refusedBefore) {
|
|
3768
|
+
return refusedBefore;
|
|
3769
|
+
}
|
|
3770
|
+
const ensured = await ensureProcessForRecoveredItem(sessionId, target, process);
|
|
3771
|
+
if ("error" in ensured) {
|
|
3772
|
+
return {
|
|
3773
|
+
ok: false,
|
|
3774
|
+
status: ensured.status,
|
|
3775
|
+
body: { error: ensured.error },
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
process = ensured.process;
|
|
3779
|
+
const refusedAfter = refuseIfNewerEntries(process);
|
|
3780
|
+
if (refusedAfter) {
|
|
3781
|
+
return refusedAfter;
|
|
3782
|
+
}
|
|
3783
|
+
return { ok: true, process, group };
|
|
3784
|
+
};
|
|
3785
|
+
// POST /api/sessions/:sessionId/recovered-queue/:queueId/resume - Move
|
|
3786
|
+
// restart-paused patient queue entries back into the live patient queue,
|
|
3787
|
+
// through the requested entry: resuming a non-head entry also resumes every
|
|
3788
|
+
// recovered entry before it, so compose order is preserved.
|
|
3789
|
+
routes.post("/sessions/:sessionId/recovered-queue/:queueId/resume", async (c) => {
|
|
3790
|
+
const sessionId = c.req.param("sessionId");
|
|
3791
|
+
const queueId = c.req.param("queueId");
|
|
3792
|
+
const resolved = await resolveRecoveredGroupForDelivery(sessionId, queueId, "Drain or cancel newer queued messages before resuming recovered work.");
|
|
3793
|
+
if (!resolved.ok) {
|
|
3794
|
+
return c.json(resolved.body, resolved.status);
|
|
3795
|
+
}
|
|
3796
|
+
const { process, group } = resolved;
|
|
3797
|
+
const { last, error } = await resumeRecoveredGroup(process, group, {
|
|
3798
|
+
promoteIfReady: true,
|
|
3799
|
+
});
|
|
3800
|
+
if (error || !last) {
|
|
3801
|
+
return c.json({
|
|
3802
|
+
error: "Failed to resume recovered queued message",
|
|
3803
|
+
reason: error,
|
|
3804
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3805
|
+
}, 409);
|
|
3806
|
+
}
|
|
3807
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
3808
|
+
return c.json({
|
|
3809
|
+
resumed: true,
|
|
3810
|
+
resumedCount: group.length,
|
|
3811
|
+
deferred: last.deferred,
|
|
3812
|
+
promoted: last.promoted,
|
|
3813
|
+
position: last.position,
|
|
3814
|
+
processId: process.id,
|
|
3815
|
+
processState: reportableProcessState(process),
|
|
3816
|
+
permissionMode: process.permissionMode,
|
|
3817
|
+
modeVersion: process.modeVersion,
|
|
3818
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
3819
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3820
|
+
serverTimestamp: Date.now(),
|
|
3821
|
+
});
|
|
2290
3822
|
});
|
|
2291
|
-
// POST /api/sessions/:sessionId/
|
|
2292
|
-
|
|
3823
|
+
// POST /api/sessions/:sessionId/recovered-queue/:queueId/steer - Steer a
|
|
3824
|
+
// restart-paused patient queue entry into the session now, without waiting
|
|
3825
|
+
// for verified quiet: the entry and every recovered or live patient entry
|
|
3826
|
+
// composed before it rejoin the live queue and steer in compose order.
|
|
3827
|
+
routes.post("/sessions/:sessionId/recovered-queue/:queueId/steer", async (c) => {
|
|
3828
|
+
const sessionId = c.req.param("sessionId");
|
|
3829
|
+
const queueId = c.req.param("queueId");
|
|
3830
|
+
const resolved = await resolveRecoveredGroupForDelivery(sessionId, queueId, "Drain or cancel newer queued messages before steering recovered work.");
|
|
3831
|
+
if (!resolved.ok) {
|
|
3832
|
+
return c.json(resolved.body, resolved.status);
|
|
3833
|
+
}
|
|
3834
|
+
const { process, group } = resolved;
|
|
3835
|
+
const { lastMessage, error } = await resumeRecoveredGroup(process, group, { promoteIfReady: false });
|
|
3836
|
+
if (error || !lastMessage?.tempId) {
|
|
3837
|
+
return c.json({
|
|
3838
|
+
error: "Failed to steer recovered queued message",
|
|
3839
|
+
reason: error,
|
|
3840
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3841
|
+
}, 409);
|
|
3842
|
+
}
|
|
3843
|
+
const steerResult = process.steerPatientDeferredMessagesThrough(lastMessage.tempId);
|
|
3844
|
+
if (!steerResult.success) {
|
|
3845
|
+
return c.json({
|
|
3846
|
+
error: steerResult.error ?? "Failed to steer recovered queued message",
|
|
3847
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3848
|
+
}, 409);
|
|
3849
|
+
}
|
|
3850
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
3851
|
+
return c.json({
|
|
3852
|
+
steered: true,
|
|
3853
|
+
count: steerResult.steered,
|
|
3854
|
+
processId: process.id,
|
|
3855
|
+
processState: reportableProcessState(process),
|
|
3856
|
+
permissionMode: process.permissionMode,
|
|
3857
|
+
modeVersion: process.modeVersion,
|
|
3858
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
3859
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3860
|
+
serverTimestamp: Date.now(),
|
|
3861
|
+
});
|
|
3862
|
+
});
|
|
3863
|
+
// DELETE /api/sessions/:sessionId/deferred/:tempId - Cancel a deferred message
|
|
3864
|
+
routes.delete("/sessions/:sessionId/deferred/:tempId", async (c) => {
|
|
2293
3865
|
const sessionId = c.req.param("sessionId");
|
|
2294
3866
|
const tempId = c.req.param("tempId");
|
|
2295
3867
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
2296
3868
|
if (!process) {
|
|
2297
3869
|
return c.json({ error: "No active process for session" }, 404);
|
|
2298
3870
|
}
|
|
2299
|
-
const
|
|
2300
|
-
if (!
|
|
3871
|
+
const cancelled = process.cancelDeferredMessage(tempId);
|
|
3872
|
+
if (!cancelled) {
|
|
2301
3873
|
return c.json({ error: "Deferred message not found" }, 404);
|
|
2302
3874
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
tempId: taken.message.tempId,
|
|
2306
|
-
mode: taken.message.mode,
|
|
2307
|
-
attachments: taken.message.attachments,
|
|
2308
|
-
placement: taken.placement,
|
|
2309
|
-
});
|
|
3875
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
3876
|
+
return c.json({ cancelled: true });
|
|
2310
3877
|
});
|
|
2311
|
-
// POST /api/sessions/:sessionId/deferred/:tempId/
|
|
2312
|
-
|
|
3878
|
+
// POST /api/sessions/:sessionId/deferred/:tempId/steer - Steer a patient
|
|
3879
|
+
// queued message, and every patient entry ahead of it, into the session now.
|
|
3880
|
+
routes.post("/sessions/:sessionId/deferred/:tempId/steer", async (c) => {
|
|
2313
3881
|
const sessionId = c.req.param("sessionId");
|
|
2314
3882
|
const tempId = c.req.param("tempId");
|
|
2315
3883
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
2316
3884
|
if (!process) {
|
|
2317
3885
|
return c.json({ error: "No active process for session" }, 404);
|
|
2318
3886
|
}
|
|
2319
|
-
const
|
|
3887
|
+
const result = process.steerPatientDeferredMessagesThrough(tempId);
|
|
3888
|
+
if (!result.success) {
|
|
3889
|
+
const status = result.error === "Deferred message not found" ? 404 : 409;
|
|
3890
|
+
return c.json({ error: result.error }, status);
|
|
3891
|
+
}
|
|
3892
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
2320
3893
|
return c.json({
|
|
2321
|
-
|
|
2322
|
-
|
|
3894
|
+
steered: true,
|
|
3895
|
+
count: result.steered,
|
|
3896
|
+
// Include any still-paused recovered entries so the client's list does
|
|
3897
|
+
// not lose their chips when a live entry is steered.
|
|
3898
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
2323
3899
|
});
|
|
2324
3900
|
});
|
|
2325
3901
|
// PUT /api/sessions/:sessionId/mode - Update permission mode without sending a message
|
|
@@ -2387,32 +3963,65 @@ export function createSessionsRoutes(deps) {
|
|
|
2387
3963
|
const requestBefore = process.getPendingInputRequest();
|
|
2388
3964
|
const permissionModeBefore = process.permissionMode;
|
|
2389
3965
|
if (process.state.type !== "waiting-input") {
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
3966
|
+
if (isApprovalAuditLogEnabled(deps)) {
|
|
3967
|
+
try {
|
|
3968
|
+
await appendApprovalAuditLog(deps.dataDir, {
|
|
3969
|
+
timestamp: new Date().toISOString(),
|
|
3970
|
+
sessionId,
|
|
3971
|
+
processId: process.id,
|
|
3972
|
+
provider: process.provider,
|
|
3973
|
+
requestId: body.requestId,
|
|
3974
|
+
request: requestBefore,
|
|
3975
|
+
response: body.response,
|
|
3976
|
+
normalizedResponse,
|
|
3977
|
+
answers: body.answers,
|
|
3978
|
+
feedback: body.feedback,
|
|
3979
|
+
accepted: false,
|
|
3980
|
+
failure: "No pending input request",
|
|
3981
|
+
permissionModeBefore,
|
|
3982
|
+
permissionModeAfter: process.permissionMode,
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
catch (error) {
|
|
3986
|
+
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
3987
|
+
}
|
|
2410
3988
|
}
|
|
2411
3989
|
return c.json({ error: "No pending input request" }, 400);
|
|
2412
3990
|
}
|
|
2413
3991
|
// Call respondToInput which resolves the SDK's canUseTool promise
|
|
2414
3992
|
const accepted = process.respondToInput(body.requestId, normalizedResponse, body.answers, body.feedback);
|
|
2415
3993
|
if (!accepted) {
|
|
3994
|
+
if (isApprovalAuditLogEnabled(deps)) {
|
|
3995
|
+
try {
|
|
3996
|
+
await appendApprovalAuditLog(deps.dataDir, {
|
|
3997
|
+
timestamp: new Date().toISOString(),
|
|
3998
|
+
sessionId,
|
|
3999
|
+
processId: process.id,
|
|
4000
|
+
provider: process.provider,
|
|
4001
|
+
requestId: body.requestId,
|
|
4002
|
+
request: requestBefore,
|
|
4003
|
+
response: body.response,
|
|
4004
|
+
normalizedResponse,
|
|
4005
|
+
answers: body.answers,
|
|
4006
|
+
feedback: body.feedback,
|
|
4007
|
+
accepted: false,
|
|
4008
|
+
failure: "Invalid request ID or no pending request",
|
|
4009
|
+
permissionModeBefore,
|
|
4010
|
+
permissionModeAfter: process.permissionMode,
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
catch (error) {
|
|
4014
|
+
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
return c.json({ error: "Invalid request ID or no pending request" }, 400);
|
|
4018
|
+
}
|
|
4019
|
+
// If approve_accept_edits, switch the permission mode
|
|
4020
|
+
if (isApproveAcceptEdits) {
|
|
4021
|
+
process.setPermissionMode("acceptEdits");
|
|
4022
|
+
}
|
|
4023
|
+
const pendingInputRequest = process.getPendingInputRequest();
|
|
4024
|
+
if (isApprovalAuditLogEnabled(deps)) {
|
|
2416
4025
|
try {
|
|
2417
4026
|
await appendApprovalAuditLog(deps.dataDir, {
|
|
2418
4027
|
timestamp: new Date().toISOString(),
|
|
@@ -2425,8 +4034,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2425
4034
|
normalizedResponse,
|
|
2426
4035
|
answers: body.answers,
|
|
2427
4036
|
feedback: body.feedback,
|
|
2428
|
-
accepted:
|
|
2429
|
-
failure: "Invalid request ID or no pending request",
|
|
4037
|
+
accepted: true,
|
|
2430
4038
|
permissionModeBefore,
|
|
2431
4039
|
permissionModeAfter: process.permissionMode,
|
|
2432
4040
|
});
|
|
@@ -2434,32 +4042,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2434
4042
|
catch (error) {
|
|
2435
4043
|
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
2436
4044
|
}
|
|
2437
|
-
return c.json({ error: "Invalid request ID or no pending request" }, 400);
|
|
2438
|
-
}
|
|
2439
|
-
// If approve_accept_edits, switch the permission mode
|
|
2440
|
-
if (isApproveAcceptEdits) {
|
|
2441
|
-
process.setPermissionMode("acceptEdits");
|
|
2442
|
-
}
|
|
2443
|
-
const pendingInputRequest = process.getPendingInputRequest();
|
|
2444
|
-
try {
|
|
2445
|
-
await appendApprovalAuditLog(deps.dataDir, {
|
|
2446
|
-
timestamp: new Date().toISOString(),
|
|
2447
|
-
sessionId,
|
|
2448
|
-
processId: process.id,
|
|
2449
|
-
provider: process.provider,
|
|
2450
|
-
requestId: body.requestId,
|
|
2451
|
-
request: requestBefore,
|
|
2452
|
-
response: body.response,
|
|
2453
|
-
normalizedResponse,
|
|
2454
|
-
answers: body.answers,
|
|
2455
|
-
feedback: body.feedback,
|
|
2456
|
-
accepted: true,
|
|
2457
|
-
permissionModeBefore,
|
|
2458
|
-
permissionModeAfter: process.permissionMode,
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
catch (error) {
|
|
2462
|
-
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
2463
4045
|
}
|
|
2464
4046
|
return c.json({ accepted: true, pendingInputRequest });
|
|
2465
4047
|
});
|
|
@@ -2542,7 +4124,9 @@ export function createSessionsRoutes(deps) {
|
|
|
2542
4124
|
body.heartbeatTurnsEnabled === undefined &&
|
|
2543
4125
|
body.heartbeatTurnsAfterMinutes === undefined &&
|
|
2544
4126
|
body.heartbeatTurnText === undefined &&
|
|
2545
|
-
body.heartbeatForceAfterMinutes === undefined
|
|
4127
|
+
body.heartbeatForceAfterMinutes === undefined &&
|
|
4128
|
+
body.promptSuggestionMode === undefined &&
|
|
4129
|
+
body.recapAfterSeconds === undefined) {
|
|
2546
4130
|
return c.json({
|
|
2547
4131
|
error: "At least one session metadata field must be provided",
|
|
2548
4132
|
}, 400);
|
|
@@ -2606,6 +4190,36 @@ export function createSessionsRoutes(deps) {
|
|
|
2606
4190
|
: typeof body.parentSessionId === "string"
|
|
2607
4191
|
? body.parentSessionId.trim() || null
|
|
2608
4192
|
: null;
|
|
4193
|
+
// promptSuggestionMode: null/"" clears the preference (revert to default);
|
|
4194
|
+
// a valid enum value is stored; any other value is rejected.
|
|
4195
|
+
let promptSuggestionMode;
|
|
4196
|
+
if (body.promptSuggestionMode !== undefined) {
|
|
4197
|
+
if (body.promptSuggestionMode === null ||
|
|
4198
|
+
body.promptSuggestionMode === "") {
|
|
4199
|
+
promptSuggestionMode = null;
|
|
4200
|
+
}
|
|
4201
|
+
else if (typeof body.promptSuggestionMode === "string" &&
|
|
4202
|
+
PROMPT_SUGGESTION_MODES.includes(body.promptSuggestionMode)) {
|
|
4203
|
+
promptSuggestionMode =
|
|
4204
|
+
body.promptSuggestionMode;
|
|
4205
|
+
}
|
|
4206
|
+
else {
|
|
4207
|
+
return c.json({ error: "promptSuggestionMode must be one of: off, native" }, 400);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
let recapAfterSeconds;
|
|
4211
|
+
if (body.recapAfterSeconds !== undefined) {
|
|
4212
|
+
if (body.recapAfterSeconds === null || body.recapAfterSeconds === "") {
|
|
4213
|
+
recapAfterSeconds = null;
|
|
4214
|
+
}
|
|
4215
|
+
else if (typeof body.recapAfterSeconds === "number" &&
|
|
4216
|
+
Number.isFinite(body.recapAfterSeconds)) {
|
|
4217
|
+
recapAfterSeconds = clampRecapAfterSeconds(body.recapAfterSeconds);
|
|
4218
|
+
}
|
|
4219
|
+
else {
|
|
4220
|
+
return c.json({ error: "recapAfterSeconds must be null or a finite number" }, 400);
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
2609
4223
|
await deps.sessionMetadataService.updateMetadata(sessionId, {
|
|
2610
4224
|
title: body.title,
|
|
2611
4225
|
archived: body.archived,
|
|
@@ -2615,6 +4229,8 @@ export function createSessionsRoutes(deps) {
|
|
|
2615
4229
|
heartbeatTurnsAfterMinutes,
|
|
2616
4230
|
heartbeatTurnText,
|
|
2617
4231
|
heartbeatForceAfterMinutes,
|
|
4232
|
+
promptSuggestionMode,
|
|
4233
|
+
recapAfterSeconds,
|
|
2618
4234
|
});
|
|
2619
4235
|
// Emit SSE event so sidebar and other clients can update
|
|
2620
4236
|
if (deps.eventBus) {
|
|
@@ -2629,6 +4245,8 @@ export function createSessionsRoutes(deps) {
|
|
|
2629
4245
|
heartbeatTurnsAfterMinutes,
|
|
2630
4246
|
heartbeatTurnText,
|
|
2631
4247
|
heartbeatForceAfterMinutes,
|
|
4248
|
+
promptSuggestionMode: promptSuggestionMode ?? undefined,
|
|
4249
|
+
recapAfterSeconds: recapAfterSeconds ?? undefined,
|
|
2632
4250
|
timestamp: new Date().toISOString(),
|
|
2633
4251
|
});
|
|
2634
4252
|
}
|
|
@@ -2674,7 +4292,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2674
4292
|
}
|
|
2675
4293
|
// Get original session to extract title for the clone
|
|
2676
4294
|
const reader = deps.readerFactory(project);
|
|
2677
|
-
let originalSession = await reader.getSessionSummary(sessionId, projectId);
|
|
4295
|
+
let originalSession = await reader.getSessionSummary(sessionId, projectId, { readMode: "head" });
|
|
2678
4296
|
let cloneProvider = project.provider;
|
|
2679
4297
|
let result;
|
|
2680
4298
|
const shouldCloneFromCodex = isCodexProviderName(body.provider) ||
|
|
@@ -2691,7 +4309,9 @@ export function createSessionsRoutes(deps) {
|
|
|
2691
4309
|
}
|
|
2692
4310
|
originalSession =
|
|
2693
4311
|
originalSession ??
|
|
2694
|
-
(await codexReader.getSessionSummary(sessionId, projectId
|
|
4312
|
+
(await codexReader.getSessionSummary(sessionId, projectId, {
|
|
4313
|
+
readMode: "head",
|
|
4314
|
+
})) ??
|
|
2695
4315
|
null;
|
|
2696
4316
|
cloneProvider =
|
|
2697
4317
|
originalSession?.provider ??
|
|
@@ -2734,36 +4354,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2734
4354
|
return c.json({ error: message }, 500);
|
|
2735
4355
|
}
|
|
2736
4356
|
});
|
|
2737
|
-
// ============ Worker Queue Endpoints ============
|
|
2738
|
-
// GET /api/status/workers - Get worker activity for safe restart indicator
|
|
2739
|
-
routes.get("/status/workers", (c) => {
|
|
2740
|
-
const activity = deps.supervisor.getWorkerActivity();
|
|
2741
|
-
return c.json(activity);
|
|
2742
|
-
});
|
|
2743
|
-
// GET /api/queue - Get all queued requests
|
|
2744
|
-
routes.get("/queue", (c) => {
|
|
2745
|
-
const queue = deps.supervisor.getQueueInfo();
|
|
2746
|
-
const poolStatus = deps.supervisor.getWorkerPoolStatus();
|
|
2747
|
-
return c.json({ queue, ...poolStatus });
|
|
2748
|
-
});
|
|
2749
|
-
// GET /api/queue/:queueId - Get specific queue entry position
|
|
2750
|
-
routes.get("/queue/:queueId", (c) => {
|
|
2751
|
-
const queueId = c.req.param("queueId");
|
|
2752
|
-
const position = deps.supervisor.getQueuePosition(queueId);
|
|
2753
|
-
if (position === undefined) {
|
|
2754
|
-
return c.json({ error: "Queue entry not found" }, 404);
|
|
2755
|
-
}
|
|
2756
|
-
return c.json({ queueId, position });
|
|
2757
|
-
});
|
|
2758
|
-
// DELETE /api/queue/:queueId - Cancel a queued request
|
|
2759
|
-
routes.delete("/queue/:queueId", (c) => {
|
|
2760
|
-
const queueId = c.req.param("queueId");
|
|
2761
|
-
const cancelled = deps.supervisor.cancelQueuedRequest(queueId);
|
|
2762
|
-
if (!cancelled) {
|
|
2763
|
-
return c.json({ error: "Queue entry not found or already processed" }, 404);
|
|
2764
|
-
}
|
|
2765
|
-
return c.json({ cancelled: true });
|
|
2766
|
-
});
|
|
2767
4357
|
return routes;
|
|
2768
4358
|
}
|
|
2769
4359
|
//# sourceMappingURL=sessions.js.map
|