yepanywhere 0.5.2 → 0.6.1
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 +107 -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 +266 -17
- package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js +128 -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 +93 -1
- package/bundled/@yep-anywhere/shared/dist/git-status.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/git-status.js +1 -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 +22 -11
- package/bundled/@yep-anywhere/shared/dist/index.d.ts.map +1 -1
- package/bundled/@yep-anywhere/shared/dist/index.js +9 -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/server-capabilities.d.ts +248 -0
- package/bundled/@yep-anywhere/shared/dist/server-capabilities.d.ts.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/server-capabilities.js +251 -0
- package/bundled/@yep-anywhere/shared/dist/server-capabilities.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/tool-display-actions.d.ts +26 -0
- package/bundled/@yep-anywhere/shared/dist/tool-display-actions.d.ts.map +1 -0
- package/bundled/@yep-anywhere/shared/dist/tool-display-actions.js +9 -0
- package/bundled/@yep-anywhere/shared/dist/tool-display-actions.js.map +1 -0
- 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-BNhVdIq4.js +6 -0
- package/client-dist/assets/es-Csn9n24J.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-DKx4tSpo.js +6 -0
- package/client-dist/assets/index-C41clQ66.js +561 -0
- package/client-dist/assets/index-LbeFv8ym.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-DAkhkwQZ.js +6 -0
- package/client-dist/assets/zh-CN-B7lY0cc6.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/codeModeExec.d.ts +27 -0
- package/dist/codex/codeModeExec.d.ts.map +1 -0
- package/dist/codex/codeModeExec.js +264 -0
- package/dist/codex/codeModeExec.js.map +1 -0
- 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/displayActions.d.ts +48 -0
- package/dist/codex/displayActions.d.ts.map +1 -0
- package/dist/codex/displayActions.js +674 -0
- package/dist/codex/displayActions.js.map +1 -0
- package/dist/codex/normalization.d.ts +10 -6
- package/dist/codex/normalization.d.ts.map +1 -1
- package/dist/codex/normalization.js +94 -261
- 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 +691 -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-metadata-patch.d.ts +24 -0
- package/dist/routes/session-metadata-patch.d.ts.map +1 -0
- package/dist/routes/session-metadata-patch.js +119 -0
- package/dist/routes/session-metadata-patch.js.map +1 -0
- package/dist/routes/session-project-routing.d.ts +25 -0
- package/dist/routes/session-project-routing.d.ts.map +1 -0
- package/dist/routes/session-project-routing.js +32 -0
- package/dist/routes/session-project-routing.js.map +1 -0
- package/dist/routes/session-provider-resolution.d.ts +28 -0
- package/dist/routes/session-provider-resolution.d.ts.map +1 -0
- package/dist/routes/session-provider-resolution.js +22 -0
- package/dist/routes/session-provider-resolution.js.map +1 -0
- package/dist/routes/session-queue-summaries.d.ts +21 -0
- package/dist/routes/session-queue-summaries.d.ts.map +1 -0
- package/dist/routes/session-queue-summaries.js +79 -0
- package/dist/routes/session-queue-summaries.js.map +1 -0
- package/dist/routes/session-recovered-queue.d.ts +43 -0
- package/dist/routes/session-recovered-queue.d.ts.map +1 -0
- package/dist/routes/session-recovered-queue.js +132 -0
- package/dist/routes/session-recovered-queue.js.map +1 -0
- 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/session-thinking-options.d.ts +12 -0
- package/dist/routes/session-thinking-options.d.ts.map +1 -0
- package/dist/routes/session-thinking-options.js +7 -0
- package/dist/routes/session-thinking-options.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 +1965 -679
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/settings-parsers.d.ts +37 -0
- package/dist/routes/settings-parsers.d.ts.map +1 -0
- package/dist/routes/settings-parsers.js +896 -0
- package/dist/routes/settings-parsers.js.map +1 -0
- package/dist/routes/settings.d.ts +7 -1
- package/dist/routes/settings.d.ts.map +1 -1
- package/dist/routes/settings.js +114 -258
- 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 +16 -1
- package/dist/routes/version.d.ts.map +1 -1
- package/dist/routes/version.js +27 -8
- 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 +5 -0
- package/dist/sdk/messageQueue.d.ts.map +1 -1
- package/dist/sdk/messageQueue.js +47 -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-model-catalog.d.ts +34 -0
- package/dist/sdk/providers/codex-model-catalog.d.ts.map +1 -0
- package/dist/sdk/providers/codex-model-catalog.js +378 -0
- package/dist/sdk/providers/codex-model-catalog.js.map +1 -0
- package/dist/sdk/providers/codex-notification-guards.d.ts +15 -0
- package/dist/sdk/providers/codex-notification-guards.d.ts.map +1 -0
- package/dist/sdk/providers/codex-notification-guards.js +159 -0
- package/dist/sdk/providers/codex-notification-guards.js.map +1 -0
- 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/ImageGenerationItem.d.ts +9 -0
- package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.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 +39 -1
- package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/ThreadId.d.ts +5 -0
- package/dist/sdk/providers/codex-protocol/generated/ThreadId.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.d.ts +7 -0
- package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.js.map +1 -0
- 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/AskForApproval.d.ts +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/AskForApproval.d.ts.map +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts +12 -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/CodexErrorInfo.d.ts +1 -1
- package/dist/sdk/providers/codex-protocol/generated/v2/CodexErrorInfo.d.ts.map +1 -1
- 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 +9 -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 +21 -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/ThreadExtra.d.ts +5 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts +71 -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/ThreadHistoryMode.d.ts +2 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.d.ts.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.js +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.js.map +1 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts +22 -12
- 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 +14 -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 +23 -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/Turn.d.ts +3 -0
- package/dist/sdk/providers/codex-protocol/generated/v2/Turn.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-summary-helpers.d.ts +41 -0
- package/dist/sdk/providers/codex-summary-helpers.d.ts.map +1 -0
- package/dist/sdk/providers/codex-summary-helpers.js +207 -0
- package/dist/sdk/providers/codex-summary-helpers.js.map +1 -0
- package/dist/sdk/providers/codex.d.ts +23 -26
- package/dist/sdk/providers/codex.d.ts.map +1 -1
- package/dist/sdk/providers/codex.js +429 -623
- 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-models.d.ts +17 -0
- package/dist/sdk/providers/opencode-models.d.ts.map +1 -0
- package/dist/sdk/providers/opencode-models.js +83 -0
- package/dist/sdk/providers/opencode-models.js.map +1 -0
- 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 +44 -4
- package/dist/sdk/providers/opencode.d.ts.map +1 -1
- package/dist/sdk/providers/opencode.js +329 -102
- 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 +43 -1
- 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 +806 -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 +1070 -142
- 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 +4 -1
- package/dist/sessions/normalization.d.ts.map +1 -1
- package/dist/sessions/normalization.js +109 -21
- 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 +6 -0
- package/dist/sessions/pagination.d.ts.map +1 -1
- package/dist/sessions/pagination.js +158 -5
- 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 +259 -30
- package/dist/supervisor/Process.d.ts.map +1 -1
- package/dist/supervisor/Process.js +1282 -229
- 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,32 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ALL_PERMISSION_MODES, buildEffectiveAgentContext, getModelContextWindow, isUrlProjectId, isWorkstreamId, mainWorkstreamId, 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";
|
|
5
6
|
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";
|
|
9
|
+
import { resolveCanonicalProjectRedirect } from "./session-project-routing.js";
|
|
8
10
|
import { ensureRemoteDirectory } from "../sdk/remote-spawn.js";
|
|
11
|
+
import { parseSlashCommandSubmission } from "../sdk/slashCommandEmulation.js";
|
|
9
12
|
import { getProjectDirFromCwd, syncSessions } from "../sdk/session-sync.js";
|
|
10
13
|
import { appendApprovalAuditLog } from "../security/approvalAuditLog.js";
|
|
11
14
|
import { CodexSessionReader } from "../sessions/codex-reader.js";
|
|
12
15
|
import { cloneClaudeSession, cloneCodexSession } from "../sessions/fork.js";
|
|
13
|
-
import { GeminiSessionReader } from "../sessions/gemini-reader.js";
|
|
14
|
-
import { buildDag } from "../sessions/dag.js";
|
|
15
16
|
import { GrokSessionReader } from "../sessions/grok-reader.js";
|
|
17
|
+
import { extractLastAgentExcerpt } from "../sessions/agent-excerpt.js";
|
|
16
18
|
import { normalizeSession } from "../sessions/normalization.js";
|
|
17
|
-
import {
|
|
19
|
+
import { applyRecapOverlayToSession, applyRecapOverlayToSummary, hasEquivalentRecapMessage, hasUnreadProviderContent, latestRecapMessage, mergeRecapMessages, } from "../sessions/recap-overlays.js";
|
|
20
|
+
import { sliceAfterMessageIdWithMatch, sliceAtCompactAndUserTurnBoundaries, sliceAtCompactBoundaries, sliceAtUserTurnBoundary, } from "../sessions/pagination.js";
|
|
21
|
+
import { augmentTaskListSnapshots, pruneTaskListSnapshotsToLatest, } from "../augments/task-list-augments.js";
|
|
18
22
|
import { augmentEditToolUses, augmentPersistedSessionMessages, } from "../sessions/persisted-augments.js";
|
|
19
|
-
import { findSessionSummaryAcrossProviders } from "../sessions/provider-resolution.js";
|
|
20
|
-
import {
|
|
23
|
+
import { findSessionSummaryAcrossProviders, getSessionSources, } from "../sessions/provider-resolution.js";
|
|
24
|
+
import { getProvider } from "../sdk/providers/index.js";
|
|
25
|
+
import { getStaticSlashCommandsForProvider } from "../sdk/providers/staticSlashCommands.js";
|
|
26
|
+
import { ResumeCompactionError } from "../supervisor/Supervisor.js";
|
|
27
|
+
import { buildUserMessageMetadata, normalizeOptionalServiceTier, parseHelperSettings, parseOptionalExecutor, parseOptionalResumeMode, } from "./session-request-helpers.js";
|
|
28
|
+
import { resolveCompactPercent, resolveCompactWindow, } from "./session-compact-thresholds.js";
|
|
29
|
+
import { getClaudeResumeBlockerFromReader, } from "./session-claude-resume-guard.js";
|
|
30
|
+
import { isClaudeSdkProviderName, isCodexProviderName, providerResolutionDeps, } from "./session-provider-resolution.js";
|
|
31
|
+
import { parseSessionMetadataPatch } from "./session-metadata-patch.js";
|
|
32
|
+
import { recoveredPatientQueueSummaries, sessionQueueSummaries, } from "./session-queue-summaries.js";
|
|
33
|
+
import { reportableProcessState, resolveRecoveredGroupForDelivery, resumeRecoveredGroup, } from "./session-recovered-queue.js";
|
|
34
|
+
import { buildThinkingOptions } from "./session-thinking-options.js";
|
|
21
35
|
const SESSION_DETAIL_SLOW_LOG_MS = 250;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
36
|
+
const DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS = 2;
|
|
37
|
+
const LARGE_FULL_HISTORY_MESSAGE_THRESHOLD = 1000;
|
|
38
|
+
async function getSessionSlashCommands(process, provider) {
|
|
39
|
+
if (process?.supportsDynamicCommands) {
|
|
40
|
+
const commands = await process.supportedCommands();
|
|
41
|
+
if (commands)
|
|
42
|
+
return commands;
|
|
43
|
+
}
|
|
44
|
+
return getStaticSlashCommandsForProvider(provider);
|
|
45
|
+
}
|
|
24
46
|
function roundedMs(value) {
|
|
25
47
|
return Math.round(value * 10) / 10;
|
|
26
48
|
}
|
|
27
|
-
function isClaudeSdkProviderName(provider) {
|
|
28
|
-
return provider === "claude" || provider === "claude-ollama";
|
|
29
|
-
}
|
|
30
49
|
/**
|
|
31
50
|
* Type guard to check if a result is a QueuedResponse
|
|
32
51
|
*/
|
|
@@ -39,241 +58,19 @@ function isQueuedResponse(result) {
|
|
|
39
58
|
function isQueueFullResponse(result) {
|
|
40
59
|
return "error" in result && result.error === "queue_full";
|
|
41
60
|
}
|
|
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
|
-
function isCodexProviderName(provider) {
|
|
181
|
-
return provider === "codex" || provider === "codex-oss";
|
|
182
|
-
}
|
|
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
|
|
213
|
-
}
|
|
214
|
-
function parseIsoTimestamp(value) {
|
|
215
|
-
if (typeof value !== "string" || !value.trim())
|
|
216
|
-
return undefined;
|
|
217
|
-
return Number.isNaN(Date.parse(value)) ? undefined : value;
|
|
218
|
-
}
|
|
219
|
-
function parseDeliveryIntent(value) {
|
|
220
|
-
return typeof value === "string" &&
|
|
221
|
-
USER_MESSAGE_DELIVERY_INTENTS.has(value)
|
|
222
|
-
? value
|
|
223
|
-
: undefined;
|
|
224
|
-
}
|
|
225
|
-
function parseShortString(value, maxLength) {
|
|
226
|
-
if (typeof value !== "string")
|
|
227
|
-
return undefined;
|
|
228
|
-
const trimmed = value.trim();
|
|
229
|
-
return trimmed ? trimmed.slice(0, maxLength) : undefined;
|
|
230
|
-
}
|
|
231
|
-
function parseStringList(value, maxItems, maxLength) {
|
|
232
|
-
if (!Array.isArray(value))
|
|
233
|
-
return undefined;
|
|
234
|
-
const parsed = value
|
|
235
|
-
.map((entry) => parseShortString(entry, maxLength))
|
|
236
|
-
.filter((entry) => entry !== undefined)
|
|
237
|
-
.slice(0, maxItems);
|
|
238
|
-
return parsed.length > 0 ? parsed : undefined;
|
|
67
|
+
function isApprovalAuditLogEnabled(deps) {
|
|
68
|
+
return (deps.serverSettingsService?.getSetting("approvalAuditLogEnabled") === true);
|
|
239
69
|
}
|
|
240
|
-
function
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
: {};
|
|
245
|
-
const composition = {
|
|
246
|
-
typingStartedAt: parseIsoTimestamp(rawComposition.typingStartedAt),
|
|
247
|
-
typingEndedAt: parseIsoTimestamp(rawComposition.typingEndedAt),
|
|
248
|
-
lastEditedAt: parseIsoTimestamp(rawComposition.lastEditedAt),
|
|
249
|
-
submittedAt: parseIsoTimestamp(rawComposition.submittedAt),
|
|
250
|
-
};
|
|
251
|
-
const cleanComposition = Object.fromEntries(Object.entries(composition).filter(([, value]) => value !== undefined));
|
|
252
|
-
const clientTimestamp = typeof body.clientTimestamp === "number" && Number.isFinite(body.clientTimestamp)
|
|
253
|
-
? body.clientTimestamp
|
|
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;
|
|
268
|
-
return {
|
|
269
|
-
deliveryIntent: parseDeliveryIntent(rawMetadata.deliveryIntent) ?? fallbackIntent,
|
|
270
|
-
...(Object.keys(cleanComposition).length > 0
|
|
271
|
-
? { composition: cleanComposition }
|
|
272
|
-
: {}),
|
|
273
|
-
...(speech ? { speech } : {}),
|
|
274
|
-
...(clientTimestamp !== undefined ? { clientTimestamp } : {}),
|
|
275
|
-
serverReceivedAt: new Date(serverTimestamp).toISOString(),
|
|
276
|
-
};
|
|
70
|
+
async function resolveSessionReaderForAgentContent({ deps, project, sessionId, projectId, }) {
|
|
71
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
72
|
+
const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
|
|
73
|
+
return resolved?.source.reader ?? deps.readerFactory(project);
|
|
277
74
|
}
|
|
278
75
|
const RESTART_HANDOFF_MAX_CHARS = 40_000;
|
|
279
76
|
const RESTART_HANDOFF_JSON_MAX_CHARS = 2_000;
|
|
@@ -336,7 +133,9 @@ function renderRestartContent(content) {
|
|
|
336
133
|
case "text":
|
|
337
134
|
return typed.text ?? "";
|
|
338
135
|
case "thinking":
|
|
339
|
-
return typed.thinking
|
|
136
|
+
return typed.thinking
|
|
137
|
+
? `[thinking]\n${typed.thinking}`
|
|
138
|
+
: "[thinking]";
|
|
340
139
|
case "tool_use":
|
|
341
140
|
return `[tool_use ${typed.name ?? "unknown"}]\n${stringifyForRestart(typed.input, RESTART_HANDOFF_JSON_MAX_CHARS)}`;
|
|
342
141
|
case "tool_result":
|
|
@@ -372,6 +171,33 @@ function messageContent(message) {
|
|
|
372
171
|
const nested = message.message;
|
|
373
172
|
return nested?.content ?? message.content;
|
|
374
173
|
}
|
|
174
|
+
function messageId(message) {
|
|
175
|
+
if (!message) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return ((typeof message.uuid === "string" && message.uuid) ||
|
|
179
|
+
(typeof message.id === "string" && message.id) ||
|
|
180
|
+
undefined);
|
|
181
|
+
}
|
|
182
|
+
function findDurableRecapCursor(afterMessageId, recaps) {
|
|
183
|
+
if (!afterMessageId) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return recaps.find((recap) => recap.uuid === afterMessageId || recap.id === afterMessageId);
|
|
187
|
+
}
|
|
188
|
+
function sliceAfterDurableRecapCursor(params) {
|
|
189
|
+
const index = params.messages.findIndex((message) => {
|
|
190
|
+
const id = messageId(message);
|
|
191
|
+
return (id === params.cursorId ||
|
|
192
|
+
id === params.recap.uuid ||
|
|
193
|
+
id === params.recap.id ||
|
|
194
|
+
hasEquivalentRecapMessage([message], params.recap));
|
|
195
|
+
});
|
|
196
|
+
if (index < 0) {
|
|
197
|
+
return { messages: params.messages, found: false };
|
|
198
|
+
}
|
|
199
|
+
return { messages: params.messages.slice(index + 1), found: true };
|
|
200
|
+
}
|
|
375
201
|
function messageHasToolResult(message) {
|
|
376
202
|
if (message.toolUseResult !== undefined) {
|
|
377
203
|
return true;
|
|
@@ -382,6 +208,32 @@ function messageHasToolResult(message) {
|
|
|
382
208
|
typeof block === "object" &&
|
|
383
209
|
block.type === "tool_result"));
|
|
384
210
|
}
|
|
211
|
+
function messageTextContent(message) {
|
|
212
|
+
const content = messageContent(message);
|
|
213
|
+
if (typeof content === "string") {
|
|
214
|
+
return content;
|
|
215
|
+
}
|
|
216
|
+
if (!Array.isArray(content)) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const textBlocks = content
|
|
220
|
+
.map((block) => block &&
|
|
221
|
+
typeof block === "object" &&
|
|
222
|
+
block.type === "text" &&
|
|
223
|
+
typeof block.text === "string"
|
|
224
|
+
? block.text
|
|
225
|
+
: "")
|
|
226
|
+
.filter(Boolean);
|
|
227
|
+
return textBlocks.length > 0 ? textBlocks.join("\n") : undefined;
|
|
228
|
+
}
|
|
229
|
+
function isSlashCommandSkillBodyUserMessage(message) {
|
|
230
|
+
if (message.isMeta !== true) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return (messageTextContent(message)
|
|
234
|
+
?.trimStart()
|
|
235
|
+
.startsWith("Base directory for this skill:") === true);
|
|
236
|
+
}
|
|
385
237
|
function isRestartInternalCompactCommand(message) {
|
|
386
238
|
if (messageRole(message) !== "user" || messageHasToolResult(message)) {
|
|
387
239
|
return false;
|
|
@@ -751,8 +603,111 @@ function isHumanUserMessage(message) {
|
|
|
751
603
|
message.type;
|
|
752
604
|
return role === "user" && !messageHasToolResult(message);
|
|
753
605
|
}
|
|
606
|
+
function isCompactSummaryUserMessage(message) {
|
|
607
|
+
return message.isCompactSummary === true;
|
|
608
|
+
}
|
|
609
|
+
function isUserAuthoredRequest(message) {
|
|
610
|
+
return (isHumanUserMessage(message) &&
|
|
611
|
+
!isCompactSummaryUserMessage(message) &&
|
|
612
|
+
!isSlashCommandSkillBodyUserMessage(message));
|
|
613
|
+
}
|
|
614
|
+
function resolveForkAfterBoundary(messages, sourceMessageId, sourceIsBusy) {
|
|
615
|
+
const sourceIndex = messages.findIndex((message) => messageId(message) === sourceMessageId);
|
|
616
|
+
const sourceMessage = messages[sourceIndex];
|
|
617
|
+
if (sourceIndex < 0 || !sourceMessage) {
|
|
618
|
+
return { error: "Selected source message was not found", status: 404 };
|
|
619
|
+
}
|
|
620
|
+
if (!isUserAuthoredRequest(sourceMessage)) {
|
|
621
|
+
return {
|
|
622
|
+
error: "sourceMessageId must identify a user-authored request",
|
|
623
|
+
status: 400,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
let nextUserIndex = -1;
|
|
627
|
+
for (let index = sourceIndex + 1; index < messages.length; index += 1) {
|
|
628
|
+
const candidate = messages[index];
|
|
629
|
+
if (candidate && isUserAuthoredRequest(candidate)) {
|
|
630
|
+
nextUserIndex = index;
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (nextUserIndex < 0 && sourceIsBusy) {
|
|
635
|
+
return {
|
|
636
|
+
error: "The selected turn is still in progress",
|
|
637
|
+
status: 409,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
const searchEnd = nextUserIndex >= 0 ? nextUserIndex - 1 : messages.length - 1;
|
|
641
|
+
let hasAssistantResponse = false;
|
|
642
|
+
for (let index = sourceIndex + 1; index <= searchEnd; index += 1) {
|
|
643
|
+
const candidate = messages[index];
|
|
644
|
+
if (candidate && messageRole(candidate) === "assistant") {
|
|
645
|
+
hasAssistantResponse = true;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (!hasAssistantResponse) {
|
|
650
|
+
return {
|
|
651
|
+
error: "The selected request has no completed assistant response",
|
|
652
|
+
status: 409,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
let boundary;
|
|
656
|
+
for (let index = searchEnd; index > sourceIndex; index -= 1) {
|
|
657
|
+
const candidate = messages[index];
|
|
658
|
+
const role = candidate ? messageRole(candidate) : undefined;
|
|
659
|
+
if (candidate &&
|
|
660
|
+
messageId(candidate) &&
|
|
661
|
+
(role === "user" || role === "assistant")) {
|
|
662
|
+
boundary = candidate;
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const retainedThroughMessageId = messageId(boundary);
|
|
667
|
+
if (!boundary || !retainedThroughMessageId) {
|
|
668
|
+
return { error: "Completed turn boundary has no message id", status: 409 };
|
|
669
|
+
}
|
|
670
|
+
const placementAfterMessageId = [...messages].reverse().map(messageId).find(Boolean) ??
|
|
671
|
+
retainedThroughMessageId;
|
|
672
|
+
const rendered = renderRestartContent(messageContent(boundary)).trim();
|
|
673
|
+
return {
|
|
674
|
+
placementAfterMessageId,
|
|
675
|
+
retainedThroughMessageId,
|
|
676
|
+
retainedThroughContext: rendered
|
|
677
|
+
? truncateForRestart(rendered, 1200)
|
|
678
|
+
: undefined,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function forkSummaryTitle(summary, fallback) {
|
|
682
|
+
const firstLine = summary
|
|
683
|
+
.split("\n")
|
|
684
|
+
.map((line) => line.trim())
|
|
685
|
+
.find(Boolean);
|
|
686
|
+
const candidate = firstLine
|
|
687
|
+
?.replace(/^#{1,6}\s*/u, "")
|
|
688
|
+
.replace(/^title:\s*/iu, "")
|
|
689
|
+
.trim()
|
|
690
|
+
.replace(/[.!?]+$/u, "");
|
|
691
|
+
return truncateSessionTitle(candidate || fallback || "Forked session");
|
|
692
|
+
}
|
|
693
|
+
function generatedRetitleCandidate(title) {
|
|
694
|
+
const firstLine = title
|
|
695
|
+
.split("\n")
|
|
696
|
+
.map((line) => line.trim())
|
|
697
|
+
.find(Boolean);
|
|
698
|
+
if (!firstLine) {
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
const candidate = normalizeRestartTitleCandidate(firstLine
|
|
702
|
+
.replace(/^title:\s*/iu, "")
|
|
703
|
+
.replace(/^["'“”‘’]+|["'“”‘’]+$/gu, "")
|
|
704
|
+
.replace(/[.!?]+$/u, "")
|
|
705
|
+
.trim());
|
|
706
|
+
return candidate ? truncateSessionTitle(candidate) : undefined;
|
|
707
|
+
}
|
|
754
708
|
function messageTitleCandidate(message) {
|
|
755
|
-
if (!isHumanUserMessage(message) ||
|
|
709
|
+
if (!isHumanUserMessage(message) ||
|
|
710
|
+
isRestartInternalCompactCommand(message)) {
|
|
756
711
|
return undefined;
|
|
757
712
|
}
|
|
758
713
|
const nested = message.message;
|
|
@@ -793,6 +748,18 @@ function deriveRestartTitle(params) {
|
|
|
793
748
|
const title = /^Handoff:/i.test(base) ? base : `Handoff: ${base}`;
|
|
794
749
|
return truncateRestartTitle(title);
|
|
795
750
|
}
|
|
751
|
+
function deriveForkTitle(params) {
|
|
752
|
+
const candidates = [
|
|
753
|
+
params.preferredTitle,
|
|
754
|
+
params.sourceSession.customTitle,
|
|
755
|
+
params.sourceSession.title,
|
|
756
|
+
latestUserTitleCandidate(params.sourceSession.messages),
|
|
757
|
+
];
|
|
758
|
+
const base = candidates.map(normalizeRestartTitleCandidate).find(Boolean) ??
|
|
759
|
+
"forked session";
|
|
760
|
+
const title = /^Fork:/i.test(base) ? base : `Fork: ${base}`;
|
|
761
|
+
return truncateRestartTitle(title);
|
|
762
|
+
}
|
|
796
763
|
function buildRestartHandoff(params) {
|
|
797
764
|
const { handoffTitle, sourceSession, sourceProvider, sourceModel, sourceProcess, compactAttempt, projectPath, reason, omittedCount, transcript, } = params;
|
|
798
765
|
const oldProcessLine = sourceProcess
|
|
@@ -847,12 +814,14 @@ function isRestartReplacementActivity(message) {
|
|
|
847
814
|
function sdkMessagesToClientMessages(sdkMessages) {
|
|
848
815
|
const messages = [];
|
|
849
816
|
for (const msg of sdkMessages) {
|
|
817
|
+
const rawFields = msg;
|
|
850
818
|
if (isCompactBoundaryMessage(msg)) {
|
|
851
819
|
const content = (typeof msg.message?.content === "string"
|
|
852
820
|
? msg.message.content
|
|
853
821
|
: undefined) ??
|
|
854
822
|
(typeof msg.content === "string" ? msg.content : "Context compacted");
|
|
855
823
|
messages.push({
|
|
824
|
+
...rawFields,
|
|
856
825
|
id: msg.uuid ?? `msg-${Date.now()}-${messages.length}`,
|
|
857
826
|
type: "system",
|
|
858
827
|
role: "system",
|
|
@@ -889,6 +858,7 @@ function sdkMessagesToClientMessages(sdkMessages) {
|
|
|
889
858
|
continue;
|
|
890
859
|
}
|
|
891
860
|
messages.push({
|
|
861
|
+
...rawFields,
|
|
892
862
|
id: msg.uuid ?? `msg-${Date.now()}-${messages.length}`,
|
|
893
863
|
type: msg.type,
|
|
894
864
|
role: msg.type,
|
|
@@ -1005,6 +975,31 @@ function extractContextUsageFromSDKMessages(sdkMessages, model, provider, resolv
|
|
|
1005
975
|
}
|
|
1006
976
|
export function createSessionsRoutes(deps) {
|
|
1007
977
|
const routes = new Hono();
|
|
978
|
+
const activeForkSummaryJobs = new Map();
|
|
979
|
+
const emitTranscriptDisplayObjects = (sessionId) => {
|
|
980
|
+
deps.eventBus?.emit({
|
|
981
|
+
type: "session-metadata-changed",
|
|
982
|
+
sessionId,
|
|
983
|
+
transcriptDisplayObjects: deps.sessionMetadataService?.getTranscriptDisplayObjects(sessionId) ??
|
|
984
|
+
[],
|
|
985
|
+
timestamp: new Date().toISOString(),
|
|
986
|
+
});
|
|
987
|
+
};
|
|
988
|
+
const updateForkSummaryChildMetadata = async (childSessionId, parentSessionId, title, archived) => {
|
|
989
|
+
await deps.sessionMetadataService?.updateMetadata(childSessionId, {
|
|
990
|
+
title,
|
|
991
|
+
archived,
|
|
992
|
+
parentSessionId,
|
|
993
|
+
});
|
|
994
|
+
deps.eventBus?.emit({
|
|
995
|
+
type: "session-metadata-changed",
|
|
996
|
+
sessionId: childSessionId,
|
|
997
|
+
title,
|
|
998
|
+
archived,
|
|
999
|
+
parentSessionId,
|
|
1000
|
+
timestamp: new Date().toISOString(),
|
|
1001
|
+
});
|
|
1002
|
+
};
|
|
1008
1003
|
const getCodexReader = (projectPath) => deps.codexReaderFactory?.(projectPath) ??
|
|
1009
1004
|
(deps.codexSessionsDir
|
|
1010
1005
|
? new CodexSessionReader({
|
|
@@ -1012,13 +1007,6 @@ export function createSessionsRoutes(deps) {
|
|
|
1012
1007
|
projectPath,
|
|
1013
1008
|
})
|
|
1014
1009
|
: null);
|
|
1015
|
-
const getGrokReader = (projectPath) => deps.grokReaderFactory?.(projectPath) ??
|
|
1016
|
-
(deps.grokSessionsDir
|
|
1017
|
-
? new GrokSessionReader({
|
|
1018
|
-
sessionsDir: deps.grokSessionsDir,
|
|
1019
|
-
projectPath,
|
|
1020
|
-
})
|
|
1021
|
-
: null);
|
|
1022
1010
|
let unscopedGrokReader;
|
|
1023
1011
|
const getUnscopedGrokReader = () => {
|
|
1024
1012
|
if (unscopedGrokReader !== undefined) {
|
|
@@ -1045,8 +1033,91 @@ export function createSessionsRoutes(deps) {
|
|
|
1045
1033
|
const search = new URL(requestUrl).search;
|
|
1046
1034
|
return `/api/projects/${canonicalProjectId}/sessions/${encodeURIComponent(sessionId)}${suffix}${search}`;
|
|
1047
1035
|
};
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1036
|
+
const buildSessionProjectRedirectPath = (canonicalProjectId, sessionId, suffix, requestUrl) => {
|
|
1037
|
+
const search = new URL(requestUrl).search;
|
|
1038
|
+
return `/api/projects/${canonicalProjectId}/sessions/${encodeURIComponent(sessionId)}${suffix}${search}`;
|
|
1039
|
+
};
|
|
1040
|
+
const resolveSessionProjectRouting = async (requestProjectId, sessionId) => {
|
|
1041
|
+
const workingProjectId = deps.sessionMetadataService?.getMetadata(sessionId)?.workingProjectId;
|
|
1042
|
+
const activeProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
1043
|
+
const redirectProjectId = resolveCanonicalProjectRedirect({
|
|
1044
|
+
requestProjectId,
|
|
1045
|
+
workingProjectId,
|
|
1046
|
+
activeProcessProjectId: typeof activeProcess?.projectId === "string"
|
|
1047
|
+
? activeProcess.projectId
|
|
1048
|
+
: undefined,
|
|
1049
|
+
});
|
|
1050
|
+
if (redirectProjectId) {
|
|
1051
|
+
return { redirectProjectId };
|
|
1052
|
+
}
|
|
1053
|
+
const workingProject = await deps.scanner.getOrCreateProject(requestProjectId);
|
|
1054
|
+
if (!workingProject) {
|
|
1055
|
+
return { error: "Project not found", status: 404 };
|
|
1056
|
+
}
|
|
1057
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
1058
|
+
const overlayTranscriptProjectId = metadata?.workingProjectId === requestProjectId &&
|
|
1059
|
+
metadata.transcriptProjectId &&
|
|
1060
|
+
metadata.transcriptProjectId !== requestProjectId
|
|
1061
|
+
? metadata.transcriptProjectId
|
|
1062
|
+
: undefined;
|
|
1063
|
+
// Default: the transcript lives under the working (request) project.
|
|
1064
|
+
let transcriptProjectId = requestProjectId;
|
|
1065
|
+
let transcriptProject = workingProject;
|
|
1066
|
+
// Overlay case (session "moved" to a different working project): the
|
|
1067
|
+
// transcript may live under a distinct project. Trust the recorded pointer
|
|
1068
|
+
// only if the transcript is actually findable there — a stale/wrong pointer
|
|
1069
|
+
// must not permanently brick viewing, so fall back to the working project.
|
|
1070
|
+
if (overlayTranscriptProjectId) {
|
|
1071
|
+
const overlayProject = await deps.scanner.getOrCreateProject(overlayTranscriptProjectId);
|
|
1072
|
+
const overlayHasTranscript = overlayProject
|
|
1073
|
+
? Boolean(await findSessionSummaryAcrossProviders(overlayProject, sessionId, overlayTranscriptProjectId, providerResolutionDeps(deps), deps.sessionMetadataService?.getProvider(sessionId), { readMode: "head" }))
|
|
1074
|
+
: false;
|
|
1075
|
+
if (overlayProject && overlayHasTranscript) {
|
|
1076
|
+
transcriptProjectId = overlayTranscriptProjectId;
|
|
1077
|
+
transcriptProject = overlayProject;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return {
|
|
1081
|
+
transcriptProject,
|
|
1082
|
+
transcriptProjectId,
|
|
1083
|
+
workingProject,
|
|
1084
|
+
workingProjectId: requestProjectId,
|
|
1085
|
+
};
|
|
1086
|
+
};
|
|
1087
|
+
const getGlobalInstructions = () => buildEffectiveAgentContext({
|
|
1088
|
+
globalInstructions: deps.serverSettingsService?.getSetting("globalInstructions"),
|
|
1089
|
+
hints: deps.serverSettingsService?.getSetting("agentContextHints"),
|
|
1090
|
+
});
|
|
1091
|
+
const resolveLaunchWorkstreamTarget = (project, rawWorkstreamId) => {
|
|
1092
|
+
if (rawWorkstreamId === undefined) {
|
|
1093
|
+
return { projectPath: project.path };
|
|
1094
|
+
}
|
|
1095
|
+
if (!isWorkstreamId(rawWorkstreamId)) {
|
|
1096
|
+
return { error: "Invalid workstream ID format", status: 400 };
|
|
1097
|
+
}
|
|
1098
|
+
if (deps.serverSettingsService?.getSetting("workstreamsEnabled") !== true) {
|
|
1099
|
+
return { error: "Workstreams are disabled", status: 404 };
|
|
1100
|
+
}
|
|
1101
|
+
if (!deps.workstreamService) {
|
|
1102
|
+
return { error: "Workstreams unavailable", status: 503 };
|
|
1103
|
+
}
|
|
1104
|
+
const workstreamId = rawWorkstreamId;
|
|
1105
|
+
if (workstreamId === mainWorkstreamId(project.id)) {
|
|
1106
|
+
return { projectPath: project.path };
|
|
1107
|
+
}
|
|
1108
|
+
if (!deps.sessionMetadataService) {
|
|
1109
|
+
return { error: "Session metadata unavailable", status: 503 };
|
|
1110
|
+
}
|
|
1111
|
+
const workstream = deps.workstreamService.getWorkstream(project.id, workstreamId);
|
|
1112
|
+
if (workstream?.kind !== "checkout") {
|
|
1113
|
+
return { error: "Workstream not found", status: 404 };
|
|
1114
|
+
}
|
|
1115
|
+
if (workstream.status !== "active") {
|
|
1116
|
+
return { error: "Workstream is not active", status: 409 };
|
|
1117
|
+
}
|
|
1118
|
+
return { projectPath: workstream.path, workstreamId };
|
|
1119
|
+
};
|
|
1120
|
+
const persistLaunchMetadata = async (sessionId, provider, executor, initialPrompt, requestedModel, promptSuggestionMode, recapAfterSeconds, workstreamId) => {
|
|
1050
1121
|
if (!deps.sessionMetadataService) {
|
|
1051
1122
|
return;
|
|
1052
1123
|
}
|
|
@@ -1059,18 +1130,40 @@ export function createSessionsRoutes(deps) {
|
|
|
1059
1130
|
if (initialPrompt?.trim()) {
|
|
1060
1131
|
await deps.sessionMetadataService.setInitialPrompt(sessionId, initialPrompt);
|
|
1061
1132
|
}
|
|
1133
|
+
// Persist the requested YA model id (the launch alias, incl. "default") so
|
|
1134
|
+
// per-model settings still key by it after a server restart, instead of
|
|
1135
|
+
// falling back to the reported model. See topics/provider-abstraction.md.
|
|
1136
|
+
if (requestedModel) {
|
|
1137
|
+
await deps.sessionMetadataService.setRequestedModel(sessionId, requestedModel);
|
|
1138
|
+
}
|
|
1139
|
+
// Persist the resolved prompt-suggestion mode so a later resume (which may
|
|
1140
|
+
// omit it from the request body) recovers the per-session preference
|
|
1141
|
+
// instead of falling back to the provider's native default.
|
|
1142
|
+
if (promptSuggestionMode !== undefined) {
|
|
1143
|
+
await deps.sessionMetadataService.updateMetadata(sessionId, {
|
|
1144
|
+
promptSuggestionMode,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
if (recapAfterSeconds !== undefined) {
|
|
1148
|
+
await deps.sessionMetadataService.updateMetadata(sessionId, {
|
|
1149
|
+
recapAfterSeconds,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
if (workstreamId !== undefined) {
|
|
1153
|
+
await deps.sessionMetadataService.setWorkstream(sessionId, workstreamId);
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
const loadProviderSession = async (project, sessionId, projectId, preferredProvider, afterMessageId, options) => {
|
|
1157
|
+
for (const source of getSessionSources(project, providerResolutionDeps(deps), preferredProvider)) {
|
|
1158
|
+
const loaded = await source.reader.getSession(sessionId, projectId, afterMessageId, options);
|
|
1159
|
+
if (loaded) {
|
|
1160
|
+
return loaded;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return null;
|
|
1062
1164
|
};
|
|
1063
1165
|
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);
|
|
1166
|
+
const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), preferredProvider, { readMode: "head" });
|
|
1074
1167
|
if (resolved) {
|
|
1075
1168
|
const loaded = await resolved.source.reader.getSession(sessionId, projectId, undefined, { includeOrphans: false });
|
|
1076
1169
|
if (loaded) {
|
|
@@ -1092,6 +1185,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1092
1185
|
processId: process.id,
|
|
1093
1186
|
permissionMode: process.permissionMode,
|
|
1094
1187
|
modeVersion: process.modeVersion,
|
|
1188
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
1095
1189
|
},
|
|
1096
1190
|
provider: process.provider,
|
|
1097
1191
|
model: process.resolvedModel ?? process.model,
|
|
@@ -1127,7 +1221,8 @@ export function createSessionsRoutes(deps) {
|
|
|
1127
1221
|
unsubscribe = null;
|
|
1128
1222
|
};
|
|
1129
1223
|
unsubscribe = replacement.subscribe((event) => {
|
|
1130
|
-
if (event.type === "message" &&
|
|
1224
|
+
if (event.type === "message" &&
|
|
1225
|
+
isRestartReplacementActivity(event.message)) {
|
|
1131
1226
|
cleanup();
|
|
1132
1227
|
void deps.supervisor.abortProcess(oldProcess.id).catch((error) => {
|
|
1133
1228
|
console.warn(`[restart] Failed to abort old process ${oldProcess.id}:`, error);
|
|
@@ -1157,11 +1252,19 @@ export function createSessionsRoutes(deps) {
|
|
|
1157
1252
|
if (!isUrlProjectId(projectId)) {
|
|
1158
1253
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1159
1254
|
}
|
|
1160
|
-
const
|
|
1161
|
-
if (
|
|
1162
|
-
return c.
|
|
1255
|
+
const routing = await resolveSessionProjectRouting(projectId, c.req.param("sessionId"));
|
|
1256
|
+
if ("redirectProjectId" in routing) {
|
|
1257
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, c.req.param("sessionId"), "", c.req.url), 307);
|
|
1258
|
+
}
|
|
1259
|
+
if ("error" in routing) {
|
|
1260
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1163
1261
|
}
|
|
1164
|
-
const reader =
|
|
1262
|
+
const reader = await resolveSessionReaderForAgentContent({
|
|
1263
|
+
deps,
|
|
1264
|
+
project: routing.transcriptProject,
|
|
1265
|
+
sessionId: c.req.param("sessionId"),
|
|
1266
|
+
projectId: routing.transcriptProjectId,
|
|
1267
|
+
});
|
|
1165
1268
|
const mappings = await reader.getAgentMappings();
|
|
1166
1269
|
return c.json({ mappings });
|
|
1167
1270
|
});
|
|
@@ -1174,17 +1277,30 @@ export function createSessionsRoutes(deps) {
|
|
|
1174
1277
|
if (!isUrlProjectId(projectId)) {
|
|
1175
1278
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1176
1279
|
}
|
|
1177
|
-
const
|
|
1178
|
-
if (
|
|
1179
|
-
return c.
|
|
1280
|
+
const routing = await resolveSessionProjectRouting(projectId, c.req.param("sessionId"));
|
|
1281
|
+
if ("redirectProjectId" in routing) {
|
|
1282
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, c.req.param("sessionId"), "", c.req.url), 307);
|
|
1180
1283
|
}
|
|
1181
|
-
|
|
1284
|
+
if ("error" in routing) {
|
|
1285
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1286
|
+
}
|
|
1287
|
+
const reader = await resolveSessionReaderForAgentContent({
|
|
1288
|
+
deps,
|
|
1289
|
+
project: routing.transcriptProject,
|
|
1290
|
+
sessionId: c.req.param("sessionId"),
|
|
1291
|
+
projectId: routing.transcriptProjectId,
|
|
1292
|
+
});
|
|
1182
1293
|
const agentSession = await reader.getAgentSession(agentId);
|
|
1183
1294
|
if (!agentSession) {
|
|
1184
1295
|
return c.json({ error: "Agent session not found" }, 404);
|
|
1185
1296
|
}
|
|
1186
1297
|
// Add server-rendered HTML to text blocks for markdown display
|
|
1187
|
-
await augmentTextBlocks(agentSession.messages
|
|
1298
|
+
await augmentTextBlocks(agentSession.messages, {
|
|
1299
|
+
projectFileLinks: {
|
|
1300
|
+
projectId: routing.workingProjectId,
|
|
1301
|
+
projectPath: routing.workingProject.path,
|
|
1302
|
+
},
|
|
1303
|
+
});
|
|
1188
1304
|
return c.json(agentSession);
|
|
1189
1305
|
});
|
|
1190
1306
|
// GET /api/projects/:projectId/sessions/:sessionId/metadata - Get session metadata only (no messages)
|
|
@@ -1196,10 +1312,16 @@ export function createSessionsRoutes(deps) {
|
|
|
1196
1312
|
if (!isUrlProjectId(projectId)) {
|
|
1197
1313
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1198
1314
|
}
|
|
1199
|
-
const
|
|
1200
|
-
if (
|
|
1201
|
-
return c.
|
|
1315
|
+
const routing = await resolveSessionProjectRouting(projectId, sessionId);
|
|
1316
|
+
if ("redirectProjectId" in routing) {
|
|
1317
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "/metadata", c.req.url), 307);
|
|
1318
|
+
}
|
|
1319
|
+
if ("error" in routing) {
|
|
1320
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1202
1321
|
}
|
|
1322
|
+
const project = routing.workingProject;
|
|
1323
|
+
const transcriptProject = routing.transcriptProject;
|
|
1324
|
+
const transcriptProjectId = routing.transcriptProjectId;
|
|
1203
1325
|
// Check if session is actively owned by a process
|
|
1204
1326
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
1205
1327
|
// Check if session is being controlled by an external program
|
|
@@ -1211,6 +1333,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1211
1333
|
processId: process.id,
|
|
1212
1334
|
permissionMode: process.permissionMode,
|
|
1213
1335
|
modeVersion: process.modeVersion,
|
|
1336
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
1214
1337
|
}
|
|
1215
1338
|
: isExternal
|
|
1216
1339
|
? { owner: "external" }
|
|
@@ -1222,23 +1345,18 @@ export function createSessionsRoutes(deps) {
|
|
|
1222
1345
|
const lastSeenAt = lastSeenEntry?.timestamp;
|
|
1223
1346
|
// Get pending input request from active process
|
|
1224
1347
|
const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
|
|
1225
|
-
|
|
1226
|
-
const slashCommands = process?.supportsDynamicCommands
|
|
1227
|
-
? await process.supportedCommands()
|
|
1228
|
-
: null;
|
|
1348
|
+
const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
|
|
1229
1349
|
// 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;
|
|
1350
|
+
const metadataProvider = metadata?.provider ??
|
|
1351
|
+
deps.sessionMetadataService?.getProvider(sessionId);
|
|
1352
|
+
const slashCommands = await getSessionSlashCommands(process, process?.provider ?? metadataProvider ?? project.provider);
|
|
1353
|
+
const deferredMessages = sessionQueueSummaries(deps, sessionId, process);
|
|
1354
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider ?? process?.provider);
|
|
1355
|
+
const rawSessionSummary = sessionSummaryResult?.summary ?? null;
|
|
1356
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1357
|
+
const sessionSummary = rawSessionSummary
|
|
1358
|
+
? applyRecapOverlayToSummary(rawSessionSummary, recapMessages)
|
|
1359
|
+
: null;
|
|
1242
1360
|
if (!sessionSummary && !process) {
|
|
1243
1361
|
const canonicalProjectId = await getGrokNativeProjectId(sessionId, projectId);
|
|
1244
1362
|
if (canonicalProjectId) {
|
|
@@ -1246,14 +1364,13 @@ export function createSessionsRoutes(deps) {
|
|
|
1246
1364
|
}
|
|
1247
1365
|
return c.json({ error: "Session not found" }, 404);
|
|
1248
1366
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
? deps.notificationService.hasUnread(sessionId, sessionSummary.updatedAt)
|
|
1367
|
+
const hasUnread = rawSessionSummary
|
|
1368
|
+
? hasUnreadProviderContent(deps.notificationService, sessionId, rawSessionSummary.updatedAt)
|
|
1252
1369
|
: undefined;
|
|
1253
1370
|
const response = {
|
|
1254
1371
|
session: {
|
|
1255
1372
|
id: sessionId,
|
|
1256
|
-
projectId:
|
|
1373
|
+
projectId: routing.workingProjectId,
|
|
1257
1374
|
title: sessionSummary?.title ?? null,
|
|
1258
1375
|
fullTitle: sessionSummary?.fullTitle ?? null,
|
|
1259
1376
|
createdAt: sessionSummary?.createdAt ?? new Date().toISOString(),
|
|
@@ -1279,29 +1396,158 @@ export function createSessionsRoutes(deps) {
|
|
|
1279
1396
|
heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
|
|
1280
1397
|
heartbeatTurnText: metadata?.heartbeatTurnText,
|
|
1281
1398
|
heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes ?? undefined,
|
|
1399
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
1400
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
1401
|
+
workingProjectId: metadata?.workingProjectId,
|
|
1402
|
+
transcriptProjectId: metadata?.transcriptProjectId,
|
|
1403
|
+
workstreamId: metadata?.workstreamId,
|
|
1404
|
+
transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
|
|
1282
1405
|
lastSeenAt,
|
|
1283
1406
|
hasUnread,
|
|
1284
1407
|
},
|
|
1285
1408
|
ownership,
|
|
1286
1409
|
processState: process?.state.type ?? null,
|
|
1410
|
+
providerRuntimeStatus,
|
|
1287
1411
|
pendingInputRequest,
|
|
1288
1412
|
slashCommands,
|
|
1413
|
+
...(deferredMessages.length > 0 ? { deferredMessages } : {}),
|
|
1289
1414
|
};
|
|
1290
1415
|
return c.json(response);
|
|
1291
1416
|
});
|
|
1417
|
+
// PUT /api/projects/:projectId/sessions/:sessionId/project
|
|
1418
|
+
// Reclassify a session under a different YA project without sending any
|
|
1419
|
+
// provider-visible turn. The provider transcript remains under
|
|
1420
|
+
// transcriptProjectId; relative file links and UI routing use projectId.
|
|
1421
|
+
routes.put("/projects/:projectId/sessions/:sessionId/project", async (c) => {
|
|
1422
|
+
const projectId = c.req.param("projectId");
|
|
1423
|
+
const sessionId = c.req.param("sessionId");
|
|
1424
|
+
if (!isUrlProjectId(projectId)) {
|
|
1425
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1426
|
+
}
|
|
1427
|
+
if (!deps.sessionMetadataService) {
|
|
1428
|
+
return c.json({ error: "Session metadata service unavailable" }, 503);
|
|
1429
|
+
}
|
|
1430
|
+
let body;
|
|
1431
|
+
try {
|
|
1432
|
+
body = await c.req.json();
|
|
1433
|
+
}
|
|
1434
|
+
catch {
|
|
1435
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
1436
|
+
}
|
|
1437
|
+
const targetProjectId = body.projectId;
|
|
1438
|
+
if (typeof targetProjectId !== "string" ||
|
|
1439
|
+
!isUrlProjectId(targetProjectId)) {
|
|
1440
|
+
return c.json({ error: "Invalid target project ID format" }, 400);
|
|
1441
|
+
}
|
|
1442
|
+
const targetProject = await deps.scanner.getOrCreateProject(targetProjectId);
|
|
1443
|
+
if (!targetProject) {
|
|
1444
|
+
return c.json({ error: "Target project not found" }, 404);
|
|
1445
|
+
}
|
|
1446
|
+
const metadata = deps.sessionMetadataService.getMetadata(sessionId);
|
|
1447
|
+
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
1448
|
+
const transcriptProjectId = metadata?.transcriptProjectId ??
|
|
1449
|
+
process?.projectId ??
|
|
1450
|
+
projectId;
|
|
1451
|
+
const transcriptProject = transcriptProjectId === targetProjectId
|
|
1452
|
+
? targetProject
|
|
1453
|
+
: await deps.scanner.getOrCreateProject(transcriptProjectId);
|
|
1454
|
+
if (!transcriptProject) {
|
|
1455
|
+
return c.json({ error: "Transcript project not found" }, 404);
|
|
1456
|
+
}
|
|
1457
|
+
if (!process) {
|
|
1458
|
+
const metadataProvider = metadata?.provider ??
|
|
1459
|
+
deps.sessionMetadataService.getProvider(sessionId);
|
|
1460
|
+
const summary = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
|
|
1461
|
+
if (!summary) {
|
|
1462
|
+
return c.json({ error: "Session not found" }, 404);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const storedWorkingProjectId = targetProjectId === transcriptProjectId ? undefined : targetProjectId;
|
|
1466
|
+
const storedTranscriptProjectId = targetProjectId === transcriptProjectId ? undefined : transcriptProjectId;
|
|
1467
|
+
await deps.sessionMetadataService.setWorkingProject(sessionId, storedWorkingProjectId, storedTranscriptProjectId);
|
|
1468
|
+
deps.eventBus?.emit({
|
|
1469
|
+
type: "session-metadata-changed",
|
|
1470
|
+
sessionId,
|
|
1471
|
+
projectId: targetProjectId,
|
|
1472
|
+
transcriptProjectId: storedTranscriptProjectId ?? null,
|
|
1473
|
+
timestamp: new Date().toISOString(),
|
|
1474
|
+
});
|
|
1475
|
+
return c.json({
|
|
1476
|
+
updated: true,
|
|
1477
|
+
projectId: targetProjectId,
|
|
1478
|
+
transcriptProjectId: storedTranscriptProjectId ?? null,
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
// POST /api/projects/:projectId/sessions/:sessionId/refresh-preview
|
|
1482
|
+
// On-demand recompute of the hover-card recent-activity excerpt for a
|
|
1483
|
+
// non-running session (its live state is otherwise only refreshed when the
|
|
1484
|
+
// session is resumed). Fast reverse-scan of the JSONL, then push the result
|
|
1485
|
+
// to lists/hovers via a session-updated event so the preview updates in
|
|
1486
|
+
// place without flicker. See topics/session-hovercard-recent-activity.md.
|
|
1487
|
+
routes.post("/projects/:projectId/sessions/:sessionId/refresh-preview", async (c) => {
|
|
1488
|
+
const projectId = c.req.param("projectId");
|
|
1489
|
+
const sessionId = c.req.param("sessionId");
|
|
1490
|
+
if (!isUrlProjectId(projectId)) {
|
|
1491
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1492
|
+
}
|
|
1493
|
+
const routing = await resolveSessionProjectRouting(projectId, sessionId);
|
|
1494
|
+
if ("redirectProjectId" in routing) {
|
|
1495
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "/refresh-preview", c.req.url), 307);
|
|
1496
|
+
}
|
|
1497
|
+
if ("error" in routing) {
|
|
1498
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1499
|
+
}
|
|
1500
|
+
const transcriptProject = routing.transcriptProject;
|
|
1501
|
+
const transcriptProjectId = routing.transcriptProjectId;
|
|
1502
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
1503
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1504
|
+
let lastAgentText;
|
|
1505
|
+
if (recapMessages.length > 0) {
|
|
1506
|
+
const summaryResult = await findSessionSummaryAcrossProviders(transcriptProject, sessionId, transcriptProjectId, providerResolutionDeps(deps), metadataProvider);
|
|
1507
|
+
if (summaryResult?.summary) {
|
|
1508
|
+
lastAgentText = applyRecapOverlayToSummary(summaryResult.summary, recapMessages).lastAgentText;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
// Claude uses the fast reverse-scan when there is no durable overlay to
|
|
1512
|
+
// compare; every other provider goes through the cross-provider load +
|
|
1513
|
+
// normalize, then a shared extractor on the uniform message form.
|
|
1514
|
+
const reader = deps.readerFactory(transcriptProject);
|
|
1515
|
+
lastAgentText ??= reader.getLastAgentExcerpt
|
|
1516
|
+
? await reader.getLastAgentExcerpt(sessionId)
|
|
1517
|
+
: undefined;
|
|
1518
|
+
if (lastAgentText === undefined) {
|
|
1519
|
+
const normalized = await loadRestartSourceSession(transcriptProject, sessionId, transcriptProjectId, metadataProvider);
|
|
1520
|
+
if (normalized) {
|
|
1521
|
+
lastAgentText = extractLastAgentExcerpt(normalized.messages);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (lastAgentText !== undefined) {
|
|
1525
|
+
deps.eventBus?.emit({
|
|
1526
|
+
type: "session-updated",
|
|
1527
|
+
sessionId,
|
|
1528
|
+
projectId: routing.workingProjectId,
|
|
1529
|
+
lastAgentText,
|
|
1530
|
+
timestamp: new Date().toISOString(),
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
return c.json({ lastAgentText: lastAgentText ?? null });
|
|
1534
|
+
});
|
|
1292
1535
|
// GET /api/projects/:projectId/sessions/:sessionId - Get session detail
|
|
1293
1536
|
// Optional query params:
|
|
1294
1537
|
// ?afterMessageId=<id> - incremental forward-fetch (append new messages)
|
|
1295
1538
|
// ?tailCompactions=<n> - return only last N compact boundaries worth of messages
|
|
1296
1539
|
// ?beforeMessageId=<id> - cursor for loading older chunks (used with tailCompactions)
|
|
1297
|
-
// ?tailTurns=<n> -
|
|
1298
|
-
// ?tailFrom=<id> -
|
|
1540
|
+
// ?tailTurns=<n> - recent-turn selector within the authorized history scope
|
|
1541
|
+
// ?tailFrom=<id> - start selector within the authorized history scope
|
|
1542
|
+
// ?fullHistory=1 - explicitly authorize selectors to inspect full history
|
|
1299
1543
|
routes.get("/projects/:projectId/sessions/:sessionId", async (c) => {
|
|
1300
1544
|
const requestStartMs = performance.now();
|
|
1301
1545
|
const projectId = c.req.param("projectId");
|
|
1302
1546
|
const sessionId = c.req.param("sessionId");
|
|
1303
1547
|
const afterMessageId = c.req.query("afterMessageId");
|
|
1304
1548
|
const publicShare = c.req.query("publicShare") === "1";
|
|
1549
|
+
const fullHistory = c.req.query("fullHistory") === "1";
|
|
1550
|
+
const fullHistoryReason = c.req.query("fullHistoryReason");
|
|
1305
1551
|
const tailCompactionsParam = c.req.query("tailCompactions");
|
|
1306
1552
|
const beforeMessageId = c.req.query("beforeMessageId");
|
|
1307
1553
|
const tailTurnsParam = c.req.query("tailTurns");
|
|
@@ -1312,15 +1558,39 @@ export function createSessionsRoutes(deps) {
|
|
|
1312
1558
|
const tailTurns = tailTurnsParam !== undefined
|
|
1313
1559
|
? Number.parseInt(tailTurnsParam, 10)
|
|
1314
1560
|
: undefined;
|
|
1561
|
+
const requestedTailCompactions = tailCompactions !== undefined &&
|
|
1562
|
+
!Number.isNaN(tailCompactions) &&
|
|
1563
|
+
tailCompactions > 0
|
|
1564
|
+
? tailCompactions
|
|
1565
|
+
: undefined;
|
|
1566
|
+
const requestedTailTurns = tailTurns !== undefined && !Number.isNaN(tailTurns) && tailTurns > 0
|
|
1567
|
+
? tailTurns
|
|
1568
|
+
: undefined;
|
|
1569
|
+
const defaultedToCompactTail = !fullHistory &&
|
|
1570
|
+
!afterMessageId &&
|
|
1571
|
+
requestedTailCompactions === undefined;
|
|
1572
|
+
const unboundedRequestDefaultedToCompactTail = defaultedToCompactTail &&
|
|
1573
|
+
!tailFrom &&
|
|
1574
|
+
requestedTailTurns === undefined;
|
|
1575
|
+
const effectiveTailCompactions = requestedTailCompactions ??
|
|
1576
|
+
(defaultedToCompactTail
|
|
1577
|
+
? DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS
|
|
1578
|
+
: undefined);
|
|
1315
1579
|
// Validate projectId format at API boundary
|
|
1316
1580
|
if (!isUrlProjectId(projectId)) {
|
|
1317
1581
|
return c.json({ error: "Invalid project ID format" }, 400);
|
|
1318
1582
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return c.json({ error: "Project not found" }, 404);
|
|
1583
|
+
const routing = await resolveSessionProjectRouting(projectId, sessionId);
|
|
1584
|
+
if ("redirectProjectId" in routing) {
|
|
1585
|
+
return c.redirect(buildSessionProjectRedirectPath(routing.redirectProjectId, sessionId, "", c.req.url), 307);
|
|
1323
1586
|
}
|
|
1587
|
+
if ("error" in routing) {
|
|
1588
|
+
return c.json({ error: routing.error }, routing.status);
|
|
1589
|
+
}
|
|
1590
|
+
const project = routing.workingProject;
|
|
1591
|
+
const effectiveProjectId = routing.workingProjectId;
|
|
1592
|
+
const transcriptProject = routing.transcriptProject;
|
|
1593
|
+
const transcriptProjectId = routing.transcriptProjectId;
|
|
1324
1594
|
const projectResolvedMs = performance.now();
|
|
1325
1595
|
// Check if session is actively owned by a process
|
|
1326
1596
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
@@ -1330,70 +1600,30 @@ export function createSessionsRoutes(deps) {
|
|
|
1330
1600
|
// Only mark tools as "aborted" if we owned the session and know it terminated
|
|
1331
1601
|
const wasEverOwned = deps.supervisor.wasEverOwned(sessionId);
|
|
1332
1602
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
1333
|
-
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1603
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1604
|
+
const recapCursor = findDurableRecapCursor(afterMessageId, recapMessages);
|
|
1605
|
+
const providerAfterMessageId = recapCursor ? undefined : afterMessageId;
|
|
1606
|
+
const primaryReaderAfterMessageId = isClaudeSdkProviderName(project.provider) ||
|
|
1607
|
+
isClaudeSdkProviderName(metadataProvider)
|
|
1608
|
+
? undefined
|
|
1609
|
+
: providerAfterMessageId;
|
|
1610
|
+
const loadedSession = await loadProviderSession(transcriptProject, sessionId, transcriptProjectId, metadataProvider ?? process?.provider, primaryReaderAfterMessageId, {
|
|
1336
1611
|
// Only include orphaned tool info if:
|
|
1337
1612
|
// 1. We previously owned this session (not external)
|
|
1338
1613
|
// 2. No active process (tools aren't potentially in progress)
|
|
1339
1614
|
// When we own the session, tools without results might be pending approval
|
|
1340
1615
|
includeOrphans: wasEverOwned && !process,
|
|
1341
1616
|
});
|
|
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
1617
|
const readEndMs = performance.now();
|
|
1391
1618
|
let session = loadedSession ? normalizeSession(loadedSession) : null;
|
|
1392
1619
|
const normalizedMessageCount = session?.messages.length ?? 0;
|
|
1393
1620
|
const normalizeEndMs = performance.now();
|
|
1621
|
+
if (session && isClaudeSdkProviderName(session.provider)) {
|
|
1622
|
+
augmentTaskListSnapshots(session.messages);
|
|
1623
|
+
}
|
|
1394
1624
|
let incrementalAnchorFound = false;
|
|
1395
|
-
if (session &&
|
|
1396
|
-
const sliced = sliceAfterMessageIdWithMatch(session.messages,
|
|
1625
|
+
if (session && providerAfterMessageId) {
|
|
1626
|
+
const sliced = sliceAfterMessageIdWithMatch(session.messages, providerAfterMessageId);
|
|
1397
1627
|
session = {
|
|
1398
1628
|
...session,
|
|
1399
1629
|
messages: sliced.messages,
|
|
@@ -1407,6 +1637,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1407
1637
|
processId: process.id,
|
|
1408
1638
|
permissionMode: process.permissionMode,
|
|
1409
1639
|
modeVersion: process.modeVersion,
|
|
1640
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
1410
1641
|
}
|
|
1411
1642
|
: isExternal
|
|
1412
1643
|
? { owner: "external" }
|
|
@@ -1414,12 +1645,16 @@ export function createSessionsRoutes(deps) {
|
|
|
1414
1645
|
// Get pending input request from active process (for tool approval prompts)
|
|
1415
1646
|
// This ensures clients get pending requests immediately without waiting for SSE
|
|
1416
1647
|
const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
|
|
1417
|
-
|
|
1648
|
+
const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
|
|
1649
|
+
// Get available slash commands (for "/" button and typed slash menu)
|
|
1418
1650
|
// 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
|
-
|
|
1651
|
+
// after ~30s, so we attach them to the REST response. Providers with known
|
|
1652
|
+
// native built-ins, such as Codex, can expose those while stopped.
|
|
1653
|
+
const slashCommands = await getSessionSlashCommands(process, process?.provider ??
|
|
1654
|
+
session?.provider ??
|
|
1655
|
+
metadataProvider ??
|
|
1656
|
+
project.provider);
|
|
1657
|
+
const deferredMessages = sessionQueueSummaries(deps, sessionId, process);
|
|
1423
1658
|
if (!session) {
|
|
1424
1659
|
// Session file doesn't exist yet - only valid if we own the process
|
|
1425
1660
|
if (process) {
|
|
@@ -1436,22 +1671,23 @@ export function createSessionsRoutes(deps) {
|
|
|
1436
1671
|
: mis
|
|
1437
1672
|
? (m, p) => mis.getContextWindow(m, p)
|
|
1438
1673
|
: undefined);
|
|
1439
|
-
//
|
|
1440
|
-
|
|
1441
|
-
mis.recordContextWindow(process.resolvedModel, sdkContextWindow, process.provider);
|
|
1442
|
-
}
|
|
1674
|
+
// (Durable recording happens at the observation point in Process via
|
|
1675
|
+
// onContextWindowObserved, not as a side effect of this GET.)
|
|
1443
1676
|
// Get metadata even for new sessions (in case it was set before file was written)
|
|
1444
1677
|
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
1678
|
+
const recapMessages = deps.sessionMetadataService?.getRecapMessages?.(sessionId) ?? [];
|
|
1679
|
+
const visibleProcessMessages = mergeRecapMessages(processMessages, recapMessages);
|
|
1445
1680
|
// Get notification data for new sessions too
|
|
1446
1681
|
const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
|
|
1447
|
-
const
|
|
1682
|
+
const latestRecap = latestRecapMessage(recapMessages);
|
|
1683
|
+
const newSessionUpdatedAt = latestRecap?.timestamp ?? new Date().toISOString();
|
|
1448
1684
|
const hasUnread = deps.notificationService
|
|
1449
1685
|
? deps.notificationService.hasUnread(sessionId, newSessionUpdatedAt)
|
|
1450
1686
|
: undefined;
|
|
1451
1687
|
return c.json({
|
|
1452
1688
|
session: {
|
|
1453
1689
|
id: sessionId,
|
|
1454
|
-
projectId,
|
|
1690
|
+
projectId: effectiveProjectId,
|
|
1455
1691
|
title: null,
|
|
1456
1692
|
createdAt: new Date().toISOString(),
|
|
1457
1693
|
updatedAt: newSessionUpdatedAt,
|
|
@@ -1466,16 +1702,24 @@ export function createSessionsRoutes(deps) {
|
|
|
1466
1702
|
heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
|
|
1467
1703
|
heartbeatTurnText: metadata?.heartbeatTurnText,
|
|
1468
1704
|
heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes,
|
|
1705
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
1706
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
1707
|
+
workingProjectId: metadata?.workingProjectId,
|
|
1708
|
+
transcriptProjectId: metadata?.transcriptProjectId,
|
|
1709
|
+
workstreamId: metadata?.workstreamId,
|
|
1710
|
+
transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
|
|
1469
1711
|
lastSeenAt: lastSeenEntry?.timestamp,
|
|
1470
1712
|
hasUnread,
|
|
1471
1713
|
provider: process.provider,
|
|
1472
1714
|
model: process.resolvedModel,
|
|
1473
1715
|
contextUsage,
|
|
1474
1716
|
},
|
|
1475
|
-
messages:
|
|
1717
|
+
messages: visibleProcessMessages,
|
|
1476
1718
|
ownership,
|
|
1719
|
+
providerRuntimeStatus,
|
|
1477
1720
|
pendingInputRequest,
|
|
1478
1721
|
slashCommands,
|
|
1722
|
+
...(deferredMessages.length > 0 ? { deferredMessages } : {}),
|
|
1479
1723
|
});
|
|
1480
1724
|
}
|
|
1481
1725
|
const canonicalProjectId = await getGrokNativeProjectId(sessionId, projectId);
|
|
@@ -1489,37 +1733,41 @@ export function createSessionsRoutes(deps) {
|
|
|
1489
1733
|
// Get notification data (lastSeenAt, hasUnread)
|
|
1490
1734
|
const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
|
|
1491
1735
|
const lastSeenAt = lastSeenEntry?.timestamp;
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
//
|
|
1496
|
-
// is an opt-in stronger client memory cap, so it wins over compact tails.
|
|
1497
|
-
// Skip both when afterMessageId is present since that's an incremental
|
|
1498
|
-
// forward-fetch use case.
|
|
1736
|
+
// Apply pagination BEFORE expensive augmentation. Compact boundaries set
|
|
1737
|
+
// the authorized history scope by default; turn selectors may narrow that
|
|
1738
|
+
// scope but cannot broaden it unless fullHistory=1 removed the default.
|
|
1739
|
+
// Skip initial-window slicing for incremental forward fetches.
|
|
1499
1740
|
let paginationInfo;
|
|
1500
1741
|
if (!afterMessageId &&
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
: 20, tailFrom);
|
|
1742
|
+
effectiveTailCompactions !== undefined &&
|
|
1743
|
+
!beforeMessageId &&
|
|
1744
|
+
(tailFrom || requestedTailTurns !== undefined)) {
|
|
1745
|
+
const sliced = sliceAtCompactAndUserTurnBoundaries(session.messages, effectiveTailCompactions, requestedTailTurns ?? 20, tailFrom);
|
|
1506
1746
|
session = { ...session, messages: sliced.messages };
|
|
1507
1747
|
paginationInfo = sliced.pagination;
|
|
1508
1748
|
}
|
|
1509
|
-
else if (
|
|
1510
|
-
|
|
1511
|
-
|
|
1749
|
+
else if (!afterMessageId &&
|
|
1750
|
+
effectiveTailCompactions === undefined &&
|
|
1751
|
+
(tailFrom || requestedTailTurns !== undefined)) {
|
|
1752
|
+
const sliced = sliceAtUserTurnBoundary(session.messages, requestedTailTurns ?? 20, tailFrom);
|
|
1753
|
+
session = { ...session, messages: sliced.messages };
|
|
1754
|
+
paginationInfo = sliced.pagination;
|
|
1755
|
+
}
|
|
1756
|
+
else if (effectiveTailCompactions !== undefined &&
|
|
1512
1757
|
!afterMessageId) {
|
|
1513
|
-
const sliced = sliceAtCompactBoundaries(session.messages,
|
|
1758
|
+
const sliced = sliceAtCompactBoundaries(session.messages, effectiveTailCompactions, beforeMessageId);
|
|
1514
1759
|
session = { ...session, messages: sliced.messages };
|
|
1515
1760
|
paginationInfo = sliced.pagination;
|
|
1516
1761
|
}
|
|
1762
|
+
if (isClaudeSdkProviderName(session.provider)) {
|
|
1763
|
+
pruneTaskListSnapshotsToLatest(session.messages);
|
|
1764
|
+
}
|
|
1517
1765
|
const sliceEndMs = performance.now();
|
|
1518
1766
|
// Codex normalized IDs can drift between stream and JSONL. If an
|
|
1519
1767
|
// incremental request misses its anchor, never return the full historical
|
|
1520
1768
|
// session into a compact-tail client; bound the fallback to the same tail
|
|
1521
1769
|
// window used for initial loads.
|
|
1522
|
-
if (
|
|
1770
|
+
if (providerAfterMessageId && !incrementalAnchorFound) {
|
|
1523
1771
|
const sliced = sliceAtCompactBoundaries(session.messages, 2);
|
|
1524
1772
|
session = { ...session, messages: sliced.messages };
|
|
1525
1773
|
paginationInfo = sliced.pagination;
|
|
@@ -1529,9 +1777,33 @@ export function createSessionsRoutes(deps) {
|
|
|
1529
1777
|
await augmentEditToolUses(session.messages);
|
|
1530
1778
|
}
|
|
1531
1779
|
else {
|
|
1532
|
-
await augmentPersistedSessionMessages(session.messages
|
|
1780
|
+
await augmentPersistedSessionMessages(session.messages, {
|
|
1781
|
+
projectFileLinks: {
|
|
1782
|
+
projectId: effectiveProjectId,
|
|
1783
|
+
projectPath: project.path,
|
|
1784
|
+
},
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
// The overlay reassigns `session` below; hasUnreadProviderContent needs
|
|
1788
|
+
// the pre-overlay timestamp.
|
|
1789
|
+
const preRecapUpdatedAt = session.updatedAt;
|
|
1790
|
+
if (!beforeMessageId) {
|
|
1791
|
+
session = applyRecapOverlayToSession(session, recapMessages);
|
|
1792
|
+
if (recapCursor && afterMessageId) {
|
|
1793
|
+
const sliced = sliceAfterDurableRecapCursor({
|
|
1794
|
+
messages: session.messages,
|
|
1795
|
+
recap: recapCursor,
|
|
1796
|
+
cursorId: afterMessageId,
|
|
1797
|
+
});
|
|
1798
|
+
session = { ...session, messages: sliced.messages };
|
|
1799
|
+
incrementalAnchorFound = sliced.found;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
session = applyRecapOverlayToSummary(session, recapMessages);
|
|
1533
1804
|
}
|
|
1534
1805
|
const augmentEndMs = performance.now();
|
|
1806
|
+
const hasUnread = hasUnreadProviderContent(deps.notificationService, sessionId, preRecapUpdatedAt);
|
|
1535
1807
|
// Override context usage with SDK-reported context window from live process
|
|
1536
1808
|
// The reader uses hardcoded defaults; the process captures the real value at runtime
|
|
1537
1809
|
let { contextUsage } = session;
|
|
@@ -1542,29 +1814,80 @@ export function createSessionsRoutes(deps) {
|
|
|
1542
1814
|
percentage: Math.round((contextUsage.inputTokens / cw) * 100),
|
|
1543
1815
|
contextWindow: cw,
|
|
1544
1816
|
};
|
|
1545
|
-
//
|
|
1546
|
-
|
|
1817
|
+
// Durable recording happens at the observation point in Process via
|
|
1818
|
+
// onContextWindowObserved; this block only overrides the displayed value.
|
|
1547
1819
|
}
|
|
1548
1820
|
const { messages: _messages, ...sessionMetadata } = session;
|
|
1549
1821
|
const totalMs = performance.now() - requestStartMs;
|
|
1822
|
+
if (unboundedRequestDefaultedToCompactTail &&
|
|
1823
|
+
normalizedMessageCount >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
|
|
1824
|
+
getLogger().warn({
|
|
1825
|
+
beforeMessageId: beforeMessageId ?? null,
|
|
1826
|
+
defaultTailCompactions: DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS,
|
|
1827
|
+
event: "session_detail_unbounded_defaulted_to_tail",
|
|
1828
|
+
normalizedMessageCount,
|
|
1829
|
+
projectId: effectiveProjectId,
|
|
1830
|
+
transcriptProjectId,
|
|
1831
|
+
provider: session.provider,
|
|
1832
|
+
publicShare,
|
|
1833
|
+
returnedMessageCount: session.messages.length,
|
|
1834
|
+
sessionId,
|
|
1835
|
+
timings: {
|
|
1836
|
+
augmentMs: roundedMs(augmentEndMs - sliceEndMs),
|
|
1837
|
+
normalizeMs: roundedMs(normalizeEndMs - readEndMs),
|
|
1838
|
+
projectMs: roundedMs(projectResolvedMs - requestStartMs),
|
|
1839
|
+
readMs: roundedMs(readEndMs - projectResolvedMs),
|
|
1840
|
+
routeMs: roundedMs(sliceEndMs - normalizeEndMs),
|
|
1841
|
+
totalMs: roundedMs(totalMs),
|
|
1842
|
+
},
|
|
1843
|
+
totalMessageCount: session.messageCount,
|
|
1844
|
+
}, "SESSION_DETAIL: defaulted unbounded request to compact tail");
|
|
1845
|
+
}
|
|
1846
|
+
if (fullHistory &&
|
|
1847
|
+
session.messages.length >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
|
|
1848
|
+
getLogger().warn({
|
|
1849
|
+
event: "session_detail_full_history_large",
|
|
1850
|
+
fullHistoryReason: fullHistoryReason ?? null,
|
|
1851
|
+
normalizedMessageCount,
|
|
1852
|
+
projectId: effectiveProjectId,
|
|
1853
|
+
transcriptProjectId,
|
|
1854
|
+
provider: session.provider,
|
|
1855
|
+
publicShare,
|
|
1856
|
+
returnedMessageCount: session.messages.length,
|
|
1857
|
+
sessionId,
|
|
1858
|
+
timings: {
|
|
1859
|
+
augmentMs: roundedMs(augmentEndMs - sliceEndMs),
|
|
1860
|
+
normalizeMs: roundedMs(normalizeEndMs - readEndMs),
|
|
1861
|
+
projectMs: roundedMs(projectResolvedMs - requestStartMs),
|
|
1862
|
+
readMs: roundedMs(readEndMs - projectResolvedMs),
|
|
1863
|
+
routeMs: roundedMs(sliceEndMs - normalizeEndMs),
|
|
1864
|
+
totalMs: roundedMs(totalMs),
|
|
1865
|
+
},
|
|
1866
|
+
totalMessageCount: session.messageCount,
|
|
1867
|
+
}, "SESSION_DETAIL: large explicit full-history request");
|
|
1868
|
+
}
|
|
1550
1869
|
if (totalMs >= SESSION_DETAIL_SLOW_LOG_MS) {
|
|
1551
1870
|
getLogger().warn({
|
|
1552
1871
|
afterMessageId: afterMessageId ?? null,
|
|
1553
1872
|
beforeMessageId: beforeMessageId ?? null,
|
|
1554
1873
|
event: "session_detail_slow",
|
|
1874
|
+
defaultedToCompactTail,
|
|
1875
|
+
fullHistory,
|
|
1876
|
+
fullHistoryReason: fullHistoryReason ?? null,
|
|
1555
1877
|
incrementalAnchorFound: afterMessageId
|
|
1556
1878
|
? incrementalAnchorFound
|
|
1557
1879
|
: null,
|
|
1558
1880
|
normalizedMessageCount,
|
|
1559
1881
|
owned: Boolean(process),
|
|
1560
1882
|
processState: process?.state.type ?? null,
|
|
1561
|
-
projectId,
|
|
1883
|
+
projectId: effectiveProjectId,
|
|
1884
|
+
transcriptProjectId,
|
|
1562
1885
|
provider: session.provider,
|
|
1563
1886
|
publicShare,
|
|
1564
1887
|
returnedMessageCount: session.messages.length,
|
|
1565
1888
|
sessionId,
|
|
1566
|
-
tailCompactions:
|
|
1567
|
-
tailTurns:
|
|
1889
|
+
tailCompactions: effectiveTailCompactions ?? null,
|
|
1890
|
+
tailTurns: requestedTailTurns ?? null,
|
|
1568
1891
|
timings: {
|
|
1569
1892
|
augmentMs: roundedMs(augmentEndMs - sliceEndMs),
|
|
1570
1893
|
normalizeMs: roundedMs(normalizeEndMs - readEndMs),
|
|
@@ -1579,6 +1902,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1579
1902
|
return c.json({
|
|
1580
1903
|
session: {
|
|
1581
1904
|
...sessionMetadata,
|
|
1905
|
+
projectId: effectiveProjectId,
|
|
1582
1906
|
ownership,
|
|
1583
1907
|
contextUsage,
|
|
1584
1908
|
customTitle: metadata?.customTitle,
|
|
@@ -1590,6 +1914,12 @@ export function createSessionsRoutes(deps) {
|
|
|
1590
1914
|
heartbeatTurnsAfterMinutes: metadata?.heartbeatTurnsAfterMinutes,
|
|
1591
1915
|
heartbeatTurnText: metadata?.heartbeatTurnText,
|
|
1592
1916
|
heartbeatForceAfterMinutes: metadata?.heartbeatForceAfterMinutes,
|
|
1917
|
+
promptSuggestionMode: metadata?.promptSuggestionMode,
|
|
1918
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
1919
|
+
workingProjectId: metadata?.workingProjectId,
|
|
1920
|
+
transcriptProjectId: metadata?.transcriptProjectId,
|
|
1921
|
+
workstreamId: metadata?.workstreamId,
|
|
1922
|
+
transcriptDisplayObjects: metadata?.transcriptDisplayObjects,
|
|
1593
1923
|
// Model comes from the session reader (extracted from JSONL)
|
|
1594
1924
|
model: session.model,
|
|
1595
1925
|
lastSeenAt,
|
|
@@ -1597,9 +1927,11 @@ export function createSessionsRoutes(deps) {
|
|
|
1597
1927
|
},
|
|
1598
1928
|
messages: session.messages,
|
|
1599
1929
|
ownership,
|
|
1930
|
+
providerRuntimeStatus,
|
|
1600
1931
|
pendingInputRequest,
|
|
1601
1932
|
slashCommands,
|
|
1602
1933
|
...(paginationInfo && { pagination: paginationInfo }),
|
|
1934
|
+
...(deferredMessages.length > 0 ? { deferredMessages } : {}),
|
|
1603
1935
|
});
|
|
1604
1936
|
});
|
|
1605
1937
|
// POST /api/projects/:projectId/sessions - Start new session
|
|
@@ -1632,6 +1964,10 @@ export function createSessionsRoutes(deps) {
|
|
|
1632
1964
|
if (helperSettings.error) {
|
|
1633
1965
|
return c.json({ error: helperSettings.error }, 400);
|
|
1634
1966
|
}
|
|
1967
|
+
const workstreamTarget = resolveLaunchWorkstreamTarget(project, body.workstreamId);
|
|
1968
|
+
if ("error" in workstreamTarget) {
|
|
1969
|
+
return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
|
|
1970
|
+
}
|
|
1635
1971
|
const serverTimestamp = Date.now();
|
|
1636
1972
|
const userMessage = {
|
|
1637
1973
|
text: body.message,
|
|
@@ -1642,10 +1978,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1642
1978
|
tempId: body.tempId,
|
|
1643
1979
|
metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
|
|
1644
1980
|
};
|
|
1645
|
-
|
|
1646
|
-
const { thinking, effort } = body.thinking
|
|
1647
|
-
? thinkingOptionToConfig(body.thinking)
|
|
1648
|
-
: { thinking: undefined, effort: undefined };
|
|
1981
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
1649
1982
|
// Convert model option (undefined or "default" means use CLI default)
|
|
1650
1983
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1651
1984
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
@@ -1656,7 +1989,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1656
1989
|
model: body.model,
|
|
1657
1990
|
serviceTier,
|
|
1658
1991
|
});
|
|
1659
|
-
const result = await deps.supervisor.startSession(
|
|
1992
|
+
const result = await deps.supervisor.startSession(workstreamTarget.projectPath, userMessage, body.mode, {
|
|
1660
1993
|
model,
|
|
1661
1994
|
serviceTier,
|
|
1662
1995
|
thinking,
|
|
@@ -1666,8 +1999,12 @@ export function createSessionsRoutes(deps) {
|
|
|
1666
1999
|
globalInstructions: getGlobalInstructions(),
|
|
1667
2000
|
permissions: body.permissions,
|
|
1668
2001
|
recapMode: helperSettings.recapMode,
|
|
2002
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1669
2003
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1670
2004
|
helperSideModel: helperSettings.helperSideModel,
|
|
2005
|
+
}, {
|
|
2006
|
+
projectId: project.id,
|
|
2007
|
+
workstreamId: workstreamTarget.workstreamId,
|
|
1671
2008
|
});
|
|
1672
2009
|
// Check if queue is full
|
|
1673
2010
|
if (isQueueFullResponse(result)) {
|
|
@@ -1677,13 +2014,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1677
2014
|
if (isQueuedResponse(result)) {
|
|
1678
2015
|
return c.json({ ...result, serverTimestamp }, 202); // 202 Accepted - queued for processing
|
|
1679
2016
|
}
|
|
1680
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message);
|
|
2017
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds, workstreamTarget.workstreamId);
|
|
1681
2018
|
return c.json({
|
|
1682
2019
|
sessionId: result.sessionId,
|
|
1683
2020
|
processId: result.id,
|
|
1684
2021
|
projectId: result.projectId,
|
|
1685
2022
|
permissionMode: result.permissionMode,
|
|
1686
2023
|
modeVersion: result.modeVersion,
|
|
2024
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1687
2025
|
serverTimestamp,
|
|
1688
2026
|
});
|
|
1689
2027
|
});
|
|
@@ -1715,14 +2053,15 @@ export function createSessionsRoutes(deps) {
|
|
|
1715
2053
|
if (helperSettings.error) {
|
|
1716
2054
|
return c.json({ error: helperSettings.error }, 400);
|
|
1717
2055
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
2056
|
+
const workstreamTarget = resolveLaunchWorkstreamTarget(project, body.workstreamId);
|
|
2057
|
+
if ("error" in workstreamTarget) {
|
|
2058
|
+
return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
|
|
2059
|
+
}
|
|
2060
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
1722
2061
|
// Convert model option (undefined or "default" means use CLI default)
|
|
1723
2062
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1724
2063
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
1725
|
-
const result = await deps.supervisor.createSession(
|
|
2064
|
+
const result = await deps.supervisor.createSession(workstreamTarget.projectPath, body.mode, {
|
|
1726
2065
|
model,
|
|
1727
2066
|
serviceTier,
|
|
1728
2067
|
thinking,
|
|
@@ -1732,8 +2071,12 @@ export function createSessionsRoutes(deps) {
|
|
|
1732
2071
|
globalInstructions: getGlobalInstructions(),
|
|
1733
2072
|
permissions: body.permissions,
|
|
1734
2073
|
recapMode: helperSettings.recapMode,
|
|
2074
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1735
2075
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1736
2076
|
helperSideModel: helperSettings.helperSideModel,
|
|
2077
|
+
}, {
|
|
2078
|
+
projectId: project.id,
|
|
2079
|
+
workstreamId: workstreamTarget.workstreamId,
|
|
1737
2080
|
});
|
|
1738
2081
|
// Check if queue is full
|
|
1739
2082
|
if (isQueueFullResponse(result)) {
|
|
@@ -1743,13 +2086,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1743
2086
|
if (isQueuedResponse(result)) {
|
|
1744
2087
|
return c.json({ ...result, serverTimestamp: Date.now() }, 202); // 202 Accepted - queued for processing
|
|
1745
2088
|
}
|
|
1746
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor);
|
|
2089
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, undefined, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds, workstreamTarget.workstreamId);
|
|
1747
2090
|
return c.json({
|
|
1748
2091
|
sessionId: result.sessionId,
|
|
1749
2092
|
processId: result.id,
|
|
1750
2093
|
projectId: result.projectId,
|
|
1751
2094
|
permissionMode: result.permissionMode,
|
|
1752
2095
|
modeVersion: result.modeVersion,
|
|
2096
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1753
2097
|
serverTimestamp: Date.now(),
|
|
1754
2098
|
});
|
|
1755
2099
|
});
|
|
@@ -1784,9 +2128,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1784
2128
|
tempId: body.tempId,
|
|
1785
2129
|
metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
|
|
1786
2130
|
};
|
|
1787
|
-
const { thinking, effort } = body
|
|
1788
|
-
? thinkingOptionToConfig(body.thinking)
|
|
1789
|
-
: { thinking: undefined, effort: undefined };
|
|
2131
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
1790
2132
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1791
2133
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
1792
2134
|
const result = await deps.supervisor.startSession(projectPath, userMessage, body.mode, {
|
|
@@ -1799,6 +2141,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1799
2141
|
globalInstructions: getGlobalInstructions(),
|
|
1800
2142
|
permissions: body.permissions,
|
|
1801
2143
|
recapMode: helperSettings.recapMode,
|
|
2144
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1802
2145
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1803
2146
|
helperSideModel: helperSettings.helperSideModel,
|
|
1804
2147
|
});
|
|
@@ -1808,13 +2151,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1808
2151
|
if (isQueuedResponse(result)) {
|
|
1809
2152
|
return c.json({ ...result, serverTimestamp: Date.now() }, 202);
|
|
1810
2153
|
}
|
|
1811
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message);
|
|
2154
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, body.message, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds);
|
|
1812
2155
|
return c.json({
|
|
1813
2156
|
sessionId: result.sessionId,
|
|
1814
2157
|
processId: result.id,
|
|
1815
2158
|
projectId: result.projectId,
|
|
1816
2159
|
permissionMode: result.permissionMode,
|
|
1817
2160
|
modeVersion: result.modeVersion,
|
|
2161
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1818
2162
|
serverTimestamp,
|
|
1819
2163
|
});
|
|
1820
2164
|
});
|
|
@@ -1836,9 +2180,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1836
2180
|
return c.json({ error: helperSettings.error }, 400);
|
|
1837
2181
|
}
|
|
1838
2182
|
const projectPath = await ensureDetachedProjectPath(executor);
|
|
1839
|
-
const { thinking, effort } = body
|
|
1840
|
-
? thinkingOptionToConfig(body.thinking)
|
|
1841
|
-
: { thinking: undefined, effort: undefined };
|
|
2183
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
1842
2184
|
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
1843
2185
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
1844
2186
|
const result = await deps.supervisor.createSession(projectPath, body.mode, {
|
|
@@ -1851,6 +2193,7 @@ export function createSessionsRoutes(deps) {
|
|
|
1851
2193
|
globalInstructions: getGlobalInstructions(),
|
|
1852
2194
|
permissions: body.permissions,
|
|
1853
2195
|
recapMode: helperSettings.recapMode,
|
|
2196
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds,
|
|
1854
2197
|
promptSuggestionMode: helperSettings.promptSuggestionMode,
|
|
1855
2198
|
helperSideModel: helperSettings.helperSideModel,
|
|
1856
2199
|
});
|
|
@@ -1860,13 +2203,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1860
2203
|
if (isQueuedResponse(result)) {
|
|
1861
2204
|
return c.json({ ...result, serverTimestamp: Date.now() }, 202);
|
|
1862
2205
|
}
|
|
1863
|
-
await persistLaunchMetadata(result.sessionId, body.provider, executor);
|
|
2206
|
+
await persistLaunchMetadata(result.sessionId, body.provider, executor, undefined, body.model, result.promptSuggestionMode, helperSettings.recapAfterSeconds);
|
|
1864
2207
|
return c.json({
|
|
1865
2208
|
sessionId: result.sessionId,
|
|
1866
2209
|
processId: result.id,
|
|
1867
2210
|
projectId: result.projectId,
|
|
1868
2211
|
permissionMode: result.permissionMode,
|
|
1869
2212
|
modeVersion: result.modeVersion,
|
|
2213
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
1870
2214
|
serverTimestamp: Date.now(),
|
|
1871
2215
|
});
|
|
1872
2216
|
});
|
|
@@ -1901,6 +2245,11 @@ export function createSessionsRoutes(deps) {
|
|
|
1901
2245
|
if (helperSettings.error) {
|
|
1902
2246
|
return c.json({ error: helperSettings.error }, 400);
|
|
1903
2247
|
}
|
|
2248
|
+
const parsedResumeMode = parseOptionalResumeMode(body.resumeMode);
|
|
2249
|
+
if (parsedResumeMode.error) {
|
|
2250
|
+
return c.json({ error: parsedResumeMode.error }, 400);
|
|
2251
|
+
}
|
|
2252
|
+
const resumeMode = parsedResumeMode.resumeMode ?? "full";
|
|
1904
2253
|
const serverTimestamp = Date.now();
|
|
1905
2254
|
const userMessage = {
|
|
1906
2255
|
text: body.message,
|
|
@@ -1911,12 +2260,15 @@ export function createSessionsRoutes(deps) {
|
|
|
1911
2260
|
tempId: body.tempId,
|
|
1912
2261
|
metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
|
|
1913
2262
|
};
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
//
|
|
1919
|
-
const
|
|
2263
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
2264
|
+
// Convert model option (undefined or "default" means use CLI default). When
|
|
2265
|
+
// the client sends no model (e.g. resume after a server restart), recover the
|
|
2266
|
+
// YA model id persisted at launch so the process keeps its requested alias
|
|
2267
|
+
// and per-model settings stay keyed by it. See topics/provider-abstraction.md.
|
|
2268
|
+
const requestedModel = body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId);
|
|
2269
|
+
const model = requestedModel && requestedModel !== "default"
|
|
2270
|
+
? requestedModel
|
|
2271
|
+
: undefined;
|
|
1920
2272
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
1921
2273
|
// Use client-provided executor, falling back to saved executor from metadata.
|
|
1922
2274
|
let executor = parsedBodyExecutor.executor;
|
|
@@ -1945,23 +2297,14 @@ export function createSessionsRoutes(deps) {
|
|
|
1945
2297
|
await deps.sessionMetadataService.setExecutor(sessionId, executor);
|
|
1946
2298
|
}
|
|
1947
2299
|
}
|
|
1948
|
-
const globalInstructions =
|
|
2300
|
+
const globalInstructions = getGlobalInstructions();
|
|
1949
2301
|
// Look up the session's original provider so we resume with the correct one
|
|
1950
2302
|
// (e.g., claude-ollama sessions need the Ollama provider, not default Claude).
|
|
1951
2303
|
// Check metadata first (explicitly saved on creation), then fall back to reader.
|
|
1952
2304
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
1953
2305
|
let providerName = metadataProvider ?? body.provider;
|
|
1954
2306
|
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);
|
|
2307
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider ?? body.provider, { readMode: "head" });
|
|
1965
2308
|
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
1966
2309
|
providerName =
|
|
1967
2310
|
sessionSummary?.provider ??
|
|
@@ -1969,6 +2312,20 @@ export function createSessionsRoutes(deps) {
|
|
|
1969
2312
|
body.provider ??
|
|
1970
2313
|
project.provider;
|
|
1971
2314
|
}
|
|
2315
|
+
const previousProcess = deps.supervisor.getProcessForSession?.(sessionId);
|
|
2316
|
+
const resumeDiagnostics = {
|
|
2317
|
+
requestedMode: resumeMode,
|
|
2318
|
+
provider: providerName,
|
|
2319
|
+
previousProcess: previousProcess
|
|
2320
|
+
? {
|
|
2321
|
+
processId: previousProcess.id,
|
|
2322
|
+
state: previousProcess.state.type,
|
|
2323
|
+
provider: previousProcess.provider,
|
|
2324
|
+
supportsDynamicCommands: previousProcess.supportsDynamicCommands,
|
|
2325
|
+
}
|
|
2326
|
+
: null,
|
|
2327
|
+
};
|
|
2328
|
+
let resumeSessionAt;
|
|
1972
2329
|
if (isClaudeSdkProviderName(providerName)) {
|
|
1973
2330
|
let blocker = null;
|
|
1974
2331
|
try {
|
|
@@ -1983,7 +2340,21 @@ export function createSessionsRoutes(deps) {
|
|
|
1983
2340
|
error: error instanceof Error ? error.message : String(error),
|
|
1984
2341
|
}, "Failed to check Claude session for resume-blocking API error");
|
|
1985
2342
|
}
|
|
1986
|
-
if (blocker) {
|
|
2343
|
+
if (blocker?.resumeAtMessageId) {
|
|
2344
|
+
// Recoverable: resume up to the last good assistant message,
|
|
2345
|
+
// dropping the API-error tail, instead of forcing a handoff.
|
|
2346
|
+
resumeSessionAt = blocker.resumeAtMessageId;
|
|
2347
|
+
getLogger().info({
|
|
2348
|
+
event: "claude_resume_truncated_after_api_error",
|
|
2349
|
+
sessionId,
|
|
2350
|
+
projectId,
|
|
2351
|
+
providerName,
|
|
2352
|
+
messageId: blocker.messageId,
|
|
2353
|
+
apiErrorStatus: blocker.apiErrorStatus,
|
|
2354
|
+
resumeSessionAt,
|
|
2355
|
+
}, "Resuming Claude session before SDK API-error tail");
|
|
2356
|
+
}
|
|
2357
|
+
else if (blocker) {
|
|
1987
2358
|
getLogger().warn({
|
|
1988
2359
|
event: "claude_resume_blocked_after_api_error",
|
|
1989
2360
|
sessionId,
|
|
@@ -1995,36 +2366,241 @@ export function createSessionsRoutes(deps) {
|
|
|
1995
2366
|
return c.json({
|
|
1996
2367
|
error: blocker.error,
|
|
1997
2368
|
recovery: blocker.recovery,
|
|
2369
|
+
resume: {
|
|
2370
|
+
...resumeDiagnostics,
|
|
2371
|
+
blockedReason: "claude-api-error-tail",
|
|
2372
|
+
},
|
|
1998
2373
|
}, 409);
|
|
1999
2374
|
}
|
|
2000
2375
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2376
|
+
let result;
|
|
2377
|
+
try {
|
|
2378
|
+
result = await deps.supervisor.resumeSession(sessionId, project.path, userMessage, body.mode, {
|
|
2379
|
+
model,
|
|
2380
|
+
serviceTier,
|
|
2381
|
+
thinking,
|
|
2382
|
+
effort,
|
|
2383
|
+
providerName,
|
|
2384
|
+
executor,
|
|
2385
|
+
globalInstructions,
|
|
2386
|
+
permissions: body.permissions,
|
|
2387
|
+
recapMode: helperSettings.recapMode,
|
|
2388
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ??
|
|
2389
|
+
deps.sessionMetadataService?.getRecapAfterSeconds?.(sessionId),
|
|
2390
|
+
// Body value wins; otherwise recover the per-session preference from
|
|
2391
|
+
// metadata so a body-less resume does not default back to native.
|
|
2392
|
+
promptSuggestionMode: helperSettings.promptSuggestionMode ??
|
|
2393
|
+
deps.sessionMetadataService?.getPromptSuggestionMode?.(sessionId),
|
|
2394
|
+
helperSideModel: helperSettings.helperSideModel,
|
|
2395
|
+
resumeMode,
|
|
2396
|
+
resumeSessionAt,
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
catch (error) {
|
|
2400
|
+
if (error instanceof ResumeCompactionError) {
|
|
2401
|
+
getLogger().warn({
|
|
2402
|
+
event: "resume_compaction_failed",
|
|
2403
|
+
sessionId,
|
|
2404
|
+
projectId,
|
|
2405
|
+
providerName,
|
|
2406
|
+
attempt: error.attempt,
|
|
2407
|
+
}, "Compact-first resume failed");
|
|
2408
|
+
return c.json({
|
|
2409
|
+
error: error.message,
|
|
2410
|
+
recovery: error.recovery,
|
|
2411
|
+
resume: {
|
|
2412
|
+
...resumeDiagnostics,
|
|
2413
|
+
compaction: error.attempt,
|
|
2414
|
+
},
|
|
2415
|
+
}, 409);
|
|
2416
|
+
}
|
|
2417
|
+
throw error;
|
|
2418
|
+
}
|
|
2014
2419
|
// Check if queue is full
|
|
2015
2420
|
if (isQueueFullResponse(result)) {
|
|
2016
2421
|
return c.json({ error: "Queue is full", maxQueueSize: result.maxQueueSize }, 503);
|
|
2017
2422
|
}
|
|
2018
2423
|
// Check if request was queued
|
|
2019
2424
|
if (isQueuedResponse(result)) {
|
|
2020
|
-
return c.json({
|
|
2425
|
+
return c.json({
|
|
2426
|
+
...result,
|
|
2427
|
+
serverTimestamp: Date.now(),
|
|
2428
|
+
resume: { ...resumeDiagnostics, outcome: "queued" },
|
|
2429
|
+
}, 202); // 202 Accepted - queued for processing
|
|
2021
2430
|
}
|
|
2022
2431
|
return c.json({
|
|
2023
2432
|
processId: result.id,
|
|
2024
2433
|
permissionMode: result.permissionMode,
|
|
2025
2434
|
modeVersion: result.modeVersion,
|
|
2435
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
2026
2436
|
serverTimestamp,
|
|
2437
|
+
resume: {
|
|
2438
|
+
...resumeDiagnostics,
|
|
2439
|
+
outcome: "started",
|
|
2440
|
+
compaction: resumeMode === "compact-first" ? { status: "completed" } : undefined,
|
|
2441
|
+
},
|
|
2442
|
+
});
|
|
2443
|
+
});
|
|
2444
|
+
// POST /api/projects/:projectId/sessions/:sessionId/reactivate
|
|
2445
|
+
// Spawn a live harness process bound to the session WITHOUT delivering a turn,
|
|
2446
|
+
// so the client can read live process state (model options) before messaging.
|
|
2447
|
+
// Idempotent: returns the existing process if the session is already owned.
|
|
2448
|
+
routes.post("/projects/:projectId/sessions/:sessionId/reactivate", async (c) => {
|
|
2449
|
+
const projectId = c.req.param("projectId");
|
|
2450
|
+
const sessionId = c.req.param("sessionId");
|
|
2451
|
+
if (!isUrlProjectId(projectId)) {
|
|
2452
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2453
|
+
}
|
|
2454
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2455
|
+
if (!project) {
|
|
2456
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2457
|
+
}
|
|
2458
|
+
// Already owned by a live process - return it (idempotent).
|
|
2459
|
+
const existing = deps.supervisor.getProcessForSession(sessionId);
|
|
2460
|
+
if (existing) {
|
|
2461
|
+
return c.json({
|
|
2462
|
+
processId: existing.id,
|
|
2463
|
+
permissionMode: existing.permissionMode,
|
|
2464
|
+
modeVersion: existing.modeVersion,
|
|
2465
|
+
recapAfterSeconds: existing.recapAfterSeconds,
|
|
2466
|
+
serverTimestamp: Date.now(),
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
// Body is optional - allows overriding mode/model/executor.
|
|
2470
|
+
let body = {};
|
|
2471
|
+
try {
|
|
2472
|
+
body = await c.req.json();
|
|
2473
|
+
}
|
|
2474
|
+
catch {
|
|
2475
|
+
// No body - resume with the session's saved settings.
|
|
2476
|
+
}
|
|
2477
|
+
const parsedBodyExecutor = parseOptionalExecutor(body.executor);
|
|
2478
|
+
if (parsedBodyExecutor.error) {
|
|
2479
|
+
return c.json({ error: parsedBodyExecutor.error }, 400);
|
|
2480
|
+
}
|
|
2481
|
+
const helperSettings = parseHelperSettings(body);
|
|
2482
|
+
if (helperSettings.error) {
|
|
2483
|
+
return c.json({ error: helperSettings.error }, 400);
|
|
2484
|
+
}
|
|
2485
|
+
// Resolve provider/model/executor from the YA launch record (persisted on
|
|
2486
|
+
// launch) so reactivation resumes with the correct backend and model.
|
|
2487
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2488
|
+
const providerName = metadata?.provider ??
|
|
2489
|
+
body.provider ??
|
|
2490
|
+
project.provider;
|
|
2491
|
+
const executor = parsedBodyExecutor.executor ?? metadata?.executor;
|
|
2492
|
+
// Prefer an explicit override, else the YA id the session was launched
|
|
2493
|
+
// with; "default" means let the backend pick (pass undefined).
|
|
2494
|
+
const rawModel = body.model && body.model !== "default"
|
|
2495
|
+
? body.model
|
|
2496
|
+
: metadata?.requestedModel;
|
|
2497
|
+
const model = rawModel && rawModel !== "default" ? rawModel : undefined;
|
|
2498
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
2499
|
+
let process;
|
|
2500
|
+
try {
|
|
2501
|
+
process = await deps.supervisor.reactivateSession(project.path, sessionId, body.mode, {
|
|
2502
|
+
model,
|
|
2503
|
+
thinking,
|
|
2504
|
+
effort,
|
|
2505
|
+
providerName,
|
|
2506
|
+
executor,
|
|
2507
|
+
globalInstructions: getGlobalInstructions(),
|
|
2508
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ?? metadata?.recapAfterSeconds,
|
|
2509
|
+
recapMode: helperSettings.recapMode ?? metadata?.recapMode,
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
catch (error) {
|
|
2513
|
+
return c.json({
|
|
2514
|
+
error: error instanceof Error
|
|
2515
|
+
? error.message
|
|
2516
|
+
: "Failed to reactivate session",
|
|
2517
|
+
}, 503);
|
|
2518
|
+
}
|
|
2519
|
+
// Keep the launch record current (provider/executor) for this session.
|
|
2520
|
+
await persistLaunchMetadata(sessionId, providerName, executor);
|
|
2521
|
+
return c.json({
|
|
2522
|
+
processId: process.id,
|
|
2523
|
+
permissionMode: process.permissionMode,
|
|
2524
|
+
modeVersion: process.modeVersion,
|
|
2525
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
2526
|
+
serverTimestamp: Date.now(),
|
|
2527
|
+
});
|
|
2528
|
+
});
|
|
2529
|
+
// POST /api/projects/:projectId/sessions/:sessionId/recap
|
|
2530
|
+
// Away-recap trigger, keyed by session (not process) so it survives a server
|
|
2531
|
+
// restart that killed the process. A live process recaps directly in its own
|
|
2532
|
+
// mode; a cold fork-mode session is revived — without ever preempting a live
|
|
2533
|
+
// worker — and recapped from its on-disk transcript. See topics/fork-recap.md.
|
|
2534
|
+
routes.post("/projects/:projectId/sessions/:sessionId/recap", async (c) => {
|
|
2535
|
+
const projectId = c.req.param("projectId");
|
|
2536
|
+
const sessionId = c.req.param("sessionId");
|
|
2537
|
+
if (!isUrlProjectId(projectId)) {
|
|
2538
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2539
|
+
}
|
|
2540
|
+
let sinceMs = null;
|
|
2541
|
+
try {
|
|
2542
|
+
const body = await c.req.json();
|
|
2543
|
+
if (typeof body.hiddenSinceMs === "number" &&
|
|
2544
|
+
Number.isFinite(body.hiddenSinceMs)) {
|
|
2545
|
+
sinceMs = body.hiddenSinceMs;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
catch {
|
|
2549
|
+
// Empty body is accepted.
|
|
2550
|
+
}
|
|
2551
|
+
// Live process: recap directly in whatever mode it runs.
|
|
2552
|
+
const live = deps.supervisor.getProcessForSession(sessionId);
|
|
2553
|
+
if (live && !live.isTerminated) {
|
|
2554
|
+
const result = await deps.supervisor.requestRecap(live.id, { sinceMs });
|
|
2555
|
+
return c.json(result);
|
|
2556
|
+
}
|
|
2557
|
+
// Cold session: only fork mode can recap from the on-disk transcript —
|
|
2558
|
+
// side-session/native recaps need in-memory recent text a revived process
|
|
2559
|
+
// lacks. Other modes simply skip until the session is live again.
|
|
2560
|
+
const recapMode = deps.sessionMetadataService?.getRecapMode(sessionId);
|
|
2561
|
+
if (recapMode !== "fork") {
|
|
2562
|
+
return c.json({
|
|
2563
|
+
supported: true,
|
|
2564
|
+
emitted: false,
|
|
2565
|
+
reason: "recap requires a live process for this recap mode",
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2569
|
+
if (!project) {
|
|
2570
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2571
|
+
}
|
|
2572
|
+
const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2573
|
+
const providerName = metadata?.provider ?? project.provider;
|
|
2574
|
+
const rawModel = metadata?.requestedModel;
|
|
2575
|
+
const model = rawModel && rawModel !== "default" ? rawModel : undefined;
|
|
2576
|
+
let process;
|
|
2577
|
+
try {
|
|
2578
|
+
process = await deps.supervisor.reactivateSession(project.path, sessionId, undefined, {
|
|
2579
|
+
model,
|
|
2580
|
+
providerName,
|
|
2581
|
+
executor: metadata?.executor,
|
|
2582
|
+
globalInstructions: getGlobalInstructions(),
|
|
2583
|
+
recapAfterSeconds: metadata?.recapAfterSeconds,
|
|
2584
|
+
recapMode: "fork",
|
|
2585
|
+
}, { preempt: false });
|
|
2586
|
+
}
|
|
2587
|
+
catch (error) {
|
|
2588
|
+
// At capacity with no idle worker to drop (background recaps never
|
|
2589
|
+
// preempt), or reactivation failed — skip the recap rather than error.
|
|
2590
|
+
const atCapacity = error instanceof Error && error.message.includes("worker capacity");
|
|
2591
|
+
return c.json({
|
|
2592
|
+
supported: true,
|
|
2593
|
+
emitted: false,
|
|
2594
|
+
reason: atCapacity
|
|
2595
|
+
? "recap skipped: at worker capacity"
|
|
2596
|
+
: "recap skipped: session could not be revived",
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
const result = await deps.supervisor.requestRecap(process.id, {
|
|
2600
|
+
sinceMs,
|
|
2601
|
+
revived: true,
|
|
2027
2602
|
});
|
|
2603
|
+
return c.json(result);
|
|
2028
2604
|
});
|
|
2029
2605
|
// POST /api/projects/:projectId/sessions/:sessionId/restart
|
|
2030
2606
|
// Start a fresh session from a bounded handoff, then terminate the old YA-owned process.
|
|
@@ -2061,10 +2637,18 @@ export function createSessionsRoutes(deps) {
|
|
|
2061
2637
|
}
|
|
2062
2638
|
executor = parsedSavedExecutor.executor;
|
|
2063
2639
|
}
|
|
2640
|
+
if (body.restartMode !== undefined &&
|
|
2641
|
+
body.restartMode !== "handoff" &&
|
|
2642
|
+
body.restartMode !== "fork") {
|
|
2643
|
+
return c.json({ error: 'restartMode must be "handoff" or "fork"' }, 400);
|
|
2644
|
+
}
|
|
2645
|
+
const restartMode = body.restartMode ?? "handoff";
|
|
2064
2646
|
const oldProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
2065
2647
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
2066
2648
|
const preferredSourceProvider = metadataProvider ?? oldProcess?.provider ?? project.provider;
|
|
2067
|
-
|
|
2649
|
+
// Fork copies the transcript as-is; pre-restart compaction is a
|
|
2650
|
+
// handoff-only mitigation.
|
|
2651
|
+
const compactAttempt = restartMode === "fork" ? undefined : await tryRestartCompact(oldProcess);
|
|
2068
2652
|
const oldProcessInterrupted = await interruptOldProcessForHandoff(oldProcess);
|
|
2069
2653
|
const sourceSession = await loadRestartSourceSession(project, sessionId, projectId, preferredSourceProvider, oldProcess);
|
|
2070
2654
|
if (!sourceSession) {
|
|
@@ -2073,6 +2657,103 @@ export function createSessionsRoutes(deps) {
|
|
|
2073
2657
|
const sourceProvider = sourceSession.provider ?? preferredSourceProvider;
|
|
2074
2658
|
const providerName = body.provider ?? sourceProvider;
|
|
2075
2659
|
const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2660
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
2661
|
+
const model = body.model && body.model !== "default" ? body.model : undefined;
|
|
2662
|
+
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
2663
|
+
if (restartMode === "fork") {
|
|
2664
|
+
if (body.provider && body.provider !== sourceProvider) {
|
|
2665
|
+
return c.json({
|
|
2666
|
+
error: "Fork keeps the source provider; omit provider or match the source session",
|
|
2667
|
+
}, 400);
|
|
2668
|
+
}
|
|
2669
|
+
if (!deps.supervisor.supportsForkSession(sourceProvider)) {
|
|
2670
|
+
return c.json({ error: `${sourceProvider} does not support transcript fork` }, 400);
|
|
2671
|
+
}
|
|
2672
|
+
const forkTitle = deriveForkTitle({
|
|
2673
|
+
preferredTitle: originalMetadata?.customTitle,
|
|
2674
|
+
sourceSession,
|
|
2675
|
+
});
|
|
2676
|
+
let fork;
|
|
2677
|
+
try {
|
|
2678
|
+
fork = await deps.supervisor.forkSession({
|
|
2679
|
+
sessionId,
|
|
2680
|
+
projectPath: project.path,
|
|
2681
|
+
providerName: sourceProvider,
|
|
2682
|
+
upToMessageId: body.forkUpToMessageId,
|
|
2683
|
+
title: forkTitle,
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
catch (error) {
|
|
2687
|
+
getLogger().warn({
|
|
2688
|
+
event: "restart_fork_failed",
|
|
2689
|
+
sessionId,
|
|
2690
|
+
projectId,
|
|
2691
|
+
sourceProvider,
|
|
2692
|
+
upToMessageId: body.forkUpToMessageId,
|
|
2693
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2694
|
+
}, "Transcript fork failed");
|
|
2695
|
+
return c.json({
|
|
2696
|
+
error: error instanceof Error ? error.message : "Transcript fork failed",
|
|
2697
|
+
}, 500);
|
|
2698
|
+
}
|
|
2699
|
+
const forkMessage = body.reason?.trim() || "Continue from this fork point.";
|
|
2700
|
+
const result = await deps.supervisor.resumeSession(fork.sessionId, project.path, { text: forkMessage, mode: body.mode }, body.mode, {
|
|
2701
|
+
model,
|
|
2702
|
+
serviceTier,
|
|
2703
|
+
thinking,
|
|
2704
|
+
effort,
|
|
2705
|
+
providerName: sourceProvider,
|
|
2706
|
+
executor,
|
|
2707
|
+
globalInstructions: getGlobalInstructions(),
|
|
2708
|
+
permissions: body.permissions,
|
|
2709
|
+
recapMode: helperSettings.recapMode,
|
|
2710
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ??
|
|
2711
|
+
originalMetadata?.recapAfterSeconds,
|
|
2712
|
+
// Inherit the source session's preference unless the body overrides.
|
|
2713
|
+
promptSuggestionMode: helperSettings.promptSuggestionMode ??
|
|
2714
|
+
originalMetadata?.promptSuggestionMode,
|
|
2715
|
+
helperSideModel: helperSettings.helperSideModel,
|
|
2716
|
+
});
|
|
2717
|
+
if (isQueueFullResponse(result)) {
|
|
2718
|
+
return c.json({ error: "Queue is full", maxQueueSize: result.maxQueueSize }, 503);
|
|
2719
|
+
}
|
|
2720
|
+
if (isQueuedResponse(result)) {
|
|
2721
|
+
deps.supervisor.cancelQueuedRequest(result.queueId);
|
|
2722
|
+
return c.json({
|
|
2723
|
+
error: "Fork could not start immediately; old process was left running",
|
|
2724
|
+
}, 503);
|
|
2725
|
+
}
|
|
2726
|
+
await persistLaunchMetadata(result.sessionId, sourceProvider, executor, undefined, body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId), result.promptSuggestionMode, result.recapAfterSeconds);
|
|
2727
|
+
if (deps.sessionMetadataService) {
|
|
2728
|
+
await deps.sessionMetadataService.updateMetadata(result.sessionId, {
|
|
2729
|
+
title: forkTitle,
|
|
2730
|
+
});
|
|
2731
|
+
deps.eventBus?.emit({
|
|
2732
|
+
type: "session-metadata-changed",
|
|
2733
|
+
sessionId: result.sessionId,
|
|
2734
|
+
title: forkTitle,
|
|
2735
|
+
timestamp: new Date().toISOString(),
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
const oldProcessAbortDeferred = abortOldProcessAfterReplacementActivity(oldProcess, result);
|
|
2739
|
+
return c.json({
|
|
2740
|
+
sessionId: result.sessionId,
|
|
2741
|
+
processId: result.id,
|
|
2742
|
+
projectId: result.projectId,
|
|
2743
|
+
provider: result.provider,
|
|
2744
|
+
model: result.resolvedModel ?? result.model,
|
|
2745
|
+
title: forkTitle,
|
|
2746
|
+
permissionMode: result.permissionMode,
|
|
2747
|
+
modeVersion: result.modeVersion,
|
|
2748
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
2749
|
+
restartedFrom: sessionId,
|
|
2750
|
+
forkUpToMessageId: body.forkUpToMessageId,
|
|
2751
|
+
oldProcessId: oldProcess?.id,
|
|
2752
|
+
oldProcessInterrupted,
|
|
2753
|
+
oldProcessAbortDeferred,
|
|
2754
|
+
oldProcessAborted: false,
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2076
2757
|
const handoffTitle = deriveRestartTitle({
|
|
2077
2758
|
preferredTitle: originalMetadata?.customTitle,
|
|
2078
2759
|
sourceSession,
|
|
@@ -2090,11 +2771,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2090
2771
|
omittedCount,
|
|
2091
2772
|
transcript,
|
|
2092
2773
|
});
|
|
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
2774
|
const result = await deps.supervisor.startSession(project.path, {
|
|
2099
2775
|
text: handoff,
|
|
2100
2776
|
mode: body.mode,
|
|
@@ -2109,7 +2785,11 @@ export function createSessionsRoutes(deps) {
|
|
|
2109
2785
|
globalInstructions: getGlobalInstructions(),
|
|
2110
2786
|
permissions: body.permissions,
|
|
2111
2787
|
recapMode: helperSettings.recapMode,
|
|
2112
|
-
|
|
2788
|
+
recapAfterSeconds: helperSettings.recapAfterSeconds ??
|
|
2789
|
+
originalMetadata?.recapAfterSeconds,
|
|
2790
|
+
// Inherit the source session's preference unless the body overrides.
|
|
2791
|
+
promptSuggestionMode: helperSettings.promptSuggestionMode ??
|
|
2792
|
+
originalMetadata?.promptSuggestionMode,
|
|
2113
2793
|
helperSideModel: helperSettings.helperSideModel,
|
|
2114
2794
|
});
|
|
2115
2795
|
if (isQueueFullResponse(result)) {
|
|
@@ -2121,7 +2801,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2121
2801
|
error: "Restart could not start immediately; old process was left running",
|
|
2122
2802
|
}, 503);
|
|
2123
2803
|
}
|
|
2124
|
-
await persistLaunchMetadata(result.sessionId, providerName, executor);
|
|
2804
|
+
await persistLaunchMetadata(result.sessionId, providerName, executor, undefined, body.model ?? deps.sessionMetadataService?.getRequestedModel(sessionId), result.promptSuggestionMode, result.recapAfterSeconds);
|
|
2125
2805
|
if (deps.sessionMetadataService) {
|
|
2126
2806
|
await deps.sessionMetadataService.updateMetadata(result.sessionId, {
|
|
2127
2807
|
title: handoffTitle,
|
|
@@ -2143,6 +2823,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2143
2823
|
title: handoffTitle,
|
|
2144
2824
|
permissionMode: result.permissionMode,
|
|
2145
2825
|
modeVersion: result.modeVersion,
|
|
2826
|
+
recapAfterSeconds: result.recapAfterSeconds,
|
|
2146
2827
|
restartedFrom: sessionId,
|
|
2147
2828
|
oldProcessId: oldProcess?.id,
|
|
2148
2829
|
oldProcessInterrupted,
|
|
@@ -2150,54 +2831,628 @@ export function createSessionsRoutes(deps) {
|
|
|
2150
2831
|
oldProcessAborted: false,
|
|
2151
2832
|
});
|
|
2152
2833
|
});
|
|
2153
|
-
// POST /api/sessions/:sessionId/
|
|
2154
|
-
|
|
2834
|
+
// POST /api/projects/:projectId/sessions/:sessionId/fork
|
|
2835
|
+
// Fork the provider transcript into a new resumable session without
|
|
2836
|
+
// starting a process or sending any message ("fork from here" / rewind).
|
|
2837
|
+
// The forked session opens cold; the next user send resumes it normally.
|
|
2838
|
+
routes.post("/projects/:projectId/sessions/:sessionId/fork", async (c) => {
|
|
2839
|
+
const projectId = c.req.param("projectId");
|
|
2155
2840
|
const sessionId = c.req.param("sessionId");
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
return c.json({ error: "No active process for session" }, 404);
|
|
2841
|
+
if (!isUrlProjectId(projectId)) {
|
|
2842
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2159
2843
|
}
|
|
2160
|
-
|
|
2844
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2845
|
+
if (!project) {
|
|
2846
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2847
|
+
}
|
|
2848
|
+
let body = {};
|
|
2161
2849
|
try {
|
|
2162
2850
|
body = await c.req.json();
|
|
2163
2851
|
}
|
|
2164
2852
|
catch {
|
|
2165
|
-
|
|
2853
|
+
// Body is optional; full-transcript fork.
|
|
2166
2854
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2855
|
+
const upToMessageId = typeof body.upToMessageId === "string" && body.upToMessageId
|
|
2856
|
+
? body.upToMessageId
|
|
2857
|
+
: undefined;
|
|
2858
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
2859
|
+
const sourceProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
2860
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), sourceProcess?.provider ?? metadataProvider, { readMode: "head" });
|
|
2861
|
+
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
2862
|
+
const providerName = sourceProcess?.provider ??
|
|
2863
|
+
metadataProvider ??
|
|
2864
|
+
sessionSummary?.provider ??
|
|
2865
|
+
project.provider;
|
|
2866
|
+
if (!deps.supervisor.supportsForkSession(providerName)) {
|
|
2867
|
+
return c.json({ error: `${providerName} does not support transcript fork` }, 400);
|
|
2169
2868
|
}
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
return c.json({
|
|
2183
|
-
error: "Process terminated",
|
|
2184
|
-
reason: process.terminationReason,
|
|
2185
|
-
}, 410); // 410 Gone
|
|
2869
|
+
const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
|
|
2870
|
+
let baseTitle = normalizeRestartTitleCandidate(originalMetadata?.customTitle);
|
|
2871
|
+
if (!baseTitle) {
|
|
2872
|
+
try {
|
|
2873
|
+
const summary = await deps
|
|
2874
|
+
.readerFactory(project)
|
|
2875
|
+
.getSessionSummary(sessionId, projectId, { readMode: "head" });
|
|
2876
|
+
baseTitle = normalizeRestartTitleCandidate(summary?.title);
|
|
2877
|
+
}
|
|
2878
|
+
catch {
|
|
2879
|
+
// Title is cosmetic; fall through to the provider default.
|
|
2880
|
+
}
|
|
2186
2881
|
}
|
|
2187
|
-
const
|
|
2188
|
-
?
|
|
2189
|
-
:
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2882
|
+
const forkTitle = baseTitle
|
|
2883
|
+
? truncateSessionTitle(/^Fork:/i.test(baseTitle) ? baseTitle : `Fork: ${baseTitle}`)
|
|
2884
|
+
: undefined;
|
|
2885
|
+
let fork;
|
|
2886
|
+
try {
|
|
2887
|
+
fork = await deps.supervisor.forkSession({
|
|
2888
|
+
sessionId,
|
|
2889
|
+
projectPath: project.path,
|
|
2890
|
+
providerName,
|
|
2891
|
+
upToMessageId,
|
|
2892
|
+
title: forkTitle,
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
catch (error) {
|
|
2896
|
+
getLogger().warn({
|
|
2897
|
+
event: "session_fork_failed",
|
|
2898
|
+
sessionId,
|
|
2899
|
+
projectId,
|
|
2900
|
+
providerName,
|
|
2901
|
+
upToMessageId,
|
|
2902
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2903
|
+
}, "Transcript fork failed");
|
|
2904
|
+
return c.json({
|
|
2905
|
+
error: error instanceof Error ? error.message : "Transcript fork failed",
|
|
2906
|
+
}, 500);
|
|
2907
|
+
}
|
|
2908
|
+
const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId)).executor;
|
|
2909
|
+
await persistLaunchMetadata(fork.sessionId, providerName, savedExecutor, undefined, deps.sessionMetadataService?.getRequestedModel(sessionId), originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
|
|
2910
|
+
if (deps.sessionMetadataService) {
|
|
2911
|
+
await deps.sessionMetadataService.updateMetadata(fork.sessionId, {
|
|
2912
|
+
title: forkTitle,
|
|
2913
|
+
parentSessionId: sessionId,
|
|
2914
|
+
});
|
|
2915
|
+
deps.eventBus?.emit({
|
|
2916
|
+
type: "session-metadata-changed",
|
|
2917
|
+
sessionId: fork.sessionId,
|
|
2918
|
+
title: forkTitle,
|
|
2919
|
+
parentSessionId: sessionId,
|
|
2920
|
+
timestamp: new Date().toISOString(),
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
return c.json({
|
|
2924
|
+
sessionId: fork.sessionId,
|
|
2925
|
+
projectId,
|
|
2926
|
+
provider: providerName,
|
|
2927
|
+
title: forkTitle,
|
|
2928
|
+
forkedFrom: sessionId,
|
|
2929
|
+
upToMessageId,
|
|
2930
|
+
});
|
|
2931
|
+
});
|
|
2932
|
+
// POST /api/projects/:projectId/sessions/:sessionId/retitle
|
|
2933
|
+
// Generate a proposed session title through an archived helper fork. This
|
|
2934
|
+
// never updates the source session title; the client must explicitly accept
|
|
2935
|
+
// the returned proposal through the ordinary metadata update route.
|
|
2936
|
+
routes.post("/projects/:projectId/sessions/:sessionId/retitle", async (c) => {
|
|
2937
|
+
const projectId = c.req.param("projectId");
|
|
2938
|
+
const sessionId = c.req.param("sessionId");
|
|
2939
|
+
if (!isUrlProjectId(projectId)) {
|
|
2940
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
2941
|
+
}
|
|
2942
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
2943
|
+
if (!project) {
|
|
2944
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
2945
|
+
}
|
|
2946
|
+
if (!deps.sessionMetadataService) {
|
|
2947
|
+
return c.json({ error: "Session metadata service not available" }, 503);
|
|
2948
|
+
}
|
|
2949
|
+
let body = {};
|
|
2950
|
+
try {
|
|
2951
|
+
body = await c.req.json();
|
|
2952
|
+
}
|
|
2953
|
+
catch {
|
|
2954
|
+
// Body is optional.
|
|
2955
|
+
}
|
|
2956
|
+
const currentTitle = typeof body.currentTitle === "string"
|
|
2957
|
+
? body.currentTitle.trim().slice(0, 200)
|
|
2958
|
+
: undefined;
|
|
2959
|
+
let lengthTarget;
|
|
2960
|
+
if (body.lengthTarget !== undefined) {
|
|
2961
|
+
if (typeof body.lengthTarget !== "number" ||
|
|
2962
|
+
!Number.isInteger(body.lengthTarget) ||
|
|
2963
|
+
body.lengthTarget < 20 ||
|
|
2964
|
+
body.lengthTarget > 132) {
|
|
2965
|
+
return c.json({ error: "lengthTarget must be an integer between 20 and 132" }, 400);
|
|
2966
|
+
}
|
|
2967
|
+
lengthTarget = body.lengthTarget;
|
|
2968
|
+
}
|
|
2969
|
+
const metadataProvider = deps.sessionMetadataService.getProvider(sessionId);
|
|
2970
|
+
let sourceProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
2971
|
+
const liveSourceProcess = sourceProcess && !sourceProcess.isTerminated ? sourceProcess : undefined;
|
|
2972
|
+
const sessionSummaryResult = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), liveSourceProcess?.provider ?? metadataProvider, { readMode: "head" });
|
|
2973
|
+
const sessionSummary = sessionSummaryResult?.summary ?? null;
|
|
2974
|
+
const providerName = liveSourceProcess?.provider ??
|
|
2975
|
+
metadataProvider ??
|
|
2976
|
+
sessionSummary?.provider ??
|
|
2977
|
+
sourceProcess?.provider ??
|
|
2978
|
+
project.provider;
|
|
2979
|
+
if (!deps.supervisor.supportsForkSession(providerName)) {
|
|
2980
|
+
return c.json({ error: `${providerName} does not support transcript fork` }, 400);
|
|
2981
|
+
}
|
|
2982
|
+
const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService.getExecutor(sessionId)).executor;
|
|
2983
|
+
let requestedModel = deps.sessionMetadataService.getRequestedModel(sessionId) ??
|
|
2984
|
+
liveSourceProcess?.model;
|
|
2985
|
+
const sourceMetadata = deps.sessionMetadataService.getMetadata(sessionId);
|
|
2986
|
+
const promptSuggestionMode = sourceMetadata?.promptSuggestionMode;
|
|
2987
|
+
const recapAfterSeconds = sourceMetadata?.recapAfterSeconds;
|
|
2988
|
+
const abortController = new AbortController();
|
|
2989
|
+
const abortFromRequest = () => abortController.abort();
|
|
2990
|
+
if (c.req.raw.signal.aborted) {
|
|
2991
|
+
abortController.abort();
|
|
2992
|
+
}
|
|
2993
|
+
else {
|
|
2994
|
+
c.req.raw.signal.addEventListener("abort", abortFromRequest, {
|
|
2995
|
+
once: true,
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
let generatorSessionId;
|
|
2999
|
+
try {
|
|
3000
|
+
if (!liveSourceProcess) {
|
|
3001
|
+
sourceProcess = await deps.supervisor.reactivateSession(project.path, sessionId, undefined, {
|
|
3002
|
+
model: requestedModel && requestedModel !== "default"
|
|
3003
|
+
? requestedModel
|
|
3004
|
+
: undefined,
|
|
3005
|
+
providerName,
|
|
3006
|
+
executor: savedExecutor,
|
|
3007
|
+
globalInstructions: getGlobalInstructions(),
|
|
3008
|
+
promptSuggestionMode,
|
|
3009
|
+
recapAfterSeconds,
|
|
3010
|
+
});
|
|
3011
|
+
requestedModel = requestedModel ?? sourceProcess.model;
|
|
3012
|
+
}
|
|
3013
|
+
const generator = await deps.supervisor.forkSession({
|
|
3014
|
+
sessionId,
|
|
3015
|
+
projectPath: project.path,
|
|
3016
|
+
providerName,
|
|
3017
|
+
title: "Retitle generator",
|
|
3018
|
+
});
|
|
3019
|
+
generatorSessionId = generator.sessionId;
|
|
3020
|
+
await updateForkSummaryChildMetadata(generator.sessionId, sessionId, "Retitle generator", true);
|
|
3021
|
+
await persistLaunchMetadata(generator.sessionId, providerName, savedExecutor, undefined, requestedModel, promptSuggestionMode, recapAfterSeconds);
|
|
3022
|
+
if (abortController.signal.aborted) {
|
|
3023
|
+
throw new DOMException("Retitle cancelled", "AbortError");
|
|
3024
|
+
}
|
|
3025
|
+
const generated = await deps.supervisor.generateSummary(providerName, {
|
|
3026
|
+
purpose: "session-retitle",
|
|
3027
|
+
strategy: "fork",
|
|
3028
|
+
generatorSessionId: generator.sessionId,
|
|
3029
|
+
cwd: project.path,
|
|
3030
|
+
currentTitle,
|
|
3031
|
+
lengthTarget,
|
|
3032
|
+
signal: abortController.signal,
|
|
3033
|
+
});
|
|
3034
|
+
const title = generatedRetitleCandidate(generated.text);
|
|
3035
|
+
if (!title) {
|
|
3036
|
+
throw new Error("Retitle generation returned empty title");
|
|
3037
|
+
}
|
|
3038
|
+
return c.json({ title, generatorSessionId: generator.sessionId });
|
|
3039
|
+
}
|
|
3040
|
+
catch (error) {
|
|
3041
|
+
const cancelled = abortController.signal.aborted ||
|
|
3042
|
+
(error instanceof DOMException && error.name === "AbortError");
|
|
3043
|
+
const message = error instanceof Error ? error.message : "Retitle generation failed";
|
|
3044
|
+
getLogger().warn({
|
|
3045
|
+
event: cancelled
|
|
3046
|
+
? "session_retitle_cancelled"
|
|
3047
|
+
: "session_retitle_failed",
|
|
3048
|
+
sessionId,
|
|
3049
|
+
projectId,
|
|
3050
|
+
providerName,
|
|
3051
|
+
generatorSessionId,
|
|
3052
|
+
error: message,
|
|
3053
|
+
}, cancelled ? "Session retitle cancelled" : "Session retitle failed");
|
|
3054
|
+
return c.json({ error: message }, cancelled ? 400 : 500);
|
|
3055
|
+
}
|
|
3056
|
+
finally {
|
|
3057
|
+
c.req.raw.signal.removeEventListener("abort", abortFromRequest);
|
|
3058
|
+
abortController.abort();
|
|
3059
|
+
}
|
|
3060
|
+
});
|
|
3061
|
+
// POST /api/projects/:projectId/sessions/:sessionId/fork-summary
|
|
3062
|
+
// Start a server-owned whole-context fork-after-summary job. The response
|
|
3063
|
+
// returns after durable job creation; generation continues independently of
|
|
3064
|
+
// the requesting client connection.
|
|
3065
|
+
routes.post("/projects/:projectId/sessions/:sessionId/fork-summary", async (c) => {
|
|
3066
|
+
const projectId = c.req.param("projectId");
|
|
3067
|
+
const sessionId = c.req.param("sessionId");
|
|
3068
|
+
if (!isUrlProjectId(projectId)) {
|
|
3069
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3070
|
+
}
|
|
3071
|
+
const project = await deps.scanner.getOrCreateProject(projectId);
|
|
3072
|
+
if (!project) {
|
|
3073
|
+
return c.json({ error: "Project not found or path does not exist" }, 404);
|
|
3074
|
+
}
|
|
3075
|
+
let body;
|
|
3076
|
+
try {
|
|
3077
|
+
body = await c.req.json();
|
|
3078
|
+
}
|
|
3079
|
+
catch {
|
|
3080
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3081
|
+
}
|
|
3082
|
+
const sourceMessageId = typeof body.sourceMessageId === "string" && body.sourceMessageId.trim()
|
|
3083
|
+
? body.sourceMessageId.trim()
|
|
3084
|
+
: undefined;
|
|
3085
|
+
if (!sourceMessageId) {
|
|
3086
|
+
return c.json({ error: "sourceMessageId is required" }, 400);
|
|
3087
|
+
}
|
|
3088
|
+
if (body.mode !== undefined &&
|
|
3089
|
+
(typeof body.mode !== "string" ||
|
|
3090
|
+
!ALL_PERMISSION_MODES.includes(body.mode))) {
|
|
3091
|
+
return c.json({ error: "Invalid permission mode" }, 400);
|
|
3092
|
+
}
|
|
3093
|
+
if (body.autoOpenWhenReady !== undefined &&
|
|
3094
|
+
typeof body.autoOpenWhenReady !== "boolean") {
|
|
3095
|
+
return c.json({ error: "autoOpenWhenReady must be a boolean" }, 400);
|
|
3096
|
+
}
|
|
3097
|
+
const mode = body.mode;
|
|
3098
|
+
const instructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3099
|
+
const autoOpenWhenReady = body.autoOpenWhenReady === true;
|
|
3100
|
+
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
3101
|
+
const sourceProcess = deps.supervisor.getProcessForSession(sessionId);
|
|
3102
|
+
const providerName = metadataProvider ?? sourceProcess?.provider ?? project.provider;
|
|
3103
|
+
if (!deps.supervisor.supportsForkSession(providerName)) {
|
|
3104
|
+
return c.json({ error: `${providerName} does not support transcript fork` }, 400);
|
|
3105
|
+
}
|
|
3106
|
+
if (!deps.sessionMetadataService) {
|
|
3107
|
+
return c.json({ error: "Session metadata service not available" }, 503);
|
|
3108
|
+
}
|
|
3109
|
+
if (activeForkSummaryJobs.has(sessionId)) {
|
|
3110
|
+
return c.json({ error: "A fork summary is already generating for this session" }, 409);
|
|
3111
|
+
}
|
|
3112
|
+
const sourceSession = sourceProcess
|
|
3113
|
+
? {
|
|
3114
|
+
messages: sdkMessagesToClientMessages(sourceProcess.getMessageHistory()),
|
|
3115
|
+
}
|
|
3116
|
+
: await loadRestartSourceSession(project, sessionId, projectId, providerName);
|
|
3117
|
+
if (!sourceSession) {
|
|
3118
|
+
return c.json({ error: "Source session not found" }, 404);
|
|
3119
|
+
}
|
|
3120
|
+
const boundary = resolveForkAfterBoundary(sourceSession.messages, sourceMessageId, sourceProcess?.state.type === "in-turn" ||
|
|
3121
|
+
sourceProcess?.state.type === "waiting-input");
|
|
3122
|
+
if ("error" in boundary) {
|
|
3123
|
+
return c.json({ error: boundary.error }, boundary.status);
|
|
3124
|
+
}
|
|
3125
|
+
const displayObject = {
|
|
3126
|
+
id: randomUUID(),
|
|
3127
|
+
kind: "fork-summary",
|
|
3128
|
+
createdAt: new Date().toISOString(),
|
|
3129
|
+
placementAfterMessageId: boundary.placementAfterMessageId,
|
|
3130
|
+
sourceMessageId,
|
|
3131
|
+
retainedThroughMessageId: boundary.retainedThroughMessageId,
|
|
3132
|
+
status: "generating",
|
|
3133
|
+
autoOpenWhenReady: autoOpenWhenReady || undefined,
|
|
3134
|
+
};
|
|
3135
|
+
const abortController = new AbortController();
|
|
3136
|
+
activeForkSummaryJobs.set(sessionId, {
|
|
3137
|
+
objectId: displayObject.id,
|
|
3138
|
+
abortController,
|
|
3139
|
+
});
|
|
3140
|
+
try {
|
|
3141
|
+
await deps.sessionMetadataService.addTranscriptDisplayObject(sessionId, displayObject);
|
|
3142
|
+
}
|
|
3143
|
+
catch (error) {
|
|
3144
|
+
activeForkSummaryJobs.delete(sessionId);
|
|
3145
|
+
throw error;
|
|
3146
|
+
}
|
|
3147
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3148
|
+
const originalMetadata = deps.sessionMetadataService.getMetadata(sessionId);
|
|
3149
|
+
const baseTitle = normalizeRestartTitleCandidate(originalMetadata?.customTitle ?? sourceSession.title);
|
|
3150
|
+
const fallbackTitle = baseTitle
|
|
3151
|
+
? truncateSessionTitle(/^Fork:/i.test(baseTitle) ? baseTitle : `Fork: ${baseTitle}`)
|
|
3152
|
+
: undefined;
|
|
3153
|
+
const savedExecutor = parseOptionalExecutor(deps.sessionMetadataService.getExecutor(sessionId)).executor;
|
|
3154
|
+
const requestedModel = deps.sessionMetadataService.getRequestedModel(sessionId) ??
|
|
3155
|
+
sourceProcess?.model;
|
|
3156
|
+
void (async () => {
|
|
3157
|
+
let generatorSessionId;
|
|
3158
|
+
let targetSessionId;
|
|
3159
|
+
let targetTitle;
|
|
3160
|
+
let targetProcessId;
|
|
3161
|
+
let completed = false;
|
|
3162
|
+
try {
|
|
3163
|
+
const generator = await deps.supervisor.forkSession({
|
|
3164
|
+
sessionId,
|
|
3165
|
+
projectPath: project.path,
|
|
3166
|
+
providerName,
|
|
3167
|
+
title: "Fork summary generator",
|
|
3168
|
+
});
|
|
3169
|
+
generatorSessionId = generator.sessionId;
|
|
3170
|
+
await updateForkSummaryChildMetadata(generator.sessionId, sessionId, "Fork summary generator", true);
|
|
3171
|
+
await persistLaunchMetadata(generator.sessionId, providerName, savedExecutor, undefined, requestedModel, originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
|
|
3172
|
+
if (abortController.signal.aborted) {
|
|
3173
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3174
|
+
}
|
|
3175
|
+
const generated = await deps.supervisor.generateSummary(providerName, {
|
|
3176
|
+
purpose: "fork-after-summary",
|
|
3177
|
+
strategy: "fork",
|
|
3178
|
+
generatorSessionId: generator.sessionId,
|
|
3179
|
+
cwd: project.path,
|
|
3180
|
+
afterTurnMessageId: boundary.retainedThroughMessageId,
|
|
3181
|
+
afterTurnContext: boundary.retainedThroughContext,
|
|
3182
|
+
instructions,
|
|
3183
|
+
signal: abortController.signal,
|
|
3184
|
+
});
|
|
3185
|
+
if (abortController.signal.aborted) {
|
|
3186
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3187
|
+
}
|
|
3188
|
+
const title = forkSummaryTitle(generated.text, fallbackTitle);
|
|
3189
|
+
targetTitle = title;
|
|
3190
|
+
const target = await deps.supervisor.forkSession({
|
|
3191
|
+
sessionId,
|
|
3192
|
+
projectPath: project.path,
|
|
3193
|
+
providerName,
|
|
3194
|
+
upToMessageId: boundary.retainedThroughMessageId,
|
|
3195
|
+
title,
|
|
3196
|
+
});
|
|
3197
|
+
targetSessionId = target.sessionId;
|
|
3198
|
+
await updateForkSummaryChildMetadata(target.sessionId, sessionId, title, true);
|
|
3199
|
+
await persistLaunchMetadata(target.sessionId, providerName, savedExecutor, undefined, requestedModel, originalMetadata?.promptSuggestionMode, originalMetadata?.recapAfterSeconds);
|
|
3200
|
+
if (abortController.signal.aborted) {
|
|
3201
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3202
|
+
}
|
|
3203
|
+
const result = await deps.supervisor.resumeSession(target.sessionId, project.path, { text: generated.text, mode }, mode, {
|
|
3204
|
+
providerName,
|
|
3205
|
+
executor: savedExecutor,
|
|
3206
|
+
globalInstructions: getGlobalInstructions(),
|
|
3207
|
+
model: requestedModel,
|
|
3208
|
+
promptSuggestionMode: originalMetadata?.promptSuggestionMode,
|
|
3209
|
+
recapAfterSeconds: originalMetadata?.recapAfterSeconds,
|
|
3210
|
+
});
|
|
3211
|
+
if (isQueueFullResponse(result)) {
|
|
3212
|
+
throw new Error("Queue is full");
|
|
3213
|
+
}
|
|
3214
|
+
if (isQueuedResponse(result)) {
|
|
3215
|
+
deps.supervisor.cancelQueuedRequest(result.queueId);
|
|
3216
|
+
throw new Error("Fork summary could not start immediately");
|
|
3217
|
+
}
|
|
3218
|
+
targetProcessId = result.id;
|
|
3219
|
+
if (abortController.signal.aborted) {
|
|
3220
|
+
await deps.supervisor.abortProcess(result.id);
|
|
3221
|
+
targetProcessId = undefined;
|
|
3222
|
+
throw new DOMException("Fork summary cancelled", "AbortError");
|
|
3223
|
+
}
|
|
3224
|
+
await persistLaunchMetadata(result.sessionId, providerName, savedExecutor, undefined, requestedModel, result.promptSuggestionMode, result.recapAfterSeconds);
|
|
3225
|
+
await updateForkSummaryChildMetadata(result.sessionId, sessionId, title, false);
|
|
3226
|
+
await deps.sessionMetadataService?.updateTranscriptDisplayObject(sessionId, displayObject.id, (object) => ({
|
|
3227
|
+
...object,
|
|
3228
|
+
status: "ready",
|
|
3229
|
+
targetSessionId: result.sessionId,
|
|
3230
|
+
title,
|
|
3231
|
+
error: undefined,
|
|
3232
|
+
}));
|
|
3233
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3234
|
+
completed = true;
|
|
3235
|
+
}
|
|
3236
|
+
catch (error) {
|
|
3237
|
+
const cancelled = abortController.signal.aborted ||
|
|
3238
|
+
(error instanceof DOMException && error.name === "AbortError");
|
|
3239
|
+
const logCleanupFailure = (stage, cleanupError) => {
|
|
3240
|
+
getLogger().warn({
|
|
3241
|
+
event: "fork_after_summary_cleanup_failed",
|
|
3242
|
+
sessionId,
|
|
3243
|
+
projectId,
|
|
3244
|
+
providerName,
|
|
3245
|
+
stage,
|
|
3246
|
+
error: cleanupError instanceof Error
|
|
3247
|
+
? cleanupError.message
|
|
3248
|
+
: String(cleanupError),
|
|
3249
|
+
}, "Fork-after-summary cleanup failed");
|
|
3250
|
+
};
|
|
3251
|
+
if (generatorSessionId) {
|
|
3252
|
+
try {
|
|
3253
|
+
await updateForkSummaryChildMetadata(generatorSessionId, sessionId, "Fork summary generator", true);
|
|
3254
|
+
}
|
|
3255
|
+
catch (cleanupError) {
|
|
3256
|
+
logCleanupFailure("archive-generator", cleanupError);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (targetSessionId && targetTitle) {
|
|
3260
|
+
try {
|
|
3261
|
+
await updateForkSummaryChildMetadata(targetSessionId, sessionId, targetTitle, true);
|
|
3262
|
+
}
|
|
3263
|
+
catch (cleanupError) {
|
|
3264
|
+
logCleanupFailure("archive-target", cleanupError);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
if (!completed && targetProcessId) {
|
|
3268
|
+
try {
|
|
3269
|
+
await deps.supervisor.abortProcess(targetProcessId);
|
|
3270
|
+
}
|
|
3271
|
+
catch (cleanupError) {
|
|
3272
|
+
logCleanupFailure("abort-target-process", cleanupError);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
if (!cancelled) {
|
|
3276
|
+
const message = error instanceof Error
|
|
3277
|
+
? error.message
|
|
3278
|
+
: "Summary generation failed";
|
|
3279
|
+
getLogger().warn({
|
|
3280
|
+
event: "fork_after_summary_failed",
|
|
3281
|
+
sessionId,
|
|
3282
|
+
projectId,
|
|
3283
|
+
providerName,
|
|
3284
|
+
sourceMessageId,
|
|
3285
|
+
error: message,
|
|
3286
|
+
}, "Fork-after-summary job failed");
|
|
3287
|
+
try {
|
|
3288
|
+
await deps.sessionMetadataService?.updateTranscriptDisplayObject(sessionId, displayObject.id, (object) => ({
|
|
3289
|
+
...object,
|
|
3290
|
+
status: "error",
|
|
3291
|
+
error: message,
|
|
3292
|
+
}));
|
|
3293
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3294
|
+
}
|
|
3295
|
+
catch (cleanupError) {
|
|
3296
|
+
logCleanupFailure("persist-error-state", cleanupError);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
finally {
|
|
3301
|
+
const active = activeForkSummaryJobs.get(sessionId);
|
|
3302
|
+
if (active?.objectId === displayObject.id) {
|
|
3303
|
+
activeForkSummaryJobs.delete(sessionId);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
})().catch((error) => {
|
|
3307
|
+
getLogger().error({
|
|
3308
|
+
event: "fork_after_summary_unhandled_failure",
|
|
3309
|
+
sessionId,
|
|
3310
|
+
projectId,
|
|
3311
|
+
providerName,
|
|
3312
|
+
sourceMessageId,
|
|
3313
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3314
|
+
}, "Fork-after-summary job escaped its guarded workflow");
|
|
3315
|
+
});
|
|
3316
|
+
return c.json({ displayObject }, 202);
|
|
3317
|
+
});
|
|
3318
|
+
routes.post("/projects/:projectId/sessions/:sessionId/fork-summary/:objectId/cancel", async (c) => {
|
|
3319
|
+
const projectId = c.req.param("projectId");
|
|
3320
|
+
const sessionId = c.req.param("sessionId");
|
|
3321
|
+
const objectId = c.req.param("objectId");
|
|
3322
|
+
if (!isUrlProjectId(projectId)) {
|
|
3323
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3324
|
+
}
|
|
3325
|
+
const active = activeForkSummaryJobs.get(sessionId);
|
|
3326
|
+
const object = deps.sessionMetadataService
|
|
3327
|
+
?.getTranscriptDisplayObjects(sessionId)
|
|
3328
|
+
.find((candidate) => candidate.id === objectId);
|
|
3329
|
+
if (!object) {
|
|
3330
|
+
return c.json({ error: "Transcript display object not found" }, 404);
|
|
3331
|
+
}
|
|
3332
|
+
if (active?.objectId === objectId && object.status === "generating") {
|
|
3333
|
+
active.abortController.abort();
|
|
3334
|
+
}
|
|
3335
|
+
else if (object.status !== "error") {
|
|
3336
|
+
return c.json({ error: "Fork summary job is not active" }, 409);
|
|
3337
|
+
}
|
|
3338
|
+
await deps.sessionMetadataService?.removeTranscriptDisplayObject(sessionId, objectId);
|
|
3339
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3340
|
+
return c.json({
|
|
3341
|
+
transcriptDisplayObjects: deps.sessionMetadataService?.getTranscriptDisplayObjects(sessionId) ??
|
|
3342
|
+
[],
|
|
3343
|
+
});
|
|
3344
|
+
});
|
|
3345
|
+
routes.patch("/projects/:projectId/sessions/:sessionId/fork-summary/:objectId", async (c) => {
|
|
3346
|
+
const projectId = c.req.param("projectId");
|
|
3347
|
+
const sessionId = c.req.param("sessionId");
|
|
3348
|
+
const objectId = c.req.param("objectId");
|
|
3349
|
+
if (!isUrlProjectId(projectId)) {
|
|
3350
|
+
return c.json({ error: "Invalid project ID format" }, 400);
|
|
3351
|
+
}
|
|
3352
|
+
if (!deps.sessionMetadataService) {
|
|
3353
|
+
return c.json({ error: "Session metadata service not available" }, 503);
|
|
3354
|
+
}
|
|
3355
|
+
let body;
|
|
3356
|
+
try {
|
|
3357
|
+
body = await c.req.json();
|
|
3358
|
+
}
|
|
3359
|
+
catch {
|
|
3360
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3361
|
+
}
|
|
3362
|
+
const action = body.action === "opened" || body.action === "clicked"
|
|
3363
|
+
? body.action
|
|
3364
|
+
: undefined;
|
|
3365
|
+
if (body.autoOpenWhenReady !== undefined &&
|
|
3366
|
+
typeof body.autoOpenWhenReady !== "boolean") {
|
|
3367
|
+
return c.json({ error: "autoOpenWhenReady must be a boolean" }, 400);
|
|
3368
|
+
}
|
|
3369
|
+
if (body.action !== undefined && !action) {
|
|
3370
|
+
return c.json({ error: "action must be opened or clicked" }, 400);
|
|
3371
|
+
}
|
|
3372
|
+
const now = new Date().toISOString();
|
|
3373
|
+
const updated = await deps.sessionMetadataService.updateTranscriptDisplayObject(sessionId, objectId, (object) => ({
|
|
3374
|
+
...object,
|
|
3375
|
+
...(typeof body.autoOpenWhenReady === "boolean"
|
|
3376
|
+
? { autoOpenWhenReady: body.autoOpenWhenReady || undefined }
|
|
3377
|
+
: {}),
|
|
3378
|
+
...(action === "opened" ? { openedAt: now } : {}),
|
|
3379
|
+
...(action === "clicked" ? { clickedAt: now } : {}),
|
|
3380
|
+
}));
|
|
3381
|
+
if (!updated) {
|
|
3382
|
+
return c.json({ error: "Transcript display object not found" }, 404);
|
|
3383
|
+
}
|
|
3384
|
+
emitTranscriptDisplayObjects(sessionId);
|
|
3385
|
+
return c.json({
|
|
3386
|
+
displayObject: updated,
|
|
3387
|
+
transcriptDisplayObjects: deps.sessionMetadataService.getTranscriptDisplayObjects(sessionId),
|
|
3388
|
+
});
|
|
3389
|
+
});
|
|
3390
|
+
// POST /api/sessions/:sessionId/messages - Queue message
|
|
3391
|
+
routes.post("/sessions/:sessionId/messages", async (c) => {
|
|
3392
|
+
const sessionId = c.req.param("sessionId");
|
|
3393
|
+
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
3394
|
+
if (!process) {
|
|
3395
|
+
return c.json({ error: "No active process for session" }, 404);
|
|
3396
|
+
}
|
|
3397
|
+
let body;
|
|
3398
|
+
try {
|
|
3399
|
+
body = await c.req.json();
|
|
3400
|
+
}
|
|
3401
|
+
catch {
|
|
3402
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3403
|
+
}
|
|
3404
|
+
if (!body.message) {
|
|
3405
|
+
return c.json({ error: "Message is required" }, 400);
|
|
3406
|
+
}
|
|
3407
|
+
const serverTimestamp = Date.now();
|
|
3408
|
+
const userMessage = {
|
|
3409
|
+
text: body.message,
|
|
3410
|
+
images: body.images,
|
|
3411
|
+
documents: body.documents,
|
|
3412
|
+
attachments: body.attachments,
|
|
3413
|
+
mode: body.mode,
|
|
3414
|
+
tempId: body.tempId,
|
|
3415
|
+
metadata: buildUserMessageMetadata(body, serverTimestamp, body.deferred ? "deferred" : "direct"),
|
|
3416
|
+
};
|
|
3417
|
+
// Check if process is terminated
|
|
3418
|
+
if (process.isTerminated) {
|
|
3419
|
+
return c.json({
|
|
3420
|
+
error: "Process terminated",
|
|
3421
|
+
reason: process.terminationReason,
|
|
3422
|
+
}, 410); // 410 Gone
|
|
3423
|
+
}
|
|
3424
|
+
// Provider-native slash commands (e.g. Codex `/compact`) are dispatched
|
|
3425
|
+
// through the provider's own protocol rather than delivered as turn text the
|
|
3426
|
+
// model would never interpret. Claude's `/compact` reports handled:false and
|
|
3427
|
+
// falls through to normal delivery so the SDK handles it as a regular turn
|
|
3428
|
+
// (and any trailing focus instructions reach the SDK verbatim). A deferred
|
|
3429
|
+
// submission keeps normal queue semantics.
|
|
3430
|
+
if (!body.deferred) {
|
|
3431
|
+
const parsed = parseSlashCommandSubmission(body.message);
|
|
3432
|
+
if (parsed) {
|
|
3433
|
+
const providerResult = await process.runProviderCommand(parsed.name, parsed.argument);
|
|
3434
|
+
if (providerResult.handled) {
|
|
3435
|
+
if (providerResult.error) {
|
|
3436
|
+
return c.json({ error: "Failed to run command", reason: providerResult.error }, 409);
|
|
3437
|
+
}
|
|
3438
|
+
return c.json({ queued: true, serverTimestamp });
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
const resolvedModel = body.model && body.model !== "default"
|
|
3443
|
+
? body.model
|
|
3444
|
+
: (process.resolvedModel ?? process.model);
|
|
3445
|
+
const modelInfoService = deps.modelInfoService;
|
|
3446
|
+
const resolveContextWindow = modelInfoService
|
|
3447
|
+
? (model, provider) => modelInfoService.getContextWindow(model, provider)
|
|
3448
|
+
: getModelContextWindow;
|
|
3449
|
+
let compactQueued = false;
|
|
3450
|
+
if (!body.deferred && !body.thinking && body.model === undefined) {
|
|
3451
|
+
const compactAttempt = await tryQueueTargetedAutoCompact({
|
|
3452
|
+
process,
|
|
3453
|
+
model: resolvedModel,
|
|
3454
|
+
message: body.message,
|
|
3455
|
+
resolveContextWindow,
|
|
2201
3456
|
});
|
|
2202
3457
|
compactQueued = compactAttempt.queued;
|
|
2203
3458
|
}
|
|
@@ -2210,7 +3465,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2210
3465
|
await process.primeSupportedCommandsForMessage(userMessage);
|
|
2211
3466
|
const deferredResult = process.deferMessage(userMessage, {
|
|
2212
3467
|
promoteIfReady: true,
|
|
2213
|
-
placement: parseDeferredPlacement(body),
|
|
2214
3468
|
});
|
|
2215
3469
|
if (!deferredResult.success) {
|
|
2216
3470
|
return c.json({
|
|
@@ -2218,6 +3472,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2218
3472
|
reason: deferredResult.error,
|
|
2219
3473
|
}, 410);
|
|
2220
3474
|
}
|
|
3475
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
2221
3476
|
return c.json({
|
|
2222
3477
|
queued: true,
|
|
2223
3478
|
deferred: deferredResult.deferred,
|
|
@@ -2227,10 +3482,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2227
3482
|
serverTimestamp,
|
|
2228
3483
|
});
|
|
2229
3484
|
}
|
|
2230
|
-
|
|
2231
|
-
const { thinking, effort } = body.thinking
|
|
2232
|
-
? thinkingOptionToConfig(body.thinking)
|
|
2233
|
-
: { thinking: undefined, effort: undefined };
|
|
3485
|
+
const { thinking, effort } = buildThinkingOptions(body);
|
|
2234
3486
|
const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
|
|
2235
3487
|
const metadataExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId));
|
|
2236
3488
|
if (metadataExecutor.error) {
|
|
@@ -2244,9 +3496,26 @@ export function createSessionsRoutes(deps) {
|
|
|
2244
3496
|
? body.model
|
|
2245
3497
|
: (process.resolvedModel ?? process.model);
|
|
2246
3498
|
const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
|
|
3499
|
+
// Per-model preemptive-compaction threshold (task 029). The route holds the
|
|
3500
|
+
// settings; the Supervisor stays settings-agnostic. Key strictly by the YA
|
|
3501
|
+
// model id: the alias the user picked this turn, else the live requested
|
|
3502
|
+
// alias, else the alias persisted at launch (survives restart), else the
|
|
3503
|
+
// provider's reported→YA-id helper for sessions YA didn't start. No family
|
|
3504
|
+
// fallback. See topics/provider-abstraction.md § Per-model settings keying.
|
|
3505
|
+
const yaModelId = body.model ??
|
|
3506
|
+
process.requestedModel ??
|
|
3507
|
+
deps.sessionMetadataService?.getRequestedModel(sessionId) ??
|
|
3508
|
+
getProvider(process.provider)?.yaModelIdForReported?.(process.resolvedModel);
|
|
3509
|
+
const compactAtContextPercent = resolveCompactPercent(deps.serverSettingsService?.getSetting("clientDefaults")
|
|
3510
|
+
?.compactAtContextPercent, yaModelId);
|
|
3511
|
+
// Resolve the effective window here too — process.contextWindow is
|
|
3512
|
+
// unreliable (often undefined) and the base resolver ignores always-1M.
|
|
3513
|
+
const compactAtContextWindow = compactAtContextPercent === undefined
|
|
3514
|
+
? undefined
|
|
3515
|
+
: resolveCompactWindow(process.provider, [yaModelId, process.resolvedModel, process.model], resolveContextWindow);
|
|
2247
3516
|
// Use queueMessageToSession which handles thinking mode changes
|
|
2248
3517
|
// If thinking mode changed, it will restart the process automatically
|
|
2249
|
-
const queueGlobalInstructions =
|
|
3518
|
+
const queueGlobalInstructions = getGlobalInstructions();
|
|
2250
3519
|
const result = await deps.supervisor.queueMessageToSession(sessionId, process.projectPath, userMessage, body.mode, {
|
|
2251
3520
|
model,
|
|
2252
3521
|
serviceTier,
|
|
@@ -2259,6 +3528,8 @@ export function createSessionsRoutes(deps) {
|
|
|
2259
3528
|
undefined,
|
|
2260
3529
|
globalInstructions: queueGlobalInstructions,
|
|
2261
3530
|
permissions: body.permissions,
|
|
3531
|
+
compactAtContextPercent,
|
|
3532
|
+
compactAtContextWindow,
|
|
2262
3533
|
});
|
|
2263
3534
|
if (!result.success) {
|
|
2264
3535
|
return c.json({
|
|
@@ -2274,8 +3545,114 @@ export function createSessionsRoutes(deps) {
|
|
|
2274
3545
|
serverTimestamp,
|
|
2275
3546
|
});
|
|
2276
3547
|
});
|
|
3548
|
+
// DELETE /api/sessions/:sessionId/recovered-queue/:queueId - Delete a
|
|
3549
|
+
// restart-paused patient queue entry by durable queue id.
|
|
3550
|
+
routes.delete("/sessions/:sessionId/recovered-queue/:queueId", async (c) => {
|
|
3551
|
+
const sessionId = c.req.param("sessionId");
|
|
3552
|
+
const queueId = c.req.param("queueId");
|
|
3553
|
+
const service = deps.sessionQueuePersistenceService;
|
|
3554
|
+
if (!service) {
|
|
3555
|
+
return c.json({ error: "Session queue persistence unavailable" }, 503);
|
|
3556
|
+
}
|
|
3557
|
+
const item = service
|
|
3558
|
+
.listSession(sessionId)
|
|
3559
|
+
.find((candidate) => candidate.id === queueId);
|
|
3560
|
+
if (item?.kind !== "patient" || item.status !== "paused-after-restart") {
|
|
3561
|
+
return c.json({ error: "Recovered queued message not found" }, 404);
|
|
3562
|
+
}
|
|
3563
|
+
await service.deleteItem(queueId);
|
|
3564
|
+
return c.json({
|
|
3565
|
+
deleted: true,
|
|
3566
|
+
deferredMessages: recoveredPatientQueueSummaries(deps, sessionId),
|
|
3567
|
+
});
|
|
3568
|
+
});
|
|
3569
|
+
const recoveredQueueDeps = {
|
|
3570
|
+
sessionQueuePersistenceService: deps.sessionQueuePersistenceService,
|
|
3571
|
+
sessionMetadataService: deps.sessionMetadataService,
|
|
3572
|
+
supervisor: deps.supervisor,
|
|
3573
|
+
getGlobalInstructions,
|
|
3574
|
+
persistLaunchMetadata,
|
|
3575
|
+
};
|
|
3576
|
+
// POST /api/sessions/:sessionId/recovered-queue/:queueId/resume - Move
|
|
3577
|
+
// restart-paused patient queue entries back into the live patient queue,
|
|
3578
|
+
// through the requested entry: resuming a non-head entry also resumes every
|
|
3579
|
+
// recovered entry before it, so compose order is preserved.
|
|
3580
|
+
routes.post("/sessions/:sessionId/recovered-queue/:queueId/resume", async (c) => {
|
|
3581
|
+
const sessionId = c.req.param("sessionId");
|
|
3582
|
+
const queueId = c.req.param("queueId");
|
|
3583
|
+
const resolved = await resolveRecoveredGroupForDelivery(recoveredQueueDeps, sessionId, queueId, "Drain or cancel newer queued messages before resuming recovered work.");
|
|
3584
|
+
if (!resolved.ok) {
|
|
3585
|
+
return c.json(resolved.body, resolved.status);
|
|
3586
|
+
}
|
|
3587
|
+
const { process, group } = resolved;
|
|
3588
|
+
const { last, error } = await resumeRecoveredGroup(process, group, {
|
|
3589
|
+
promoteIfReady: true,
|
|
3590
|
+
});
|
|
3591
|
+
if (error || !last) {
|
|
3592
|
+
return c.json({
|
|
3593
|
+
error: "Failed to resume recovered queued message",
|
|
3594
|
+
reason: error,
|
|
3595
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3596
|
+
}, 409);
|
|
3597
|
+
}
|
|
3598
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
3599
|
+
return c.json({
|
|
3600
|
+
resumed: true,
|
|
3601
|
+
resumedCount: group.length,
|
|
3602
|
+
deferred: last.deferred,
|
|
3603
|
+
promoted: last.promoted,
|
|
3604
|
+
position: last.position,
|
|
3605
|
+
processId: process.id,
|
|
3606
|
+
processState: reportableProcessState(process),
|
|
3607
|
+
permissionMode: process.permissionMode,
|
|
3608
|
+
modeVersion: process.modeVersion,
|
|
3609
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
3610
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3611
|
+
serverTimestamp: Date.now(),
|
|
3612
|
+
});
|
|
3613
|
+
});
|
|
3614
|
+
// POST /api/sessions/:sessionId/recovered-queue/:queueId/steer - Steer a
|
|
3615
|
+
// restart-paused patient queue entry into the session now, without waiting
|
|
3616
|
+
// for verified quiet: the entry and every recovered or live patient entry
|
|
3617
|
+
// composed before it rejoin the live queue and steer in compose order.
|
|
3618
|
+
routes.post("/sessions/:sessionId/recovered-queue/:queueId/steer", async (c) => {
|
|
3619
|
+
const sessionId = c.req.param("sessionId");
|
|
3620
|
+
const queueId = c.req.param("queueId");
|
|
3621
|
+
const resolved = await resolveRecoveredGroupForDelivery(recoveredQueueDeps, sessionId, queueId, "Drain or cancel newer queued messages before steering recovered work.");
|
|
3622
|
+
if (!resolved.ok) {
|
|
3623
|
+
return c.json(resolved.body, resolved.status);
|
|
3624
|
+
}
|
|
3625
|
+
const { process, group } = resolved;
|
|
3626
|
+
const { lastMessage, error } = await resumeRecoveredGroup(process, group, { promoteIfReady: false });
|
|
3627
|
+
if (error || !lastMessage?.tempId) {
|
|
3628
|
+
return c.json({
|
|
3629
|
+
error: "Failed to steer recovered queued message",
|
|
3630
|
+
reason: error,
|
|
3631
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3632
|
+
}, 409);
|
|
3633
|
+
}
|
|
3634
|
+
const steerResult = process.steerPatientDeferredMessagesThrough(lastMessage.tempId);
|
|
3635
|
+
if (!steerResult.success) {
|
|
3636
|
+
return c.json({
|
|
3637
|
+
error: steerResult.error ?? "Failed to steer recovered queued message",
|
|
3638
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3639
|
+
}, 409);
|
|
3640
|
+
}
|
|
3641
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
3642
|
+
return c.json({
|
|
3643
|
+
steered: true,
|
|
3644
|
+
count: steerResult.steered,
|
|
3645
|
+
processId: process.id,
|
|
3646
|
+
processState: reportableProcessState(process),
|
|
3647
|
+
permissionMode: process.permissionMode,
|
|
3648
|
+
modeVersion: process.modeVersion,
|
|
3649
|
+
recapAfterSeconds: process.recapAfterSeconds,
|
|
3650
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
3651
|
+
serverTimestamp: Date.now(),
|
|
3652
|
+
});
|
|
3653
|
+
});
|
|
2277
3654
|
// DELETE /api/sessions/:sessionId/deferred/:tempId - Cancel a deferred message
|
|
2278
|
-
routes.delete("/sessions/:sessionId/deferred/:tempId", (c) => {
|
|
3655
|
+
routes.delete("/sessions/:sessionId/deferred/:tempId", async (c) => {
|
|
2279
3656
|
const sessionId = c.req.param("sessionId");
|
|
2280
3657
|
const tempId = c.req.param("tempId");
|
|
2281
3658
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
@@ -2286,40 +3663,45 @@ export function createSessionsRoutes(deps) {
|
|
|
2286
3663
|
if (!cancelled) {
|
|
2287
3664
|
return c.json({ error: "Deferred message not found" }, 404);
|
|
2288
3665
|
}
|
|
3666
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
2289
3667
|
return c.json({ cancelled: true });
|
|
2290
3668
|
});
|
|
2291
|
-
//
|
|
2292
|
-
|
|
3669
|
+
// DELETE /api/sessions/:sessionId/steering/:tempId - Cancel a sent steering
|
|
3670
|
+
// message that has not yet been consumed by the provider.
|
|
3671
|
+
routes.delete("/sessions/:sessionId/steering/:tempId", async (c) => {
|
|
2293
3672
|
const sessionId = c.req.param("sessionId");
|
|
2294
3673
|
const tempId = c.req.param("tempId");
|
|
2295
3674
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
2296
3675
|
if (!process) {
|
|
2297
3676
|
return c.json({ error: "No active process for session" }, 404);
|
|
2298
3677
|
}
|
|
2299
|
-
const
|
|
2300
|
-
if (!
|
|
2301
|
-
return c.json({ error: "
|
|
3678
|
+
const cancelled = process.cancelUnconfirmedSteerMessage(tempId);
|
|
3679
|
+
if (!cancelled) {
|
|
3680
|
+
return c.json({ error: "Steering message already acted on or not found" }, 404);
|
|
2302
3681
|
}
|
|
2303
|
-
return c.json({
|
|
2304
|
-
message: taken.message.text,
|
|
2305
|
-
tempId: taken.message.tempId,
|
|
2306
|
-
mode: taken.message.mode,
|
|
2307
|
-
attachments: taken.message.attachments,
|
|
2308
|
-
placement: taken.placement,
|
|
2309
|
-
});
|
|
3682
|
+
return c.json({ cancelled: true });
|
|
2310
3683
|
});
|
|
2311
|
-
// POST /api/sessions/:sessionId/deferred/:tempId/
|
|
2312
|
-
|
|
3684
|
+
// POST /api/sessions/:sessionId/deferred/:tempId/steer - Steer a patient
|
|
3685
|
+
// queued message, and every patient entry ahead of it, into the session now.
|
|
3686
|
+
routes.post("/sessions/:sessionId/deferred/:tempId/steer", async (c) => {
|
|
2313
3687
|
const sessionId = c.req.param("sessionId");
|
|
2314
3688
|
const tempId = c.req.param("tempId");
|
|
2315
3689
|
const process = deps.supervisor.getProcessForSession(sessionId);
|
|
2316
3690
|
if (!process) {
|
|
2317
3691
|
return c.json({ error: "No active process for session" }, 404);
|
|
2318
3692
|
}
|
|
2319
|
-
const
|
|
3693
|
+
const result = process.steerPatientDeferredMessagesThrough(tempId);
|
|
3694
|
+
if (!result.success) {
|
|
3695
|
+
const status = result.error === "Deferred message not found" ? 404 : 409;
|
|
3696
|
+
return c.json({ error: result.error }, status);
|
|
3697
|
+
}
|
|
3698
|
+
await process.waitForPatientQueuePersistenceIdle();
|
|
2320
3699
|
return c.json({
|
|
2321
|
-
|
|
2322
|
-
|
|
3700
|
+
steered: true,
|
|
3701
|
+
count: result.steered,
|
|
3702
|
+
// Include any still-paused recovered entries so the client's list does
|
|
3703
|
+
// not lose their chips when a live entry is steered.
|
|
3704
|
+
deferredMessages: sessionQueueSummaries(deps, sessionId, process),
|
|
2323
3705
|
});
|
|
2324
3706
|
});
|
|
2325
3707
|
// PUT /api/sessions/:sessionId/mode - Update permission mode without sending a message
|
|
@@ -2387,32 +3769,65 @@ export function createSessionsRoutes(deps) {
|
|
|
2387
3769
|
const requestBefore = process.getPendingInputRequest();
|
|
2388
3770
|
const permissionModeBefore = process.permissionMode;
|
|
2389
3771
|
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
|
-
|
|
3772
|
+
if (isApprovalAuditLogEnabled(deps)) {
|
|
3773
|
+
try {
|
|
3774
|
+
await appendApprovalAuditLog(deps.dataDir, {
|
|
3775
|
+
timestamp: new Date().toISOString(),
|
|
3776
|
+
sessionId,
|
|
3777
|
+
processId: process.id,
|
|
3778
|
+
provider: process.provider,
|
|
3779
|
+
requestId: body.requestId,
|
|
3780
|
+
request: requestBefore,
|
|
3781
|
+
response: body.response,
|
|
3782
|
+
normalizedResponse,
|
|
3783
|
+
answers: body.answers,
|
|
3784
|
+
feedback: body.feedback,
|
|
3785
|
+
accepted: false,
|
|
3786
|
+
failure: "No pending input request",
|
|
3787
|
+
permissionModeBefore,
|
|
3788
|
+
permissionModeAfter: process.permissionMode,
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
catch (error) {
|
|
3792
|
+
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
3793
|
+
}
|
|
2410
3794
|
}
|
|
2411
3795
|
return c.json({ error: "No pending input request" }, 400);
|
|
2412
3796
|
}
|
|
2413
3797
|
// Call respondToInput which resolves the SDK's canUseTool promise
|
|
2414
3798
|
const accepted = process.respondToInput(body.requestId, normalizedResponse, body.answers, body.feedback);
|
|
2415
3799
|
if (!accepted) {
|
|
3800
|
+
if (isApprovalAuditLogEnabled(deps)) {
|
|
3801
|
+
try {
|
|
3802
|
+
await appendApprovalAuditLog(deps.dataDir, {
|
|
3803
|
+
timestamp: new Date().toISOString(),
|
|
3804
|
+
sessionId,
|
|
3805
|
+
processId: process.id,
|
|
3806
|
+
provider: process.provider,
|
|
3807
|
+
requestId: body.requestId,
|
|
3808
|
+
request: requestBefore,
|
|
3809
|
+
response: body.response,
|
|
3810
|
+
normalizedResponse,
|
|
3811
|
+
answers: body.answers,
|
|
3812
|
+
feedback: body.feedback,
|
|
3813
|
+
accepted: false,
|
|
3814
|
+
failure: "Invalid request ID or no pending request",
|
|
3815
|
+
permissionModeBefore,
|
|
3816
|
+
permissionModeAfter: process.permissionMode,
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
catch (error) {
|
|
3820
|
+
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
return c.json({ error: "Invalid request ID or no pending request" }, 400);
|
|
3824
|
+
}
|
|
3825
|
+
// If approve_accept_edits, switch the permission mode
|
|
3826
|
+
if (isApproveAcceptEdits) {
|
|
3827
|
+
process.setPermissionMode("acceptEdits");
|
|
3828
|
+
}
|
|
3829
|
+
const pendingInputRequest = process.getPendingInputRequest();
|
|
3830
|
+
if (isApprovalAuditLogEnabled(deps)) {
|
|
2416
3831
|
try {
|
|
2417
3832
|
await appendApprovalAuditLog(deps.dataDir, {
|
|
2418
3833
|
timestamp: new Date().toISOString(),
|
|
@@ -2425,8 +3840,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2425
3840
|
normalizedResponse,
|
|
2426
3841
|
answers: body.answers,
|
|
2427
3842
|
feedback: body.feedback,
|
|
2428
|
-
accepted:
|
|
2429
|
-
failure: "Invalid request ID or no pending request",
|
|
3843
|
+
accepted: true,
|
|
2430
3844
|
permissionModeBefore,
|
|
2431
3845
|
permissionModeAfter: process.permissionMode,
|
|
2432
3846
|
});
|
|
@@ -2434,32 +3848,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2434
3848
|
catch (error) {
|
|
2435
3849
|
console.warn("[approval-audit] Failed to append audit log:", error);
|
|
2436
3850
|
}
|
|
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
3851
|
}
|
|
2464
3852
|
return c.json({ accepted: true, pendingInputRequest });
|
|
2465
3853
|
});
|
|
@@ -2534,101 +3922,27 @@ export function createSessionsRoutes(deps) {
|
|
|
2534
3922
|
catch {
|
|
2535
3923
|
return c.json({ error: "Invalid JSON body" }, 400);
|
|
2536
3924
|
}
|
|
2537
|
-
|
|
2538
|
-
if (
|
|
2539
|
-
|
|
2540
|
-
body.starred === undefined &&
|
|
2541
|
-
body.parentSessionId === undefined &&
|
|
2542
|
-
body.heartbeatTurnsEnabled === undefined &&
|
|
2543
|
-
body.heartbeatTurnsAfterMinutes === undefined &&
|
|
2544
|
-
body.heartbeatTurnText === undefined &&
|
|
2545
|
-
body.heartbeatForceAfterMinutes === undefined) {
|
|
2546
|
-
return c.json({
|
|
2547
|
-
error: "At least one session metadata field must be provided",
|
|
2548
|
-
}, 400);
|
|
2549
|
-
}
|
|
2550
|
-
let heartbeatForceAfterMinutes;
|
|
2551
|
-
if (body.heartbeatForceAfterMinutes !== undefined) {
|
|
2552
|
-
if (body.heartbeatForceAfterMinutes === null ||
|
|
2553
|
-
body.heartbeatForceAfterMinutes === 0) {
|
|
2554
|
-
heartbeatForceAfterMinutes = null;
|
|
2555
|
-
}
|
|
2556
|
-
else if (typeof body.heartbeatForceAfterMinutes === "number" &&
|
|
2557
|
-
Number.isInteger(body.heartbeatForceAfterMinutes) &&
|
|
2558
|
-
body.heartbeatForceAfterMinutes >= 1 &&
|
|
2559
|
-
body.heartbeatForceAfterMinutes <= 1440) {
|
|
2560
|
-
heartbeatForceAfterMinutes = body.heartbeatForceAfterMinutes;
|
|
2561
|
-
}
|
|
2562
|
-
else {
|
|
2563
|
-
return c.json({
|
|
2564
|
-
error: "heartbeatForceAfterMinutes must be null or an integer between 1 and 1440",
|
|
2565
|
-
}, 400);
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
let heartbeatTurnsAfterMinutes;
|
|
2569
|
-
if (body.heartbeatTurnsAfterMinutes !== undefined) {
|
|
2570
|
-
if (body.heartbeatTurnsAfterMinutes === null ||
|
|
2571
|
-
body.heartbeatTurnsAfterMinutes === 0) {
|
|
2572
|
-
heartbeatTurnsAfterMinutes = null;
|
|
2573
|
-
}
|
|
2574
|
-
else if (typeof body.heartbeatTurnsAfterMinutes === "number" &&
|
|
2575
|
-
Number.isInteger(body.heartbeatTurnsAfterMinutes) &&
|
|
2576
|
-
body.heartbeatTurnsAfterMinutes >= 1 &&
|
|
2577
|
-
body.heartbeatTurnsAfterMinutes <= 1440) {
|
|
2578
|
-
heartbeatTurnsAfterMinutes = body.heartbeatTurnsAfterMinutes;
|
|
2579
|
-
}
|
|
2580
|
-
else {
|
|
2581
|
-
return c.json({
|
|
2582
|
-
error: "heartbeatTurnsAfterMinutes must be null or an integer between 1 and 1440",
|
|
2583
|
-
}, 400);
|
|
2584
|
-
}
|
|
3925
|
+
const parsed = parseSessionMetadataPatch(body);
|
|
3926
|
+
if (!parsed.ok) {
|
|
3927
|
+
return c.json({ error: parsed.error }, parsed.status);
|
|
2585
3928
|
}
|
|
2586
|
-
const
|
|
2587
|
-
|
|
2588
|
-
: body.heartbeatTurnText === null || body.heartbeatTurnText === ""
|
|
2589
|
-
? null
|
|
2590
|
-
: typeof body.heartbeatTurnText === "string"
|
|
2591
|
-
? body.heartbeatTurnText.slice(0, 200)
|
|
2592
|
-
: null;
|
|
2593
|
-
if (body.heartbeatTurnText !== undefined &&
|
|
2594
|
-
body.heartbeatTurnText !== null &&
|
|
2595
|
-
body.heartbeatTurnText !== "" &&
|
|
2596
|
-
typeof body.heartbeatTurnText !== "string") {
|
|
2597
|
-
return c.json({ error: "heartbeatTurnText must be a string or null" }, 400);
|
|
2598
|
-
}
|
|
2599
|
-
if (body.parentSessionId !== undefined &&
|
|
2600
|
-
body.parentSessionId !== null &&
|
|
2601
|
-
typeof body.parentSessionId !== "string") {
|
|
2602
|
-
return c.json({ error: "parentSessionId must be a string or null" }, 400);
|
|
2603
|
-
}
|
|
2604
|
-
const parentSessionId = body.parentSessionId === undefined
|
|
2605
|
-
? undefined
|
|
2606
|
-
: typeof body.parentSessionId === "string"
|
|
2607
|
-
? body.parentSessionId.trim() || null
|
|
2608
|
-
: null;
|
|
2609
|
-
await deps.sessionMetadataService.updateMetadata(sessionId, {
|
|
2610
|
-
title: body.title,
|
|
2611
|
-
archived: body.archived,
|
|
2612
|
-
starred: body.starred,
|
|
2613
|
-
parentSessionId,
|
|
2614
|
-
heartbeatTurnsEnabled: body.heartbeatTurnsEnabled,
|
|
2615
|
-
heartbeatTurnsAfterMinutes,
|
|
2616
|
-
heartbeatTurnText,
|
|
2617
|
-
heartbeatForceAfterMinutes,
|
|
2618
|
-
});
|
|
3929
|
+
const { patch } = parsed;
|
|
3930
|
+
await deps.sessionMetadataService.updateMetadata(sessionId, patch);
|
|
2619
3931
|
// Emit SSE event so sidebar and other clients can update
|
|
2620
3932
|
if (deps.eventBus) {
|
|
2621
3933
|
deps.eventBus.emit({
|
|
2622
3934
|
type: "session-metadata-changed",
|
|
2623
3935
|
sessionId,
|
|
2624
|
-
title:
|
|
2625
|
-
archived:
|
|
2626
|
-
starred:
|
|
2627
|
-
parentSessionId,
|
|
2628
|
-
heartbeatTurnsEnabled:
|
|
2629
|
-
heartbeatTurnsAfterMinutes,
|
|
2630
|
-
heartbeatTurnText,
|
|
2631
|
-
heartbeatForceAfterMinutes,
|
|
3936
|
+
title: patch.title,
|
|
3937
|
+
archived: patch.archived,
|
|
3938
|
+
starred: patch.starred,
|
|
3939
|
+
parentSessionId: patch.parentSessionId,
|
|
3940
|
+
heartbeatTurnsEnabled: patch.heartbeatTurnsEnabled,
|
|
3941
|
+
heartbeatTurnsAfterMinutes: patch.heartbeatTurnsAfterMinutes,
|
|
3942
|
+
heartbeatTurnText: patch.heartbeatTurnText,
|
|
3943
|
+
heartbeatForceAfterMinutes: patch.heartbeatForceAfterMinutes,
|
|
3944
|
+
promptSuggestionMode: patch.promptSuggestionMode ?? undefined,
|
|
3945
|
+
recapAfterSeconds: patch.recapAfterSeconds ?? undefined,
|
|
2632
3946
|
timestamp: new Date().toISOString(),
|
|
2633
3947
|
});
|
|
2634
3948
|
}
|
|
@@ -2674,7 +3988,7 @@ export function createSessionsRoutes(deps) {
|
|
|
2674
3988
|
}
|
|
2675
3989
|
// Get original session to extract title for the clone
|
|
2676
3990
|
const reader = deps.readerFactory(project);
|
|
2677
|
-
let originalSession = await reader.getSessionSummary(sessionId, projectId);
|
|
3991
|
+
let originalSession = await reader.getSessionSummary(sessionId, projectId, { readMode: "head" });
|
|
2678
3992
|
let cloneProvider = project.provider;
|
|
2679
3993
|
let result;
|
|
2680
3994
|
const shouldCloneFromCodex = isCodexProviderName(body.provider) ||
|
|
@@ -2691,7 +4005,9 @@ export function createSessionsRoutes(deps) {
|
|
|
2691
4005
|
}
|
|
2692
4006
|
originalSession =
|
|
2693
4007
|
originalSession ??
|
|
2694
|
-
(await codexReader.getSessionSummary(sessionId, projectId
|
|
4008
|
+
(await codexReader.getSessionSummary(sessionId, projectId, {
|
|
4009
|
+
readMode: "head",
|
|
4010
|
+
})) ??
|
|
2695
4011
|
null;
|
|
2696
4012
|
cloneProvider =
|
|
2697
4013
|
originalSession?.provider ??
|
|
@@ -2734,36 +4050,6 @@ export function createSessionsRoutes(deps) {
|
|
|
2734
4050
|
return c.json({ error: message }, 500);
|
|
2735
4051
|
}
|
|
2736
4052
|
});
|
|
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
4053
|
return routes;
|
|
2768
4054
|
}
|
|
2769
4055
|
//# sourceMappingURL=sessions.js.map
|