yepanywhere 0.6.0 → 0.6.2

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.
Files changed (225) hide show
  1. package/bundled/@yep-anywhere/shared/dist/app-types.d.ts +25 -0
  2. package/bundled/@yep-anywhere/shared/dist/app-types.d.ts.map +1 -1
  3. package/bundled/@yep-anywhere/shared/dist/app-types.js.map +1 -1
  4. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts +179 -14
  5. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.d.ts.map +1 -1
  6. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js +89 -0
  7. package/bundled/@yep-anywhere/shared/dist/codex-schema/session.js.map +1 -1
  8. package/bundled/@yep-anywhere/shared/dist/codex-tool-correlation.d.ts +25 -0
  9. package/bundled/@yep-anywhere/shared/dist/codex-tool-correlation.d.ts.map +1 -0
  10. package/bundled/@yep-anywhere/shared/dist/codex-tool-correlation.js +52 -0
  11. package/bundled/@yep-anywhere/shared/dist/codex-tool-correlation.js.map +1 -0
  12. package/bundled/@yep-anywhere/shared/dist/codex-web-run.d.ts +46 -0
  13. package/bundled/@yep-anywhere/shared/dist/codex-web-run.d.ts.map +1 -0
  14. package/bundled/@yep-anywhere/shared/dist/codex-web-run.js +14 -0
  15. package/bundled/@yep-anywhere/shared/dist/codex-web-run.js.map +1 -0
  16. package/bundled/@yep-anywhere/shared/dist/git-status.d.ts +29 -6
  17. package/bundled/@yep-anywhere/shared/dist/git-status.d.ts.map +1 -1
  18. package/bundled/@yep-anywhere/shared/dist/git-status.js +1 -6
  19. package/bundled/@yep-anywhere/shared/dist/git-status.js.map +1 -1
  20. package/bundled/@yep-anywhere/shared/dist/index.d.ts +7 -2
  21. package/bundled/@yep-anywhere/shared/dist/index.d.ts.map +1 -1
  22. package/bundled/@yep-anywhere/shared/dist/index.js +2 -0
  23. package/bundled/@yep-anywhere/shared/dist/index.js.map +1 -1
  24. package/bundled/@yep-anywhere/shared/dist/server-capabilities.d.ts +248 -0
  25. package/bundled/@yep-anywhere/shared/dist/server-capabilities.d.ts.map +1 -0
  26. package/bundled/@yep-anywhere/shared/dist/server-capabilities.js +251 -0
  27. package/bundled/@yep-anywhere/shared/dist/server-capabilities.js.map +1 -0
  28. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.d.ts +26 -0
  29. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.d.ts.map +1 -0
  30. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.js +9 -0
  31. package/bundled/@yep-anywhere/shared/dist/tool-display-actions.js.map +1 -0
  32. package/bundled/@yep-anywhere/shared/package.json +1 -1
  33. package/client-dist/assets/de-BNhVdIq4.js +6 -0
  34. package/client-dist/assets/es-Csn9n24J.js +6 -0
  35. package/client-dist/assets/fr-DKx4tSpo.js +6 -0
  36. package/client-dist/assets/index-1y2h7U7U.js +582 -0
  37. package/client-dist/assets/index-Cn_ZDP6u.css +1 -0
  38. package/client-dist/assets/ja-DAkhkwQZ.js +6 -0
  39. package/client-dist/assets/zh-CN-B7lY0cc6.js +6 -0
  40. package/client-dist/index.html +2 -2
  41. package/dist/app.d.ts.map +1 -1
  42. package/dist/app.js.map +1 -1
  43. package/dist/codex/codeModeExec.d.ts +30 -0
  44. package/dist/codex/codeModeExec.d.ts.map +1 -0
  45. package/dist/codex/codeModeExec.js +419 -0
  46. package/dist/codex/codeModeExec.js.map +1 -0
  47. package/dist/codex/displayActions.d.ts +48 -0
  48. package/dist/codex/displayActions.d.ts.map +1 -0
  49. package/dist/codex/displayActions.js +674 -0
  50. package/dist/codex/displayActions.js.map +1 -0
  51. package/dist/codex/normalization.d.ts +10 -7
  52. package/dist/codex/normalization.d.ts.map +1 -1
  53. package/dist/codex/normalization.js +179 -502
  54. package/dist/codex/normalization.js.map +1 -1
  55. package/dist/codex/webRun.d.ts +42 -0
  56. package/dist/codex/webRun.d.ts.map +1 -0
  57. package/dist/codex/webRun.js +199 -0
  58. package/dist/codex/webRun.js.map +1 -0
  59. package/dist/indexes/SessionIndexService.d.ts +1 -1
  60. package/dist/indexes/SessionIndexService.d.ts.map +1 -1
  61. package/dist/indexes/SessionIndexService.js +16 -4
  62. package/dist/indexes/SessionIndexService.js.map +1 -1
  63. package/dist/routes/git-status.d.ts.map +1 -1
  64. package/dist/routes/git-status.js +70 -0
  65. package/dist/routes/git-status.js.map +1 -1
  66. package/dist/routes/project-queue.d.ts.map +1 -1
  67. package/dist/routes/project-queue.js.map +1 -1
  68. package/dist/routes/public-shares.d.ts +1 -1
  69. package/dist/routes/public-shares.d.ts.map +1 -1
  70. package/dist/routes/public-shares.js +4 -6
  71. package/dist/routes/public-shares.js.map +1 -1
  72. package/dist/routes/session-metadata-patch.d.ts +24 -0
  73. package/dist/routes/session-metadata-patch.d.ts.map +1 -0
  74. package/dist/routes/session-metadata-patch.js +119 -0
  75. package/dist/routes/session-metadata-patch.js.map +1 -0
  76. package/dist/routes/session-project-routing.d.ts +25 -0
  77. package/dist/routes/session-project-routing.d.ts.map +1 -0
  78. package/dist/routes/session-project-routing.js +32 -0
  79. package/dist/routes/session-project-routing.js.map +1 -0
  80. package/dist/routes/session-provider-resolution.d.ts +28 -0
  81. package/dist/routes/session-provider-resolution.d.ts.map +1 -0
  82. package/dist/routes/session-provider-resolution.js +22 -0
  83. package/dist/routes/session-provider-resolution.js.map +1 -0
  84. package/dist/routes/session-queue-summaries.d.ts +21 -0
  85. package/dist/routes/session-queue-summaries.d.ts.map +1 -0
  86. package/dist/routes/session-queue-summaries.js +79 -0
  87. package/dist/routes/session-queue-summaries.js.map +1 -0
  88. package/dist/routes/session-recovered-queue.d.ts +43 -0
  89. package/dist/routes/session-recovered-queue.d.ts.map +1 -0
  90. package/dist/routes/session-recovered-queue.js +132 -0
  91. package/dist/routes/session-recovered-queue.js.map +1 -0
  92. package/dist/routes/session-thinking-options.d.ts +12 -0
  93. package/dist/routes/session-thinking-options.d.ts.map +1 -0
  94. package/dist/routes/session-thinking-options.js +7 -0
  95. package/dist/routes/session-thinking-options.js.map +1 -0
  96. package/dist/routes/sessions.d.ts.map +1 -1
  97. package/dist/routes/sessions.js +110 -409
  98. package/dist/routes/sessions.js.map +1 -1
  99. package/dist/routes/settings-parsers.d.ts +37 -0
  100. package/dist/routes/settings-parsers.d.ts.map +1 -0
  101. package/dist/routes/settings-parsers.js +896 -0
  102. package/dist/routes/settings-parsers.js.map +1 -0
  103. package/dist/routes/settings.d.ts.map +1 -1
  104. package/dist/routes/settings.js +4 -894
  105. package/dist/routes/settings.js.map +1 -1
  106. package/dist/routes/version.d.ts +0 -2
  107. package/dist/routes/version.d.ts.map +1 -1
  108. package/dist/routes/version.js +8 -10
  109. package/dist/routes/version.js.map +1 -1
  110. package/dist/sdk/cli-detection.d.ts +15 -1
  111. package/dist/sdk/cli-detection.d.ts.map +1 -1
  112. package/dist/sdk/cli-detection.js +159 -28
  113. package/dist/sdk/cli-detection.js.map +1 -1
  114. package/dist/sdk/messageQueue.d.ts +5 -0
  115. package/dist/sdk/messageQueue.d.ts.map +1 -1
  116. package/dist/sdk/messageQueue.js +21 -0
  117. package/dist/sdk/messageQueue.js.map +1 -1
  118. package/dist/sdk/providers/codex-model-catalog.d.ts +34 -0
  119. package/dist/sdk/providers/codex-model-catalog.d.ts.map +1 -0
  120. package/dist/sdk/providers/codex-model-catalog.js +378 -0
  121. package/dist/sdk/providers/codex-model-catalog.js.map +1 -0
  122. package/dist/sdk/providers/codex-notification-guards.d.ts +15 -0
  123. package/dist/sdk/providers/codex-notification-guards.d.ts.map +1 -0
  124. package/dist/sdk/providers/codex-notification-guards.js +159 -0
  125. package/dist/sdk/providers/codex-notification-guards.js.map +1 -0
  126. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.d.ts +9 -0
  127. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.d.ts.map +1 -0
  128. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.js +3 -0
  129. package/dist/sdk/providers/codex-protocol/generated/ImageGenerationItem.js.map +1 -0
  130. package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.d.ts +5 -5
  131. package/dist/sdk/providers/codex-protocol/generated/MultiAgentMode.d.ts.map +1 -1
  132. package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts +1 -0
  133. package/dist/sdk/providers/codex-protocol/generated/ResponseItem.d.ts.map +1 -1
  134. package/dist/sdk/providers/codex-protocol/generated/ThreadId.d.ts +5 -0
  135. package/dist/sdk/providers/codex-protocol/generated/ThreadId.d.ts.map +1 -1
  136. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.d.ts +7 -0
  137. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.d.ts.map +1 -0
  138. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.js +3 -0
  139. package/dist/sdk/providers/codex-protocol/generated/WebSearchItem.js.map +1 -0
  140. package/dist/sdk/providers/codex-protocol/generated/v2/AskForApproval.d.ts +1 -1
  141. package/dist/sdk/providers/codex-protocol/generated/v2/AskForApproval.d.ts.map +1 -1
  142. package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts +3 -0
  143. package/dist/sdk/providers/codex-protocol/generated/v2/CapabilityRootLocation.d.ts.map +1 -1
  144. package/dist/sdk/providers/codex-protocol/generated/v2/CodexErrorInfo.d.ts +1 -1
  145. package/dist/sdk/providers/codex-protocol/generated/v2/CodexErrorInfo.d.ts.map +1 -1
  146. package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.d.ts +3 -0
  147. package/dist/sdk/providers/codex-protocol/generated/v2/McpToolCallAppContext.d.ts.map +1 -1
  148. package/dist/sdk/providers/codex-protocol/generated/v2/Thread.d.ts +13 -0
  149. package/dist/sdk/providers/codex-protocol/generated/v2/Thread.d.ts.map +1 -1
  150. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.d.ts +5 -0
  151. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.d.ts.map +1 -0
  152. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.js +3 -0
  153. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadExtra.js.map +1 -0
  154. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts +7 -0
  155. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadForkParams.d.ts.map +1 -1
  156. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.d.ts +2 -0
  157. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.d.ts.map +1 -0
  158. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.js +3 -0
  159. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadHistoryMode.js.map +1 -0
  160. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts +5 -13
  161. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadItem.d.ts.map +1 -1
  162. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.d.ts +3 -0
  163. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadRollbackParams.d.ts.map +1 -1
  164. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartParams.d.ts +10 -0
  165. package/dist/sdk/providers/codex-protocol/generated/v2/ThreadStartParams.d.ts.map +1 -1
  166. package/dist/sdk/providers/codex-protocol/generated/v2/Turn.d.ts +3 -0
  167. package/dist/sdk/providers/codex-protocol/generated/v2/Turn.d.ts.map +1 -1
  168. package/dist/sdk/providers/codex-summary-helpers.d.ts +41 -0
  169. package/dist/sdk/providers/codex-summary-helpers.d.ts.map +1 -0
  170. package/dist/sdk/providers/codex-summary-helpers.js +207 -0
  171. package/dist/sdk/providers/codex-summary-helpers.js.map +1 -0
  172. package/dist/sdk/providers/codex.d.ts +0 -25
  173. package/dist/sdk/providers/codex.d.ts.map +1 -1
  174. package/dist/sdk/providers/codex.js +160 -703
  175. package/dist/sdk/providers/codex.js.map +1 -1
  176. package/dist/sdk/providers/opencode-models.d.ts +17 -0
  177. package/dist/sdk/providers/opencode-models.d.ts.map +1 -0
  178. package/dist/sdk/providers/opencode-models.js +83 -0
  179. package/dist/sdk/providers/opencode-models.js.map +1 -0
  180. package/dist/sdk/providers/opencode.d.ts +1 -9
  181. package/dist/sdk/providers/opencode.d.ts.map +1 -1
  182. package/dist/sdk/providers/opencode.js +1 -82
  183. package/dist/sdk/providers/opencode.js.map +1 -1
  184. package/dist/sdk/types.d.ts +3 -1
  185. package/dist/sdk/types.d.ts.map +1 -1
  186. package/dist/services/ProjectQueueService.d.ts.map +1 -1
  187. package/dist/services/ProjectQueueService.js +3 -1
  188. package/dist/services/ProjectQueueService.js.map +1 -1
  189. package/dist/sessions/claude-summary.d.ts.map +1 -1
  190. package/dist/sessions/claude-summary.js +13 -1
  191. package/dist/sessions/claude-summary.js.map +1 -1
  192. package/dist/sessions/codex-reader.d.ts +0 -2
  193. package/dist/sessions/codex-reader.d.ts.map +1 -1
  194. package/dist/sessions/codex-reader.js +46 -84
  195. package/dist/sessions/codex-reader.js.map +1 -1
  196. package/dist/sessions/codex-user-turn-provenance.d.ts +40 -0
  197. package/dist/sessions/codex-user-turn-provenance.d.ts.map +1 -0
  198. package/dist/sessions/codex-user-turn-provenance.js +165 -0
  199. package/dist/sessions/codex-user-turn-provenance.js.map +1 -0
  200. package/dist/sessions/normalization.d.ts.map +1 -1
  201. package/dist/sessions/normalization.js +80 -51
  202. package/dist/sessions/normalization.js.map +1 -1
  203. package/dist/sessions/pagination.d.ts +6 -0
  204. package/dist/sessions/pagination.d.ts.map +1 -1
  205. package/dist/sessions/pagination.js +42 -2
  206. package/dist/sessions/pagination.js.map +1 -1
  207. package/dist/subscriptions.d.ts.map +1 -1
  208. package/dist/subscriptions.js +1 -0
  209. package/dist/subscriptions.js.map +1 -1
  210. package/dist/supervisor/Process.d.ts +12 -0
  211. package/dist/supervisor/Process.d.ts.map +1 -1
  212. package/dist/supervisor/Process.js +178 -22
  213. package/dist/supervisor/Process.js.map +1 -1
  214. package/dist/supervisor/Supervisor.d.ts +5 -1
  215. package/dist/supervisor/Supervisor.d.ts.map +1 -1
  216. package/dist/supervisor/Supervisor.js +47 -0
  217. package/dist/supervisor/Supervisor.js.map +1 -1
  218. package/package.json +2 -2
  219. package/client-dist/assets/de-CHMDbVD5.js +0 -6
  220. package/client-dist/assets/es-CK8MPF_C.js +0 -6
  221. package/client-dist/assets/fr-49PXQiUL.js +0 -6
  222. package/client-dist/assets/index-B-09fVut.js +0 -557
  223. package/client-dist/assets/index-uJnfvD1f.css +0 -1
  224. package/client-dist/assets/ja-CrCGPmoH.js +0 -6
  225. package/client-dist/assets/zh-CN-AFJxv5ke.js +0 -6
@@ -1,4 +1,4 @@
1
- import { ALL_PERMISSION_MODES, PROMPT_SUGGESTION_MODES, buildEffectiveAgentContext, clampRecapAfterSeconds, getModelContextWindow, isUrlProjectId, isWorkstreamId, mainWorkstreamId, thinkingOptionToConfig, truncateSessionTitle, } from "../../bundled/@yep-anywhere/shared/dist/index.js";
1
+ import { ALL_PERMISSION_MODES, buildEffectiveAgentContext, getModelContextWindow, isUrlProjectId, isWorkstreamId, mainWorkstreamId, truncateSessionTitle, } from "../../bundled/@yep-anywhere/shared/dist/index.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir } from "node:fs/promises";
4
4
  import { performance } from "node:perf_hooks";
@@ -6,6 +6,7 @@ import { Hono } from "hono";
6
6
  import { augmentTextBlocks } from "../augments/markdown-augments.js";
7
7
  import { getLogger } from "../logging/logger.js";
8
8
  import { DETACHED_PROJECT_PATH, encodeProjectId } from "../projects/paths.js";
9
+ import { resolveCanonicalProjectRedirect } from "./session-project-routing.js";
9
10
  import { ensureRemoteDirectory } from "../sdk/remote-spawn.js";
10
11
  import { parseSlashCommandSubmission } from "../sdk/slashCommandEmulation.js";
11
12
  import { getProjectDirFromCwd, syncSessions } from "../sdk/session-sync.js";
@@ -16,7 +17,7 @@ import { GrokSessionReader } from "../sessions/grok-reader.js";
16
17
  import { extractLastAgentExcerpt } from "../sessions/agent-excerpt.js";
17
18
  import { normalizeSession } from "../sessions/normalization.js";
18
19
  import { applyRecapOverlayToSession, applyRecapOverlayToSummary, hasEquivalentRecapMessage, hasUnreadProviderContent, latestRecapMessage, mergeRecapMessages, } from "../sessions/recap-overlays.js";
19
- import { sliceAfterMessageIdWithMatch, sliceAtCompactBoundaries, sliceAtUserTurnBoundary, } from "../sessions/pagination.js";
20
+ import { sliceAfterMessageIdWithMatch, sliceAtCompactAndUserTurnBoundaries, sliceAtCompactBoundaries, sliceAtUserTurnBoundary, } from "../sessions/pagination.js";
20
21
  import { augmentTaskListSnapshots, pruneTaskListSnapshotsToLatest, } from "../augments/task-list-augments.js";
21
22
  import { augmentEditToolUses, augmentPersistedSessionMessages, } from "../sessions/persisted-augments.js";
22
23
  import { findSessionSummaryAcrossProviders, getSessionSources, } from "../sessions/provider-resolution.js";
@@ -26,6 +27,11 @@ import { ResumeCompactionError } from "../supervisor/Supervisor.js";
26
27
  import { buildUserMessageMetadata, normalizeOptionalServiceTier, parseHelperSettings, parseOptionalExecutor, parseOptionalResumeMode, } from "./session-request-helpers.js";
27
28
  import { resolveCompactPercent, resolveCompactWindow, } from "./session-compact-thresholds.js";
28
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";
29
35
  const SESSION_DETAIL_SLOW_LOG_MS = 250;
30
36
  const DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS = 2;
31
37
  const LARGE_FULL_HISTORY_MESSAGE_THRESHOLD = 1000;
@@ -40,9 +46,6 @@ async function getSessionSlashCommands(process, provider) {
40
46
  function roundedMs(value) {
41
47
  return Math.round(value * 10) / 10;
42
48
  }
43
- function isClaudeSdkProviderName(provider) {
44
- return provider === "claude" || provider === "claude-ollama";
45
- }
46
49
  /**
47
50
  * Type guard to check if a result is a QueuedResponse
48
51
  */
@@ -55,111 +58,15 @@ function isQueuedResponse(result) {
55
58
  function isQueueFullResponse(result) {
56
59
  return "error" in result && result.error === "queue_full";
57
60
  }
58
- function isCodexProviderName(provider) {
59
- return provider === "codex" || provider === "codex-oss";
60
- }
61
61
  const AUTO_COMPACT_CONTEXT_PERCENT_THRESHOLD = 85;
62
62
  const AUTO_COMPACT_MODEL_PREFIXES = ["gpt-5.3-codex-spark"];
63
63
  const AUTO_COMPACT_TARGET_PROVIDERS = new Set([
64
64
  "codex",
65
65
  "codex-oss",
66
66
  ]);
67
- function providerResolutionDeps(deps) {
68
- return {
69
- readerFactory: deps.readerFactory,
70
- sessionIndexService: deps.sessionIndexService,
71
- codexSessionsDir: deps.codexSessionsDir,
72
- codexReaderFactory: deps.codexReaderFactory,
73
- geminiSessionsDir: deps.geminiSessionsDir,
74
- geminiReaderFactory: deps.geminiReaderFactory,
75
- geminiHashToCwd: deps.geminiScanner?.getHashToCwd(),
76
- grokSessionsDir: deps.grokSessionsDir,
77
- grokReaderFactory: deps.grokReaderFactory,
78
- piSessionsDir: deps.piSessionsDir,
79
- piReaderFactory: deps.piReaderFactory,
80
- };
81
- }
82
- function persistedPatientQueueSummary(item) {
83
- const attachmentCount = (item.message.attachments?.length ?? 0) +
84
- (item.message.images?.length ?? 0) +
85
- (item.message.documents?.length ?? 0);
86
- const tempId = item.message.tempId ?? item.source?.tempId;
87
- return {
88
- id: item.id,
89
- ...(tempId ? { tempId } : {}),
90
- content: item.message.text,
91
- timestamp: item.queuedAt,
92
- ...(item.message.attachments?.length
93
- ? { attachments: item.message.attachments }
94
- : {}),
95
- ...(attachmentCount > 0 ? { attachmentCount } : {}),
96
- ...(item.message.metadata ? { metadata: item.message.metadata } : {}),
97
- kind: "patient",
98
- status: item.status === "paused-after-restart" ? item.status : "queued",
99
- sessionId: item.sessionId,
100
- projectId: item.projectId,
101
- queuedAt: item.queuedAt,
102
- createdAt: item.createdAt,
103
- updatedAt: item.updatedAt,
104
- };
105
- }
106
67
  function isApprovalAuditLogEnabled(deps) {
107
68
  return (deps.serverSettingsService?.getSetting("approvalAuditLogEnabled") === true);
108
69
  }
109
- function recoveredPatientQueueSummaries(deps, sessionId) {
110
- return recoveredPatientQueueItems(deps, sessionId).map(persistedPatientQueueSummary);
111
- }
112
- function recoveredPatientQueueItems(deps, sessionId) {
113
- if (!deps.sessionQueuePersistenceService) {
114
- return [];
115
- }
116
- return deps.sessionQueuePersistenceService
117
- .listSession(sessionId)
118
- .filter((item) => item.kind === "patient" && item.status === "paused-after-restart")
119
- .sort((left, right) => left.queuedAt.localeCompare(right.queuedAt));
120
- }
121
- function sessionQueueSummaries(deps, sessionId, process) {
122
- const recovered = recoveredPatientQueueSummaries(deps, sessionId);
123
- if (process && typeof process.getDeferredQueueSummary === "function") {
124
- const messages = [
125
- ...process.getDeferredQueueSummary(),
126
- ...recovered,
127
- ];
128
- return messages.sort((left, right) => (left.queuedAt ?? left.timestamp).localeCompare(right.queuedAt ?? right.timestamp));
129
- }
130
- if (process) {
131
- return recovered;
132
- }
133
- return recovered;
134
- }
135
- function recoveredPatientUserMessage(item) {
136
- const mode = item.message.mode ?? item.mode;
137
- const metadata = {
138
- ...item.message.metadata,
139
- deliveryIntent: "patient",
140
- };
141
- // Live-queue chip actions (cancel, steer) address entries by tempId, so a
142
- // recovered entry that was persisted without one gets a stable fallback.
143
- const tempId = item.message.tempId ?? item.source?.tempId ?? `recovered-${item.id}`;
144
- return {
145
- ...item.message,
146
- tempId,
147
- ...(mode ? { mode } : {}),
148
- metadata,
149
- };
150
- }
151
- /**
152
- * Count live patient queue entries composed after the given recovered entry.
153
- * Resuming or steering the recovered entry would deliver its older content
154
- * behind these newer ones, so callers reject while any exist. Regular
155
- * deferred entries never block recovered work: the regular lane delivers on
156
- * turn boundaries and may pass patient work by design.
157
- */
158
- function livePatientEntriesNewerThan(process, queuedAt) {
159
- const live = process?.getDeferredQueueSummary() ?? [];
160
- return live.filter((entry) => entry.metadata?.deliveryIntent === "patient" &&
161
- entry.timestamp.localeCompare(queuedAt) > 0).length;
162
- }
163
70
  async function resolveSessionReaderForAgentContent({ deps, project, sessionId, projectId, }) {
164
71
  const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
165
72
  const resolved = await findSessionSummaryAcrossProviders(project, sessionId, projectId, providerResolutionDeps(deps), metadataProvider, { readMode: "head" });
@@ -701,6 +608,7 @@ function isCompactSummaryUserMessage(message) {
701
608
  }
702
609
  function isUserAuthoredRequest(message) {
703
610
  return (isHumanUserMessage(message) &&
611
+ message.isSynthetic !== true &&
704
612
  !isCompactSummaryUserMessage(message) &&
705
613
  !isSlashCommandSkillBodyUserMessage(message));
706
614
  }
@@ -1132,30 +1040,43 @@ export function createSessionsRoutes(deps) {
1132
1040
  };
1133
1041
  const resolveSessionProjectRouting = async (requestProjectId, sessionId) => {
1134
1042
  const workingProjectId = deps.sessionMetadataService?.getMetadata(sessionId)?.workingProjectId;
1135
- if (workingProjectId && workingProjectId !== requestProjectId) {
1136
- return { redirectProjectId: workingProjectId };
1137
- }
1138
1043
  const activeProcess = deps.supervisor.getProcessForSession(sessionId);
1139
- if (activeProcess &&
1140
- typeof activeProcess.projectId === "string" &&
1141
- isUrlProjectId(activeProcess.projectId) &&
1142
- activeProcess.projectId !== requestProjectId) {
1143
- return { redirectProjectId: activeProcess.projectId };
1044
+ const redirectProjectId = resolveCanonicalProjectRedirect({
1045
+ requestProjectId,
1046
+ workingProjectId,
1047
+ activeProcessProjectId: typeof activeProcess?.projectId === "string"
1048
+ ? activeProcess.projectId
1049
+ : undefined,
1050
+ });
1051
+ if (redirectProjectId) {
1052
+ return { redirectProjectId };
1144
1053
  }
1145
1054
  const workingProject = await deps.scanner.getOrCreateProject(requestProjectId);
1146
1055
  if (!workingProject) {
1147
1056
  return { error: "Project not found", status: 404 };
1148
1057
  }
1149
1058
  const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
1150
- const transcriptProjectId = metadata?.workingProjectId === requestProjectId &&
1151
- metadata.transcriptProjectId
1059
+ const overlayTranscriptProjectId = metadata?.workingProjectId === requestProjectId &&
1060
+ metadata.transcriptProjectId &&
1061
+ metadata.transcriptProjectId !== requestProjectId
1152
1062
  ? metadata.transcriptProjectId
1153
- : requestProjectId;
1154
- const transcriptProject = transcriptProjectId === requestProjectId
1155
- ? workingProject
1156
- : await deps.scanner.getOrCreateProject(transcriptProjectId);
1157
- if (!transcriptProject) {
1158
- return { error: "Transcript project not found", status: 404 };
1063
+ : undefined;
1064
+ // Default: the transcript lives under the working (request) project.
1065
+ let transcriptProjectId = requestProjectId;
1066
+ let transcriptProject = workingProject;
1067
+ // Overlay case (session "moved" to a different working project): the
1068
+ // transcript may live under a distinct project. Trust the recorded pointer
1069
+ // only if the transcript is actually findable there — a stale/wrong pointer
1070
+ // must not permanently brick viewing, so fall back to the working project.
1071
+ if (overlayTranscriptProjectId) {
1072
+ const overlayProject = await deps.scanner.getOrCreateProject(overlayTranscriptProjectId);
1073
+ const overlayHasTranscript = overlayProject
1074
+ ? Boolean(await findSessionSummaryAcrossProviders(overlayProject, sessionId, overlayTranscriptProjectId, providerResolutionDeps(deps), deps.sessionMetadataService?.getProvider(sessionId), { readMode: "head" }))
1075
+ : false;
1076
+ if (overlayProject && overlayHasTranscript) {
1077
+ transcriptProjectId = overlayTranscriptProjectId;
1078
+ transcriptProject = overlayProject;
1079
+ }
1159
1080
  }
1160
1081
  return {
1161
1082
  transcriptProject,
@@ -1425,7 +1346,9 @@ export function createSessionsRoutes(deps) {
1425
1346
  const lastSeenAt = lastSeenEntry?.timestamp;
1426
1347
  // Get pending input request from active process
1427
1348
  const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
1428
- const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
1349
+ const providerRuntimeStatus = deps.supervisor.getProviderRuntimeStatusForSession?.(sessionId) ??
1350
+ process?.getProviderRuntimeStatus() ??
1351
+ null;
1429
1352
  // Read minimal session info from disk (just for title/timestamps, no messages)
1430
1353
  const metadataProvider = metadata?.provider ??
1431
1354
  deps.sessionMetadataService?.getProvider(sessionId);
@@ -1617,9 +1540,9 @@ export function createSessionsRoutes(deps) {
1617
1540
  // ?afterMessageId=<id> - incremental forward-fetch (append new messages)
1618
1541
  // ?tailCompactions=<n> - return only last N compact boundaries worth of messages
1619
1542
  // ?beforeMessageId=<id> - cursor for loading older chunks (used with tailCompactions)
1620
- // ?tailTurns=<n> - aggressive opt-in client memory cap by recent user turns
1621
- // ?tailFrom=<id> - aggressive opt-in client memory cap from a user message id
1622
- // ?fullHistory=1 - explicit opt-in to the full transcript
1543
+ // ?tailTurns=<n> - recent-turn selector within the authorized history scope
1544
+ // ?tailFrom=<id> - start selector within the authorized history scope
1545
+ // ?fullHistory=1 - explicitly authorize selectors to inspect full history
1623
1546
  routes.get("/projects/:projectId/sessions/:sessionId", async (c) => {
1624
1547
  const requestStartMs = performance.now();
1625
1548
  const projectId = c.req.param("projectId");
@@ -1648,9 +1571,10 @@ export function createSessionsRoutes(deps) {
1648
1571
  : undefined;
1649
1572
  const defaultedToCompactTail = !fullHistory &&
1650
1573
  !afterMessageId &&
1651
- !tailFrom &&
1652
- requestedTailTurns === undefined &&
1653
1574
  requestedTailCompactions === undefined;
1575
+ const unboundedRequestDefaultedToCompactTail = defaultedToCompactTail &&
1576
+ !tailFrom &&
1577
+ requestedTailTurns === undefined;
1654
1578
  const effectiveTailCompactions = requestedTailCompactions ??
1655
1579
  (defaultedToCompactTail
1656
1580
  ? DEFAULT_SESSION_DETAIL_TAIL_COMPACTIONS
@@ -1724,7 +1648,9 @@ export function createSessionsRoutes(deps) {
1724
1648
  // Get pending input request from active process (for tool approval prompts)
1725
1649
  // This ensures clients get pending requests immediately without waiting for SSE
1726
1650
  const pendingInputRequest = process?.state.type === "waiting-input" ? process.state.request : null;
1727
- const providerRuntimeStatus = process?.getProviderRuntimeStatus() ?? null;
1651
+ const providerRuntimeStatus = deps.supervisor.getProviderRuntimeStatusForSession?.(sessionId) ??
1652
+ process?.getProviderRuntimeStatus() ??
1653
+ null;
1728
1654
  // Get available slash commands (for "/" button and typed slash menu)
1729
1655
  // The init message that normally carries these gets discarded from the SSE buffer
1730
1656
  // after ~30s, so we attach them to the REST response. Providers with known
@@ -1812,12 +1738,21 @@ export function createSessionsRoutes(deps) {
1812
1738
  // Get notification data (lastSeenAt, hasUnread)
1813
1739
  const lastSeenEntry = deps.notificationService?.getLastSeen(sessionId);
1814
1740
  const lastSeenAt = lastSeenEntry?.timestamp;
1815
- // Apply pagination if requested (BEFORE expensive augmentation). tailTurns
1816
- // is an opt-in stronger client memory cap, so it wins over compact tails.
1817
- // Skip both when afterMessageId is present since that's an incremental
1818
- // forward-fetch use case.
1741
+ // Apply pagination BEFORE expensive augmentation. Compact boundaries set
1742
+ // the authorized history scope by default; turn selectors may narrow that
1743
+ // scope but cannot broaden it unless fullHistory=1 removed the default.
1744
+ // Skip initial-window slicing for incremental forward fetches.
1819
1745
  let paginationInfo;
1820
1746
  if (!afterMessageId &&
1747
+ effectiveTailCompactions !== undefined &&
1748
+ !beforeMessageId &&
1749
+ (tailFrom || requestedTailTurns !== undefined)) {
1750
+ const sliced = sliceAtCompactAndUserTurnBoundaries(session.messages, effectiveTailCompactions, requestedTailTurns ?? 20, tailFrom);
1751
+ session = { ...session, messages: sliced.messages };
1752
+ paginationInfo = sliced.pagination;
1753
+ }
1754
+ else if (!afterMessageId &&
1755
+ effectiveTailCompactions === undefined &&
1821
1756
  (tailFrom || requestedTailTurns !== undefined)) {
1822
1757
  const sliced = sliceAtUserTurnBoundary(session.messages, requestedTailTurns ?? 20, tailFrom);
1823
1758
  session = { ...session, messages: sliced.messages };
@@ -1889,7 +1824,7 @@ export function createSessionsRoutes(deps) {
1889
1824
  }
1890
1825
  const { messages: _messages, ...sessionMetadata } = session;
1891
1826
  const totalMs = performance.now() - requestStartMs;
1892
- if (defaultedToCompactTail &&
1827
+ if (unboundedRequestDefaultedToCompactTail &&
1893
1828
  normalizedMessageCount >= LARGE_FULL_HISTORY_MESSAGE_THRESHOLD) {
1894
1829
  getLogger().warn({
1895
1830
  beforeMessageId: beforeMessageId ?? null,
@@ -2048,10 +1983,7 @@ export function createSessionsRoutes(deps) {
2048
1983
  tempId: body.tempId,
2049
1984
  metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
2050
1985
  };
2051
- // Convert thinking option to SDK config
2052
- const { thinking, effort } = body.thinking
2053
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2054
- : { thinking: undefined, effort: undefined };
1986
+ const { thinking, effort } = buildThinkingOptions(body);
2055
1987
  // Convert model option (undefined or "default" means use CLI default)
2056
1988
  const model = body.model && body.model !== "default" ? body.model : undefined;
2057
1989
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
@@ -2130,10 +2062,7 @@ export function createSessionsRoutes(deps) {
2130
2062
  if ("error" in workstreamTarget) {
2131
2063
  return c.json({ error: workstreamTarget.error }, workstreamTarget.status);
2132
2064
  }
2133
- // Convert thinking option to SDK config
2134
- const { thinking, effort } = body.thinking
2135
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2136
- : { thinking: undefined, effort: undefined };
2065
+ const { thinking, effort } = buildThinkingOptions(body);
2137
2066
  // Convert model option (undefined or "default" means use CLI default)
2138
2067
  const model = body.model && body.model !== "default" ? body.model : undefined;
2139
2068
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
@@ -2204,9 +2133,7 @@ export function createSessionsRoutes(deps) {
2204
2133
  tempId: body.tempId,
2205
2134
  metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
2206
2135
  };
2207
- const { thinking, effort } = body.thinking
2208
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2209
- : { thinking: undefined, effort: undefined };
2136
+ const { thinking, effort } = buildThinkingOptions(body);
2210
2137
  const model = body.model && body.model !== "default" ? body.model : undefined;
2211
2138
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
2212
2139
  const result = await deps.supervisor.startSession(projectPath, userMessage, body.mode, {
@@ -2258,9 +2185,7 @@ export function createSessionsRoutes(deps) {
2258
2185
  return c.json({ error: helperSettings.error }, 400);
2259
2186
  }
2260
2187
  const projectPath = await ensureDetachedProjectPath(executor);
2261
- const { thinking, effort } = body.thinking
2262
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2263
- : { thinking: undefined, effort: undefined };
2188
+ const { thinking, effort } = buildThinkingOptions(body);
2264
2189
  const model = body.model && body.model !== "default" ? body.model : undefined;
2265
2190
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
2266
2191
  const result = await deps.supervisor.createSession(projectPath, body.mode, {
@@ -2340,10 +2265,7 @@ export function createSessionsRoutes(deps) {
2340
2265
  tempId: body.tempId,
2341
2266
  metadata: buildUserMessageMetadata(body, serverTimestamp, "direct"),
2342
2267
  };
2343
- // Convert thinking option to SDK config
2344
- const { thinking, effort } = body.thinking
2345
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2346
- : { thinking: undefined, effort: undefined };
2268
+ const { thinking, effort } = buildThinkingOptions(body);
2347
2269
  // Convert model option (undefined or "default" means use CLI default). When
2348
2270
  // the client sends no model (e.g. resume after a server restart), recover the
2349
2271
  // YA model id persisted at launch so the process keeps its requested alias
@@ -2578,9 +2500,7 @@ export function createSessionsRoutes(deps) {
2578
2500
  ? body.model
2579
2501
  : metadata?.requestedModel;
2580
2502
  const model = rawModel && rawModel !== "default" ? rawModel : undefined;
2581
- const { thinking, effort } = body.thinking
2582
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2583
- : { thinking: undefined, effort: undefined };
2503
+ const { thinking, effort } = buildThinkingOptions(body);
2584
2504
  let process;
2585
2505
  try {
2586
2506
  process = await deps.supervisor.reactivateSession(project.path, sessionId, body.mode, {
@@ -2742,9 +2662,7 @@ export function createSessionsRoutes(deps) {
2742
2662
  const sourceProvider = sourceSession.provider ?? preferredSourceProvider;
2743
2663
  const providerName = body.provider ?? sourceProvider;
2744
2664
  const originalMetadata = deps.sessionMetadataService?.getMetadata(sessionId);
2745
- const { thinking, effort } = body.thinking
2746
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
2747
- : { thinking: undefined, effort: undefined };
2665
+ const { thinking, effort } = buildThinkingOptions(body);
2748
2666
  const model = body.model && body.model !== "default" ? body.model : undefined;
2749
2667
  const serviceTier = normalizeOptionalServiceTier(body.serviceTier);
2750
2668
  if (restartMode === "fork") {
@@ -3569,10 +3487,7 @@ export function createSessionsRoutes(deps) {
3569
3487
  serverTimestamp,
3570
3488
  });
3571
3489
  }
3572
- // Convert thinking option to SDK config
3573
- const { thinking, effort } = body.thinking
3574
- ? thinkingOptionToConfig(body.thinking, body.showThinking)
3575
- : { thinking: undefined, effort: undefined };
3490
+ const { thinking, effort } = buildThinkingOptions(body);
3576
3491
  const metadataProvider = deps.sessionMetadataService?.getProvider(sessionId);
3577
3492
  const metadataExecutor = parseOptionalExecutor(deps.sessionMetadataService?.getExecutor(sessionId));
3578
3493
  if (metadataExecutor.error) {
@@ -3656,131 +3571,12 @@ export function createSessionsRoutes(deps) {
3656
3571
  deferredMessages: recoveredPatientQueueSummaries(deps, sessionId),
3657
3572
  });
3658
3573
  });
3659
- // A recovered patient queue entry may need a fresh process before it can
3660
- // rejoin the live queue. Shared by the recovered-queue resume/steer routes.
3661
- const ensureProcessForRecoveredItem = async (sessionId, item, existing) => {
3662
- let process = existing;
3663
- const mode = item.message.mode ?? item.mode;
3664
- const metadata = deps.sessionMetadataService?.getMetadata(sessionId);
3665
- const parsedExecutor = parseOptionalExecutor(item.executor ?? metadata?.executor);
3666
- if (parsedExecutor.error) {
3667
- return { error: parsedExecutor.error, status: 400 };
3668
- }
3669
- const rawModel = item.model ?? metadata?.requestedModel;
3670
- const model = rawModel && rawModel !== "default" ? rawModel : undefined;
3671
- if (!process) {
3672
- try {
3673
- process = await deps.supervisor.reactivateSession(item.projectPath, sessionId, mode, {
3674
- model,
3675
- serviceTier: item.serviceTier,
3676
- providerName: item.provider,
3677
- executor: parsedExecutor.executor,
3678
- globalInstructions: getGlobalInstructions(),
3679
- recapAfterSeconds: metadata?.recapAfterSeconds,
3680
- promptSuggestionMode: metadata?.promptSuggestionMode,
3681
- });
3682
- }
3683
- catch (error) {
3684
- return {
3685
- error: error instanceof Error
3686
- ? error.message
3687
- : "Failed to reactivate session",
3688
- status: 503,
3689
- };
3690
- }
3691
- await persistLaunchMetadata(sessionId, item.provider, parsedExecutor.executor, undefined, item.model ?? metadata?.requestedModel, metadata?.promptSuggestionMode, metadata?.recapAfterSeconds);
3692
- }
3693
- else if (mode) {
3694
- process.setPermissionMode(mode);
3695
- }
3696
- return { process };
3697
- };
3698
- // Move recovered entries back into the live patient queue in queuedAt
3699
- // order. On a mid-group failure the earlier resumes stand; the caller
3700
- // reports the failed entry.
3701
- const resumeRecoveredGroup = async (process, group, options) => {
3702
- let last;
3703
- let lastMessage;
3704
- for (const item of group) {
3705
- const userMessage = recoveredPatientUserMessage(item);
3706
- await process.primeSupportedCommandsForMessage(userMessage);
3707
- const result = process.deferMessage(userMessage, {
3708
- promoteIfReady: options.promoteIfReady,
3709
- persistedQueueId: item.id,
3710
- timestamp: item.queuedAt,
3711
- });
3712
- if (!result.success) {
3713
- return {
3714
- last,
3715
- lastMessage,
3716
- error: result.error ?? "Failed to queue message",
3717
- };
3718
- }
3719
- last = result;
3720
- lastMessage = userMessage;
3721
- }
3722
- return { last, lastMessage };
3723
- };
3724
- const reportableProcessState = (process) => process.state.type === "waiting-input" || process.state.type === "in-turn"
3725
- ? process.state.type
3726
- : "idle";
3727
- // Shared prologue for the recovered-queue resume/steer routes: resolve the
3728
- // target and the recovered group composed before it, refuse while newer
3729
- // live patient entries exist, and ensure a live process. The newer-entries
3730
- // guard runs again after the ensure — its await is a window for new
3731
- // arrivals, and recovered context must never jump a newer patient entry.
3732
- const resolveRecoveredGroupForDelivery = async (sessionId, queueId, refusal) => {
3733
- if (!deps.sessionQueuePersistenceService) {
3734
- return {
3735
- ok: false,
3736
- status: 503,
3737
- body: { error: "Session queue persistence unavailable" },
3738
- };
3739
- }
3740
- const recoveredItems = recoveredPatientQueueItems(deps, sessionId);
3741
- const targetIndex = recoveredItems.findIndex((candidate) => candidate.id === queueId);
3742
- const target = recoveredItems[targetIndex];
3743
- if (!target) {
3744
- return {
3745
- ok: false,
3746
- status: 404,
3747
- body: { error: "Recovered queued message not found" },
3748
- };
3749
- }
3750
- const group = recoveredItems.slice(0, targetIndex + 1);
3751
- let process = deps.supervisor.getProcessForSession(sessionId);
3752
- if (process?.isTerminated) {
3753
- process = undefined;
3754
- }
3755
- const refuseIfNewerEntries = (candidate) => livePatientEntriesNewerThan(candidate, target.queuedAt) > 0
3756
- ? {
3757
- ok: false,
3758
- status: 409,
3759
- body: {
3760
- error: refusal,
3761
- headQueueId: target.id,
3762
- deferredMessages: sessionQueueSummaries(deps, sessionId, candidate),
3763
- },
3764
- }
3765
- : null;
3766
- const refusedBefore = refuseIfNewerEntries(process);
3767
- if (refusedBefore) {
3768
- return refusedBefore;
3769
- }
3770
- const ensured = await ensureProcessForRecoveredItem(sessionId, target, process);
3771
- if ("error" in ensured) {
3772
- return {
3773
- ok: false,
3774
- status: ensured.status,
3775
- body: { error: ensured.error },
3776
- };
3777
- }
3778
- process = ensured.process;
3779
- const refusedAfter = refuseIfNewerEntries(process);
3780
- if (refusedAfter) {
3781
- return refusedAfter;
3782
- }
3783
- return { ok: true, process, group };
3574
+ const recoveredQueueDeps = {
3575
+ sessionQueuePersistenceService: deps.sessionQueuePersistenceService,
3576
+ sessionMetadataService: deps.sessionMetadataService,
3577
+ supervisor: deps.supervisor,
3578
+ getGlobalInstructions,
3579
+ persistLaunchMetadata,
3784
3580
  };
3785
3581
  // POST /api/sessions/:sessionId/recovered-queue/:queueId/resume - Move
3786
3582
  // restart-paused patient queue entries back into the live patient queue,
@@ -3789,7 +3585,7 @@ export function createSessionsRoutes(deps) {
3789
3585
  routes.post("/sessions/:sessionId/recovered-queue/:queueId/resume", async (c) => {
3790
3586
  const sessionId = c.req.param("sessionId");
3791
3587
  const queueId = c.req.param("queueId");
3792
- const resolved = await resolveRecoveredGroupForDelivery(sessionId, queueId, "Drain or cancel newer queued messages before resuming recovered work.");
3588
+ const resolved = await resolveRecoveredGroupForDelivery(recoveredQueueDeps, sessionId, queueId, "Drain or cancel newer queued messages before resuming recovered work.");
3793
3589
  if (!resolved.ok) {
3794
3590
  return c.json(resolved.body, resolved.status);
3795
3591
  }
@@ -3827,7 +3623,7 @@ export function createSessionsRoutes(deps) {
3827
3623
  routes.post("/sessions/:sessionId/recovered-queue/:queueId/steer", async (c) => {
3828
3624
  const sessionId = c.req.param("sessionId");
3829
3625
  const queueId = c.req.param("queueId");
3830
- const resolved = await resolveRecoveredGroupForDelivery(sessionId, queueId, "Drain or cancel newer queued messages before steering recovered work.");
3626
+ const resolved = await resolveRecoveredGroupForDelivery(recoveredQueueDeps, sessionId, queueId, "Drain or cancel newer queued messages before steering recovered work.");
3831
3627
  if (!resolved.ok) {
3832
3628
  return c.json(resolved.body, resolved.status);
3833
3629
  }
@@ -3875,6 +3671,21 @@ export function createSessionsRoutes(deps) {
3875
3671
  await process.waitForPatientQueuePersistenceIdle();
3876
3672
  return c.json({ cancelled: true });
3877
3673
  });
3674
+ // DELETE /api/sessions/:sessionId/steering/:tempId - Cancel a sent steering
3675
+ // message that has not yet been consumed by the provider.
3676
+ routes.delete("/sessions/:sessionId/steering/:tempId", async (c) => {
3677
+ const sessionId = c.req.param("sessionId");
3678
+ const tempId = c.req.param("tempId");
3679
+ const process = deps.supervisor.getProcessForSession(sessionId);
3680
+ if (!process) {
3681
+ return c.json({ error: "No active process for session" }, 404);
3682
+ }
3683
+ const cancelled = process.cancelUnconfirmedSteerMessage(tempId);
3684
+ if (!cancelled) {
3685
+ return c.json({ error: "Steering message already acted on or not found" }, 404);
3686
+ }
3687
+ return c.json({ cancelled: true });
3688
+ });
3878
3689
  // POST /api/sessions/:sessionId/deferred/:tempId/steer - Steer a patient
3879
3690
  // queued message, and every patient entry ahead of it, into the session now.
3880
3691
  routes.post("/sessions/:sessionId/deferred/:tempId/steer", async (c) => {
@@ -4116,137 +3927,27 @@ export function createSessionsRoutes(deps) {
4116
3927
  catch {
4117
3928
  return c.json({ error: "Invalid JSON body" }, 400);
4118
3929
  }
4119
- // At least one field must be provided
4120
- if (body.title === undefined &&
4121
- body.archived === undefined &&
4122
- body.starred === undefined &&
4123
- body.parentSessionId === undefined &&
4124
- body.heartbeatTurnsEnabled === undefined &&
4125
- body.heartbeatTurnsAfterMinutes === undefined &&
4126
- body.heartbeatTurnText === undefined &&
4127
- body.heartbeatForceAfterMinutes === undefined &&
4128
- body.promptSuggestionMode === undefined &&
4129
- body.recapAfterSeconds === undefined) {
4130
- return c.json({
4131
- error: "At least one session metadata field must be provided",
4132
- }, 400);
4133
- }
4134
- let heartbeatForceAfterMinutes;
4135
- if (body.heartbeatForceAfterMinutes !== undefined) {
4136
- if (body.heartbeatForceAfterMinutes === null ||
4137
- body.heartbeatForceAfterMinutes === 0) {
4138
- heartbeatForceAfterMinutes = null;
4139
- }
4140
- else if (typeof body.heartbeatForceAfterMinutes === "number" &&
4141
- Number.isInteger(body.heartbeatForceAfterMinutes) &&
4142
- body.heartbeatForceAfterMinutes >= 1 &&
4143
- body.heartbeatForceAfterMinutes <= 1440) {
4144
- heartbeatForceAfterMinutes = body.heartbeatForceAfterMinutes;
4145
- }
4146
- else {
4147
- return c.json({
4148
- error: "heartbeatForceAfterMinutes must be null or an integer between 1 and 1440",
4149
- }, 400);
4150
- }
4151
- }
4152
- let heartbeatTurnsAfterMinutes;
4153
- if (body.heartbeatTurnsAfterMinutes !== undefined) {
4154
- if (body.heartbeatTurnsAfterMinutes === null ||
4155
- body.heartbeatTurnsAfterMinutes === 0) {
4156
- heartbeatTurnsAfterMinutes = null;
4157
- }
4158
- else if (typeof body.heartbeatTurnsAfterMinutes === "number" &&
4159
- Number.isInteger(body.heartbeatTurnsAfterMinutes) &&
4160
- body.heartbeatTurnsAfterMinutes >= 1 &&
4161
- body.heartbeatTurnsAfterMinutes <= 1440) {
4162
- heartbeatTurnsAfterMinutes = body.heartbeatTurnsAfterMinutes;
4163
- }
4164
- else {
4165
- return c.json({
4166
- error: "heartbeatTurnsAfterMinutes must be null or an integer between 1 and 1440",
4167
- }, 400);
4168
- }
4169
- }
4170
- const heartbeatTurnText = body.heartbeatTurnText === undefined
4171
- ? undefined
4172
- : body.heartbeatTurnText === null || body.heartbeatTurnText === ""
4173
- ? null
4174
- : typeof body.heartbeatTurnText === "string"
4175
- ? body.heartbeatTurnText.slice(0, 200)
4176
- : null;
4177
- if (body.heartbeatTurnText !== undefined &&
4178
- body.heartbeatTurnText !== null &&
4179
- body.heartbeatTurnText !== "" &&
4180
- typeof body.heartbeatTurnText !== "string") {
4181
- return c.json({ error: "heartbeatTurnText must be a string or null" }, 400);
4182
- }
4183
- if (body.parentSessionId !== undefined &&
4184
- body.parentSessionId !== null &&
4185
- typeof body.parentSessionId !== "string") {
4186
- return c.json({ error: "parentSessionId must be a string or null" }, 400);
4187
- }
4188
- const parentSessionId = body.parentSessionId === undefined
4189
- ? undefined
4190
- : typeof body.parentSessionId === "string"
4191
- ? body.parentSessionId.trim() || null
4192
- : null;
4193
- // promptSuggestionMode: null/"" clears the preference (revert to default);
4194
- // a valid enum value is stored; any other value is rejected.
4195
- let promptSuggestionMode;
4196
- if (body.promptSuggestionMode !== undefined) {
4197
- if (body.promptSuggestionMode === null ||
4198
- body.promptSuggestionMode === "") {
4199
- promptSuggestionMode = null;
4200
- }
4201
- else if (typeof body.promptSuggestionMode === "string" &&
4202
- PROMPT_SUGGESTION_MODES.includes(body.promptSuggestionMode)) {
4203
- promptSuggestionMode =
4204
- body.promptSuggestionMode;
4205
- }
4206
- else {
4207
- return c.json({ error: "promptSuggestionMode must be one of: off, native" }, 400);
4208
- }
3930
+ const parsed = parseSessionMetadataPatch(body);
3931
+ if (!parsed.ok) {
3932
+ return c.json({ error: parsed.error }, parsed.status);
4209
3933
  }
4210
- let recapAfterSeconds;
4211
- if (body.recapAfterSeconds !== undefined) {
4212
- if (body.recapAfterSeconds === null || body.recapAfterSeconds === "") {
4213
- recapAfterSeconds = null;
4214
- }
4215
- else if (typeof body.recapAfterSeconds === "number" &&
4216
- Number.isFinite(body.recapAfterSeconds)) {
4217
- recapAfterSeconds = clampRecapAfterSeconds(body.recapAfterSeconds);
4218
- }
4219
- else {
4220
- return c.json({ error: "recapAfterSeconds must be null or a finite number" }, 400);
4221
- }
4222
- }
4223
- await deps.sessionMetadataService.updateMetadata(sessionId, {
4224
- title: body.title,
4225
- archived: body.archived,
4226
- starred: body.starred,
4227
- parentSessionId,
4228
- heartbeatTurnsEnabled: body.heartbeatTurnsEnabled,
4229
- heartbeatTurnsAfterMinutes,
4230
- heartbeatTurnText,
4231
- heartbeatForceAfterMinutes,
4232
- promptSuggestionMode,
4233
- recapAfterSeconds,
4234
- });
3934
+ const { patch } = parsed;
3935
+ await deps.sessionMetadataService.updateMetadata(sessionId, patch);
4235
3936
  // Emit SSE event so sidebar and other clients can update
4236
3937
  if (deps.eventBus) {
4237
3938
  deps.eventBus.emit({
4238
3939
  type: "session-metadata-changed",
4239
3940
  sessionId,
4240
- title: body.title,
4241
- archived: body.archived,
4242
- starred: body.starred,
4243
- parentSessionId,
4244
- heartbeatTurnsEnabled: body.heartbeatTurnsEnabled,
4245
- heartbeatTurnsAfterMinutes,
4246
- heartbeatTurnText,
4247
- heartbeatForceAfterMinutes,
4248
- promptSuggestionMode: promptSuggestionMode ?? undefined,
4249
- recapAfterSeconds: recapAfterSeconds ?? undefined,
3941
+ title: patch.title,
3942
+ archived: patch.archived,
3943
+ starred: patch.starred,
3944
+ parentSessionId: patch.parentSessionId,
3945
+ heartbeatTurnsEnabled: patch.heartbeatTurnsEnabled,
3946
+ heartbeatTurnsAfterMinutes: patch.heartbeatTurnsAfterMinutes,
3947
+ heartbeatTurnText: patch.heartbeatTurnText,
3948
+ heartbeatForceAfterMinutes: patch.heartbeatForceAfterMinutes,
3949
+ promptSuggestionMode: patch.promptSuggestionMode ?? undefined,
3950
+ recapAfterSeconds: patch.recapAfterSeconds ?? undefined,
4250
3951
  timestamp: new Date().toISOString(),
4251
3952
  });
4252
3953
  }