History

fix(orchestration): stabilize Codex turn mapping and settlement

- Preserve Codex turn identity while suppressing duplicate diff notifications - Optimize settlement projections and isolate thread visit handling - Add concurrency and regression coverage across server and mobile

Julius Marminge committed 2 months ago

Commit 6502e221 parent 25d2531167 files · +189949 −140Browse files

Unified · Split

129 files
  1. apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift +189 −0
  2. apps/mobile/scripts/notification-center-manager.test.ts +65 −0
  3. apps/server/src/git/GitManager.ts +1 −1
  4. apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +4 −1
  5. apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +1 −0
  6. apps/server/src/orchestration-v2/Orchestrator.ts +55 −31
  7. apps/server/src/orchestration-v2/ProjectionSettlement.test.ts +363 −0
  8. apps/server/src/orchestration-v2/ProjectionStore.ts +192 −0
  9. apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts +2 −0
  10. apps/server/src/orchestration-v2/ThreadSettlementService.test.ts +6 −2
  11. apps/server/src/orchestration-v2/ThreadSettlementService.ts +6 −6
  12. apps/server/src/orchestration-v2/runtimeLayer.test.ts +32 −0
  13. apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson +1 −1
  14. apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson +1 −1
  15. apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson +1 −1
  16. apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson +1 −1
  17. apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson +2 −2
  18. apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson +1 −1
  19. apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson +1 −1
  20. apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson +1 −1
  21. apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson +1 −1
  22. apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson +1 −1
  23. apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson +1 −1
  24. apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson +1 −1
  25. apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson +1 −1
  26. apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson +1 −1
  27. apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson +1 −1
  28. apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson +1 −1
  29. apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson +1 −1
  30. apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson +1 −1
  31. apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson +1 −1
  32. apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson +1 −1
  33. apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson +1 −1
  34. apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson +1 −1
  35. apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson +1 −1
  36. apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson +1 −1
  37. apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson +1 −1
  38. apps/server/src/persistence/Migrations.ts +2 −0
  39. apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts +24 −0
  40. apps/server/src/provider/Layers/EventNdjsonLogger.test.ts +16 −0
  41. apps/server/src/provider/Layers/EventNdjsonLogger.ts +1 −0
  42. apps/server/src/relay/AgentAwarenessRelay.test.ts +573 −0
  43. apps/server/src/relay/AgentAwarenessRelay.ts +167 −26
  44. apps/server/src/server.ts +1 −1
  45. apps/server/src/vcs/GitVcsDriver.ts +9 −1
  46. apps/server/src/vcs/GitVcsDriverCore.test.ts +84 −7
  47. apps/server/src/vcs/GitVcsDriverCore.ts +21 −9
  48. apps/web/src/components/CommandPalette.tsx +9 −1
  49. apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx +180 −0
  50. apps/web/src/components/ThreadStatusIndicators.test.ts +19 −0
  51. apps/web/src/components/ThreadStatusIndicators.tsx +59 −17
  52. apps/web/src/connection/desktopLocal.test.ts +62 −1
  53. apps/web/src/connection/desktopLocal.ts +19 −2
  54. audits/orchestrator-v2/2026-09-02/AUDIT.md +99 −0
  55. audits/orchestrator-v2/2026-09-02/DECISIONS.tsv +28 −0
  56. audits/orchestrator-v2/2026-09-02/FEATURE-MAP.md +89 −0
  57. audits/orchestrator-v2/2026-09-02/branch-commits.txt +332 −0
  58. audits/orchestrator-v2/2026-09-02/changed-files.tsv +942 −0
  59. audits/orchestrator-v2/2026-09-02/clients.md +147 −0
  60. audits/orchestrator-v2/2026-09-02/cross-cutting.md +44 −0
  61. audits/orchestrator-v2/2026-09-02/main-feature-file-map.tsv +166 −0
  62. audits/orchestrator-v2/2026-09-02/main-since-prior.txt +165 −0
  63. audits/orchestrator-v2/2026-09-02/new-branch-work.txt +21 −0
  64. audits/orchestrator-v2/2026-09-02/persistence-runtime-probes.test.ts +210 −0
  65. audits/orchestrator-v2/2026-09-02/persistence.md +236 −0
  66. audits/orchestrator-v2/2026-09-02/prior-fixes-range-diff.txt +44 −0
  67. audits/orchestrator-v2/2026-09-02/providers.md +188 −0
  68. audits/orchestrator-v2/2026-09-02/references.json +11 −0
  69. audits/orchestrator-v2/2026-09-04/AUDIT.md +151 −0
  70. audits/orchestrator-v2/2026-09-04/DECISIONS.tsv +51 −0
  71. audits/orchestrator-v2/2026-09-04/FEATURE-MAP.md +110 −0
  72. audits/orchestrator-v2/2026-09-04/branch-commits.txt +333 −0
  73. audits/orchestrator-v2/2026-09-04/changed-files-final.tsv +999 −0
  74. audits/orchestrator-v2/2026-09-04/changed-files.tsv +991 −0
  75. audits/orchestrator-v2/2026-09-04/clients.md +157 −0
  76. audits/orchestrator-v2/2026-09-04/closing-references.json +15 −0
  77. audits/orchestrator-v2/2026-09-04/cross-cutting.md +64 −0
  78. audits/orchestrator-v2/2026-09-04/dirty-stat.txt +50 −0
  79. audits/orchestrator-v2/2026-09-04/drift-interim.json +8 −0
  80. audits/orchestrator-v2/2026-09-04/initial-status.txt +57 −0
  81. audits/orchestrator-v2/2026-09-04/main-feature-file-map.tsv +241 −0
  82. audits/orchestrator-v2/2026-09-04/main-late-arrivals.txt +7 −0
  83. audits/orchestrator-v2/2026-09-04/main-only-commits.txt +8 −0
  84. audits/orchestrator-v2/2026-09-04/main-since-prior.txt +240 −0
  85. audits/orchestrator-v2/2026-09-04/persistence-audit-probes.test.ts +323 −0
  86. audits/orchestrator-v2/2026-09-04/persistence.md +312 −0
  87. audits/orchestrator-v2/2026-09-04/pr-metadata-final.json +257 −0
  88. audits/orchestrator-v2/2026-09-04/pr-metadata.json +261 −0
  89. audits/orchestrator-v2/2026-09-04/prior-audit-range-diff-summary.txt +336 −0
  90. audits/orchestrator-v2/2026-09-04/prior-audit-range-diff.txt +106728 −0
  91. audits/orchestrator-v2/2026-09-04/provider-adapter-regression-probes.test.ts +207 −0
  92. audits/orchestrator-v2/2026-09-04/providers.md +109 −0
  93. audits/orchestrator-v2/2026-09-04/recent-branch-work.txt +24 −0
  94. audits/orchestrator-v2/2026-09-04/recent-review-comments.json +1 −0
  95. audits/orchestrator-v2/2026-09-04/references.json +11 −0
  96. audits/orchestrator-v2/2026-09-04/worktree-drift.diff +561 −0
  97. audits/orchestrator-v2/2026-09-04/worktree-drift/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts.snapshot +363 −0
  98. audits/orchestrator-v2/2026-09-04/worktree-drift/apps/server/src/relay/AgentAwarenessRelay.test.ts.snapshot +573 −0
  99. audits/orchestrator-v2/2026-09-04/worktree-drift/apps/server/src/relay/AgentAwarenessRelay.ts.snapshot +758 −0
  100. audits/orchestrator-v2/2026-09-04/worktree-drift/docs/internals/mobile-development.md.snapshot +61 −0
  101. audits/orchestrator-v2/2026-09-04/worktree-drift/docs/internals/performance-regressions.md.snapshot +86 −0
  102. audits/orchestrator-v2/2026-09-04/worktree-drift/docs/internals/providers.md.snapshot +411 −0
  103. audits/orchestrator-v2/2026-09-04/worktree-final-manifest.json +310 −0
  104. audits/orchestrator-v2/2026-09-04/worktree-manifest.json +282 −0
  105. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift.snapshot +189 −0
  106. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/mobile/scripts/notification-center-manager.test.ts.snapshot +65 −0
  107. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/git/GitManager.ts.snapshot +2719 −0
  108. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts.snapshot +4033 −0
  109. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts.snapshot +5465 −0
  110. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/Orchestrator.ts.snapshot +7524 −0
  111. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts.snapshot +363 −0
  112. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ProjectionStore.ts.snapshot +3940 −0
  113. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts.snapshot +292 −0
  114. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts.snapshot +716 −0
  115. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.ts.snapshot +371 −0
  116. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/runtimeLayer.test.ts.snapshot +1751 −0
  117. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson.snapshot +51 −0
  118. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson.snapshot +52 −0
  119. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson.snapshot +67 −0
  120. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson.snapshot +619 −0
  121. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson.snapshot +78 −0
  122. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson.snapshot +52 −0
  123. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson.snapshot +31 −0
  124. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson.snapshot +577 −0
  125. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson.snapshot +33 −0
  126. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson.snapshot +25 −0
  127. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson.snapshot +33 −0
  128. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson.snapshot +21 −0
  129. audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson.snapshot +96 −0
apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift+189−0
@@ -0,0 +1,189 @@
1import Foundation
2
3// The registry runs on macOS without starting a simulator. Only the OS-facing
4// types are replaced; the tests compile the dependency's actual Swift source.
5public enum UIBackgroundFetchResult {
6 case noData
7}
8
9public struct UNNotificationPresentationOptions: OptionSet {
10 public let rawValue: Int
11 public init(rawValue: Int) { self.rawValue = rawValue }
12}
13
14public final class UNNotification: NSObject {}
15
16public final class UNNotificationResponse: NSObject {
17 let identifier: String
18 init(_ identifier: String) { self.identifier = identifier }
19}
20
21public protocol UNUserNotificationCenterDelegate: AnyObject {}
22
23public final class UNUserNotificationCenter: NSObject {
24 private static let instance = UNUserNotificationCenter()
25 public weak var delegate: UNUserNotificationCenterDelegate?
26 public static func current() -> UNUserNotificationCenter { instance }
27}
28
29private final class TestDelegate: NotificationDelegate {
30 private let lock = NSLock()
31 private var recordedEvents: [String] = []
32 var onEvent: ((String) -> Void)?
33 var onResponse: ((UNNotificationResponse) -> Bool)?
34
35 var events: [String] { lock.withLock { recordedEvents } }
36
37 private func record(_ event: String) {
38 lock.withLock { recordedEvents.append(event) }
39 onEvent?(event)
40 }
41
42 func didRegister(_ deviceToken: String) { record("registered") }
43 func didFailRegistration(_ error: Error) { record("failed") }
44 func openSettings(_ notification: UNNotification?) { record("settings") }
45
46 func willPresent(_ notification: UNNotification, completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) -> Bool {
47 record("present")
48 return false
49 }
50
51 func didReceive(_ userInfo: [AnyHashable: Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool {
52 record("background")
53 return false
54 }
55
56 func didReceive(_ response: UNNotificationResponse, completionHandler: @escaping () -> Void) -> Bool {
57 record(response.identifier)
58 return onResponse?(response) ?? false
59 }
60}
61
62private func require(_ condition: @autoclosure () -> Bool, _ message: String) throws {
63 if !condition() {
64 throw NSError(domain: "NotificationCenterManagerRegression", code: 1, userInfo: [NSLocalizedDescriptionKey: message])
65 }
66}
67
68private func deliver(_ identifier: String) {
69 NotificationCenterManager.shared.userNotificationCenter(
70 UNUserNotificationCenter.current(),
71 didReceive: UNNotificationResponse(identifier),
72 withCompletionHandler: {}
73 )
74}
75
76private func reentrantCallbacks() throws {
77 let manager = NotificationCenterManager.shared
78 let center = UNUserNotificationCenter.current()
79 let callbacks: [(String, () -> Void)] = [
80 ("registered", { manager.didRegister("token") }),
81 ("failed", { manager.didFailRegistration(NSError(domain: "test", code: 1)) }),
82 ("settings", { manager.userNotificationCenter(center, openSettingsFor: nil) }),
83 ("present", { manager.userNotificationCenter(center, willPresent: UNNotification(), withCompletionHandler: { _ in }) }),
84 ("background", { manager.didReceive([:], completionHandler: { _ in }) })
85 ]
86
87 for (event, callback) in callbacks {
88 let original = TestDelegate()
89 let replacement = TestDelegate()
90 original.onEvent = { [weak original] _ in
91 guard let original else { return }
92 manager.removeDelegate(original)
93 manager.addDelegate(replacement)
94 }
95 manager.addDelegate(original)
96 callback()
97 try require(original.events == [event], "The original delegate must receive the callback once")
98 try require(replacement.events.isEmpty, "New delegates must not join an in-flight callback snapshot")
99 callback()
100 try require(replacement.events == [event], "Reentrant registration must take effect for the next callback")
101 manager.removeDelegate(replacement)
102 }
103}
104
105private func registrationDuringDelivery() throws {
106 let manager = NotificationCenterManager.shared
107 let original = TestDelegate()
108 let receiver = TestDelegate()
109 receiver.onResponse = { _ in true }
110 original.onResponse = { _ in
111 manager.addDelegate(receiver)
112 return false
113 }
114 manager.addDelegate(original)
115 deliver("handoff")
116 try require(receiver.events == ["handoff"], "A delegate registering during delivery must not miss the pending response")
117 manager.removeDelegate(original)
118 manager.removeDelegate(receiver)
119}
120
121private func responseDuringReplay() throws {
122 let manager = NotificationCenterManager.shared
123 deliver("first")
124 let original = TestDelegate()
125 original.onResponse = { response in
126 if response.identifier == "first" {
127 deliver("second")
128 return true
129 }
130 return false
131 }
132 manager.addDelegate(original)
133 let receiver = TestDelegate()
134 receiver.onResponse = { _ in true }
135 manager.addDelegate(receiver)
136 try require(receiver.events == ["second"], "Finishing a replay must retain responses received during callbacks")
137 manager.removeDelegate(original)
138 manager.removeDelegate(receiver)
139}
140
141private func concurrentRegistrations() throws {
142 let manager = NotificationCenterManager.shared
143 let delegates = (0..<512).map { _ in
144 let delegate = TestDelegate()
145 delegate.onResponse = { _ in true }
146 return delegate
147 }
148 DispatchQueue.concurrentPerform(iterations: delegates.count) { index in
149 manager.addDelegate(delegates[index])
150 if index.isMultiple(of: 16) {
151 manager.didRegister("during-add")
152 deliver("during-add")
153 }
154 }
155 let before = delegates.map { $0.events.count }
156 manager.didRegister("after-add")
157 for (index, delegate) in delegates.enumerated() {
158 try require(delegate.events.count == before[index] + 1, "Concurrent registration must retain every delegate exactly once")
159 }
160 DispatchQueue.concurrentPerform(iterations: delegates.count) { index in
161 manager.removeDelegate(delegates[index])
162 if index.isMultiple(of: 16) {
163 manager.didRegister("during-remove")
164 deliver("during-remove")
165 }
166 }
167 let removed = delegates.map { $0.events.count }
168 manager.didRegister("after-remove")
169 try require(delegates.map { $0.events.count } == removed, "Removed delegates must not receive new callbacks")
170}
171
172@main
173private enum RegressionTests {
174 static func main() {
175 do {
176 switch CommandLine.arguments.last {
177 case "reentrant": try reentrantCallbacks()
178 case "handoff": try registrationDuringDelivery()
179 case "pending": try responseDuringReplay()
180 case "concurrent": try concurrentRegistrations()
181 default: throw NSError(domain: "NotificationCenterManagerRegression", code: 2)
182 }
183 print("passed")
184 } catch {
185 FileHandle.standardError.write(Data("\(error.localizedDescription)\n".utf8))
186 exit(1)
187 }
188 }
189}
apps/mobile/scripts/notification-center-manager.test.ts+65−0
@@ -0,0 +1,65 @@
1// @effect-diagnostics nodeBuiltinImport:off - Compiles and runs the native dependency regression directly.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeFS from "node:fs";
4import * as NodeOS from "node:os";
5import * as NodePath from "node:path";
6import * as NodeURL from "node:url";
7import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test";
8
9// oxlint-disable-next-line t3code/no-global-process-runtime -- This native integration test uses the host Swift compiler.
10describe.skipIf(NodeOS.platform() !== "darwin")(
11 "NotificationCenterManager native concurrency",
12 () => {
13 let directory: string;
14 let executable: string;
15
16 beforeAll(() => {
17 directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-notifications-test-"));
18 executable = NodePath.join(directory, "notification-regression");
19 const source = NodeFS.readFileSync(
20 new URL(
21 "../node_modules/expo-notifications/ios/ExpoNotifications/Notifications/NotificationCenterManager.swift",
22 import.meta.url,
23 ),
24 "utf8",
25 );
26 const manager = NodePath.join(directory, "NotificationCenterManager.swift");
27 NodeFS.writeFileSync(
28 manager,
29 source.replace(/^import (ExpoModulesCore|UserNotifications)\n/gm, ""),
30 );
31 NodeChildProcess.execFileSync(
32 "swiftc",
33 [
34 "-swift-version",
35 "5",
36 "-sanitize=thread",
37 manager,
38 NodeURL.fileURLToPath(
39 new URL("./fixtures/NotificationCenterManagerRegression.swift", import.meta.url),
40 ),
41 "-o",
42 executable,
43 ],
44 { timeout: 30_000, encoding: "utf8" },
45 );
46 });
47
48 afterAll(() => {
49 if (directory) NodeFS.rmSync(directory, { recursive: true, force: true });
50 });
51
52 it.each([
53 ["reentrant", "allows callbacks to replace delegates without deadlocking"],
54 ["handoff", "delivers responses to delegates registering during delivery"],
55 ["pending", "retains new responses received while replaying pending responses"],
56 ["concurrent", "registers, removes, and broadcasts concurrently without data races"],
57 ])("%s: %s", (name) => {
58 const output = NodeChildProcess.execFileSync(executable, [name], {
59 encoding: "utf8",
60 timeout: 15_000,
61 });
62 expect(output.trim()).toBe("passed");
63 });
64 },
65);
apps/server/src/git/GitManager.ts+1−1
@@ -923,7 +923,7 @@ export const make = Effect.gen(function* () { · 922 unmodified lines
923 } satisfies GitVcsDriver.GitStatusDetails;923 } satisfies GitVcsDriver.GitStatusDetails;
924 const readLocalStatus = Effect.fn("readLocalStatus")(function* (cwd: string) {924 const readLocalStatus = Effect.fn("readLocalStatus")(function* (cwd: string) {
925 const details = yield* gitCore925 const details = yield* gitCore
926 .statusDetailsLocal(cwd)926 .statusDetailsLocal(cwd, { includeDivergence: false })
927 .pipe(927 .pipe(
928 Effect.catchIf(isNotGitRepositoryError, () => Effect.succeed(nonRepositoryStatusDetails)),928 Effect.catchIf(isNotGitRepositoryError, () => Effect.succeed(nonRepositoryStatusDetails)),
929 );929 );
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts+4−1
@@ -1083,7 +1083,10 @@ function codexReplayPreamble(input: { · 1082 unmodified lines
1083 method: "initialize",1083 method: "initialize",
1084 params: {1084 params: {
1085 clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: "0.1.0" },1085 clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: "0.1.0" },
1086 capabilities: { experimentalApi: true },1086 capabilities: {
1087 experimentalApi: true,
1088 optOutNotificationMethods: ["turn/diff/updated"],
1089 },
1087 },1090 },
1088 },1091 },
1089 },1092 },
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+1−0
@@ -173,6 +173,7 @@ const CODEX_CLIENT_INFO = { · 172 unmodified lines
173} as const;173} as const;
174const CODEX_CLIENT_CAPABILITIES = {174const CODEX_CLIENT_CAPABILITIES = {
175 experimentalApi: true,175 experimentalApi: true,
176 optOutNotificationMethods: ["turn/diff/updated"],
176} as const;177} as const;
177 178
178export const CodexProviderCapabilitiesV2 = {179export const CodexProviderCapabilitiesV2 = {
apps/server/src/orchestration-v2/Orchestrator.ts+55−31
@@ -1386,6 +1386,49 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 1385 unmodified lines
1386 });1386 });
1387 });1387 });
1388 1388
1389 const dispatchThreadVisit = Effect.fn("orchestrationV2.dispatch.threadVisit")(function* (
1390 command: Extract<OrchestrationV2Command, { readonly type: "thread.visit" }>,
1391 events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
1392 ) {
1393 const thread = yield* projectionStore
1394 .getThread(command.threadId)
1395 .pipe(
1396 Effect.mapError(
1397 (cause) => new OrchestratorProjectionError({ threadId: command.threadId, cause }),
1398 ),
1399 );
1400 if (thread.deletedAt !== null) {
1401 return yield* new OrchestratorDispatchError({
1402 commandId: command.commandId,
1403 commandType: command.type,
1404 cause: `Thread ${command.threadId} is deleted.`,
1405 });
1406 }
1407 const visitedAt = DateTime.make(command.visitedAt);
1408 if (Option.isNone(visitedAt)) {
1409 return yield* new OrchestratorDispatchError({
1410 commandId: command.commandId,
1411 commandType: command.type,
1412 cause: `Thread ${command.threadId} visit time ${command.visitedAt} is not a valid timestamp.`,
1413 });
1414 }
1415 const movesForward =
1416 thread.lastVisitedAt === null ||
1417 DateTime.toEpochMillis(visitedAt.value) > DateTime.toEpochMillis(thread.lastVisitedAt);
1418 // Viewing a thread changes read state only. Loading its transcript (or
1419 // bumping updatedAt) makes a routine read receipt scale with its history.
1420 yield* emit(
1421 events,
1422 command,
1423 )({
1424 type: "thread.visited",
1425 threadId: command.threadId,
1426 providerInstanceId: thread.providerInstanceId,
1427 occurredAt: yield* DateTime.now,
1428 payload: movesForward ? { ...thread, lastVisitedAt: visitedAt.value } : thread,
1429 });
1430 });
1431
1389 const dispatchThreadMutation = Effect.fn("orchestrationV2.dispatch.threadMutation")(function* (1432 const dispatchThreadMutation = Effect.fn("orchestrationV2.dispatch.threadMutation")(function* (
1390 command: Extract<1433 command: Extract<
1391 OrchestrationV2Command,1434 OrchestrationV2Command,
@@ -1401,7 +1444,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 9 unmodified lines
1401 | "thread.pin"1444 | "thread.pin"
1402 | "thread.unpin"1445 | "thread.unpin"
1403 | "thread.pin.reorder"1446 | "thread.pin.reorder"
1404 | "thread.visit"
1405 | "thread.mark-unread"1447 | "thread.mark-unread"
1406 | "thread.metadata.update"1448 | "thread.metadata.update"
1407 | "thread.title.regeneration.complete"1449 | "thread.title.regeneration.complete"
@@ -1548,18 +1590,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 140 unmodified lines
1548 }1590 }
1549 snoozedUntil = parsedSnoozedUntil.value;1591 snoozedUntil = parsedSnoozedUntil.value;
1550 }1592 }
1551 let visitedAt: DateTime.Utc | null = null;
1552 if (command.type === "thread.visit") {
1553 const parsedVisitedAt = DateTime.make(command.visitedAt);
1554 if (Option.isNone(parsedVisitedAt)) {
1555 return yield* new OrchestratorDispatchError({
1556 commandId: command.commandId,
1557 commandType: command.type,
1558 cause: `Thread ${command.threadId} visit time ${command.visitedAt} is not a valid timestamp.`,
1559 });
1560 }
1561 visitedAt = parsedVisitedAt.value;
1562 }
1563 let markUnreadVisitedAt: DateTime.Utc | null = null;1593 let markUnreadVisitedAt: DateTime.Utc | null = null;
1564 if (command.type === "thread.mark-unread") {1594 if (command.type === "thread.mark-unread") {
1565 const latestRunCompletedAt = projection.runs.at(-1)?.completedAt ?? null;1595 const latestRunCompletedAt = projection.runs.at(-1)?.completedAt ?? null;
@@ -1678,17 +1708,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 112 unmodified lines
1678 updatedAt: keyUnchanged ? thread.updatedAt : now,1708 updatedAt: keyUnchanged ? thread.updatedAt : now,
1679 };1709 };
1680 }1710 }
1681 // Visited tracking records read state only; it must not bump updatedAt,
1682 // otherwise viewing a thread would count as activity (and re-trigger the
1683 // clients that dispatch thread.visit whenever updatedAt advances).
1684 case "thread.visit": {
1685 const previousVisitedAt = thread.lastVisitedAt;
1686 const movesForward =
1687 visitedAt !== null &&
1688 (previousVisitedAt === null ||
1689 DateTime.toEpochMillis(visitedAt) > DateTime.toEpochMillis(previousVisitedAt));
1690 return movesForward ? { ...thread, lastVisitedAt: visitedAt } : thread;
1691 }
1692 case "thread.mark-unread":1711 case "thread.mark-unread":
1693 return { ...thread, lastVisitedAt: markUnreadVisitedAt };1712 return { ...thread, lastVisitedAt: markUnreadVisitedAt };
1694 case "thread.metadata.update":1713 case "thread.metadata.update":
@@ -1751,8 +1770,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 56 unmodified lines
1751 return "thread.unpinned" as const;1770 return "thread.unpinned" as const;
1752 case "thread.pin.reorder":1771 case "thread.pin.reorder":
1753 return "thread.pin-reordered" as const;1772 return "thread.pin-reordered" as const;
1754 case "thread.visit":
1755 return "thread.visited" as const;
1756 case "thread.mark-unread":1773 case "thread.mark-unread":
1757 return "thread.marked-unread" as const;1774 return "thread.marked-unread" as const;
1758 case "thread.metadata.update":1775 case "thread.metadata.update":
@@ -6921,16 +6938,24 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 5162 unmodified lines
6921 case "thread.create":6938 case "thread.create":
6922 yield* dispatchThreadCreate(command, events);6939 yield* dispatchThreadCreate(command, events);
6923 break;6940 break;
6941 case "thread.visit":
6942 yield* dispatchThreadVisit(command, events);
6943 break;
6924 case "thread.auto-settle": {6944 case "thread.auto-settle": {
6925 // Automatic settlement (#8600): the sweep evaluated a shell snapshot,6945 // Automatic settlement (#8600): the sweep evaluated a shell snapshot,
6926 // so re-check against the live thread before settling. Any change6946 // so re-check against the live thread before settling. Any change
6927 // after the snapshot — or any explicit override, including the6947 // after the snapshot — or any explicit override, including the
6928 // un-settle button's "active" — wins over the sweep.6948 // un-settle button's "active" — wins over the sweep.
6929 const projection = yield* loadProjectionForCommand(command);6949 const thread = yield* projectionStore
6950 .getThread(command.threadId)
6951 .pipe(
6952 Effect.mapError(
6953 (cause) => new OrchestratorProjectionError({ threadId: command.threadId, cause }),
6954 ),
6955 );
6930 if (6956 if (
6931 projection.thread.settledOverride !== null ||6957 thread.settledOverride !== null ||
6932 DateTime.toEpochMillis(projection.thread.updatedAt) >6958 DateTime.toEpochMillis(thread.updatedAt) > DateTime.toEpochMillis(command.snapshotAt)
6933 DateTime.toEpochMillis(command.snapshotAt)
6934 ) {6959 ) {
6935 return yield* new OrchestratorDispatchError({6960 return yield* new OrchestratorDispatchError({
6936 commandId: command.commandId,6961 commandId: command.commandId,
@@ -6960,7 +6985,6 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 23 unmodified lines
6960 case "thread.pin":6985 case "thread.pin":
6961 case "thread.unpin":6986 case "thread.unpin":
6962 case "thread.pin.reorder":6987 case "thread.pin.reorder":
6963 case "thread.visit":
6964 case "thread.mark-unread":6988 case "thread.mark-unread":
6965 case "thread.metadata.update":6989 case "thread.metadata.update":
6966 case "thread.title.regeneration.complete":6990 case "thread.title.regeneration.complete":
apps/server/src/orchestration-v2/ProjectionSettlement.test.ts+363−0
@@ -0,0 +1,363 @@
1import { assert, it } from "@effect/vitest";
2import {
3 EventId,
4 MessageId,
5 NodeId,
6 type OrchestrationV2AppThread,
7 type OrchestrationV2Run,
8 ProjectId,
9 ProviderDriverKind,
10 ProviderInstanceId,
11 ProviderThreadId,
12 RunId,
13 RuntimeRequestId,
14 ThreadId,
15 TurnItemId,
16} from "@t3tools/contracts";
17import * as DateTime from "effect/DateTime";
18import * as Effect from "effect/Effect";
19import * as Layer from "effect/Layer";
20import * as Schema from "effect/Schema";
21import * as SqlClient from "effect/unstable/sql/SqlClient";
22import * as Statement from "effect/unstable/sql/Statement";
23
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
25import {
26 ProjectionStoreV2,
27 layer as projectionStoreLayer,
28 layerMemory,
29} from "./ProjectionStore.ts";
30import { isAutoSettlementCandidate, resolveAutoSettlementAt } from "./ThreadSettlementService.ts";
31
32const SqlLayer = projectionStoreLayer.pipe(Layer.provideMerge(SqlitePersistenceMemory));
33const now = DateTime.makeUnsafe("2026-09-04T12:00:00Z");
34const old = DateTime.subtract(now, { days: 10 });
35const providerInstanceId = ProviderInstanceId.make("codex");
36const modelSelection = { instanceId: providerInstanceId, model: "test-model" };
37const encodeNumbers = Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.Number)));
38
39const createThread = Effect.fn(function* (
40 name: string,
41 overrides: Partial<OrchestrationV2AppThread> = {},
42) {
43 const store = yield* ProjectionStoreV2;
44 const threadId = ThreadId.make(`thread:settlement:${name}`);
45 const thread: OrchestrationV2AppThread = {
46 createdBy: "user",
47 creationSource: "web",
48 id: threadId,
49 projectId: ProjectId.make("project:settlement"),
50 title: name,
51 providerInstanceId,
52 modelSelection,
53 runtimeMode: "full-access",
54 interactionMode: "default",
55 branch: "feature",
56 worktreePath: null,
57 activeProviderThreadId: null,
58 lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId },
59 forkedFrom: null,
60 createdAt: old,
61 updatedAt: old,
62 archivedAt: null,
63 settledOverride: null,
64 settledAt: null,
65 lastVisitedAt: null,
66 deletedAt: null,
67 ...overrides,
68 };
69 yield* store.apply({
70 id: EventId.make(`event:${threadId}`),
71 type: "thread.created",
72 threadId,
73 occurredAt: old,
74 payload: thread,
75 });
76 return threadId;
77});
78
79const createRun = Effect.fn(function* (
80 threadId: ThreadId,
81 status: OrchestrationV2Run["status"] = "completed",
82 ordinal = 1,
83) {
84 const store = yield* ProjectionStoreV2;
85 const runId = RunId.make(`run:${threadId}:${ordinal}`);
86 yield* store.apply({
87 id: EventId.make(`event:${runId}`),
88 type: "run.created",
89 threadId,
90 occurredAt: old,
91 payload: {
92 id: runId,
93 threadId,
94 ordinal,
95 providerInstanceId,
96 modelSelection,
97 providerThreadId: null,
98 userMessageId: MessageId.make(`message:${runId}`),
99 rootNodeId: null,
100 activeAttemptId: null,
101 status,
102 requestedAt: old,
103 startedAt: status === "queued" ? null : old,
104 completedAt: ["completed", "rolled_back", "failed"].includes(status) ? old : null,
105 checkpointId: null,
106 contextHandoffId: null,
107 },
108 });
109 return runId;
110});
111
112const createItem = Effect.fn(function* (
113 threadId: ThreadId,
114 runId: RunId,
115 status: "idle" | "running" | "completed",
116 persistent = false,
117) {
118 const store = yield* ProjectionStoreV2;
119 yield* store.apply({
120 id: EventId.make(`event:item:${runId}`),
121 type: "turn-item.updated",
122 threadId,
123 occurredAt: old,
124 payload: {
125 id: TurnItemId.make(`item:${runId}`),
126 threadId,
127 runId,
128 nodeId: null,
129 providerThreadId: null,
130 providerTurnId: null,
131 nativeItemRef: null,
132 parentItemId: null,
133 ordinal: 1,
134 type: "dynamic_tool",
135 status,
136 title: null,
137 startedAt: old,
138 completedAt: status === "completed" ? old : null,
139 updatedAt: old,
140 toolName: "background",
141 input: persistent ? { persistent: true } : null,
142 output: null,
143 },
144 });
145});
146
147for (const [name, testLayer] of [
148 ["sql", SqlLayer],
149 ["memory", layerMemory],
150] as const) {
151 it.effect(
152 `${name}: discovers settlement work with the same activity and background semantics as the shell`,
153 () =>
154 Effect.gen(function* () {
155 const store = yield* ProjectionStoreV2;
156 const idle = yield* createThread("idle");
157 const completed = yield* createThread("completed");
158 yield* createRun(completed);
159 for (const [name, overrides] of [
160 ["archived", { archivedAt: old }],
161 ["deleted", { deletedAt: old }],
162 ["settled", { settledOverride: "settled" }],
163 ["unsettled", { settledOverride: "active" }],
164 ["pinned", { pinnedAt: old }],
165 ] satisfies ReadonlyArray<readonly [string, Partial<OrchestrationV2AppThread>]>) {
166 yield* createRun(yield* createThread(name, overrides));
167 }
168 for (const status of ["preparing", "starting", "running", "waiting"] as const) {
169 yield* createRun(yield* createThread(status), status);
170 }
171 const queued = yield* createThread("queued");
172 yield* createRun(queued, "queued");
173 const blocked = yield* createThread("blocked");
174 yield* store.apply({
175 id: EventId.make("event:settlement:request"),
176 type: "runtime-request.updated",
177 threadId: blocked,
178 occurredAt: old,
179 payload: {
180 id: RuntimeRequestId.make("request:settlement"),
181 nodeId: NodeId.make("node:settlement"),
182 providerTurnId: null,
183 nativeRequestRef: null,
184 kind: "user_input",
185 status: "pending",
186 responseCapability: { type: "not_resumable", reason: "Process stopped" },
187 createdAt: old,
188 resolvedAt: null,
189 },
190 });
191 const recent = yield* createThread("recent-message");
192 yield* createRun(recent);
193 yield* store.apply({
194 id: EventId.make("event:settlement:message"),
195 type: "message.updated",
196 threadId: recent,
197 occurredAt: now,
198 payload: {
199 createdBy: "user",
200 creationSource: "web",
201 id: MessageId.make("message:settlement:recent"),
202 threadId: recent,
203 runId: null,
204 nodeId: null,
205 role: "user",
206 text: "Next turn",
207 attachments: [],
208 streaming: false,
209 createdAt: now,
210 updatedAt: now,
211 },
212 });
213 yield* createRun(
214 yield* createThread("snoozed", {
215 snoozedUntil: DateTime.add(now, { days: 1 }),
216 snoozedAt: now,
217 }),
218 );
219 const woke = yield* createThread("woke", {
220 snoozedUntil: DateTime.add(now, { days: 1 }),
221 snoozedAt: DateTime.subtract(old, { days: 1 }),
222 });
223 yield* createRun(woke);
224 const background = yield* createThread("idle-background");
225 yield* createItem(background, yield* createRun(background), "idle");
226 const persistent = yield* createThread("persistent-monitor");
227 yield* createItem(persistent, yield* createRun(persistent), "running", true);
228 const rolledBack = yield* createThread("rolled-back-background");
229 yield* createItem(rolledBack, yield* createRun(rolledBack, "rolled_back"), "running");
230 yield* createRun(rolledBack, "completed", 2);
231 const roster = yield* createThread("provider-roster");
232 yield* createRun(roster);
233 yield* store.apply({
234 id: EventId.make("event:settlement:roster"),
235 type: "provider-thread.updated",
236 threadId: roster,
237 occurredAt: old,
238 payload: {
239 id: ProviderThreadId.make("provider-thread:settlement"),
240 appThreadId: roster,
241 ownerNodeId: null,
242 driver: ProviderDriverKind.make("codex"),
243 providerInstanceId,
244 providerSessionId: null,
245 nativeThreadRef: null,
246 nativeConversationHeadRef: null,
247 status: "idle",
248 firstRunOrdinal: null,
249 lastRunOrdinal: null,
250 handoffIds: [],
251 forkedFrom: null,
252 createdAt: old,
253 updatedAt: old,
254 pendingBackgroundTasks: [{ taskId: "running-task" }],
255 },
256 });
257 const candidates = yield* store.getSettlementCandidates();
258 const shell = yield* store.getShellSnapshot({ location: "active" });
259 const eligible = candidates.filter((thread) =>
260 isAutoSettlementCandidate(thread, DateTime.toEpochMillis(now)),
261 );
262 assert.deepEqual(
263 new Set(eligible.map((thread) => thread.id)),
264 new Set([idle, completed, queued, woke, persistent, rolledBack]),
265 );
266 assert.deepEqual(
267 new Set(eligible.map((thread) => thread.id)),
268 new Set(
269 shell.threads
270 .filter((thread) => isAutoSettlementCandidate(thread, DateTime.toEpochMillis(now)))
271 .map((thread) => thread.id),
272 ),
273 );
274 for (const candidate of candidates) {
275 const expected = shell.threads.find((thread) => thread.id === candidate.id)!;
276 assert.deepEqual(candidate.pendingBackgroundTasks, expected.pendingBackgroundTasks);
277 const settings = {
278 pullRequest: null,
279 nowMs: DateTime.toEpochMillis(now),
280 autoSettleAfterDays: 7,
281 autoSettleOnMerge: false,
282 };
283 assert.deepEqual(
284 resolveAutoSettlementAt({ ...settings, thread: candidate }),
285 resolveAutoSettlementAt({ ...settings, thread: expected }),
286 );
287 }
288 assert.equal(
289 DateTime.formatIso((yield* store.getThread(completed)).createdAt),
290 DateTime.formatIso(old),
291 );
292 assert.equal(
293 (yield* store.getThread(ThreadId.make("missing")).pipe(Effect.flip))._tag,
294 "ProjectionStoreThreadNotFoundError",
295 );
296 }).pipe(Effect.provide(testLayer)),
297 );
298}
299
300it.effect(
301 "reads settlement candidates and thread metadata without loading historical or archived payloads",
302 () =>
303 Effect.gen(function* () {
304 const store = yield* ProjectionStoreV2;
305 const sql = yield* SqlClient.SqlClient;
306 const source = yield* createThread("historical-source", { archivedAt: old });
307 const sourceRun = yield* createRun(source);
308 const threadId = yield* createThread("fork", {
309 forkedFrom: { type: "run", threadId: source, runId: sourceRun },
310 });
311 const runId = yield* createRun(threadId);
312 yield* createItem(threadId, runId, "completed");
313 yield* sql`UPDATE orchestration_v2_projection_turn_items SET payload_json = '{broken' WHERE thread_id = ${threadId}`;
314 yield* sql`UPDATE orchestration_v2_projection_runs SET payload_json = '{broken' WHERE thread_id = ${source}`;
315 yield* sql`INSERT INTO orchestration_v2_projection_turn_items
316 SELECT 'history:' || value, i.thread_id, i.run_id, i.node_id, i.provider_thread_id, i.provider_turn_id,
317 i.parent_item_id, i.ordinal + value, i.type, i.status, i.updated_at, i.payload_json
318 FROM orchestration_v2_projection_turn_items i, json_each(${encodeNumbers(Array.from({ length: 2000 }, (_, i) => i + 1))})
319 WHERE i.thread_id = ${threadId}`;
320 const queries: Array<readonly [string, ReadonlyArray<unknown>]> = [];
321 const record: Statement.Transformer = (statement) =>
322 Effect.sync(() => {
323 queries.push(statement.compile());
324 return statement;
325 });
326 const candidates = yield* store
327 .getSettlementCandidates()
328 .pipe(Effect.provideService(Statement.CurrentTransformer, record));
329 assert.deepEqual(
330 candidates.map((thread) => thread.id),
331 [threadId],
332 );
333 assert.isTrue(queries.every(([query]) => !query.includes("COUNT(")));
334 const pendingQuery = queries.find(([query]) =>
335 query.includes("FROM orchestration_v2_projection_turn_items"),
336 );
337 assert.isDefined(pendingQuery);
338 const metadata = yield* store.getThread(threadId);
339 assert.equal(metadata.id, threadId);
340 assert.equal((yield* store.getThreadProjection(threadId).pipe(Effect.exit))._tag, "Failure");
341 const plan = yield* sql.unsafe<{ readonly detail: string }>(
342 `EXPLAIN QUERY PLAN ${pendingQuery![0]}`,
343 pendingQuery![1],
344 );
345 assert.isTrue(plan.some((row) => row.detail.includes("turn_items_shell_pending_idx")));
346 const countsPlan = yield* sql<{ readonly detail: string }>`EXPLAIN QUERY PLAN
347 SELECT thread_id, run_id, COUNT(*) FROM orchestration_v2_projection_turn_items
348 WHERE thread_id = ${threadId} AND run_id IS NOT NULL GROUP BY thread_id, run_id`;
349 assert.isTrue(
350 countsPlan.some((row) =>
351 row.detail.includes(
352 "COVERING INDEX orchestration_v2_projection_turn_items_thread_run_idx",
353 ),
354 ),
355 );
356 assert.isFalse(countsPlan.some((row) => row.detail.includes("TEMP B-TREE")));
357 const messagePlan = yield* sql<{ readonly detail: string }>`EXPLAIN QUERY PLAN
358 SELECT updated_at FROM orchestration_v2_projection_messages
359 WHERE thread_id = ${threadId} AND role = 'user' ORDER BY updated_at DESC, message_id DESC LIMIT 1`;
360 assert.isTrue(messagePlan.some((row) => row.detail.includes("messages_latest_user_idx")));
361 assert.isFalse(messagePlan.some((row) => row.detail.includes("TEMP B-TREE")));
362 }).pipe(Effect.provide(SqlLayer)),
363);
apps/server/src/orchestration-v2/ProjectionStore.ts+192−0
@@ -1,4 +1,5 @@
1import type {1import type {
2 OrchestrationV2AppThread,
2 OrchestrationV2ConversationMessage,3 OrchestrationV2ConversationMessage,
3 OrchestrationV2DomainEvent,4 OrchestrationV2DomainEvent,
4 OrchestrationV2ProjectedTurnItem,5 OrchestrationV2ProjectedTurnItem,
@@ -106,6 +107,32 @@ export type ProjectionRecoveryKind = · 101 unmodified lines
106 | "subagent-results"107 | "subagent-results"
107 | "delegated-completions";108 | "delegated-completions";
108 109
110/** Thread activity needed by settlement, without transcript or fork history. */
111export type ProjectionSettlementCandidate = Pick<
112 OrchestrationV2ThreadShell,
113 | "id"
114 | "projectId"
115 | "branch"
116 | "worktreePath"
117 | "linkedPullRequest"
118 | "createdAt"
119 | "updatedAt"
120 | "archivedAt"
121 | "settledOverride"
122 | "pinnedAt"
123 | "snoozedUntil"
124 | "snoozedAt"
125 | "latestRunId"
126 | "latestRunRequestedAt"
127 | "latestRunStartedAt"
128 | "latestRunCompletedAt"
129 | "latestUserMessageAt"
130 | "status"
131 | "activityRunStatus"
132 | "pendingRuntimeRequest"
133 | "pendingBackgroundTasks"
134>;
135
109const ProjectionCheckpointContext = Schema.Struct({136const ProjectionCheckpointContext = Schema.Struct({
110 runs: Schema.Array(137 runs: Schema.Array(
111 OrchestrationV2RunJsonSchema.mapFields(({ id, ordinal, status }) => ({ id, ordinal, status })),138 OrchestrationV2RunJsonSchema.mapFields(({ id, ordinal, status }) => ({ id, ordinal, status })),
@@ -143,6 +170,13 @@ export interface ProjectionStoreV2Shape { · 31 unmodified lines
143 readonly getThreadShell: (170 readonly getThreadShell: (
144 threadId: ThreadId,171 threadId: ThreadId,
145 ) => Effect.Effect<OrchestrationV2ThreadShell | null, ProjectionStoreV2Error>;172 ) => Effect.Effect<OrchestrationV2ThreadShell | null, ProjectionStoreV2Error>;
173 readonly getThread: (
174 threadId: ThreadId,
175 ) => Effect.Effect<OrchestrationV2AppThread, ProjectionStoreV2Error>;
176 readonly getSettlementCandidates: () => Effect.Effect<
177 ReadonlyArray<ProjectionSettlementCandidate>,
178 ProjectionStoreV2Error
179 >;
146 readonly getThreadProjection: (180 readonly getThreadProjection: (
147 threadId: ThreadId,181 threadId: ThreadId,
148 ) => Effect.Effect<OrchestrationV2ThreadProjection, ProjectionStoreV2Error>;182 ) => Effect.Effect<OrchestrationV2ThreadProjection, ProjectionStoreV2Error>;
@@ -585,6 +619,18 @@ type ShellRunRow = { · 436 unmodified lines
585 readonly ordinal: number;619 readonly ordinal: number;
586};620};
587 621
622type SettlementThreadRow = Pick<
623 ShellThreadRow,
624 | "thread_id"
625 | "payload_json"
626 | "latest_run_id"
627 | "latest_run_status"
628 | "latest_run_requested_at"
629 | "latest_run_started_at"
630 | "latest_run_completed_at"
631 | "latest_user_message_at"
632>;
633
588type ShellRunItemCountRow = {634type ShellRunItemCountRow = {
589 readonly thread_id: string;635 readonly thread_id: string;
590 readonly run_id: string;636 readonly run_id: string;
@@ -2954,6 +3000,24 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = · 2363 unmodified lines
2954 const getThreadProjection: ProjectionStoreV2Shape["getThreadProjection"] = (threadId) =>3000 const getThreadProjection: ProjectionStoreV2Shape["getThreadProjection"] = (threadId) =>
2955 readProjection(threadId, new Set());3001 readProjection(threadId, new Set());
2956 3002
3003 const getThread: ProjectionStoreV2Shape["getThread"] = (threadId) =>
3004 Effect.gen(function* () {
3005 const rows = yield* sql<PayloadRow>`
3006 SELECT payload_json FROM orchestration_v2_projection_threads
3007 WHERE thread_id = ${threadId}
3008 `;
3009 if (rows[0] === undefined) {
3010 return yield* new ProjectionStoreThreadNotFoundError({ threadId });
3011 }
3012 return yield* decodeThreadPayload(rows[0].payload_json);
3013 }).pipe(
3014 Effect.mapError((cause) =>
3015 isProjectionStoreThreadNotFoundError(cause)
3016 ? cause
3017 : new ProjectionStoreReadError({ threadId, cause }),
3018 ),
3019 );
3020
2957 const getCheckpointContext: ProjectionStoreV2Shape["getCheckpointContext"] = (threadId) =>3021 const getCheckpointContext: ProjectionStoreV2Shape["getCheckpointContext"] = (threadId) =>
2958 sql3022 sql
2959 .withTransaction(3023 .withTransaction(
@@ -3320,6 +3384,104 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = · 360 unmodified lines
3320 return { providerThreadsByThreadId, pendingTurnItemsByThreadId };3384 return { providerThreadsByThreadId, pendingTurnItemsByThreadId };
3321 });3385 });
3322 3386
3387 const getSettlementCandidates: ProjectionStoreV2Shape["getSettlementCandidates"] = () =>
3388 sql
3389 .withTransaction(
3390 Effect.gen(function* () {
3391 // Settlement needs activity timestamps, not item counts, run history,
3392 // provider sessions or inherited fork rows. Reject parked and busy
3393 // threads before loading the remaining candidates' background work.
3394 const rows = yield* sql<SettlementThreadRow>`
3395 SELECT t.thread_id, t.payload_json,
3396 r.run_id AS latest_run_id,
3397 r.status AS latest_run_status,
3398 r.requested_at AS latest_run_requested_at,
3399 json_extract(r.payload_json, '$.startedAt') AS latest_run_started_at,
3400 r.completed_at AS latest_run_completed_at,
3401 (
3402 SELECT message.updated_at
3403 FROM orchestration_v2_projection_messages message
3404 WHERE message.thread_id = t.thread_id AND message.role = 'user'
3405 ORDER BY message.updated_at DESC, message.message_id DESC
3406 LIMIT 1
3407 ) AS latest_user_message_at
3408 FROM orchestration_v2_projection_threads t
3409 LEFT JOIN orchestration_v2_projection_runs r ON r.run_id = (
3410 SELECT latest.run_id FROM orchestration_v2_projection_runs latest
3411 WHERE latest.thread_id = t.thread_id
3412 ORDER BY latest.ordinal DESC, latest.run_id DESC
3413 LIMIT 1
3414 )
3415 WHERE t.deleted_at IS NULL
3416 AND json_extract(t.payload_json, '$.archivedAt') IS NULL
3417 AND json_extract(t.payload_json, '$.settledOverride') IS NULL
3418 AND json_extract(t.payload_json, '$.pinnedAt') IS NULL
3419 AND NOT EXISTS (
3420 SELECT 1 FROM orchestration_v2_projection_runs active
3421 WHERE active.thread_id = t.thread_id
3422 AND active.status IN ('preparing', 'starting', 'running', 'waiting')
3423 )
3424 AND NOT EXISTS (
3425 SELECT 1 FROM orchestration_v2_projection_runtime_requests request
3426 WHERE request.thread_id = t.thread_id AND request.status = 'pending'
3427 )
3428 ORDER BY t.updated_at ASC, t.thread_id ASC
3429 `;
3430 if (rows.length === 0) return [];
3431 const threadIds = rows.map((row) => ThreadId.make(row.thread_id));
3432 const { providerThreadsByThreadId, pendingTurnItemsByThreadId } =
3433 yield* pendingBackgroundDataByThreadId({
3434 providerThreadRows: yield* selectShellProviderThreadRows(threadIds),
3435 pendingTurnItemRows: yield* selectShellPendingTurnItemRows(threadIds),
3436 });
3437 return yield* Effect.forEach(rows, (row) =>
3438 Effect.gen(function* () {
3439 const thread = yield* decodeThreadPayload(row.payload_json);
3440 const status = shellStatusFromStoredRunStatus(row.latest_run_status);
3441 const latestRunId =
3442 row.latest_run_id === null ? null : RunId.make(row.latest_run_id);
3443 return {
3444 ...thread,
3445 pinnedAt: thread.pinnedAt ?? null,
3446 snoozedUntil: thread.snoozedUntil ?? null,
3447 snoozedAt: thread.snoozedAt ?? null,
3448 status,
3449 latestRunId,
3450 latestRunRequestedAt:
3451 row.latest_run_requested_at === null
3452 ? null
3453 : DateTime.makeUnsafe(row.latest_run_requested_at),
3454 latestRunStartedAt:
3455 row.latest_run_started_at === null
3456 ? null
3457 : DateTime.makeUnsafe(row.latest_run_started_at),
3458 latestRunCompletedAt:
3459 row.latest_run_completed_at === null
3460 ? null
3461 : DateTime.makeUnsafe(row.latest_run_completed_at),
3462 latestUserMessageAt:
3463 row.latest_user_message_at === null
3464 ? null
3465 : DateTime.makeUnsafe(row.latest_user_message_at),
3466 activityRunStatus: null,
3467 pendingRuntimeRequest: null,
3468 pendingBackgroundTasks: derivePendingBackgroundWork({
3469 latestRun:
3470 latestRunId === null || status === "idle"
3471 ? null
3472 : { id: latestRunId, ordinal: 0, status },
3473 providerThreads: providerThreadsByThreadId.get(thread.id) ?? [],
3474 turnItems: pendingTurnItemsByThreadId.get(thread.id) ?? [],
3475 activeProviderThreadId: thread.activeProviderThreadId,
3476 hasActiveRun: false,
3477 }),
3478 } satisfies ProjectionSettlementCandidate;
3479 }),
3480 );
3481 }),
3482 )
3483 .pipe(Effect.mapError((cause) => new ProjectionStoreSetupError({ cause })));
3484
3323 const shellThreadStateFromRow = (input: {3485 const shellThreadStateFromRow = (input: {
3324 readonly row: ShellThreadRow;3486 readonly row: ShellThreadRow;
3325 readonly runOrdinalsByThreadId: ReadonlyMap<ThreadId, Map<RunId, number>>;3487 readonly runOrdinalsByThreadId: ReadonlyMap<ThreadId, Map<RunId, number>>;
@@ -3566,6 +3728,8 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = · 240 unmodified lines
3566 apply,3728 apply,
3567 getShellSnapshot,3729 getShellSnapshot,
3568 getThreadShell,3730 getThreadShell,
3731 getThread,
3732 getSettlementCandidates,
3569 getThreadProjection,3733 getThreadProjection,
3570 getCheckpointContext,3734 getCheckpointContext,
3571 getRecoveryThreadIds,3735 getRecoveryThreadIds,
@@ -3638,6 +3802,34 @@ export const layerMemory: Layer.Layer<ProjectionStoreV2> = Layer.effect( · 66 unmodified lines
3638 .pipe(Effect.map(threadShellFromProjection));3802 .pipe(Effect.map(threadShellFromProjection));
3639 return shell.deletedAt === null ? shell : null;3803 return shell.deletedAt === null ? shell : null;
3640 }),3804 }),
3805 getThread: (threadId) =>
3806 Effect.gen(function* () {
3807 const projection = (yield* Ref.get(replayState)).projections.get(threadId);
3808 if (projection === undefined) {
3809 return yield* new ProjectionStoreThreadNotFoundError({ threadId });
3810 }
3811 return projection.thread;
3812 }),
3813 getSettlementCandidates: () =>
3814 Effect.gen(function* () {
3815 const projections = (yield* Ref.get(replayState)).projections;
3816 return [...projections.values()]
3817 .filter(
3818 ({ thread, runs, runtimeRequests }) =>
3819 thread.deletedAt === null &&
3820 thread.archivedAt === null &&
3821 thread.settledOverride === null &&
3822 thread.pinnedAt == null &&
3823 !runs.some(isActivityRunForShell) &&
3824 !runtimeRequests.some((request) => request.status === "pending"),
3825 )
3826 .map(threadShellFromProjection)
3827 .toSorted(
3828 (left, right) =>
3829 DateTime.toEpochMillis(left.updatedAt) - DateTime.toEpochMillis(right.updatedAt) ||
3830 left.id.localeCompare(right.id),
3831 );
3832 }),
3641 getRecoveryThreadIds: (kind) =>3833 getRecoveryThreadIds: (kind) =>
3642 Effect.gen(function* () {3834 Effect.gen(function* () {
3643 const projections = (yield* Ref.get(replayState)).projections;3835 const projections = (yield* Ref.get(replayState)).projections;
apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts+2−0
@@ -216,6 +216,8 @@ it.effect( · 215 unmodified lines
216 apply: () => Effect.void,216 apply: () => Effect.void,
217 getShellSnapshot: () => Effect.die("unused getShellSnapshot"),217 getShellSnapshot: () => Effect.die("unused getShellSnapshot"),
218 getThreadShell: () => Effect.die("unused getThreadShell"),218 getThreadShell: () => Effect.die("unused getThreadShell"),
219 getThread: () => Ref.get(projection).pipe(Effect.map((state) => state.thread)),
220 getSettlementCandidates: () => Effect.die("unused getSettlementCandidates"),
219 getThreadProjection: () => Ref.get(projection),221 getThreadProjection: () => Ref.get(projection),
220 getCheckpointContext: () => Effect.die("not used"),222 getCheckpointContext: () => Effect.die("not used"),
221 getRecoveryThreadIds: () => Effect.die("unused getRecoveryThreadIds"),223 getRecoveryThreadIds: () => Effect.die("unused getRecoveryThreadIds"),
apps/server/src/orchestration-v2/ThreadSettlementService.test.ts+6−2
@@ -34,6 +34,7 @@ import { ServerActivation } from "../serverActivation.ts"; · 33 unmodified lines
34import { ServerSettingsService } from "../serverSettings.ts";34import { ServerSettingsService } from "../serverSettings.ts";
35import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";35import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";
36import { OrchestratorV2, type OrchestratorV2Shape } from "./Orchestrator.ts";36import { OrchestratorV2, type OrchestratorV2Shape } from "./Orchestrator.ts";
37import { ProjectionStoreV2 } from "./ProjectionStore.ts";
37import * as ThreadSettlementService from "./ThreadSettlementService.ts";38import * as ThreadSettlementService from "./ThreadSettlementService.ts";
38 39
39import {40import {
@@ -417,12 +418,15 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options: · 377 unmodified lines
417 getProjectShellsWithoutEnrichment: () =>418 getProjectShellsWithoutEnrichment: () =>
418 Ref.get(snapshots).pipe(Effect.map((snapshot) => snapshot.projects)),419 Ref.get(snapshots).pipe(Effect.map((snapshot) => snapshot.projects)),
419 }),420 }),
420 Layer.mock(OrchestratorV2)({421 Layer.mock(ProjectionStoreV2)({
421 getShellSnapshot: () =>422 getSettlementCandidates: () =>
422 Ref.updateAndGet(snapshotReadCount, (count) => count + 1).pipe(423 Ref.updateAndGet(snapshotReadCount, (count) => count + 1).pipe(
423 Effect.tap((count) => Queue.offer(snapshotReads, count)),424 Effect.tap((count) => Queue.offer(snapshotReads, count)),
424 Effect.andThen(Ref.get(snapshots)),425 Effect.andThen(Ref.get(snapshots)),
426 Effect.map((snapshot) => snapshot.threads),
425 ),427 ),
428 }),
429 Layer.mock(OrchestratorV2)({
426 dispatch,430 dispatch,
427 }),431 }),
428 Layer.mock(GitManager)({432 Layer.mock(GitManager)({
apps/server/src/orchestration-v2/ThreadSettlementService.ts+6−6
@@ -17,6 +17,7 @@ import * as ServerSettings from "../serverSettings.ts"; · 16 unmodified lines
17import { forkParked } from "../serverActivation.ts";17import { forkParked } from "../serverActivation.ts";
18import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";18import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";
19import { OrchestratorV2 } from "./Orchestrator.ts";19import { OrchestratorV2 } from "./Orchestrator.ts";
20import { ProjectionStoreV2, type ProjectionSettlementCandidate } from "./ProjectionStore.ts";
20 21
21export interface SettlementPullRequest {22export interface SettlementPullRequest {
22 readonly state: "open" | "closed" | "merged";23 readonly state: "open" | "closed" | "merged";
@@ -94,7 +95,7 @@ function pullRequestSettles( · 71 unmodified lines
94 95
95/** Cheap checks that run before any source control lookup. */96/** Cheap checks that run before any source control lookup. */
96export function isAutoSettlementCandidate(97export function isAutoSettlementCandidate(
97 thread: OrchestrationV2ThreadShell,98 thread: ProjectionSettlementCandidate,
98 nowMs: number,99 nowMs: number,
99): boolean {100): boolean {
100 if (thread.archivedAt !== null || thread.settledOverride !== null) return false;101 if (thread.archivedAt !== null || thread.settledOverride !== null) return false;
@@ -118,7 +119,7 @@ export function isAutoSettlementCandidate( · 17 unmodified lines
118}119}
119 120
120export function resolveAutoSettlementAt(input: {121export function resolveAutoSettlementAt(input: {
121 readonly thread: OrchestrationV2ThreadShell;122 readonly thread: ProjectionSettlementCandidate;
122 readonly pullRequest: SettlementPullRequest | null;123 readonly pullRequest: SettlementPullRequest | null;
123 readonly nowMs: number;124 readonly nowMs: number;
124 readonly autoSettleAfterDays: number | null;125 readonly autoSettleAfterDays: number | null;
@@ -151,6 +152,7 @@ export class ThreadSettlementServiceV2 extends Context.Service< · 26 unmodified lines
151 152
152export const make = Effect.gen(function* () {153export const make = Effect.gen(function* () {
153 const orchestrator = yield* OrchestratorV2;154 const orchestrator = yield* OrchestratorV2;
155 const projections = yield* ProjectionStoreV2;
154 const snapshots = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;156 const snapshots = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
155 const settingsService = yield* ServerSettings.ServerSettingsService;157 const settingsService = yield* ServerSettings.ServerSettingsService;
156 const git = yield* GitManager.GitManager;158 const git = yield* GitManager.GitManager;
@@ -161,7 +163,7 @@ export const make = Effect.gen(function* () { · 4 unmodified lines
161 const sweep = Effect.fn("ThreadSettlementServiceV2.sweep")(function* (163 const sweep = Effect.fn("ThreadSettlementServiceV2.sweep")(function* (
162 mergedPullRequest: PullRequestService.PullRequestMergeEvent | null,164 mergedPullRequest: PullRequestService.PullRequestMergeEvent | null,
163 ) {165 ) {
164 const snapshot = yield* orchestrator.getShellSnapshot();166 const threads = yield* projections.getSettlementCandidates();
165 const projectShells = yield* snapshots.getProjectShellsWithoutEnrichment();167 const projectShells = yield* snapshots.getProjectShellsWithoutEnrichment();
166 const nowMs = DateTime.toEpochMillis(yield* DateTime.now);168 const nowMs = DateTime.toEpochMillis(yield* DateTime.now);
167 const projects = new Map(projectShells.map((project) => [project.id, project]));169 const projects = new Map(projectShells.map((project) => [project.id, project]));
@@ -169,9 +171,7 @@ export const make = Effect.gen(function* () { · 1 unmodified line
169 // the merged pull request: most threads carry no link and settle from171 // the merged pull request: most threads carry no link and settle from
170 // their branch lookup, which would otherwise wait for the next minute's172 // their branch lookup, which would otherwise wait for the next minute's
171 // sweep on a possibly stale cached answer.173 // sweep on a possibly stale cached answer.
172 const candidates = snapshot.threads.filter((thread) =>174 const candidates = threads.filter((thread) => isAutoSettlementCandidate(thread, nowMs));
173 isAutoSettlementCandidate(thread, nowMs),
174 );
175 // Use the same cwd as the sidebar so both paths share GitManager's PR cache.175 // Use the same cwd as the sidebar so both paths share GitManager's PR cache.
176 const lookupCwdByThreadId = new Map<string, string>();176 const lookupCwdByThreadId = new Map<string, string>();
177 yield* Effect.forEach(177 yield* Effect.forEach(
apps/server/src/orchestration-v2/runtimeLayer.test.ts+32−0
@@ -1634,6 +1634,38 @@ it.layer(SharedApplicationDataPlaneTestLayer)("visited projection", (it) => { · 1633 unmodified lines
1634 })1634 })
1635 .pipe(Effect.flip);1635 .pipe(Effect.flip);
1636 assert.instanceOf(markUnread, OrchestratorDispatchError);1636 assert.instanceOf(markUnread, OrchestratorDispatchError);
1637
1638 // A read receipt must not decode any transcript, including inherited or
1639 // unreadable historical rows. It only advances the thread's watermark.
1640 const sql = yield* SqlClient.SqlClient;
1641 yield* sql`INSERT INTO orchestration_v2_projection_turn_items (
1642 turn_item_id, thread_id, ordinal, type, status, updated_at, payload_json
1643 ) VALUES (
1644 'item:visited:unreadable-history', ${threadId}, 1, 'dynamic_tool', 'completed',
1645 ${visitedAt}, '{broken'
1646 )`;
1647 const nextVisitedAt = "2026-07-24T02:00:00.000Z";
1648 yield* orchestrator.dispatch({
1649 type: "thread.visit",
1650 commandId: CommandId.make("runtime-layer-visited-without-history"),
1651 threadId,
1652 visitedAt: nextVisitedAt,
1653 });
1654 const [watermark] = yield* sql<{ readonly visited_at: string; readonly updated_at: string }>`
1655 SELECT json_extract(payload_json, '$.lastVisitedAt') AS visited_at, updated_at
1656 FROM orchestration_v2_projection_threads WHERE thread_id = ${threadId}
1657 `;
1658 assert.equal(watermark!.visited_at, nextVisitedAt);
1659 assert.equal(watermark!.updated_at, DateTime.formatIso(createdUpdatedAt));
1660 const invalidVisit = yield* orchestrator
1661 .dispatch({
1662 type: "thread.visit",
1663 commandId: CommandId.make("runtime-layer-visited-invalid-timestamp"),
1664 threadId,
1665 visitedAt: "invalid-timestamp",
1666 })
1667 .pipe(Effect.flip);
1668 assert.instanceOf(invalidVisit, OrchestratorDispatchError);
1637 }),1669 }),
1638 );1670 );
1639});1671});
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.128.0","scenario":"message_steering","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"message_steering.ndjson","description":"One active turn receives an immediate turn/steer request."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.128.0","scenario":"message_steering","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"message_steering.ndjson","description":"One active turn receives an immediate turn/steer request."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.128.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex_p","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.128.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex_p","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"multi_turn","metadata":{"source":"codex-app-server-probe","fileName":"multi_turn.ndjson","description":"One thread with two sequential user turns."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"multi_turn","metadata":{"source":"codex-app-server-probe","fileName":"multi_turn.ndjson","description":"One thread with two sequential user turns."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"plan_questions","metadata":{"source":"codex-app-server-probe","fileName":"plan_questions.ndjson","description":"One plan-mode turn that asks a structured clarifying question."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"plan_questions","metadata":{"source":"codex-app-server-probe","fileName":"plan_questions.ndjson","description":"One plan-mode turn that asks a structured clarifying question."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"proposed_plan","metadata":{"source":"codex-app-server-probe","fileName":"proposed_plan.ndjson","description":"One plan-mode turn that emits a proposed plan document."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"proposed_plan","metadata":{"source":"codex-app-server-probe","fileName":"proposed_plan.ndjson","description":"One plan-mode turn that emits a proposed plan document."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson+2−2
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"provider_thread_resume","metadata":{"source":"codex-app-server-probe","fileName":"provider_thread_resume.ndjson","description":"One provider-native thread is started, completed, then resumed by thread id in a fresh app-server session."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"provider_thread_resume","metadata":{"source":"codex-app-server-probe","fileName":"provider_thread_resume.ndjson","description":"One provider-native thread is started, completed, then resumed by thread id in a fresh app-server session."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
@@ -33,7 +33,7 @@ · 27 unmodified lines
33{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019db117-b50d-7a01-a6b6-65e47d749c3e","status":{"type":"idle"}}}}33{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019db117-b50d-7a01-a6b6-65e47d749c3e","status":{"type":"idle"}}}}
34{"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019db117-b50d-7a01-a6b6-65e47d749c3e","turn":{"id":"019db117-b519-7fa3-85c8-7762b26478a7","items":[],"status":"completed","error":null,"startedAt":1776792614,"completedAt":1776792622,"durationMs":8794}}}}34{"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019db117-b50d-7a01-a6b6-65e47d749c3e","turn":{"id":"019db117-b519-7fa3-85c8-7762b26478a7","items":[],"status":"completed","error":null,"startedAt":1776792614,"completedAt":1776792622,"durationMs":8794}}}}
35{"type":"runtime_exit","status":"success"}35{"type":"runtime_exit","status":"success"}
36{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}36{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
37{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}37{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
38{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}38{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
39{"type":"expect_outbound","label":"thread/resume","frame":{"id":2,"method":"thread/resume","params":{"threadId":"019db117-b50d-7a01-a6b6-65e47d749c3e","excludeTurns":true}}}39{"type":"expect_outbound","label":"thread/resume","frame":{"id":2,"method":"thread/resume","params":{"threadId":"019db117-b50d-7a01-a6b6-65e47d749c3e","excludeTurns":true}}}
apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"queued_turn","metadata":{"source":"codex-app-server-probe","fileName":"queued_turn.ndjson","description":"One thread with a queued second user turn."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"queued_turn","metadata":{"source":"codex-app-server-probe","fileName":"queued_turn.ndjson","description":"One thread with a queued second user turn."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"simple","metadata":{"source":"codex-app-server-probe","fileName":"simple.ndjson","description":"One thread and one turn with a deterministic text-only response."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"simple","metadata":{"source":"codex-app-server-probe","fileName":"simple.ndjson","description":"One thread and one turn with a deterministic text-only response."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"subagent","metadata":{"source":"codex-app-server-probe","fileName":"subagent.ndjson","description":"One root turn that asks Codex to spawn two collab agents."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"subagent","metadata":{"source":"codex-app-server-probe","fileName":"subagent.ndjson","description":"One root turn that asks Codex to spawn two collab agents."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"subagent_continue","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"subagent_continue.ndjson","description":"A root turn spawns one native Codex subagent, then a later root turn resumes and messages the same child thread."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"subagent_continue","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"subagent_continue.ndjson","description":"A root turn spawns one native Codex subagent, then a later root turn resumes and messages the same child thread."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0","codexHome":"/tmp/codex-home","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0","codexHome":"/tmp/codex-home","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.141.0","scenario":"subagent_v2","metadata":{"source":"observed-codex-app-server-replay-fixture","fileName":"subagent_v2.ndjson","description":"Agents-v2 Codex subagent spawn observed from a real run: parent emits subAgentActivity(started), child emits a normal turn and final agent message."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.141.0","scenario":"subagent_v2","metadata":{"source":"observed-codex-app-server-replay-fixture","fileName":"subagent_v2.ndjson","description":"Agents-v2 Codex subagent spawn observed from a real run: parent emits subAgentActivity(started), child emits a normal turn and final agent message."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.141.0","codexHome":"/tmp/codex-home","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.141.0","codexHome":"/tmp/codex-home","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.141.0","scenario":"subagent_v2_nested","metadata":{"source":"observed-codex-app-server-replay-fixture","description":"Agents-v2 Codex nested subagent spawn: root emits a subAgentActivity, that child emits another, and that grandchild emits a final nested child."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.141.0","scenario":"subagent_v2_nested","metadata":{"source":"observed-codex-app-server-replay-fixture","description":"Agents-v2 Codex nested subagent spawn: root emits a subAgentActivity, that child emits another, and that grandchild emits a final nested child."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.141.0","codexHome":"/tmp/codex-home","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.141.0","codexHome":"/tmp/codex-home","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"thread_fork_native","metadata":{"source":"hand-authored-replay","description":"One completed source turn, a native thread/fork, and the first target fork turn."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"thread_fork_native","metadata":{"source":"hand-authored-replay","description":"One completed source turn, a native thread/fork, and the first target fork turn."}}
2{"type":"expect_outbound","label":"initialize/source","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize/source","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize/source","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0","codexHome":"/tmp/codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize/source","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0","codexHome":"/tmp/codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized/source","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized/source","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start/source","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start/source","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_fork_native_continue","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_fork_native_continue.ndjson","description":"One source marker turn, a native thread fork, a fork-local marker turn, and a recall turn that requires both contexts."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_fork_native_continue","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_fork_native_continue.ndjson","description":"One source marker turn, a native thread fork, a fork-local marker turn, and a recall turn that requires both contexts."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.124.0-alpha.3","scenario":"thread_fork_native_prior_turn","metadata":{"source":"codex-app-server-live-probe-normalized-for-orchestrator-replay","description":"Two completed source turns, native thread/fork, rollback of the fork to the first source turn, and a fork turn that only sees the first turn.","sourceThreadId":"019dd6ba-2681-7bf0-b051-141b0cbcbb27","forkThreadId":"019dd6ba-47b7-7092-8688-9cf7fe5f6498","firstTurnId":"019dd6ba-268b-7f82-8f4c-9fde0edcfd55","secondTurnId":"019dd6ba-416d-7902-95cf-d4179c3f4738","repeatTurnId":"019dd6ba-47eb-7041-ad45-5abe752c28c9","normalization":"Native frames are live. Initialize frames are normalized to the T3 adapter client info, and a fork-session initialize was inserted to match orchestrator provider-session lifecycle."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.124.0-alpha.3","scenario":"thread_fork_native_prior_turn","metadata":{"source":"codex-app-server-live-probe-normalized-for-orchestrator-replay","description":"Two completed source turns, native thread/fork, rollback of the fork to the first source turn, and a fork turn that only sees the first turn.","sourceThreadId":"019dd6ba-2681-7bf0-b051-141b0cbcbb27","forkThreadId":"019dd6ba-47b7-7092-8688-9cf7fe5f6498","firstTurnId":"019dd6ba-268b-7f82-8f4c-9fde0edcfd55","secondTurnId":"019dd6ba-416d-7902-95cf-d4179c3f4738","repeatTurnId":"019dd6ba-47eb-7041-ad45-5abe752c28c9","normalization":"Native frames are live. Initialize frames are normalized to the T3 adapter client info, and a fork-session initialize was inserted to match orchestrator provider-session lifecycle."}}
2{"type":"expect_outbound","label":"initialize/source","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize/source","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize/source","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.124.0-alpha.3","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize/source","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.124.0-alpha.3","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized/source","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized/source","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_fork_native_siblings","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_fork_native_siblings.ndjson","description":"One source marker turn and two independent native forks that each recall the source plus only their own fork marker."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_fork_native_siblings","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_fork_native_siblings.ndjson","description":"One source marker turn and two independent native forks that each recall the source plus only their own fork marker."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_merge_back_continue","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_merge_back_continue.ndjson","description":"A source thread consumes one fork-delta handoff and later recalls source and transferred context."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_merge_back_continue","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_merge_back_continue.ndjson","description":"A source thread consumes one fork-delta handoff and later recalls source and transferred context."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_merge_back_siblings","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_merge_back_siblings.ndjson","description":"Two sibling fork deltas are merged sequentially into one source provider thread and recalled together."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.137.0","scenario":"thread_merge_back_siblings","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"thread_merge_back_siblings.ndjson","description":"Two sibling fork deltas are merged sequentially into one source provider thread and recalled together."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.137.0 (Mac OS 26.5.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"thread_rollback","metadata":{"source":"codex-app-server-probe","fileName":"thread_rollback.ndjson","description":"One thread completes two turns, rolls back the most recent turn, then starts another turn."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"thread_rollback","metadata":{"source":"codex-app-server-probe","fileName":"thread_rollback.ndjson","description":"One thread completes two turns, rolls back the most recent turn, then starts another turn."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"todo_list","metadata":{"source":"codex-app-server-probe","fileName":"todo_list.ndjson","description":"One turn that asks Codex to emit progress through update_plan."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"todo_list","metadata":{"source":"codex-app-server-probe","fileName":"todo_list.ndjson","description":"One turn that asks Codex to emit progress through update_plan."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"tool_call_read_only_on_request","metadata":{"source":"codex-app-server-probe","fileName":"tool_call_read_only_on_request.ndjson","description":"Write a small probe file with read-only full filesystem visibility and on-request approvals."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"tool_call_read_only_on_request","metadata":{"source":"codex-app-server-probe","fileName":"tool_call_read_only_on_request.ndjson","description":"Write a small probe file with read-only full filesystem visibility and on-request approvals."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"tool_call_restricted_granular","metadata":{"source":"codex-app-server-probe","fileName":"tool_call_restricted_granular.ndjson","description":"Write a small probe file with restricted read access and granular approval flags enabled."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"tool_call_restricted_granular","metadata":{"source":"codex-app-server-probe","fileName":"tool_call_restricted_granular.ndjson","description":"Write a small probe file with restricted read access and granular approval flags enabled."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"tool_call_workspace_never","metadata":{"source":"codex-app-server-probe","fileName":"tool_call_workspace_never.ndjson","description":"Write a small probe file with workspace-write sandbox policy and never approvals."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"tool_call_workspace_never","metadata":{"source":"codex-app-server-probe","fileName":"tool_call_workspace_never.ndjson","description":"Write a small probe file with workspace-write sandbox policy and never approvals."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"turn_interrupt","metadata":{"source":"codex-app-server-probe","fileName":"turn_interrupt.ndjson","description":"One active turn is interrupted before it finishes naturally."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"turn_interrupt","metadata":{"source":"codex-app-server-probe","fileName":"turn_interrupt.ndjson","description":"One active turn is interrupted before it finishes naturally."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.128.0","scenario":"turn_interrupt_mid_tool","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"turn_interrupt_mid_tool.ndjson","description":"One active turn is interrupted after Codex has already executed a local command."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.128.0","scenario":"turn_interrupt_mid_tool","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"turn_interrupt_mid_tool.ndjson","description":"One active turn is interrupted after Codex has already executed a local command."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.128.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex_p","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.128.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex_p","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson+1−1
@@ -1,5 +1,5 @@
1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"web_search","metadata":{"source":"codex-app-server-probe","fileName":"web_search.ndjson","description":"One turn with a native Codex webSearch item that should become normalized progress."}}1{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"web_search","metadata":{"source":"codex-app-server-probe","fileName":"web_search.ndjson","description":"One turn with a native Codex webSearch item that should become normalized progress."}}
2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}2{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}3{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
apps/server/src/persistence/Migrations.ts+2−0
@@ -70,6 +70,7 @@ import Migration0055 from "./Migrations/055_ScheduledTasks.ts"; · 69 unmodified lines
70import Migration0056 from "./Migrations/056_LegacyV1ImportState.ts";70import Migration0056 from "./Migrations/056_LegacyV1ImportState.ts";
71import Migration0057 from "./Migrations/057_ApplicationEventSequenceIndexes.ts";71import Migration0057 from "./Migrations/057_ApplicationEventSequenceIndexes.ts";
72import Migration0058 from "./Migrations/058_OrchestrationV2RecoveryIndexes.ts";72import Migration0058 from "./Migrations/058_OrchestrationV2RecoveryIndexes.ts";
73import Migration0059 from "./Migrations/059_OrchestrationV2ShellIndexes.ts";
73 74
74/**75/**
75 * Migration loader with all migrations defined inline.76 * Migration loader with all migrations defined inline.
@@ -140,6 +141,7 @@ export const migrationEntries = [ · 64 unmodified lines
140 [56, "LegacyV1ImportState", Migration0056],141 [56, "LegacyV1ImportState", Migration0056],
141 [57, "ApplicationEventSequenceIndexes", Migration0057],142 [57, "ApplicationEventSequenceIndexes", Migration0057],
142 [58, "OrchestrationV2RecoveryIndexes", Migration0058],143 [58, "OrchestrationV2RecoveryIndexes", Migration0058],
144 [59, "OrchestrationV2ShellIndexes", Migration0059],
143] as const;145] as const;
144 146
145export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const);147export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const);
apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts+24−0
@@ -0,0 +1,24 @@
1import * as Effect from "effect/Effect";
2import * as SqlClient from "effect/unstable/sql/SqlClient";
3
4export default Effect.gen(function* () {
5 const sql = yield* SqlClient.SqlClient;
6
7 yield* sql`
8 CREATE INDEX orchestration_v2_projection_turn_items_thread_run_idx
9 ON orchestration_v2_projection_turn_items(thread_id, run_id)
10 `;
11 // Shell background work includes idle items as well as pending/running/waiting.
12 // Keep this predicate aligned with ProjectionStore's pending-item query.
13 yield* sql`
14 CREATE INDEX orchestration_v2_projection_turn_items_shell_pending_idx
15 ON orchestration_v2_projection_turn_items(thread_id, run_id)
16 WHERE type IN ('command_execution', 'dynamic_tool', 'subagent')
17 AND status NOT IN ('completed', 'interrupted', 'failed', 'cancelled')
18 `;
19 yield* sql`
20 CREATE INDEX orchestration_v2_projection_messages_latest_user_idx
21 ON orchestration_v2_projection_messages(thread_id, updated_at DESC, message_id DESC)
22 WHERE role = 'user'
23 `;
24});
apps/server/src/provider/Layers/EventNdjsonLogger.test.ts+16−0
@@ -319,6 +319,22 @@ describe("EventNdjsonLogger", () => { · 318 unmodified lines
319 { method: "thread/realtime/transcript/delta", payload: circularDelta },319 { method: "thread/realtime/transcript/delta", payload: circularDelta },
320 threadId,320 threadId,
321 );321 );
322 yield* native.write({ method: "turn/diff/updated", payload: circularDelta }, threadId);
323 yield* native.write(
324 {
325 event: {
326 direction: "incoming",
327 stage: "decoded",
328 payload: {
329 method: "turn/diff/updated",
330 get params() {
331 throw new Error("unused diff snapshots must not be traversed");
332 },
333 },
334 },
335 },
336 threadId,
337 );
322 yield* native.write(338 yield* native.write(
323 {339 {
324 event: {340 event: {
apps/server/src/provider/Layers/EventNdjsonLogger.ts+1−0
@@ -57,6 +57,7 @@ const transientNativeMethods = new Set([ · 56 unmodified lines
57 "item/reasoning/textDelta",57 "item/reasoning/textDelta",
58 "thread/realtime/outputAudio/delta",58 "thread/realtime/outputAudio/delta",
59 "thread/realtime/transcript/delta",59 "thread/realtime/transcript/delta",
60 "turn/diff/updated",
60]);61]);
61const transientAcpUpdates = new Set(["agent_message_chunk", "agent_thought_chunk"]);62const transientAcpUpdates = new Set(["agent_message_chunk", "agent_thought_chunk"]);
62 63
apps/server/src/relay/AgentAwarenessRelay.test.ts+573−0
@@ -0,0 +1,573 @@
1import { assert, describe, it } from "@effect/vitest";
2import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
3import {
4 EnvironmentId,
5 type OrchestrationV2ThreadShell,
6 ProjectId,
7 ProviderInstanceId,
8 ThreadId,
9} from "@t3tools/contracts";
10import { RelayAgentActivityState } from "@t3tools/contracts/relay";
11import * as DateTime from "effect/DateTime";
12import * as Deferred from "effect/Deferred";
13import * as Effect from "effect/Effect";
14import * as Fiber from "effect/Fiber";
15import * as Option from "effect/Option";
16import * as Ref from "effect/Ref";
17import * as Schema from "effect/Schema";
18import * as Stream from "effect/Stream";
19import * as TestClock from "effect/testing/TestClock";
20import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
21
22import { SecretStoreReadError, ServerSecretStore } from "../auth/ServerSecretStore.ts";
23import {
24 PUBLISH_AGENT_ACTIVITY_SECRET,
25 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
26 RELAY_ISSUER_SECRET,
27 RELAY_URL_SECRET,
28} from "../cloud/config.ts";
29import { ServerEnvironment } from "../environment/ServerEnvironment.ts";
30import { ThreadManagementService } from "../orchestration-v2/ThreadManagementService.ts";
31import { ProjectService } from "../project/ProjectService.ts";
32import {
33 make,
34 makeAgentAwarenessPublishWorker,
35 shouldPublishAgentAwarenessEvent,
36} from "./AgentAwarenessRelay.ts";
37
38const THREAD_ID = ThreadId.make("relay-thread");
39const SECOND_THREAD_ID = ThreadId.make("relay-thread-2");
40const PROJECT_ID = ProjectId.make("relay-project");
41const NOW = "2026-09-04T12:00:00.000Z";
42
43function shell(overrides: Partial<OrchestrationV2ThreadShell> = {}): OrchestrationV2ThreadShell {
44 return {
45 id: THREAD_ID,
46 projectId: PROJECT_ID,
47 title: "Thread",
48 providerInstanceId: ProviderInstanceId.make("codex"),
49 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "test-model" },
50 runtimeMode: "full-access",
51 interactionMode: "default",
52 worktreePath: null,
53 activeProviderThreadId: null,
54 lineage: { rootThreadId: THREAD_ID, parentThreadId: null, relationshipToParent: null },
55 forkedFrom: null,
56 createdBy: "user",
57 creationSource: "web",
58 activeRunId: null,
59 latestVisibleMessage: null,
60 hasActionableProposedPlan: false,
61 itemCount: 0,
62 visibleItemCount: 0,
63 lastVisitedAt: null,
64 deletedAt: null,
65 branch: null,
66 linkedPullRequest: null,
67 status: "running",
68 activityRunStatus: null,
69 pendingRuntimeRequest: null,
70 pendingBackgroundTasks: [],
71 latestRunId: null,
72 latestRunRequestedAt: null,
73 latestRunStartedAt: null,
74 latestRunCompletedAt: null,
75 latestUserMessageAt: null,
76 createdAt: DateTime.makeUnsafe(NOW),
77 updatedAt: DateTime.makeUnsafe(NOW),
78 archivedAt: null,
79 settledOverride: null,
80 settledAt: null,
81 snoozedUntil: null,
82 snoozedAt: null,
83 pinnedAt: null,
84 ...overrides,
85 };
86}
87
88const PublishPayload = Schema.Struct({ state: Schema.NullOr(RelayAgentActivityState) });
89const decodePublishPayload = Schema.decodeUnknownSync(Schema.fromJsonString(PublishPayload));
90const unused = () => Effect.die("Unexpected test dependency call");
91
92const makeTestRelay = Effect.fnUntraced(function* (
93 options: {
94 readonly respond?: (attempt: number) => Response;
95 readonly failSecretRead?: (name: string) => boolean;
96 } = {},
97) {
98 const values = new Map<string, Uint8Array>([
99 [PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("true")],
100 [RELAY_URL_SECRET, new TextEncoder().encode("https://relay.example.test")],
101 [RELAY_ISSUER_SECRET, new TextEncoder().encode("https://relay.example.test")],
102 [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, new TextEncoder().encode("credential-1")],
103 ]);
104 const secretReads: string[] = [];
105 const secrets = ServerSecretStore.of({
106 get: (name) =>
107 Effect.suspend(() => {
108 secretReads.push(name);
109 if (options.failSecretRead?.(name)) {
110 return Effect.fail(
111 new SecretStoreReadError({ resource: name, cause: "temporary read failure" }),
112 );
113 }
114 return Effect.succeed(Option.fromUndefinedOr(values.get(name)));
115 }),
116 create: (name, value) => Effect.sync(() => void values.set(name, value)),
117 set: (name, value) => Effect.sync(() => void values.set(name, value)),
118 remove: (name) => Effect.sync(() => void values.delete(name)),
119 getOrCreateRandom: unused,
120 });
121 const currentShell = yield* Ref.make<OrchestrationV2ThreadShell | null>(shell());
122 const shellReads: ThreadId[] = [];
123 const threads = ThreadManagementService.of({
124 getThreadShell: (threadId) =>
125 Effect.sync(() => shellReads.push(threadId)).pipe(Effect.andThen(Ref.get(currentShell))),
126 getShellSnapshot: unused,
127 ensureLegacyTranscript: unused,
128 dispatch: unused,
129 getThreadProjection: unused,
130 getCheckpointContext: unused,
131 getThreadSnapshot: unused,
132 getThreadSnapshotWindow: unused,
133 getProjectThread: unused,
134 listProjectThreads: unused,
135 sendToThread: unused,
136 waitForThread: unused,
137 interruptThread: unused,
138 getThreadEventSequence: unused,
139 streamStoredEvents: Stream.empty,
140 streamStoredEventsFrom: () => Stream.empty,
141 streamDomainEvents: Stream.empty,
142 });
143 const publications: Array<{
144 readonly url: string;
145 readonly authorization: string | null;
146 readonly state: RelayAgentActivityState | null;
147 }> = [];
148 const fetch: typeof globalThis.fetch = Object.assign(
149 (
150 input: Parameters<typeof globalThis.fetch>[0],
151 init?: Parameters<typeof globalThis.fetch>[1],
152 ) => {
153 const body = init?.body;
154 if (typeof body !== "string" && !(body instanceof Uint8Array)) {
155 return Promise.reject(new Error("Expected a serialized activity publish payload"));
156 }
157 const payload = decodePublishPayload(
158 typeof body === "string" ? body : new TextDecoder().decode(body),
159 );
160 publications.push({
161 url: String(input),
162 authorization: new Headers(init?.headers).get("authorization"),
163 state: payload.state,
164 });
165 return Promise.resolve(
166 options.respond?.(publications.length) ?? Response.json({ ok: true, deliveries: [] }),
167 );
168 },
169 { preconnect: () => {} },
170 );
171 const relay = yield* make.pipe(
172 Effect.provideService(ServerSecretStore, secrets),
173 Effect.provideService(ThreadManagementService, threads),
174 Effect.provideService(ServerEnvironment, {
175 getEnvironmentId: Effect.succeed(EnvironmentId.make("relay-environment")),
176 getDescriptor: unused(),
177 }),
178 Effect.provideService(ProjectService, {
179 create: unused,
180 bootstrap: unused,
181 update: unused,
182 delete: unused,
183 getByWorkspaceRoot: unused,
184 snapshot: unused(),
185 getById: () =>
186 Effect.succeed(
187 Option.some({
188 id: PROJECT_ID,
189 title: "Project",
190 workspaceRoot: "/workspace",
191 defaultModelSelection: null,
192 scripts: [],
193 createdAt: NOW,
194 updatedAt: NOW,
195 deletedAt: null,
196 }),
197 ),
198 }),
199 Effect.provideService(FetchHttpClient.Fetch, fetch),
200 Effect.provide(NodeCrypto.layer),
201 );
202 return { relay, secrets, secretReads, currentShell, shellReads, publications };
203});
204
205describe("AgentAwarenessRelay", () => {
206 it("ignores transcript and tool updates but retains activity and metadata changes", () => {
207 for (const type of [
208 "message.updated",
209 "turn-item.updated",
210 "provider-turn.updated",
211 "thread.visited",
212 "thread.pinned",
213 ] as const) {
214 assert.isFalse(shouldPublishAgentAwarenessEvent({ type }));
215 }
216 for (const type of [
217 "run.created",
218 "run.updated",
219 "runtime-request.updated",
220 "thread.metadata-updated",
221 "thread.model-selection-updated",
222 "thread.provider-switched",
223 "thread.archived",
224 "thread.unarchived",
225 "thread.deleted",
226 ] as const) {
227 assert.isTrue(shouldPublishAgentAwarenessEvent({ type }));
228 }
229 });
230
231 it.effect("coalesces queued updates and reruns a thread dirtied during publishing", () =>
232 Effect.gen(function* () {
233 const started = yield* Deferred.make<void>();
234 const release = yield* Deferred.make<void>();
235 const rerunStarted = yield* Deferred.make<void>();
236 const releaseRerun = yield* Deferred.make<void>();
237 const processed: Array<{ threadId: ThreadId; revision: number }> = [];
238 let revision = 1;
239 const worker = yield* makeAgentAwarenessPublishWorker((threadId) =>
240 Effect.gen(function* () {
241 const currentRevision = revision;
242 const index = processed.length;
243 processed.push({ threadId, revision: currentRevision });
244 if (index === 0) {
245 yield* Deferred.succeed(started, undefined);
246 yield* Deferred.await(release);
247 } else if (threadId === THREAD_ID) {
248 yield* Deferred.succeed(rerunStarted, undefined);
249 yield* Deferred.await(releaseRerun);
250 }
251 }),
252 );
253 yield* worker.enqueue(THREAD_ID);
254 yield* Deferred.await(started);
255 revision = 2;
256 for (let i = 0; i < 100; i++) {
257 yield* worker.enqueue(SECOND_THREAD_ID);
258 yield* worker.enqueue(THREAD_ID);
259 }
260 const drained = yield* Deferred.make<void>();
261 const draining = yield* worker.drain.pipe(
262 Effect.andThen(Deferred.succeed(drained, undefined)),
263 Effect.forkChild,
264 );
265 yield* Deferred.succeed(release, undefined);
266 yield* Deferred.await(rerunStarted);
267 assert.isFalse(yield* Deferred.isDone(drained));
268 yield* Deferred.succeed(releaseRerun, undefined);
269 yield* Fiber.join(draining);
270 assert.deepEqual(processed, [
271 { threadId: THREAD_ID, revision: 1 },
272 { threadId: SECOND_THREAD_ID, revision: 2 },
273 { threadId: THREAD_ID, revision: 2 },
274 ]);
275 }),
276 );
277
278 it.effect("deduplicates state and republishes title changes", () =>
279 Effect.gen(function* () {
280 const { relay, currentShell, publications } = yield* makeTestRelay();
281 yield* relay.publishThread(THREAD_ID);
282 yield* Ref.set(
283 currentShell,
284 shell({ updatedAt: DateTime.makeUnsafe("2026-09-04T13:00:00Z") }),
285 );
286 yield* relay.publishThread(THREAD_ID);
287 assert.equal(publications.length, 1);
288 yield* Ref.set(currentShell, shell({ title: "Renamed thread" }));
289 yield* relay.publishThread(THREAD_ID);
290 assert.equal(publications.length, 2);
291 assert.equal(publications[1]?.state?.threadTitle, "Renamed thread");
292 }),
293 );
294
295 it.effect("stops before shell reads when disabled and republishes after re-enabling", () =>
296 Effect.gen(function* () {
297 const { relay, secrets, secretReads, shellReads, publications } = yield* makeTestRelay();
298 yield* relay.publishThread(THREAD_ID);
299 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
300 const previousReads = secretReads.length;
301 yield* relay.publishThread(THREAD_ID);
302 assert.deepEqual(secretReads.slice(previousReads), [PUBLISH_AGENT_ACTIVITY_SECRET]);
303 assert.equal(shellReads.length, 1);
304 assert.equal(publications.length, 1);
305 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("true"));
306 yield* relay.publishThread(THREAD_ID);
307 assert.equal(publications.length, 2);
308 }),
309 );
310
311 it.effect("republishes unchanged state with fresh credentials after relinking", () =>
312 Effect.gen(function* () {
313 const { relay, secrets, publications } = yield* makeTestRelay();
314 yield* relay.publishThread(THREAD_ID);
315 yield* secrets.set(
316 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
317 new TextEncoder().encode("credential-2"),
318 );
319 yield* relay.publishThread(THREAD_ID);
320 assert.equal(publications.length, 2);
321 assert.equal(publications[0]?.authorization, "Bearer credential-1");
322 assert.equal(publications[1]?.authorization, "Bearer credential-2");
323 yield* secrets.set(
324 RELAY_URL_SECRET,
325 new TextEncoder().encode("https://new-relay.example.test"),
326 );
327 yield* relay.publishThread(THREAD_ID);
328 assert.equal(publications.length, 3);
329 assert.isTrue(publications[2]?.url.startsWith("https://new-relay.example.test/") ?? false);
330 }),
331 );
332
333 it.effect("retries a failed final state without another thread event", () =>
334 Effect.gen(function* () {
335 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
336 respond: (attempt) =>
337 attempt === 2
338 ? new Response("relay unavailable", { status: 503 })
339 : Response.json({ ok: true, deliveries: [] }),
340 });
341 yield* relay.publishThread(THREAD_ID);
342 yield* Ref.set(currentShell, shell({ status: "completed" }));
343 yield* relay.publishThread(THREAD_ID);
344 assert.equal(publications.length, 2);
345 yield* TestClock.adjust("1 second");
346 yield* relay.drain;
347 assert.equal(publications.length, 3);
348 assert.equal(publications[2]?.state?.phase, "completed");
349 yield* TestClock.adjust("1 minute");
350 yield* relay.drain;
351 assert.equal(shellReads.length, 3);
352 }),
353 );
354
355 it.effect("retries the latest shell and credentials with first-completion confirmation", () =>
356 Effect.gen(function* () {
357 const { relay, secrets, currentShell, publications } = yield* makeTestRelay({
358 respond: (attempt) =>
359 attempt === 1
360 ? new Response("relay unavailable", { status: 503 })
361 : Response.json({ ok: true, deliveries: [] }),
362 });
363 yield* relay.publishThread(THREAD_ID);
364 yield* Ref.set(currentShell, shell({ status: "completed", title: "Final title" }));
365 yield* secrets.set(
366 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
367 new TextEncoder().encode("credential-2"),
368 );
369 yield* TestClock.adjust("1 second");
370 yield* relay.drain;
371 assert.equal(publications.length, 1);
372 yield* TestClock.adjust("5 seconds");
373 yield* relay.drain;
374 assert.equal(publications.length, 2);
375 assert.equal(publications[1]?.authorization, "Bearer credential-2");
376 assert.equal(publications[1]?.state?.phase, "completed");
377 assert.equal(publications[1]?.state?.threadTitle, "Final title");
378 }),
379 );
380
381 it.effect("cancels a stale retry when a newer update publishes successfully", () =>
382 Effect.gen(function* () {
383 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
384 respond: (attempt) =>
385 attempt === 1
386 ? new Response("relay unavailable", { status: 503 })
387 : Response.json({ ok: true, deliveries: [] }),
388 });
389 yield* relay.publishThread(THREAD_ID);
390 yield* Ref.set(currentShell, shell({ title: "New title" }));
391 yield* relay.publishThread(THREAD_ID);
392 assert.equal(publications.length, 2);
393 assert.equal(publications[1]?.state?.threadTitle, "New title");
394 yield* TestClock.adjust("1 minute");
395 yield* relay.drain;
396 assert.equal(shellReads.length, 2);
397 }),
398 );
399
400 it.effect.each(["disable", "unlink"] as const)(
401 "stops retries before shell reads after %s",
402 (change) =>
403 Effect.gen(function* () {
404 const { relay, secrets, shellReads, publications } = yield* makeTestRelay({
405 respond: () => new Response("relay unavailable", { status: 503 }),
406 });
407 yield* relay.publishThread(THREAD_ID);
408 if (change === "disable") {
409 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
410 } else {
411 yield* secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET);
412 }
413 yield* TestClock.adjust("1 second");
414 yield* relay.drain;
415 yield* TestClock.adjust("1 minute");
416 yield* relay.drain;
417 assert.equal(publications.length, 1);
418 assert.equal(shellReads.length, 1);
419 }),
420 );
421
422 it.effect("recovers from a transient configuration read failure", () =>
423 Effect.gen(function* () {
424 let failRead = false;
425 const { relay, shellReads, publications } = yield* makeTestRelay({
426 failSecretRead: (name) => failRead && name === PUBLISH_AGENT_ACTIVITY_SECRET,
427 });
428 failRead = true;
429 yield* relay.publishThread(THREAD_ID);
430 assert.equal(shellReads.length, 0);
431 failRead = false;
432 yield* TestClock.adjust("1 second");
433 yield* relay.drain;
434 assert.equal(publications.length, 1);
435 assert.equal(publications[0]?.state?.phase, "running");
436 }),
437 );
438
439 it.effect("bounds retry attempts and resets the budget for a newer update", () =>
440 Effect.gen(function* () {
441 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
442 respond: () => new Response("relay unavailable", { status: 503 }),
443 });
444 yield* relay.publishThread(THREAD_ID);
445 let attempts = 1;
446 for (const delay of [1_000, 2_000, 4_000, 8_000, 16_000]) {
447 yield* TestClock.adjust(delay - 1);
448 yield* relay.drain;
449 assert.equal(publications.length, attempts);
450 yield* TestClock.adjust(1);
451 yield* relay.drain;
452 attempts += 1;
453 assert.equal(publications.length, attempts);
454 }
455 yield* TestClock.adjust("1 minute");
456 yield* relay.drain;
457 assert.equal(publications.length, 6);
458 assert.equal(shellReads.length, 6);
459 yield* Ref.set(currentShell, shell({ title: "New update" }));
460 yield* relay.publishThread(THREAD_ID);
461 yield* TestClock.adjust("1 second");
462 yield* relay.drain;
463 assert.equal(publications.length, 8);
464 assert.equal(publications[7]?.state?.threadTitle, "New update");
465 }),
466 );
467
468 it.effect("retries a confirmed completion without restarting its confirmation window", () =>
469 Effect.gen(function* () {
470 const { relay, currentShell, publications } = yield* makeTestRelay({
471 respond: (attempt) =>
472 attempt <= 2
473 ? new Response("relay unavailable", { status: 503 })
474 : Response.json({ ok: true, deliveries: [] }),
475 });
476 yield* Ref.set(currentShell, shell({ status: "completed" }));
477 yield* relay.publishThread(THREAD_ID);
478 yield* TestClock.adjust("5 seconds");
479 yield* relay.drain;
480 assert.equal(publications.length, 1);
481 yield* TestClock.adjust("1 second");
482 yield* relay.drain;
483 assert.equal(publications.length, 2);
484 yield* TestClock.adjust("2 seconds");
485 yield* relay.drain;
486 assert.equal(publications.length, 3);
487 assert.isTrue(publications.every((publication) => publication.state?.phase === "completed"));
488 yield* TestClock.adjust("1 minute");
489 yield* relay.drain;
490 assert.equal(publications.length, 3);
491 }),
492 );
493
494 it.effect("does not reset an exhausted retry budget for old confirmation timers", () =>
495 Effect.gen(function* () {
496 let unavailable = false;
497 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
498 respond: () =>
499 unavailable
500 ? new Response("relay unavailable", { status: 503 })
501 : Response.json({ ok: true, deliveries: [] }),
502 });
503 yield* relay.publishThread(THREAD_ID);
504 // Leave six obsolete confirmations at 5,000 through 5,005 milliseconds.
505 for (let i = 0; i < 6; i++) {
506 yield* Ref.set(currentShell, null);
507 yield* relay.publishThread(THREAD_ID);
508 yield* Ref.set(currentShell, shell());
509 yield* relay.publishThread(THREAD_ID);
510 yield* TestClock.adjust(1);
511 }
512 unavailable = true;
513 yield* Ref.set(currentShell, shell({ status: "failed" }));
514 yield* relay.publishThread(THREAD_ID);
515 yield* TestClock.adjust(1_000);
516 yield* relay.drain;
517 yield* TestClock.adjust(2_000);
518 yield* relay.drain;
519 yield* TestClock.adjust(1_994);
520 yield* relay.drain;
521 for (let i = 0; i < 5; i++) {
522 yield* TestClock.adjust(1);
523 yield* relay.drain;
524 }
525 const attempts = publications.length;
526 const reads = shellReads.length;
527 yield* TestClock.adjust("1 minute");
528 yield* relay.drain;
529 assert.equal(publications.length, attempts);
530 assert.equal(shellReads.length, reads);
531 }),
532 );
533
534 it.effect("confirms tombstones and cancels stale confirmations when activity recovers", () =>
535 Effect.gen(function* () {
536 const { relay, currentShell, publications } = yield* makeTestRelay();
537 yield* relay.publishThread(THREAD_ID);
538 yield* Ref.set(currentShell, null);
539 yield* relay.publishThread(THREAD_ID);
540 assert.equal(publications.length, 1);
541 yield* Ref.set(currentShell, shell());
542 yield* relay.publishThread(THREAD_ID);
543 yield* TestClock.adjust("5 seconds");
544 yield* relay.drain;
545 assert.equal(publications.length, 1);
546 yield* Ref.set(currentShell, null);
547 yield* relay.publishThread(THREAD_ID);
548 assert.equal(publications.length, 1);
549 yield* TestClock.adjust("5 seconds");
550 yield* relay.drain;
551 assert.equal(publications.length, 2);
552 assert.equal(publications[1]?.state, null);
553 }),
554 );
555
556 it.effect("confirms a first completed state and respects disabling during confirmation", () =>
557 Effect.gen(function* () {
558 const { relay, secrets, currentShell, publications } = yield* makeTestRelay();
559 yield* Ref.set(currentShell, shell({ status: "completed" }));
560 yield* relay.publishThread(THREAD_ID);
561 assert.equal(publications.length, 0);
562 yield* TestClock.adjust("5 seconds");
563 yield* relay.drain;
564 assert.equal(publications[0]?.state?.phase, "completed");
565 yield* Ref.set(currentShell, null);
566 yield* relay.publishThread(THREAD_ID);
567 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
568 yield* TestClock.adjust("5 seconds");
569 yield* relay.drain;
570 assert.equal(publications.length, 1);
571 }),
572 );
573});
apps/server/src/relay/AgentAwarenessRelay.ts+167−26
@@ -23,6 +23,7 @@ import * as Context from "effect/Context"; · 22 unmodified lines
23import * as Crypto from "effect/Crypto";23import * as Crypto from "effect/Crypto";
24import * as DateTime from "effect/DateTime";24import * as DateTime from "effect/DateTime";
25import * as Effect from "effect/Effect";25import * as Effect from "effect/Effect";
26import * as Fiber from "effect/Fiber";
26import * as Layer from "effect/Layer";27import * as Layer from "effect/Layer";
27import * as Option from "effect/Option";28import * as Option from "effect/Option";
28import * as Ref from "effect/Ref";29import * as Ref from "effect/Ref";
@@ -51,6 +52,7 @@ export class AgentAwarenessRelay extends Context.Service< · 22 unmodified lines
51 AgentAwarenessRelay,52 AgentAwarenessRelay,
52 {53 {
53 readonly publishThread: (threadId: ThreadId) => Effect.Effect<void>;54 readonly publishThread: (threadId: ThreadId) => Effect.Effect<void>;
55 readonly drain: Effect.Effect<void>;
54 readonly start: () => Effect.Effect<void, never, Scope.Scope>;56 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
55 }57 }
56>()("t3/relay/AgentAwarenessRelay") {}58>()("t3/relay/AgentAwarenessRelay") {}
@@ -59,13 +61,74 @@ export function eventThreadId(event: OrchestrationV2DomainEvent): ThreadId { · 2 unmodified lines
59 return event.threadId;61 return event.threadId;
60}62}
61 63
62export function shouldPublishAgentAwarenessEvent(_event: OrchestrationV2DomainEvent): boolean {64export function shouldPublishAgentAwarenessEvent(
63 // Publishing is identity-deduplicated below. Watching every V2 event keeps65 event: Pick<OrchestrationV2DomainEvent, "type">,
64 // this consumer correct as the shell projection evolves without duplicating66): boolean {
65 // projection-specific relevance rules here.67 // projectThreadAwarenessV2 reads thread metadata, run status, and pending requests.
66 return true;68 // Message bodies and tool progress cannot change the published activity.
69 switch (event.type) {
70 case "thread.created":
71 case "thread.archived":
72 case "thread.unarchived":
73 case "thread.deleted":
74 case "thread.metadata-updated":
75 case "thread.model-selection-updated":
76 case "thread.provider-switched":
77 case "run.created":
78 case "run.updated":
79 case "runtime-request.updated":
80 return true;
81 case "thread.settled":
82 case "thread.unsettled":
83 case "thread.snoozed":
84 case "thread.unsnoozed":
85 case "thread.pinned":
86 case "thread.unpinned":
87 case "thread.pin-reordered":
88 case "thread.visited":
89 case "thread.marked-unread":
90 case "thread.runtime-mode-updated":
91 case "thread.interaction-mode-updated":
92 case "run-attempt.created":
93 case "run-attempt.updated":
94 case "node.updated":
95 case "subagent.updated":
96 case "provider-session.attached":
97 case "provider-session.updated":
98 case "provider-session.detached":
99 case "provider-thread.updated":
100 case "provider-turn.updated":
101 case "message.updated":
102 case "turn-item.updated":
103 case "plan.updated":
104 case "checkpoint-scope.created":
105 case "checkpoint.captured":
106 case "checkpoint.rollback-requested":
107 case "context-handoff.updated":
108 case "context-transfer.created":
109 case "context-transfer.updated":
110 return false;
111 }
67}112}
68 113
114export const makeAgentAwarenessPublishWorker = Effect.fnUntraced(function* <R>(
115 publish: (threadId: ThreadId) => Effect.Effect<void, never, R>,
116) {
117 const queued = new Set<ThreadId>();
118 const worker = yield* makeDrainableWorker((threadId: ThreadId) =>
119 Effect.sync(() => queued.delete(threadId)).pipe(Effect.andThen(publish(threadId))),
120 );
121 const enqueue = (threadId: ThreadId) =>
122 Effect.suspend(() => {
123 if (queued.has(threadId)) return Effect.void;
124 // Removing the ID when processing starts allows one new queue entry
125 // for updates received while the current snapshot is being published.
126 queued.add(threadId);
127 return worker.enqueue(threadId);
128 }).pipe(Effect.uninterruptible);
129 return { enqueue, drain: worker.drain };
130});
131
69export function agentAwarenessPublishIdentity(state: RelayAgentActivityState | null): string {132export function agentAwarenessPublishIdentity(state: RelayAgentActivityState | null): string {
70 if (state === null) {133 if (state === null) {
71 return "null";134 return "null";
@@ -90,6 +153,7 @@ export function resolveAgentActivityPublishingStartupState(input: { · 18 unmodified lines
90 153
91const RELAY_AGENT_ACTIVITY_DETAIL_MAX_LENGTH = 160;154const RELAY_AGENT_ACTIVITY_DETAIL_MAX_LENGTH = 160;
92const REDACTED_RELAY_AGENT_FAILURE_DETAIL = "The agent run failed.";155const REDACTED_RELAY_AGENT_FAILURE_DETAIL = "The agent run failed.";
156const RELAY_AGENT_ACTIVITY_RETRY_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 16_000] as const;
93 157
94export function sanitizeRelayAgentActivityState(158export function sanitizeRelayAgentActivityState(
95 state: RelayAgentActivityState | null,159 state: RelayAgentActivityState | null,
@@ -268,6 +332,7 @@ export const make = Effect.gen(function* () { · 172 unmodified lines
268 const threads = yield* ThreadManagement.ThreadManagementService;332 const threads = yield* ThreadManagement.ThreadManagementService;
269 const projects = yield* ProjectService.ProjectService;333 const projects = yield* ProjectService.ProjectService;
270 const crypto = yield* Crypto.Crypto;334 const crypto = yield* Crypto.Crypto;
335 const scope = yield* Effect.scope;
271 const cloudLinkKeyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);336 const cloudLinkKeyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
272 const activeSnapshotPublishedRef = yield* Ref.make(false);337 const activeSnapshotPublishedRef = yield* Ref.make(false);
273 const publishedStateByThreadRef = yield* Ref.make(new Map<ThreadId, string>());338 const publishedStateByThreadRef = yield* Ref.make(new Map<ThreadId, string>());
@@ -305,6 +370,9 @@ export const make = Effect.gen(function* () { · 31 unmodified lines
305 transformClient: relayEnvironmentClient(relayConfig.environmentCredential),370 transformClient: relayEnvironmentClient(relayConfig.environmentCredential),
306 }).pipe(Effect.provide(FetchHttpClient.layer));371 }).pipe(Effect.provide(FetchHttpClient.layer));
307 372
373 let cachedRelayClient: Effect.Success<ReturnType<typeof makeRelayClient>> | undefined;
374 let publishedRelayConfig: NonNullable<Effect.Success<typeof readRelayConfig>> | undefined;
375
308 // Deadlines for publishes that need confirmation (tombstones and376 // Deadlines for publishes that need confirmation (tombstones and
309 // first-state completions). The confirming publish is re-enqueued through377 // first-state completions). The confirming publish is re-enqueued through
310 // the same drainable worker as every other publish, so a confirmed378 // the same drainable worker as every other publish, so a confirmed
@@ -312,25 +380,53 @@ export const make = Effect.gen(function* () { · 1 unmodified line
312 // clears the deadline. Assigned after the worker exists.380 // clears the deadline. Assigned after the worker exists.
313 const publishConfirmDeadlines = new Map<ThreadId, number>();381 const publishConfirmDeadlines = new Map<ThreadId, number>();
314 let schedulePublishConfirm: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;382 let schedulePublishConfirm: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;
383 const publishRetries = new Map<
384 ThreadId,
385 { readonly attempts: number; timer: Fiber.Fiber<void> | undefined }
386 >();
387 const cancelPublishRetry = Effect.fnUntraced(function* (threadId: ThreadId) {
388 const retry = publishRetries.get(threadId);
389 publishRetries.delete(threadId);
390 if (retry?.timer !== undefined) yield* Fiber.interrupt(retry.timer);
391 });
392 const cancelPublishRetries = Effect.suspend(() =>
393 Effect.forEach([...publishRetries.keys()], cancelPublishRetry, { discard: true }),
394 );
395 let schedulePublishRetry: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;
396 const resetPublishedConnection = Effect.gen(function* () {
397 publishedRelayConfig = undefined;
398 cachedRelayClient = undefined;
399 publishConfirmDeadlines.clear();
400 yield* Ref.set(publishedStateByThreadRef, new Map());
401 });
315 402
316 const publishThreadUnsafe = Effect.fn("publishThreadUnsafe")(function* (threadId: ThreadId) {403 const publishThreadUnsafe = Effect.fn("publishThreadUnsafe")(function* (threadId: ThreadId) {
317 const publishAgentActivity = yield* readPublishAgentActivityEnabled.pipe(404 const publishAgentActivity = yield* readPublishAgentActivityEnabled;
318 Effect.orElseSucceed(() => false),
319 );
320 if (!publishAgentActivity) {405 if (!publishAgentActivity) {
406 yield* cancelPublishRetries;
407 yield* resetPublishedConnection;
321 yield* Effect.logDebug("agent activity publish skipped; publication disabled", {408 yield* Effect.logDebug("agent activity publish skipped; publication disabled", {
322 threadId,409 threadId,
323 });410 });
324 return;411 return;
325 }412 }
326 const relayConfig = yield* readRelayConfig.pipe(Effect.orElseSucceed(() => null));413 const relayConfig = yield* readRelayConfig;
327 if (!relayConfig) {414 if (!relayConfig) {
415 yield* cancelPublishRetries;
416 yield* resetPublishedConnection;
328 yield* Effect.logDebug("agent activity publish skipped; relay link credentials unavailable", {417 yield* Effect.logDebug("agent activity publish skipped; relay link credentials unavailable", {
329 threadId,418 threadId,
330 });419 });
331 return;420 return;
332 }421 }
333 const relayClient = yield* makeRelayClient(relayConfig);422 if (
423 publishedRelayConfig?.url !== relayConfig.url ||
424 publishedRelayConfig.issuer !== relayConfig.issuer ||
425 publishedRelayConfig.environmentCredential !== relayConfig.environmentCredential
426 ) {
427 yield* resetPublishedConnection;
428 publishedRelayConfig = relayConfig;
429 }
334 const environmentId = yield* serverEnvironment.getEnvironmentId;430 const environmentId = yield* serverEnvironment.getEnvironmentId;
335 431
336 const publishState = (input: {432 const publishState = (input: {
@@ -339,6 +435,10 @@ export const make = Effect.gen(function* () { · 2 unmodified lines
339 readonly reason: string;435 readonly reason: string;
340 }) =>436 }) =>
341 Effect.gen(function* () {437 Effect.gen(function* () {
438 if (cachedRelayClient === undefined) {
439 cachedRelayClient = yield* makeRelayClient(relayConfig);
440 }
441 const relayClient = cachedRelayClient;
342 const proof = yield* makePublishProof({442 const proof = yield* makePublishProof({
343 privateKey: cloudLinkKeyPair.privateKey,443 privateKey: cloudLinkKeyPair.privateKey,
344 relayIssuer: relayConfig.issuer,444 relayIssuer: relayConfig.issuer,
@@ -441,7 +541,6 @@ export const make = Effect.gen(function* () { · 96 unmodified lines
441 if (nowMs < deadline) {541 if (nowMs < deadline) {
442 return;542 return;
443 }543 }
444 publishConfirmDeadlines.delete(threadId);
445 yield* Effect.logInfo("agent activity deferred publish confirmed", {544 yield* Effect.logInfo("agent activity deferred publish confirmed", {
446 environmentId,545 environmentId,
447 threadId,546 threadId,
@@ -471,6 +570,7 @@ export const make = Effect.gen(function* () { · 23 unmodified lines
471 state: snapshot.state,570 state: snapshot.state,
472 reason: snapshot.reason,571 reason: snapshot.reason,
473 });572 });
573 publishConfirmDeadlines.delete(threadId);
474 yield* Ref.update(publishedStateByThreadRef, (publishedStates) => {574 yield* Ref.update(publishedStateByThreadRef, (publishedStates) => {
475 const nextPublishedStates = new Map(publishedStates);575 const nextPublishedStates = new Map(publishedStates);
476 nextPublishedStates.set(threadId, publishIdentity);576 nextPublishedStates.set(threadId, publishIdentity);
@@ -478,17 +578,63 @@ export const make = Effect.gen(function* () { · 1 unmodified line
478 });578 });
479 });579 });
480 580
481 const publishThread: AgentAwarenessRelay["Service"]["publishThread"] = (threadId) =>581 const processThreadPublish = Effect.fnUntraced(function* (threadId: ThreadId) {
482 publishThreadUnsafe(threadId).pipe(582 const retry = publishRetries.get(threadId);
583 if (retry?.timer !== undefined) {
584 const timer = retry.timer;
585 retry.timer = undefined;
586 yield* Fiber.interrupt(timer);
587 }
588 yield* publishThreadUnsafe(threadId).pipe(
589 Effect.tap(() => cancelPublishRetry(threadId)),
483 Effect.catchCause((cause) => {590 Effect.catchCause((cause) => {
591 if (Cause.hasInterruptsOnly(cause)) return Effect.void;
484 return Effect.logWarning("agent activity publish failed", {592 return Effect.logWarning("agent activity publish failed", {
485 threadId,593 threadId,
486 cause: Cause.pretty(cause),594 cause: Cause.pretty(cause),
487 });595 }).pipe(Effect.andThen(schedulePublishRetry(threadId)));
488 }),596 }),
489 Effect.withSpan("AgentAwarenessRelay.publishThread"),597 Effect.withSpan("AgentAwarenessRelay.publishThread"),
490 withRelayClientTracing,598 withRelayClientTracing,
491 );599 );
600 });
601
602 const worker = yield* makeAgentAwarenessPublishWorker(processThreadPublish);
603 const enqueueThreadPublish = (threadId: ThreadId) =>
604 cancelPublishRetry(threadId).pipe(Effect.andThen(worker.enqueue(threadId)));
605 const publishThread: AgentAwarenessRelay["Service"]["publishThread"] = (threadId) =>
606 enqueueThreadPublish(threadId).pipe(Effect.andThen(worker.drain));
607
608 schedulePublishRetry = Effect.fnUntraced(function* (threadId: ThreadId) {
609 const attempts = publishRetries.get(threadId)?.attempts ?? 0;
610 const delayMs = RELAY_AGENT_ACTIVITY_RETRY_DELAYS_MS[attempts];
611 if (delayMs === undefined) {
612 // Keep the exhausted budget until fresh activity or a successful publish
613 // clears it; an old confirmation timer must not start another retry series.
614 yield* Effect.logWarning("agent activity publish retry budget exhausted", { threadId });
615 return;
616 }
617 const retry = {
618 attempts: attempts + 1,
619 timer: undefined as Fiber.Fiber<void> | undefined,
620 };
621 publishRetries.set(threadId, retry);
622 const timer = yield* Effect.sleep(delayMs).pipe(
623 Effect.andThen(
624 Effect.suspend(() => {
625 if (publishRetries.get(threadId) !== retry) return Effect.void;
626 retry.timer = undefined;
627 return worker.enqueue(threadId);
628 }),
629 ),
630 Effect.forkIn(scope),
631 );
632 if (publishRetries.get(threadId) !== retry) {
633 yield* Fiber.interrupt(timer);
634 } else {
635 retry.timer = timer;
636 }
637 });
492 638
493 const publishActiveThreadsUnsafe = Effect.gen(function* () {639 const publishActiveThreadsUnsafe = Effect.gen(function* () {
494 const publishAgentActivity = yield* readPublishAgentActivityEnabled.pipe(640 const publishAgentActivity = yield* readPublishAgentActivityEnabled.pipe(
@@ -520,7 +666,8 @@ export const make = Effect.gen(function* () { · 25 unmodified lines
520 yield* Effect.logInfo("publishing active agent activity snapshot", {666 yield* Effect.logInfo("publishing active agent activity snapshot", {
521 count: activeThreadIds.length,667 count: activeThreadIds.length,
522 });668 });
523 yield* Effect.forEach(activeThreadIds, publishThread, { concurrency: 4, discard: true });669 yield* Effect.forEach(activeThreadIds, enqueueThreadPublish, { discard: true });
670 yield* worker.drain;
524 return true;671 return true;
525 });672 });
526 673
@@ -542,10 +689,8 @@ export const make = Effect.gen(function* () { · 15 unmodified lines
542 }689 }
543 });690 });
544 691
545 const worker = yield* makeDrainableWorker(publishThread);
546
547 schedulePublishConfirm = (threadId) =>692 schedulePublishConfirm = (threadId) =>
548 Effect.forkDetach(693 Effect.forkIn(
549 Effect.sleep("5 seconds").pipe(694 Effect.sleep("5 seconds").pipe(
550 Effect.andThen(worker.enqueue(threadId)),695 Effect.andThen(worker.enqueue(threadId)),
551 Effect.catchCause((cause) =>696 Effect.catchCause((cause) =>
@@ -555,6 +700,7 @@ export const make = Effect.gen(function* () { · 3 unmodified lines
555 }),700 }),
556 ),701 ),
557 ),702 ),
703 scope,
558 ).pipe(Effect.asVoid);704 ).pipe(Effect.asVoid);
559 705
560 const start: AgentAwarenessRelay["Service"]["start"] = Effect.fn("AgentAwarenessRelay.start")(706 const start: AgentAwarenessRelay["Service"]["start"] = Effect.fn("AgentAwarenessRelay.start")(
@@ -591,18 +737,12 @@ export const make = Effect.gen(function* () { · 30 unmodified lines
591 Stream.runForEach(threads.streamDomainEvents, (event) => {737 Stream.runForEach(threads.streamDomainEvents, (event) => {
592 const threadId = eventThreadId(event);738 const threadId = eventThreadId(event);
593 if (!shouldPublishAgentAwarenessEvent(event)) {739 if (!shouldPublishAgentAwarenessEvent(event)) {
594 return Effect.logDebug(740 return Effect.void;
595 "agent activity publishing ignored event without activity changes",
596 {
597 eventType: event.type,
598 threadId,
599 },
600 );
601 }741 }
602 return Effect.logDebug("agent activity publishing queued thread publish", {742 return Effect.logDebug("agent activity publishing queued thread publish", {
603 eventType: event.type,743 eventType: event.type,
604 threadId,744 threadId,
605 }).pipe(Effect.andThen(worker.enqueue(threadId)));745 }).pipe(Effect.andThen(enqueueThreadPublish(threadId)));
606 }),746 }),
607 );747 );
608 },748 },
@@ -610,6 +750,7 @@ export const make = Effect.gen(function* () { · 1 unmodified line
610 750
611 return AgentAwarenessRelay.of({751 return AgentAwarenessRelay.of({
612 publishThread,752 publishThread,
753 drain: worker.drain,
613 start,754 start,
614 });755 });
615});756});
apps/server/src/server.ts+1−1
@@ -412,7 +412,7 @@ const OrchestrationApplicationLayerLive = CheckpointDiffQuery.layer.pipe( · 411 unmodified lines
412// so every client sees the same shelf.412// so every client sees the same shelf.
413const ThreadSettlementWorkerLive = Layer.effectDiscard(413const ThreadSettlementWorkerLive = Layer.effectDiscard(
414 ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())),414 ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())),
415).pipe(Layer.provide(PullRequestServiceLive));415).pipe(Layer.provide(PullRequestServiceLive), Layer.provide(ProjectionStoreV2.layer));
416 416
417const AntigravityInstallationRefreshLive = Layer.effectDiscard(417const AntigravityInstallationRefreshLive = Layer.effectDiscard(
418 Effect.gen(function* () {418 Effect.gen(function* () {
apps/server/src/vcs/GitVcsDriver.ts+9−1
@@ -74,6 +74,11 @@ export interface GitStatusDetails { · 73 unmodified lines
74 aheadOfDefaultCount: number;74 aheadOfDefaultCount: number;
75}75}
76 76
77export interface GitLocalStatusOptions {
78 /** Skip revision walks and return zero divergence counts for local-only consumers. */
79 readonly includeDivergence?: boolean;
80}
81
77export interface GitRemoteStatusDetails {82export interface GitRemoteStatusDetails {
78 isRepo: boolean;83 isRepo: boolean;
79 defaultBranch: string | null;84 defaultBranch: string | null;
@@ -246,7 +251,10 @@ export class GitVcsDriver extends Context.Service< · 166 unmodified lines
246 readonly execute: (input: ExecuteGitInput) => Effect.Effect<ExecuteGitResult, GitCommandError>;251 readonly execute: (input: ExecuteGitInput) => Effect.Effect<ExecuteGitResult, GitCommandError>;
247 readonly status: (input: VcsStatusInput) => Effect.Effect<VcsStatusResult, GitCommandError>;252 readonly status: (input: VcsStatusInput) => Effect.Effect<VcsStatusResult, GitCommandError>;
248 readonly statusDetails: (cwd: string) => Effect.Effect<GitStatusDetails, GitCommandError>;253 readonly statusDetails: (cwd: string) => Effect.Effect<GitStatusDetails, GitCommandError>;
249 readonly statusDetailsLocal: (cwd: string) => Effect.Effect<GitStatusDetails, GitCommandError>;254 readonly statusDetailsLocal: (
255 cwd: string,
256 options?: GitLocalStatusOptions,
257 ) => Effect.Effect<GitStatusDetails, GitCommandError>;
250 readonly statusDetailsRemote: (258 readonly statusDetailsRemote: (
251 cwd: string,259 cwd: string,
252 options?: GitRemoteStatusOptions,260 options?: GitRemoteStatusOptions,
apps/server/src/vcs/GitVcsDriverCore.test.ts+84−7
@@ -8,6 +8,7 @@ import * as Effect from "effect/Effect"; · 7 unmodified lines
8import * as FileSystem from "effect/FileSystem";8import * as FileSystem from "effect/FileSystem";
9import * as Fiber from "effect/Fiber";9import * as Fiber from "effect/Fiber";
10import * as Layer from "effect/Layer";10import * as Layer from "effect/Layer";
11import * as Logger from "effect/Logger";
11import * as Path from "effect/Path";12import * as Path from "effect/Path";
12import * as PlatformError from "effect/PlatformError";13import * as PlatformError from "effect/PlatformError";
13import * as Ref from "effect/Ref";14import * as Ref from "effect/Ref";
@@ -553,7 +554,7 @@ it.effect("refreshes the current branch after an external checkout", () => · 539 unmodified lines
553 ).pipe(Effect.provide(TestLayer)),554 ).pipe(Effect.provide(TestLayer)),
554);555);
555 556
556it.effect("backs off failed upstream refreshes across linked worktrees", () =>557it.effect("backs off and logs failed fetch attempts across linked worktrees", () =>
557 Effect.scoped(558 Effect.scoped(
558 Effect.gen(function* () {559 Effect.gen(function* () {
559 const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;560 const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
@@ -573,6 +574,15 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () => · 13 unmodified lines
573 const driver = yield* makeGitVcsDriverCore().pipe(574 const driver = yield* makeGitVcsDriverCore().pipe(
574 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingFetchSpawner),575 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingFetchSpawner),
575 );576 );
577 const warnings: string[] = [];
578 const logger = Logger.make<unknown, void>(({ message }) => {
579 warnings.push(String(message));
580 });
581 const readRemoteStatus = (workingDirectory: string) =>
582 driver
583 .statusDetailsRemote(workingDirectory)
584 .pipe(Effect.provideService(Logger.CurrentLoggers, new Set([logger])));
585 const fileSystem = yield* FileSystem.FileSystem;
576 const cwd = yield* makeTmpDir();586 const cwd = yield* makeTmpDir();
577 const remote = yield* makeTmpDir("git-vcs-driver-remote-");587 const remote = yield* makeTmpDir("git-vcs-driver-remote-");
578 const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-");588 const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-");
@@ -616,25 +626,31 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () => · 37 unmodified lines
616 );626 );
617 yield* Ref.set(fetchAttempts, 0);627 yield* Ref.set(fetchAttempts, 0);
618 628
619 yield* driver.statusDetailsRemote(cwd);629 yield* readRemoteStatus(cwd);
620 yield* driver.statusDetailsRemote(worktreePath);630 yield* readRemoteStatus(worktreePath);
621 assert.equal(yield* Ref.get(fetchAttempts), 1);631 assert.equal(yield* Ref.get(fetchAttempts), 1);
632 assert.lengthOf(warnings, 1);
633 assert.include(warnings[0], "Background Git fetch failed");
622 634
623 yield* TestClock.adjust("29 seconds");635 yield* TestClock.adjust("29 seconds");
624 yield* driver.statusDetailsRemote(worktreePath);636 yield* readRemoteStatus(worktreePath);
625 assert.equal(yield* Ref.get(fetchAttempts), 1);637 assert.equal(yield* Ref.get(fetchAttempts), 1);
638 assert.lengthOf(warnings, 1);
626 639
627 yield* TestClock.adjust("1 second");640 yield* TestClock.adjust("1 second");
628 yield* driver.statusDetailsRemote(cwd);641 yield* readRemoteStatus(cwd);
629 assert.equal(yield* Ref.get(fetchAttempts), 2);642 assert.equal(yield* Ref.get(fetchAttempts), 2);
643 assert.lengthOf(warnings, 2);
630 644
631 yield* TestClock.adjust("59 seconds");645 yield* TestClock.adjust("59 seconds");
632 yield* driver.statusDetailsRemote(worktreePath);646 yield* readRemoteStatus(worktreePath);
633 assert.equal(yield* Ref.get(fetchAttempts), 2);647 assert.equal(yield* Ref.get(fetchAttempts), 2);
648 assert.lengthOf(warnings, 2);
634 649
635 yield* TestClock.adjust("1 second");650 yield* TestClock.adjust("1 second");
636 yield* driver.statusDetailsRemote(cwd);651 yield* readRemoteStatus(cwd);
637 assert.equal(yield* Ref.get(fetchAttempts), 3);652 assert.equal(yield* Ref.get(fetchAttempts), 3);
653 assert.lengthOf(warnings, 3);
638 }),654 }),
639 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),655 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
640);656);
@@ -1027,6 +1043,67 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { · 386 unmodified lines
1027 }),1043 }),
1028 );1044 );
1029 1045
1046 it.effect("can read local files without traversing revision history", () =>
1047 Effect.gen(function* () {
1048 const cwd = yield* makeTmpDir();
1049 const remote = yield* makeTmpDir("git-vcs-driver-remote-");
1050 const { initialBranch } = yield* initRepoWithCommit(cwd);
1051 yield* git(remote, ["init", "--bare"]);
1052 yield* git(cwd, ["remote", "add", "origin", remote]);
1053 yield* git(cwd, ["push", "-u", "origin", initialBranch]);
1054 yield* git(cwd, ["checkout", "-b", "feature/local-only"]);
1055 yield* writeTextFile(cwd, "feature.txt", "feature\n");
1056 yield* git(cwd, ["add", "feature.txt"]);
1057 yield* git(cwd, ["commit", "-m", "feature commit"]);
1058 yield* writeTextFile(cwd, "feature.txt", "feature\nlocal edit\n");
1059 yield* writeTextFile(cwd, "untracked.txt", "untracked\n");
1060
1061 const delegate = yield* ChildProcessSpawner.ChildProcessSpawner;
1062 const commands: ReadonlyArray<string>[] = [];
1063 const spawner = ChildProcessSpawner.make((command) =>
1064 Effect.gen(function* () {
1065 if (!ChildProcess.isStandardCommand(command)) {
1066 return yield* Effect.die("expected a standard Git command");
1067 }
1068 commands.push(command.args);
1069 return yield* delegate.spawn(command);
1070 }),
1071 );
1072 const driver = yield* makeGitVcsDriverCore().pipe(
1073 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
1074 Effect.provide(ServerConfigLayer),
1075 );
1076
1077 for (const hasUpstream of [false, true]) {
1078 if (hasUpstream) {
1079 yield* git(cwd, ["push", "-u", "origin", "feature/local-only"]);
1080 }
1081 commands.length = 0;
1082 const local = yield* driver.statusDetailsLocal(cwd, { includeDivergence: false });
1083 assert.isFalse(commands.some((args) => args.includes("rev-list")));
1084 assert.isTrue(commands.some((args) => args.includes("--no-ahead-behind")));
1085 assert.equal(local.hasUpstream, hasUpstream);
1086 assert.isTrue(local.hasWorkingTreeChanges);
1087 assert.deepEqual(local.workingTree.files, [
1088 { path: "feature.txt", insertions: 1, deletions: 0 },
1089 { path: "untracked.txt", insertions: 0, deletions: 0 },
1090 ]);
1091
1092 commands.length = 0;
1093 const full = yield* driver.statusDetailsLocal(cwd);
1094 assert.isTrue(commands.some((args) => args.includes("rev-list")));
1095 assert.equal(full.aheadCount, hasUpstream ? 0 : 1);
1096 assert.equal(full.aheadOfDefaultCount, 1);
1097 assert.deepEqual(local, {
1098 ...full,
1099 aheadCount: 0,
1100 behindCount: 0,
1101 aheadOfDefaultCount: 0,
1102 });
1103 }
1104 }),
1105 );
1106
1030 it.effect("reports remote divergence without reading working-tree details", () =>1107 it.effect("reports remote divergence without reading working-tree details", () =>
1031 Effect.gen(function* () {1108 Effect.gen(function* () {
1032 const cwd = yield* makeTmpDir();1109 const cwd = yield* makeTmpDir();
apps/server/src/vcs/GitVcsDriverCore.ts+21−9
@@ -1246,6 +1246,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 1245 unmodified lines
1246 return yield* fetchRemoteForStatus(cacheKey.gitCommonDir, cacheKey.remoteName).pipe(1246 return yield* fetchRemoteForStatus(cacheKey.gitCommonDir, cacheKey.remoteName).pipe(
1247 Effect.tap(() => Effect.sync(() => clearStatusRemoteRefreshFailures(cacheKey))),1247 Effect.tap(() => Effect.sync(() => clearStatusRemoteRefreshFailures(cacheKey))),
1248 Effect.tapError(() => Effect.sync(() => recordStatusRemoteRefreshFailure(cacheKey))),1248 Effect.tapError(() => Effect.sync(() => recordStatusRemoteRefreshFailure(cacheKey))),
1249 Effect.tapCause((cause) => Effect.logWarning("Background Git fetch failed", cause)),
1249 Effect.as(true as const),1250 Effect.as(true as const),
1250 );1251 );
1251 });1252 });
@@ -1270,13 +1271,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 18 unmodified lines
1270 const upstream = yield* resolveCurrentUpstream(cwd);1271 const upstream = yield* resolveCurrentUpstream(cwd);
1271 if (!upstream) return;1272 if (!upstream) return;
1272 const gitCommonDir = yield* resolveGitCommonDir(cwd);1273 const gitCommonDir = yield* resolveGitCommonDir(cwd);
1274 // The cache loader logs failed attempts; cache hits keep using the last fetched refs.
1273 yield* Cache.get(1275 yield* Cache.get(
1274 statusRemoteRefreshCache,1276 statusRemoteRefreshCache,
1275 new StatusRemoteRefreshCacheKey({1277 new StatusRemoteRefreshCacheKey({
1276 gitCommonDir,1278 gitCommonDir,
1277 remoteName: upstream.remoteName,1279 remoteName: upstream.remoteName,
1278 }),1280 }),
1279 );1281 ).pipe(Effect.ignore);
1280 });1282 });
1281 1283
1282 const resolveDefaultBranchName = (1284 const resolveDefaultBranchName = (
@@ -1583,11 +1585,21 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 300 unmodified lines
1583 };1585 };
1584 });1586 });
1585 1587
1586 const readStatusDetailsLocal = Effect.fn("readStatusDetailsLocal")(function* (cwd: string) {1588 const readStatusDetailsLocal = Effect.fn("readStatusDetailsLocal")(function* (
1589 cwd: string,
1590 options?: GitVcsDriver.GitLocalStatusOptions,
1591 ) {
1592 const includeDivergence = options?.includeDivergence !== false;
1593 const statusArgs = [
1594 "status",
1595 "--porcelain=2",
1596 "--branch",
1597 ...(includeDivergence ? [] : ["--no-ahead-behind"]),
1598 ];
1587 const statusResult = yield* executeGitWithStableDiagnostics(1599 const statusResult = yield* executeGitWithStableDiagnostics(
1588 "GitVcsDriver.statusDetails.status",1600 "GitVcsDriver.statusDetails.status",
1589 cwd,1601 cwd,
1590 ["status", "--porcelain=2", "--branch"],1602 statusArgs,
1591 {1603 {
1592 allowNonZeroExit: true,1604 allowNonZeroExit: true,
1593 },1605 },
@@ -1610,7 +1622,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 16 unmodified lines
1610 ...gitCommandContext({1622 ...gitCommandContext({
1611 operation: "GitVcsDriver.statusDetails.status",1623 operation: "GitVcsDriver.statusDetails.status",
1612 cwd,1624 cwd,
1613 args: ["status", "--porcelain=2", "--branch"],1625 args: statusArgs,
1614 }),1626 }),
1615 detail: "Git status failed.",1627 detail: "Git status failed.",
1616 exitCode: statusResult.exitCode,1628 exitCode: statusResult.exitCode,
@@ -1710,7 +1722,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 93 unmodified lines
1710 upstreamRef = value.length > 0 ? value : null;1722 upstreamRef = value.length > 0 ? value : null;
1711 continue;1723 continue;
1712 }1724 }
1713 if (line.startsWith("# branch.ab ")) {1725 if (includeDivergence && line.startsWith("# branch.ab ")) {
1714 const value = line.slice("# branch.ab ".length).trim();1726 const value = line.slice("# branch.ab ".length).trim();
1715 const parsed = parseBranchAb(value);1727 const parsed = parseBranchAb(value);
1716 aheadCount = parsed.ahead;1728 aheadCount = parsed.ahead;
@@ -1725,7 +1737,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 8 unmodified lines
1725 }1737 }
1726 1738
1727 const fallbackAheadCount =1739 const fallbackAheadCount =
1728 !upstreamRef && refName1740 includeDivergence && !upstreamRef && refName
1729 ? yield* computeAheadCountAgainstBase(cwd, refName).pipe(Effect.orElseSucceed(() => 0))1741 ? yield* computeAheadCountAgainstBase(cwd, refName).pipe(Effect.orElseSucceed(() => 0))
1730 : null;1742 : null;
1731 1743
@@ -1738,7 +1750,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 6 unmodified lines
1738 refName !== null &&1750 refName !== null &&
1739 (refName === defaultBranch ||1751 (refName === defaultBranch ||
1740 (defaultBranch === null && (refName === "main" || refName === "master")));1752 (defaultBranch === null && (refName === "main" || refName === "master")));
1741 if (refName && !isDefaultBranch) {1753 if (includeDivergence && refName && !isDefaultBranch) {
1742 aheadOfDefaultCount =1754 aheadOfDefaultCount =
1743 fallbackAheadCount !== null1755 fallbackAheadCount !== null
1744 ? fallbackAheadCount1756 ? fallbackAheadCount
@@ -1788,8 +1800,8 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 43 unmodified lines
1788 1800
1789 const statusDetailsLocal: GitVcsDriver.GitVcsDriver["Service"]["statusDetailsLocal"] = Effect.fn(1801 const statusDetailsLocal: GitVcsDriver.GitVcsDriver["Service"]["statusDetailsLocal"] = Effect.fn(
1790 "statusDetailsLocal",1802 "statusDetailsLocal",
1791 )(function* (cwd) {1803 )(function* (cwd, options) {
1792 return yield* readStatusDetailsLocal(cwd);1804 return yield* readStatusDetailsLocal(cwd, options);
1793 });1805 });
1794 1806
1795 const statusDetails: GitVcsDriver.GitVcsDriver["Service"]["statusDetails"] = Effect.fn(1807 const statusDetails: GitVcsDriver.GitVcsDriver["Service"]["statusDetails"] = Effect.fn(
apps/web/src/components/CommandPalette.tsx+9−1
@@ -1186,7 +1186,14 @@ function OpenCommandPaletteDialog(props: { · 1185 unmodified lines
1186 projectTitleById,1186 projectTitleById,
1187 sortOrder: clientSettings.sidebarThreadSortOrder,1187 sortOrder: clientSettings.sidebarThreadSortOrder,
1188 icon: <MessageSquareIcon className={ITEM_ICON_CLASS} />,1188 icon: <MessageSquareIcon className={ITEM_ICON_CLASS} />,
1189 renderLeadingContent: (thread) => <ThreadRowLeadingStatus thread={thread} />,1189 renderLeadingContent: (thread) => (
1190 <ThreadRowLeadingStatus
1191 thread={thread}
1192 snapshot={changeRequestSnapshotByKey.get(
1193 scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
1194 )}
1195 />
1196 ),
1190 renderTrailingContent: (thread) => <ThreadRowTrailingStatus thread={thread} />,1197 renderTrailingContent: (thread) => <ThreadRowTrailingStatus thread={thread} />,
1191 renderDescription: (thread, { projectTitle }) => {1198 renderDescription: (thread, { projectTitle }) => {
1192 const modelInstanceId =1199 const modelInstanceId =
@@ -1238,6 +1245,7 @@ function OpenCommandPaletteDialog(props: { · 45 unmodified lines
1238 }),1245 }),
1239 [1246 [
1240 activeThreadId,1247 activeThreadId,
1248 changeRequestSnapshotByKey,
1241 clientSettings.sidebarThreadSortOrder,1249 clientSettings.sidebarThreadSortOrder,
1242 navigate,1250 navigate,
1243 projectCwdById,1251 projectCwdById,
apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx+180−0
@@ -0,0 +1,180 @@
1import { ProjectId, ThreadId, type VcsStatusResult } from "@t3tools/contracts";
2import { act, type ComponentProps, type ReactNode } from "react";
3import { create, type ReactTestRenderer } from "react-test-renderer";
4import { afterEach, expect, it, vi } from "vite-plus/test";
5
6import { AppAtomRegistryProvider } from "../rpc/atomRegistry";
7import { makeThreadFixture } from "../test-fixtures";
8
9const state = vi.hoisted(() => ({ queries: [] as string[] }));
10
11vi.mock("../state/entities", () => ({ useProject: () => null }));
12vi.mock("../uiStateStore", () => ({ useUiStateStore: () => undefined }));
13vi.mock("../state/vcs", async () => {
14 const { AsyncResult, Atom } = await import("effect/unstable/reactivity");
15 const statuses = Atom.family((cwd: string) =>
16 Atom.make(() => {
17 state.queries.push(`vcs:${cwd}`);
18 return AsyncResult.success({
19 isRepo: true,
20 hasPrimaryRemote: true,
21 isDefaultRef: false,
22 refName: "feature/test",
23 hasWorkingTreeChanges: false,
24 workingTree: { files: [], insertions: 0, deletions: 0 },
25 hasUpstream: true,
26 aheadCount: 0,
27 behindCount: 0,
28 pr: {
29 number: 42,
30 title: "Observed worktree PR",
31 url: "https://github.com/example/repo/pull/42",
32 baseRef: "main",
33 headRef: "feature/test",
34 state: "open",
35 },
36 } satisfies VcsStatusResult);
37 }),
38 );
39 return {
40 vcsEnvironment: { status: ({ input }: { input: { cwd: string } }) => statuses(input.cwd) },
41 };
42});
43vi.mock("../state/pullRequests", async (importOriginal) => {
44 const original = await importOriginal<typeof import("../state/pullRequests")>();
45 const { AsyncResult, Atom } = await import("effect/unstable/reactivity");
46 const { ProjectId } = await import("@t3tools/contracts");
47 const summaries = Atom.family((number: number) =>
48 Atom.make(() => {
49 state.queries.push(`pr:${number}`);
50 return AsyncResult.success({
51 provider: "github" as const,
52 projectId: ProjectId.make("project-test"),
53 repository: "example/repo",
54 number,
55 title: "Observed linked PR",
56 url: `https://github.com/example/repo/pull/${number}`,
57 state: "open" as const,
58 headBranch: "feature/test",
59 baseBranch: "main",
60 updatedAt: "2026-09-04T00:00:00.000Z",
61 });
62 }),
63 );
64 return {
65 ...original,
66 linkedPullRequestDetailAtom: ({ input }: { input: { number: number } }) =>
67 summaries(input.number),
68 };
69});
70vi.mock("./ui/tooltip", async () => {
71 const { cloneElement, isValidElement } = await import("react");
72 return {
73 Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
74 TooltipTrigger({
75 render,
76 children,
77 }: ComponentProps<typeof import("./ui/tooltip").TooltipTrigger>) {
78 return isValidElement(render) ? cloneElement(render, undefined, children) : children;
79 },
80 TooltipPopup: () => null,
81 };
82});
83
84import { ThreadRowLeadingStatus } from "./ThreadStatusIndicators";
85
86afterEach(() => {
87 vi.unstubAllGlobals();
88});
89
90it("leases only visible palette results while retaining observed and cached PR badges", async () => {
91 const observers: Array<{ setVisible: (visible: boolean) => void }> = [];
92 const scrollRoot = {};
93 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
94 vi.stubGlobal(
95 "IntersectionObserver",
96 class {
97 constructor(callback: (entries: ReadonlyArray<{ isIntersecting: boolean }>) => void) {
98 observers.push({ setVisible: (visible) => callback([{ isIntersecting: visible }]) });
99 }
100 observe() {}
101 disconnect() {}
102 },
103 );
104 const threads = Array.from({ length: 128 }, (_, index) =>
105 makeThreadFixture({
106 id: ThreadId.make(`palette-thread-${index}`),
107 branch: "feature/test",
108 worktreePath: `/test/worktree-${index}`,
109 linkedPullRequest:
110 index % 2 === 0
111 ? null
112 : {
113 projectId: ProjectId.make("project-test"),
114 repository: "example/repo",
115 number: index,
116 url: `https://github.com/example/repo/pull/${index}`,
117 },
118 }),
119 );
120 let renderer: ReactTestRenderer | undefined;
121 state.queries.length = 0;
122 try {
123 await act(async () => {
124 renderer = create(
125 <AppAtomRegistryProvider>
126 {threads.map((thread, index) => (
127 <span key={thread.id}>
128 <ThreadRowLeadingStatus
129 thread={thread}
130 snapshot={
131 index === 126
132 ? {
133 branch: "feature/test",
134 sourceControlProvider: undefined,
135 pr: {
136 number: 126,
137 title: "Cached badge",
138 url: "https://github.com/example/repo/pull/126",
139 baseRef: "main",
140 headRef: "feature/test",
141 state: "open",
142 },
143 }
144 : undefined
145 }
146 />
147 {thread.title}
148 </span>
149 ))}
150 </AppAtomRegistryProvider>,
151 { createNodeMock: () => ({ parentElement: { closest: () => scrollRoot } }) },
152 );
153 });
154 const mounted = renderer!;
155 const badgeCount = () =>
156 mounted.root.findAll(
157 (node) => node.type === "span" && String(node.props["aria-label"] ?? "").startsWith("PR #"),
158 ).length;
159 expect(observers).toHaveLength(128);
160 expect(state.queries).toEqual([]);
161 expect(badgeCount()).toBe(1);
162
163 await act(async () => {
164 for (const observer of observers.slice(0, 8)) observer.setVisible(true);
165 });
166 expect(state.queries).toHaveLength(8);
167 expect(state.queries.filter((key) => key.startsWith("vcs:"))).toHaveLength(4);
168 expect(state.queries.filter((key) => key.startsWith("pr:"))).toHaveLength(4);
169 expect(badgeCount()).toBe(9);
170
171 await act(async () => {
172 for (const observer of observers.slice(0, 8)) observer.setVisible(false);
173 for (const observer of observers.slice(8, 16)) observer.setVisible(true);
174 });
175 expect(state.queries).toHaveLength(16);
176 expect(badgeCount()).toBe(17);
177 } finally {
178 await act(async () => renderer?.unmount());
179 }
180});
apps/web/src/components/ThreadStatusIndicators.test.ts+19−0
@@ -554,6 +554,25 @@ describe("resolveDisplayedThreadPr + nextThreadChangeRequestSnapshot", () => { · 553 unmodified lines
554 ).toEqual(mergedPr);554 ).toEqual(mergedPr);
555 });555 });
556 556
557 it("shows the last matching PR snapshot while its live status lease is released", () => {
558 const openPr = { ...mergedPr, state: "open" as const };
559 const input = {
560 threadBranch: featureBranch,
561 gitStatus: null,
562 snapshot: snapshotFor(featureBranch, openPr, provider),
563 retainTerminalOnBranchMismatch: false,
564 };
565 expect(resolveDisplayedThreadPr(input)).toEqual(openPr);
566 expect(resolveDisplayedThreadPrProvider(input)).toEqual(provider);
567 expect(resolveDisplayedThreadPr({ ...input, threadBranch: "feature/other" })).toBeNull();
568 expect(
569 resolveDisplayedThreadPr({
570 ...input,
571 snapshot: { ...input.snapshot, linkedPullRequest },
572 }),
573 ).toBeNull();
574 });
575
557 it("retains a merged PR after a main checkout", () => {576 it("retains a merged PR after a main checkout", () => {
558 const matchingStatus = status({577 const matchingStatus = status({
559 refName: featureBranch,578 refName: featureBranch,
apps/web/src/components/ThreadStatusIndicators.tsx+59−17
@@ -12,7 +12,7 @@ import { · 11 unmodified lines
12} from "@t3tools/contracts";12} from "@t3tools/contracts";
13import { Atom } from "effect/unstable/reactivity";13import { Atom } from "effect/unstable/reactivity";
14import { FolderGit2Icon, TerminalIcon } from "lucide-react";14import { FolderGit2Icon, TerminalIcon } from "lucide-react";
15import { useMemo } from "react";15import { useCallback, useMemo } from "react";
16import { appAtomRegistry } from "../rpc/atomRegistry";16import { appAtomRegistry } from "../rpc/atomRegistry";
17import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments";17import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments";
18import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";18import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
@@ -27,6 +27,8 @@ import { · 8 unmodified lines
27 resolveThreadLastVisitedAt,27 resolveThreadLastVisitedAt,
28 resolveThreadStatusPill,28 resolveThreadStatusPill,
29 type ThreadStatusPill,29 type ThreadStatusPill,
30 useRetainedValue,
31 useSidebarRowSubscriptionLease,
30} from "./Sidebar.logic";32} from "./Sidebar.logic";
31import { resolvePullRequestState } from "./pullRequest/pullRequestPresentation";33import { resolvePullRequestState } from "./pullRequest/pullRequestPresentation";
32import type { SidebarThreadSummary } from "../types";34import type { SidebarThreadSummary } from "../types";
@@ -58,9 +60,10 @@ export interface LinkedThreadPullRequestStatus { · 25 unmodified lines
58export function useLinkedThreadPullRequest(60export function useLinkedThreadPullRequest(
59 environmentId: EnvironmentId | null,61 environmentId: EnvironmentId | null,
60 linkedPullRequest: ThreadLinkedPullRequest | null | undefined,62 linkedPullRequest: ThreadLinkedPullRequest | null | undefined,
63 enabled = true,
61): LinkedThreadPullRequestStatus | null {64): LinkedThreadPullRequestStatus | null {
62 const queried = useEnvironmentQuery(65 const queried = useEnvironmentQuery(
63 environmentId === null || linkedPullRequest == null66 !enabled || environmentId === null || linkedPullRequest == null
64 ? null67 ? null
65 : linkedPullRequestDetailAtom({68 : linkedPullRequestDetailAtom({
66 environmentId,69 environmentId,
@@ -353,7 +356,7 @@ export function nextThreadChangeRequestSnapshot(input: { · 286 unmodified lines
353 * checkouts only, a cached merged/closed PR for the thread. Local thread356 * checkouts only, a cached merged/closed PR for the thread. Local thread
354 * metadata follows the shared checkout, so the cached branch intentionally357 * metadata follows the shared checkout, so the cached branch intentionally
355 * survives that metadata changing to the newly checked-out branch. Open PRs358 * survives that metadata changing to the newly checked-out branch. Open PRs
356 * are never retained — their state can still change.359 * are retained only while live status is absent and the branch still matches.
357 */360 */
358export function resolveDisplayedThreadPr(input: {361export function resolveDisplayedThreadPr(input: {
359 threadBranch: string | null;362 threadBranch: string | null;
@@ -388,6 +391,15 @@ export function resolveDisplayedThreadPr(input: { · 28 unmodified lines
388 return gitStatus.pr;391 return gitStatus.pr;
389 }392 }
390 393
394 if (
395 gitStatus === null &&
396 threadBranch !== null &&
397 snapshot?.branch === threadBranch &&
398 snapshot.linkedPullRequest === undefined
399 ) {
400 return snapshot.pr;
401 }
402
391 if (403 if (
392 threadBranch !== null &&404 threadBranch !== null &&
393 retainTerminalOnBranchMismatch &&405 retainTerminalOnBranchMismatch &&
@@ -434,6 +446,15 @@ export function resolveDisplayedThreadPrProvider(input: { · 40 unmodified lines
434 return gitStatus.sourceControlProvider;446 return gitStatus.sourceControlProvider;
435 }447 }
436 448
449 if (
450 gitStatus === null &&
451 threadBranch !== null &&
452 snapshot?.branch === threadBranch &&
453 snapshot.linkedPullRequest === undefined
454 ) {
455 return snapshot.sourceControlProvider;
456 }
457
437 if (458 if (
438 threadBranch !== null &&459 threadBranch !== null &&
439 retainTerminalOnBranchMismatch &&460 retainTerminalOnBranchMismatch &&
@@ -550,7 +571,19 @@ export function ThreadStatusLabel({ · 110 unmodified lines
550 * like the command palette. Shows the change request state icon (if present) and the571 * like the command palette. Shows the change request state icon (if present) and the
551 * thread status dot, matching the sidebar's leading indicators.572 * thread status dot, matching the sidebar's leading indicators.
552 */573 */
553export function ThreadRowLeadingStatus({ thread }: { thread: SidebarThreadSummary }) {574export function ThreadRowLeadingStatus({
575 thread,
576 snapshot,
577}: {
578 thread: SidebarThreadSummary;
579 snapshot?: ThreadChangeRequestSnapshot | undefined;
580}) {
581 const { leaseLiveStatus, rowRef } = useSidebarRowSubscriptionLease(false);
582 // Observe the containing title even when this thread has no badge yet.
583 const statusRef = useCallback(
584 (node: HTMLSpanElement | null) => rowRef(node?.parentElement ?? null),
585 [rowRef],
586 );
554 const threadRef = scopeThreadRef(thread.environmentId, thread.id);587 const threadRef = scopeThreadRef(thread.environmentId, thread.id);
555 const localLastVisitedAt = useUiStateStore(588 const localLastVisitedAt = useUiStateStore(
556 (state) => state.threadLastVisitedAtById[scopedThreadKey(threadRef)],589 (state) => state.threadLastVisitedAtById[scopedThreadKey(threadRef)],
@@ -567,9 +600,11 @@ export function ThreadRowLeadingStatus({ thread }: { thread: SidebarThreadSummar · 10 unmodified lines
567 const linkedPullRequest = useLinkedThreadPullRequest(600 const linkedPullRequest = useLinkedThreadPullRequest(
568 thread.environmentId,601 thread.environmentId,
569 thread.linkedPullRequest,602 thread.linkedPullRequest,
603 leaseLiveStatus,
570 );604 );
571 const gitStatus = useEnvironmentQuery(605 const gitStatus = useEnvironmentQuery(
572 thread.linkedPullRequest == null &&606 leaseLiveStatus &&
607 thread.linkedPullRequest == null &&
573 (thread.branch != null || thread.worktreePath !== null) &&608 (thread.branch != null || thread.worktreePath !== null) &&
574 gitCwd !== null609 gitCwd !== null
575 ? vcsEnvironment.status({610 ? vcsEnvironment.status({
@@ -578,14 +613,20 @@ export function ThreadRowLeadingStatus({ thread }: { thread: SidebarThreadSummar · 2 unmodified lines
578 })613 })
579 : null,614 : null,
580 );615 );
581 const pr =616 const visibleGitStatus = useRetainedValue(
582 thread.linkedPullRequest == null617 JSON.stringify([thread.environmentId, gitCwd]),
583 ? resolveThreadPr({ threadBranch: thread.branch, gitStatus: gitStatus.data })618 gitStatus.data,
584 : (linkedPullRequest?.pr ?? null);
585 const prStatus = prStatusIndicator(
586 pr,
587 linkedPullRequest?.sourceControlProvider ?? gitStatus.data?.sourceControlProvider,
588 );619 );
620 const displayedPrInput = {
621 threadBranch: thread.branch,
622 gitStatus: visibleGitStatus,
623 snapshot,
624 retainTerminalOnBranchMismatch: thread.worktreePath === null,
625 linkedPullRequest: thread.linkedPullRequest,
626 linkedPullRequestStatus: linkedPullRequest,
627 };
628 const pr = resolveDisplayedThreadPr(displayedPrInput);
629 const prStatus = prStatusIndicator(pr, resolveDisplayedThreadPrProvider(displayedPrInput));
589 const threadStatus = resolveThreadStatusPill({630 const threadStatus = resolveThreadStatusPill({
590 thread: {631 thread: {
591 ...thread,632 ...thread,
@@ -593,12 +634,13 @@ export function ThreadRowLeadingStatus({ thread }: { thread: SidebarThreadSummar · 1 unmodified line
593 },634 },
594 });635 });
595 636
596 if (!prStatus && !threadStatus) {
597 return null;
598 }
599
600 return (637 return (
601 <span className="inline-flex shrink-0 items-center gap-1.5">638 <span
639 ref={statusRef}
640 className={
641 prStatus || threadStatus ? "inline-flex shrink-0 items-center gap-1.5" : "contents"
642 }
643 >
602 {prStatus && pr ? (644 {prStatus && pr ? (
603 <Tooltip>645 <Tooltip>
604 <TooltipTrigger646 <TooltipTrigger
apps/web/src/connection/desktopLocal.test.ts+62−1
@@ -2,7 +2,11 @@ import { · 1 unmodified line
2 BearerConnectionTarget,2 BearerConnectionTarget,
3 PrimaryConnectionTarget,3 PrimaryConnectionTarget,
4} from "@t3tools/client-runtime/connection";4} from "@t3tools/client-runtime/connection";
5import { EnvironmentId, PRIMARY_LOCAL_ENVIRONMENT_ID } from "@t3tools/contracts";5import {
6 type DesktopEnvironmentBootstrap,
7 EnvironmentId,
8 PRIMARY_LOCAL_ENVIRONMENT_ID,
9} from "@t3tools/contracts";
6import { describe, expect, it } from "vite-plus/test";10import { describe, expect, it } from "vite-plus/test";
7 11
8import {12import {
@@ -38,6 +42,63 @@ describe("desktop local connection identity", () => { · 29 unmodified lines
38});42});
39 43
40describe("desktop local topology reads", () => {44describe("desktop local topology reads", () => {
45 it("reuses snapshots when polling returns fresh objects for the same topology", () => {
46 const secondary: DesktopEnvironmentBootstrap = {
47 id: "wsl:default",
48 label: "WSL",
49 runningDistro: "Ubuntu",
50 httpBaseUrl: "http://127.0.0.1:4000",
51 wsBaseUrl: "ws://127.0.0.1:4000",
52 bootstrapToken: "bootstrap-1",
53 };
54 let entries = [secondary];
55 const reader = createDesktopSecondaryBootstrapsReader(() => ({
56 getLocalEnvironmentBootstraps: () => entries.map((entry) => ({ ...entry })),
57 }));
58
59 const connected = reader.readSnapshot();
60 expect(reader.readSnapshot()).toBe(connected);
61 expect(reader.readResult()).toEqual({ _tag: "Success", bootstraps: connected });
62 expect(reader.readSnapshot()).toBe(connected);
63
64 entries = [];
65 const empty = reader.readSnapshot();
66 expect(empty).toEqual([]);
67 expect(reader.readSnapshot()).toBe(empty);
68 });
69
70 it.each<Partial<DesktopEnvironmentBootstrap>>([
71 { id: "wsl:Debian" },
72 { label: "Renamed backend" },
73 { runningDistro: "Debian" },
74 { httpBaseUrl: "http://127.0.0.1:4001" },
75 { wsBaseUrl: "ws://127.0.0.1:4001" },
76 { bootstrapToken: "bootstrap-2" },
77 ])("publishes bootstrap changes: %j", (change) => {
78 let secondary: DesktopEnvironmentBootstrap = {
79 id: "wsl:default",
80 label: "WSL",
81 runningDistro: "Ubuntu",
82 httpBaseUrl: "http://127.0.0.1:4000",
83 wsBaseUrl: "ws://127.0.0.1:4000",
84 bootstrapToken: "bootstrap-1",
85 };
86 const reader = createDesktopSecondaryBootstrapsReader(() => ({
87 getLocalEnvironmentBootstraps: () => [{ ...secondary }],
88 }));
89 const before = reader.readSnapshot();
90 secondary = { ...secondary, ...change };
91 const after = reader.readSnapshot();
92 expect(after).not.toBe(before);
93 expect(after).toEqual([secondary]);
94 expect(reader.readSnapshot()).toBe(after);
95 });
96
97 it("keeps the empty snapshot stable without a desktop bridge", () => {
98 const reader = createDesktopSecondaryBootstrapsReader(() => undefined);
99 expect(reader.readSnapshot()).toBe(reader.readSnapshot());
100 });
101
41 it("distinguishes a successful empty topology from a read failure", () => {102 it("distinguishes a successful empty topology from a read failure", () => {
42 let readBootstraps = () => [];103 let readBootstraps = () => [];
43 const reader = createDesktopSecondaryBootstrapsReader(() => ({104 const reader = createDesktopSecondaryBootstrapsReader(() => ({
apps/web/src/connection/desktopLocal.ts+19−2
@@ -67,13 +67,30 @@ export function createDesktopSecondaryBootstrapsReader( · 66 unmodified lines
67 const readResult = (): DesktopSecondaryBootstrapsRead => {67 const readResult = (): DesktopSecondaryBootstrapsRead => {
68 const bridge = resolveBridge();68 const bridge = resolveBridge();
69 if (bridge === undefined) {69 if (bridge === undefined) {
70 snapshot = [];70 if (snapshot.length > 0) snapshot = [];
71 return { _tag: "Success", bootstraps: snapshot };71 return { _tag: "Success", bootstraps: snapshot };
72 }72 }
73 try {73 try {
74 snapshot = bridge74 const next = bridge
75 .getLocalEnvironmentBootstraps()75 .getLocalEnvironmentBootstraps()
76 .filter((entry) => entry.id !== PRIMARY_LOCAL_ENVIRONMENT_ID);76 .filter((entry) => entry.id !== PRIMARY_LOCAL_ENVIRONMENT_ID);
77 if (
78 next.length !== snapshot.length ||
79 next.some((entry, index) => {
80 const previous = snapshot[index];
81 return (
82 previous === undefined ||
83 entry.id !== previous.id ||
84 entry.label !== previous.label ||
85 entry.runningDistro !== previous.runningDistro ||
86 entry.httpBaseUrl !== previous.httpBaseUrl ||
87 entry.wsBaseUrl !== previous.wsBaseUrl ||
88 entry.bootstrapToken !== previous.bootstrapToken
89 );
90 })
91 ) {
92 snapshot = next;
93 }
77 return { _tag: "Success", bootstraps: snapshot };94 return { _tag: "Success", bootstraps: snapshot };
78 } catch (cause) {95 } catch (cause) {
79 return { _tag: "Failure", cause };96 return { _tag: "Failure", cause };
audits/orchestrator-v2/2026-09-02/AUDIT.md+99−0
@@ -0,0 +1,99 @@
1# Orchestrator V2 audit — 2026-09-02
2
3The current branch has **17 confirmed findings: 14 correctness or feature-parity problems and three unbounded data-loading/transfer paths**. The performance findings establish unnecessary read/decode or transfer work, not measured latency or memory numbers. The earlier provider/history/compatibility fixes inspected in this pass remain present. Several newly found problems already existed before the latest rebases; they are not all new rebase mistakes.
4
5The most urgent findings are the existing-V2 database upgrade failure, project deletion that partially deletes or orphans threads, full-thread diff failure after the second run, OpenCode work surviving cancellation/release, and background Cursor metadata generation with unrestricted tool access. The web sidebar and attachment flows also have concrete regressions.
6
7This is an audit, not a fix batch. Product code and existing tests were left unchanged; nothing was committed or pushed. Reports and audit-only probes are inside the repository at your request. Three GPT-5.6 Sol reviewers worked with bounded concurrency; the primary agent reviewed their claims, traced cross-cutting paths, and rejected a false positive.
8
9| Reference | Value |
10| ----------------------------------------- | ---------------------------------------------------------------------------------------------- |
11| Branch | `t3code/codex-turn-mapping` |
12| Audited HEAD | `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0` |
13| Fetched main / merge base | `57a66608b918d673eeec7e6c94ea5906b756fcd0` |
14| Branch extent | 332 branch-only commits, 0 main-only commits; 942 changed files |
15| Previous reviewed object | `47f5b100440591d2f49aa30cf3bb69eacae07f52` |
16| Rebased equivalent of previous review tip | `c1791ab2637` |
17| Inventoried additions | 165 main commits since the old comparison base; 21 branch commits after the rebased review tip |
18
19Main being an ancestor does not prove parity: retained V1 implementations can be bypassed by V2 adapters and services. Likewise, a commit being an ancestor of the rebased review tip does not establish that the same main behavior was available at the original review. The classifications below follow current call paths and, where stated, the actual historical code.
20
21## Fix decisions
22
23**P1** means fix before rollout or the next substantial use of the affected path. **P2** means a confirmed, more limited behavior or scaling problem. Every row has a concrete trigger. A `YES` approves fixing that row; a `NO` accepts or defers the stated behavior. All decisions remain open; recommendations are not approvals.
24
25| ID | Priority | Difference and affected path | Classification | Recommendation |
26| --- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
27| F01 | P1 — upgrade blocker | Old V2 databases ending at migration 052 fail at current 053 because the legacy-import table already exists. They also skip main 044's project-model repair. | New round-20 migration renumbering error. Actual current-migrator reproduction. | **YES:** reconcile supported old migration histories and apply the missed repair. Fresh-schema tests are insufficient. |
28| F02 | P1 | A visible thread from an environment without hydrated provider config can crash the web/desktop sidebar. Its fallback constant is declared after an unconditional return. | Round-18 reconciliation regression. Deterministic source trace. | **YES:** restore the module-scope fallback and cover cached/disconnected environments. |
29| F03 | P1 | Full-thread diff fails after an ordinary second run: all root runs share one checkpoint scope, but the zero-baseline lookup requires that scope still be owned by run one. | Older V2 integration bug, present in the previous reviewed source. Real allocator/reducer probe plus diff-path trace. | **YES:** resolve the baseline through the actual shared scope; replace the impossible two-scope test fixture. |
30| F04 | P1 | Forced project deletion over WebSocket deletes imported V2 threads, then legacy validation rejects deletion of the still-populated project. | Older mixed V1/V2 lifecycle integration bug. Source-confirmed partial commit. | **YES:** carry force through and coordinate validation/deletion across both representations before destructive work. |
31| F05 | P1 | HTTP and live/offline CLI project removal bypass V2 thread cleanup. A V2-only populated project can be deleted without force; imported projects reject force. HTTP create also drops the missing-directory flag. | Older transport divergence from the shared project contract. | **YES:** route these transports through the same V2-aware lifecycle and preserve contract fields. |
32| F06 | P1 | OpenCode Stop discards child-list, child-abort, and traversal-timeout failures, so descendants can continue after apparent success. | Incomplete round-18 port of main #9005. | **YES:** preserve main's cancellation/acknowledgment and error semantics with bounded descendant cleanup. |
33| F07 | P1 | Releasing an OpenCode session connected to an external server only disconnects local SSE; root/child work can continue after T3 shutdown or runtime release. | Missing main #9005 teardown behavior in V2. | **YES:** perform bounded root/descendant abort before closing the event stream; log teardown failures. |
34| F08 | P1 | Cursor title/branch/commit/PR generation runs a tool-capable agent in the real cwd with sandboxing and approval review disabled. A metadata request can modify files or run commands. | Older SDK migration lost main's Ask boundary; the SDK migration itself is intentional. Local installed SDK implementation confirms the policy. | **YES:** restore an enforced non-writing metadata path; merely enabling the workspace-write sandbox is not equivalent. |
35| F09 | P1 | Web/desktop queued-message editing accepts generic files but omits them when saving. File-only edits can also be cleared when another client starts/cancels the queued run. | Branch-only editor violates the existing attachment invariant. | **YES:** include files in upload, emptiness, replacement, and draft-recovery paths. |
36| F10 | P1 | Mobile filters all non-image message attachments out before its file/PDF/video renderers, so persisted attachments appear absent. | Round-11 reconciliation regression, older than the latest 21 commits. | **YES:** retain the complete attachment list in the feed. |
37| F11 | P2 | Claude workspace-image `Read` calls appear as generic tool rows on web/desktop/mobile, losing main's image preview. | Missing main #9119 feature. Client fallbacks were checked and do not derive the image path. | **YES:** derive a validated preview path from retained Read input on both clients; a new contract variant is not required. |
38| F12 | P2 | A transient automatic-title error is treated as successful completion after one call and clears the marker. Main retries twice before giving up. | Missing main #8087 retry behavior. | **YES:** restore bounded initial-title retries while preserving stale-request guards and final cleanup. |
39| F13 | P2 | A failure that happened before a later snooze counts as an early wake, allowing automatic settlement before the requested wake time. | New V2 auto-settlement port omitted main's timestamp ordering rule. | **YES:** require failure evidence newer than the snooze, with before/after cases. |
40| F14 | P2 — performance | Every nonempty checkpoint diff loads and decodes a full V2 transcript and potentially full fork ancestors. Main uses narrow checkpoint context. | Older missed main #8988/#8992 performance invariant. Source-confirmed read cardinality. | **YES:** query only the workspace/run/scope/checkpoint fields needed for the diff. |
41| F15 | P2 — performance | Startup recovery sequentially loads full histories for every active and archived thread before command readiness, including terminal threads needing no recovery. | Older V2 scaling gap compared with main's shell/runtime-state recovery. Source-confirmed read cardinality. | **YES:** select recovery candidates and load only their required runtime state. |
42| F16 | P1 — performance | HTTP snapshot failure or a warm reconnect exceeding 128 events / 1 MiB falls back to a full lifetime projection over WebSocket and clears progressive-history metadata. Healthy cold opens are bounded. | Missing main #8992/#9000 fallback-window invariant. Source-confirmed server-to-client path; no measured payload size. | **YES:** apply the existing bounded snapshot/SQL/budget path to socket fallbacks and preserve a usable history cursor. |
43| F17 | P2 | Mobile's active V2 assistant row bypasses its specialized Markdown renderer, losing Codex file citations/template actions. On iOS it also skips environment-aware inline image/video rendering and falls back to raw URIs. | Missing main #8584 and #9023 behavior at one actual mobile caller. Web retains the intended paths. | **YES:** restore the assistant renderer, its scoped image callback, and the template action into the V2 composer. |
44
45Detailed evidence, line references, triggers, proposed validation, and limits:
46
47- [Persistence/runtime report](persistence.md): F01, F03–F05, F13–F15.
48- [Client report](clients.md): F02, F09, F10, F17.
49- [Provider report](providers.md): F06–F08, F11, F16.
50- [Cross-cutting report](cross-cutting.md): F12; independent review of migration, project, checkpoint, packaging, auth, and costs.
51
52F04 and F05 can share a project-mutation implementation, but their transport and mixed-store failure cases need separate coverage. F06 and F07 share descendant traversal but have different failure contracts: explicit Stop should surface failure; release should be bounded and log failure. F03 must be correct before F14 optimizes its reads. No recommendation requires a particular large abstraction.
53
54## Intentional differences and product decisions
55
56These are separate from the 17 findings. Previously accepted architectural choices are not reopened as bugs merely because main differs.
57
58| ID | Decision | Current V2 behavior | Recommendation |
59| --- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60| D01 | Port continuation after a server update now? | Main #9167 resumes active threads across restart. V2 intentionally withholds `serverUpdateThreadContinuation`; round 20 explicitly defers a V2 equivalent because recovery terminalizes active runs. | **YES before claiming parity**, or explicitly keep deferred. This is documented omission, not an accidental rebase resolution. |
61| D02 | Resolve inherited markdown links against the source thread? | Inspectors use the row's `sourceThreadId`; normal message/plan markdown uses the active fork. The difference matters once source and fork worktrees diverge. | **Decision needed:** YES selects source ownership; NO keeps current-fork resolution. Main has no equivalent inherited-row model to decide this automatically. |
62| D03 | Keep the Cursor SDK boundary? | SDK auth, model discovery, approval/question capabilities, and native fork/rollback support differ from V1 ACP. | **Keep the previously accepted migration.** F08's background write permission loss is a separate defect. |
63| D04 | Keep the documented legacy-import scope? | Messages and thread metadata are imported; rich old tool/run/checkpoint/approval/plan history and native provider continuity are not. First V2 continuation uses a fresh session and bounded imported context. | **Keep the previously accepted scope**, with the migration docs and product messaging. F01 is an upgrade failure, not part of this tradeoff. |
64| D05 | Keep protocol V2 incompatibility? | Old clients/servers are rejected by protocol negotiation; the CORS protocol-header fix remains. | **Keep the compatibility gate.** Deploy matching client/server versions. |
65| D06 | Keep bounded portable handoff summaries? | Ordinary portable handoffs collapse/truncate each summarized item at 240 characters; legacy-import context has a separate 32,000-character budget. | **Keep the previously accepted limit** unless fuller handoff fidelity is now required. |
66| D07 | Keep compact mobile queue controls? | Mobile can reorder, promote, and cancel queued work, but does not mirror web's composer editor/thumbnails. Working/thinking presentation also differs by surface. | **Keep as a surface choice.** F10's hidden persisted attachments is independent. |
67
68## Checks that remain unproven
69
70| ID | Question | What this audit establishes | Proposed decision |
71| --- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
72| V01 | Does V2 need main's active-tool WebSocket coalescer? | V1 #8368 coalesces stable tool updates; V2 detail streams deliver every stored event, and several adapters emit repeated full tool state. No comparable frame/byte measurement was taken. | **YES: measure a provider replay and set a frame/byte budget.** Do not copy main's claimed percentage to V2 or coalesce persisted events blindly. |
73| V02 | Is execution-triggered per-cwd skill refresh needed outside web? | Web requests cwd catalogs; Claude independently scans at execution. No equivalent V2 launch/reuse refresh was found for Codex/OpenCode, but no current-client failure was established. | **YES: add a focused stale/new-cwd scenario for CLI/mobile execution** before treating this as a confirmed missing feature. |
74| V03 | Do retained UI paths work in the real clients? | Source routing and focused logic tests cover much of the port. Sidebar hydration, file edits/rendering, visibility-lease intersection changes, and hosted project defaults lack the relevant integrated assertions. | Run targeted interaction tests with any fixes. A real web/mobile/desktop pass needs the separately required browser/simulator authorization. |
75
76The proposed claim that "both auto-settlement settings off means all minute Git/PR work is wasted" was rejected on review. Both main and V2 still settle a **closed** PR independently of those two settings. Returning early would remove intended behavior; this is not a confirmed bug or an approved optimization.
77
78## Coverage and validation
79
80The [feature map](FEATURE-MAP.md) organizes retained, changed, missing, and deliberately deferred behavior across providers, clients, persistence, remote connections, and tooling. The [main commit/file map](main-feature-file-map.tsv) lists all 165 incoming main commits and their file comparisons. Of those, 46 touch only files identical to current main; 119 touch at least one differing file. That is a triage aid, not 165 independently verified features or proof retained code is called.
81
82The [previous-fix range diff](prior-fixes-range-diff.txt) compares 41 old fix/review commits with 38 rebased equivalents. Dropped/changed client patches were reviewed against current implementations; disappearance of a patch does not by itself mean its behavior was lost. The provider, persistence, and client reports contain the invariant-by-invariant results.
83
84| Focused validation group | Result |
85| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
86| Root server/compatibility/title/CLI/usage/awareness/delegation/handoff/schedule | 17 file executions, 121 tests passed |
87| Sol provider and work-log suites | 8 file executions, 177 tests passed |
88| Sol client and latest-main follow-up suites | 16 file executions, 599 tests passed |
89| Sol persistence/history/recovery/settlement/checkpoint suites | 16 file executions, 90 tests passed |
90| Disposable upgrade probe | Real current SQL migrator reproduces the old-052 → current-053 failure in an in-memory database |
91| Disposable checkpoint probe | Real allocator and projection reducer confirm one shared root scope, reassigned to run two |
92
93The retained [audit probes](persistence-runtime-probes.test.ts) pass 3/3: they expect/capture the current migration failure, verify checkpoint scope reuse, and verify closed-PR settlement with the optional settings off. Run `vp test run audits/orchestrator-v2/2026-09-02/persistence-runtime-probes.test.ts` from the repo root. The exact output is in `persistence-runtime-probes.log`. These probes are separate from the product-test counts above.
94
95Suite counts include overlap between reviewers and are not a count of unique tests. Green tests do not disprove the findings: the reports identify missing scenarios, and the checkpoint happy-path fixture models a scope layout production cannot produce.
96
97No repo-wide checks, dev servers, browsers, simulators, live providers, external OpenCode calls, or production-state writes were used. The source audit covers the changed subsystems and feature families; it is not an exhaustive execution of every path in 942 changed files. Performance severity remains qualitative without a workload benchmark.
98
99Use [DECISIONS.tsv](DECISIONS.tsv) to record yes/no/defer decisions. Full references and inventories are alongside this report in [references.json](references.json), [branch-commits.txt](branch-commits.txt), [new-branch-work.txt](new-branch-work.txt), [main-since-prior.txt](main-since-prior.txt), and [changed-files.tsv](changed-files.tsv).
audits/orchestrator-v2/2026-09-02/DECISIONS.tsv+28−0
@@ -0,0 +1,28 @@
1id priority category decision_question recommendation decision evidence
2F01 P1 new rebase upgrade defect Fix upgrades from old V2 migration histories, including the missed main044 repair? YES OPEN persistence.md — finding 1
3F02 P1 new rebase client defect Fix the sidebar fallback declaration so cached/disconnected environments do not crash? YES OPEN clients.md — sidebar finding
4F03 P1 older V2 correctness defect Fix the shared checkpoint-scope baseline lookup after an ordinary second run? YES OPEN persistence.md — finding 5
5F04 P1 older mixed-store lifecycle defect Fix forced WS project deletion rejecting after imported V2 threads were deleted? YES OPEN persistence.md — finding 2
6F05 P1 older transport lifecycle defect Make HTTP/live CLI/offline CLI project mutations honor V2 threads, force and creation fields? YES OPEN persistence.md — finding 3
7F06 P1 partial main port Port OpenCode descendant cancellation and error/acknowledgment semantics? YES OPEN providers.md — OpenCode Stop
8F07 P1 missing main lifecycle behavior Abort external OpenCode root and child work when releasing the runtime? YES OPEN providers.md — external OpenCode release
9F08 P1 older SDK behavior regression Restore an enforced non-writing boundary for Cursor background metadata generation? YES OPEN providers.md — Cursor metadata
10F09 P1 branch-only attachment defect Preserve generic files in queued-message save and cross-client edit recovery? YES OPEN clients.md — queue-edit finding
11F10 P1 older rebase mobile defect Restore all persisted attachment types to the mobile feed? YES OPEN clients.md — attachment-filter finding
12F11 P2 missing main feature Restore Claude workspace-image Read previews on both clients? YES OPEN providers.md — Claude image preview
13F12 P2 missing main fix Restore bounded retries for transient initial-title failures? YES OPEN cross-cutting.md — automatic title retries
14F13 P2 new V2 port edge case Require failure evidence newer than a snooze before auto-settlement treats it as a wake? YES OPEN persistence.md — finding 4
15F14 P2 confirmed query cost Use narrow V2 checkpoint queries instead of full transcript hydration for diffs? YES OPEN persistence.md — finding 6
16F15 P2 confirmed startup cost Restrict startup recovery to relevant runtime state instead of every full history? YES OPEN persistence.md — finding 7
17F16 P1 confirmed reconnect payload cost Keep WS fallback snapshots bounded on HTTP failure and large reconnect gaps? YES OPEN providers.md — WebSocket snapshot fallbacks
18F17 P2 missing main mobile renderer Restore the mobile assistant Markdown adapter, citation/template actions and iOS scoped image/video renderer? YES OPEN clients.md — assistant renderer finding
19D01 decision explicitly deferred main feature Port active-thread continuation across server updates now? YES for parity; otherwise explicitly defer OPEN AUDIT.md — D01; cross-cutting.md
20D02 decision new V2 ownership semantics Should inherited message/plan links resolve against their source thread instead of the active fork? Product choice: YES=source; NO=current fork OPEN clients.md — inherited fork ownership
21D03 decision previously accepted architecture Keep the Cursor SDK boundary and its documented capability differences? KEEP; fix F08 independently PREVIOUSLY ACCEPTED providers.md — intentional differences
22D04 decision previously accepted architecture Keep legacy import limited to messages/metadata and a fresh provider session? KEEP documented scope PREVIOUSLY ACCEPTED docs/user/thread-migration.md; persistence.md
23D05 decision previously accepted compatibility Keep protocol V2 rejection of incompatible old clients/servers? KEEP PREVIOUSLY ACCEPTED cross-cutting.md — compatibility
24D06 decision previously accepted context tradeoff Keep 240-character portable item summaries and separate 32000-character legacy context budget? KEEP unless fuller fidelity is required PREVIOUSLY ACCEPTED cross-cutting.md — handoff limits
25D07 decision established surface difference Keep compact mobile queue controls without duplicating web editing/thumbnails? KEEP; fix F10 independently OPEN clients.md — surface differences
26V01 verification unmeasured stream cost Measure V2 tool-update frame/byte counts against main's coalescing invariant? YES: measure before prescribing the change OPEN providers.md — coalescing validation gap
27V02 verification unproven skills parity Check cwd skill discovery after CLI/mobile execution or reuse outside web? YES: focused scenario OPEN providers.md — execution-triggered refresh
28V03 verification interaction coverage Run focused real-client checks for fixes and retained visibility/hosted-settings paths? YES when authorized; no browser permission assumed OPEN clients.md — verification gaps
audits/orchestrator-v2/2026-09-02/FEATURE-MAP.md+89−0
@@ -0,0 +1,89 @@
1# Feature map for the 2026-09-02 audit
2
3Frozen comparison: branch `d2f1f511f4` against main `57a66608b9`. Finding and decision IDs refer to [AUDIT.md](AUDIT.md). **Retained** means the relevant source path was traced and no discrepancy was found in this audit; it is not a guarantee for every provider/client combination. Exact source references and test cases are in the linked domain reports.
4
5The complete mechanical inventories are [942 changed files](changed-files.tsv), [332 branch commits](branch-commits.txt), [165 main commits](main-since-prior.txt), and the [main commit/file comparison](main-feature-file-map.tsv). The last file distinguishes unchanged leaf implementations from files requiring V2 behavior review. Its `review-v2-path` label describes that triage category, not a test result.
6
7## Providers and registration
8
9| Feature family | Main behavior / V2 implementation | Audit result |
10| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
11| Instance registration, capabilities, models | Shared provider registry/drivers feed V2 adapters; removed custom models disappear, Claude remote manifests and bounded OpenCode version checks are retained. | Retained on inspected paths. [Provider evidence](providers.md). |
12| Cwd/project skills | Claude, Codex, and OpenCode expose cwd snapshots; web requests missing snapshots, Claude also reads skills at execution. | Main execution-time refresh outside web remains unverified for Codex/OpenCode: V02. |
13| Codex prompt content and usage | Native image input plus generic attachment path text; live token usage and persisted terminal-omission behavior. | Prior fixes retained; focused shared/adapter/projection tests passed. |
14| Codex feedback | Persistent feedback anchors, duplicate/in-flight guards, environment/thread ownership. | Prior fixes retained on web/mobile shared command paths. [Client evidence](clients.md). |
15| Claude approvals | Session-only permission suggestions, abort already signaled, policy-specific tool access, questions handled in every runtime mode. | Prior fixes retained; focused adapter tests passed. |
16| Claude questions | Structured/multiple questions, multi-select normalization, structured response contract. | Prior fixes retained. |
17| Claude plans, todos, compaction, subagents | Stable planning item identities/lifecycle, resume and compaction controls, model propagation across native task ordering. | Prior fixes retained. |
18| Claude workspace-image reads | Main classifies image Read input for inline preview. V2 preserves input but neither client derives a preview path. | Missing main behavior: F11. |
19| OpenCode prompt admission | Native request/message correlation, generation-owned status reconciliation and retries, cancellation of pending initial/steer HTTP prompts. | Prior fixes retained; targeted tests passed. |
20| OpenCode cancellation | Main aborts the descendant tree and reports relevant failures. V2's new descendant traversal discards failures. | Partial port: F06. |
21| OpenCode external-server teardown | Main aborts root and descendants when releasing the session. V2 release closes its subscription only. | Missing lifecycle behavior: F07. |
22| Cursor SDK boundary | Native SDK replaces ACP, with different auth/model/capability support. | Intentional D03. Background text-generation write boundary is separately broken: F08. |
23| Grok and generic ACP | Flavored ACP model selection, health and interruption controls; generic adapter/runtime support remains. | No additional source-confirmed regression found in the provider review. |
24| Source-control and title generation | Main PR-linked title context and shared prompt changes remain in use. Initial title transient-error retries are absent. | Retained prompts; F12 and Cursor-specific F08. |
25
26## Persistence, thread lifecycle, and orchestration
27
28| Feature family | Main behavior / V2 implementation | Audit result |
29| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
30| Database upgrades | Numeric migration runner plus inserted main migration 044; previously applied V2 IDs were shifted. | Fresh-schema tests pass, old-V2 upgrade fails: F01. |
31| Legacy import | Metadata, pins/order, snooze and PR links copied and repaired; legacy rows kept for compatibility. | Prior metadata/search fixes retained. Loss of rich legacy history/native continuity is documented D04. |
32| Project removal | Main enforces nonempty/force checks and deletes its thread representation consistently. V2 has mixed-store and transport-specific paths. | F04 over WS; F05 over HTTP and live/offline CLI. |
33| Thread archive/delete visibility | V2 lifecycle, archived shell separation, migrated ownership filters in legacy search. | Prior filtering and history visibility fixes retained. Project-level cleanup is the exception above. |
34| Worktree recovery | Missing worktree is recreated before provider turn start. Shared Git paths preserve local-only base support. | Recovery fix retained; scoped existing tests passed. |
35| Queue ordering/delivery | Durable queued runs, delegated-completion priority, promote/steer/cancel controls. | Queue-order and delegated-completion tests passed. File editing is a separate client defect F09. |
36| Settlement, pinning, snoozing | V2 server sweep evaluates main's inactivity/PR rules with live/pending-work exclusions and snapshot guards. | Most rules retained; failure-before-snooze ordering missing: F13. Closed-PR settlement remains intentionally active with optional settings off. |
37| Unsettled ordering | Stable reanchoring and shared max(created/updated/unsettled) sorting. | Prior server and client fixes retained. |
38| Recovery/outbox/receipts | Durable event/projection/effect transactions, replayable vs process-bound effects, checkpoint wait recovery, typed command receipts. | Focused recovery, effect worker, finalization, and runtime tests passed. Startup's query scope is overbroad: F15. |
39| Checkpoint capture | Shared deterministic scope and ordinal refs; replayable capture/finalization. | Capture invariants retained. The diff query assumes an incompatible scope layout: F03. |
40| Checkpoint query cost | Main queries narrow checkpoint context; V2 loads full projection. | Confirmed data-loading regression: F14. |
41| History pagination | V2 SQL filters the visible cohort before LIMIT, retains paired stop requests and nested fork identities, and pages to the actual end. | Earlier fixes retained; SQL/history tests passed. |
42| HTTP detail and WS fallback payloads | V2 central row/byte/control-plane budgets, overflow reporting, bounded SQL window protect healthy HTTP cold opens. Socket fallbacks bypass them. | Earlier HTTP budgeting fixes retained, but failed HTTP loads and large reconnect gaps expose F16. |
43| Portable provider handoffs | V2 supports context transfer and provider changes, using bounded portable summaries and a separate legacy-import budget. | Documented D06; focused context-handoff tests passed. Full provider-switch/fork permutations were not live-tested. |
44| Delegated work | App-owned child tasks, durable completion transfer, parent wake/deduplication. | Selected completion-delivery tests passed; branch-added feature rather than a direct V1-equivalence claim. |
45| Scheduled tasks | App-owned interval/fixed-time scheduling and legacy interval compatibility. | Existing schedule tests passed, including legacy sub-minute floor and missed-time grace. No wall-clock/live scheduler test was run. |
46| Server update continuation | Main's active-thread restart continuation is gated off by the V2 server descriptor. | Explicitly deferred D01, distinct from recovery correctness. |
47
48## Web, desktop, mobile, and shared client state
49
50| Feature family | Main behavior / V2 implementation | Audit result |
51| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52| Sidebar environment/provider fallback | Cached shells can precede provider config. Main has a module-scope empty map; V2 declaration sits after return. | Web/desktop crash: F02. |
53| Sidebar status subscriptions | Visibility leases, overscan, active-row retention, short idle grace for VCS and linked PR state. | Source port retained; intersection-driven release lacks integrated coverage (V03). |
54| Composer draft/worktree setup | Promotion retains text entered during setup and moves the draft to its canonical environment/thread scope. | Main #9197 retained; direct store tests passed. |
55| New-thread model choices | Sticky user selections seed drafts; only explicit provider options persist. Hosted project defaults use the representative environment. | Main #9164/#9142 retained in source. Hosted picker/mutation interaction remains untested. |
56| Context meter/settings | Opt-in default false, Settings control, composer gating. Shared server preference paths remain. | Main #9190 retained; contract tests passed. |
57| Attachments in ordinary sends | Shared upload claims, generic-file prompt descriptions, native image inputs, ownership and caps. | Prior fixes retained. Queue edit and mobile display have F09/F10. |
58| Queued-message editing | Web accepts generic files in ordinary composer edit mode but saves only new images. | F09. Compact mobile controls are D07, not the same bug. |
59| Feed attachments | Main mobile keeps images and generic files; V2 filters to images before rendering. | F10 blocks visible file/PDF/video history despite retained renderer implementations. |
60| Media/file viewer entry points | File/share acquisition, eager upload, navigation guards, offline voice, external file viewing, stale-save coordination, and panel refresh paths remain wired to V2. | Detailed caller coverage is in [clients.md](clients.md). End-to-end media remains partial: F10 hides attached files, and F17 bypasses iOS assistant inline media resolution. |
61| Feedback placement | Timestamp-anchored rows and direct duplicate guards on both surfaces. | Prior fixes retained. |
62| Plan mode | Disabled legacy plan mode forces effective default mode before persistence/dispatch. | Prior web fix retained; no contradictory mobile override found. |
63| Markdown files/images | Workspace assets, Windows paths, editor/reveal/modifier routing and owning-environment selection. | Prior web direct-row fixes retained. iOS assistant inline media is broken by F17; inherited fork context remains D02. |
64| PR links and actions | Environment-scoped resolution, link/unlink, external/open actions and current-row ownership. | Prior behavior retained in inspected paths. Shared PR panel/driver changes listed in the main file map remain outside V2 event translation. |
65| Inline citations | Selected assistant text, active environment/thread ownership, V2 assistant wrapper, mobile readable fallback. | Main #9146 retained; citation/draft tests passed. |
66| Codex file citations, artifact templates, iOS inline media | Web still uses the directive renderer and appends chosen template prompts. Mobile bypasses its assistant helper, including its native image callback. | F17 covers the single caller gap behind main #8584/#9023 losses. Android's custom image renderer remains wired. This is separate from selected-text inline citations above. |
67| Activity groups and errors | Failed tools remain visible; web working/thinking rows and compact native groups use different presentations. | Relevant prior fixes retained. No real scrolling/timing/GPU measurement was performed. |
68| Mobile header and scroll fixes | Native back/title state patch, width/header dependencies, post-animation bounds updates. | Prior patches/current source retained; no simulator pass. |
69| Project picker | Contained combobox and popup overflow constraints. | Prior fix absorbed into current source and retained. |
70| Desktop | Wraps the web client, while shell/IPC/packaging and update infrastructure remain separate. | Web defects apply to desktop too. Native quit/update/preview behavior was source/inventory checked, not exercised on a second installed app. |
71
72## Remote operation, costs, packaging, and compatibility
73
74| Feature family | Main behavior / V2 implementation | Audit result |
75| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
76| Protocol negotiation | V2 descriptor/socket gating and CORS allowance for the protocol header. | Previous fixes retained; WS/client compatibility tests passed. Intentional version break D05. |
77| Remote cookie identity | Environment identity initializes auth dependencies; cookie isolation paths retained. | Main #8085 survives the cutover in inspected source. |
78| Relay credential refresh | Refresh-before-expiry implementation unchanged from main. | Main #9178 retained at the shared layer; no actual expiry/relay run. |
79| HTTP route limits | 512-character parameter limit remains wired to router serve. | Main #8898 retained. |
80| RPC scopes and awareness | Renamed/new V2 RPCs explicitly classified; awareness uses current event tail rather than replaying all history. | No removed scope boundary found; shared awareness tests passed. |
81| Attachment cleanup ordering | Main defers deletion until command commit. V2 transaction persists cleanup outbox before worker notification. | Invariant retained; attachment-store/claim tests passed. |
82| Billing/token pricing | Cached Claude pricing, incremental transcript reader, scan-cache preservation unchanged from main; V2 usage projection retains omitted terminal values. | Main #8806/#9024/#8540 and previous V2 usage fixes retained; targeted tests passed. No confirmed token-price regression. |
83| Active-stream network cost | Several adapters emit repeated full tool state; main's semantic coalescer is absent from V2 detail delivery. | V01: source exposure, no measured frame/byte regression. |
84| Reconnect/fallback transfer cost | Main preserves a bounded fallback window; V2 sends a lifetime projection after HTTP failure or an over-budget resume. | Confirmed F16. Individual tool-value truncation does not bound collection cardinality. |
85| Startup/diff database cost | Queries read more transcript state than the operations need. | Confirmed F14/F15; no invented benchmark percentages. |
86| Server bundle/service launch | Both server and service-launcher entry points remain included in build configuration. | Previous packaging fix retained; no packaging/release build run. |
87| Shared non-orchestration features | PR driver/cache changes, themes, editor leaf components, native build/quit/preview updates, Windows shell utilities, release/tooling changes. | Inventoried against main. Unchanged leaf files are distinguished in the commit/file map; this audit does not claim end-to-end OS/build coverage for them. |
88
89The remaining gaps are explicit in [AUDIT.md](AUDIT.md): provider/burst performance, cwd refresh outside web, and real-client interaction. The review does not treat lack of a test, a renamed V1 component, or a deliberate V2 representation change as proof of a regression.
audits/orchestrator-v2/2026-09-02/branch-commits.txt+332−0
@@ -0,0 +1,332 @@
1d2f1f511f4 fix: reconcile main's round-20 features after the rebase
20550e0a34d fix(web): realign the composer and timeline with main
3e6da41e5c6 fix(web): right-align the stash shoulder tab again
4f0174c4577 fix: reconcile main's round-19 features after the rebase
52191297898 feat(server): evaluate automatic thread settlement in the v2 orchestrator
67697286069 fix: reconcile main's round-18 features after the rebase
799e940dc2b feat(web): port working and thinking timeline rows to orchestration v2
8acdbe292f0 fix: reconcile main's round-17 features after the rebase
94b35166d02 fix(mobile): keep scroll bounds current after animations
10fa54f54607 fix(chat): remove added tool summary status counts
11b27f50b65a feat(mobile): port chat summaries and transitions to orchestration v2
1260bc3cff93 feat(web): summarize T3 orchestration actions
1372e0c434be fix(web): keep composer shortcut tooltip stable on Mod
146bcc0e39da fix(web): match composer actions to draft and modifier state
156ce4cb0a15 fix(web): keep queued messages in place while editing
16e16d934d8a fix(web): keep queued message editing inside the queue panel
17b2afc4cb45 refactor(web): use shared banner rows for queued messages
1885a3c12568 fix(web): keep stash separate from the composer activity column
19e82c717855 fix(web): share the outline for joined composer tabs
20b1533813ce fix(web): align queue headers and prevent stash overlap
21ed6aafa93f fix(web): port composer activity and grouping to orchestration v2
22c1791ab263 fix(orchestration): select visible history before limiting SQL
2347ae99f517 fix(server): recover OpenCode status reconciliation
240fc565db8a fix(orchestration): retain nested fork history when paging
2507889933e3 fix(server): cancel pending OpenCode prompts safely
26870014496f fix(orchestration): page history through its true end
27051565fc96 fix(web): retain markdown workspace ownership
28be7b07634b fix(clients): anchor feedback in conversation order
29de79f44221 fix(server): correlate OpenCode prompt admission
30dbb6021f84 fix(server): normalize Claude question answers
3170f82caaa0 fix(server): preserve Claude planning lifecycle
3226dfbc2984 fix(server): preserve provider usage in persisted turns
33733db7269f fix(server): allow protocol negotiation in CORS
343d5bab73d8 fix(server): restore Claude resume compaction
350921786018 perf(orchestration): bound complete thread snapshots
369a4734af8e perf(orchestration): bound history reads in SQL
37ff9f875f17 fix(server): project Claude plans and todos
38ecf3dd2c9a fix(server): restore Claude structured questions
3998670f337e fix(server): guard OpenCode prompt admission races
4068104a497e chore(repo): remove tracked audit scratch files
4119a30d03be docs: state portable handoff limits
423e1ff1531d docs: explain legacy thread migration
43995f4e7e5a fix(protocol): reject incompatible orchestration peers
4444f2004810 fix(web): scope markdown actions to their environment
455f19d550ae fix(web): restore markdown file chip actions
46642056fdd9 fix(server): keep current provider context usage
477fca26e6fb fix(web): preserve Windows markdown paths
4853c6857ec9 fix(web): load workspace markdown images through assets
498bbf7f50c8 fix(server): preserve generic provider attachments
50e44588afa4 fix(clients): restore Codex feedback submission
51dcff638944 fix(orchestration): recreate missing worktrees before turns
52eacc09d40a fix(orchestration): honor migrated thread visibility in search
532cfe234e46 fix(server): preserve Claude subagent models
54930dd22e72 fix(web): honor disabled legacy plan mode
5582e1b8ed60 fix(orchestration): preserve legacy thread metadata
56feedc3f6bb fix(server): include service launcher in bundle build
576b3690e063 fix(server): observe pre-aborted Claude approvals
587e71ff8a80 fix(orchestration): reanchor unsettled threads
5934a4d026a5 fix(server): keep Claude session approvals ephemeral
60b9400c36a3 fix(web): keep failed tool items in the collapsed group summaries
61e0ab034b43 fix: reconcile main's round-16 features after the rebase
6291572bcaf4 chore: refresh macroscope ui-consistency check
638e0d521beb chore: retrigger ci
64bbbaeeb2a3 fix(server): inject HostProcessPlatform into the Grok plan extractor
656211ffaa69 fix(lint): allowlist the queue and relationships interop boundaries
6679ab9ff211 fix(mobile): replace remaining dark: utilities with adaptive semantic tokens
67d17919b845 fix(web): dedupe the composer glass styles and align the chat column width
68aafe194a88 fix: reconcile main's round-14 features after the rebase
693fd374226d fix(web): drag-to-reorder queued messages and retire stale pending rows
70a1d309ea77 feat(web): show attachments on queued messages and edit them in the composer
711c58988751 feat(server): claim uploaded attachments at v2 dispatch
72bf963f1d4f fix(web): restore the full-screen file-drop target over the chat column
73084d72779d fix: restore main's automatic thread settling after the revert
74a2efa89c95 fix: reconcile main's round-12 features after the rebase
75690ea07bac fix(opencode): route child-session approvals through the v2 adapter
760301dc423e fix: reconcile main's round-11 features after the rebase
77c380aa6c87 fix(web): collapsed tool rows preview inputs for every tool type
781c3b15a835 fix(web): show the command on collapsed tool rows, not its stdout
7972056ac893 fix(web): surface v2 todo-list plans as task progress
80c0cbbbc844 fix(web): collapse settled tool runs behind main's summary toggles
81a8df962501 fix(web): converge ChatComposer on main's drawer-era body
82d81b12f2aa fix(web): adopt main's attached-composer surface contract so the glass survives shoulder tabs
830b8e40fbc5 fix(web): repaint the composer glass and strip the thread-panel popover chrome
848fdce45017 feat(grok): capture exit_plan_mode into the v2 proposed-plan card (#8358)
852c188dd57b fix(web): keep following the stream after returning to the live edge (#6519)
86dfc21af186 feat(orchestration-v2): show live context usage in the meter (#8144)
8747b1560306 fix(grok): fail hung prompts on xAI rate-limit completions (#8358, partial)
88a854b2fcfd feat(analytics): credit v2 threads and turns to the starting client (#7774)
894eb1099038 feat(orchestration-v2): route Codex thread feedback uploads through v2 (#7949)
90f894a3531f feat(orchestration-v2): carry approval options and app names to the client (#8058)
91650a24252e feat(orchestration-v2): project linked pull requests on threads (#8160)
921533041bfd fix(web): stop mis-marking recovered and text-reported tool failures in the v2 work log
93ab8569a7a3 fix: reconcile main's round-10 features after the rebase
945d1e08ef48 fix(orchestration): show provider retries in the work log
958bf0b5fcb3 refactor(web): finish aligning the branch with main's style simplification
96f22abd94cd fix(web): restore the titlebar sizing and timeline fade lost to main's style simplification
972e42519705 feat(server): honor withheld agent browser access in the v2 runtime
98796911d54d fix: reconcile main's round-9 features after the rebase
995c722de01c feat(mobile): surface prominent activity status and metadata
100d734b66fff fix(server): reject replaying a command receipt across threads in v2
101ace57ebdfc fix: reconcile main's round-8 features after the rebase
102e74f01c66a perf(server): keep shell snapshots bounded and active-only
103a39f6a267d fix(web): size the titlebar layout-control icons like the sidebar trigger
1047c8af3708e fix(web): align titlebar clusters to one shared pixel inset
1053d812634cf fix(web): keep the titlebar layout controls fixed across right-panel toggles
106b16ac84793 fix: reconcile main's round-6 features after the rebase
107e861e74df5 feat(contracts): track thread title regeneration
108862479b515 feat(orchestration): bound thread history and resume payloads
109167f5f6068 fix(web): restore main's collapse chrome and tab-status keying on the PR panel
110365ffce101 feat(web): prioritize pull request row actions
1119e7a5b0566 fix(web): reconcile main's round-5 features after the rebase
1126f39090bcb fix(mobile): port main's composer stabilization into the v2 thread screens
11315d7ed4ddc feat(web): add pull request actions to thread details
114e69b9944e5 fix(web): restore the branch's slim chat header
115e3fb981cac test(server): expect attachment saved-at lines in ClaudeAdapterV2 turn text
11675fdf57d99 test(web): restore main's right-panel migration expectations after the panel-visibility merge
1171ffb1eaf78 fix(server): port round-3 main fixes into the v2 orchestrator
118fbfa125f2d chore(server): renumber v2 migrations 038-046 to 041-049 after main's 038-040
119f2d1f16c3c fix: repair rerere-damaged files and reconcile main's round-3 features with v2
120fddf025c0b fix(web): show Git action success inline in panel
12109c99a51a1 fix(web): let LegendList own end-follow and disclosure anchoring (#5449)
122bed1245b22 fix(web): port the refined live-follow gesture gating to the v2 timeline
1234e87742e9b chore(web): prune plan-sidebar leftovers after the inline-plans rework
124e778ae6f30 fix: port main fixes stranded by the v2 rewrite (round 2)
125bfb9d9465a fix(server): renumber v2 migrations after main's 037_ProjectionTurnsKeysetIndex
12620cec19eb5 fix: repair conflict-marker artifacts from rebase auto-resolutions
127ec7370e7e5 fix(web): align git action progress button layout
1282b4e393ff5 feat(orchestrator): Surface waiting background work (#4378)
1296a924e9d5f test(server): align migration expectations with renumbered ids
1302e3b863826 fix: port main fixes stranded by the v2 rewrite
131fa557cd774 fix(server): renumber v2 migrations after main's 036_ProjectionThreadsPinned
1323699b557e9 fix(web): remove open PR actions from git controls
13314ca09db7b fix(server): stop tying codex text-generation temp files to the caller's scope (#5406)
1347f3ebe40d9 fix(orchestrator): Prevent redundant delegated completion turns (#5311)
1351b91a552e0 fix(test): keep codex replay recovery off the repo checkout
1364ffad32204 chore: resolve lint warnings across v2 code
13739fab14dc4 test(server): cover v2 thread title regeneration
138fa775c22ad fix(web): restore compact header sizing for project script controls
139d4e31cb20f fix: send mcp-protocol-version header in worktree registration test
140704808d923 fix: adopt effect beta.103 APIs in rebased v2 code
1414e72ea725c feat(chat): refine V2 conversation UI (#5307)
142d13cb6b8f5 fix(server): restore worktree branch naming in the v2 orchestrator (#5309)
14378aea07a31 fix(orchestrator): Order thread lineage by creation time (#5310)
1446a8fcc6576 fix(orchestration-v2): restore generated thread titles (#5176)
1456a7824f352 fix: reconcile rebase with latest main
146cb592a7a92 fix(web): keep Git progress title anchored
147790fe93a99 fix(web): remove elevated thread details panel styling
1483cd279a07a refactor(orchestration): split thread-not-sendable into typed errors
149aadfcb978b fix(orchestration): promoted queued messages keep the queued_turn intent
150e2284c99f1 fix(orchestration): thread visits no longer create activity loops (#5038)
151888283cd7f feat(orchestration): port thread title regeneration to the v2 runtime
1525e29230798 perf(web): keep timeline minimap animations off the main thread
153c123bddb2b fix(relay): stop replaying the whole event store into the awareness relay
1543dc8b570af fix(chat): prevent stale timeline scroll and rerenders
1550ca2c503d9 perf(orchestration): per-thread shell deltas, visit throttling, event compaction (#4971)
1560d57ac71ae [codex] feat(web): show git progress in the commit button (#4963)
1577b28551f4b feat(orchestration): track provider retries and thread visits
1580ee02d5490 feat(server): surface legacy thread migration progress
159dd9b6b0d4b fix: close failed provider adapter scopes
1606832caec1f fix: preserve thread management failure semantics
161981b2ddc74 fix: address latest orchestration review findings
162a2b1e1ef93 fix: preserve orchestration task identity
1635de1955591 fix: address orchestration review findings
164fa408d129f fix(mobile): label queued message intent
1659aeb65f738 fix: address orchestration review findings
1668af2b58d15 fix(orchestrator): preserve migrated and nested history
167327189894e fix(server): preserve project mutation client errors
168e59043aebb fix(orchestrator): validate rollback and search links
169614c744be9 fix(orchestrator): close cancellation edge cases
170af590d13ae fix(orchestrator): decode direct Claude result blocks
1719c3a6b75e9 fix(orchestrator): validate replay edge cases
17269299bf47b fix(orchestrator): preserve terminal effect outcomes
1737b1e9dad86 fix(orchestrator): preserve retryable effect failures
174c7a62d3c36 fix(orchestrator): avoid replaying settled effects
175c17955171a fix(worktrees): make handoff rollback atomic
17649ad52e66d fix(orchestrator): release stranded effect claims
1770a9a06ba64 fix(orchestrator): execute resolved runtime responses
178785e1388d0 fix(orchestrator): harden provider edge cases
179ff10b65fe6 fix(orchestrator): handle fresh review edge cases
1805a2a714d78 fix(orchestrator): address late review findings
18180e5ef9865 fix(mobile): gate thread controls on live runs
1826179f46681 fix(checkpoints): retain thread-start baseline after failed runs
183c2145dde12 fix(client): preserve live thread relationships
184153a1f8548 fix(web): enforce secure provider field defaults
185afeb2089d2 fix(claude): preserve explicit model options
1869f71aa9ca6 fix(server): isolate deterministic attachment ids
187c6adb55a0a fix(testkit): harden provider replay recording
1880d5dbddd16 fix(acp): enforce task and permission invariants
1896c30ce289b fix(orchestration): preserve imported conversation state
19009963b2172 fix(checkpoints): preserve valid run history
191345a9a4a3a fix(acp): discover final teardown descendants
192120242d9fb fix(cursor): log close attempts before execution
1930db5a1d2e6 fix(mobile): distinguish queued and waiting archive states
1941fdab432e8 fix(mobile): allow archiving post-provider work
1950900ea01d9 fix(orchestration): cancel queued work on archive
1967252e40574 fix(orchestration): handle checkpoint-wait runs
1973edb72687d fix(acp): make xai cancellation reliable
1988d27ca499e fix(contracts): reject invalid legacy intervals
19951e044f7ad fix(orchestration): preserve legacy schedule compatibility
2008006f38f2e fix(mobile): preserve active thread state
20132d563e07c fix(orchestration): harden scheduled task startup
2026c82f71da5 fix(orchestrator): schedule effects from durable deadlines (#4656)
2033ba155db4f feat: migrate v1 state into orchestrator v2 (#4400)
204d59b7c2ba3 chore(orchestrator): refresh checks after main sync
205ef8219c2e3 fix(web): restore checked-in project scripts
2064d9180dc78 fix: ignore subagents when sorting sidebar projects
2071e42d8e909 fix: hide subagent threads from v2 lists
2080e018044d7 fix(claude): Settle positive task-notification results
209ba3c821004 fix(orchestrator): Wake settled parents when delegated children finish
2109649b22600 fix(acp): Preserve wake evidence across an app-owned wake
211da04feea48 fix(grok): Prevent spurious wake run after in-turn monitors
2124a3797b482 test(orchestrator): align merged V2 compatibility checks
21316b8599b26 fix(server): clean up Claude replay failures
21445d60f9a51 fix(server): enforce ACP auth and preserve fork provenance
215bafa7a432b fix(server): keep derived threads awake
2160bc459bd0f fix(mobile): wait for fork shell before navigation
2177ae175a5f3 Add worktree handoff and status tools to the t3-code MCP server (#3754)
21883ea5f70c1 feat(subagents): disclose projected results consistently (#3866)
2192a25e1d2de fix(orchestrator): hydrate shell cache and group multi-environment projects (#3640)
220b2e4223af6 test(orchestrator): align Codex approval reviewer replays (#4457)
221c93936748b fix(orchestrator): Preserve claude/codex post-interrupt recovery state (#4229)
2222f50f6b93f test(orchestrator): Align post-merge CTM fixtures (#4193)
223b07b35f8fc fix(web): contain thread details panel effects
2247e3d211988 refactor(web): use shared glass surfaces
22592bfc55bc3 fix(web): remove stacked composer shadows
22639505241c8 fix(web): restore v2 composer chrome
227092daf3c49 fix(server): preserve released migration ordering
2288e173085a7 fix(orchestration): clarify agent delegation and scheduling
229d8b06653df Unify T3 MCP tool presentation across clients
230c026716d34 Render T3 MCP tools with branded timeline labels
231643047ad47 fix(grok): align ACP extensions with open source runtime
2321695b69e12 fix(mobile): support Hermes collection sorting
233a210dc8c28 test(orchestrator): align integration fixtures
2348e879081c3 fix(orchestrator): Harden Grok v2 runtime lifecycle
235a307962b2b fix(orchestrator): dedupe Grok continuation dispatch
236555b0469e9 fix(orchestrator): harden Grok v2 lifecycle (#3578)
2379a065758da fix(acp): bind MCP credentials to activated threads
23814f5595b68 fix(acp): release turns after interrupt timeout
239f17413cc12 fix(claude): reopen queries after MCP credential rotation
240cbcfea8b7d fix(claude): enforce read-only tool availability
241bf9a8775d7 fix(claude): honor never-approval runtime policies
24266f3b79c8e fix(claude): allow questions during plan mode
243cec49e2ca0 fix(claude): preserve approvals with full-access sandbox
2446f0e825e48 fix(claude): redact launch arguments from protocol logs
24517d2f5d2d4 test(desktop): expect orchestrator v2 state directory
2466e01338ad3 fix(ci): restore Claude permission request identity
2474060032ea1 fix(orchestrator): align Claude permission replay with SDK
248ec90f1ec95 feat(orchestrator): pass model options through MCP thread targets (#3872)
249822242fc77 fix(orchestrator): scope Claude MCP tool pre-approval (#3862)
250935aa5deb7 [orchestrator-v2] fix(orchestrator): Codex background command completion and subagent resume (#3908)
25155fd431a06 [orchestrator-v2] fix(orchestrator): Restore Claude session continuity for resume, wake, and idle release (#3860)
252698f41eb60 feat(orchestrator): Add shared provider continuation and background item plumbing
25325df2c7c87 feat: scheduled tasks (automations) (#3638)
2545dc316e8eb Fix Claude task turn mapping
255f2711e089d Allow provider switching via handoff in chat threads
256695c57b422 Remove early access badges from Cursor and Grok
2576c78a01831 Switch Cursor provider to the official SDK
2585f2045fcc3 fix(web): align thread details panel controls and menus (#3606)
259f0bd8850aa Require Cursor API key for provider checks
260036f3f9daf Keep persistent cards visible in folded turns
2610eb1fc59ab Clarify thread relationship icons and ordering
2624313ad89dd Map nested Codex subagent threads correctly
2639f06992061 Adopt userdata-v2 and subagent activity mapping
264db9310c2f6 Map Grok task envelopes to subagent lineage
2654d8e17808b Add iOS associated domains for Clerk
2662e05af6746 Retire V1 client orchestration parity
267af6028c82d Expose V2 thread workflows on mobile
268327f7287bb Enrich mobile V2 execution items
2691a2b7432e9 Render mobile timelines from V2 turn items
2706c6e346d38 Hide subagent threads and simplify thread controls
2710531d50ed5 Split open-in editor controls into panel and toolbar variants
2722aad9bc514 Reserve space for inline thread details panel
273ecddebcc67 Map thread panel into title bar and sidebar
274fcc2a2d096 Record created threads and subagent progress
275ef051d86d8 Handle preparing turns across provider orchestration
276c39d610069 Integrate orchestration V2 controls and process recovery
277b1c074b2ec Complete orchestration V2 frontend cutover
278c9dec10181 Split the V2 frontend plan into parity and enrichment phases
279b56d0e7d53 Integrate orchestration v2 with the application runtime
280f254d805e7 Guarantee MCP revocation during session release
281708aed97c9 Remove MCP credential expiration
282e9e19735a9 Require MCP registry for V2 provider sessions
2833f52b6a9ec Complete orchestration V2 application services
284900c8ec277 Start orchestration V2 application services
28574115dcb01 Align orchestration V2 with Effect service conventions
28665345a5bb4 Share Codex sessions across orchestration threads
287fb1ebd0f2f Map orchestration turns to provider instances
288a115e55b34 Add Orchestration V2 application integration plan
289e35661a849 Add MCP thread management and Codex turn mapping
290ad109c26f5 Add ACP replay harness and session lifecycle support
2914ff48c0983 Handle segmented Cursor turns and stable visible timelines
2928dcf191a94 Add Cursor SDK orchestration replay support
2933056bfd7c0 Add orchestration MCP toolkit
2946b81144d2c refactor(orchestration-v2): adopt host process spawn policy
295d855d91f0a wip
2967078dac915 feat(orchestration-v2): model native subagents
297a4a63953ed Map orchestration v2 WS methods to auth scopes
29873fe5fd878 fix(orchestration-v2): preserve source history on merged switch
29998e98085af fix(orchestration-v2): compose provider switch merge context
30008430b8115 feat(orchestration-v2): add merge-back replay coverage
3012e38197d2b fix(orchestration-v2): resolve cross-provider forks
302e2ba0751f7 feat(orchestration-v2): support cross-provider handoff
303631219bca5 feat(orchestration-v2): wire claude adapter primitives
304b76bbc23de Document Cursor SDK MCP projection for V2
3052182b71bfa Add turn-interrupt replay coverage and protocol logging
306617f327995 Support active Claude steering and turn replay mapping
307c1e8a12537 Map Claude turns to runtime query policies
30841c5f212a7 Map Claude replay fixtures to multi-turn turns
309d4cd542799 Add model selection to orchestration runs
310166fd1c53c Extract Claude SDK query runner from provider adapter
311b460c06441 Add Claude replay fixture recorder
312052c24ec16 Add V2 command capability policy
313be1d5acc70 Add merge-back context handoff support
31476dc095de6 Add orchestration V2 backend checklist
315ca9ba20375 Add thread fork lineage and lazy context transfer
3165e551ef722 Implement orchestration v2 runtime
31775120285ae Map Codex turns into orchestration v2
318eb8af12dca Add orchestration v2 replay and service contracts
319783f4f6e8d Add orchestration v2 docs and probe fixtures
320fa13e794bb Address Codex review feedback
32174942087a3 Switch Codex provider checks to app-server probe
3223182df2608 Flush native logs on adapter shutdown
3233bded8ba56 decoders
32499e5494242 Scope Codex session runtime lifetimes
325a527023141 Normalize Codex IDs and preserve streamed stdout decoding
3264c8648f791 Return Cursor ACP runtime with explicit scope
327ba5d340a04 Delay Codex provider availability until checked
328d914dc0329 resynclock
3294da42ca393 revert more
33077a63c13b8 nit
331e911c41199 Integrate Codex app-server support
33215dff31236 chore(ov2): preserve the integration base for replay
audits/orchestrator-v2/2026-09-02/changed-files.tsv+942−0
@@ -0,0 +1,942 @@
10 429 .github/scripts/thread-transfer-report.cjs
20 292 .github/scripts/thread-transfer-report.test.cjs
310 0 .github/workflows/ci.yml
40 75 .github/workflows/thread-transfer-report.yml
51 0 README.md
62 0 apps/desktop/src/app/DesktopEnvironment.test.ts
71 1 apps/desktop/src/backend/tailscaleEndpointProvider.ts
81 0 apps/desktop/src/settings/DesktopClientSettings.test.ts
91 1 apps/marketing/src/pages/index.astro
1084 0 apps/mobile/generated-uniwind-themes.css
1116 0 apps/mobile/scripts/generate-uniwind-themes.mts
122 7 apps/mobile/src/components/BrandMark.tsx
1310 0 apps/mobile/src/components/brandAssets.ts
14166 4 apps/mobile/src/connection/environment-cache-store.test.ts
1517 32 apps/mobile/src/connection/environment-cache-store.ts
169 2 apps/mobile/src/connection/runtime.ts
171 0 apps/mobile/src/connection/storage.ts
1815 25 apps/mobile/src/features/archive/archivedThreadList.test.ts
193 3 apps/mobile/src/features/home/HomeScreen.tsx
2028 14 apps/mobile/src/features/home/homeListItems.test.ts
213 6 apps/mobile/src/features/home/homeThreadList.test.ts
2240 0 apps/mobile/src/features/home/threadArchive.test.ts
2316 0 apps/mobile/src/features/home/threadArchive.ts
243 5 apps/mobile/src/features/home/useThreadListActions.ts
254 1 apps/mobile/src/features/keyboard/hardwareKeyboardCommands.ts
260 1 apps/mobile/src/features/projects/AddProjectScreen.tsx
277 11 apps/mobile/src/features/review/reviewModel.test.ts
2811 10 apps/mobile/src/features/review/reviewModel.ts
2913 6 apps/mobile/src/features/review/useReviewSections.ts
306 4 apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx
316 2 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
3211 3 apps/mobile/src/features/threads/PendingApprovalCard.tsx
3320 6 apps/mobile/src/features/threads/PendingUserInputCard.tsx
34203 0 apps/mobile/src/features/threads/ThreadActivityInspector.tsx
355 6 apps/mobile/src/features/threads/ThreadComposer.tsx
3686 81 apps/mobile/src/features/threads/ThreadDetailScreen.tsx
37358 202 apps/mobile/src/features/threads/ThreadFeed.tsx
381 1 apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
39150 0 apps/mobile/src/features/threads/ThreadQueueControl.tsx
40307 0 apps/mobile/src/features/threads/ThreadRelationshipsBanner.tsx
4132 29 apps/mobile/src/features/threads/ThreadRouteScreen.tsx
4242 0 apps/mobile/src/features/threads/thread-activity-row-presentation.test.ts
4340 0 apps/mobile/src/features/threads/thread-activity-row-presentation.ts
4414 0 apps/mobile/src/features/threads/thread-feed-item-size.test.ts
4521 0 apps/mobile/src/features/threads/thread-feed-item-size.ts
4611 4 apps/mobile/src/features/threads/thread-list-v2-items.tsx
4726 0 apps/mobile/src/features/threads/threadActivityFileNavigation.test.ts
4822 0 apps/mobile/src/features/threads/threadActivityFileNavigation.ts
4938 0 apps/mobile/src/features/threads/threadForkNavigation.test.ts
5026 0 apps/mobile/src/features/threads/threadForkNavigation.ts
5193 46 apps/mobile/src/features/threads/threadListV2.test.ts
5237 8 apps/mobile/src/features/threads/threadListV2.ts
5314 14 apps/mobile/src/features/threads/threadPresentation.ts
5469 0 apps/mobile/src/features/threads/threadQueueControlPresentation.test.ts
5552 0 apps/mobile/src/features/threads/threadQueueControlPresentation.ts
5634 0 apps/mobile/src/features/threads/userMessageIntentBadge.test.ts
5735 0 apps/mobile/src/features/threads/userMessageIntentBadge.ts
5851 0 apps/mobile/src/lib/modelOptions.ts
591 1 apps/mobile/src/lib/projectThreadStartTurn.test.ts
6013 11 apps/mobile/src/lib/projectThreadStartTurn.ts
612 2 apps/mobile/src/lib/scopedEntities.ts
62664 1816 apps/mobile/src/lib/threadActivity.test.ts
63663 1381 apps/mobile/src/lib/threadActivity.ts
64257 0 apps/mobile/src/lib/threadActivityInspector.test.ts
65308 0 apps/mobile/src/lib/threadActivityInspector.ts
661 28 apps/mobile/src/state/queries.ts
672 1 apps/mobile/src/state/threads.ts
682 2 apps/mobile/src/state/use-pending-new-tasks.ts
6933 31 apps/mobile/src/state/use-selected-thread-requests.ts
705 4 apps/mobile/src/state/use-selected-thread-worktree.ts
7192 64 apps/mobile/src/state/use-thread-composer-state.ts
7250 5 apps/mobile/src/state/use-thread-detail.ts
7313 6 apps/mobile/src/state/use-thread-outbox-drain.ts
7447 16 apps/mobile/src/state/use-thread-selection.ts
7556 0 apps/mobile/src/state/v2-item-support.ts
7672 0 apps/mobile/src/test-fixtures.ts
773 0 apps/server/README.md
780 235 apps/server/integration/NetworkTransferMeasurement.integration.ts
790 592 apps/server/integration/OrchestrationEngineHarness.integration.ts
800 568 apps/server/integration/TestProviderAdapter.integration.ts
810 294 apps/server/integration/TransferBudgetReport.integration.ts
820 212 apps/server/integration/TransferBudgetScenario.integration.ts
830 162 apps/server/integration/fixtures/providerRuntime.ts
840 372 apps/server/integration/fixtures/transferBudget.ts
850 1441 apps/server/integration/orchestrationEngine.integration.test.ts
860 359 apps/server/integration/orphanedProviderSessionStartup.integration.test.ts
870 386 apps/server/integration/providerService.integration.test.ts
887 1 apps/server/package.json
89536 43 apps/server/scripts/acp-mock-agent.ts
9078 0 apps/server/scripts/acp-replay-agent.test.ts
91285 0 apps/server/scripts/acp-replay-agent.ts
9237 0 apps/server/scripts/acp-thread-spawn-helper.c
9312 0 apps/server/scripts/acpMockCancellationState.test.ts
943 0 apps/server/scripts/acpMockCancellationState.ts
9543 0 apps/server/scripts/claudeReplayRecordingConfig.test.ts
9632 0 apps/server/scripts/claudeReplayRecordingConfig.ts
9735 0 apps/server/scripts/codexReplayRecordingRecords.test.ts
989 0 apps/server/scripts/codexReplayRecordingRecords.ts
9944 0 apps/server/scripts/cursorReplayRecordingWorkspace.test.ts
10029 0 apps/server/scripts/cursorReplayRecordingWorkspace.ts
101358 0 apps/server/scripts/probe-claude-fork-local-rollback-replay.ts
102444 0 apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts
1031310 0 apps/server/scripts/record-codex-app-server-replay-fixture.ts
104222 0 apps/server/scripts/record-cursor-agent-sdk-replay-fixture.ts
10526 0 apps/server/scripts/replayRecorderDeferredRegistry.test.ts
10643 0 apps/server/scripts/replayRecorderDeferredRegistry.ts
10722 0 apps/server/src/attachmentStore.test.ts
10814 0 apps/server/src/attachmentStore.ts
10920 9 apps/server/src/auth/RpcAuthorization.ts
1100 635 apps/server/src/bin.test.ts
111169 413 apps/server/src/checkpointing/CheckpointDiffQuery.test.ts
11266 100 apps/server/src/checkpointing/CheckpointDiffQuery.ts
11357 0 apps/server/src/claudeModelOptions.test.ts
11459 0 apps/server/src/claudeModelOptions.ts
11599 2 apps/server/src/cli/project.test.ts
11663 32 apps/server/src/cli/project.ts
1173 1 apps/server/src/environment/ServerEnvironment.test.ts
1188 4 apps/server/src/environment/ServerEnvironment.ts
11912 0 apps/server/src/git/GitWorkflowService.ts
12052 1 apps/server/src/http.test.ts
1213 0 apps/server/src/httpCors.ts
12221 1 apps/server/src/mcp/McpHttpServer.ts
1233 2 apps/server/src/mcp/McpInvocationContext.ts
1247 0 apps/server/src/mcp/McpProviderSession.ts
1251 0 apps/server/src/mcp/McpSessionRegistry.test.ts
12628 0 apps/server/src/mcp/McpSessionRegistry.testkit.ts
12715 1 apps/server/src/mcp/McpSessionRegistry.ts
128307 0 apps/server/src/mcp/OrchestratorMcpService.activity.test.ts
129292 0 apps/server/src/mcp/OrchestratorMcpService.test.ts
1301681 0 apps/server/src/mcp/OrchestratorMcpService.ts
1312620 0 apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts
1321062 0 apps/server/src/mcp/WorktreeMcpService.test.ts
133494 0 apps/server/src/mcp/WorktreeMcpService.ts
134114 0 apps/server/src/mcp/toolkits/orchestrator/handlers.ts
13528 0 apps/server/src/mcp/toolkits/orchestrator/tools.test.ts
136248 0 apps/server/src/mcp/toolkits/orchestrator/tools.ts
13722 0 apps/server/src/mcp/toolkits/worktree/handlers.ts
138137 0 apps/server/src/mcp/toolkits/worktree/registration.test.ts
13947 0 apps/server/src/mcp/toolkits/worktree/tools.ts
14012 0 apps/server/src/observability/Metrics.ts
141209 0 apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts
14212118 0 apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts
143195 0 apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts
1446170 0 apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
145159 0 apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts
14692 0 apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts
147184 0 apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.ts
1485619 0 apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
149770 0 apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.test.ts
1502555 0 apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts
1515780 0 apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
1523644 0 apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
153237 0 apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts
1545261 0 apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
155196 0 apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
156102 0 apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.test.ts
1571013 0 apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
1582538 0 apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
159215 0 apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
160525 0 apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
161379 0 apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts
16282 0 apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts
163375 0 apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.ts
1641173 0 apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts
16539 0 apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.test.ts
166416 0 apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts
1673335 0 apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts
168184 0 apps/server/src/orchestration-v2/AttachmentClaims.test.ts
169126 0 apps/server/src/orchestration-v2/AttachmentClaims.ts
17041 0 apps/server/src/orchestration-v2/AttachmentPrompt.test.ts
17126 0 apps/server/src/orchestration-v2/AttachmentPrompt.ts
172327 0 apps/server/src/orchestration-v2/CheckpointCaptureService.test.ts
173273 0 apps/server/src/orchestration-v2/CheckpointCaptureService.ts
17484 0 apps/server/src/orchestration-v2/CheckpointPolicy.ts
175331 0 apps/server/src/orchestration-v2/CheckpointRollbackService.test.ts
176288 0 apps/server/src/orchestration-v2/CheckpointRollbackService.ts
17772 0 apps/server/src/orchestration-v2/CheckpointService.test.ts
178564 0 apps/server/src/orchestration-v2/CheckpointService.ts
179310 0 apps/server/src/orchestration-v2/CommandPolicy.test.ts
180428 0 apps/server/src/orchestration-v2/CommandPolicy.ts
181167 0 apps/server/src/orchestration-v2/CommandReceiptStore.ts
182164 0 apps/server/src/orchestration-v2/ContextHandoffService.test.ts
183459 0 apps/server/src/orchestration-v2/ContextHandoffService.ts
184279 0 apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts
185549 0 apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
186612 0 apps/server/src/orchestration-v2/EffectOutbox.ts
187789 0 apps/server/src/orchestration-v2/EffectWorker.test.ts
188671 0 apps/server/src/orchestration-v2/EffectWorker.ts
189662 0 apps/server/src/orchestration-v2/EventSink.ts
190139 0 apps/server/src/orchestration-v2/EventStore.ts
1912100 0 apps/server/src/orchestration-v2/FoundationPersistence.test.ts
192199 0 apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts
193407 0 apps/server/src/orchestration-v2/IdAllocator.ts
19467 0 apps/server/src/orchestration-v2/KeyedSerialExecutor.test.ts
19555 0 apps/server/src/orchestration-v2/KeyedSerialExecutor.ts
196344 0 apps/server/src/orchestration-v2/LegacyV1ThreadImporter.test.ts
197739 0 apps/server/src/orchestration-v2/LegacyV1ThreadImporter.ts
198100 0 apps/server/src/orchestration-v2/Orchestrator.migration.test.ts
1997349 0 apps/server/src/orchestration-v2/Orchestrator.ts
200391 0 apps/server/src/orchestration-v2/ProjectionMaintenance.ts
2012785 0 apps/server/src/orchestration-v2/ProjectionStore.test.ts
2023303 0 apps/server/src/orchestration-v2/ProjectionStore.ts
203554 0 apps/server/src/orchestration-v2/ProviderAdapter.ts
20444 0 apps/server/src/orchestration-v2/ProviderAdapterDriver.ts
205211 0 apps/server/src/orchestration-v2/ProviderAdapterRegistry.test.ts
206319 0 apps/server/src/orchestration-v2/ProviderAdapterRegistry.ts
20774 0 apps/server/src/orchestration-v2/ProviderContinuationRequests.ts
208735 0 apps/server/src/orchestration-v2/ProviderContinuationService.test.ts
209208 0 apps/server/src/orchestration-v2/ProviderContinuationService.ts
210654 0 apps/server/src/orchestration-v2/ProviderEventIngestor.test.ts
211327 0 apps/server/src/orchestration-v2/ProviderEventIngestor.ts
212124 0 apps/server/src/orchestration-v2/ProviderFailure.test.ts
213202 0 apps/server/src/orchestration-v2/ProviderFailure.ts
21484 0 apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.regression.test.ts
215948 0 apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.test.ts
216532 0 apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts
21755 0 apps/server/src/orchestration-v2/ProviderSelectionTransition.test.ts
21837 0 apps/server/src/orchestration-v2/ProviderSelectionTransition.ts
2192347 0 apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
2201685 0 apps/server/src/orchestration-v2/ProviderSessionManager.ts
221176 0 apps/server/src/orchestration-v2/ProviderSessionTransitionPolicy.test.ts
22296 0 apps/server/src/orchestration-v2/ProviderSessionTransitionPolicy.ts
223128 0 apps/server/src/orchestration-v2/ProviderSwitchService.test.ts
224189 0 apps/server/src/orchestration-v2/ProviderSwitchService.ts
225287 0 apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
226296 0 apps/server/src/orchestration-v2/ProviderTurnControlService.ts
227129 0 apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts
22816 0 apps/server/src/orchestration-v2/ProviderTurnStartService.testkit.ts
229561 0 apps/server/src/orchestration-v2/ProviderTurnStartService.ts
23043 0 apps/server/src/orchestration-v2/ProviderTurnTokenUsage.test.ts
23151 0 apps/server/src/orchestration-v2/QueuedRunOrder.test.ts
23232 0 apps/server/src/orchestration-v2/QueuedRunOrder.ts
23313 0 apps/server/src/orchestration-v2/RandomUuid.ts
23471 0 apps/server/src/orchestration-v2/ResourceCleanupService.ts
2353263 0 apps/server/src/orchestration-v2/RunExecutionService.test.ts
2361364 0 apps/server/src/orchestration-v2/RunExecutionService.ts
23741 0 apps/server/src/orchestration-v2/RunFinalizationService.test.ts
23897 0 apps/server/src/orchestration-v2/RunFinalizationService.ts
239103 0 apps/server/src/orchestration-v2/RuntimePolicy.test.ts
240146 0 apps/server/src/orchestration-v2/RuntimePolicy.ts
241298 0 apps/server/src/orchestration-v2/RuntimeRequestService.test.ts
242141 0 apps/server/src/orchestration-v2/RuntimeRequestService.ts
243644 0 apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts
244382 0 apps/server/src/orchestration-v2/ShellStream.test.ts
245191 0 apps/server/src/orchestration-v2/ShellStream.ts
246102 0 apps/server/src/orchestration-v2/SubagentProjection.test.ts
247191 0 apps/server/src/orchestration-v2/SubagentProjection.ts
248168 0 apps/server/src/orchestration-v2/TODO.md
249148 0 apps/server/src/orchestration-v2/ThreadForkService.test.ts
250116 0 apps/server/src/orchestration-v2/ThreadForkService.ts
2511098 0 apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
252589 0 apps/server/src/orchestration-v2/ThreadLaunchService.ts
25360 0 apps/server/src/orchestration-v2/ThreadLifecycleService.test.ts
254142 0 apps/server/src/orchestration-v2/ThreadLifecycleService.ts
255320 0 apps/server/src/orchestration-v2/ThreadManagementService.test.ts
256692 0 apps/server/src/orchestration-v2/ThreadManagementService.ts
257213 0 apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
258319 0 apps/server/src/orchestration-v2/ThreadSettlementService.ts
25975 0 apps/server/src/orchestration-v2/ThreadStream.test.ts
26051 0 apps/server/src/orchestration-v2/ThreadStream.ts
261433 0 apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts
262259 0 apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts
263107 0 apps/server/src/orchestration-v2/TurnItemPositionStore.ts
26435 0 apps/server/src/orchestration-v2/UserFacingErrors.test.ts
26562 0 apps/server/src/orchestration-v2/UserFacingErrors.ts
26638 0 apps/server/src/orchestration-v2/V1ImportBoundary.test.ts
267113 0 apps/server/src/orchestration-v2/WireProjection.test.ts
268108 0 apps/server/src/orchestration-v2/WireProjection.ts
26923 0 apps/server/src/orchestration-v2/applicationLayer.ts
27047 0 apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts
271258 0 apps/server/src/orchestration-v2/http.ts
2721574 0 apps/server/src/orchestration-v2/runtimeLayer.test.ts
273264 0 apps/server/src/orchestration-v2/runtimeLayer.ts
274399 0 apps/server/src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
275735 0 apps/server/src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts
27613 0 apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts
277417 0 apps/server/src/orchestration-v2/testkit/OrchestratorReplayFixtures.contract.test.ts
278242 0 apps/server/src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts
279292 0 apps/server/src/orchestration-v2/testkit/OrchestratorReplayRecovery.integration.test.ts
280600 0 apps/server/src/orchestration-v2/testkit/OrchestratorScenario.ts
28117 0 apps/server/src/orchestration-v2/testkit/ProviderReplayGate.testkit.test.ts
28278 0 apps/server/src/orchestration-v2/testkit/ProviderReplayGate.testkit.ts
283413 0 apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
2841251 0 apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
28574 0 apps/server/src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts
28698 0 apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.test.ts
287162 0 apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts
2881774 0 apps/server/src/orchestration-v2/testkit/ThreadFork.integration.test.ts
289668 0 apps/server/src/orchestration-v2/testkit/ThreadMergeBack.integration.test.ts
29011 0 apps/server/src/orchestration-v2/testkit/fixtures/acp_elicitation/grok_transcript.ndjson
29113 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/claude_transcript.ndjson
29226 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/input.ts
29396 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/output.ts
29419 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/claude_transcript.ndjson
29525 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/input.ts
29639 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/output.ts
29712 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/claude_transcript.ndjson
2987 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/input.ts
29958 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/output.ts
30010 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/claude_transcript.ndjson
30122 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/input.ts
30271 0 apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/output.ts
30317 0 apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/grok_transcript.ndjson
3049 0 apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/input.ts
30595 0 apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/output.ts
306807 0 apps/server/src/orchestration-v2/testkit/fixtures/index.ts
30743 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_output.ts
30813 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson
30943 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_output.ts
31051 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson
31156 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/cursor_output.ts
31221 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/cursor_transcript.ndjson
31355 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_output.ts
31414 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_transcript.ndjson
31531 0 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/input.ts
31653 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/claude_output.ts
31714 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/claude_transcript.ndjson
31840 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_output.ts
31952 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson
32030 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/cursor_transcript.ndjson
32112 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/grok_transcript.ndjson
32214 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/input.ts
32319 0 apps/server/src/orchestration-v2/testkit/fixtures/multi_turn_restart/claude_transcript.ndjson
32410 0 apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/input.ts
32536 0 apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/opencode_transcript.ndjson
32643 0 apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/output.ts
3277 0 apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/input.ts
32830 0 apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/opencode_transcript.ndjson
32971 0 apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/output.ts
33050 0 apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_output.ts
33167 0 apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson
33211 0 apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/grok_transcript.ndjson
33317 0 apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/input.ts
33426 0 apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/opencode_output.ts
33523 0 apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/opencode_transcript.ndjson
33638 0 apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_output.ts
337619 0 apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson
33839 0 apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/cursor_output.ts
339132 0 apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/cursor_transcript.ndjson
3408 0 apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/input.ts
34178 0 apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson
34260 0 apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/cursor_transcript.ndjson
34342 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_cancelled_while_active/codex_output.ts
34425 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_cancelled_while_active/input.ts
34514 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/claude_transcript.ndjson
34649 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_output.ts
34752 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson
34830 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/cursor_transcript.ndjson
34912 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/grok_transcript.ndjson
35014 0 apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/input.ts
3511184 0 apps/server/src/orchestration-v2/testkit/fixtures/shared.ts
35233 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/claude_output.ts
35310 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/claude_transcript.ndjson
35433 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_output.ts
35531 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson
35611 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/cursor_transcript.ndjson
35712 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/grok_transcript.ndjson
3587 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/input.ts
35919 0 apps/server/src/orchestration-v2/testkit/fixtures/simple/opencode_transcript.ndjson
360135 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/claude_output.ts
36129 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/claude_transcript.ndjson
362121 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_output.ts
363577 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson
36496 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/cursor_output.ts
365474 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/cursor_transcript.ndjson
3667 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent/input.ts
36728 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/README.md
36859 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_output.ts
36933 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson
37014 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/input.ts
37185 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_output.ts
37225 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson
3737 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/input.ts
374131 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_output.ts
37533 0 apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson
37621 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/claude_transcript.ndjson
37721 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson
37849 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/README.md
37925 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/claude_transcript.ndjson
38096 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson
38135 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_fork_local_rollback/claude_transcript.ndjson
38226 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/claude_transcript.ndjson
383933 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson
38448 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/README.md
38532 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/claude_transcript.ndjson
386130 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson
38745 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/README.md
38834 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/claude_transcript.ndjson
389112 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson
39046 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/README.md
39149 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/claude_transcript.ndjson
392177 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson
39369 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/claude_output.ts
39424 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/claude_transcript.ndjson
39568 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_output.ts
396100 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson
39721 0 apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/input.ts
39839 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_output.ts
399105 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson
40062 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/cursor_output.ts
401104 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/cursor_transcript.ndjson
40250 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/grok_output.ts
40316 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/grok_transcript.ndjson
4047 0 apps/server/src/orchestration-v2/testkit/fixtures/todo_list/input.ts
40562 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/claude_output.ts
40616 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/claude_transcript.ndjson
40764 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/cursor_output.ts
40827 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/cursor_transcript.ndjson
40915 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/grok_transcript.ndjson
4107 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/input.ts
41150 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/claude_output.ts
41215 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/claude_transcript.ndjson
41336 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_output.ts
41465 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson
41513 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/grok_transcript.ndjson
41610 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/input.ts
41741 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/claude_output.ts
41815 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/claude_transcript.ndjson
41937 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_output.ts
420137 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson
42110 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/input.ts
42234 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/claude_output.ts
42313 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/claude_transcript.ndjson
42429 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_output.ts
42557 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson
4267 0 apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/input.ts
42751 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/claude_output.ts
4287 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/claude_transcript.ndjson
42949 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_output.ts
43021 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson
4319 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/grok_transcript.ndjson
43210 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/input.ts
43314 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/opencode_transcript.ndjson
43495 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/claude_output.ts
43511 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/claude_transcript.ndjson
436146 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_output.ts
43735 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson
43875 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/cursor_output.ts
43943 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/cursor_transcript.ndjson
44010 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/input.ts
441129 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/claude_output.ts
44220 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/claude_transcript.ndjson
44315 0 apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/input.ts
44458 0 apps/server/src/orchestration-v2/testkit/fixtures/web_search/claude_output.ts
44516 0 apps/server/src/orchestration-v2/testkit/fixtures/web_search/claude_transcript.ndjson
44641 0 apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_output.ts
44722 0 apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson
4487 0 apps/server/src/orchestration-v2/testkit/fixtures/web_search/input.ts
4494 0 apps/server/src/orchestration-v2/testkit/index.ts
450621 0 apps/server/src/orchestration-v2/threadHistoryPaging.test.ts
451497 0 apps/server/src/orchestration-v2/threadHistoryPaging.ts
4520 268 apps/server/src/orchestration/ActivityPayloadProjection.test.ts
4530 600 apps/server/src/orchestration/ActivityPayloadProjection.ts
4540 1261 apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
4550 946 apps/server/src/orchestration/Layers/CheckpointReactor.ts
4560 1646 apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts
45727 46 apps/server/src/orchestration/Layers/OrchestrationEngine.ts
4580 104 apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts
4590 40 apps/server/src/orchestration/Layers/OrchestrationReactor.ts
460105 0 apps/server/src/orchestration/Layers/ProjectEnrichmentProjection.test.ts
4610 3700 apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts
46215 30 apps/server/src/orchestration/Layers/ProjectionPipeline.ts
463102 0 apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.search.test.ts
4640 2728 apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts
465317 461 apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
4660 3385 apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
4670 1605 apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
4680 144 apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.activity.test.ts
4690 70 apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts
4700 3650 apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
4710 2110 apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
4720 39 apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts
4730 139 apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts
4740 124 apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts
4750 360 apps/server/src/orchestration/Normalizer.attachments.test.ts
4760 73 apps/server/src/orchestration/Normalizer.test.ts
4770 299 apps/server/src/orchestration/Normalizer.ts
4781 1 apps/server/src/orchestration/Schemas.ts
4790 40 apps/server/src/orchestration/Services/CheckpointReactor.ts
4807 7 apps/server/src/orchestration/Services/OrchestrationEngine.ts
4810 32 apps/server/src/orchestration/Services/OrchestrationReactor.ts
4821 1 apps/server/src/orchestration/Services/ProjectionPipeline.ts
48319 8 apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts
4840 42 apps/server/src/orchestration/Services/ProviderCommandReactor.ts
4850 41 apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts
4860 66 apps/server/src/orchestration/Services/RuntimeReceiptBus.ts
4870 40 apps/server/src/orchestration/Services/ThreadDeletionReactor.ts
4880 171 apps/server/src/orchestration/ThreadLiveEventCoalescer.test.ts
4890 207 apps/server/src/orchestration/ThreadLiveEventCoalescer.ts
4900 616 apps/server/src/orchestration/ThreadSettlementReactor.test.ts
4910 191 apps/server/src/orchestration/ThreadSettlementReactor.ts
4920 232 apps/server/src/orchestration/commandInvariants.test.ts
4932 3 apps/server/src/orchestration/commandInvariants.ts
4940 217 apps/server/src/orchestration/decider.delete.test.ts
4953 3 apps/server/src/orchestration/decider.ts
4960 110 apps/server/src/orchestration/http.ts
4970 960 apps/server/src/orchestration/projector.test.ts
4986 2 apps/server/src/orchestration/projector.ts
49937 1 apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts
500111 1 apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts
501323 3 apps/server/src/persistence/Layers/OrchestrationEventStore.ts
5021 1 apps/server/src/persistence/Layers/ProjectionCheckpoints.ts
5031 1 apps/server/src/persistence/Layers/ProjectionTurns.ts
50418 0 apps/server/src/persistence/Migrations.ts
505231 0 apps/server/src/persistence/Migrations/045_046_OrchestrationV2.test.ts
506303 0 apps/server/src/persistence/Migrations/045_OrchestrationV2.ts
50728 0 apps/server/src/persistence/Migrations/046_OrchestrationV2Subagents.ts
50864 0 apps/server/src/persistence/Migrations/047_OrchestrationV2Foundation.test.ts
509180 0 apps/server/src/persistence/Migrations/047_OrchestrationV2Foundation.ts
51027 0 apps/server/src/persistence/Migrations/048_OrchestrationV2ProviderSessionBindings.ts
51123 0 apps/server/src/persistence/Migrations/049_OrchestrationV2ThreadLaunchWorkflows.ts
512135 0 apps/server/src/persistence/Migrations/050_ApplicationEventSource.test.ts
513281 0 apps/server/src/persistence/Migrations/050_ApplicationEventSource.ts
51480 0 apps/server/src/persistence/Migrations/051_OrchestrationV2EffectCancellation.test.ts
51578 0 apps/server/src/persistence/Migrations/051_OrchestrationV2EffectCancellation.ts
51642 0 apps/server/src/persistence/Migrations/052_ScheduledTasks.ts
51727 0 apps/server/src/persistence/Migrations/053_LegacyV1ImportState.ts
5182 7 apps/server/src/persistence/ProviderSessionRuntime.ts
5197 6 apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts
52046 4 apps/server/src/persistence/Services/OrchestrationEventStore.ts
5214 2 apps/server/src/persistence/Services/ProjectionCheckpoints.ts
5222 5 apps/server/src/persistence/Services/ProjectionPendingApprovals.ts
5232 8 apps/server/src/persistence/Services/ProjectionThreadActivities.ts
5242 8 apps/server/src/persistence/Services/ProjectionThreadMessages.ts
5252 7 apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts
5262 8 apps/server/src/persistence/Services/ProjectionThreadSessions.ts
5275 3 apps/server/src/persistence/Services/ProjectionTurns.ts
528343 0 apps/server/src/project/ProjectEnrichmentService.test.ts
529297 0 apps/server/src/project/ProjectEnrichmentService.ts
530412 0 apps/server/src/project/ProjectService.test.ts
531395 0 apps/server/src/project/ProjectService.ts
53262 184 apps/server/src/project/ProjectSetupScriptRunner.test.ts
53325 17 apps/server/src/project/ProjectSetupScriptRunner.ts
53452 0 apps/server/src/project/http.test.ts
53584 0 apps/server/src/project/http.ts
5363 0 apps/server/src/provider/CodexDeveloperInstructions.ts
537144 0 apps/server/src/provider/Drivers/AcpRegistryDriver.ts
53836 22 apps/server/src/provider/Drivers/ClaudeDriver.ts
53924 13 apps/server/src/provider/Drivers/CodexDriver.ts
54035 69 apps/server/src/provider/Drivers/CursorDriver.ts
54124 7 apps/server/src/provider/Drivers/GrokDriver.ts
54223 9 apps/server/src/provider/Drivers/OpenCodeDriver.ts
5432 165 apps/server/src/provider/Errors.ts
5440 5408 apps/server/src/provider/Layers/ClaudeAdapter.test.ts
5450 4820 apps/server/src/provider/Layers/ClaudeAdapter.ts
5460 1684 apps/server/src/provider/Layers/CodexAdapter.test.ts
5470 2038 apps/server/src/provider/Layers/CodexAdapter.ts
5480 1500 apps/server/src/provider/Layers/CursorAdapter.test.ts
5490 1193 apps/server/src/provider/Layers/CursorAdapter.ts
550158 576 apps/server/src/provider/Layers/CursorProvider.test.ts
551137 949 apps/server/src/provider/Layers/CursorProvider.ts
55273 0 apps/server/src/provider/Layers/CursorSdkCatalog.ts
5530 2399 apps/server/src/provider/Layers/GrokAdapter.test.ts
5540 2130 apps/server/src/provider/Layers/GrokAdapter.ts
5550 1 apps/server/src/provider/Layers/GrokProvider.ts
5560 5358 apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
5570 3285 apps/server/src/provider/Layers/OpenCodeAdapter.ts
5580 183 apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts
5590 92 apps/server/src/provider/Layers/ProviderAdapterRegistry.ts
56011 3 apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts
56121 14 apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts
56236 0 apps/server/src/provider/Layers/ProviderOrchestrationAdapterInfrastructure.ts
56374 8 apps/server/src/provider/Layers/ProviderRegistry.test.ts
56424 0 apps/server/src/provider/Layers/ProviderRegistry.ts
5650 2583 apps/server/src/provider/Layers/ProviderService.test.ts
5660 1277 apps/server/src/provider/Layers/ProviderService.ts
5670 271 apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts
5680 197 apps/server/src/provider/Layers/ProviderSessionDirectory.ts
5690 640 apps/server/src/provider/Layers/ProviderSessionReaper.test.ts
5700 152 apps/server/src/provider/Layers/ProviderSessionReaper.ts
57140 0 apps/server/src/provider/NativeProtocolLogging.ts
5727 7 apps/server/src/provider/ProviderDriver.ts
5730 19 apps/server/src/provider/Services/ClaudeAdapter.ts
5740 23 apps/server/src/provider/Services/CodexAdapter.ts
5750 19 apps/server/src/provider/Services/CursorAdapter.ts
5760 16 apps/server/src/provider/Services/GrokAdapter.ts
5770 19 apps/server/src/provider/Services/OpenCodeAdapter.ts
5780 138 apps/server/src/provider/Services/ProviderAdapter.ts
5790 68 apps/server/src/provider/Services/ProviderAdapterRegistry.ts
5800 130 apps/server/src/provider/Services/ProviderService.ts
5810 70 apps/server/src/provider/Services/ProviderSessionDirectory.ts
5820 15 apps/server/src/provider/Services/ProviderSessionReaper.ts
58347 0 apps/server/src/provider/T3OrchestrationInstructions.test.ts
58435 0 apps/server/src/provider/T3OrchestrationInstructions.ts
5850 28 apps/server/src/provider/acp/AcpAdapterSupport.test.ts
5860 56 apps/server/src/provider/acp/AcpAdapterSupport.ts
587304 4 apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts
58810 6 apps/server/src/provider/acp/AcpNativeLogging.ts
589263 0 apps/server/src/provider/acp/AcpRegistrySupport.test.ts
590633 0 apps/server/src/provider/acp/AcpRegistrySupport.ts
59193 0 apps/server/src/provider/acp/AcpRuntimeModel.test.ts
59231 0 apps/server/src/provider/acp/AcpRuntimeModel.ts
5931090 0 apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts
5941549 119 apps/server/src/provider/acp/AcpSessionRuntime.ts
5950 153 apps/server/src/provider/acp/CursorAcpCliProbe.test.ts
5960 154 apps/server/src/provider/acp/CursorAcpExtension.test.ts
5970 113 apps/server/src/provider/acp/CursorAcpExtension.ts
5980 121 apps/server/src/provider/acp/CursorAcpSupport.test.ts
5990 115 apps/server/src/provider/acp/CursorAcpSupport.ts
60021 0 apps/server/src/provider/acp/GrokAcpCliProbe.test.ts
60124 0 apps/server/src/provider/acp/GrokAcpSupport.test.ts
60224 0 apps/server/src/provider/acp/GrokAcpSupport.ts
6031425 76 apps/server/src/provider/acp/XAiAcpExtension.test.ts
6041121 308 apps/server/src/provider/acp/XAiAcpExtension.ts
6053 0 apps/server/src/provider/builtInDrivers.ts
60652 0 apps/server/src/provider/cursorSdkModel.ts
60711 11 apps/server/src/provider/providerMaintenanceRunner.test.ts
6080 74 apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts
6090 727 apps/server/src/relay/AgentAwarenessRelay.test.ts
61045 69 apps/server/src/relay/AgentAwarenessRelay.ts
611123 0 apps/server/src/scheduledTasks/Schedule.test.ts
612104 0 apps/server/src/scheduledTasks/Schedule.ts
613799 0 apps/server/src/scheduledTasks/ScheduledTaskService.ts
6140 9616 apps/server/src/server.test.ts
61551 56 apps/server/src/server.ts
61612 6 apps/server/src/serverActivation.ts
61725 2 apps/server/src/serverLifecycleEvents.test.ts
6186 5 apps/server/src/serverLifecycleEvents.ts
6190 660 apps/server/src/serverRuntimeStartup.reconcile.test.ts
62076 249 apps/server/src/serverRuntimeStartup.test.ts
621286 474 apps/server/src/serverRuntimeStartup.ts
62230 0 apps/server/src/textGeneration/CodexTextGeneration.test.ts
62327 9 apps/server/src/textGeneration/CodexTextGeneration.ts
624143 257 apps/server/src/textGeneration/CursorTextGeneration.test.ts
62568 77 apps/server/src/textGeneration/CursorTextGeneration.ts
6261 1 apps/server/src/textGeneration/TextGeneration.test.ts
6279 0 apps/server/src/vcs/GitVcsDriver.ts
6287 1 apps/server/src/vcs/GitVcsDriverCore.test.ts
62915 0 apps/server/src/vcs/GitVcsDriverCore.ts
63040 0 apps/server/src/ws.test.ts
6311029 1082 apps/server/src/ws.ts
6320 611 apps/server/test/ActivityPayloadProjection.test.ts
63315 0 apps/web/src/appearanceFonts.test.ts
6346 0 apps/web/src/appearanceFonts.ts
6353 0 apps/web/src/components/AppSidebarLayout.tsx
63613 0 apps/web/src/components/BranchToolbar.logic.test.ts
6377 0 apps/web/src/components/BranchToolbar.logic.ts
63842 2 apps/web/src/components/BranchToolbar.tsx
63966 21 apps/web/src/components/BranchToolbarBranchSelector.tsx
64098 39 apps/web/src/components/BranchToolbarEnvModeSelector.tsx
64150 26 apps/web/src/components/BranchToolbarEnvironmentSelector.tsx
642257 413 apps/web/src/components/ChatView.logic.test.ts
643132 154 apps/web/src/components/ChatView.logic.ts
6441665 1385 apps/web/src/components/ChatView.tsx
6455 6 apps/web/src/components/CommandPalette.logic.test.ts
6461 1 apps/web/src/components/CommandPalette.logic.ts
64714 4 apps/web/src/components/CommandPalette.tsx
64849 44 apps/web/src/components/DiffPanel.tsx
649107 22 apps/web/src/components/GitActionsControl.logic.test.ts
650106 29 apps/web/src/components/GitActionsControl.logic.ts
651442 307 apps/web/src/components/GitActionsControl.tsx
6524 4 apps/web/src/components/LegacySidebar.tsx
65345 0 apps/web/src/components/LegacyThreadMigrationToast.tsx
65434 0 apps/web/src/components/ProjectScriptsControl.test.tsx
655153 39 apps/web/src/components/ProjectScriptsControl.tsx
6563 0 apps/web/src/components/RightPanelTabs.tsx
657512 266 apps/web/src/components/Sidebar.logic.test.ts
658164 77 apps/web/src/components/Sidebar.logic.ts
659105 155 apps/web/src/components/Sidebar.tsx
6607 2 apps/web/src/components/ThreadStatusIndicators.tsx
6616 6 apps/web/src/components/chat/ChangedFilesTree.test.tsx
66212 12 apps/web/src/components/chat/ChangedFilesTree.tsx
663111 27 apps/web/src/components/chat/ChatComposer.tsx
66437 370 apps/web/src/components/chat/ChatHeader.tsx
6657 4 apps/web/src/components/chat/ComposerPendingApprovalActions.test.tsx
6666 4 apps/web/src/components/chat/ComposerPendingApprovalActions.tsx
6679 5 apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx
6683 1 apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
6693 2 apps/web/src/components/chat/ComposerPendingUserInputPanel.test.tsx
6709 4 apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
6710 44 apps/web/src/components/chat/ComposerPrimaryActions.test.tsx
67281 47 apps/web/src/components/chat/ComposerPrimaryActions.tsx
6730 62 apps/web/src/components/chat/ContextWindowMeter.test.tsx
674583 595 apps/web/src/components/chat/MessagesTimeline.logic.test.ts
675349 174 apps/web/src/components/chat/MessagesTimeline.logic.ts
6761330 355 apps/web/src/components/chat/MessagesTimeline.test.tsx
6771013 416 apps/web/src/components/chat/MessagesTimeline.tsx
67822 0 apps/web/src/components/chat/OpenInPicker.logic.ts
6791 22 apps/web/src/components/chat/{ChatHeader.test.ts => OpenInPicker.test.ts}
68055 18 apps/web/src/components/chat/OpenInPicker.tsx
68143 0 apps/web/src/components/chat/OpenInPickerShortcut.ts
6826 0 apps/web/src/components/chat/PanelLayoutControls.test.tsx
683123 38 apps/web/src/components/chat/PanelLayoutControls.tsx
6842 0 apps/web/src/components/chat/ProposedPlanCard.tsx
685171 0 apps/web/src/components/chat/QueuedRunsControl.test.tsx
686451 0 apps/web/src/components/chat/QueuedRunsControl.tsx
687190 0 apps/web/src/components/chat/ThreadAutomationsPanel.tsx
68891 0 apps/web/src/components/chat/ThreadDetailsPanel.test.tsx
689280 0 apps/web/src/components/chat/ThreadDetailsPanel.tsx
690415 0 apps/web/src/components/chat/ThreadDetailsPrRow.tsx
69149 0 apps/web/src/components/chat/ThreadRelationshipsControl.test.tsx
692373 0 apps/web/src/components/chat/ThreadRelationshipsControl.tsx
69360 0 apps/web/src/components/chat/TimelineSystemDivider.tsx
694312 0 apps/web/src/components/chat/V2ItemInspector.tsx
695358 0 apps/web/src/components/chat/V2LifecycleRow.tsx
69631 0 apps/web/src/components/chat/composerDispatch.test.ts
69715 0 apps/web/src/components/chat/composerDispatch.ts
69829 1 apps/web/src/components/chat/externalLinkContextMenu.test.ts
69910 2 apps/web/src/components/chat/externalLinkContextMenu.ts
70054 0 apps/web/src/components/chat/threadDetailsPanelStyles.ts
7013 3 apps/web/src/components/chat/useAssistantCitationTarget.ts
7020 1 apps/web/src/components/files/FilePreviewPanel.tsx
70330 111 apps/web/src/components/preview/PreviewPanelShell.tsx
70421 0 apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx
7051 1 apps/web/src/components/preview/addBrowserSurface.test.ts
70628 0 apps/web/src/components/preview/previewMiniPlayerLayout.test.ts
70721 0 apps/web/src/components/preview/previewMiniPlayerLayout.ts
70898 0 apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts
709126 0 apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
710344 0 apps/web/src/components/pullRequest/usePullRequestActions.ts
7111 6 apps/web/src/components/settings/AddProviderInstanceDialog.tsx
7126 1 apps/web/src/components/settings/KeybindingsSettings.logic.test.ts
71372 1 apps/web/src/components/settings/ProviderInstanceCard.test.ts
714135 3 apps/web/src/components/settings/ProviderInstanceCard.tsx
71528 0 apps/web/src/components/settings/ProviderSettingsForm.test.ts
716741 0 apps/web/src/components/settings/ScheduledTasksSettings.tsx
71756 4 apps/web/src/components/settings/SettingsPanels.tsx
7183 0 apps/web/src/components/settings/SettingsSidebarNav.tsx
71938 3 apps/web/src/components/settings/providerDriverMeta.ts
7204 0 apps/web/src/components/settings/settingsSearch.test.ts
7217 0 apps/web/src/components/settings/settingsSearch.ts
7229 1 apps/web/src/components/ui/popover.tsx
723119 0 apps/web/src/composerDraftStore.ts
7246 2 apps/web/src/connection/runtime.ts
72535 53 apps/web/src/connection/storage.ts
7261 1 apps/web/src/diffFileActions.test.ts
7276 20 apps/web/src/diffPanelStore.test.ts
7285 5 apps/web/src/diffPanelStore.ts
72925 0 apps/web/src/hooks/useElementWidth.ts
7302 2 apps/web/src/hooks/useHandleNewThread.ts
73191 0 apps/web/src/hooks/usePreviewPanelInlineSize.ts
7325 2 apps/web/src/hooks/useThreadActionMenu.ts
73333 3 apps/web/src/hooks/useThreadActions.ts
73445 0 apps/web/src/hooks/useThreadVisitedMigration.ts
73511 9 apps/web/src/hooks/useTurnDiffSummaries.ts
736186 1 apps/web/src/index.css
73739 19 apps/web/src/keybindings.test.ts
7385 1 apps/web/src/keybindings.ts
73950 72 apps/web/src/lib/contextWindow.test.ts
74063 32 apps/web/src/lib/contextWindow.ts
74154 0 apps/web/src/lib/orchestrationV2Timeline.test.ts
74222 0 apps/web/src/lib/orchestrationV2Timeline.ts
7439 10 apps/web/src/lib/threadSort.test.ts
74424 0 apps/web/src/pendingUserInput.test.ts
74511 0 apps/web/src/pendingUserInput.ts
74641 0 apps/web/src/providerInstances.test.ts
74718 0 apps/web/src/providerInstances.ts
74823 0 apps/web/src/providerUpdateDismissal.test.ts
74955 2 apps/web/src/providerUpdateDismissal.ts
75019 0 apps/web/src/rightPanelLayout.test.ts
75114 0 apps/web/src/rightPanelLayout.ts
75282 124 apps/web/src/rightPanelStore.test.ts
753206 64 apps/web/src/rightPanelStore.ts
75421 0 apps/web/src/routeTree.gen.ts
7552 0 apps/web/src/routes/__root.tsx
75610 27 apps/web/src/routes/_chat.$environmentId.$threadId.tsx
7572 2 apps/web/src/routes/_chat.draft.$draftId.tsx
7586 0 apps/web/src/routes/_chat.pull-requests.tsx
7597 0 apps/web/src/routes/settings.scheduled-tasks.tsx
7600 172 apps/web/src/session-logic.command-output.test.ts
761766 2371 apps/web/src/session-logic.test.ts
762582 1530 apps/web/src/session-logic.ts
76368 42 apps/web/src/state/entities.ts
7640 24 apps/web/src/state/queries.ts
76513 0 apps/web/src/state/server.ts
7661 1 apps/web/src/state/sourceControlActions.ts
7672 1 apps/web/src/state/threads.ts
76862 0 apps/web/src/state/v2ItemSupport.ts
76957 0 apps/web/src/state/waitForAtomValue.test.ts
77056 0 apps/web/src/state/waitForAtomValue.ts
771136 0 apps/web/src/test-fixtures.ts
77216 22 apps/web/src/threadRoutes.test.ts
7735 6 apps/web/src/threadRoutes.ts
7748 1 apps/web/src/threadSync.test.ts
77539 0 apps/web/src/timestampFormat.test.ts
77631 0 apps/web/src/timestampFormat.ts
77734 14 apps/web/src/types.ts
7781 0 apps/web/src/uiStateStore.test.ts
7799 0 apps/web/src/versionSkew.test.ts
78014 0 apps/web/src/versionSkew.ts
7815 6 apps/web/src/worktreeCleanup.test.ts
78211 0 docs/README.md
78319 0 docs/internals/context-handoffs.md
78436 0 docs/internals/legacy-orchestration-migration.md
7859 13 docs/internals/overview.md
78632 0 docs/internals/performance-regressions.md
787166 0 docs/orchestration-v2/README.md
788679 0 docs/orchestration-v2/core-graph-and-data-model.md
789231 0 docs/orchestration-v2/entity-ids-and-correlation.md
790535 0 docs/orchestration-v2/feature-lifecycles.md
791413 0 docs/orchestration-v2/orchestrator-mcp-server.md
792284 0 docs/orchestration-v2/provider-capability-system.md
793233 0 docs/orchestration-v2/provider-switching-and-context.md
794233 0 docs/orchestration-v2/testing-strategy.md
795286 0 docs/orchestration-v2/thread-lineage-and-context-transfer.md
79617 0 docs/user/activity-log.md
79711 0 docs/user/appearance.md
79820 0 docs/user/composer.md
79968 0 docs/user/cursor.md
80019 0 docs/user/portable-handoffs.md
80161 0 docs/user/thread-migration.md
8024 0 docs/user/updating.md
8032 0 oxlint-plugin-t3code/rules/no-mobile-uniwind-theme-escape-hatches.ts
8041 0 package.json
80536 0 packages/client-runtime/package.json
80657 0 packages/client-runtime/src/connection/compatibility.test.ts
80727 0 packages/client-runtime/src/connection/compatibility.ts
8084 5 packages/client-runtime/src/connection/registry.test.ts
80942 3 packages/client-runtime/src/connection/resolver.test.ts
81039 9 packages/client-runtime/src/connection/resolver.ts
811475 40 packages/client-runtime/src/operations/commands.test.ts
812765 240 packages/client-runtime/src/operations/commands.ts
8131 0 packages/client-runtime/src/operations/index.ts
8141 2 packages/client-runtime/src/operations/projects.test.ts
8152 4 packages/client-runtime/src/operations/projects.ts
81647 0 packages/client-runtime/src/operations/threadTitle.test.ts
81733 0 packages/client-runtime/src/operations/threadTitle.ts
8181 0 packages/client-runtime/src/platform/index.ts
819143 0 packages/client-runtime/src/platform/orchestrationCache.test.ts
82036 0 packages/client-runtime/src/platform/orchestrationCache.ts
8216 6 packages/client-runtime/src/platform/persistence.ts
8224 3 packages/client-runtime/src/rpc/client.ts
8232 2 packages/client-runtime/src/state/archivedThreads.test.ts
8243 3 packages/client-runtime/src/state/archivedThreads.ts
825214 0 packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts
826160 0 packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts
827436 299 packages/client-runtime/src/state/entities.test.ts
82822 0 packages/client-runtime/src/state/environmentHttpAuth.test.ts
82915 0 packages/client-runtime/src/state/environmentHttpAuth.ts
830205 0 packages/client-runtime/src/state/itemSupport.test.ts
831138 0 packages/client-runtime/src/state/itemSupport.ts
832211 15 packages/client-runtime/src/state/models.ts
83332 7 packages/client-runtime/src/state/orchestration.ts
834314 0 packages/client-runtime/src/state/orchestrationV2Projection.test.ts
835257 0 packages/client-runtime/src/state/orchestrationV2Projection.ts
836110 0 packages/client-runtime/src/state/orchestrationV2TestFixtures.ts
8372 2 packages/client-runtime/src/state/projectEntities.ts
83850 8 packages/client-runtime/src/state/server.ts
839469 54 packages/client-runtime/src/state/shell-sync.test.ts
84010 2 packages/client-runtime/src/state/shell.test.ts
84172 23 packages/client-runtime/src/state/shell.ts
842379 132 packages/client-runtime/src/state/shellReducer.test.ts
843129 24 packages/client-runtime/src/state/shellReducer.ts
84410 6 packages/client-runtime/src/state/shellSnapshotHttp.ts
8452 2 packages/client-runtime/src/state/snapshots.ts
84664 0 packages/client-runtime/src/state/subagentRuntime.ts
84752 0 packages/client-runtime/src/state/threadCheckpoints.ts
848112 0 packages/client-runtime/src/state/threadCommands.ts
84998 0 packages/client-runtime/src/state/threadDetail.test.ts
85031 138 packages/client-runtime/src/state/threadDetail.ts
851114 0 packages/client-runtime/src/state/threadExecution.test.ts
852104 0 packages/client-runtime/src/state/threadExecution.ts
85315 0 packages/client-runtime/src/state/threadFeedback.test.ts
8549 0 packages/client-runtime/src/state/threadFeedback.ts
85566 0 packages/client-runtime/src/state/threadHistoryController.test.ts
85693 0 packages/client-runtime/src/state/threadHistoryController.ts
85749 0 packages/client-runtime/src/state/threadHistoryHttp.ts
858416 0 packages/client-runtime/src/state/threadHistoryMerge.test.ts
859156 0 packages/client-runtime/src/state/threadHistoryMerge.ts
8600 1260 packages/client-runtime/src/state/threadReducer.test.ts
8610 756 packages/client-runtime/src/state/threadReducer.ts
862421 0 packages/client-runtime/src/state/threadRelationships.test.ts
863236 0 packages/client-runtime/src/state/threadRelationships.ts
86490 0 packages/client-runtime/src/state/threadRequests.ts
86579 27 packages/client-runtime/src/state/threadSettled.ts
86610 10 packages/client-runtime/src/state/threadShell.ts
86751 41 packages/client-runtime/src/state/threadSnapshotHttp.ts
86812 0 packages/client-runtime/src/state/threadSort.test.ts
86911 26 packages/client-runtime/src/state/threadState.ts
870354 0 packages/client-runtime/src/state/threadWorkflows.test.ts
871152 0 packages/client-runtime/src/state/threadWorkflows.ts
8720 544 packages/client-runtime/src/state/threads-pagination.test.ts
873696 208 packages/client-runtime/src/state/threads-sync.test.ts
874398 418 packages/client-runtime/src/state/threads.ts
87536 0 packages/client-runtime/src/state/turnItemPresentation.test.ts
8768 0 packages/client-runtime/src/state/turnItemPresentation.ts
87745 16 packages/client-runtime/src/state/vcsAction.test.ts
87813 2 packages/client-runtime/src/state/vcsAction.ts
879151 0 packages/client-runtime/src/t3ToolSummary.test.ts
880190 0 packages/client-runtime/src/t3ToolSummary.ts
88138 18 packages/client-runtime/src/work-log/presentation.test.ts
882164 90 packages/client-runtime/src/work-log/presentation.ts
8834 0 packages/contracts/package.json
88463 0 packages/contracts/src/applicationEvent.test.ts
885120 0 packages/contracts/src/applicationEvent.ts
8861 1 packages/contracts/src/assets.test.ts
8872 2 packages/contracts/src/assets.ts
88828 0 packages/contracts/src/baseSchemas.ts
889114 0 packages/contracts/src/chatAttachment.ts
89065 0 packages/contracts/src/checkpointDiff.ts
89113 0 packages/contracts/src/environment.ts
89271 33 packages/contracts/src/environmentHttp.ts
89312 0 packages/contracts/src/index.ts
89420 20 packages/contracts/src/ipc.ts
8955 0 packages/contracts/src/keybindings.test.ts
8961 0 packages/contracts/src/keybindings.ts
8973 0 packages/contracts/src/model.ts
89863 0 packages/contracts/src/modelSelection.ts
8995 4 packages/contracts/src/orchestration.test.ts
90055 532 packages/contracts/src/orchestration.ts
90124 0 packages/contracts/src/orchestrationProject.ts
902772 0 packages/contracts/src/orchestrationV2.test.ts
9032693 0 packages/contracts/src/orchestrationV2.ts
904168 0 packages/contracts/src/orchestratorMcp.test.ts
905560 0 packages/contracts/src/orchestratorMcp.ts
90694 0 packages/contracts/src/project.ts
9075 3 packages/contracts/src/provider.ts
90860 0 packages/contracts/src/providerPolicy.ts
9091 1 packages/contracts/src/providerRuntime.ts
91011 1 packages/contracts/src/rpc.test.ts
911182 58 packages/contracts/src/rpc.ts
91252 0 packages/contracts/src/scheduledTask.test.ts
913182 0 packages/contracts/src/scheduledTask.ts
91417 0 packages/contracts/src/server.ts
91543 0 packages/contracts/src/settings.test.ts
91656 22 packages/contracts/src/settings.ts
9174 2 packages/contracts/src/t3ProjectFile.test.ts
9181 1 packages/contracts/src/t3ProjectFile.ts
919127 0 packages/contracts/src/worktreeMcp.ts
92010 0 packages/effect-acp/src/client.ts
921255 0 packages/effect-acp/src/protocol.test.ts
922248 10 packages/effect-acp/src/protocol.ts
9238 0 packages/effect-codex-app-server/package.json
92411 1 packages/effect-codex-app-server/src/client.ts
925146 0 packages/effect-codex-app-server/src/replay.test.ts
926532 0 packages/effect-codex-app-server/src/replay.ts
92716 0 packages/shared/package.json
92812 0 packages/shared/src/Array.test.ts
9296 0 packages/shared/src/Array.ts
93057 150 packages/shared/src/agentAwareness.test.ts
93154 75 packages/shared/src/agentAwareness.ts
93238 1 packages/shared/src/model.test.ts
93339 0 packages/shared/src/model.ts
93415 15 packages/shared/src/orchestrationTiming.ts
935432 0 packages/shared/src/orchestrationV2PendingBackgroundWork.test.ts
936238 0 packages/shared/src/orchestrationV2PendingBackgroundWork.ts
937110 0 packages/shared/src/orchestrationV2Timeline.test.ts
93873 0 packages/shared/src/orchestrationV2Timeline.ts
93952 0 packages/shared/src/t3McpToolPresentation.test.ts
940119 0 packages/shared/src/t3McpToolPresentation.ts
941142 5 pnpm-lock.yaml
9421 0 vite.config.ts
audits/orchestrator-v2/2026-09-02/clients.md+147−0
@@ -0,0 +1,147 @@
1# Client parity audit: orchestrator v2 rebases
2
3Audit target: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0`
4
5Upstream baseline: `57a66608b918d673eeec7e6c94ea5906b756fcd0` (verified ancestor)
6
7Prior review object: `47f5b100440591d2f49aa30cf3bb69eacae07f52`; rebased prior tip: `c1791ab2637`
8
9Scope: `apps/web`, `apps/mobile`, `packages/client-runtime`, and relevant desktop entrypoints. I read both supplied inventories, followed the affected UI actions through client-runtime commands and projections, and compared current code to the supplied upstream main object. This was a source review backed by focused existing tests. I did not use a browser, dev server, provider, or production state.
10
11## Confirmed findings
12
13All four findings below are source-confirmed. I did not reproduce them in a real client.
14
15### High: the web sidebar crashes when a listed environment has no hydrated provider config
16
17- **Current evidence:** `apps/web/src/components/Sidebar.tsx:3680-3682` and `:3789-3791` evaluate `EMPTY_PROVIDER_ENTRIES` as the fallback for a missing environment entry. The binding is declared inside `Sidebar` only at `:3997`, after the component's unconditional return at `:3996`, so it remains in the temporal dead zone for that render. The map is expected to be incomplete: `packages/client-runtime/src/state/shell.ts:421-433` adds only non-null server configs, while the sidebar builds provider maps from that result at `apps/web/src/components/Sidebar.tsx:1887-1899`.
18- **Upstream main evidence:** at `57a66608b918d673eeec7e6c94ea5906b756fcd0`, `apps/web/src/components/Sidebar.tsx:265` declares the empty map at module scope and uses it at `:3721-3722`. `git blame` attributes the misplaced current declaration to round 18 reconciliation commit `7697286069f`.
19- **Reachable trigger:** open web or desktop with a cached/disconnected, reconnecting, newly added, or otherwise not-yet-configured environment that still contributes a visible thread. Either the normal thread list or search results can take the fallback.
20- **Impact:** React render throws `ReferenceError: Cannot access 'EMPTY_PROVIDER_ENTRIES' before initialization`; the whole sidebar/client surface can fail instead of showing cached threads. Any cached shell can expose it, with disconnected/reconnecting remote environments making it especially reproducible.
21- **Minimal action:** restore the module-scope declaration from main. Add a rendered sidebar behavior test whose catalog/shell contains a visible remote thread while that environment's config atom is null, and assert that the row remains usable.
22
23### High: editing a queued web message accepts generic files, then silently omits them from the saved edit
24
25- **Current evidence:** edit mode uses the ordinary composer and its file picker remains enabled (`apps/web/src/components/chat/ChatComposer.tsx:842-855`, `:4390-4422`). Generic files are accepted into `composerDraft.files` at `:3151-3186`. `ChatView` reads those files at `apps/web/src/components/ChatView.tsx:5892-5904`, but the queued-edit branch at `:5938-5971` checks and serializes only `composerImages`; `composerFiles` does not participate in the empty-edit guard, upload list, or replacement attachment list. The shared command already supports mixed stored/upload attachments and persists them before dispatch (`packages/client-runtime/src/operations/commands.ts:213-225`, `:242-267`, `:843-857`).
26- **Related loss path:** if the queued run starts or is cancelled from another client, `apps/web/src/components/ChatView.tsx:3323-3345` treats an edit as dirty only when text changed or images exist. A file-only edit is cleared. The move helper itself can transfer files (`apps/web/src/composerDraftStore.ts:3875-3946`); the caller simply never invokes it for a file-only edit.
27- **Upstream main evidence:** v1 has no queued-message editor, but its established send invariant snapshots both images and files and runs both through attachment upload (`57a...:apps/web/src/components/ChatView.tsx:5897-5903`, `:5950-5962`). The v2 edit contract likewise explicitly accepts `UploadChatAttachment`; this is not an unsupported contract case.
28- **Reachable trigger:** while an active run has a queued follow-up, choose **Edit in the composer**, attach a PDF/video/text file, then save. With no text or retained attachment, the button appears to do nothing; with text or another attachment, the edit succeeds and the newly selected file disappears. A second client starting/cancelling the run also drops a file-only unsaved edit.
29- **Impact:** user-selected content is silently excluded from the queued message, including on remote/multiple-environment setups where queued editing is especially useful. The local draft is then cleared after the successful text/image edit.
30- **Minimal action:** feed `composerFiles` through the existing attachment-upload path and include them in the queued edit's emptiness and replacement calculations; include files in the cross-client dirty check. If generic files are intentionally unsupported for edits, hide/disable that picker during edit mode with an explicit reason instead of accepting and dropping them. Add an interaction test that edits a queued run, selects a generic file, saves, and verifies the dispatched edit contains the persisted attachment; separately exercise a file-only edit when another client removes the run and verify it is retained in the base composer.
31
32### High: mobile filters out every non-image message attachment before its file/video/PDF renderer can run
33
34- **Current evidence:** `apps/mobile/src/features/threads/ThreadFeed.tsx:1618-1626` reduces every message's attachments to `type === "image"`. The user-message and assistant-message branches still contain file and unknown renderers at `:1678-1698` and `:1774-1794`, but those branches are unreachable after the filter.
35- **Upstream main evidence:** `57a...:apps/mobile/src/features/threads/ThreadFeed.tsx:1527-1533` keeps `message.attachments ?? []`, and `:1580-1600` reaches `MessageAttachmentFile`. The image-only filter entered in round 11 reconciliation commit `0301dc423e3`; its diff changes this exact line from the complete list to the filter.
36- **Reachable trigger:** send or queue a PDF, video, text file, or other generic attachment from web or desktop, then read the message in the mobile thread feed. The projection still carries user attachments (`apps/mobile/src/lib/threadActivity.ts:1055-1077`), so this is solely a presentation loss.
37- **Impact:** mobile history makes attached files appear absent. Users cannot open the existing PDF/file preview or video flow, and cross-client conversations give conflicting accounts of what was sent. Data remains persisted, but the mobile UI hides it.
38- **Minimal action:** restore `const attachments = message.attachments ?? []`. Add a feed interaction test with a projected file/video attachment that presses the rendered row and observes the preview/video callback; do not limit the test to component props or static markup.
39
40### Medium: mobile's active v2 assistant row bypasses the specialized Markdown renderer
41
42- **Current evidence:** `AssistantMarkdownContent` still exists at `apps/mobile/src/features/threads/ThreadFeed.tsx:969-1016`. It splits artifact-template blocks, converts file citations to ordinary Markdown links, gives template cards an `onUse` action, and passes the thread's `renderImage` callback to native Markdown. The active v2 assistant branch never calls it. Instead, `:1755-1772` sends raw `message.text` directly to the generic renderers and omits `renderImage` from `SelectableMarkdownText`. `ThreadFeedProps` also has no artifact-template callback (`:232-256`), and the real `ThreadDetailScreen` call at `apps/mobile/src/features/threads/ThreadDetailScreen.tsx:683-715` supplies none.
43- **iOS media consequence:** `apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx:64-80` sets the image-renderer context to `null` when the caller omits that prop. `NativeMarkdownBlock.ios.tsx:380-422` then falls back to `<Image source={{ uri: href }}>`. That fallback does not resolve a workspace-relative or host-machine path through the owning environment, cannot select the video renderer, and has no signed `media-file` resource. The omitted callback is the one that creates scoped image/video previews at `apps/mobile/src/features/threads/ThreadFeed.tsx:2342-2390`. Android's fallback Markdown path still receives the custom image renderer through `styles.renderers` (`:1364-1371`, `:1763-1771`).
44- **V2 text remains verbatim:** the Codex adapter appends `payload.delta` without conversion (`apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts:3157-3167`), chooses the provider's final `payload.item.text` at `:3683-3687`, and copies that string unchanged into both the message and assistant turn item at `:2436-2468`. Client-runtime upserts the event payload unchanged (`packages/client-runtime/src/state/orchestrationV2Projection.ts:224-225`), and mobile copies `item.text` into the feed message (`apps/mobile/src/lib/threadActivity.ts:1055-1081`). The only mobile Codex directive conversion calls are inside the bypassed `AssistantMarkdownContent`, so the trigger is real rather than already normalized upstream.
45- **Upstream main evidence:** `57a...:apps/mobile/src/features/threads/ThreadDetailScreen.tsx:623-636` appends the selected template prompt to the live composer and passes that callback at `:690-716`. Main's `ThreadFeed.tsx:1453-1455`, `:1633-1641`, and `:2698-2710` route assistant text through `AssistantMarkdownContent`, including `onUseArtifactTemplate` and `renderMarkdownImage`. The callback and active invocation were removed during the v2 reconciliation diff even though the adapter component was retained.
46- **Reachable trigger:** read a mobile v2 Codex response containing a `codex-file-citation` or `codex-artifact-template` directive. On iOS, any assistant Markdown workspace/host image or image-syntax video reaches the same bypassed call and loses environment-aware resolution.
47- **Impact:** mobile does not produce the file citation's navigable link, and artifact templates lose the **Use template** action. On iOS, assistant workspace/host images and embedded-video Markdown also fail or render through the wrong primitive. Web remains intact through `apps/web/src/components/chat/MessagesTimeline.tsx:1590-1614` and `apps/web/src/components/ChatMarkdown.tsx:2096-2109`, `:2362-2368`.
48- **Minimal action:** restore `AssistantMarkdownContent` in the current assistant-row branch, restore the `ThreadFeed` callback, and adapt main's composer-append handler to the current v2 draft setter. That one caller restoration also passes `renderMarkdownImage` on iOS. Add an integrated feed/composer behavior test that selects a template and observes the draft change, a citation press that reaches scoped file navigation, and an iOS native Markdown image/video case that observes the environment-scoped media renderer rather than the direct-URI fallback.
49
50## Intentional or established surface divergences
51
52- **Mobile queue controls remain compact.** Web exposes thumbnail rows and composer-based queued-message editing (`apps/web/src/components/chat/QueuedRunsControl.tsx:44-95`, `:331-440`). Mobile exposes environment/thread-scoped reorder, promote-to-steer, and cancel controls but no editor or thumbnails (`apps/mobile/src/features/threads/ThreadQueueControl.tsx:18-72`, `:74-149`). Neither supplied main inventory nor the v1 mobile baseline establishes queued editing as a mobile requirement, so I did not report the absence itself as a regression. The hidden persisted attachments in the feed are independent and are reported above.
53- **Desktop inherits the web client.** The only branch-vs-main desktop changes are `DesktopEnvironment.test.ts`, `tailscaleEndpointProvider.ts`, and `DesktopClientSettings.test.ts`; there is no separate desktop composer/timeline implementation to reconcile. The two web findings therefore apply to desktop's wrapped web surface as well.
54- **Working/thinking presentation differs by surface.** Web derives explicit active-tool and working/thinking rows in `apps/web/src/components/chat/MessagesTimeline.logic.ts:760-845` and `:1067-1075`; mobile folds live activity into native grouped work rows in `apps/mobile/src/lib/threadActivity.ts:798-917`. Both retain terminal failure visibility. This looks like established compact-mobile presentation, not a semantic loss.
55
56## Both-main bugs
57
58No confirmed defect in this scope is shared by current branch and the supplied main baseline. Each finding above is either introduced by the v2/reconciliation work or is specific to a branch-only feature.
59
60## Uncertainty requiring a product decision
61
62### Inherited fork markdown currently resolves against the active fork, not the row's owning thread
63
64V2 feed rows preserve `sourceThreadId` (`packages/client-runtime/src/state/threadHistoryMerge.ts:54-88`; `apps/mobile/src/lib/threadActivity.ts:1055-1077`). Inspector file navigation correctly uses that source (`apps/mobile/src/features/threads/ThreadActivityInspector.tsx:84-105`), and the web inspector fix does the same (`apps/web/src/components/chat/V2ItemInspector.tsx:132-142`). Regular timeline markdown instead receives the active route thread on web (`apps/web/src/components/chat/MessagesTimeline.tsx:684-712`, `:1590-1613`) and the active screen thread on mobile (`apps/mobile/src/features/threads/ThreadFeed.tsx:2301-2340`, `:2342-2390`). Proposed-plan timeline rows also pass only the active route thread (`apps/web/src/components/chat/MessagesTimeline.tsx:1711-1726`).
65
66This may be intentional: a fork's continuation starts from the same workspace and users may expect inherited file links to open in the current checkout. It may instead violate the prior "owning environment/thread" rule when source and fork worktrees diverge. Main v1 has no equivalent projected inherited-row model, so there is no clean parity oracle. Before changing it, decide whether inherited message/plan markdown denotes historical source state or current-fork state, then add a behavioral fork test that opens a workspace file after the two worktrees diverge and verifies the selected thread/resource.
67
68## Requested latest-main follow-up
69
70All six requested commits are ancestors of the audited HEAD.
71
72| Main change | Current source result | Test coverage |
73| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
74| `f14f41b894` composer draft preservation during worktree setup | **Retained.** Promotion cleanup moves the draft to the canonical scoped thread instead of revoking it (`apps/web/src/composerDraftStore.ts:1586-1618`, `:2794-2805`). This includes text entered after setup starts. | Direct store tests cover edits made during promotion and finalization without a pre-mark (`apps/web/src/composerDraftStore.test.ts:1461-1485`). Passed in the follow-up run below. |
75| `5392c9bb99` sticky model selections | **Retained.** A user model pick updates both the current draft and sticky selection (`apps/web/src/components/ChatView.tsx:7096-7102`). Both existing and fresh new-thread paths seed sticky state before project/carried overrides (`apps/web/src/hooks/useHandleNewThread.ts:263-280`, `:395-414`). Web and mobile retain only explicitly selected provider options (`apps/web/src/components/chat/composerProviderState.tsx:82-100`, `apps/mobile/src/lib/modelOptions.ts:42-61`, `packages/shared/src/model.ts:246-258`). | Web draft-store, web provider-state, mobile model-options, and shared model tests passed. No gap found in the audited call paths. |
76| `a19f01fc19` opt-in context meter | **Retained.** The client setting defaults to false (`packages/contracts/src/settings.ts:270-276`), Settings exposes the opt-in (`apps/web/src/components/settings/SettingsPanels.tsx:1896-1908`), and the composer passes context usage to its primary actions only when enabled (`apps/web/src/components/chat/ChatComposer.tsx:4423-4427`). | Contract decoding and explicit opt-in tests at `packages/contracts/src/settings.test.ts:203-212` passed. No source gap found. |
77| `e7deb2aaf4` inline citations | **Retained.** Chat route state resolves only citations owned by the active environment/thread (`apps/web/src/components/ChatView.tsx:1470-1481`), selected assistant text enters the composer through `citeAssistantText` (`:1531-1545`), and v2 assistant rows remain wrapped in `AssistantCitationSource` (`apps/web/src/components/chat/MessagesTimeline.tsx:1590-1614`). Mobile preserves the intended readable-text fallback (`apps/mobile/src/features/threads/ThreadFeed.tsx:1618-1622`). | The assistant-citation lifecycle suite and composer draft persistence tests passed. No v2-specific source gap found. |
78| `fc53b27303` sidebar visibility leases | **Retained, with a test gap.** The lease uses the sidebar scroll viewport plus 160 px overscan and keeps active rows leased (`apps/web/src/components/Sidebar.logic.ts:20-64`). Default sidebar rows and search results gate VCS subscriptions with it (`apps/web/src/components/Sidebar.tsx:790-809`, `:1624-1637`); the legacy sidebar gates both VCS and linked-PR subscriptions (`apps/web/src/components/LegacySidebar.tsx:381-423`, `:464-477`). Client-runtime keeps short idle grace periods (`packages/client-runtime/src/state/vcs.ts:32-36`, `packages/client-runtime/src/state/pullRequests.ts:35-47`). | `Sidebar.logic.test.ts` passed, but it does not exercise `useSidebarRowSubscriptionLease` or subscription release after an intersection change. That remains a focused behavior-test gap, not a source regression. |
79| `0e1570bde5` hosted explicit project defaults | **Retained, with a test gap.** Project Settings reads providers and option settings from the representative project's environment, not the absent hosted primary (`apps/web/src/components/settings/ProjectSettingsPanel.tsx:293-307`, `:424-452`). The explicit selection fans out through environment-scoped project updates to every logical-project member (`:366-400`, `:450-452`, `:860-892`). Primary-only server settings separately go inert on the hosted app (`apps/web/src/hooks/useSettings.ts:321-333`, `apps/web/src/components/settings/settingsLayout.tsx:166-217`). | Hosted-app detection tests passed, but there is no focused Project Settings behavior test proving that a hosted remote environment's providers populate the default-model picker and that selection dispatches to the correct environment members. Source routing is intact. |
80
81## Final bounded file/media coverage
82
83This pass traced each named main change into its current v2 caller; unchanged leaf utilities were not treated as parity evidence by themselves. No additional tests were run for this final pass, as requested.
84
85| Main change | Current v2 caller result |
86| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87| `#8237` (`86c9a9288b`) mobile file/share receive | **Partial.** The native share presentation reaches the New Task sheet (`apps/mobile/src/Stack.tsx:368-389`), the selected environment owns capability filtering and draft merge (`apps/mobile/src/features/threads/NewTaskDraftScreen.tsx:521-644`), and manual file picks reach the same draft (`:809-835`). Submission carries the attachments through environment-scoped preparation into v2 `thread.turn.start` (`:963-977`; `apps/mobile/src/features/threads/use-project-actions.ts:66-134`; `apps/mobile/src/lib/projectThreadStartTurn.ts:37-87`). Acquisition and delivery are retained; generic received files are hidden after delivery by the already-reported `ThreadFeed.tsx:1624-1626` filter. |
88| `#8978` (`9bc7a56848`) upload while composing | **Partial.** The root worker remains mounted outside composers (`apps/mobile/src/Stack.tsx:353-360`), keys transfers by environment and attachment, and updates only drafts that still own the file (`apps/mobile/src/state/composer-attachment-uploads.ts:50-125`; `apps/mobile/src/lib/composerAttachmentUploadQueue.ts:16-78`). Existing and new-task composers block connected sends until uploads are ready (`apps/mobile/src/features/threads/ThreadComposer.tsx:377-385`; `NewTaskDraftScreen.tsx:881-888`, `:1038-1048`), and v2 consumes the uploaded references. Upload/delivery is retained; post-send generic-file visibility is partial only because of the existing attachment-filter finding. |
89| `#8914` (`746c932e16`) defer draft navigation | **Retained.** Successful v2 creation clears the draft, records a replacement action, and waits until the share/submission removal guard drops before dispatching it on the next frame (`apps/mobile/src/features/threads/NewTaskDraftScreen.tsx:331-347`, `:963-1020`). Failure returns without navigation or draft clearing (`:991-999`). |
90| `#8614` (`352710d497`) offline iPhone voice | **Retained.** Both real composers use the local voice controller and commit transcription through their existing draft setters (`apps/mobile/src/features/threads/ThreadComposer.tsx:349-385`; `NewTaskDraftScreen.tsx:318-347`). The controller snapshots owner/text/selection and blocks send while busy (`apps/mobile/src/features/voice-input/useVoiceInputController.ts:63-126`, `:199-216`); the iOS adapter prepares and transcribes on-device (`apps/mobile/src/native/voiceTranscription.ios.ts:36-97`). Its output therefore follows the normal online or outbox-backed v2 send path without a provider dependency. |
91| `#9140` (`f46a709ee4`) external Markdown/HTML/PDF | **Retained.** Web assistant rows pass the active v2 thread and workspace to `ChatMarkdown` (`apps/web/src/components/chat/MessagesTimeline.tsx:1590-1614`), which routes outside-workspace non-media files into the scoped file panel and HTML/PDF into preview (`apps/web/src/components/ChatMarkdown.tsx:2313-2358`). Mobile link handling preserves absolute paths and scopes host PDF/media resources to the active environment/thread (`apps/mobile/src/features/threads/ThreadFeed.tsx:2250-2340`; `apps/mobile/src/features/files/filePath.ts:13-48`). Host text remains read-only in the viewers. The inherited-fork ownership question remains the separate uncertainty above. |
92| `#9143` (`d937e30759`) web viewer | **Retained.** The v2 right-panel surface supplies the active project environment, workspace, and thread to a stable `FilePreviewPanel` instance (`apps/web/src/components/ChatView.tsx:7287-7311`). The panel chooses video/image/HTML/PDF/Markdown/source modes, uses a sandbox for HTML, and keeps host files read-only (`apps/web/src/components/files/FilePreviewPanel.tsx:200-315`, `:947-999`, `:1159-1248`). |
93| `#9023` (`beae2147a94`) host-file/video streaming | **Partial across clients.** Web/desktop v2 media resolves signed resources against the row's thread environment (`apps/web/src/components/ChatMarkdown.tsx:2016-2057`, `:2611-2693`), and Electron permits streaming plus custom-scheme media (`apps/desktop/src/electron/ElectronProtocol.ts:85-94`, `:112-134`). Mobile's scoped Markdown and file-route resource builders remain (`apps/mobile/src/features/threads/ThreadFeed.tsx:2286-2390`; `apps/mobile/src/features/files/workspaceFileAssetUrl.ts:21-40`). The file route and Android Markdown caller still reach them. The active iOS assistant caller omits `renderImage`, however, so workspace/host images and image-syntax videos fall back to a direct URI instead of these builders (`apps/mobile/src/features/threads/ThreadFeed.tsx:1755-1772`; `apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx:380-422`). Video/generic message attachments are separately blocked by the already-reported image-only filter. Both mobile losses share existing findings rather than adding another one. |
94| `#8630` (`d2042d288e`) stale writes on close | **Retained.** Both editable source and rendered task-list surfaces use the coordinator (`apps/web/src/components/files/FilePreviewPanel.tsx:553-581`, `:610-624`, `:874-899`). Unmount/close disposes it, clears the debounce, and flushes the latest revision; if a save was already in flight, completion immediately persists the newer revision (`apps/web/src/components/files/fileSaveCoordinator.ts:21-34`, `:50-77`). The active v2 file panel unmounts through the right-panel branch at `apps/web/src/components/ChatView.tsx:7287-7313`. |
95| `#8968` (`f2a914b858`) panel state during refresh | **Retained.** Workspace mutations refresh data without changing the file-panel key (`apps/web/src/components/ChatView.tsx:7287-7311`). The file tree batches path deltas rather than resetting its model and suppresses replayed reveals (`apps/web/src/components/files/FileBrowserPanel.tsx:274-353`). Diff refreshes keep stable identity keys and loader callbacks while content versions change (`apps/web/src/components/DiffPanel.tsx:280-326`, `:403-435`, `:919-970`; `apps/web/src/components/diffs/AnnotatableCodeView.tsx:126-167`). This preserves expansion, search, selection, collapse, and scroll state across refresh. |
96| `#8584` (`c1e70b5f8`) Codex citations/artifact templates | **Partial; new finding above.** The shared parser/template prompt helpers remain (`packages/client-runtime/src/codexMarkdownDirectives.ts:187-215`, `:308-358`; `packages/client-runtime/src/codexArtifactTemplates.ts:97-127`). Web's real v2 row still renders citations/templates and appends a chosen template to the current composer (`apps/web/src/components/chat/MessagesTimeline.tsx:1590-1614`; `apps/web/src/components/ChatView.tsx:3229-3247`). Mobile retains the adapter component but the active v2 assistant row and real screen no longer call or configure it (`apps/mobile/src/features/threads/ThreadFeed.tsx:969-1016`, `:1755-1772`; `apps/mobile/src/features/threads/ThreadDetailScreen.tsx:683-715`). |
97
98## Feature coverage and prior-fix retention
99
100| Area | Web / desktop | Mobile | Result |
101| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
102| Persistent feedback placement and duplicate guards | Feedback rows are passed as anchored messages (`apps/web/src/components/ChatView.tsx:2936-2954`) and inserted before the first later timestamp (`apps/web/src/session-logic.ts:761-777`); direct in-flight guard at `ChatView.tsx:5874`, `:6024`, `:6055` | Equivalent anchored insertion at `apps/mobile/src/lib/threadActivity.ts:1113-1122`; mobile calls the shared guard at `apps/mobile/src/state/use-thread-composer-state.ts:257-258`, implemented in `packages/client-runtime/src/state/threadFeedback.ts:35-42` | Retained |
103| Effective plan-mode dispatch | Disabled plan mode forces `default` (`apps/web/src/components/ChatView.logic.ts:69-76`); the same effective value is persisted and dispatched (`ChatView.tsx:6380-6454`) | Mobile uses its existing explicit interaction-mode flow; no contradictory v2 override found | Retained |
104| Markdown workspace images, Windows paths, owning environment | `threadRef.environmentId` wins at `apps/web/src/components/ChatMarkdown.tsx:2016`; normalized file/image resolution and workspace assets remain at `:2099-2140`, `:2618-2690`; inspector and plan callers pass ownership at `V2ItemInspector.tsx:132-142` and `ProposedPlanCard.tsx:175-188` | Workspace links/images use the screen environment and thread; inspector activity links retain projected source ownership | Retained for direct/current rows; inherited-row semantics noted above |
105| File-chip editor/reveal/modifier behavior | File chips retain panel/editor/reveal actions and modifier routing in `apps/web/src/components/ChatMarkdown.tsx:1604-1949`, `:2256-2358` | Native file navigation/preview paths remain reachable for markdown links | Retained |
106| Pull-request open/link/unlink | PR resolution filters projects to the thread environment and metadata updates use that scoped thread (`apps/web/src/components/ChatMarkdown.tsx:2145-2192`); context-menu link/unlink failure reporting remains at `:2495-2555` | No matching mobile PR-link mutation entrypoint in the audited changes | Retained where applicable |
107| Unsettled sorting | Shared sort key uses max of created/updated/unsettled timestamps (`packages/client-runtime/src/state/threadSort.ts:72-88`); web and mobile consume it from `Sidebar.logic.ts:668-686` and `threadListV2.ts:192-213` | Same shared rule | Retained |
108| Mobile title/header/native back patch | N/A | `CompactBrandTitle.tsx:63-70` clears stale native left items; home/sidebar header option versions include width/native item dependencies (`features/home/HomeRouteScreen.tsx:140-155`, `features/threads/ThreadNavigationSidebar.tsx:1087-1144`); `patches/react-native-screens@4.26.2.patch` remains installed via the lockfile | Retained |
109| Project-picker containment | `apps/web/src/components/Sidebar.tsx:3549-3616` uses the contained combobox; popup root has `min-w-0` and `overflow-hidden` at `apps/web/src/components/ui/combobox.tsx:171-184` | N/A | Retained |
110| Failed tool summaries / grouped activity | Grouping preserves failure state and final-call failure presentation in `apps/web/src/components/chat/MessagesTimeline.logic.ts:907-994` | Group construction keeps failed/severe items visible in `apps/mobile/src/lib/threadActivity.ts:798-917` | Retained |
111| Composer/stash attachment ownership and duplicate protection | Stash restores only in the owning environment and validates uploads before consuming the entry (`apps/web/src/components/chat/ChatComposer.tsx:2433-2502`); normal send attachment caps/dedup remain. Queued-edit generic files are the exception reported above | Existing mobile draft/composer paths remain environment scoped | Mostly retained; one confirmed queue-edit loss |
112| Queue actions across environments | Web queue commands use explicit `environmentId` + `threadId` (`QueuedRunsControl.tsx:44-60`); mobile does the same (`ThreadQueueControl.tsx:18-72`) | Scoped correctly | Retained |
113| Timeline/activity/working-thinking, rounds 17–20 | Recent reconciliation paths and their focused logic tests were inspected; no additional semantic regression confirmed | Recent feed/group/header changes inspected; non-image attachment filtering and the bypassed Codex directive renderer are confirmed regressions | Two confirmed mobile regressions |
114
115## Verification
116
117Focused existing tests:
118
119```text
120vp test run packages/client-runtime/src/state/threadFeedback.test.ts \
121 apps/web/src/components/ChatView.logic.test.ts \
122 apps/web/src/session-logic.test.ts \
123 apps/web/src/components/chat/MessagesTimeline.logic.test.ts \
124 apps/mobile/src/lib/threadActivity.test.ts \
125 apps/mobile/src/features/threads/threadListV2.test.ts \
126 apps/web/src/components/ChatMarkdown.workspace-images.test.tsx \
127 apps/web/src/components/chat/QueuedRunsControl.test.tsx
128
1298 test files passed; 241 tests passed.
130```
131
132Follow-up targeted tests:
133
134```text
135vp test run apps/web/src/composerDraftStore.test.ts \
136 apps/web/src/components/chat/composerProviderState.test.tsx \
137 apps/mobile/src/lib/modelOptions.test.ts \
138 packages/shared/src/model.test.ts \
139 packages/contracts/src/settings.test.ts \
140 apps/web/src/components/chat/AssistantCitationSource.test.ts \
141 apps/web/src/components/Sidebar.logic.test.ts \
142 apps/web/src/hostedPairing.test.ts
143
1448 test files passed; 358 tests passed.
145```
146
147The green suites do not cover the missing-config sidebar render, generic-file queued edit, mobile non-image attachment rendering, the mobile specialized-Markdown caller regression including iOS media, visibility-lease intersection behavior, or hosted Project Settings behavior. They were not rerun during the final bounded file/media pass. React Doctor's review criteria were applied during the source review; its network-installed CLI was not run because this audit made no React edits and the user requested avoiding broad/network-heavy tooling without clear value. No browser or real-client integration pass was run, per instruction.
audits/orchestrator-v2/2026-09-02/cross-cutting.md+44−0
@@ -0,0 +1,44 @@
1Current-branch cross-cutting review, 2026-09-02
2
3Frozen branch: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0`. Main: `57a66608b918d673eeec7e6c94ea5906b756fcd0`. Read-only product review; all files in this directory are uncommitted audit artifacts.
4
5**Confirmed missed port: automatic title retries (P2).** Main `fc262f1a28` (#8087) retries `generateThreadTitle` twice using exponential backoff. The v2 replacement calls `generateThreadTitle` once (`apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts:223`) and catches non-interruption failures as a successful completion (`:236`). It then clears the title marker (`:249`); the effect worker sees success and cannot retry. Initial generation is only armed when a `titleSeed` arrives with no existing messages (`Orchestrator.ts:2915`). A transient typed provider error therefore leaves the fallback title until manual regeneration. This is distinct from preserving the final-failure cleanup behavior. The common `TextGeneration.ts:150` dispatcher does not add a retry. Recommendation: port bounded retry for initial titles and retain stale-request guards/final cleanup. Source trace confirms the missing retry; the existing failure test covers cleanup after one call, not recovery from a transient typed failure.
6
7**Explicitly deferred main feature: continue active threads after an update.** Main `5b7d72aad1` (#9167) persists continuation markers and resumes active work after server restart. Current `ServerEnvironment.ts:230` deliberately withholds `serverUpdateThreadContinuation`; `serverRuntimeStartup.ts` and `ws.ts:1647` do not implement the main marker/continuation path. The round-20 commit (`d2f1f511f4`) explicitly records that v2 recovery terminalizes runs and a v2 equivalent is still needed. Web settings and capability-gated request plumbing are present. Recommendation: make a release decision to port this or keep it deferred; do not label it an accidental conflict resolution.
8
9**Root source traces reviewed with the persistence reviewer.**
10
11| Finding | Source trace | Confirmed scope / evidence |
12| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13| Existing v2 database upgrade after migration renumbering | The last commit shifts old 044–052 to 045–053. Effect SQL migrator skips IDs at/below the recorded maximum; new 053 unconditionally creates a table old 052 already created. | A disposable run of the real migrator reproduces startup failure at 053. Main 044's repair is also skipped. Fresh-database success is insufficient. |
14| Removing a project with imported legacy threads | `ws.ts:1238` deletes v2 threads then `ProjectService.ts:350` dispatches a legacy project deletion without force. Legacy command bootstrap still reads `projection_threads`, which the importer preserves. | Source-confirmed partial deletion: legacy validation rejects after v2 thread deletions have committed. No integrated deletion was performed. |
15| Project deletion from HTTP/CLI bypasses v2 lifecycle | `project/http.ts:76` and offline `cli/project.ts:457` call project deletion directly, drop `force`, and never enumerate/delete v2 threads. Live CLI uses this HTTP endpoint. | Source-confirmed orphaning for v2-only projects and ineffective force for imported projects. HTTP also drops `createWorkspaceRootIfMissing`. |
16| Checkpoint baseline after an ordinary second run | `CheckpointDiffQuery.ts:154` requires a scope owned by ordinal 1. `IdAllocator.ts:295` allocates one root scope per thread; the projector upserts its owner to each new run. | Sol's real allocator/reducer probe confirms two runs produce one scope owned by run 2. The diff lookup therefore fails even with a valid baseline. Root review broadened the initial cancelled-first-run finding to ordinary multi-run threads. |
17| Diff query reads full transcript | Main #8988/#8992 uses narrow checkpoint context; v2 `CheckpointDiffQuery.ts:102` calls full `getThreadProjection`. | Source confirms an unbounded read/decode of unrelated messages/tool bodies and ancestor histories; no latency or memory benchmark was run. |
18
19The persistence report also confirms that a stale failure can defeat a later snooze, and that startup recovery reads full projections of every active and archived thread. Root rejected the proposed "both settlement settings disabled" early return: a closed PR still intentionally settles under both main and v2, even with those two settings off. Skipping the lookup would change that behavior.
20
21**Source paths checked for retained behavior.**
22
23| Area | Current result / evidence |
24| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
25| Main inclusion | Main is the exact merge base and an ancestor: 0 main-only, 332 branch-only commits; 942 changed files. This does not prove behavior parity. |
26| New-main inventory | 165 main commits since the previous comparison; 46 touch only files identical to current main, 119 touch at least one differing file. See `main-feature-file-map.tsv`; file equality is not proof the runtime calls that path. |
27| Previous fixes | `prior-fixes-range-diff.txt` compares all 41 previous fix/review commits with the 38 rebased equivalents. Most backend fixes are exact patches; changed/dropped client patches are assigned explicit client review. |
28| Upgrade protocol / remote compatibility | Protocol-2 descriptor and socket gating remain. `httpCors.ts` still permits the orchestration protocol header. Current WS and client compatibility tests pass. Older clients/servers are intentionally incompatible. |
29| Remote cookie isolation | Main #8085 identity initialization and auth dependency wiring survive in `ServerEnvironment.ts`, `server.ts`; `auth/EnvironmentAuth.ts` and HTTP handling retain main code. |
30| Relay credentials | Main #9178 credential refresh files are unchanged from main; no new discrepancy identified. |
31| Long HTTP thread IDs | Main #8898 `HTTP_ROUTER_CONFIG.maxParamLength = 512` and its use by `HttpRouter.serve` remain in `server.ts:126` and `:688`. |
32| Local-only worktree bases | Main #8751's remote-branch existence check is ported into `ThreadLaunchService.ts:242-286`; V2 falls back to the local base when the requested origin branch is absent. |
33| Service launcher packaging | `apps/server/package.json` retains main's dual bundle command including `src/service-launcher.ts`. |
34| Remote desktop update | Current server descriptor/RPC/auth support remains; server continuation is separately withheld as noted above. No desktop runtime test was performed. |
35| Attachment cleanup transaction ordering | Main #7941 defers cleanup until after commit. V2 encodes deletion cleanup in its outbox: `Orchestrator.ts:1971`, `EventSink.ts:397` transaction commits event/projection/effect together, then notifies workers at `:447`; cleanup runs through `EffectWorker.ts:285` and `ResourceCleanupService.ts:48`. No v2 pre-commit deletion found on this path. |
36| Pending uploads | Copy-claim behavior retains retry source and releases failed claims; focused AttachmentClaims and attachment-store tests pass. |
37| Main PR-title prompt fix | #9191 TextGenerationPrompts source/tests are identical to main and invoked by the retained text-generation layer; focused tests pass. |
38| Billing / usage collection | Pricing and transcript/cache readers for #8806, #9024, #8540 are identical to main. In-run provider usage is reviewed separately by the provider/persistence children. |
39| Auth scopes | Every renamed v2 RPC and new project/assets/scheduler RPC remains explicitly classified; no removed main scope boundary found. |
40| Legacy transcript and portable handoff limits | Current user/internal docs still describe lost legacy rich history/fresh sessions and the 32,000-character migration budget versus 240-character per-item portable summaries. These are retained architectural tradeoffs from the earlier audit. |
41
42**Final cross-boundary checks.** Root traced a second path around the HTTP history budget and asked the provider reviewer to validate it through both current clients. F16 is confirmed: an unavailable cold HTTP loader, or a warm reconnect above the replay limits, reaches `ws.ts:860`'s full snapshot and resets progressive-history metadata. Healthy cold opens remain bounded. Root also reviewed the mobile assistant-renderer gap with the client reviewer: bypassing `AssistantMarkdownContent` loses both Codex directive actions and iOS environment-aware inline media (F17). These are concrete caller gaps despite the retained helper implementations.
43
44**Root validation.** `root-server-tests.log`: 6 files, 38 tests passed. `root-compatibility-tests.log`: 1 file, 3 passed. `root-title-cli-tests.log`: 3 files, 35 passed. `root-usage-tests.log`: 3 files, 29 passed. `root-awareness-tests.log`: 1 file, 4 passed. `root-core-tests.log`: 3 files, 12 passed (delegated completion, context handoff, and schedule calculation). Total: 17 file executions, 121 tests passed. The existing tests do not cover populated-project CLI removal or upgraded v2 migration IDs; the separate Sol upgrade probe supplies the migration evidence. No product code changes, repo-wide checks, browsers, live providers, or dev servers.
audits/orchestrator-v2/2026-09-02/main-feature-file-map.tsv+166−0
@@ -0,0 +1,166 @@
1main_commit main_feature_or_fix comparison files_at_commit same_as_current_main different_from_current_main files_needing_behavior_review files_identical_to_current_main
257a66608b918d673eeec7e6c94ea5906b756fcd0 fix(pull-requests): align checkout control with author (#9196) identical-final-files 1 1 0 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
36e3bac3722d19b2e2cb736c2e789568eaf151032 fix(web): prevent connection rows from wrapping during removal (#8706) identical-final-files 1 1 0 apps/web/src/components/settings/ConnectionsSettings.tsx
46effe0a2fab1bcd48315485b580419377451c757 feat(web): redesign provider editor and models list (#8508) review-v2-path 7 6 1 apps/web/src/components/settings/ProviderInstanceCard.tsx apps/web/src/components/settings/ProviderAccentColorPicker.tsx; apps/web/src/components/settings/ProviderModelsSection.test.ts; apps/web/src/components/settings/ProviderModelsSection.tsx; apps/web/src/components/settings/ProviderSettingsForm.tsx; apps/web/src/components/settings/ProviderSettingsPanel.environment.test.tsx; apps/web/src/components/settings/ProviderSettingsPanel.tsx
5bc918e74ace5dbb4fe1ce73b59d06a9ca1be9ed3 fix(server): discover project skills for Claude (#9210) review-v2-path 1 0 1 apps/server/src/provider/Drivers/ClaudeDriver.ts
670cd258d8aac43ea57494527b00bf36de3efa6c0 fix(web): prevent two-digit list markers from being clipped (#9101) review-v2-path 3 2 1 apps/web/src/index.css apps/web/src/components/ChatMarkdown.test.tsx; apps/web/src/components/ChatMarkdown.tsx
7f14f41b894448298a86865c9114e6700245356e7 fix(web): preserve composer draft during worktree setup (#9197) review-v2-path 2 1 1 apps/web/src/composerDraftStore.ts apps/web/src/composerDraftStore.test.ts
87e9d5a7efa70f92f91d960a4f50243ba44d805da fix(mobile): prevent message and composer overlap (#9195) review-v2-path 4 2 2 apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx apps/mobile/src/lib/wideMarkdownBlocks.test.ts; apps/mobile/src/lib/wideMarkdownBlocks.ts
95b7d72aad14ed37e8e5e4c02a6d49814bfe528ac feat(updates): continue active threads across server restarts (#9167) review-v2-path 26 6 20 apps/desktop/src/settings/DesktopClientSettings.test.ts; apps/server/src/environment/ServerEnvironment.test.ts; apps/server/src/environment/ServerEnvironment.ts; apps/server/src/provider/Layers/CodexAdapter.ts; apps/server/src/provider/Layers/ProviderService.test.ts; apps/server/src/provider/Layers/ProviderService.ts; apps/server/src/provider/Services/ProviderAdapter.ts; apps/server/src/server.test.ts; apps/server/src/serverRuntimeStartup.reconcile.test.ts; apps/server/src/serverRuntimeStartup.ts; apps/server/src/ws.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/settings/SettingsPanels.tsx; apps/web/src/components/settings/settingsSearch.ts; apps/web/src/versionSkew.ts; docs/user/updating.md; packages/contracts/src/environment.ts; packages/contracts/src/provider.ts; packages/contracts/src/server.ts; packages/contracts/src/settings.ts apps/server/src/cloud/selfUpdate.test.ts; apps/server/src/cloud/selfUpdate.ts; apps/server/src/desktopUpdate/DesktopAppUpdate.ts; apps/web/src/components/ServerUpdateAction.test.tsx; apps/web/src/components/ServerUpdateAction.tsx; apps/web/src/components/settings/ConnectionsSettings.tsx
1014f15cfed48bc7519e8e5eeac03f483f4b11ef3a fix(server): stop titling linked PR threads from local git history (#9191) identical-final-files 2 2 0 apps/server/src/textGeneration/TextGenerationPrompts.test.ts; apps/server/src/textGeneration/TextGenerationPrompts.ts
11dd6879ffea24da9fc16cb62d7d6072d2c5b2bae3 fix(pull-requests): reuse github api reads (#9176) review-v2-path 25 17 8 apps/server/src/auth/RpcAuthorization.ts; apps/server/src/orchestration/ThreadSettlementReactor.test.ts; apps/server/src/orchestration/ThreadSettlementReactor.ts; apps/server/src/server.test.ts; apps/server/src/server.ts; apps/server/src/ws.ts; apps/web/src/routes/_chat.pull-requests.tsx; packages/contracts/src/rpc.ts apps/mobile/src/state/use-thread-pr.ts; apps/server/src/pullRequest/GitHubPullRequestCli.test.ts; apps/server/src/pullRequest/GitHubPullRequestCli.ts; apps/server/src/pullRequest/GitHubPullRequestProvider.test.ts; apps/server/src/pullRequest/GitHubPullRequestProvider.ts; apps/server/src/pullRequest/PullRequestProvider.ts; apps/server/src/pullRequest/PullRequestService.test.ts; apps/server/src/pullRequest/PullRequestService.ts; apps/server/src/sourceControl/GitHubCli.test.ts; apps/server/src/sourceControl/GitHubCli.ts; apps/server/src/sourceControl/GitHubSourceControlProvider.ts; apps/server/src/vcs/VcsProcess.test.ts; apps/server/src/vcs/VcsProcess.ts; apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx; apps/web/src/state/pullRequests.ts; packages/client-runtime/src/state/pullRequests.ts; packages/contracts/src/pullRequest.ts
129e646ad84c9d5008187eabf479e85619df9de8ca fix(connect): refresh relay credentials before expiry (#9178) review-v2-path 8 7 1 packages/client-runtime/src/connection/resolver.test.ts packages/client-runtime/src/authorization/service.ts; packages/client-runtime/src/connection/model.ts; packages/client-runtime/src/connection/supervisor.test.ts; packages/client-runtime/src/connection/supervisor.ts; packages/client-runtime/src/state/pullRequestDiffHttp.test.ts; packages/client-runtime/src/state/pullRequestDiffHttp.ts; packages/client-runtime/src/state/pullRequests.ts
13a19f01fc19d209d9962c4bb66372ed9e03e320a9 feat(web): make context window indicator opt-in (#9190) review-v2-path 6 0 6 apps/desktop/src/settings/DesktopClientSettings.test.ts; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/settings/SettingsPanels.tsx; apps/web/src/components/settings/settingsSearch.ts; packages/contracts/src/settings.test.ts; packages/contracts/src/settings.ts
146ff537f03c4df500da7283c0566fe6f8feca709b fix(web): remove projects with archived threads (#8798) review-v2-path 2 1 1 apps/web/src/components/LegacySidebar.tsx apps/web/src/components/settings/ProjectSettingsPanel.tsx
15a81a52afbb4e03ba82b2743801772151ed8c7d70 fix(server): allow local-only worktree bases (#8751) review-v2-path 6 0 6 apps/server/src/git/GitWorkflowService.ts; apps/server/src/server.test.ts; apps/server/src/vcs/GitVcsDriver.ts; apps/server/src/vcs/GitVcsDriverCore.test.ts; apps/server/src/vcs/GitVcsDriverCore.ts; apps/server/src/ws.ts
16a56b0cd7178dfef299aa49b26773a80a34abe205 fix(server): allow large Azure DevOps PR lists (#8572) identical-final-files 4 4 0 apps/server/src/pullRequest/AzureDevOpsPullRequestCli.test.ts; apps/server/src/pullRequest/AzureDevOpsPullRequestCli.ts; apps/server/src/sourceControl/AzureDevOpsCli.test.ts; apps/server/src/sourceControl/AzureDevOpsCli.ts
174116db9807cb37b49d3db35c7a45301f5e851b01 fix(server): bound OpenCode version probes (#8750) identical-final-files 3 3 0 apps/server/src/provider/Layers/OpenCodeProvider.test.ts; apps/server/src/provider/Layers/OpenCodeProvider.ts; apps/server/src/provider/opencodeRuntime.ts
18d2042d288eaf7aaf7feda85eb7d1a6f546302c12 fix(web): avoid stale file writes on close (#8630) identical-final-files 2 2 0 apps/web/src/components/files/fileSaveCoordinator.test.ts; apps/web/src/components/files/fileSaveCoordinator.ts
197a8df3338f910cd18543cd3ea98b8b10385e8dcd fix(desktop): skip cached monitor compiler check (#9184) identical-final-files 3 3 0 docs/internals/scripts.md; scripts/build-desktop-artifact.test.ts; scripts/build-desktop-artifact.ts
20827345a07a3d2348abc63e5215e57184024078d8 fix(web): model info button opens its details on click (#9177) identical-final-files 1 1 0 apps/web/src/components/settings/ProviderModelsSection.tsx
215392c9bb99c4be1f1c3fd78743df62885fd214dd fix(models): restore sticky new-thread selections (#9164) review-v2-path 17 6 11 apps/mobile/src/lib/modelOptions.ts; apps/server/src/cli/project.ts; apps/server/src/orchestration/decider.ts; apps/server/src/persistence/Migrations.ts; apps/server/src/serverRuntimeStartup.test.ts; apps/server/src/serverRuntimeStartup.ts; apps/web/src/components/CommandPalette.tsx; docs/user/composer.md; packages/contracts/src/orchestration.ts; packages/shared/src/model.test.ts; packages/shared/src/model.ts apps/mobile/src/lib/modelOptions.test.ts; apps/server/src/orchestration/decider.projectThreadEnvMode.test.ts; apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts; apps/server/src/persistence/Migrations/044_ClearAutomaticProjectModelDefaults.ts; apps/web/src/components/chat/composerProviderState.test.tsx; apps/web/src/components/chat/composerProviderState.tsx
22535c83dea5025e49df12df4a14b4d1dfb7d373ce fix(web): copying a code block no longer copies triple backticks (#8448) identical-final-files 2 2 0 apps/web/src/markdown-clipboard.test.ts; apps/web/src/markdown-clipboard.ts
23f9d1c65d475807f70d29deff71b16a922a61c4c0 chore: bump vendored GhosttyKit and update terminal integration (#9155) identical-final-files 31 31 0 apps/mobile/modules/t3-terminal/README.md; apps/mobile/modules/t3-terminal/THIRD_PARTY_NOTICES.md; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/allocator.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/color.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/key.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/key/encoder.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/key/event.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/osc.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/paste.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/result.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/sgr.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/Headers/ghostty/vt/wasm.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64-simulator/libghostty-fat.a; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/allocator.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/color.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/key.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/key/encoder.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/key/event.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/osc.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/paste.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/result.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/sgr.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/Headers/ghostty/vt/wasm.h; apps/mobile/modules/t3-terminal/Vendor/libghostty/GhosttyKit.xcframework/ios-arm64/libghostty-fat.a; apps/mobile/modules/t3-terminal/Vendor/libghostty/VERSION; apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift; apps/mobile/modules/t3-terminal/scripts/build-libghostty-ios16.sh
240681d854994ec0ceed0562ae74549ccf8ef2ec41 fix(pull-requests): expand code tab diffs by default (#9174) identical-final-files 2 2 0 apps/web/src/components/pullRequest/pullRequestDiff.logic.test.ts; apps/web/src/components/pullRequest/pullRequestDiff.logic.ts
2580a14b6588f0e856983e6d10002cde9e00e9d3e2 fix(server): discover project skills for Codex and OpenCode (#8778) review-v2-path 24 10 14 apps/server/src/orchestration/Layers/ProviderCommandReactor.ts; apps/server/src/provider/Drivers/CodexDriver.ts; apps/server/src/provider/Drivers/OpenCodeDriver.ts; apps/server/src/provider/Layers/OpenCodeAdapter.test.ts; apps/server/src/provider/Layers/ProviderRegistry.test.ts; apps/server/src/provider/Layers/ProviderRegistry.ts; apps/server/src/provider/ProviderDriver.ts; apps/server/src/provider/providerMaintenanceRunner.test.ts; apps/server/src/ws.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; packages/client-runtime/src/state/server.ts; packages/contracts/src/rpc.ts; packages/contracts/src/server.ts apps/server/src/provider/Layers/CodexProvider.ts; apps/server/src/provider/Layers/OpenCodeProvider.test.ts; apps/server/src/provider/Layers/OpenCodeProvider.ts; apps/server/src/provider/OpenCodeServerOwner.test.ts; apps/server/src/provider/Services/ProviderRegistry.ts; apps/server/src/provider/opencodeRuntime.ts; apps/server/src/provider/testUtils/providerRegistryMock.ts; apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts; packages/client-runtime/src/providerSkills.test.ts; packages/client-runtime/src/providerSkills.ts
26082358f9ef6feaadfaafd59a7512da26beb00100 fix(desktop): check artifact build prerequisites (#8975) identical-final-files 4 4 0 CONTRIBUTING.md; docs/internals/scripts.md; scripts/build-desktop-artifact.test.ts; scripts/build-desktop-artifact.ts
2743bafd4674841fb8782f5174033b684aa0d83f67 fix(web): open PR toast actions in app (#9006) review-v2-path 1 0 1 apps/web/src/components/GitActionsControl.tsx
288339508f5c244a63a845004393d7d9e701152d6e fix(chat): align failed task progress test (#9172) review-v2-path 1 0 1 apps/web/src/components/chat/MessagesTimeline.logic.test.ts
29c15735dd8894f829ce396c922ce4b5a48073f05a fix(chat): replace failed tools with thinking (#9165) review-v2-path 2 0 2 apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts
30133db22fae34cfb2e62a806e99d22674b0376f35 feat(web): copy the full error report from the error page (#9166) review-v2-path 1 0 1 apps/web/src/routes/__root.tsx
31b520120cf169ce63a5606447a292451e97622c9a fix(chat): improve tool group summaries and scrolling (#9106) review-v2-path 27 12 15 apps/mobile/src/features/threads/ThreadFeed.tsx; apps/mobile/src/lib/threadActivity.test.ts; apps/mobile/src/lib/threadActivity.ts; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/session-logic.test.ts; apps/web/src/session-logic.ts; packages/client-runtime/package.json; packages/client-runtime/src/work-log/presentation.test.ts; packages/client-runtime/src/work-log/presentation.ts apps/mobile/src/features/threads/thread-feed-live-follow.test.ts; apps/mobile/src/features/threads/thread-feed-live-follow.ts; apps/mobile/src/features/threads/thread-work-log.tsx; apps/mobile/src/lib/layout.test.ts; apps/mobile/src/lib/layout.ts; apps/web/src/components/T3Wordmark.tsx; apps/web/src/components/sidebar/SidebarChrome.tsx; docs/user/tool-activity.md; packages/client-runtime/src/work-log/commandLabel.test.ts; packages/client-runtime/src/work-log/commandLabel.ts; packages/client-runtime/src/work-log/scrollAnchor.test.ts; packages/client-runtime/src/work-log/scrollAnchor.ts
32db4bf9497b524f35e665af9e941aa8faeb05ea9e chore: remove unused code and brittle tests (#9150) review-v2-path 41 24 17 apps/server/integration/OrchestrationEngineHarness.integration.ts; apps/server/package.json; apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts; apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts; apps/server/src/provider/Layers/ProviderAdapterRegistry.ts; apps/server/src/provider/Layers/ProviderService.test.ts; apps/server/src/provider/Services/ProviderAdapterRegistry.ts; apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts; apps/web/src/components/chat/ChangedFilesTree.test.tsx; apps/web/src/components/chat/ComposerPrimaryActions.test.tsx; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/composerDraftStore.ts; apps/web/src/keybindings.ts; apps/web/src/lib/contextWindow.ts; apps/web/src/providerInstances.ts; pnpm-lock.yaml; vite.config.ts .github/workflows/release.yml; apps/desktop/src/wsl/DesktopWslEnvironment.test.ts; apps/marketing/public/apple-touch-icon.webp; apps/marketing/public/favicon-16x16.webp; apps/marketing/public/favicon-32x32.webp; apps/marketing/public/icon.png; apps/marketing/public/screenshot.webp; apps/marketing/tweets.md; apps/mobile/package.json; apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.test.ts; apps/mobile/src/features/terminal/terminalRouteBootstrap.ts; apps/server/src/textGeneration/TextGeneration.ts; apps/web/src/cloud/linkEnvironment.ts; apps/web/src/components/DiffPanelShell.tsx; apps/web/src/components/ProviderUpdateLaunchNotification.logic.ts; apps/web/src/components/chat/ComposerCommandMenu.test.tsx; apps/web/src/components/chat/providerIconUtils.ts; apps/web/src/components/desktopUpdate.logic.ts; apps/web/src/composer-logic.ts; apps/web/src/lib/utils.ts; apps/web/src/portDiscoveryState.ts; apps/web/src/providerModels.ts; apps/web/src/routes/-chatIndexTitlebar.test.ts; apps/web/src/themePalette.ts
33b8262b41228e03b2e1e14b701be449034349c8ba fix(desktop): hold-to-quit no longer gets stuck (#9141) identical-final-files 4 4 0 apps/desktop/src/window/DesktopWindow.test.ts; apps/desktop/src/window/DesktopWindow.ts; apps/desktop/src/window/QuitHold.test.ts; apps/desktop/src/window/QuitHold.ts
34cde12790d721ab2a90471750fd75d9f5bf13f57d fix(contracts): accept legacy pull request checkout results (#8238) identical-final-files 2 2 0 packages/contracts/src/git.test.ts; packages/contracts/src/git.ts
35a1a2bb1cd9f000f53fbeae752ed5c38f152eadf2 fix(web): label keybinding condition removal actions (#8664) review-v2-path 3 2 1 apps/web/src/components/settings/KeybindingsSettings.logic.test.ts apps/web/src/components/settings/KeybindingsSettings.logic.ts; apps/web/src/components/settings/KeybindingsSettings.tsx
36e9db39ce05c706122bb747fd5e7e70e1aac78935 fix(web): align composer notices and stash (#8890) review-v2-path 8 5 3 apps/web/src/components/ChatView.tsx; apps/web/src/versionSkew.test.ts; apps/web/src/versionSkew.ts apps/web/src/components/ServerUpdateAction.tsx; apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/chat/ComposerBannerStack.tsx; apps/web/src/components/chat/ComposerStashBadge.tsx; apps/web/src/components/chat/ComposerTasksBadge.tsx
37941acb4f919bcef337d4d9e1623101bdc30d3616 fix(provider): drop removed custom models from the model picker (#9075) review-v2-path 6 4 2 apps/server/src/provider/Layers/ProviderRegistry.test.ts; apps/server/src/provider/Layers/ProviderRegistry.ts apps/server/src/provider/providerStatusCache.test.ts; apps/server/src/provider/providerStatusCache.ts; apps/web/src/modelSelection.test.ts; apps/web/src/modelSelection.ts
38e7deb2aaf4d4d089618dc0c8f393fa0b8b6c77e9 feat(web): cite assistant responses with inline citations (#9146) review-v2-path 49 34 15 apps/mobile/src/features/threads/ThreadFeed.tsx; apps/mobile/src/lib/projectThreadStartTurn.test.ts; apps/mobile/src/lib/projectThreadStartTurn.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.ts; apps/server/src/provider/Layers/ProviderService.test.ts; apps/server/src/provider/Layers/ProviderService.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/components/chat/useAssistantCitationTarget.ts; apps/web/src/index.css; docs/user/composer.md; packages/contracts/src/index.ts; packages/shared/package.json apps/web/src/components/ChatMarkdown.tsx; apps/web/src/components/ComposerCitationNode.tsx; apps/web/src/components/ComposerPromptEditor.test.ts; apps/web/src/components/ComposerPromptEditor.tsx; apps/web/src/components/ThreadTerminalDrawer.test.ts; apps/web/src/components/ThreadTerminalDrawer.tsx; apps/web/src/components/chat/AssistantCitationChip.tsx; apps/web/src/components/chat/AssistantCitationCommentEditor.tsx; apps/web/src/components/chat/AssistantCitationSource.test.ts; apps/web/src/components/chat/AssistantCitationSource.tsx; apps/web/src/components/chat/AssistantSelectionToolbar.tsx; apps/web/src/components/chat/ComposerStashMenu.tsx; apps/web/src/components/chat/composerSubmission.test.ts; apps/web/src/components/chat/composerSubmission.ts; apps/web/src/components/composerInlineTokenPaste.ts; apps/web/src/composer-editor-mentions.test.ts; apps/web/src/composer-editor-mentions.ts; apps/web/src/composer-logic.test.ts; apps/web/src/composer-logic.ts; apps/web/src/composerDraftStore.test.ts; apps/web/src/lib/assistantCitationNavigation.test.ts; apps/web/src/lib/assistantCitationNavigation.ts; apps/web/src/lib/assistantTextSelection.test.ts; apps/web/src/lib/assistantTextSelection.ts; apps/web/src/lib/selectionActions.test.ts; apps/web/src/lib/selectionActions.ts; apps/web/src/markdown-clipboard.test.ts; apps/web/src/promptStashStore.test.ts; apps/web/src/terminal/ghostty/surface.ts; docs/internals/assistant-citations.md; docs/internals/providers.md; packages/contracts/src/assistantCitations.ts; packages/shared/src/assistantCitations.test.ts; packages/shared/src/assistantCitations.ts
392ab7973fe06eecf8b0a0d1da8481d926fca1ee1b fix(web): hide build pill in narrow sidebars (#9159) identical-final-files 1 1 0 apps/web/src/components/sidebar/SidebarChrome.tsx
409fdafdf114a88bd7309883b4578e74cbe786d04d feat(pull-requests): copy provider checkout commands (#9086) review-v2-path 9 7 2 apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts; apps/web/src/components/pullRequest/pullRequestDetail.logic.ts apps/server/src/pullRequest/BitbucketPullRequestProvider.ts; apps/server/src/pullRequest/PullRequestProvider.ts; apps/server/src/pullRequest/PullRequestService.ts; apps/server/src/pullRequest/bitbucketPullRequestJson.test.ts; apps/server/src/pullRequest/bitbucketPullRequestJson.ts; apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx; packages/contracts/src/pullRequest.ts
4147a95332a2b8fe47da9fb0af97fd6f0b542a18ee fix(web): browse folders from file breadcrumbs (#8910) review-v2-path 5 4 1 apps/web/src/components/files/FilePreviewPanel.tsx apps/web/src/components/files/FileBreadcrumbs.tsx; apps/web/src/components/files/filePath.test.ts; apps/web/src/components/files/filePath.ts; apps/web/src/components/files/projectFilesQueryState.test.tsx
42fea1af81fdab64a19353224d60b4efa60f929740 fix(web): compact project settings actions (#9160) identical-final-files 1 1 0 apps/web/src/components/settings/ProjectSettingsPanel.tsx
43d937e30759ceae421bf3cc1b84414de4b67df76c feat(web): render HTML and PDF files in the file viewer (#9143) review-v2-path 2 0 2 apps/web/src/components/files/FilePreviewPanel.tsx; docs/user/composer.md
44f46a709ee487e1e6e04423fcb7032a26eb103129 feat(files): open markdown, HTML, and PDF files outside the workspace (#9140) review-v2-path 26 18 8 apps/mobile/src/features/threads/ThreadFeed.tsx; apps/server/src/http.test.ts; apps/web/src/components/RightPanelTabs.tsx; apps/web/src/components/files/FilePreviewPanel.tsx; apps/web/src/rightPanelStore.ts; docs/user/composer.md; packages/contracts/src/assets.ts; packages/contracts/src/project.ts apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx; apps/mobile/src/features/files/filePath.test.ts; apps/mobile/src/features/files/filePath.ts; apps/mobile/src/features/files/workspaceFileAssetUrl.ts; apps/server/src/assets/AssetAccess.test.ts; apps/server/src/assets/AssetAccess.ts; apps/server/src/http.ts; apps/server/src/workspace/WorkspaceFileSystem.test.ts; apps/server/src/workspace/WorkspaceFileSystem.ts; apps/web/src/browser/openFileInPreview.ts; apps/web/src/components/ChatMarkdown.tsx; apps/web/src/components/files/filePath.test.ts; apps/web/src/components/files/filePath.ts; apps/web/src/markdown-links.test.ts; apps/web/src/markdown-links.ts; apps/web/src/terminal-links.ts; docs/internals/environment-auth.md; packages/shared/src/filePreview.ts
45f2a914b8588bf3038472c14b129fbc5ddb3a2992 fix(web): preserve panel state across workspace refreshes (#8968) review-v2-path 7 6 1 apps/web/src/components/DiffPanel.tsx apps/web/src/components/diffs/AnnotatableCodeView.tsx; apps/web/src/components/files/FileBrowserPanel.tsx; apps/web/src/components/files/fileTreePathReconciliation.test.ts; apps/web/src/components/files/fileTreePathReconciliation.ts; apps/web/src/lib/diffRendering.test.ts; apps/web/src/lib/diffRendering.ts
468401f4d85887786fbb773d3fd41b8f07a77624b8 fix(web): darken neutral control surfaces (#9064) review-v2-path 5 4 1 apps/web/src/index.css apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/chat/ComposerSurface.tsx; apps/web/src/themePalette.test.ts; apps/web/src/themePalette.ts
470e1570bde5b64458449a45989f1b56d6fd651883 fix(web): project default model works on the hosted app (#9142) review-v2-path 8 7 1 apps/web/src/components/settings/SettingsPanels.tsx apps/web/src/components/settings/IntegrationsSettings.tsx; apps/web/src/components/settings/ProjectSettingsPanel.tsx; apps/web/src/components/settings/SourceControlSettings.tsx; apps/web/src/components/settings/SourceControlWritingSettings.tsx; apps/web/src/components/settings/settingsLayout.tsx; apps/web/src/hooks/useSettings.ts; apps/web/src/hostedPairing.ts
48c37fd136edf546fd58daeac146548436c1fa38c2 test(server): measure shell, second client, and reconnect transfer (#9157) review-v2-path 6 1 5 apps/server/integration/NetworkTransferMeasurement.integration.ts; apps/server/integration/OrchestrationEngineHarness.integration.ts; apps/server/integration/TransferBudgetReport.integration.ts; apps/server/integration/TransferBudgetScenario.integration.ts; apps/server/src/server.test.ts apps/server/integration/SqlStatementCounter.integration.ts
49b2f25d390a8546e42eb6186115b673a53b8c38dc feat(desktop): update the desktop app on remote Macs from the Update button (#6554) review-v2-path 41 27 14 apps/server/src/auth/RpcAuthorization.ts; apps/server/src/environment/ServerEnvironment.test.ts; apps/server/src/environment/ServerEnvironment.ts; apps/server/src/server.ts; apps/server/src/ws.ts; apps/web/src/components/ChatView.tsx; apps/web/src/versionSkew.test.ts; apps/web/src/versionSkew.ts; docs/user/updating.md; packages/client-runtime/src/connection/registry.test.ts; packages/client-runtime/src/state/server.ts; packages/contracts/src/environment.ts; packages/contracts/src/rpc.ts; packages/contracts/src/server.ts apps/desktop/src/app/DesktopApp.ts; apps/desktop/src/app/DesktopLifecycle.test.ts; apps/desktop/src/app/DesktopLifecycle.ts; apps/desktop/src/backend/DesktopBackendManager.test.ts; apps/desktop/src/backend/DesktopBackendPool.test.ts; apps/desktop/src/telemetry/DesktopTelemetryPublisher.test.ts; apps/desktop/src/telemetry/DesktopTelemetryPublisher.ts; apps/desktop/src/updates/DesktopRemoteUpdates.test.ts; apps/desktop/src/updates/DesktopRemoteUpdates.ts; apps/desktop/src/updates/DesktopUpdates.test.ts; apps/desktop/src/updates/DesktopUpdates.ts; apps/desktop/src/updates/remoteUpdateFlow.test.ts; apps/desktop/src/updates/remoteUpdateFlow.ts; apps/desktop/src/updates/updatesTestHarness.ts; apps/desktop/src/window/DesktopApplicationMenu.test.ts; apps/server/src/cloud/selfUpdate.test.ts; apps/server/src/cloud/selfUpdate.ts; apps/server/src/desktopUpdate/DesktopAppUpdate.test.ts; apps/server/src/desktopUpdate/DesktopAppUpdate.ts; apps/server/src/resourceTelemetry/DesktopTelemetryReceiver.ts; apps/web/src/components/ServerUpdateAction.test.tsx; apps/web/src/components/ServerUpdateAction.tsx; apps/web/src/components/settings/ConnectionsSettings.tsx; docs/internals/server-updates.md; packages/client-runtime/src/state/runtime.test.ts; packages/client-runtime/src/state/server.test.ts; packages/contracts/src/resourceTelemetry.ts
5080c708a1fa8986bce0b2d1ba4d35e79d9e4094ba perf(web): halve the cold-start bundle by splitting Clerk and cold routes (#9058) review-v2-path 9 8 1 apps/web/src/components/AppSidebarLayout.tsx apps/web/src/components/clerk/BrowserManagedAuthShell.tsx; apps/web/src/components/clerk/ElectronManagedAuthShell.tsx; apps/web/src/components/settings/ThemeEditorHost.tsx; apps/web/src/lib/chunkReloadGuard.test.ts; apps/web/src/lib/chunkReloadGuard.ts; apps/web/src/main.tsx; apps/web/src/router.ts; apps/web/vite.config.ts
51a434677eca737771dd64703545c13eb11ba92ce2 fix(grok): health check, model selection, and stop all work against the real CLI (#9154) review-v2-path 14 2 12 apps/server/scripts/acp-mock-agent.ts; apps/server/src/provider/Layers/GrokAdapter.test.ts; apps/server/src/provider/Layers/GrokAdapter.ts; apps/server/src/provider/Layers/GrokProvider.ts; apps/server/src/provider/acp/AcpRuntimeModel.ts; apps/server/src/provider/acp/AcpSessionRuntime.ts; apps/server/src/provider/acp/GrokAcpSupport.test.ts; apps/server/src/provider/acp/GrokAcpSupport.ts; apps/server/src/provider/acp/XAiAcpExtension.ts; packages/contracts/src/model.ts; packages/effect-acp/src/protocol.test.ts; packages/effect-acp/src/protocol.ts apps/server/src/provider/Layers/GrokProvider.test.ts; docs/internals/providers.md
52083d4de5b0fd33efa28363546b6ed89e191c5200 fix(clients): stop repeating expanded commands (#9120) review-v2-path 11 1 10 apps/mobile/src/lib/threadActivity.test.ts; apps/mobile/src/lib/threadActivity.ts; apps/server/src/orchestration/ActivityPayloadProjection.test.ts; apps/server/src/orchestration/ActivityPayloadProjection.ts; apps/server/test/ActivityPayloadProjection.test.ts; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/session-logic.command-output.test.ts; apps/web/src/session-logic.ts; packages/client-runtime/src/work-log/presentation.test.ts; packages/client-runtime/src/work-log/presentation.ts apps/mobile/src/features/threads/thread-work-log.tsx
53cdbf324aa043b1bc5705159127d2b55f186d1b9b fix(web): keep generated muted foreground dimmer than entered text (#9113) identical-final-files 2 2 0 apps/web/src/themePalette.test.ts; apps/web/src/themePalette.ts
54d0b19b32e01d7abc8829c01353d759fed3d304ed fix(claude): preview images read from the workspace (#9119) review-v2-path 10 0 10 apps/mobile/src/lib/threadActivity.test.ts; apps/mobile/src/lib/threadActivity.ts; apps/server/src/orchestration/ActivityPayloadProjection.test.ts; apps/server/src/orchestration/ActivityPayloadProjection.ts; apps/server/src/provider/Layers/ClaudeAdapter.test.ts; apps/server/src/provider/Layers/ClaudeAdapter.ts; apps/web/src/session-logic.test.ts; apps/web/src/session-logic.ts; packages/client-runtime/src/work-log/presentation.test.ts; packages/client-runtime/src/work-log/presentation.ts
55716069f40f000887cb01685099a16e19d9194307 fix(server): keep attachments until the command commits (#7941) review-v2-path 13 0 13 apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts; apps/server/src/orchestration/Layers/OrchestrationEngine.ts; apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts; apps/server/src/orchestration/Layers/ProjectionPipeline.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.ts; apps/server/src/orchestration/Services/OrchestrationEngine.ts; apps/server/src/orchestration/Services/ProjectionPipeline.ts; apps/server/src/orchestration/Services/ProviderCommandReactor.ts; apps/server/src/relay/AgentAwarenessRelay.test.ts; apps/server/src/serverRuntimeStartup.reconcile.test.ts; apps/server/src/serverRuntimeStartup.test.ts; docs/internals/overview.md
560e77fbd3d0d79eec5247e75583a04590a1785133 fix(server): prevent accidental service downgrades (#5302) identical-final-files 6 6 0 apps/server/src/cli/service.test.ts; apps/server/src/cli/service.ts; apps/server/src/cloud/bootService.test.ts; apps/server/src/cloud/bootService.ts; apps/server/src/cloud/serviceProtocol.ts; docs/user/background-service.md
578efd4e95fcb08478c9c6e8eee384bfca559f62cc fix(settings): sync auto-settle and other shared preferences across environments (#9147) review-v2-path 10 7 3 apps/web/src/components/settings/SettingsPanels.tsx; docs/internals/overview.md; packages/client-runtime/package.json apps/mobile/src/features/settings/SettingsRouteScreen.tsx; apps/web/src/components/settings/SharedSettingsMismatchAlert.tsx; apps/web/src/components/settings/SourceControlSettings.tsx; apps/web/src/hooks/useSettings.ts; docs/user/thread-sidebar.md; packages/client-runtime/src/state/sharedSettings.test.ts; packages/client-runtime/src/state/sharedSettings.ts
585014e5fcdd6e5f4a53d0fea4cbecba733821a854 fix(desktop): show newest changes in nightly previews (#9138) review-v2-path 17 15 2 docs/user/updating.md; packages/contracts/src/ipc.ts apps/desktop/src/updates/DesktopUpdates.test.ts; apps/desktop/src/updates/DesktopUpdates.ts; apps/desktop/src/updates/releaseNotes.test.ts; apps/desktop/src/updates/releaseNotes.ts; apps/desktop/src/updates/updateMachine.test.ts; apps/desktop/src/updates/updateMachine.ts; apps/web/src/components/desktopUpdate.logic.test.ts; apps/web/src/components/desktopUpdate.logic.ts; apps/web/src/components/desktopUpdate.toast.test.tsx; apps/web/src/components/desktopUpdate.toast.tsx; apps/web/src/components/sidebar/SidebarUpdatePill.test.tsx; apps/web/src/components/sidebar/SidebarUpdatePill.tsx; apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.test.tsx; apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx; apps/web/src/state/desktopUpdate.test.ts
59fc53b273039cf5e0a932582b1fbb56e7223ad4ab perf(clients): lease sidebar status by visibility (#9052) review-v2-path 6 3 3 apps/web/src/components/LegacySidebar.tsx; apps/web/src/components/Sidebar.logic.ts; apps/web/src/components/Sidebar.tsx apps/mobile/src/state/use-thread-pr.ts; packages/client-runtime/src/state/pullRequests.ts; packages/client-runtime/src/state/vcs.ts
606866fd6b5ccff82333ad4386ce68cdec03531b68 perf(client-runtime): keep turn and checkpoint refs stable while streaming (#9145) review-v2-path 2 0 2 packages/client-runtime/src/state/threadReducer.test.ts; packages/client-runtime/src/state/threadReducer.ts
617e460f429b740180cd72730418262a2df971ba54 fix(server): bound orchestration replay payloads (#8992) review-v2-path 10 0 10 apps/server/src/checkpointing/CheckpointDiffQuery.test.ts; apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts; apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts; apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts; apps/server/src/project/ProjectSetupScriptRunner.test.ts; apps/server/src/provider/Layers/ProviderSessionReaper.test.ts; apps/server/src/server.test.ts; apps/server/src/serverRuntimeStartup.test.ts; apps/server/src/ws.ts
62c2283ce14628127ef1758974ddab4f5b1f89d47a perf: make streaming projection and activity appends incremental (#9152) review-v2-path 4 0 4 apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts; apps/server/src/orchestration/Layers/ProjectionPipeline.ts; packages/client-runtime/src/state/threadReducer.test.ts; packages/client-runtime/src/state/threadReducer.ts
63feb3ea7ebf83de2e301adb1f20300b262773ec54 fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine (#8360) review-v2-path 7 4 3 apps/web/src/components/DiffPanel.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/components/files/FilePreviewPanel.tsx apps/web/src/components/DiffWorkerPoolProvider.tsx; apps/web/src/components/pullRequest/PullRequestCodeTab.tsx; apps/web/src/components/settings/SettingsFontPreviews.tsx; apps/web/src/lib/syntaxHighlighting.ts
64ea71a19d4181ce13b0e8f374f45ba9237ee4a9ce fix(claude): skills picked from the composer now run (#9128) review-v2-path 16 12 4 apps/server/src/provider/Layers/ClaudeAdapter.test.ts; apps/server/src/provider/Layers/ClaudeAdapter.ts; docs/user/composer.md; packages/contracts/src/server.ts apps/mobile/src/features/threads/use-composer-command-menu.ts; apps/server/src/provider/Drivers/ClaudeSkillDispatch.test.ts; apps/server/src/provider/Drivers/ClaudeSkillDispatch.ts; apps/server/src/provider/Drivers/ClaudeSkills.test.ts; apps/server/src/provider/Drivers/ClaudeSkills.ts; apps/web/src/components/chat/composerSlashCommandSearch.test.ts; apps/web/src/components/chat/composerSlashCommandSearch.ts; apps/web/src/providerSkillSearch.test.ts; apps/web/src/providerSkillSearch.ts; docs/user/providers-claude.md; packages/client-runtime/src/providerSkills.test.ts; packages/client-runtime/src/providerSkills.ts
659a7b1e21e51609266adf657bcab0b43b6bcd445c perf(provider): bound persisted session lookups (#8909) review-v2-path 2 0 2 apps/server/src/provider/Layers/ProviderService.test.ts; apps/server/src/provider/Layers/ProviderService.ts
6698725df00a729ba226ae8610a3488e5b9168029f fix(web): mute routine notices and update actions (#9063) review-v2-path 9 7 2 apps/web/src/components/settings/ProviderInstanceCard.tsx; apps/web/src/components/settings/SettingsPanels.tsx apps/web/src/components/ProviderUpdateEnvironmentRows.tsx; apps/web/src/components/ProviderUpdatePrimaryNotification.tsx; apps/web/src/components/ServerUpdateAction.tsx; apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/sidebar/DesktopUpdateStatusIcon.tsx; apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx; apps/web/src/components/sidebar/SidebarUpdatePill.tsx
6708aad594f0059d28dd23914f9d37ad4e695fff75 chore: delete dead code, unused deps, and duplicate helpers (#9129) review-v2-path 103 75 28 apps/mobile/src/features/threads/threadPresentation.ts; apps/server/integration/OrchestrationEngineHarness.integration.ts; apps/server/integration/providerService.integration.test.ts; apps/server/src/bin.test.ts; apps/server/src/httpCors.ts; apps/server/src/orchestration/commandInvariants.ts; apps/server/src/persistence/Migrations.ts; apps/server/src/provider/Drivers/ClaudeDriver.ts; apps/server/src/provider/Drivers/CodexDriver.ts; apps/server/src/provider/Drivers/CursorDriver.ts; apps/server/src/provider/Drivers/GrokDriver.ts; apps/server/src/provider/Drivers/OpenCodeDriver.ts; apps/server/src/provider/Layers/ProviderRegistry.ts; apps/server/src/provider/Layers/ProviderSessionDirectory.ts; apps/server/src/provider/acp/AcpRuntimeModel.ts; apps/server/src/serverRuntimeStartup.test.ts; apps/server/src/vcs/GitVcsDriver.ts; apps/web/src/index.css; apps/web/src/state/entities.ts; package.json; packages/client-runtime/src/state/models.ts; packages/client-runtime/src/state/server.ts; packages/contracts/src/ipc.ts; packages/contracts/src/providerRuntime.ts; packages/contracts/src/settings.ts; packages/effect-codex-app-server/package.json; packages/shared/src/model.ts; pnpm-lock.yaml apps/desktop/src/preview/PickLabelPosition.ts; apps/mobile/package.json; apps/mobile/src/components/GlassSafeAreaView.tsx; apps/mobile/src/features/agent-awareness/remoteRegistration.ts; apps/mobile/src/features/cloud/managedRelayState.ts; apps/mobile/src/features/diffs/nativeReviewDiffSurface.test.ts; apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx; apps/mobile/src/features/files/filePath.test.ts; apps/mobile/src/features/files/filePath.ts; apps/mobile/src/features/files/preload-workspace-file.ts; apps/mobile/src/features/review/ReviewHighlighterProvider.tsx; apps/mobile/src/features/review/diffParser.ts; apps/mobile/src/features/review/reviewCommentSelection.ts; apps/mobile/src/features/review/reviewState.ts; apps/mobile/src/features/review/shikiReviewHighlighter.ts; apps/mobile/src/features/terminal/ThreadTerminalPanel.tsx; apps/mobile/src/features/terminal/threadTerminalPanelModel.ts; apps/mobile/src/features/threads/use-composer-command-menu.test.ts; apps/mobile/src/features/threads/use-composer-command-menu.ts; apps/mobile/src/features/threads/use-legacy-plan-mode-enabled.ts; apps/mobile/src/state/auth.ts; apps/mobile/src/state/git.ts; apps/mobile/src/state/use-composer-path-search.ts; apps/server/scripts/cursor-acp-model-mismatch-probe.ts; apps/server/src/cli/config.ts; apps/server/src/cli/pair.ts; apps/server/src/cli/triage.ts; apps/server/src/cloud/bootService.ts; apps/server/src/keybindings.ts; apps/server/src/mcp/toolkits/preview/tools.ts; apps/server/src/pathExpansion.ts; apps/server/src/persistence/Layers/ProviderSessionRuntime.ts; apps/server/src/provider/Drivers/instanceIdentity.ts; apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts; apps/server/src/provider/acp/AcpCoreRuntimeEvents.ts; apps/server/src/provider/providerStatusCache.ts; apps/server/src/serverRuntimeState.ts; apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.test.ts; apps/server/src/sourceControl/BitbucketSourceControlProvider.test.ts; apps/server/src/sourceControl/SourceControlRepositoryService.ts; apps/server/src/telemetry/Services/AnalyticsService.ts; apps/server/src/textGeneration/TextGenerationPresets.ts; apps/server/src/textGeneration/TextGenerationPrompts.test.ts; apps/server/src/workspace/WorkspaceEntries.ts; apps/server/src/workspace/WorkspacePaths.ts; apps/web/package.json; apps/web/public/mockServiceWorker.js; apps/web/src/components/SplashScreen.tsx; apps/web/src/components/ui/button.test.tsx; apps/web/src/components/ui/card.tsx; apps/web/src/components/ui/field.tsx; apps/web/src/components/ui/fieldset.tsx; apps/web/src/components/ui/form.tsx; apps/web/src/environments/primary/context.ts; apps/web/src/environments/primary/index.ts; apps/web/src/historyBootstrap.ts; apps/web/src/hooks/useSettings.ts; apps/web/src/lib/terminalUiStateCleanup.ts; apps/web/src/localApi.ts; apps/web/src/observability/clientTracing.ts; apps/web/src/orchestrationEventEffects.ts; apps/web/src/orchestrationRecovery.ts; apps/web/src/remoteOpen.ts; apps/web/src/rpc/atomRegistry.ts; apps/web/src/rpc/requestLatencyState.ts; packages/client-runtime/src/connection/catalog.ts; packages/client-runtime/src/state/runtime.ts; packages/effect-acp/package.json; packages/effect-acp/test/examples/cursor-acp-client.example.ts; packages/shared/src/schemaJson.ts; packages/shared/src/themePalettes.ts; packages/tailscale/package.json; packages/tailscale/src/tailscale.ts; scripts/announce-connect-ga.ts; scripts/package.json
68b21d87243ea637fbb6fb50667e9a8547e1b6c4e8 chore: vouch six repeat contributors (#9131) identical-final-files 1 1 0 .github/VOUCHED.td
6904efa7907e9ec207e2d6af459ce3b2ffd55f6107 feat(cli): open projects in the running desktop app (#8824) review-v2-path 26 20 6 apps/web/src/hooks/useHandleNewThread.ts; apps/web/src/routes/__root.tsx; apps/web/src/state/entities.ts; packages/contracts/src/index.ts; packages/contracts/src/ipc.ts; packages/shared/package.json apps/desktop/src/app/DesktopApp.ts; apps/desktop/src/app/DesktopAppActivation.test.ts; apps/desktop/src/app/DesktopAppActivation.ts; apps/desktop/src/app/DesktopAppActivationBroker.test.ts; apps/desktop/src/app/DesktopAppActivationBroker.ts; apps/desktop/src/ipc/DesktopIpcHandlers.ts; apps/desktop/src/ipc/channels.ts; apps/desktop/src/ipc/methods/appActivation.ts; apps/desktop/src/main.ts; apps/desktop/src/preload.ts; apps/server/src/bin.ts; apps/server/src/cli/app.test.ts; apps/server/src/cli/app.ts; apps/web/src/components/desktop/DesktopAppActivationCoordinator.tsx; apps/web/src/desktopAppActivation.test.ts; apps/web/src/desktopAppActivation.ts; docs/user/install.md; packages/contracts/src/desktopAppActivation.ts; packages/shared/src/desktopAppControl.test.ts; packages/shared/src/desktopAppControl.ts
70beae2147a9487ec47ac992319f2216914b4cb62d fix(media): preview host files and stream videos across clients (#9023) review-v2-path 82 66 16 apps/mobile/src/features/threads/ThreadFeed.tsx; apps/server/src/http.test.ts; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/components/chat/externalLinkContextMenu.test.ts; apps/web/src/components/chat/externalLinkContextMenu.ts; apps/web/src/components/files/FilePreviewPanel.tsx; docs/user/composer.md; packages/client-runtime/package.json; packages/client-runtime/src/work-log/presentation.test.ts; packages/client-runtime/src/work-log/presentation.ts; packages/contracts/src/assets.ts; pnpm-lock.yaml apps/desktop/src/electron/ElectronProtocol.test.ts; apps/desktop/src/electron/ElectronProtocol.ts; apps/mobile/modules/t3-markdown-text/assets/file-icons/pierre_video.png; apps/mobile/modules/t3-markdown-text/package.json; apps/mobile/modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs; apps/mobile/modules/t3-markdown-text/src/markdownFileIcons.generated.ts; apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts; apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts; apps/mobile/src/components/FilePreview.ios.tsx; apps/mobile/src/components/FilePreview.tsx; apps/mobile/src/components/FilePreviewModal.tsx; apps/mobile/src/components/MediaActionsMenu.tsx; apps/mobile/src/components/MediaImagePreview.tsx; apps/mobile/src/components/MediaSourceCaption.tsx; apps/mobile/src/components/MediaVideoPlayer.tsx; apps/mobile/src/components/MediaVideoPreviewModal.tsx; apps/mobile/src/components/VideoPreviewModal.ios.tsx; apps/mobile/src/components/VideoPreviewModal.tsx; apps/mobile/src/components/VideoThumbnailImage.tsx; apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx; apps/mobile/src/features/files/WorkspaceFileImagePreview.tsx; apps/mobile/src/features/files/WorkspaceFileVideoPreview.tsx; apps/mobile/src/features/files/filePath.ts; apps/mobile/src/features/files/preload-workspace-file.ts; apps/mobile/src/features/files/workspaceFileAssetUrl.ts; apps/mobile/src/lib/markdownLinks.test.ts; apps/mobile/src/lib/markdownMedia.test.ts; apps/mobile/src/lib/markdownMedia.ts; apps/mobile/src/lib/mediaActions.ts; apps/mobile/src/lib/nativeMarkdownText.test.ts; apps/mobile/src/lib/videoPreviewSource.ts; apps/mobile/src/state/assets.ts; apps/mobile/src/state/use-atom-query-runner.ts; apps/server/src/assets/AssetAccess.test.ts; apps/server/src/assets/AssetAccess.ts; apps/server/src/assets/MediaFile.ts; apps/server/src/http.ts; apps/web/src/assets/assetUrls.ts; apps/web/src/components/ChatMarkdown.tsx; apps/web/src/components/ChatMarkdown.workspace-images.test.tsx; apps/web/src/components/chat/ExpandedImageDialog.test.tsx; apps/web/src/components/chat/ExpandedImageDialog.tsx; apps/web/src/components/chat/ExpandedImagePreview.test.ts; apps/web/src/components/chat/ExpandedImagePreview.tsx; apps/web/src/components/files/projectFilesQueryState.ts; apps/web/src/components/media/MediaActions.tsx; apps/web/src/components/media/MediaVideoPlayer.tsx; apps/web/src/components/media/OpenMediaLink.tsx; apps/web/src/components/media/mediaContent.ts; apps/web/src/components/pullRequest/PullRequestMarkdown.tsx; apps/web/src/contextMenuFallback.ts; apps/web/src/lib/videoFirstFrame.test.ts; apps/web/src/lib/videoFirstFrame.ts; apps/web/src/markdown-links.test.ts; apps/web/src/markdown-links.ts; apps/web/src/pierre-icons.ts; apps/web/src/state/use-atom-query-runner.ts; docs/internals/environment-auth.md; packages/client-runtime/src/markdownImages.ts; packages/client-runtime/src/markdownLinks.test.ts; packages/client-runtime/src/markdownLinks.ts; packages/client-runtime/src/mediaReference.test.ts; packages/client-runtime/src/mediaReference.ts; packages/shared/src/filePreview.test.ts; packages/shared/src/filePreview.ts; packages/shared/src/video.ts
7160cef47ec983637ddc68faed7b1488b6f3c3a175 chore(release): prepare v0.0.38 review-v2-path 4 2 2 apps/server/package.json; packages/contracts/package.json apps/desktop/package.json; apps/web/package.json
72590a579f2e9292ce314c69e459e19620004578fe fix(chat): keep latest command live between messages (#9098) review-v2-path 4 0 4 apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx
730222aa255d11babd242dbe5ed0947e5fc5eaefee fix(web): preserve theme when toggling advanced colors (#8500) identical-final-files 1 1 0 apps/web/src/components/settings/ThemeEditorPanel.tsx
74c0995d2eaf8ec787b3318ed1169ae266ed1529f8 fix(web): keep the selected environment when changing projects (#9102) review-v2-path 3 2 1 docs/user/composer.md apps/web/src/environmentGrouping.test.ts; apps/web/src/sidebarProjectGrouping.ts
75d0b4acbd13b2b602710e4a7d60c42f4799a409be fix(web): keep theme placeholder text dimmer than entered text (#9104) identical-final-files 3 3 0 apps/web/src/themePalette.test.ts; apps/web/src/themePalette.ts; apps/web/src/vscodeThemeImport.test.ts
763b3465f2a9dbc541a8806e39d2889abb77b93f5d fix(web): changing projects no longer creates a draft (#9097) review-v2-path 8 5 3 apps/web/src/components/ChatView.tsx; apps/web/src/composerDraftStore.ts; apps/web/src/hooks/useHandleNewThread.ts apps/web/src/components/chat/DraftHeroHeadline.tsx; apps/web/src/composerDraftStore.test.ts; apps/web/src/lib/attachmentUploadQueue.test.ts; apps/web/src/lib/chatThreadActions.test.ts; apps/web/src/lib/chatThreadActions.ts
77692eb1a5792b9930959b19805acf2bf2611318c9 fix(web): sync sidebar PR state from open panel (#9092) review-v2-path 3 1 2 apps/web/src/components/ChatView.tsx; apps/web/src/components/ThreadStatusIndicators.tsx apps/web/src/components/ThreadStatusIndicators.test.ts
78163d50846bc8989b5858802bb02253254dcee0dd "Revert ""fix(chat): reuse one row for live activity"" (#9096)" review-v2-path 4 0 4 apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx
7903542836834d008a097450cf24d0d9c6f965b859 feat(models): discover Claude models from remote manifest (#9084) review-v2-path 26 18 8 apps/server/src/provider/Drivers/ClaudeDriver.ts; apps/server/src/provider/Layers/ClaudeAdapter.test.ts; apps/server/src/provider/Layers/ClaudeAdapter.ts; apps/server/src/provider/Layers/ProviderRegistry.test.ts; packages/contracts/src/model.ts; packages/contracts/src/server.ts; packages/shared/src/model.test.ts; packages/shared/src/model.ts apps/server/src/provider/ClaudeModelCatalog.test.ts; apps/server/src/provider/ClaudeModelCatalog.testFixtures.ts; apps/server/src/provider/ClaudeModelCatalog.ts; apps/server/src/provider/ClaudeModelManifest.ts; apps/server/src/provider/Layers/ClaudeProvider.ts; apps/server/src/provider/ModelManifest.test.ts; apps/server/src/provider/ModelManifest.ts; apps/server/src/provider/model-manifest.json; apps/server/src/textGeneration/ClaudeTextGeneration.test.ts; apps/server/src/textGeneration/ClaudeTextGeneration.ts; apps/web/src/components/chat/ModelPickerContent.tsx; apps/web/src/components/chat/composerProviderState.test.tsx; apps/web/src/components/chat/modelPickerModelHighlights.ts; apps/web/src/components/chat/providerIconUtils.ts; apps/web/src/modelSelection.ts; apps/web/src/providerModels.test.ts; apps/web/src/providerModels.ts; docs/internals/model-manifest.md
80a924fbe08e681ef56d8cfb8b99e1e803d52aa463 fix(chat): reuse one row for live activity (#9062) review-v2-path 4 0 4 apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx
81cb007469161ff0db2bc2dc8123c4b30e186aae50 feat(web): open project settings from thread menus (#8925) review-v2-path 6 3 3 apps/web/src/components/LegacySidebar.tsx; apps/web/src/components/Sidebar.tsx; apps/web/src/hooks/useThreadActionMenu.ts apps/web/src/components/threadActionMenu.logic.test.ts; apps/web/src/components/threadActionMenu.logic.ts; apps/web/src/contextMenuFallback.ts
829d1879b142a2f5d01383357646a4679d1a2bd202 feat(desktop): add configurable quit shortcut confirmation (#9076) review-v2-path 11 5 6 apps/desktop/src/settings/DesktopClientSettings.test.ts; apps/web/src/components/settings/SettingsPanels.tsx; apps/web/src/components/settings/settingsSearch.ts; packages/contracts/src/ipc.ts; packages/contracts/src/settings.test.ts; packages/contracts/src/settings.ts apps/desktop/src/preload.ts; apps/desktop/src/window/DesktopWindow.ts; apps/desktop/src/window/QuitHold.test.ts; apps/desktop/src/window/QuitHold.ts; apps/web/src/components/QuitHoldOverlay.tsx
83ef7014d851f56bb037a9da963095ffd883c7fa08 fix(preview): restore recording and macOS rendering after Electron 43 (#9001) review-v2-path 11 10 1 packages/contracts/src/ipc.ts apps/desktop/src/ipc/methods/preview.ts; apps/desktop/src/preview/Manager.test.ts; apps/desktop/src/preview/Manager.ts; apps/web/src/browser/HostedBrowserWebview.tsx; apps/web/src/browser/browserRecording.test.ts; apps/web/src/browser/browserRecording.ts; apps/web/src/browser/hostedBrowserWebviewStyle.test.ts; apps/web/src/browser/hostedBrowserWebviewStyle.ts; apps/web/src/components/preview/PreviewView.test.tsx; apps/web/src/components/preview/PreviewView.tsx
84c17d02cff98f2e7b590d4c3a5775d1faa47c2e7b feat(claude): add Claude Fable 5.1 model (#9078) review-v2-path 7 3 4 apps/server/src/provider/Layers/ClaudeAdapter.test.ts; apps/server/src/provider/Layers/ProviderRegistry.test.ts; packages/contracts/src/model.ts; packages/shared/src/model.test.ts apps/server/src/provider/Layers/ClaudeProvider.ts; apps/server/src/provider/ModelManifest.test.ts; apps/server/src/provider/model-manifest.json
85643b21edaa08eb1d19d633dbb0b275164bfa60be fix(server): cache project favicon resolution (#9080) identical-final-files 2 2 0 apps/server/src/project/ProjectFaviconResolver.test.ts; apps/server/src/project/ProjectFaviconResolver.ts
86261380f91f764673c7fcefcb6a3f775526dbb776 fix(mobile): keep thread scroll bounds current after animations (#9013) review-v2-path 7 4 3 apps/mobile/src/features/threads/ThreadDetailScreen.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx; pnpm-lock.yaml apps/mobile/src/features/threads/thread-feed-live-follow.test.ts; apps/mobile/src/features/threads/thread-feed-live-follow.ts; apps/mobile/src/features/threads/thread-work-log.tsx; patches/@legendapp__list@3.3.5.patch
872d156a83b96ebf2e4a9c6017251baad357ae6ab1 feat(shortcuts): copy active thread reference (#8994) review-v2-path 20 12 8 apps/mobile/src/features/keyboard/hardwareKeyboardCommands.ts; apps/mobile/src/state/use-thread-selection.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/CommandPalette.tsx; apps/web/src/keybindings.test.ts; packages/contracts/src/keybindings.test.ts; packages/contracts/src/keybindings.ts; packages/shared/package.json apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3KeyboardCommandsModule.kt; apps/mobile/modules/t3-native-controls/expo-module.config.json; apps/mobile/modules/t3-native-controls/ios/T3KeyboardCommandsModule.swift; apps/mobile/src/features/keyboard/HardwareKeyboardCommandProvider.tsx; apps/mobile/src/lib/copyTextWithHaptic.test.ts; apps/mobile/src/lib/copyTextWithHaptic.ts; apps/mobile/src/native/T3KeyboardCommands.android.tsx; apps/server/src/keybindings.test.ts; docs/user/keybindings.md; packages/shared/src/keybindings.ts; packages/shared/src/threadReference.test.ts; packages/shared/src/threadReference.ts
88b5b6abb11e61ad2c9946b8d1346653c6c43d2261 fix(web): block type-to-focus behind open dialogs (#8139) review-v2-path 1 0 1 apps/web/src/components/ChatView.tsx
899dbdcece5f488c66f6b9ac516b610f45bbbb676a fix(web): align un-settle banner action (#9033) identical-final-files 1 1 0 apps/web/src/components/chat/ComposerBannerStack.tsx
90b883fc066ea5c9bebbe1c3e9b4bc2471aab3685f perf(client-runtime): halve server config bootstrap traffic (#8367) review-v2-path 20 11 9 apps/web/src/connection/runtime.ts; packages/client-runtime/src/connection/registry.test.ts; packages/client-runtime/src/operations/commands.test.ts; packages/client-runtime/src/rpc/client.ts; packages/client-runtime/src/state/server.ts; packages/client-runtime/src/state/shell-sync.test.ts; packages/client-runtime/src/state/threads-pagination.test.ts; packages/client-runtime/src/state/threads-sync.test.ts; packages/client-runtime/src/state/vcsAction.test.ts apps/web/src/cloud/linkEnvironment.test.ts; packages/client-runtime/src/connection/layer.ts; packages/client-runtime/src/connection/supervisor.test.ts; packages/client-runtime/src/rpc/client.test.ts; packages/client-runtime/src/rpc/session.test.ts; packages/client-runtime/src/rpc/session.ts; packages/client-runtime/src/state/pullRequests.test.ts; packages/client-runtime/src/state/server.test.ts; packages/client-runtime/src/state/serverConfigProjection.ts; packages/client-runtime/src/state/sourceControl.test.ts; packages/client-runtime/src/state/vcs.test.ts
91e86604d3372acccd9f6a33a2c4ae46f4e2685541 perf(server): skip full-message reads while streaming (#9032) review-v2-path 5 2 3 apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts; apps/server/src/orchestration/Layers/ProjectionPipeline.ts; apps/server/src/persistence/Services/ProjectionThreadMessages.ts apps/server/src/persistence/Layers/ProjectionThreadMessages.test.ts; apps/server/src/persistence/Layers/ProjectionThreadMessages.ts
923c73fa7ce02b7ee6b2904ec58e47c06262aebf5d perf(web): defer pull request line stats until visible (#6471) review-v2-path 5 4 1 apps/web/src/routes/_chat.pull-requests.tsx apps/web/src/components/pullRequest/PullRequestRow.tsx; apps/web/src/components/pullRequest/pullRequestList.logic.test.ts; apps/web/src/components/pullRequest/pullRequestList.logic.ts; apps/web/src/state/pullRequests.ts
9362d39bf00d5ddd83a9b36a81321fe9aa4d2502bb fix(server): stop OpenCode child sessions (#9005) review-v2-path 3 1 2 apps/server/src/provider/Layers/OpenCodeAdapter.test.ts; apps/server/src/provider/Layers/OpenCodeAdapter.ts docs/user/providers-opencode.md
948b033de48247086c1b6c6968ce7cf33b358a1ec5 fix(clients): dedupe skills in composer menus (#8043) identical-final-files 5 5 0 apps/mobile/src/features/threads/use-composer-command-menu.ts; apps/web/src/providerSkillSearch.test.ts; apps/web/src/providerSkillSearch.ts; packages/client-runtime/src/providerSkills.test.ts; packages/client-runtime/src/providerSkills.ts
95f32f9a2f41342bf8a1a109d6ebe9b70044c5311b fix(server): settle threads server-side (#8600) review-v2-path 55 17 38 apps/desktop/src/settings/DesktopClientSettings.test.ts; apps/mobile/src/features/home/HomeScreen.tsx; apps/mobile/src/features/home/useThreadListActions.ts; apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx; apps/mobile/src/features/threads/thread-list-v2-items.tsx; apps/mobile/src/features/threads/threadListV2.test.ts; apps/mobile/src/features/threads/threadListV2.ts; apps/server/integration/OrchestrationEngineHarness.integration.ts; apps/server/src/environment/ServerEnvironment.ts; apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts; apps/server/src/orchestration/Layers/OrchestrationEngine.ts; apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts; apps/server/src/orchestration/Layers/OrchestrationReactor.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.ts; apps/server/src/orchestration/ThreadSettlementReactor.test.ts; apps/server/src/orchestration/ThreadSettlementReactor.ts; apps/server/src/orchestration/decider.ts; apps/server/src/persistence/Layers/OrchestrationEventStore.ts; apps/server/src/persistence/Services/OrchestrationEventStore.ts; apps/server/src/server.test.ts; apps/server/src/server.ts; apps/server/src/ws.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/Sidebar.logic.ts; apps/web/src/components/Sidebar.tsx; apps/web/src/components/chat/ChatHeader.tsx; apps/web/src/components/settings/SettingsPanels.tsx; apps/web/src/components/settings/settingsSearch.test.ts; apps/web/src/components/settings/settingsSearch.ts; apps/web/src/hooks/useThreadActionMenu.ts; apps/web/src/hooks/useThreadActions.ts; docs/internals/overview.md; packages/client-runtime/src/state/threadSettled.ts; packages/contracts/src/environment.ts; packages/contracts/src/orchestration.ts; packages/contracts/src/settings.test.ts; packages/contracts/src/settings.ts apps/mobile/src/features/settings/SettingsRouteScreen.tsx; apps/mobile/src/persistence/mobile-preferences.ts; apps/server/src/git/GitManager.test.ts; apps/server/src/git/GitManager.ts; apps/server/src/orchestration/Errors.ts; apps/server/src/orchestration/ThreadSettlementPolicy.test.ts; apps/server/src/orchestration/ThreadSettlementPolicy.ts; apps/server/src/orchestration/decider.settled.test.ts; apps/server/src/serverSettings.test.ts; apps/web/src/components/ThreadStatusIndicators.test.ts; apps/web/src/components/settings/useAvailableSettingsSearchItems.ts; apps/web/src/hooks/useNowMinute.ts; apps/web/src/hooks/useSettings.test.ts; apps/web/src/hooks/useSettings.ts; docs/user/thread-sidebar.md; packages/client-runtime/src/state/threadSettled.test.ts; packages/client-runtime/src/state/threadSnoozed.test.ts
967e4ce3bbb16c3cfa1ea756de917d8f9398e5999e perf(server): cut chatty tool-update frames by 90% (#8368) review-v2-path 5 0 5 apps/server/src/orchestration/ActivityPayloadProjection.ts; apps/server/src/orchestration/ThreadLiveEventCoalescer.test.ts; apps/server/src/orchestration/ThreadLiveEventCoalescer.ts; apps/server/src/server.test.ts; apps/server/src/ws.ts
978f1ef8b9eb72ac6cd8510364608e01dc7febf9c1 perf(server): scan only appended transcript bytes for usage summaries (#9024) identical-final-files 6 6 0 apps/server/src/usage/UsageService.test.ts; apps/server/src/usage/UsageService.ts; apps/server/src/usage/usageScanCache.test.ts; apps/server/src/usage/usageScanCache.ts; apps/server/src/usage/usageTranscriptReader.test.ts; apps/server/src/usage/usageTranscriptReader.ts
980bfb6df34b26dfe0162db6c09dca00bc8c5a5ec4 perf(server): cut idle CPU use and stop provider event leaks (#8187) review-v2-path 19 11 8 apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts; apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts; apps/server/src/provider/Layers/OpenCodeAdapter.test.ts; apps/server/src/provider/Layers/OpenCodeAdapter.ts; apps/server/src/provider/acp/AcpNativeLogging.ts; apps/server/src/vcs/GitVcsDriverCore.ts; packages/effect-acp/src/protocol.test.ts; packages/effect-acp/src/protocol.ts apps/server/src/project/RepositoryIdentityResolver.test.ts; apps/server/src/project/RepositoryIdentityResolver.ts; apps/server/src/provider/Layers/EventNdjsonLogger.test.ts; apps/server/src/provider/Layers/EventNdjsonLogger.ts; apps/server/src/provider/acp/AcpNativeLogging.test.ts; apps/server/src/resourceTelemetry/NativeTelemetryClient.test.ts; apps/server/src/resourceTelemetry/NativeTelemetryClient.ts; docs/internals/resource-telemetry.md; native/resource-monitor/src/main.rs; packages/effect-codex-app-server/src/protocol.test.ts; packages/effect-codex-app-server/src/protocol.ts
99a9ffb8279614df6ae2f1f4b7f09a0dc42edef797 perf(server): bound snapshot activity payload memory (#9000) review-v2-path 5 0 5 apps/server/src/orchestration/ActivityPayloadProjection.test.ts; apps/server/src/orchestration/ActivityPayloadProjection.ts; apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts; apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts
10073776d4e52087331245e82ebf3c053ae0991935d test: remove static presentation snapshots (#9008) identical-final-files 2 2 0 apps/mobile/src/lib/typography.test.ts; apps/web/src/components/ui/menu.test.tsx
1010947c30e6946b2ad6d6cd518fd44292e75e834e8 fix(client): use package import for markdown image helpers (#9010) review-v2-path 1 0 1 packages/client-runtime/src/work-log/presentation.ts
102ce71c04f0aa9d2e5cd340e2a04cb1b0d5e24419d feat(client): render viewed images in work logs (#8936) review-v2-path 7 2 5 apps/mobile/src/features/threads/ThreadFeed.tsx; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.tsx; packages/client-runtime/src/work-log/presentation.test.ts; packages/client-runtime/src/work-log/presentation.ts apps/mobile/src/features/threads/thread-work-log.tsx; apps/web/src/components/ChatMarkdown.tsx
103ff93aba61dc1f7839713580bc8ee08da2a395f9a feat(web): search individual settings by detail (#8831) review-v2-path 19 12 7 apps/web/src/components/CommandPalette.logic.test.ts; apps/web/src/components/CommandPalette.logic.ts; apps/web/src/components/CommandPalette.tsx; apps/web/src/components/settings/SettingsPanels.tsx; apps/web/src/components/settings/SettingsSidebarNav.tsx; apps/web/src/components/settings/settingsSearch.test.ts; apps/web/src/components/settings/settingsSearch.ts apps/web/src/components/settings/ConnectionsSettings.tsx; apps/web/src/components/settings/ProviderSettingsPanel.environment.test.tsx; apps/web/src/components/settings/ProviderSettingsPanel.logic.test.ts; apps/web/src/components/settings/ProviderSettingsPanel.logic.ts; apps/web/src/components/settings/ProviderSettingsPanel.tsx; apps/web/src/components/settings/SourceControlSettings.tsx; apps/web/src/components/settings/SourceControlWritingSettings.tsx; apps/web/src/components/settings/ThemeSettings.tsx; apps/web/src/components/settings/settingsLayout.tsx; apps/web/src/components/settings/useAvailableSettingsSearchItems.ts; apps/web/src/lib/utils.ts; docs/user/keybindings.md
104d35c71d1b975660ec6a71bbceb6aa7dfd0e8c3d5 feat(web): add pull request list filters (#8809) review-v2-path 7 6 1 apps/web/src/routes/_chat.pull-requests.tsx apps/web/src/components/pullRequest/PullRequestListFilters.test.tsx; apps/web/src/components/pullRequest/PullRequestListFilters.tsx; apps/web/src/components/pullRequest/PullRequestRow.tsx; apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx; apps/web/src/components/pullRequest/pullRequestList.logic.ts; docs/user/source-control.md
105b17cc3d1bf0f4a5deee6dd6a470e36970b864a9b perf(server): reduce frequency of full tool call output being loaded into memory from db (#8988) review-v2-path 10 1 9 apps/server/src/orchestration/Layers/CheckpointReactor.ts; apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts; apps/server/src/orchestration/Layers/ProjectionPipeline.ts; apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts; apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.ts; apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts; apps/server/src/persistence/Services/ProjectionThreadActivities.ts apps/server/src/persistence/Layers/ProjectionThreadActivities.ts
10642a8fd5103b4af57b16be721a9864ca3a5c18c5b feat(pull-requests): link GitHub references in markdown (#8812) identical-final-files 5 5 0 apps/web/src/components/ChatMarkdown.tsx; apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx; apps/web/src/components/pullRequest/PullRequestMarkdown.tsx; apps/web/src/components/pullRequest/pullRequestMarkdown.logic.ts; apps/web/src/vendor/mdast-find-and-replace.ts
107c78ae50a5a5fdf8f42d0aaa0103b26ee836f0cfc fix(server): isolate remote web session cookies (#8085) review-v2-path 18 13 5 apps/server/src/bin.test.ts; apps/server/src/environment/ServerEnvironment.test.ts; apps/server/src/environment/ServerEnvironment.ts; apps/server/src/server.test.ts; apps/server/src/server.ts apps/server/src/auth/EnvironmentAuth.test.ts; apps/server/src/auth/EnvironmentAuth.ts; apps/server/src/auth/EnvironmentAuthAdmin.test.ts; apps/server/src/auth/EnvironmentAuthPolicy.test.ts; apps/server/src/auth/EnvironmentAuthPolicy.ts; apps/server/src/auth/SessionStore.test.ts; apps/server/src/auth/SessionStore.ts; apps/server/src/auth/http.ts; apps/server/src/auth/utils.test.ts; apps/server/src/auth/utils.ts; apps/server/src/cli/connect.ts; apps/server/src/cli/pair.test.ts; docs/internals/remote.md
1089ecfc07a8b3acadd1612665b6e24425f625480de fix(chat): keep agent activity visible between actions (#8984) review-v2-path 4 0 4 apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx
1099bc7a56848eb7c7546605e54ff02c6abea176f96 feat(mobile): upload attachments while composing (#8978) review-v2-path 23 17 6 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/lib/projectThreadStartTurn.ts; apps/mobile/src/state/use-thread-composer-state.ts; apps/mobile/src/state/use-thread-outbox-drain.ts; docs/user/composer.md apps/mobile/src/Stack.tsx; apps/mobile/src/components/ComposerAttachmentStrip.tsx; apps/mobile/src/features/cloud/CloudAuthProvider.test.ts; apps/mobile/src/features/cloud/CloudAuthProvider.tsx; apps/mobile/src/features/cloud/cloud-drafts.ts; apps/mobile/src/features/threads/use-project-actions.ts; apps/mobile/src/lib/attachmentUpload.test.ts; apps/mobile/src/lib/attachmentUpload.ts; apps/mobile/src/lib/composer-image-schema.ts; apps/mobile/src/lib/composerAttachmentUploadQueue.test.ts; apps/mobile/src/lib/composerAttachmentUploadQueue.ts; apps/mobile/src/lib/composerImages.ts; apps/mobile/src/state/composer-attachment-uploads.ts; apps/mobile/src/state/use-composer-drafts.test.ts; apps/mobile/src/state/use-composer-drafts.ts; apps/mobile/src/state/use-thread-outbox-drain.test.ts; docs/internals/connection-runtime.md
11085b656ff300f71060ad6305c7e1e29a72b442ce9 style: format CodeRabbit configuration identical-final-files 1 1 0 .coderabbit.yaml
1110df043fd4eaa190eb491a3060836156eb0ae915e Add auto_review configuration to coderabbit.yaml identical-final-files 1 1 0 .coderabbit.yaml
11217f00f60248374aafa2efb9b54ff08ce52e60a0a feat(web): add expand/collapse all control to the files surface (#8889) identical-final-files 3 3 0 apps/web/src/components/files/FileBrowserPanel.tsx; apps/web/src/components/files/fileTreeExpansion.test.ts; apps/web/src/components/files/fileTreeExpansion.ts
113c50b0b4ef8d61ffe3de1cfa6249f906709f7da1f fix(web): make WSL settings searchable (#8881) review-v2-path 6 3 3 apps/web/src/components/settings/SettingsSidebarNav.tsx; apps/web/src/components/settings/settingsSearch.test.ts; apps/web/src/components/settings/settingsSearch.ts apps/web/src/components/settings/ConnectionsSettings.logic.test.ts; apps/web/src/components/settings/ConnectionsSettings.logic.ts; apps/web/src/components/settings/ConnectionsSettings.tsx
114929f7e6479d00754dee4e4554b24b25478b8064d fix(shared): preserve Windows shell PATH priority (#8748) identical-final-files 2 2 0 packages/shared/src/shell.test.ts; packages/shared/src/shell.ts
11541adccc83e819c286dcdf32cd8b5f55af8bb0b49 fix(server): allow long thread IDs in HTTP routes (#8898) review-v2-path 2 0 2 apps/server/src/server.test.ts; apps/server/src/server.ts
116f8e4accf27415eb9f7f4106087d2a1b0186d66b9 feat(mobile): add native image and PDF previews (#8959) review-v2-path 26 22 4 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx; docs/user/composer.md apps/mobile/app.config.ts; apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift; apps/mobile/modules/t3-native-controls/ios/T3NativeFilePresentation.swift; apps/mobile/src/components/ComposerAttachmentStrip.tsx; apps/mobile/src/components/FilePreview.ios.tsx; apps/mobile/src/components/FilePreview.tsx; apps/mobile/src/components/FilePreviewModal.tsx; apps/mobile/src/components/VideoPreviewModal.ios.tsx; apps/mobile/src/components/VideoPreviewModal.tsx; apps/mobile/src/components/VideoThumbnailImage.tsx; apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx; apps/mobile/src/features/files/WorkspaceFileImagePreview.tsx; apps/mobile/src/features/review/ReviewCommentComposerSheet.tsx; apps/mobile/src/lib/composerFiles.test.ts; apps/mobile/src/lib/composerImages.ts; apps/mobile/src/lib/filePreview.test.ts; apps/mobile/src/lib/filePreview.ts; apps/mobile/src/lib/localAttachmentPreview.test.ts; apps/mobile/src/lib/localAttachmentPreview.ts; apps/mobile/src/lib/localVideoPreview.test.ts; apps/mobile/src/lib/localVideoPreview.ts; docs/internals/mobile-navigation.md
1176d15c5bbc3f3c331a7466bb94c17413a35f7b659 fix(server): preserve usage cache outside walked roots (#8540) identical-final-files 2 2 0 apps/server/src/usage/usageScanCache.test.ts; apps/server/src/usage/usageScanCache.ts
118f47e74004af232f0e3df8dc10093601d1c2c3ea3 fix(web): prevent chat metadata overlap (#8851) review-v2-path 3 0 3 apps/web/src/components/BranchToolbar.tsx; apps/web/src/components/BranchToolbarEnvModeSelector.tsx; apps/web/src/components/chat/ChatHeader.tsx
11931c1c5996f88e3acf1566adc11c9b51ac7561554 feat(mobile): add video playback with native iOS controls (#8919) review-v2-path 37 29 8 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx; apps/server/src/http.test.ts; apps/web/src/types.ts; docs/user/composer.md; packages/shared/package.json; pnpm-lock.yaml apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift; apps/mobile/modules/t3-native-controls/ios/T3NativePresentation.swift; apps/mobile/modules/t3-native-controls/ios/T3NativeVideoPresentation.swift; apps/mobile/package.json; apps/mobile/src/components/ComposerAttachmentStrip.tsx; apps/mobile/src/components/NativePresentation.ios.tsx; apps/mobile/src/components/NativePresentation.tsx; apps/mobile/src/components/VideoAttachmentMenu.tsx; apps/mobile/src/components/VideoAttachmentTile.tsx; apps/mobile/src/components/VideoPreviewModal.ios.tsx; apps/mobile/src/components/VideoPreviewModal.tsx; apps/mobile/src/components/VideoThumbnailImage.tsx; apps/mobile/src/lib/attachmentDownload.test.ts; apps/mobile/src/lib/attachmentDownload.ts; apps/mobile/src/lib/composerAttachmentFiles.ts; apps/mobile/src/lib/composerFiles.test.ts; apps/mobile/src/lib/composerImages.ts; apps/mobile/src/lib/localVideoPreview.test.ts; apps/mobile/src/lib/localVideoPreview.ts; apps/mobile/src/lib/shareFileFromSource.ios.ts; apps/mobile/src/lib/shareFileFromSource.ts; apps/mobile/src/lib/videoThumbnails.test.ts; apps/mobile/src/lib/videoThumbnails.ts; apps/mobile/src/state/use-composer-drafts.test.ts; apps/mobile/src/state/use-composer-drafts.ts; apps/server/src/http.ts; docs/internals/mobile-navigation.md; packages/shared/src/video.test.ts; packages/shared/src/video.ts
120ef84bc9873a6c4565fbeb64dce3f552570e95a2d fix(chat): smooth worktree setup status (#8922) review-v2-path 16 2 14 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/lib/threadActivity.test.ts; apps/mobile/src/lib/threadActivity.ts; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/routes/_chat.draft.$draftId.tsx; apps/web/src/session-logic.test.ts; apps/web/src/session-logic.ts; packages/client-runtime/src/work-log/presentation.ts apps/mobile/src/features/threads/thread-work-log.tsx; apps/web/src/components/chat/timelineScrollAnchoring.ts
1214a9d2d0ced2a2b899dbee9e4a5162fd83f81edb8 chore(deps): bump Electron to 43.4.1 (#8626) review-v2-path 4 3 1 pnpm-lock.yaml apps/desktop/package.json; apps/desktop/src/preview/BrowserSession.test.ts; apps/desktop/src/preview/BrowserSession.ts
1225ce92c2f192040bf77c0211fa33bf03c74c031ef fix(mobile): shimmer active tool rows (#8932) review-v2-path 3 1 2 apps/mobile/src/lib/threadActivity.test.ts; apps/mobile/src/lib/threadActivity.ts apps/mobile/src/features/threads/thread-work-log.tsx
12335da5813315dc4e0c20602ed1918ceccc916b7eb fix(web): show scrollbar for wide markdown tables (#8868) identical-final-files 1 1 0 apps/web/src/components/ChatMarkdown.tsx
124038bf3739b871a56b2defdb09605344801448ced Delete app.json (#8934) identical-final-files 1 1 0 app.json
1254e8e64fc065a4a72535eee5fe60b689f5b48d35c chore: disable CodeRabbit review status (#8933) identical-final-files 1 1 0 .coderabbit.yaml
126746c932e164d49ddb4ac98a42176194b11bf0699 fix(mobile): defer draft navigation until submission completes (#8914) review-v2-path 1 0 1 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
127bba79cc254b65969bde6b6bfc3032c3b5b9316ae fix(web): hide invalid slash skill completions (#8904) review-v2-path 3 2 1 apps/web/src/components/chat/ChatComposer.tsx apps/web/src/components/chat/composerSlashCommandSearch.test.ts; apps/web/src/components/chat/composerSlashCommandSearch.ts
1282921050c698fb195e2f5590b7cfab8fdfe0ec729 fix(contracts): accept CLI event origins (#8905) review-v2-path 2 0 2 apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts; packages/contracts/src/baseSchemas.ts
129ad38700ac678b8c8a0310d434a44d94a7ee6a47f chore(macroscope): review diagnostic overrides (#8917) identical-final-files 2 2 0 .macroscope/approvability.md; .macroscope/check-run-agents/effect-service-conventions.md
130f86c5e8c8700b76250ed1073700a5b9db47e2d57 fix(server): skip IDE detection in Claude probes (#8634) identical-final-files 2 2 0 apps/server/src/provider/Layers/ClaudeCapabilitiesProbe.test.ts; apps/server/src/provider/Layers/ClaudeProvider.ts
1319b2d04317c68233782e0630464ac86d77d0686f3 fix(mobile): replace Callstack glass with Expo glass (#8862) review-v2-path 5 4 1 pnpm-lock.yaml apps/mobile/package.json; apps/mobile/src/features/threads/GitActionProgressOverlay.tsx; apps/mobile/src/features/threads/floating-working-control.tsx; docs/user/mobile-appearance.md
132e9c4775e8738381140af4e5248507b15fc1cd491 fix(web): mark pull request links as external (#8856) identical-final-files 1 1 0 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
1333958111057c10c10350dd9c20ec2a2df00f504be fix(preview): improve browser recording quality (#8839) review-v2-path 15 8 7 apps/desktop/src/settings/DesktopClientSettings.test.ts; apps/web/src/components/settings/SettingsPanels.tsx; apps/web/src/components/settings/settingsSearch.test.ts; apps/web/src/components/settings/settingsSearch.ts; packages/contracts/src/ipc.ts; packages/contracts/src/settings.test.ts; packages/contracts/src/settings.ts apps/desktop/src/ipc/methods/preview.ts; apps/desktop/src/preview/Manager.test.ts; apps/desktop/src/preview/Manager.ts; apps/web/src/browser/browserRecording.test.ts; apps/web/src/browser/browserRecording.ts; apps/web/src/components/settings/IntegrationsSettings.tsx; apps/web/src/components/settings/SettingsPanels.logic.test.ts; apps/web/src/components/settings/SettingsPanels.logic.ts
1343f62e6fa65c2a2a91a367be4ee95da1b50007bde fix(web): widen sync banners and simplify the working timer (#8855) review-v2-path 4 0 4 apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; docs/user/composer.md
1359842518c9a0af14fb3a3e90993692a6e34032682 fix(web): address composer banner review follow-ups (#8850) review-v2-path 10 6 4 apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx; docs/user/composer.md apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/chat/ComposerBannerStack.tsx; apps/web/src/components/chat/ComposerServerUpdateStatus.tsx; apps/web/src/components/chat/ComposerStashMenu.tsx; apps/web/src/components/chat/ComposerSurface.tsx; apps/web/src/components/chat/ComposerTasksBadge.tsx
13630175a8af04c0daa359652b5e8dc8230b40b462a fix(web): restore unified activity logs and composer banners (#8734) review-v2-path 33 17 16 apps/web/src/components/BranchToolbar.tsx; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx; apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx; apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/index.css; apps/web/src/session-logic.test.ts; apps/web/src/session-logic.ts; apps/web/src/versionSkew.test.ts; apps/web/src/versionSkew.ts apps/web/src/components/chat/ComposerActivityStatus.tsx; apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/chat/ComposerBannerStack.test.tsx; apps/web/src/components/chat/ComposerBannerStack.tsx; apps/web/src/components/chat/ComposerCommandMenu.test.tsx; apps/web/src/components/chat/ComposerCommandMenu.tsx; apps/web/src/components/chat/ComposerPlanFollowUpBanner.tsx; apps/web/src/components/chat/ComposerServerUpdateStatus.tsx; apps/web/src/components/chat/ComposerStashBadge.test.tsx; apps/web/src/components/chat/ComposerStashBadge.tsx; apps/web/src/components/chat/ComposerStashMenu.test.tsx; apps/web/src/components/chat/ComposerStashMenu.tsx; apps/web/src/components/chat/ComposerSurface.tsx; apps/web/src/components/chat/ComposerTasksBadge.test.tsx; apps/web/src/components/chat/ComposerTasksBadge.tsx; apps/web/src/components/chat/ThreadSyncStatusPill.test.tsx; apps/web/src/components/chat/ThreadSyncStatusPill.tsx
137f9137a0c89f6190d27813bb46dbb6d1bb796f080 fix(mobile): map native menu icon colors explicitly identical-final-files 1 1 0 apps/mobile/src/components/ControlPill.tsx
138e3dcc1615c9167048ecfe536f87ad1d353fa8a49 Add mobile composer attachment menu with video support (#8843) review-v2-path 19 13 6 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadDetailScreen.tsx; apps/mobile/src/features/threads/ThreadRouteScreen.tsx; apps/mobile/src/state/use-thread-composer-state.ts; docs/user/composer.md apps/mobile/app.config.ts; apps/mobile/src/components/AppSymbol.tsx; apps/mobile/src/components/ComposerAttachmentButton.tsx; apps/mobile/src/components/ComposerToolbar.tsx; apps/mobile/src/components/ControlPill.tsx; apps/mobile/src/features/sharing/incoming-share-model.test.ts; apps/mobile/src/features/voice-input/ComposerDictationControl.tsx; apps/mobile/src/lib/composerFiles.test.ts; apps/mobile/src/lib/composerImages.ts; apps/mobile/src/lib/menu-action-colors.test.ts; apps/mobile/src/lib/menu-action-colors.ts; apps/mobile/src/state/use-composer-drafts.test.ts; docs/internals/mobile-navigation.md
1397963ac7404ff2196c3e8e4198ecc02a5e742b0a1 chore(release): prepare v0.0.37 review-v2-path 4 2 2 apps/server/package.json; packages/contracts/package.json apps/desktop/package.json; apps/web/package.json
140cefec32d6fc5d14f03e110ebdde534bdbcc9b62b fix(web): prevent pull request metadata overlap (#8790) review-v2-path 3 2 1 apps/web/src/routes/_chat.pull-requests.tsx apps/web/src/components/pullRequest/PullRequestRow.tsx; apps/web/src/components/pullRequest/pullRequestPresentation.tsx
141352710d497cc640553e3e18e23fb5a5f3f890466 feat(mobile): add offline iPhone voice input (#8614) review-v2-path 36 29 7 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadDetailScreen.tsx; docs/README.md; docs/user/composer.md; packages/client-runtime/package.json; pnpm-lock.yaml apps/mobile/app.config.ts; apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorModule.swift; apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift; apps/mobile/package.json; apps/mobile/src/components/GlassSurface.tsx; apps/mobile/src/features/threads/use-composer-command-menu.test.ts; apps/mobile/src/features/threads/use-composer-command-menu.ts; apps/mobile/src/features/voice-input/ComposerDictationControl.tsx; apps/mobile/src/features/voice-input/useVoiceInputController.ts; apps/mobile/src/features/voice-input/voiceInputMetering.test.ts; apps/mobile/src/features/voice-input/voiceInputMetering.ts; apps/mobile/src/features/voice-input/voiceInputPresentation.test.ts; apps/mobile/src/features/voice-input/voiceInputPresentation.ts; apps/mobile/src/native/T3ComposerEditor.ios.tsx; apps/mobile/src/native/T3ComposerEditor.native.tsx; apps/mobile/src/native/T3ComposerEditor.tsx; apps/mobile/src/native/T3ComposerEditor.types.ts; apps/mobile/src/native/voiceTranscription.ios.test.ts; apps/mobile/src/native/voiceTranscription.ios.ts; apps/mobile/src/native/voiceTranscription.ts; docs/internals/voice-input.md; packages/client-runtime/README.md; packages/client-runtime/src/voice-input/controller.test.ts; packages/client-runtime/src/voice-input/controller.ts; packages/client-runtime/src/voice-input/index.ts; packages/client-runtime/src/voice-input/transcription.ts; patches/@react-native-ai__apple@0.12.0.patch; patches/expo-audio@57.0.4.patch; pnpm-workspace.yaml
1428b817cbcaad71a53e2ef73f3881067f8aa8094bc fix(web): use circle alert for failed tool calls (#8840) review-v2-path 2 0 2 apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx
14317c48f7fc1af66110706b013bf41901ebcd8aedb fix(web): fold interim turn responses (#8828) review-v2-path 2 0 2 apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts
144e4f7b14fab0850bff134a3f6bfaf5b71cc8ac9fc chore: add Windows setup script to t3.json (#8814) identical-final-files 1 1 0 t3.json
145c1e70b5f8c93428c1fe7af62f24ee6ff2bffff6e fix(web,mobile): render Codex citations and artifact templates (#8584) review-v2-path 19 9 10 apps/mobile/src/features/threads/ThreadDetailScreen.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.tsx; packages/client-runtime/package.json; pnpm-lock.yaml apps/web/src/components/ChatMarkdown.test.tsx; apps/web/src/components/ChatMarkdown.tsx; apps/web/src/markdown-clipboard.test.ts; packages/client-runtime/src/codexArtifactTemplates.test.ts; packages/client-runtime/src/codexArtifactTemplates.ts; packages/client-runtime/src/codexFileCitations.test.ts; packages/client-runtime/src/codexFileCitations.ts; packages/client-runtime/src/codexMarkdownDirectives.test.ts; packages/client-runtime/src/codexMarkdownDirectives.ts
146e09b88b6a51bb7e4309498f01c2f36bf5ada43f4 fix(web): keep right panel synced with agent edits (#8803) review-v2-path 7 4 3 apps/web/src/components/ChatView.tsx; apps/web/src/components/DiffPanel.tsx; apps/web/src/components/files/FilePreviewPanel.tsx apps/web/src/components/files/FileBrowserPanel.tsx; apps/web/src/components/files/projectFilesQueryState.test.tsx; apps/web/src/hooks/useWorkspaceMutationRefresh.test.ts; apps/web/src/hooks/useWorkspaceMutationRefresh.ts
1475885a68adb61805460047512040838bbb5e7ed65 fix(web): keep image preview above sidebar control (#8811) identical-final-files 1 1 0 apps/web/src/components/chat/ExpandedImageDialog.tsx
1489072aa1fd711170af425bf6a1a2828d0d54bbfa6 fix(server): stop overpricing cached Claude tokens (#8806) identical-final-files 4 4 0 apps/server/src/usage/usagePricing.test.ts; apps/server/src/usage/usagePricing.ts; apps/server/src/usage/usageScanCache.test.ts; apps/server/src/usage/usageScanCache.ts
14960f2ce0279d524bd70a573f6e0b6e9fab56e4b3e fix(git): follow repository instructions in generated source control text (#8804) identical-final-files 4 4 0 apps/server/src/git/GitManager.test.ts; apps/server/src/git/GitManager.ts; apps/server/src/textGeneration/TextGenerationPrompts.ts; docs/user/source-control.md
1508f525af5afa93dff58775030d9b503d3bda36b7b fix(web): open agent images in expanded preview (#8807) review-v2-path 2 1 1 apps/web/src/components/chat/MessagesTimeline.tsx apps/web/src/components/ChatMarkdown.tsx
15112fe2d6d03062c5d7fc3beff168f8c4af1235b50 fix(windows): strip quotes from repaired PATH (#8746) identical-final-files 4 4 0 apps/desktop/src/shell/DesktopShellEnvironment.test.ts; apps/desktop/src/shell/DesktopShellEnvironment.ts; packages/shared/src/shell.test.ts; packages/shared/src/shell.ts
1526e324b9bbfc72e694c3c16abeb59716c37fc02d5 fix(web): reduce title bar scroll fade height (#8799) review-v2-path 5 1 4 apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/index.css; apps/web/src/routes/_chat.pull-requests.tsx apps/web/src/components/settings/settingsLayout.tsx
15386c9a9288b5704e79bbe68fb0834d56036456f3e feat(mobile): pick, share, and receive files in threads (#8237) review-v2-path 47 37 10 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx; apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadDetailScreen.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx; apps/mobile/src/features/threads/ThreadRouteScreen.tsx; apps/mobile/src/lib/projectThreadStartTurn.ts; apps/mobile/src/state/use-thread-composer-state.ts; apps/mobile/src/state/use-thread-outbox-drain.ts; docs/user/composer.md; pnpm-lock.yaml apps/mobile/app.config.ts; apps/mobile/src/components/ComposerAttachmentStrip.tsx; apps/mobile/src/connection/platform.ts; apps/mobile/src/features/home/usePendingTaskListActions.ts; apps/mobile/src/features/sharing/IncomingShareProvider.tsx; apps/mobile/src/features/sharing/incoming-share-inbox.test.ts; apps/mobile/src/features/sharing/incoming-share-inbox.ts; apps/mobile/src/features/sharing/incoming-share-model.test.ts; apps/mobile/src/features/sharing/incoming-share-model.ts; apps/mobile/src/features/sharing/incoming-share-storage.test.ts; apps/mobile/src/features/sharing/incoming-share-storage.ts; apps/mobile/src/features/threads/NewTaskRouteScreen.tsx; apps/mobile/src/features/threads/new-task-flow-provider.tsx; apps/mobile/src/features/threads/use-project-actions.ts; apps/mobile/src/lib/attachmentDownload.test.ts; apps/mobile/src/lib/attachmentDownload.ts; apps/mobile/src/lib/attachmentUpload.test.ts; apps/mobile/src/lib/attachmentUpload.ts; apps/mobile/src/lib/composer-image-schema.ts; apps/mobile/src/lib/composerAttachmentFiles.test.ts; apps/mobile/src/lib/composerAttachmentFiles.ts; apps/mobile/src/lib/composerFiles.test.ts; apps/mobile/src/lib/composerImages.ts; apps/mobile/src/state/attachments.ts; apps/mobile/src/state/pending-task-editor-writes.test.ts; apps/mobile/src/state/pending-task-editor-writes.ts; apps/mobile/src/state/thread-outbox-manager.ts; apps/mobile/src/state/thread-outbox-model.ts; apps/mobile/src/state/thread-outbox-removal.test.ts; apps/mobile/src/state/thread-outbox-removal.ts; apps/mobile/src/state/thread-outbox.test.ts; apps/mobile/src/state/thread-outbox.ts; apps/mobile/src/state/use-composer-drafts.test.ts; apps/mobile/src/state/use-composer-drafts.ts; apps/mobile/src/state/use-thread-outbox-drain.test.ts; patches/expo-sharing@57.0.16.patch; pnpm-workspace.yaml
1547880a6e583f8a04db2eaeef2366ce9c81c3f7bec fix(grok): allow model changes in existing threads (#8392) review-v2-path 2 1 1 apps/server/src/provider/Layers/GrokProvider.ts apps/server/src/provider/Layers/GrokProvider.test.ts
1557980dfddb193864e07a5bcc013b0ccb93fb4300c fix(web,mobile): snooze menu no longer offers the same wake time twice (#8741) review-v2-path 2 1 1 packages/client-runtime/src/state/threadSettled.ts packages/client-runtime/src/state/threadSnoozed.test.ts
156ac4aae101d060b5355a5de58e23c7387886d9021 feat(web): play video attachments in chat (#8688) review-v2-path 23 13 10 apps/server/src/http.test.ts; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/composerDraftStore.ts; apps/web/src/types.ts; docs/user/composer.md apps/desktop/src/electron/ElectronProtocol.test.ts; apps/desktop/src/electron/ElectronProtocol.ts; apps/server/src/assets/AssetAccess.test.ts; apps/server/src/assets/AssetAccess.ts; apps/server/src/http.ts; apps/web/src/components/chat/ExpandedImageDialog.test.tsx; apps/web/src/components/chat/ExpandedImageDialog.tsx; apps/web/src/components/chat/ExpandedImagePreview.test.ts; apps/web/src/components/chat/ExpandedImagePreview.tsx; apps/web/src/components/chat/composerAttachmentFiles.test.ts; apps/web/src/components/chat/composerAttachmentFiles.ts; apps/web/src/components/pullRequest/PullRequestMarkdown.tsx; apps/web/src/composerDraftStore.test.ts
157f15680bd3c08a154bacb9b4d163cc8608a310970 feat(mobile): update tool summaries and chat transitions (#8793) review-v2-path 14 5 9 apps/mobile/src/features/threads/ThreadComposer.tsx; apps/mobile/src/features/threads/ThreadDetailScreen.tsx; apps/mobile/src/features/threads/ThreadFeed.tsx; apps/mobile/src/lib/threadActivity.test.ts; apps/mobile/src/lib/threadActivity.ts; apps/web/src/components/chat/MessagesTimeline.tsx; packages/client-runtime/package.json; packages/client-runtime/src/work-log/presentation.ts; pnpm-lock.yaml apps/mobile/src/features/threads/floating-working-control.tsx; apps/mobile/src/features/threads/thread-work-log.tsx; packages/client-runtime/src/work-log/commandLabel.ts; patches/@legendapp__list@3.3.5.patch; patches/react-native-keyboard-controller@1.21.13.patch
1582daff8c25adf701fddd062ae93b94cc57d420ec2 test(web): remove tests for unreachable helpers (#8738) review-v2-path 13 0 13 apps/web/src/appearanceFonts.test.ts; apps/web/src/appearanceFonts.ts; apps/web/src/components/Sidebar.logic.test.ts; apps/web/src/components/Sidebar.logic.ts; apps/web/src/pendingUserInput.test.ts; apps/web/src/pendingUserInput.ts; apps/web/src/providerUpdateDismissal.test.ts; apps/web/src/providerUpdateDismissal.ts; apps/web/src/threadSync.test.ts; apps/web/src/timestampFormat.test.ts; apps/web/src/timestampFormat.ts; apps/web/src/versionSkew.test.ts; apps/web/src/versionSkew.ts
1598dcb96314c976899e4df6951fb9af03131c2a46f revert(web): restore previous composer banners (#8733) review-v2-path 33 17 16 apps/web/src/components/BranchToolbar.tsx; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx; apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx; apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/index.css; apps/web/src/session-logic.test.ts; apps/web/src/session-logic.ts; apps/web/src/versionSkew.test.ts; apps/web/src/versionSkew.ts apps/web/src/components/chat/ComposerActivityStatus.tsx; apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/chat/ComposerBannerStack.test.tsx; apps/web/src/components/chat/ComposerBannerStack.tsx; apps/web/src/components/chat/ComposerCommandMenu.test.tsx; apps/web/src/components/chat/ComposerCommandMenu.tsx; apps/web/src/components/chat/ComposerPlanFollowUpBanner.tsx; apps/web/src/components/chat/ComposerServerUpdateStatus.tsx; apps/web/src/components/chat/ComposerStashBadge.test.tsx; apps/web/src/components/chat/ComposerStashBadge.tsx; apps/web/src/components/chat/ComposerStashMenu.test.tsx; apps/web/src/components/chat/ComposerStashMenu.tsx; apps/web/src/components/chat/ComposerSurface.tsx; apps/web/src/components/chat/ComposerTasksBadge.test.tsx; apps/web/src/components/chat/ComposerTasksBadge.tsx; apps/web/src/components/chat/ThreadSyncStatusPill.test.tsx; apps/web/src/components/chat/ThreadSyncStatusPill.tsx
1601f8ed54add4133ac39effceded8fc1fff12d8e03 fix(mobile): reduce dev-client reload and Metro startup cost (#8694) review-v2-path 31 27 4 apps/mobile/src/connection/runtime.ts; apps/mobile/src/features/home/HomeScreen.tsx; packages/client-runtime/src/connection/registry.test.ts; pnpm-lock.yaml .agents/skills/test-t3-mobile/SKILL.md; AGENTS.md; apps/mobile/README.md; apps/mobile/app.config.ts; apps/mobile/package.json; apps/mobile/src/components/AppSymbol.ios.tsx; apps/mobile/src/components/AppSymbol.tabler.d.ts; apps/mobile/src/components/AppSymbol.tsx; apps/mobile/src/components/ControlPill.tsx; apps/mobile/src/features/home/HomeRouteScreen.tsx; apps/mobile/src/features/home/home-thread-navigation.test.ts; apps/mobile/src/features/home/home-thread-navigation.ts; apps/mobile/src/features/home/thread-swipe-actions.tsx; apps/mobile/src/lib/foundation-fast-refresh.ts; apps/mobile/src/lib/hot-swappable-atom-runtime.test.ts; apps/mobile/src/lib/hot-swappable-atom-runtime.ts; apps/mobile/src/lib/runtime.ts; apps/mobile/src/lib/uniwind-dev-refresh.test.ts; apps/mobile/src/state/atom-registry.ts; apps/mobile/src/state/remote-environment-projections.test.ts; apps/mobile/src/state/remote-environment-projections.ts; apps/mobile/src/state/use-remote-environment-registry.ts; docs/internals/mobile-development.md; packages/client-runtime/src/connection/registry.ts; patches/@react-native-menu__menu@2.0.0.patch; patches/uniwind@1.11.0.patch; pnpm-workspace.yaml
1613d32797f6f4614205debf8a61f09e950f5f9a81c fix(web): unify activity logs and composer banners (#8693) review-v2-path 33 17 16 apps/web/src/components/BranchToolbar.tsx; apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx; apps/web/src/components/chat/ChatComposer.tsx; apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx; apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx; apps/web/src/components/chat/MessagesTimeline.logic.test.ts; apps/web/src/components/chat/MessagesTimeline.logic.ts; apps/web/src/components/chat/MessagesTimeline.test.tsx; apps/web/src/components/chat/MessagesTimeline.tsx; apps/web/src/index.css; apps/web/src/session-logic.test.ts; apps/web/src/session-logic.ts; apps/web/src/versionSkew.test.ts; apps/web/src/versionSkew.ts apps/web/src/components/chat/ComposerActivityStatus.tsx; apps/web/src/components/chat/ComposerBanner.tsx; apps/web/src/components/chat/ComposerBannerStack.test.tsx; apps/web/src/components/chat/ComposerBannerStack.tsx; apps/web/src/components/chat/ComposerCommandMenu.test.tsx; apps/web/src/components/chat/ComposerCommandMenu.tsx; apps/web/src/components/chat/ComposerPlanFollowUpBanner.tsx; apps/web/src/components/chat/ComposerServerUpdateStatus.tsx; apps/web/src/components/chat/ComposerStashBadge.test.tsx; apps/web/src/components/chat/ComposerStashBadge.tsx; apps/web/src/components/chat/ComposerStashMenu.test.tsx; apps/web/src/components/chat/ComposerStashMenu.tsx; apps/web/src/components/chat/ComposerSurface.tsx; apps/web/src/components/chat/ComposerTasksBadge.test.tsx; apps/web/src/components/chat/ComposerTasksBadge.tsx; apps/web/src/components/chat/ThreadSyncStatusPill.test.tsx; apps/web/src/components/chat/ThreadSyncStatusPill.tsx
162c0e09f323ac9f6bf4b9119cbad841db3379588d6 fix(web): render nested markdown images correctly (#8501) review-v2-path 8 7 1 apps/web/src/components/files/FilePreviewPanel.tsx apps/web/src/components/ChatMarkdown.tsx; apps/web/src/components/ChatMarkdown.workspace-images.test.tsx; apps/web/src/components/files/FileMarkdownPreview.tsx; apps/web/src/markdown-links.test.ts; apps/web/src/markdown-links.ts; packages/client-runtime/src/markdownImages.test.ts; packages/client-runtime/src/markdownImages.ts
16372c44a847c0a76f33b0d21f47548125b7032ec35 perf(desktop): skip duplicate browser updates (#8018) identical-final-files 4 4 0 apps/desktop/src/preview/Manager.test.ts; apps/desktop/src/preview/Manager.ts; apps/web/src/previewStateStore.test.ts; apps/web/src/previewStateStore.ts
164660cddd3bc9801e089afcabba11c62f41aeac5c3 fix(web): four composer spacing defects (#8090) review-v2-path 6 3 3 apps/web/src/components/ChatView.logic.test.ts; apps/web/src/components/ChatView.logic.ts; apps/web/src/components/ChatView.tsx apps/web/src/components/chat/ComposerStashBadge.tsx; apps/web/src/components/chat/ComposerTasksBadge.test.tsx; apps/web/src/components/chat/ComposerTasksBadge.tsx
165ebb9b9fda03d33a81cad3ce7f4d1106adba3044b fix(client-runtime): refresh edited pull request comments (#8094) identical-final-files 2 2 0 packages/client-runtime/src/state/pullRequests.test.ts; packages/client-runtime/src/state/pullRequests.ts
166fc262f1a28d8305951c751f2486da6ca72e6c1d1 fix(server): retry automatic thread title generation (#8087) review-v2-path 2 0 2 apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts; apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
audits/orchestrator-v2/2026-09-02/main-since-prior.txt+165−0
@@ -0,0 +1,165 @@
157a66608b9 fix(pull-requests): align checkout control with author (#9196)
26e3bac3722 fix(web): prevent connection rows from wrapping during removal (#8706)
36effe0a2fa feat(web): redesign provider editor and models list (#8508)
4bc918e74ac fix(server): discover project skills for Claude (#9210)
570cd258d8a fix(web): prevent two-digit list markers from being clipped (#9101)
6f14f41b894 fix(web): preserve composer draft during worktree setup (#9197)
77e9d5a7efa fix(mobile): prevent message and composer overlap (#9195)
85b7d72aad1 feat(updates): continue active threads across server restarts (#9167)
914f15cfed4 fix(server): stop titling linked PR threads from local git history (#9191)
10dd6879ffea fix(pull-requests): reuse github api reads (#9176)
119e646ad84c fix(connect): refresh relay credentials before expiry (#9178)
12a19f01fc19 feat(web): make context window indicator opt-in (#9190)
136ff537f03c fix(web): remove projects with archived threads (#8798)
14a81a52afbb fix(server): allow local-only worktree bases (#8751)
15a56b0cd717 fix(server): allow large Azure DevOps PR lists (#8572)
164116db9807 fix(server): bound OpenCode version probes (#8750)
17d2042d288e fix(web): avoid stale file writes on close (#8630)
187a8df3338f fix(desktop): skip cached monitor compiler check (#9184)
19827345a07a fix(web): model info button opens its details on click (#9177)
205392c9bb99 fix(models): restore sticky new-thread selections (#9164)
21535c83dea5 fix(web): copying a code block no longer copies triple backticks (#8448)
22f9d1c65d47 chore: bump vendored GhosttyKit and update terminal integration (#9155)
230681d85499 fix(pull-requests): expand code tab diffs by default (#9174)
2480a14b6588 fix(server): discover project skills for Codex and OpenCode (#8778)
25082358f9ef fix(desktop): check artifact build prerequisites (#8975)
2643bafd4674 fix(web): open PR toast actions in app (#9006)
278339508f5c fix(chat): align failed task progress test (#9172)
28c15735dd88 fix(chat): replace failed tools with thinking (#9165)
29133db22fae feat(web): copy the full error report from the error page (#9166)
30b520120cf1 fix(chat): improve tool group summaries and scrolling (#9106)
31db4bf9497b chore: remove unused code and brittle tests (#9150)
32b8262b4122 fix(desktop): hold-to-quit no longer gets stuck (#9141)
33cde12790d7 fix(contracts): accept legacy pull request checkout results (#8238)
34a1a2bb1cd9 fix(web): label keybinding condition removal actions (#8664)
35e9db39ce05 fix(web): align composer notices and stash (#8890)
36941acb4f91 fix(provider): drop removed custom models from the model picker (#9075)
37e7deb2aaf4 feat(web): cite assistant responses with inline citations (#9146)
382ab7973fe0 fix(web): hide build pill in narrow sidebars (#9159)
399fdafdf114 feat(pull-requests): copy provider checkout commands (#9086)
4047a95332a2 fix(web): browse folders from file breadcrumbs (#8910)
41fea1af81fd fix(web): compact project settings actions (#9160)
42d937e30759 feat(web): render HTML and PDF files in the file viewer (#9143)
43f46a709ee4 feat(files): open markdown, HTML, and PDF files outside the workspace (#9140)
44f2a914b858 fix(web): preserve panel state across workspace refreshes (#8968)
458401f4d858 fix(web): darken neutral control surfaces (#9064)
460e1570bde5 fix(web): project default model works on the hosted app (#9142)
47c37fd136ed test(server): measure shell, second client, and reconnect transfer (#9157)
48b2f25d390a feat(desktop): update the desktop app on remote Macs from the Update button (#6554)
4980c708a1fa perf(web): halve the cold-start bundle by splitting Clerk and cold routes (#9058)
50a434677eca fix(grok): health check, model selection, and stop all work against the real CLI (#9154)
51083d4de5b0 fix(clients): stop repeating expanded commands (#9120)
52cdbf324aa0 fix(web): keep generated muted foreground dimmer than entered text (#9113)
53d0b19b32e0 fix(claude): preview images read from the workspace (#9119)
54716069f40f fix(server): keep attachments until the command commits (#7941)
550e77fbd3d0 fix(server): prevent accidental service downgrades (#5302)
568efd4e95fc fix(settings): sync auto-settle and other shared preferences across environments (#9147)
575014e5fcdd fix(desktop): show newest changes in nightly previews (#9138)
58fc53b27303 perf(clients): lease sidebar status by visibility (#9052)
596866fd6b5c perf(client-runtime): keep turn and checkpoint refs stable while streaming (#9145)
607e460f429b fix(server): bound orchestration replay payloads (#8992)
61c2283ce146 perf: make streaming projection and activity appends incremental (#9152)
62feb3ea7ebf fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine (#8360)
63ea71a19d41 fix(claude): skills picked from the composer now run (#9128)
649a7b1e21e5 perf(provider): bound persisted session lookups (#8909)
6598725df00a fix(web): mute routine notices and update actions (#9063)
6608aad594f0 chore: delete dead code, unused deps, and duplicate helpers (#9129)
67b21d87243e chore: vouch six repeat contributors (#9131)
6804efa7907e feat(cli): open projects in the running desktop app (#8824)
69beae2147a9 fix(media): preview host files and stream videos across clients (#9023)
7060cef47ec9 chore(release): prepare v0.0.38
71590a579f2e fix(chat): keep latest command live between messages (#9098)
720222aa255d fix(web): preserve theme when toggling advanced colors (#8500)
73c0995d2eaf fix(web): keep the selected environment when changing projects (#9102)
74d0b4acbd13 fix(web): keep theme placeholder text dimmer than entered text (#9104)
753b3465f2a9 fix(web): changing projects no longer creates a draft (#9097)
76692eb1a579 fix(web): sync sidebar PR state from open panel (#9092)
77163d50846b Revert "fix(chat): reuse one row for live activity" (#9096)
780354283683 feat(models): discover Claude models from remote manifest (#9084)
79a924fbe08e fix(chat): reuse one row for live activity (#9062)
80cb00746916 feat(web): open project settings from thread menus (#8925)
819d1879b142 feat(desktop): add configurable quit shortcut confirmation (#9076)
82ef7014d851 fix(preview): restore recording and macOS rendering after Electron 43 (#9001)
83c17d02cff9 feat(claude): add Claude Fable 5.1 model (#9078)
84643b21edaa fix(server): cache project favicon resolution (#9080)
85261380f91f fix(mobile): keep thread scroll bounds current after animations (#9013)
862d156a83b9 feat(shortcuts): copy active thread reference (#8994)
87b5b6abb11e fix(web): block type-to-focus behind open dialogs (#8139)
889dbdcece5f fix(web): align un-settle banner action (#9033)
89b883fc066e perf(client-runtime): halve server config bootstrap traffic (#8367)
90e86604d337 perf(server): skip full-message reads while streaming (#9032)
913c73fa7ce0 perf(web): defer pull request line stats until visible (#6471)
9262d39bf00d fix(server): stop OpenCode child sessions (#9005)
938b033de482 fix(clients): dedupe skills in composer menus (#8043)
94f32f9a2f41 fix(server): settle threads server-side (#8600)
957e4ce3bbb1 perf(server): cut chatty tool-update frames by 90% (#8368)
968f1ef8b9eb perf(server): scan only appended transcript bytes for usage summaries (#9024)
970bfb6df34b perf(server): cut idle CPU use and stop provider event leaks (#8187)
98a9ffb82796 perf(server): bound snapshot activity payload memory (#9000)
9973776d4e52 test: remove static presentation snapshots (#9008)
1000947c30e69 fix(client): use package import for markdown image helpers (#9010)
101ce71c04f0a feat(client): render viewed images in work logs (#8936)
102ff93aba61d feat(web): search individual settings by detail (#8831)
103d35c71d1b9 feat(web): add pull request list filters (#8809)
104b17cc3d1bf perf(server): reduce frequency of full tool call output being loaded into memory from db (#8988)
10542a8fd5103 feat(pull-requests): link GitHub references in markdown (#8812)
106c78ae50a5a fix(server): isolate remote web session cookies (#8085)
1079ecfc07a8b fix(chat): keep agent activity visible between actions (#8984)
1089bc7a56848 feat(mobile): upload attachments while composing (#8978)
10985b656ff30 style: format CodeRabbit configuration
1100df043fd4e Add auto_review configuration to coderabbit.yaml
11117f00f6024 feat(web): add expand/collapse all control to the files surface (#8889)
112c50b0b4ef8 fix(web): make WSL settings searchable (#8881)
113929f7e6479 fix(shared): preserve Windows shell PATH priority (#8748)
11441adccc83e fix(server): allow long thread IDs in HTTP routes (#8898)
115f8e4accf27 feat(mobile): add native image and PDF previews (#8959)
1166d15c5bbc3 fix(server): preserve usage cache outside walked roots (#8540)
117f47e74004a fix(web): prevent chat metadata overlap (#8851)
11831c1c5996f feat(mobile): add video playback with native iOS controls (#8919)
119ef84bc9873 fix(chat): smooth worktree setup status (#8922)
1204a9d2d0ced chore(deps): bump Electron to 43.4.1 (#8626)
1215ce92c2f19 fix(mobile): shimmer active tool rows (#8932)
12235da581331 fix(web): show scrollbar for wide markdown tables (#8868)
123038bf3739b Delete app.json (#8934)
1244e8e64fc06 chore: disable CodeRabbit review status (#8933)
125746c932e16 fix(mobile): defer draft navigation until submission completes (#8914)
126bba79cc254 fix(web): hide invalid slash skill completions (#8904)
1272921050c69 fix(contracts): accept CLI event origins (#8905)
128ad38700ac6 chore(macroscope): review diagnostic overrides (#8917)
129f86c5e8c87 fix(server): skip IDE detection in Claude probes (#8634)
1309b2d04317c fix(mobile): replace Callstack glass with Expo glass (#8862)
131e9c4775e87 fix(web): mark pull request links as external (#8856)
1323958111057 fix(preview): improve browser recording quality (#8839)
1333f62e6fa65 fix(web): widen sync banners and simplify the working timer (#8855)
1349842518c9a fix(web): address composer banner review follow-ups (#8850)
13530175a8af0 fix(web): restore unified activity logs and composer banners (#8734)
136f9137a0c89 fix(mobile): map native menu icon colors explicitly
137e3dcc1615c Add mobile composer attachment menu with video support (#8843)
1387963ac7404 chore(release): prepare v0.0.37
139cefec32d6f fix(web): prevent pull request metadata overlap (#8790)
140352710d497 feat(mobile): add offline iPhone voice input (#8614)
1418b817cbcaa fix(web): use circle alert for failed tool calls (#8840)
14217c48f7fc1 fix(web): fold interim turn responses (#8828)
143e4f7b14fab chore: add Windows setup script to t3.json (#8814)
144c1e70b5f8c fix(web,mobile): render Codex citations and artifact templates (#8584)
145e09b88b6a5 fix(web): keep right panel synced with agent edits (#8803)
1465885a68adb fix(web): keep image preview above sidebar control (#8811)
1479072aa1fd7 fix(server): stop overpricing cached Claude tokens (#8806)
14860f2ce0279 fix(git): follow repository instructions in generated source control text (#8804)
1498f525af5af fix(web): open agent images in expanded preview (#8807)
15012fe2d6d03 fix(windows): strip quotes from repaired PATH (#8746)
1516e324b9bbf fix(web): reduce title bar scroll fade height (#8799)
15286c9a9288b feat(mobile): pick, share, and receive files in threads (#8237)
1537880a6e583 fix(grok): allow model changes in existing threads (#8392)
1547980dfddb1 fix(web,mobile): snooze menu no longer offers the same wake time twice (#8741)
155ac4aae101d feat(web): play video attachments in chat (#8688)
156f15680bd3c feat(mobile): update tool summaries and chat transitions (#8793)
1572daff8c25a test(web): remove tests for unreachable helpers (#8738)
1588dcb96314c revert(web): restore previous composer banners (#8733)
1591f8ed54add fix(mobile): reduce dev-client reload and Metro startup cost (#8694)
1603d32797f6f fix(web): unify activity logs and composer banners (#8693)
161c0e09f323a fix(web): render nested markdown images correctly (#8501)
16272c44a847c perf(desktop): skip duplicate browser updates (#8018)
163660cddd3bc fix(web): four composer spacing defects (#8090)
164ebb9b9fda0 fix(client-runtime): refresh edited pull request comments (#8094)
165fc262f1a28 fix(server): retry automatic thread title generation (#8087)
audits/orchestrator-v2/2026-09-02/new-branch-work.txt+21−0
@@ -0,0 +1,21 @@
1d2f1f511f4 fix: reconcile main's round-20 features after the rebase
20550e0a34d fix(web): realign the composer and timeline with main
3e6da41e5c6 fix(web): right-align the stash shoulder tab again
4f0174c4577 fix: reconcile main's round-19 features after the rebase
52191297898 feat(server): evaluate automatic thread settlement in the v2 orchestrator
67697286069 fix: reconcile main's round-18 features after the rebase
799e940dc2b feat(web): port working and thinking timeline rows to orchestration v2
8acdbe292f0 fix: reconcile main's round-17 features after the rebase
94b35166d02 fix(mobile): keep scroll bounds current after animations
10fa54f54607 fix(chat): remove added tool summary status counts
11b27f50b65a feat(mobile): port chat summaries and transitions to orchestration v2
1260bc3cff93 feat(web): summarize T3 orchestration actions
1372e0c434be fix(web): keep composer shortcut tooltip stable on Mod
146bcc0e39da fix(web): match composer actions to draft and modifier state
156ce4cb0a15 fix(web): keep queued messages in place while editing
16e16d934d8a fix(web): keep queued message editing inside the queue panel
17b2afc4cb45 refactor(web): use shared banner rows for queued messages
1885a3c12568 fix(web): keep stash separate from the composer activity column
19e82c717855 fix(web): share the outline for joined composer tabs
20b1533813ce fix(web): align queue headers and prevent stash overlap
21ed6aafa93f fix(web): port composer activity and grouping to orchestration v2
audits/orchestrator-v2/2026-09-02/persistence-runtime-probes.test.ts+210−0
@@ -0,0 +1,210 @@
1import { assert, it } from "../../../apps/server/node_modules/@effect/vitest/dist/index.js";
2import {
3 EventId,
4 type ModelSelection,
5 NodeId,
6 type OrchestrationV2AppThread,
7 type OrchestrationV2DomainEvent,
8 ProjectId,
9 ProviderInstanceId,
10 ProviderThreadId,
11 RunId,
12 ThreadId,
13} from "../../../apps/server/node_modules/@t3tools/contracts/src/index.ts";
14import * as Cause from "../../../apps/server/node_modules/effect/dist/Cause.js";
15import * as DateTime from "../../../apps/server/node_modules/effect/dist/DateTime.js";
16import * as Effect from "../../../apps/server/node_modules/effect/dist/Effect.js";
17import * as SqlClient from "../../../apps/server/node_modules/effect/dist/unstable/sql/SqlClient.js";
18
19import {
20 IdAllocatorV2,
21 layer as idAllocatorLayer,
22} from "../../../apps/server/src/orchestration-v2/IdAllocator.ts";
23import {
24 applyToProjection,
25 emptyProjection,
26 threadShellFromProjection,
27} from "../../../apps/server/src/orchestration-v2/ProjectionStore.ts";
28import { shouldAutoSettleThread } from "../../../apps/server/src/orchestration-v2/ThreadSettlementService.ts";
29import {
30 migrationEntries,
31 runMigrations,
32} from "../../../apps/server/src/persistence/Migrations.ts";
33import * as NodeSqliteClient from "../../../apps/server/src/persistence/NodeSqliteClient.ts";
34
35const providerInstanceId = ProviderInstanceId.make("codex");
36const modelSelection = {
37 instanceId: providerInstanceId,
38 model: "gpt-5.4",
39} satisfies ModelSelection;
40
41function makeThread(threadId: ThreadId, now: DateTime.Utc): OrchestrationV2AppThread {
42 return {
43 createdBy: "user",
44 creationSource: "web",
45 id: threadId,
46 projectId: ProjectId.make(`project:${threadId}`),
47 title: `Thread ${threadId}`,
48 providerInstanceId,
49 modelSelection,
50 runtimeMode: "full-access",
51 interactionMode: "default",
52 branch: null,
53 worktreePath: null,
54 activeProviderThreadId: null,
55 lineage: {
56 parentThreadId: null,
57 relationshipToParent: null,
58 rootThreadId: threadId,
59 },
60 forkedFrom: null,
61 createdAt: now,
62 updatedAt: now,
63 archivedAt: null,
64 settledOverride: null,
65 settledAt: null,
66 lastVisitedAt: null,
67 deletedAt: null,
68 };
69}
70
71function threadCreatedEvent(
72 thread: OrchestrationV2AppThread,
73 now: DateTime.Utc,
74): Extract<OrchestrationV2DomainEvent, { readonly type: "thread.created" }> {
75 return {
76 id: EventId.make(`event:create:${thread.id}`),
77 type: "thread.created",
78 threadId: thread.id,
79 providerInstanceId,
80 occurredAt: now,
81 payload: thread,
82 };
83}
84
85it.effect("reproduces the old-052 to current-053 migration failure", () =>
86 Effect.gen(function* () {
87 const sql = yield* SqlClient.SqlClient;
88 yield* runMigrations({ toMigrationInclusive: 43 });
89
90 // Current 45-53 are the byte-equivalent old branch 44-52 bodies. Apply
91 // them under their old IDs/names to reproduce a database from c1791ab2637.
92 for (const [currentId, name, migration] of migrationEntries.filter(([id]) => id >= 45)) {
93 yield* migration;
94 yield* sql`
95 INSERT INTO effect_sql_migrations (migration_id, name)
96 VALUES (${currentId - 1}, ${name})
97 `;
98 }
99
100 const before = yield* sql<{ readonly migration_id: number; readonly name: string }>`
101 SELECT migration_id, name
102 FROM effect_sql_migrations
103 ORDER BY migration_id DESC
104 LIMIT 1
105 `;
106 assert.deepStrictEqual(before, [{ migration_id: 52, name: "LegacyV1ImportState" }]);
107
108 const exit = yield* Effect.exit(runMigrations());
109 assert.strictEqual(exit._tag, "Failure");
110 if (exit._tag === "Failure") {
111 assert.match(Cause.pretty(exit.cause), /Migration "53_LegacyV1ImportState" failed/);
112 assert.match(Cause.pretty(exit.cause), /already exists/);
113 }
114
115 const after = yield* sql<{ readonly migration_id: number; readonly name: string }>`
116 SELECT migration_id, name
117 FROM effect_sql_migrations
118 ORDER BY migration_id DESC
119 LIMIT 1
120 `;
121 assert.deepStrictEqual(after, before);
122 }).pipe(Effect.provide(NodeSqliteClient.layerMemory())),
123);
124
125it.effect("reproduces production root-scope reuse across ordinary runs", () =>
126 Effect.gen(function* () {
127 const ids = yield* IdAllocatorV2;
128 const threadId = ThreadId.make("thread:audit-scope");
129 const now = DateTime.makeUnsafe("2026-09-02T00:00:00.000Z");
130 const firstScopeId = yield* ids.allocate.checkpointScope({ threadId, name: "root" });
131 const secondScopeId = yield* ids.allocate.checkpointScope({ threadId, name: "root" });
132 const firstRunId = RunId.make("run:audit-scope:1");
133 const secondRunId = RunId.make("run:audit-scope:2");
134 const providerThreadId = ProviderThreadId.make("provider-thread:audit-scope");
135 let projection = emptyProjection(threadCreatedEvent(makeThread(threadId, now), now));
136 const scopeEvent = (
137 eventId: EventId,
138 scopeId: typeof firstScopeId,
139 runId: RunId,
140 nodeId: NodeId,
141 ): Extract<OrchestrationV2DomainEvent, { readonly type: "checkpoint-scope.created" }> => ({
142 id: eventId,
143 type: "checkpoint-scope.created",
144 threadId,
145 runId,
146 nodeId,
147 occurredAt: now,
148 payload: {
149 id: scopeId,
150 threadId,
151 runId,
152 nodeId,
153 parentScopeId: null,
154 providerThreadId,
155 kind: "root_run",
156 ordinalWithinParent: 0,
157 advancesAppRunCount: true,
158 cwd: "/repo",
159 createdAt: now,
160 },
161 });
162
163 projection = applyToProjection(
164 projection,
165 scopeEvent(
166 EventId.make("event:scope:1"),
167 firstScopeId,
168 firstRunId,
169 NodeId.make("node:scope:1"),
170 ),
171 );
172 projection = applyToProjection(
173 projection,
174 scopeEvent(
175 EventId.make("event:scope:2"),
176 secondScopeId,
177 secondRunId,
178 NodeId.make("node:scope:2"),
179 ),
180 );
181
182 assert.strictEqual(firstScopeId, secondScopeId);
183 assert.strictEqual(projection.checkpointScopes.length, 1);
184 assert.strictEqual(projection.checkpointScopes[0]?.runId, secondRunId);
185 }).pipe(Effect.provide(idAllocatorLayer)),
186);
187
188it("confirms closed PR settlement remains active when optional settings are off", () => {
189 const now = Date.parse("2026-09-02T12:00:00.000Z");
190 const createdAt = DateTime.makeUnsafe(now - 20 * 60_000);
191 const threadId = ThreadId.make("thread:audit-settlement");
192 const projection = emptyProjection(
193 threadCreatedEvent(makeThread(threadId, createdAt), createdAt),
194 );
195 const thread = threadShellFromProjection(projection);
196
197 assert.strictEqual(
198 shouldAutoSettleThread({
199 thread,
200 pullRequest: {
201 state: "closed",
202 updatedAt: "2026-09-02T12:00:00.000Z",
203 },
204 nowMs: now,
205 autoSettleAfterDays: null,
206 autoSettleOnMerge: false,
207 }),
208 true,
209 );
210});
audits/orchestrator-v2/2026-09-02/persistence.md+236−0
@@ -0,0 +1,236 @@
1# Orchestration V2 persistence, runtime, and performance audit
2
3Date: 2026-09-02
4Frozen branch: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0`
5Current main: `57a66608b918d673eeec7e6c94ea5906b756fcd0`
6Prior reviewed tip: `c1791ab2637` (`47f5b100440591d2f49aa30cf3bb69eacae07f52` before rebase)
7
8This was a read-only review of `apps/server/src/orchestration-v2` excluding provider adapters, plus the persistence and project boundaries explicitly requested by the root review. No product source or tests were edited. The only writes are this audit report and the retained audit-only probe artifacts beside it. No live database, provider, server, browser, or production state was used.
9
10## Summary
11
12I confirmed seven findings:
13
14| Severity | Classification | Finding | Evidence basis |
15| -------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
16| Blocker | Branch-upgrade rebase regression | Existing V2 databases through old migration 052 fail on current 053, while new main migration 044 is skipped. | Retained actual migration-runner/in-memory SQLite reproduction. |
17| High | Preexisting V2 integration bug | WebSocket forced project deletion commits V2 thread deletions before legacy project deletion rejects the still-present imported V1 threads. | Deterministic source trace only; no integrated transport run. |
18| High | Preexisting V2 integration bug | HTTP and offline CLI project mutation bypass V2 deletion and drop `force`; create also drops `createWorkspaceRootIfMissing`. | Deterministic source trace only; no integrated transport run. |
19| High | V2 correctness gap | Full-thread diff fails after an ordinary second run because production reuses one root scope and updates its `runId`, while the query requires that scope to belong to run 1. | Retained real allocator/projection reproduction plus query source trace. |
20| Medium | New auto-settlement regression | A failure that predates a snooze is treated as waking the snoozed thread and can settle it early. | Source and policy-test trace; no timed service run. |
21| Medium | Preexisting missed-main performance gap | Checkpoint diff loads an unbounded full V2 thread projection instead of narrow checkpoint context. | SQL/source cardinality trace; not benchmarked. |
22| Medium | Preexisting V2 performance gap | Startup recovery loads every persisted row for every active and archived thread before accepting commands. | SQL/source cardinality trace; not benchmarked. |
23
24The previously fixed token-usage, legacy metadata, migrated search, missing-worktree recovery, unsettled ordering, SQL visibility, stop-request dependency, nested-fork cursor, true-end pagination, and bounded payload invariants remain present at the frozen head. Focused tests passed, but they do not cover the broken upgrade history or populated-project deletion boundaries.
25
26## Confirmed findings relative to main and prior invariants
27
28### 1. Blocker: an existing pre-rebase V2 database cannot migrate to the current head
29
30**Current code.** The current manifest inserts main's `ClearAutomaticProjectModelDefaults` at ID 44 and shifts the old branch migrations to IDs 45 through 53 (`apps/server/src/persistence/Migrations.ts:58-67`, `:123-132`). The migration API documents that only IDs above the latest recorded ID run (`Migrations.ts:156-169`). The underlying Effect migrator implements exactly that rule and does not compare historical names: `currentId <= latestMigrationId` is skipped at `.repos/effect-smol/packages/effect/src/unstable/sql/Migrator.ts:228-259`, specifically `:248-252`.
31
32Current 053 then unconditionally creates `orchestration_v2_legacy_imports` and its index (`apps/server/src/persistence/Migrations/053_LegacyV1ImportState.ts:9-26`). Neither statement uses `IF NOT EXISTS`.
33
34**Historical evidence.** At `c1791ab2637`, the branch manifest assigned `OrchestrationV2` through `LegacyV1ImportState` IDs 44 through 52 (`apps/server/src/persistence/Migrations.ts` at that commit, lines 58-66 and 122-130). Old `052_LegacyV1ImportState.ts` and current `053_LegacyV1ImportState.ts` are byte-identical; both hash to `efc494840b8319531495a28a82f6624947e54be9846ee6f5a260cd7eae27fe1c`. Main's new 044 performs data repair in both `projection_projects` and `orchestration_events` (`apps/server/src/persistence/Migrations/044_ClearAutomaticProjectModelDefaults.ts:7-50`).
35
36**Actual disposable reproduction.** I ran the real current `runMigrations` with the repository's NodeSqlite layer against an in-memory database. The fixture first applied the current schema bodies corresponding to the old branch sequence and recorded the old IDs and names 44 through 52, ending with `[52, "LegacyV1ImportState"]` and an existing `orchestration_v2_legacy_imports` table. On the next current migration run:
37
38```text
39latest before current run: 52_LegacyV1ImportState
40current run: MigrationError: Migration "53_LegacyV1ImportState" failed
41SQLite cause: table orchestration_v2_legacy_imports already exists
42latest after rollback: 52_LegacyV1ImportState
43```
44
45This exercises the actual migration loader, tracking table, transaction, and SQLite DDL. It does not write any live database.
46
47**Reachable trigger.** Start this frozen build against any database that successfully ran branch tip `c1791ab2637` through migration 52.
48
49**Impact.** Startup fails on 053. Before that failure, the migrator silently treats current 044 through 052 as already applied because their numeric IDs are not above 52. In particular, main's automatic project-model repair at current 044 is never applied to an upgraded branch database. Making only the table creation idempotent fixes the crash but still leaves that repair skipped.
50
51**Compatibility fix.** The migration strategy must explicitly cover every supported historical manifest, not only the reproduced old-052 tip. Making current 053 use `IF NOT EXISTS` would unblock that one cohort, but it would neither apply skipped main 044 nor make arbitrary partially migrated old V2 cohorts safe. For example, an old database ending at 44 can run current 45's shifted copy of the same V2 DDL and collide earlier in the sequence.
52
53Use a manifest-aware compatibility bridge before the ordinary numeric runner, or make every shifted 45-53 step safe for its corresponding old predecessor, then append an idempotent migration that reapplies main 044's repair. A manifest-aware bridge can identify old history by recorded `(migration_id, name)` pairs, reconcile those records/schema to the new numbering, and leave released-main histories distinct. Do not infer history from the maximum ID alone. Add table-driven migration tests for each supported old branch stopping point plus released-main and fresh histories. The exact old-052 regression must remain a fixture. The existing fresh-database test at `apps/server/src/persistence/Migrations/045_046_OrchestrationV2.test.ts:174-230` starts before these IDs and cannot detect any of these upgrade failures.
54
55### 2. High: forced WebSocket project removal can delete V2 threads and then reject the project delete
56
57**Current code.** The WebSocket mutation handler obtains active and archived V2 shells, enforces `force`, and dispatches one `thread.delete` command at a time (`apps/server/src/ws.ts:1238-1258`). Those are independently committed mutations. It then calls `ProjectService.delete` without `force` (`ws.ts:1259-1262`). `ProjectDeleteInput` has no `force` member (`apps/server/src/project/ProjectService.ts:42-45`), and the service dispatches a legacy `project.delete` without it (`ProjectService.ts:348-371`).
58
59The legacy decider rejects a nonempty project unless its command contains `force: true`; when force is present, it deletes legacy threads and the project as one decided sequence (`apps/server/src/orchestration/decider.ts:274-306`). The legacy engine bootstraps that invariant from `getCommandReadModel` (`apps/server/src/orchestration/Layers/OrchestrationEngine.ts:344-345`). That query reads every `projection_threads` row (`apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts:430-463`, `:1858-1918`) and constructs the thread read model at `:2010-2049`.
60
61The V1 importer copies rows from `projection_threads`; it does not delete or supersede those rows (`apps/server/src/orchestration-v2/LegacyV1ThreadImporter.ts:459-560`). It also refuses to import the same thread again once a V2 `thread.created` event exists (`LegacyV1ThreadImporter.ts:481-489`).
62
63**Reachable trigger.** Use the WebSocket `project.delete` mutation with `force: true` on a project whose legacy threads have been imported into V2. This is the normal state after the V2 legacy-shell startup import.
64
65**Observed behavior from the source path.** Each V2 thread deletion succeeds first. The subsequent legacy `ProjectService.delete` sees the intact active `projection_threads` rows and rejects because the forwarded command has no force. The project remains, its V1 rows remain, and its V2 thread copies are deleted. A later importer pass will not restore them because their V2 creation events still exist.
66
67**Impact.** A user-authorized project removal fails after partially committing destructive work. The V2 UI loses the project's threads while reporting that the project could not be deleted.
68
69**Minimal fix.** Preserve `force` through `ProjectDeleteInput` and the legacy dispatch so this exact mixed-store path cannot reject after the V2 deletes. Prefer a single project-deletion coordinator used by all transports, with validation over both V1 and V2 and one transactional event commit; otherwise an unrelated failure in the per-thread loop can still partially delete a project. Add a focused test with one imported active thread and one imported archived thread, then force-delete through the WebSocket handler and assert the project and both representations are deleted.
70
71**Classification.** This predates `c1791ab2637`: the current deletion sequence traces to `b1c074b2ec3`, which is an ancestor of the prior rebased tip. It is a previously missed V2 integration bug relative to the V1 force-delete invariant, not a regression introduced by the final rebase.
72
73### 3. High: HTTP and offline CLI project mutations bypass the V2 lifecycle and drop contract fields
74
75**Current code.** `ProjectMutation` explicitly carries `createWorkspaceRootIfMissing` and deletion `force` (`packages/contracts/src/project.ts:70-95`). The WebSocket create handler forwards the former (`apps/server/src/ws.ts:1215-1223`), and `ProjectService.create` uses it when normalizing the workspace (`apps/server/src/project/ProjectService.ts:253-269`).
76
77The production HTTP endpoint is registered at `apps/server/src/server.ts:465-475` from the contract at `packages/contracts/src/environmentHttp.ts:560-575`. Its handler calls `ProjectService` directly. Create omits `createWorkspaceRootIfMissing`, and delete omits `force` and all V2 thread enumeration/deletion (`apps/server/src/project/http.ts:45-80`).
78
79The live CLI uses that HTTP endpoint (`apps/server/src/cli/project.ts:324-338`). Its offline path also maps delete directly to `ProjectService.delete` without force (`cli/project.ts:423-459`). The user-facing command accepts `--force`, describes it as deleting all threads, and includes the flag in its transport command (`cli/project.ts:522-555`). Main V1's invariant and forced sequence are the decider behavior at `apps/server/src/orchestration/decider.ts:274-306`; the prior CLI path passed the force field through to the engine.
80
81**Reachable triggers and actual behavior.**
82
831. A V2-only project with active or archived V2 threads can be deleted by `POST /api/projects/mutate`. The legacy read model sees no V1 thread, so the project deletion succeeds while the V2 threads remain live and reference a deleted project.
842. An imported project with legacy rows rejects HTTP or CLI `--force` removal because `force` is dropped.
853. `t3 project remove`, live and offline, inherits these deletion outcomes despite promising complete forced cleanup.
864. HTTP and offline CLI project creation with `createWorkspaceRootIfMissing: true` behaves as false because the flag is dropped.
87
88**Impact.** The same typed mutation has materially different safety and filesystem semantics by transport. The deletion variant can orphan V2 threads or make `--force` ineffective.
89
90**Minimal fix.** Route WebSocket, HTTP, live CLI, and offline CLI mutations through one V2-aware project mutation coordinator. Forward both optional fields unchanged. Add transport-parity tests for V2-only populated projects, imported projects, archived threads, force false/true, and missing workspace creation.
91
92**Classification.** The direct HTTP/offline mappings trace to `b56d0e7d53fb`, an ancestor of `c1791ab2637`. This is a previously missed V2 transport integration bug, not a final-rebase regression. New main CLI behavior makes the existing mismatch directly user-facing in `t3 project remove`.
93
94### 4. Medium: auto-settlement treats a failure predating the snooze as an early wake
95
96**Current code.** A future snooze is bypassed whenever `thread.status === "failed"`, with no comparison to `snoozedAt` (`apps/server/src/orchestration-v2/ThreadSettlementService.ts:95-116`, especially `:107-116`). The service then performs normal inactivity/PR evaluation and can dispatch `thread.auto-settle` (`ThreadSettlementService.ts:216-270`). The current test asserts that changing only the status to failed wakes the snooze (`apps/server/src/orchestration-v2/ThreadSettlementService.test.ts:70-87`).
97
98**Main/prior invariant.** Main V1 only treats an error as a wake when the error session was updated after the snooze. It compares `thread.session.updatedAt` with `thread.snoozedAt` (`apps/server/src/orchestration/ThreadSettlementPolicy.ts` at main, lines 89-107, specifically 98-101). Completion has the same after-snooze rule.
99
100**Reachable trigger.** Let a thread fail, then snooze that already-failed thread into the future. On the next minute sweep, V2 classifies the stale failure as an early wake. If the inactivity or PR rule matches, it settles the thread before the requested wake time.
101
102**Impact.** A newer explicit snooze loses to older failure state, so settled/active sidebar placement contradicts the user's latest action.
103
104**Minimal fix.** Require the failure evidence to be newer than `snoozedAt`, as V1 does. The V2 shell already exposes `latestRunCompletedAt`; for a failed latest run, require that timestamp to be non-null and greater than `snoozedAt`. Add tests for failure before snooze (parked) and failure after snooze (candidate).
105
106## Confirmed V2 correctness and performance gaps
107
108### 5. High: full-thread diff fails after an ordinary second run
109
110**Current code.** For `fromTurnCount === 0`, `CheckpointDiffQuery` finds `runs.ordinal === 1`, requires a `root_run` scope whose current `runId` is that first run, and synthesizes its ordinal-0 ref (`apps/server/src/checkpointing/CheckpointDiffQuery.ts:157-180`). `getFullThreadDiff` always delegates to that path (`CheckpointDiffQuery.ts:204-248`).
111
112Production does not keep one distinct root scope row per run. `IdAllocator` derives a checkpoint scope solely from `threadId` and the constant scope name (`apps/server/src/orchestration-v2/IdAllocator.ts:295-300`). `makeRootRunScope` always requests the name `"root"`, but places the current run, node, provider thread, cwd, and creation time in the payload (`apps/server/src/orchestration-v2/CheckpointService.ts:184-210`, with the constant at `:27`). Each ordinary immediate run emits `checkpoint-scope.created` (`apps/server/src/orchestration-v2/Orchestrator.ts:3558-3567`).
113
114Both projection implementations replace the prior scope because the deterministic ID is the same. The in-memory path uses `upsertById` (`apps/server/src/orchestration-v2/ProjectionStore.ts:343-347`), and SQL uses `ON CONFLICT(scope_id) DO UPDATE`, including overwriting `run_id` (`ProjectionStore.ts:1773-1815`, specifically `:1803-1814`). Checkpoint IDs and refs remain stable by shared scope plus ordinal (`apps/server/src/orchestration-v2/CheckpointService.ts:161-181`). Thus, after run 2, the one root scope row belongs to run 2 while the valid ordinal-0 baseline from run 1 remains under that shared scope ID.
115
116**Test-fixture mismatch.** `CheckpointDiffQuery.test.ts:23-49` fabricates `firstScopeId` and `secondScopeId` and keeps both rows. That state cannot be produced by the real allocator for two root runs in one thread. The happy-path assertion at `:70-96` therefore hides the production failure. The missing-baseline test at `:176-196` also treats absence of a run-1-owned scope as an error, even though that is the normal projection after run 2.
117
118**Actual disposable allocation/projection probe.** I ran the real `IdAllocatorV2.allocate.checkpointScope` twice for the same thread and `"root"`, then applied two real `checkpoint-scope.created` events through `applyToProjection`. The two allocated IDs were equal; after the second event, `checkpointScopes.length` was 1 and the stored row's `runId` was `run:2`. The one-case audit-only Vitest probe passed and was removed afterward.
119
120**Reachable trigger and behavior.** Complete two ordinary immediate runs in a Git workspace, then request `getFullThreadDiff` to turn 2. The target checkpoint and shared ordinal-0 ref exist. The sole root scope now has `runId` 2, so the lookup constrained to run 1 returns undefined and the query raises `CheckpointRefUnavailableError` for the `from` ref. A cancelled/deferred first run is another trigger, but is not required.
121
122**Impact.** Full-thread diff is unavailable for normal multi-turn threads from the second completed run onward.
123
124**Minimal fix.** Stop joining the zero baseline through `firstRun.id`. Resolve the root scope by the target checkpoint's shared `scopeId`, then use that scope's ordinal-0 checkpoint/ref. Prefer the stored ready baseline ref and validate that it exists. Rewrite the happy-path test with the real allocator or one shared root scope whose current `runId` is run 2, and retain separate corrupt/missing-baseline coverage.
125
126**Classification.** The bad lookup traces to `b56d0e7d53fb`, while deterministic scope reuse and projection upsert predate it. `b56d0e7d53fb` is an ancestor of `c1791ab2637`, so this is a previously missed V2 correctness bug rather than a final-rebase regression.
127
128### 6. Medium: checkpoint diff reintroduces unbounded transcript hydration fixed on main
129
130**Current code.** Every nonempty diff first calls `ThreadManagementService.getThreadProjection` (`apps/server/src/checkpointing/CheckpointDiffQuery.ts:102-121`). V2's method is the unwindowed `readProjection` path (`apps/server/src/orchestration-v2/ProjectionStore.ts:2598-2599`). With no window, that path loads every turn item (`ProjectionStore.ts:2023-2030`) and selects all rows from each run, attempt, node, session, provider thread/turn, request, message, plan, checkpoint, and handoff table (`ProjectionStore.ts:2163-2399`), then decodes all of them (`ProjectionStore.ts:2401-2433`). Fork ancestry can recurse through additional full projections (`ProjectionStore.ts:2467-2589`).
131
132**Main invariant.** Main #8988 (`b17cc3d1bf0`) reduced full tool-output hydration, and #8992 (`7e460f429b7`) added a dedicated full-thread-diff query. The retained V1 implementation demonstrates the intended shape: a small thread/workspace query at `apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts:985-1001`, a single-row full-diff query at `:1533-1562`, and narrow service methods at `:2512-2583`. Main's `CheckpointDiffQuery` calls `getThreadCheckpointContext` or `getFullThreadDiffContext`; current V2 does neither.
133
134**Reachable trigger.** Request a turn diff or full-thread diff for any long thread, especially one with large tool outputs or fork ancestry.
135
136**Impact/cardinality.** Work is O(all persisted child rows for the thread and its fork ancestors), although the operation needs only workspace identity, run status/ordinal, scope identity, and a few checkpoint refs. A diff request can load and decode unrelated transcript bodies into memory.
137
138**Minimal fix.** Add V2 SQL queries equivalent to `getThreadCheckpointContext` and `getFullThreadDiffContext`, against V2 projection tables, and have the diff service use them. The full-thread endpoint needs one target checkpoint, the latest available completed turn count, a valid zero baseline, and the workspace path; it should not instantiate `OrchestrationV2ThreadProjection`. Add a cardinality test with a large unrelated tool payload and assert that the narrow query does not read/decode it.
139
140**Classification.** This gap was already present at `c1791ab2637`, even though main's #8988/#8992 optimizations were already in its ancestry. It is a missed main performance invariant from the prior review, not a new final-rebase regression.
141
142### 7. Medium: startup recovery fully hydrates every active and archived thread before readiness
143
144**Current code.** Recovery loads the V2 shell, iterates `shell.threads` plus `shell.archivedThreads` sequentially, and calls full `getThreadProjection` for every one (`apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts:470-499`). The reconciliation only needs nonterminal runs, pending runtime requests, live provider/background state, and unsettled process-bound effects (`ProviderRuntimeRecoveryService.ts:130-175` and the remainder of `reconcileProjection`).
145
146As above, `getThreadProjection` is unbounded (`apps/server/src/orchestration-v2/ProjectionStore.ts:2023-2030`, `:2163-2433`, `:2598-2599`) and recursively loads complete source projections for forked threads (`:2467-2589`). Startup awaits this recovery as an ordered phase (`apps/server/src/serverRuntimeStartup.ts:456-508`) and does not signal command readiness until `:615-617`.
147
148**Reachable trigger.** Restart an environment with many terminal threads, archived threads, long transcripts, tool payloads, checkpoints, or fork chains. No active provider work is required.
149
150**Impact/cardinality.** Startup database reads and decoding are O(total retained V2 projection history), not O(recoverable runtime state). The loop is sequential across threads. This can delay every local and remote client from issuing commands and creates a peak-memory cost before readiness. I did not benchmark it, so no latency number is claimed.
151
152**Classification.** This is a preexisting V2 design/performance gap, not a regression newly introduced by the final rebase. V1's startup read model also scans all thread shells, but it does not hydrate every transcript row, so this is not classified as a main-both bug.
153
154**Minimal fix.** Add an indexed recovery-candidate query that returns only thread IDs with a nonterminal run, pending request, live provider session/background item, or pending/running process-bound outbox entry. Then load a narrow recovery projection for only those IDs. Add a startup test containing many large terminal archived threads and assert that they are not passed to full projection reads.
155
156## Rejected candidate
157
158### Source-control lookup with inactivity and merge settings disabled is intentional
159
160An earlier draft classified recurring Git/PR lookup as unnecessary when `sidebarAutoSettleAfterDays` is null and `sidebarAutoSettleOnMerge` is false. Reviewer feedback correctly rejected that conclusion.
161
162Closed pull requests are an always-on settlement rule. V2 accepts `state === "closed"` regardless of `autoSettleOnMerge` and only gates the `"merged"` state on that setting (`apps/server/src/orchestration-v2/ThreadSettlementService.ts:71-91`). `shouldAutoSettleThread` evaluates that PR rule before the optional inactivity rule (`:119-140`). Main V1 has the same policy (`apps/server/src/orchestration/ThreadSettlementPolicy.ts:45-86`), and its test explicitly expects a closed request to settle with merge settlement disabled (`ThreadSettlementPolicy.test.ts:65-66`).
163
164The retained audit probe confirms that a closed PR newer than the user's last action settles with both optional settings off. Therefore the sweep must inspect PR state to preserve current product behavior. An early return based only on those settings would be a regression. No performance finding or fix recommendation remains for this candidate.
165
166## Intentional differences and separately owned gaps
167
168| Area | Classification and result |
169| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
170| Continue active threads after server update (#9167) | Explicitly deferred for V2 at this head. V2 recovery terminalizes interrupted work rather than persisting main's continuation marker. This is a feature-intent gap owned by the root review, not reported above as an accidental persistence regression. |
171| Automatic title retry (#8087) | The root review independently confirmed that V2 lacks main's bounded retries. It is omitted from this report's findings to avoid duplicate ownership. |
172| Provider adapters and client surfaces | Excluded by assignment and covered by other reviewers. Persistence-facing provider state was reviewed, but adapter protocol correctness was not. |
173| Legacy import retention | Keeping `projection_threads` after import is current design and is not itself labeled a bug. The project-deletion coordinators are buggy because they assume one representation while both remain authoritative for different paths. |
174
175## Prior-fix retention status
176
177The supplied `prior-fixes-range-diff.txt` shows the previous review patch series rebased through `c1791ab2637`. I rechecked the production paths, not only patch identity.
178
179| Prior invariant | Status at frozen head | Current evidence |
180| ----------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181| Persist current token usage | Preserved | Live merge retains existing usage when a terminal update omits it (`apps/server/src/orchestration-v2/ProjectionStore.ts:159-169`); SQL projection reads the prior payload before overwrite (`:1556-1574`). |
182| Persisted provider usage | Preserved | Same SQL merge path above; `ProviderTurnTokenUsage.test.ts:12-42` covers the terminal omission case, and `ProjectionStore.test.ts:93-199` covers persistence. |
183| Import legacy pins/order/snooze/PR metadata | Preserved | Import mapping at `LegacyV1ThreadImporter.ts:142-210`; repair selection and patch at `:390-457`. |
184| Repair already imported legacy shells | Preserved | Metadata-repair events at `LegacyV1ThreadImporter.ts:390-457`; focused importer tests passed. |
185| Migrated ownership/archive/deletion search | Preserved | Search joins V1 and V2 ownership and filters both lifecycle representations at `apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts:814-877`; search tests passed. |
186| Missing-worktree send recovery | Preserved | Existence check and recreate-before-turn path at `apps/server/src/orchestration-v2/ProviderTurnStartService.ts:142-177`; focused tests passed. |
187| Unsettled ordering/state | Preserved | Explicit settlement transitions retain/reanchor `unsettledAt` at `apps/server/src/orchestration-v2/Orchestrator.ts:1574-1598`; automatic completion path at `:2870-2893`; runtime tests passed. |
188| Automatic-completion queue order | Preserved | `queuedRunsInDeliveryOrder` prioritizes delegated completion, then queue position and ordinal (`apps/server/src/orchestration-v2/QueuedRunOrder.ts:12-31`); focused tests passed. |
189| SQL visible cohort before `LIMIT` | Preserved | Eligibility filters rollback, cancelled queued messages, superseded interrupts, source cutoff, and required-run handling before `selected ... LIMIT` (`apps/server/src/orchestration-v2/ProjectionStore.ts:2031-2126`). Projection/history tests passed. |
190| Paired stop-request dependency | Preserved | SQL retained cohort adds the matching `run_interrupt_request` (`ProjectionStore.ts:2101-2110`); bounded projection also reserves dependencies (`threadHistoryPaging.ts:400-448`). |
191| Nested fork cursor identity and empty ancestors | Preserved | Cursor source identity at `threadHistoryPaging.ts:92-140` and `:218-249`; recursive source anchoring and empty-ancestor suppression at `ProjectionStore.ts:2467-2589`. Focused nested-fork tests passed. |
192| History actual-end pagination | Preserved | SQL fetch uses max page plus two rows (`apps/server/src/orchestration-v2/http.ts:132-149`); history response uses the computed page end (`:216-254`). Focused pagination tests passed. |
193| Snapshot/detail/control-payload budget | Preserved | Central budgets and control-plane reservation at `apps/server/src/orchestration-v2/threadHistoryPaging.ts:10-46`, `:381-496`; bounded HTTP exposes overflow status at `apps/server/src/orchestration-v2/http.ts:195-212`. |
194| Waiting checkpoint recovery | Preserved, with separate diff bug above | Recovery leaves a waiting run intact when its checkpoint effect is replayable (`ProviderRuntimeRecoveryService.ts:134-155`); checkpoint capture is at-least-once (`CheckpointCaptureService.ts:68-93`). |
195| Outbox recovery and settlement | No new correctness regression found | Reconciliation requeues replayable work and retires process-bound work; focused recovery, runtime-layer, checkpoint-capture, run-finalization, and effect-worker tests passed. The full-state startup cost is finding 7. |
196
197## Coverage
198
199| Area | Files/path inspected | Result |
200| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
201| Migration numbering and runner semantics | `Migrations.ts`, old `c179` manifest, current 044/053, Effect `Migrator.ts` | Blocker confirmed with real in-memory runner. |
202| Projection persistence and rebuild | `ProjectionStore.ts`, `ProjectionMaintenance.ts`, event projection tests | Prior state/usage/history fixes retained; no additional data-loss finding. |
203| Windowing/history SQL | `ProjectionStore.ts`, `threadHistoryPaging.ts`, V2 HTTP handlers | Prior SQL visibility, dependency, fork, pagination, and budget fixes retained. |
204| Legacy import and repair | `LegacyV1ThreadImporter.ts`, importer tests, V1 search query | Metadata/search fixes retained; preserved V1 rows expose deletion boundary bug. |
205| Runtime recovery | `ProviderRuntimeRecoveryService.ts`, `serverRuntimeStartup.ts`, recovery tests | Semantics tests pass; all-thread full hydration cost confirmed. |
206| Queue and settlement | `QueuedRunOrder.ts`, `ThreadSettlementService.ts`, orchestrator settlement commands | Queue fix retained; snooze ordering regression confirmed; disabled-policy cost candidate rejected because closed-PR settlement is always active. |
207| Effect queue/outbox/leases | `EffectOutbox.ts`, `EffectWorker.ts`, runtime layer and worker tests | No additional correctness finding; recovery candidate query remains too broad. |
208| Checkpoint scheduling/capture/diff | `CheckpointCaptureService.ts`, `CheckpointService.ts`, `IdAllocator.ts`, `RunExecutionService.ts`, `CheckpointDiffQuery.ts` | Capture/replay invariant retained; ordinary multi-run shared-scope diff bug and full-read cost confirmed. |
209| Project lifecycle boundary | WS, HTTP, CLI, `ProjectService`, V1 decider/read model | Two high-severity preexisting transport/coordinator bugs confirmed. |
210| New main server features | Main-since-prior inventory, checkpoint optimizations, settlement, update continuation, project removal/model migration | Relevant discrepancies classified above; continuation/title are separately owned. |
211
212## Focused validation
213
214All commands used repository-local disposable state or mocks. No repo-wide check was run.
215
216| Test group | Result |
217| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
218| `EffectWorker.test.ts`, `ThreadSettlementService.test.ts`, migration 045/046 test | 3 files, 24 tests passed |
219| `ProjectionStore.test.ts`, `threadHistoryPaging.test.ts`, legacy importer, two runtime recovery suites, provider-turn start/usage, queue order | 8 files, 45 tests passed |
220| Search, runtime layer, checkpoint capture, run finalization | 4 files, 16 tests passed |
221| `CheckpointDiffQuery.test.ts` | 1 file, 5 tests passed; the current missing-first-scope expectation is part of finding 5 |
222| Product-test subtotal | 16 files, 90 tests passed |
223| Retained audit-only migration/scope/policy probes | 1 file, 3 tests passed |
224
225The retained `audits/orchestrator-v2/2026-09-02/persistence-runtime-probes.test.ts` uses the actual migration runner with in-memory SQLite, the real checkpoint allocator and projection reducer, and the production V2 settlement policy. Its fixtures use typed domain events, branded identifiers, real `DateTime.Utc` values, `emptyProjection`, and `threadShellFromProjection`; there are no permissive projection/event casts. Its migration case expects and captures the current 053 failure, so the probe suite itself passes while reproducing the blocker. Its scope case confirms that two root-run allocations produce one shared scope row whose `runId` is overwritten by run 2. Its policy case records the reviewer rejection above. Audit probes are not included in the 90-product-test total.
226
227Exact rerun command and output are retained in `audits/orchestrator-v2/2026-09-02/persistence-runtime-probes.log`.
228
229## Remaining validation gaps
230
231- I did not mutate product tests to add the missing historical-manifest fixtures. The retained audit-only probe uses the same migration loader and SQL layer for the confirmed old-052 cohort; other supported partial old-branch stopping points still need table-driven validation.
232- I did not invoke a live WebSocket/HTTP server or CLI. The project-deletion outcomes are deterministic source traces across typed handlers, committed V2 commands, and the legacy decider. An integrated disposable server test should be added with the fixes.
233- I did not benchmark startup recovery or checkpoint diff. Cardinality claims above follow the explicit unwindowed SQL and loops; they do not claim measured latency or memory.
234- I did not test live GitHub/PR providers. Settlement PR grouping and local Git call order were inspected statically.
235- Provider adapters, browser clients, desktop/mobile behavior, build/auth compatibility, and title retries are assigned to other reviewers/root.
236- The explicit server-update continuation gap needs a product decision and V2 design; it is not silently counted as passing parity.
audits/orchestrator-v2/2026-09-02/prior-fixes-range-diff.txt+44−0
@@ -0,0 +1,44 @@
1 1: 1a5dc7e5d6 = 1: 34a4d026a5 fix(server): keep Claude session approvals ephemeral
2 2: d853e44391 < -: ---------- fix(web): keep project picker popup inside the sidebar
3 3: 1b7221098f < -: ---------- fix(mobile): prevent header overflow and back-button artifacts
4 4: f5fb255599 = 2: 7e71ff8a80 fix(orchestration): reanchor unsettled threads
5 5: 5f4cd75874 = 3: 6b3690e063 fix(server): observe pre-aborted Claude approvals
6 6: a0e7fa2f3e = 4: feedc3f6bb fix(server): include service launcher in bundle build
7 7: 893b1c947a = 5: 82e1b8ed60 fix(orchestration): preserve legacy thread metadata
8 8: d7fa7f7133 ! 6: 930dd22e72 fix(web): honor disabled legacy plan mode
9 9: e127cf7480 = 7: 2cfe234e46 fix(server): preserve Claude subagent models
1010: 1340b474d6 = 8: eacc09d40a fix(orchestration): honor migrated thread visibility in search
1111: 0090064448 = 9: dcff638944 fix(orchestration): recreate missing worktrees before turns
1212: 1b0437897c ! 10: e44588afa4 fix(clients): restore Codex feedback submission
1313: 33607d9bcb = 11: 8bbf7f50c8 fix(server): preserve generic provider attachments
1414: 532feb8b88 ! 12: 53c6857ec9 fix(web): load workspace markdown images through assets
1515: cfa734416d < -: ---------- fix(web): preserve Windows markdown paths
16 -: ---------- > 13: 7fca26e6fb fix(web): preserve Windows markdown paths
1716: 201dfc6c7e = 14: 642056fdd9 fix(server): keep current provider context usage
1817: 3a846888a8 < -: ---------- fix(web): restore markdown file chip actions
19 -: ---------- > 15: 5f19d550ae fix(web): restore markdown file chip actions
2018: 0bb0d2d36e ! 16: 44f2004810 fix(web): scope markdown actions to their environment
2119: 37cfa6296d < -: ---------- fix(web): restore pull request markdown actions
2220: ae6d09b0a8 = 17: 995f4e7e5a fix(protocol): reject incompatible orchestration peers
2321: 1677769660 ! 18: 3e1ff1531d docs: explain legacy thread migration
2422: 14b4b8966d = 19: 19a30d03be docs: state portable handoff limits
2523: 335bad52e0 = 20: 68104a497e chore(repo): remove tracked audit scratch files
2624: 6db9f290b2 = 21: 98670f337e fix(server): guard OpenCode prompt admission races
2725: 686fd94e5d = 22: ecf3dd2c9a fix(server): restore Claude structured questions
2826: cb8b2b5125 = 23: ff9f875f17 fix(server): project Claude plans and todos
2927: d9cc96b47b = 24: 9a4734af8e perf(orchestration): bound history reads in SQL
3028: e05e23f1e4 = 25: 0921786018 perf(orchestration): bound complete thread snapshots
3129: b0a77b042e = 26: 3d5bab73d8 fix(server): restore Claude resume compaction
3230: 825b95aed9 < -: ---------- fix(server): allow protocol negotiation in CORS
33 -: ---------- > 27: 733db7269f fix(server): allow protocol negotiation in CORS
3431: 63d9e93e62 = 28: 26dfbc2984 fix(server): preserve provider usage in persisted turns
3532: 3cd9445411 = 29: 70f82caaa0 fix(server): preserve Claude planning lifecycle
3633: 48e2ea9892 = 30: dbb6021f84 fix(server): normalize Claude question answers
3734: b8b10b3f9f = 31: de79f44221 fix(server): correlate OpenCode prompt admission
3835: 06483fbdfa = 32: be7b07634b fix(clients): anchor feedback in conversation order
3936: 52de095cf3 = 33: 051565fc96 fix(web): retain markdown workspace ownership
4037: 25b367a4c8 = 34: 870014496f fix(orchestration): page history through its true end
4138: d6a5a888bc = 35: 07889933e3 fix(server): cancel pending OpenCode prompts safely
4239: 677dd4f4e5 = 36: 0fc565db8a fix(orchestration): retain nested fork history when paging
4340: 0079d8b918 = 37: 47ae99f517 fix(server): recover OpenCode status reconciliation
4441: 47f5b10044 = 38: c1791ab263 fix(orchestration): select visible history before limiting SQL
audits/orchestrator-v2/2026-09-02/providers.md+188−0
@@ -0,0 +1,188 @@
1# Orchestrator V2 provider behavior audit
2
3Date: 2026-09-02
4Audited HEAD: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0`
5Comparison main: `57a66608b918d673eeec7e6c94ea5906b756fcd0`
6Prior reviewed object: `47f5b100440591d2f49aa30cf3bb69eacae07f52`
7Rebased prior-equivalent tip: `c1791ab2637`
8
9## Verdict
10
11The supplied main is an ancestor of the frozen HEAD, and the branch is 332 commits ahead. The previously reported provider fixes survive the rebase. I found five confirmed behavior regressions against current main V1:
12
131. Cursor background text generation runs a full agent with its sandbox and approval boundary disabled.
142. Claude workspace image reads do not become image previews in either current V2 client projection.
153. OpenCode Stop reports descendant cleanup as successful even when child discovery or abort fails or times out.
164. Releasing an OpenCode V2 session connected to an external server never asks that server to abort root or child work.
175. V2 reconnect and HTTP-failure fallbacks bypass the bounded thread-snapshot path and can send the full lifetime projection over WebSocket.
18
19The Cursor SDK migration itself is intentional, but I found no evidence that removing the metadata-generation write guard was an accepted part of it. The Claude result is a missing user-visible main feature. The OpenCode findings partially port main's `#9005` fix but leave reachable stop/teardown behavior weaker than V1. The snapshot issue is not the ordinary healthy cold-open path—the bounded HTTP request protects that case—but it is an active fallback used by both current clients.
20
21## Confirmed regressions
22
23### P1 — Cursor metadata generation runs with unrestricted write/tool access
24
25**Current V2 evidence:** `apps/server/src/textGeneration/CursorTextGeneration.ts:75-157`.
26
27- Every Cursor commit message, change-request description, branch name, and thread title calls `Agent.prompt` with `mode: "agent"`, `local.autoReview: false`, and `local.sandboxOptions.enabled: false` (`90-104`; callers at `159-251`). The focused test explicitly pins those options but mocks the SDK, so it does not exercise their permission behavior (`CursorTextGeneration.test.ts:38-85`).
28- The installed dependency is `@cursor/sdk@1.0.22`. Its public types expose only `"agent" | "plan"` modes and a boolean sandbox switch (`node_modules/.pnpm/@cursor+sdk@1.0.22/node_modules/@cursor/sdk/dist/esm/options.d.ts:3-4, 63-65, 108-145, 218-240`). Inspection of the installed runtime bundle shows that `enabled: false` immediately selects `defaultSandboxPolicy: { type: "insecure_none" }`, before considering the loaded per-user sandbox policy; because that policy is insecure and `autoReview` is false, it supplies neither `approvalMode` nor a `pendingDecisionProvider` (`dist/esm/357.js:1`). The same bundle maps `mode: "agent"` to the native `AGENT` mode and implements `Agent.prompt` as create/send/wait/dispose, so this is the normal tool-capable local agent path rather than a text-only inference call.
29
30**Main evidence:** supplied main's `apps/server/src/textGeneration/CursorTextGeneration.ts:85-105` starts Cursor ACP and deliberately calls `runtime.setMode("ask")` before prompting. The accompanying Cursor ACP capability names Ask as “Request permission before making any changes,” contrasted with Code's full tool access (`apps/server/scripts/acp-mock-agent.ts` at supplied main: `271-287`). The SDK migration commit `6c78a01831d` documents the intentional boundary/auth/model-discovery change, but neither its message nor its implementation records an intentional relaxation for background metadata generation.
31
32**Reachable trigger:** Select Cursor as the text-generation/source-control writer. Initial and regenerated titles invoke the service against the real worktree (`ThreadTitleRegenerationService.ts:184-245`), commit generation passes the real source-control cwd (`GitManager.ts:1618-1657`), and automatic worktree naming does likewise (`ThreadLaunchService.ts:178-213`). The title prompt explicitly tells the model to use available tools for linked-only context (`TextGenerationPrompts.ts:219-243`). Any resulting shell, edit, or delete tool call executes without main's ask boundary; the user message and diff content supplied to these prompts can also request or induce such a call.
33
34**User impact:** A background title/branch generation or a commit/PR-copy request can modify or delete workspace files, run commands, or perform other agent actions even though the operation is presented as metadata generation. Explicitly disabling the SDK sandbox also overrides a user's configured Cursor sandbox for these calls.
35
36**Minimal recommendation:** Restore an enforced non-writing boundary for Cursor text generation before using `Agent.prompt`. The installed SDK does not expose an Ask mode, and `sandboxOptions.enabled: true` maps to workspace read-write, so neither that flag nor an unverified switch to Plan should be treated as equivalent. Use a documented SDK text-only/read-only policy if one is added; otherwise retain a guarded metadata path or do not offer Cursor for these background generators. Add a behavioral integration test with a fake/local SDK runtime that attempts shell and file-write tools and proves they are denied; do not merely assert option shape.
37
38### P1 — OpenCode Stop swallows descendant cleanup failures
39
40**Current V2 evidence:** `apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts:2986-3069`.
41
42- V2 first waits for `session.abort` on the root (`3013-3029`). It then describes descendant cleanup as best-effort (`3030-3033`).
43- A failed `session.children` call is converted to `Option.none` (`3038-3043`), each child `session.abort` failure is ignored (`3049-3055`), and the whole traversal's 15-second timeout or failure is ignored (`3058-3059`). The outer `ProviderAdapterInterruptError` mapping therefore never sees any descendant cleanup failure.
44- V2 also waits up to ten seconds for the root HTTP abort even when native session events have already acknowledged or completed cancellation (`3013-3029`).
45
46**Main evidence:** commit `62d39bf00d` (`fix(server): stop OpenCode child sessions (#9005)`). At main, `apps/server/src/provider/Layers/OpenCodeAdapter.ts:707-774` walks all descendants and returns the first non-not-found list/abort failure. Its interrupt state machine at `2987-3122` races the HTTP request with native acknowledgment/completion and propagates a descendant failure or timeout unless the turn has independently completed.
47
48**Reachable trigger:** An OpenCode turn has spawned a task child or grandchild and the user presses Stop while `session.children` or a child `session.abort` errors or hangs. A slow root abort request with a prompt native acknowledgment also reaches the avoidable delay.
49
50**User impact:** T3 can acknowledge Stop while one or more OpenCode child sessions continue executing commands or modifying files. Failures leave no caller-visible indication. The root abort can also keep the Stop command pending for up to ten seconds after native cancellation is already known.
51
52**Minimal recommendation:** Port main's cancellation state machine into V2: keep prompt-admission cancellation, race root abort against native acknowledgment/terminal completion, traverse descendants with the cycle guard and bounded concurrency, ignore only explicit not-found responses, and propagate the first other failure or timeout while the cancellation is still live. Add adapter tests for nested children, list failure, child-abort failure, timeout, and acknowledgment winning a pending HTTP request.
53
54### P1 — External OpenCode work survives V2 session release
55
56**Current V2 evidence:**
57
58- An OpenCode session may connect to an external configured server at `apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts:882-902`.
59- Its only explicit scope finalizer aborts the local SSE subscription controller at `apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts:2488-2496`. The only root/descendant `session.abort` calls in this adapter are inside `interruptTurn` at `3013-3059`; session release has no corresponding path.
60- The V2 runtime interface has no close/teardown callback (`apps/server/src/orchestration-v2/ProviderAdapter.ts:470-535`). The manager releases sessions by closing the adapter scope (`apps/server/src/orchestration-v2/ProviderSessionManager.ts:611-706`) for idle timeout, runtime error, manual shutdown, and server shutdown (`ProviderSessionManager.ts:47-52, 761-839, 1361-1389, 1555-1565`).
61- Normal server shutdown calls `providerSessions.shutdown` before terminalizing durable work (`apps/server/src/serverRuntimeStartup.ts:386-402`). For an external OpenCode server, closing T3's scope only disconnects its event stream; there is no owned local child process whose teardown could stop the remote sessions.
62
63**Main evidence:** the same main commit, `62d39bf00d`. Main implements `abortOpenCodeSessionForTeardown` at `apps/server/src/provider/Layers/OpenCodeAdapter.ts:776-787` and calls it from normal context stop at `828-860`. It explicitly aborts the parent, snapshots the child tree, and aborts descendants before closing local handles.
64
65**Reachable trigger:** Use an OpenCode provider configured with `serverUrl`, start work, then shut down T3, manually close/detach the single-thread session, hit the idle-release path, or lose the V2 event stream without first completing a successful explicit interrupt.
66
67**User impact:** T3 marks/releases the session and loses its event stream while the external OpenCode server can continue root or child work unseen. On shutdown this can leave commands and file writes running after the local UI/server has stopped.
68
69**Minimal recommendation:** Add a provider-runtime teardown effect (or an OpenCode-specific scope finalizer) that best-effort aborts the root and the complete descendant tree before the SSE subscription is closed. Preserve strict failure reporting for explicit Stop, but time-box and log teardown failures. Cover external connections and all four manager release reasons in focused tests.
70
71### P1 — V2 WebSocket snapshot fallbacks bypass the bounded history path
72
73**Current V2 evidence:**
74
75- The server advertises `threadSnapshotPagination`, but the V2 subscription input has no window/limit field (`apps/server/src/ws.ts:770-792`; `packages/contracts/src/orchestrationV2.ts:2430-2443`). Both a subscription without `afterSequence` and a resume rejected by the 128-event or 1 MiB replay limits call `snapshotThenLive` (`apps/server/src/ws.ts:829-922`; limits at `apps/server/src/orchestration-v2/ThreadStream.ts:1-50`). That function calls the unwindowed `getThreadSnapshot`, projects it, and emits it as one socket item (`ws.ts:860-884`).
76- The call remains unwindowed through `ThreadManagementService` (`apps/server/src/orchestration-v2/ThreadManagementService.ts:413-423`). `ProjectionStore` consequently reads every turn item and all rows in each projection collection without a `LIMIT` when `window` is absent, and recursively does the same for inherited source-thread history (`apps/server/src/orchestration-v2/ProjectionStore.ts:2023-2030, 2146-2399, 2467-2589, 2601-2626`). The bounded SQL read is a separate API at `2628-2677`.
77- Wire projection caps individual detail/dynamic values at 32 KiB/16 KiB, but maps every `turnItems` and `visibleTurnItems` entry; it does not cap collection cardinality (`apps/server/src/orchestration-v2/WireProjection.ts:7-99`). The bounded HTTP implementation instead budgets at most 75 timeline items and about 1 MiB, including the duplicate local-item cost across those two arrays (`apps/server/src/orchestration-v2/threadHistoryPaging.ts:9-16, 61-79, 420-496`).
78- The current web and mobile runtimes both install the shared bounded HTTP loader. A healthy cold open therefore gets the intended bounded snapshot first. The loader has a six-second budget and deliberately returns `unavailable` on transient failure so the socket is used (`packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts:23-65, 109-156`). When no projection was installed, the client subscribes without `afterSequence`; a warm cached projection skips HTTP and subscribes with its sequence (`packages/client-runtime/src/state/threads.ts:580-647`; the latter behavior is pinned at `threads-sync.test.ts:289-308, 472-507`). A received socket snapshot is installed as a full replacement and clears progressive-history metadata (`threads.ts:288-307`), so this is neither a dead nor control-plane-only fallback.
79
80**Main evidence:** Main `#8992` (`7e460f429b`) bounds replay and falls back to a snapshot; `#9000` (`a9ffb827961`) adds bounded snapshot activity reads/projection. Crucially, current main's subscription contract accepts `turnLimit` and defines it as the fallback-snapshot window (`packages/contracts/src/orchestration.ts` at supplied main: `630-653`). The client always sends its ten-user-turn initial limit to pagination-capable servers specifically so a missing cursor or failed resume does not redownload the full thread (`packages/client-runtime/src/state/threads.ts` at supplied main: `43-50, 560-637`), and the server passes that limit into `getThreadDetailSnapshot` on the fallback path (`apps/server/src/ws.ts` at supplied main: `1525-1654`). Main `#9032` (`e86604d337`) avoids repeated full-message reads while streaming; it does not close or establish this snapshot bound and is not used as proof for the finding.
81
82**Reachable trigger:** Either (1) open a thread with an empty client cache while the bounded HTTP request times out or fails transiently, or (2) reconnect/foreground a warm cached thread after more than 128 thread events or more than 1 MiB of encoded replay events accumulated. The first subscribes without a cursor; the second makes `decideThreadResume` choose snapshot. Both reach the same unwindowed snapshot call.
83
84**User impact:** On a long-lived thread, the server queries and decodes every historical row across the projection tables (and inherited parent history), serializes the full projection, and sends it as one WebSocket snapshot. Work and payload size therefore grow with lifetime thread cardinality despite the advertised paginated path; remote clients are especially exposed when the bounded HTTP request is the part that failed. The client then loses its load-earlier cursor because it treats the socket result as complete. This audit proves the unbounded cardinality and reachable paths from source; it did not measure a percentage, frame size, or memory peak.
85
86**Minimal recommendation:** Extend the V2 subscription with a pagination-capable fallback request, then build its socket snapshot through `getThreadSnapshotWindow` plus the existing bounded projection/budget logic and carry progressive-history metadata in the stream item. Preserve an explicit full fallback for legacy clients if needed. Add focused cases for a warm bounded cache whose gap exceeds 128 events and a cold loader-unavailable path; assert a bounded timeline, retained live control state, a usable history cursor, and the payload-budget signal rather than a timing percentage.
87
88### P2 — Claude workspace image reads lose image-preview projection
89
90**Current V2 evidence:**
91
92- Claude's V2 tool classifier only permits `command_execution`, `file_change`, `dynamic_tool`, and `web_search`; `Read` is always `dynamic_tool` (`apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:1392-1447`).
93- The actual tool-start caller has the parsed `toolInput` but calls `classifyClaudeNativeTool(input.toolName)` without it (`ClaudeAdapterV2.ts:3601-3630`). Artifact construction can consequently emit only those four item types (`ClaudeAdapterV2.ts:3001-3039`).
94- V2 does retain the native Read input in the generic `dynamic_tool.input` field (`ClaudeAdapterV2.ts:3001-3039`; contract at `packages/contracts/src/orchestrationV2.ts:1069-1075`), so contract shape alone does not prove the preview is lost. The actual client projections do: web puts the input only in `toolData` and supplies neither `detail` nor `viewedImagePath` (`apps/web/src/session-logic.ts:591-597`), and mobile does the same (`apps/mobile/src/lib/threadActivity.ts:467-472`).
95- The shared preview detector reads only an explicit `viewedImagePath` or a single-line `detail` on a Read entry; it never inspects `dynamic_tool.input` (`packages/client-runtime/src/work-log/presentation.ts:354-385`). Consequently the web renderer receives `null` at `MessagesTimeline.tsx:3275-3282` and cannot enter its image branch at `3378-3389`; mobile likewise receives `null` at `apps/mobile/src/features/threads/thread-work-log.tsx:651-665` and skips its image branch at `759-770`. The expanded inspector renders structured input/output JSON only (`apps/web/src/components/chat/V2ItemInspector.tsx:227-244`).
96
97**Main evidence:** commit `d0b19b32e0` (`fix(claude): preview images read from the workspace (#9119)`). Main's `apps/server/src/provider/Layers/ClaudeAdapter.ts:717-727` recognizes only `Read`/`Read file` calls whose `file_path` or `path` passes `isWorkspaceImagePreviewPath`; `730-777` classifies them as `image_view`; `1188-1192` retains the path as display detail. Main's regression test at `apps/server/src/provider/Layers/ClaudeAdapter.test.ts:1438-1574` verifies the streamed Read input transitions from a generic start to `image_view` updates/completion carrying the image path.
98
99**Reachable trigger:** Claude invokes its `Read` or `Read file` tool for a supported workspace image such as PNG, JPEG, GIF, or WebP.
100
101**User impact:** V2 renders a generic Read tool row (and structured JSON when expanded) instead of main's inline workspace image preview on both web/desktop and mobile.
102
103**Minimal recommendation:** A new turn-item variant is not required. On both client projections, recognize `dynamic_tool` names `Read` / `Read file`, extract `file_path` or `path` from `input`, validate it with the shared workspace-image predicate, and populate `viewedImagePath`; alternatively project the same explicit path server-side if maintainers want a provider-neutral contract field. Preserve the generic-start-to-image transition when streamed partial JSON reveals the path only later. Add the main streamed-input regression case plus web and mobile projection/render coverage.
104
105## Prior-fix status
106
107| Prior concern | Status at frozen HEAD | Evidence |
108| -------------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
109| Generic attachments for all providers | Preserved | Shared path text is built by `AttachmentPrompt.ts:11-25`; Claude, Codex, Cursor, ACP/Grok, and OpenCode all call it on their actual prompt paths. Native image blocks remain additive where the provider supports them. The focused attachment/adapter tests passed. |
110| Claude session-scoped permission suggestions | Preserved | `ClaudeAdapterV2.ts:1894-1945` rewrites every suggested permission destination to `session` and synthesizes a whole-tool session rule if none was supplied. |
111| Claude pre-aborted approval/user-input signals | Preserved | `ClaudeAdapterV2.ts:1948-1973` installs the listener and immediately observes `signal.aborted`; cancellation listeners are removed by the callback finalizer. |
112| Claude structured and multi-select questions in all permission modes | Preserved | `AskUserQuestion` is handled before the permission-callback policy gate at `ClaudeAdapterV2.ts:4714-4807`; `canUseTool` is installed unconditionally at `5029-5031`. The full-access structured-question test passed. |
113| Claude plan/todo identity and lifecycle | Preserved | Stable per-native-item IDs and latest-per-kind supersession live at `ClaudeAdapterV2.ts:2417-2420, 3468-3598`; the generic completion regression test passed. |
114| Claude compaction controls/resume | Preserved | `autoCompactWindow` and `resumeSessionAt` are compiled at `ClaudeAdapterV2.ts:730-753`; the resume dialog is projected through structured user input at `4882-4955`; resumed queries pass the native head at `4972-5031`. |
115| Claude/Codex token usage | Preserved | Claude emits root-assistant usage at `ClaudeAdapterV2.ts:4359-4387`; Codex retains its usage conversion and provider-turn updates. `ProviderTurnTokenUsage.test.ts` passed. |
116| Claude subagent model propagation | Preserved | Pending assistant-snapshot models are retained until task registration and used by the subagent lifecycle; the ordering regression test (`keeps a subagent snapshot model that arrives before task_started`) passed. |
117| OpenCode native message correlation | Preserved | Each initial/steer prompt receives a generated `admissionMessageId` (`OpenCodeAdapterV2.ts:2787-2845, 2929-2955`), and only the matching native user message advances admission (`2256-2266`). |
118| OpenCode pending initial/steer prompt cancellation | Preserved | Initial and steer requests combine the SDK signal with a turn-owned abort controller; interrupt invalidates the generation and aborts it (`OpenCodeAdapterV2.ts:2787-2850, 2929-2965, 3000-3011`). |
119| OpenCode generation-owned status retry | Preserved | Admission reconciliation checks turn identity and generation before every status request and adoption (`OpenCodeAdapterV2.ts:2017-2091`). Transient-retry, stale-generation, delayed-reply, and abort-winning-status tests passed. |
120
121## Current-main feature coverage
122
123| Main provider change since the prior reviewed base | V2 status |
124| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
125| Claude project/per-cwd skills (`bc918e74ac`, later `#9210`) | Driver-side `snapshotForCwd` is present, and Claude's actual query path also resolves cwd skills. Covered, with the refresh caveat below. |
126| Codex/OpenCode per-cwd skills (`80a14b6588`) | Driver `snapshotForCwd` implementations are present for both. Web requests cwd snapshots. Covered for the web entry path; execution-trigger refresh parity remains unverified. |
127| Remote Claude model manifest (`0354283683`) | Shared driver/catalog path is used by V2. Covered. |
128| OpenCode bounded version probes (`4116db9807`) | Shared OpenCode runtime/driver path is used by V2. Covered. |
129| Removed custom models disappear (`941acb4f91`) | Shared provider registry and client selection normalization apply to V2. Covered. |
130| Grok health/model selection/Stop (`a434677eca`, `7880a6e583`) | V2 ACP flavor carries live model switching and Grok-specific interrupt/runtime-restart controls. Covered in the shared runtime path. |
131| Claude workspace image preview (`d0b19b32e0`) | **Missing; confirmed regression above.** |
132| OpenCode child-session stop (`62d39bf00d`) | **Partial; explicit interrupt traversal exists, but failure semantics and release teardown are missing.** |
133| Bounded V1 persisted-session lookup (`9a7b1e21e5`) | Not directly applicable: V2 uses `ProviderSessionManagerV2`'s keyed live-session map and projection APIs rather than V1's ProviderService scan. |
134| Provider-event leak/idle CPU fixes (`0bfb6df34b`) | The OpenCode assistant merge change is present; other changes target V1 ingestion/logging or shared runtimes. No V2-specific regression found in this provider audit. |
135| Active tool-update frame coalescing (`7e4ce3bbb1`, `#8368`) | No equivalent V2 thread-detail coalescer was found. This is a performance validation gap, detailed below; the audit did not reproduce or quantify the main PR's 90% reduction against V2. |
136| Bounded replay/snapshot work (`#8992`, `#9000`, `#9032`) | **Partial.** V2 bounds replay and has a bounded HTTP snapshot, but its no-cursor and over-budget WebSocket fallbacks use the full-cardinality projection; confirmed regression above. `#9032` concerns streaming-message reads and was not treated as snapshot proof. |
137| Cursor text-generation Ask guard (present at supplied main) | **Missing after the intentional Cursor SDK migration; confirmed regression above.** |
138
139Provider execution paths inspected: Claude Agent SDK; Codex app-server; Cursor Agent SDK; OpenCode SDK/SSE; Grok through the flavored ACP adapter; ACP Registry through the generic ACP adapter; provider driver creation, model/skill snapshot plumbing, session lifecycle, and the relevant orchestration V2 contracts.
140
141## Intentional differences
142
143- **Restart continuation is deliberately withheld in V2.** Main's `5b7d72aad1` / `#9167` keeps active V1 threads resumable across server restarts. The frozen HEAD's commit message explicitly says the server-side continuation markers were not ported because they live in the V1 session directory and V2 recovery terminalizes running runs. That matches `ProviderRuntimeRecoveryService.ts:130-190, 450-526`. I do not classify this as a regression without a V2 durability design.
144- **Cursor uses the Cursor Agent SDK rather than main V1's ACP boundary.** Its orchestration capabilities intentionally advertise no live approvals/structured questions and no native fork/rollback (`CursorAdapterV2.ts:101-158`). I found no evidence that the chosen SDK boundary exposes equivalent interactive callbacks that V2 is silently dropping. This intentional D01/API-boundary change does not make unrestricted background text generation intentional; that separate loss is the confirmed finding above.
145- **V2 session residency differs from V1.** The V1 persisted-session lookup optimization does not map one-for-one to V2's keyed in-memory manager. This is an architectural difference, not missing parity.
146
147## Suspected or unverified cases
148
149- **Execution-triggered per-cwd skill refresh:** main V1 refreshes the workspace provider snapshot both after starting a provider session and when reusing one (`ProviderCommandReactor.ts` at supplied main: `660-681, 712-740`). V2 has the registry RPC (`apps/server/src/ws.ts:1574-1587`) and web proactively requests a missing cwd snapshot (`apps/web/src/components/chat/ChatComposer.tsx:1143-1194`), but I found no equivalent refresh in V2 launch/run execution. Claude independently scans skills while forming its query, so execution is safe there; Codex/OpenCode slash-command discovery appears UI-facing. I did not find a reachable current-client failure, so this remains a parity/test gap rather than a confirmed regression.
150- **Active-detail WebSocket coalescing/performance:** main commit `7e4ce3bbb1` adds a semantic, stable-tool-call coalescer with a 50 ms/512-event bound before V1 live thread-detail delivery (`ThreadLiveEventCoalescer.ts` at that commit: `13-14, 20-89, 91-205`; V1 `ws.ts:1499-1513`). The V2 thread subscription maps every stored event directly to a wire event at `apps/server/src/ws.ts:807-827` and uses that same uncoalesced stream for live delivery at `883, 918`. The grouped/coalesced V2 streams at `ws.ts:1001-1026, 1181-1190` are shell/archive paths, not active thread detail. Static provider paths can generate repeated full-state events: Cursor sends a node plus turn-item update on every shell-output delta (`CursorAdapterV2.ts:1094-1102, 1838-1847`), ACP/Grok reprojects tool state for every native tool-call update (`AcpAdapterV2.ts:2604-2613, 3693-3701`), and OpenCode reprojects every tool `message.part.updated` (`OpenCodeAdapterV2.ts:2270-2295`). Codex assistant text has its own adapter coalescer, but each `item/plan/delta` emits full node, plan, and turn-item events (`CodexAdapterV2.ts:3171-3205`). This establishes exposure to chatty frames, not a measured regression: V2 event shapes, persistence, client reduction, and provider fixture rates differ from V1, and main's coalescer targeted `tool.updated`, not plan deltas. Add an integrated V2 stream test using a burst of stable tool updates plus an interleaved boundary, and record frame/byte counts on checked-in provider replays before assigning severity or claiming the main PR's 90% figure. Any fix should coalesce only wire delivery, retain persisted events, key by turn plus stable item identity, and preserve sequence/completion-marker semantics.
151
152## Known-main bugs
153
154None identified in the provider behaviors used as comparison evidence. The five findings above are branch omissions or weaker semantics, not defects inherited unchanged from main.
155
156## Verification and gaps
157
158- Read `AGENTS.md` and `.repos/effect-smol/LLMS.md` before assessing Effect scopes, finalizers, races, and error handling.
159- Verified the exact frozen HEAD, supplied main ancestry, and 332-commit count before and after inspection.
160- Ran:
161
162 ```text
163 vp test run \
164 apps/server/src/orchestration-v2/AttachmentPrompt.test.ts \
165 apps/server/src/orchestration-v2/ProviderTurnTokenUsage.test.ts \
166 apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts \
167 apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts
168 ```
169
170 Result: 4 files passed, 96 tests passed.
171
172- Ran the bounded follow-up suite for the Cursor SDK wrapper and the web/mobile work-log projections:
173
174 ```text
175 vp test run \
176 apps/server/src/textGeneration/CursorTextGeneration.test.ts \
177 packages/client-runtime/src/work-log/presentation.test.ts \
178 apps/web/src/session-logic.test.ts \
179 apps/mobile/src/lib/threadActivity.test.ts
180 ```
181
182 Result: 4 files passed, 81 tests passed. These tests confirm the current option and projection shapes; they do not attempt a live provider call or prove tool denial/image rendering, which is why the missing behavioral cases remain gaps below.
183
184- Per instructions, no live provider, server, browser, external OpenCode server, or production state was used. SDK behavior was validated from checked-in types, testkits, full callers, current-main adapter tests, and the exact installed `@cursor/sdk@1.0.22` type declarations/runtime bundle.
185- The focused suite has no Cursor text-generation tool-denial test, no V2 Claude workspace-image projection test, no OpenCode child-list/child-abort failure test, no external-server session-release test, and no V2 large-gap or HTTP-unavailable test asserting a bounded socket snapshot with progressive-history metadata. Those are the direct verification gaps behind the five recommendations.
186- The final WebSocket snapshot check was source-only; no additional test was run because the existing fixtures stop at client subscription inputs or the pure replay decision and do not exercise the server fallback through client history installation.
187- No focused V2 test currently demonstrates semantic coalescing or establishes a frame/byte budget for active tool-update or plan-delta bursts. No performance percentage was inferred from main's benchmark claim.
188- No product or test source was edited. This report is the only file written by this audit.
audits/orchestrator-v2/2026-09-02/references.json+11−0
@@ -0,0 +1,11 @@
1{
2 "branch": "t3code/codex-turn-mapping",
3 "head": "d2f1f511f4cc833bc930d6c355cd0f9b61e835a0",
4 "main": "57a66608b918d673eeec7e6c94ea5906b756fcd0",
5 "merge_base": "57a66608b918d673eeec7e6c94ea5906b756fcd0",
6 "prior_review": "47f5b100440591d2f49aa30cf3bb69eacae07f52",
7 "rebased_prior_review_tip": "c1791ab2637",
8 "date": "2026-09-02",
9 "initial_status": "clean",
10 "mode": "read-only audit; no fixes, commits, browsers, servers or live provider tests"
11}
audits/orchestrator-v2/2026-09-04/AUDIT.md+151−0
@@ -0,0 +1,151 @@
1# Orchestrator V2 re-audit, September 4, 2026
2
3The branch still needs corrective work before rollout. **13 of the previous 17 findings remain open; four are fixed. This audit adds five findings: four missed provider ports and one scheduler scaling defect.** Fifteen main-only commits are assessed separately: eight at the initial capture and seven that arrived during review. Their absence is not evidence of another bad rebase, and V1-specific changes need an equivalent-behavior check before being ported.
4
5The most consequential open paths are historical V2 upgrades, mixed V1/V2 project deletion, full checkpoint diffs after a second run, and provider operations that hang or continue after the UI considers them finished. The recent recovery, query, coalescing, and client rendering fixes are real improvements and are credited below.
6
7This was an audit, not an implementation pass. Three GPT-5.6 Sol reviewers worked in bounded parallel groups; the parent reviewed their evidence and resolved classification disagreements. No product source or existing test was edited by this audit. Artifacts and audit-only probes are kept here, uncommitted, as requested. Existing local work was preserved.
8
9## Reviewed state
10
11| Reference | Value |
12| ------------------------------ | ------------------------------------------------------------------------------------------- |
13| Branch | `t3code/codex-turn-mapping` |
14| Committed target | `8af5734365f7c45bc08b57066dbae42f9f7d4235` |
15| Initial fetched `origin/main` | `d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70` |
16| Main at the final audit cutoff | `f6db4206258b0ef30e8dd8949627acfd209bf338` |
17| Merge base | `c8f77e0d441264efb0acfac312e852c81ae3da83` |
18| Prior audit target | `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0` |
19| Prior audit main | `57a66608b918d673eeec7e6c94ea5906b756fcd0` |
20| Initial comparison | 333 branch-only commits; eight main-only commits; 991 changed files between the final trees |
21| Closing comparison | 333 branch-only commits; 15 main-only commits; 999 changed files between the final trees |
22| Incoming-main inventory | 240 initial commits plus seven late arrivals since the preceding audit's main reference |
23| Pull request | [#2829](https://github.com/pingdotgg/t3code/pull/2829) |
24
25Committed findings use the frozen Git revisions, not whatever another process subsequently writes into this worktree. Local changes are reviewed separately in the domain reports. The initial capture contained 56 dirty paths; the final overlay contains 59. Later changes and their hashes are recorded in `worktree-final-manifest.json` and `worktree-drift.diff`; `closing-references.json` confirms they had not changed again. Frozen copies end in `.snapshot` so they cannot accidentally become product test suites. The local `main` branch is stale and was not used as the baseline.
26
27The initial file and main-commit inventories are in [changed-files.tsv](changed-files.tsv), [main-feature-file-map.tsv](main-feature-file-map.tsv), and [main-since-prior.txt](main-since-prior.txt). [changed-files-final.tsv](changed-files-final.tsv) and [main-late-arrivals.txt](main-late-arrivals.txt) record the closing comparison and seven additional commits. The [feature map](FEATURE-MAP.md) records behavior, surfaces, exceptions, and validation limits. Inventory coverage is not a claim that every changed line or every live integration was executed. Main commits after the explicit cutoff are outside this audit.
28
29## What changed since the previous audit
30
31| Previous item | Current result | Evidence |
32| ----------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33| F02: sidebar initialization crash | **Fixed in committed HEAD.** | `Sidebar.tsx:209-212` declares `EMPTY_PROVIDER_ENTRIES` at module scope before every use. |
34| F14: full transcript hydration for checkpoint diffs | **Fixed in committed HEAD.** | `CheckpointDiffQuery.ts:102-121` uses the three narrow `getCheckpointContext` queries. F03's baseline ownership bug is separate. |
35| F15: recovery loads every active and archived history | **Fixed in committed HEAD.** | Recovery selects `getRecoveryThreadIds("runtime")` and loads only candidates. Corruption verification still scans canonical rows; that is recorded separately as V04. |
36| F17: mobile assistant renderer bypass | **Fixed in committed HEAD.** | The active assistant row now uses the specialized citation/template renderer with its scoped media callback and composer template action. |
37| V01: missing tool-event coalescer | **Implementation gap addressed.** | V2 coalesces live tool updates and bounds buffered/unacknowledged events. A provider-workload byte/frame benchmark is still absent. |
38| V02: cwd catalog refresh outside web | **Broad concern closed.** | Mobile refreshes cwd-specific catalogs; no narrower execution-only stale-catalog failure was established. |
39| F01: historical V2 migration failure | **Still open, reproduced against more histories.** | Actual migrations fail for old 052, prior-audit 053, and pre-reconciliation 055 manifests. Fresh/main-to-V2 and committed 058 to local 059 work. |
40| F05: project transport divergence | **Still open and broadened.** | HTTP/offline CLI now also omit recently added project update fields. |
41
42Range-diff review found 265 equal patches, 64 changed pairs, three unmatched old patches, and four unmatched new patches. The old Codex availability patch was absorbed by the managed-driver initializer, not lost. Integration/lint bookkeeping accounts for two unmatched pairs. The two remaining new commits carry resource work and main reconciliation. See [the concise range-diff inventory](prior-audit-range-diff-summary.txt); an unmatched patch alone was not treated as a regression.
43
44## Open findings
45
46P1 means fix before rollout for the stated trigger. P2 means a concrete correctness or scaling issue that should be scheduled; it does not imply every user hits it. These 18 rows exclude the main-only changes, intentional product decisions, and unmeasured validation questions.
47
48| ID | Priority | Trigger and observable consequence | Classification | Recommended action |
49| --- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
50| F01 | P1 | Starting this build on reproduced historical V2 databases fails migration with duplicate-column/table errors. New main migrations below the old numeric maximum are also skipped. | Rebase/branch-history compatibility regression. | Reconcile recorded migration identities and repair skipped main work; test supported historical manifests. Making one DDL statement idempotent is insufficient. |
51| F03 | P1 | After two ordinary runs, full diff from baseline zero rejects because the shared root checkpoint scope now belongs to run 2, while the query requires run-1 ownership. | V2 checkpoint model/query mismatch. | Resolve the baseline using the actual shared scope; build the regression fixture through the real allocator/projector. |
52| F04 | P1 | Force-deleting a project with imported V1 threads over WebSocket commits V2 thread deletions, then rejects the force-less legacy project deletion. Deleted V2 history is not reimported. | Destructive split lifecycle regression. | Validate and coordinate mixed-store deletion before irreversible work; preserve force and cover partial failure. |
53| F05 | P1 | HTTP and live/offline CLI can orphan V2 threads on project deletion, reject requested forced deletion, or silently drop missing-root creation and new project update fields. | Cross-transport port omission. | Route transports through the same lifecycle semantics and preserve every typed input field. |
54| F06 | P1 | OpenCode Stop ignores descendant enumeration/abort failures and traversal timeout, so child work can continue after success is reported. | Missing explicit-Stop failure behavior from main. | Bound traversal but propagate relevant failures for explicit Stop; tolerate only known benign not-found cases. |
55| F07 | P1 | Releasing a session attached to an external OpenCode server aborts local SSE but does not stop its root/child provider work. | Missing teardown behavior from main. | Perform bounded root/descendant shutdown and log cleanup failures. |
56| F08 | P1 | Cursor background title/metadata generation runs unrestricted SDK agent mode in the real project directory, with its sandbox disabled. | Lost metadata permission boundary during the accepted SDK migration. | Restore a read-only/non-tool boundary for metadata work; a timeout alone cannot prevent writes. |
57| F09 | P1 | Saving a queued edit with newly selected generic files drops those files; a file-only edit can also be cleared when another device advances the queued run. | Bug in V2's added queue editor. | Include files in saveability, uploads, dirty-state detection, and recovery. |
58| F10 | P1 | Mobile reloads a message containing a file/PDF/video but shows no attachment because the V2 feed filters all non-images before rendering. | Client port regression from main. | Preserve the full attachment list and use the existing type-specific renderers. |
59| F11 | P2 | Claude reads a workspace image, but both client adapters flatten it into a generic tool row without the preview path. | Missing main image-preview feature. | Derive and validate image paths from retained Read input on web and mobile. |
60| F12 | P2 | A transient initial title-generation error ends the job after one call and clears its marker. Main retries before giving up. | Missing main retry behavior. | Restore bounded initial retries while preserving stale-request guards and final cleanup. |
61| F13 | P2 | A thread fails, then is snoozed into the future; its older failed status immediately defeats that newer snooze and permits early settlement. | Missing timestamp ordering in the settlement port. | Require failure evidence newer than `snoozedAt`; test both orderings. |
62| F16 | P1, performance | HTTP snapshot failure or a reconnect beyond 128 events/1 MiB sends a full lifetime projection over WebSocket and loses progressive-history metadata. Healthy HTTP opens are bounded. | Missing bounded fallback invariant from main. | Use the existing window/budget query for socket fallback and preserve a usable history cursor. |
63| F18 | P1 | OpenCode's native SSE iterable ends cleanly without local abort. The adapter ignores the end and keeps its public queue alive, so subsequent provider events never arrive and turns can stick. | New missed main port from [#9653](https://github.com/pingdotgg/t3code/pull/9653). Domain ID `PROV-OPENCODE-01`. | Treat unexpected EOF as a broken session and terminalize/reconnect through the intended lifecycle. |
64| F19 | P1 | An OpenCode approval/question reply never resolves. The durable request is already resolved, while its unbounded SDK call holds an effect worker and blocks that thread's subsequent non-title effects. | New missed main port from [#9653](https://github.com/pingdotgg/t3code/pull/9653). Domain ID `PROV-OPENCODE-02`. | Pass the SDK abort signal, bound the call, and make delivery failure/retry behavior explicit. |
65| F20 | P1 | Claude returns `subtype: success`, `is_error: false`, and a dead-turn `terminal_reason` such as `api_error` or `blocking_limit`. V2 records successful completion without the failure explanation. | New missed SDK-result port from [#9135](https://github.com/pingdotgg/t3code/pull/9135). Domain ID `PROV-CLAUDE-01`. | Classify structured terminal reasons before success and preserve the user-facing failure. |
66| F21 | P2 | OpenCode emits `step-finish` token counts, but V2 ignores them and emits terminal provider-turn usage as unavailable. | New missed telemetry port from [#9132](https://github.com/pingdotgg/t3code/pull/9132). Domain ID `PROV-OPENCODE-03`. | Accumulate tokens with correct turn ownership and complete/partial/unavailable status. This finding does not establish increased billing or a broken separate usage dashboard. |
67| F22 | P2, performance | Every five-second scheduler tick reads, decodes, and sorts all tasks, including disabled and far-future tasks, despite an existing due-task index. | Scaling defect in a branch-only feature. Domain ID `PERSIST-N01`. | Query due candidates directly; retain separate crash recovery and corrupt-row isolation. |
68
69The detailed reports contain file/line evidence, actual callers, main counterparts, and proof limits:
70
71- [Persistence and lifecycle](persistence.md): F01, F03-F05, F13-F15, F22; historical migration and allocator/policy probes.
72- [Providers](providers.md): F06-F08, F11, F18-F21; all seven built-in drivers and capability differences.
73- [Clients](clients.md): F02, F09-F10, F17, inherited rendering and queue decisions, recent main UI/performance work.
74- [Cross-cutting review](cross-cutting.md): F12, F16, protocol/remote boundaries, packaging, usage, coalescing, rebase inventory, CI.
75
76F04/F05 can share a project lifecycle implementation, but mixed-store deletion and each transport need distinct coverage. F06/F07 share traversal mechanics but have different contracts: explicit Stop surfaces failure; release is bounded and logs failure. F14 is fixed without fixing F03. Live coalescing is fixed without fixing F16 or unused-stream lifetime.
77
78## Main-only catch-up
79
80The first eight changes below were absent at the initial capture. Seven more arrived during review and are assessed in the addendum. None is counted among the 18 findings. The first column is a stable decision ID, not a severity rank.
81
82| ID | Main change | Current branch difference | Recommendation |
83| --- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
84| M01 | [#9740](https://github.com/pingdotgg/t3code/pull/9740), stop unused thread streams | Live/detail atoms retain subscriptions for five minutes after the last consumer. Main closes them immediately and retains only a separate warm resume snapshot. Applies to all clients and remote connections. | Port, high performance priority. Client report `CLIENT-01`. |
85| M02 | [#9744](https://github.com/pingdotgg/t3code/pull/9744), update notice refinement | Base notice exists; narrow-layout threshold, tooltip truncation, and icon refinement are absent. | Port as visual polish; no total feature loss claimed. |
86| M03 | [#9627](https://github.com/pingdotgg/t3code/pull/9627), PR author profile links | Avatar/name render without the new non-bot profile link. | Port, ordinary feature catch-up. |
87| M04 | [#9748](https://github.com/pingdotgg/t3code/pull/9748), terminal history byte cap | History is limited by line count only; a giant line and saved-history load remain unbounded. Main adds an 8 MiB cap and bounded Unicode-safe tail reads. | Port, high performance priority. |
88| M05 | [#9747](https://github.com/pingdotgg/t3code/pull/9747), terminal metadata indexing | Every mounted row consumer rescans/sorts environment terminal metadata. Local palette visibility work only gates PR/VCS reads. | Port the shared index and stable identities. Client report `CLIENT-02`. |
89| M06 | [#9749](https://github.com/pingdotgg/t3code/pull/9749), mobile parsed-review cache bound | Per-section keep-alive entries retain parsed diffs indefinitely and prewarming includes all loaded sections. Main bounds retention and nearby prewarming. | Port, high mobile memory priority. Client report `CLIENT-03`. |
90| M07 | [#9743](https://github.com/pingdotgg/t3code/pull/9743), new-thread Project settings shortcut | Sidebar and command palette access remain; the new-thread header callback is unwired. | Port the missing entry point. |
91| M08 | [#9739](https://github.com/pingdotgg/t3code/pull/9739), fold a single trailing activity | One successful late tool row stays detached below a settled answer. Main folds only that safe single-row case. | Port, presentation priority. Client report `CLIENT-04`. |
92
93Late arrivals, reviewed separately against the unchanged branch:
94
95| ID | Main change | Current branch difference | Recommendation |
96| --- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
97| M09 | [#9752](https://github.com/pingdotgg/t3code/pull/9752), defer mobile file highlighter startup | Opening a file route can start the highlighter before any source view needs it, including on image/video/preview routes. The manager deduplicates later starts; source highlighting already has a lazy consumer. | Port; remove redundant route-wide startup while preserving source highlighting. |
98| M10 | [#9758](https://github.com/pingdotgg/t3code/pull/9758), avoid history reads for metadata/control commands | V2 metadata mutations, initial title generation, runtime-request replies and provider controls still load full projections. Local `getThread` work only narrows visits and auto-settlement. Branch-name generation already consumes launch input directly. | Adapt narrow queries to V2; preserve session-detach and target-identity guards. Keep the context needed by turn start or explicit title regeneration. An app-thread-only lookup cannot replace every read safely. |
99| M11 | [#9760](https://github.com/pingdotgg/t3code/pull/9760), defer history image URL requests | Chat scope requests asset URLs for all loaded user attachments before row virtualization. Main requests historical image URLs from mounted user rows. | Port the row-scoped lookup while preserving pending local-preview handoff. |
100| M12 | [#9762](https://github.com/pingdotgg/t3code/pull/9762), current model classification | Bundled Codex manifest lacks `gpt-6-astra` in its current list. Runtime remote-manifest refresh can compensate online; offline/fallback classification remains stale. | Port the small bundled-data change. This is not missing provider execution support. |
101| M13 | [#9753](https://github.com/pingdotgg/t3code/pull/9753), proactive diff activation | A completed run's empty/non-ready checkpoint can open the diff panel, including over an active PR panel. V2 checks summary existence only. | Adapt main's active-surface/readiness/nonempty guard to V2 run summaries. |
102| M14 | [#9759](https://github.com/pingdotgg/t3code/pull/9759), sidebar background prominence | Unread/wake state prevents dimming a still-working thread. Main reserves that prominence for ready or action-required states. | Port the presentation rule using V2 statuses. |
103| M15 | [#9763](https://github.com/pingdotgg/t3code/pull/9763), automatic-pull reset | The on/off switch works, but its standard reset-to-default button is absent. | Port the small reset affordance; the underlying feature is retained. |
104
105## Intentional differences and decisions
106
107Previously accepted architectural choices are not reopened merely because their implementations differ from V1.
108
109| ID | Question | Current behavior and recommendation |
110| --- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
111| D01 | Port continuation after a server update now? | V2 explicitly withholds `serverUpdateThreadContinuation`; recovery terminalizes running work. Main resumes it. Port before claiming full update-continuation parity, or keep the deferral explicit. |
112| D02 | Should inherited message/plan links use their source worktree? | Normal web/mobile Markdown uses the active fork; the inspector uses source ownership. Decide source-history versus current-fork semantics before changing this. Main has no equivalent inherited-row oracle. |
113| D03 | Keep the Cursor SDK boundary? | Keep the accepted SDK migration and explicit unsupported capabilities. F08's metadata permission loss is an independent bug. |
114| D04 | Keep the limited legacy import? | Keep the documented metadata/message import, fresh provider continuation, and bounded imported context. Rich old tools/runs/checkpoints/approvals/plans and native-session continuity remain intentionally excluded. F01 is not part of this tradeoff. |
115| D05 | Keep protocol V2 incompatibility? | Keep the version gate and deploy matching clients/servers. CORS permits the protocol header. |
116| D06 | Keep bounded portable summaries? | Keep the accepted 240-character per-item portable summary limit and separate 32,000-character legacy context budget unless fuller fidelity is now required. |
117| D07 | Keep compact mobile queue controls? | Mobile supports reorder/steer/cancel; active server queues still lack web's composer editor and thumbnails. New-task outbox editing is a different path. Keep as a surface choice unless parity is now wanted. F10 is independent. |
118| D08 | What happens to schedules bound to archived/deleted threads? | Binding/upsert does not reject archived targets; later sends reject them, and recurring tasks stay enabled. Choose reject/disable behavior or deliberate waiting for unarchive. This is a branch-only lifecycle decision, not a missing main feature. |
119
120## Cost and validation limits
121
122| ID | What remains unproven | Next useful proof |
123| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124| V01 | Actual frame/byte savings from V2 coalescing and the workload impact of fallback snapshots. Source and focused tests establish bounds on live events, not end-to-end payload cost. | Replay representative provider activity and large-thread reconnects; measure frames, bytes, and retained state. Keep persisted events intact. |
125| V03 | Real interaction behavior across web, desktop, mobile, and remote/relay connections. | An authorized integrated pass for attachments, queue edits, inherited links, and fallback resume. No browser/simulator permission was given for this audit. |
126| V04 | Latency of the deliberate startup corruption scan. It pages in groups of 500 and decodes each canonical row once, but still scans all 16 projection tables before command readiness. | Benchmark representative retained databases before choosing a different strategy. Preserve corrupt-row detection/recovery; do not label this the old F15 history-hydration bug. |
127
128Cost findings are based on concrete ownership, SQL, retention, or transport paths. No measured CPU, latency, heap, or billing multiplier is asserted. F06/F07 permit provider work to continue after intended shutdown, but this audit did not measure resulting spend. F21 is missing telemetry, not proof of excess spend. The old V1 transfer benchmark/report workflow remains deliberately removed; focused V2 wire tests are not an equivalent workload benchmark.
129
130## Validation and CI
131
132| Focused batch | Passing checks | Evidence |
133| ------------------------ | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
134| Parent cross-cutting | 19 file executions, 193 tests | Five `root-*-tests*.log` batches covering authorization, startup, WS helpers, coalescing/budgets, awareness, usage, attachments, title handling, native-command routing and launch guards. |
135| Providers | Nine files, 215 tests | [providers-tests.log](providers-tests.log), seven V2 adapters plus auth/registry. These mocked suites do not exercise real provider processes. |
136| Persistence | Ten product files, 52 tests | [persistence-tests.log](persistence-tests.log), recovery, checkpointing, import, scheduling, project handlers, and local settlement. |
137| Clients | Three committed files/21 tests; five local-overlay selections/60 tests | [clients-focused-tests.log](clients-focused-tests.log), queue behavior, status leases, desktop topology, and patched Swift notification harness. No simulator ran. |
138| Persistence audit probes | Six cases using real migration, allocator/projector, and policy implementations | [persistence-audit-probes.test.ts](persistence-audit-probes.test.ts). Passing means the asserted failures/state mismatches reproduced; it does not mean those bugs are fixed. |
139| Provider audit probe | One case through the actual Claude V2 adapter | [provider-adapter-regression-probes.test.ts](provider-adapter-regression-probes.test.ts) and its [log](provider-adapter-regression-probes.log). A structured `api_error` result incorrectly emits completed/no failure. |
140
141The parent re-ran both audit probe files together after reviewing their code: all seven assertions passed in [reviewed-probes.log](reviewed-probes.log). This confirms the reproductions, not repaired behavior. Repeat runs are not counted as additional coverage.
142
143One initial test invocation accidentally discovered a frozen `.test.ts` copy under this audit directory. Its relative import failed while the actual three product suites passed. Copies were renamed to `.snapshot`, the manifest was updated, and the rerun passed. This was an audit setup failure, not a product regression.
144
145Current-head [CI Check](https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744766) fails formatting in seven files: `WorktreeMcpService.ts`, `EffectOutbox.ts`, `cursorSdkModel.ts`, two V2 architecture docs, `boundedThreadSnapshotHttp.ts`, and `threadSnapshotHttp.ts`. Generic tests and server shards 1/2 passed; server shard 3 was cancelled. Rust and release smoke passed. Web/mobile/macOS previews were skipped. Recent review comments since the previous audit cutoff were empty. These statuses apply to committed HEAD, not the uncommitted overlay.
146
147No repo-wide checks, dev servers, browser sessions, provider processes, production database writes, commits, pushes, PR creation, or external review comments were performed. Green focused tests do not contradict findings whose production trigger their fixtures omit. The full historical-manifest matrix and live client/provider validation remain incomplete.
148
149## Decision sheet
150
151[DECISIONS.tsv](DECISIONS.tsv) gives each finding, catch-up item, and product question a separate row. `user_decision` is blank: enter `yes`, `no`, or `defer`. Fixed findings are retained as closed rows so a later rebase audit can test them again. The recommendations do not authorize or implement new fixes.
audits/orchestrator-v2/2026-09-04/DECISIONS.tsv+51−0
@@ -0,0 +1,51 @@
1id category status priority difference proposed_action recommendation user_decision validation evidence
2F01 correctness open P1 Historical V2 upgrades fail Repair migration identity reconciliation and skipped main work yes Three historical full manifests plus partial checkpoints; never touch live userdata persistence.md
3F02 correctness fixed closed Sidebar empty-provider initialization No new fix; revalidate after future rebases no action Module-scope declaration precedes all uses clients.md
4F03 correctness open P1 Full diff fails after ordinary run 2 Resolve baseline through actual shared checkpoint scope yes Build two runs through real allocator/projector and request full diff persistence.md
5F04 correctness open P1 WebSocket forced project deletion partially commits Coordinate V1/V2 deletion and preserve force yes Imported plus V2-only, active plus archived, force false/true and downstream rejection persistence.md
6F05 correctness open-broadened P1 HTTP and CLI project lifecycle drops fields and cleanup Use consistent typed lifecycle semantics across transports yes HTTP/live/offline CLI; populated deletion and every create/update field persistence.md
7F06 correctness open P1 OpenCode Stop hides descendant failures Propagate explicit Stop failures with bounded traversal yes Nested descendant enumeration and abort failures; benign not-found; timeout providers.md
8F07 correctness open P1 External OpenCode work survives release Bound root/descendant shutdown and log cleanup failures yes External server root and child activity stops during session release providers.md
9F08 correctness open P1 Cursor metadata helper can write project files Restore constrained metadata generation yes Metadata generation cannot invoke write/command tools at project cwd providers.md
10F09 correctness open P1 Queued edit loses newly added generic files Carry files through save and external-start recovery yes File-only and mixed edits; another device starts/cancels queued run clients.md
11F10 correctness open P1 Mobile persisted non-image attachments disappear Remove image-only filtering before attachment renderer yes Reload file/PDF/video messages and open scoped attachments clients.md
12F11 correctness open P2 Claude Read image previews absent Derive validated preview path on both clients yes Image Read versus non-image Read; web and mobile scoped assets providers.md
13F12 correctness open P2 Initial automatic title has no transient retry Restore bounded retry with stale marker protection yes Transient typed failure then success; exhausted retries; stale request cross-cutting.md
14F13 correctness open P2 Old failure defeats a newer snooze Require failed-run evidence newer than snoozedAt yes Failure before snooze versus after; closed-PR rule preserved persistence.md
15F14 performance fixed closed Checkpoint query hydrated full history No new fix; retain narrow checkpoint context no action Three metadata queries; no transcript/fork hydration persistence.md
16F15 performance fixed closed Runtime recovery loaded all terminal histories No new fix; retain recovery candidate selection no action Terminal histories excluded; actual incomplete work still selected persistence.md
17F16 performance open P1 WebSocket fallback sends lifetime history Window and budget socket snapshots and keep history cursor yes HTTP failure and large/invalid reconnect; frame/row bounds; later history paging cross-cutting.md
18F17 correctness fixed closed Mobile assistant renderer bypass No new fix; retain scoped specialized renderer no action Citations, template action, iOS scoped image/video callback are wired clients.md
19F18 missed-main-port new P1 Clean OpenCode SSE EOF leaves session apparently ready Handle unexpected clean EOF through failure/reconnect lifecycle yes Deterministic stream completion; no timing-based absence assertion providers.md: PROV-OPENCODE-01
20F19 missed-main-port new P1 OpenCode approval/question reply can hang effect lane Abort and time-bound SDK replies; define delivery failure handling yes Never-resolving reply; cancellation; next same-thread effect; both reply types providers.md: PROV-OPENCODE-02
21F20 missed-main-port new P1 Claude dead-turn reason reported completed Classify structured terminal_reason and preserve failure detail yes Actual adapter reproducer plus all SDK dead-turn reasons and interrupts providers.md: PROV-CLAUDE-01
22F21 missed-main-port new P2 OpenCode per-turn token telemetry unavailable Accumulate step-finish usage with correct turn ownership yes Multiple steps, partial usage, cache/reasoning tokens, out-of-turn events providers.md: PROV-OPENCODE-03
23F22 performance new P2 Scheduler scans all rows every five seconds Query only due candidates and retain corrupt-row isolation yes Many future/disabled tasks; only due rows decoded; crash recovery separate persistence.md: PERSIST-N01
24M01 main-only-catch-up absent high performance Release unused thread streams Port [#9740](https://github.com/pingdotgg/t3code/pull/9740) yes Last consumer closes stream; remount retains cursor/warm snapshot; all clients clients.md: CLIENT-01
25M02 main-only-catch-up absent polish Server update notice refinement Port [#9744](https://github.com/pingdotgg/t3code/pull/9744) yes Narrow layout and tooltip; no baseline notice removal clients.md
26M03 main-only-catch-up absent feature Pull request author profile links Port [#9627](https://github.com/pingdotgg/t3code/pull/9627) yes Non-bot author link preserves existing avatar/name clients.md
27M04 main-only-catch-up absent high performance Bound terminal history by bytes Port [#9748](https://github.com/pingdotgg/t3code/pull/9748) yes Giant partial line, UTF-8 tail, saved history read and attach payload cross-cutting.md
28M05 main-only-catch-up absent performance Index terminal metadata once per snapshot Port [#9747](https://github.com/pingdotgg/t3code/pull/9747) yes Many row consumers reuse one index and unaffected array identities clients.md: CLIENT-02
29M06 main-only-catch-up absent high performance Bound mobile parsed review cache and prewarm Port [#9749](https://github.com/pingdotgg/t3code/pull/9749) yes Count/source-size limits, large entries, adjacent prewarm, registry isolation clients.md: CLIENT-03
30M07 main-only-catch-up absent entry point New-thread Project settings shortcut Port [#9743](https://github.com/pingdotgg/t3code/pull/9743) yes Header path works; sidebar and palette remain available clients.md
31M08 main-only-catch-up absent presentation Fold one safe trailing activity Port [#9739](https://github.com/pingdotgg/t3code/pull/9739) yes Single ordinary success folded; failures, resource rows and groups stay visible clients.md: CLIENT-04
32M09 main-only-catch-up absent performance Defer mobile file highlighter startup Port [#9752](https://github.com/pingdotgg/t3code/pull/9752) yes First image/video/preview route does not load highlighter; source route still lazily highlights clients.md: late-main addendum
33M10 main-only-catch-up missing; local groundwork performance Avoid full history for metadata and provider control Adapt narrow-read invariant from [#9758](https://github.com/pingdotgg/t3code/pull/9758) to V2 yes Metadata edits, interrupt and runtime replies do not read unrelated history; actual turn/title context retained persistence.md: M10
34M11 main-only-catch-up absent performance Defer signed URLs for offscreen history images Port [#9760](https://github.com/pingdotgg/t3code/pull/9760) yes Only mounted image rows query URLs; pending local-preview handoff preserved clients.md: late-main addendum
35M12 main-only-catch-up bundled fallback behind catalog Current Codex model classification Port bundled manifest from [#9762](https://github.com/pingdotgg/t3code/pull/9762) yes Offline bundle marks gpt-6-astra current; online remote refresh already can compensate cross-cutting.md
36M13 main-only-catch-up absent correctness Prevent empty proactive diffs replacing a pull request Adapt [#9753](https://github.com/pingdotgg/t3code/pull/9753) to V2 run summaries yes Active PR remains selected; only ready nonempty checkpoints open; loading can defer cross-cutting.md
37M14 main-only-catch-up absent presentation Dim background working threads consistently Adapt [#9759](https://github.com/pingdotgg/t3code/pull/9759) using V2 statuses yes Unread/wake does not promote background work; active/selected/action-required states remain clear cross-cutting.md
38M15 main-only-catch-up absent affordance Reset automatic pull to default Port [#9763](https://github.com/pingdotgg/t3code/pull/9763) reset action yes Reset calls existing false setter; on/off switch remains functional cross-cutting.md
39D01 product-decision explicitly deferred decision Server-update continuation Port V2 continuation or keep capability explicitly unavailable yes for parity, otherwise defer Restart active work through intended recovery semantics cross-cutting.md
40D02 product-decision unresolved decision Inherited Markdown source ownership Choose source worktree versus current fork for inherited references decision needed Diverged source/fork worktrees; normal messages, plans and inspector; both clients clients.md
41D03 product-decision accepted decision Cursor SDK architecture Keep SDK migration and explicit unsupported capabilities keep F08 permission boundary is an independent required fix providers.md
42D04 product-decision accepted decision Legacy import scope Keep metadata/message import with fresh provider continuation keep Docs and product messaging remain accurate; F01 still needs repair persistence.md
43D05 product-decision accepted decision Protocol V2 compatibility gate Keep rejecting mismatched peers keep Matching hosted/local/mobile/desktop server protocol; CORS header cross-cutting.md
44D06 product-decision accepted decision Portable context summary limits Keep per-item and legacy-import context budgets keep Explicitly accept fidelity tradeoff unless requirements change AUDIT.md
45D07 product-decision accepted decision Compact mobile active-thread queue Keep reorder/steer/cancel without web editor and thumbnails keep New-task outbox editor is separate; F10 attachment rendering remains a bug clients.md
46D08 product-decision unresolved decision Schedules targeting archived or deleted threads Choose disable/reject or deliberate waiting for unarchive decision needed Archive/unarchive/delete existing target; new binding; recurring failure state cross-cutting.md
47V01 validation implementation fixed; measurement open validation V2 wire workload budget Measure coalescing and fallback on representative provider replay yes Frames, bytes, retained state; persisted events untouched cross-cutting.md
48V02 validation closed as broad concern closed Cwd catalog refresh outside web No broad fix without a narrower reproduction no action Mobile refresh retained; no execution-only stale failure established providers.md
49V03 validation not run validation Real client and remote interactions Run integrated pass when explicitly authorized yes with authorization Web/desktop/mobile attachments, queue edits, inherited links, remote fallback AUDIT.md
50V04 validation unmeasured tradeoff validation Startup canonical projection verification Benchmark before changing full corruption verification measure first Large retained DB, startup latency and memory; preserve corruption recovery persistence.md
51C01 ci failed merge gate Current-head formatting failure Fix seven formatting errors and obtain non-cancelled required checks yes CI Check plus server shard 3; do not confuse dirty overlay with committed CI ci-check-failure.log
audits/orchestrator-v2/2026-09-04/FEATURE-MAP.md+110−0
@@ -0,0 +1,110 @@
1# Main and Orchestrator V2 feature map
2
3Comparison: committed V2 `8af5734365f7c45bc08b57066dbae42f9f7d4235` against initially fetched main `d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70`, with uncommitted work reviewed separately. Seven late main commits through the final cutoff `f6db4206258b0ef30e8dd8949627acfd209bf338` are assessed as M09-M15. See [AUDIT.md](AUDIT.md) for findings, decisions, and evidence limits.
4
5“Retained” means the inspected implementation and its active caller have an equivalent path, not that every surface was exercised. “Shared” means the implementation matches the initial main; changed callers still need review. “Intentional” records an architectural choice rather than treating file deletion as feature deletion. Findings F01-F22 exclude closed IDs F02/F14/F15/F17. Main-only items M01-M15 are assessed separately for equivalent behavior or catch-up work.
6
7## Core, data, and transports
8
9| Feature | Main behavior / invariant | V2 committed behavior | Assessment |
10| ----------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11| Commands, events, read models | Durable commands/events and client projections | Replaced with V2 run/node/provider-thread graph, typed receipts, outbox and canonical projections | Intentional architecture; broad source and focused lifecycle review, not a V1 file-for-file port. |
12| Fresh/current-main database upgrade | Latest main schema starts cleanly | Main 047 enters V2 048-058 | Retained. Local 059 upgrade is also probed. |
13| Historical V2 database upgrade | A previously used branch database should remain readable | Old 052/053/055 manifests collide with renumbered migrations and skip lower-numbered main work | F01, three reproduced cohorts; partial historical stopping points remain untested. |
14| Legacy thread discovery/import | Existing conversations remain discoverable | Metadata/shell import plus on-demand messages; importer leaves V1 source rows intact | Retained within D04's accepted import scope. |
15| Rich legacy history and provider continuity | V1 retains native history, runs, tools and checkpoints | Rich old state is not reconstructed as V2 graph; first continuation is fresh with bounded imported context | D04, explicit limitation; not equivalent to F01's startup failure. |
16| Startup effect recovery | Recover pending durable work and reconcile live provider state | Replay-safe/process-bound separation, typed receipts, candidate selection | Retained in tested paths; F15 fixed. |
17| Corrupt projection recovery | Detect unusable persisted state before serving commands | All 16 canonical tables paged and decoded before readiness | V04, deliberate safety/performance tradeoff; peak page size bounded, total work unbenchmarked. |
18| Metadata and provider-control reads | Latest main uses shell queries for checks/replies/interrupts that do not need conversation detail | Several V2 mutation/control services still load full projections; local narrow reads cover visit and auto-settlement only | M10, adapt to V2 with required run/request/session metadata preserved. Branch-name generation already uses launch input directly; initial titles still load full history to locate one message. |
19| Project create/update | Typed project fields and missing-root option survive transport | WebSocket preserves current fields; HTTP/offline CLI drop several fields | F05. Main's new auto-pull/icon/default-environment fields make the omission broader. |
20| Project deletion | Force semantics and associated-thread cleanup stay consistent | WebSocket deletes V2 children before force-less legacy delete; HTTP/CLI bypass V2 cleanup | F04/F05; imported, V2-only, active and archived children need explicit tests. |
21| Thread archive/unarchive/delete | Reverse states and explicit thread identity | Commands go through V2 WebSocket orchestration; HTTP provides reads | No separate duplicate mutation path found. Project-owned cleanup is the exception above. |
22| HTTP thread reads | Bounded shell/detail/history access | Dedicated V2 query services and windowed snapshots | Retained for healthy HTTP; F16 is the socket fallback. |
23| Reconnect and snapshot fallback | Large history must not become one unbounded recovery frame | Full projection used after HTTP failure or invalid/large reconnect range | F16. Per-item truncation and live event budgets do not bound row count. |
24| Live tool update coalescing | Merge repeated active tool updates without dropping terminal state | Stable-ID coalescing, ordered boundary flushes, bounded live/unacknowledged buffers | V01 implementation concern addressed; focused tests pass. |
25| Unused stream lifetime | New main releases live subscription at last consumer | Five-minute live/detail retention remains | M01. Shared by web, desktop and mobile. |
26| Protocol negotiation and HTTP CORS | Compatible peers negotiate version; protocol header permitted | Explicit protocol V2 gate and CORS allowlist retained | D05, matching deployment required. |
27| RPC authorization | Every exposed method maps to a scope | New V2/project/assets/scheduler methods have explicit mappings | Inspected and focused tests pass; no authorization bypass found. |
28| Pairing, credential refresh and remote identity | Pairing secrets excluded from read models; refresh does not disconnect | Shared implementations match main; V2 routing retains environment identity | Retained in source, not exercised against a live relay. |
29| Long delegated-task IDs | Encoded IDs must reach environment/relay routes | Both routers permit 512-character parameters; relay worker wires config | Retained. |
30| Relay push limits and decoding | Bound stalled pushes and avoid redundant parsing | Relevant shared relay files match main | Retained; local awareness overlay reviewed separately. |
31| Server-update continuation | Main continues active work across server restart | Capability explicitly withheld; recovery terminalizes active work | D01, acknowledged deferral. |
32
33## Provider and registration parity
34
35The production built-in registry includes Codex, Claude, Cursor, Grok, OpenCode, Antigravity, and ACP Registry. An older helper's shorter list is not the production registration path. The managed-driver initialization, settings hydration, catalog discovery, and auth paths were reviewed separately from per-turn adapters. See [providers.md](providers.md) for precise capability limits and line evidence.
36
37| Provider | Retained or explicit V2 behavior | Actual gaps / decisions |
38| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
39| Codex | Pending/unavailable state until health check; auth/discovery; native resume/fork/from-turn/rollback; tool approvals; structured and async questions; native compaction; per-turn usage | No new committed parity defect found in reviewed scope. Local `turn/diff/updated` opt-out is not committed and has focused coverage. |
40| Claude | Registry/auth; resume/fork/from-turn/rollback; approvals/questions; slash-command attachment ordering; native compaction; usage | F11 Read-image presentation; F20 dead-turn result classification. SDK 0.3.260 is present, but its terminal-reason handling was not fully ported. |
41| Cursor | Accepted SDK implementation; auth/model/skills discovery; resume; mode mapping; interrupt-restart | D03: fork/rollback/approval/question capabilities are explicitly unsupported. F08: background metadata uses unrestricted agent mode in real cwd. |
42| OpenCode | Local/external server configuration; access policy; workspace skills through SDK; resume/fork/from-turn/rollback; native compaction; root stop timeout | F06 descendant Stop failures; F07 external teardown; F18 clean SSE EOF; F19 reply hangs; F21 token accumulation. |
43| Grok | Registered ACP specialization; auth/catalog; session loading; permission and question extensions; interrupt/teardown | Unsupported native fork/rollback/usage are explicit protocol limitations. No new concrete regression established. |
44| Antigravity | Registered official ACP agent; managed health/profile auth; negotiated capability/catalog updates; subagent translation; constrained metadata helper | No new concrete server parity defect established. Capability limits follow the runtime; no real sign-in/provider session was launched. |
45| ACP Registry | Instance-specific command/auth discovery; negotiated loading/fork/model/MCP capabilities; permissions/questions; interrupt/teardown | Native rollback and text generation are explicitly unsupported; live request IDs cannot be resumed after process loss. |
46
47| Cross-provider feature | Current V2 path | Assessment |
48| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
49| Provider availability | Codex driver uses `makePendingCodexProvider`, with unchecked `installed: false` | Dropped-patch lead rejected; behavior was absorbed. |
50| Cwd-specific catalogs | Web and mobile request selected-workspace catalogs; Claude also scans for execution | Prior broad V02 concern closed. No narrower CLI-only failure proved. |
51| Bundled current-model classification | Initial main manifest is retained; remote refresh can update it independently | M12: latest main adds `gpt-6-astra`; offline fallback still labels it legacy. |
52| Native compaction | Launch guards recognize native commands; run execution calls adapter `compactThread`; unsupported providers reject explicitly | Retained; focused launch/execution and adapter tests pass. |
53| Logout / session teardown | Auth service passes V2 session stop to controller; native logout command is guarded during launch | Shared path retained. OpenCode external cleanup still has F07. |
54| Automatic title generation | Shared prompts/dispatch and stale-request cleanup | F12 lacks main's bounded initial retry. Cursor boundary F08 is independent. |
55| Usage dashboard/pricing/limits | Shared readers, pricing, proxy-account deduplication, limits and reset-credit APIs are retained | Inspected shared source and focused usage tests pass. F21 concerns per-turn OpenCode telemetry, not demonstrated dashboard loss. |
56| Provider text/event logging | V2 protocol-event log infrastructure replaces older orchestration logging | Bounded logging work retained; dirty event logger work reviewed separately. No production-volume benchmark run. |
57
58## Checkpoints, workspaces, and background work
59
60| Feature | V2 behavior | Assessment |
61| ---------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
62| Full diff from thread start | Query requires a run-1-owned root scope, but real allocator reassigns shared scope on every run | F03. Existing happy-path fixture creates impossible separate root scopes. |
63| Checkpoint read cardinality | Three narrow context queries; shared summary store avoids full patches | F14 fixed; no history hydration needed for diff metadata. |
64| Rollback | Typed checkpoint restore with state checks | Focused rollback tests pass; not a live Git/provider restoration exercise. |
65| Fork/from-turn and lineage | Provider capability drives native fork; otherwise bounded context handoff | Intentional V2 model. Provider capabilities and inherited ownership are explicit. |
66| Portable context transfer | Per-item truncation and separate legacy import budget | D06, accepted fidelity limit. |
67| Worktree cleanup and Git patch rendering | Shared Git implementation and recent main cleanup/prefix handling remain | No new concrete omission found in inspected shared paths. |
68| Automatic project pull | Opt-in project policy reaches VCS broadcaster and startup auto-pull | Retained; not a missing provider launch hook. HTTP can fail to update the setting through F05. |
69| Automatic pull reset UI | On/off switch remains functional; standard reset button is missing | M15, late-main affordance only. |
70| PR state after a turn | Execution refreshes cached PR lookup; checkpoint finalization refreshes branch PR state | Main's newly-opened/changed PR discovery path remains wired. |
71| Auto-settlement | Closed PRs settle independently; merged and age options are respected; local work narrows candidates | F13's stale-failure/snooze ordering persists. Do not skip closed-PR handling when both optional settings are off. |
72| Snooze completion wake | Completion requires evidence newer than snooze | Retained; failure ordering is the exception F13. |
73| Durable schedules | Branch-only schedule persistence, due-run computation, missed-run and crash recovery | Focused scheduling tests pass. F22 full polling scan; D08 archived-target policy. |
74| Active awareness | V2 run/request/metadata state feeds relay activity | Committed behavior retained; local coalescing/config-cache/retry changes separately inspected and tested. |
75
76## Client surfaces and entry points
77
78Desktop uses the web UI, so a web fix normally needs the same renderer there plus any native IPC boundary. Mobile has its own React Native call sites and cannot be inferred from web parity.
79
80| Feature | Web / desktop | Mobile | Assessment |
81| ---------------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
82| Sidebar shell hydration | Stable empty provider entries and grouped shell identities | Native shell/list path | F02 fixed; recent thread-list identity work retained. |
83| Working-row prominence | Unread/wake state keeps background-working rows prominent | Separate native list | M14, late-main visual policy. |
84| Queued work | Reorder, steer, cancel, thumbnails and queued composer editor | Reorder, steer and cancel; pending new-task outbox has a separate editor | F09 drops generic files in web queue edits; D07 compact mobile queue is explicit. |
85| Message attachments | Generic files and images render | Image-only filter prevents file/PDF/video renderer reachability | F10. Server projection and asset transport retain the attachment. |
86| Offscreen attachment URLs | All loaded user attachments resolve at chat scope, ahead of virtualization | Separate native attachment path | M11, late main moves history image lookup into mounted rows. |
87| Assistant citations, media and templates | Specialized Markdown path remains | Active V2 rows now use specialized renderer, scoped media and template callback | F17 fixed. Integrated interactions were not run. |
88| Claude Read image preview | Generic dynamic-tool conversion omits derived preview path | Same missing projection into presentation | F11, both clients need a decision/implementation. |
89| Inherited Markdown references | Normal messages/plans use active fork; inspector uses source | Active screen thread/workspace owns callbacks | D02, source-history versus active-fork semantics. |
90| Streaming rows/Markdown | V2 provenance-aware row reuse and stable Markdown rendering retained | Feed row reuse retained | Source equivalents inspected; no frame-rate benchmark. |
91| Terminal rendering | Existing incremental output and hidden-panel machinery retained | Native terminal flow | M04 history byte bound missing; M05 repeated row metadata scans; no visual terminal pass. |
92| Diff review | Web diff/tree/settings features remain | Parsed section cache and all-section prewarming are unbounded | M06, mobile memory priority. |
93| File highlighter startup | Separate web file renderer | First file-preview route can initialize highlighting even for an image/video; later starts are deduplicated | M09, late main defers initialization to the already-lazy source consumer. |
94| Settled work folding | Prior trailing-group safety retained; single late success remains detached | Separate native grouping | M08, presentation-only; compaction/failed/interrupted groups have separate safeguards. |
95| Proactive diff panels | Any completed-run checkpoint summary can open diff, without checking active PR or nonempty ready state | Separate native flow | M13, late-main guard missing in V2's run-summary caller. |
96| Project settings entry points | Sidebar and contextual command palette work; new-thread header shortcut absent | Separate project/settings flow | M07, missing one entry point, not the whole feature. |
97| PR author identity | Name/avatar render; new profile link absent | Separate native presentation | M03. |
98| Update notices | Existing status component with older narrow-layout rules | Separate mobile flow | M02 polish; D01 is the distinct continuation capability gap. |
99| Browser profiles/import/SSH/preview IPC | Shared native handlers, preload and contracts present | Not applicable to desktop IPC | No production Electron behavior divergence found beyond a type annotation. No packaged app launched. |
100| Connection modes | Explicit environment identity across hosted web, local web, desktop and relay | Shared client-runtime connection core | No new remote-only defect found in inspected identity/auth paths; real remote validation remains V03. |
101
102## Local work and coverage limits
103
104Local changes were not attributed to committed HEAD. The dirty overlay includes narrower settlement and visit reads plus migration 059 indexes, cheaper local Git status, visibility-leased palette PR/VCS data, stable desktop topology identity, Codex notification opt-out, logger bounds, awareness coalescing and retries, and the Expo notification synchronization patch. Focused local tests passed; no new concrete regression was found in the inspected changes. F01/F03-F13/F16 and the new findings remain as documented.
105
106Other work continued during the audit. Later awareness source/tests, projection-settlement tests, and three internal docs are captured separately in `worktree-drift`. The awareness changes were re-read; they retain bounded retries and stop retrying on disabled/unlinked configuration. Hashes and paths identify what was reviewed and tested without overwriting anyone else's work.
107
108All seven production providers, the V1-to-V2 registration boundary, WebSocket/HTTP/live-CLI/offline-CLI project lifecycle, web/desktop/mobile client callers, and local/remote protocol boundaries were included in source review. The initial eight unmerged main commits and seven late arrivals were inspected individually. The initial 240-commit incoming inventory and 991-file tree comparison guided the review; the closing incoming inventory has 247 commits, with final file counts recorded in `closing-references.json`. Equality or a matching patch was not treated as sufficient proof of feature parity after a caller moved. The client report additionally traces six recent ports through actual V2 callers: file-backed image readiness, hidden terminal visibility, static chat status, failed preview-capture cleanup, Antigravity unknown-auth behavior, and sidebar multi-select unpin. All six are retained.
109
110No live provider, browser, simulator, packaged desktop app, full release build, or repository-wide test suite ran. Windows/Linux native execution, full historical migration stopping points, actual provider stream recovery, end-to-end relay expiry, and workload-level CPU/heap/frame measurements remain outside this audit's proof. Marketing-only/shared unchanged files received inventory review rather than a separate UI audit.
audits/orchestrator-v2/2026-09-04/branch-commits.txt+333−0
@@ -0,0 +1,333 @@
18af5734365f7c45bc08b57066dbae42f9f7d4235 fix: reconcile main updates with orchestration v2
2d98f1d5e38fb7dac334193d1cd7e103997b486e8 fix(server): reduce v2 recovery and runtime resource usage
396aff3564b3c2a9a543fed654388d2741fb6de9f fix: reconcile main's round-20 features after the rebase
44f924bfd310d5d673f038ed9ce3be3fe50fba959 fix(web): realign the composer and timeline with main
54199e9db5169f6a88a04457a1bd6224deaf2853e fix(web): right-align the stash shoulder tab again
6dc4cd901af59e732c06ff60bfea9d8c730f2146e fix: reconcile main's round-19 features after the rebase
7702ef5e5851264ea231c9cb4ecfd0b5f9c016042 feat(server): evaluate automatic thread settlement in the v2 orchestrator
8ddf58d90d47e2b6aaefc61bc15e6c3028b61c3e0 fix: reconcile main's round-18 features after the rebase
938f3e4d0e02cd58f79fa7ebe37250ee93c973f60 feat(web): port working and thinking timeline rows to orchestration v2
1027ef79dc301dc4400e3fafe834d514fa3b8de942 fix: reconcile main's round-17 features after the rebase
1142dee149f7861f9d84650c04e047f5fd48b3466b fix(mobile): keep scroll bounds current after animations
128b2f951e68a7f5effa16201263e4b47cbf517ad0 fix(chat): remove added tool summary status counts
136bc5b0f798228f387e9e42cabe51ae3d99a1cfa1 feat(mobile): port chat summaries and transitions to orchestration v2
14cafacd53b80eb399df5ad0cac37d9f6777d46e80 feat(web): summarize T3 orchestration actions
155145881d4c23094f7ca14deea1ee0cc8c2603219 fix(web): keep composer shortcut tooltip stable on Mod
16f8ed1f080ddec3c13eed613927178b0614cbf696 fix(web): match composer actions to draft and modifier state
175d5f370ad678f933e66b215eaef856656a464b9f fix(web): keep queued messages in place while editing
181a56fa6768fdd7ef063e291bcfc8900f07926995 fix(web): keep queued message editing inside the queue panel
19b952c574d5350c880f1f2f805d39839baeae3845 refactor(web): use shared banner rows for queued messages
201a600882893faf73f596d57ddba72a5b239482b2 fix(web): keep stash separate from the composer activity column
21f21af84a704c3435c461a199468f7ce56232741f fix(web): share the outline for joined composer tabs
2261e3568dac81141fa3d5e7c90a78eb4cb485207c fix(web): align queue headers and prevent stash overlap
237efa4e84b1a52583fcc3249293e92e94d0fadab4 fix(web): port composer activity and grouping to orchestration v2
2493620dfab0f877d204b97c5b5c3d9ea5352f080e fix(orchestration): select visible history before limiting SQL
25f45dde3c8615033422102a0202ba17dc04e3a858 fix(server): recover OpenCode status reconciliation
2642bafcb317e48299ccfb82a8b07882447cbc740c fix(orchestration): retain nested fork history when paging
271fc6d2c90585d74f1e5e3b53363e5c2a2c9b37e9 fix(server): cancel pending OpenCode prompts safely
2814322467cf335509915dfdcb5167a6d97341703b fix(orchestration): page history through its true end
298d2c45c539916b048cf794ad0752c7876946bbae fix(web): retain markdown workspace ownership
30d4debe5ae8d9465528dfbb75e77be01f8e306837 fix(clients): anchor feedback in conversation order
31a8370c67278c8031d78a214c312e1191495e39b4 fix(server): correlate OpenCode prompt admission
323d2d5711be63952a5ada478638d10ce6d7802ecf fix(server): normalize Claude question answers
33a4891183060e0c4c125e842d0ed6004ec33431da fix(server): preserve Claude planning lifecycle
344c25c80607d0a76ff68fcf9a5bedd050fdbec71b fix(server): preserve provider usage in persisted turns
3503d8aef938d607bf4d03d7910eadc2054398c831 fix(server): allow protocol negotiation in CORS
3648dab8cd209d056b95f31105281d2b550be87b7f fix(server): restore Claude resume compaction
37ac7e04ae29e7351c1a36f92a446ea3e9aba7d3ea perf(orchestration): bound complete thread snapshots
38d16e890b16d5dc5acedfbe4a334d40fe0c8c0426 perf(orchestration): bound history reads in SQL
399991452ad2f5318a95ffa9d41992fe421b97dd0b fix(server): project Claude plans and todos
40537861cf9a92f7fe4efe01786ef8409422aa29e5 fix(server): restore Claude structured questions
410439f0f162af8ae9753349686437fe269d667158 fix(server): guard OpenCode prompt admission races
422e88e58640ca167a411196318fa773b47fc86519 chore(repo): remove tracked audit scratch files
43d9e87450162db54237c995151a2a371169060ce5 docs: state portable handoff limits
44adc3d1acaa308bd0e18adfefbed2e74237d1a60f docs: explain legacy thread migration
458853fd4386d5079c54ad5f6f9a96ee682377d327 fix(protocol): reject incompatible orchestration peers
46875b9d88568a12af71283bb4c95221b3c50e98bb fix(web): scope markdown actions to their environment
47a4cee6104d5f088c886a202f5dec8fb70df99489 fix(web): restore markdown file chip actions
48980df245b1955831d3869b1ec83be2aa2c055861 fix(server): keep current provider context usage
49fa61cae19d4d6c946207a2abb904dedde00a7c1c fix(web): preserve Windows markdown paths
50dfaf815157fbc94e858ea771c3f64d20db2c34e3 fix(web): load workspace markdown images through assets
511322c48d5d22686a413a898f1af9276514c469cf fix(server): preserve generic provider attachments
5254c0c030ef4876979122944db0689a67f7e6175a fix(clients): restore Codex feedback submission
531acca70823ec6bca52b56d87a8af5a748416e811 fix(orchestration): recreate missing worktrees before turns
54202da64207f35cdccfdba6bd63d94d19ce3c004d fix(orchestration): honor migrated thread visibility in search
55d72da77e7737b2b6b0c6067492a8bc00551669e5 fix(server): preserve Claude subagent models
568f0ada76d8f6875e074990170d229f7cc157d62c fix(web): honor disabled legacy plan mode
574fdafec1d5699d67326b25f66f7dc8a59cf5819a fix(orchestration): preserve legacy thread metadata
58635f552718bbb34c39a8bc7b89d6f01eed22203b fix(server): include service launcher in bundle build
597933dd671dcf72bf1aadc2b207f32178b3a480bf fix(server): observe pre-aborted Claude approvals
60b28c4a4c1257f7a7957ce028270c2283fa4426db fix(orchestration): reanchor unsettled threads
61608793e39e16db8ef01fc906210143c1ca741ecb fix(server): keep Claude session approvals ephemeral
62b14ca6bf3aea8d77518e2ee8982864c03c8799f7 fix(web): keep failed tool items in the collapsed group summaries
6312bf10144bec533bb5aab073a223c4d342bc103b fix: reconcile main's round-16 features after the rebase
642fe6d79c49bda1f9e8e9ff8b289f6f5df5f19292 chore: refresh macroscope ui-consistency check
652a94e7b2d286576174cdbe7a60a861bb54960608 chore: retrigger ci
66edbe21f803a7a61ab1c0e16048a12febb1cfc6b2 fix(server): inject HostProcessPlatform into the Grok plan extractor
67e89268b409baf86103e38f3c2ab5299facfead86 fix(lint): allowlist the queue and relationships interop boundaries
68e7d813ad06c639bda0f8f430176f433ebef4607c fix(mobile): replace remaining dark: utilities with adaptive semantic tokens
69731e44ac8627a73f05fcd50c8b71dd9c48d9a95c fix(web): dedupe the composer glass styles and align the chat column width
7003c974d7dd927c94ac132fd6fd58dd75d190367e fix: reconcile main's round-14 features after the rebase
71399df7a43976776b350c7bbb4e0a4d62d6552cb8 fix(web): drag-to-reorder queued messages and retire stale pending rows
72dbe86d12fd1767fb84b4e33c33b4453329d9713c feat(web): show attachments on queued messages and edit them in the composer
73e3d09455f4f177cd2a51c263d18e95f2fa41d55f feat(server): claim uploaded attachments at v2 dispatch
742dbca2799fc0515913916e2cb026a02ce1947684 fix(web): restore the full-screen file-drop target over the chat column
75bbde25092291d843bb5777f482a869aeb197dc3b fix: restore main's automatic thread settling after the revert
767b35f0756fbec4fe4016e3a2a71594a69642030d fix: reconcile main's round-12 features after the rebase
77344d661accea0e076b388d40711e04313b640050 fix(opencode): route child-session approvals through the v2 adapter
78a2c1f7fde395ceb7246a53def1466ad0f93deedb fix: reconcile main's round-11 features after the rebase
79698db66a384504caf9c140b9fba337cac2a541c8 fix(web): collapsed tool rows preview inputs for every tool type
80d209d13ff9701c8938d147f98b313a3a044e7f56 fix(web): show the command on collapsed tool rows, not its stdout
816bc36d5fdef70f482dd0a292e0433459558e585c fix(web): surface v2 todo-list plans as task progress
8215ac91d2ee36371be6e2cc259ca4012a26460924 fix(web): collapse settled tool runs behind main's summary toggles
837e9527c2a7e7c09ef8e09b97bb4a9608d11c3ab4 fix(web): converge ChatComposer on main's drawer-era body
842c7ab194c49cb00d0b90228a1dea70df5448f98d fix(web): adopt main's attached-composer surface contract so the glass survives shoulder tabs
85c6ac7fae2f6e34030ae1d6cbd7a70d5eab9d33d5 fix(web): repaint the composer glass and strip the thread-panel popover chrome
86718ff37efae80d2a84e341b72983e3836e9e9a69 feat(grok): capture exit_plan_mode into the v2 proposed-plan card (#8358)
87d7f68523648f1ab52d4a9bda171b1461d87c548e fix(web): keep following the stream after returning to the live edge (#6519)
888c559a0fe9dc4d417d0270c22e7c9198433db1d8 feat(orchestration-v2): show live context usage in the meter (#8144)
89197d245344fa63c46cd8525875a06ce582321bfc fix(grok): fail hung prompts on xAI rate-limit completions (#8358, partial)
90d4735799dd360d56ec9585a6a8e9ab5803898917 feat(analytics): credit v2 threads and turns to the starting client (#7774)
9196a5bf0bde62d3804c12d571a6a573a7085556b8 feat(orchestration-v2): route Codex thread feedback uploads through v2 (#7949)
924650a6971b4cab6808fbec7a919b3b0dd49188b4 feat(orchestration-v2): carry approval options and app names to the client (#8058)
93f2b786b92ec0df5c53d144849f8fb241e1ca4733 feat(orchestration-v2): project linked pull requests on threads (#8160)
942c3c47d1dd35812edea0a842d9e7fb894a4acc63 fix(web): stop mis-marking recovered and text-reported tool failures in the v2 work log
9506a2b41a06c648433c0bacd8c5e8be2d18ab3fbb fix: reconcile main's round-10 features after the rebase
966d31e48c7d22d57026c2d4913c233d5165bfd633 fix(orchestration): show provider retries in the work log
973bbb50c08e8e12dd03b1a8fda2d4f6d9bde30b63 refactor(web): finish aligning the branch with main's style simplification
98bd70826128670a103dc832264b874e42e55823eb fix(web): restore the titlebar sizing and timeline fade lost to main's style simplification
990e4c8cf65be39cd4c03829a5480047fdde620072 feat(server): honor withheld agent browser access in the v2 runtime
10040b35dbb9f2b9f6aec8049c3c9abcd493993bd89 fix: reconcile main's round-9 features after the rebase
1011c3f3ff94a8d148b9d3e9de190d509794935305c feat(mobile): surface prominent activity status and metadata
102358fa7b42d1cc1f25b840292b7aecd2c1a132255 fix(server): reject replaying a command receipt across threads in v2
10395ea8072fa8d8ba1037b5e147ecd31976d3ffa52 fix: reconcile main's round-8 features after the rebase
104eeb374557dc3c06894fcaa676b295dfaf5ce5080 perf(server): keep shell snapshots bounded and active-only
10566051a7d8549a1200d8f3eef4d77d4eb1918d7c5 fix(web): size the titlebar layout-control icons like the sidebar trigger
1068e026d246427ef6a25c5efe4f4501c747afcf29f fix(web): align titlebar clusters to one shared pixel inset
107ecbf6849d78edee17458ef834dc33d9366873457 fix(web): keep the titlebar layout controls fixed across right-panel toggles
1080c2def0526e53deacdc5a981efe7ac9eda6b0290 fix: reconcile main's round-6 features after the rebase
10935e8172373ef618965fa3c601d56c29fc4dc5586 feat(contracts): track thread title regeneration
11054912ca1722280d2ab1c5a4e98cb1d4841ed43ab feat(orchestration): bound thread history and resume payloads
1114569224ce1d2fdd96e36436d374bee88e3bd5422 fix(web): restore main's collapse chrome and tab-status keying on the PR panel
1128c976ef74d2ab13998c2e8672669aafe6b313928 feat(web): prioritize pull request row actions
113647dcb34c3a4784c8161e78c7e95b56dbcec137b fix(web): reconcile main's round-5 features after the rebase
114cf8159205d6e018f3d7125b2a58bb1e185d25ae9 fix(mobile): port main's composer stabilization into the v2 thread screens
115b23bae2e07ce3c31e5e919528279e1c7ce5c6024 feat(web): add pull request actions to thread details
116c6429f0ec600ded05245e1c312d6e47910b3b31b fix(web): restore the branch's slim chat header
117c95f718a92a2584980a0a6ed51b59a34690e54cd test(server): expect attachment saved-at lines in ClaudeAdapterV2 turn text
11817abb7b57ae29a8a206fd0db3c2802e8c78dd1d4 test(web): restore main's right-panel migration expectations after the panel-visibility merge
119b10a1a4a613ab4529aa916d705fdde6422e4b625 fix(server): port round-3 main fixes into the v2 orchestrator
120976fa4da28739720063f879cfb6176829924b326 chore(server): renumber v2 migrations 038-046 to 041-049 after main's 038-040
1217b7a3c68f5eca0d6bcb6f4f21b88bb7c78b87c25 fix: repair rerere-damaged files and reconcile main's round-3 features with v2
122f568e4df2cec22e56415ea6c32c268fd7eec6599 fix(web): show Git action success inline in panel
123cd782e9cdce402854a1e1d65c74a4e96dab8115c fix(web): let LegendList own end-follow and disclosure anchoring (#5449)
1241b95828aec9fa44f490b293038fdb10d5d4a3ffe fix(web): port the refined live-follow gesture gating to the v2 timeline
125be208f5073b0b48894bbf54d20e08c4a96fef654 chore(web): prune plan-sidebar leftovers after the inline-plans rework
126103f1e6cd2281cf475d25db3cc81cf114e2fa9b0 fix: port main fixes stranded by the v2 rewrite (round 2)
1277222e6bdf716ef72fa1baa8a87a9c982bce12ffe fix(server): renumber v2 migrations after main's 037_ProjectionTurnsKeysetIndex
128acbb25bc1758a19f205404fc3967485ec6f85af0 fix: repair conflict-marker artifacts from rebase auto-resolutions
1293f8506737ca05b4ea1c9841477c9cd243b5c41e0 fix(web): align git action progress button layout
130a657cb37357f6df029275fd104b6aaaff27faf95 feat(orchestrator): Surface waiting background work (#4378)
131690257b3cd2626f8faa318bf915de8f4a9bc81df test(server): align migration expectations with renumbered ids
1328ba222e52010b4bb6be4e32937c92d3d5a961b9b fix: port main fixes stranded by the v2 rewrite
133e609aabd27d6e7d3b59bf18d4a0688003281af70 fix(server): renumber v2 migrations after main's 036_ProjectionThreadsPinned
134ec1fd6195a5c8fb7d2c8962c83a147d6ec38974f fix(web): remove open PR actions from git controls
13500efd07bd4277f4e589e20a6666f37864b964bd4 fix(server): stop tying codex text-generation temp files to the caller's scope (#5406)
136cf0835134461535d82d94beb583bb409a9829ff4 fix(orchestrator): Prevent redundant delegated completion turns (#5311)
137648d1f42b862b222b9cbacacc075e69ea6d6a283 fix(test): keep codex replay recovery off the repo checkout
1381691887191e0a382f93fea30d65fb55df462e6bd chore: resolve lint warnings across v2 code
13969d932b37a291825aad7136b3f89f95551fac18a test(server): cover v2 thread title regeneration
140413885f1d3ca859a7da348ec08b89260adc2c53a fix(web): restore compact header sizing for project script controls
141ea285714f2e848643c5734c52a1cab4ea3b0ae49 fix: send mcp-protocol-version header in worktree registration test
1420d5f04f604193d5248c52da181b540a1e60c78fe fix: adopt effect beta.103 APIs in rebased v2 code
14351d7089afc6f1860cc5cbc0742c466528a52e05e feat(chat): refine V2 conversation UI (#5307)
1447c9c6dcb7f12659ea09b7199f9ae9aaf4df4b644 fix(server): restore worktree branch naming in the v2 orchestrator (#5309)
1454fad4c56afcd0ddd3f2fb30ab08bbb7f78e77206 fix(orchestrator): Order thread lineage by creation time (#5310)
146daa272bac55305833675cbb83c57afdfdd6fe192 fix(orchestration-v2): restore generated thread titles (#5176)
147f44c22a14a8380c385079a2f1effb742159a64dd fix: reconcile rebase with latest main
148b8a1f761729fe117afc0e7e69cc722ae434373a8 fix(web): keep Git progress title anchored
1493df8422d5694c56a7fbe81d77199e77e69e17857 fix(web): remove elevated thread details panel styling
1501c378332b103b258af1a655ae025f8517b591193 refactor(orchestration): split thread-not-sendable into typed errors
151a78cc2c52384e0f9153ee83468544234ddf3da81 fix(orchestration): promoted queued messages keep the queued_turn intent
1528c7e1c13fa2c289ededc69a393c161251f675b06 fix(orchestration): thread visits no longer create activity loops (#5038)
1535d0644e2af933d494c969cc22592aec59d14220c feat(orchestration): port thread title regeneration to the v2 runtime
1541fc6545515ef29d6c9698f4ece7547f8baaa9fa8 perf(web): keep timeline minimap animations off the main thread
155673e0f49e7eef76545b9bc3a7ccc8ad46566ff15 fix(relay): stop replaying the whole event store into the awareness relay
15616620e9fe4b656303cdd646dd6d5daaca741afb5 fix(chat): prevent stale timeline scroll and rerenders
157b5d353cc8a3f0f1253629ec3a5fd9ad01e6d0d13 perf(orchestration): per-thread shell deltas, visit throttling, event compaction (#4971)
158bedec74e8575b7e50641a03b0985bf74ffd44259 [codex] feat(web): show git progress in the commit button (#4963)
15927806ef4d8513393503556108f973ee2bb3de94f feat(orchestration): track provider retries and thread visits
160a12849d59167dde40a7028813ef839c39161a095 feat(server): surface legacy thread migration progress
1615c24e3506aee88519926bc490b04d9c122b5ef68 fix: close failed provider adapter scopes
162d4990c75c3a674ef39ad54877eae6549decdf872 fix: preserve thread management failure semantics
163dbc5127f330b114e4668a0b1b82c553b95bdb608 fix: address latest orchestration review findings
1641e65d374c192348690365a7cd1c669a0d3c44e95 fix: preserve orchestration task identity
16588a1900091bb28b2596386bb88962d9b6c226598 fix: address orchestration review findings
1663e72c6a17b22dff3843ad5123cf3ee626d37491b fix(mobile): label queued message intent
16701c127f54ede66e2b9e6393a0ffe3a612f048861 fix: address orchestration review findings
16810c462fbd20b1c0147a76c6505b8301f871168a7 fix(orchestrator): preserve migrated and nested history
169014e9a2621e861a688fe81f279d7b16c42c3865d fix(server): preserve project mutation client errors
1706fcbf009d4268d240c8264aeac37108cdf5905ec fix(orchestrator): validate rollback and search links
1712e813f8c4bd22ce905cfceee7a02d7ec5095468b fix(orchestrator): close cancellation edge cases
172697fff8d50d5518e0c9febb7575e4dbf3e0150bf fix(orchestrator): decode direct Claude result blocks
1739a404d4672f5c3b7272642804d0969e21dd88fec fix(orchestrator): validate replay edge cases
174ddb3c272115d65e4ac7879667684f4d00752f20c fix(orchestrator): preserve terminal effect outcomes
17583cf7d6dfa67d36172423d9263061c4e04c37e56 fix(orchestrator): preserve retryable effect failures
176f081c2c84c465fb04ba2f0106ff02928b82a0920 fix(orchestrator): avoid replaying settled effects
177c2a48e3c8e9979afb34953cf31ebdb82fa6d5674 fix(worktrees): make handoff rollback atomic
178dd2b88149757c41c79c14c889ee30e07628d7cb2 fix(orchestrator): release stranded effect claims
179d6eb526629a8dbcd0ac096e8d596d0711278e85a fix(orchestrator): execute resolved runtime responses
180b65d8ca4cf639b8197c13f3a507287694896d1c7 fix(orchestrator): harden provider edge cases
181739c3ff962d3f70eada4f1fddaabc738462fe816 fix(orchestrator): handle fresh review edge cases
182088c56b41f31d61bd5d8be115b4fc2b90ee3ee1b fix(orchestrator): address late review findings
18391e3a339592acdd321301eb2abaa02787466eded fix(mobile): gate thread controls on live runs
18467f85a5c781131e8e1db51d11087e16cea279768 fix(checkpoints): retain thread-start baseline after failed runs
185b605ddde083344d75718d9f8511a6dba7e2ffc99 fix(client): preserve live thread relationships
1860954b3bb5977fac6a3d6bb2b30a0d85ae05405ea fix(web): enforce secure provider field defaults
1872ca3166d40908502b575f7e3e36a4aaae3e47348 fix(claude): preserve explicit model options
1884cf4ed7202f7220608ec77dd1dbb630afa63e014 fix(server): isolate deterministic attachment ids
189bd653b476bfcf0d0db332d9e06cc2d805b96b331 fix(testkit): harden provider replay recording
1906ab76be22432d266dc0b2574659be43dfc5cb1b1 fix(acp): enforce task and permission invariants
191c2b51fc09027b688a01094515c15e2f62bce3a34 fix(orchestration): preserve imported conversation state
192d0fca84e65107f02c81ecf97a6f91e97ebe579ab fix(checkpoints): preserve valid run history
193ff5e6f1b32dc7904b75c32e5b3fda6d1cddd7646 fix(acp): discover final teardown descendants
1944fd8f62bc947245442785fa75fa720f7d2cc082d fix(cursor): log close attempts before execution
19514d68b3c2adf008872fb8a4cd7ee1c71d393274a fix(mobile): distinguish queued and waiting archive states
1963087d27841d78909de6134b6919f6eedc1fb5a06 fix(mobile): allow archiving post-provider work
1979761382b9dfa80e6e4ed66a391f6906c79fc88b6 fix(orchestration): cancel queued work on archive
19849654af76e5c04f7bf62b51f0a65b9f7253a2967 fix(orchestration): handle checkpoint-wait runs
199cedc11513d4a262786fffdb1415c5c947c78cb35 fix(acp): make xai cancellation reliable
200385f1537b784a1abd337eb30ec427a8fb41ec878 fix(contracts): reject invalid legacy intervals
201657ee69e5ff0fac40c23f59ebdd26a59f2ef099d fix(orchestration): preserve legacy schedule compatibility
202384873780e83648f68a0c24a4b426d869ce8b51e fix(mobile): preserve active thread state
20373fb62aeed30b7977bb552ed2bcefa1dac77e330 fix(orchestration): harden scheduled task startup
20481619adf57bf4a8e964b33b22cc7eb4950c18524 fix(orchestrator): schedule effects from durable deadlines (#4656)
205e684489f7d8aae43e7c8e7aa339e68b56ec4f984 feat: migrate v1 state into orchestrator v2 (#4400)
2064569c531d594c3018bbcbbe4fadbfc74ee2500f5 chore(orchestrator): refresh checks after main sync
207933d9c0421749fe4d1205f2c5477e01f6c3a7f18 fix(web): restore checked-in project scripts
208bc1a9c7d76baba0d25ce08cc91a89b7c7124ed5b fix: ignore subagents when sorting sidebar projects
209dda77e7bb49fccd5cf211565ea88c6396af04c48 fix: hide subagent threads from v2 lists
210f0b7a4ed427259519850bd4933ca01ab1178e8a7 fix(claude): Settle positive task-notification results
211f4553701dbb3d453687129ff1c50ae0aea042376 fix(orchestrator): Wake settled parents when delegated children finish
212af0f7759971f9163adefbcd5f21029b6a6d1cb36 fix(acp): Preserve wake evidence across an app-owned wake
213e05564e15a5bd00e03b3414226eb5bffa26ceb20 fix(grok): Prevent spurious wake run after in-turn monitors
2140bcafedc7ca250d3bcb5bf407a0d182a2617d4b4 test(orchestrator): align merged V2 compatibility checks
2158fb4634569577ac40cad07b4103f903dd8646b22 fix(server): clean up Claude replay failures
216961a655902ecda13d01a23bb0ef1f79a920ef6fe fix(server): enforce ACP auth and preserve fork provenance
21755cb69380550972f8e44a92936be163e0e1ac7bf fix(server): keep derived threads awake
21864a9daf87cd8260f66d0d5d3e5ccb44e786ece1b fix(mobile): wait for fork shell before navigation
219d52c1e6e6d5638215f55c11fe357872037388b8b Add worktree handoff and status tools to the t3-code MCP server (#3754)
220780302519d3e38170a9b8f926bac13272b5a8368 feat(subagents): disclose projected results consistently (#3866)
221dff34c177aa4312146726f7d3e8300db39e15da9 fix(orchestrator): hydrate shell cache and group multi-environment projects (#3640)
222c75cec919c37c526a572c153cabcf8cba75c6ce3 test(orchestrator): align Codex approval reviewer replays (#4457)
2233e6d9c12dd69e3210fd3976d1d0504ef7dd5681e fix(orchestrator): Preserve claude/codex post-interrupt recovery state (#4229)
224df52327334430eec54aaaefb93ae4b41a9861147 test(orchestrator): Align post-merge CTM fixtures (#4193)
225b51d858ac04ab281df13bcd7ec1d3acd14d02aae fix(web): contain thread details panel effects
2263fa4661dd66fa3e59e65ba9c20431987986e9ec1 refactor(web): use shared glass surfaces
22780a9b6628b86d0b6dc6cc4ed6a1bd9683159f489 fix(web): remove stacked composer shadows
228f18cbfb6841f29ac13e1c540bf486b89c3a3c4f0 fix(web): restore v2 composer chrome
229cd095953df759b49c9b19d045853058ca82421ed fix(server): preserve released migration ordering
230bcf43a8f0185e7ac94fbbffcb14c6dda13d079ad fix(orchestration): clarify agent delegation and scheduling
231917fad6c322bc2c8a75c023e193067b96ab0813b Unify T3 MCP tool presentation across clients
2321093a70b448df21a18b408e0fff2eb79963a98ea Render T3 MCP tools with branded timeline labels
2332fd782d05bc061773d39515dc8daebb0986f3dd8 fix(grok): align ACP extensions with open source runtime
234fa905ec63629cbee1fd6b4619b0bb209a2d05aa0 fix(mobile): support Hermes collection sorting
235b116daa728b3473a7b493169f616c37db327ed2f test(orchestrator): align integration fixtures
236007b51b0ce57caded57da85108cd3da10db7dd2e fix(orchestrator): Harden Grok v2 runtime lifecycle
237f006bffe139212841f36e6aca84491dd1c8ff47a fix(orchestrator): dedupe Grok continuation dispatch
238333ecf1ff797ba2eb6940a9f43752a6cc7c34958 fix(orchestrator): harden Grok v2 lifecycle (#3578)
239be7bb48c0aad9f2137d2b00dfad09aecdc4ed572 fix(acp): bind MCP credentials to activated threads
2401124866e9f672272fd9759fe16231dd6866887b2 fix(acp): release turns after interrupt timeout
2411e4a96b561036e50945d022cd3cdaf05d271bb1b fix(claude): reopen queries after MCP credential rotation
242f218e04f09a13da565a981b35efb5bd33090236b fix(claude): enforce read-only tool availability
2433c091989c6fde3d53abfb2050b4d98a8e0986c2b fix(claude): honor never-approval runtime policies
244bad24edaf5c00b594333ff675307f910a3e44a9c fix(claude): allow questions during plan mode
245bfb55f793b386dec833a0d62a1dded65c477b47d fix(claude): preserve approvals with full-access sandbox
2465ce823a928ce9aad26754ad361989583ceabe2b0 fix(claude): redact launch arguments from protocol logs
247b9668c948391803ce80440869d83f59686ee280c test(desktop): expect orchestrator v2 state directory
2485d7d125331d609c13083cef3564dccad69e4ea44 fix(ci): restore Claude permission request identity
2498a79bdd091cde84416bd22949ccebec2fe97f425 fix(orchestrator): align Claude permission replay with SDK
250a9f67f41d33abc6d5d9c4580fd36d2b7be8d732c feat(orchestrator): pass model options through MCP thread targets (#3872)
251e15cb8b34f64ec9676a59626020af98aa4ec2d68 fix(orchestrator): scope Claude MCP tool pre-approval (#3862)
252f11c744887c8d8839316c7a5f080912ec756be21 [orchestrator-v2] fix(orchestrator): Codex background command completion and subagent resume (#3908)
25347ec735048db5b7d45728108bc93e62e002a769d [orchestrator-v2] fix(orchestrator): Restore Claude session continuity for resume, wake, and idle release (#3860)
254c1bae1c69fcac25f0cb36a6c9f0ff5b9bee5bf18 feat(orchestrator): Add shared provider continuation and background item plumbing
2552871cf85de7f2d3eda78c31b121f447bbd6fa47f feat: scheduled tasks (automations) (#3638)
256cb43524758270f7dbb9f5380d4fca94bcf9abaf8 Fix Claude task turn mapping
25789eb3f0bb822d6615168a7ea19bcfa585936b98b Allow provider switching via handoff in chat threads
25861dec910677504c3b47501913d130c6a56eff35b Remove early access badges from Cursor and Grok
2598e2e8edd364de25e83caf6ca2ae42755071245e6 Switch Cursor provider to the official SDK
2600d4886de7c94e2d5c457fc2329ba4d7b49ca7653 fix(web): align thread details panel controls and menus (#3606)
261f8579194c5a67774e41da249b6d67f4f778e00f4 Require Cursor API key for provider checks
262aa9c46184f2e93fed7a4e6d32ea1c638b5e0f5df Keep persistent cards visible in folded turns
26347f269d43643fe1c2d31e14230fcedb93255ca67 Clarify thread relationship icons and ordering
26430d6cf06ec933d2edaa7c434083dcd073c28592e Map nested Codex subagent threads correctly
2654a7e85f0fca573de31740f728dee6a7c6e019401 Adopt userdata-v2 and subagent activity mapping
266569c448f0094ca43951224d98b2dfce7fb44760d Map Grok task envelopes to subagent lineage
2671ee6b0622eb06e918928c71466aad9f7c678be7c Add iOS associated domains for Clerk
268e70b8940053b4ca8db6f2956dd3ad5a8f4da8ea4 Retire V1 client orchestration parity
26906c57e49daacf968501a1960c11d7061edd0fb02 Expose V2 thread workflows on mobile
270d5a4342a3daa5a83f373c62733b2e921e24eccc5 Enrich mobile V2 execution items
2714b5fa27f95ccbd83cd7d47d193fdc09eb492819c Render mobile timelines from V2 turn items
272c8dce47576c9cb90e5112cb8fcabb6ba57bcfa77 Hide subagent threads and simplify thread controls
273b9ae98f979413ce98b96f87ad11ee6bcc5df157d Split open-in editor controls into panel and toolbar variants
2747d5eedaf9f67801c5ed3b9eb648aade5c82c8f98 Reserve space for inline thread details panel
275b9717bb8517d8bf954b18dc83fa79326733387d8 Map thread panel into title bar and sidebar
276890f95e6a7fbaa906eb50157314e0932cbe732fa Record created threads and subagent progress
2779b1752222929c1665c4b46ba72ae3458e7272827 Handle preparing turns across provider orchestration
27861182514a62cad88a235fe68684d575f245103bd Integrate orchestration V2 controls and process recovery
27965e85e1e2d4f4d36a3682e4ede0b63f830f8c29d Complete orchestration V2 frontend cutover
28078b451958a7eba484ca9af0078f8af8980d13ad6 Split the V2 frontend plan into parity and enrichment phases
281c0bb02d6da0aa4648f838202e4fac152166fc3f8 Integrate orchestration v2 with the application runtime
282418deadc7263301c86eba31ab7a878df40f2c749 Guarantee MCP revocation during session release
2837ba8f57840af1f86b25871dfb1a3880c8c18cf6f Remove MCP credential expiration
284591c8b76b6e459af28c15b1213931d2239bbb7e4 Require MCP registry for V2 provider sessions
2854d85c0c89bd57b61f0419a702f68c69997d3f96e Complete orchestration V2 application services
2865bac980a78eed9e356c205d63a32a7ad67ac608f Start orchestration V2 application services
2879caaa2e23f802ebfa7ff84c241201fbf15f79bc0 Align orchestration V2 with Effect service conventions
288f1d7d1c122621d22401c92d07e590d6ab78f95f2 Share Codex sessions across orchestration threads
289dab646dc400a921db52e665eba87a4169205c846 Map orchestration turns to provider instances
290915e6b385a29c24350fcf00fbe752fc849dba5fb Add Orchestration V2 application integration plan
2917cfebe0be47dcf77d7d2446c8757f434d8d8f99b Add MCP thread management and Codex turn mapping
292b43bc2b62c41dcce16cad95fe61c47cb6e4d774d Add ACP replay harness and session lifecycle support
29337045a02fe9b96e283b50dde70bb4e66d2280a2d Handle segmented Cursor turns and stable visible timelines
29426e930ea40800bcb19430f15bd3d0b86a8593997 Add Cursor SDK orchestration replay support
2958cc9df27fa72c9940e78f44863396293243237f3 Add orchestration MCP toolkit
296a244d5c89514f23019e0975b21d6ca91dab4f91b refactor(orchestration-v2): adopt host process spawn policy
2975dec17fc19c7984020f3af870b8b43de96460be9 wip
298d175a63a1bcfc114e6b63f094fa75991ad67700e feat(orchestration-v2): model native subagents
2994391068367180f05515fe210618a93da9555df53 Map orchestration v2 WS methods to auth scopes
3003c06f0c2c9c389f45b2872c7099aff42bc5b0dac fix(orchestration-v2): preserve source history on merged switch
30128cc526e1f8b82e88e0318367504924796ba432e fix(orchestration-v2): compose provider switch merge context
30286f785623b169c309ce3feca151cd832fcadc4fd feat(orchestration-v2): add merge-back replay coverage
30369cba683fa066b6a496a9ae6946fc5d7eb7ed62e fix(orchestration-v2): resolve cross-provider forks
3041ed757ba0c5e05ebe7df83bb61dc1be15dbcb8a0 feat(orchestration-v2): support cross-provider handoff
305bf94a66361a6f4454461d057cfd76d2c12a9344d feat(orchestration-v2): wire claude adapter primitives
306ec935be6a56201c2a4006b5af16223f1b267d04d Document Cursor SDK MCP projection for V2
307ebf98e4217765858626681c4d516f20de6c27ece Add turn-interrupt replay coverage and protocol logging
3081be35b30fa1fa7d6b82d01b204ea5be6aac4ae85 Support active Claude steering and turn replay mapping
309d9f4e700c077c2ceede384b684979628270bb5d3 Map Claude turns to runtime query policies
310c71caf0f964ccb80cc60cce7e5438ef2095af669 Map Claude replay fixtures to multi-turn turns
311f806017a3c6fd4d986233e1ca7334197089e4d57 Add model selection to orchestration runs
31205d619192a41b686ab3ef228e91b15fea640a1a1 Extract Claude SDK query runner from provider adapter
3135c6af6923c2dd66017476d9c7ae261ca1ebe9150 Add Claude replay fixture recorder
3143a75dbb5a18b15fbc8f129a36d8844e65d0d83a4 Add V2 command capability policy
31588b265dab638b61c7d81652783f048445abc03bb Add merge-back context handoff support
3165169fe6083b191cac4c76451d914d1266b834db0 Add orchestration V2 backend checklist
317959c0e22e73063bdc0394ad6243b7b1f937c61ce Add thread fork lineage and lazy context transfer
31841937c522ee8c013d80d4538c06431589daea6eb Implement orchestration v2 runtime
31967d40b8e8ee93bb94e5d2407b5bd2fee265ff122 Map Codex turns into orchestration v2
32028999d551518a8b3368b18bb46a91988569b75ae Add orchestration v2 replay and service contracts
32199de1c8833ea1d8f16976ed19bbc239c74f626c7 Add orchestration v2 docs and probe fixtures
322d2a22ec16520e6d690924471c38f53d7c7e139a2 Address Codex review feedback
323ce08dee1407d352821edc904274b890901aea815 Switch Codex provider checks to app-server probe
324a1d85aa33315cf862303ad6e729e1e72f3d26aae Flush native logs on adapter shutdown
325faf4d6c04e25bb531e45a809ec657262c07a2a8b decoders
326c41b812f43f9172c6ca3aceaae17a213f6e5ee04 Scope Codex session runtime lifetimes
327f355bd32b1075f5e0c1f734b99899cba0bf55691 Normalize Codex IDs and preserve streamed stdout decoding
328773f789c82880988ea775aeff5abc3b7afe3a224 Return Cursor ACP runtime with explicit scope
329fdf63358fa26262c3a3c1e06be2c674b99237a09 resynclock
3303fe3534dbefd1d13e2dedfda8a706aa21306219b revert more
3318852b94978538d264cad8ce8781e557d9c25d745 nit
332cc2e380a1ed3ebdc664650fc87933c89f2050ac6 Integrate Codex app-server support
333b8b7f894fcd31c400bbb51e197daf04c56728005 chore(ov2): preserve the integration base for replay
audits/orchestrator-v2/2026-09-04/changed-files-final.tsv+999−0
@@ -0,0 +1,999 @@
1D .github/scripts/thread-transfer-report.cjs
2D .github/scripts/thread-transfer-report.test.cjs
3M .github/workflows/ci.yml
4D .github/workflows/thread-transfer-report.yml
5M README.md
6M apps/desktop/src/app/DesktopEnvironment.test.ts
7M apps/desktop/src/backend/tailscaleEndpointProvider.ts
8M apps/desktop/src/settings/DesktopClientSettings.test.ts
9A apps/marketing/public/app-desktop.webp
10M apps/marketing/src/pages/index.astro
11M apps/mobile/generated-uniwind-themes.css
12M apps/mobile/scripts/generate-uniwind-themes.mts
13M apps/mobile/src/components/BrandMark.tsx
14A apps/mobile/src/components/brandAssets.ts
15M apps/mobile/src/connection/environment-cache-store.test.ts
16M apps/mobile/src/connection/environment-cache-store.ts
17M apps/mobile/src/connection/runtime.ts
18M apps/mobile/src/connection/storage.ts
19M apps/mobile/src/features/archive/archivedThreadList.test.ts
20M apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
21M apps/mobile/src/features/home/HomeScreen.tsx
22M apps/mobile/src/features/home/homeListItems.test.ts
23M apps/mobile/src/features/home/homeThreadList.test.ts
24A apps/mobile/src/features/home/threadArchive.test.ts
25A apps/mobile/src/features/home/threadArchive.ts
26M apps/mobile/src/features/home/useThreadListActions.ts
27M apps/mobile/src/features/keyboard/hardwareKeyboardCommands.ts
28M apps/mobile/src/features/projects/AddProjectScreen.tsx
29A apps/mobile/src/features/review/ReviewHighlighterProvider.tsx
30A apps/mobile/src/features/review/reviewHighlighterState.test.ts
31A apps/mobile/src/features/review/reviewHighlighterState.ts
32M apps/mobile/src/features/review/reviewModel.test.ts
33M apps/mobile/src/features/review/reviewModel.ts
34M apps/mobile/src/features/review/reviewState.test.ts
35M apps/mobile/src/features/review/reviewState.ts
36M apps/mobile/src/features/review/shikiReviewHighlighter.test.ts
37D apps/mobile/src/features/review/useReviewDiffPrewarming.test.ts
38M apps/mobile/src/features/review/useReviewDiffPrewarming.ts
39M apps/mobile/src/features/review/useReviewSections.ts
40M apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx
41M apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
42M apps/mobile/src/features/threads/PendingApprovalCard.tsx
43M apps/mobile/src/features/threads/PendingUserInputCard.tsx
44A apps/mobile/src/features/threads/ThreadActivityInspector.tsx
45M apps/mobile/src/features/threads/ThreadComposer.tsx
46M apps/mobile/src/features/threads/ThreadDetailScreen.tsx
47M apps/mobile/src/features/threads/ThreadFeed.tsx
48M apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
49A apps/mobile/src/features/threads/ThreadQueueControl.tsx
50A apps/mobile/src/features/threads/ThreadRelationshipsBanner.tsx
51M apps/mobile/src/features/threads/ThreadRouteScreen.tsx
52A apps/mobile/src/features/threads/thread-activity-row-presentation.test.ts
53A apps/mobile/src/features/threads/thread-activity-row-presentation.ts
54A apps/mobile/src/features/threads/thread-feed-item-size.test.ts
55A apps/mobile/src/features/threads/thread-feed-item-size.ts
56M apps/mobile/src/features/threads/thread-list-v2-items.tsx
57A apps/mobile/src/features/threads/threadActivityFileNavigation.test.ts
58A apps/mobile/src/features/threads/threadActivityFileNavigation.ts
59A apps/mobile/src/features/threads/threadForkNavigation.test.ts
60A apps/mobile/src/features/threads/threadForkNavigation.ts
61M apps/mobile/src/features/threads/threadListV2.test.ts
62M apps/mobile/src/features/threads/threadListV2.ts
63M apps/mobile/src/features/threads/threadPresentation.ts
64A apps/mobile/src/features/threads/threadQueueControlPresentation.test.ts
65A apps/mobile/src/features/threads/threadQueueControlPresentation.ts
66A apps/mobile/src/features/threads/userMessageIntentBadge.test.ts
67A apps/mobile/src/features/threads/userMessageIntentBadge.ts
68M apps/mobile/src/lib/modelOptions.ts
69M apps/mobile/src/lib/projectThreadStartTurn.test.ts
70M apps/mobile/src/lib/projectThreadStartTurn.ts
71M apps/mobile/src/lib/scopedEntities.ts
72M apps/mobile/src/lib/threadActivity.test.ts
73M apps/mobile/src/lib/threadActivity.ts
74A apps/mobile/src/lib/threadActivityInspector.test.ts
75A apps/mobile/src/lib/threadActivityInspector.ts
76M apps/mobile/src/state/queries.ts
77M apps/mobile/src/state/threads.ts
78M apps/mobile/src/state/use-pending-new-tasks.ts
79M apps/mobile/src/state/use-selected-thread-requests.ts
80M apps/mobile/src/state/use-selected-thread-worktree.ts
81M apps/mobile/src/state/use-thread-composer-state.ts
82M apps/mobile/src/state/use-thread-detail.ts
83M apps/mobile/src/state/use-thread-outbox-drain.test.ts
84M apps/mobile/src/state/use-thread-outbox-drain.ts
85M apps/mobile/src/state/use-thread-selection.ts
86A apps/mobile/src/state/v2-item-support.ts
87A apps/mobile/src/test-fixtures.ts
88A apps/server/README.md
89D apps/server/integration/NetworkTransferMeasurement.integration.ts
90D apps/server/integration/OrchestrationEngineHarness.integration.ts
91D apps/server/integration/TestProviderAdapter.integration.ts
92D apps/server/integration/TransferBudgetReport.integration.ts
93D apps/server/integration/TransferBudgetScenario.integration.ts
94D apps/server/integration/fixtures/providerRuntime.ts
95D apps/server/integration/fixtures/transferBudget.ts
96D apps/server/integration/orchestrationEngine.integration.test.ts
97D apps/server/integration/orphanedProviderSessionStartup.integration.test.ts
98D apps/server/integration/providerService.integration.test.ts
99M apps/server/package.json
100M apps/server/scripts/acp-mock-agent.ts
101A apps/server/scripts/acp-replay-agent.test.ts
102A apps/server/scripts/acp-replay-agent.ts
103A apps/server/scripts/acp-thread-spawn-helper.c
104A apps/server/scripts/acpMockCancellationState.test.ts
105A apps/server/scripts/acpMockCancellationState.ts
106A apps/server/scripts/claudeReplayRecordingConfig.test.ts
107A apps/server/scripts/claudeReplayRecordingConfig.ts
108A apps/server/scripts/codexReplayRecordingRecords.test.ts
109A apps/server/scripts/codexReplayRecordingRecords.ts
110A apps/server/scripts/cursorReplayRecordingWorkspace.test.ts
111A apps/server/scripts/cursorReplayRecordingWorkspace.ts
112A apps/server/scripts/probe-claude-fork-local-rollback-replay.ts
113A apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts
114A apps/server/scripts/record-codex-app-server-replay-fixture.ts
115A apps/server/scripts/record-cursor-agent-sdk-replay-fixture.ts
116A apps/server/scripts/replayRecorderDeferredRegistry.test.ts
117A apps/server/scripts/replayRecorderDeferredRegistry.ts
118M apps/server/src/attachmentStore.test.ts
119M apps/server/src/attachmentStore.ts
120M apps/server/src/auth/RpcAuthorization.ts
121D apps/server/src/bin.test.ts
122M apps/server/src/checkpointing/CheckpointDiffQuery.test.ts
123M apps/server/src/checkpointing/CheckpointDiffQuery.ts
124A apps/server/src/claudeModelOptions.test.ts
125A apps/server/src/claudeModelOptions.ts
126M apps/server/src/cli/project.test.ts
127M apps/server/src/cli/project.ts
128M apps/server/src/environment/ServerEnvironment.test.ts
129M apps/server/src/environment/ServerEnvironment.ts
130M apps/server/src/git/GitWorkflowService.ts
131M apps/server/src/http.test.ts
132M apps/server/src/httpCors.ts
133M apps/server/src/mcp/McpHttpServer.ts
134M apps/server/src/mcp/McpInvocationContext.ts
135M apps/server/src/mcp/McpProviderSession.ts
136M apps/server/src/mcp/McpSessionRegistry.test.ts
137A apps/server/src/mcp/McpSessionRegistry.testkit.ts
138M apps/server/src/mcp/McpSessionRegistry.ts
139A apps/server/src/mcp/OrchestratorMcpService.activity.test.ts
140A apps/server/src/mcp/OrchestratorMcpService.test.ts
141A apps/server/src/mcp/OrchestratorMcpService.ts
142A apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts
143A apps/server/src/mcp/WorktreeMcpService.test.ts
144A apps/server/src/mcp/WorktreeMcpService.ts
145A apps/server/src/mcp/toolkits/orchestrator/handlers.ts
146A apps/server/src/mcp/toolkits/orchestrator/tools.test.ts
147A apps/server/src/mcp/toolkits/orchestrator/tools.ts
148A apps/server/src/mcp/toolkits/worktree/handlers.ts
149A apps/server/src/mcp/toolkits/worktree/registration.test.ts
150A apps/server/src/mcp/toolkits/worktree/tools.ts
151M apps/server/src/observability/Metrics.ts
152A apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts
153A apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts
154A apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts
155A apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
156A apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts
157A apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts
158A apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.ts
159A apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts
160A apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.ts
161A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
162A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.test.ts
163A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts
164A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
165A apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
166A apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts
167A apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
168A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
169A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.test.ts
170A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
171A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
172A apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
173A apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
174A apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts
175A apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts
176A apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.ts
177A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts
178A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.test.ts
179A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts
180A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts
181A apps/server/src/orchestration-v2/AttachmentClaims.test.ts
182A apps/server/src/orchestration-v2/AttachmentClaims.ts
183A apps/server/src/orchestration-v2/AttachmentPrompt.test.ts
184A apps/server/src/orchestration-v2/AttachmentPrompt.ts
185A apps/server/src/orchestration-v2/CheckpointCaptureService.test.ts
186A apps/server/src/orchestration-v2/CheckpointCaptureService.ts
187A apps/server/src/orchestration-v2/CheckpointPolicy.ts
188A apps/server/src/orchestration-v2/CheckpointRollbackService.test.ts
189A apps/server/src/orchestration-v2/CheckpointRollbackService.ts
190A apps/server/src/orchestration-v2/CheckpointService.test.ts
191A apps/server/src/orchestration-v2/CheckpointService.ts
192A apps/server/src/orchestration-v2/CommandPolicy.test.ts
193A apps/server/src/orchestration-v2/CommandPolicy.ts
194A apps/server/src/orchestration-v2/CommandReceiptStore.ts
195A apps/server/src/orchestration-v2/ContextHandoffService.test.ts
196A apps/server/src/orchestration-v2/ContextHandoffService.ts
197A apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts
198A apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
199A apps/server/src/orchestration-v2/EffectOutbox.ts
200A apps/server/src/orchestration-v2/EffectWorker.test.ts
201A apps/server/src/orchestration-v2/EffectWorker.ts
202A apps/server/src/orchestration-v2/EventSink.ts
203A apps/server/src/orchestration-v2/EventStore.ts
204A apps/server/src/orchestration-v2/FoundationPersistence.test.ts
205A apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts
206A apps/server/src/orchestration-v2/IdAllocator.ts
207A apps/server/src/orchestration-v2/KeyedSerialExecutor.test.ts
208A apps/server/src/orchestration-v2/KeyedSerialExecutor.ts
209A apps/server/src/orchestration-v2/LegacyV1ThreadImporter.test.ts
210A apps/server/src/orchestration-v2/LegacyV1ThreadImporter.ts
211A apps/server/src/orchestration-v2/Orchestrator.migration.test.ts
212A apps/server/src/orchestration-v2/Orchestrator.ts
213A apps/server/src/orchestration-v2/ProjectionMaintenance.ts
214A apps/server/src/orchestration-v2/ProjectionRecovery.test.ts
215A apps/server/src/orchestration-v2/ProjectionStore.test.ts
216A apps/server/src/orchestration-v2/ProjectionStore.ts
217A apps/server/src/orchestration-v2/ProviderAdapter.ts
218A apps/server/src/orchestration-v2/ProviderAdapterDriver.ts
219A apps/server/src/orchestration-v2/ProviderAdapterRegistry.test.ts
220A apps/server/src/orchestration-v2/ProviderAdapterRegistry.ts
221A apps/server/src/orchestration-v2/ProviderContinuationRequests.ts
222A apps/server/src/orchestration-v2/ProviderContinuationService.test.ts
223A apps/server/src/orchestration-v2/ProviderContinuationService.ts
224A apps/server/src/orchestration-v2/ProviderEventIngestor.test.ts
225A apps/server/src/orchestration-v2/ProviderEventIngestor.ts
226A apps/server/src/orchestration-v2/ProviderFailure.test.ts
227A apps/server/src/orchestration-v2/ProviderFailure.ts
228A apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.regression.test.ts
229A apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.test.ts
230A apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts
231A apps/server/src/orchestration-v2/ProviderSelectionTransition.test.ts
232A apps/server/src/orchestration-v2/ProviderSelectionTransition.ts
233A apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
234A apps/server/src/orchestration-v2/ProviderSessionManager.ts
235A apps/server/src/orchestration-v2/ProviderSessionTransitionPolicy.test.ts
236A apps/server/src/orchestration-v2/ProviderSessionTransitionPolicy.ts
237A apps/server/src/orchestration-v2/ProviderSwitchService.test.ts
238A apps/server/src/orchestration-v2/ProviderSwitchService.ts
239A apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
240A apps/server/src/orchestration-v2/ProviderTurnControlService.ts
241A apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts
242A apps/server/src/orchestration-v2/ProviderTurnStartService.testkit.ts
243A apps/server/src/orchestration-v2/ProviderTurnStartService.ts
244A apps/server/src/orchestration-v2/ProviderTurnTokenUsage.test.ts
245A apps/server/src/orchestration-v2/QueuedRunOrder.test.ts
246A apps/server/src/orchestration-v2/QueuedRunOrder.ts
247A apps/server/src/orchestration-v2/RandomUuid.ts
248A apps/server/src/orchestration-v2/ResourceCleanupService.ts
249A apps/server/src/orchestration-v2/RunExecutionService.test.ts
250A apps/server/src/orchestration-v2/RunExecutionService.ts
251A apps/server/src/orchestration-v2/RunFinalizationService.test.ts
252A apps/server/src/orchestration-v2/RunFinalizationService.ts
253A apps/server/src/orchestration-v2/RuntimePolicy.test.ts
254A apps/server/src/orchestration-v2/RuntimePolicy.ts
255A apps/server/src/orchestration-v2/RuntimeRequestService.test.ts
256A apps/server/src/orchestration-v2/RuntimeRequestService.ts
257A apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts
258A apps/server/src/orchestration-v2/ShellStream.test.ts
259A apps/server/src/orchestration-v2/ShellStream.ts
260A apps/server/src/orchestration-v2/SubagentProjection.test.ts
261A apps/server/src/orchestration-v2/SubagentProjection.ts
262A apps/server/src/orchestration-v2/TODO.md
263A apps/server/src/orchestration-v2/ThreadForkService.test.ts
264A apps/server/src/orchestration-v2/ThreadForkService.ts
265A apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
266A apps/server/src/orchestration-v2/ThreadLaunchService.ts
267A apps/server/src/orchestration-v2/ThreadLifecycleService.test.ts
268A apps/server/src/orchestration-v2/ThreadLifecycleService.ts
269R060 apps/server/src/orchestration/ThreadLiveEventCoalescer.test.ts apps/server/src/orchestration-v2/ThreadLiveEventCoalescer.test.ts
270R062 apps/server/src/orchestration/ThreadLiveEventCoalescer.ts apps/server/src/orchestration-v2/ThreadLiveEventCoalescer.ts
271A apps/server/src/orchestration-v2/ThreadManagementService.test.ts
272A apps/server/src/orchestration-v2/ThreadManagementService.ts
273A apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
274R059 apps/server/src/orchestration/ThreadSettlementReactor.ts apps/server/src/orchestration-v2/ThreadSettlementService.ts
275A apps/server/src/orchestration-v2/ThreadStream.test.ts
276A apps/server/src/orchestration-v2/ThreadStream.ts
277A apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts
278A apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts
279A apps/server/src/orchestration-v2/TurnItemPositionStore.ts
280A apps/server/src/orchestration-v2/UserFacingErrors.test.ts
281A apps/server/src/orchestration-v2/UserFacingErrors.ts
282A apps/server/src/orchestration-v2/V1ImportBoundary.test.ts
283A apps/server/src/orchestration-v2/WireProjection.test.ts
284A apps/server/src/orchestration-v2/WireProjection.ts
285A apps/server/src/orchestration-v2/applicationLayer.ts
286A apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts
287A apps/server/src/orchestration-v2/http.ts
288A apps/server/src/orchestration-v2/runtimeLayer.test.ts
289A apps/server/src/orchestration-v2/runtimeLayer.ts
290A apps/server/src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
291A apps/server/src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts
292A apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts
293A apps/server/src/orchestration-v2/testkit/OrchestratorReplayFixtures.contract.test.ts
294A apps/server/src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts
295A apps/server/src/orchestration-v2/testkit/OrchestratorReplayRecovery.integration.test.ts
296A apps/server/src/orchestration-v2/testkit/OrchestratorScenario.ts
297A apps/server/src/orchestration-v2/testkit/ProviderReplayGate.testkit.test.ts
298A apps/server/src/orchestration-v2/testkit/ProviderReplayGate.testkit.ts
299A apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
300A apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
301A apps/server/src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts
302A apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.test.ts
303A apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts
304A apps/server/src/orchestration-v2/testkit/ThreadFork.integration.test.ts
305A apps/server/src/orchestration-v2/testkit/ThreadMergeBack.integration.test.ts
306A apps/server/src/orchestration-v2/testkit/fixtures/acp_elicitation/grok_transcript.ndjson
307A apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/claude_transcript.ndjson
308A apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/input.ts
309A apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/output.ts
310A apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/claude_transcript.ndjson
311A apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/input.ts
312A apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/output.ts
313A apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/claude_transcript.ndjson
314A apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/input.ts
315A apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/output.ts
316A apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/claude_transcript.ndjson
317A apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/input.ts
318A apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/output.ts
319A apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/grok_transcript.ndjson
320A apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/input.ts
321A apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/output.ts
322A apps/server/src/orchestration-v2/testkit/fixtures/index.ts
323A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_output.ts
324A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson
325A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_output.ts
326A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson
327A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/cursor_output.ts
328A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/cursor_transcript.ndjson
329A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_output.ts
330A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_transcript.ndjson
331A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/input.ts
332A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/claude_output.ts
333A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/claude_transcript.ndjson
334A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_output.ts
335A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson
336A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/cursor_transcript.ndjson
337A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/grok_transcript.ndjson
338A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/input.ts
339A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn_restart/claude_transcript.ndjson
340A apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/input.ts
341A apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/opencode_transcript.ndjson
342A apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/output.ts
343A apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/input.ts
344A apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/opencode_transcript.ndjson
345A apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/output.ts
346A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_output.ts
347A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson
348A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/grok_transcript.ndjson
349A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/input.ts
350A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/opencode_output.ts
351A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/opencode_transcript.ndjson
352A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_output.ts
353A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson
354A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/cursor_output.ts
355A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/cursor_transcript.ndjson
356A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/input.ts
357A apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson
358A apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/cursor_transcript.ndjson
359A apps/server/src/orchestration-v2/testkit/fixtures/queued_cancelled_while_active/codex_output.ts
360A apps/server/src/orchestration-v2/testkit/fixtures/queued_cancelled_while_active/input.ts
361A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/claude_transcript.ndjson
362A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_output.ts
363A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson
364A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/cursor_transcript.ndjson
365A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/grok_transcript.ndjson
366A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/input.ts
367A apps/server/src/orchestration-v2/testkit/fixtures/shared.ts
368A apps/server/src/orchestration-v2/testkit/fixtures/simple/claude_output.ts
369A apps/server/src/orchestration-v2/testkit/fixtures/simple/claude_transcript.ndjson
370A apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_output.ts
371A apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson
372A apps/server/src/orchestration-v2/testkit/fixtures/simple/cursor_transcript.ndjson
373A apps/server/src/orchestration-v2/testkit/fixtures/simple/grok_transcript.ndjson
374A apps/server/src/orchestration-v2/testkit/fixtures/simple/input.ts
375A apps/server/src/orchestration-v2/testkit/fixtures/simple/opencode_transcript.ndjson
376A apps/server/src/orchestration-v2/testkit/fixtures/subagent/claude_output.ts
377A apps/server/src/orchestration-v2/testkit/fixtures/subagent/claude_transcript.ndjson
378A apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_output.ts
379A apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson
380A apps/server/src/orchestration-v2/testkit/fixtures/subagent/cursor_output.ts
381A apps/server/src/orchestration-v2/testkit/fixtures/subagent/cursor_transcript.ndjson
382A apps/server/src/orchestration-v2/testkit/fixtures/subagent/input.ts
383A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/README.md
384A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_output.ts
385A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson
386A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/input.ts
387A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_output.ts
388A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson
389A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/input.ts
390A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_output.ts
391A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson
392A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/claude_transcript.ndjson
393A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson
394A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/README.md
395A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/claude_transcript.ndjson
396A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson
397A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_fork_local_rollback/claude_transcript.ndjson
398A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/claude_transcript.ndjson
399A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson
400A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/README.md
401A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/claude_transcript.ndjson
402A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson
403A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/README.md
404A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/claude_transcript.ndjson
405A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson
406A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/README.md
407A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/claude_transcript.ndjson
408A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson
409A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/claude_output.ts
410A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/claude_transcript.ndjson
411A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_output.ts
412A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson
413A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/input.ts
414A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_output.ts
415A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson
416A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/cursor_output.ts
417A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/cursor_transcript.ndjson
418A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/grok_output.ts
419A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/grok_transcript.ndjson
420A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/input.ts
421A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/claude_output.ts
422A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/claude_transcript.ndjson
423A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/cursor_output.ts
424A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/cursor_transcript.ndjson
425A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/grok_transcript.ndjson
426A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/input.ts
427A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/claude_output.ts
428A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/claude_transcript.ndjson
429A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_output.ts
430A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson
431A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/grok_transcript.ndjson
432A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/input.ts
433A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/claude_output.ts
434A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/claude_transcript.ndjson
435A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_output.ts
436A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson
437A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/input.ts
438A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/claude_output.ts
439A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/claude_transcript.ndjson
440A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_output.ts
441A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson
442A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/input.ts
443A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/claude_output.ts
444A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/claude_transcript.ndjson
445A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_output.ts
446A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson
447A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/grok_transcript.ndjson
448A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/input.ts
449A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/opencode_transcript.ndjson
450A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/claude_output.ts
451A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/claude_transcript.ndjson
452A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_output.ts
453A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson
454A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/cursor_output.ts
455A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/cursor_transcript.ndjson
456A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/input.ts
457A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/claude_output.ts
458A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/claude_transcript.ndjson
459A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/input.ts
460A apps/server/src/orchestration-v2/testkit/fixtures/web_search/claude_output.ts
461A apps/server/src/orchestration-v2/testkit/fixtures/web_search/claude_transcript.ndjson
462A apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_output.ts
463A apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson
464A apps/server/src/orchestration-v2/testkit/fixtures/web_search/input.ts
465A apps/server/src/orchestration-v2/testkit/index.ts
466A apps/server/src/orchestration-v2/threadHistoryPaging.test.ts
467A apps/server/src/orchestration-v2/threadHistoryPaging.ts
468D apps/server/src/orchestration/ActivityPayloadProjection.test.ts
469D apps/server/src/orchestration/ActivityPayloadProjection.ts
470D apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
471D apps/server/src/orchestration/Layers/CheckpointReactor.ts
472D apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts
473M apps/server/src/orchestration/Layers/OrchestrationEngine.ts
474D apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts
475D apps/server/src/orchestration/Layers/OrchestrationReactor.ts
476A apps/server/src/orchestration/Layers/ProjectEnrichmentProjection.test.ts
477D apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts
478M apps/server/src/orchestration/Layers/ProjectionPipeline.ts
479A apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.search.test.ts
480D apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts
481M apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
482D apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
483D apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
484D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.activity.test.ts
485D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts
486D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
487D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
488D apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts
489D apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts
490D apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts
491M apps/server/src/orchestration/LiveStreamBudget.test.ts
492M apps/server/src/orchestration/LiveStreamBudget.ts
493D apps/server/src/orchestration/Normalizer.attachments.test.ts
494D apps/server/src/orchestration/Normalizer.test.ts
495D apps/server/src/orchestration/Normalizer.ts
496M apps/server/src/orchestration/Schemas.ts
497D apps/server/src/orchestration/Services/CheckpointReactor.ts
498M apps/server/src/orchestration/Services/OrchestrationEngine.ts
499D apps/server/src/orchestration/Services/OrchestrationReactor.ts
500M apps/server/src/orchestration/Services/ProjectionPipeline.ts
501M apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts
502D apps/server/src/orchestration/Services/ProviderCommandReactor.ts
503D apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts
504D apps/server/src/orchestration/Services/RuntimeReceiptBus.ts
505D apps/server/src/orchestration/Services/ThreadDeletionReactor.ts
506D apps/server/src/orchestration/ThreadSettlementReactor.test.ts
507D apps/server/src/orchestration/commandInvariants.test.ts
508M apps/server/src/orchestration/commandInvariants.ts
509D apps/server/src/orchestration/decider.delete.test.ts
510M apps/server/src/orchestration/decider.ts
511D apps/server/src/orchestration/http.ts
512D apps/server/src/orchestration/projector.test.ts
513M apps/server/src/orchestration/projector.ts
514M apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts
515A apps/server/src/persistence/Layers/OrchestrationEventStore.sequence.test.ts
516M apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts
517M apps/server/src/persistence/Layers/OrchestrationEventStore.ts
518M apps/server/src/persistence/Layers/ProjectionCheckpoints.ts
519M apps/server/src/persistence/Layers/ProjectionTurns.ts
520M apps/server/src/persistence/Migrations.ts
521A apps/server/src/persistence/Migrations/048_049_OrchestrationV2.test.ts
522A apps/server/src/persistence/Migrations/048_OrchestrationV2.ts
523A apps/server/src/persistence/Migrations/049_OrchestrationV2Subagents.ts
524A apps/server/src/persistence/Migrations/050_OrchestrationV2Foundation.test.ts
525A apps/server/src/persistence/Migrations/050_OrchestrationV2Foundation.ts
526A apps/server/src/persistence/Migrations/051_OrchestrationV2ProviderSessionBindings.ts
527A apps/server/src/persistence/Migrations/052_OrchestrationV2ThreadLaunchWorkflows.ts
528A apps/server/src/persistence/Migrations/053_ApplicationEventSource.test.ts
529A apps/server/src/persistence/Migrations/053_ApplicationEventSource.ts
530A apps/server/src/persistence/Migrations/054_OrchestrationV2EffectCancellation.test.ts
531A apps/server/src/persistence/Migrations/054_OrchestrationV2EffectCancellation.ts
532A apps/server/src/persistence/Migrations/055_ScheduledTasks.ts
533A apps/server/src/persistence/Migrations/056_LegacyV1ImportState.ts
534A apps/server/src/persistence/Migrations/057_ApplicationEventSequenceIndexes.ts
535A apps/server/src/persistence/Migrations/058_OrchestrationV2RecoveryIndexes.ts
536M apps/server/src/persistence/ProviderSessionRuntime.ts
537M apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts
538M apps/server/src/persistence/Services/OrchestrationEventStore.ts
539M apps/server/src/persistence/Services/ProjectionCheckpoints.ts
540M apps/server/src/persistence/Services/ProjectionPendingApprovals.ts
541M apps/server/src/persistence/Services/ProjectionThreadActivities.ts
542M apps/server/src/persistence/Services/ProjectionThreadMessages.ts
543M apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts
544M apps/server/src/persistence/Services/ProjectionThreadSessions.ts
545M apps/server/src/persistence/Services/ProjectionTurns.ts
546A apps/server/src/project/ProjectEnrichmentService.test.ts
547A apps/server/src/project/ProjectEnrichmentService.ts
548A apps/server/src/project/ProjectService.test.ts
549A apps/server/src/project/ProjectService.ts
550M apps/server/src/project/ProjectSetupScriptRunner.test.ts
551M apps/server/src/project/ProjectSetupScriptRunner.ts
552A apps/server/src/project/http.test.ts
553A apps/server/src/project/http.ts
554A apps/server/src/provider/ClaudeTurnTokenUsage.ts
555M apps/server/src/provider/CodexDeveloperInstructions.ts
556A apps/server/src/provider/CodexToolPresentation.ts
557A apps/server/src/provider/CodexTurnTokenUsage.ts
558A apps/server/src/provider/Drivers/AcpRegistryDriver.ts
559M apps/server/src/provider/Drivers/AntigravityDriver.test.ts
560M apps/server/src/provider/Drivers/AntigravityDriver.ts
561M apps/server/src/provider/Drivers/ClaudeDriver.ts
562M apps/server/src/provider/Drivers/CodexDriver.ts
563M apps/server/src/provider/Drivers/CursorDriver.ts
564A apps/server/src/provider/Drivers/CursorSkills.test.ts
565M apps/server/src/provider/Drivers/GrokDriver.ts
566M apps/server/src/provider/Drivers/OpenCodeDriver.ts
567M apps/server/src/provider/Errors.ts
568D apps/server/src/provider/Layers/AntigravityAdapter.test.ts
569D apps/server/src/provider/Layers/AntigravityAdapter.ts
570D apps/server/src/provider/Layers/ClaudeAdapter.test.ts
571D apps/server/src/provider/Layers/ClaudeAdapter.ts
572D apps/server/src/provider/Layers/CodexAdapter.test.ts
573D apps/server/src/provider/Layers/CodexAdapter.ts
574D apps/server/src/provider/Layers/CursorAdapter.test.ts
575D apps/server/src/provider/Layers/CursorAdapter.ts
576M apps/server/src/provider/Layers/CursorProvider.test.ts
577M apps/server/src/provider/Layers/CursorProvider.ts
578A apps/server/src/provider/Layers/CursorSdkCatalog.ts
579M apps/server/src/provider/Layers/EventNdjsonLogger.test.ts
580M apps/server/src/provider/Layers/EventNdjsonLogger.ts
581D apps/server/src/provider/Layers/GrokAdapter.test.ts
582D apps/server/src/provider/Layers/GrokAdapter.ts
583M apps/server/src/provider/Layers/GrokProvider.ts
584D apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
585D apps/server/src/provider/Layers/OpenCodeAdapter.ts
586D apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts
587D apps/server/src/provider/Layers/ProviderAdapterRegistry.ts
588M apps/server/src/provider/Layers/ProviderAuthService.test.ts
589M apps/server/src/provider/Layers/ProviderAuthService.ts
590M apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts
591M apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts
592A apps/server/src/provider/Layers/ProviderOrchestrationAdapterInfrastructure.ts
593M apps/server/src/provider/Layers/ProviderRegistry.test.ts
594M apps/server/src/provider/Layers/ProviderRegistry.ts
595D apps/server/src/provider/Layers/ProviderService.test.ts
596D apps/server/src/provider/Layers/ProviderService.ts
597D apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts
598D apps/server/src/provider/Layers/ProviderSessionDirectory.ts
599D apps/server/src/provider/Layers/ProviderSessionReaper.test.ts
600D apps/server/src/provider/Layers/ProviderSessionReaper.ts
601M apps/server/src/provider/Layers/ProviderUsageLimitsIngestion.ts
602A apps/server/src/provider/NativeProtocolLogging.ts
603M apps/server/src/provider/ProviderDriver.ts
604D apps/server/src/provider/Services/ClaudeAdapter.ts
605D apps/server/src/provider/Services/CodexAdapter.ts
606D apps/server/src/provider/Services/CursorAdapter.ts
607D apps/server/src/provider/Services/GrokAdapter.ts
608D apps/server/src/provider/Services/OpenCodeAdapter.ts
609D apps/server/src/provider/Services/ProviderAdapter.ts
610D apps/server/src/provider/Services/ProviderAdapterRegistry.ts
611D apps/server/src/provider/Services/ProviderService.ts
612D apps/server/src/provider/Services/ProviderSessionDirectory.ts
613D apps/server/src/provider/Services/ProviderSessionReaper.ts
614A apps/server/src/provider/T3OrchestrationInstructions.test.ts
615A apps/server/src/provider/T3OrchestrationInstructions.ts
616A apps/server/src/provider/TurnTokenUsage.test.ts
617D apps/server/src/provider/acp/AcpAdapterSupport.test.ts
618D apps/server/src/provider/acp/AcpAdapterSupport.ts
619M apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts
620M apps/server/src/provider/acp/AcpNativeLogging.ts
621A apps/server/src/provider/acp/AcpRegistrySupport.test.ts
622A apps/server/src/provider/acp/AcpRegistrySupport.ts
623M apps/server/src/provider/acp/AcpRuntimeModel.test.ts
624M apps/server/src/provider/acp/AcpRuntimeModel.ts
625A apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts
626M apps/server/src/provider/acp/AcpSessionRuntime.ts
627M apps/server/src/provider/acp/AntigravityAcpSupport.ts
628A apps/server/src/provider/acp/AntigravityClientFiles.ts
629M apps/server/src/provider/acp/AntigravityProtocol.ts
630D apps/server/src/provider/acp/CursorAcpCliProbe.test.ts
631D apps/server/src/provider/acp/CursorAcpExtension.test.ts
632D apps/server/src/provider/acp/CursorAcpExtension.ts
633D apps/server/src/provider/acp/CursorAcpSupport.test.ts
634D apps/server/src/provider/acp/CursorAcpSupport.ts
635M apps/server/src/provider/acp/GrokAcpCliProbe.test.ts
636M apps/server/src/provider/acp/GrokAcpSupport.test.ts
637M apps/server/src/provider/acp/GrokAcpSupport.ts
638M apps/server/src/provider/acp/XAiAcpExtension.test.ts
639M apps/server/src/provider/acp/XAiAcpExtension.ts
640M apps/server/src/provider/builtInDrivers.ts
641A apps/server/src/provider/cursorSdkModel.ts
642M apps/server/src/provider/model-manifest.json
643M apps/server/src/provider/providerInstallation.test.ts
644M apps/server/src/provider/providerMaintenanceRunner.test.ts
645D apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts
646D apps/server/src/relay/AgentAwarenessRelay.test.ts
647M apps/server/src/relay/AgentAwarenessRelay.ts
648A apps/server/src/scheduledTasks/Schedule.test.ts
649A apps/server/src/scheduledTasks/Schedule.ts
650A apps/server/src/scheduledTasks/ScheduledTaskService.ts
651D apps/server/src/server.test.ts
652M apps/server/src/server.ts
653M apps/server/src/serverActivation.ts
654M apps/server/src/serverLifecycleEvents.test.ts
655M apps/server/src/serverLifecycleEvents.ts
656D apps/server/src/serverRuntimeStartup.reconcile.test.ts
657M apps/server/src/serverRuntimeStartup.test.ts
658M apps/server/src/serverRuntimeStartup.ts
659M apps/server/src/terminal/Manager.test.ts
660M apps/server/src/terminal/Manager.ts
661M apps/server/src/textGeneration/CodexTextGeneration.test.ts
662M apps/server/src/textGeneration/CodexTextGeneration.ts
663M apps/server/src/textGeneration/CursorTextGeneration.test.ts
664M apps/server/src/textGeneration/CursorTextGeneration.ts
665M apps/server/src/textGeneration/TextGeneration.test.ts
666M apps/server/src/vcs/GitVcsDriver.ts
667M apps/server/src/vcs/GitVcsDriverCore.test.ts
668M apps/server/src/vcs/GitVcsDriverCore.ts
669A apps/server/src/ws.test.ts
670M apps/server/src/ws.ts
671D apps/server/test/ActivityPayloadProjection.test.ts
672M apps/web/src/appearanceFonts.test.ts
673M apps/web/src/appearanceFonts.ts
674M apps/web/src/components/AppSidebarLayout.tsx
675M apps/web/src/components/BranchToolbar.logic.test.ts
676M apps/web/src/components/BranchToolbar.logic.ts
677M apps/web/src/components/BranchToolbar.tsx
678M apps/web/src/components/BranchToolbarBranchSelector.tsx
679M apps/web/src/components/BranchToolbarEnvModeSelector.tsx
680M apps/web/src/components/BranchToolbarEnvironmentSelector.tsx
681M apps/web/src/components/ChatView.logic.test.ts
682M apps/web/src/components/ChatView.logic.ts
683M apps/web/src/components/ChatView.tsx
684M apps/web/src/components/CommandPalette.logic.test.ts
685M apps/web/src/components/CommandPalette.logic.ts
686M apps/web/src/components/CommandPalette.tsx
687M apps/web/src/components/DiffPanel.tsx
688M apps/web/src/components/GitActionsControl.logic.test.ts
689M apps/web/src/components/GitActionsControl.logic.ts
690M apps/web/src/components/GitActionsControl.tsx
691M apps/web/src/components/LegacySidebar.tsx
692A apps/web/src/components/LegacyThreadMigrationToast.tsx
693M apps/web/src/components/ProjectScriptsControl.tsx
694M apps/web/src/components/RightPanelTabs.tsx
695M apps/web/src/components/Sidebar.logic.test.ts
696M apps/web/src/components/Sidebar.logic.ts
697M apps/web/src/components/Sidebar.tsx
698M apps/web/src/components/ThreadStatusIndicators.tsx
699M apps/web/src/components/chat/ChangedFilesTree.test.tsx
700M apps/web/src/components/chat/ChangedFilesTree.tsx
701M apps/web/src/components/chat/ChatComposer.tsx
702M apps/web/src/components/chat/ChatHeader.tsx
703M apps/web/src/components/chat/ComposerBanner.tsx
704M apps/web/src/components/chat/ComposerBannerStack.tsx
705M apps/web/src/components/chat/ComposerPendingApprovalActions.test.tsx
706M apps/web/src/components/chat/ComposerPendingApprovalActions.tsx
707M apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx
708M apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
709M apps/web/src/components/chat/ComposerPendingUserInputPanel.test.tsx
710M apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
711M apps/web/src/components/chat/ComposerPrimaryActions.test.tsx
712M apps/web/src/components/chat/ComposerPrimaryActions.tsx
713M apps/web/src/components/chat/ComposerServerUpdateStatus.tsx
714D apps/web/src/components/chat/ContextWindowMeter.test.tsx
715M apps/web/src/components/chat/MessagesTimeline.logic.test.ts
716M apps/web/src/components/chat/MessagesTimeline.logic.ts
717M apps/web/src/components/chat/MessagesTimeline.test.tsx
718M apps/web/src/components/chat/MessagesTimeline.tsx
719A apps/web/src/components/chat/OpenInPicker.logic.ts
720R073 apps/web/src/components/chat/ChatHeader.test.ts apps/web/src/components/chat/OpenInPicker.test.ts
721M apps/web/src/components/chat/OpenInPicker.tsx
722A apps/web/src/components/chat/OpenInPickerShortcut.ts
723M apps/web/src/components/chat/PanelLayoutControls.tsx
724M apps/web/src/components/chat/ProposedPlanCard.tsx
725A apps/web/src/components/chat/QueuedRunsControl.test.tsx
726A apps/web/src/components/chat/QueuedRunsControl.tsx
727A apps/web/src/components/chat/ThreadAutomationsPanel.tsx
728A apps/web/src/components/chat/ThreadDetailsPanel.test.tsx
729A apps/web/src/components/chat/ThreadDetailsPanel.tsx
730A apps/web/src/components/chat/ThreadDetailsPrRow.tsx
731A apps/web/src/components/chat/ThreadRelationshipsControl.test.tsx
732A apps/web/src/components/chat/ThreadRelationshipsControl.tsx
733A apps/web/src/components/chat/TimelineSystemDivider.tsx
734A apps/web/src/components/chat/V2ItemInspector.tsx
735A apps/web/src/components/chat/V2LifecycleRow.tsx
736A apps/web/src/components/chat/composerDispatch.test.ts
737A apps/web/src/components/chat/composerDispatch.ts
738M apps/web/src/components/chat/externalLinkContextMenu.test.ts
739M apps/web/src/components/chat/externalLinkContextMenu.ts
740A apps/web/src/components/chat/threadDetailsPanelStyles.ts
741M apps/web/src/components/chat/useAssistantCitationTarget.ts
742M apps/web/src/components/files/FilePreviewPanel.tsx
743M apps/web/src/components/preview/PreviewPanelShell.tsx
744M apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx
745M apps/web/src/components/preview/addBrowserSurface.test.ts
746M apps/web/src/components/preview/previewMiniPlayerLayout.test.ts
747M apps/web/src/components/preview/previewMiniPlayerLayout.ts
748M apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
749M apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts
750M apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
751M apps/web/src/components/pullRequest/pullRequestPresentation.tsx
752A apps/web/src/components/pullRequest/usePullRequestActions.ts
753M apps/web/src/components/settings/AddProviderInstanceDialog.tsx
754M apps/web/src/components/settings/KeybindingsSettings.logic.test.ts
755M apps/web/src/components/settings/ProjectSettingsPanel.tsx
756M apps/web/src/components/settings/ProviderInstanceCard.test.ts
757M apps/web/src/components/settings/ProviderInstanceCard.tsx
758M apps/web/src/components/settings/ProviderSettingsForm.test.ts
759A apps/web/src/components/settings/ScheduledTasksSettings.tsx
760M apps/web/src/components/settings/SettingsPanels.tsx
761M apps/web/src/components/settings/SettingsSidebarNav.tsx
762M apps/web/src/components/settings/providerDriverMeta.ts
763M apps/web/src/components/settings/settingsSearch.test.ts
764M apps/web/src/components/settings/settingsSearch.ts
765M apps/web/src/components/ui/popover.tsx
766M apps/web/src/composerDraftStore.ts
767M apps/web/src/connection/runtime.ts
768M apps/web/src/connection/storage.ts
769M apps/web/src/diffFileActions.test.ts
770M apps/web/src/diffPanelStore.test.ts
771M apps/web/src/diffPanelStore.ts
772A apps/web/src/hooks/useElementWidth.ts
773M apps/web/src/hooks/useHandleNewThread.ts
774A apps/web/src/hooks/usePreviewPanelInlineSize.ts
775M apps/web/src/hooks/useThreadActionMenu.ts
776M apps/web/src/hooks/useThreadActions.ts
777A apps/web/src/hooks/useThreadVisitedMigration.ts
778M apps/web/src/hooks/useTurnDiffSummaries.ts
779M apps/web/src/index.css
780M apps/web/src/keybindings.test.ts
781M apps/web/src/keybindings.ts
782M apps/web/src/lib/contextWindow.test.ts
783M apps/web/src/lib/contextWindow.ts
784A apps/web/src/lib/orchestrationV2Timeline.test.ts
785A apps/web/src/lib/orchestrationV2Timeline.ts
786M apps/web/src/lib/threadSort.test.ts
787M apps/web/src/pendingUserInput.test.ts
788M apps/web/src/pendingUserInput.ts
789M apps/web/src/providerInstances.test.ts
790M apps/web/src/providerInstances.ts
791A apps/web/src/providerUpdateDismissal.test.ts
792M apps/web/src/providerUpdateDismissal.ts
793A apps/web/src/rightPanelLayout.test.ts
794M apps/web/src/rightPanelLayout.ts
795M apps/web/src/rightPanelStore.test.ts
796M apps/web/src/rightPanelStore.ts
797M apps/web/src/routeTree.gen.ts
798M apps/web/src/routes/__root.tsx
799M apps/web/src/routes/_chat.$environmentId.$threadId.tsx
800M apps/web/src/routes/_chat.draft.$draftId.tsx
801M apps/web/src/routes/_chat.pull-requests.tsx
802A apps/web/src/routes/settings.scheduled-tasks.tsx
803D apps/web/src/session-logic.command-output.test.ts
804M apps/web/src/session-logic.test.ts
805M apps/web/src/session-logic.ts
806M apps/web/src/state/entities.ts
807M apps/web/src/state/queries.ts
808M apps/web/src/state/server.ts
809M apps/web/src/state/sourceControlActions.ts
810D apps/web/src/state/terminalSessions.test.ts
811M apps/web/src/state/terminalSessions.ts
812M apps/web/src/state/threads.ts
813A apps/web/src/state/v2ItemSupport.ts
814A apps/web/src/state/waitForAtomValue.test.ts
815A apps/web/src/state/waitForAtomValue.ts
816A apps/web/src/test-fixtures.ts
817M apps/web/src/threadRoutes.test.ts
818M apps/web/src/threadRoutes.ts
819M apps/web/src/threadSync.test.ts
820M apps/web/src/timestampFormat.test.ts
821M apps/web/src/timestampFormat.ts
822M apps/web/src/types.ts
823M apps/web/src/uiStateStore.test.ts
824M apps/web/src/versionSkew.test.ts
825M apps/web/src/versionSkew.ts
826M apps/web/src/worktreeCleanup.test.ts
827M docs/README.md
828M docs/internals/connection-runtime.md
829A docs/internals/context-handoffs.md
830A docs/internals/legacy-orchestration-migration.md
831M docs/internals/overview.md
832A docs/internals/performance-regressions.md
833M docs/internals/providers.md
834M docs/internals/terminal-runtime.md
835A docs/orchestration-v2/README.md
836A docs/orchestration-v2/core-graph-and-data-model.md
837A docs/orchestration-v2/entity-ids-and-correlation.md
838A docs/orchestration-v2/feature-lifecycles.md
839A docs/orchestration-v2/orchestrator-mcp-server.md
840A docs/orchestration-v2/provider-capability-system.md
841A docs/orchestration-v2/provider-switching-and-context.md
842A docs/orchestration-v2/testing-strategy.md
843A docs/orchestration-v2/thread-lineage-and-context-transfer.md
844A docs/user/activity-log.md
845A docs/user/appearance.md
846M docs/user/composer.md
847A docs/user/cursor.md
848A docs/user/portable-handoffs.md
849M docs/user/source-control.md
850D docs/user/terminal.md
851A docs/user/thread-migration.md
852M docs/user/updating.md
853M infra/relay/src/http/Api.test.ts
854M infra/relay/src/http/Api.ts
855M infra/relay/src/worker.ts
856M package.json
857M packages/client-runtime/package.json
858A packages/client-runtime/src/connection/compatibility.test.ts
859A packages/client-runtime/src/connection/compatibility.ts
860M packages/client-runtime/src/connection/registry.test.ts
861M packages/client-runtime/src/connection/resolver.test.ts
862M packages/client-runtime/src/connection/resolver.ts
863M packages/client-runtime/src/operations/commands.test.ts
864M packages/client-runtime/src/operations/commands.ts
865M packages/client-runtime/src/operations/index.ts
866M packages/client-runtime/src/operations/projects.test.ts
867M packages/client-runtime/src/operations/projects.ts
868A packages/client-runtime/src/operations/threadTitle.test.ts
869A packages/client-runtime/src/operations/threadTitle.ts
870M packages/client-runtime/src/platform/index.ts
871A packages/client-runtime/src/platform/orchestrationCache.test.ts
872A packages/client-runtime/src/platform/orchestrationCache.ts
873M packages/client-runtime/src/platform/persistence.ts
874M packages/client-runtime/src/rpc/client.ts
875M packages/client-runtime/src/state/archivedThreads.test.ts
876M packages/client-runtime/src/state/archivedThreads.ts
877A packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts
878A packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts
879M packages/client-runtime/src/state/entities.test.ts
880A packages/client-runtime/src/state/environmentHttpAuth.test.ts
881M packages/client-runtime/src/state/environmentHttpAuth.ts
882A packages/client-runtime/src/state/itemSupport.test.ts
883A packages/client-runtime/src/state/itemSupport.ts
884M packages/client-runtime/src/state/models.ts
885M packages/client-runtime/src/state/orchestration.ts
886A packages/client-runtime/src/state/orchestrationV2Projection.test.ts
887A packages/client-runtime/src/state/orchestrationV2Projection.ts
888A packages/client-runtime/src/state/orchestrationV2TestFixtures.ts
889M packages/client-runtime/src/state/projectEntities.ts
890M packages/client-runtime/src/state/server.ts
891M packages/client-runtime/src/state/shell-sync.test.ts
892M packages/client-runtime/src/state/shell.test.ts
893M packages/client-runtime/src/state/shell.ts
894M packages/client-runtime/src/state/shellReducer.test.ts
895M packages/client-runtime/src/state/shellReducer.ts
896M packages/client-runtime/src/state/shellSnapshotHttp.ts
897M packages/client-runtime/src/state/snapshots.ts
898M packages/client-runtime/src/state/subagentRuntime.ts
899A packages/client-runtime/src/state/threadCheckpoints.ts
900M packages/client-runtime/src/state/threadCommands.ts
901A packages/client-runtime/src/state/threadDetail.test.ts
902M packages/client-runtime/src/state/threadDetail.ts
903A packages/client-runtime/src/state/threadExecution.test.ts
904A packages/client-runtime/src/state/threadExecution.ts
905M packages/client-runtime/src/state/threadFeedback.test.ts
906M packages/client-runtime/src/state/threadFeedback.ts
907A packages/client-runtime/src/state/threadHistoryController.test.ts
908A packages/client-runtime/src/state/threadHistoryController.ts
909A packages/client-runtime/src/state/threadHistoryHttp.ts
910A packages/client-runtime/src/state/threadHistoryMerge.test.ts
911A packages/client-runtime/src/state/threadHistoryMerge.ts
912D packages/client-runtime/src/state/threadReducer.test.ts
913D packages/client-runtime/src/state/threadReducer.ts
914A packages/client-runtime/src/state/threadRelationships.test.ts
915A packages/client-runtime/src/state/threadRelationships.ts
916A packages/client-runtime/src/state/threadRequests.test.ts
917A packages/client-runtime/src/state/threadRequests.ts
918M packages/client-runtime/src/state/threadRetention.ts
919M packages/client-runtime/src/state/threadSettled.ts
920A packages/client-runtime/src/state/threadShell.test.ts
921M packages/client-runtime/src/state/threadShell.ts
922M packages/client-runtime/src/state/threadSnapshotHttp.ts
923M packages/client-runtime/src/state/threadSort.test.ts
924M packages/client-runtime/src/state/threadSort.ts
925M packages/client-runtime/src/state/threadState.ts
926A packages/client-runtime/src/state/threadWorkflows.test.ts
927A packages/client-runtime/src/state/threadWorkflows.ts
928M packages/client-runtime/src/state/threads-atoms.test.ts
929D packages/client-runtime/src/state/threads-pagination.test.ts
930M packages/client-runtime/src/state/threads-sync.test.ts
931M packages/client-runtime/src/state/threads.ts
932A packages/client-runtime/src/state/turnItemPresentation.test.ts
933A packages/client-runtime/src/state/turnItemPresentation.ts
934M packages/client-runtime/src/state/vcsAction.test.ts
935M packages/client-runtime/src/state/vcsAction.ts
936A packages/client-runtime/src/t3ToolSummary.test.ts
937A packages/client-runtime/src/t3ToolSummary.ts
938M packages/client-runtime/src/work-log/presentation.test.ts
939M packages/client-runtime/src/work-log/presentation.ts
940M packages/contracts/package.json
941A packages/contracts/src/applicationEvent.test.ts
942A packages/contracts/src/applicationEvent.ts
943M packages/contracts/src/assets.test.ts
944M packages/contracts/src/assets.ts
945M packages/contracts/src/baseSchemas.ts
946A packages/contracts/src/chatAttachment.ts
947A packages/contracts/src/checkpointDiff.ts
948M packages/contracts/src/environment.ts
949M packages/contracts/src/environmentHttp.ts
950M packages/contracts/src/index.ts
951M packages/contracts/src/ipc.ts
952M packages/contracts/src/keybindings.test.ts
953M packages/contracts/src/keybindings.ts
954M packages/contracts/src/model.ts
955A packages/contracts/src/modelSelection.ts
956M packages/contracts/src/orchestration.test.ts
957M packages/contracts/src/orchestration.ts
958A packages/contracts/src/orchestrationProject.ts
959A packages/contracts/src/orchestrationV2.test.ts
960A packages/contracts/src/orchestrationV2.ts
961A packages/contracts/src/orchestratorMcp.test.ts
962A packages/contracts/src/orchestratorMcp.ts
963M packages/contracts/src/project.ts
964M packages/contracts/src/provider.ts
965A packages/contracts/src/providerPolicy.ts
966M packages/contracts/src/providerRuntime.ts
967M packages/contracts/src/rpc.test.ts
968M packages/contracts/src/rpc.ts
969A packages/contracts/src/scheduledTask.test.ts
970A packages/contracts/src/scheduledTask.ts
971M packages/contracts/src/server.ts
972M packages/contracts/src/settings.test.ts
973M packages/contracts/src/settings.ts
974M packages/contracts/src/t3ProjectFile.test.ts
975M packages/contracts/src/t3ProjectFile.ts
976A packages/contracts/src/worktreeMcp.ts
977M packages/effect-acp/src/client.ts
978M packages/effect-acp/src/protocol.test.ts
979M packages/effect-acp/src/protocol.ts
980M packages/effect-codex-app-server/package.json
981M packages/effect-codex-app-server/src/client.ts
982A packages/effect-codex-app-server/src/replay.test.ts
983A packages/effect-codex-app-server/src/replay.ts
984M packages/shared/package.json
985A packages/shared/src/Array.test.ts
986A packages/shared/src/Array.ts
987M packages/shared/src/agentAwareness.test.ts
988M packages/shared/src/agentAwareness.ts
989M packages/shared/src/model.test.ts
990M packages/shared/src/model.ts
991M packages/shared/src/orchestrationTiming.ts
992A packages/shared/src/orchestrationV2PendingBackgroundWork.test.ts
993A packages/shared/src/orchestrationV2PendingBackgroundWork.ts
994A packages/shared/src/orchestrationV2Timeline.test.ts
995A packages/shared/src/orchestrationV2Timeline.ts
996A packages/shared/src/t3McpToolPresentation.test.ts
997A packages/shared/src/t3McpToolPresentation.ts
998M pnpm-lock.yaml
999M vite.config.ts
audits/orchestrator-v2/2026-09-04/changed-files.tsv+991−0
@@ -0,0 +1,991 @@
1D .github/scripts/thread-transfer-report.cjs
2D .github/scripts/thread-transfer-report.test.cjs
3M .github/workflows/ci.yml
4D .github/workflows/thread-transfer-report.yml
5M README.md
6M apps/desktop/src/app/DesktopEnvironment.test.ts
7M apps/desktop/src/backend/tailscaleEndpointProvider.ts
8M apps/desktop/src/settings/DesktopClientSettings.test.ts
9A apps/marketing/public/app-desktop.webp
10M apps/marketing/src/pages/index.astro
11M apps/mobile/generated-uniwind-themes.css
12M apps/mobile/scripts/generate-uniwind-themes.mts
13M apps/mobile/src/components/BrandMark.tsx
14A apps/mobile/src/components/brandAssets.ts
15M apps/mobile/src/connection/environment-cache-store.test.ts
16M apps/mobile/src/connection/environment-cache-store.ts
17M apps/mobile/src/connection/runtime.ts
18M apps/mobile/src/connection/storage.ts
19M apps/mobile/src/features/archive/archivedThreadList.test.ts
20M apps/mobile/src/features/home/HomeScreen.tsx
21M apps/mobile/src/features/home/homeListItems.test.ts
22M apps/mobile/src/features/home/homeThreadList.test.ts
23A apps/mobile/src/features/home/threadArchive.test.ts
24A apps/mobile/src/features/home/threadArchive.ts
25M apps/mobile/src/features/home/useThreadListActions.ts
26M apps/mobile/src/features/keyboard/hardwareKeyboardCommands.ts
27M apps/mobile/src/features/projects/AddProjectScreen.tsx
28M apps/mobile/src/features/review/reviewModel.test.ts
29M apps/mobile/src/features/review/reviewModel.ts
30M apps/mobile/src/features/review/reviewState.test.ts
31M apps/mobile/src/features/review/reviewState.ts
32D apps/mobile/src/features/review/useReviewDiffPrewarming.test.ts
33M apps/mobile/src/features/review/useReviewDiffPrewarming.ts
34M apps/mobile/src/features/review/useReviewSections.ts
35M apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx
36M apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
37M apps/mobile/src/features/threads/PendingApprovalCard.tsx
38M apps/mobile/src/features/threads/PendingUserInputCard.tsx
39A apps/mobile/src/features/threads/ThreadActivityInspector.tsx
40M apps/mobile/src/features/threads/ThreadComposer.tsx
41M apps/mobile/src/features/threads/ThreadDetailScreen.tsx
42M apps/mobile/src/features/threads/ThreadFeed.tsx
43M apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
44A apps/mobile/src/features/threads/ThreadQueueControl.tsx
45A apps/mobile/src/features/threads/ThreadRelationshipsBanner.tsx
46M apps/mobile/src/features/threads/ThreadRouteScreen.tsx
47A apps/mobile/src/features/threads/thread-activity-row-presentation.test.ts
48A apps/mobile/src/features/threads/thread-activity-row-presentation.ts
49A apps/mobile/src/features/threads/thread-feed-item-size.test.ts
50A apps/mobile/src/features/threads/thread-feed-item-size.ts
51M apps/mobile/src/features/threads/thread-list-v2-items.tsx
52A apps/mobile/src/features/threads/threadActivityFileNavigation.test.ts
53A apps/mobile/src/features/threads/threadActivityFileNavigation.ts
54A apps/mobile/src/features/threads/threadForkNavigation.test.ts
55A apps/mobile/src/features/threads/threadForkNavigation.ts
56M apps/mobile/src/features/threads/threadListV2.test.ts
57M apps/mobile/src/features/threads/threadListV2.ts
58M apps/mobile/src/features/threads/threadPresentation.ts
59A apps/mobile/src/features/threads/threadQueueControlPresentation.test.ts
60A apps/mobile/src/features/threads/threadQueueControlPresentation.ts
61A apps/mobile/src/features/threads/userMessageIntentBadge.test.ts
62A apps/mobile/src/features/threads/userMessageIntentBadge.ts
63M apps/mobile/src/lib/modelOptions.ts
64M apps/mobile/src/lib/projectThreadStartTurn.test.ts
65M apps/mobile/src/lib/projectThreadStartTurn.ts
66M apps/mobile/src/lib/scopedEntities.ts
67M apps/mobile/src/lib/threadActivity.test.ts
68M apps/mobile/src/lib/threadActivity.ts
69A apps/mobile/src/lib/threadActivityInspector.test.ts
70A apps/mobile/src/lib/threadActivityInspector.ts
71M apps/mobile/src/state/queries.ts
72M apps/mobile/src/state/threads.ts
73M apps/mobile/src/state/use-pending-new-tasks.ts
74M apps/mobile/src/state/use-selected-thread-requests.ts
75M apps/mobile/src/state/use-selected-thread-worktree.ts
76M apps/mobile/src/state/use-thread-composer-state.ts
77M apps/mobile/src/state/use-thread-detail.ts
78M apps/mobile/src/state/use-thread-outbox-drain.test.ts
79M apps/mobile/src/state/use-thread-outbox-drain.ts
80M apps/mobile/src/state/use-thread-selection.ts
81A apps/mobile/src/state/v2-item-support.ts
82A apps/mobile/src/test-fixtures.ts
83A apps/server/README.md
84D apps/server/integration/NetworkTransferMeasurement.integration.ts
85D apps/server/integration/OrchestrationEngineHarness.integration.ts
86D apps/server/integration/TestProviderAdapter.integration.ts
87D apps/server/integration/TransferBudgetReport.integration.ts
88D apps/server/integration/TransferBudgetScenario.integration.ts
89D apps/server/integration/fixtures/providerRuntime.ts
90D apps/server/integration/fixtures/transferBudget.ts
91D apps/server/integration/orchestrationEngine.integration.test.ts
92D apps/server/integration/orphanedProviderSessionStartup.integration.test.ts
93D apps/server/integration/providerService.integration.test.ts
94M apps/server/package.json
95M apps/server/scripts/acp-mock-agent.ts
96A apps/server/scripts/acp-replay-agent.test.ts
97A apps/server/scripts/acp-replay-agent.ts
98A apps/server/scripts/acp-thread-spawn-helper.c
99A apps/server/scripts/acpMockCancellationState.test.ts
100A apps/server/scripts/acpMockCancellationState.ts
101A apps/server/scripts/claudeReplayRecordingConfig.test.ts
102A apps/server/scripts/claudeReplayRecordingConfig.ts
103A apps/server/scripts/codexReplayRecordingRecords.test.ts
104A apps/server/scripts/codexReplayRecordingRecords.ts
105A apps/server/scripts/cursorReplayRecordingWorkspace.test.ts
106A apps/server/scripts/cursorReplayRecordingWorkspace.ts
107A apps/server/scripts/probe-claude-fork-local-rollback-replay.ts
108A apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts
109A apps/server/scripts/record-codex-app-server-replay-fixture.ts
110A apps/server/scripts/record-cursor-agent-sdk-replay-fixture.ts
111A apps/server/scripts/replayRecorderDeferredRegistry.test.ts
112A apps/server/scripts/replayRecorderDeferredRegistry.ts
113M apps/server/src/attachmentStore.test.ts
114M apps/server/src/attachmentStore.ts
115M apps/server/src/auth/RpcAuthorization.ts
116D apps/server/src/bin.test.ts
117M apps/server/src/checkpointing/CheckpointDiffQuery.test.ts
118M apps/server/src/checkpointing/CheckpointDiffQuery.ts
119A apps/server/src/claudeModelOptions.test.ts
120A apps/server/src/claudeModelOptions.ts
121M apps/server/src/cli/project.test.ts
122M apps/server/src/cli/project.ts
123M apps/server/src/environment/ServerEnvironment.test.ts
124M apps/server/src/environment/ServerEnvironment.ts
125M apps/server/src/git/GitWorkflowService.ts
126M apps/server/src/http.test.ts
127M apps/server/src/httpCors.ts
128M apps/server/src/mcp/McpHttpServer.ts
129M apps/server/src/mcp/McpInvocationContext.ts
130M apps/server/src/mcp/McpProviderSession.ts
131M apps/server/src/mcp/McpSessionRegistry.test.ts
132A apps/server/src/mcp/McpSessionRegistry.testkit.ts
133M apps/server/src/mcp/McpSessionRegistry.ts
134A apps/server/src/mcp/OrchestratorMcpService.activity.test.ts
135A apps/server/src/mcp/OrchestratorMcpService.test.ts
136A apps/server/src/mcp/OrchestratorMcpService.ts
137A apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts
138A apps/server/src/mcp/WorktreeMcpService.test.ts
139A apps/server/src/mcp/WorktreeMcpService.ts
140A apps/server/src/mcp/toolkits/orchestrator/handlers.ts
141A apps/server/src/mcp/toolkits/orchestrator/tools.test.ts
142A apps/server/src/mcp/toolkits/orchestrator/tools.ts
143A apps/server/src/mcp/toolkits/worktree/handlers.ts
144A apps/server/src/mcp/toolkits/worktree/registration.test.ts
145A apps/server/src/mcp/toolkits/worktree/tools.ts
146M apps/server/src/observability/Metrics.ts
147A apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts
148A apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts
149A apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts
150A apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
151A apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts
152A apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts
153A apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.ts
154A apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts
155A apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.ts
156A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
157A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.test.ts
158A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts
159A apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
160A apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
161A apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts
162A apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
163A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.test.ts
164A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.test.ts
165A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts
166A apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.ts
167A apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts
168A apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts
169A apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts
170A apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts
171A apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.ts
172A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts
173A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.test.ts
174A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts
175A apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.ts
176A apps/server/src/orchestration-v2/AttachmentClaims.test.ts
177A apps/server/src/orchestration-v2/AttachmentClaims.ts
178A apps/server/src/orchestration-v2/AttachmentPrompt.test.ts
179A apps/server/src/orchestration-v2/AttachmentPrompt.ts
180A apps/server/src/orchestration-v2/CheckpointCaptureService.test.ts
181A apps/server/src/orchestration-v2/CheckpointCaptureService.ts
182A apps/server/src/orchestration-v2/CheckpointPolicy.ts
183A apps/server/src/orchestration-v2/CheckpointRollbackService.test.ts
184A apps/server/src/orchestration-v2/CheckpointRollbackService.ts
185A apps/server/src/orchestration-v2/CheckpointService.test.ts
186A apps/server/src/orchestration-v2/CheckpointService.ts
187A apps/server/src/orchestration-v2/CommandPolicy.test.ts
188A apps/server/src/orchestration-v2/CommandPolicy.ts
189A apps/server/src/orchestration-v2/CommandReceiptStore.ts
190A apps/server/src/orchestration-v2/ContextHandoffService.test.ts
191A apps/server/src/orchestration-v2/ContextHandoffService.ts
192A apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts
193A apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
194A apps/server/src/orchestration-v2/EffectOutbox.ts
195A apps/server/src/orchestration-v2/EffectWorker.test.ts
196A apps/server/src/orchestration-v2/EffectWorker.ts
197A apps/server/src/orchestration-v2/EventSink.ts
198A apps/server/src/orchestration-v2/EventStore.ts
199A apps/server/src/orchestration-v2/FoundationPersistence.test.ts
200A apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts
201A apps/server/src/orchestration-v2/IdAllocator.ts
202A apps/server/src/orchestration-v2/KeyedSerialExecutor.test.ts
203A apps/server/src/orchestration-v2/KeyedSerialExecutor.ts
204A apps/server/src/orchestration-v2/LegacyV1ThreadImporter.test.ts
205A apps/server/src/orchestration-v2/LegacyV1ThreadImporter.ts
206A apps/server/src/orchestration-v2/Orchestrator.migration.test.ts
207A apps/server/src/orchestration-v2/Orchestrator.ts
208A apps/server/src/orchestration-v2/ProjectionMaintenance.ts
209A apps/server/src/orchestration-v2/ProjectionRecovery.test.ts
210A apps/server/src/orchestration-v2/ProjectionStore.test.ts
211A apps/server/src/orchestration-v2/ProjectionStore.ts
212A apps/server/src/orchestration-v2/ProviderAdapter.ts
213A apps/server/src/orchestration-v2/ProviderAdapterDriver.ts
214A apps/server/src/orchestration-v2/ProviderAdapterRegistry.test.ts
215A apps/server/src/orchestration-v2/ProviderAdapterRegistry.ts
216A apps/server/src/orchestration-v2/ProviderContinuationRequests.ts
217A apps/server/src/orchestration-v2/ProviderContinuationService.test.ts
218A apps/server/src/orchestration-v2/ProviderContinuationService.ts
219A apps/server/src/orchestration-v2/ProviderEventIngestor.test.ts
220A apps/server/src/orchestration-v2/ProviderEventIngestor.ts
221A apps/server/src/orchestration-v2/ProviderFailure.test.ts
222A apps/server/src/orchestration-v2/ProviderFailure.ts
223A apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.regression.test.ts
224A apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.test.ts
225A apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts
226A apps/server/src/orchestration-v2/ProviderSelectionTransition.test.ts
227A apps/server/src/orchestration-v2/ProviderSelectionTransition.ts
228A apps/server/src/orchestration-v2/ProviderSessionManager.test.ts
229A apps/server/src/orchestration-v2/ProviderSessionManager.ts
230A apps/server/src/orchestration-v2/ProviderSessionTransitionPolicy.test.ts
231A apps/server/src/orchestration-v2/ProviderSessionTransitionPolicy.ts
232A apps/server/src/orchestration-v2/ProviderSwitchService.test.ts
233A apps/server/src/orchestration-v2/ProviderSwitchService.ts
234A apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
235A apps/server/src/orchestration-v2/ProviderTurnControlService.ts
236A apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts
237A apps/server/src/orchestration-v2/ProviderTurnStartService.testkit.ts
238A apps/server/src/orchestration-v2/ProviderTurnStartService.ts
239A apps/server/src/orchestration-v2/ProviderTurnTokenUsage.test.ts
240A apps/server/src/orchestration-v2/QueuedRunOrder.test.ts
241A apps/server/src/orchestration-v2/QueuedRunOrder.ts
242A apps/server/src/orchestration-v2/RandomUuid.ts
243A apps/server/src/orchestration-v2/ResourceCleanupService.ts
244A apps/server/src/orchestration-v2/RunExecutionService.test.ts
245A apps/server/src/orchestration-v2/RunExecutionService.ts
246A apps/server/src/orchestration-v2/RunFinalizationService.test.ts
247A apps/server/src/orchestration-v2/RunFinalizationService.ts
248A apps/server/src/orchestration-v2/RuntimePolicy.test.ts
249A apps/server/src/orchestration-v2/RuntimePolicy.ts
250A apps/server/src/orchestration-v2/RuntimeRequestService.test.ts
251A apps/server/src/orchestration-v2/RuntimeRequestService.ts
252A apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts
253A apps/server/src/orchestration-v2/ShellStream.test.ts
254A apps/server/src/orchestration-v2/ShellStream.ts
255A apps/server/src/orchestration-v2/SubagentProjection.test.ts
256A apps/server/src/orchestration-v2/SubagentProjection.ts
257A apps/server/src/orchestration-v2/TODO.md
258A apps/server/src/orchestration-v2/ThreadForkService.test.ts
259A apps/server/src/orchestration-v2/ThreadForkService.ts
260A apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
261A apps/server/src/orchestration-v2/ThreadLaunchService.ts
262A apps/server/src/orchestration-v2/ThreadLifecycleService.test.ts
263A apps/server/src/orchestration-v2/ThreadLifecycleService.ts
264R060 apps/server/src/orchestration/ThreadLiveEventCoalescer.test.ts apps/server/src/orchestration-v2/ThreadLiveEventCoalescer.test.ts
265R062 apps/server/src/orchestration/ThreadLiveEventCoalescer.ts apps/server/src/orchestration-v2/ThreadLiveEventCoalescer.ts
266A apps/server/src/orchestration-v2/ThreadManagementService.test.ts
267A apps/server/src/orchestration-v2/ThreadManagementService.ts
268A apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
269R059 apps/server/src/orchestration/ThreadSettlementReactor.ts apps/server/src/orchestration-v2/ThreadSettlementService.ts
270A apps/server/src/orchestration-v2/ThreadStream.test.ts
271A apps/server/src/orchestration-v2/ThreadStream.ts
272A apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts
273A apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts
274A apps/server/src/orchestration-v2/TurnItemPositionStore.ts
275A apps/server/src/orchestration-v2/UserFacingErrors.test.ts
276A apps/server/src/orchestration-v2/UserFacingErrors.ts
277A apps/server/src/orchestration-v2/V1ImportBoundary.test.ts
278A apps/server/src/orchestration-v2/WireProjection.test.ts
279A apps/server/src/orchestration-v2/WireProjection.ts
280A apps/server/src/orchestration-v2/applicationLayer.ts
281A apps/server/src/orchestration-v2/builtInProviderAdapterDrivers.ts
282A apps/server/src/orchestration-v2/http.ts
283A apps/server/src/orchestration-v2/runtimeLayer.test.ts
284A apps/server/src/orchestration-v2/runtimeLayer.ts
285A apps/server/src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
286A apps/server/src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts
287A apps/server/src/orchestration-v2/testkit/DeterministicRuntime.ts
288A apps/server/src/orchestration-v2/testkit/OrchestratorReplayFixtures.contract.test.ts
289A apps/server/src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts
290A apps/server/src/orchestration-v2/testkit/OrchestratorReplayRecovery.integration.test.ts
291A apps/server/src/orchestration-v2/testkit/OrchestratorScenario.ts
292A apps/server/src/orchestration-v2/testkit/ProviderReplayGate.testkit.test.ts
293A apps/server/src/orchestration-v2/testkit/ProviderReplayGate.testkit.ts
294A apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
295A apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts
296A apps/server/src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts
297A apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.test.ts
298A apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts
299A apps/server/src/orchestration-v2/testkit/ThreadFork.integration.test.ts
300A apps/server/src/orchestration-v2/testkit/ThreadMergeBack.integration.test.ts
301A apps/server/src/orchestration-v2/testkit/fixtures/acp_elicitation/grok_transcript.ndjson
302A apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/claude_transcript.ndjson
303A apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/input.ts
304A apps/server/src/orchestration-v2/testkit/fixtures/claude_background_task_after_root/output.ts
305A apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/claude_transcript.ndjson
306A apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/input.ts
307A apps/server/src/orchestration-v2/testkit/fixtures/claude_idle_resume/output.ts
308A apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/claude_transcript.ndjson
309A apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/input.ts
310A apps/server/src/orchestration-v2/testkit/fixtures/claude_local_bash_task/output.ts
311A apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/claude_transcript.ndjson
312A apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/input.ts
313A apps/server/src/orchestration-v2/testkit/fixtures/claude_result_is_error/output.ts
314A apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/grok_transcript.ndjson
315A apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/input.ts
316A apps/server/src/orchestration-v2/testkit/fixtures/grok_subagent_lineage/output.ts
317A apps/server/src/orchestration-v2/testkit/fixtures/index.ts
318A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_output.ts
319A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson
320A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_output.ts
321A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson
322A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/cursor_output.ts
323A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/cursor_transcript.ndjson
324A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_output.ts
325A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_transcript.ndjson
326A apps/server/src/orchestration-v2/testkit/fixtures/message_steering/input.ts
327A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/claude_output.ts
328A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/claude_transcript.ndjson
329A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_output.ts
330A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson
331A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/cursor_transcript.ndjson
332A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/grok_transcript.ndjson
333A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/input.ts
334A apps/server/src/orchestration-v2/testkit/fixtures/multi_turn_restart/claude_transcript.ndjson
335A apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/input.ts
336A apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/opencode_transcript.ndjson
337A apps/server/src/orchestration-v2/testkit/fixtures/opencode_child_approval/output.ts
338A apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/input.ts
339A apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/opencode_transcript.ndjson
340A apps/server/src/orchestration-v2/testkit/fixtures/opencode_subagent/output.ts
341A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_output.ts
342A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson
343A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/grok_transcript.ndjson
344A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/input.ts
345A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/opencode_output.ts
346A apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/opencode_transcript.ndjson
347A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_output.ts
348A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson
349A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/cursor_output.ts
350A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/cursor_transcript.ndjson
351A apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/input.ts
352A apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson
353A apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/cursor_transcript.ndjson
354A apps/server/src/orchestration-v2/testkit/fixtures/queued_cancelled_while_active/codex_output.ts
355A apps/server/src/orchestration-v2/testkit/fixtures/queued_cancelled_while_active/input.ts
356A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/claude_transcript.ndjson
357A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_output.ts
358A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson
359A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/cursor_transcript.ndjson
360A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/grok_transcript.ndjson
361A apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/input.ts
362A apps/server/src/orchestration-v2/testkit/fixtures/shared.ts
363A apps/server/src/orchestration-v2/testkit/fixtures/simple/claude_output.ts
364A apps/server/src/orchestration-v2/testkit/fixtures/simple/claude_transcript.ndjson
365A apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_output.ts
366A apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson
367A apps/server/src/orchestration-v2/testkit/fixtures/simple/cursor_transcript.ndjson
368A apps/server/src/orchestration-v2/testkit/fixtures/simple/grok_transcript.ndjson
369A apps/server/src/orchestration-v2/testkit/fixtures/simple/input.ts
370A apps/server/src/orchestration-v2/testkit/fixtures/simple/opencode_transcript.ndjson
371A apps/server/src/orchestration-v2/testkit/fixtures/subagent/claude_output.ts
372A apps/server/src/orchestration-v2/testkit/fixtures/subagent/claude_transcript.ndjson
373A apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_output.ts
374A apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson
375A apps/server/src/orchestration-v2/testkit/fixtures/subagent/cursor_output.ts
376A apps/server/src/orchestration-v2/testkit/fixtures/subagent/cursor_transcript.ndjson
377A apps/server/src/orchestration-v2/testkit/fixtures/subagent/input.ts
378A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/README.md
379A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_output.ts
380A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson
381A apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/input.ts
382A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_output.ts
383A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson
384A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/input.ts
385A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_output.ts
386A apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson
387A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/claude_transcript.ndjson
388A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson
389A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/README.md
390A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/claude_transcript.ndjson
391A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson
392A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_fork_local_rollback/claude_transcript.ndjson
393A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/claude_transcript.ndjson
394A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson
395A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/README.md
396A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/claude_transcript.ndjson
397A apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson
398A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/README.md
399A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/claude_transcript.ndjson
400A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson
401A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/README.md
402A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/claude_transcript.ndjson
403A apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson
404A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/claude_output.ts
405A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/claude_transcript.ndjson
406A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_output.ts
407A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson
408A apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/input.ts
409A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_output.ts
410A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson
411A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/cursor_output.ts
412A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/cursor_transcript.ndjson
413A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/grok_output.ts
414A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/grok_transcript.ndjson
415A apps/server/src/orchestration-v2/testkit/fixtures/todo_list/input.ts
416A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/claude_output.ts
417A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/claude_transcript.ndjson
418A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/cursor_output.ts
419A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/cursor_transcript.ndjson
420A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/grok_transcript.ndjson
421A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only/input.ts
422A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/claude_output.ts
423A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/claude_transcript.ndjson
424A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_output.ts
425A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson
426A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/grok_transcript.ndjson
427A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/input.ts
428A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/claude_output.ts
429A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/claude_transcript.ndjson
430A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_output.ts
431A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson
432A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/input.ts
433A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/claude_output.ts
434A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/claude_transcript.ndjson
435A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_output.ts
436A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson
437A apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/input.ts
438A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/claude_output.ts
439A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/claude_transcript.ndjson
440A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_output.ts
441A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson
442A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/grok_transcript.ndjson
443A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/input.ts
444A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/opencode_transcript.ndjson
445A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/claude_output.ts
446A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/claude_transcript.ndjson
447A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_output.ts
448A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson
449A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/cursor_output.ts
450A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/cursor_transcript.ndjson
451A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/input.ts
452A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/claude_output.ts
453A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/claude_transcript.ndjson
454A apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_restart/input.ts
455A apps/server/src/orchestration-v2/testkit/fixtures/web_search/claude_output.ts
456A apps/server/src/orchestration-v2/testkit/fixtures/web_search/claude_transcript.ndjson
457A apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_output.ts
458A apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson
459A apps/server/src/orchestration-v2/testkit/fixtures/web_search/input.ts
460A apps/server/src/orchestration-v2/testkit/index.ts
461A apps/server/src/orchestration-v2/threadHistoryPaging.test.ts
462A apps/server/src/orchestration-v2/threadHistoryPaging.ts
463D apps/server/src/orchestration/ActivityPayloadProjection.test.ts
464D apps/server/src/orchestration/ActivityPayloadProjection.ts
465D apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
466D apps/server/src/orchestration/Layers/CheckpointReactor.ts
467D apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts
468M apps/server/src/orchestration/Layers/OrchestrationEngine.ts
469D apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts
470D apps/server/src/orchestration/Layers/OrchestrationReactor.ts
471A apps/server/src/orchestration/Layers/ProjectEnrichmentProjection.test.ts
472D apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts
473M apps/server/src/orchestration/Layers/ProjectionPipeline.ts
474A apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.search.test.ts
475D apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts
476M apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
477D apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
478D apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
479D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.activity.test.ts
480D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.approval.test.ts
481D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts
482D apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
483D apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts
484D apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts
485D apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts
486M apps/server/src/orchestration/LiveStreamBudget.test.ts
487M apps/server/src/orchestration/LiveStreamBudget.ts
488D apps/server/src/orchestration/Normalizer.attachments.test.ts
489D apps/server/src/orchestration/Normalizer.test.ts
490D apps/server/src/orchestration/Normalizer.ts
491M apps/server/src/orchestration/Schemas.ts
492D apps/server/src/orchestration/Services/CheckpointReactor.ts
493M apps/server/src/orchestration/Services/OrchestrationEngine.ts
494D apps/server/src/orchestration/Services/OrchestrationReactor.ts
495M apps/server/src/orchestration/Services/ProjectionPipeline.ts
496M apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts
497D apps/server/src/orchestration/Services/ProviderCommandReactor.ts
498D apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts
499D apps/server/src/orchestration/Services/RuntimeReceiptBus.ts
500D apps/server/src/orchestration/Services/ThreadDeletionReactor.ts
501D apps/server/src/orchestration/ThreadSettlementReactor.test.ts
502D apps/server/src/orchestration/commandInvariants.test.ts
503M apps/server/src/orchestration/commandInvariants.ts
504D apps/server/src/orchestration/decider.delete.test.ts
505M apps/server/src/orchestration/decider.ts
506D apps/server/src/orchestration/http.ts
507D apps/server/src/orchestration/projector.test.ts
508M apps/server/src/orchestration/projector.ts
509M apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts
510A apps/server/src/persistence/Layers/OrchestrationEventStore.sequence.test.ts
511M apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts
512M apps/server/src/persistence/Layers/OrchestrationEventStore.ts
513M apps/server/src/persistence/Layers/ProjectionCheckpoints.ts
514M apps/server/src/persistence/Layers/ProjectionTurns.ts
515M apps/server/src/persistence/Migrations.ts
516A apps/server/src/persistence/Migrations/048_049_OrchestrationV2.test.ts
517A apps/server/src/persistence/Migrations/048_OrchestrationV2.ts
518A apps/server/src/persistence/Migrations/049_OrchestrationV2Subagents.ts
519A apps/server/src/persistence/Migrations/050_OrchestrationV2Foundation.test.ts
520A apps/server/src/persistence/Migrations/050_OrchestrationV2Foundation.ts
521A apps/server/src/persistence/Migrations/051_OrchestrationV2ProviderSessionBindings.ts
522A apps/server/src/persistence/Migrations/052_OrchestrationV2ThreadLaunchWorkflows.ts
523A apps/server/src/persistence/Migrations/053_ApplicationEventSource.test.ts
524A apps/server/src/persistence/Migrations/053_ApplicationEventSource.ts
525A apps/server/src/persistence/Migrations/054_OrchestrationV2EffectCancellation.test.ts
526A apps/server/src/persistence/Migrations/054_OrchestrationV2EffectCancellation.ts
527A apps/server/src/persistence/Migrations/055_ScheduledTasks.ts
528A apps/server/src/persistence/Migrations/056_LegacyV1ImportState.ts
529A apps/server/src/persistence/Migrations/057_ApplicationEventSequenceIndexes.ts
530A apps/server/src/persistence/Migrations/058_OrchestrationV2RecoveryIndexes.ts
531M apps/server/src/persistence/ProviderSessionRuntime.ts
532M apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts
533M apps/server/src/persistence/Services/OrchestrationEventStore.ts
534M apps/server/src/persistence/Services/ProjectionCheckpoints.ts
535M apps/server/src/persistence/Services/ProjectionPendingApprovals.ts
536M apps/server/src/persistence/Services/ProjectionThreadActivities.ts
537M apps/server/src/persistence/Services/ProjectionThreadMessages.ts
538M apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts
539M apps/server/src/persistence/Services/ProjectionThreadSessions.ts
540M apps/server/src/persistence/Services/ProjectionTurns.ts
541A apps/server/src/project/ProjectEnrichmentService.test.ts
542A apps/server/src/project/ProjectEnrichmentService.ts
543A apps/server/src/project/ProjectService.test.ts
544A apps/server/src/project/ProjectService.ts
545M apps/server/src/project/ProjectSetupScriptRunner.test.ts
546M apps/server/src/project/ProjectSetupScriptRunner.ts
547A apps/server/src/project/http.test.ts
548A apps/server/src/project/http.ts
549A apps/server/src/provider/ClaudeTurnTokenUsage.ts
550M apps/server/src/provider/CodexDeveloperInstructions.ts
551A apps/server/src/provider/CodexToolPresentation.ts
552A apps/server/src/provider/CodexTurnTokenUsage.ts
553A apps/server/src/provider/Drivers/AcpRegistryDriver.ts
554M apps/server/src/provider/Drivers/AntigravityDriver.test.ts
555M apps/server/src/provider/Drivers/AntigravityDriver.ts
556M apps/server/src/provider/Drivers/ClaudeDriver.ts
557M apps/server/src/provider/Drivers/CodexDriver.ts
558M apps/server/src/provider/Drivers/CursorDriver.ts
559A apps/server/src/provider/Drivers/CursorSkills.test.ts
560M apps/server/src/provider/Drivers/GrokDriver.ts
561M apps/server/src/provider/Drivers/OpenCodeDriver.ts
562M apps/server/src/provider/Errors.ts
563D apps/server/src/provider/Layers/AntigravityAdapter.test.ts
564D apps/server/src/provider/Layers/AntigravityAdapter.ts
565D apps/server/src/provider/Layers/ClaudeAdapter.test.ts
566D apps/server/src/provider/Layers/ClaudeAdapter.ts
567D apps/server/src/provider/Layers/CodexAdapter.test.ts
568D apps/server/src/provider/Layers/CodexAdapter.ts
569D apps/server/src/provider/Layers/CursorAdapter.test.ts
570D apps/server/src/provider/Layers/CursorAdapter.ts
571M apps/server/src/provider/Layers/CursorProvider.test.ts
572M apps/server/src/provider/Layers/CursorProvider.ts
573A apps/server/src/provider/Layers/CursorSdkCatalog.ts
574M apps/server/src/provider/Layers/EventNdjsonLogger.test.ts
575M apps/server/src/provider/Layers/EventNdjsonLogger.ts
576D apps/server/src/provider/Layers/GrokAdapter.test.ts
577D apps/server/src/provider/Layers/GrokAdapter.ts
578M apps/server/src/provider/Layers/GrokProvider.ts
579D apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
580D apps/server/src/provider/Layers/OpenCodeAdapter.ts
581D apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts
582D apps/server/src/provider/Layers/ProviderAdapterRegistry.ts
583M apps/server/src/provider/Layers/ProviderAuthService.test.ts
584M apps/server/src/provider/Layers/ProviderAuthService.ts
585M apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts
586M apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts
587A apps/server/src/provider/Layers/ProviderOrchestrationAdapterInfrastructure.ts
588M apps/server/src/provider/Layers/ProviderRegistry.test.ts
589M apps/server/src/provider/Layers/ProviderRegistry.ts
590D apps/server/src/provider/Layers/ProviderService.test.ts
591D apps/server/src/provider/Layers/ProviderService.ts
592D apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts
593D apps/server/src/provider/Layers/ProviderSessionDirectory.ts
594D apps/server/src/provider/Layers/ProviderSessionReaper.test.ts
595D apps/server/src/provider/Layers/ProviderSessionReaper.ts
596M apps/server/src/provider/Layers/ProviderUsageLimitsIngestion.ts
597A apps/server/src/provider/NativeProtocolLogging.ts
598M apps/server/src/provider/ProviderDriver.ts
599D apps/server/src/provider/Services/ClaudeAdapter.ts
600D apps/server/src/provider/Services/CodexAdapter.ts
601D apps/server/src/provider/Services/CursorAdapter.ts
602D apps/server/src/provider/Services/GrokAdapter.ts
603D apps/server/src/provider/Services/OpenCodeAdapter.ts
604D apps/server/src/provider/Services/ProviderAdapter.ts
605D apps/server/src/provider/Services/ProviderAdapterRegistry.ts
606D apps/server/src/provider/Services/ProviderService.ts
607D apps/server/src/provider/Services/ProviderSessionDirectory.ts
608D apps/server/src/provider/Services/ProviderSessionReaper.ts
609A apps/server/src/provider/T3OrchestrationInstructions.test.ts
610A apps/server/src/provider/T3OrchestrationInstructions.ts
611A apps/server/src/provider/TurnTokenUsage.test.ts
612D apps/server/src/provider/acp/AcpAdapterSupport.test.ts
613D apps/server/src/provider/acp/AcpAdapterSupport.ts
614M apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts
615M apps/server/src/provider/acp/AcpNativeLogging.ts
616A apps/server/src/provider/acp/AcpRegistrySupport.test.ts
617A apps/server/src/provider/acp/AcpRegistrySupport.ts
618M apps/server/src/provider/acp/AcpRuntimeModel.test.ts
619M apps/server/src/provider/acp/AcpRuntimeModel.ts
620A apps/server/src/provider/acp/AcpSessionRuntime.processTree.test.ts
621M apps/server/src/provider/acp/AcpSessionRuntime.ts
622M apps/server/src/provider/acp/AntigravityAcpSupport.ts
623A apps/server/src/provider/acp/AntigravityClientFiles.ts
624M apps/server/src/provider/acp/AntigravityProtocol.ts
625D apps/server/src/provider/acp/CursorAcpCliProbe.test.ts
626D apps/server/src/provider/acp/CursorAcpExtension.test.ts
627D apps/server/src/provider/acp/CursorAcpExtension.ts
628D apps/server/src/provider/acp/CursorAcpSupport.test.ts
629D apps/server/src/provider/acp/CursorAcpSupport.ts
630M apps/server/src/provider/acp/GrokAcpCliProbe.test.ts
631M apps/server/src/provider/acp/GrokAcpSupport.test.ts
632M apps/server/src/provider/acp/GrokAcpSupport.ts
633M apps/server/src/provider/acp/XAiAcpExtension.test.ts
634M apps/server/src/provider/acp/XAiAcpExtension.ts
635M apps/server/src/provider/builtInDrivers.ts
636A apps/server/src/provider/cursorSdkModel.ts
637M apps/server/src/provider/providerInstallation.test.ts
638M apps/server/src/provider/providerMaintenanceRunner.test.ts
639D apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts
640D apps/server/src/relay/AgentAwarenessRelay.test.ts
641M apps/server/src/relay/AgentAwarenessRelay.ts
642A apps/server/src/scheduledTasks/Schedule.test.ts
643A apps/server/src/scheduledTasks/Schedule.ts
644A apps/server/src/scheduledTasks/ScheduledTaskService.ts
645D apps/server/src/server.test.ts
646M apps/server/src/server.ts
647M apps/server/src/serverActivation.ts
648M apps/server/src/serverLifecycleEvents.test.ts
649M apps/server/src/serverLifecycleEvents.ts
650D apps/server/src/serverRuntimeStartup.reconcile.test.ts
651M apps/server/src/serverRuntimeStartup.test.ts
652M apps/server/src/serverRuntimeStartup.ts
653M apps/server/src/terminal/Manager.test.ts
654M apps/server/src/terminal/Manager.ts
655M apps/server/src/textGeneration/CodexTextGeneration.test.ts
656M apps/server/src/textGeneration/CodexTextGeneration.ts
657M apps/server/src/textGeneration/CursorTextGeneration.test.ts
658M apps/server/src/textGeneration/CursorTextGeneration.ts
659M apps/server/src/textGeneration/TextGeneration.test.ts
660M apps/server/src/vcs/GitVcsDriver.ts
661M apps/server/src/vcs/GitVcsDriverCore.test.ts
662M apps/server/src/vcs/GitVcsDriverCore.ts
663A apps/server/src/ws.test.ts
664M apps/server/src/ws.ts
665D apps/server/test/ActivityPayloadProjection.test.ts
666M apps/web/src/appearanceFonts.test.ts
667M apps/web/src/appearanceFonts.ts
668M apps/web/src/components/AppSidebarLayout.tsx
669M apps/web/src/components/BranchToolbar.logic.test.ts
670M apps/web/src/components/BranchToolbar.logic.ts
671M apps/web/src/components/BranchToolbar.tsx
672M apps/web/src/components/BranchToolbarBranchSelector.tsx
673M apps/web/src/components/BranchToolbarEnvModeSelector.tsx
674M apps/web/src/components/BranchToolbarEnvironmentSelector.tsx
675M apps/web/src/components/ChatView.logic.test.ts
676M apps/web/src/components/ChatView.logic.ts
677M apps/web/src/components/ChatView.tsx
678M apps/web/src/components/CommandPalette.logic.test.ts
679M apps/web/src/components/CommandPalette.logic.ts
680M apps/web/src/components/CommandPalette.tsx
681M apps/web/src/components/DiffPanel.tsx
682M apps/web/src/components/GitActionsControl.logic.test.ts
683M apps/web/src/components/GitActionsControl.logic.ts
684M apps/web/src/components/GitActionsControl.tsx
685M apps/web/src/components/LegacySidebar.tsx
686A apps/web/src/components/LegacyThreadMigrationToast.tsx
687M apps/web/src/components/ProjectScriptsControl.tsx
688M apps/web/src/components/RightPanelTabs.tsx
689M apps/web/src/components/Sidebar.logic.test.ts
690M apps/web/src/components/Sidebar.logic.ts
691M apps/web/src/components/Sidebar.tsx
692M apps/web/src/components/ThreadStatusIndicators.tsx
693M apps/web/src/components/chat/ChangedFilesTree.test.tsx
694M apps/web/src/components/chat/ChangedFilesTree.tsx
695M apps/web/src/components/chat/ChatComposer.tsx
696M apps/web/src/components/chat/ChatHeader.tsx
697M apps/web/src/components/chat/ComposerBanner.tsx
698M apps/web/src/components/chat/ComposerBannerStack.tsx
699M apps/web/src/components/chat/ComposerPendingApprovalActions.test.tsx
700M apps/web/src/components/chat/ComposerPendingApprovalActions.tsx
701M apps/web/src/components/chat/ComposerPendingApprovalPanel.test.tsx
702M apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
703M apps/web/src/components/chat/ComposerPendingUserInputPanel.test.tsx
704M apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
705M apps/web/src/components/chat/ComposerPrimaryActions.test.tsx
706M apps/web/src/components/chat/ComposerPrimaryActions.tsx
707M apps/web/src/components/chat/ComposerServerUpdateStatus.tsx
708D apps/web/src/components/chat/ContextWindowMeter.test.tsx
709M apps/web/src/components/chat/MessagesTimeline.logic.test.ts
710M apps/web/src/components/chat/MessagesTimeline.logic.ts
711M apps/web/src/components/chat/MessagesTimeline.test.tsx
712M apps/web/src/components/chat/MessagesTimeline.tsx
713A apps/web/src/components/chat/OpenInPicker.logic.ts
714R073 apps/web/src/components/chat/ChatHeader.test.ts apps/web/src/components/chat/OpenInPicker.test.ts
715M apps/web/src/components/chat/OpenInPicker.tsx
716A apps/web/src/components/chat/OpenInPickerShortcut.ts
717M apps/web/src/components/chat/PanelLayoutControls.tsx
718M apps/web/src/components/chat/ProposedPlanCard.tsx
719A apps/web/src/components/chat/QueuedRunsControl.test.tsx
720A apps/web/src/components/chat/QueuedRunsControl.tsx
721A apps/web/src/components/chat/ThreadAutomationsPanel.tsx
722A apps/web/src/components/chat/ThreadDetailsPanel.test.tsx
723A apps/web/src/components/chat/ThreadDetailsPanel.tsx
724A apps/web/src/components/chat/ThreadDetailsPrRow.tsx
725A apps/web/src/components/chat/ThreadRelationshipsControl.test.tsx
726A apps/web/src/components/chat/ThreadRelationshipsControl.tsx
727A apps/web/src/components/chat/TimelineSystemDivider.tsx
728A apps/web/src/components/chat/V2ItemInspector.tsx
729A apps/web/src/components/chat/V2LifecycleRow.tsx
730A apps/web/src/components/chat/composerDispatch.test.ts
731A apps/web/src/components/chat/composerDispatch.ts
732M apps/web/src/components/chat/externalLinkContextMenu.test.ts
733M apps/web/src/components/chat/externalLinkContextMenu.ts
734A apps/web/src/components/chat/threadDetailsPanelStyles.ts
735M apps/web/src/components/chat/useAssistantCitationTarget.ts
736M apps/web/src/components/files/FilePreviewPanel.tsx
737M apps/web/src/components/preview/PreviewPanelShell.tsx
738M apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx
739M apps/web/src/components/preview/addBrowserSurface.test.ts
740M apps/web/src/components/preview/previewMiniPlayerLayout.test.ts
741M apps/web/src/components/preview/previewMiniPlayerLayout.ts
742M apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
743M apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts
744M apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
745M apps/web/src/components/pullRequest/pullRequestPresentation.tsx
746A apps/web/src/components/pullRequest/usePullRequestActions.ts
747M apps/web/src/components/settings/AddProviderInstanceDialog.tsx
748M apps/web/src/components/settings/KeybindingsSettings.logic.test.ts
749M apps/web/src/components/settings/ProviderInstanceCard.test.ts
750M apps/web/src/components/settings/ProviderInstanceCard.tsx
751M apps/web/src/components/settings/ProviderSettingsForm.test.ts
752A apps/web/src/components/settings/ScheduledTasksSettings.tsx
753M apps/web/src/components/settings/SettingsPanels.tsx
754M apps/web/src/components/settings/SettingsSidebarNav.tsx
755M apps/web/src/components/settings/providerDriverMeta.ts
756M apps/web/src/components/settings/settingsSearch.test.ts
757M apps/web/src/components/settings/settingsSearch.ts
758M apps/web/src/components/ui/popover.tsx
759M apps/web/src/composerDraftStore.ts
760M apps/web/src/connection/runtime.ts
761M apps/web/src/connection/storage.ts
762M apps/web/src/diffFileActions.test.ts
763M apps/web/src/diffPanelStore.test.ts
764M apps/web/src/diffPanelStore.ts
765A apps/web/src/hooks/useElementWidth.ts
766M apps/web/src/hooks/useHandleNewThread.ts
767A apps/web/src/hooks/usePreviewPanelInlineSize.ts
768M apps/web/src/hooks/useThreadActionMenu.ts
769M apps/web/src/hooks/useThreadActions.ts
770A apps/web/src/hooks/useThreadVisitedMigration.ts
771M apps/web/src/hooks/useTurnDiffSummaries.ts
772M apps/web/src/index.css
773M apps/web/src/keybindings.test.ts
774M apps/web/src/keybindings.ts
775M apps/web/src/lib/contextWindow.test.ts
776M apps/web/src/lib/contextWindow.ts
777A apps/web/src/lib/orchestrationV2Timeline.test.ts
778A apps/web/src/lib/orchestrationV2Timeline.ts
779M apps/web/src/lib/threadSort.test.ts
780M apps/web/src/pendingUserInput.test.ts
781M apps/web/src/pendingUserInput.ts
782M apps/web/src/providerInstances.test.ts
783M apps/web/src/providerInstances.ts
784A apps/web/src/providerUpdateDismissal.test.ts
785M apps/web/src/providerUpdateDismissal.ts
786A apps/web/src/rightPanelLayout.test.ts
787M apps/web/src/rightPanelLayout.ts
788M apps/web/src/rightPanelStore.test.ts
789M apps/web/src/rightPanelStore.ts
790M apps/web/src/routeTree.gen.ts
791M apps/web/src/routes/__root.tsx
792M apps/web/src/routes/_chat.$environmentId.$threadId.tsx
793M apps/web/src/routes/_chat.draft.$draftId.tsx
794M apps/web/src/routes/_chat.pull-requests.tsx
795A apps/web/src/routes/settings.scheduled-tasks.tsx
796D apps/web/src/session-logic.command-output.test.ts
797M apps/web/src/session-logic.test.ts
798M apps/web/src/session-logic.ts
799M apps/web/src/state/entities.ts
800M apps/web/src/state/queries.ts
801M apps/web/src/state/server.ts
802M apps/web/src/state/sourceControlActions.ts
803D apps/web/src/state/terminalSessions.test.ts
804M apps/web/src/state/terminalSessions.ts
805M apps/web/src/state/threads.ts
806A apps/web/src/state/v2ItemSupport.ts
807A apps/web/src/state/waitForAtomValue.test.ts
808A apps/web/src/state/waitForAtomValue.ts
809A apps/web/src/test-fixtures.ts
810M apps/web/src/threadRoutes.test.ts
811M apps/web/src/threadRoutes.ts
812M apps/web/src/threadSync.test.ts
813M apps/web/src/timestampFormat.test.ts
814M apps/web/src/timestampFormat.ts
815M apps/web/src/types.ts
816M apps/web/src/uiStateStore.test.ts
817M apps/web/src/versionSkew.test.ts
818M apps/web/src/versionSkew.ts
819M apps/web/src/worktreeCleanup.test.ts
820M docs/README.md
821M docs/internals/connection-runtime.md
822A docs/internals/context-handoffs.md
823A docs/internals/legacy-orchestration-migration.md
824M docs/internals/overview.md
825A docs/internals/performance-regressions.md
826M docs/internals/providers.md
827M docs/internals/terminal-runtime.md
828A docs/orchestration-v2/README.md
829A docs/orchestration-v2/core-graph-and-data-model.md
830A docs/orchestration-v2/entity-ids-and-correlation.md
831A docs/orchestration-v2/feature-lifecycles.md
832A docs/orchestration-v2/orchestrator-mcp-server.md
833A docs/orchestration-v2/provider-capability-system.md
834A docs/orchestration-v2/provider-switching-and-context.md
835A docs/orchestration-v2/testing-strategy.md
836A docs/orchestration-v2/thread-lineage-and-context-transfer.md
837A docs/user/activity-log.md
838A docs/user/appearance.md
839M docs/user/composer.md
840A docs/user/cursor.md
841A docs/user/portable-handoffs.md
842D docs/user/terminal.md
843A docs/user/thread-migration.md
844M docs/user/updating.md
845M infra/relay/src/http/Api.test.ts
846M infra/relay/src/http/Api.ts
847M infra/relay/src/worker.ts
848M package.json
849M packages/client-runtime/package.json
850A packages/client-runtime/src/connection/compatibility.test.ts
851A packages/client-runtime/src/connection/compatibility.ts
852M packages/client-runtime/src/connection/registry.test.ts
853M packages/client-runtime/src/connection/resolver.test.ts
854M packages/client-runtime/src/connection/resolver.ts
855M packages/client-runtime/src/operations/commands.test.ts
856M packages/client-runtime/src/operations/commands.ts
857M packages/client-runtime/src/operations/index.ts
858M packages/client-runtime/src/operations/projects.test.ts
859M packages/client-runtime/src/operations/projects.ts
860A packages/client-runtime/src/operations/threadTitle.test.ts
861A packages/client-runtime/src/operations/threadTitle.ts
862M packages/client-runtime/src/platform/index.ts
863A packages/client-runtime/src/platform/orchestrationCache.test.ts
864A packages/client-runtime/src/platform/orchestrationCache.ts
865M packages/client-runtime/src/platform/persistence.ts
866M packages/client-runtime/src/rpc/client.ts
867M packages/client-runtime/src/state/archivedThreads.test.ts
868M packages/client-runtime/src/state/archivedThreads.ts
869A packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts
870A packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts
871M packages/client-runtime/src/state/entities.test.ts
872A packages/client-runtime/src/state/environmentHttpAuth.test.ts
873M packages/client-runtime/src/state/environmentHttpAuth.ts
874A packages/client-runtime/src/state/itemSupport.test.ts
875A packages/client-runtime/src/state/itemSupport.ts
876M packages/client-runtime/src/state/models.ts
877M packages/client-runtime/src/state/orchestration.ts
878A packages/client-runtime/src/state/orchestrationV2Projection.test.ts
879A packages/client-runtime/src/state/orchestrationV2Projection.ts
880A packages/client-runtime/src/state/orchestrationV2TestFixtures.ts
881M packages/client-runtime/src/state/projectEntities.ts
882M packages/client-runtime/src/state/server.ts
883M packages/client-runtime/src/state/shell-sync.test.ts
884M packages/client-runtime/src/state/shell.test.ts
885M packages/client-runtime/src/state/shell.ts
886M packages/client-runtime/src/state/shellReducer.test.ts
887M packages/client-runtime/src/state/shellReducer.ts
888M packages/client-runtime/src/state/shellSnapshotHttp.ts
889M packages/client-runtime/src/state/snapshots.ts
890M packages/client-runtime/src/state/subagentRuntime.ts
891A packages/client-runtime/src/state/threadCheckpoints.ts
892M packages/client-runtime/src/state/threadCommands.ts
893A packages/client-runtime/src/state/threadDetail.test.ts
894M packages/client-runtime/src/state/threadDetail.ts
895A packages/client-runtime/src/state/threadExecution.test.ts
896A packages/client-runtime/src/state/threadExecution.ts
897M packages/client-runtime/src/state/threadFeedback.test.ts
898M packages/client-runtime/src/state/threadFeedback.ts
899A packages/client-runtime/src/state/threadHistoryController.test.ts
900A packages/client-runtime/src/state/threadHistoryController.ts
901A packages/client-runtime/src/state/threadHistoryHttp.ts
902A packages/client-runtime/src/state/threadHistoryMerge.test.ts
903A packages/client-runtime/src/state/threadHistoryMerge.ts
904D packages/client-runtime/src/state/threadReducer.test.ts
905D packages/client-runtime/src/state/threadReducer.ts
906A packages/client-runtime/src/state/threadRelationships.test.ts
907A packages/client-runtime/src/state/threadRelationships.ts
908A packages/client-runtime/src/state/threadRequests.test.ts
909A packages/client-runtime/src/state/threadRequests.ts
910M packages/client-runtime/src/state/threadRetention.ts
911M packages/client-runtime/src/state/threadSettled.ts
912A packages/client-runtime/src/state/threadShell.test.ts
913M packages/client-runtime/src/state/threadShell.ts
914M packages/client-runtime/src/state/threadSnapshotHttp.ts
915M packages/client-runtime/src/state/threadSort.test.ts
916M packages/client-runtime/src/state/threadSort.ts
917M packages/client-runtime/src/state/threadState.ts
918A packages/client-runtime/src/state/threadWorkflows.test.ts
919A packages/client-runtime/src/state/threadWorkflows.ts
920M packages/client-runtime/src/state/threads-atoms.test.ts
921D packages/client-runtime/src/state/threads-pagination.test.ts
922M packages/client-runtime/src/state/threads-sync.test.ts
923M packages/client-runtime/src/state/threads.ts
924A packages/client-runtime/src/state/turnItemPresentation.test.ts
925A packages/client-runtime/src/state/turnItemPresentation.ts
926M packages/client-runtime/src/state/vcsAction.test.ts
927M packages/client-runtime/src/state/vcsAction.ts
928A packages/client-runtime/src/t3ToolSummary.test.ts
929A packages/client-runtime/src/t3ToolSummary.ts
930M packages/client-runtime/src/work-log/presentation.test.ts
931M packages/client-runtime/src/work-log/presentation.ts
932M packages/contracts/package.json
933A packages/contracts/src/applicationEvent.test.ts
934A packages/contracts/src/applicationEvent.ts
935M packages/contracts/src/assets.test.ts
936M packages/contracts/src/assets.ts
937M packages/contracts/src/baseSchemas.ts
938A packages/contracts/src/chatAttachment.ts
939A packages/contracts/src/checkpointDiff.ts
940M packages/contracts/src/environment.ts
941M packages/contracts/src/environmentHttp.ts
942M packages/contracts/src/index.ts
943M packages/contracts/src/ipc.ts
944M packages/contracts/src/keybindings.test.ts
945M packages/contracts/src/keybindings.ts
946M packages/contracts/src/model.ts
947A packages/contracts/src/modelSelection.ts
948M packages/contracts/src/orchestration.test.ts
949M packages/contracts/src/orchestration.ts
950A packages/contracts/src/orchestrationProject.ts
951A packages/contracts/src/orchestrationV2.test.ts
952A packages/contracts/src/orchestrationV2.ts
953A packages/contracts/src/orchestratorMcp.test.ts
954A packages/contracts/src/orchestratorMcp.ts
955M packages/contracts/src/project.ts
956M packages/contracts/src/provider.ts
957A packages/contracts/src/providerPolicy.ts
958M packages/contracts/src/providerRuntime.ts
959M packages/contracts/src/rpc.test.ts
960M packages/contracts/src/rpc.ts
961A packages/contracts/src/scheduledTask.test.ts
962A packages/contracts/src/scheduledTask.ts
963M packages/contracts/src/server.ts
964M packages/contracts/src/settings.test.ts
965M packages/contracts/src/settings.ts
966M packages/contracts/src/t3ProjectFile.test.ts
967M packages/contracts/src/t3ProjectFile.ts
968A packages/contracts/src/worktreeMcp.ts
969M packages/effect-acp/src/client.ts
970M packages/effect-acp/src/protocol.test.ts
971M packages/effect-acp/src/protocol.ts
972M packages/effect-codex-app-server/package.json
973M packages/effect-codex-app-server/src/client.ts
974A packages/effect-codex-app-server/src/replay.test.ts
975A packages/effect-codex-app-server/src/replay.ts
976M packages/shared/package.json
977A packages/shared/src/Array.test.ts
978A packages/shared/src/Array.ts
979M packages/shared/src/agentAwareness.test.ts
980M packages/shared/src/agentAwareness.ts
981M packages/shared/src/model.test.ts
982M packages/shared/src/model.ts
983M packages/shared/src/orchestrationTiming.ts
984A packages/shared/src/orchestrationV2PendingBackgroundWork.test.ts
985A packages/shared/src/orchestrationV2PendingBackgroundWork.ts
986A packages/shared/src/orchestrationV2Timeline.test.ts
987A packages/shared/src/orchestrationV2Timeline.ts
988A packages/shared/src/t3McpToolPresentation.test.ts
989A packages/shared/src/t3McpToolPresentation.ts
990M pnpm-lock.yaml
991M vite.config.ts
audits/orchestrator-v2/2026-09-04/clients.md+157−0
@@ -0,0 +1,157 @@
1# Client parity and regression audit, 2026-09-04
2
3## Result
4
5The committed client surface still has two previously reported correctness defects: F09 silently drops newly attached generic files from queued-message edits, and F10 hides every non-image attachment in the mobile V2 feed. F02 and F17 are fixed in committed HEAD. D02 remains an unresolved ownership decision, and D07 remains the deliberate compact mobile queue surface even after the large composer port.
6
7I found four additional differences from the frozen main tree. Three are current-main-only performance fixes that have not yet been incorporated, not rebase regressions: immediate thread-stream release from [#9740](https://github.com/pingdotgg/t3code/pull/9740), indexed terminal metadata from [#9747](https://github.com/pingdotgg/t3code/pull/9747), and the bounded mobile parsed-review cache from [#9749](https://github.com/pingdotgg/t3code/pull/9749). The fourth is the current-main-only trailing-activity fold fix from [#9739](https://github.com/pingdotgg/t3code/pull/9739). Main lag is kept separate from lost earlier ports throughout this report.
8
9Review anchors:
10
11- committed branch: `8af5734365f7c45bc08b57066dbae42f9f7d4235`
12- frozen main: `d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70`
13- merge base: `c8f77e0d441264efb0acfac312e852c81ae3da83`
14- previous audit branch/main: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0` / `57a66608b918d673eeec7e6c94ea5906b756fcd0`
15
16Committed evidence was read with `git show <frozen-sha>:<path>`. Local-only evidence came from the `.snapshot` files named by `worktree-manifest.json`. All scoped overlay, manifest, and live-worktree hashes matched before the local tests ran.
17
18## Open committed findings
19
20### F09, P1: queued-message edits still discard newly attached generic files
21
22`apps/web/src/components/ChatView.tsx:6596-6610` reads both `images` and `files` from the real composer. Once queued edit mode is active, however, the save path copies only `composerImages` into `newEditImages` at `:6644-6649`. Its attachment-only guard checks original attachments and new images, but not `composerFiles`, at `:6650-6655`. It uploads only `newEditImages` at `:6659-6667`, sends original attachments plus those image uploads at `:6668-6677`, then clears the whole edit draft at `:6684-6688`.
23
24The actual caller does not disable generic attachment input during editing. `ChatView` points `ChatComposer` at the queued-edit draft and passes ordinary attachment capability props at `:8400-8410`; the queue editor is selected at `:8421-8430`. The editor even renders the original attachment set at `:8508-8510`. Therefore:
25
26- A file-only new edit with no text, original attachment, or image cannot be saved because the guard returns.
27- If text or an image makes the edit sendable, the command succeeds without the generic file and the subsequent draft clear destroys the local selection.
28- If another client starts or cancels the queued run first, the recovery effect calls the file-capable `moveComposerPromptAndImages` helper, but its dirtiness check ignores files at `:3540-3547`. A file-only unsaved edit therefore takes the clearing branch at `:3555-3560`. The helper itself demonstrably supports `source.files` and writes them to the destination in `apps/web/src/composerDraftStore.ts:3911-4000`; the caller never reaches it for that state.
29
30Existing committed queue tests verify thumbnails, optimistic-row removal, and keeping the source row visible while editing. They do not submit a generic file or exercise file-only external-start recovery. Main has no equivalent queued editor, so this is a bug in the V2 branch feature rather than a lost main port.
31
32### F10, P1: mobile V2 feed still filters out non-image attachments
33
34`apps/mobile/src/features/threads/ThreadFeed.tsx:1508-1516` derives the attachment list for every message with `attachment.type === "image"`. Both user and assistant branches then contain correct file and unknown renderers at `:1569-1590` and `:1657-1678`, but those branches are unreachable because the shared list has already discarded files, PDFs, videos, and unknown attachment kinds.
35
36Frozen main uses `message.attachments ?? []` without the filter at `apps/mobile/src/features/threads/ThreadFeed.tsx:1407-1414`. Mobile attachment upload and persisted V2 projections can therefore contain a valid non-image attachment that survives transport but has no visible message chip or preview entry after reopening the thread. This remains a committed branch regression, independent of D07's compact queue controls.
37
38## New provisional findings
39
40### CLIENT-01, P1 performance: shared thread streams remain live for five minutes after their last consumer
41
42This is a current-main-only parity gap, not a rebase regression. In committed HEAD, `packages/client-runtime/src/state/threadRetention.ts:1-3` explicitly retains stream-backed state for five minutes. Every derived detail atom uses that TTL at `packages/client-runtime/src/state/threadDetail.ts:20-73`, and the raw `environment-thread-state` atom also uses it at `packages/client-runtime/src/state/threads.ts:687-696`.
43
44Trigger: open a thread detail or mount any detail/status consumer, then navigate away. The atom remains mounted during its idle TTL, so the `subscribeThread` stream and its in-memory projection continue receiving events for up to five minutes. Repeated navigation can accumulate one live stream per recently visited thread on web, desktop, or mobile. Remote and relay connections pay the same unnecessary event traffic and server projection work. No multiplier or measured byte count is claimed.
45
46Frozen main's [#9740](https://github.com/pingdotgg/t3code/pull/9740) splits these lifetimes. Derived and live atoms use `Atom.setIdleTTL(0)` in `threadDetail.ts:75-82` and `threads.ts:827-844`, so the last consumer closes the stream immediately. A distinct resume atom retains only a warm snapshot/cursor for five minutes at `threads.ts:816-825`. Main's behavioral tests close the first stream at last unmount and reopen from the retained sequence without another HTTP load. The branch's existing `threads-atoms.test.ts:12-22` instead codifies the old five-minute live retention.
47
48This mechanism is separate from live-event coalescing and snapshot size. The committed server now calls `coalesceThreadLiveStream` at `apps/server/src/ws.ts:813-834`, which reduces update frequency while a stream exists. It does not end unused streams. F16 is also separate: it concerns an unbounded single fallback snapshot outside the coalesced event path.
49
50### CLIENT-02, P2 performance: each terminal-status consumer rescans and resorts all environment metadata
51
52This is also a current-main-only parity gap. `apps/web/src/state/terminalSessions.ts:51-82` filters, maps, allocates, and numerically sorts the entire environment terminal array inside every `useKnownTerminalSessions` consumer whenever the metadata snapshot changes. `useThreadRunningTerminalIds` immediately performs another selection at `:85-90`.
53
54The operation is not confined to one chat view. It is called from sidebar rows (`apps/web/src/components/Sidebar.tsx:826` and `:1763`), legacy sidebar rows (`apps/web/src/components/LegacySidebar.tsx:386`), every command-palette thread row through `ThreadRowTrailingStatus` (`apps/web/src/components/ThreadStatusIndicators.tsx:671-675` and `CommandPalette.tsx:1189`), and several `ChatView` consumers. A terminal metadata update therefore does repeated O(terminal count) scans, conversions, sorts, and fresh-array publication across the number of mounted rows. The local command-palette patch bounds VCS and linked-PR reads only; trailing terminal consumers remain on this committed implementation.
55
56Frozen main's [#9747](https://github.com/pingdotgg/t3code/pull/9747) builds one WeakMap-backed ordered index per immutable metadata snapshot, groups summaries by thread once, and reuses unaffected group/session identities in `apps/web/src/state/terminalSessions.ts:20-121`. Its test at `terminalSessions.test.ts:152-170` verifies that 40 thread selectors cause exactly one traversal of the 40-entry source array. The branch has no equivalent selector or test.
57
58### CLIENT-03, P1 performance: mobile parsed review diffs are retained and prewarmed without a bound
59
60This is a current-main-only parity gap. Committed `apps/mobile/src/features/review/reviewState.ts:90-95` creates a keep-alive family entry for every `threadKey:sectionId`. `getCachedReviewParsedDiff` stores both the normalized source string and full parsed structure in that permanent entry at `:259-281`. `useReviewDiffPrewarming` collects every loaded non-selected section at `apps/mobile/src/features/review/useReviewDiffPrewarming.ts:58-65` and schedules all of them at `:70-93`.
61
62Trigger: browse review sections across multiple threads, especially large diffs. Each distinct section can retain its source and parsed representation for the app-registry lifetime, and idle prewarming eagerly creates entries for all loaded sections. The native review adapter is keyed by these parsed objects, so permanent parsed ownership also prevents those associated native-data entries from becoming collectible. On memory-constrained mobile devices, the retained working set grows with review history rather than an explicit budget. No heap-size estimate is claimed.
63
64Frozen main's [#9749](https://github.com/pingdotgg/t3code/pull/9749) replaces the family with one registry-local LRU-style cache capped at eight diffs and 4 MiB of source characters (`reviewState.ts:90-104`, `:291-327`). It skips oversized inputs and prewarms only nearby sections that fit the same budget (`useReviewDiffPrewarming.ts:38-99`).
65
66### CLIENT-04, P2 presentation: one ordinary late activity remains outside an otherwise settled fold
67
68This is the fourth current-main-only gap. Committed `apps/web/src/components/chat/MessagesTimeline.logic.ts:639-643` deliberately leaves work arriving after the terminal assistant message visible. The fold loop at `:719-731` skips every non-compaction entry after that message, including the common case where exactly one successful ordinary tool activity lands late.
69
70The consequence is a detached tool row below a settled assistant response even though the rest of that turn is behind its `Worked for ...` fold. Failures, interrupted work, persistent resources, and multi-row trailing groups should remain explicit; the concrete mismatch is the single non-failed activity case. Frozen main's [#9739](https://github.com/pingdotgg/t3code/pull/9739) admits exactly that one case into the fold while retaining the safeguards for failures and larger groups. This is user-visible clutter, not lost data.
71
72## Prior-ID status
73
74| ID | Frozen committed status | Evidence and classification |
75| --- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
76| F02 | **Resolved** | `EMPTY_PROVIDER_ENTRIES` is now module-scoped at `apps/web/src/components/Sidebar.tsx:209-212`, before uses at `:3854-3857` and `:3974-3977`. There is no render-time TDZ. This is a committed fix, not only a dirty-overlay change. |
77| F09 | **Open, P1** | The queued save/recovery callers still ignore `composerFiles`; detailed above. Committed defect. |
78| F10 | **Open, P1** | Mobile still filters the shared message attachment list to images before dispatching to file renderers. Committed regression from main. |
79| F17 | **Resolved** | `AssistantMarkdownContent` now splits artifact templates, transforms Codex citations, and passes the scoped image renderer at `apps/mobile/src/features/threads/ThreadFeed.tsx:824-870`. Active assistant rows call it with `onUseArtifactTemplate` and `renderMarkdownImage` at `:1647-1655`. `ThreadDetailScreen` appends selected templates into the composer at `apps/mobile/src/features/threads/ThreadDetailScreen.tsx:655-669` and passes the callback into the feed at `:722-755`. This is committed. No focused component test currently proves all three behaviors together. |
80| D02 | **Still open decision** | Normal web inherited assistant messages use the timeline's active `ctx.threadRef`/`markdownCwd` at `MessagesTimeline.tsx:1713-1736`; inherited proposed plans use the same active values at `:1899-1914`. Inspector reasoning is different and explicitly builds a ref from `projectedItem.sourceThreadId` at `V2ItemInspector.tsx:132-142`. Mobile builds all Markdown link/image handlers from the active screen `environmentId`, `threadId`, and `workspaceRoot` at `ThreadFeed.tsx:2139-2180` and `:2256-2303`. Thus ordinary inherited message/plan links still resolve against the active fork, not the source worktree. Main has no inherited-row oracle. Decide source-history ownership versus current-fork ownership before changing it. |
81| D07 | **Retained intentional difference** | The active-thread mobile queue still exposes reorder, steer, and cancel only in `ThreadQueueControl.tsx:18-149`. Shared workflow state carries queued attachments (`packages/client-runtime/src/state/threadWorkflows.ts:21-25`, `:100-113`), but the mobile row destructures only `run` and `text` and renders no edit action or thumbnail. The large composer port added full editing for pending new-task outbox entries in `NewTaskDraftScreen`, not for server-projected queued runs. This is a surface choice, not a new regression. |
82
83Root's cross-domain status is recorded without re-auditing ownership here: F13 remains an error-without-timestamp settlement case, F14 improved, F15 is fixed, and F16 remains open. F15's separate startup verification scan is an accepted performance tradeoff. The client-side F16 consequence is precise: committed `packages/client-runtime/src/state/threads.ts:643-647` sends no `turnLimit`, and a fallback snapshot replaces state without a usable progressive-history cursor. The healthy bounded HTTP path and committed live-event coalescer are not affected by that statement.
84
85## Old bot lead disposition
86
87- **Stopped/cancelled tools shown as green Completed: rejected.** `apps/web/src/session-logic.ts:481-498` maps cancelled and interrupted V2 items to `stopped`. `workEntryIndicatesToolSuccess` excludes `stopped` and `idle` at `:270-285`; the neutral-status path handles the remainder. A settled neutral row may be omitted by folding, but the current source does not relabel it as a successful green completion.
88- **Server-thread environment selector appears actionable but can no-op: source-confirmed in both trees, not elevated as a V2 parity finding.** Committed `ChatView.tsx:3358-3374` allows the callback only when `draftId` exists, while `:8539-8545` passes it whenever multiple environments exist. An empty server-backed thread with no live runtime can therefore render an unlocked selector whose callback returns. Frozen main has the same shape at `ChatView.tsx:3145-3165` and `:8047-8053`. It is a shared pre-existing edge case, most reachable through externally created/imported/failed empty threads because ordinary new-thread navigation uses a draft route.
89
90## Frozen-main feature matrix
91
92| Main change | Equivalent V2 behavior at frozen HEAD | Classification |
93| ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
94| [#9740](https://github.com/pingdotgg/t3code/pull/9740), stop unused thread streams | **Missing.** Live and derived atoms retain five-minute TTLs. | CLIENT-01, main-only catch-up required. |
95| [#9744](https://github.com/pingdotgg/t3code/pull/9744), update notice refinement | Base update notice exists; the narrower banner threshold, tooltip truncation, and download icon are not present. | Main-only visual polish, no correctness finding. No UI rendering performed. |
96| [#9627](https://github.com/pingdotgg/t3code/pull/9627), link PR author profiles | Author avatar/name render, but no non-bot GitHub profile URL is exposed. | Main-only feature omission, low-risk and not V2-specific. |
97| [#9748](https://github.com/pingdotgg/t3code/pull/9748), bound terminal history by bytes | Server-owned and outside this domain. Its client attach payload consequences are covered by root. | Excluded here. |
98| [#9747](https://github.com/pingdotgg/t3code/pull/9747), index terminal metadata | **Missing.** Every row consumer still filters/maps/sorts. | CLIENT-02, main-only catch-up required. |
99| [#9749](https://github.com/pingdotgg/t3code/pull/9749), bound mobile parsed review cache | **Missing.** Keep-alive per-section cache and all-section prewarm remain. | CLIENT-03, main-only catch-up required. |
100| [#9743](https://github.com/pingdotgg/t3code/pull/9743), project settings from new-thread header | The command palette already offers contextual Project settings at `CommandPalette.tsx:1748-1769`, and sidebar project settings remain. `ChatHeader` has the callback surface, but frozen `ChatView` does not wire it for a draft; main does. | Main-only entry-point shortcut, not a loss of all access. |
101| [#9739](https://github.com/pingdotgg/t3code/pull/9739), fold one late activity | **Missing.** All ordinary post-message activity stays outside the fold. | CLIENT-04, main-only catch-up required. |
102
103Fresh checks against earlier post-audit main work also found equivalent branch-specific implementations rather than treating file differences as omissions. Streaming assistant row reuse from [#9725](https://github.com/pingdotgg/t3code/pull/9725) is represented by `deriveMessagesTimelineRowsWithState` and V2 provenance-aware streaming replacement in `MessagesTimeline.logic.ts:1331-1429` and `session-logic.ts:844-880`. Thread-list identity reuse from [#9716](https://github.com/pingdotgg/t3code/pull/9716) is present through the WeakMap-backed scoped-shell cache and stable grouped arrays in `packages/client-runtime/src/state/threadShell.ts:34-170`. Recent desktop browser-profile/import, SSH-resolution, and screenshot-failure IPC contracts and handlers are also present in frozen HEAD. These are not findings.
104
105## Bounded six-port caller sweep
106
107This follow-up was source-only against frozen committed HEAD. It traces the active V2 callers but does not claim runtime or rendered-UI verification. All six named main changes retain equivalent behavior; differing files reflect later V2 work rather than dropped ports.
108
109- [#9713](https://github.com/pingdotgg/t3code/pull/9713), file-backed mobile image drafts: **retained**. `attachmentUpload.ts:192-226` reads file-backed images when the legacy inline path needs bytes, and `:312-322` waits for that conversion before returning `ready`. The immediate new-thread caller waits on `prepareTurnAttachments` at `use-project-actions.ts:83-107` and sends only `prepared.attachments` at `:131-151`. The offline and queued caller does the same at `use-thread-outbox-drain.ts:118-155`, `:697-706`, and `:874-884`. `projectThreadStartTurn.ts:15-53` no longer has a fallback that treats an absent inline `dataUrl` as ready.
110- [#9718](https://github.com/pingdotgg/t3code/pull/9718), hidden terminal rendering: **retained active caller**. The V2 right-panel terminal passes `rightPanelOpen` into `PersistentThreadTerminalPanel` at `ChatView.tsx:7910-7927`, and that panel passes `visible` into `ThreadTerminalDrawer` at `:1165-1283`. The otherwise shared drawer calls the terminal surface's visibility gate and suppresses hidden focus and fit work at `ThreadTerminalDrawer.tsx:438-489` and `:913-943`; both split and single viewports receive the flag at `:1492-1543`. The changed `ChatView` layout did not strand the unchanged terminal gate.
111- [#9709](https://github.com/pingdotgg/t3code/pull/9709), continuous chat status animation removal: **retained**. The running update icon is static at `ComposerServerUpdateStatus.tsx:9-20`. V2 setup, compaction, working, and thinking rows render static text or icons at `MessagesTimeline.tsx:2541-2584`, and `LiveActivityRow` has no shimmer overlay at `:2588-2608`. The removed `live-activity-focus` and ultrathink animation utilities have no active caller or definition in frozen HEAD. The remaining timer text update is not a CSS animation.
112- [#9127](https://github.com/pingdotgg/t3code/pull/9127), desktop preview-capture failure unlock: **retained end to end**. Desktop reports `screenshotFailed` when crop capture fails at `apps/desktop/src/preview/Manager.ts:2473-2487`, and the IPC schema carries it at `packages/contracts/src/ipc.ts:936-947`. The renderer bounds its own crop conversion to five seconds at `apps/web/src/lib/previewAnnotation.ts:113-147`, keeps the annotation without the crop, and always clears the active pick in `PreviewView.tsx:580-630`. A failed capture cannot leave the composer locked on this path.
113- [#9647](https://github.com/pingdotgg/t3code/pull/9647), Antigravity restart sign-in behavior: **retained active callers**. `ChatComposer.tsx:1644-1651` still uses `getAntigravitySendBlockReason` for the actual send gate. The helper permits `auth.status === "unknown"` after validating installation and a selected model at `ChatView.logic.ts:415-428`; it blocks only explicit unauthenticated state. The active banner also suppresses the installed Antigravity warning while auth is unknown at `ProviderStatusBanner.tsx:9-31`, and `ChatView.tsx:3279-3292` uses that result.
114- [#9651](https://github.com/pingdotgg/t3code/pull/9651), sidebar multi-select unpin: **retained**. The active multi-select context-menu caller filters selected rows to pin-capable, currently pinned threads and adds `Unpin (k)` at `Sidebar.tsx:2978-3019`. Selecting it calls `attemptUnpin` for exactly that filtered set and clears selection at `:3101-3107`. `Sidebar.logic.ts:209-218` omits the action when the pinned count is zero.
115
116No finding was added for this six-item sweep.
117
118## Late-main addendum
119
120Main advanced after the frozen comparison to `bc03c3640d6d3bb44e5fb477bfd78d7484cd0e00`. The branch HEAD and overlay did not move. These two items are late-main catch-up gaps, not claims that an earlier port was lost.
121
122- **M09, defer the mobile file highlighter from [#9752](https://github.com/pingdotgg/t3code/pull/9752): missing.** Frozen HEAD wraps every valid `ThreadFileScreen` in `ReviewHighlighterProvider` at `ThreadFilesRouteScreen.tsx:825-943`. The provider immediately calls `useReviewHighlighterState` at `ReviewHighlighterProvider.tsx:11-19`, whose mount effect prepares the engine and initial language set at `reviewHighlighterState.ts:164-176`. This happens for image, video, browser-preview, Markdown-preview, and source routes even though only `SourceFileSurface` consumes `sourceHighlightAtom`. That real consumer already starts `highlightSourceFile` lazily through `sourceHighlightingState.ts:29-50`. The late main commit removes the redundant route-wide provider and manager, so the branch still pays highlighter startup on file routes that do not need syntax highlighting.
123- **M11, defer history image URLs from [#9760](https://github.com/pingdotgg/t3code/pull/9760): missing.** Frozen `ChatView` walks every loaded user message attachment at `ChatView.tsx:2910-2919`, turns the full set into asset resources, and calls `useAssetUrls` at `:2959-2977`. The resulting URLs are projected into the whole timeline at `:3072-3130`, before virtualization determines which message rows are mounted. The active V2 `UserTimelineRow` at `MessagesTimeline.tsx:1381-1445` merely consumes those prefilled URLs. The late main commit keeps only pending local-preview handoffs at chat scope and moves image resource selection plus `useAssetUrls` into each mounted user row. The branch therefore still requests signed URLs for offscreen history, including IDs collected from non-image user attachments. This is a post-freeze performance gap, not an old-regression finding.
124
125## Surface and entry-point matrix
126
127| Contract or behavior | Web | Desktop shell | Mobile | Remote / multi-environment consequence |
128| --------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
129| V2 queued work | Reorder, steer, cancel, thumbnails, composer editing; F09 loses newly selected generic files | Same web UI | Reorder, steer, cancel only by D07; pending new-task outbox has a separate editor | Commands carry explicit environment/thread IDs. F09 is especially easy to hit while another device advances the queue. |
130| Persisted message attachments | Images and generic files render | Same web UI | F10 hides non-images before otherwise valid file/PDF/video renderers | Assets remain environment/thread scoped; the defect is client filtering after projection. |
131| Assistant Markdown/templates/citations/images | Specialized renderer retained; D02 uses active fork for ordinary inherited rows | Same web renderer | F17 fixed; scoped iOS media and template callback restored; D02 still uses active screen ownership | Direct rows resolve against the selected environment. Source-versus-fork semantics remain undecided only for inherited history. |
132| Thread stream lifetime | Five-minute live idle retention | Same web client runtime | Same shared runtime | CLIENT-01 keeps unused remote streams and event delivery alive; coalescing reduces event rate but not lifetime. |
133| Terminal row status | Repeated metadata scan/sort in sidebar, palette, and chat | Same web UI plus desktop terminals | Separate native terminal flow | CLIENT-02 scales with mounted web rows and terminal count. The dirty palette patch does not gate trailing terminal reads. |
134| New-thread Project settings | Sidebar and command palette work; header shortcut absent | Same web UI | Separate project selection/settings flow | No total feature loss; [#9743](https://github.com/pingdotgg/t3code/pull/9743) adds one web/desktop entry point. |
135| Settled activity folding | Single late success remains detached | Same web UI | Separate native grouping implementation | CLIENT-04 is presentation-only and does not change wire state. |
136| Review diff cache | Not applicable | Not applicable | Unbounded parsed keep-alive cache | CLIENT-03 is device-local memory growth; remote source size can accelerate it. |
137| Desktop IPC parity | Browser profiles/import, SSH host resolution, and preview failure contracts are present | Handlers/preload present | Not applicable | No frozen-main desktop-shell omission found in the inspected recent contracts. |
138
139## Dirty overlay, local-only review
140
141No new concrete defect was found in the scoped dirty overlay. These are not committed fixes and do not change the committed status of any finding above.
142
143- **Command palette / thread indicators.** The snapshot passes the existing thread change-request snapshot into each leading status row. `ThreadRowLeadingStatus` observes its containing result with the shared sidebar lease, gates linked-PR and VCS queries on near-viewport visibility, retains the last live value by environment/worktree key, and falls back only to a same-branch, non-linked snapshot. The local 128-row test verifies zero offscreen reads, eight reads for the first visible set, eight more after moving visibility, and retained cached badges. It does not cover the trailing terminal query, which is why CLIENT-02 remains.
144- **Desktop local topology.** `desktopLocal.ts.snapshot` keeps the last array identity when a bridge returns fresh objects with equal `id`, label, distro, HTTP URL, WS URL, and bootstrap token. It publishes a new identity when any field changes and keeps the bridge-less empty array stable. Tests cover every compared field.
145- **Expo notifications patch.** The patch protects delegates and pending responses with `NSLock`, snapshots delegate lists before callbacks, appends a response before releasing the lock, and removes only the exact handled response identities afterward. That ordering covers both interleavings between delegate registration and delivery and preserves responses created during a replay callback. Workspace patch mapping and lockfile hash wiring match the patch. The native test compiles the installed patched Swift source with ThreadSanitizer; no simulator was launched.
146
147## Tests and limitations
148
149Detailed output is in `clients-focused-tests.log`.
150
151- The six-port sweep and M09/M11 addendum are source-only. No tests were rerun for this follow-up.
152- **Committed-source evidence:** 3 files, 21 tests passed. Before execution, all three live files had SHA-256 hashes identical to frozen committed HEAD. These cover queue ordering/capability presentation and web queue-row behavior. They do not cover F09 submission or D02 routing.
153- **Local-only overlay evidence:** 5 files, 60 tests passed. The four requested filter strings selected one additional committed `.test.tsx` file because Vite+ file filtering is substring-based; it is not counted as separate intended coverage. The scoped live files matched the suffixed frozen snapshots and manifest hashes before execution. The only warning was `react-test-renderer` deprecation.
154- The root batch's earlier copied-test import failure was audit-artifact discovery noise. All frozen overlay test copies now end in `.snapshot`; no copied audit test ran in this client batch.
155- No browser, desktop app, provider, server, simulator, or real UI renderer was launched. No visual behavior is claimed for update-notice layout, attachment chips, folds, or Markdown interaction.
156- No live T3 userdata was opened or mutated. No product code or existing test was edited. No repo-wide command, commit, push, GitHub action, or review comment was performed.
157- Source inspection proves the stated control/data paths. It does not provide measured frame sizes, heap growth, CPU percentages, or end-to-end remote latency.
audits/orchestrator-v2/2026-09-04/closing-references.json+15−0
@@ -0,0 +1,15 @@
1{
2 "capturedAt": "2026-09-04T19:30:28.136740+00:00",
3 "head": "8af5734365f7c45bc08b57066dbae42f9f7d4235",
4 "initialMain": "d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70",
5 "reviewedMainAtClose": "f6db4206258b0ef30e8dd8949627acfd209bf338",
6 "mainOnlyCommits": 15,
7 "branchOnlyCommits": 333,
8 "initialTreeChangedFiles": 991,
9 "closingTreeChangedFiles": 999,
10 "incomingMainCommitsSincePreviousAudit": 247,
11 "dirtyFiles": 59,
12 "dirtyFilesChangedSinceFinalManifest": [],
13 "noLongerDirty": [],
14 "cutoffNote": "Main revisions after reviewedMainAtClose are outside this audit."
15}
audits/orchestrator-v2/2026-09-04/cross-cutting.md+64−0
@@ -0,0 +1,64 @@
1# Cross-cutting review, 2026-09-04
2
3Committed target: `8af5734365f7c45bc08b57066dbae42f9f7d4235`. Main: `d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70`. The worktree is changing independently; frozen local copies have a `.snapshot` suffix and are listed in `worktree-manifest.json`. These are review artifacts, not product changes.
4
5## Prior findings
6
7**F12 remains open, P2.** Initial automatic titles still make one request. [ThreadTitleRegenerationService.ts](/Users/julius/.t3/worktrees/codething-mvp/t3code-7f11a674/apps/server/src/orchestration-v2/ThreadTitleRegenerationService.ts:223) calls `generateThreadTitle`, then catches non-interruption failures as completion at line 236 and clears the request marker. The shared text-generation layer adds no retry. Main's `ProviderCommandReactor.ts:963` retries initial generation twice with exponential backoff, from [#8087](https://github.com/pingdotgg/t3code/pull/8087). A transient typed provider error therefore leaves the fallback title until manual regeneration. The existing failure test deliberately dies, asserts one call, and verifies cleanup; it does not test transient typed failure followed by success. Restore bounded retries for initial titles while retaining stale-request guards and cleanup. Source is unchanged between the preceding audited revision and this committed target.
8
9**F16 remains open, P1 for large histories.** [ws.ts](/Users/julius/.t3/worktrees/codething-mvp/t3code-7f11a674/apps/server/src/ws.ts:867) still obtains `getThreadSnapshot`, projects the entire result, and emits it as one socket snapshot. Invalid cursors and reconnect ranges above 128 events or 1 MiB use this function. A cold HTTP snapshot failure uses it too. `ThreadManagementService` delegates this to the full projection-store snapshot, despite having a separate windowed snapshot method. Per-item wire truncation does not cap the number of rows. [threads.ts](/Users/julius/.t3/worktrees/codething-mvp/t3code-7f11a674/packages/client-runtime/src/state/threads.ts:301) accepts the full snapshot and resets progressive history metadata. Main's `ws.ts:1676` forwards the client's `turnLimit` when obtaining the fallback snapshot. The existing healthy HTTP path is bounded; this finding concerns fallback. Route fallback through the bounded query/budget path and carry a valid history cursor. No latency or memory multiplier is claimed.
10
11**V01's missing-coalescer concern is addressed in committed code.** `ws.ts:827` now wires `coalesceThreadLiveStream`. The new implementation combines repeated running tool updates by thread, run, and stable item ID in a 50 ms window. Lifecycle/message boundaries flush survivors in sequence order. Its tests exercise independent calls, terminal boundaries, cancellation, and retained-buffer limits. The separate `LiveStreamBudget` also limits queued and unacknowledged live events. This does not fix F16 because the snapshot frame is produced outside that bounded event path. Actual provider-workload byte/frame comparisons remain unmeasured.
12
13**D01 is still an explicit deferral.** [ServerEnvironment.ts](/Users/julius/.t3/worktrees/codething-mvp/t3code-7f11a674/apps/server/src/environment/ServerEnvironment.ts:230) withholds `serverUpdateThreadContinuation` and explains that V2 recovery terminalizes running work instead. Main's continuation feature from [#9167](https://github.com/pingdotgg/t3code/pull/9167) is not provided. This is a documented product decision, not evidence of a careless conflict resolution.
14
15## Main lag and retained boundaries
16
17There are eight main-only commits. The [main-only inventory](main-only-commits.txt) is separate from old porting failures. In particular, [#9748](https://github.com/pingdotgg/t3code/pull/9748) adds an 8 MiB terminal-history cap, bounded Unicode-safe tail reads, and protection against giant partial lines. This branch still limits terminal history by 5,000 lines only and reads the entire saved history before capping it at `terminal/Manager.ts:1494`. A long line remains unbounded in memory, storage, and attach snapshots. That change is independent of V2 and should be incorporated when catching up with main. Main's preceding incremental terminal-history change is already present.
18
19| Boundary | Current evidence |
20| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
21| PR and base | [#2829](https://github.com/pingdotgg/t3code/pull/2829) targets main and has the reviewed head. Main is eight commits ahead of the merge base; there are 333 branch-only commits and 991 changed files in the final-tree comparison. |
22| Incoming main inventory | 240 commits since the previous main reference. 64 touch only files now identical to main; 176 touch at least one differing file. File equality is a triage aid, not proof of a caller being retained. |
23| Rebase patch inventory | The range comparison has 265 equal patches, 64 changed pairs, three unmatched old patches and four unmatched new patches. Two unmatched pairs are integration/lint bookkeeping. The unmatched old Codex availability patch is absorbed: `CodexProvider.ts:489-525` still initializes an unchecked provider with `installed: false`. The prior audit tip maps exactly to `96aff3564b3`; the two subsequent commits contain the new resource work and main reconciliation. |
24| Protocol and CORS | Protocol V2 negotiation remains explicit, and `httpCors.ts` permits the protocol header. Renamed/new RPCs retain explicit auth-scope entries. Focused tests pass. |
25| Remote identity, credentials and pairing | Shared auth implementations apart from the RPC mapping, and cloud credential refresh code, match main. This includes the pairing read-model and refresh-without-disconnect changes. No live relay or expiry flow was exercised. |
26| Long delegated IDs | Both environment HTTP routing and relay HTTP routing allow 512-character parameters. The relay worker supplies that router configuration. |
27| Relay push and decoding changes | Main's stalled-push bounds and decoding changes are in identical relay files outside the V2 awareness adapter. |
28| Background metadata prompts and pricing | Shared text-generation prompts/dispatcher, usage readers and cloud code match main. Provider-specific permissions and token translation are reviewed separately. |
29| Checkpoint summaries | Shared `CheckpointStore` matches main, including avoiding full patches when only summaries are needed. F03 concerns the V2 baseline query, not that shared implementation. |
30| Automatic project pulls and PR refresh | The opt-in project setting reaches `VcsStatusBroadcaster`'s policy and startup `autoPullProjects`. V2 run execution invokes the cached `refreshAfterTurn` observer, and checkpoint finalization refreshes branch PR status. These replacements remain wired to the shared implementations. |
31| Desktop and service launch | Server bundle still includes both the main binary and service launcher. The only differing production Electron file has a type annotation in the Tailscale fallback, with no behavioral change. Web findings still affect desktop. No packaging or native runtime was launched. |
32| Performance CI | The V1-specific transfer-report workflow was deliberately removed by commit `103f1e6cd22`. Its commit message calls for a V2 replacement. A focused V2 wire regression script now exists, but the old built-app benchmark/report is not restored. Keep this distinction when claiming performance verification. |
33
34## Additional lifecycle question
35
36The old bot observation about archived scheduled-task targets still has a valid source path, but it is not a main-to-V2 regression because main has no equivalent scheduled-task service. `OrchestratorMcpService.ts:953` accepts the current thread as a schedule target without checking whether it is archived, and the generic scheduled-task upsert does not validate its target. Later, `ThreadManagementService.ts:483` rejects sends to archived threads. Recurring tasks retain their enabled state and compute the next occurrence after each failure. Archiving or deleting an existing bound target raises the same lifecycle question. Decide whether to disable such schedules, reject new bindings, or keep schedules enabled for later unarchive. No scheduling action or live provider was invoked for this audit.
37
38## Late main arrivals
39
40While the reviewed branch and local source stayed unchanged, `origin/main` advanced to `bc03c3640d6d3bb44e5fb477bfd78d7484cd0e00`. The four new commits are recorded in `main-late-arrivals.txt`; M09-M12 in the consolidated report cover them. The original frozen-main comparison and its eight missing commits remain reproducible.
41
42**M12, bundled model classification.** [#9762](https://github.com/pingdotgg/t3code/pull/9762) adds `gpt-6-astra` to the Codex `currentModels` list and advances the bundled manifest timestamp. The branch's manifest still matches the earlier main version. `ModelManifest.ts:210-221` classifies built-in models missing from that list as legacy. This affects the bundled/offline fallback: the existing runtime manifest refresh can obtain the updated main file and compensate online. It is not evidence that the provider cannot execute the model or that model discovery is absent. Incorporate the small data update during main catch-up.
43
44The parent independently checked OpenCode's installed SDK SSE generator: `dist/v2/gen/core/serverSentEvents.gen.js:117` exits the stream loop on normal completion. A clean response-body EOF does not automatically resubscribe, which supports F18's trigger. The adapter finding remains source-backed, without a timing-based absence test.
45
46Three final UI commits arrived before the cutoff was set at `f6db4206258b0ef30e8dd8949627acfd209bf338`. The parent reviewed these directly:
47
48- **M13, proactive diff activation, [#9753](https://github.com/pingdotgg/t3code/pull/9753).** V2 `ChatView.tsx:4261-4275` opens a diff whenever a completed run has any checkpoint summary and the workspace is a Git repository. It does not check whether the active surface is a pull request or whether the checkpoint is ready and nonempty. `threadCheckpoints.ts:29-51` preserves empty-file and non-ready summaries, so the active caller can replace a PR panel with an empty or unavailable diff. Main's new decision helper rejects those cases and preserves deferred loading. Adapt it to V2's `runId` summaries.
49- **M14, sidebar background prominence, [#9759](https://github.com/pingdotgg/t3code/pull/9759).** V2 `Sidebar.tsx:866-893` lets unread/woken state override dimming even while a thread is still working. Main dims unselected/unfocused working or monitoring rows regardless of unread completion, while retaining unread/wake prominence for ready and action-required states. Port the presentation rule using V2 statuses. This is visual policy catch-up, not lost conversation data.
50- **M15, automatic-pull reset affordance, [#9763](https://github.com/pingdotgg/t3code/pull/9763).** `ProjectSettingsPanel.tsx:989-999` still renders the working on/off switch without the new reset button. Main adds a reset action that calls the existing setter with `false`. The feature and its reverse operation already work; only the standard reset affordance is absent.
51
52The initial eight main-only commits plus these seven arrivals total 15 main-only commits at the final cutoff. No later main revision is included in this audit.
53
54## Focused checks and limitations
55
56- `root-contract-tests.log`: 6 file executions, 41 tests passed, covering WS compatibility, startup, RPC authorization, title handling, live coalescing, and wire projection.
57- `root-budget-awareness-tests-rerun.log`: 3 file executions, 28 tests passed, covering live-stream budgets and shared/local awareness behavior.
58- `root-resume-tests.log`: 3 file executions, 25 tests passed, covering resume decisions, active/archive shell streams, and HTTP contracts. The resume tests verify the fallback decision, not a bounded end-to-end fallback snapshot.
59- `root-usage-attachments-tests.log`: 5 file executions, 38 tests passed, covering usage readers/pricing/proxy limits, attachment claims, and metadata prompts.
60- `root-native-command-tests.log`: 2 file executions, 61 tests passed, covering V2 run execution and launch behavior, including native compaction routing and logout guards.
61- The first awareness run also selected a frozen audit copy because Vitest's file filter matched its suffix. Its import failure was an audit-artifact discovery problem; the three actual suites passed. Frozen files were renamed with `.snapshot`, and the rerun passed. No product source was changed to resolve this.
62- These are focused tests on the current worktree, including its local awareness work. They do not establish correctness for every client, provider, upgrade history, or fallback transport.
63
64Current-head [CI Check](https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744766) fails formatting in seven files. The generic test job and server shards 1 and 2 passed; server shard 3 was cancelled. Release smoke and Rust passed; web/mobile/macOS preview builds were skipped. The [captured metadata](pr-metadata.json) and [failed log](ci-check-failure.log) retain exact evidence. CI does not cover the uncommitted overlay. No repo-wide check, browser, simulator, dev server, live-provider validation, production database write, commit, push, or review comment was performed.
audits/orchestrator-v2/2026-09-04/dirty-stat.txt+50−0
@@ -0,0 +1,50 @@
1 apps/server/src/git/GitManager.ts | 2 +-
2 .../Adapters/CodexAdapterV2.test.ts | 5 +-
3 .../orchestration-v2/Adapters/CodexAdapterV2.ts | 1 +
4 apps/server/src/orchestration-v2/Orchestrator.ts | 86 +++++----
5 .../server/src/orchestration-v2/ProjectionStore.ts | 192 +++++++++++++++++++++
6 .../ProviderTurnControlService.test.ts | 2 +
7 .../ThreadSettlementService.test.ts | 8 +-
8 .../orchestration-v2/ThreadSettlementService.ts | 12 +-
9 .../src/orchestration-v2/runtimeLayer.test.ts | 32 ++++
10 .../message_steering/codex_transcript.ndjson | 2 +-
11 .../fixtures/multi_turn/codex_transcript.ndjson | 2 +-
12 .../plan_questions/codex_transcript.ndjson | 2 +-
13 .../fixtures/proposed_plan/codex_transcript.ndjson | 2 +-
14 .../provider_thread_resume/codex_transcript.ndjson | 4 +-
15 .../fixtures/queued_turn/codex_transcript.ndjson | 2 +-
16 .../fixtures/simple/codex_transcript.ndjson | 2 +-
17 .../fixtures/subagent/codex_transcript.ndjson | 2 +-
18 .../subagent_continue/codex_transcript.ndjson | 2 +-
19 .../fixtures/subagent_v2/codex_transcript.ndjson | 2 +-
20 .../subagent_v2_nested/codex_transcript.ndjson | 2 +-
21 .../thread_fork_native/codex_transcript.ndjson | 2 +-
22 .../codex_transcript.ndjson | 2 +-
23 .../codex_transcript.ndjson | 2 +-
24 .../codex_transcript.ndjson | 2 +-
25 .../codex_transcript.ndjson | 2 +-
26 .../codex_transcript.ndjson | 2 +-
27 .../thread_rollback/codex_transcript.ndjson | 2 +-
28 .../fixtures/todo_list/codex_transcript.ndjson | 2 +-
29 .../codex_transcript.ndjson | 2 +-
30 .../codex_transcript.ndjson | 2 +-
31 .../codex_transcript.ndjson | 2 +-
32 .../turn_interrupt/codex_transcript.ndjson | 2 +-
33 .../codex_transcript.ndjson | 2 +-
34 .../fixtures/web_search/codex_transcript.ndjson | 2 +-
35 apps/server/src/persistence/Migrations.ts | 2 +
36 .../src/provider/Layers/EventNdjsonLogger.test.ts | 16 ++
37 .../src/provider/Layers/EventNdjsonLogger.ts | 1 +
38 apps/server/src/relay/AgentAwarenessRelay.ts | 120 +++++++++++--
39 apps/server/src/server.ts | 2 +-
40 apps/server/src/vcs/GitVcsDriver.ts | 10 +-
41 apps/server/src/vcs/GitVcsDriverCore.test.ts | 90 +++++++++-
42 apps/server/src/vcs/GitVcsDriverCore.ts | 30 +++-
43 apps/web/src/components/CommandPalette.tsx | 10 +-
44 .../src/components/ThreadStatusIndicators.test.ts | 19 ++
45 apps/web/src/components/ThreadStatusIndicators.tsx | 76 ++++++--
46 apps/web/src/connection/desktopLocal.test.ts | 63 ++++++-
47 apps/web/src/connection/desktopLocal.ts | 21 ++-
48 pnpm-lock.yaml | 5 +-
49 pnpm-workspace.yaml | 1 +
50 49 files changed, 732 insertions(+), 126 deletions(-)
audits/orchestrator-v2/2026-09-04/drift-interim.json+8−0
@@ -0,0 +1,8 @@
1[
2 "apps/server/src/orchestration-v2/ProjectionSettlement.test.ts",
3 "apps/server/src/relay/AgentAwarenessRelay.test.ts",
4 "apps/server/src/relay/AgentAwarenessRelay.ts",
5 "docs/internals/mobile-development.md",
6 "docs/internals/performance-regressions.md",
7 "docs/internals/providers.md"
8]
audits/orchestrator-v2/2026-09-04/initial-status.txt+57−0
@@ -0,0 +1,57 @@
1 M apps/server/src/git/GitManager.ts
2 M apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
3 M apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
4 M apps/server/src/orchestration-v2/Orchestrator.ts
5 M apps/server/src/orchestration-v2/ProjectionStore.ts
6 M apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
7 M apps/server/src/orchestration-v2/ThreadSettlementService.test.ts
8 M apps/server/src/orchestration-v2/ThreadSettlementService.ts
9 M apps/server/src/orchestration-v2/runtimeLayer.test.ts
10 M apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson
11 M apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson
12 M apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson
13 M apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson
14 M apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson
15 M apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson
16 M apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson
17 M apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson
18 M apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson
19 M apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson
20 M apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson
21 M apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson
22 M apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson
23 M apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson
24 M apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson
25 M apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson
26 M apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson
27 M apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson
28 M apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson
29 M apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson
30 M apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson
31 M apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson
32 M apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson
33 M apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson
34 M apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson
35 M apps/server/src/persistence/Migrations.ts
36 M apps/server/src/provider/Layers/EventNdjsonLogger.test.ts
37 M apps/server/src/provider/Layers/EventNdjsonLogger.ts
38 M apps/server/src/relay/AgentAwarenessRelay.ts
39 M apps/server/src/server.ts
40 M apps/server/src/vcs/GitVcsDriver.ts
41 M apps/server/src/vcs/GitVcsDriverCore.test.ts
42 M apps/server/src/vcs/GitVcsDriverCore.ts
43 M apps/web/src/components/CommandPalette.tsx
44 M apps/web/src/components/ThreadStatusIndicators.test.ts
45 M apps/web/src/components/ThreadStatusIndicators.tsx
46 M apps/web/src/connection/desktopLocal.test.ts
47 M apps/web/src/connection/desktopLocal.ts
48 M pnpm-lock.yaml
49 M pnpm-workspace.yaml
50?? apps/mobile/scripts/fixtures/
51?? apps/mobile/scripts/notification-center-manager.test.ts
52?? apps/server/src/orchestration-v2/ProjectionSettlement.test.ts
53?? apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts
54?? apps/server/src/relay/AgentAwarenessRelay.test.ts
55?? apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx
56?? audits/
57?? patches/expo-notifications@57.0.15.patch
audits/orchestrator-v2/2026-09-04/main-feature-file-map.tsv+241−0
@@ -0,0 +1,241 @@
1commit subject files differing_files differing_paths
2d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70 perf(client): stop thread streams when unused (#9740) 6 6 docs/internals/connection-runtime.md;packages/client-runtime/src/state/threadDetail.ts;packages/client-runtime/src/state/threadRetention.ts;packages/client-runtime/src/state/threads-atoms.test.ts;packages/client-runtime/src/state/threads-sync.test.ts;packages/client-runtime/src/state/threads.ts
30de956ed2f92b8a6ead56e1566801db232d365dc fix(web): refine server update notice (#9744) 4 4 apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ComposerBanner.tsx;apps/web/src/components/chat/ComposerBannerStack.tsx;apps/web/src/components/chat/ComposerServerUpdateStatus.tsx
4a76b898b3aa4fd4081104b06a3b2c942bd4fecb7 feat(web): link pull request authors to profiles (#9627) 2 2 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/components/pullRequest/pullRequestPresentation.tsx
5cf9729d5ee9660c08556e823080d3bb19648ed28 perf(server): bound terminal history by bytes (#9748) 4 4 apps/server/src/terminal/Manager.test.ts;apps/server/src/terminal/Manager.ts;docs/internals/terminal-runtime.md;docs/user/terminal.md
6fec606f9ae524277ef1f6886e614f32d9e15e36e perf(web): avoid repeated terminal metadata scans (#9747) 2 2 apps/web/src/state/terminalSessions.test.ts;apps/web/src/state/terminalSessions.ts
7d6e29dc9dee943b34d6b0d11441fa944c7bff7c9 perf(mobile): bound the parsed review cache (#9749) 4 4 apps/mobile/src/features/review/reviewState.test.ts;apps/mobile/src/features/review/reviewState.ts;apps/mobile/src/features/review/useReviewDiffPrewarming.test.ts;apps/mobile/src/features/review/useReviewDiffPrewarming.ts
8cbe93e8dfba68ff6fbe8c69e43a633fdc79db62d fix(web): show project settings for new threads (#9743) 2 2 apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatHeader.tsx
9cfc9bf34156ddcc4a98b7f5c67193adb5aedce06 fix(web): fold single trailing activity (#9739) 3 3 apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.test.tsx
10c8f77e0d441264efb0acfac312e852c81ae3da83 perf(server): stop caching unused OpenCode tool parts (#9738) 2 2 apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts
11088cc3f95599b7b933289d719fa1e9b0608cb4cd fix(relay): bound stalled push requests (#9734) 3 0
1219c1710a88a2c87c159d76269dacdf0b17ddd9f4 perf(web): reuse timeline rows while text streams (#9725) 6 6 apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.tsx;apps/web/src/session-logic.test.ts;apps/web/src/session-logic.ts
13c7bf3115f2223ede5cb2316613dc7369155dbeb1 feat(web): unpin threads from the sidebar multi-select menu (#9651) 3 3 apps/web/src/components/Sidebar.logic.test.ts;apps/web/src/components/Sidebar.logic.ts;apps/web/src/components/Sidebar.tsx
147d5dc66c151a9ca7bfef45cffc9d3516842c5566 fix(web): mute composer helper text (#9654) 1 0
1550bfca43d76ced00f5d67cfbab8bc44c50eb0e53 perf(server): replay only the selected thread (#9726) 12 12 apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts;apps/server/src/orchestration/Layers/OrchestrationEngine.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;apps/server/src/orchestration/Services/OrchestrationEngine.ts;apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts;apps/server/src/persistence/Layers/OrchestrationEventStore.ts;apps/server/src/persistence/Services/OrchestrationEventStore.ts;apps/server/src/relay/AgentAwarenessRelay.test.ts;apps/server/src/server.test.ts;apps/server/src/serverRuntimeStartup.reconcile.test.ts;apps/server/src/serverRuntimeStartup.test.ts;apps/server/src/ws.ts
16c4353bc6b972fa527579b530f01cc8744b2407d2 fix(mobile): read file-backed image drafts before enabling them (#9713) 18 3 apps/mobile/src/lib/projectThreadStartTurn.test.ts;apps/mobile/src/lib/projectThreadStartTurn.ts;apps/mobile/src/state/use-thread-outbox-drain.ts
175eab021a5185e492b6b021f83564143d5263e8a6 perf(web): stop rendering hidden terminals (#9718) 4 1 apps/web/src/components/ChatView.tsx
189eb4d71681dc7d002082db2f8b4bacf7614412f4 fix(mobile): remove provider setup (#9721) 19 3 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx;apps/mobile/src/state/use-thread-composer-state.ts;apps/mobile/src/state/use-thread-outbox-drain.ts
19120fab18d84f1eb993ecc7fa858a8dd212747079 fix(web): keep the slash menu above the composer when vertical space is short (#9625) 2 1 apps/web/src/components/chat/ChatComposer.tsx
208357eef14cbd5ed063e4a64404c33d1bfbc78446 fix(web): match provider settings layout for disconnected devices (#9619) 4 1 apps/web/src/components/settings/ProviderInstanceCard.tsx
218ccb933a8aae461b616b199ab287164c3311a755 test(server): allow either valid file-search match (#9720) 1 0
22108f295cc3672716c3cb8291ce487846df5ce098 fix(server): bound slow-client event buffers (#9715) 6 6 apps/server/src/orchestration/LiveStreamBudget.test.ts;apps/server/src/orchestration/LiveStreamBudget.ts;apps/server/src/orchestration/ThreadLiveEventCoalescer.test.ts;apps/server/src/orchestration/ThreadLiveEventCoalescer.ts;apps/server/src/server.test.ts;apps/server/src/ws.ts
23d536b0580d044967dc644498d8c5a6e96464f767 fix(server): settle inactive threads with open PRs (#9610) 4 1 apps/server/src/orchestration/ThreadSettlementReactor.test.ts
24c66f15f39e61fda0a9a100578833ec12e7526859 perf(client): reduce thread-list update work (#9716) 2 2 packages/client-runtime/src/state/entities.test.ts;packages/client-runtime/src/state/threadShell.ts
2595103905f5f045523994213bf76bfb14acc31b27 feat(web): preview pull request links (#9631) 5 0
26da7e46d08e85bcb07ecd78721a40f7b612fac2f2 perf(web): stop replaying terminal buffers on rollover (#9707) 8 0
277839140e5e93d3f401d7eb45b86cf1a234eb3609 fix(mobile): preserve saved work after storage read failures (#9710) 5 0
28c7c1dfe4df99edf65a49d8a31b39ef1361f37f44 perf(web): stop continuous chat status animations (#9709) 6 5 apps/web/src/components/chat/ComposerServerUpdateStatus.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;apps/web/src/index.css;docs/user/composer.md
29c75299ee2085a121bceb6df76796e971fe92b5b6 perf(relay): avoid repeated activity decoding (#9708) 4 0
30dffb4cd3b16dc6f41aced99922950ee3083082c6 perf(server): use one query for buffered provider events (#9706) 10 10 apps/server/src/checkpointing/CheckpointDiffQuery.test.ts;apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts;apps/server/src/project/ProjectSetupScriptRunner.test.ts;apps/server/src/provider/Layers/ProviderSessionReaper.test.ts;apps/server/src/serverRuntimeStartup.test.ts
313bbbc1d9fd8b3d649c60ba0137c7dae93a6aab3f perf(server): stop rebuilding terminal history per chunk (#9703) 3 3 apps/server/src/terminal/Manager.test.ts;apps/server/src/terminal/Manager.ts;docs/internals/terminal-runtime.md
32010d6bb1b5281cb1e6eacf1ee0f2500973242836 perf(marketing): serve website fonts locally (#9701) 12 0
33b3e1d88590489da2bb63b95c18a57e6f400b8b7f perf(web): defer diff workers until a code view opens (#9692) 11 5 apps/web/src/components/ChatView.tsx;apps/web/src/components/DiffPanel.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;apps/web/src/components/files/FilePreviewPanel.tsx
34c163d502dd32b993c03d8c20a5fae55b159bd8dc perf(server): avoid full patches for checkpoint summaries (#9694) 14 5 apps/server/package.json;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.ts;apps/server/src/vcs/GitVcsDriver.ts;pnpm-lock.yaml
354ee2a9d046a0986451c703c9221bb32ba53540bb perf(marketing): stop continuous homepage motion (#9697) 1 1 apps/marketing/src/pages/index.astro
361587f248dd81ed45e214d476451ebf16dbfadb1a feat(server): measure provider turn token usage (#9132) 14 10 apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;apps/server/src/provider/Layers/ProviderService.test.ts;apps/server/src/provider/Layers/ProviderService.ts;docs/README.md;packages/contracts/src/providerRuntime.ts
37dab5f6e6e02e78675655e69503aa89654e5b8050 perf(web): defer composer draft serialization (#9695) 3 1 apps/web/src/composerDraftStore.ts
38777f5bb2e11fe30e7fdcb5741b0b1d9bb20924d6 perf(mobile): reuse diff rows during comment edits (#9693) 2 0
39246064993535e5d90107d3d7784ceef3cc883435 perf(clients): avoid waiting to read cached relay tokens (#9691) 2 0
40ec8b2119c377f5c1dbe6235b221ef98eca31a96e perf(server): omit repeated OpenCode progress logs (#9689) 3 3 apps/server/src/provider/Layers/EventNdjsonLogger.test.ts;apps/server/src/provider/Layers/EventNdjsonLogger.ts;docs/internals/providers.md
4144dc8ae259f5c3349f7ab8045e39bd62408b52a9 perf(mobile): reuse chat feed rows during streaming (#9688) 3 3 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/mobile/src/state/use-thread-composer-state.ts
42f2e3764c257a7e27c8171d7dd1e38d4383074206 perf(server): stop retaining unused OpenCode tool history (#9684) 1 1 apps/server/src/provider/Layers/OpenCodeAdapter.ts
432263e13fda8c9a4f1b6f4dee32e3c9020195e2aa perf(server): batch projector cursor writes (#9671) 4 2 apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts
4427e6cc27fe0f3cff53a44905e40615b7db99c80c perf(server): cache and stream static web assets (#9669) 3 1 apps/server/src/server.test.ts
458e3aa324b57dd645980b203d5f8a5b9f9dc53a84 perf(marketing): serve images at their display size (#9682) 8 3 apps/marketing/public/app-desktop.webp;apps/marketing/src/pages/index.astro;pnpm-lock.yaml
467cf5b284e6e37895f535433c77f728b3e4292c9b perf(mobile): skip unused legacy list work (#9679) 3 2 apps/mobile/src/features/home/HomeScreen.tsx;apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
47887ece307131bdc853cc10f3b82067dee77c4ecf perf(web): keep Markdown mounted during streaming (#9677) 5 2 apps/web/src/components/chat/MessagesTimeline.tsx;pnpm-lock.yaml
483b6be3ef4daa848e10c095d8a088064ac836be7f perf(mobile): bound diff syntax highlighting work (#9673) 2 0
49082cab224624eb3a6cd494df3719c59014fb0c99 fix(web): show machine icons in the environment picker (#9668) 1 1 apps/web/src/components/CommandPalette.tsx
50cccd7e3c885065e925f559c5708378cdb3b51eb3 perf(web): speed up terminal snapshots (#9663) 3 0
518ac5462920c45cdee63af15b2598909736f2ec84 perf(server): stop loading message bodies for thread summaries (#9662) 7 4 apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/persistence/Services/ProjectionPendingApprovals.ts;apps/server/src/persistence/Services/ProjectionThreadMessages.ts
52560afffdea82000d757c98ea79678aee75f8648c fix(server): update Claude Agent SDK to 0.3.260 (#9135) 5 4 apps/server/package.json;apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;pnpm-lock.yaml
53caa8a0db98f9d32e98a1645caa7f7dd37b14f187 fix(desktop): quit immediately on a second shortcut press (#9657) 5 1 apps/web/src/components/settings/SettingsPanels.tsx
5401f3e50eca5102ccd881de6f942a98fe6a518ad4 fix(server): unblock OpenCode approvals and stop (#9653) 17 12 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;apps/web/src/session-logic.test.ts;apps/web/src/session-logic.ts;packages/client-runtime/src/work-log/presentation.test.ts;packages/client-runtime/src/work-log/presentation.ts
55f0347322441f3b8e473a8d13ea7006cbcb4fb761 feat(web): show which sidebar threads hold an unsent draft (#9658) 4 2 apps/web/src/components/Sidebar.tsx;apps/web/src/composerDraftStore.ts
56d5b94100863057fb4629f9ad4a35753d16917924 feat(mobile): paste the phone clipboard into the terminal (#9199) 8 1 apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx
57d487dfbf46be344e818725be70ee04be2436bfb4 fix(web): resume Antigravity threads without repeated sign-in (#9647) 5 2 apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts
58eb77683e5544e071db74831bae052bbd8a7d5f88 fix(server): prevent duplicate desktop clients after restart 8 1 apps/server/src/server.test.ts
5914bf3f6d1644a37029be58429e8f0138e1ceb743 fix(web): toggle a single stashed prompt with Cmd+S (#9644) 3 2 apps/web/src/components/chat/ChatComposer.tsx;docs/user/composer.md
6009d13de4381925fa2a6dea74eff8185fa301e905 feat(mobile): make chat text selectable on Android (#8779) 10 1 pnpm-lock.yaml
615f878d2a85807618a4c8571cdef5daa3124672d6 fix(web,mobile): fold context compaction under settled turn folds (#9623) 2 2 apps/mobile/src/lib/threadActivity.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts
62ec3ec6f0b4e005c47aff07d4d9e31506241bce3a fix(web): mute sidebar branch name to match worktree icon (#9622) 1 1 apps/web/src/components/Sidebar.tsx
632152d44de2db30a6bae965b0afd30be080e5c872 fix(server): load OpenCode workspace skills via SDK to avoid 64KB CLI pipe truncation (#9585) 1 1 apps/server/src/provider/Drivers/OpenCodeDriver.ts
64706231535ceac8618712913dfdc4a058c2ffb0d8 fix(web): match composer pull request state icons (#9375) 4 3 apps/web/src/components/BranchToolbarBranchSelector.tsx;apps/web/src/components/LegacySidebar.tsx;apps/web/src/components/ThreadStatusIndicators.tsx
6593c3ab4ffe408a3e06228a33efc8a0745da91178 fix(web): snooze menu no longer overlaps thread details (#9601) 1 1 apps/web/src/components/Sidebar.tsx
664cc800c7593db13726171918572afe3502c43ba6 fix(web): keep command palette above composer menus (#9613) 1 1 apps/web/src/components/chat/ChatComposer.tsx
6799e3b721c5255ded20b00ba1798f848bfc0f1f65 fix(connect): diagnose incomplete headless server setup (#9602) 11 1 apps/server/src/server.ts
68c3b8825bf476cbce5e061c0f99570cf1f6723b89 fix: preserve tool icons on failed calls (#9606) 5 3 apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;apps/web/src/index.css
6961a91b6ef1bd45424169c6650362b358d49bbe34 fix(web): group image views like other tool calls (#9597) 17 14 apps/server/src/provider/CodexDeveloperInstructions.ts;apps/server/src/provider/Layers/AntigravityAdapter.test.ts;apps/server/src/provider/Layers/AntigravityAdapter.ts;apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CursorAdapter.test.ts;apps/server/src/provider/Layers/CursorAdapter.ts;apps/server/src/provider/Layers/GrokAdapter.test.ts;apps/server/src/provider/Layers/GrokAdapter.ts;apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;docs/internals/providers.md
7000f8b7c28056188e3c5630160806a0afe51c9010 fix: show idle subagent batches without completion marks (#9616) 11 6 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/server/src/provider/Layers/AntigravityAdapter.test.ts;apps/server/src/provider/Layers/AntigravityAdapter.ts;apps/web/src/components/chat/MessagesTimeline.tsx;packages/client-runtime/src/state/subagentRuntime.ts
71f1e90e388b86fe4b007a55c0e685a1fa878115e6 refactor(web): move usage provider controls to settings (#9599) 10 2 apps/web/src/components/settings/settingsSearch.test.ts;apps/web/src/components/settings/settingsSearch.ts
72caab2fdbac041ac2e851ad4fa3ac4a40a1d4a8f6 fix(web): render draft PRs in gray (#9537) 30 4 apps/web/src/components/Sidebar.tsx;apps/web/src/components/ThreadStatusIndicators.tsx;apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/components/pullRequest/pullRequestPresentation.tsx
735cc369b7eb882dbea8d5ad21ba688c73a0058748 fix(pull-requests): refresh data after thread turns (#9496) 15 9 apps/server/integration/OrchestrationEngineHarness.integration.ts;apps/server/src/auth/RpcAuthorization.ts;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.ts;apps/server/src/ws.ts;apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/routes/_chat.pull-requests.tsx;packages/client-runtime/src/rpc/client.ts;packages/contracts/src/rpc.ts
74f559fe0ba6fb5950bd14a2404f10b9c94b33f696 fix(web): show context meter in compact composer (#9430) 1 1 apps/web/src/components/chat/ChatComposer.tsx
7539abb9d1d6ae6501c573b9dc0cb9c28e2f75659c fix(connect): refresh authorization without disconnecting (#9582) 2 0
7657832803eed4c87c462de92892777a0934019721 fix(desktop): restore panel titlebar interactions (#9591) 2 2 apps/web/src/components/ChatView.tsx;apps/web/src/components/RightPanelTabs.tsx
7707891e9569c88457516b44c08c820471762969e8 fix(web): bound disconnected send toasts (#9592) 1 1 apps/web/src/components/ChatView.tsx
78b34ff8f56469afa8f3f85d89894e1b4cf49b5213 fix(usage): deduplicate CLI proxy subscription accounts (#9584) 5 0
7909b81a34954c990f70257ae05efbb602c90aac97 fix(mobile): render workspace images in markdown file previews (#8769) 4 1 apps/mobile/src/features/threads/ThreadFeed.tsx
802675e3c70327719a99af4ae6e53e7b74fb8a9be0 fix(antigravity): keep subagent batches active after launch (#9579) 7 4 apps/server/src/provider/Layers/AntigravityAdapter.test.ts;apps/server/src/provider/Layers/AntigravityAdapter.ts;apps/server/src/provider/acp/AntigravityProtocol.ts;packages/client-runtime/src/state/subagentRuntime.ts
81bf40fa786c521b552eb554bbd4f2c75c4123cd03 fix(web): align the sidebar wordmark by baseline (#9578) 1 0
820cb02abf5b3af2985d9dd23a637a63388e98fd49 fix: better shell syntax handling for labels (#9371) 6 4 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/web/src/session-logic.test.ts;apps/web/src/session-logic.ts
8342bdea1c9c1d4b7c5c2e77cd23cf53fae68a6fd4 fix(web): stabilize right panel transitions (#9554) 2 2 apps/web/src/components/ChatView.tsx;apps/web/src/components/preview/PreviewPanelShell.tsx
8495390ed78458f139cb795bab4baa53ec39222ff7 chore: vouch august contributors (#9557) 1 0
852b10398cca3fa74a7c2187c8d9c23ba789333f5b fix(web): render settings sidebar immediately (#9563) 2 2 apps/web/src/components/AppSidebarLayout.tsx;apps/web/src/components/settings/SettingsSidebarNav.tsx
86d7884ce90b9845e6e8aa737dfe02062b91b11c91 fix(web): make settings sidebar sub-section buttons full width (#9562) 1 1 apps/web/src/components/settings/SettingsSidebarNav.tsx
8765f1839ae82af4e67f389f23e4ccc50f51a4a83f fix(web): keep codex restart responses continuous (#9560) 2 2 apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts
88fee2e0ff8168b82ab19f496fcc874c1f3871f522 test(web): fix flaky startup and Tailwind tests (#9558) 2 0
899e1bc36a0843699db54ee28abbdac70584ae8f33 fix(web): keep the last message visible when the resting composer expands (#9553) 5 3 apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;docs/user/composer.md
90710f6dc417ebf303eede3df3605a6938482d83ab fix(web): simplify expanded tool details (#9549) 1 1 apps/web/src/components/chat/MessagesTimeline.tsx
91f96a220b5b154ea44c94bf43929c6362cd511699 ci: add on-demand Windows test workflow (#9538) 3 0
924e547318b60031eb546d8cf2b84ad9fa0785a87a fix(server): find newly opened pull requests after agent turns (#9125) 11 6 apps/server/integration/OrchestrationEngineHarness.integration.ts;apps/server/src/git/GitWorkflowService.ts;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;docs/internals/overview.md
931641b4aba58ab495cad7a9800b173af3416e16dd feat(usage): redeem Codex reset credits from the Limits tab (#9534) 18 9 apps/server/src/auth/RpcAuthorization.ts;apps/server/src/provider/Drivers/CodexDriver.ts;apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts;apps/server/src/provider/ProviderDriver.ts;apps/server/src/server.ts;apps/server/src/ws.ts;packages/client-runtime/src/state/server.ts;packages/contracts/src/rpc.ts
94232de5e8aac61e6f3de4bc61d10746167db9e905 feat(marketing): fresh screenshot and floating marks on the homepage (#9547) 6 2 apps/marketing/public/app-desktop.webp;apps/marketing/src/pages/index.astro
95617edab6539ee6573055dc9fc45fdeacdb12f673 fix(server): reveal normalized paths in File Explorer (#9551) 2 0
963e2c1a66f74f0a45768332c360cd3682f61129e1 fix(web): thread error banner no longer shifts the chat (#9473) 2 1 apps/web/src/components/ChatView.tsx
97f239b77df93077e40c27cc5c5909e94266571859 fix(web): close composer menus when their controls hide (#9541) 6 1 apps/web/src/components/chat/ChatComposer.tsx
983c3e05ccfe34ab7af273f4a4faa72909cafd7a21 fix(web): measure collapsed model labels at their visible width (#9540) 2 0
9954aef6fbe16f637092505b30bd25230c4b0744d8 fix(web): restore composer controls as space becomes available (#9539) 2 0
1005989de44a24888dab02854477ea1d0f50ac3a4a6 fix(mobile): keep store screenshots free of system banners and show dictation (#9548) 2 0
101c5ba51d629b3813182cf3e161cc3f23b1e541dc3 feat(providers): add context compaction command (#9293) 53 42 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx;apps/mobile/src/features/threads/ThreadComposer.tsx;apps/mobile/src/features/threads/ThreadDetailScreen.tsx;apps/mobile/src/features/threads/ThreadFeed.tsx;apps/mobile/src/features/threads/ThreadRouteScreen.tsx;apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/mobile/src/state/use-thread-composer-state.ts;apps/mobile/src/state/use-thread-outbox-drain.ts;apps/server/integration/orphanedProviderSessionStartup.integration.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/provider/Layers/CursorProvider.ts;apps/server/src/provider/Layers/GrokProvider.ts;apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts;apps/server/src/provider/Layers/ProviderService.test.ts;apps/server/src/provider/Layers/ProviderService.ts;apps/server/src/provider/Layers/ProviderSessionReaper.test.ts;apps/server/src/provider/Services/ProviderAdapter.ts;apps/server/src/provider/Services/ProviderService.ts;apps/server/src/serverRuntimeStartup.reconcile.test.ts;apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;docs/user/composer.md;packages/contracts/src/providerRuntime.ts
1026f405370c8e552da9dcfdd6922e85789fd918340 fix(dev): keep shared dev reloads and hot updates working (#9543) 11 0
103dddc0bdcb2230147e207efb17df2e49dbe1bdd8c fix(server): include SQLite conditions in persistence errors 2 0
10407c4ab5077fa9c32bf34ab07dd2caa3907878dd9 fix(web): keep automatic project icons consistent (#9535) 5 2 apps/web/src/components/LegacySidebar.tsx;apps/web/src/components/Sidebar.tsx
1050ba06a122bc73f705bfc18875366b4eeb1160992 fix(web): settle the resting composer layout with a pixel of slack (#9482) 3 1 apps/web/src/components/chat/ChatComposer.tsx
106f54ab901fa77f76eeb1e1cdfa103b9ed6f5ee4ca Fix worktree removal timing out on large install trees (#3902) 2 2 apps/server/src/vcs/GitVcsDriverCore.test.ts;apps/server/src/vcs/GitVcsDriverCore.ts
1076319a9714881a1d25549f797c468fabebae92813 fix(desktop): preview CDP sessions no longer hard-crash the app (#9068) 2 0
10875ab5ab3fb6ad35117da754644c404a31b2fed84 fix(codex): accept rate limit errors on thread resume (#8897) 3 0
109b90898077e60290a1eb7bea4224f325fd3bb1595 fix(server): back off relay client restarts after rapid exits (#8788) 2 0
1102b96220f00de09b61327c8e881b7f509f8cc5a79 fix(server): settle branch threads immediately on pull request merge (#9528) 3 2 apps/server/src/orchestration/ThreadSettlementReactor.test.ts;apps/server/src/orchestration/ThreadSettlementReactor.ts
111343db2c328af56fe0fa7672f3af055550832689b feat(web): reorganize settings pages (#9354) 15 5 apps/web/src/components/settings/ProviderInstanceCard.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/SettingsSidebarNav.tsx;apps/web/src/components/settings/settingsSearch.test.ts;apps/web/src/components/settings/settingsSearch.ts
11219d8ab2ae9fc562ee7b216a0d72903fbfafa9572 feat(usage): show Codex and Claude subscription limits on a Limits tab (#9507) 56 21 apps/server/src/environment/ServerEnvironment.ts;apps/server/src/provider/Drivers/ClaudeDriver.ts;apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts;apps/server/src/provider/Layers/ProviderUsageLimitsIngestion.ts;apps/server/src/server.test.ts;apps/server/src/server.ts;apps/server/src/ws.ts;apps/web/src/state/server.ts;apps/web/src/timestampFormat.ts;packages/client-runtime/src/state/server.ts;packages/contracts/src/environment.ts;packages/contracts/src/index.ts;packages/contracts/src/providerRuntime.ts;packages/contracts/src/rpc.ts;packages/contracts/src/server.ts;packages/contracts/src/settings.ts;packages/shared/package.json
1130a0b6be96833adae68b36540c00d347eda278736 fix(web): keep right panel controls clickable (#9517) 2 2 apps/web/src/components/ChatView.tsx;apps/web/src/components/RightPanelTabs.tsx
114d76b24dd15a219666941ab1b4967d8f738adcda0 feat(codex): support async questions (#9512) 26 22 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/server/src/checkpointing/CheckpointDiffQuery.test.ts;apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts;apps/server/src/orchestration/Layers/OrchestrationEngine.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts;apps/server/src/orchestration/decider.ts;apps/server/src/orchestration/projector.ts;apps/server/src/project/ProjectSetupScriptRunner.test.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/provider/Layers/ProviderSessionReaper.test.ts;apps/server/src/server.test.ts;apps/server/src/serverRuntimeStartup.reconcile.test.ts;apps/server/src/serverRuntimeStartup.test.ts;apps/web/src/session-logic.test.ts;apps/web/src/session-logic.ts;docs/internals/providers.md;packages/contracts/src/providerRuntime.ts
115e3723e06b5b154a3e0ede55c02e2cabc5301d3bf chore: drop comment events from Cursor hygiene forwarder (#9527) 1 0
1169d28c21a26aeef198cb064fe466e49cbeabfe09c fix(auth): keep pairing credentials out of access read models (#9523) 11 1 apps/server/src/server.test.ts
11744701efd6790c39dc82cb08104f7295e1780210f chore: forward issue/PR/discussion events to Cursor hygiene (#9518) 1 0
1189c9ae3dc0e94a957d9c4a61bb211caf914828054 fix(server): keep events during thread subscription startup (#9521) 2 2 apps/server/src/server.test.ts;apps/server/src/ws.ts
119c0ebc882b88dbcffd5a712e403b3c3c517ba4ea6 fix(web): return focus to the composer after closing a media preview (#9513) 1 0
120522ebe65a542ace6f22d9ad95f95ef9ade7e87f1 fix(web): keep the composer open while selecting timeline text (#9499) 3 1 apps/web/src/components/chat/ChatComposer.tsx
1216382268323f86e01b2d40206bcf33beccb3fb64a fix(web): let paste expand a resting composer (#9498) 1 1 apps/web/src/components/ChatView.tsx
122f8a14b28ff80a92d8f4df9a9aa53bfacb8195199 feat(antigravity): show subagent calls and results (#9515) 7 5 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/server/src/provider/Layers/AntigravityAdapter.test.ts;apps/server/src/provider/Layers/AntigravityAdapter.ts;apps/server/src/provider/acp/AntigravityProtocol.ts
1233653cb22ffb30bf133fecc30c955941fe267cf1c fix(desktop): address the browser import review left over from the stack (#9516) 9 0
1248ea52c8f2f4361e875d72ab30d72d37cfae4d87f fix(antigravity): update managed runtime to 1.1.1 (#9509) 2 0
125eb334ca57448742139fb8ec38fb397c1e51c45c5 fix(antigravity): handle native sign-in URLs on stderr (#9514) 6 4 apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts;apps/server/src/provider/acp/AcpSessionRuntime.ts;apps/server/src/provider/acp/AntigravityAcpSupport.ts;docs/internals/providers.md
126baf67b6e3accd48d510fd0d39a4e6b978a1eea80 fix(antigravity): keep model choices up to date (#9511) 10 7 apps/server/src/provider/Drivers/AntigravityDriver.ts;apps/server/src/provider/Layers/AntigravityAdapter.test.ts;apps/server/src/provider/Layers/AntigravityAdapter.ts;apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts;apps/server/src/provider/acp/AcpRuntimeModel.ts;apps/server/src/provider/acp/AcpSessionRuntime.ts;docs/internals/providers.md
127f25e4428961982e3935300cfd3c78f7d0fac4ee9 fix(antigravity): allow slow runtime startup during setup (#9510) 7 2 apps/server/src/provider/Drivers/AntigravityDriver.test.ts;apps/server/src/provider/Drivers/AntigravityDriver.ts
128498ab9c399d5e8c3097a286be14d03238e071ac1 feat(desktop): resolve Chromium cookie keys on Linux (#7261) 27 1 .github/workflows/ci.yml
129ff5843410d44796676b06627788d94354849c4d7 feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc and Firefox (#7260) 13 0
13039449e53e31a56103192aa7905e89fc92c977a4a feat(desktop): import browser cookies into a profile (#7255) 27 5 apps/web/src/components/settings/settingsSearch.test.ts;apps/web/src/components/settings/settingsSearch.ts;packages/contracts/src/index.ts;packages/contracts/src/ipc.ts;pnpm-lock.yaml
131e01c153c18a94a2aa33df7115c6cfee1eb09880f fix(antigravity): forward Google sign-in URLs from browser helper (#9425) 9 3 apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts;apps/server/src/provider/acp/AcpSessionRuntime.ts;apps/server/src/provider/acp/AntigravityAcpSupport.ts
132ef4cc6085e24d6309252412c6dab5482ac11a09f fix(mobile): resolve Antigravity provider icon and normalize driver matching (#9495) 2 0
1330aae1e2ad7ab174004f39746eb4c72b905de932d fix(antigravity): discover legacy workspace skills (#9410) 3 0
13480b53730d1e821563e5c348fdf4f5fc7616a49f7 fix(mobile): let back swipe pop from horizontal scroll edges (#9493) 3 1 pnpm-lock.yaml
135409bc4fa6f3bb9869052bdba4244b30606457f20 fix(mobile): keep the machine glyph next to the environment label (#9486) 1 1 apps/mobile/src/features/threads/thread-list-v2-items.tsx
13612e8997e58dbca8f1bd8c63b67d662eb69cf0e0d fix(web): keep agent browser preview visible (#9484) 19 7 apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx;apps/web/src/components/preview/previewMiniPlayerLayout.ts;apps/web/src/rightPanelLayout.ts;packages/contracts/src/settings.ts
137c726c30a148c2add6a3ec7f31f54ae48dc5d2f0c fix(web): keep opencode icon hollow in collapsed composer (#9492) 2 1 apps/web/src/components/chat/ChatComposer.tsx
138678f23a69943e3eef7171f452b44f2931d2ef21f fix(desktop): restore second-press quit fallback (#9485) 2 0
13977138cf33194a303adfbbd69cc93efa69f84245d fix(web): dont collapse composer when interacting with bottom row (#9490) 7 5 apps/web/src/components/BranchToolbar.tsx;apps/web/src/components/BranchToolbarBranchSelector.tsx;apps/web/src/components/BranchToolbarEnvModeSelector.tsx;apps/web/src/components/BranchToolbarEnvironmentSelector.tsx;apps/web/src/components/chat/ChatComposer.tsx
1400869ad648b67a286d7a47af878f1d256d0ff689f fix(web): let the PR reviewer and label search boxes take keystrokes (#9479) 1 0
1414b8b5d9e0177002c84a6f55837670aa0ef816915 fix(desktop): refresh generated annotation styles (#9488) 1 0
142373be93e68bf3d32207471b27e976ac84bff806a fix(web): move workflow approval beside checks (#9465) 1 1 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
14303728361aa7beb9c13da320097450e6fe65aac3e feat(web): let users turn off composer collapse on blur and scroll (#9469) 10 7 apps/desktop/src/settings/DesktopClientSettings.test.ts;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/settingsSearch.ts;docs/user/composer.md;packages/contracts/src/settings.test.ts;packages/contracts/src/settings.ts
144493fbb58870c912b9cb2ba6c2f1dae938877a59a fix(web): reuse pull request list data while loading (#9467) 3 2 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/routes/_chat.pull-requests.tsx
145d2b6f3b9296f682c6158b894ab33d98d0c4bfb2b fix(server): full-access OpenCode threads no longer ask for approvals (#9282) 3 3 apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;docs/internals/providers.md
14636c4e9cf5c0123e33d65f2af9497ee090404b532 fix(server): keep a/ and b/ prefixes in rendered git patches (#9438) 4 3 apps/server/src/vcs/GitVcsDriver.ts;apps/server/src/vcs/GitVcsDriverCore.test.ts;apps/server/src/vcs/GitVcsDriverCore.ts
147de025aa69ffb0ce1a45d30aed25c60454660b62d fix(mobile): show loading and syncing in the working pill (#9466) 4 3 apps/mobile/src/features/threads/ThreadComposer.tsx;apps/mobile/src/features/threads/ThreadDetailScreen.tsx;apps/mobile/src/features/threads/ThreadRouteScreen.tsx
148db8d60f486c5fc1a80d01b591a359f0c87f0868c fix(web): render transparent previews on white (#9463) 1 0
14946e8b1a23ab14fa2c128c6b315955d8eb976d85f fix(web): make right panel tabs easier to scroll (#9461) 1 1 apps/web/src/components/RightPanelTabs.tsx
150d5825e1d2fb1703ced2bbe2661f6a4937dd530bf fix(web): stop clipping the traits chevron on long Codex effort labels (#9433) 1 0
151126afb56fca56c663f4f40acd46f123ab27eee2b fix(web): banner buttons no longer expand the resting composer (#9452) 3 1 apps/web/src/components/chat/ChatComposer.tsx
1528bd544cdfd22aa38ab82bc1adc0b851755a299ef fix(web): keep agent images collapsed (#9460) 1 1 apps/web/src/components/chat/MessagesTimeline.tsx
153c78f05a45e1c2b274d8b0ab2b4d2d88b4767c968 fix(server): reuse pr state when settling threads (#9459) 2 2 apps/server/src/orchestration/ThreadSettlementReactor.test.ts;apps/server/src/orchestration/ThreadSettlementReactor.ts
1544e89d74436a167c01f25a6a5283638843398ea3a fix(web): make project icons the default (#9457) 6 0
155645d58547d282eb2aaf6c48e5907625fc112386a fix(web): prioritize authored pull requests (#9453) 4 1 apps/web/src/routes/_chat.pull-requests.tsx
15621b9dda5afb00a33e228a68d2ccc885bba7285dc fix(web): unify skeleton loading animations on one pulse (#9448) 4 1 apps/web/src/index.css
157d4ba2a1f11498eae9683f6dc95a08b1c17c29765 fix(composer): mute fast icon when collapsed (#9451) 1 0
1582120fbc185737b71f09de020b95224f9e636d1e5 fix(web): avoid duplicate Antigravity install status (#9419) 2 0
159cfddb4201df8941bbfde008919da70fe5ec7552b fix(mobile): skip unsupported shared settings targets (#9381) 6 1 docs/internals/overview.md
16057626eb6eaa436b22260068d23f4e3df5f389cd1 fix(web): prevent loading ssh environments from overriding navigation (#9168) 2 1 apps/web/src/hooks/useHandleNewThread.ts
161098bf5329727fcd7d973bf842e6b4d50d6e7b924 fix(web): preserve explicit preview navigation URLs (#8902) 2 0
16277e35c561259733d880ab62a43aad0894d301d9b fix(web): send cited messages with Cmd+Enter (#9307) 5 1 apps/web/src/components/chat/ChatComposer.tsx
163fff33f9e851912363c5b1f3ac65598be35eb5f0d perf(ci): reuse dependency checks in release builds (#9399) 2 0
1641e051873094c0c75cd35fef89c90461c22cce76b fix(antigravity): refresh the model manifest so older Gemini models fold as legacy (#9397) 5 1 apps/server/src/provider/Drivers/AntigravityDriver.ts
16519c97ea56d30b3a2de31a060f8f47d6b7404b78f fix(web): unlock the composer when preview capture fails (#9127) 8 1 packages/contracts/src/ipc.ts
1662b745efe57cddc1753d3007d081c64bd20e3ab79 fix(usage): price new models without waiting a day for the rate table (#9202) 11 4 apps/server/src/auth/RpcAuthorization.ts;apps/server/src/ws.ts;packages/client-runtime/src/state/server.ts;packages/contracts/src/rpc.ts
1672aa907b1969237efdaced29612fb46ba51be7041 fix(mobile): show an error instead of an endless preview spinner (#9123) 6 0
16806336460c9988f29c71e839c4c9c840c4552e077 feat(providers): add Google Antigravity via the official ACP agent (#9348) 169 83 README.md;apps/mobile/src/features/threads/NewTaskDraftScreen.tsx;apps/mobile/src/features/threads/PendingApprovalCard.tsx;apps/mobile/src/features/threads/PendingUserInputCard.tsx;apps/mobile/src/features/threads/ThreadComposer.tsx;apps/mobile/src/features/threads/ThreadDetailScreen.tsx;apps/mobile/src/lib/modelOptions.ts;apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/mobile/src/state/use-selected-thread-requests.ts;apps/mobile/src/state/use-thread-composer-state.ts;apps/mobile/src/state/use-thread-outbox-drain.test.ts;apps/mobile/src/state/use-thread-outbox-drain.ts;apps/server/integration/OrchestrationEngineHarness.integration.ts;apps/server/integration/orphanedProviderSessionStartup.integration.test.ts;apps/server/package.json;apps/server/scripts/acp-mock-agent.ts;apps/server/src/auth/RpcAuthorization.ts;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/provider/Drivers/AntigravityDriver.test.ts;apps/server/src/provider/Drivers/AntigravityDriver.ts;apps/server/src/provider/Layers/AntigravityAdapter.test.ts;apps/server/src/provider/Layers/AntigravityAdapter.ts;apps/server/src/provider/Layers/ProviderAuthService.test.ts;apps/server/src/provider/Layers/ProviderAuthService.ts;apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts;apps/server/src/provider/Layers/ProviderRegistry.ts;apps/server/src/provider/Layers/ProviderService.test.ts;apps/server/src/provider/Layers/ProviderService.ts;apps/server/src/provider/Layers/ProviderSessionReaper.test.ts;apps/server/src/provider/ProviderDriver.ts;apps/server/src/provider/Services/ProviderAdapter.ts;apps/server/src/provider/Services/ProviderService.ts;apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts;apps/server/src/provider/acp/AcpRuntimeModel.test.ts;apps/server/src/provider/acp/AcpRuntimeModel.ts;apps/server/src/provider/acp/AcpSessionRuntime.ts;apps/server/src/provider/acp/AntigravityAcpSupport.ts;apps/server/src/provider/acp/AntigravityProtocol.ts;apps/server/src/provider/builtInDrivers.ts;apps/server/src/provider/providerInstallation.test.ts;apps/server/src/server.test.ts;apps/server/src/server.ts;apps/server/src/serverRuntimeStartup.reconcile.test.ts;apps/server/src/ws.ts;apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/ComposerPendingApprovalActions.test.tsx;apps/web/src/components/chat/ComposerPendingApprovalActions.tsx;apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx;apps/web/src/components/settings/ProviderInstanceCard.tsx;apps/web/src/components/settings/ProviderSettingsForm.test.ts;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/providerDriverMeta.ts;apps/web/src/components/settings/settingsSearch.test.ts;apps/web/src/components/settings/settingsSearch.ts;apps/web/src/composerDraftStore.ts;apps/web/src/pendingUserInput.test.ts;apps/web/src/pendingUserInput.ts;apps/web/src/session-logic.test.ts;apps/web/src/session-logic.ts;docs/internals/overview.md;docs/internals/providers.md;packages/client-runtime/src/rpc/client.ts;packages/client-runtime/src/state/server.ts;packages/contracts/src/index.ts;packages/contracts/src/model.ts;packages/contracts/src/orchestration.ts;packages/contracts/src/providerRuntime.ts;packages/contracts/src/rpc.ts;packages/contracts/src/server.ts;packages/contracts/src/settings.ts;packages/effect-acp/src/client.ts;packages/effect-acp/src/protocol.test.ts;packages/effect-acp/src/protocol.ts;pnpm-lock.yaml
169652515a349741d234111b85f27597be3265d1ffc fix(web): render assistant images inline in chat (#9126) 4 3 apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.tsx
170044ea8e347d980a215057c325de8335d38f0f9aa fix(web): stop the resting composer layout loop (#9393) 7 3 apps/web/src/components/BranchToolbar.logic.ts;apps/web/src/components/BranchToolbar.tsx;apps/web/src/components/chat/ChatComposer.tsx
17118573d60aab1344bf124a044b01445086e910c4c fix(claude): expand slash commands when a message has attachments (#9122) 2 2 apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts
1725d1b02cdeebff4b0b4f15a1e8425b06ae3cfe8d1 feat(marketing): put named-developer quotes on the landing page (#9385) 10 0
1734b26132d2c740ff344ef3fae7ae62e9765759e29 "fix(web): keep trailing tool groups out of ""Worked for"" accordion (#9384)" 4 4 apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx
1745b8445b7a777ab1070aa97b062b1618971073a96 fix(web): collapse the resting composer (#7855) 33 12 apps/web/src/components/BranchToolbar.logic.test.ts;apps/web/src/components/BranchToolbar.logic.ts;apps/web/src/components/BranchToolbar.tsx;apps/web/src/components/BranchToolbarBranchSelector.tsx;apps/web/src/components/BranchToolbarEnvModeSelector.tsx;apps/web/src/components/BranchToolbarEnvironmentSelector.tsx;apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/ComposerPrimaryActions.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;docs/user/composer.md
17524799de4f85a9071afc2420362784ea46041d544 fix(mobile): size expanded tool groups correctly (#9359) 1 0
1761f7a3c11ca864898b136a35011ad10777ea07544 fix(mobile): pressed and disabled styles no longer apply unconditionally (#9355) 2 1 pnpm-lock.yaml
177cf0bb4c3571badbc5aaa8189cd5cbe3563ec73c1 fix(web): match project icon chooser button sizes (#9368) 1 0
178c89e3e12a41e13e09e0103d08a41d0688a99fb49 fix(web): settled sidebar rows use the project fallback icon (#9366) 1 1 apps/web/src/components/Sidebar.tsx
179829c3db94830fc70b5754513b8a370f7301ca213 fix(environments): draw the machine icon everywhere an environment is named (#9365) 10 5 apps/mobile/src/features/projects/AddProjectScreen.tsx;apps/mobile/src/features/threads/NewTaskDraftScreen.tsx;apps/web/src/components/BranchToolbarEnvironmentSelector.tsx;apps/web/src/components/CommandPalette.tsx;apps/web/src/components/ThreadStatusIndicators.tsx
1805eb4f452ee0fcde7b5bbc93d62de9118b70a33b2 test(web): remove static markup-only component tests (#9364) 6 0
181d4bd8923ad8cab346a854967015b06d9e5cd77f7 feat(web): mod+w closes the active right panel tab before the window (#9363) 7 4 apps/web/src/components/ChatView.tsx;apps/web/src/keybindings.test.ts;apps/web/src/routes/_chat.pull-requests.tsx;packages/contracts/src/keybindings.ts
182b5f4e8137b3cbd657fbe2b698f9db4e321acbc3d feat(web): suggest ssh hosts in a dropdown under the host field (#9171) 9 1 packages/contracts/src/ipc.ts
1839f9359bd8132c425493720080149dcc0d3da9436 fix(web): stop usage summary requests reporting slow RPCs (#9358) 2 0
184854541a04e07b7960698b381bcfd2fda73eb276c fix(pull-requests): shared state + not settling? (#9332) 9 5 apps/server/src/orchestration/ThreadSettlementReactor.test.ts;apps/server/src/orchestration/ThreadSettlementReactor.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/ThreadStatusIndicators.tsx;apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
18548ba76bc26337f23d1f0991bbac51f996a3333dc fix(web): collapse oldest pull request comments (#9323) 1 0
1866cf0c6ea55d281f65c80502ec1871b0adf472025 feat: display native app and browser icons in work logs (#9093) 32 21 apps/mobile/src/features/threads/ThreadFeed.tsx;apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/server.test.ts;apps/server/src/server.ts;apps/server/src/ws.ts;apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.tsx;apps/web/src/session-logic.test.ts;apps/web/src/session-logic.ts;packages/client-runtime/package.json;packages/client-runtime/src/work-log/presentation.test.ts;packages/client-runtime/src/work-log/presentation.ts;packages/contracts/src/assets.ts;packages/contracts/src/providerRuntime.ts;packages/shared/package.json
1870bc59bbae391ee45ab3a7cfb2dc703c8b27e5772 fix(web): let the pull request list use wide screens (#9351) 2 1 apps/web/src/routes/_chat.pull-requests.tsx
1881575ada305b9686b9771df2dca304f391e85a76e fix(mobile): stop indented code overflowing Android chat bubbles (#9347) 2 0
189f6c04c552c203350705f9ab1e47773ea736af245 feat(web): add customizable project icons (#9137) 34 16 apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts;apps/server/src/orchestration/decider.ts;apps/server/src/orchestration/projector.ts;apps/server/src/persistence/Migrations.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/CommandPalette.tsx;apps/web/src/components/LegacySidebar.tsx;apps/web/src/components/Sidebar.tsx;apps/web/src/components/chat/ChatHeader.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/routes/_chat.pull-requests.tsx;packages/contracts/src/orchestration.test.ts;packages/contracts/src/orchestration.ts
1906a5a18cb1bc9ea878afa1146950439b5d4744110 fix(web): add press feedback to buttons (#9349) 1 0
19118062da9425909a0a92bce0b692c9de6fbba56ee feat(web): choose whether links open in the default browser or in T3 Code (#9339) 24 8 apps/desktop/src/settings/DesktopClientSettings.test.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/GitActionsControl.tsx;apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/settingsSearch.test.ts;apps/web/src/components/settings/settingsSearch.ts;packages/contracts/src/settings.ts
192d42254dfb462e4ab23a42d4bef53b1ce00a72396 fix(web): resolve Vite sourcemap and supports warnings (#9343) 3 1 apps/web/src/components/chat/ComposerBanner.tsx
1932a3cfe456375fd34b906f849b04706109dc74170 fix(web): collapse PR header actions to icons when narrow (#9334) 1 1 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx
1945f84efa1ec2fb3cd6f6c54545cc34bb77ac1ddb8 feat(web): add PageUp/PageDown chat navigation (#9315) 5 2 apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx
1951aa44a071f66bdfd9430356ab824b5a6985fb459 feat(web): add a file tree to the diff panel and pull request code tab (#9330) 8 1 apps/web/src/components/DiffPanel.tsx
196f5fbb1bcb0db378c61addd5b49ef2d95bd888168 chore: upgrade vite-plus to 0.3.0 (#9327) 31 8 apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx;apps/mobile/src/features/threads/threadListV2.ts;apps/server/src/mcp/McpHttpServer.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/ProviderService.test.ts;pnpm-lock.yaml
1973b2de9da1c8763602e283e8d14b41b5d57a9d0c7 chore: dedupe lightningcss and tailwind node bindings (#9331) 3 2 apps/mobile/generated-uniwind-themes.css;pnpm-lock.yaml
1989409dd20a9fbce491d49d09c79b289d8fb8bfe3e fix(web): make the diff layout toggle a persisted setting (#9326) 9 7 apps/desktop/src/settings/DesktopClientSettings.test.ts;apps/web/src/components/DiffPanel.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/settingsSearch.ts;apps/web/src/diffPanelStore.test.ts;apps/web/src/diffPanelStore.ts;packages/contracts/src/settings.ts
19912f1fc427efad4b683835a72bda6f74bebb5d641 fix(web): line up the titlebar wordmark label and version pill (#9255) 2 0
20066419a1d1b80110c9702edae641bddcc2888fc98 fix(dev): share dev servers on the loopback Vite actually binds (#9324) 2 0
20185f2479ffe5c9d8ffa91f1bfae5234df4061292f refactor(mobile): style plain views with Uniwind classes instead of the theme bridge (#9322) 5 2 apps/mobile/src/features/threads/thread-list-v2-items.tsx;vite.config.ts
202b9b1b8fdddf9d006fdb820af770063e1f968345b chore(ci): narrow the Effect conventions check-run agent (#9321) 1 0
203194f838e7636f97739c440c7855aed195dbd7f52 chore: audit lint directives and move plugin allowlists into config (#9300) 21 3 apps/server/src/relay/AgentAwarenessRelay.test.ts;apps/server/src/server.test.ts;vite.config.ts
204ec44bc56f598be16ffbc22ed0e4e095447043844 fix(chat): keep live tool labels in present tense (#9316) 8 7 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.test.tsx;packages/client-runtime/src/work-log/presentation.test.ts;packages/client-runtime/src/work-log/presentation.ts
205922bd692251bc803c12a3fab159efe83c957bb70 refactor(media): unify file and media previews across clients (#9253) 52 11 apps/mobile/src/features/threads/ThreadFeed.tsx;apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;docs/user/composer.md;packages/client-runtime/package.json;packages/client-runtime/src/work-log/presentation.test.ts;packages/client-runtime/src/work-log/presentation.ts
20631eeb443305a4e11c8b20a7c8ff2b3f5e841eb0f fix(sidebar): collapse settled and snoozed shelves by default (#9314) 4 1 apps/web/src/components/Sidebar.tsx
2079ebbeda5a03fd8fce7be147b65ea396e50721c68 feat(web): apply and remove labels from the pull request tab (#9313) 19 3 apps/server/src/auth/RpcAuthorization.ts;apps/server/src/ws.ts;packages/contracts/src/rpc.ts
2080fbb94248581aaefda36e4f8a40ec2c6455c779a feat(environments): draw each environment as the machine it runs on (#9299) 32 17 apps/mobile/src/features/home/HomeScreen.tsx;apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx;apps/mobile/src/features/threads/thread-list-v2-items.tsx;apps/server/src/environment/ServerEnvironment.ts;apps/web/src/components/BranchToolbar.logic.ts;apps/web/src/components/BranchToolbar.tsx;apps/web/src/components/ChatView.tsx;apps/web/src/components/LegacySidebar.tsx;apps/web/src/components/Sidebar.tsx;apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/components/settings/settingsSearch.ts;apps/web/src/routes/_chat.pull-requests.tsx;packages/contracts/src/baseSchemas.ts;packages/contracts/src/environment.ts;packages/contracts/src/server.ts;packages/contracts/src/settings.test.ts;packages/contracts/src/settings.ts
209d897641d738c67dd7c12cbb3a273b43fe17a5eb1 fix(pull-requests): keep cached PR chrome on reopen (#9294) 5 3 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts;apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
210355fbd96d5e90b52961ac5f1e035112fc3bad4a9 fix(web): stop remounting markdown on every activity delta (#9306) 1 1 apps/web/src/components/chat/MessagesTimeline.tsx
211ef6cc0b362019f32152470ed96c1aa1b813fb7f6 chore(ci): only run check-run agents on vouched contributors (#9298) 2 0
212e94603adfac7d8734f9af01e33f67e776daf482d chore(ci): narrow the UI consistency check-run agent (#9297) 1 0
21377512998485718eb1b6c336e20f196eb40a6a32f feat(web): preview document attachments in the file viewer (#9292) 16 11 apps/server/src/http.test.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/RightPanelTabs.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx;apps/web/src/components/files/FilePreviewPanel.tsx;apps/web/src/rightPanelStore.test.ts;apps/web/src/rightPanelStore.ts;apps/web/src/types.ts;docs/user/composer.md;packages/contracts/src/assets.ts
214dbc7bfa3f36cc61a3f1a2015e47d71ae9dee5421 fix(opencode): show Reasoning selector for OpenCode models (#9287) 3 0
2152971ec3209d7ef1b00d7ded70fe6342816b1539f fix(server): preserve automatic settlement timestamps (#9254) 22 15 apps/mobile/src/features/threads/thread-list-v2-items.tsx;apps/mobile/src/features/threads/threadListV2.test.ts;apps/mobile/src/features/threads/threadListV2.ts;apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;apps/server/src/orchestration/ThreadSettlementReactor.test.ts;apps/server/src/orchestration/ThreadSettlementReactor.ts;apps/server/src/orchestration/decider.ts;apps/server/src/persistence/Migrations.ts;apps/web/src/components/Sidebar.logic.ts;apps/web/src/components/Sidebar.tsx;docs/internals/overview.md;packages/client-runtime/src/state/threadSort.test.ts;packages/client-runtime/src/state/threadSort.ts;packages/contracts/src/orchestration.ts
21615fea6c5f40331c8325f33bd08b2dcbf924e935a fix(providers): discover workspace skills everywhere (#9180) 15 10 apps/mobile/src/features/threads/NewTaskDraftScreen.tsx;apps/mobile/src/features/threads/ThreadComposer.tsx;apps/mobile/src/features/threads/ThreadDetailScreen.tsx;apps/server/src/provider/Drivers/CursorDriver.ts;apps/server/src/provider/Drivers/GrokDriver.ts;apps/server/src/provider/Layers/CursorAdapter.test.ts;apps/server/src/provider/Layers/CursorAdapter.ts;apps/server/src/provider/Layers/CursorProvider.test.ts;apps/server/src/provider/Layers/GrokProvider.ts;apps/web/src/components/chat/ChatComposer.tsx
217443b4ebfe83fcfe64c34b09ecb5a5fffdebb85c7 fix(pull-requests): missing features & better behaviour (#9188) 34 5 apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts;apps/web/src/components/pullRequest/pullRequestDetail.logic.ts;apps/web/src/components/pullRequest/pullRequestPresentation.tsx;apps/web/src/routes/_chat.pull-requests.tsx
2184ba39a6f408bdee468df2e3ffdf7d5dc08e7b59d fix(desktop): detect installed Spectre libs for Windows builds 1 0
219994bd7373cf3a335c204a617604e690ed4c00cba fix(cursor): honor auto and full access modes (#9283) 5 4 apps/server/src/provider/Layers/CursorAdapter.test.ts;apps/server/src/provider/Layers/CursorAdapter.ts;apps/server/src/provider/acp/CursorAcpSupport.test.ts;apps/server/src/provider/acp/CursorAcpSupport.ts
220c742edd46c5b6792ec8647f934a4703f9103aa82 fix(web): show scroll-to-end as soon as the last message slips under the composer (#9280) 4 4 apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.test.tsx;apps/web/src/components/chat/MessagesTimeline.tsx
22163f334baf3432c4404ddfc9e71d33bf16575f0ed "Revert ""feat(providers): add context compaction across harnesses"" (#9284)" 35 27 apps/server/integration/orphanedProviderSessionStartup.integration.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/provider/Layers/CursorProvider.ts;apps/server/src/provider/Layers/GrokProvider.ts;apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts;apps/server/src/provider/Layers/ProviderService.test.ts;apps/server/src/provider/Layers/ProviderService.ts;apps/server/src/provider/Layers/ProviderSessionReaper.test.ts;apps/server/src/provider/Services/ProviderAdapter.ts;apps/server/src/provider/Services/ProviderService.ts;apps/server/src/serverRuntimeStartup.reconcile.test.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;docs/user/composer.md;packages/contracts/src/providerRuntime.ts
222064392ffc7fc58fcc3018fd2ba2df33fbb21f1de fix(web): offer browser profiles from the empty-panel launcher (#9279) 1 1 apps/web/src/components/RightPanelTabs.tsx
223b59b7d0af9536a2d61ddfcf8d33420b890b2faca fix(web): unify control sizing across settings pages (#9281) 20 4 apps/web/src/components/BranchToolbarBranchSelector.tsx;apps/web/src/components/settings/AddProviderInstanceDialog.tsx;apps/web/src/components/settings/ProviderInstanceCard.tsx;apps/web/src/components/settings/SettingsPanels.tsx
224fb93902ee24d4ba380508df33d25478d3baf7a72 feat(web): add proactive panels (#9276) 9 8 apps/desktop/src/settings/DesktopClientSettings.test.ts;apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/settingsSearch.ts;packages/contracts/src/settings.test.ts;packages/contracts/src/settings.ts
225535557b3f785629e6f48cad40c5a4b78b3b3c5d6 feat(providers): add context compaction across harnesses (#8808) 35 27 apps/server/integration/orphanedProviderSessionStartup.integration.test.ts;apps/server/src/orchestration/Layers/CheckpointReactor.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts;apps/server/src/orchestration/Layers/ProviderCommandReactor.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts;apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts;apps/server/src/provider/Layers/ClaudeAdapter.test.ts;apps/server/src/provider/Layers/ClaudeAdapter.ts;apps/server/src/provider/Layers/CodexAdapter.test.ts;apps/server/src/provider/Layers/CodexAdapter.ts;apps/server/src/provider/Layers/CursorProvider.ts;apps/server/src/provider/Layers/GrokProvider.ts;apps/server/src/provider/Layers/OpenCodeAdapter.test.ts;apps/server/src/provider/Layers/OpenCodeAdapter.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts;apps/server/src/provider/Layers/ProviderService.test.ts;apps/server/src/provider/Layers/ProviderService.ts;apps/server/src/provider/Layers/ProviderSessionReaper.test.ts;apps/server/src/provider/Services/ProviderAdapter.ts;apps/server/src/provider/Services/ProviderService.ts;apps/server/src/serverRuntimeStartup.reconcile.test.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/MessagesTimeline.test.tsx;docs/user/composer.md;packages/contracts/src/providerRuntime.ts
2261eb36b45ef7aa5c028380afb7266bcabf258a55e fix(web): show pull request state icons in tabs (#9112) 7 6 apps/web/src/components/ChatView.tsx;apps/web/src/components/RightPanelTabs.tsx;apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx;apps/web/src/rightPanelStore.test.ts;apps/web/src/rightPanelStore.ts;apps/web/src/routes/_chat.pull-requests.tsx
227ba3cb0773859334fe9f75016295bb3893c9e6044 feat(projects): automatically pull clean default branches (#9277) 20 11 apps/server/integration/orphanedProviderSessionStartup.integration.test.ts;apps/server/src/orchestration/Layers/ProjectionPipeline.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts;apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts;apps/server/src/orchestration/decider.ts;apps/server/src/orchestration/projector.ts;apps/server/src/persistence/Migrations.ts;apps/server/src/server.ts;apps/server/src/serverRuntimeStartup.test.ts;apps/server/src/serverRuntimeStartup.ts;packages/contracts/src/orchestration.ts
22891c8d4771ccb503a9dde65190b87db642df0a6ea feat(web): add opt-in panel animations (#8830) 20 13 apps/desktop/src/settings/DesktopClientSettings.test.ts;apps/web/src/components/AppSidebarLayout.tsx;apps/web/src/components/ChatView.tsx;apps/web/src/components/RightPanelTabs.tsx;apps/web/src/components/chat/ChatComposer.tsx;apps/web/src/components/chat/ChatHeader.tsx;apps/web/src/components/preview/PreviewPanelShell.tsx;apps/web/src/components/settings/SettingsPanels.tsx;apps/web/src/components/settings/settingsSearch.test.ts;apps/web/src/components/settings/settingsSearch.ts;apps/web/src/routes/_chat.pull-requests.tsx;packages/contracts/src/settings.test.ts;packages/contracts/src/settings.ts
229ca63d42d670837b918081d1fc1ebada553814b4c refactor(shared): move the node:sqlite Effect SQL client into shared (#7272) 22 1 packages/shared/package.json
230134d51096ea0d00a53a499e8f0c87e31fafb0006 feat(desktop): browser profiles for the preview browser (#7254) 40 9 apps/desktop/src/settings/DesktopClientSettings.test.ts;apps/web/src/components/ChatView.tsx;apps/web/src/components/RightPanelTabs.tsx;apps/web/src/components/preview/addBrowserSurface.test.ts;apps/web/src/components/settings/settingsSearch.ts;apps/web/src/routes/_chat.pull-requests.tsx;packages/contracts/src/index.ts;packages/contracts/src/ipc.ts;packages/contracts/src/settings.ts
23128ddaf75917140e5e4355d4386bc5d14d9dad7b6 fix(web): confirm closing agent-controlled browsers (#9272) 3 3 apps/web/src/components/ChatView.logic.test.ts;apps/web/src/components/ChatView.logic.ts;apps/web/src/components/ChatView.tsx
2325a9b56291f82b9269346053594d8b5dfca736976 fix(web): warn when shared settings have no target environment (#9207) 1 0
23346b5c66406b9942589d7e9132beeafda2434f113 fix(chat): show single tool calls without summaries (#9267) 5 5 apps/mobile/src/lib/threadActivity.test.ts;apps/mobile/src/lib/threadActivity.ts;apps/web/src/components/chat/MessagesTimeline.logic.test.ts;apps/web/src/components/chat/MessagesTimeline.logic.ts;apps/web/src/components/chat/MessagesTimeline.tsx
23462c68dc41f9d9d7ecaf5193a6a8d997ebf671a61 fix(mobile): show filled filter icon on Android when filters are active (#9217) 1 0
235f90e2f2bd26e22b77ccf781cccdf95afd3c3ac1c fix(server): subscribe before provider settings watcher (#9271) 2 2 apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts;apps/server/src/provider/Layers/ProviderRegistry.test.ts
236b57726ca842624b713572529109ac49b17c93fb3 feat(web): add copy path button to diff headers (#2403) 6 1 apps/web/src/components/DiffPanel.tsx
2378d5b712de3cbd84118327808c403756e8894014a fix(desktop): exclude opposite macOS pty prebuilds (#9240) 1 0
2389159b808d35a88e74fc91e11070f3270cdb321f9 feat(mobile): long-press file references for path and open actions (#9258) 17 2 apps/mobile/src/features/threads/ThreadFeed.tsx;docs/user/composer.md
2392a7a449cca3c7760b689a54b3aa02476a206dbc9 fix(web): hide deleted providers with prototype keys (#8337) 2 2 apps/web/src/providerInstances.test.ts;apps/web/src/providerInstances.ts
240aab4049646ffa8b9bcff92562992e6c3a8554bf0 fix(ci): keep Expo Sharing patch applied (#9248) 5 1 pnpm-lock.yaml
241b5a09e13fa464a8681e0bf9df404dd3c0afe8ac8 fix(release): pin patched expo-sharing version (#9250) 2 1 pnpm-lock.yaml
audits/orchestrator-v2/2026-09-04/main-late-arrivals.txt+7−0
@@ -0,0 +1,7 @@
1f6db4206258b0ef30e8dd8949627acfd209bf338 fix(web): reset automatic pull to default (#9763)
245bd3b631bf1e84520c90ebdfdd1b135871ced14 fix(sidebar): mute background working threads (#9759)
3d115a96763b76d00f06b06272688cf68eebe8206 fix(web): stop empty diffs replacing pull requests (#9753)
4bc03c3640d6d3bb44e5fb477bfd78d7484cd0e00 fix(models): make GPT-6-Astra current (#9762)
515eda897d37aad232fca5089f5eb6ef7b726c31c perf(web): defer image URL requests for thread history (#9760)
66365919f2e5bcfb4fa4020b95e19af26ae40979f perf(server): skip history reads for metadata commands (#9758)
777b655c47b82c9b2aad09e447117b7618a16e21d perf(mobile): defer file preview highlighter startup (#9752)
audits/orchestrator-v2/2026-09-04/main-only-commits.txt+8−0
@@ -0,0 +1,8 @@
1d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70 perf(client): stop thread streams when unused (#9740)
20de956ed2f92b8a6ead56e1566801db232d365dc fix(web): refine server update notice (#9744)
3a76b898b3aa4fd4081104b06a3b2c942bd4fecb7 feat(web): link pull request authors to profiles (#9627)
4cf9729d5ee9660c08556e823080d3bb19648ed28 perf(server): bound terminal history by bytes (#9748)
5fec606f9ae524277ef1f6886e614f32d9e15e36e perf(web): avoid repeated terminal metadata scans (#9747)
6d6e29dc9dee943b34d6b0d11441fa944c7bff7c9 perf(mobile): bound the parsed review cache (#9749)
7cbe93e8dfba68ff6fbe8c69e43a633fdc79db62d fix(web): show project settings for new threads (#9743)
8cfc9bf34156ddcc4a98b7f5c67193adb5aedce06 fix(web): fold single trailing activity (#9739)
audits/orchestrator-v2/2026-09-04/main-since-prior.txt+240−0
@@ -0,0 +1,240 @@
1d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70 perf(client): stop thread streams when unused (#9740)
20de956ed2f92b8a6ead56e1566801db232d365dc fix(web): refine server update notice (#9744)
3a76b898b3aa4fd4081104b06a3b2c942bd4fecb7 feat(web): link pull request authors to profiles (#9627)
4cf9729d5ee9660c08556e823080d3bb19648ed28 perf(server): bound terminal history by bytes (#9748)
5fec606f9ae524277ef1f6886e614f32d9e15e36e perf(web): avoid repeated terminal metadata scans (#9747)
6d6e29dc9dee943b34d6b0d11441fa944c7bff7c9 perf(mobile): bound the parsed review cache (#9749)
7cbe93e8dfba68ff6fbe8c69e43a633fdc79db62d fix(web): show project settings for new threads (#9743)
8cfc9bf34156ddcc4a98b7f5c67193adb5aedce06 fix(web): fold single trailing activity (#9739)
9c8f77e0d441264efb0acfac312e852c81ae3da83 perf(server): stop caching unused OpenCode tool parts (#9738)
10088cc3f95599b7b933289d719fa1e9b0608cb4cd fix(relay): bound stalled push requests (#9734)
1119c1710a88a2c87c159d76269dacdf0b17ddd9f4 perf(web): reuse timeline rows while text streams (#9725)
12c7bf3115f2223ede5cb2316613dc7369155dbeb1 feat(web): unpin threads from the sidebar multi-select menu (#9651)
137d5dc66c151a9ca7bfef45cffc9d3516842c5566 fix(web): mute composer helper text (#9654)
1450bfca43d76ced00f5d67cfbab8bc44c50eb0e53 perf(server): replay only the selected thread (#9726)
15c4353bc6b972fa527579b530f01cc8744b2407d2 fix(mobile): read file-backed image drafts before enabling them (#9713)
165eab021a5185e492b6b021f83564143d5263e8a6 perf(web): stop rendering hidden terminals (#9718)
179eb4d71681dc7d002082db2f8b4bacf7614412f4 fix(mobile): remove provider setup (#9721)
18120fab18d84f1eb993ecc7fa858a8dd212747079 fix(web): keep the slash menu above the composer when vertical space is short (#9625)
198357eef14cbd5ed063e4a64404c33d1bfbc78446 fix(web): match provider settings layout for disconnected devices (#9619)
208ccb933a8aae461b616b199ab287164c3311a755 test(server): allow either valid file-search match (#9720)
21108f295cc3672716c3cb8291ce487846df5ce098 fix(server): bound slow-client event buffers (#9715)
22d536b0580d044967dc644498d8c5a6e96464f767 fix(server): settle inactive threads with open PRs (#9610)
23c66f15f39e61fda0a9a100578833ec12e7526859 perf(client): reduce thread-list update work (#9716)
2495103905f5f045523994213bf76bfb14acc31b27 feat(web): preview pull request links (#9631)
25da7e46d08e85bcb07ecd78721a40f7b612fac2f2 perf(web): stop replaying terminal buffers on rollover (#9707)
267839140e5e93d3f401d7eb45b86cf1a234eb3609 fix(mobile): preserve saved work after storage read failures (#9710)
27c7c1dfe4df99edf65a49d8a31b39ef1361f37f44 perf(web): stop continuous chat status animations (#9709)
28c75299ee2085a121bceb6df76796e971fe92b5b6 perf(relay): avoid repeated activity decoding (#9708)
29dffb4cd3b16dc6f41aced99922950ee3083082c6 perf(server): use one query for buffered provider events (#9706)
303bbbc1d9fd8b3d649c60ba0137c7dae93a6aab3f perf(server): stop rebuilding terminal history per chunk (#9703)
31010d6bb1b5281cb1e6eacf1ee0f2500973242836 perf(marketing): serve website fonts locally (#9701)
32b3e1d88590489da2bb63b95c18a57e6f400b8b7f perf(web): defer diff workers until a code view opens (#9692)
33c163d502dd32b993c03d8c20a5fae55b159bd8dc perf(server): avoid full patches for checkpoint summaries (#9694)
344ee2a9d046a0986451c703c9221bb32ba53540bb perf(marketing): stop continuous homepage motion (#9697)
351587f248dd81ed45e214d476451ebf16dbfadb1a feat(server): measure provider turn token usage (#9132)
36dab5f6e6e02e78675655e69503aa89654e5b8050 perf(web): defer composer draft serialization (#9695)
37777f5bb2e11fe30e7fdcb5741b0b1d9bb20924d6 perf(mobile): reuse diff rows during comment edits (#9693)
38246064993535e5d90107d3d7784ceef3cc883435 perf(clients): avoid waiting to read cached relay tokens (#9691)
39ec8b2119c377f5c1dbe6235b221ef98eca31a96e perf(server): omit repeated OpenCode progress logs (#9689)
4044dc8ae259f5c3349f7ab8045e39bd62408b52a9 perf(mobile): reuse chat feed rows during streaming (#9688)
41f2e3764c257a7e27c8171d7dd1e38d4383074206 perf(server): stop retaining unused OpenCode tool history (#9684)
422263e13fda8c9a4f1b6f4dee32e3c9020195e2aa perf(server): batch projector cursor writes (#9671)
4327e6cc27fe0f3cff53a44905e40615b7db99c80c perf(server): cache and stream static web assets (#9669)
448e3aa324b57dd645980b203d5f8a5b9f9dc53a84 perf(marketing): serve images at their display size (#9682)
457cf5b284e6e37895f535433c77f728b3e4292c9b perf(mobile): skip unused legacy list work (#9679)
46887ece307131bdc853cc10f3b82067dee77c4ecf perf(web): keep Markdown mounted during streaming (#9677)
473b6be3ef4daa848e10c095d8a088064ac836be7f perf(mobile): bound diff syntax highlighting work (#9673)
48082cab224624eb3a6cd494df3719c59014fb0c99 fix(web): show machine icons in the environment picker (#9668)
49cccd7e3c885065e925f559c5708378cdb3b51eb3 perf(web): speed up terminal snapshots (#9663)
508ac5462920c45cdee63af15b2598909736f2ec84 perf(server): stop loading message bodies for thread summaries (#9662)
51560afffdea82000d757c98ea79678aee75f8648c fix(server): update Claude Agent SDK to 0.3.260 (#9135)
52caa8a0db98f9d32e98a1645caa7f7dd37b14f187 fix(desktop): quit immediately on a second shortcut press (#9657)
5301f3e50eca5102ccd881de6f942a98fe6a518ad4 fix(server): unblock OpenCode approvals and stop (#9653)
54f0347322441f3b8e473a8d13ea7006cbcb4fb761 feat(web): show which sidebar threads hold an unsent draft (#9658)
55d5b94100863057fb4629f9ad4a35753d16917924 feat(mobile): paste the phone clipboard into the terminal (#9199)
56d487dfbf46be344e818725be70ee04be2436bfb4 fix(web): resume Antigravity threads without repeated sign-in (#9647)
57eb77683e5544e071db74831bae052bbd8a7d5f88 fix(server): prevent duplicate desktop clients after restart
5814bf3f6d1644a37029be58429e8f0138e1ceb743 fix(web): toggle a single stashed prompt with Cmd+S (#9644)
5909d13de4381925fa2a6dea74eff8185fa301e905 feat(mobile): make chat text selectable on Android (#8779)
605f878d2a85807618a4c8571cdef5daa3124672d6 fix(web,mobile): fold context compaction under settled turn folds (#9623)
61ec3ec6f0b4e005c47aff07d4d9e31506241bce3a fix(web): mute sidebar branch name to match worktree icon (#9622)
622152d44de2db30a6bae965b0afd30be080e5c872 fix(server): load OpenCode workspace skills via SDK to avoid 64KB CLI pipe truncation (#9585)
63706231535ceac8618712913dfdc4a058c2ffb0d8 fix(web): match composer pull request state icons (#9375)
6493c3ab4ffe408a3e06228a33efc8a0745da91178 fix(web): snooze menu no longer overlaps thread details (#9601)
654cc800c7593db13726171918572afe3502c43ba6 fix(web): keep command palette above composer menus (#9613)
6699e3b721c5255ded20b00ba1798f848bfc0f1f65 fix(connect): diagnose incomplete headless server setup (#9602)
67c3b8825bf476cbce5e061c0f99570cf1f6723b89 fix: preserve tool icons on failed calls (#9606)
6861a91b6ef1bd45424169c6650362b358d49bbe34 fix(web): group image views like other tool calls (#9597)
6900f8b7c28056188e3c5630160806a0afe51c9010 fix: show idle subagent batches without completion marks (#9616)
70f1e90e388b86fe4b007a55c0e685a1fa878115e6 refactor(web): move usage provider controls to settings (#9599)
71caab2fdbac041ac2e851ad4fa3ac4a40a1d4a8f6 fix(web): render draft PRs in gray (#9537)
725cc369b7eb882dbea8d5ad21ba688c73a0058748 fix(pull-requests): refresh data after thread turns (#9496)
73f559fe0ba6fb5950bd14a2404f10b9c94b33f696 fix(web): show context meter in compact composer (#9430)
7439abb9d1d6ae6501c573b9dc0cb9c28e2f75659c fix(connect): refresh authorization without disconnecting (#9582)
7557832803eed4c87c462de92892777a0934019721 fix(desktop): restore panel titlebar interactions (#9591)
7607891e9569c88457516b44c08c820471762969e8 fix(web): bound disconnected send toasts (#9592)
77b34ff8f56469afa8f3f85d89894e1b4cf49b5213 fix(usage): deduplicate CLI proxy subscription accounts (#9584)
7809b81a34954c990f70257ae05efbb602c90aac97 fix(mobile): render workspace images in markdown file previews (#8769)
792675e3c70327719a99af4ae6e53e7b74fb8a9be0 fix(antigravity): keep subagent batches active after launch (#9579)
80bf40fa786c521b552eb554bbd4f2c75c4123cd03 fix(web): align the sidebar wordmark by baseline (#9578)
810cb02abf5b3af2985d9dd23a637a63388e98fd49 fix: better shell syntax handling for labels (#9371)
8242bdea1c9c1d4b7c5c2e77cd23cf53fae68a6fd4 fix(web): stabilize right panel transitions (#9554)
8395390ed78458f139cb795bab4baa53ec39222ff7 chore: vouch august contributors (#9557)
842b10398cca3fa74a7c2187c8d9c23ba789333f5b fix(web): render settings sidebar immediately (#9563)
85d7884ce90b9845e6e8aa737dfe02062b91b11c91 fix(web): make settings sidebar sub-section buttons full width (#9562)
8665f1839ae82af4e67f389f23e4ccc50f51a4a83f fix(web): keep codex restart responses continuous (#9560)
87fee2e0ff8168b82ab19f496fcc874c1f3871f522 test(web): fix flaky startup and Tailwind tests (#9558)
889e1bc36a0843699db54ee28abbdac70584ae8f33 fix(web): keep the last message visible when the resting composer expands (#9553)
89710f6dc417ebf303eede3df3605a6938482d83ab fix(web): simplify expanded tool details (#9549)
90f96a220b5b154ea44c94bf43929c6362cd511699 ci: add on-demand Windows test workflow (#9538)
914e547318b60031eb546d8cf2b84ad9fa0785a87a fix(server): find newly opened pull requests after agent turns (#9125)
921641b4aba58ab495cad7a9800b173af3416e16dd feat(usage): redeem Codex reset credits from the Limits tab (#9534)
93232de5e8aac61e6f3de4bc61d10746167db9e905 feat(marketing): fresh screenshot and floating marks on the homepage (#9547)
94617edab6539ee6573055dc9fc45fdeacdb12f673 fix(server): reveal normalized paths in File Explorer (#9551)
953e2c1a66f74f0a45768332c360cd3682f61129e1 fix(web): thread error banner no longer shifts the chat (#9473)
96f239b77df93077e40c27cc5c5909e94266571859 fix(web): close composer menus when their controls hide (#9541)
973c3e05ccfe34ab7af273f4a4faa72909cafd7a21 fix(web): measure collapsed model labels at their visible width (#9540)
9854aef6fbe16f637092505b30bd25230c4b0744d8 fix(web): restore composer controls as space becomes available (#9539)
995989de44a24888dab02854477ea1d0f50ac3a4a6 fix(mobile): keep store screenshots free of system banners and show dictation (#9548)
100c5ba51d629b3813182cf3e161cc3f23b1e541dc3 feat(providers): add context compaction command (#9293)
1016f405370c8e552da9dcfdd6922e85789fd918340 fix(dev): keep shared dev reloads and hot updates working (#9543)
102dddc0bdcb2230147e207efb17df2e49dbe1bdd8c fix(server): include SQLite conditions in persistence errors
10307c4ab5077fa9c32bf34ab07dd2caa3907878dd9 fix(web): keep automatic project icons consistent (#9535)
1040ba06a122bc73f705bfc18875366b4eeb1160992 fix(web): settle the resting composer layout with a pixel of slack (#9482)
105f54ab901fa77f76eeb1e1cdfa103b9ed6f5ee4ca Fix worktree removal timing out on large install trees (#3902)
1066319a9714881a1d25549f797c468fabebae92813 fix(desktop): preview CDP sessions no longer hard-crash the app (#9068)
10775ab5ab3fb6ad35117da754644c404a31b2fed84 fix(codex): accept rate limit errors on thread resume (#8897)
108b90898077e60290a1eb7bea4224f325fd3bb1595 fix(server): back off relay client restarts after rapid exits (#8788)
1092b96220f00de09b61327c8e881b7f509f8cc5a79 fix(server): settle branch threads immediately on pull request merge (#9528)
110343db2c328af56fe0fa7672f3af055550832689b feat(web): reorganize settings pages (#9354)
11119d8ab2ae9fc562ee7b216a0d72903fbfafa9572 feat(usage): show Codex and Claude subscription limits on a Limits tab (#9507)
1120a0b6be96833adae68b36540c00d347eda278736 fix(web): keep right panel controls clickable (#9517)
113d76b24dd15a219666941ab1b4967d8f738adcda0 feat(codex): support async questions (#9512)
114e3723e06b5b154a3e0ede55c02e2cabc5301d3bf chore: drop comment events from Cursor hygiene forwarder (#9527)
1159d28c21a26aeef198cb064fe466e49cbeabfe09c fix(auth): keep pairing credentials out of access read models (#9523)
11644701efd6790c39dc82cb08104f7295e1780210f chore: forward issue/PR/discussion events to Cursor hygiene (#9518)
1179c9ae3dc0e94a957d9c4a61bb211caf914828054 fix(server): keep events during thread subscription startup (#9521)
118c0ebc882b88dbcffd5a712e403b3c3c517ba4ea6 fix(web): return focus to the composer after closing a media preview (#9513)
119522ebe65a542ace6f22d9ad95f95ef9ade7e87f1 fix(web): keep the composer open while selecting timeline text (#9499)
1206382268323f86e01b2d40206bcf33beccb3fb64a fix(web): let paste expand a resting composer (#9498)
121f8a14b28ff80a92d8f4df9a9aa53bfacb8195199 feat(antigravity): show subagent calls and results (#9515)
1223653cb22ffb30bf133fecc30c955941fe267cf1c fix(desktop): address the browser import review left over from the stack (#9516)
1238ea52c8f2f4361e875d72ab30d72d37cfae4d87f fix(antigravity): update managed runtime to 1.1.1 (#9509)
124eb334ca57448742139fb8ec38fb397c1e51c45c5 fix(antigravity): handle native sign-in URLs on stderr (#9514)
125baf67b6e3accd48d510fd0d39a4e6b978a1eea80 fix(antigravity): keep model choices up to date (#9511)
126f25e4428961982e3935300cfd3c78f7d0fac4ee9 fix(antigravity): allow slow runtime startup during setup (#9510)
127498ab9c399d5e8c3097a286be14d03238e071ac1 feat(desktop): resolve Chromium cookie keys on Linux (#7261)
128ff5843410d44796676b06627788d94354849c4d7 feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc and Firefox (#7260)
12939449e53e31a56103192aa7905e89fc92c977a4a feat(desktop): import browser cookies into a profile (#7255)
130e01c153c18a94a2aa33df7115c6cfee1eb09880f fix(antigravity): forward Google sign-in URLs from browser helper (#9425)
131ef4cc6085e24d6309252412c6dab5482ac11a09f fix(mobile): resolve Antigravity provider icon and normalize driver matching (#9495)
1320aae1e2ad7ab174004f39746eb4c72b905de932d fix(antigravity): discover legacy workspace skills (#9410)
13380b53730d1e821563e5c348fdf4f5fc7616a49f7 fix(mobile): let back swipe pop from horizontal scroll edges (#9493)
134409bc4fa6f3bb9869052bdba4244b30606457f20 fix(mobile): keep the machine glyph next to the environment label (#9486)
13512e8997e58dbca8f1bd8c63b67d662eb69cf0e0d fix(web): keep agent browser preview visible (#9484)
136c726c30a148c2add6a3ec7f31f54ae48dc5d2f0c fix(web): keep opencode icon hollow in collapsed composer (#9492)
137678f23a69943e3eef7171f452b44f2931d2ef21f fix(desktop): restore second-press quit fallback (#9485)
13877138cf33194a303adfbbd69cc93efa69f84245d fix(web): dont collapse composer when interacting with bottom row (#9490)
1390869ad648b67a286d7a47af878f1d256d0ff689f fix(web): let the PR reviewer and label search boxes take keystrokes (#9479)
1404b8b5d9e0177002c84a6f55837670aa0ef816915 fix(desktop): refresh generated annotation styles (#9488)
141373be93e68bf3d32207471b27e976ac84bff806a fix(web): move workflow approval beside checks (#9465)
14203728361aa7beb9c13da320097450e6fe65aac3e feat(web): let users turn off composer collapse on blur and scroll (#9469)
143493fbb58870c912b9cb2ba6c2f1dae938877a59a fix(web): reuse pull request list data while loading (#9467)
144d2b6f3b9296f682c6158b894ab33d98d0c4bfb2b fix(server): full-access OpenCode threads no longer ask for approvals (#9282)
14536c4e9cf5c0123e33d65f2af9497ee090404b532 fix(server): keep a/ and b/ prefixes in rendered git patches (#9438)
146de025aa69ffb0ce1a45d30aed25c60454660b62d fix(mobile): show loading and syncing in the working pill (#9466)
147db8d60f486c5fc1a80d01b591a359f0c87f0868c fix(web): render transparent previews on white (#9463)
14846e8b1a23ab14fa2c128c6b315955d8eb976d85f fix(web): make right panel tabs easier to scroll (#9461)
149d5825e1d2fb1703ced2bbe2661f6a4937dd530bf fix(web): stop clipping the traits chevron on long Codex effort labels (#9433)
150126afb56fca56c663f4f40acd46f123ab27eee2b fix(web): banner buttons no longer expand the resting composer (#9452)
1518bd544cdfd22aa38ab82bc1adc0b851755a299ef fix(web): keep agent images collapsed (#9460)
152c78f05a45e1c2b274d8b0ab2b4d2d88b4767c968 fix(server): reuse pr state when settling threads (#9459)
1534e89d74436a167c01f25a6a5283638843398ea3a fix(web): make project icons the default (#9457)
154645d58547d282eb2aaf6c48e5907625fc112386a fix(web): prioritize authored pull requests (#9453)
15521b9dda5afb00a33e228a68d2ccc885bba7285dc fix(web): unify skeleton loading animations on one pulse (#9448)
156d4ba2a1f11498eae9683f6dc95a08b1c17c29765 fix(composer): mute fast icon when collapsed (#9451)
1572120fbc185737b71f09de020b95224f9e636d1e5 fix(web): avoid duplicate Antigravity install status (#9419)
158cfddb4201df8941bbfde008919da70fe5ec7552b fix(mobile): skip unsupported shared settings targets (#9381)
15957626eb6eaa436b22260068d23f4e3df5f389cd1 fix(web): prevent loading ssh environments from overriding navigation (#9168)
160098bf5329727fcd7d973bf842e6b4d50d6e7b924 fix(web): preserve explicit preview navigation URLs (#8902)
16177e35c561259733d880ab62a43aad0894d301d9b fix(web): send cited messages with Cmd+Enter (#9307)
162fff33f9e851912363c5b1f3ac65598be35eb5f0d perf(ci): reuse dependency checks in release builds (#9399)
1631e051873094c0c75cd35fef89c90461c22cce76b fix(antigravity): refresh the model manifest so older Gemini models fold as legacy (#9397)
16419c97ea56d30b3a2de31a060f8f47d6b7404b78f fix(web): unlock the composer when preview capture fails (#9127)
1652b745efe57cddc1753d3007d081c64bd20e3ab79 fix(usage): price new models without waiting a day for the rate table (#9202)
1662aa907b1969237efdaced29612fb46ba51be7041 fix(mobile): show an error instead of an endless preview spinner (#9123)
16706336460c9988f29c71e839c4c9c840c4552e077 feat(providers): add Google Antigravity via the official ACP agent (#9348)
168652515a349741d234111b85f27597be3265d1ffc fix(web): render assistant images inline in chat (#9126)
169044ea8e347d980a215057c325de8335d38f0f9aa fix(web): stop the resting composer layout loop (#9393)
17018573d60aab1344bf124a044b01445086e910c4c fix(claude): expand slash commands when a message has attachments (#9122)
1715d1b02cdeebff4b0b4f15a1e8425b06ae3cfe8d1 feat(marketing): put named-developer quotes on the landing page (#9385)
1724b26132d2c740ff344ef3fae7ae62e9765759e29 fix(web): keep trailing tool groups out of "Worked for" accordion (#9384)
1735b8445b7a777ab1070aa97b062b1618971073a96 fix(web): collapse the resting composer (#7855)
17424799de4f85a9071afc2420362784ea46041d544 fix(mobile): size expanded tool groups correctly (#9359)
1751f7a3c11ca864898b136a35011ad10777ea07544 fix(mobile): pressed and disabled styles no longer apply unconditionally (#9355)
176cf0bb4c3571badbc5aaa8189cd5cbe3563ec73c1 fix(web): match project icon chooser button sizes (#9368)
177c89e3e12a41e13e09e0103d08a41d0688a99fb49 fix(web): settled sidebar rows use the project fallback icon (#9366)
178829c3db94830fc70b5754513b8a370f7301ca213 fix(environments): draw the machine icon everywhere an environment is named (#9365)
1795eb4f452ee0fcde7b5bbc93d62de9118b70a33b2 test(web): remove static markup-only component tests (#9364)
180d4bd8923ad8cab346a854967015b06d9e5cd77f7 feat(web): mod+w closes the active right panel tab before the window (#9363)
181b5f4e8137b3cbd657fbe2b698f9db4e321acbc3d feat(web): suggest ssh hosts in a dropdown under the host field (#9171)
1829f9359bd8132c425493720080149dcc0d3da9436 fix(web): stop usage summary requests reporting slow RPCs (#9358)
183854541a04e07b7960698b381bcfd2fda73eb276c fix(pull-requests): shared state + not settling? (#9332)
18448ba76bc26337f23d1f0991bbac51f996a3333dc fix(web): collapse oldest pull request comments (#9323)
1856cf0c6ea55d281f65c80502ec1871b0adf472025 feat: display native app and browser icons in work logs (#9093)
1860bc59bbae391ee45ab3a7cfb2dc703c8b27e5772 fix(web): let the pull request list use wide screens (#9351)
1871575ada305b9686b9771df2dca304f391e85a76e fix(mobile): stop indented code overflowing Android chat bubbles (#9347)
188f6c04c552c203350705f9ab1e47773ea736af245 feat(web): add customizable project icons (#9137)
1896a5a18cb1bc9ea878afa1146950439b5d4744110 fix(web): add press feedback to buttons (#9349)
19018062da9425909a0a92bce0b692c9de6fbba56ee feat(web): choose whether links open in the default browser or in T3 Code (#9339)
191d42254dfb462e4ab23a42d4bef53b1ce00a72396 fix(web): resolve Vite sourcemap and supports warnings (#9343)
1922a3cfe456375fd34b906f849b04706109dc74170 fix(web): collapse PR header actions to icons when narrow (#9334)
1935f84efa1ec2fb3cd6f6c54545cc34bb77ac1ddb8 feat(web): add PageUp/PageDown chat navigation (#9315)
1941aa44a071f66bdfd9430356ab824b5a6985fb459 feat(web): add a file tree to the diff panel and pull request code tab (#9330)
195f5fbb1bcb0db378c61addd5b49ef2d95bd888168 chore: upgrade vite-plus to 0.3.0 (#9327)
1963b2de9da1c8763602e283e8d14b41b5d57a9d0c7 chore: dedupe lightningcss and tailwind node bindings (#9331)
1979409dd20a9fbce491d49d09c79b289d8fb8bfe3e fix(web): make the diff layout toggle a persisted setting (#9326)
19812f1fc427efad4b683835a72bda6f74bebb5d641 fix(web): line up the titlebar wordmark label and version pill (#9255)
19966419a1d1b80110c9702edae641bddcc2888fc98 fix(dev): share dev servers on the loopback Vite actually binds (#9324)
20085f2479ffe5c9d8ffa91f1bfae5234df4061292f refactor(mobile): style plain views with Uniwind classes instead of the theme bridge (#9322)
201b9b1b8fdddf9d006fdb820af770063e1f968345b chore(ci): narrow the Effect conventions check-run agent (#9321)
202194f838e7636f97739c440c7855aed195dbd7f52 chore: audit lint directives and move plugin allowlists into config (#9300)
203ec44bc56f598be16ffbc22ed0e4e095447043844 fix(chat): keep live tool labels in present tense (#9316)
204922bd692251bc803c12a3fab159efe83c957bb70 refactor(media): unify file and media previews across clients (#9253)
20531eeb443305a4e11c8b20a7c8ff2b3f5e841eb0f fix(sidebar): collapse settled and snoozed shelves by default (#9314)
2069ebbeda5a03fd8fce7be147b65ea396e50721c68 feat(web): apply and remove labels from the pull request tab (#9313)
2070fbb94248581aaefda36e4f8a40ec2c6455c779a feat(environments): draw each environment as the machine it runs on (#9299)
208d897641d738c67dd7c12cbb3a273b43fe17a5eb1 fix(pull-requests): keep cached PR chrome on reopen (#9294)
209355fbd96d5e90b52961ac5f1e035112fc3bad4a9 fix(web): stop remounting markdown on every activity delta (#9306)
210ef6cc0b362019f32152470ed96c1aa1b813fb7f6 chore(ci): only run check-run agents on vouched contributors (#9298)
211e94603adfac7d8734f9af01e33f67e776daf482d chore(ci): narrow the UI consistency check-run agent (#9297)
21277512998485718eb1b6c336e20f196eb40a6a32f feat(web): preview document attachments in the file viewer (#9292)
213dbc7bfa3f36cc61a3f1a2015e47d71ae9dee5421 fix(opencode): show Reasoning selector for OpenCode models (#9287)
2142971ec3209d7ef1b00d7ded70fe6342816b1539f fix(server): preserve automatic settlement timestamps (#9254)
21515fea6c5f40331c8325f33bd08b2dcbf924e935a fix(providers): discover workspace skills everywhere (#9180)
216443b4ebfe83fcfe64c34b09ecb5a5fffdebb85c7 fix(pull-requests): missing features & better behaviour (#9188)
2174ba39a6f408bdee468df2e3ffdf7d5dc08e7b59d fix(desktop): detect installed Spectre libs for Windows builds
218994bd7373cf3a335c204a617604e690ed4c00cba fix(cursor): honor auto and full access modes (#9283)
219c742edd46c5b6792ec8647f934a4703f9103aa82 fix(web): show scroll-to-end as soon as the last message slips under the composer (#9280)
22063f334baf3432c4404ddfc9e71d33bf16575f0ed Revert "feat(providers): add context compaction across harnesses" (#9284)
221064392ffc7fc58fcc3018fd2ba2df33fbb21f1de fix(web): offer browser profiles from the empty-panel launcher (#9279)
222b59b7d0af9536a2d61ddfcf8d33420b890b2faca fix(web): unify control sizing across settings pages (#9281)
223fb93902ee24d4ba380508df33d25478d3baf7a72 feat(web): add proactive panels (#9276)
224535557b3f785629e6f48cad40c5a4b78b3b3c5d6 feat(providers): add context compaction across harnesses (#8808)
2251eb36b45ef7aa5c028380afb7266bcabf258a55e fix(web): show pull request state icons in tabs (#9112)
226ba3cb0773859334fe9f75016295bb3893c9e6044 feat(projects): automatically pull clean default branches (#9277)
22791c8d4771ccb503a9dde65190b87db642df0a6ea feat(web): add opt-in panel animations (#8830)
228ca63d42d670837b918081d1fc1ebada553814b4c refactor(shared): move the node:sqlite Effect SQL client into shared (#7272)
229134d51096ea0d00a53a499e8f0c87e31fafb0006 feat(desktop): browser profiles for the preview browser (#7254)
23028ddaf75917140e5e4355d4386bc5d14d9dad7b6 fix(web): confirm closing agent-controlled browsers (#9272)
2315a9b56291f82b9269346053594d8b5dfca736976 fix(web): warn when shared settings have no target environment (#9207)
23246b5c66406b9942589d7e9132beeafda2434f113 fix(chat): show single tool calls without summaries (#9267)
23362c68dc41f9d9d7ecaf5193a6a8d997ebf671a61 fix(mobile): show filled filter icon on Android when filters are active (#9217)
234f90e2f2bd26e22b77ccf781cccdf95afd3c3ac1c fix(server): subscribe before provider settings watcher (#9271)
235b57726ca842624b713572529109ac49b17c93fb3 feat(web): add copy path button to diff headers (#2403)
2368d5b712de3cbd84118327808c403756e8894014a fix(desktop): exclude opposite macOS pty prebuilds (#9240)
2379159b808d35a88e74fc91e11070f3270cdb321f9 feat(mobile): long-press file references for path and open actions (#9258)
2382a7a449cca3c7760b689a54b3aa02476a206dbc9 fix(web): hide deleted providers with prototype keys (#8337)
239aab4049646ffa8b9bcff92562992e6c3a8554bf0 fix(ci): keep Expo Sharing patch applied (#9248)
240b5a09e13fa464a8681e0bf9df404dd3c0afe8ac8 fix(release): pin patched expo-sharing version (#9250)
audits/orchestrator-v2/2026-09-04/persistence-audit-probes.test.ts+323−0
@@ -0,0 +1,323 @@
1import { assert, it } from "../../../apps/server/node_modules/@effect/vitest/dist/index.js";
2import {
3 EventId,
4 MessageId,
5 type ModelSelection,
6 NodeId,
7 type OrchestrationV2AppThread,
8 type OrchestrationV2DomainEvent,
9 ProjectId,
10 ProviderInstanceId,
11 ProviderThreadId,
12 RunId,
13 ThreadId,
14} from "../../../apps/server/node_modules/@t3tools/contracts/src/index.ts";
15import * as Cause from "../../../apps/server/node_modules/effect/dist/Cause.js";
16import * as DateTime from "../../../apps/server/node_modules/effect/dist/DateTime.js";
17import * as Effect from "../../../apps/server/node_modules/effect/dist/Effect.js";
18import * as SqlClient from "../../../apps/server/node_modules/effect/dist/unstable/sql/SqlClient.js";
19
20import {
21 IdAllocatorV2,
22 layer as idAllocatorLayer,
23} from "../../../apps/server/src/orchestration-v2/IdAllocator.ts";
24import {
25 applyToProjection,
26 emptyProjection,
27 threadShellFromProjection,
28} from "../../../apps/server/src/orchestration-v2/ProjectionStore.ts";
29import { isAutoSettlementCandidate } from "../../../apps/server/src/orchestration-v2/ThreadSettlementService.ts";
30import {
31 migrationEntries,
32 runMigrations,
33} from "../../../apps/server/src/persistence/Migrations.ts";
34import * as NodeSqliteClient from "../../../packages/shared/src/nodeSqliteClient.ts";
35
36const providerInstanceId = ProviderInstanceId.make("codex");
37const modelSelection = {
38 instanceId: providerInstanceId,
39 model: "gpt-5.4",
40} satisfies ModelSelection;
41
42function makeThread(
43 threadId: ThreadId,
44 now: DateTime.Utc,
45 overrides: Partial<OrchestrationV2AppThread> = {},
46): OrchestrationV2AppThread {
47 return {
48 createdBy: "user",
49 creationSource: "web",
50 id: threadId,
51 projectId: ProjectId.make(`project:${threadId}`),
52 title: `Thread ${threadId}`,
53 providerInstanceId,
54 modelSelection,
55 runtimeMode: "full-access",
56 interactionMode: "default",
57 branch: null,
58 worktreePath: null,
59 activeProviderThreadId: null,
60 lineage: {
61 parentThreadId: null,
62 relationshipToParent: null,
63 rootThreadId: threadId,
64 },
65 forkedFrom: null,
66 createdAt: now,
67 updatedAt: now,
68 archivedAt: null,
69 settledOverride: null,
70 settledAt: null,
71 lastVisitedAt: null,
72 deletedAt: null,
73 ...overrides,
74 };
75}
76
77function threadCreatedEvent(
78 thread: OrchestrationV2AppThread,
79 now: DateTime.Utc,
80): Extract<OrchestrationV2DomainEvent, { readonly type: "thread.created" }> {
81 return {
82 id: EventId.make(`event:create:${thread.id}`),
83 type: "thread.created",
84 threadId: thread.id,
85 providerInstanceId,
86 occurredAt: now,
87 payload: thread,
88 };
89}
90
91const applyHistoricalCohort = (
92 mappings: ReadonlyArray<readonly [currentId: number, historicalId: number]>,
93) =>
94 Effect.gen(function* () {
95 const sql = yield* SqlClient.SqlClient;
96 for (const [currentId, historicalId] of mappings) {
97 const entry = migrationEntries.find(([id]) => id === currentId);
98 assert.ok(entry, `missing current migration ${currentId}`);
99 const [, name, migration] = entry;
100 yield* migration;
101 yield* sql`
102 INSERT INTO effect_sql_migrations (migration_id, name)
103 VALUES (${historicalId}, ${name})
104 `;
105 }
106 });
107
108it.effect("upgrades committed-058 state through the live-059 overlay", () =>
109 Effect.gen(function* () {
110 const sql = yield* SqlClient.SqlClient;
111 yield* runMigrations({ toMigrationInclusive: 58 });
112 const executed = yield* runMigrations();
113 assert.deepStrictEqual(executed, [[59, "OrchestrationV2ShellIndexes"]]);
114 const indexes = yield* sql<{ readonly name: string }>`
115 SELECT name FROM sqlite_master
116 WHERE type = 'index' AND name LIKE 'orchestration_v2_%_idx'
117 `;
118 const names = new Set(indexes.map(({ name }) => name));
119 assert.strictEqual(names.has("orchestration_v2_projection_turn_items_shell_pending_idx"), true);
120 assert.strictEqual(names.has("orchestration_v2_projection_messages_latest_user_idx"), true);
121 }).pipe(Effect.provide(NodeSqliteClient.layerMemory())),
122);
123
124it.effect("reproduces the old-052 cohort collision against the current migrator", () =>
125 Effect.gen(function* () {
126 const sql = yield* SqlClient.SqlClient;
127 yield* runMigrations({ toMigrationInclusive: 43 });
128 yield* applyHistoricalCohort(
129 Array.from({ length: 9 }, (_, offset) => [48 + offset, 44 + offset] as const),
130 );
131
132 const before = yield* sql<{ readonly migration_id: number; readonly name: string }>`
133 SELECT migration_id, name
134 FROM effect_sql_migrations
135 ORDER BY migration_id DESC
136 LIMIT 1
137 `;
138 assert.deepStrictEqual(before, [{ migration_id: 52, name: "LegacyV1ImportState" }]);
139
140 const exit = yield* Effect.exit(runMigrations());
141 assert.strictEqual(exit._tag, "Failure");
142 if (exit._tag === "Failure") {
143 const failure = Cause.pretty(exit.cause);
144 assert.match(failure, /Migration "53_ApplicationEventSource" failed/);
145 assert.match(failure, /duplicate column name|already exists/i);
146 }
147
148 const after = yield* sql<{ readonly migration_id: number; readonly name: string }>`
149 SELECT migration_id, name
150 FROM effect_sql_migrations
151 ORDER BY migration_id DESC
152 LIMIT 1
153 `;
154 assert.deepStrictEqual(after, before);
155 }).pipe(Effect.provide(NodeSqliteClient.layerMemory())),
156);
157
158it.effect("reproduces the old-055 cohort collision and skipped main columns", () =>
159 Effect.gen(function* () {
160 const sql = yield* SqlClient.SqlClient;
161 yield* runMigrations({ toMigrationInclusive: 44 });
162 yield* applyHistoricalCohort([
163 ...Array.from({ length: 9 }, (_, offset) => [48 + offset, 45 + offset] as const),
164 [57, 54],
165 [58, 55],
166 ]);
167
168 const projectColumns = yield* sql<{ readonly name: string }>`
169 SELECT name FROM pragma_table_info('projection_projects')
170 `;
171 const columnNames = new Set(projectColumns.map(({ name }) => name));
172 assert.strictEqual(columnNames.has("auto_pull"), false);
173 assert.strictEqual(columnNames.has("project_icon"), false);
174
175 const exit = yield* Effect.exit(runMigrations());
176 assert.strictEqual(exit._tag, "Failure");
177 if (exit._tag === "Failure") {
178 const failure = Cause.pretty(exit.cause);
179 assert.match(failure, /Migration "56_LegacyV1ImportState" failed/);
180 assert.match(failure, /already exists/i);
181 }
182 }).pipe(Effect.provide(NodeSqliteClient.layerMemory())),
183);
184
185it.effect("reproduces the prior-audit old-053 cohort collision", () =>
186 Effect.gen(function* () {
187 const sql = yield* SqlClient.SqlClient;
188 yield* runMigrations({ toMigrationInclusive: 44 });
189 yield* applyHistoricalCohort(
190 Array.from({ length: 9 }, (_, offset) => [48 + offset, 45 + offset] as const),
191 );
192
193 const before = yield* sql<{ readonly migration_id: number; readonly name: string }>`
194 SELECT migration_id, name
195 FROM effect_sql_migrations
196 ORDER BY migration_id DESC
197 LIMIT 1
198 `;
199 assert.deepStrictEqual(before, [{ migration_id: 53, name: "LegacyV1ImportState" }]);
200
201 const exit = yield* Effect.exit(runMigrations());
202 assert.strictEqual(exit._tag, "Failure");
203 if (exit._tag === "Failure") {
204 const failure = Cause.pretty(exit.cause);
205 assert.match(failure, /Migration "56_LegacyV1ImportState" failed/);
206 assert.match(failure, /already exists/i);
207 }
208
209 const after = yield* sql<{ readonly migration_id: number; readonly name: string }>`
210 SELECT migration_id, name
211 FROM effect_sql_migrations
212 ORDER BY migration_id DESC
213 LIMIT 1
214 `;
215 assert.deepStrictEqual(after, before);
216 }).pipe(Effect.provide(NodeSqliteClient.layerMemory())),
217);
218
219it.effect("reproduces root checkpoint-scope reuse across ordinary runs", () =>
220 Effect.gen(function* () {
221 const ids = yield* IdAllocatorV2;
222 const threadId = ThreadId.make("thread:audit-scope");
223 const now = DateTime.makeUnsafe("2026-09-04T00:00:00.000Z");
224 const firstScopeId = yield* ids.allocate.checkpointScope({ threadId, name: "root" });
225 const secondScopeId = yield* ids.allocate.checkpointScope({ threadId, name: "root" });
226 const firstRunId = RunId.make("run:audit-scope:1");
227 const secondRunId = RunId.make("run:audit-scope:2");
228 const providerThreadId = ProviderThreadId.make("provider-thread:audit-scope");
229 let projection = emptyProjection(threadCreatedEvent(makeThread(threadId, now), now));
230 const scopeEvent = (
231 eventId: EventId,
232 scopeId: typeof firstScopeId,
233 runId: RunId,
234 nodeId: NodeId,
235 ): Extract<OrchestrationV2DomainEvent, { readonly type: "checkpoint-scope.created" }> => ({
236 id: eventId,
237 type: "checkpoint-scope.created",
238 threadId,
239 runId,
240 nodeId,
241 occurredAt: now,
242 payload: {
243 id: scopeId,
244 threadId,
245 runId,
246 nodeId,
247 parentScopeId: null,
248 providerThreadId,
249 kind: "root_run",
250 ordinalWithinParent: 0,
251 advancesAppRunCount: true,
252 cwd: "/repo",
253 createdAt: now,
254 },
255 });
256
257 projection = applyToProjection(
258 projection,
259 scopeEvent(
260 EventId.make("event:scope:1"),
261 firstScopeId,
262 firstRunId,
263 NodeId.make("node:scope:1"),
264 ),
265 );
266 projection = applyToProjection(
267 projection,
268 scopeEvent(
269 EventId.make("event:scope:2"),
270 secondScopeId,
271 secondRunId,
272 NodeId.make("node:scope:2"),
273 ),
274 );
275
276 assert.strictEqual(firstScopeId, secondScopeId);
277 assert.strictEqual(projection.checkpointScopes.length, 1);
278 assert.strictEqual(projection.checkpointScopes[0]?.runId, secondRunId);
279 }).pipe(Effect.provide(idAllocatorLayer)),
280);
281
282it("reproduces a stale failed run waking a later snooze", () => {
283 const threadId = ThreadId.make("thread:audit-snooze");
284 const createdAt = DateTime.makeUnsafe("2026-08-01T00:00:00.000Z");
285 const failedAt = DateTime.makeUnsafe("2026-08-20T00:00:00.000Z");
286 const snoozedAt = DateTime.makeUnsafe("2026-09-01T00:00:00.000Z");
287 const snoozedUntil = DateTime.makeUnsafe("2026-09-10T00:00:00.000Z");
288 let projection = emptyProjection(
289 threadCreatedEvent(makeThread(threadId, createdAt, { snoozedAt, snoozedUntil }), createdAt),
290 );
291 projection = applyToProjection(projection, {
292 id: EventId.make("event:audit-snooze:failed-run"),
293 type: "run.created",
294 threadId,
295 providerInstanceId,
296 occurredAt: failedAt,
297 payload: {
298 id: RunId.make("run:audit-snooze:1"),
299 threadId,
300 ordinal: 1,
301 providerInstanceId,
302 modelSelection,
303 providerThreadId: null,
304 userMessageId: MessageId.make("message:audit-snooze:1"),
305 rootNodeId: null,
306 activeAttemptId: null,
307 status: "failed",
308 requestedAt: failedAt,
309 startedAt: failedAt,
310 completedAt: failedAt,
311 checkpointId: null,
312 contextHandoffId: null,
313 },
314 });
315
316 assert.strictEqual(
317 isAutoSettlementCandidate(
318 threadShellFromProjection(projection),
319 Date.parse("2026-09-04T00:00:00.000Z"),
320 ),
321 true,
322 );
323});
audits/orchestrator-v2/2026-09-04/persistence.md+312−0
@@ -0,0 +1,312 @@
1# Orchestrator V2 persistence and lifecycle audit
2
3Date: 2026-09-04
4Frozen branch HEAD: `8af5734365f7c45bc08b57066dbae42f9f7d4235`
5Initial fetched main: `d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70`
6Final fetched main: `bc03c3640d6d3bb44e5fb477bfd78d7484cd0e00`
7Merge base: `c8f77e0d441264efb0acfac312e852c81ae3da83`
8Prior audit HEAD: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0`
9
10This audit is limited to schema upgrades, legacy import, projections, startup and recovery, outbox behavior, checkpoint diff and rollback, project and thread lifecycle boundaries, auto-settlement and snooze behavior, durable scheduling, and query cardinality. I reviewed committed source with `git show <commit>:<path>` and local-only changes from the `.snapshot` paths recorded in `worktree-manifest.json`. I did not modify product source or product tests.
11
12## Outcome
13
14Five prior correctness findings remain open, and both prior performance findings are fixed. I found one additional concrete performance issue in the durable scheduler. The final main fingerprint added one late performance-parity gap, M10. Startup corruption verification has a separate, unmeasured safety/performance tradeoff recorded below; it is not a continuation of F15.
15
16| ID | Priority | Frozen committed status | Frozen overlay status | Result |
17| ----------- | -------- | ------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
18| F01 | P1 | Open | Open | Old V2 migration histories at 052, 053, and 055 still collide with renumbered current migrations and skip newer main migrations. |
19| F03 | P1 | Open | Open | A second ordinary run still reuses and reassigns the only root checkpoint scope, while full diff requires a run-1-owned root scope. |
20| F04 | P1 | Open | Open | WebSocket forced project deletion still commits V2 thread deletions before a force-less legacy project deletion can reject. |
21| F05 | P1 | Open, broadened | Open | HTTP and live/offline CLI still bypass the V2-aware project lifecycle. HTTP also drops the newly added update fields. |
22| F13 | P2 | Open | Open | Failed status still wakes a later snooze without proving the failure occurred after the snooze. |
23| F14 | P2 | Fixed | Fixed | Checkpoint diff now uses a narrow three-query checkpoint context instead of hydrating the transcript. |
24| F15 | P2 | Fixed | Fixed | Startup recovery now selects unfinished candidates and no longer sequentially loads every active and archived thread history. |
25| PERSIST-N01 | P2 | New | Open | The five-second scheduler poll full-scans and decodes every scheduled task instead of using the due-task index. |
26| M10 | P2 | Missing late-main parity | Partial groundwork only | V2 metadata and provider-control paths still hydrate unrelated history after main [#9758](https://github.com/pingdotgg/t3code/pull/9758) stopped doing so. |
27
28F01, F03, F04, F05, and F13 are rollout risks. F14 is a real fix in the last committed reconciliation. F15 is fixed by the candidate-selection work in `d98f1d5e38`.
29
30## F01: historical V2 migration cohorts still cannot upgrade
31
32### Evidence
33
34The frozen manifest puts main migrations at 44 through 47 and the V2 sequence at 48 through 58 (`apps/server/src/persistence/Migrations.ts` at frozen HEAD, lines 58-72 and 128-142). The runner executes only IDs above the latest recorded ID (`Migrations.ts:166-184`). It does not use the recorded migration names to reconcile a renamed history.
35
36I verified the manifest-selected migration bodies by Git blob identity, not by filename inference:
37
38| Historical cohort | Historical manifest | Current identical bodies |
39| ------------------------- | --------------------- | ------------------------ |
40| `c1791ab2637` | V2 migrations 044-052 | Current 048-056 |
41| Prior audit `d2f1f511...` | V2 migrations 045-053 | Current 048-056 |
42| `d98f1d5e38` | V2 migrations 045-055 | Current 048-058 |
43
44The old `c1791ab2637` tree contains an unused `044_ClearAutomaticProjectModelDefaults.ts`, but its manifest imports and records `044_OrchestrationV2`. The cohort comparison and probe use the manifest-selected file.
45
46The audit probe uses the actual current migration functions to create those byte-identical historical schemas under their recorded old IDs, then invokes the actual current migrator against in-memory SQLite. Results:
47
48- Old 052 starts current 053 `ApplicationEventSource`, whose old 049 body already ran. Its unconditional `ALTER TABLE ... ADD COLUMN application_event_version` at `053_ApplicationEventSource.ts:33-47` fails with a duplicate-column error.
49- Old 053 starts current 054 and reaches current 056 `LegacyV1ImportState`, whose old 053 body already created the table. `056_LegacyV1ImportState.ts:9-26` fails because the table already exists.
50- Old 055 starts current 056 and fails at the same preexisting legacy-import table.
51
52All three failures roll the attempted migration batch back to the prior recorded maximum. The probe also confirms that old 055 lacks `auto_pull` and `project_icon_json` because numeric IDs 45 through 47 are treated as already applied.
53
54The skipped work depends on cohort:
55
56- Old 052 skips current 044 through 052, including `ClearAutomaticProjectModelDefaults`, `ProjectionProjectsAutoPull`, `RepairAutomaticSettlementTimestamps`, and `ProjectionProjectIcon`.
57- Old 053 and 055 already applied current 044 under that name, but skip current 045 through 047 and the renumbered V2 steps below their maximum.
58
59Main itself ends at migration 047. A database produced by current main therefore advances into branch migration 048 correctly. Fresh installs also work. This is a branch-history compatibility regression, not main lag.
60
61### Trigger and consequence
62
63Start the frozen build against a database previously opened by any of the reproduced V2 cohorts. Server startup fails before readiness. Making only one DDL statement idempotent would move the collision but would not execute the skipped main data repairs and schema additions.
64
65### Required coverage
66
67Use a manifest-aware bridge keyed by recorded `(migration_id, name)` history, then append idempotent repair migrations for any main work skipped by old numeric maxima. Add table-driven upgrade fixtures for each supported historical manifest and useful partial stopping points. Fresh-schema tests cannot prove upgrade compatibility.
68
69### Overlay
70
71The frozen overlay adds migration 059 with three indexes. The audit probe confirms committed 058 upgrades through live 059 and the indexes exist. Historical cohorts still fail at 053 or 056 before 059 can run, so the finding applies equally to frozen committed 058 and the local 059 overlay.
72
73## F03: full diff still fails after an ordinary second run
74
75### Evidence
76
77For a zero baseline, `CheckpointDiffQuery` finds run ordinal 1 and requires a `root_run` scope whose current `runId` equals that first run (`apps/server/src/checkpointing/CheckpointDiffQuery.ts` at frozen HEAD, lines 157-170). If it cannot find that scope, it returns `CheckpointRefUnavailableError` at lines 173-179.
78
79Production creates a different state:
80
81- `IdAllocator.allocate.checkpointScope` derives the ID only from `threadId` and scope name (`orchestration-v2/IdAllocator.ts:295-300`).
82- Every ordinary root run requests the same name, `root`, while storing the current `runId` in the scope payload (`CheckpointService.ts:184-210`).
83- Projection upsert replaces the prior row for that deterministic scope ID, including its ownership fields.
84
85The real allocator/reducer audit probe allocates and applies two ordinary root scopes. Both allocations return the same ID; after run 2, there is one scope row and its `runId` is run 2. Root independently confirmed the same production ownership shape.
86
87The product test fabricates two different root scope IDs and retains one scope for each run (`checkpointing/CheckpointDiffQuery.test.ts:18-44`). Its happy path therefore cannot be produced by the allocator it is meant to cover.
88
89### Trigger and consequence
90
91Complete two ordinary runs in one Git-backed thread, then request a full diff through turn 2. The shared scope's ordinal-zero baseline ref is still derivable, and the target checkpoint exists, but the query rejects because the shared scope is no longer owned by run 1. Full-thread diff fails from the second completed run onward.
92
93The zero baseline should be resolved from the actual shared root scope, normally the target checkpoint's scope, rather than by joining scope ownership to run ordinal 1. Replace the two-scope fixture with state made by the real allocator/projector.
94
95### Overlay
96
97No frozen overlay file changes `CheckpointDiffQuery`, `IdAllocator`, or `CheckpointService`. The ProjectionStore overlay does not change checkpoint-scope ownership. F03 remains open locally.
98
99## F04: WebSocket force deletion can partially commit
100
101### Evidence
102
103The WebSocket project handler reads active and archived V2 shells, checks `force`, and independently dispatches one `thread.delete` per thread (`apps/server/src/ws.ts` at frozen HEAD, lines 1299-1319). Only after those commits does it call `ProjectService.delete`, omitting `force` (`ws.ts:1320-1323`).
104
105`ProjectDeleteInput` has no force field, and `ProjectService.delete` dispatches a legacy `project.delete` without one (`project/ProjectService.ts:48-51,363-386`). The legacy decider rejects any nonempty project unless `force === true`; with force, it decides thread and project deletion as one sequence (`orchestration/decider.ts:280-313`).
106
107The legacy importer reads `projection_threads` and writes V2 events, but it intentionally leaves the V1 rows in place (`orchestration-v2/LegacyV1ThreadImporter.ts:459-490`). Once a V2 `thread.created` event exists, the importer does not create that thread again.
108
109### Trigger and consequence
110
111Force-delete through WebSocket a project containing imported V1 threads. V2 thread deletions commit first. The legacy project delete sees the still-present V1 threads and rejects because force was dropped. The project and V1 rows remain, the V2 copies are deleted, and later import does not restore them. This is a destructive partial commit, not merely a mismatched error message.
112
113Main's V1 WebSocket path normalizes the original command and sends it intact to one decider transaction (`ws.ts` at main, lines 1263-1296); the decider carries force through the thread/project sequence. The V2 split coordinator regresses that invariant.
114
115### Overlay
116
117The frozen overlay does not contain `ws.ts`, `ProjectService.ts`, the legacy decider, or the importer. F04 is unchanged.
118
119## F05: project lifecycle still diverges across transports
120
121### Evidence
122
123The shared `ProjectMutation` contract carries:
124
125- `createWorkspaceRootIfMissing` on create;
126- `autoPull`, `projectIcon`, `faviconPath`, and `defaultThreadEnvMode` on update;
127- `force` on delete.
128
129See `packages/contracts/src/project.ts` at frozen HEAD, lines 116-145. The WebSocket create and update branches forward these fields (`apps/server/src/ws.ts:1264-1298`) and perform V2 thread validation/deletion for project removal.
130
131The HTTP mutation handler calls `ProjectService` directly and drops all of the fields above except the older title/root/model/scripts set (`apps/server/src/project/http.ts:45-80`). The live CLI delegates to this HTTP endpoint (`cli/project.ts:324-338`). The offline CLI duplicates the same reduced mapping and direct legacy deletion (`cli/project.ts:423-459`).
132
133The last committed reconciliation added `autoPull`, project icon, favicon, and default environment mode to the shared update contract and WebSocket mapping, but did not add them to HTTP or offline CLI. This broadens F05; it is not a separate finding.
134
135### Triggers and consequences
136
137- Delete a V2-only populated project through HTTP. The V1 read model can see no thread, so the legacy project row is deleted while V2 threads remain and reference a deleted project.
138- Force-delete an imported populated project through HTTP, live CLI, or offline CLI. Force is dropped, so the legacy decider rejects.
139- Send a typed HTTP create with `createWorkspaceRootIfMissing: true`. The service receives the default false behavior.
140- Send a typed HTTP update for `autoPull`, project icon, favicon, or default thread environment mode. The request succeeds without applying that field.
141
142The current HTTP tests cover only error translation (`project/http.test.ts:15-51`). The CLI integration covers add, title rename, and empty-project removal (`cli/project.test.ts:116-133`). Neither suite exercises the dropped fields or populated mixed-store deletion.
143
144One lifecycle coordinator should own validation and mutation across V1/V2, with WebSocket, HTTP, live CLI, and offline CLI preserving the same typed input. Tests need imported and V2-only projects, active and archived threads, force false/true, missing-root creation, and every update field.
145
146### Thread lifecycle note
147
148V2 thread mutation commands remain on the WebSocket command path. The V2 HTTP surface reviewed here serves shell, detail, and history reads through `ThreadManagementService`; it does not define a competing HTTP thread-mutation implementation. I found no separate thread archive/unarchive/delete divergence within that intended split. Project deletion is the cross-store exception because it owns thread cleanup.
149
150### Overlay
151
152No project transport or lifecycle source is present in the frozen overlay. F05 remains open locally.
153
154## F13: stale failure still wakes a newer snooze
155
156### Evidence
157
158For a future snooze, V2 defines `wokeOnError` as `thread.status === "failed"` with no time comparison (`orchestration-v2/ThreadSettlementService.ts` at frozen HEAD, lines 108-117). Completion correctly requires `latestRunCompletedAt > snoozedAt` in the next two lines.
159
160Main's V1 policy requires the error session's `updatedAt` to be newer than `snoozedAt` (`orchestration/ThreadSettlementPolicy.ts` at main, lines 98-109). The V2 shell already exposes the latest run completion time needed for an equivalent ordering check.
161
162The audit probe creates a projected failed run completed on August 20, then a snooze beginning September 1 and ending September 10. At September 4, the production V2 policy returns `true`, treating the old failure as an early wake. The frozen overlay's unit test also explicitly expects any failed status to wake a future snooze without a failure timestamp (`ThreadSettlementService.test.ts.snapshot:127-139`).
163
164### Trigger and consequence
165
166Fail a thread, then snooze it into the future. At the next settlement evaluation, the stale failure defeats the newer snooze. If the thread also matches closed-PR or inactivity settlement policy, it can move to settled before the requested wake time.
167
168Require failed-run completion evidence newer than `snoozedAt`. Cover both failure-before-snooze and failure-after-snooze through the projected candidate path.
169
170### Rejected lead
171
172Closed pull requests should continue to settle when the optional day and on-merge settings are off. `pullRequestSettles` accepts `closed` independently and gates only `merged` on `autoSettleOnMerge` (`ThreadSettlementService.ts:80-92`). The frozen overlay preserves this behavior and its test at `ThreadSettlementService.test.ts.snapshot:230-238`. An unconditional early return when both optional settings are off would remove valid closed-PR behavior and is not recommended.
173
174## F14: checkpoint diff hydration is fixed
175
176Commit `8af5734365` changes checkpoint diff to call `getCheckpointContext` (`checkpointing/CheckpointDiffQuery.ts:102-121`). `ProjectionStore.getCheckpointContext` performs three narrow queries for run ID/ordinal/status, scope ID/run/kind/cwd, and checkpoint scope/run/app ordinal/status/ref (`orchestration-v2/ProjectionStore.ts:2957-3002`). It does not read transcript messages, turn items, provider histories, or fork ancestry.
177
178The focused checkpoint diff suite passes 5 tests. This fixes the prior read-cardinality issue. It does not fix F03 because the narrow query faithfully returns the production shared-scope ownership that the zero-baseline logic mishandles.
179
180The frozen ProjectionStore overlay retains the narrow query unchanged.
181
182## F15: startup recovery targeting is fixed
183
184Commit `d98f1d5e38` removes the prior recovery call over every active and archived shell. `ProviderRuntimeRecoveryService` now asks for `getRecoveryThreadIds("runtime")` and full-loads only those candidates (`ProviderRuntimeRecoveryService.ts:482-511`). The candidate SQL selects nonterminal runs, pending runtime requests, nonterminal sessions, provider-owned background work, relevant nonterminal turn items, and pending/running outbox entries (`ProjectionStore.ts:2696-2787`). The focused recovery tests confirm terminal histories are not loaded for runtime reconciliation.
185
186That fixes F15 as previously defined and recommended. It also removes repeated shared-session and fork-history hydration. The frozen overlay does not regress this path.
187
188## Separate startup corruption-verification tradeoff
189
190Startup still blocks command readiness on `projectionMaintenance.verify` (`serverRuntimeStartup.ts:518-553`). Verification calls `getUnreadableThreadIds` (`ProjectionMaintenance.ts:75-119`), which pages through all 16 canonical projection tables, selects each `payload_json`, and schema-decodes every row (`ProjectionStore.ts:2789-2952`). Messages, turn items, provider turns, checkpoints, active threads, archived threads, and terminal history are all included. Paging at 500 bounds peak row memory and each canonical row is decoded once, but total startup work remains O(all retained V2 projection rows).
191
192This is not the old F15 recovery behavior. It is an intentional safety check that detects corrupt canonical rows and propagates unreadability through fork ancestry before accepting commands. I did not benchmark its latency or memory impact, so it is not reported as a defect. Benchmark representative large databases before deciding whether the readiness cost is acceptable. Any optimization must preserve equivalent corruption detection, for example through validated incremental metadata or another complete verification strategy; simply removing or narrowing the scan would weaken recovery safety.
193
194The frozen overlay improves periodic settlement separately by adding `getSettlementCandidates` and narrow `getThread` reads. Migration 059 supplies the relevant indexes. It does not change startup verification.
195
196## PERSIST-N01: the scheduler ignores its due-task index every five seconds
197
198Priority: P2 performance.
199
200### Evidence
201
202`ScheduledTaskService.selectAllRows` selects every column of every scheduled task and orders the complete table by update time (`scheduledTasks/ScheduledTaskService.ts` at frozen HEAD, lines 175-199). `listTasksLenient` decodes every row's schedule, workspace strategy, and model selection (`:207-224`). `runDueTasks` calls that full-list path and only then filters enabled, due, and non-running tasks in memory (`:583-609`). The loop repeats every five seconds (`:665-669`).
203
204Migration 055 already creates `idx_scheduled_tasks_due(enabled, next_run_at)` with a partial predicate for enabled tasks having a next run (`persistence/Migrations/055_ScheduledTasks.ts:32-36`). The polling query has no `WHERE` clause, so it cannot use that index to bound candidates by `next_run_at`.
205
206### Trigger and consequence
207
208Accumulate many disabled tasks or enabled tasks scheduled far in the future. Even when nothing is due, the server reads, allocates, JSON-decodes, and sorts every row every five seconds. Cost is O(all scheduled tasks) per poll instead of O(due tasks), creating steady database and CPU load.
209
210This scheduler is branch-only at the compared main commit, so the issue is not main lag. It is a scaling defect in the added durable scheduling core. Query due rows directly with `enabled = 1`, non-null `next_run_at`, `next_run_at <= now`, and non-running status. Crash recovery can keep its separate one-time running-row query. Add a behavior/cardinality test that seeds many future/disabled rows and proves the poll decodes only due rows.
211
212## M10 late arrival: metadata and provider-control paths still hydrate history
213
214Priority: P2 performance and failure isolation. Status: missing at frozen committed HEAD. The frozen overlay adds useful groundwork but does not address the affected paths.
215
216Late main commit `6365919f2e5bcfb4fa4020b95e19af26ae40979f`, [#9758](https://github.com/pingdotgg/t3code/pull/9758), splits `ProviderCommandReactor` reads into thread shell and thread detail. Main now uses shell reads for session state, compaction guards, post-generation title checks, interrupt and recovery, approval and user-input forwarding, session stop, and runtime-mode propagation (`ProviderCommandReactor.ts` at that commit, lines 528-538, 572-608, 984-995, 1375-1385, 1442-1640, and 1700-1715). It keeps detail reads for the two operations that consume transcript data: title regeneration and turn start (`:1008-1058` and `:1178-1201`). Three changed behavior tests put invalid JSON in an old message and prove approval response, user-input response, and session stop no longer decode that unrelated body (`ProviderCommandReactor.test.ts` at that commit, lines 517-530, 3556-3595, 3597-3640, and 3846-3886).
217
218V2 distributes the same responsibilities across services, so copying the V1 reactor would be the wrong fix. The actual paths are:
219
220| Behavior | V2 committed path | Assessment |
221| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
222| Worktree and branch naming | `ThreadLaunchService.ts:194-213` generates from the supplied initial message without reading history. | Generation is already superseded by the V2 design. The workspace and rename results still dispatch `thread.metadata.update` through the full projection path at `:287-319`. |
223| Initial title generation | `ThreadTitleRegenerationService.ts:184-228` loads the whole projection to locate one initial message, then dispatches completion at `:169-182` and `:247-253`. | Missing. Initial generation needs one message and thread metadata, not the full transcript. |
224| Explicit title regeneration | The same service formats the transcript at `:190-228`. | Full history is intentional here, matching main's retained detail read. Only the stale guard and completion write should be narrow. |
225| Metadata and mode mutations | `Orchestrator.ts:1389-1425` routes `thread.metadata.update`, `thread.title.regeneration.complete`, and runtime/interaction mode changes through `getThreadProjection`. Their reducers use thread payload fields at `:1694-1721`; workspace and runtime-mode changes also inspect provider sessions to plan detaches at `:1884-1900`. `ThreadLifecycleService.ts:80-87` then reads the full projection again for its result. | Missing transcript isolation, but not every case is app-thread-only. Title completion and interaction mode need only the thread. Workspace changes and runtime mode need a narrow thread-plus-session context. None needs messages. |
226| Approval and user-input responses | `RuntimeRequestService.ts:76-125` loads the full projection to locate one runtime request before calling the live session. | Missing, but a narrow runtime-request lookup is required. The app-thread-only `getThread` read cannot replace this safely. |
227| Interrupt and restart | `ProviderTurnControlService.ts:80-162` loads the full projection to validate one provider thread and turn; restart polls the full projection again at `:211-231`. | Missing. This needs a narrow provider execution context, not a V1 shell port or an app-thread-only read. |
228| Session stop and detach | `Orchestrator.ts:2003-2053` loads the full projection to validate one provider session. `ProviderSessionManager.ts:1566-1605` loads it again when a multi-thread-capable runtime must interrupt attached active turns. | Missing transcript isolation. Preserve the exact session and active-turn checks in a narrow context. |
229| Provider turn start | `ProviderTurnStartService.ts:91-125` reads runs, nodes, attempts, messages, checkpoint scopes, handoffs, and provider state. | No gap. Main also keeps detail for turn start. |
230
231The committed generic mutation handler creates a concrete failure boundary. `getThreadProjection` selects every message payload for an unwindowed read (`ProjectionStore.ts:2398-2402`) and schema-decodes all of them (`:2515-2531`). A long thread therefore pays O(retained history) to rename a branch, update a title, complete title regeneration, or change runtime mode. One malformed old message also rejects the metadata command before it can emit its event. This is the same isolation problem that the new main tests cover, expressed through V2's projection tables.
232
233The dirty overlay is not a local-only fix for M10. `ProjectionStore.ts.snapshot:173-175` and `:3003-3019` add `getThread`, which selects and decodes only the app-thread payload. `Orchestrator.ts.snapshot:1389-1430` uses it for `thread.visit`, and `:6944-6975` uses it for the auto-settlement guard. However, `thread.metadata.update`, `thread.title.regeneration.complete`, and mode mutations remain in `dispatchThreadMutation`, whose first operation is still `getThreadProjection` (`Orchestrator.ts.snapshot:1432-1468` and `:6988-6995`). Runtime requests and provider-turn control are unchanged in the frozen overlay.
234
235The smallest V2-shaped fix is command-specific. Route title-only metadata, title completion, and interaction-mode mutations through the overlay's `getThread`. Give workspace metadata and runtime-mode mutations a narrow context containing the app thread plus the provider-session fields their detach logic uses. Initial title generation can use a narrow message lookup plus app-thread metadata. Runtime-request response, session detach, and provider-turn control need narrow queries keyed by their existing IDs. Keep full or purpose-built context reads for explicit title regeneration and turn start. Regression coverage should corrupt an unrelated historical message and prove metadata update, title completion, runtime-request response, interrupt, and session stop remain usable without weakening stale-request, detach, or target-identity checks.
236
237## Feature parity matrix
238
239| Area | Main or intended behavior | Frozen committed branch | Frozen overlay | Assessment |
240| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------ |
241| Fresh/main migration | Main 047 advances into V2 048 | Works | 058 to 059 probe passes | Parity for fresh/current-main histories |
242| Historical V2 upgrade | Supported old branch state should start | 052/053/055 cohorts fail | Still fail below 059 | F01 open |
243| Legacy shell import | Import metadata cheaply and hydrate transcript on demand | Preserved and idempotent | Unchanged | Pass |
244| Projection recovery targeting | Load only unfinished/recoverable threads | Candidate SQL after `d98f1d5e38` | Unchanged | Pass |
245| Projection startup validation | Detect corrupt projection rows before readiness | Paged decode of every canonical row | Unchanged | Unmeasured safety/performance tradeoff; benchmark and decide |
246| Outbox recovery | Requeue durable work, retire process-bound work, drain before readiness | Preserved; focused recovery tests pass | Unchanged | Pass within tested cases |
247| Checkpoint diff read model | Narrow metadata lookup | Three narrow queries | Preserved | F14 fixed |
248| Full diff after run 2 | Resolve the real baseline for shared scope | Requires run-1-owned scope | Unchanged | F03 open |
249| Checkpoint rollback | Validate state and restore through typed service | Focused rollback tests pass | Unchanged | Pass within tested cases |
250| WS project create/update | Preserve typed fields | Preserves current fields | Unchanged | Pass |
251| WS forced project delete | Validate and delete without partial commit | Split V2 deletes then force-less V1 delete | Unchanged | F04 open |
252| HTTP project mutation | Same semantics as WS | Drops fields and V2 cleanup | Unchanged | F05 open |
253| Live CLI project mutation | Same as typed server lifecycle | Inherits HTTP divergence | Unchanged | F05 open |
254| Offline CLI project mutation | Same lifecycle without server | Direct reduced `ProjectService` mapping | Unchanged | F05 open |
255| V2 HTTP thread reads | Bounded shell/detail/history reads | Uses V2 projection services | Overlay narrows visit/settlement metadata | Pass in scope |
256| Metadata/provider command reads after [#9758](https://github.com/pingdotgg/t3code/pull/9758) | Shell or narrow context unless transcript is consumed | Generic metadata mutations and provider control hydrate full history | `getThread` exists but is used only for visit and the auto-settlement guard | M10 missing; overlay is partial groundwork |
257| Snooze ordering | Only later failure/completion wakes early | Completion ordered, failure unordered | Still unordered | F13 open |
258| Closed-PR settlement | Closed PR settles independently of optional flags | Preserved | Preserved | Pass; false positive rejected |
259| Settlement query cost | Avoid full shell/history reads per sweep | Full shell snapshot at committed HEAD | Narrow candidate and metadata reads | Local-only improvement |
260| Durable schedule calculation | Fixed-time/interval semantics and missed-run handling | Focused tests pass | Unchanged | Pass |
261| Durable schedule polling | Query only due work | Full table decode every 5 seconds | Unchanged | PERSIST-N01 |
262| Git/VCS status used by settlement | Local status should not pay unnecessary divergence walks | Full local status at committed HEAD | `--no-ahead-behind`; remote status retains divergence/PR lookup | Local-only improvement; no correctness regression found |
263
264## Frozen overlay review
265
266I inspected the requested files separately from committed evidence:
267
268- `Orchestrator.ts.snapshot` moves visits and auto-settlement guards to the new narrow thread metadata read. It preserves forward-only `lastVisitedAt`, deletion checks, and the settlement snapshot guard.
269- `ProjectionStore.ts.snapshot` adds narrow thread and settlement candidate queries. Its SQL excludes archived, deleted, overridden, pinned, active-run, and pending-request rows before loading candidate background data. The paired SQL/memory parity test passes. F13 remains because failed ordering is policy logic after the query.
270- `ThreadSettlementService.ts.snapshot` consumes the candidate query instead of the full shell snapshot. Closed-PR behavior remains correct.
271- `Migrations.ts.snapshot` and `059_OrchestrationV2ShellIndexes.ts.snapshot` add indexes used by the settlement candidate path. The committed-058 to local-059 upgrade probe passes.
272- `GitManager.ts.snapshot`, `GitVcsDriver.ts.snapshot`, and `GitVcsDriverCore.ts.snapshot` let local-only status skip ahead/behind revision walks. Remote status still computes upstream divergence and PR association. The review/action path still requests full status. I found no lifecycle or settlement correctness regression from this change.
273- `server.ts.snapshot` supplies `ProjectionStoreV2.layer` to the application layer required by the new settlement dependency.
274- The new `getThread` primitive is suitable for M10's title-only, title-completion, and other app-thread-only handlers, but the overlay has not routed them through it. It cannot replace workspace/runtime-mode, provider-session, provider-turn, or runtime-request lookups because those need child records.
275
276The overlay's `ProjectionSettlement.test.ts.snapshot` is substantive behavior coverage. It compares SQL and memory candidates, tests background work parity, corrupt historical payload isolation, and query-plan index use. The live version passed. It does not test failure-before-snooze, and the existing settlement unit test currently encodes the opposite behavior.
277
278## Focused validation
279
280No repository-wide check was run. Unique product coverage was 10 files and 52 tests, all passing:
281
282- checkpoint diff, checkpoint rollback, legacy import;
283- projection recovery and provider runtime recovery;
284- schedule calculation;
285- project HTTP error mapping and CLI empty-project lifecycle;
286- live dirty-overlay settlement policy and SQL/memory projection settlement.
287
288The audit-only probe has exactly 6 passing cases:
289
290| Probe name | Result |
291| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
292| `upgrades committed-058 state through the live-059 overlay` | Passed |
293| `reproduces the old-052 cohort collision against the current migrator` | Passed; asserted the expected current-053 migration failure and rollback |
294| `reproduces the old-055 cohort collision and skipped main columns` | Passed; asserted the expected current-056 migration failure and absent main columns |
295| `reproduces the prior-audit old-053 cohort collision` | Passed; asserted the expected current-056 migration failure and rollback |
296| `reproduces root checkpoint-scope reuse across ordinary runs` | Passed; one shared scope remained owned by run 2 |
297| `reproduces a stale failed run waking a later snooze` | Passed; production policy returned `true` for the reproduced stale-failure state |
298
299Exact totals for consolidation: 10 unique product test files with 52 tests passed, plus 1 audit-only probe file with 6 tests passed. Combined, 11 unique test files and 58 tests passed. Repeated authoring/rerun invocations are not counted as additional coverage.
300
301The exact commands and results are in `persistence-tests.log`. The probe is `persistence-audit-probes.test.ts`. It uses in-memory SQLite and actual implementations. No live T3 userdata was opened.
302
303The earlier copied-test import failure reported by root was caused by unsuffixed audit snapshots entering Vitest discovery. All frozen overlay copies now end in `.snapshot`; none was discovered in these runs. It is not a product regression.
304
305## Limitations
306
307- I did not launch a server, provider, browser, simulator, or live/offline CLI process. The project lifecycle findings are deterministic handler/service/decider traces; integrated disposable transport tests remain necessary with a fix.
308- I did not benchmark startup verification or scheduler polling. Startup verification is recorded only as a safety/performance tradeoff; PERSIST-N01 is based on explicit polling SQL and loop cardinality, not measured latency or memory.
309- M10 is a source-only review of late main commit `6365919f2e5bcfb4fa4020b95e19af26ae40979f` and its direct V2 equivalents. I ran no additional tests for this addendum.
310- Migration probes cover three known complete historical manifests. Other partial stopping points still need a table-driven compatibility matrix.
311- I did not modify existing tests. Current green tests do not contradict F03, F04, F05, or F13 because their fixtures omit the production trigger or encode the faulty behavior.
312- Provider adapter protocol behavior, auth/relay, title retry, PR metadata, WebSocket fallback F16, and the separately reviewed archived-thread scheduler/MCP lead are outside this report.
audits/orchestrator-v2/2026-09-04/pr-metadata-final.json+257−0
@@ -0,0 +1,257 @@
1{
2 "baseRefName": "main",
3 "headRefOid": "8af5734365f7c45bc08b57066dbae42f9f7d4235",
4 "number": 2829,
5 "state": "OPEN",
6 "statusCheckRollup": [
7 {
8 "__typename": "CheckRun",
9 "completedAt": "2026-09-04T18:25:05Z",
10 "conclusion": "FAILURE",
11 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744766",
12 "name": "Check",
13 "startedAt": "2026-09-04T18:23:36Z",
14 "status": "COMPLETED",
15 "workflowName": "CI"
16 },
17 {
18 "__typename": "CheckRun",
19 "completedAt": "2026-09-04T18:23:27Z",
20 "conclusion": "SKIPPED",
21 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624915/job/101129746493",
22 "name": "Build macOS Apple Silicon preview",
23 "startedAt": "2026-09-04T18:23:27Z",
24 "status": "COMPLETED",
25 "workflowName": "Desktop macOS Preview"
26 },
27 {
28 "__typename": "CheckRun",
29 "completedAt": "2026-09-04T18:23:27Z",
30 "conclusion": "SKIPPED",
31 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624962/job/101129746252",
32 "name": "EAS Preview",
33 "startedAt": "2026-09-04T18:23:27Z",
34 "status": "COMPLETED",
35 "workflowName": "Mobile EAS Preview"
36 },
37 {
38 "__typename": "CheckRun",
39 "completedAt": "2026-09-04T18:23:27Z",
40 "conclusion": "SKIPPED",
41 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624883/job/101129745113",
42 "name": "Deploy web preview",
43 "startedAt": "2026-09-04T18:23:27Z",
44 "status": "COMPLETED",
45 "workflowName": "Web Preview"
46 },
47 {
48 "__typename": "CheckRun",
49 "completedAt": "2026-09-04T18:24:30Z",
50 "conclusion": "SUCCESS",
51 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624996/job/101129747755",
52 "name": "Native fingerprint diff",
53 "startedAt": "2026-09-04T18:23:36Z",
54 "status": "COMPLETED",
55 "workflowName": "Mobile Fingerprint Check"
56 },
57 {
58 "__typename": "CheckRun",
59 "completedAt": "2026-09-04T18:23:33Z",
60 "conclusion": "SUCCESS",
61 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622732/job/101129737618",
62 "name": "Prepare PR size config",
63 "startedAt": "2026-09-04T18:23:28Z",
64 "status": "COMPLETED",
65 "workflowName": "PR Size"
66 },
67 {
68 "__typename": "CheckRun",
69 "completedAt": "2026-09-04T18:23:29Z",
70 "conclusion": "SUCCESS",
71 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622705/job/101129737077",
72 "name": "Collect PR targets",
73 "startedAt": "2026-09-04T18:23:27Z",
74 "status": "COMPLETED",
75 "workflowName": "PR Vouch"
76 },
77 {
78 "__typename": "CheckRun",
79 "completedAt": "2026-09-04T18:23:27Z",
80 "conclusion": "SKIPPED",
81 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624915/job/101129745554",
82 "name": "Remove preview download",
83 "startedAt": "2026-09-04T18:23:27Z",
84 "status": "COMPLETED",
85 "workflowName": "Desktop macOS Preview"
86 },
87 {
88 "__typename": "CheckRun",
89 "completedAt": "2026-09-04T18:23:33Z",
90 "conclusion": "SKIPPED",
91 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622732/job/101129775867",
92 "name": "Sync PR size label definitions",
93 "startedAt": "2026-09-04T18:23:34Z",
94 "status": "COMPLETED",
95 "workflowName": "PR Size"
96 },
97 {
98 "__typename": "CheckRun",
99 "completedAt": "2026-09-04T18:25:06Z",
100 "conclusion": "SUCCESS",
101 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744778",
102 "name": "Test",
103 "startedAt": "2026-09-04T18:23:37Z",
104 "status": "COMPLETED",
105 "workflowName": "CI"
106 },
107 {
108 "__typename": "CheckRun",
109 "completedAt": "2026-09-04T18:23:43Z",
110 "conclusion": "SUCCESS",
111 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622705/job/101129757508",
112 "name": "Label PR 2829",
113 "startedAt": "2026-09-04T18:23:32Z",
114 "status": "COMPLETED",
115 "workflowName": "PR Vouch"
116 },
117 {
118 "__typename": "CheckRun",
119 "completedAt": "2026-09-04T18:23:27Z",
120 "conclusion": "SKIPPED",
121 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624915/job/101129746083",
122 "name": "Publish anonymous download",
123 "startedAt": "2026-09-04T18:23:27Z",
124 "status": "COMPLETED",
125 "workflowName": "Desktop macOS Preview"
126 },
127 {
128 "__typename": "CheckRun",
129 "completedAt": "2026-09-04T18:26:43Z",
130 "conclusion": "SUCCESS",
131 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744870",
132 "name": "Test Server 1",
133 "startedAt": "2026-09-04T18:23:35Z",
134 "status": "COMPLETED",
135 "workflowName": "CI"
136 },
137 {
138 "__typename": "CheckRun",
139 "completedAt": "2026-09-04T18:23:54Z",
140 "conclusion": "SUCCESS",
141 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622732/job/101129772818",
142 "name": "Label PR size",
143 "startedAt": "2026-09-04T18:23:36Z",
144 "status": "COMPLETED",
145 "workflowName": "PR Size"
146 },
147 {
148 "__typename": "CheckRun",
149 "completedAt": "2026-09-04T18:26:01Z",
150 "conclusion": "SUCCESS",
151 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744788",
152 "name": "Test Server 2",
153 "startedAt": "2026-09-04T18:23:36Z",
154 "status": "COMPLETED",
155 "workflowName": "CI"
156 },
157 {
158 "__typename": "CheckRun",
159 "completedAt": "2026-09-04T18:33:55Z",
160 "conclusion": "CANCELLED",
161 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744689",
162 "name": "Test Server 3",
163 "startedAt": "2026-09-04T18:23:34Z",
164 "status": "COMPLETED",
165 "workflowName": "CI"
166 },
167 {
168 "__typename": "CheckRun",
169 "completedAt": "2026-09-04T18:24:03Z",
170 "conclusion": "SUCCESS",
171 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744861",
172 "name": "Rust",
173 "startedAt": "2026-09-04T18:23:36Z",
174 "status": "COMPLETED",
175 "workflowName": "CI"
176 },
177 {
178 "__typename": "CheckRun",
179 "completedAt": "2026-09-04T18:23:45Z",
180 "conclusion": "SUCCESS",
181 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744696",
182 "name": "Mobile Native Changes",
183 "startedAt": "2026-09-04T18:23:35Z",
184 "status": "COMPLETED",
185 "workflowName": "CI"
186 },
187 {
188 "__typename": "CheckRun",
189 "completedAt": "2026-09-04T18:24:57Z",
190 "conclusion": "SUCCESS",
191 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744508",
192 "name": "Release Smoke",
193 "startedAt": "2026-09-04T18:23:34Z",
194 "status": "COMPLETED",
195 "workflowName": "CI"
196 },
197 {
198 "__typename": "CheckRun",
199 "completedAt": "2026-09-04T18:25:00Z",
200 "conclusion": "SUCCESS",
201 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129836308",
202 "name": "Mobile Native Static Analysis",
203 "startedAt": "2026-09-04T18:24:05Z",
204 "status": "COMPLETED",
205 "workflowName": "CI"
206 },
207 {
208 "__typename": "CheckRun",
209 "completedAt": "2026-09-04T18:26:19Z",
210 "conclusion": "NEUTRAL",
211 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129651357",
212 "name": "Macroscope - Approvability Check",
213 "startedAt": "2026-09-04T18:23:08Z",
214 "status": "COMPLETED",
215 "workflowName": ""
216 },
217 {
218 "__typename": "CheckRun",
219 "completedAt": "2026-09-04T18:23:46Z",
220 "conclusion": "SKIPPED",
221 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129648791",
222 "name": "Macroscope - Correctness Check",
223 "startedAt": "2026-09-04T18:23:07Z",
224 "status": "COMPLETED",
225 "workflowName": ""
226 },
227 {
228 "__typename": "CheckRun",
229 "completedAt": "2026-09-04T18:23:39Z",
230 "conclusion": "SKIPPED",
231 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129783475",
232 "name": "Macroscope - Effect Service Conventions",
233 "startedAt": "2026-09-04T18:23:35Z",
234 "status": "COMPLETED",
235 "workflowName": ""
236 },
237 {
238 "__typename": "CheckRun",
239 "completedAt": "2026-09-04T18:23:40Z",
240 "conclusion": "SKIPPED",
241 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129786339",
242 "name": "Macroscope - UI Consistency",
243 "startedAt": "2026-09-04T18:23:36Z",
244 "status": "COMPLETED",
245 "workflowName": ""
246 },
247 {
248 "__typename": "StatusContext",
249 "context": "CodeRabbit",
250 "startedAt": "2026-09-04T18:23:29Z",
251 "state": "SUCCESS",
252 "targetUrl": ""
253 }
254 ],
255 "updatedAt": "2026-09-04T18:52:35Z",
256 "url": "https://github.com/pingdotgg/t3code/pull/2829"
257}
audits/orchestrator-v2/2026-09-04/pr-metadata.json+261−0
@@ -0,0 +1,261 @@
1{
2 "baseRefName": "main",
3 "body": "## Summary\n- wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances\n- add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates\n- update debugger model/provider picker and improve user-facing orchestration errors\n\n## Validation\n- bun fmt\n- bun lint\n- bun typecheck\n- bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent\n- bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts\n- bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude\n\n## Notes\n- Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.\n\n## Closes\n\nVerified against the branch with code/commit evidence.\n\n### High confidence\nCloses #4952\nCloses #4873\nCloses #4775\nCloses #4795\nCloses #4710\nCloses #4668\nCloses #4619\nCloses #4584\nCloses #4561\nCloses #4713\nCloses #4198\nCloses #4452\nCloses #3797\nCloses #4232\nCloses #3666\nCloses #3580\nCloses #2785\nCloses #2789\nCloses #3138\nCloses #1404\nCloses #231\nCloses #216\n\n### Medium confidence (under review)\nCloses #4568\nCloses #4766\nCloses #4495\nCloses #4456\nCloses #4399\nCloses #3744\nCloses #2921\nCloses #3624\nCloses #3149\nCloses #2336\nCloses #538\nCloses #2173\nCloses #2065\n\n<!-- Macroscope's pull request summary starts here -->\n<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->\n<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->\n> [!NOTE]\n> ### Introduce orchestration V2 runtime, provider adapters, MCP toolkits, and scheduled tasks\n> - Adds a complete orchestration V2 runtime: event store, projection store, effect outbox/worker, run execution, checkpoint capture/rollback, context handoff, thread launch/lifecycle/fork services, runtime policy, provider session management, and runtime recovery\n> - Adds V2 provider adapter framework with built-in adapters for Claude, Codex, Cursor, Grok, OpenCode, Antigravity, and ACP Registry, all sharing a common ACP or SDK integration layer\n> - Adds orchestrator and worktree MCP toolkits with typed tool definitions, handlers, and HTTP registration\n> - Adds a scheduled-task service with interval and fixed-time schedules, persistence, live subscriptions, and CRUD operations\n> - Adds database migrations 048–058 for V2 event/projection schema, subagents, provider-session bindings, thread-launch workflows, application event sourcing, effect cancellation, scheduled tasks, legacy import state, and recovery indexes\n> - Adds a legacy V1 thread importer for one-way migration of existing thread shells and transcripts into V2\n> - Migrates web and mobile clients, shared packages, and client-runtime state to consume V2 projections, shells, and runtime models instead of the legacy session/turn model\n> - Risk: replaces the legacy orchestration engine's thread handling with V2; the old `OrchestrationEngine` now handles project commands only and no longer performs thread auto-settlement, user-input activity lookup, or thread replay. Cursor settings no longer define `binaryPath` or `apiEndpoint`. Provider instances expose `orchestrationAdapter` instead of `adapter`.\n>\n> <!-- Macroscope's review summary starts here -->\n>\n> <sup><a href=\"https://app.macroscope.com\">Macroscope</a> summarized 8af5734.</sup>\n> <!-- Macroscope's review summary ends here -->\n>\n<!-- Macroscope's pull request summary ends here -->\n\n<!-- CURSOR_SUMMARY -->\n---\n\n> [!NOTE]\n> **Medium Risk**\n> Mobile thread persistence and list/archive/stop behavior change with V2 runtime semantics; removing the thread-transfer report workflow reduces PR visibility into transfer budget regressions.\n> \n> **Overview**\n> This slice of the orchestration V2 rollout **retires the thread-transfer PR comment pipeline** (trusted publisher script, tests, and `workflow_run` workflow) while CI can still emit transfer artifacts; it also **installs `build-essential` in CI** so ACP process-tree fixtures compile instead of soft-skipping.\n> \n> **Mobile** moves onto shared V2 client-runtime pieces: SQLite cache uses `ORCHESTRATION_CACHE_SCHEMA_VERSION` and stored V2 shell/thread snapshots, runtime wiring swaps in bounded thread snapshot loading and history control, and thread detail/review/archive flows read **projections** (`runtime`, `RuntimeRequestId`, checkpoint summaries from `runId`) instead of V1 session/turn shapes. UX additions include **activity inspector**, **queue control**, **relationships banner**, progressive **history controls**, server **visit** watermarking, stricter **archive** rules via `threadCanArchive`, and approval/user-input cards that honor **live vs dead** provider `responseCapability`.\n> \n> Smaller touches: shared **brand mark** module, new **uniwind adaptive color** tokens, desktop env test for user-data dir names, README link to appearance docs, and marketing copy for Cursor harness.\n> \n> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9eeed8c17109be3752d2f8e1643583d1f579ea2d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>\n<!-- /CURSOR_SUMMARY -->\n\n",
4 "headRefName": "t3code/codex-turn-mapping",
5 "headRefOid": "8af5734365f7c45bc08b57066dbae42f9f7d4235",
6 "isDraft": false,
7 "number": 2829,
8 "state": "OPEN",
9 "statusCheckRollup": [
10 {
11 "__typename": "CheckRun",
12 "completedAt": "2026-09-04T18:25:05Z",
13 "conclusion": "FAILURE",
14 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744766",
15 "name": "Check",
16 "startedAt": "2026-09-04T18:23:36Z",
17 "status": "COMPLETED",
18 "workflowName": "CI"
19 },
20 {
21 "__typename": "CheckRun",
22 "completedAt": "2026-09-04T18:23:27Z",
23 "conclusion": "SKIPPED",
24 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624915/job/101129746493",
25 "name": "Build macOS Apple Silicon preview",
26 "startedAt": "2026-09-04T18:23:27Z",
27 "status": "COMPLETED",
28 "workflowName": "Desktop macOS Preview"
29 },
30 {
31 "__typename": "CheckRun",
32 "completedAt": "2026-09-04T18:23:27Z",
33 "conclusion": "SKIPPED",
34 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624962/job/101129746252",
35 "name": "EAS Preview",
36 "startedAt": "2026-09-04T18:23:27Z",
37 "status": "COMPLETED",
38 "workflowName": "Mobile EAS Preview"
39 },
40 {
41 "__typename": "CheckRun",
42 "completedAt": "2026-09-04T18:23:27Z",
43 "conclusion": "SKIPPED",
44 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624883/job/101129745113",
45 "name": "Deploy web preview",
46 "startedAt": "2026-09-04T18:23:27Z",
47 "status": "COMPLETED",
48 "workflowName": "Web Preview"
49 },
50 {
51 "__typename": "CheckRun",
52 "completedAt": "2026-09-04T18:24:30Z",
53 "conclusion": "SUCCESS",
54 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624996/job/101129747755",
55 "name": "Native fingerprint diff",
56 "startedAt": "2026-09-04T18:23:36Z",
57 "status": "COMPLETED",
58 "workflowName": "Mobile Fingerprint Check"
59 },
60 {
61 "__typename": "CheckRun",
62 "completedAt": "2026-09-04T18:23:33Z",
63 "conclusion": "SUCCESS",
64 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622732/job/101129737618",
65 "name": "Prepare PR size config",
66 "startedAt": "2026-09-04T18:23:28Z",
67 "status": "COMPLETED",
68 "workflowName": "PR Size"
69 },
70 {
71 "__typename": "CheckRun",
72 "completedAt": "2026-09-04T18:23:29Z",
73 "conclusion": "SUCCESS",
74 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622705/job/101129737077",
75 "name": "Collect PR targets",
76 "startedAt": "2026-09-04T18:23:27Z",
77 "status": "COMPLETED",
78 "workflowName": "PR Vouch"
79 },
80 {
81 "__typename": "CheckRun",
82 "completedAt": "2026-09-04T18:23:27Z",
83 "conclusion": "SKIPPED",
84 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624915/job/101129745554",
85 "name": "Remove preview download",
86 "startedAt": "2026-09-04T18:23:27Z",
87 "status": "COMPLETED",
88 "workflowName": "Desktop macOS Preview"
89 },
90 {
91 "__typename": "CheckRun",
92 "completedAt": "2026-09-04T18:23:33Z",
93 "conclusion": "SKIPPED",
94 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622732/job/101129775867",
95 "name": "Sync PR size label definitions",
96 "startedAt": "2026-09-04T18:23:34Z",
97 "status": "COMPLETED",
98 "workflowName": "PR Size"
99 },
100 {
101 "__typename": "CheckRun",
102 "completedAt": "2026-09-04T18:25:06Z",
103 "conclusion": "SUCCESS",
104 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744778",
105 "name": "Test",
106 "startedAt": "2026-09-04T18:23:37Z",
107 "status": "COMPLETED",
108 "workflowName": "CI"
109 },
110 {
111 "__typename": "CheckRun",
112 "completedAt": "2026-09-04T18:23:43Z",
113 "conclusion": "SUCCESS",
114 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622705/job/101129757508",
115 "name": "Label PR 2829",
116 "startedAt": "2026-09-04T18:23:32Z",
117 "status": "COMPLETED",
118 "workflowName": "PR Vouch"
119 },
120 {
121 "__typename": "CheckRun",
122 "completedAt": "2026-09-04T18:23:27Z",
123 "conclusion": "SKIPPED",
124 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624915/job/101129746083",
125 "name": "Publish anonymous download",
126 "startedAt": "2026-09-04T18:23:27Z",
127 "status": "COMPLETED",
128 "workflowName": "Desktop macOS Preview"
129 },
130 {
131 "__typename": "CheckRun",
132 "completedAt": "2026-09-04T18:26:43Z",
133 "conclusion": "SUCCESS",
134 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744870",
135 "name": "Test Server 1",
136 "startedAt": "2026-09-04T18:23:35Z",
137 "status": "COMPLETED",
138 "workflowName": "CI"
139 },
140 {
141 "__typename": "CheckRun",
142 "completedAt": "2026-09-04T18:23:54Z",
143 "conclusion": "SUCCESS",
144 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905622732/job/101129772818",
145 "name": "Label PR size",
146 "startedAt": "2026-09-04T18:23:36Z",
147 "status": "COMPLETED",
148 "workflowName": "PR Size"
149 },
150 {
151 "__typename": "CheckRun",
152 "completedAt": "2026-09-04T18:26:01Z",
153 "conclusion": "SUCCESS",
154 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744788",
155 "name": "Test Server 2",
156 "startedAt": "2026-09-04T18:23:36Z",
157 "status": "COMPLETED",
158 "workflowName": "CI"
159 },
160 {
161 "__typename": "CheckRun",
162 "completedAt": "2026-09-04T18:33:55Z",
163 "conclusion": "CANCELLED",
164 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744689",
165 "name": "Test Server 3",
166 "startedAt": "2026-09-04T18:23:34Z",
167 "status": "COMPLETED",
168 "workflowName": "CI"
169 },
170 {
171 "__typename": "CheckRun",
172 "completedAt": "2026-09-04T18:24:03Z",
173 "conclusion": "SUCCESS",
174 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744861",
175 "name": "Rust",
176 "startedAt": "2026-09-04T18:23:36Z",
177 "status": "COMPLETED",
178 "workflowName": "CI"
179 },
180 {
181 "__typename": "CheckRun",
182 "completedAt": "2026-09-04T18:23:45Z",
183 "conclusion": "SUCCESS",
184 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744696",
185 "name": "Mobile Native Changes",
186 "startedAt": "2026-09-04T18:23:35Z",
187 "status": "COMPLETED",
188 "workflowName": "CI"
189 },
190 {
191 "__typename": "CheckRun",
192 "completedAt": "2026-09-04T18:24:57Z",
193 "conclusion": "SUCCESS",
194 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129744508",
195 "name": "Release Smoke",
196 "startedAt": "2026-09-04T18:23:34Z",
197 "status": "COMPLETED",
198 "workflowName": "CI"
199 },
200 {
201 "__typename": "CheckRun",
202 "completedAt": "2026-09-04T18:25:00Z",
203 "conclusion": "SUCCESS",
204 "detailsUrl": "https://github.com/pingdotgg/t3code/actions/runs/33905624991/job/101129836308",
205 "name": "Mobile Native Static Analysis",
206 "startedAt": "2026-09-04T18:24:05Z",
207 "status": "COMPLETED",
208 "workflowName": "CI"
209 },
210 {
211 "__typename": "CheckRun",
212 "completedAt": "2026-09-04T18:26:19Z",
213 "conclusion": "NEUTRAL",
214 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129651357",
215 "name": "Macroscope - Approvability Check",
216 "startedAt": "2026-09-04T18:23:08Z",
217 "status": "COMPLETED",
218 "workflowName": ""
219 },
220 {
221 "__typename": "CheckRun",
222 "completedAt": "2026-09-04T18:23:46Z",
223 "conclusion": "SKIPPED",
224 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129648791",
225 "name": "Macroscope - Correctness Check",
226 "startedAt": "2026-09-04T18:23:07Z",
227 "status": "COMPLETED",
228 "workflowName": ""
229 },
230 {
231 "__typename": "CheckRun",
232 "completedAt": "2026-09-04T18:23:39Z",
233 "conclusion": "SKIPPED",
234 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129783475",
235 "name": "Macroscope - Effect Service Conventions",
236 "startedAt": "2026-09-04T18:23:35Z",
237 "status": "COMPLETED",
238 "workflowName": ""
239 },
240 {
241 "__typename": "CheckRun",
242 "completedAt": "2026-09-04T18:23:40Z",
243 "conclusion": "SKIPPED",
244 "detailsUrl": "https://github.com/pingdotgg/t3code/pull/2829/checks?check_run_id=101129786339",
245 "name": "Macroscope - UI Consistency",
246 "startedAt": "2026-09-04T18:23:36Z",
247 "status": "COMPLETED",
248 "workflowName": ""
249 },
250 {
251 "__typename": "StatusContext",
252 "context": "CodeRabbit",
253 "startedAt": "2026-09-04T18:23:29Z",
254 "state": "SUCCESS",
255 "targetUrl": ""
256 }
257 ],
258 "title": "feat(orchestrator): introduce new orchestrator",
259 "updatedAt": "2026-09-04T18:52:35Z",
260 "url": "https://github.com/pingdotgg/t3code/pull/2829"
261}
audits/orchestrator-v2/2026-09-04/prior-audit-range-diff-summary.txt+336−0
@@ -0,0 +1,336 @@
1 1: 15dff312363 < -: ----------- chore(ov2): preserve the integration base for replay
2 -: ----------- > 1: b8b7f894fcd chore(ov2): preserve the integration base for replay
3 2: e911c41199e ! 2: cc2e380a1ed Integrate Codex app-server support
4 3: 77a63c13b82 ! 3: 8852b949785 nit
5 4: 4da42ca393a ! 4: 3fe3534dbef revert more
6 5: d914dc03290 = 5: fdf63358fa2 resynclock
7 6: ba5d340a047 < -: ----------- Delay Codex provider availability until checked
8 7: 4c8648f791f = 6: 773f789c828 Return Cursor ACP runtime with explicit scope
9 8: a5270231419 = 7: f355bd32b10 Normalize Codex IDs and preserve streamed stdout decoding
10 9: 99e5494242a ! 8: c41b812f43f Scope Codex session runtime lifetimes
11 10: 3bded8ba565 = 9: faf4d6c04e2 decoders
12 11: 3182df2608d = 10: a1d85aa3331 Flush native logs on adapter shutdown
13 12: 74942087a35 ! 11: ce08dee1407 Switch Codex provider checks to app-server probe
14 13: fa13e794bbe ! 12: d2a22ec1652 Address Codex review feedback
15 14: 783f4f6e8d2 = 13: 99de1c8833e Add orchestration v2 docs and probe fixtures
16 15: eb8af12dca3 = 14: 28999d55151 Add orchestration v2 replay and service contracts
17 16: 75120285ae7 = 15: 67d40b8e8ee Map Codex turns into orchestration v2
18 17: 5e551ef7227 ! 16: 41937c522ee Implement orchestration v2 runtime
19 18: ca9ba20375c = 17: 959c0e22e73 Add thread fork lineage and lazy context transfer
20 19: 76dc095de6a = 18: 5169fe6083b Add orchestration V2 backend checklist
21 20: be1d5acc702 = 19: 88b265dab63 Add merge-back context handoff support
22 21: 052c24ec16d = 20: 3a75dbb5a18 Add V2 command capability policy
23 22: b460c064415 = 21: 5c6af6923c2 Add Claude replay fixture recorder
24 23: 166fd1c53cf = 22: 05d619192a4 Extract Claude SDK query runner from provider adapter
25 24: d4cd5427996 = 23: f806017a3c6 Add model selection to orchestration runs
26 25: 41c5f212a75 = 24: c71caf0f964 Map Claude replay fixtures to multi-turn turns
27 26: c1e8a125374 = 25: d9f4e700c07 Map Claude turns to runtime query policies
28 27: 617f3279952 = 26: 1be35b30fa1 Support active Claude steering and turn replay mapping
29 28: 2182b71bfab = 27: ebf98e42177 Add turn-interrupt replay coverage and protocol logging
30 29: b76bbc23de9 = 28: ec935be6a56 Document Cursor SDK MCP projection for V2
31 30: 631219bca58 = 29: bf94a66361a feat(orchestration-v2): wire claude adapter primitives
32 31: e2ba0751f78 = 30: 1ed757ba0c5 feat(orchestration-v2): support cross-provider handoff
33 32: 2e38197d2b2 = 31: 69cba683fa0 fix(orchestration-v2): resolve cross-provider forks
34 33: 08430b8115f = 32: 86f785623b1 feat(orchestration-v2): add merge-back replay coverage
35 34: 98e98085afd = 33: 28cc526e1f8 fix(orchestration-v2): compose provider switch merge context
36 35: 73fe5fd8780 = 34: 3c06f0c2c9c fix(orchestration-v2): preserve source history on merged switch
37 36: a4a63953eda ! 35: 43910683671 Map orchestration v2 WS methods to auth scopes
38 37: 7078dac915d = 36: d175a63a1bc feat(orchestration-v2): model native subagents
39 38: d855d91f0a3 ! 37: 5dec17fc19c wip
40 39: 6b81144d2cd = 38: a244d5c8951 refactor(orchestration-v2): adopt host process spawn policy
41 40: 3056bfd7c01 = 39: 8cc9df27fa7 Add orchestration MCP toolkit
42 41: 8dcf191a942 ! 40: 26e930ea408 Add Cursor SDK orchestration replay support
43 42: 4ff48c09838 = 41: 37045a02fe9 Handle segmented Cursor turns and stable visible timelines
44 43: ad109c26f5a ! 42: b43bc2b62c4 Add ACP replay harness and session lifecycle support
45 44: e35661a8497 = 43: 7cfebe0be47 Add MCP thread management and Codex turn mapping
46 45: a115e55b347 = 44: 915e6b385a2 Add Orchestration V2 application integration plan
47 46: fb1ebd0f2f7 ! 45: dab646dc400 Map orchestration turns to provider instances
48 47: 65345a5bb44 = 46: f1d7d1c1226 Share Codex sessions across orchestration threads
49 48: 74115dcb013 = 47: 9caaa2e23f8 Align orchestration V2 with Effect service conventions
50 49: 900c8ec2778 = 48: 5bac980a78e Start orchestration V2 application services
51 50: 3f52b6a9ec1 = 49: 4d85c0c89bd Complete orchestration V2 application services
52 51: e9e19735a94 = 50: 591c8b76b6e Require MCP registry for V2 provider sessions
53 52: 708aed97c9f = 51: 7ba8f57840a Remove MCP credential expiration
54 53: f254d805e70 = 52: 418deadc726 Guarantee MCP revocation during session release
55 54: b56d0e7d53f ! 53: c0bb02d6da0 Integrate orchestration v2 with the application runtime
56 55: c9dec101819 = 54: 78b451958a7 Split the V2 frontend plan into parity and enrichment phases
57 56: b1c074b2ec3 ! 55: 65e85e1e2d4 Complete orchestration V2 frontend cutover
58 57: c39d6100691 ! 56: 61182514a62 Integrate orchestration V2 controls and process recovery
59 58: ef051d86d84 ! 57: 9b175222292 Handle preparing turns across provider orchestration
60 59: fcc2a2d0963 = 58: 890f95e6a7f Record created threads and subagent progress
61 60: ecddebcc67d ! 59: b9717bb8517 Map thread panel into title bar and sidebar
62 61: 2aad9bc514f ! 60: 7d5eedaf9f6 Reserve space for inline thread details panel
63 62: 0531d50ed58 = 61: b9ae98f9794 Split open-in editor controls into panel and toolbar variants
64 63: 6c6e346d381 ! 62: c8dce47576c Hide subagent threads and simplify thread controls
65 64: 1a2b7432e98 ! 63: 4b5fa27f95c Render mobile timelines from V2 turn items
66 65: 327f7287bb8 ! 64: d5a4342a3da Enrich mobile V2 execution items
67 66: af6028c82dc ! 65: 06c57e49daa Expose V2 thread workflows on mobile
68 67: 2e05af67463 ! 66: e70b8940053 Retire V1 client orchestration parity
69 68: 4d8e17808be = 67: 1ee6b0622eb Add iOS associated domains for Clerk
70 69: db9310c2f62 = 68: 569c448f009 Map Grok task envelopes to subagent lineage
71 70: 9f069920613 = 69: 4a7e85f0fca Adopt userdata-v2 and subagent activity mapping
72 71: 4313ad89dd3 = 70: 30d6cf06ec9 Map nested Codex subagent threads correctly
73 72: 0eb1fc59abf = 71: 47f269d4364 Clarify thread relationship icons and ordering
74 73: 036f3f9daf4 = 72: aa9c46184f2 Keep persistent cards visible in folded turns
75 74: f0bd8850aa2 ! 73: f8579194c5a Require Cursor API key for provider checks
76 75: 5f2045fcc32 = 74: 0d4886de7c9 fix(web): align thread details panel controls and menus (#3606)
77 76: 6c78a01831d ! 75: 8e2e8edd364 Switch Cursor provider to the official SDK
78 77: 695c57b4227 = 76: 61dec910677 Remove early access badges from Cursor and Grok
79 78: f2711e089d1 = 77: 89eb3f0bb82 Allow provider switching via handoff in chat threads
80 79: 5dc316e8ebe = 78: cb435247582 Fix Claude task turn mapping
81 80: 25df2c7c873 ! 79: 2871cf85de7 feat: scheduled tasks (automations) (#3638)
82 81: 698f41eb600 = 80: c1bae1c69fc feat(orchestrator): Add shared provider continuation and background item plumbing
83 82: 55fd431a069 ! 81: 47ec735048d [orchestrator-v2] fix(orchestrator): Restore Claude session continuity for resume, wake, and idle release (#3860)
84 83: 935aa5deb7a = 82: f11c744887c [orchestrator-v2] fix(orchestrator): Codex background command completion and subagent resume (#3908)
85 84: 822242fc77b = 83: e15cb8b34f6 fix(orchestrator): scope Claude MCP tool pre-approval (#3862)
86 85: ec90f1ec95e = 84: a9f67f41d33 feat(orchestrator): pass model options through MCP thread targets (#3872)
87 86: 4060032ea1d = 85: 8a79bdd091c fix(orchestrator): align Claude permission replay with SDK
88 87: 6e01338ad37 = 86: 5d7d125331d fix(ci): restore Claude permission request identity
89 88: 17d2f5d2d42 = 87: b9668c94839 test(desktop): expect orchestrator v2 state directory
90 89: 6f0e825e483 = 88: 5ce823a928c fix(claude): redact launch arguments from protocol logs
91 90: cec49e2ca05 = 89: bfb55f793b3 fix(claude): preserve approvals with full-access sandbox
92 91: 66f3b79c8e1 = 90: bad24edaf5c fix(claude): allow questions during plan mode
93 92: bf9a8775d7c = 91: 3c091989c6f fix(claude): honor never-approval runtime policies
94 93: cbcfea8b7d0 = 92: f218e04f09a fix(claude): enforce read-only tool availability
95 94: f17413cc120 = 93: 1e4a96b5610 fix(claude): reopen queries after MCP credential rotation
96 95: 14f5595b686 = 94: 1124866e9f6 fix(acp): release turns after interrupt timeout
97 96: 9a065758da1 = 95: be7bb48c0aa fix(acp): bind MCP credentials to activated threads
98 97: 555b0469e9d ! 96: 333ecf1ff79 fix(orchestrator): harden Grok v2 lifecycle (#3578)
99 98: a307962b2ba = 97: f006bffe139 fix(orchestrator): dedupe Grok continuation dispatch
100 99: 8e879081c3f = 98: 007b51b0ce5 fix(orchestrator): Harden Grok v2 runtime lifecycle
101100: a210dc8c288 = 99: b116daa728b test(orchestrator): align integration fixtures
102101: 1695b69e128 = 100: fa905ec6362 fix(mobile): support Hermes collection sorting
103102: 643047ad47f = 101: 2fd782d05bc fix(grok): align ACP extensions with open source runtime
104103: c026716d343 ! 102: 1093a70b448 Render T3 MCP tools with branded timeline labels
105104: d8b06653dff ! 103: 917fad6c322 Unify T3 MCP tool presentation across clients
106105: 8e173085a7c ! 104: bcf43a8f018 fix(orchestration): clarify agent delegation and scheduling
107106: 092daf3c497 = 105: cd095953df7 fix(server): preserve released migration ordering
108107: 39505241c82 = 106: f18cbfb6841 fix(web): restore v2 composer chrome
109108: 92bfc55bc39 = 107: 80a9b6628b8 fix(web): remove stacked composer shadows
110109: 7e3d2119880 = 108: 3fa4661dd66 refactor(web): use shared glass surfaces
111110: b07b35f8fcc = 109: b51d858ac04 fix(web): contain thread details panel effects
112111: 2f50f6b93f0 = 110: df523273344 test(orchestrator): Align post-merge CTM fixtures (#4193)
113112: c93936748b4 = 111: 3e6d9c12dd6 fix(orchestrator): Preserve claude/codex post-interrupt recovery state (#4229)
114113: b2e4223af67 = 112: c75cec919c3 test(orchestrator): align Codex approval reviewer replays (#4457)
115114: 2a25e1d2deb = 113: dff34c177aa fix(orchestrator): hydrate shell cache and group multi-environment projects (#3640)
116115: 83ea5f70c16 = 114: 780302519d3 feat(subagents): disclose projected results consistently (#3866)
117116: 7ae175a5f35 = 115: d52c1e6e6d5 Add worktree handoff and status tools to the t3-code MCP server (#3754)
118117: 0bc459bd0fa = 116: 64a9daf87cd fix(mobile): wait for fork shell before navigation
119118: bafa7a432ba = 117: 55cb6938055 fix(server): keep derived threads awake
120119: 45d60f9a51b = 118: 961a655902e fix(server): enforce ACP auth and preserve fork provenance
121120: 16b8599b267 = 119: 8fb46345695 fix(server): clean up Claude replay failures
122121: 4a3797b4825 = 120: 0bcafedc7ca test(orchestrator): align merged V2 compatibility checks
123122: da04feea481 = 121: e05564e15a5 fix(grok): Prevent spurious wake run after in-turn monitors
124123: 9649b226006 = 122: af0f7759971 fix(acp): Preserve wake evidence across an app-owned wake
125124: ba3c8210040 = 123: f4553701dbb fix(orchestrator): Wake settled parents when delegated children finish
126125: 0e018044d74 = 124: f0b7a4ed427 fix(claude): Settle positive task-notification results
127126: 1e42d8e9098 ! 125: dda77e7bb49 fix: hide subagent threads from v2 lists
128127: 4d9180dc781 = 126: bc1a9c7d76b fix: ignore subagents when sorting sidebar projects
129128: ef8219c2e3d = 127: 933d9c04217 fix(web): restore checked-in project scripts
130129: d59b7c2ba35 = 128: 4569c531d59 chore(orchestrator): refresh checks after main sync
131130: 3ba155db4fa = 129: e684489f7d8 feat: migrate v1 state into orchestrator v2 (#4400)
132131: 6c82f71da51 = 130: 81619adf57b fix(orchestrator): schedule effects from durable deadlines (#4656)
133132: 32d563e07ce = 131: 73fb62aeed3 fix(orchestration): harden scheduled task startup
134133: 8006f38f2e6 ! 132: 384873780e8 fix(mobile): preserve active thread state
135134: 51e044f7ad1 = 133: 657ee69e5ff fix(orchestration): preserve legacy schedule compatibility
136135: 8d27ca499e3 = 134: 385f1537b78 fix(contracts): reject invalid legacy intervals
137136: 3edb72687d6 = 135: cedc11513d4 fix(acp): make xai cancellation reliable
138137: 7252e405740 = 136: 49654af76e5 fix(orchestration): handle checkpoint-wait runs
139138: 0900ea01d94 = 137: 9761382b9df fix(orchestration): cancel queued work on archive
140139: 1fdab432e8f = 138: 3087d27841d fix(mobile): allow archiving post-provider work
141140: 0db5a1d2e6d = 139: 14d68b3c2ad fix(mobile): distinguish queued and waiting archive states
142141: 120242d9fb5 = 140: 4fd8f62bc94 fix(cursor): log close attempts before execution
143142: 345a9a4a3a6 = 141: ff5e6f1b32d fix(acp): discover final teardown descendants
144143: 09963b2172d = 142: d0fca84e651 fix(checkpoints): preserve valid run history
145144: 6c30ce289bd = 143: c2b51fc0902 fix(orchestration): preserve imported conversation state
146145: 0d5dbddd166 = 144: 6ab76be2243 fix(acp): enforce task and permission invariants
147146: c6adb55a0a7 = 145: bd653b476bf fix(testkit): harden provider replay recording
148147: 9f71aa9ca6b = 146: 4cf4ed7202f fix(server): isolate deterministic attachment ids
149148: afeb2089d20 = 147: 2ca3166d409 fix(claude): preserve explicit model options
150149: 153a1f8548e = 148: 0954b3bb597 fix(web): enforce secure provider field defaults
151150: c2145dde126 = 149: b605ddde083 fix(client): preserve live thread relationships
152151: 6179f46681c = 150: 67f85a5c781 fix(checkpoints): retain thread-start baseline after failed runs
153152: 80e5ef9865d = 151: 91e3a339592 fix(mobile): gate thread controls on live runs
154153: 5a2a714d781 ! 152: 088c56b41f3 fix(orchestrator): address late review findings
155154: ff10b65fe6e = 153: 739c3ff962d fix(orchestrator): handle fresh review edge cases
156155: 785e1388d01 = 154: b65d8ca4cf6 fix(orchestrator): harden provider edge cases
157156: 0a9a06ba642 = 155: d6eb526629a fix(orchestrator): execute resolved runtime responses
158157: 49ad52e66d3 = 156: dd2b8814975 fix(orchestrator): release stranded effect claims
159158: c17955171a4 = 157: c2a48e3c8e9 fix(worktrees): make handoff rollback atomic
160159: c7a62d3c36d = 158: f081c2c84c4 fix(orchestrator): avoid replaying settled effects
161160: 7b1e9dad867 = 159: 83cf7d6dfa6 fix(orchestrator): preserve retryable effect failures
162161: 69299bf47b7 = 160: ddb3c272115 fix(orchestrator): preserve terminal effect outcomes
163162: 9c3a6b75e98 = 161: 9a404d4672f fix(orchestrator): validate replay edge cases
164163: af590d13aea = 162: 697fff8d50d fix(orchestrator): decode direct Claude result blocks
165164: 614c744be91 = 163: 2e813f8c4bd fix(orchestrator): close cancellation edge cases
166165: e59043aebb2 = 164: 6fcbf009d42 fix(orchestrator): validate rollback and search links
167166: 327189894e0 = 165: 014e9a2621e fix(server): preserve project mutation client errors
168167: 8af2b58d152 = 166: 10c462fbd20 fix(orchestrator): preserve migrated and nested history
169168: 9aeb65f738d = 167: 01c127f54ed fix: address orchestration review findings
170169: fa408d129f1 = 168: 3e72c6a17b2 fix(mobile): label queued message intent
171170: 5de19555913 = 169: 88a1900091b fix: address orchestration review findings
172171: a2b1e1ef93f = 170: 1e65d374c19 fix: preserve orchestration task identity
173172: 981b2ddc749 = 171: dbc5127f330 fix: address latest orchestration review findings
174173: 6832caec1f1 = 172: d4990c75c3a fix: preserve thread management failure semantics
175174: dd9b6b0d4bd = 173: 5c24e3506ae fix: close failed provider adapter scopes
176175: 0ee02d54900 = 174: a12849d5916 feat(server): surface legacy thread migration progress
177176: 7b28551f4bb ! 175: 27806ef4d85 feat(orchestration): track provider retries and thread visits
178177: 0d57ac71aed = 176: bedec74e857 [codex] feat(web): show git progress in the commit button (#4963)
179178: 0ca2c503d95 = 177: b5d353cc8a3 perf(orchestration): per-thread shell deltas, visit throttling, event compaction (#4971)
180179: 3dc8b570afc = 178: 16620e9fe4b fix(chat): prevent stale timeline scroll and rerenders
181180: c123bddb2bf = 179: 673e0f49e7e fix(relay): stop replaying the whole event store into the awareness relay
182181: 5e292307986 = 180: 1fc6545515e perf(web): keep timeline minimap animations off the main thread
183182: 888283cd7ff = 181: 5d0644e2af9 feat(orchestration): port thread title regeneration to the v2 runtime
184183: e2284c99f17 = 182: 8c7e1c13fa2 fix(orchestration): thread visits no longer create activity loops (#5038)
185184: aadfcb978b7 = 183: a78cc2c5238 fix(orchestration): promoted queued messages keep the queued_turn intent
186185: 3cd279a07a3 = 184: 1c378332b10 refactor(orchestration): split thread-not-sendable into typed errors
187186: 790fe93a99d = 185: 3df8422d569 fix(web): remove elevated thread details panel styling
188187: cb592a7a920 = 186: b8a1f761729 fix(web): keep Git progress title anchored
189188: 6a7824f3525 ! 187: f44c22a14a8 fix: reconcile rebase with latest main
190189: 6a8fcc6576e ! 188: daa272bac55 fix(orchestration-v2): restore generated thread titles (#5176)
191190: 78aea07a319 = 189: 4fad4c56afc fix(orchestrator): Order thread lineage by creation time (#5310)
192191: d13cb6b8f50 = 190: 7c9c6dcb7f1 fix(server): restore worktree branch naming in the v2 orchestrator (#5309)
193192: 4e72ea725c1 ! 191: 51d7089afc6 feat(chat): refine V2 conversation UI (#5307)
194193: 704808d9235 = 192: 0d5f04f6041 fix: adopt effect beta.103 APIs in rebased v2 code
195194: d4e31cb20fd = 193: ea285714f2e fix: send mcp-protocol-version header in worktree registration test
196195: fa775c22ad2 = 194: 413885f1d3c fix(web): restore compact header sizing for project script controls
197196: 39fab14dc40 = 195: 69d932b37a2 test(server): cover v2 thread title regeneration
198197: 4ffad32204a ! 196: 1691887191e chore: resolve lint warnings across v2 code
199198: 1b91a552e00 = 197: 648d1f42b86 fix(test): keep codex replay recovery off the repo checkout
200199: 7f3ebe40d91 = 198: cf083513446 fix(orchestrator): Prevent redundant delegated completion turns (#5311)
201200: 14ca09db7b5 = 199: 00efd07bd42 fix(server): stop tying codex text-generation temp files to the caller's scope (#5406)
202201: 3699b557e91 = 200: ec1fd6195a5 fix(web): remove open PR actions from git controls
203202: fa557cd7741 = 201: e609aabd27d fix(server): renumber v2 migrations after main's 036_ProjectionThreadsPinned
204203: 2e3b8638261 ! 202: 8ba222e5201 fix: port main fixes stranded by the v2 rewrite
205204: 6a924e9d5f4 = 203: 690257b3cd2 test(server): align migration expectations with renumbered ids
206205: 2b4e393ff5b = 204: a657cb37357 feat(orchestrator): Surface waiting background work (#4378)
207206: ec7370e7e52 = 205: 3f8506737ca fix(web): align git action progress button layout
208207: 20cec19eb52 = 206: acbb25bc175 fix: repair conflict-marker artifacts from rebase auto-resolutions
209208: bfb9d9465a3 = 207: 7222e6bdf71 fix(server): renumber v2 migrations after main's 037_ProjectionTurnsKeysetIndex
210209: e778ae6f30a ! 208: 103f1e6cd22 fix: port main fixes stranded by the v2 rewrite (round 2)
211210: 4e87742e9ba = 209: be208f5073b chore(web): prune plan-sidebar leftovers after the inline-plans rework
212211: bed1245b22d = 210: 1b95828aec9 fix(web): port the refined live-follow gesture gating to the v2 timeline
213212: 09c99a51a1d = 211: cd782e9cdce fix(web): let LegendList own end-follow and disclosure anchoring (#5449)
214213: fddf025c0bf = 212: f568e4df2ce fix(web): show Git action success inline in panel
215214: f2d1f16c3c7 ! 213: 7b7a3c68f5e fix: repair rerere-damaged files and reconcile main's round-3 features with v2
216215: fbfa125f2df = 214: 976fa4da287 chore(server): renumber v2 migrations 038-046 to 041-049 after main's 038-040
217216: 1ffb1eaf785 = 215: b10a1a4a613 fix(server): port round-3 main fixes into the v2 orchestrator
218217: 75fdf57d998 = 216: 17abb7b57ae test(web): restore main's right-panel migration expectations after the panel-visibility merge
219218: e3fb981cacd = 217: c95f718a92a test(server): expect attachment saved-at lines in ClaudeAdapterV2 turn text
220219: e69b9944e54 = 218: c6429f0ec60 fix(web): restore the branch's slim chat header
221220: 15d7ed4ddc5 ! 219: b23bae2e07c feat(web): add pull request actions to thread details
222221: 6f39090bcbc = 220: cf8159205d6 fix(mobile): port main's composer stabilization into the v2 thread screens
223222: 9e7a5b0566e = 221: 647dcb34c3a fix(web): reconcile main's round-5 features after the rebase
224223: 365ffce1014 ! 222: 8c976ef74d2 feat(web): prioritize pull request row actions
225224: 167f5f6068b = 223: 4569224ce1d fix(web): restore main's collapse chrome and tab-status keying on the PR panel
226225: 862479b5159 = 224: 54912ca1722 feat(orchestration): bound thread history and resume payloads
227226: e861e74df5e = 225: 35e8172373e feat(contracts): track thread title regeneration
228227: b16ac847938 = 226: 0c2def0526e fix: reconcile main's round-6 features after the rebase
229228: 3d812634cfd = 227: ecbf6849d78 fix(web): keep the titlebar layout controls fixed across right-panel toggles
230229: 7c8af3708e0 = 228: 8e026d24642 fix(web): align titlebar clusters to one shared pixel inset
231230: a39f6a267d8 = 229: 66051a7d854 fix(web): size the titlebar layout-control icons like the sidebar trigger
232231: e74f01c66a9 = 230: eeb374557dc perf(server): keep shell snapshots bounded and active-only
233232: ace57ebdfc6 ! 231: 95ea8072fa8 fix: reconcile main's round-8 features after the rebase
234233: d734b66fff0 = 232: 358fa7b42d1 fix(server): reject replaying a command receipt across threads in v2
235234: 5c722de01c3 ! 233: 1c3f3ff94a8 feat(mobile): surface prominent activity status and metadata
236235: 796911d54dd ! 234: 40b35dbb9f2 fix: reconcile main's round-9 features after the rebase
237236: 2e425197055 ! 235: 0e4c8cf65be feat(server): honor withheld agent browser access in the v2 runtime
238237: f22abd94cd9 = 236: bd708261286 fix(web): restore the titlebar sizing and timeline fade lost to main's style simplification
239238: 8bf0b5fcb34 = 237: 3bbb50c08e8 refactor(web): finish aligning the branch with main's style simplification
240239: 5d1e08ef48f = 238: 6d31e48c7d2 fix(orchestration): show provider retries in the work log
241240: ab8569a7a35 ! 239: 06a2b41a06c fix: reconcile main's round-10 features after the rebase
242241: 1533041bfd6 ! 240: 2c3c47d1dd3 fix(web): stop mis-marking recovered and text-reported tool failures in the v2 work log
243242: 650a24252e6 = 241: f2b786b92ec feat(orchestration-v2): project linked pull requests on threads (#8160)
244243: f894a3531f6 = 242: 4650a6971b4 feat(orchestration-v2): carry approval options and app names to the client (#8058)
245244: 4eb10990383 = 243: 96a5bf0bde6 feat(orchestration-v2): route Codex thread feedback uploads through v2 (#7949)
246245: a854b2fcfd6 = 244: d4735799dd3 feat(analytics): credit v2 threads and turns to the starting client (#7774)
247246: 47b1560306c = 245: 197d245344f fix(grok): fail hung prompts on xAI rate-limit completions (#8358, partial)
248247: dfc21af186a = 246: 8c559a0fe9d feat(orchestration-v2): show live context usage in the meter (#8144)
249248: 2c188dd57be = 247: d7f68523648 fix(web): keep following the stream after returning to the live edge (#6519)
250249: 8fdce450171 = 248: 718ff37efae feat(grok): capture exit_plan_mode into the v2 proposed-plan card (#8358)
251250: 0b8e40fbc57 = 249: c6ac7fae2f6 fix(web): repaint the composer glass and strip the thread-panel popover chrome
252251: d81b12f2aae = 250: 2c7ab194c49 fix(web): adopt main's attached-composer surface contract so the glass survives shoulder tabs
253252: a8df9625013 = 251: 7e9527c2a7e fix(web): converge ChatComposer on main's drawer-era body
254253: c0cbbbc8441 = 252: 15ac91d2ee3 fix(web): collapse settled tool runs behind main's summary toggles
255254: 72056ac8934 = 253: 6bc36d5fdef fix(web): surface v2 todo-list plans as task progress
256255: 1c3b15a8352 = 254: d209d13ff97 fix(web): show the command on collapsed tool rows, not its stdout
257256: c380aa6c877 = 255: 698db66a384 fix(web): collapsed tool rows preview inputs for every tool type
258257: 0301dc423e3 ! 256: a2c1f7fde39 fix: reconcile main's round-11 features after the rebase
259258: 690ea07bacf = 257: 344d661acce fix(opencode): route child-session approvals through the v2 adapter
260259: a2efa89c95e = 258: 7b35f0756fb fix: reconcile main's round-12 features after the rebase
261260: 084d72779dd = 259: bbde2509229 fix: restore main's automatic thread settling after the revert
262261: bf963f1d4f5 = 260: 2dbca2799fc fix(web): restore the full-screen file-drop target over the chat column
263262: 1c589887516 = 261: e3d09455f4f feat(server): claim uploaded attachments at v2 dispatch
264263: a1d309ea774 = 262: dbe86d12fd1 feat(web): show attachments on queued messages and edit them in the composer
265264: 3fd374226d0 = 263: 399df7a4397 fix(web): drag-to-reorder queued messages and retire stale pending rows
266265: aafe194a883 = 264: 03c974d7dd9 fix: reconcile main's round-14 features after the rebase
267266: d17919b8451 = 265: 731e44ac862 fix(web): dedupe the composer glass styles and align the chat column width
268267: 79ab9ff2114 ! 266: e7d813ad06c fix(mobile): replace remaining dark: utilities with adaptive semantic tokens
269268: 6211ffaa69a < -: ----------- fix(lint): allowlist the queue and relationships interop boundaries
270 -: ----------- > 267: e89268b409b fix(lint): allowlist the queue and relationships interop boundaries
271269: bbbaeeb2a3e = 268: edbe21f803a fix(server): inject HostProcessPlatform into the Grok plan extractor
272270: 8e0d521bebd = 269: 2a94e7b2d28 chore: retrigger ci
273271: 91572bcaf43 = 270: 2fe6d79c49b chore: refresh macroscope ui-consistency check
274272: e0ab034b430 ! 271: 12bf10144be fix: reconcile main's round-16 features after the rebase
275273: b9400c36a37 = 272: b14ca6bf3ae fix(web): keep failed tool items in the collapsed group summaries
276274: 34a4d026a5f = 273: 608793e39e1 fix(server): keep Claude session approvals ephemeral
277275: 7e71ff8a806 ! 274: b28c4a4c125 fix(orchestration): reanchor unsettled threads
278276: 6b3690e0632 = 275: 7933dd671dc fix(server): observe pre-aborted Claude approvals
279277: feedc3f6bbe = 276: 635f552718b fix(server): include service launcher in bundle build
280278: 82e1b8ed604 = 277: 4fdafec1d56 fix(orchestration): preserve legacy thread metadata
281279: 930dd22e720 = 278: 8f0ada76d8f fix(web): honor disabled legacy plan mode
282280: 2cfe234e46c = 279: d72da77e773 fix(server): preserve Claude subagent models
283281: eacc09d40ab = 280: 202da64207f fix(orchestration): honor migrated thread visibility in search
284282: dcff638944a = 281: 1acca70823e fix(orchestration): recreate missing worktrees before turns
285283: e44588afa4c ! 282: 54c0c030ef4 fix(clients): restore Codex feedback submission
286284: 8bbf7f50c83 = 283: 1322c48d5d2 fix(server): preserve generic provider attachments
287285: 53c6857ec93 = 284: dfaf815157f fix(web): load workspace markdown images through assets
288286: 7fca26e6fbf = 285: fa61cae19d4 fix(web): preserve Windows markdown paths
289287: 642056fdd9e = 286: 980df245b19 fix(server): keep current provider context usage
290288: 5f19d550ae3 = 287: a4cee6104d5 fix(web): restore markdown file chip actions
291289: 44f2004810b = 288: 875b9d88568 fix(web): scope markdown actions to their environment
292290: 995f4e7e5ad ! 289: 8853fd4386d fix(protocol): reject incompatible orchestration peers
293291: 3e1ff1531dd = 290: adc3d1acaa3 docs: explain legacy thread migration
294292: 19a30d03be9 = 291: d9e87450162 docs: state portable handoff limits
295293: 68104a497e1 = 292: 2e88e58640c chore(repo): remove tracked audit scratch files
296294: 98670f337e4 = 293: 0439f0f162a fix(server): guard OpenCode prompt admission races
297295: ecf3dd2c9a0 = 294: 537861cf9a9 fix(server): restore Claude structured questions
298296: ff9f875f17a = 295: 9991452ad2f fix(server): project Claude plans and todos
299297: 9a4734af8e5 = 296: d16e890b16d perf(orchestration): bound history reads in SQL
300298: 0921786018a = 297: ac7e04ae29e perf(orchestration): bound complete thread snapshots
301299: 3d5bab73d85 = 298: 48dab8cd209 fix(server): restore Claude resume compaction
302300: 733db7269f6 = 299: 03d8aef938d fix(server): allow protocol negotiation in CORS
303301: 26dfbc29847 = 300: 4c25c80607d fix(server): preserve provider usage in persisted turns
304302: 70f82caaa0c = 301: a4891183060 fix(server): preserve Claude planning lifecycle
305303: dbb6021f840 = 302: 3d2d5711be6 fix(server): normalize Claude question answers
306304: de79f442213 = 303: a8370c67278 fix(server): correlate OpenCode prompt admission
307305: be7b07634bb = 304: d4debe5ae8d fix(clients): anchor feedback in conversation order
308306: 051565fc96f = 305: 8d2c45c5399 fix(web): retain markdown workspace ownership
309307: 870014496f4 = 306: 14322467cf3 fix(orchestration): page history through its true end
310308: 07889933e39 = 307: 1fc6d2c9058 fix(server): cancel pending OpenCode prompts safely
311309: 0fc565db8ac = 308: 42bafcb317e fix(orchestration): retain nested fork history when paging
312310: 47ae99f5176 = 309: f45dde3c861 fix(server): recover OpenCode status reconciliation
313311: c1791ab2637 = 310: 93620dfab0f fix(orchestration): select visible history before limiting SQL
314312: ed6aafa93f5 ! 311: 7efa4e84b1a fix(web): port composer activity and grouping to orchestration v2
315313: b1533813ce1 = 312: 61e3568dac8 fix(web): align queue headers and prevent stash overlap
316314: e82c7178550 = 313: f21af84a704 fix(web): share the outline for joined composer tabs
317315: 85a3c125681 = 314: 1a600882893 fix(web): keep stash separate from the composer activity column
318316: b2afc4cb458 = 315: b952c574d53 refactor(web): use shared banner rows for queued messages
319317: e16d934d8a0 = 316: 1a56fa6768f fix(web): keep queued message editing inside the queue panel
320318: 6ce4cb0a15d = 317: 5d5f370ad67 fix(web): keep queued messages in place while editing
321319: 6bcc0e39da0 = 318: f8ed1f080dd fix(web): match composer actions to draft and modifier state
322320: 72e0c434bea = 319: 5145881d4c2 fix(web): keep composer shortcut tooltip stable on Mod
323321: 60bc3cff930 ! 320: cafacd53b80 feat(web): summarize T3 orchestration actions
324322: b27f50b65aa ! 321: 6bc5b0f7982 feat(mobile): port chat summaries and transitions to orchestration v2
325323: fa54f546075 = 322: 8b2f951e68a fix(chat): remove added tool summary status counts
326324: 4b35166d028 = 323: 42dee149f78 fix(mobile): keep scroll bounds current after animations
327325: acdbe292f03 ! 324: 27ef79dc301 fix: reconcile main's round-17 features after the rebase
328326: 99e940dc2b3 = 325: 38f3e4d0e02 feat(web): port working and thinking timeline rows to orchestration v2
329327: 7697286069f ! 326: ddf58d90d47 fix: reconcile main's round-18 features after the rebase
330328: 2191297898e ! 327: 702ef5e5851 feat(server): evaluate automatic thread settlement in the v2 orchestrator
331329: f0174c45778 ! 328: dc4cd901af5 fix: reconcile main's round-19 features after the rebase
332330: e6da41e5c6f = 329: 4199e9db516 fix(web): right-align the stash shoulder tab again
333331: 0550e0a34db ! 330: 4f924bfd310 fix(web): realign the composer and timeline with main
334332: d2f1f511f4c = 331: 96aff3564b3 fix: reconcile main's round-20 features after the rebase
335 -: ----------- > 332: d98f1d5e38f fix(server): reduce v2 recovery and runtime resource usage
336 -: ----------- > 333: 8af5734365f fix: reconcile main updates with orchestration v2
audits/orchestrator-v2/2026-09-04/prior-audit-range-diff.txt+106728−0
@@ -0,0 +1,106728 @@
1 1: 15dff312363 < -: ----------- chore(ov2): preserve the integration base for replay
2 -: ----------- > 1: b8b7f894fcd chore(ov2): preserve the integration base for replay
3 2: e911c41199e ! 2: cc2e380a1ed Integrate Codex app-server support
4 @@ apps/server/integration/orchestrationEngine.integration.test.ts: it.live("forwar
5
6 ## apps/server/package.json ##
7 @@
8 - "@types/node": "catalog:",
9 + "@types/yauzl": "^3.4.0",
10 "effect-acp": "workspace:*",
11 "effect-codex-app-server": "workspace:*",
12 - "vite-plus": "catalog:"
13 @@ apps/server/package.json
14 "node": "^22.16 || ^23.11 || >=24.10"
15
16 ## apps/server/scripts/acp-mock-agent.ts ##
17 +@@
18 + import * as NodeFS from "node:fs";
19 +
20 + import * as Effect from "effect/Effect";
21 +-import * as Deferred from "effect/Deferred";
22 +
23 + import * as NodeServices from "@effect/platform-node/NodeServices";
24 + import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
25 @@ apps/server/scripts/acp-mock-agent.ts: import * as AcpError from "effect-acp/errors";
26 import type * as AcpSchema from "effect-acp/schema";
27
28 const requestLogPath = process.env.T3_ACP_REQUEST_LOG_PATH;
29 -const exitLogPath = process.env.T3_ACP_EXIT_LOG_PATH;
30 +-const antigravityProfile = process.env.T3_ACP_ANTIGRAVITY === "1";
31 const emitToolCalls = process.env.T3_ACP_EMIT_TOOL_CALLS === "1";
32 const emitInterleavedAssistantToolCalls =
33 process.env.T3_ACP_EMIT_INTERLEAVED_ASSISTANT_TOOL_CALLS === "1";
34 +@@ apps/server/scripts/acp-mock-agent.ts: const emitContentThenHang = process.env.T3_ACP_EMIT_CONTENT_THEN_HANG === "1";
35 + const emitPlanThenHang = process.env.T3_ACP_EMIT_PLAN_THEN_HANG === "1";
36 + const emitActiveToolThenHang = process.env.T3_ACP_EMIT_ACTIVE_TOOL_THEN_HANG === "1";
37 + const emitForeignSessionUpdates = process.env.T3_ACP_EMIT_FOREIGN_SESSION_UPDATES === "1";
38 +-const waitForResumeRelease = process.env.T3_ACP_WAIT_FOR_RESUME_RELEASE === "1";
39 +-const completeFirstPromptOnCancel = process.env.T3_ACP_COMPLETE_FIRST_PROMPT_ON_CANCEL === "1";
40 +-const floodStderr = process.env.T3_ACP_FLOOD_STDERR === "1";
41 + const hangPromptForever = process.env.T3_ACP_HANG_PROMPT_FOREVER === "1";
42 + const hangFirstPromptForever = process.env.T3_ACP_HANG_FIRST_PROMPT_FOREVER === "1";
43 + const emitLateUpdateAfterCancel = process.env.T3_ACP_EMIT_LATE_UPDATE_AFTER_CANCEL === "1";
44 +@@ apps/server/scripts/acp-mock-agent.ts: const permissionRequestCount = Math.max(
45 + );
46 + const sessionId = "mock-session-1";
47 +
48 +-let currentModeId = antigravityProfile ? "default" : "ask";
49 +-let currentModelId = antigravityProfile ? "gemini-test-low" : "default";
50 ++let currentModeId = "ask";
51 ++let currentModelId = "default";
52 + let parameterizedModelPicker = false;
53 + let currentReasoning = "medium";
54 + let currentContext = "272k";
55 @@ apps/server/scripts/acp-mock-agent.ts: let promptCount = 0;
56 let overlappingFirstPromptId: string | undefined;
57 const cancelledSessions = new Set<string>();
58 @@ apps/server/scripts/acp-mock-agent.ts: let promptCount = 0;
59 -});
60 -
61 function configOptions(): ReadonlyArray<AcpSchema.SessionConfigOption> {
62 +- if (antigravityProfile) {
63 +- return [
64 +- {
65 +- id: "model",
66 +- name: "Model",
67 +- category: "model",
68 +- type: "select",
69 +- currentValue: currentModelId,
70 +- options: antigravityModels.map((model) => ({ value: model.modelId, name: model.name })),
71 +- },
72 +- {
73 +- id: "mode",
74 +- name: "Mode",
75 +- category: "mode",
76 +- type: "select",
77 +- currentValue: currentModeId,
78 +- options: availableModes.map((mode) => ({ value: mode.id, name: mode.name })),
79 +- },
80 +- ];
81 +- }
82 if (parameterizedModelPicker) {
83 const baseOptions: Array<AcpSchema.SessionConfigOption> = [
84 + {
85 +@@ apps/server/scripts/acp-mock-agent.ts: function availableModels(): ReadonlyArray<{
86 + }));
87 + }
88 +
89 +-const antigravityModels = [
90 +- { modelId: "gemini-test-low", name: "Gemini Test Low" },
91 +- { modelId: "gemini-test-high", name: "Gemini Test High" },
92 +-] satisfies ReadonlyArray<AcpSchema.ModelInfo>;
93 +-
94 +-const availableModes: ReadonlyArray<AcpSchema.SessionMode> = antigravityProfile
95 +- ? [
96 +- { id: "default", name: "Default" },
97 +- { id: "auto_edit", name: "Auto edit" },
98 +- { id: "yolo", name: "YOLO" },
99 +- ]
100 +- : [
101 +- {
102 +- id: "ask",
103 +- name: "Ask",
104 +- description: "Request permission before making any changes",
105 +- },
106 +- {
107 +- id: "architect",
108 +- name: "Architect",
109 +- description: "Design and plan software systems without implementation",
110 +- },
111 +- {
112 +- id: "code",
113 +- name: "Code",
114 +- description: "Write and modify code with full tool access",
115 +- },
116 +- ];
117 ++const availableModes: ReadonlyArray<AcpSchema.SessionMode> = [
118 ++ {
119 ++ id: "ask",
120 ++ name: "Ask",
121 ++ description: "Request permission before making any changes",
122 ++ },
123 ++ {
124 ++ id: "architect",
125 ++ name: "Architect",
126 ++ description: "Design and plan software systems without implementation",
127 ++ },
128 ++ {
129 ++ id: "code",
130 ++ name: "Code",
131 ++ description: "Write and modify code with full tool access",
132 ++ },
133 ++];
134 +
135 + function modeState(): AcpSchema.SessionModeState {
136 + return {
137 +@@ apps/server/scripts/acp-mock-agent.ts: const grokAcpModels: ReadonlyArray<AcpSchema.ModelInfo> = [
138 + ];
139 +
140 + function modelState(): AcpSchema.SessionModelState {
141 +- if (antigravityProfile) {
142 +- return { currentModelId, availableModels: antigravityModels };
143 +- }
144 + const modelId = grokAcpModels.some((model) => model.modelId === currentModelId)
145 + ? currentModelId
146 + : "grok-4.6";
147 +@@ apps/server/scripts/acp-mock-agent.ts: function modelState(): AcpSchema.SessionModelState {
148 +
149 + const program = Effect.gen(function* () {
150 + const agent = yield* EffectAcpAgent.AcpAgent;
151 +- const resumeRelease = yield* Deferred.make<void>();
152 +- const nativeCancelRequested = yield* Deferred.make<void>();
153 +- const nativeCancelRelease = yield* Deferred.make<void>();
154 +- const publishAntigravityCommands = (targetSessionId: string) =>
155 +- agent.client.sessionUpdate({
156 +- sessionId: targetSessionId,
157 +- update: {
158 +- sessionUpdate: "available_commands_update",
159 +- availableCommands: [
160 +- { name: "plan", description: "Plan a task", input: { hint: "task" } },
161 +- { name: "logout", description: "Sign out" },
162 +- ],
163 +- },
164 +- });
165 +
166 + yield* agent.handleInitialize((request) =>
167 +- Effect.gen(function* () {
168 +- if (floodStderr) {
169 +- yield* Effect.promise(
170 +- () =>
171 +- new Promise<void>((resolve) => {
172 +- process.stderr.write("stderr".repeat(350_000), () => resolve());
173 +- }),
174 +- );
175 +- }
176 ++ Effect.sync(() => {
177 + parameterizedModelPicker =
178 + request.clientCapabilities?._meta?.parameterizedModelPicker === true;
179 +- if (antigravityProfile) {
180 +- return {
181 +- protocolVersion: 1,
182 +- agentInfo: { name: "antigravity-acp", version: "mock" },
183 +- agentCapabilities: {
184 +- loadSession: true,
185 +- sessionCapabilities: { resume: {} },
186 +- auth: { logout: {} },
187 +- promptCapabilities: { image: true, embeddedContext: true },
188 +- },
189 +- authMethods: [{ id: "oauth-personal", name: "Sign in with Google" }],
190 +- };
191 +- }
192 + return {
193 + protocolVersion: 1,
194 +- agentCapabilities: { loadSession: true, sessionCapabilities: { resume: {} } },
195 ++ agentCapabilities: { loadSession: true },
196 + // Grok advertises model state before any session exists; the provider
197 + // health check reads it from here without authenticating.
198 + _meta: { modelState: modelState() },
199 +@@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
200 + }),
201 + );
202 +
203 +- // Mirrors the real agent: the API key method reads GEMINI_API_KEY from the
204 +- // process environment and rejects when it is missing.
205 +- yield* agent.handleAuthenticate((request) =>
206 +- !antigravityProfile || request.methodId === "oauth-personal"
207 +- ? Effect.succeed({})
208 +- : request.methodId === "gemini-api-key" && process.env.GEMINI_API_KEY
209 +- ? Effect.succeed({})
210 +- : Effect.fail(
211 +- AcpError.AcpRequestError.invalidParams(
212 +- `Mock Antigravity rejected auth method ${request.methodId}.`,
213 +- ),
214 +- ),
215 +- );
216 +- if (antigravityProfile) {
217 +- yield* agent.handleLogout(() => Effect.succeed({}));
218 +- }
219 ++ yield* agent.handleAuthenticate(() => Effect.succeed({}));
220 +
221 + yield* agent.handleCreateSession(() =>
222 +- Effect.gen(function* () {
223 +- if (antigravityProfile) {
224 +- yield* publishAntigravityCommands(sessionId);
225 +- }
226 +- return {
227 +- sessionId,
228 +- modes: modeState(),
229 +- models: modelState(),
230 +- configOptions: configOptions(),
231 +- };
232 +- }),
233 +- );
234 +-
235 +- yield* agent.handleResumeSession((request) =>
236 +- Effect.gen(function* () {
237 +- yield* agent.client.sessionUpdate({
238 +- sessionId: request.sessionId,
239 +- update: {
240 +- sessionUpdate: "user_message_chunk",
241 +- content: { type: "text", text: "native-resume-started" },
242 +- },
243 +- });
244 +- if (waitForResumeRelease) {
245 +- yield* Deferred.await(resumeRelease);
246 +- }
247 +- if (antigravityProfile) {
248 +- yield* publishAntigravityCommands(request.sessionId);
249 +- }
250 +- return {
251 +- modes: modeState(),
252 +- models: modelState(),
253 +- configOptions: configOptions(),
254 +- _meta: { nativeResume: true },
255 +- };
256 ++ Effect.succeed({
257 ++ sessionId,
258 ++ modes: modeState(),
259 ++ models: modelState(),
260 ++ configOptions: configOptions(),
261 + }),
262 + );
263 +
264 +@@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
265 +
266 + yield* agent.handleSetSessionModel((request) =>
267 + Effect.gen(function* () {
268 +- if (!modelState().availableModels.some((model) => model.modelId === request.modelId)) {
269 ++ if (!grokAcpModels.some((model) => model.modelId === request.modelId)) {
270 + return yield* AcpError.AcpRequestError.invalidParams(
271 + `Unknown mock model id: ${request.modelId}`,
272 + {
273 @@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
274 );
275
276 @@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
277 - Effect.gen(function* () {
278 - const cancelledSessionId = String(sessionId ?? "mock-session-1");
279 - cancelledSessions.add(cancelledSessionId);
280 +- if (completeFirstPromptOnCancel) {
281 +- yield* Deferred.succeed(nativeCancelRequested, undefined);
282 +- yield* agent.client.sessionUpdate({
283 +- sessionId: cancelledSessionId,
284 +- update: {
285 +- sessionUpdate: "agent_thought_chunk",
286 +- content: { type: "text", text: "native-cancel-received" },
287 +- },
288 +- });
289 +- }
290 - if (emitLateUpdateAfterCancel) {
291 - yield* Effect.sleep("50 millis");
292 - yield* Effect.sync(() => {
293 @@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
294 }),
295 );
296
297 -
298 - ## apps/server/src/git/GitManager.test.ts ##
299 -@@ apps/server/src/git/GitManager.test.ts: const GitManagerTestLayer = GitVcsDriver.layer.pipe(
300 - );
301 -
302 - it.layer(GitManagerTestLayer)("GitManager", (it) => {
303 -- it.effect("status includes PR metadata when branch already has an open PR", () =>
304 -+ const LONG_EFFECT_TEST_TIMEOUT_MS = 30_000;
305 -+ const effect = (
306 -+ name: string,
307 -+ test: Parameters<typeof it.effect>[1],
308 -+ timeout = LONG_EFFECT_TEST_TIMEOUT_MS,
309 -+ ) => it.effect(name, test, timeout);
310 -+
311 -+ effect("status includes PR metadata when branch already has an open PR", () =>
312 - Effect.gen(function* () {
313 - const repoDir = yield* makeTempDir("t3code-git-manager-");
314 - yield* initRepo(repoDir);
315 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
316 - baseRef: "main",
317 - headRef: "feature/status-open-pr",
318 - state: "open",
319 -- updatedAt: null,
320 - });
321 - }),
322 - );
323 -
324 -- it.effect("status trims PR metadata returned by gh before publishing it", () =>
325 -+ effect("status trims PR metadata returned by gh before publishing it", () =>
326 - Effect.gen(function* () {
327 - const repoDir = yield* makeTempDir("t3code-git-manager-");
328 - yield* initRepo(repoDir);
329 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
330 - baseRef: "main",
331 - headRef: "feature/status-trimmed-pr",
332 - state: "open",
333 -- updatedAt: null,
334 - });
335 - }),
336 - );
337 -
338 -- it.effect("status ignores invalid gh pr list entries and keeps valid ones", () =>
339 -+ effect("status ignores invalid gh pr list entries and keeps valid ones", () =>
340 - Effect.gen(function* () {
341 - const repoDir = yield* makeTempDir("t3code-git-manager-");
342 - yield* initRepo(repoDir);
343 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
344 - baseRef: "main",
345 - headRef: "feature/status-valid-pr-entry",
346 - state: "open",
347 -- updatedAt: null,
348 - });
349 - }),
350 - );
351 -
352 -- it.effect("status preserves lowercase merged and closed PR states from gh json", () =>
353 -+ effect("status preserves lowercase merged and closed PR states from gh json", () =>
354 - Effect.gen(function* () {
355 - const repoDir = yield* makeTempDir("t3code-git-manager-");
356 - yield* initRepo(repoDir);
357 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
358 - baseRef: "main",
359 - headRef: "feature/status-lowercase-state",
360 - state: "merged",
361 -- updatedAt: "2026-01-02T00:00:00.000Z",
362 - });
363 - }),
364 - );
365 -
366 -- it.effect("status returns an explicit non-repo result for non-git directories", () =>
367 -+ effect("status returns an explicit non-repo result for non-git directories", () =>
368 - Effect.gen(function* () {
369 - const cwd = yield* makeTempDir("t3code-git-manager-non-repo-");
370 - const { manager } = yield* makeManager();
371 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
372 - }),
373 - );
374 -
375 -- it.effect("status returns an explicit non-repo result for deleted directories", () =>
376 -+ effect("status returns an explicit non-repo result for deleted directories", () =>
377 - Effect.gen(function* () {
378 - const rootDir = yield* makeTempDir("t3code-git-manager-missing-dir-");
379 - const cwd = NodePath.join(rootDir, "deleted-repo");
380 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
381 - }),
382 - );
383 -
384 -- it.effect("status briefly caches repeated lookups for the same cwd", () =>
385 -+ effect("status briefly caches repeated lookups for the same cwd", () =>
386 - Effect.gen(function* () {
387 - const repoDir = yield* makeTempDir("t3code-git-manager-");
388 - yield* initRepo(repoDir);
389 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
390 - expect(ghCalls.filter((call) => call.startsWith("pr list ")).length).toBeGreaterThan(0);
391 - }),
392 - );
393 --
394 - it.effect("status still looks up PRs for a branch pushed without --set-upstream", () =>
395 - Effect.gen(function* () {
396 - const repoDir = yield* makeTempDir("t3code-git-manager-");
397 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
398 - });
399 -
400 - it.effect(
401 -+ effect(
402 - "status ignores unrelated fork PRs when the current branch tracks the same repository",
403 - () =>
404 - Effect.gen(function* () {
405 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
406 - }),
407 - );
408 -
409 -- it.effect(
410 -+ effect(
411 - "status detects cross-repo PRs from the upstream remote URL owner",
412 - () =>
413 - Effect.gen(function* () {
414 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
415 - baseRef: "main",
416 - headRef: "statemachine",
417 - state: "open",
418 -- updatedAt: "2026-03-10T07:00:00.000Z",
419 - });
420 - expect(ghCalls).toContain(
421 - "pr list --head jasonLaster:statemachine --state all --limit 20 --json number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner",
422 - );
423 - }),
424 -- 20_000,
425 -+ LONG_EFFECT_TEST_TIMEOUT_MS,
426 - );
427 -
428 -- it.effect(
429 -- "status preserves a fork PR whose head is named after the default branch",
430 -- () =>
431 -- Effect.gen(function* () {
432 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
433 -- yield* initRepo(repoDir);
434 -- const originDir = yield* createBareRemote();
435 -- const forkDir = yield* createBareRemote();
436 -- yield* runGit(repoDir, ["remote", "add", "origin", originDir]);
437 -- yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
438 -- yield* runGit(repoDir, ["remote", "set-head", "origin", "main"]);
439 -- yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
440 -- yield* runGit(repoDir, ["push", "fork-seed", "main"]);
441 -- yield* runGit(repoDir, ["checkout", "-b", "t3code/pr-777/main"]);
442 -- yield* runGit(repoDir, ["branch", "--set-upstream-to", "fork-seed/main"]);
443 -- yield* configureVisibleRemoteUrlWithLocalRewrite(
444 -- repoDir,
445 -- "fork-seed",
446 -- "git@github.com:contributor/codething-mvp.git",
447 -- forkDir,
448 -- );
449 --
450 -- const { manager, ghCalls } = yield* makeManager({
451 -- ghScenario: {
452 -- prListByHeadSelector: {
453 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
454 -- "contributor:main": JSON.stringify([
455 -- {
456 -- number: 777,
457 -- title: "Fork PR from main",
458 -- url: "https://github.com/pingdotgg/codething-mvp/pull/777",
459 -- baseRefName: "main",
460 -- headRefName: "main",
461 -- state: "OPEN",
462 -- updatedAt: "2026-03-10T07:00:00Z",
463 -- isCrossRepository: true,
464 -- headRepository: {
465 -- nameWithOwner: "contributor/codething-mvp",
466 -- },
467 -- headRepositoryOwner: {
468 -- login: "contributor",
469 -- },
470 -- },
471 -- ]),
472 -- },
473 +@@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
474 + const requestedSessionId = String(request.sessionId ?? sessionId);
475 + promptCount += 1;
476 +
477 +- if (completeFirstPromptOnCancel && promptCount === 1) {
478 +- yield* agent.client.sessionUpdate({
479 +- sessionId: requestedSessionId,
480 +- update: {
481 +- sessionUpdate: "tool_call",
482 +- toolCallId: "native-cancel-tool",
483 +- title: "Long command",
484 +- kind: "execute",
485 +- status: "in_progress",
486 - },
487 - });
488 --
489 -- const status = yield* manager.status({ cwd: repoDir });
490 -- expect(status.refName).toBe("t3code/pr-777/main");
491 -- expect(status.pr).toEqual({
492 -- number: 777,
493 -- title: "Fork PR from main",
494 -- url: "https://github.com/pingdotgg/codething-mvp/pull/777",
495 -- baseRef: "main",
496 -- headRef: "main",
497 -- state: "open",
498 -- updatedAt: "2026-03-10T07:00:00.000Z",
499 +- yield* Deferred.await(nativeCancelRequested);
500 +- yield* Deferred.await(nativeCancelRelease);
501 +- yield* agent.client.sessionUpdate({
502 +- sessionId: requestedSessionId,
503 +- update: {
504 +- sessionUpdate: "tool_call_update",
505 +- toolCallId: "native-cancel-tool",
506 +- status: "failed",
507 +- content: [{ type: "content", content: { type: "text", text: "Cancelled." } }],
508 +- },
509 - });
510 -- expect(ghCalls).toContain(
511 -- "pr list --head contributor:main --state all --limit 20 --json number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner",
512 -- );
513 -- }),
514 -- 20_000,
515 -- );
516 +- yield* agent.client.sessionUpdate({
517 +- sessionId: requestedSessionId,
518 +- update: {
519 +- sessionUpdate: "agent_message_chunk",
520 +- content: { type: "text", text: "Request cancelled." },
521 +- },
522 +- });
523 +- return { stopReason: "cancelled", _meta: { nativeCancel: true } };
524 +- }
525 -
526 -- it.effect(
527 -+ effect(
528 - "status ignores synthetic local branch aliases when the upstream remote name contains slashes",
529 - () =>
530 - Effect.gen(function* () {
531 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
532 - baseRef: "main",
533 - headRef: "effect-atom",
534 - state: "open",
535 -- updatedAt: "2026-03-01T10:00:00.000Z",
536 - });
537 - expect(ghCalls.some((call) => call.includes("pr list --head upstream/effect-atom "))).toBe(
538 - false,
539 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
540 - ),
541 - ).toBe(false);
542 - }),
543 -- 20_000,
544 -+ LONG_EFFECT_TEST_TIMEOUT_MS,
545 - );
546 -
547 -- it.effect("status returns merged PR state when latest PR was merged", () =>
548 -+ effect("status returns merged PR state when latest PR was merged", () =>
549 - Effect.gen(function* () {
550 - const repoDir = yield* makeTempDir("t3code-git-manager-");
551 - yield* initRepo(repoDir);
552 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
553 - baseRef: "main",
554 - headRef: "feature/status-merged-pr",
555 - state: "merged",
556 -- updatedAt: "2026-01-30T10:00:00.000Z",
557 - });
558 - }),
559 + if (Number.isFinite(promptDelayMs) && promptDelayMs > 0) {
560 + yield* Effect.sleep(`${promptDelayMs} millis`);
561 + }
562 +@@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
563 );
564
565 -- it.effect("status hides merged PRs on the default branch", () =>
566 -- Effect.gen(function* () {
567 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
568 -- yield* initRepo(repoDir);
569 --
570 -- const { manager } = yield* makeManager({
571 -- ghScenario: {
572 -- prListSequence: [
573 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
574 -- JSON.stringify([
575 -- {
576 -- number: 23,
577 -- title: "Merged PR",
578 -- url: "https://github.com/pingdotgg/codething-mvp/pull/23",
579 -- baseRefName: "feature/status-default-branch-target",
580 -- headRefName: "main",
581 -- state: "MERGED",
582 -- mergedAt: "2026-01-30T10:00:00Z",
583 -- updatedAt: "2026-01-30T10:00:00Z",
584 -- },
585 -- ]),
586 -- ],
587 -- },
588 + yield* agent.handleUnknownExtRequest((method, params) => {
589 +- if (method === "_test/environment") {
590 +- return Effect.succeed({
591 +- inherited: process.env.T3_ACP_RUNTIME_AMBIENT === "sentinel",
592 +- explicit: process.env.T3_ACP_RUNTIME_EXPLICIT === "kept",
593 - });
594 --
595 -- const status = yield* manager.status({ cwd: repoDir });
596 -- expect(status.refName).toBe("main");
597 -- expect(status.pr).toBeNull();
598 -- }),
599 -- );
600 --
601 -- it.effect("status does not inherit a merged PR from a feature branch's default upstream", () =>
602 -- Effect.gen(function* () {
603 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
604 -- yield* initRepo(repoDir);
605 -- const remoteDir = yield* createBareRemote();
606 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
607 -- yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
608 -- yield* runGit(repoDir, ["remote", "set-head", "origin", "main"]);
609 -- yield* runGit(repoDir, ["checkout", "-b", "feature/from-main", "origin/main"]);
610 --
611 -- const { manager, ghCalls } = yield* makeManager({
612 -- ghScenario: {
613 -- prListSequence: [
614 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
615 -- JSON.stringify([
616 -- {
617 -- number: 54,
618 -- title: "Reverse merge from main",
619 -- url: "https://github.com/pingdotgg/codething-mvp/pull/54",
620 -- baseRefName: "je-filter-list",
621 -- headRefName: "main",
622 -- state: "MERGED",
623 -- mergedAt: "2023-09-28T03:21:10Z",
624 -- updatedAt: "2023-09-28T03:21:10Z",
625 -- },
626 -- ]),
627 -- ],
628 -- },
629 +- }
630 +- if (method === "_test/release-resume") {
631 +- return Deferred.succeed(resumeRelease, undefined).pipe(Effect.as({}));
632 +- }
633 +- if (method === "_test/finish-cancel") {
634 +- return Deferred.succeed(nativeCancelRelease, undefined).pipe(Effect.as({}));
635 +- }
636 +- if (method === "_test/startup-metadata") {
637 +- return Effect.gen(function* () {
638 +- for (const [metadataSessionId, commandName, modeId] of [
639 +- [sessionId, "plan", "code"],
640 +- ["child-session", "foreign-command", "ask"],
641 +- ] as const) {
642 +- yield* agent.client.sessionUpdate({
643 +- sessionId: metadataSessionId,
644 +- update: {
645 +- sessionUpdate: "available_commands_update",
646 +- availableCommands: [{ name: commandName, description: "Native command" }],
647 +- },
648 +- });
649 +- yield* agent.client.sessionUpdate({
650 +- sessionId: metadataSessionId,
651 +- update: { sessionUpdate: "current_mode_update", currentModeId: modeId },
652 +- });
653 +- yield* agent.client.sessionUpdate({
654 +- sessionId: metadataSessionId,
655 +- update: {
656 +- sessionUpdate: "config_option_update",
657 +- configOptions: configOptions().map((option) =>
658 +- option.type === "select" && option.category === "model"
659 +- ? {
660 +- ...option,
661 +- currentValue: metadataSessionId === sessionId ? "gpt-5.4" : "default",
662 +- }
663 +- : option,
664 +- ),
665 +- },
666 +- });
667 +- yield* agent.client.sessionUpdate({
668 +- sessionId: metadataSessionId,
669 +- update: {
670 +- sessionUpdate: "agent_message_chunk",
671 +- content: { type: "text", text: "Startup transcript must not replay." },
672 +- },
673 +- });
674 +- }
675 +- return {};
676 - });
677 --
678 -- const status = yield* manager.status({ cwd: repoDir });
679 -- expect(status.refName).toBe("feature/from-main");
680 -- expect(status.pr).toBeNull();
681 -- expect(ghCalls.some((call) => call.includes("pr list"))).toBe(false);
682 -- }),
683 -- );
684 --
685 -- it.effect("status prefers open PR when merged PR has newer updatedAt", () =>
686 -+ effect("status prefers open PR when merged PR has newer updatedAt", () =>
687 - Effect.gen(function* () {
688 - const repoDir = yield* makeTempDir("t3code-git-manager-");
689 - yield* initRepo(repoDir);
690 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
691 - baseRef: "main",
692 - headRef: "feature/status-open-over-merged",
693 - state: "open",
694 -- updatedAt: "2026-01-30T10:00:00.000Z",
695 - });
696 - }),
697 - );
698 -
699 -- it.effect("status is resilient to gh lookup failures and returns pr null", () =>
700 -+ effect("status is resilient to gh lookup failures and returns pr null", () =>
701 - Effect.gen(function* () {
702 - const repoDir = yield* makeTempDir("t3code-git-manager-");
703 - yield* initRepo(repoDir);
704 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
705 - }),
706 - );
707 -
708 -- it.effect("commits only selected files when filePaths is provided", () =>
709 -+ effect("commits only selected files when filePaths is provided", () =>
710 - Effect.gen(function* () {
711 - const repoDir = yield* makeTempDir("t3code-git-manager-");
712 - yield* initRepo(repoDir);
713 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
714 - }),
715 - );
716 -
717 -- it.effect("creates feature branch, commits, and pushes with featureBranch option", () =>
718 -+ effect("creates feature branch, commits, and pushes with featureBranch option", () =>
719 - Effect.gen(function* () {
720 - const repoDir = yield* makeTempDir("t3code-git-manager-");
721 - yield* initRepo(repoDir);
722 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
723 - }),
724 - );
725 -
726 -- it.effect("featureBranch uses custom commit message and derives branch name", () =>
727 -+ effect("featureBranch uses custom commit message and derives branch name", () =>
728 - Effect.gen(function* () {
729 - const repoDir = yield* makeTempDir("t3code-git-manager-");
730 - yield* initRepo(repoDir);
731 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
732 - }),
733 - );
734 -
735 -- it.effect("skips commit when there are no uncommitted changes", () =>
736 -+ effect("skips commit when there are no uncommitted changes", () =>
737 - Effect.gen(function* () {
738 - const repoDir = yield* makeTempDir("t3code-git-manager-");
739 - yield* initRepo(repoDir);
740 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
741 - }),
742 - );
743 -
744 -- it.effect("featureBranch returns error when worktree is clean", () =>
745 -+ effect("featureBranch returns error when worktree is clean", () =>
746 - Effect.gen(function* () {
747 - const repoDir = yield* makeTempDir("t3code-git-manager-");
748 - yield* initRepo(repoDir);
749 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
750 - }),
751 - );
752 -
753 -- it.effect("commits and pushes with upstream auto-setup when needed", () =>
754 -+ effect("commits and pushes with upstream auto-setup when needed", () =>
755 - Effect.gen(function* () {
756 - const repoDir = yield* makeTempDir("t3code-git-manager-");
757 - yield* initRepo(repoDir);
758 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
759 - }),
760 - );
761 -
762 -- it.effect(
763 -+ effect(
764 - "pushes and creates PR from a no-upstream branch when local commits are ahead of base",
765 - () =>
766 - Effect.gen(function* () {
767 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
768 - }),
769 - );
770 -
771 -- it.effect("skips push when branch is already up to date", () =>
772 -+ effect("skips push when branch is already up to date", () =>
773 - Effect.gen(function* () {
774 - const repoDir = yield* makeTempDir("t3code-git-manager-");
775 - yield* initRepo(repoDir);
776 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
777 - }),
778 - );
779 -
780 -- it.effect("pushes existing clean commits without rerunning commit logic", () =>
781 -+ effect("pushes existing clean commits without rerunning commit logic", () =>
782 - Effect.gen(function* () {
783 - const repoDir = yield* makeTempDir("t3code-git-manager-");
784 - yield* initRepo(repoDir);
785 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
786 - }),
787 - );
788 +- }
789 + if (method === "cursor/list_available_models") {
790 + return Effect.succeed({
791 + models: availableModels(),
792 +@@ apps/server/scripts/acp-mock-agent.ts: const program = Effect.gen(function* () {
793 + return Effect.succeed({});
794 + });
795
796 -- it.effect("pushes existing commits without committing dirty worktree changes", () =>
797 -- Effect.gen(function* () {
798 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
799 -- yield* initRepo(repoDir);
800 -- yield* runGit(repoDir, ["checkout", "-b", "feature/push-dirty"]);
801 -- const remoteDir = yield* createBareRemote();
802 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
803 -- NodeFS.writeFileSync(NodePath.join(repoDir, "push-dirty.txt"), "push dirty\n");
804 -- yield* runGit(repoDir, ["add", "push-dirty.txt"]);
805 -- yield* runGit(repoDir, ["commit", "-m", "Push dirty branch"]);
806 -- NodeFS.mkdirSync(NodePath.join(repoDir, ".vercel"));
807 -- NodeFS.writeFileSync(NodePath.join(repoDir, ".vercel", "project.json"), "{}\n");
808 --
809 -- const { manager } = yield* makeManager();
810 -- const result = yield* runStackedAction(manager, {
811 -- cwd: repoDir,
812 -- action: "push",
813 -- });
814 +- yield* agent.handleUnknownExtNotification((method) =>
815 +- method === "_test/exit" ? Effect.sync(() => process.exit(19)) : Effect.void,
816 +- );
817 -
818 -- expect(result.commit.status).toBe("skipped_not_requested");
819 -- expect(result.push.status).toBe("pushed");
820 -- expect(result.pr.status).toBe("skipped_not_requested");
821 -- expect(
822 -- yield* runGit(repoDir, ["status", "--porcelain"]).pipe(
823 -- Effect.map((output) => output.stdout.trim()),
824 -- ),
825 -- ).toContain("?? .vercel/");
826 -- expect(
827 -- yield* runGit(remoteDir, ["log", "-1", "--pretty=%s", "feature/push-dirty"]).pipe(
828 -- Effect.map((output) => output.stdout.trim()),
829 -- ),
830 -- ).toBe("Push dirty branch");
831 -- }),
832 -- );
833 --
834 -- it.effect("create_pr pushes a clean branch before creating the PR when needed", () =>
835 -+ effect("create_pr pushes a clean branch before creating the PR when needed", () =>
836 - Effect.gen(function* () {
837 - const repoDir = yield* makeTempDir("t3code-git-manager-");
838 - yield* initRepo(repoDir);
839 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
840 - }),
841 - );
842 -
843 -- it.effect("create_pr falls back to main when source control provider detection fails", () =>
844 -- Effect.gen(function* () {
845 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
846 -- yield* initRepo(repoDir);
847 -- yield* runGit(repoDir, ["checkout", "-b", "feature/provider-fallback"]);
848 -- NodeFS.writeFileSync(NodePath.join(repoDir, "provider-fallback.txt"), "fallback\n");
849 -- yield* runGit(repoDir, ["add", "provider-fallback.txt"]);
850 -- yield* runGit(repoDir, ["commit", "-m", "Provider fallback"]);
851 -- const remoteDir = yield* createBareRemote();
852 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
853 --
854 -- const { manager, ghCalls } = yield* makeManager({
855 -- ghScenario: {
856 -- prListSequence: [
857 -- "[]",
858 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
859 -- JSON.stringify([
860 -- {
861 -- number: 404,
862 -- title: "Provider fallback",
863 -- url: "https://github.com/pingdotgg/codething-mvp/pull/404",
864 -- baseRefName: "main",
865 -- headRefName: "feature/provider-fallback",
866 -- },
867 -- ]),
868 -- ],
869 -- },
870 -- });
871 --
872 -- const result = yield* runStackedAction(manager, {
873 -- cwd: repoDir,
874 -- action: "create_pr",
875 -- });
876 --
877 -- expect(result.pr.status).toBe("created");
878 -- expect(result.pr.number).toBe(404);
879 -- expect(
880 -- ghCalls.some((call) =>
881 -- call.includes("pr create --base main --head feature/provider-fallback"),
882 -- ),
883 -- ).toBe(true);
884 -- }),
885 -- );
886 --
887 -- it.effect("create_pr targets the remote default branch when it is not main", () =>
888 -- Effect.gen(function* () {
889 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
890 -- yield* initRepo(repoDir);
891 -- const remoteDir = yield* createBareRemote();
892 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
893 -- // A repository whose default branch is master, with no main anywhere.
894 -- yield* runGit(repoDir, ["push", "origin", "HEAD:master"]);
895 -- yield* runGit(repoDir, ["fetch", "origin"]);
896 -- yield* runGit(repoDir, ["remote", "set-head", "origin", "master"]);
897 --
898 -- yield* runGit(repoDir, ["checkout", "-b", "feature/master-default"]);
899 -- NodeFS.writeFileSync(NodePath.join(repoDir, "master-default.txt"), "master default\n");
900 -- yield* runGit(repoDir, ["add", "master-default.txt"]);
901 -- yield* runGit(repoDir, ["commit", "-m", "Master default"]);
902 --
903 -- const { manager, ghCalls } = yield* makeManager({
904 -- ghScenario: {
905 -- // Mirrors a provider that cannot report a default branch, as the Azure
906 -- // DevOps CLI does when it cannot detect the repository.
907 -- defaultBranch: "",
908 -- prListSequence: [
909 -- "[]",
910 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
911 -- JSON.stringify([
912 -- {
913 -- number: 505,
914 -- title: "Master default",
915 -- url: "https://github.com/pingdotgg/codething-mvp/pull/505",
916 -- baseRefName: "master",
917 -- headRefName: "feature/master-default",
918 -- },
919 -- ]),
920 -- ],
921 -- },
922 -- });
923 --
924 -- const result = yield* runStackedAction(manager, {
925 -- cwd: repoDir,
926 -- action: "create_pr",
927 -- });
928 --
929 -- expect(result.pr.status).toBe("created");
930 -- expect(
931 -- ghCalls.some((call) =>
932 -- call.includes("pr create --base master --head feature/master-default"),
933 -- ),
934 -- ).toBe(true);
935 -- }),
936 -- );
937 --
938 -- it.effect("returns existing PR metadata for commit/push/pr action", () =>
939 -+ effect("returns existing PR metadata for commit/push/pr action", () =>
940 - Effect.gen(function* () {
941 - const repoDir = yield* makeTempDir("t3code-git-manager-");
942 - yield* initRepo(repoDir);
943 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
944 - }),
945 - );
946 -
947 -- it.effect(
948 -+ effect(
949 - "returns existing cross-repo PR metadata using the fork owner selector",
950 - () =>
951 - Effect.gen(function* () {
952 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
953 - ).toBe(true);
954 - expect(ghCalls.some((call) => call.startsWith("pr create "))).toBe(false);
955 - }),
956 -- 12_000,
957 -+ LONG_EFFECT_TEST_TIMEOUT_MS,
958 - );
959 -
960 -- it.effect(
961 -+ effect(
962 - "returns the correct existing PR when a slash remote checks out to a synthetic local alias",
963 - () =>
964 - Effect.gen(function* () {
965 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
966 - false,
967 - );
968 - }),
969 -- 20_000,
970 -+ LONG_EFFECT_TEST_TIMEOUT_MS,
971 - );
972 -
973 -- it.effect(
974 -+ effect(
975 - "prefers owner-qualified selectors before bare branch names for cross-repo PRs",
976 - () =>
977 - Effect.gen(function* () {
978 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
979 - expect(ownerSelectorCallIndex).toBeGreaterThanOrEqual(0);
980 - expect(ghCalls.some((call) => call.startsWith("pr create "))).toBe(false);
981 - }),
982 -- 12_000,
983 -+ LONG_EFFECT_TEST_TIMEOUT_MS,
984 - );
985 -
986 -- it.effect(
987 -+ effect(
988 - "stops probing head selectors after finding an existing PR",
989 - () =>
990 - Effect.gen(function* () {
991 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
992 - "pr list --head octocat:statemachine --state open --limit 1",
993 - );
994 - }),
995 -- 12_000,
996 -- );
997 --
998 -- it.effect(
999 -- "does not reuse a cross-repo PR when GitHub omits head identity metadata",
1000 -- () =>
1001 -- Effect.gen(function* () {
1002 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
1003 -- yield* initRepo(repoDir);
1004 -- yield* runGit(repoDir, ["checkout", "-b", "statemachine"]);
1005 -- const forkDir = yield* createBareRemote();
1006 -- yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
1007 -- yield* runGit(repoDir, ["push", "-u", "fork-seed", "statemachine"]);
1008 -- yield* runGit(repoDir, [
1009 -- "config",
1010 -- "remote.fork-seed.url",
1011 -- "git@github.com:octocat/codething-mvp.git",
1012 -- ]);
1013 --
1014 -- const { manager, ghCalls } = yield* makeManager({
1015 -- ghScenario: {
1016 -- prListSequenceByHeadSelector: {
1017 -- "octocat:statemachine": [
1018 -- `[{"number":41,"title":"Ambiguous fork PR","url":"https://github.com/pingdotgg/codething-mvp/pull/41","baseRefName":"main","headRefName":"statemachine","state":"OPEN"}]`,
1019 -- `[{"number":142,"title":"Add stacked git actions","url":"https://github.com/pingdotgg/codething-mvp/pull/142","baseRefName":"main","headRefName":"statemachine","state":"OPEN","isCrossRepository":true,"headRepository":{"nameWithOwner":"octocat/codething-mvp"},"headRepositoryOwner":{"login":"octocat"}}]`,
1020 -- ],
1021 -- "fork-seed:statemachine": ["[]"],
1022 -- statemachine: ["[]"],
1023 -- },
1024 -- },
1025 -- });
1026 --
1027 -- const result = yield* runStackedAction(manager, {
1028 -- cwd: repoDir,
1029 -- action: "commit_push_pr",
1030 -- });
1031 --
1032 -- expect(result.pr.status).toBe("created");
1033 -- expect(result.pr.number).toBe(142);
1034 -- expect(ghCalls.some((call) => call.startsWith("pr create "))).toBe(true);
1035 -- }),
1036 -- 20_000,
1037 -+ LONG_EFFECT_TEST_TIMEOUT_MS,
1038 - );
1039 -
1040 -- it.effect("rejects same-repo PR metadata when matching a cross-repo head context", () =>
1041 -- Effect.sync(() => {
1042 -- const headContext = {
1043 -- headBranch: "statemachine",
1044 -- headRepositoryNameWithOwner: "pingdotgg/codething-mvp",
1045 -- headRepositoryOwnerLogin: "pingdotgg",
1046 -- isCrossRepository: true,
1047 -- };
1048 --
1049 -- expect(
1050 -- GitManager.matchesBranchHeadContext(
1051 -- {
1052 -- number: 41,
1053 -- title: "Same-repo PR",
1054 -- url: "https://github.com/pingdotgg/codething-mvp/pull/41",
1055 -- baseRefName: "main",
1056 -- headRefName: "statemachine",
1057 -- state: "open",
1058 -- updatedAt: Option.none(),
1059 -- isCrossRepository: false,
1060 -- headRepositoryNameWithOwner: "pingdotgg/codething-mvp",
1061 -- headRepositoryOwnerLogin: "pingdotgg",
1062 -- },
1063 -- headContext,
1064 -- ),
1065 -- ).toBe(false);
1066 --
1067 -- expect(
1068 -- GitManager.matchesBranchHeadContext(
1069 -- {
1070 -- number: 142,
1071 -- title: "Fork PR",
1072 -- url: "https://github.com/pingdotgg/codething-mvp/pull/142",
1073 -- baseRefName: "main",
1074 -- headRefName: "statemachine",
1075 -- state: "open",
1076 -- updatedAt: Option.none(),
1077 -- isCrossRepository: true,
1078 -- headRepositoryNameWithOwner: "pingdotgg/codething-mvp",
1079 -- headRepositoryOwnerLogin: "pingdotgg",
1080 -- },
1081 -- headContext,
1082 -- ),
1083 -- ).toBe(true);
1084 -- }),
1085 -- );
1086 --
1087 -- it.effect("accepts fork PR metadata when origin is the fork checkout remote", () =>
1088 -- Effect.sync(() => {
1089 -- const headContext = {
1090 -- headBranch: "t3code/git-audit-stability",
1091 -- headRepositoryNameWithOwner: "justsomelegs/t3code",
1092 -- headRepositoryOwnerLogin: "justsomelegs",
1093 -- isCrossRepository: false,
1094 -- };
1095 --
1096 -- expect(
1097 -- GitManager.matchesBranchHeadContext(
1098 -- {
1099 -- number: 2284,
1100 -- title: "Improve branch mismatch warnings",
1101 -- url: "https://github.com/pingdotgg/t3code/pull/2284",
1102 -- baseRefName: "main",
1103 -- headRefName: "t3code/git-audit-stability",
1104 -- state: "open",
1105 -- updatedAt: Option.none(),
1106 -- isCrossRepository: true,
1107 -- headRepositoryNameWithOwner: "justsomelegs/t3code",
1108 -- headRepositoryOwnerLogin: "justsomelegs",
1109 -- },
1110 -- headContext,
1111 -- ),
1112 -- ).toBe(true);
1113 -- }),
1114 -- );
1115 --
1116 -- it.effect("creates PR when one does not already exist", () =>
1117 -+ effect("creates PR when one does not already exist", () =>
1118 - Effect.gen(function* () {
1119 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1120 - yield* initRepo(repoDir);
1121 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1122 - }),
1123 - );
1124 -
1125 -- it.effect("generates PR content against the remote base when the local base is stale", () =>
1126 -+ effect("creates a new PR instead of reusing an unrelated fork PR with the same head branch", () =>
1127 - Effect.gen(function* () {
1128 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1129 - yield* initRepo(repoDir);
1130 -+ yield* runGit(repoDir, ["checkout", "-b", "feature/no-fork-match"]);
1131 - const remoteDir = yield* createBareRemote();
1132 - yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1133 -- yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
1134 -- yield* runGit(remoteDir, ["symbolic-ref", "HEAD", "refs/heads/main"]);
1135 --
1136 -- const peerDir = yield* makeTempDir("t3code-git-peer-");
1137 -- yield* runGit(peerDir, ["clone", remoteDir, "."]);
1138 -- yield* runGit(peerDir, ["config", "user.email", "peer@example.com"]);
1139 -- yield* runGit(peerDir, ["config", "user.name", "Peer User"]);
1140 -- NodeFS.writeFileSync(NodePath.join(peerDir, "remote.txt"), "remote\n");
1141 -- yield* runGit(peerDir, ["add", "remote.txt"]);
1142 -- yield* runGit(peerDir, ["commit", "-m", "Remote base commit"]);
1143 -- yield* runGit(peerDir, ["push", "origin", "main"]);
1144 --
1145 -- yield* runGit(repoDir, ["fetch", "origin"]);
1146 -- yield* runGit(repoDir, [
1147 -- "checkout",
1148 -- "--no-track",
1149 -- "-b",
1150 -- "feature/remote-base",
1151 -- "origin/main",
1152 -- ]);
1153 -- NodeFS.writeFileSync(NodePath.join(repoDir, "feature.txt"), "feature\n");
1154 -- yield* runGit(repoDir, ["add", "feature.txt"]);
1155 -+ fs.writeFileSync(path.join(repoDir, "changes.txt"), "change\n");
1156 -+ yield* runGit(repoDir, ["add", "changes.txt"]);
1157 - yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
1158 -- yield* runGit(repoDir, ["push", "-u", "origin", "feature/remote-base"]);
1159 -- yield* runGit(repoDir, ["config", "branch.feature/remote-base.gh-merge-base", "main"]);
1160 -+ yield* runGit(repoDir, ["push", "-u", "origin", "feature/no-fork-match"]);
1161 -
1162 -- let generatedCommitSummary = "";
1163 -- const { manager } = yield* makeManager({
1164 -+ const { manager, ghCalls } = yield* makeManager({
1165 - ghScenario: {
1166 -- prListSequence: ["[]", "[]"],
1167 -- },
1168 -- textGeneration: {
1169 -- generatePrContent: (input) => {
1170 -- generatedCommitSummary = input.commitSummary;
1171 -- return Effect.succeed({ title: "Feature PR", body: "Feature body" });
1172 -- },
1173 -+ prListSequence: [
1174 -+ JSON.stringify([
1175 -+ {
1176 -+ number: 1661,
1177 -+ title: "Fork PR with same branch name",
1178 -+ url: "https://github.com/pingdotgg/t3code/pull/1661",
1179 -+ baseRefName: "main",
1180 -+ headRefName: "feature/no-fork-match",
1181 -+ state: "OPEN",
1182 -+ isCrossRepository: true,
1183 -+ headRepository: {
1184 -+ nameWithOwner: "lnieuwenhuis/t3code",
1185 -+ },
1186 -+ headRepositoryOwner: {
1187 -+ login: "lnieuwenhuis",
1188 -+ },
1189 -+ },
1190 -+ ]),
1191 -+ JSON.stringify([
1192 -+ {
1193 -+ number: 188,
1194 -+ title: "Add stacked git actions",
1195 -+ url: "https://github.com/pingdotgg/codething-mvp/pull/188",
1196 -+ baseRefName: "main",
1197 -+ headRefName: "feature/no-fork-match",
1198 -+ state: "OPEN",
1199 -+ isCrossRepository: false,
1200 -+ },
1201 -+ ]),
1202 -+ ],
1203 - },
1204 - });
1205 --
1206 - const result = yield* runStackedAction(manager, {
1207 - cwd: repoDir,
1208 -- action: "create_pr",
1209 -+ action: "commit_push_pr",
1210 - });
1211 -
1212 - expect(result.pr.status).toBe("created");
1213 -- expect(generatedCommitSummary).toContain("Feature commit");
1214 -- expect(generatedCommitSummary).not.toContain("Remote base commit");
1215 -+ expect(result.pr.number).toBe(188);
1216 -+ expect(result.toast).toEqual({
1217 -+ title: "Created PR #188",
1218 -+ description: "Add stacked git actions",
1219 -+ cta: {
1220 -+ kind: "open_pr",
1221 -+ label: "View PR",
1222 -+ url: "https://github.com/pingdotgg/codething-mvp/pull/188",
1223 -+ },
1224 -+ });
1225 -+ expect(
1226 -+ ghCalls.some((call) => call.includes("pr create --base main --head feature/no-fork-match")),
1227 -+ ).toBe(true);
1228 - }),
1229 - );
1230 -
1231 -- it.effect(
1232 -- "creates a new PR instead of reusing an unrelated fork PR with the same head branch",
1233 -- () =>
1234 -- Effect.gen(function* () {
1235 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
1236 -- yield* initRepo(repoDir);
1237 -- yield* runGit(repoDir, ["checkout", "-b", "feature/no-fork-match"]);
1238 -- const remoteDir = yield* createBareRemote();
1239 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1240 -- NodeFS.writeFileSync(NodePath.join(repoDir, "changes.txt"), "change\n");
1241 -- yield* runGit(repoDir, ["add", "changes.txt"]);
1242 -- yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
1243 -- yield* runGit(repoDir, ["push", "-u", "origin", "feature/no-fork-match"]);
1244 --
1245 -- const { manager, ghCalls } = yield* makeManager({
1246 -- ghScenario: {
1247 -- prListSequence: [
1248 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
1249 -- JSON.stringify([
1250 -- {
1251 -- number: 1661,
1252 -- title: "Fork PR with same branch name",
1253 -- url: "https://github.com/pingdotgg/t3code/pull/1661",
1254 -- baseRefName: "main",
1255 -- headRefName: "feature/no-fork-match",
1256 -- state: "OPEN",
1257 -- isCrossRepository: true,
1258 -- headRepository: {
1259 -- nameWithOwner: "lnieuwenhuis/t3code",
1260 -- },
1261 -- headRepositoryOwner: {
1262 -- login: "lnieuwenhuis",
1263 -- },
1264 -- },
1265 -- ]),
1266 -- // @effect-diagnostics-next-line preferSchemaOverJson:off
1267 -- JSON.stringify([
1268 -- {
1269 -- number: 188,
1270 -- title: "Add stacked git actions",
1271 -- url: "https://github.com/pingdotgg/codething-mvp/pull/188",
1272 -- baseRefName: "main",
1273 -- headRefName: "feature/no-fork-match",
1274 -- state: "OPEN",
1275 -- isCrossRepository: false,
1276 -- },
1277 -- ]),
1278 -- ],
1279 -- },
1280 -- });
1281 -- const result = yield* runStackedAction(manager, {
1282 -- cwd: repoDir,
1283 -- action: "commit_push_pr",
1284 -- });
1285 --
1286 -- expect(result.pr.status).toBe("created");
1287 -- expect(result.pr.number).toBe(188);
1288 -- expect(result.toast).toEqual({
1289 -- title: "Created PR #188",
1290 -- description: "Add stacked git actions",
1291 -- cta: {
1292 -- kind: "open_pr",
1293 -- label: "View PR",
1294 -- url: "https://github.com/pingdotgg/codething-mvp/pull/188",
1295 -- },
1296 -- });
1297 -- expect(
1298 -- ghCalls.some((call) =>
1299 -- call.includes("pr create --base main --head feature/no-fork-match"),
1300 -- ),
1301 -- ).toBe(true);
1302 -- }),
1303 -- );
1304 --
1305 -- it.effect("creates cross-repo PRs with the fork owner selector and default base branch", () =>
1306 -+ effect("creates cross-repo PRs with the fork owner selector and default base branch", () =>
1307 - Effect.gen(function* () {
1308 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1309 - yield* initRepo(repoDir);
1310 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1311 - }),
1312 - );
1313 -
1314 -- it.effect("rejects push/pr actions from detached HEAD", () =>
1315 -+ effect("rejects push/pr actions from detached HEAD", () =>
1316 - Effect.gen(function* () {
1317 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1318 - yield* initRepo(repoDir);
1319 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1320 - }),
1321 - );
1322 -
1323 -- it.effect("surfaces missing gh binary errors", () =>
1324 -+ effect("surfaces missing gh binary errors", () =>
1325 - Effect.gen(function* () {
1326 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1327 - yield* initRepo(repoDir);
1328 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1329 - }),
1330 - );
1331 -
1332 -- it.effect("surfaces gh auth errors with guidance", () =>
1333 -+ effect("surfaces gh auth errors with guidance", () =>
1334 - Effect.gen(function* () {
1335 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1336 - yield* initRepo(repoDir);
1337 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1338 - }),
1339 - );
1340 -
1341 -- it.effect("resolves pull requests from #number references", () =>
1342 -+ effect("resolves pull requests from #number references", () =>
1343 - Effect.gen(function* () {
1344 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1345 - yield* initRepo(repoDir);
1346 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1347 - }),
1348 - );
1349 -
1350 -- it.effect("prepares pull request threads in local mode by checking out the PR branch", () =>
1351 -+ effect("prepares pull request threads in local mode by checking out the PR branch", () =>
1352 - Effect.gen(function* () {
1353 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1354 - yield* initRepo(repoDir);
1355 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1356 - }),
1357 - );
1358 -
1359 -- it.effect(
1360 -- "restores same-repository upstream tracking after local PR checkout without a remote ref",
1361 -- () =>
1362 -- Effect.gen(function* () {
1363 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
1364 -- yield* initRepo(repoDir);
1365 -- const remoteDir = yield* createBareRemote();
1366 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1367 -- yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
1368 -- yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-upstream"]);
1369 -- NodeFS.writeFileSync(NodePath.join(repoDir, "upstream.txt"), "upstream\n");
1370 -- yield* runGit(repoDir, ["add", "upstream.txt"]);
1371 -- yield* runGit(repoDir, ["commit", "-m", "Local upstream PR branch"]);
1372 -- yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-local-upstream"]);
1373 -- yield* runGit(repoDir, ["checkout", "main"]);
1374 -- yield* runGit(repoDir, ["branch", "-D", "feature/pr-local-upstream"]);
1375 -- yield* runGit(repoDir, [
1376 -- "update-ref",
1377 -- "-d",
1378 -- "refs/remotes/origin/feature/pr-local-upstream",
1379 -- ]);
1380 --
1381 -- const { manager } = yield* makeManager({
1382 -- ghScenario: {
1383 -- pullRequest: {
1384 -- number: 65,
1385 -- title: "Local upstream PR",
1386 -- url: "https://github.com/pingdotgg/codething-mvp/pull/65",
1387 -- baseRefName: "main",
1388 -- headRefName: "feature/pr-local-upstream",
1389 -- state: "open",
1390 -- isCrossRepository: false,
1391 -- headRepositoryNameWithOwner: "pingdotgg/codething-mvp",
1392 -- headRepositoryOwnerLogin: "pingdotgg",
1393 -- },
1394 -- repositoryCloneUrls: {
1395 -- "pingdotgg/codething-mvp": {
1396 -- url: remoteDir,
1397 -- sshUrl: remoteDir,
1398 -- },
1399 -- },
1400 -- },
1401 -- });
1402 --
1403 -- const result = yield* preparePullRequestThread(manager, {
1404 -- cwd: repoDir,
1405 -- reference: "65",
1406 -- mode: "local",
1407 -- });
1408 --
1409 -- expect(result.worktreePath).toBeNull();
1410 -- expect(result.branch).toBe("feature/pr-local-upstream");
1411 -- expect(
1412 -- (yield* runGit(repoDir, ["rev-parse", "--abbrev-ref", "@{upstream}"])).stdout.trim(),
1413 -- ).toBe("origin/feature/pr-local-upstream");
1414 -- }),
1415 -- );
1416 --
1417 -- it.effect(
1418 -- "restores same-repository upstream tracking when provider omits head repository metadata",
1419 -- () =>
1420 -- Effect.gen(function* () {
1421 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
1422 -- yield* initRepo(repoDir);
1423 -- const remoteDir = yield* createBareRemote();
1424 -- yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
1425 -- yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
1426 -- yield* runGit(repoDir, ["checkout", "-b", "feature/pr-local-no-head-repo"]);
1427 -- NodeFS.writeFileSync(NodePath.join(repoDir, "no-head-repo.txt"), "upstream\n");
1428 -- yield* runGit(repoDir, ["add", "no-head-repo.txt"]);
1429 -- yield* runGit(repoDir, ["commit", "-m", "Local PR branch without repo metadata"]);
1430 -- yield* runGit(repoDir, ["push", "-u", "origin", "feature/pr-local-no-head-repo"]);
1431 -- yield* runGit(repoDir, ["checkout", "main"]);
1432 -- yield* runGit(repoDir, ["branch", "-D", "feature/pr-local-no-head-repo"]);
1433 -- yield* runGit(repoDir, [
1434 -- "update-ref",
1435 -- "-d",
1436 -- "refs/remotes/origin/feature/pr-local-no-head-repo",
1437 -- ]);
1438 --
1439 -- const { manager } = yield* makeManager({
1440 -- ghScenario: {
1441 -- pullRequest: {
1442 -- number: 66,
1443 -- title: "Local upstream PR without repo metadata",
1444 -- url: "https://github.com/pingdotgg/codething-mvp/pull/66",
1445 -- baseRefName: "main",
1446 -- headRefName: "feature/pr-local-no-head-repo",
1447 -- state: "open",
1448 -- },
1449 -- },
1450 -- });
1451 --
1452 -- const result = yield* preparePullRequestThread(manager, {
1453 -- cwd: repoDir,
1454 -- reference: "66",
1455 -- mode: "local",
1456 -- });
1457 --
1458 -- expect(result.worktreePath).toBeNull();
1459 -- expect(result.branch).toBe("feature/pr-local-no-head-repo");
1460 -- expect(
1461 -- (yield* runGit(repoDir, ["rev-parse", "--abbrev-ref", "@{upstream}"])).stdout.trim(),
1462 -- ).toBe("origin/feature/pr-local-no-head-repo");
1463 -- }),
1464 -- );
1465 --
1466 -- it.effect("prepares pull request threads in worktree mode on the PR head branch", () =>
1467 -+ effect("prepares pull request threads in worktree mode on the PR head branch", () =>
1468 - Effect.gen(function* () {
1469 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1470 - yield* initRepo(repoDir);
1471 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1472 - }),
1473 - );
1474 -
1475 -- it.effect("preserves fork upstream tracking when preparing a local PR thread", () =>
1476 -+ effect("preserves fork upstream tracking when preparing a local PR thread", () =>
1477 - Effect.gen(function* () {
1478 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1479 - yield* initRepo(repoDir);
1480 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1481 - }),
1482 - );
1483 -
1484 -- it.effect("derives fork repository identity from PR URL when GitHub omits nameWithOwner", () =>
1485 -+ effect("derives fork repository identity from PR URL when GitHub omits nameWithOwner", () =>
1486 - Effect.gen(function* () {
1487 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1488 - yield* initRepo(repoDir);
1489 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1490 - }),
1491 - );
1492 -
1493 -- it.effect("reuses an existing dedicated worktree for the PR head branch", () =>
1494 -+ effect("reuses an existing dedicated worktree for the PR head branch", () =>
1495 - Effect.gen(function* () {
1496 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1497 - yield* initRepo(repoDir);
1498 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1499 - }),
1500 - );
1501 -
1502 -- it.effect(
1503 -+ effect(
1504 - "does not block fork PR worktree prep when the fork head branch collides with root main",
1505 - () =>
1506 - Effect.gen(function* () {
1507 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1508 - }),
1509 - );
1510 -
1511 -- it.effect(
1512 -- "does not overwrite an existing local main branch when preparing a fork PR worktree",
1513 -- () =>
1514 -- Effect.gen(function* () {
1515 -- const repoDir = yield* makeTempDir("t3code-git-manager-");
1516 -- yield* initRepo(repoDir);
1517 -- const originDir = yield* createBareRemote();
1518 -- const forkDir = yield* createBareRemote();
1519 -- yield* runGit(repoDir, ["remote", "add", "origin", originDir]);
1520 -- yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
1521 -- yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
1522 -- yield* runGit(repoDir, ["checkout", "-b", "fork-main-source"]);
1523 -- NodeFS.writeFileSync(NodePath.join(repoDir, "fork-main-second.txt"), "fork main second\n");
1524 -- yield* runGit(repoDir, ["add", "fork-main-second.txt"]);
1525 -- yield* runGit(repoDir, ["commit", "-m", "Fork main second branch"]);
1526 -- yield* runGit(repoDir, ["push", "-u", "fork-seed", "fork-main-source:main"]);
1527 -- yield* runGit(repoDir, ["checkout", "main"]);
1528 -- const localMainBefore = (yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim();
1529 -- yield* runGit(repoDir, ["checkout", "-b", "feature/root-branch"]);
1530 -+ effect("does not overwrite an existing local main branch when preparing a fork PR worktree", () =>
1531 -+ Effect.gen(function* () {
1532 -+ const repoDir = yield* makeTempDir("t3code-git-manager-");
1533 -+ yield* initRepo(repoDir);
1534 -+ const originDir = yield* createBareRemote();
1535 -+ const forkDir = yield* createBareRemote();
1536 -+ yield* runGit(repoDir, ["remote", "add", "origin", originDir]);
1537 -+ yield* runGit(repoDir, ["push", "-u", "origin", "main"]);
1538 -+ yield* runGit(repoDir, ["remote", "add", "fork-seed", forkDir]);
1539 -+ yield* runGit(repoDir, ["checkout", "-b", "fork-main-source"]);
1540 -+ fs.writeFileSync(path.join(repoDir, "fork-main-second.txt"), "fork main second\n");
1541 -+ yield* runGit(repoDir, ["add", "fork-main-second.txt"]);
1542 -+ yield* runGit(repoDir, ["commit", "-m", "Fork main second branch"]);
1543 -+ yield* runGit(repoDir, ["push", "-u", "fork-seed", "fork-main-source:main"]);
1544 -+ yield* runGit(repoDir, ["checkout", "main"]);
1545 -+ const localMainBefore = (yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim();
1546 -+ yield* runGit(repoDir, ["checkout", "-b", "feature/root-branch"]);
1547 -
1548 -- const { manager } = yield* makeManager({
1549 -- ghScenario: {
1550 -- pullRequest: {
1551 -- number: 92,
1552 -- title: "Fork main overwrite PR",
1553 -- url: "https://github.com/pingdotgg/codething-mvp/pull/92",
1554 -- baseRefName: "main",
1555 -- headRefName: "main",
1556 -- state: "open",
1557 -- isCrossRepository: true,
1558 -- headRepositoryNameWithOwner: "octocat/codething-mvp",
1559 -- headRepositoryOwnerLogin: "octocat",
1560 -- },
1561 -- repositoryCloneUrls: {
1562 -- "octocat/codething-mvp": {
1563 -- url: forkDir,
1564 -- sshUrl: forkDir,
1565 -- },
1566 -+ const { manager } = yield* makeManager({
1567 -+ ghScenario: {
1568 -+ pullRequest: {
1569 -+ number: 92,
1570 -+ title: "Fork main overwrite PR",
1571 -+ url: "https://github.com/pingdotgg/codething-mvp/pull/92",
1572 -+ baseRefName: "main",
1573 -+ headRefName: "main",
1574 -+ state: "open",
1575 -+ isCrossRepository: true,
1576 -+ headRepositoryNameWithOwner: "octocat/codething-mvp",
1577 -+ headRepositoryOwnerLogin: "octocat",
1578 -+ },
1579 -+ repositoryCloneUrls: {
1580 -+ "octocat/codething-mvp": {
1581 -+ url: forkDir,
1582 -+ sshUrl: forkDir,
1583 - },
1584 - },
1585 -- });
1586 -+ },
1587 -+ });
1588 -
1589 -- const result = yield* preparePullRequestThread(manager, {
1590 -- cwd: repoDir,
1591 -- reference: "92",
1592 -- mode: "worktree",
1593 -- });
1594 -+ const result = yield* preparePullRequestThread(manager, {
1595 -+ cwd: repoDir,
1596 -+ reference: "92",
1597 -+ mode: "worktree",
1598 -+ });
1599 -
1600 -- expect(result.branch).toBe("t3code/pr-92/main");
1601 -- expect((yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim()).toBe(localMainBefore);
1602 -- expect(
1603 -- (yield* runGit(result.worktreePath as string, [
1604 -- "rev-parse",
1605 -- "--abbrev-ref",
1606 -- "@{upstream}",
1607 -- ])).stdout.trim(),
1608 -- ).toBe("fork-seed/main");
1609 -- }),
1610 -+ expect(result.branch).toBe("t3code/pr-92/main");
1611 -+ expect((yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim()).toBe(localMainBefore);
1612 -+ expect(
1613 -+ (yield* runGit(result.worktreePath as string, [
1614 -+ "rev-parse",
1615 -+ "--abbrev-ref",
1616 -+ "@{upstream}",
1617 -+ ])).stdout.trim(),
1618 -+ ).toBe("fork-seed/main");
1619 -+ }),
1620 - );
1621 -
1622 -- it.effect("reuses an existing PR worktree and restores fork upstream tracking", () =>
1623 -+ effect("reuses an existing PR worktree and restores fork upstream tracking", () =>
1624 - Effect.gen(function* () {
1625 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1626 - yield* initRepo(repoDir);
1627 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1628 - }),
1629 - );
1630 -
1631 -- it.effect("emits ordered progress events for commit hooks", () =>
1632 -+ effect("emits ordered progress events for commit hooks", () =>
1633 - Effect.gen(function* () {
1634 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1635 - yield* initRepo(repoDir);
1636 -@@ apps/server/src/git/GitManager.test.ts: it.layer(GitManagerTestLayer)("GitManager", (it) => {
1637 - }),
1638 - );
1639 -
1640 -- it.effect("emits action_failed when a commit hook rejects", () =>
1641 -+ effect("emits action_failed when a commit hook rejects", () =>
1642 - Effect.gen(function* () {
1643 - const repoDir = yield* makeTempDir("t3code-git-manager-");
1644 - yield* initRepo(repoDir);
1645 + return yield* Effect.never;
1646 + }).pipe(
1647 + Effect.provide(
1648
1649 ## apps/server/src/git/Layers/CodexTextGeneration.ts (new) ##
1650 @@
1651 @@ apps/server/src/git/Services/TextGeneration.ts (new)
1652 +) {}
1653
1654 ## apps/server/src/orchestration/Layers/CheckpointReactor.test.ts ##
1655 -@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: async function waitForThread(
1656 - checkpoints: ReadonlyArray<{ checkpointTurnCount: number }>;
1657 - activities: ReadonlyArray<{ kind: string }>;
1658 - }) => boolean,
1659 -- timeoutMs = 15_000,
1660 -+ timeoutMs = 30_000,
1661 - ) {
1662 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: import {
1663 + } from "@t3tools/contracts";
1664 + import * as NodeServices from "@effect/platform-node/NodeServices";
1665 + import * as Clock from "effect/Clock";
1666 +-import * as Deferred from "effect/Deferred";
1667 + import * as Effect from "effect/Effect";
1668 + import * as Exit from "effect/Exit";
1669 + import * as Layer from "effect/Layer";
1670 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: import * as PubSub from "effect/PubSub";
1671 + import * as Queue from "effect/Queue";
1672 + import * as Scope from "effect/Scope";
1673 + import * as Stream from "effect/Stream";
1674 +-import { it as effectIt } from "@effect/vitest";
1675 + import { afterEach, describe, expect, it, vi } from "vite-plus/test";
1676 +
1677 + import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts";
1678 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: import {
1679 + type ProviderServiceShape,
1680 + } from "../../provider/Services/ProviderService.ts";
1681 + import { checkpointRefForThreadTurn } from "../../checkpointing/Utils.ts";
1682 +-import { ProviderValidationError } from "../../provider/Errors.ts";
1683 + import { ServerConfig } from "../../config.ts";
1684 + import * as WorkspaceEntries from "../../workspace/WorkspaceEntries.ts";
1685 + import * as WorkspacePaths from "../../workspace/WorkspacePaths.ts";
1686 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: function createProviderServiceHarness(
1687 + const rollbackConversation = vi.fn(
1688 + (_input: { readonly threadId: ThreadId; readonly numTurns: number }) => Effect.void,
1689 + );
1690 +- const assertConversationRollbackSupported = vi.fn<
1691 +- ProviderServiceShape["assertConversationRollbackSupported"]
1692 +- >(() => Effect.void);
1693 +
1694 + const unsupported = <A>() =>
1695 + Effect.die(new Error("Unsupported provider call in test")) as Effect.Effect<A, never>;
1696 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: function createProviderServiceHarness(
1697 + const service: ProviderServiceShape = {
1698 + startSession: () => unsupported(),
1699 + sendTurn: () => unsupported(),
1700 +- compactThread: () => unsupported(),
1701 + interruptTurn: () => unsupported(),
1702 + respondToRequest: () => unsupported(),
1703 + respondToUserInput: () => unsupported(),
1704 + stopSession: () => unsupported(),
1705 + listSessions,
1706 + getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }),
1707 +- assertConversationRollbackSupported,
1708 + getInstanceInfo: (instanceId) =>
1709 + Effect.succeed({
1710 + instanceId,
1711 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: function createProviderServiceHarness(
1712 +
1713 + return {
1714 + service,
1715 +- assertConversationRollbackSupported,
1716 + rollbackConversation,
1717 + emit,
1718 + };
1719 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: async function waitForThread(
1720 + checkpoints: ReadonlyArray<{ checkpointTurnCount: number }>;
1721 + activities: ReadonlyArray<{ kind: string }>;
1722 + }) => boolean,
1723 +- timeoutMs = 15_000,
1724 ++ timeoutMs = 30_000,
1725 + ) {
1726 const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs;
1727 const poll = async (): Promise<{
1728 @@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: async function waitForThread(
1729 @@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: async function w
1730 let runtime: ManagedRuntime.ManagedRuntime<
1731 | OrchestrationEngineService
1732 | CheckpointReactor
1733 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1734 + readonly providerSessionCwd?: string;
1735 + readonly providerName?: ProviderDriverKind;
1736 + readonly gitStatusRefreshCalls?: Array<string>;
1737 +- readonly pullRequestRefreshCalls?: Array<string>;
1738 + }) {
1739 + const cwd = createGitRepository();
1740 + tempDirs.push(cwd);
1741 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1742 + Effect.as({
1743 + isRepo: true,
1744 + hasPrimaryRemote: false,
1745 +- isDefaultRef:
1746 +- options?.localStatusRefName === undefined || options.localStatusRefName === "main",
1747 ++ isDefaultRef: true,
1748 + refName:
1749 + options?.localStatusRefName !== undefined ? options.localStatusRefName : "main",
1750 + hasWorkingTreeChanges: false,
1751 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1752 + }),
1753 + ),
1754 + refreshStatus: () => Effect.die("refreshStatus should not be called in this test"),
1755 +- refreshPullRequestStatus: (cwd: string) =>
1756 +- Effect.sync(() => {
1757 +- options?.pullRequestRefreshCalls?.push(cwd);
1758 +- }).pipe(Effect.as(null)),
1759 + streamStatus: () => Stream.empty,
1760 + });
1761 +
1762 @@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1763 };
1764 }
1765
1766 -- it("captures pre-turn baseline on turn.started and post-turn checkpoint on turn.completed", async () => {
1767 +- effectIt.effect("captures baseline and large turn summaries before completion receipts", () =>
1768 +- Effect.gen(function* () {
1769 +- const harness = yield* Effect.promise(() =>
1770 +- createHarness({ seedFilesystemCheckpoints: false }),
1771 +- );
1772 +- const createdAt = "2026-01-01T00:00:00.000Z";
1773 + test("captures pre-turn baseline on turn.started and post-turn checkpoint on turn.completed", async () => {
1774 - const harness = await createHarness({ seedFilesystemCheckpoints: false });
1775 ++ const harness = await createHarness({ seedFilesystemCheckpoints: false });
1776 ++ const createdAt = "2026-01-01T00:00:00.000Z";
1777 +
1778 + yield* harness.engine.dispatch({
1779 + type: "thread.session.set",
1780 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1781 + expect(gitStatusRefreshCalls).toEqual([harness.cwd]);
1782 + });
1783 +
1784 +- it("re-asks for the pull request at turn end when the thread branch is checked out", async () => {
1785 +- const pullRequestRefreshCalls: string[] = [];
1786 +- const harness = await createHarness({
1787 +- seedFilesystemCheckpoints: false,
1788 +- threadBranch: "t3code/feature",
1789 +- localStatusRefName: "t3code/feature",
1790 +- pullRequestRefreshCalls,
1791 +- });
1792 +-
1793 +- harness.provider.emit({
1794 +- type: "turn.completed",
1795 +- eventId: EventId.make("evt-turn-completed-refresh-pr"),
1796 +- provider: ProviderDriverKind.make("codex"),
1797 +- createdAt: "2026-01-01T00:00:00.000Z",
1798 +- threadId: ThreadId.make("thread-1"),
1799 +- turnId: asTurnId("turn-refresh-pr"),
1800 +- payload: { state: "completed" },
1801 +- });
1802 +-
1803 +- await harness.drain();
1804 +-
1805 +- expect(pullRequestRefreshCalls).toEqual([harness.cwd]);
1806 +- });
1807 +-
1808 +- it("re-asks for the pull request after adopting a drifted checkout", async () => {
1809 +- const pullRequestRefreshCalls: string[] = [];
1810 +- const harness = await createHarness({
1811 +- seedFilesystemCheckpoints: false,
1812 +- threadBranch: "t3code/original-branch",
1813 +- localStatusRefName: "t3code/renamed-by-agent",
1814 +- pullRequestRefreshCalls,
1815 +- });
1816 +-
1817 +- harness.provider.emit({
1818 +- type: "turn.completed",
1819 +- eventId: EventId.make("evt-turn-completed-drift-pr"),
1820 +- provider: ProviderDriverKind.make("codex"),
1821 +- createdAt: "2026-01-01T00:00:00.000Z",
1822 +- threadId: ThreadId.make("thread-1"),
1823 +- turnId: asTurnId("turn-drift-pr"),
1824 +- payload: { state: "completed" },
1825 +- });
1826 +-
1827 +- await harness.drain();
1828 +-
1829 +- expect(pullRequestRefreshCalls).toEqual([harness.cwd]);
1830 +- });
1831 +-
1832 +- it("does not re-ask for the pull request at turn end on the default branch", async () => {
1833 +- const pullRequestRefreshCalls: string[] = [];
1834 +- const harness = await createHarness({
1835 +- seedFilesystemCheckpoints: false,
1836 +- threadBranch: "main",
1837 +- localStatusRefName: "main",
1838 +- pullRequestRefreshCalls,
1839 +- });
1840 +-
1841 +- harness.provider.emit({
1842 +- type: "turn.completed",
1843 +- eventId: EventId.make("evt-turn-completed-no-pr-refresh"),
1844 +- provider: ProviderDriverKind.make("codex"),
1845 +- createdAt: "2026-01-01T00:00:00.000Z",
1846 +- threadId: ThreadId.make("thread-1"),
1847 +- turnId: asTurnId("turn-no-pr-refresh"),
1848 +- payload: { state: "completed" },
1849 +- });
1850 +-
1851 +- await harness.drain();
1852 +-
1853 +- expect(pullRequestRefreshCalls).toEqual([]);
1854 +- });
1855 +-
1856 + it("adopts a drifted checkout as the thread branch on a dedicated worktree", async () => {
1857 + const harness = await createHarness({
1858 + seedFilesystemCheckpoints: false,
1859 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1860 + });
1861 +
1862 + it("does not adopt a drifted checkout when the worktree is shared by another thread", async () => {
1863 +- const pullRequestRefreshCalls: string[] = [];
1864 + const harness = await createHarness({
1865 + seedFilesystemCheckpoints: false,
1866 + threadBranch: "t3code/original-branch",
1867 + localStatusRefName: "t3code/renamed-by-agent",
1868 + secondThreadSharingWorktree: true,
1869 +- pullRequestRefreshCalls,
1870 + });
1871 +
1872 + harness.provider.emit({
1873 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1874 + const snapshot = await harness.readModel();
1875 + const thread = snapshot.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
1876 + expect(thread?.branch).toBe("t3code/original-branch");
1877 +- expect(pullRequestRefreshCalls).toEqual([]);
1878 + });
1879 +
1880 + it("does not adopt a temporary placeholder checkout as the thread branch", async () => {
1881 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1882 + });
1883 +
1884 + it("ignores auxiliary thread turn completion while primary turn is active", async () => {
1885 +- const pullRequestRefreshCalls: string[] = [];
1886 +- const harness = await createHarness({
1887 +- seedFilesystemCheckpoints: false,
1888 +- threadBranch: "t3code/feature",
1889 +- localStatusRefName: "t3code/feature",
1890 +- pullRequestRefreshCalls,
1891 +- });
1892 ++ const harness = await createHarness({ seedFilesystemCheckpoints: false });
1893 const createdAt = "2026-01-01T00:00:00.000Z";
1894
1895 + await Effect.runPromise(
1896 @@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1897 + const midReadModel = await harness.readModel();
1898 + const midThread = midReadModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
1899 + expect(midThread?.checkpoints).toHaveLength(0);
1900 +- expect(pullRequestRefreshCalls).toEqual([]);
1901 +- expect(harness.pullRequestRefreshes).toEqual([]);
1902 +
1903 + harness.provider.emit({
1904 + type: "turn.completed",
1905 +@@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("CheckpointReactor", () => {
1906 + (entry) => entry.latestTurn?.turnId === "turn-main" && entry.checkpoints.length === 1,
1907 + );
1908 expect(thread.checkpoints[0]?.checkpointTurnCount).toBe(1);
1909 +- await harness.drain();
1910 +- expect(pullRequestRefreshCalls).toEqual([harness.cwd]);
1911 +- expect(harness.pullRequestRefreshes).toEqual([1]);
1912 });
1913
1914 - it("captures pre-turn and completion checkpoints for claude runtime events", async () => {
1915 @@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("Checkp
1916 ).toBe(true);
1917 });
1918
1919 +- effectIt.effect("rejects unsupported rewind before changing files, checkpoints, or history", () =>
1920 +- Effect.gen(function* () {
1921 +- const harness = yield* Effect.promise(() =>
1922 +- createHarness({ providerName: ProviderDriverKind.make("antigravity") }),
1923 +- );
1924 +- const threadId = ThreadId.make("thread-1");
1925 +- const createdAt = "2026-01-01T00:00:00.000Z";
1926 +- const checked = yield* Deferred.make<void>();
1927 +- harness.provider.assertConversationRollbackSupported.mockImplementation(() =>
1928 +- Deferred.succeed(checked, undefined).pipe(
1929 +- Effect.andThen(
1930 +- Effect.fail(
1931 +- new ProviderValidationError({
1932 +- operation: "ProviderService.assertConversationRollbackSupported",
1933 +- issue: "Provider 'antigravity' does not support conversation rewind.",
1934 +- }),
1935 +- ),
1936 +- ),
1937 +- ),
1938 +- );
1939 +-
1940 +- for (const turnCount of [1, 2]) {
1941 +- yield* harness.engine.dispatch({
1942 +- type: "thread.turn.start",
1943 +- commandId: CommandId.make(`cmd-unsupported-rewind-message-${turnCount}`),
1944 +- threadId,
1945 +- message: {
1946 +- messageId: MessageId.make(`message-unsupported-rewind-${turnCount}`),
1947 +- role: "user",
1948 +- text: `Keep message ${turnCount}`,
1949 +- attachments: [],
1950 +- },
1951 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
1952 +- runtimeMode: "approval-required",
1953 +- createdAt,
1954 +- });
1955 +- yield* harness.engine.dispatch({
1956 +- type: "thread.turn.diff.complete",
1957 +- commandId: CommandId.make(`cmd-unsupported-rewind-diff-${turnCount}`),
1958 +- threadId,
1959 +- turnId: asTurnId(`turn-unsupported-rewind-${turnCount}`),
1960 +- completedAt: createdAt,
1961 +- checkpointRef: checkpointRefForThreadTurn(threadId, turnCount),
1962 +- status: "ready",
1963 +- files: [],
1964 +- checkpointTurnCount: turnCount,
1965 +- createdAt,
1966 +- });
1967 +- }
1968 +- const before = (yield* Effect.promise(() => harness.readModel())).threads.find(
1969 +- (thread) => thread.id === threadId,
1970 +- );
1971 +-
1972 +- yield* harness.engine.dispatch({
1973 +- type: "thread.checkpoint.revert",
1974 +- commandId: CommandId.make("cmd-unsupported-rewind"),
1975 +- threadId,
1976 +- turnCount: 1,
1977 +- createdAt,
1978 +- });
1979 +- yield* Deferred.await(checked);
1980 +- yield* Effect.promise(() => harness.drain());
1981 +-
1982 +- const after = (yield* Effect.promise(() => harness.readModel())).threads.find(
1983 +- (thread) => thread.id === threadId,
1984 +- );
1985 +- expect(after?.checkpoints).toEqual(before?.checkpoints);
1986 +- expect(after?.messages).toEqual(before?.messages);
1987 +- expect(after?.latestTurn).toEqual(before?.latestTurn);
1988 +- expect(after?.activities).toContainEqual(
1989 +- expect.objectContaining({
1990 +- kind: "checkpoint.revert.failed",
1991 +- payload: expect.objectContaining({
1992 +- detail: expect.stringContaining("does not support conversation rewind"),
1993 +- }),
1994 +- }),
1995 +- );
1996 +- expect(harness.provider.rollbackConversation).not.toHaveBeenCalled();
1997 +- expect(NodeFS.readFileSync(NodePath.join(harness.cwd, "README.md"), "utf8")).toBe("v3\n");
1998 +- expect(gitRefExists(harness.cwd, checkpointRefForThreadTurn(threadId, 2))).toBe(true);
1999 +- }),
2000 +- );
2001 +-
2002 - it("executes provider revert and emits thread.reverted for checkpoint revert requests", async () => {
2003 + test("executes provider revert and emits thread.reverted for checkpoint revert requests", async () => {
2004 const harness = await createHarness();
2005 @@ apps/server/src/orchestration/Layers/CheckpointReactor.test.ts: describe("Checkp
2006
2007
2008 ## apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts ##
2009 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: import {
2010 + ProviderSession,
2011 + ProviderDriverKind,
2012 + ProviderInstanceId,
2013 +- ProviderSetupError,
2014 + } from "@t3tools/contracts";
2015 + import { createModelSelection } from "@t3tools/shared/model";
2016 + import {
2017 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: import {
2018 ThreadId,
2019 TurnId,
2020 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: import {
2021 -import * as PubSub from "effect/PubSub";
2022 -import * as Scope from "effect/Scope";
2023 -import * as Stream from "effect/Stream";
2024 +-import * as SqlClient from "effect/unstable/sql/SqlClient";
2025 -import { it as effectIt } from "@effect/vitest";
2026 -import { afterEach, describe, expect, it, vi } from "vite-plus/test";
2027 +import { Deferred, Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect";
2028 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: import {
2029
2030 import { deriveServerPaths, ServerConfig } from "../../config.ts";
2031 import { TextGenerationError } from "@t3tools/contracts";
2032 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: import {
2033 + ProviderService,
2034 + type ProviderServiceShape,
2035 + } from "../../provider/Services/ProviderService.ts";
2036 +-import { ProviderAuthService } from "../../provider/Services/ProviderAuthService.ts";
2037 + import { makeProviderRegistryLayer } from "../../provider/testUtils/providerRegistryMock.ts";
2038 + import { TextGeneration } from "../../textGeneration/TextGeneration.ts";
2039 + import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts";
2040 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: async function waitFor(
2041 +
2042 + describe("ProviderCommandReactor", () => {
2043 + let runtime: ManagedRuntime.ManagedRuntime<
2044 +- | OrchestrationEngineService
2045 +- | ProviderCommandReactor
2046 +- | ProjectionSnapshotQuery
2047 +- | SqlClient.SqlClient,
2048 ++ OrchestrationEngineService | ProviderCommandReactor | ProjectionSnapshotQuery,
2049 + unknown
2050 + > | null = null;
2051 + let scope: Scope.Closeable | null = null;
2052 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2053 readonly requiresNewThreadForModelChange?: boolean;
2054 readonly titleRegenerationCompletionDispatchFailures?: number;
2055 readonly titleRegenerationBeforeStart?: "one" | "two";
2056 - readonly serverActivation?: Effect.Effect<void>;
2057 +- readonly beforeReadySessionDispatch?: () => Effect.Effect<void>;
2058 +- readonly compactThreadEffect?: () => Effect.Effect<void, ProviderAdapterRequestError>;
2059 - readonly interruptTurnEffect?: () => Effect.Effect<void, ProviderAdapterRequestError>;
2060 - readonly stopSessionEffect?: () => Effect.Effect<void, ProviderAdapterRequestError>;
2061 readonly startSessionEffect?: (
2062 session: ProviderSession,
2063 ) => Effect.Effect<ProviderSession, ProviderAdapterRequestError>;
2064 +- readonly tryHandlePromptCommandEffect?: ProviderAuthService["Service"]["tryHandlePromptCommand"];
2065 + }) {
2066 + const now = "2026-01-01T00:00:00.000Z";
2067 + const baseDir =
2068 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2069 + const { stateDir } = deriveServerPathsSync(baseDir, undefined);
2070 + createdStateDirs.add(stateDir);
2071 + const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());
2072 +- const tryHandlePromptCommand = vi.fn<ProviderAuthService["Service"]["tryHandlePromptCommand"]>(
2073 +- input?.tryHandlePromptCommandEffect ?? (() => Effect.succeed(false)),
2074 +- );
2075 + let nextSessionIndex = 1;
2076 + const runtimeSessions: Array<ProviderSession> = [];
2077 + const modelSelection = input?.threadModelSelection ?? {
2078 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2079 turnId: asTurnId("turn-1"),
2080 }),
2081 );
2082 +- const compactThread = vi.fn((_: ThreadId) => input?.compactThreadEffect?.() ?? Effect.void);
2083 - const interruptTurn = vi.fn((_: unknown) => input?.interruptTurnEffect?.() ?? Effect.void);
2084 + const interruptTurn = vi.fn((_: unknown) => Effect.void);
2085 const respondToRequest = vi.fn<ProviderServiceShape["respondToRequest"]>(() => Effect.void);
2086 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
2087 const refreshStatus = vi.fn((_: string) =>
2088 Effect.succeed({
2089 isRepo: true,
2090 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2091 + const service: ProviderServiceShape = {
2092 + startSession: startSession as ProviderServiceShape["startSession"],
2093 + sendTurn: sendTurn as ProviderServiceShape["sendTurn"],
2094 +- compactThread,
2095 + interruptTurn: interruptTurn as ProviderServiceShape["interruptTurn"],
2096 + respondToRequest: respondToRequest as ProviderServiceShape["respondToRequest"],
2097 + respondToUserInput: respondToUserInput as ProviderServiceShape["respondToUserInput"],
2098 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2099 + Effect.succeed({
2100 + sessionModelSwitch: input?.sessionModelSwitch ?? "in-session",
2101 + }),
2102 +- assertConversationRollbackSupported: () => unsupported(),
2103 + getInstanceInfo: (instanceId) => {
2104 + const raw = String(instanceId);
2105 + const driverKind = ProviderDriverKind.make(
2106 +- raw.startsWith("claude")
2107 +- ? "claudeAgent"
2108 +- : raw.startsWith("codex")
2109 +- ? "codex"
2110 +- : raw.startsWith("antigravity")
2111 +- ? "antigravity"
2112 +- : raw,
2113 ++ raw.startsWith("claude") ? "claudeAgent" : raw.startsWith("codex") ? "codex" : raw,
2114 + );
2115 + return Effect.succeed({
2116 + instanceId,
2117 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2118 });
2119 },
2120 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
2121 return Stream.fromPubSub(runtimeEventPubSub);
2122 },
2123 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2124 + return Effect.die(new Error("Injected title regeneration completion failure"));
2125 + }
2126 + }
2127 +- return (
2128 +- command.type === "thread.session.set" && command.session.status === "ready"
2129 +- ? (input?.beforeReadySessionDispatch?.() ?? Effect.void)
2130 +- : Effect.void
2131 +- ).pipe(Effect.andThen(engine.dispatch(command)));
2132 ++ return engine.dispatch(command);
2133 + },
2134 + get streamDomainEvents() {
2135 + return engine.streamDomainEvents;
2136 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2137 + Layer.provideMerge(reactorOrchestrationLayer),
2138 + Layer.provideMerge(projectionSnapshotLayer),
2139 + Layer.provideMerge(Layer.succeed(ProviderService, service)),
2140 +- Layer.provide(Layer.mock(ProviderAuthService, { tryHandlePromptCommand })),
2141 + Layer.provideMerge(makeProviderRegistryLayer(providerSnapshots as never)),
2142 Layer.provideMerge(
2143 Layer.mock(GitWorkflowService.GitWorkflowService)({
2144 renameBranch,
2145 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
2146 ),
2147 Layer.provideMerge(
2148 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2149 + refreshLocalStatus: () =>
2150 + Effect.die("refreshLocalStatus should not be called in this test"),
2151 + refreshStatus,
2152 +- refreshPullRequestStatus: () =>
2153 +- Effect.die("refreshPullRequestStatus should not be called in this test"),
2154 + streamStatus: () => Stream.die("streamStatus should not be called in this test"),
2155 + }),
2156 + ),
2157 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2158 + }),
2159 + ),
2160 + Layer.provideMerge(ServerSettingsService.layerTest()),
2161 +- Layer.provideMerge(SqlitePersistenceMemory),
2162 + Layer.provideMerge(ServerConfig.layerTest(process.cwd(), baseDir)),
2163 + Layer.provideMerge(NodeServices.layer),
2164 + );
2165 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2166 + return {
2167 + engine,
2168 + readModel: () => Effect.runPromise(snapshotQuery.getSnapshot()),
2169 +- readPendingTurnStarts: () =>
2170 +- runtime!.runPromise(
2171 +- Effect.gen(function* () {
2172 +- const sql = yield* SqlClient.SqlClient;
2173 +- return yield* sql<{ readonly threadId: string }>`
2174 +- SELECT thread_id AS "threadId"
2175 +- FROM projection_turns
2176 +- WHERE turn_id IS NULL AND state = 'pending'
2177 +- `;
2178 +- }),
2179 +- ),
2180 +- tryHandlePromptCommand,
2181 + startSession,
2182 + sendTurn,
2183 +- compactThread,
2184 + interruptTurn,
2185 + respondToRequest,
2186 respondToUserInput,
2187 stopSession,
2188 renameBranch,
2189 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
2190 generateBranchName,
2191 generateThreadTitle,
2192 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2193 - expect(thread?.session?.runtimeMode).toBe("approval-required");
2194 - });
2195 + };
2196 + }
2197
2198 -- effectIt.effect("retains a turn dispatched immediately after start until activation", () =>
2199 +- effectIt.effect.each(["new", "ready", "stopped"] as const)(
2200 +- "handles sign-out for a %s thread before worktree repair, text helpers, or startup",
2201 +- (sessionStatus) =>
2202 +- Effect.gen(function* () {
2203 +- const instanceId = ProviderInstanceId.make("antigravity-personal");
2204 +- const handled = yield* Deferred.make<void>();
2205 +- const harness = yield* Effect.promise(() =>
2206 +- createHarness({
2207 +- ...(sessionStatus === "new"
2208 +- ? {}
2209 +- : {
2210 +- threadModelSelection: { instanceId, model: "gemini-3.1-pro" },
2211 +- }),
2212 +- tryHandlePromptCommandEffect: () =>
2213 +- Deferred.succeed(handled, undefined).pipe(Effect.as(true)),
2214 +- }),
2215 +- );
2216 +- const threadId = ThreadId.make("thread-1");
2217 +- const createdAt = "2026-01-01T00:00:00.000Z";
2218 +- if (sessionStatus !== "new") {
2219 +- yield* harness.engine.dispatch({
2220 +- type: "thread.session.set",
2221 +- commandId: CommandId.make("cmd-sign-out-bound-session"),
2222 +- threadId,
2223 +- session: {
2224 +- threadId,
2225 +- providerInstanceId: instanceId,
2226 +- providerName: "antigravity",
2227 +- status: sessionStatus,
2228 +- runtimeMode: "approval-required",
2229 +- activeTurnId: null,
2230 +- lastError: null,
2231 +- updatedAt: createdAt,
2232 +- },
2233 +- createdAt,
2234 +- });
2235 +- }
2236 +- yield* harness.engine.dispatch({
2237 +- type: "thread.meta.update",
2238 +- commandId: CommandId.make("cmd-sign-out-worktree"),
2239 +- threadId,
2240 +- title: "New thread",
2241 +- branch: "t3code/1234abcd",
2242 +- worktreePath: NodePath.join(harness.stateDir, "missing-worktree"),
2243 +- });
2244 +-
2245 +- yield* harness.engine.dispatch({
2246 +- type: "thread.turn.start",
2247 +- commandId: CommandId.make("cmd-provider-sign-out"),
2248 +- threadId,
2249 +- message: {
2250 +- messageId: MessageId.make("message-provider-sign-out"),
2251 +- role: "user",
2252 +- text: "/logout",
2253 +- attachments: [],
2254 +- },
2255 +- modelSelection: {
2256 +- instanceId:
2257 +- sessionStatus === "new" ? instanceId : ProviderInstanceId.make("antigravity-other"),
2258 +- model: "gemini-3.1-pro",
2259 +- },
2260 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2261 +- runtimeMode: "approval-required",
2262 +- createdAt,
2263 +- });
2264 +- yield* Deferred.await(handled);
2265 +- yield* Effect.promise(() => harness.drain());
2266 +-
2267 +- const thread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2268 +- (entry) => entry.id === threadId,
2269 +- );
2270 +- expect(thread?.session).toMatchObject({
2271 +- status: "stopped",
2272 +- providerName: "antigravity",
2273 +- providerInstanceId: instanceId,
2274 +- activeTurnId: null,
2275 +- lastError: null,
2276 +- });
2277 +- expect(thread?.messages.map((message) => message.text)).toEqual(["/logout"]);
2278 +- expect(thread?.activities).toContainEqual(
2279 +- expect.objectContaining({ kind: "provider.auth.signed-out", tone: "info", turnId: null }),
2280 +- );
2281 +- expect(yield* Effect.promise(() => harness.readPendingTurnStarts())).toEqual([]);
2282 +- expect(harness.tryHandlePromptCommand).toHaveBeenCalledWith({
2283 +- instanceId,
2284 +- text: "/logout",
2285 +- hasAttachments: false,
2286 +- });
2287 +- expect(harness.pruneWorktrees).not.toHaveBeenCalled();
2288 +- expect(harness.createWorktree).not.toHaveBeenCalled();
2289 +- expect(harness.generateThreadTitle).not.toHaveBeenCalled();
2290 +- expect(harness.generateBranchName).not.toHaveBeenCalled();
2291 +- expect(harness.startSession).not.toHaveBeenCalled();
2292 +- expect(harness.sendTurn).not.toHaveBeenCalled();
2293 +- }),
2294 +- );
2295 +-
2296 +- effectIt.effect("clears a failed sign-out request without sending it as a prompt", () =>
2297 - Effect.gen(function* () {
2298 -- const activation = yield* Deferred.make<void>();
2299 -- const started = yield* Deferred.make<ProviderSession>();
2300 +- const instanceId = ProviderInstanceId.make("antigravity-personal");
2301 +- const handled = yield* Deferred.make<void>();
2302 - const harness = yield* Effect.promise(() =>
2303 - createHarness({
2304 -- serverActivation: Deferred.await(activation),
2305 -- startSessionEffect: (session) =>
2306 -- Deferred.succeed(started, session).pipe(Effect.as(session)),
2307 +- threadModelSelection: { instanceId, model: "gemini-3.1-pro" },
2308 +- tryHandlePromptCommandEffect: () =>
2309 +- Deferred.succeed(handled, undefined).pipe(
2310 +- Effect.andThen(
2311 +- Effect.fail(
2312 +- new ProviderSetupError({
2313 +- instanceId,
2314 +- operation: "logout",
2315 +- detail: "The provider could not sign out. Try again.",
2316 +- }),
2317 +- ),
2318 +- ),
2319 +- ),
2320 - }),
2321 - );
2322 +- const threadId = ThreadId.make("thread-1");
2323 -
2324 - yield* harness.engine.dispatch({
2325 - type: "thread.turn.start",
2326 -- commandId: CommandId.make("cmd-turn-start-before-activation"),
2327 -- threadId: ThreadId.make("thread-1"),
2328 +- commandId: CommandId.make("cmd-provider-sign-out-failed"),
2329 +- threadId,
2330 - message: {
2331 -- messageId: MessageId.make("message-before-activation"),
2332 +- messageId: MessageId.make("message-provider-sign-out-failed"),
2333 - role: "user",
2334 -- text: "Start after activation",
2335 +- text: "/logout",
2336 - attachments: [],
2337 - },
2338 - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2339 - runtimeMode: "approval-required",
2340 - createdAt: "2026-01-01T00:00:00.000Z",
2341 - });
2342 -- expect(yield* Deferred.isDone(started)).toBe(false);
2343 --
2344 -- yield* Deferred.succeed(activation, undefined);
2345 -- const session = yield* Deferred.await(started);
2346 +- yield* Deferred.await(handled);
2347 - yield* Effect.promise(() => harness.drain());
2348 -- expect(session.threadId).toBe(ThreadId.make("thread-1"));
2349 -- expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({
2350 -- threadId: ThreadId.make("thread-1"),
2351 -- input: "Start after activation",
2352 +-
2353 +- const thread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2354 +- (entry) => entry.id === threadId,
2355 +- );
2356 +- expect(thread?.session).toMatchObject({
2357 +- status: "error",
2358 +- activeTurnId: null,
2359 +- lastError: expect.stringContaining("The provider could not sign out. Try again."),
2360 - });
2361 +- expect(thread?.activities).toContainEqual(
2362 +- expect.objectContaining({ kind: "provider.turn.start.failed", tone: "error" }),
2363 +- );
2364 +- expect(
2365 +- thread?.activities.some((activity) => activity.kind === "provider.auth.signed-out"),
2366 +- ).toBe(false);
2367 +- expect(yield* Effect.promise(() => harness.readPendingTurnStarts())).toEqual([]);
2368 +- expect(harness.startSession).not.toHaveBeenCalled();
2369 +- expect(harness.sendTurn).not.toHaveBeenCalled();
2370 - }),
2371 - );
2372 -
2373 -- effectIt.effect("projects starting before a slow provider session finishes", () =>
2374 +- effectIt.effect.each([
2375 +- { label: "a command mention", text: "What does /logout do?", attachments: [] },
2376 +- {
2377 +- label: "a command with an attachment",
2378 +- text: "/logout",
2379 +- attachments: [
2380 +- {
2381 +- type: "file" as const,
2382 +- id: "attached-notes",
2383 +- name: "notes.txt",
2384 +- mimeType: "text/plain",
2385 +- sizeBytes: 8,
2386 +- },
2387 +- ],
2388 +- },
2389 +- { label: "another provider's command", text: "/logout", attachments: [] },
2390 +- ])("sends $label when the provider auth handler leaves it unhandled", ({ text, attachments }) =>
2391 - Effect.gen(function* () {
2392 -- const releaseStart = yield* Deferred.make<void>();
2393 +- const started = yield* Deferred.make<void>();
2394 - const harness = yield* Effect.promise(() =>
2395 - createHarness({
2396 -- startSessionEffect: (session) => Deferred.await(releaseStart).pipe(Effect.as(session)),
2397 +- startSessionEffect: (session) =>
2398 +- Deferred.succeed(started, undefined).pipe(Effect.as(session)),
2399 - }),
2400 - );
2401 -- const now = "2026-01-01T00:00:00.000Z";
2402 -
2403 - yield* harness.engine.dispatch({
2404 - type: "thread.turn.start",
2405 -- commandId: CommandId.make("cmd-turn-start-slow-provider"),
2406 +- commandId: CommandId.make("cmd-provider-command-unhandled"),
2407 - threadId: ThreadId.make("thread-1"),
2408 - message: {
2409 -- messageId: asMessageId("user-message-slow-provider"),
2410 +- messageId: MessageId.make("message-provider-command-unhandled"),
2411 - role: "user",
2412 -- text: "start slowly",
2413 -- attachments: [],
2414 +- text,
2415 +- attachments,
2416 - },
2417 - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2418 - runtimeMode: "approval-required",
2419 -- createdAt: now,
2420 +- createdAt: "2026-01-01T00:00:00.000Z",
2421 - });
2422 +- yield* Deferred.await(started);
2423 +- yield* Effect.promise(() => harness.drain());
2424 -
2425 -- yield* Effect.promise(() => waitFor(() => harness.startSession.mock.calls.length === 1));
2426 -- const duringStartup = yield* Effect.promise(() => harness.readModel());
2427 -- expect(
2428 -- duringStartup.threads.find((entry) => entry.id === ThreadId.make("thread-1"))?.session
2429 -- ?.status,
2430 -- ).toBe("starting");
2431 -- expect(harness.sendTurn).not.toHaveBeenCalled();
2432 --
2433 -- yield* Deferred.succeed(releaseStart, undefined);
2434 -- yield* Effect.promise(() => waitFor(() => harness.sendTurn.mock.calls.length === 1));
2435 +- expect(harness.tryHandlePromptCommand).toHaveBeenCalledWith({
2436 +- instanceId: ProviderInstanceId.make("codex"),
2437 +- text,
2438 +- hasAttachments: attachments.length > 0,
2439 +- });
2440 +- expect(harness.sendTurn).toHaveBeenCalledWith(
2441 +- expect.objectContaining({
2442 +- input: text,
2443 +- ...(attachments.length > 0 ? { attachments } : {}),
2444 +- }),
2445 +- );
2446 - }),
2447 - );
2448 -
2449 -- effectIt.effect("settles a failed provider startup and allows a clean retry", () =>
2450 -- Effect.gen(function* () {
2451 -- let failStartup = true;
2452 -- const harness = yield* Effect.promise(() =>
2453 -- createHarness({
2454 -- startSessionEffect: (session) =>
2455 -- failStartup
2456 -- ? Effect.fail(
2457 -- new ProviderAdapterRequestError({
2458 -- provider: "codex",
2459 -- method: "thread.start",
2460 -- detail: "deterministic startup failure",
2461 -- }),
2462 -- )
2463 -- : Effect.succeed(session),
2464 + it("reacts to thread.turn.start by ensuring session and sending provider turn", async () => {
2465 + const harness = await createHarness();
2466 + const now = "2026-01-01T00:00:00.000Z";
2467 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2468 + expect(thread?.session?.runtimeMode).toBe("approval-required");
2469 + });
2470 +
2471 +- effectIt.effect("retains a turn dispatched immediately after start until activation", () =>
2472 +- Effect.gen(function* () {
2473 +- const activation = yield* Deferred.make<void>();
2474 +- const started = yield* Deferred.make<ProviderSession>();
2475 +- const harness = yield* Effect.promise(() =>
2476 +- createHarness({
2477 +- serverActivation: Deferred.await(activation),
2478 +- startSessionEffect: (session) =>
2479 +- Deferred.succeed(started, session).pipe(Effect.as(session)),
2480 - }),
2481 - );
2482 -- const now = "2026-01-01T00:00:00.000Z";
2483 +-
2484 +- yield* harness.engine.dispatch({
2485 +- type: "thread.turn.start",
2486 +- commandId: CommandId.make("cmd-turn-start-before-activation"),
2487 +- threadId: ThreadId.make("thread-1"),
2488 +- message: {
2489 +- messageId: MessageId.make("message-before-activation"),
2490 +- role: "user",
2491 +- text: "Start after activation",
2492 +- attachments: [],
2493 +- },
2494 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2495 +- runtimeMode: "approval-required",
2496 +- createdAt: "2026-01-01T00:00:00.000Z",
2497 +- });
2498 +- expect(yield* Deferred.isDone(started)).toBe(false);
2499 +-
2500 +- yield* Deferred.succeed(activation, undefined);
2501 +- const session = yield* Deferred.await(started);
2502 +- yield* Effect.promise(() => harness.drain());
2503 +- expect(session.threadId).toBe(ThreadId.make("thread-1"));
2504 +- expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({
2505 +- threadId: ThreadId.make("thread-1"),
2506 +- input: "Start after activation",
2507 +- });
2508 +- }),
2509 +- );
2510 + it("records session lastError and clears active turn when provider turn start fails", async () => {
2511 + const harness = await createHarness();
2512 + const now = new Date().toISOString();
2513 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
2514 + ) as never,
2515 + );
2516
2517 +- effectIt.effect("rejects /compact without conversation context", () =>
2518 +- Effect.gen(function* () {
2519 +- const harness = yield* Effect.promise(() => createHarness());
2520 - yield* harness.engine.dispatch({
2521 + await Effect.runPromise(
2522 + harness.engine.dispatch({
2523 type: "thread.turn.start",
2524 -- commandId: CommandId.make("cmd-turn-start-provider-failure"),
2525 +- commandId: CommandId.make("cmd-empty-compact"),
2526 + commandId: CommandId.make("cmd-turn-start-session-error"),
2527 threadId: ThreadId.make("thread-1"),
2528 message: {
2529 -- messageId: asMessageId("user-message-provider-failure"),
2530 +- messageId: asMessageId("user-message-empty-compact"),
2531 +- role: "user",
2532 +- text: "/compact",
2533 +- attachments: [],
2534 +- },
2535 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2536 +- runtimeMode: "approval-required",
2537 +- createdAt: "2026-01-01T00:00:00.000Z",
2538 +- });
2539 +- yield* Effect.promise(() => harness.drain());
2540 +- expect(harness.compactThread).not.toHaveBeenCalled();
2541 +- }),
2542 +- );
2543 +-
2544 +- effectIt.effect("keeps turns blocked until compaction restores the session", () =>
2545 +- Effect.gen(function* () {
2546 +- const readyDispatchStarted = yield* Deferred.make<void>();
2547 +- const releaseReadyDispatch = yield* Deferred.make<void>();
2548 +- let blockReadyDispatch = false;
2549 +- const harness = yield* Effect.promise(() =>
2550 +- createHarness({
2551 +- beforeReadySessionDispatch: () =>
2552 +- blockReadyDispatch
2553 +- ? Deferred.succeed(readyDispatchStarted, undefined).pipe(
2554 +- Effect.andThen(Deferred.await(releaseReadyDispatch)),
2555 +- )
2556 +- : Effect.void,
2557 +- }),
2558 +- );
2559 +- const threadId = ThreadId.make("thread-1");
2560 +- const now = "2026-01-01T00:00:00.000Z";
2561 +- const dispatchTurn = (id: string, text: string, createdAt: string) =>
2562 +- harness.engine.dispatch({
2563 +- type: "thread.turn.start",
2564 +- commandId: CommandId.make(`cmd-${id}`),
2565 +- threadId,
2566 +- message: {
2567 +- messageId: asMessageId(`user-message-${id}`),
2568 +- role: "user",
2569 +- text,
2570 +- attachments: [],
2571 +- },
2572 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2573 +- runtimeMode: "approval-required",
2574 +- createdAt,
2575 +- });
2576 +-
2577 +- yield* dispatchTurn("before-blocked-compact", "hello", now);
2578 +- yield* Effect.promise(() => waitFor(() => harness.sendTurn.mock.calls.length === 1));
2579 +- yield* harness.engine.dispatch({
2580 +- type: "thread.session.set",
2581 +- commandId: CommandId.make("cmd-session-ready-before-blocked-compact"),
2582 +- threadId,
2583 +- session: {
2584 +- threadId,
2585 +- status: "ready",
2586 +- providerName: "codex",
2587 +- providerInstanceId: ProviderInstanceId.make("codex"),
2588 +- runtimeMode: "approval-required",
2589 +- activeTurnId: null,
2590 +- lastError: null,
2591 +- updatedAt: now,
2592 +- },
2593 +- createdAt: now,
2594 +- });
2595 +-
2596 +- blockReadyDispatch = true;
2597 +- yield* dispatchTurn("blocked-compact", "/compact", "2026-01-01T00:00:01.000Z");
2598 +- yield* Deferred.await(readyDispatchStarted);
2599 +-
2600 +- yield* dispatchTurn("during-compact-recovery", "too soon", "2026-01-01T00:00:02.000Z");
2601 +- yield* Effect.promise(() =>
2602 +- waitFor(async () => {
2603 +- const thread = (await harness.readModel()).threads.find((entry) => entry.id === threadId);
2604 +- return (
2605 +- thread?.activities.some(
2606 +- (activity) => activity.kind === "provider.turn.start.failed",
2607 +- ) === true
2608 +- );
2609 +- }),
2610 +- );
2611 +- expect(harness.sendTurn).toHaveBeenCalledTimes(1);
2612 +- expect(yield* Effect.promise(() => harness.readPendingTurnStarts())).toEqual([
2613 +- { threadId: "thread-1" },
2614 +- ]);
2615 +-
2616 +- yield* Deferred.succeed(releaseReadyDispatch, undefined);
2617 +- yield* Effect.promise(() =>
2618 +- waitFor(async () => {
2619 +- const thread = (await harness.readModel()).threads.find((entry) => entry.id === threadId);
2620 +- return thread?.session?.status === "ready";
2621 +- }),
2622 +- );
2623 +- }),
2624 +- );
2625 +-
2626 +- effectIt.effect("does not overwrite concurrent session state after compaction failure", () =>
2627 +- Effect.gen(function* () {
2628 +- const releaseCompaction = yield* Deferred.make<void>();
2629 +- const releaseRunningCompaction = yield* Deferred.make<void>();
2630 +- const releaseFailedStop = yield* Deferred.make<void>();
2631 +- let compactionCount = 0;
2632 +- const harness = yield* Effect.promise(() =>
2633 +- createHarness({
2634 +- compactThreadEffect: () =>
2635 +- Deferred.await(
2636 +- compactionCount++ === 0 ? releaseCompaction : releaseRunningCompaction,
2637 +- ).pipe(Effect.andThen(Effect.die("Compaction stopped"))),
2638 +- stopSessionEffect: () =>
2639 +- Deferred.await(releaseFailedStop).pipe(
2640 +- Effect.andThen(
2641 +- Effect.fail(
2642 +- new ProviderAdapterRequestError({
2643 +- provider: "codex",
2644 +- method: "session.stop",
2645 +- detail: "provider stop failed",
2646 +- }),
2647 +- ),
2648 +- ),
2649 +- ),
2650 +- }),
2651 +- );
2652 +- const threadId = ThreadId.make("thread-1");
2653 +- const now = "2026-01-01T00:00:00.000Z";
2654 +- const dispatchCompact = (suffix: string, createdAt: string) =>
2655 +- harness.engine.dispatch({
2656 +- type: "thread.turn.start",
2657 +- commandId: CommandId.make(`cmd-compact-${suffix}`),
2658 +- threadId,
2659 +- message: {
2660 +- messageId: asMessageId(`user-message-compact-${suffix}`),
2661 +- role: "user",
2662 +- text: "/compact",
2663 +- attachments: [],
2664 +- },
2665 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2666 +- runtimeMode: "approval-required",
2667 +- createdAt,
2668 +- });
2669 +-
2670 +- yield* harness.engine.dispatch({
2671 +- type: "thread.turn.start",
2672 +- commandId: CommandId.make("cmd-message-before-compact"),
2673 +- threadId,
2674 +- message: {
2675 +- messageId: asMessageId("user-message-before-compact"),
2676 + messageId: asMessageId("user-message-session-error"),
2677 role: "user",
2678 -- text: "fail once",
2679 -+ text: "hello",
2680 + text: "hello",
2681 attachments: [],
2682 - },
2683 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2684 interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2685 runtimeMode: "approval-required",
2686 createdAt: now,
2687 - });
2688 -+ }),
2689 -+ );
2690 -
2691 +- yield* Effect.promise(() => waitFor(() => harness.sendTurn.mock.calls.length === 1));
2692 +- yield* harness.engine.dispatch({
2693 +- type: "thread.session.set",
2694 +- commandId: CommandId.make("cmd-session-ready-before-compact"),
2695 +- threadId,
2696 +- session: {
2697 +- threadId,
2698 +- status: "ready",
2699 +- providerName: "codex",
2700 +- providerInstanceId: ProviderInstanceId.make("codex"),
2701 +- runtimeMode: "approval-required",
2702 +- activeTurnId: null,
2703 +- lastError: null,
2704 +- updatedAt: now,
2705 +- },
2706 +- createdAt: now,
2707 +- });
2708 +- yield* dispatchCompact("before-stop", now);
2709 +- yield* Effect.promise(() => waitFor(() => harness.compactThread.mock.calls.length === 1));
2710 +- const compactingThread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2711 +- (entry) => entry.id === threadId,
2712 +- );
2713 +- expect(compactingThread?.session?.status).toBe("starting");
2714 +- yield* harness.engine.dispatch({
2715 +- type: "thread.session.stop",
2716 +- commandId: CommandId.make("cmd-stop-during-compact"),
2717 +- threadId,
2718 +- createdAt: "2026-01-01T00:00:01.000Z",
2719 +- });
2720 +- yield* Effect.promise(() => waitFor(() => harness.stopSession.mock.calls.length === 1));
2721 +- yield* Deferred.succeed(releaseCompaction, undefined);
2722 - yield* Effect.promise(() =>
2723 - waitFor(async () => {
2724 -- const readModel = await harness.readModel();
2725 +- const compactingThread = (await harness.readModel()).threads.find(
2726 +- (entry) => entry.id === threadId,
2727 +- );
2728 - return (
2729 -- readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"))?.session
2730 -- ?.status === "error"
2731 +- compactingThread?.activities.some(
2732 +- (activity) => activity.kind === "provider.turn.start.failed",
2733 +- ) === true
2734 - );
2735 - }),
2736 -+ await waitFor(async () => {
2737 -+ const readModel = await Effect.runPromise(harness.engine.getReadModel());
2738 -+ const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2739 -+ return (
2740 -+ thread?.session?.lastError ===
2741 -+ 'Provider adapter request failed (cursor) for session/set_config_option: Invalid value for session config option "model"' &&
2742 -+ thread?.session?.status === "ready" &&
2743 -+ thread?.session?.activeTurnId === null
2744 - );
2745 -- let readModel = yield* Effect.promise(() => harness.readModel());
2746 -- let thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2747 -- expect(thread?.session?.lastError).toContain("deterministic startup failure");
2748 -- expect(harness.sendTurn).not.toHaveBeenCalled();
2749 +- );
2750 +- const stoppingThread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2751 +- (entry) => entry.id === threadId,
2752 +- );
2753 +- expect(stoppingThread?.session?.status).toBe("starting");
2754 +- yield* Deferred.succeed(releaseFailedStop, undefined);
2755 +- yield* Effect.promise(() => harness.drain());
2756 +-
2757 +- const recoveredThread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2758 +- (entry) => entry.id === threadId,
2759 +- );
2760 +- expect(recoveredThread?.session?.status).toBe("ready");
2761 +- expect(
2762 +- recoveredThread?.activities.find(
2763 +- (activity) => activity.kind === "provider.session.stop.failed",
2764 +- ),
2765 +- ).toMatchObject({
2766 +- summary: "Provider session stop failed",
2767 +- payload: { detail: "provider stop failed" },
2768 +- });
2769 +-
2770 +- yield* dispatchCompact("before-running", "2026-01-01T00:00:02.000Z");
2771 +- yield* Effect.promise(() => waitFor(() => harness.compactThread.mock.calls.length === 2));
2772 +- yield* harness.engine.dispatch({
2773 +- type: "thread.session.stop",
2774 +- commandId: CommandId.make("cmd-failed-stop-before-compaction-settles"),
2775 +- threadId,
2776 +- createdAt: "2026-01-01T00:00:02.500Z",
2777 +- });
2778 +- yield* Effect.promise(() =>
2779 +- waitFor(async () => {
2780 +- const thread = (await harness.readModel()).threads.find((entry) => entry.id === threadId);
2781 +- return (
2782 +- thread?.activities.filter(
2783 +- (activity) => activity.kind === "provider.session.stop.failed",
2784 +- ).length === 2
2785 +- );
2786 +- }),
2787 +- );
2788 +- const restartedThread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2789 +- (entry) => entry.id === threadId,
2790 +- );
2791 +- expect(restartedThread?.session?.status).toBe("starting");
2792 +- const restartedSession = restartedThread?.session;
2793 +- if (!restartedSession) return yield* Effect.die("Compaction session missing");
2794 +- yield* harness.engine.dispatch({
2795 +- type: "thread.session.set",
2796 +- commandId: CommandId.make("cmd-running-during-compact"),
2797 +- threadId,
2798 +- session: {
2799 +- ...restartedSession,
2800 +- status: "running",
2801 +- activeTurnId: asTurnId("compaction-turn"),
2802 +- updatedAt: "2026-01-01T00:00:03.000Z",
2803 +- },
2804 +- createdAt: "2026-01-01T00:00:03.000Z",
2805 +- });
2806 +- yield* Deferred.succeed(releaseRunningCompaction, undefined);
2807 +- yield* Effect.promise(() => harness.drain());
2808 +- const runningThread = (yield* Effect.promise(() => harness.readModel())).threads.find(
2809 +- (entry) => entry.id === threadId,
2810 +- );
2811 +- expect(runningThread?.session?.status).toBe("running");
2812 +- }),
2813 +- );
2814 +- effectIt.effect("projects starting before a slow provider session finishes", () =>
2815 +- Effect.gen(function* () {
2816 +- const releaseStart = yield* Deferred.make<void>();
2817 +- const harness = yield* Effect.promise(() =>
2818 +- createHarness({
2819 +- startSessionEffect: (session) => Deferred.await(releaseStart).pipe(Effect.as(session)),
2820 +- }),
2821 +- );
2822 +- const now = "2026-01-01T00:00:00.000Z";
2823 -
2824 -- failStartup = false;
2825 - yield* harness.engine.dispatch({
2826 - type: "thread.turn.start",
2827 -- commandId: CommandId.make("cmd-turn-start-provider-retry"),
2828 +- commandId: CommandId.make("cmd-turn-start-slow-provider"),
2829 - threadId: ThreadId.make("thread-1"),
2830 - message: {
2831 -- messageId: asMessageId("user-message-provider-retry"),
2832 +- messageId: asMessageId("user-message-slow-provider"),
2833 - role: "user",
2834 -- text: "retry",
2835 +- text: "start slowly",
2836 - attachments: [],
2837 - },
2838 - interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2839 - runtimeMode: "approval-required",
2840 -- createdAt: "2026-01-01T00:00:01.000Z",
2841 +- createdAt: now,
2842 - });
2843 -+ });
2844 -
2845 +-
2846 +- yield* Effect.promise(() => waitFor(() => harness.startSession.mock.calls.length === 1));
2847 +- const duringStartup = yield* Effect.promise(() => harness.readModel());
2848 +- expect(
2849 +- duringStartup.threads.find((entry) => entry.id === ThreadId.make("thread-1"))?.session
2850 +- ?.status,
2851 +- ).toBe("starting");
2852 +- expect(harness.sendTurn).not.toHaveBeenCalled();
2853 +-
2854 +- yield* Deferred.succeed(releaseStart, undefined);
2855 - yield* Effect.promise(() => waitFor(() => harness.sendTurn.mock.calls.length === 1));
2856 -- readModel = yield* Effect.promise(() => harness.readModel());
2857 -- thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2858 -- expect(thread?.session?.status).toBe("starting");
2859 -- expect(thread?.session?.lastError).toBeNull();
2860 - }),
2861 - );
2862 -+ const readModel = await Effect.runPromise(harness.engine.getReadModel());
2863 -+ const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2864 -+ expect(thread?.session).toMatchObject({
2865 -+ status: "ready",
2866 -+ activeTurnId: null,
2867 -+ lastError:
2868 -+ 'Provider adapter request failed (cursor) for session/set_config_option: Invalid value for session config option "model"',
2869 -+ });
2870 -+ });
2871 -
2872 - it("retries thread title generation after a transient failure", async () => {
2873 - const harness = await createHarness();
2874 -@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2875 - ).toBe(prompt);
2876 - });
2877 -
2878 -- it("recreates a missing worktree from the thread branch before starting a turn", async () => {
2879 +-
2880 +- effectIt.effect("settles a failed provider startup and allows a clean retry", () =>
2881 +- Effect.gen(function* () {
2882 +- let failStartup = true;
2883 +- const harness = yield* Effect.promise(() =>
2884 +- createHarness({
2885 +- startSessionEffect: (session) =>
2886 +- failStartup
2887 +- ? Effect.fail(
2888 +- new ProviderAdapterRequestError({
2889 +- provider: "codex",
2890 +- method: "thread.start",
2891 +- detail: "deterministic startup failure",
2892 +- }),
2893 +- )
2894 +- : Effect.succeed(session),
2895 +- }),
2896 +- );
2897 +- const now = "2026-01-01T00:00:00.000Z";
2898 +-
2899 +- yield* harness.engine.dispatch({
2900 +- type: "thread.turn.start",
2901 +- commandId: CommandId.make("cmd-turn-start-provider-failure"),
2902 +- threadId: ThreadId.make("thread-1"),
2903 +- message: {
2904 +- messageId: asMessageId("user-message-provider-failure"),
2905 +- role: "user",
2906 +- text: "fail once",
2907 +- attachments: [],
2908 +- },
2909 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2910 +- runtimeMode: "approval-required",
2911 +- createdAt: now,
2912 +- });
2913 ++ }),
2914 ++ );
2915 +
2916 +- yield* Effect.promise(() =>
2917 +- waitFor(async () => {
2918 +- const readModel = await harness.readModel();
2919 +- return (
2920 +- readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"))?.session
2921 +- ?.status === "error"
2922 +- );
2923 +- }),
2924 ++ await waitFor(async () => {
2925 ++ const readModel = await Effect.runPromise(harness.engine.getReadModel());
2926 ++ const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2927 ++ return (
2928 ++ thread?.session?.lastError ===
2929 ++ 'Provider adapter request failed (cursor) for session/set_config_option: Invalid value for session config option "model"' &&
2930 ++ thread?.session?.status === "ready" &&
2931 ++ thread?.session?.activeTurnId === null
2932 + );
2933 +- let readModel = yield* Effect.promise(() => harness.readModel());
2934 +- let thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2935 +- expect(thread?.session?.lastError).toContain("deterministic startup failure");
2936 +- expect(harness.sendTurn).not.toHaveBeenCalled();
2937 +-
2938 +- failStartup = false;
2939 +- yield* harness.engine.dispatch({
2940 +- type: "thread.turn.start",
2941 +- commandId: CommandId.make("cmd-turn-start-provider-retry"),
2942 +- threadId: ThreadId.make("thread-1"),
2943 +- message: {
2944 +- messageId: asMessageId("user-message-provider-retry"),
2945 +- role: "user",
2946 +- text: "retry",
2947 +- attachments: [],
2948 +- },
2949 +- interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
2950 +- runtimeMode: "approval-required",
2951 +- createdAt: "2026-01-01T00:00:01.000Z",
2952 +- });
2953 ++ });
2954 +
2955 +- yield* Effect.promise(() => waitFor(() => harness.sendTurn.mock.calls.length === 1));
2956 +- readModel = yield* Effect.promise(() => harness.readModel());
2957 +- thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2958 +- expect(thread?.session?.status).toBe("starting");
2959 +- expect(thread?.session?.lastError).toBeNull();
2960 +- }),
2961 +- );
2962 ++ const readModel = await Effect.runPromise(harness.engine.getReadModel());
2963 ++ const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
2964 ++ expect(thread?.session).toMatchObject({
2965 ++ status: "ready",
2966 ++ activeTurnId: null,
2967 ++ lastError:
2968 ++ 'Provider adapter request failed (cursor) for session/set_config_option: Invalid value for session config option "model"',
2969 ++ });
2970 ++ });
2971 +
2972 + it("retries thread title generation after a transient failure", async () => {
2973 + const harness = await createHarness();
2974 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2975 + readModel.threads
2976 + .find((entry) => entry.id === ThreadId.make("thread-1"))
2977 + ?.messages.find((entry) => entry.id === asMessageId("user-message-branch-model"))?.text,
2978 +- ).toBe(prompt);
2979 +- });
2980 +-
2981 +- it("recreates a missing worktree from the thread branch before starting a turn", async () => {
2982 - const harness = await createHarness();
2983 - const now = "2026-01-01T00:00:00.000Z";
2984 - const worktreePath = NodePath.join(harness.stateDir, "missing-worktree");
2985 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
2986 - expect(harness.createWorktree.mock.invocationCallOrder[0]).toBeLessThan(
2987 - harness.startSession.mock.invocationCallOrder[0]!,
2988 - );
2989 -- });
2990 --
2991 ++ ).toBe(prompt);
2992 + });
2993 +
2994 it("forwards codex model options through session start and turn send", async () => {
2995 - const harness = await createHarness();
2996 - const now = "2026-01-01T00:00:00.000Z";
2997 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
2998 });
2999 });
3000 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("P
3001 }),
3002 ),
3003 );
3004 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts: describe("ProviderCommandReactor", () => {
3005 + commandId: CommandId.make("cmd-auto-settle-with-session"),
3006 + threadId: ThreadId.make("thread-1"),
3007 + snapshotSequence: beforeSettlement.snapshotSequence,
3008 +- settledAt: now,
3009 + });
3010 +
3011 + yield* Deferred.await(sessionStopped);
3012
3013 ## apps/server/src/orchestration/Layers/ProviderCommandReactor.ts ##
3014 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: import { isTemporaryWorktreeBranch, WORKTREE_BRANCH_PREFIX } from "@t3tools/shar
3015 + import * as Cache from "effect/Cache";
3016 + import * as Cause from "effect/Cause";
3017 + import * as Crypto from "effect/Crypto";
3018 +-import * as DateTime from "effect/DateTime";
3019 + import * as Duration from "effect/Duration";
3020 + import * as Effect from "effect/Effect";
3021 + import * as Equal from "effect/Equal";
3022 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker";
3023 +
3024 + import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts";
3025 + import { increment, orchestrationEventsProcessedTotal } from "../../observability/Metrics.ts";
3026 +-import {
3027 +- ProviderAdapterRequestError,
3028 +- ProviderAdapterValidationError,
3029 +-} from "../../provider/Errors.ts";
3030 ++import { ProviderAdapterRequestError } from "../../provider/Errors.ts";
3031 + import type { ProviderServiceError } from "../../provider/Errors.ts";
3032 + import { TextGeneration } from "../../textGeneration/TextGeneration.ts";
3033 +-import { ProviderAuthService } from "../../provider/Services/ProviderAuthService.ts";
3034 + import { ProviderService } from "../../provider/Services/ProviderService.ts";
3035 + import { ProviderRegistry } from "../../provider/Services/ProviderRegistry.ts";
3036 + import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts";
3037 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: import {
3038 + import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts";
3039 + import { GitWorkflowService } from "../../git/GitWorkflowService.ts";
3040 + const isProviderAdapterRequestError = Schema.is(ProviderAdapterRequestError);
3041 +-const isProviderAdapterValidationError = Schema.is(ProviderAdapterValidationError);
3042 + const isProviderDriverKind = Schema.is(ProviderDriverKind);
3043 +
3044 + type ProviderIntentEvent = Extract<
3045 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: function toNonEmptyProviderInput(value: string | undefined): string | undefined
3046 + return normalized && normalized.length > 0 ? normalized : undefined;
3047 + }
3048 +
3049 +-const isCompactCommandMessage = (message: ThreadTitleMessage): boolean =>
3050 +- message.role === "user" &&
3051 +- (message.attachments?.length ?? 0) === 0 &&
3052 +- message.text.trim().toLowerCase() === "/compact";
3053 + function mapProviderSessionStatusToOrchestrationStatus(
3054 + status: "connecting" | "ready" | "running" | "error" | "closed",
3055 + ): OrchestrationSession["status"] {
3056 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: function findProviderAdapterRequestError(
3057
3058 function isUnknownPendingApprovalRequestError(cause: Cause.Cause<ProviderServiceError>): boolean {
3059 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: function isUnkno
3060 const message = Cause.pretty(cause).toLowerCase();
3061 return (
3062 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3063 - ? failReason.error
3064 - : undefined;
3065 - if (providerError) {
3066 -- return providerError.detail;
3067 + const crypto = yield* Crypto.Crypto;
3068 + const orchestrationEngine = yield* OrchestrationEngineService;
3069 + const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
3070 +- const providerAuthService = yield* ProviderAuthService;
3071 + const providerService = yield* ProviderService;
3072 + const providerRegistry = yield* ProviderRegistry;
3073 + const gitWorkflow = yield* GitWorkflowService;
3074 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3075 + );
3076 +
3077 + const threadModelSelections = new Map<string, ModelSelection>();
3078 +- const compactingThreadIds = new Set<ThreadId>();
3079 +- const stoppingThreadIds = new Set<ThreadId>();
3080 +
3081 + const appendProviderFailureActivity = (input: {
3082 + readonly threadId: ThreadId;
3083 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3084 +
3085 + const formatFailureDetail = (cause: Cause.Cause<unknown>): string => {
3086 + const failReason = cause.reasons.find(Cause.isFailReason);
3087 +- if (isProviderAdapterRequestError(failReason?.error)) {
3088 +- return failReason.error.detail;
3089 +- }
3090 +- if (isProviderAdapterValidationError(failReason?.error)) {
3091 +- return failReason.error.issue;
3092 ++ const providerError = isProviderAdapterRequestError(failReason?.error)
3093 ++ ? failReason.error
3094 ++ : undefined;
3095 ++ if (providerError) {
3096 + return providerError.message;
3097 }
3098 return Cause.pretty(cause);
3099 };
3100 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3101 + });
3102 + });
3103 +
3104 +- const restoreCompaction = Effect.fnUntraced(function* (threadId: ThreadId, fromRunning = false) {
3105 +- if (stoppingThreadIds.has(threadId)) {
3106 +- compactingThreadIds.delete(threadId);
3107 +- return;
3108 +- }
3109 +- const thread = yield* resolveThread(threadId);
3110 +- if (!thread?.session) return;
3111 +- if (
3112 +- thread.session.status !== "starting" &&
3113 +- thread.session.status !== "ready" &&
3114 +- (!fromRunning || thread.session.status !== "running")
3115 +- )
3116 +- return;
3117 +- const completedAt = DateTime.formatIso(yield* DateTime.now);
3118 +- if (stoppingThreadIds.has(threadId)) {
3119 +- compactingThreadIds.delete(threadId);
3120 +- return;
3121 +- }
3122 +- yield* setThreadSession({
3123 +- threadId,
3124 +- session: {
3125 +- ...thread.session,
3126 +- status: "ready",
3127 +- activeTurnId: null,
3128 +- lastError: null,
3129 +- updatedAt: completedAt,
3130 +- },
3131 +- createdAt: completedAt,
3132 +- });
3133 +- });
3134 +-
3135 + const resolveProject = Effect.fnUntraced(function* (projectId: ProjectId) {
3136 + return yield* projectionSnapshotQuery
3137 + .getProjectShellById(projectId)
3138 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3139 thread.session && thread.session.status !== "stopped" && activeSession ? thread.id : null;
3140 if (existingSessionThreadId) {
3141 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Eff
3142 instanceChanged,
3143 shouldRestartForModelChange,
3144 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3145 + if (!thread) {
3146 + return;
3147 }
3148 -
3149 - const handleTurnStartFailure = (cause: Cause.Cause<unknown>) => {
3150 ++
3151 + const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
3152 + if (!message || message.role !== "user") {
3153 + yield* appendProviderFailureActivity({
3154 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3155 + detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
3156 + turnId: null,
3157 + createdAt: event.payload.createdAt,
3158 +- requestId: event.payload.messageId,
3159 + });
3160 + return;
3161 + }
3162 +- const appendTurnStartFailure = (summary: string, detail: string) =>
3163 +- appendProviderFailureActivity({
3164 +- threadId: event.payload.threadId,
3165 +- kind: "provider.turn.start.failed",
3166 +- summary,
3167 +- detail,
3168 +- turnId: null,
3169 +- createdAt: event.payload.createdAt,
3170 +- requestId: event.payload.messageId,
3171 +- });
3172 +-
3173 +- const handleTurnStartFailure = (cause: Cause.Cause<unknown>) => {
3174 - if (Cause.hasInterruptsOnly(cause)) {
3175 - return Effect.void;
3176 - }
3177 - const detail = formatFailureDetail(cause);
3178 - return setThreadSessionErrorOnTurnStartFailure({
3179 - threadId: event.payload.threadId,
3180 -@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3181 - createdAt: event.payload.createdAt,
3182 - }),
3183 - ),
3184 +- const detail = formatFailureDetail(cause);
3185 +- return setThreadSessionErrorOnTurnStartFailure({
3186 +- threadId: event.payload.threadId,
3187 +- detail,
3188 +- createdAt: event.payload.createdAt,
3189 +- }).pipe(
3190 +- Effect.flatMap(() => appendTurnStartFailure("Provider turn start failed", detail)),
3191 - Effect.asVoid,
3192 - );
3193 - };
3194 -
3195 +- );
3196 +- };
3197 +-
3198 - const recoverTurnStartFailure = (cause: Cause.Cause<unknown>) =>
3199 - handleTurnStartFailure(cause).pipe(
3200 - Effect.catchCause((recoveryCause) =>
3201 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Eff
3202 - ),
3203 - );
3204 -
3205 +- const authCommandHandled = yield* Effect.gen(function* () {
3206 +- // Native account commands belong to the thread's existing provider session.
3207 +- const instanceId =
3208 +- thread.session?.providerInstanceId ??
3209 +- event.payload.modelSelection?.instanceId ??
3210 +- thread.modelSelection.instanceId;
3211 +- const handled = yield* providerAuthService.tryHandlePromptCommand({
3212 +- instanceId,
3213 +- text: message.text,
3214 +- hasAttachments: (message.attachments?.length ?? 0) > 0,
3215 +- });
3216 +- if (!handled) {
3217 +- return false;
3218 +- }
3219 +-
3220 +- const instanceInfo = yield* providerService.getInstanceInfo(instanceId);
3221 +- yield* setThreadSession({
3222 +- threadId: thread.id,
3223 +- session: {
3224 +- threadId: thread.id,
3225 +- status: "stopped",
3226 +- providerName: instanceInfo.driverKind,
3227 +- providerInstanceId: instanceId,
3228 +- runtimeMode: thread.runtimeMode,
3229 +- activeTurnId: null,
3230 +- lastError: null,
3231 +- updatedAt: event.payload.createdAt,
3232 +- },
3233 +- createdAt: event.payload.createdAt,
3234 +- });
3235 +- yield* orchestrationEngine.dispatch({
3236 +- type: "thread.activity.append",
3237 +- commandId: yield* serverCommandId("provider-sign-out"),
3238 +- threadId: thread.id,
3239 +- activity: {
3240 +- id: yield* serverEventId(),
3241 +- tone: "info",
3242 +- kind: "provider.auth.signed-out",
3243 +- summary: "Provider signed out",
3244 +- payload: { providerInstanceId: instanceId },
3245 +- turnId: null,
3246 +- createdAt: event.payload.createdAt,
3247 +- },
3248 +- createdAt: event.payload.createdAt,
3249 +- });
3250 +- return true;
3251 +- }).pipe(Effect.catchCause((cause) => recoverTurnStartFailure(cause).pipe(Effect.as(true))));
3252 +- if (authCommandHandled) {
3253 +- return;
3254 +- }
3255 +
3256 + yield* ensureThreadWorktree(thread);
3257 +
3258 +- const isCompactCommand = isCompactCommandMessage(message);
3259 +- const nonCompactUserMessageCount = thread.messages.filter(
3260 +- (entry) => entry.role === "user" && !isCompactCommandMessage(entry),
3261 +- ).length;
3262 +- if (nonCompactUserMessageCount === 1 && !isCompactCommand) {
3263 ++ const isFirstUserMessageTurn =
3264 ++ thread.messages.filter((entry) => entry.role === "user").length === 1;
3265 ++ if (isFirstUserMessageTurn) {
3266 + const project = yield* resolveProject(thread.projectId);
3267 + const generationCwd =
3268 + resolveThreadWorkspaceCwd({
3269 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3270 + }
3271 + }
3272 +
3273 +- let compactionSessionEnsured = false;
3274 +- const handleCompactionFailure = (cause: Cause.Cause<unknown>) => {
3275 +- if (Cause.hasInterruptsOnly(cause)) {
3276 +- return Effect.void;
3277 +- }
3278 ++ const handleTurnStartFailure = (cause: Cause.Cause<unknown>) => {
3279 + const detail = formatFailureDetail(cause);
3280 +- if (!compactionSessionEnsured) {
3281 +- return setThreadSessionErrorOnTurnStartFailure({
3282 +- threadId: event.payload.threadId,
3283 +- detail,
3284 +- createdAt: event.payload.createdAt,
3285 +- }).pipe(
3286 +- Effect.flatMap(() => appendTurnStartFailure("Context compaction failed", detail)),
3287 +- Effect.asVoid,
3288 +- );
3289 +- }
3290 +- return appendTurnStartFailure("Context compaction failed", detail).pipe(
3291 +- Effect.ensuring(
3292 +- restoreCompaction(event.payload.threadId).pipe(
3293 +- Effect.catchCause((restoreCause) =>
3294 +- Effect.logWarning("failed to restore provider session after compaction failure", {
3295 +- threadId: event.payload.threadId,
3296 +- cause: Cause.pretty(restoreCause),
3297 +- }),
3298 +- ),
3299 +- ),
3300 +- ),
3301 +- Effect.asVoid,
3302 +- );
3303 +- };
3304 +- const recoverCompactionFailure = (cause: Cause.Cause<unknown>) =>
3305 +- handleCompactionFailure(cause).pipe(
3306 +- Effect.catchCause((recoveryCause) =>
3307 +- Effect.logWarning("provider command reactor failed to recover compaction failure", {
3308 +- eventType: event.type,
3309 ++ return setThreadSessionErrorOnTurnStartFailure({
3310 ++ threadId: event.payload.threadId,
3311 ++ detail,
3312 ++ createdAt: event.payload.createdAt,
3313 ++ }).pipe(
3314 ++ Effect.flatMap(() =>
3315 ++ appendProviderFailureActivity({
3316 + threadId: event.payload.threadId,
3317 +- cause: Cause.pretty(recoveryCause),
3318 +- originalCause: Cause.pretty(cause),
3319 ++ kind: "provider.turn.start.failed",
3320 ++ summary: "Provider turn start failed",
3321 ++ detail,
3322 ++ turnId: null,
3323 ++ createdAt: event.payload.createdAt,
3324 + }),
3325 + ),
3326 + );
3327 +- if (isCompactCommand) {
3328 +- if (nonCompactUserMessageCount === 0) {
3329 +- return yield* appendTurnStartFailure(
3330 +- "Context compaction failed",
3331 +- "Context compaction requires an existing conversation.",
3332 +- );
3333 +- }
3334 +- const latestThread = yield* resolveThread(event.payload.threadId);
3335 +- if (
3336 +- compactingThreadIds.has(event.payload.threadId) ||
3337 +- latestThread?.session?.status === "starting" ||
3338 +- latestThread?.session?.status === "running"
3339 +- ) {
3340 +- yield* appendTurnStartFailure(
3341 +- "Context compaction failed",
3342 +- "Context compaction is unavailable while a provider turn is running.",
3343 +- );
3344 +- return;
3345 +- }
3346 +- compactingThreadIds.add(event.payload.threadId);
3347 +- yield* Effect.gen(function* () {
3348 +- yield* ensureSessionForThread(
3349 +- event.payload.threadId,
3350 +- event.payload.createdAt,
3351 +- event.payload.modelSelection !== undefined
3352 +- ? { modelSelection: event.payload.modelSelection, pendingTurnStart: true }
3353 +- : { pendingTurnStart: true },
3354 +- );
3355 +- compactionSessionEnsured = true;
3356 +- if (event.payload.modelSelection !== undefined) {
3357 +- threadModelSelections.set(event.payload.threadId, event.payload.modelSelection);
3358 +- }
3359 +- yield* providerService.compactThread(
3360 +- event.payload.threadId,
3361 +- event.payload.modelSelection,
3362 +- event.payload.messageId,
3363 +- );
3364 +- }).pipe(
3365 +- Effect.andThen(restoreCompaction(event.payload.threadId, true)),
3366 +- Effect.catchCause(recoverCompactionFailure),
3367 +- Effect.ensuring(Effect.sync(() => void compactingThreadIds.delete(event.payload.threadId))),
3368 +- Effect.forkScoped,
3369 +- );
3370 +- return;
3371 +- }
3372 +- if (compactingThreadIds.has(event.payload.threadId)) {
3373 +- return yield* appendTurnStartFailure(
3374 +- "Provider turn start failed",
3375 +- "Wait for context compaction to finish before sending another message.",
3376 +- );
3377 +- }
3378 ++ };
3379 ++
3380 const sendTurnRequest = yield* buildSendTurnRequestForThread({
3381 threadId: event.payload.threadId,
3382 messageText: message.text,
3383 @@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Eff
3384
3385 yield* providerService
3386 .sendTurn(sendTurnRequest.value)
3387 -- .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped);
3388 +- .pipe(Effect.asVoid, Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped);
3389 + .pipe(Effect.catchCause(handleTurnStartFailure), Effect.forkScoped);
3390 });
3391
3392 const processTurnInterruptRequested = Effect.fn("processTurnInterruptRequested")(function* (
3393 +@@ apps/server/src/orchestration/Layers/ProviderCommandReactor.ts: const make = Effect.gen(function* () {
3394 + }
3395 +
3396 + const now = event.payload.createdAt;
3397 +- const wasCompacting = compactingThreadIds.has(thread.id);
3398 +- stoppingThreadIds.add(thread.id);
3399 +- const clearStopping = Effect.sync(() => void stoppingThreadIds.delete(thread.id));
3400 +- yield* (
3401 +- thread.session && thread.session.status !== "stopped"
3402 +- ? providerService.stopSession({ threadId: thread.id })
3403 +- : Effect.void
3404 +- ).pipe(
3405 +- Effect.matchCauseEffect({
3406 +- onFailure: (cause) => {
3407 +- if (Cause.hasInterruptsOnly(cause)) {
3408 +- return Effect.interrupt;
3409 +- }
3410 +- const detail = formatFailureDetail(cause);
3411 +- return Effect.sync(() => {
3412 +- stoppingThreadIds.delete(thread.id);
3413 +- return wasCompacting && !compactingThreadIds.has(thread.id);
3414 +- }).pipe(
3415 +- Effect.flatMap((compactionSettled) =>
3416 +- compactionSettled ? restoreCompaction(thread.id) : Effect.void,
3417 +- ),
3418 +- Effect.andThen(
3419 +- appendProviderFailureActivity({
3420 +- threadId: thread.id,
3421 +- kind: "provider.session.stop.failed",
3422 +- summary: "Provider session stop failed",
3423 +- detail,
3424 +- turnId: null,
3425 +- createdAt: now,
3426 +- }),
3427 +- ),
3428 +- );
3429 +- },
3430 +- onSuccess: () =>
3431 +- setThreadSession({
3432 +- threadId: thread.id,
3433 +- session: {
3434 +- threadId: thread.id,
3435 +- status: "stopped",
3436 +- providerName: thread.session?.providerName ?? null,
3437 +- ...(thread.session?.providerInstanceId !== undefined
3438 +- ? { providerInstanceId: thread.session.providerInstanceId }
3439 +- : {}),
3440 +- runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE,
3441 +- activeTurnId: null,
3442 +- lastError: thread.session?.lastError ?? null,
3443 +- updatedAt: now,
3444 +- },
3445 +- createdAt: now,
3446 +- }),
3447 +- }),
3448 +- Effect.ensuring(clearStopping),
3449 +- );
3450 ++ if (thread.session && thread.session.status !== "stopped") {
3451 ++ yield* providerService.stopSession({ threadId: thread.id });
3452 ++ }
3453 ++
3454 ++ yield* setThreadSession({
3455 ++ threadId: thread.id,
3456 ++ session: {
3457 ++ threadId: thread.id,
3458 ++ status: "stopped",
3459 ++ providerName: thread.session?.providerName ?? null,
3460 ++ ...(thread.session?.providerInstanceId !== undefined
3461 ++ ? { providerInstanceId: thread.session.providerInstanceId }
3462 ++ : {}),
3463 ++ runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE,
3464 ++ activeTurnId: null,
3465 ++ lastError: thread.session?.lastError ?? null,
3466 ++ updatedAt: now,
3467 ++ },
3468 ++ createdAt: now,
3469 ++ });
3470 + });
3471 +
3472 + const processDomainEvent = Effect.fn("processDomainEvent")(function* (
3473
3474 ## apps/server/src/provider/Layers/ClaudeProvider.ts ##
3475 @@ apps/server/src/provider/Layers/ClaudeProvider.ts: export const makePendingClaudeProvider = (
3476 @@ apps/server/src/provider/Layers/CodexAdapter.test.ts: lifecycleLayer("CodexAdapt
3477 -);
3478
3479 ## apps/server/src/provider/Layers/CodexAdapter.ts ##
3480 +@@
3481 + * @module CodexAdapterLive
3482 + */
3483 + import {
3484 +- EventId,
3485 + type CanonicalItemType,
3486 + type CanonicalRequestType,
3487 + type CodexSettings,
3488 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: import {
3489 + type ProviderRuntimeEvent,
3490 + type ProviderRequestKind,
3491 + type ThreadTokenUsageSnapshot,
3492 +- type ToolActivityIcon,
3493 +- type ToolActivityNativeAppReference,
3494 +- type ToolActivitySource,
3495 + type ProviderUserInputAnswers,
3496 + RuntimeItemId,
3497 + RuntimeRequestId,
3498 @@ apps/server/src/provider/Layers/CodexAdapter.ts: import {
3499 ThreadId,
3500 ProviderSendTurnInput,
3501 } from "@t3tools/contracts";
3502 -import * as Effect from "effect/Effect";
3503 +-import * as NodeCrypto from "node:crypto";
3504 -import * as Crypto from "effect/Crypto";
3505 -import * as Exit from "effect/Exit";
3506 -import * as Fiber from "effect/Fiber";
3507 @@ apps/server/src/provider/Layers/CodexAdapter.ts: import {
3508 +} from "../codex/CodexSessionRuntime.ts";
3509 import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts";
3510 import { resolveCodexLaunchArgs } from "./codexLaunchArgs.ts";
3511 +-import { codexRateLimitsToUpdate } from "./codexUsageLimits.ts";
3512 const isCodexAppServerProcessExitedError = Schema.is(CodexErrors.CodexAppServerProcessExitedError);
3513 + const isCodexAppServerTransportError = Schema.is(CodexErrors.CodexAppServerTransportError);
3514 + const isCodexSessionRuntimeThreadIdMissingError = Schema.is(
3515 @@ apps/server/src/provider/Layers/CodexAdapter.ts: const isCodexResumeCursorSchema = Schema.is(CodexResumeCursorSchema);
3516 const PROVIDER = ProviderDriverKind.make("codex");
3517
3518 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export interface CodexAdapterLi
3519 - readonly scope: Scope.Closeable;
3520 readonly runtime: CodexSessionRuntimeShape;
3521 readonly eventFiber: Fiber.Fiber<void, never>;
3522 - stopped: boolean;
3523 + readonly turnTokenUsage: CodexTurnTokenUsageState;
3524 @@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapCodexRuntimeError(
3525 method: string,
3526 error: CodexSessionRuntimeError,
3527 @@ apps/server/src/provider/Layers/CodexAdapter.ts: function readPayload<A>(
3528 }
3529
3530 function trimText(value: string | undefined | null): string | undefined {
3531 -@@ apps/server/src/provider/Layers/CodexAdapter.ts: function normalizeCodexTokenUsage(
3532 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function trimText(value: string | undefined | null): string | undefined {
3533 + return trimmed && trimmed.length > 0 ? trimmed : undefined;
3534 }
3535
3536 - function toTurnStatus(
3537 -- value: EffectCodexSchema.V2TurnCompletedNotification["turn"]["status"] | "cancelled",
3538 -+ value: EffectCodexSchema.V2TurnCompletedNotification["turn"]["status"],
3539 - ): "completed" | "failed" | "cancelled" | "interrupted" {
3540 - switch (value) {
3541 - case "completed":
3542 - case "failed":
3543 -- case "cancelled":
3544 - case "interrupted":
3545 - return value;
3546 +-function asUnknownRecord(value: unknown): Record<string, unknown> | undefined {
3547 +- return value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
3548 +-}
3549 +-
3550 +-function normalizeMcpIntentTitle(value: unknown): string | undefined {
3551 +- if (typeof value !== "string") return undefined;
3552 +- const normalized = value.trim().replace(/\s+/gu, " ");
3553 +- if (!normalized) return undefined;
3554 +- const characters = Array.from(normalized);
3555 +- return characters.length <= 80 ? normalized : `${characters.slice(0, 79).join("")}…`;
3556 +-}
3557 +-
3558 +-function normalizedHttpUrl(value: unknown): string | undefined {
3559 +- if (typeof value !== "string" || value.length > 4096) return undefined;
3560 +- try {
3561 +- const url = new URL(value);
3562 +- const href = url.href;
3563 +- return (url.protocol === "http:" || url.protocol === "https:") && href.length <= 4096
3564 +- ? href
3565 +- : undefined;
3566 +- } catch {
3567 +- return undefined;
3568 +- }
3569 +-}
3570 +-
3571 +-function normalizedImageUrl(value: unknown): string | undefined {
3572 +- if (typeof value !== "string" || value.length > 4096) return undefined;
3573 +- try {
3574 +- const url = new URL(value);
3575 +- return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "data:"
3576 +- ? url.href
3577 +- : undefined;
3578 +- } catch {
3579 +- return undefined;
3580 +- }
3581 +-}
3582 +-
3583 +-function normalizedAppId(value: unknown): string | undefined {
3584 +- if (typeof value !== "string") return undefined;
3585 +- const appId = value.trim();
3586 +- return appId.length > 0 && appId.length <= 512 && /^[A-Za-z0-9._-]+$/u.test(appId)
3587 +- ? appId
3588 +- : undefined;
3589 +-}
3590 +-
3591 +-function normalizedDisplayName(value: unknown): string | undefined {
3592 +- if (typeof value !== "string") return undefined;
3593 +- const displayName = value.trim().replace(/\s+/gu, " ");
3594 +- return displayName && displayName.length <= 160 ? displayName : undefined;
3595 +-}
3596 +-
3597 +-function normalizedSourceKeyPart(value: string): string {
3598 +- return value.trim().toLowerCase();
3599 +-}
3600 +-
3601 +-function nativeAppSourceKey(appId: string): string {
3602 +- const key = `native-app:${appId.toLowerCase()}`;
3603 +- if (key.length <= 512) return key;
3604 +- const digest = NodeCrypto.createHash("sha256").update(key).digest("hex");
3605 +- return `${key.slice(0, 512 - digest.length - 1)}:${digest}`;
3606 +-}
3607 +-
3608 +-function browserDisplayName(value: unknown): string | undefined {
3609 +- const normalized = normalizedDisplayName(value)?.toLowerCase();
3610 +- if (!normalized) return undefined;
3611 +- if (normalized.includes("chrome") || normalized === "chromium") return "Chrome";
3612 +- if (normalized.includes("edge")) return "Microsoft Edge";
3613 +- if (normalized.includes("firefox")) return "Firefox";
3614 +- if (normalized.includes("safari")) return "Safari";
3615 +- if (normalized.includes("arc")) return "Arc";
3616 +- if (normalized === "iab" || normalized.includes("in-app")) return "Browser";
3617 +- return normalizedDisplayName(value);
3618 +-}
3619 +-
3620 +-function browserNativeAppReference(name: string): ToolActivityNativeAppReference | undefined {
3621 +- switch (name) {
3622 +- case "Chrome":
3623 +- return { _tag: "display-name", displayName: "Google Chrome" };
3624 +- case "Microsoft Edge":
3625 +- case "Firefox":
3626 +- case "Safari":
3627 +- case "Arc":
3628 +- return { _tag: "display-name", displayName: name };
3629 +- default:
3630 +- return undefined;
3631 +- }
3632 +-}
3633 +-
3634 +-function appDisplayNameFromId(appId: string): string | undefined {
3635 +- const knownNames: Readonly<Record<string, string>> = {
3636 +- "com.apple.finder": "Finder",
3637 +- "com.apple.safari": "Safari",
3638 +- "com.google.chrome": "Chrome",
3639 +- "com.microsoft.edgemac": "Microsoft Edge",
3640 +- "org.mozilla.firefox": "Firefox",
3641 +- "company.thebrowser.browser": "Arc",
3642 +- };
3643 +- return knownNames[appId.toLowerCase()];
3644 +-}
3645 +-
3646 +-function nativeAppReference(value: unknown): ToolActivityNativeAppReference | undefined {
3647 +- const app = asUnknownRecord(value);
3648 +- if (app?.kind === "appId") {
3649 +- const appId = normalizedAppId(app.appId);
3650 +- return appId ? { _tag: "app-id", appId } : undefined;
3651 +- }
3652 +- if (app?.kind === "displayName") {
3653 +- const displayName = normalizedDisplayName(app.displayName);
3654 +- return displayName ? { _tag: "display-name", displayName } : undefined;
3655 +- }
3656 +- return undefined;
3657 +-}
3658 +-
3659 +-function themedLogoIcon(
3660 +- ...records: ReadonlyArray<Record<string, unknown> | undefined>
3661 +-): ToolActivityIcon | undefined {
3662 +- for (const record of records) {
3663 +- const logoUrl = normalizedImageUrl(record?.logoUrl);
3664 +- if (!logoUrl) continue;
3665 +- const logoUrlDark = normalizedImageUrl(record?.logoUrlDark ?? record?.logoDarkUrl);
3666 +- return {
3667 +- _tag: "themed-logo",
3668 +- logoUrl,
3669 +- ...(logoUrlDark ? { logoUrlDark } : {}),
3670 +- };
3671 +- }
3672 +- return undefined;
3673 +-}
3674 +-
3675 +-interface McpToolPresentation {
3676 +- readonly toolSurface?: "browser" | "computer";
3677 +- readonly toolIcon?: ToolActivityIcon;
3678 +- readonly toolSource?: ToolActivitySource;
3679 +-}
3680 +-
3681 +-function mcpToolPresentation(
3682 +- item: Extract<CodexLifecycleItem, { readonly type: "mcpToolCall" }>,
3683 +-): McpToolPresentation {
3684 +- const result = asUnknownRecord(item.result);
3685 +- const metadata = asUnknownRecord(result?._meta);
3686 +- const surface = asUnknownRecord(metadata?.["codex/toolSurface"]);
3687 +- const sourceMetadata = asUnknownRecord(metadata?.source);
3688 +- const appContext = asUnknownRecord(item.appContext);
3689 +- const sourceLogo = themedLogoIcon(surface, sourceMetadata, appContext);
3690 +- if (surface?.kind === "browserUse") {
3691 +- const screenshot = asUnknownRecord(surface.screenshot);
3692 +- const browserUse = asUnknownRecord(metadata?.browser_use);
3693 +- const openTabs = Array.isArray(surface.openTabs) ? surface.openTabs : [];
3694 +- const latestOpenTab = openTabs
3695 +- .toReversed()
3696 +- .map(asUnknownRecord)
3697 +- .find((tab) => normalizedHttpUrl(tab?.url) !== undefined);
3698 +- const selectedPage = [
3699 +- { record: screenshot, url: screenshot?.pageUrl },
3700 +- { record: browserUse, url: browserUse?.url },
3701 +- { record: latestOpenTab, url: latestOpenTab?.url },
3702 +- ]
3703 +- .map((candidate) => ({ ...candidate, pageUrl: normalizedHttpUrl(candidate.url) }))
3704 +- .find((candidate) => candidate.pageUrl !== undefined);
3705 +- const pageUrl = selectedPage?.pageUrl;
3706 +- const faviconUrl = normalizedImageUrl(
3707 +- selectedPage?.record?.faviconUrl ?? selectedPage?.record?.favIconUrl,
3708 +- );
3709 +- const faviconUrlDark = normalizedImageUrl(
3710 +- selectedPage?.record?.faviconUrlDark ?? selectedPage?.record?.favIconUrlDark,
3711 +- );
3712 +- const name =
3713 +- browserDisplayName(appContext?.appName) ??
3714 +- browserDisplayName(surface.browserFamily) ??
3715 +- browserDisplayName(surface.backend) ??
3716 +- "Browser";
3717 +- const nativeBrowserIcon = browserNativeAppReference(name);
3718 +- const sourceIcon =
3719 +- sourceLogo ??
3720 +- (nativeBrowserIcon ? ({ _tag: "native-app", app: nativeBrowserIcon } as const) : undefined);
3721 +- const sourceKeyPart = normalizedSourceKeyPart(name) || "browser";
3722 +- return {
3723 +- toolSurface: "browser",
3724 +- ...(pageUrl
3725 +- ? {
3726 +- toolIcon: {
3727 +- _tag: "website",
3728 +- pageUrl,
3729 +- ...(faviconUrl ? { faviconUrl } : {}),
3730 +- ...(faviconUrlDark ? { faviconUrlDark } : {}),
3731 +- } as const,
3732 +- }
3733 +- : {}),
3734 +- toolSource: {
3735 +- key: `browser-use:${sourceKeyPart}`,
3736 +- name,
3737 +- kind: name === "Browser" ? "browser" : "integration",
3738 +- ...(sourceIcon ? { icon: sourceIcon } : {}),
3739 +- },
3740 +- };
3741 +- }
3742 +- if (surface?.kind === "computerUse") {
3743 +- const app = nativeAppReference(surface.app);
3744 +- const args = asUnknownRecord(item.arguments);
3745 +- const argumentAppName =
3746 +- normalizedDisplayName(args?.appName) ??
3747 +- normalizedDisplayName(args?.application) ??
3748 +- normalizedDisplayName(typeof args?.app === "string" ? args.app : undefined);
3749 +- const name =
3750 +- normalizedDisplayName(appContext?.appName) ??
3751 +- argumentAppName ??
3752 +- (app?._tag === "display-name" ? app.displayName : undefined) ??
3753 +- (app?._tag === "app-id" ? appDisplayNameFromId(app.appId) : undefined) ??
3754 +- "Computer Use";
3755 +- const sourceIcon = sourceLogo ?? (app ? ({ _tag: "native-app", app } as const) : undefined);
3756 +- const sourceKey = app
3757 +- ? app._tag === "app-id"
3758 +- ? nativeAppSourceKey(app.appId)
3759 +- : `native-app-name:${normalizedSourceKeyPart(app.displayName)}`
3760 +- : "computer-use";
3761 +- return {
3762 +- toolSurface: "computer",
3763 +- ...(app ? { toolIcon: { _tag: "native-app", app } as const } : {}),
3764 +- toolSource: {
3765 +- key: sourceKey,
3766 +- name,
3767 +- kind: "computer",
3768 +- ...(sourceIcon ? { icon: sourceIcon } : {}),
3769 +- },
3770 +- };
3771 +- }
3772 +-
3773 +- return {};
3774 +-}
3775 +-
3776 + const FATAL_CODEX_STDERR_SNIPPETS = ["failed to connect to websocket"];
3777 +
3778 + function isFatalCodexProcessStderrMessage(message: string): boolean {
3779 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function completeCodexTurnTokenUsage(
3780 + }
3781 +
3782 + function toTurnStatus(
3783 +- value: EffectCodexSchema.V2TurnCompletedNotification["turn"]["status"] | "cancelled",
3784 ++ value: EffectCodexSchema.V2TurnCompletedNotification["turn"]["status"],
3785 + ): "completed" | "failed" | "cancelled" | "interrupted" {
3786 + switch (value) {
3787 + case "completed":
3788 + case "failed":
3789 +- case "cancelled":
3790 + case "interrupted":
3791 + return value;
3792 default:
3793 -@@ apps/server/src/provider/Layers/CodexAdapter.ts: function itemTitle(itemType: CanonicalItemType, item?: CodexLifecycleItem): stri
3794 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function toCanonicalItemType(raw: string | undefined | null): CanonicalItemType
3795 + return "unknown";
3796 + }
3797 +
3798 +-function boundedToolArgument(value: unknown): string | undefined {
3799 +- const normalized = typeof value === "string" ? value.trim().replace(/\s+/gu, " ") : "";
3800 +- if (!normalized) return undefined;
3801 +- return normalized.length <= 48 ? normalized : `${normalized.slice(0, 47)}…`;
3802 +-}
3803 +-
3804 +-function normalizedMcpToolName(value: string): string {
3805 +- return (
3806 +- value
3807 +- .split(/__|[./:]/u)
3808 +- .at(-1)
3809 +- ?.trim() ?? value.trim()
3810 +- );
3811 +-}
3812 +-
3813 +-function computerUseToolTitle(
3814 +- item: Extract<CodexLifecycleItem, { readonly type: "mcpToolCall" }>,
3815 +- presentation: McpToolPresentation,
3816 +-): string | undefined {
3817 +- if (normalizeItemType(item.server) !== "computer use") return undefined;
3818 +- if (item.status === "failed") return undefined;
3819 +- const tool = normalizeItemType(normalizedMcpToolName(item.tool)).replace(/ /gu, "_");
3820 +- const inProgress = item.status === "inProgress";
3821 +- const args = asUnknownRecord(item.arguments);
3822 +- const appName =
3823 +- (presentation.toolSource?.kind === "computer" && presentation.toolSource.name !== "Computer Use"
3824 +- ? presentation.toolSource.name
3825 +- : undefined) ??
3826 +- normalizedDisplayName(args?.appName) ??
3827 +- normalizedDisplayName(args?.application) ??
3828 +- normalizedDisplayName(typeof args?.app === "string" ? args.app : undefined);
3829 +- const withApp = (label: string) => (appName ? `${label} in ${appName}` : label);
3830 +- switch (tool) {
3831 +- case "list_apps":
3832 +- return inProgress ? "Listing apps" : "Listed apps";
3833 +- case "click":
3834 +- return withApp(inProgress ? "Clicking" : "Clicked");
3835 +- case "drag":
3836 +- return withApp(inProgress ? "Dragging" : "Dragged");
3837 +- case "get_app_state":
3838 +- case "get_state":
3839 +- return appName
3840 +- ? `${inProgress ? "Looking at" : "Looked at"} ${appName}`
3841 +- : inProgress
3842 +- ? "Looking at the screen"
3843 +- : "Looked at the screen";
3844 +- case "perform_accessibility_action":
3845 +- case "perform_secondary_action":
3846 +- return inProgress ? "Performing accessibility action" : "Performed accessibility action";
3847 +- case "press_key":
3848 +- return withApp(inProgress ? "Pressing key" : "Pressed key");
3849 +- case "scroll": {
3850 +- const direction = boundedToolArgument(args?.direction)?.toLowerCase();
3851 +- return withApp(`${inProgress ? "Scrolling" : "Scrolled"}${direction ? ` ${direction}` : ""}`);
3852 +- }
3853 +- case "set_value":
3854 +- return withApp(inProgress ? "Setting value" : "Set value");
3855 +- case "type_text":
3856 +- return withApp(inProgress ? "Typing text" : "Typed text");
3857 +- default:
3858 +- return undefined;
3859 +- }
3860 +-}
3861 +-
3862 +-function itemTitle(
3863 +- itemType: CanonicalItemType,
3864 +- item?: CodexLifecycleItem,
3865 +- presentation: McpToolPresentation = {},
3866 +-): string | undefined {
3867 ++function itemTitle(itemType: CanonicalItemType, item?: CodexLifecycleItem): string | undefined {
3868 + if (itemType === "mcp_tool_call" && item?.type === "mcpToolCall") {
3869 +- if (normalizedMcpToolName(item.tool) === "js") {
3870 +- const intentTitle = normalizeMcpIntentTitle(asUnknownRecord(item.arguments)?.title);
3871 +- if (intentTitle) return intentTitle;
3872 +- }
3873 +- const computerUseTitle = computerUseToolTitle(item, presentation);
3874 +- if (computerUseTitle) return computerUseTitle;
3875 + return `${item.server} · ${item.tool}`;
3876 + }
3877 + switch (itemType) {
3878 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function itemTitle(
3879 }
3880 }
3881
3882 @@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapItemLifecycle(
3883 }
3884
3885 - const detail = itemDetail(itemType, item);
3886 +- const toolPresentation = item.type === "mcpToolCall" ? mcpToolPresentation(item) : {};
3887 +- const title = itemTitle(itemType, item, toolPresentation);
3888 + const detail = itemDetail(item);
3889 const status =
3890 lifecycle === "item.started"
3891 ? "inProgress"
3892 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapItemLifecycle(
3893 + payload: {
3894 + itemType,
3895 + ...(status ? { status } : {}),
3896 +- ...(title ? { title } : {}),
3897 ++ ...(itemTitle(itemType, item) ? { title: itemTitle(itemType, item) } : {}),
3898 + ...(detail ? { detail } : {}),
3899 +- ...toolPresentation,
3900 + ...(event.payload !== undefined ? { data: event.payload } : {}),
3901 + },
3902 + };
3903 @@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapToRuntimeEvents(
3904 + if (!item) {
3905 + return [];
3906 }
3907 +- if (item.type === "agentMessage" && item.delivery === "async" && item.questions?.length) {
3908 +- return [
3909 +- {
3910 +- ...runtimeEventBase(event, canonicalThreadId),
3911 +- type: "user-input.requested",
3912 +- requestId: RuntimeRequestId.make(`codex-async:${canonicalThreadId}:${item.id}`),
3913 +- eventId: EventId.make(`codex-async:${canonicalThreadId}:${item.id}`),
3914 +- payload: {
3915 +- responseMode: "message",
3916 +- questions: item.questions.map((question, index) => ({
3917 +- id: String(index),
3918 +- header: "Question",
3919 +- question: question.title,
3920 +- options: (question.options ?? []).map((label) => ({ label, description: "" })),
3921 +- allowCustomAnswer: true,
3922 +- multiSelect: false,
3923 +- })),
3924 +- },
3925 +- },
3926 +- ];
3927 +- }
3928 const itemType = toCanonicalItemType(item.type);
3929 if (itemType === "plan") {
3930 - const detail = itemDetail(itemType, item);
3931 @@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapToRuntimeEvents(
3932 if (!detail) {
3933 return [];
3934 }
3935 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapToRuntimeEvents(
3936 + ];
3937 + }
3938 + const completed = mapItemLifecycle(event, canonicalThreadId, "item.completed");
3939 +- if (!completed || itemType !== "context_compaction") {
3940 +- return completed ? [completed] : [];
3941 +- }
3942 +- return [
3943 +- completed,
3944 +- {
3945 +- ...runtimeEventBase(event, canonicalThreadId),
3946 +- eventId: EventId.make(`${event.id}:thread-compacted`),
3947 +- type: "thread.state.changed",
3948 +- payload: { state: "compacted" },
3949 +- },
3950 +- ];
3951 ++ return completed ? [completed] : [];
3952 + }
3953 +
3954 + if (
3955 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapToRuntimeEvents(
3956 + }
3957 +
3958 + if (event.method === "account/rateLimits/updated") {
3959 +- const payload = readPayload(
3960 +- EffectCodexSchema.V2AccountRateLimitsUpdatedNotification,
3961 +- event.payload,
3962 +- );
3963 +- const limits = payload ? codexRateLimitsToUpdate(payload.rateLimits) : undefined;
3964 +- if (!limits) {
3965 ++ if (!readPayload(EffectCodexSchema.V2AccountRateLimitsUpdatedNotification, event.payload)) {
3966 + return [];
3967 + }
3968 + return [
3969 + {
3970 + type: "account.rate-limits.updated",
3971 + ...runtimeEventBase(event, canonicalThreadId),
3972 +- payload: { limits },
3973 ++ payload: {
3974 ++ rateLimits: event.payload ?? {},
3975 ++ },
3976 + },
3977 + ];
3978 + }
3979 @@ apps/server/src/provider/Layers/CodexAdapter.ts: function mapToRuntimeEvents(
3980 type: "thread.realtime.started",
3981 ...runtimeEventBase(event, canonicalThreadId),
3982 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
3983 - issue: `Expected provider '${PROVIDER}' but received '${input.provider}'.`,
3984 - });
3985 - }
3986 +-
3987 +- const existing = sessions.get(input.threadId);
3988 +- if (existing && !existing.stopped) {
3989 +- yield* Effect.suspend(() => stopSessionInternal(existing));
3990 +- }
3991 + const startSession: CodexAdapterShape["startSession"] = Effect.fn("startSession")(
3992 + function* (input) {
3993 + if (input.provider !== undefined && input.provider !== PROVIDER) {
3994 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
3995 + });
3996 + }
3997
3998 -- const existing = sessions.get(input.threadId);
3999 -- if (existing && !existing.stopped) {
4000 -- yield* Effect.suspend(() => stopSessionInternal(existing));
4001 -- }
4002 -+ const existing = sessions.get(input.threadId);
4003 -+ if (existing && !existing.stopped) {
4004 -+ yield* Effect.suspend(() => stopSessionInternal(existing));
4005 -+ }
4006 -
4007 - const serviceTier =
4008 - input.modelSelection?.instanceId === boundInstanceId
4009 - ? getCodexServiceTierOptionValue(input.modelSelection)
4010 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4011 - }
4012 - : {}),
4013 - };
4014 +- const turnTokenUsage = makeCodexTurnTokenUsageState();
4015 - const sessionScope = yield* Scope.make("sequential");
4016 - let sessionScopeTransferred = false;
4017 - yield* Effect.addFinalizer(() =>
4018 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4019 - }),
4020 - ),
4021 - );
4022 ++ const existing = sessions.get(input.threadId);
4023 ++ if (existing && !existing.stopped) {
4024 ++ yield* Effect.suspend(() => stopSessionInternal(existing));
4025 ++ }
4026 +
4027 +- // Fork into the session scope, not the calling fiber. `forkChild` makes
4028 +- // this a child of `startSession`, and Effect interrupts a fiber's
4029 +- // children when it completes, so the consumer died on return and every
4030 +- // runtime event the session emitted afterwards was dropped.
4031 +- const eventFiber = yield* Stream.runForEach(runtime.events, (event) =>
4032 +- Effect.gen(function* () {
4033 +- yield* writeNativeEvent(event);
4034 +- if (event.method === "turn/started" && event.turnId) {
4035 +- if (turnTokenUsage.activeTurnId !== event.turnId) {
4036 +- turnTokenUsage.byTurnId.clear();
4037 +- turnTokenUsage.activeTurnId = event.turnId;
4038 +- getCodexTurnAccumulator(turnTokenUsage, event.turnId);
4039 +- }
4040 +- } else if (event.method === "thread/tokenUsage/updated") {
4041 +- const payload = readPayload(
4042 +- EffectCodexSchema.V2ThreadTokenUsageUpdatedNotification,
4043 +- event.payload,
4044 +- );
4045 +- if (payload) {
4046 +- accumulateCodexTurnTokenUsage(turnTokenUsage, payload.turnId, payload.tokenUsage);
4047 +- }
4048 +- } else if (turnTokenUsage.activeTurnId) {
4049 +- const collabPayload =
4050 +- typeof event.payload === "object" && event.payload !== null
4051 +- ? (event.payload as Record<string, unknown>)
4052 +- : undefined;
4053 +- const isCollabSpawn =
4054 +- event.method === "collabAgent/started" ||
4055 +- (event.method === "collabAgent/activity" &&
4056 +- collabPayload?.activityKind === "started");
4057 +- if (isCollabSpawn && event.turnId === turnTokenUsage.activeTurnId) {
4058 +- getCodexTurnAccumulator(turnTokenUsage, turnTokenUsage.activeTurnId).hasSubagents =
4059 +- true;
4060 +- }
4061 +- }
4062 + const codexSettings = yield* serverSettingsService.getSettings.pipe(
4063 + Effect.map((settings) => settings.providers.codex),
4064 + Effect.mapError(
4065 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4066 + ),
4067 + );
4068
4069 -- // Fork into the session scope, not the calling fiber. `forkChild` makes
4070 -- // this a child of `startSession`, and Effect interrupts a fiber's
4071 -- // children when it completes, so the consumer died on return and every
4072 -- // runtime event the session emitted afterwards was dropped.
4073 -- const eventFiber = yield* Stream.runForEach(runtime.events, (event) =>
4074 -- Effect.gen(function* () {
4075 -- yield* writeNativeEvent(event);
4076 -- const runtimeEvents = mapToRuntimeEvents(event, event.threadId);
4077 +- const runtimeEvents = mapToRuntimeEvents(event, event.threadId).map((runtimeEvent) => {
4078 +- if (runtimeEvent.type === "turn.completed" && runtimeEvent.turnId) {
4079 +- return {
4080 +- ...runtimeEvent,
4081 +- payload: {
4082 +- ...runtimeEvent.payload,
4083 +- tokenUsage: completeCodexTurnTokenUsage(
4084 +- turnTokenUsage,
4085 +- String(runtimeEvent.turnId),
4086 +- runtimeEvent.payload.state === "completed",
4087 +- ),
4088 +- },
4089 +- } satisfies ProviderRuntimeEvent;
4090 +- }
4091 +- if (runtimeEvent.type === "turn.aborted" && runtimeEvent.turnId) {
4092 +- return {
4093 +- ...runtimeEvent,
4094 +- payload: {
4095 +- ...runtimeEvent.payload,
4096 +- tokenUsage: completeCodexTurnTokenUsage(
4097 +- turnTokenUsage,
4098 +- String(runtimeEvent.turnId),
4099 +- false,
4100 +- ),
4101 +- },
4102 +- } satisfies ProviderRuntimeEvent;
4103 +- }
4104 +- return runtimeEvent;
4105 ++ const eventFiber = yield* Stream.runForEach(runtime.events, (event) =>
4106 ++ Effect.gen(function* () {
4107 ++ yield* writeNativeEvent(event);
4108 ++ const runtimeEvents = mapToRuntimeEvents(event, event.threadId);
4109 ++ if (runtimeEvents.length === 0) {
4110 ++ yield* Effect.logDebug("ignoring unhandled Codex provider event", {
4111 ++ method: event.method,
4112 ++ threadId: event.threadId,
4113 ++ turnId: event.turnId,
4114 ++ itemId: event.itemId,
4115 + });
4116 - if (runtimeEvents.length === 0) {
4117 - yield* Effect.logDebug("ignoring unhandled Codex provider event", {
4118 - method: event.method,
4119 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4120 - ),
4121 - ),
4122 - );
4123 -+ const eventFiber = yield* Stream.runForEach(runtime.events, (event) =>
4124 -+ Effect.gen(function* () {
4125 -+ yield* writeNativeEvent(event);
4126 -+ const runtimeEvents = mapToRuntimeEvents(event, event.threadId);
4127 -+ if (runtimeEvents.length === 0) {
4128 -+ yield* Effect.logDebug("ignoring unhandled Codex provider event", {
4129 -+ method: event.method,
4130 -+ threadId: event.threadId,
4131 -+ turnId: event.turnId,
4132 -+ itemId: event.itemId,
4133 -+ });
4134 + return;
4135 + }
4136 + yield* Queue.offerAll(runtimeEventQueue, runtimeEvents);
4137 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4138 - scope: sessionScope,
4139 - runtime,
4140 - eventFiber,
4141 +- turnTokenUsage,
4142 - stopped: false,
4143 - });
4144 - sessionScopeTransferred = true;
4145 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4146 ),
4147 );
4148
4149 +- const compactThread: NonNullable<CodexAdapterShape["compactThread"]> = Effect.fn("compactThread")(
4150 +- function* (threadId) {
4151 +- const session = yield* requireSession(threadId);
4152 +- yield* session.runtime.compactThread.pipe(
4153 +- Effect.mapError((cause) => mapCodexRuntimeError(threadId, "thread/compact/start", cause)),
4154 +- );
4155 +- },
4156 +- );
4157 +-
4158 + const readThread: CodexAdapterShape["readThread"] = (threadId) =>
4159 + requireSession(threadId).pipe(
4160 + Effect.flatMap((session) => session.runtime.readThread),
4161 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
4162 + ),
4163 + );
4164 +
4165 - const writeNativeEvent = Effect.fnUntraced(function* (event: ProviderEvent) {
4166 + const writeNativeEvent = Effect.fn("writeNativeEvent")(function* (event: ProviderEvent) {
4167 if (!nativeEventLogger) {
4168 @@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter =
4169 );
4170
4171 return {
4172 +@@ apps/server/src/provider/Layers/CodexAdapter.ts: export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
4173 + },
4174 + startSession,
4175 + sendTurn,
4176 +- compactThread,
4177 + interruptTurn,
4178 + readThread,
4179 + rollbackThread,
4180
4181 - ## apps/server/src/provider/Layers/CodexProvider.ts ##
4182 -@@ apps/server/src/provider/Layers/CodexProvider.ts: import * as CodexErrors from "effect-codex-app-server/errors";
4183 - import type {
4184 - CodexSettings,
4185 - ServerProvider,
4186 -+ ServerProviderAuth,
4187 -+ ServerProviderSkill,
4188 - ServerProviderState,
4189 - ModelCapabilities,
4190 - ProviderOptionDescriptor,
4191 - ServerProviderModel,
4192 - ServerProviderSkill,
4193 - } from "@t3tools/contracts";
4194 --import { PREFERRED_DEFAULT_CODEX_MODELS, ServerSettingsError } from "@t3tools/contracts";
4195 -+import {
4196 -+ Cache,
4197 -+ Data,
4198 -+ Duration,
4199 -+ Effect,
4200 -+ Equal,
4201 -+ FileSystem,
4202 -+ Layer,
4203 -+ Option,
4204 -+ Path,
4205 -+ Result,
4206 -+ Stream,
4207 -+} from "effect";
4208 -+import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
4209 + ## apps/server/src/provider/Layers/CursorAdapter.test.ts ##
4210 +@@ apps/server/src/provider/Layers/CursorAdapter.test.ts: const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.
4211 + const mockAgentCommand = "node";
4212 + const mockAgentArgs = [mockAgentPath] as const;
4213
4214 - import { createModelCapabilities } from "@t3tools/shared/model";
4215 - import { resolveSpawnCommand } from "@t3tools/shared/shell";
4216 -@@ apps/server/src/provider/Layers/CodexProvider.ts: import {
4217 - buildServerProvider,
4218 - type ServerProviderDraft,
4219 - } from "../providerSnapshot.ts";
4220 --import { expandHomePath } from "../../pathExpansion.ts";
4221 --import packageJson from "../../../package.json" with { type: "json" };
4222 --const isCodexAppServerSpawnError = Schema.is(CodexErrors.CodexAppServerSpawnError);
4223 --
4224 --const CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER = "2 seconds" as const;
4225 --
4226 --const CODEX_PRESENTATION = {
4227 -- displayName: "Codex",
4228 -- showInteractionModeToggle: true,
4229 --} as const;
4230 --
4231 --export interface CodexAppServerProviderSnapshot {
4232 -- readonly account: CodexSchema.V2GetAccountResponse;
4233 -- readonly version: string | undefined;
4234 -- readonly models: ReadonlyArray<ServerProviderModel>;
4235 -- readonly skills: ReadonlyArray<ServerProviderSkill>;
4236 -+import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
4237 -+import {
4238 -+ formatCodexCliUpgradeMessage,
4239 -+ isCodexCliVersionSupported,
4240 -+ parseCodexCliVersion,
4241 -+} from "../codexCliVersion.ts";
4242 -+import {
4243 -+ adjustCodexModelsForAccount,
4244 -+ codexAuthSubLabel,
4245 -+ codexAuthSubType,
4246 -+ type CodexAccountSnapshot,
4247 -+} from "../codexAccount.ts";
4248 -+import { type CodexDiscoverySnapshot, probeCodexDiscovery } from "../codexAppServer.ts";
4249 -+import { BUILT_IN_CODEX_MODELS, DEFAULT_CODEX_MODEL_CAPABILITIES } from "../codexModels.ts";
4250 -+import { CodexProvider } from "../Services/CodexProvider.ts";
4251 -+import { ServerSettingsService } from "../../serverSettings.ts";
4252 -+import { ServerSettingsError } from "@t3tools/contracts";
4253 -+
4254 -+const PROVIDER = "codex" as const;
4255 -+const OPENAI_AUTH_PROVIDERS = new Set(["openai"]);
4256 -+
4257 -+class CodexDiscoveryCacheKey extends Data.Class<{
4258 -+ readonly binaryPath: string;
4259 -+ readonly homePath?: string;
4260 -+ readonly cwd: string;
4261 -+}> {}
4262 -+
4263 -+function buildInitialCodexProviderSnapshot(codexSettings: CodexSettings): ServerProvider {
4264 -+ const checkedAt = new Date().toISOString();
4265 -+ const models = providerModelsFromSettings(
4266 -+ BUILT_IN_CODEX_MODELS,
4267 -+ PROVIDER,
4268 -+ codexSettings.customModels,
4269 -+ DEFAULT_CODEX_MODEL_CAPABILITIES,
4270 -+ );
4271 -+
4272 -+ if (!codexSettings.enabled) {
4273 -+ return buildServerProvider({
4274 -+ provider: PROVIDER,
4275 -+ enabled: false,
4276 -+ checkedAt,
4277 -+ models,
4278 -+ skills: [],
4279 -+ probe: {
4280 -+ installed: false,
4281 -+ version: null,
4282 -+ status: "warning",
4283 -+ auth: { status: "unknown" },
4284 -+ message: "Codex is disabled in T3 Code settings.",
4285 -+ },
4286 -+ });
4287 -+ }
4288 -+
4289 -+ return buildServerProvider({
4290 -+ provider: PROVIDER,
4291 -+ enabled: true,
4292 -+ checkedAt,
4293 -+ models,
4294 -+ skills: [],
4295 -+ probe: {
4296 -+ installed: true,
4297 -+ version: null,
4298 -+ status: "warning",
4299 -+ auth: { status: "unknown" },
4300 -+ message: "Checking Codex CLI availability...",
4301 -+ },
4302 -+ });
4303 - }
4304 -
4305 - const REASONING_EFFORT_LABELS: Readonly<Record<string, string>> = {
4306 -@@ apps/server/src/provider/Layers/CodexProvider.ts: const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
4307 - readonly homePath?: string;
4308 - readonly launchArgs?: string;
4309 - readonly cwd: string;
4310 -- readonly customModels?: ReadonlyArray<string>;
4311 -- readonly environment?: NodeJS.ProcessEnv;
4312 --}) {
4313 -- // `~` is not shell-expanded when env vars are set via `child_process.spawn`,
4314 -- // so `CODEX_HOME=~/.codex_work` would reach codex verbatim and trip
4315 -- // "CODEX_HOME points to '~/.codex_work', but that path does not exist".
4316 -- // Expand here for parity with `CodexTextGeneration`/`CodexSessionRuntime`.
4317 -- const resolvedHomePath = input.homePath ? expandHomePath(input.homePath) : undefined;
4318 -- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
4319 -- const environment = {
4320 -- ...input.environment,
4321 -- ...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}),
4322 -- };
4323 -- const spawnCommand = yield* resolveSpawnCommand(
4324 -- input.binaryPath,
4325 -- codexAppServerArgs(input.launchArgs),
4326 -- {
4327 -- env: environment,
4328 -- extendEnv: true,
4329 -- },
4330 -- );
4331 -- const child = yield* spawner
4332 -- .spawn(
4333 -- ChildProcess.make(spawnCommand.command, spawnCommand.args, {
4334 -- cwd: input.cwd,
4335 -- env: environment,
4336 -- extendEnv: true,
4337 -- forceKillAfter: CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER,
4338 -- shell: spawnCommand.shell,
4339 -- }),
4340 -- )
4341 -- .pipe(
4342 -- Effect.mapError(
4343 -- (cause) =>
4344 -- new CodexErrors.CodexAppServerSpawnError({
4345 -- command: `${input.binaryPath} app-server`,
4346 -- cause,
4347 -- }),
4348 -- ),
4349 -- );
4350 -- const clientContext = yield* Layer.build(CodexClient.layerChildProcess(child));
4351 -- const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe(
4352 -- Effect.provide(clientContext),
4353 -+}) =>
4354 -+ probeCodexDiscovery(input).pipe(
4355 -+ Effect.timeoutOption(CAPABILITIES_PROBE_TIMEOUT_MS),
4356 -+ Effect.result,
4357 -+ Effect.map((result) => {
4358 -+ if (Result.isFailure(result)) return undefined;
4359 -+ return Option.isSome(result.success) ? result.success.value : undefined;
4360 -+ }),
4361 - );
4362 -
4363 - const initialize = yield* client.request("initialize", {
4364 -@@ apps/server/src/provider/Layers/CodexProvider.ts: const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
4365 - } satisfies CodexAppServerProviderSnapshot;
4366 - });
4367 -
4368 --export const probeCodexSkillsForCwd = Effect.fn("probeCodexSkillsForCwd")(function* (input: {
4369 -- readonly binaryPath: string;
4370 -- readonly homePath?: string;
4371 -- readonly launchArgs?: string;
4372 -- readonly cwd: string;
4373 -- readonly environment?: NodeJS.ProcessEnv;
4374 --}) {
4375 -- const resolvedHomePath = input.homePath ? expandHomePath(input.homePath) : undefined;
4376 -- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
4377 -- const environment = {
4378 -- ...input.environment,
4379 -- ...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}),
4380 -- };
4381 -- const spawnCommand = yield* resolveSpawnCommand(
4382 -- input.binaryPath,
4383 -- codexAppServerArgs(input.launchArgs),
4384 -- { env: environment, extendEnv: true },
4385 -+export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(function* (
4386 -+ resolveAccount?: (input: {
4387 -+ readonly binaryPath: string;
4388 -+ readonly homePath?: string;
4389 -+ }) => Effect.Effect<CodexAccountSnapshot | undefined>,
4390 -+ resolveSkills?: (input: {
4391 -+ readonly binaryPath: string;
4392 -+ readonly homePath?: string;
4393 -+ readonly cwd: string;
4394 -+ }) => Effect.Effect<ReadonlyArray<ServerProviderSkill> | undefined>,
4395 -+): Effect.fn.Return<
4396 -+ ServerProvider,
4397 -+ ServerSettingsError,
4398 -+ | ChildProcessSpawner.ChildProcessSpawner
4399 -+ | FileSystem.FileSystem
4400 -+ | Path.Path
4401 -+ | ServerSettingsService
4402 -+> {
4403 -+ const codexSettings = yield* Effect.service(ServerSettingsService).pipe(
4404 -+ Effect.flatMap((service) => service.getSettings),
4405 -+ Effect.map((settings) => settings.providers.codex),
4406 - );
4407 -- const child = yield* spawner
4408 -- .spawn(
4409 -- ChildProcess.make(spawnCommand.command, spawnCommand.args, {
4410 -- cwd: input.cwd,
4411 -- env: environment,
4412 -- extendEnv: true,
4413 -- forceKillAfter: CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER,
4414 -- shell: spawnCommand.shell,
4415 -- }),
4416 -- )
4417 -- .pipe(
4418 -- Effect.mapError(
4419 -- (cause) =>
4420 -- new CodexErrors.CodexAppServerSpawnError({
4421 -- command: `${input.binaryPath} app-server`,
4422 -- cause,
4423 -- }),
4424 -- ),
4425 -- );
4426 -- const clientContext = yield* Layer.build(CodexClient.layerChildProcess(child));
4427 -- const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe(
4428 -- Effect.provide(clientContext),
4429 -+ const checkedAt = new Date().toISOString();
4430 -+ const models = providerModelsFromSettings(
4431 -+ BUILT_IN_CODEX_MODELS,
4432 -+ PROVIDER,
4433 -+ codexSettings.customModels,
4434 -+ DEFAULT_CODEX_MODEL_CAPABILITIES,
4435 - );
4436 -- yield* client.request("initialize", buildCodexInitializeParams());
4437 -- yield* client.notify("initialized", undefined);
4438 -- const skillsResponse = yield* client.request("skills/list", { cwds: [input.cwd] });
4439 -- return parseCodexSkillsListResponse(skillsResponse, input.cwd);
4440 --});
4441 -
4442 --const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => {
4443 -- const models = new Set<string>();
4444 -- for (const model of codexSettings.customModels) {
4445 -- const trimmed = model.trim();
4446 -- if (trimmed.length > 0) {
4447 -- models.add(trimmed);
4448 -- }
4449 -+ if (!codexSettings.enabled) {
4450 -+ return buildServerProvider({
4451 -+ provider: PROVIDER,
4452 -+ enabled: false,
4453 -+ checkedAt,
4454 -+ models,
4455 -+ probe: {
4456 -+ installed: false,
4457 -+ version: null,
4458 -+ status: "warning",
4459 -+ auth: { status: "unknown" },
4460 -+ message: "Codex is disabled in T3 Code settings.",
4461 -+ },
4462 -+ });
4463 - }
4464 -- return Array.from(models, (model) => ({
4465 -- slug: model,
4466 -- name: model,
4467 -- isCustom: true,
4468 -- capabilities: null,
4469 -- }));
4470 --};
4471 -
4472 --const makePendingCodexProvider = (
4473 -- codexSettings: CodexSettings,
4474 --): Effect.Effect<ServerProviderDraft> =>
4475 -- Effect.gen(function* () {
4476 -- const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso);
4477 -- const models = emptyCodexModelsFromSettings(codexSettings);
4478 --
4479 -- if (!codexSettings.enabled) {
4480 -- return buildServerProvider({
4481 -- presentation: CODEX_PRESENTATION,
4482 -- enabled: false,
4483 -- checkedAt,
4484 -- models,
4485 -- skills: [],
4486 -- probe: {
4487 -- installed: false,
4488 -- version: null,
4489 -- status: "warning",
4490 -- auth: { status: "unknown" },
4491 -- message: "Codex is disabled in T3 Code settings.",
4492 -- },
4493 -- });
4494 -- }
4495 -+ const versionProbe = yield* runCodexCommand(["--version"]).pipe(
4496 -+ Effect.timeoutOption(DEFAULT_TIMEOUT_MS),
4497 -+ Effect.result,
4498 -+ );
4499 -
4500 -+ if (Result.isFailure(versionProbe)) {
4501 -+ const error = versionProbe.failure;
4502 - return buildServerProvider({
4503 -- presentation: CODEX_PRESENTATION,
4504 -- enabled: true,
4505 -+ provider: PROVIDER,
4506 -+ enabled: codexSettings.enabled,
4507 - checkedAt,
4508 - models,
4509 -- skills: [],
4510 - probe: {
4511 -- installed: false,
4512 -+ installed: !isCommandMissingCause(error),
4513 - version: null,
4514 -- status: "warning",
4515 -+ status: "error",
4516 - auth: { status: "unknown" },
4517 -- message: "Codex provider status has not been checked in this session yet.",
4518 -+ message: isCommandMissingCause(error)
4519 -+ ? "Codex CLI (`codex`) is not installed or not on PATH."
4520 -+ : `Failed to execute Codex CLI health check: ${error.message}.`,
4521 - },
4522 - });
4523 -- });
4524 -+ }
4525 -+
4526 -+ if (Option.isNone(versionProbe.success)) {
4527 -+ return buildServerProvider({
4528 -+ provider: PROVIDER,
4529 -+ enabled: codexSettings.enabled,
4530 -+ checkedAt,
4531 -+ models,
4532 -+ probe: {
4533 -+ installed: true,
4534 -+ version: null,
4535 -+ status: "error",
4536 -+ auth: { status: "unknown" },
4537 -+ message: "Codex CLI is installed but failed to run. Timed out while running command.",
4538 -+ },
4539 -+ });
4540 -+ }
4541 -
4542 --function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]): {
4543 -- readonly status: Exclude<ServerProviderState, "disabled">;
4544 -- readonly auth: ServerProvider["auth"];
4545 -- readonly message?: string;
4546 --} {
4547 -- const authLabel = codexAccountAuthLabel(account.account);
4548 -- const authEmail = codexAccountEmail(account.account);
4549 -- const auth = {
4550 -- status: account.account ? ("authenticated" as const) : ("unknown" as const),
4551 -- ...(account.account?.type ? { type: account.account?.type } : {}),
4552 -- ...(authLabel ? { label: authLabel } : {}),
4553 -- ...(authEmail ? { email: authEmail } : {}),
4554 -- } satisfies ServerProvider["auth"];
4555 --
4556 -- if (account.account) {
4557 -- return { status: "ready", auth };
4558 -+ const version = versionProbe.success.value;
4559 -+ const parsedVersion =
4560 -+ parseCodexCliVersion(`${version.stdout}\n${version.stderr}`) ??
4561 -+ parseGenericCliVersion(`${version.stdout}\n${version.stderr}`);
4562 -+ if (version.code !== 0) {
4563 -+ const detail = detailFromResult(version);
4564 -+ return buildServerProvider({
4565 -+ provider: PROVIDER,
4566 -+ enabled: codexSettings.enabled,
4567 -+ checkedAt,
4568 -+ models,
4569 -+ probe: {
4570 -+ installed: true,
4571 -+ version: parsedVersion,
4572 -+ status: "error",
4573 -+ auth: { status: "unknown" },
4574 -+ message: detail
4575 -+ ? `Codex CLI is installed but failed to run. ${detail}`
4576 -+ : "Codex CLI is installed but failed to run.",
4577 -+ },
4578 -+ });
4579 - }
4580 -
4581 -- if (account.requiresOpenaiAuth) {
4582 -- return {
4583 -- status: "error",
4584 -- auth: { status: "unauthenticated" },
4585 -- message: "Codex CLI is not authenticated. Run `codex login` and try again.",
4586 -- };
4587 -+ if (parsedVersion && !isCodexCliVersionSupported(parsedVersion)) {
4588 -+ return buildServerProvider({
4589 -+ provider: PROVIDER,
4590 -+ enabled: codexSettings.enabled,
4591 -+ checkedAt,
4592 -+ models,
4593 -+ probe: {
4594 -+ installed: true,
4595 -+ version: parsedVersion,
4596 -+ status: "error",
4597 -+ auth: { status: "unknown" },
4598 -+ message: formatCodexCliUpgradeMessage(parsedVersion),
4599 -+ },
4600 -+ });
4601 - }
4602 -
4603 -- return { status: "ready", auth };
4604 --}
4605 -+ const skills =
4606 -+ (resolveSkills
4607 -+ ? yield* resolveSkills({
4608 -+ binaryPath: codexSettings.binaryPath,
4609 -+ homePath: codexSettings.homePath,
4610 -+ cwd: process.cwd(),
4611 -+ }).pipe(Effect.orElseSucceed(() => undefined))
4612 -+ : undefined) ?? [];
4613 -
4614 --export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(function* (
4615 -- codexSettings: CodexSettings,
4616 -- probe: (input: {
4617 -- readonly binaryPath: string;
4618 -- readonly homePath?: string;
4619 -- readonly launchArgs?: string;
4620 -- readonly cwd: string;
4621 -- readonly customModels: ReadonlyArray<string>;
4622 -- readonly environment?: NodeJS.ProcessEnv;
4623 -- }) => Effect.Effect<
4624 -- CodexAppServerProviderSnapshot,
4625 -- CodexErrors.CodexAppServerError,
4626 -- ChildProcessSpawner.ChildProcessSpawner | Scope.Scope
4627 -- > = probeCodexAppServerProvider,
4628 -- environment?: NodeJS.ProcessEnv,
4629 --): Effect.fn.Return<
4630 -- ServerProviderDraft,
4631 -- ServerSettingsError,
4632 -- ChildProcessSpawner.ChildProcessSpawner
4633 --> {
4634 -- const resolvedEnvironment = environment ?? process.env;
4635 -- const checkedAt = DateTime.formatIso(yield* DateTime.now);
4636 -- const emptyModels = emptyCodexModelsFromSettings(codexSettings);
4637 -+ if (yield* hasCustomModelProvider) {
4638 -+ return buildServerProvider({
4639 -+ provider: PROVIDER,
4640 -+ enabled: codexSettings.enabled,
4641 -+ checkedAt,
4642 -+ models,
4643 -+ skills,
4644 -+ probe: {
4645 -+ installed: true,
4646 -+ version: parsedVersion,
4647 -+ status: "ready",
4648 -+ auth: { status: "unknown" },
4649 -+ message: "Using a custom Codex model provider; OpenAI login check skipped.",
4650 -+ },
4651 -+ });
4652 -+ }
4653 -
4654 -- if (!codexSettings.enabled) {
4655 -+ const authProbe = yield* runCodexCommand(["login", "status"]).pipe(
4656 -+ Effect.timeoutOption(DEFAULT_TIMEOUT_MS),
4657 -+ Effect.result,
4658 -+ );
4659 -+ const account = resolveAccount
4660 -+ ? yield* resolveAccount({
4661 -+ binaryPath: codexSettings.binaryPath,
4662 -+ homePath: codexSettings.homePath,
4663 -+ })
4664 -+ : undefined;
4665 -+ const resolvedModels = adjustCodexModelsForAccount(models, account);
4666 -+
4667 -+ if (Result.isFailure(authProbe)) {
4668 -+ const error = authProbe.failure;
4669 - return buildServerProvider({
4670 -- presentation: CODEX_PRESENTATION,
4671 -- enabled: false,
4672 -+ provider: PROVIDER,
4673 -+ enabled: codexSettings.enabled,
4674 - checkedAt,
4675 -- models: emptyModels,
4676 -- skills: [],
4677 -+ models: resolvedModels,
4678 -+ skills,
4679 - probe: {
4680 -- installed: false,
4681 -- version: null,
4682 -+ installed: true,
4683 -+ version: parsedVersion,
4684 - status: "warning",
4685 - auth: { status: "unknown" },
4686 -- message: "Codex is disabled in T3 Code settings.",
4687 -+ message: `Could not verify Codex authentication status: ${error.message}.`,
4688 -+ },
4689 -+ });
4690 -+ }
4691 -+
4692 -+ if (Option.isNone(authProbe.success)) {
4693 -+ return buildServerProvider({
4694 -+ provider: PROVIDER,
4695 -+ enabled: codexSettings.enabled,
4696 -+ checkedAt,
4697 -+ models: resolvedModels,
4698 -+ skills,
4699 -+ probe: {
4700 -+ installed: true,
4701 -+ version: parsedVersion,
4702 -+ status: "warning",
4703 -+ auth: { status: "unknown" },
4704 -+ message: "Could not verify Codex authentication status. Timed out while running command.",
4705 -+ },
4706 -+ });
4707 -+ }
4708 -+
4709 -+ const parsed = parseAuthStatusFromOutput(authProbe.success.value);
4710 -+ const authType = codexAuthSubType(account);
4711 -+ const authLabel = codexAuthSubLabel(account);
4712 -+ return buildServerProvider({
4713 -+ provider: PROVIDER,
4714 -+ enabled: codexSettings.enabled,
4715 -+ checkedAt,
4716 -+ models: resolvedModels,
4717 -+ skills,
4718 -+ probe: {
4719 -+ installed: true,
4720 -+ version: parsedVersion,
4721 -+ status: parsed.status,
4722 -+ auth: {
4723 -+ ...parsed.auth,
4724 -+ ...(authType ? { type: authType } : {}),
4725 -+ ...(authLabel ? { label: authLabel } : {}),
4726 -+ },
4727 -+ ...(parsed.message ? { message: parsed.message } : {}),
4728 -+ },
4729 -+ });
4730 -+});
4731 -+
4732 -+const applyCodexDiscoverySnapshot = (
4733 -+ snapshot: ServerProvider,
4734 -+ discovery: CodexDiscoverySnapshot,
4735 -+): ServerProvider => {
4736 -+ const authType = codexAuthSubType(discovery.account);
4737 -+ const authLabel = codexAuthSubLabel(discovery.account);
4738 -+
4739 -+ return {
4740 -+ ...snapshot,
4741 -+ auth: {
4742 -+ ...snapshot.auth,
4743 -+ ...(authType ? { type: authType } : {}),
4744 -+ ...(authLabel ? { label: authLabel } : {}),
4745 -+ },
4746 -+ models: adjustCodexModelsForAccount(snapshot.models, discovery.account),
4747 -+ skills: discovery.skills,
4748 -+ };
4749 -+};
4750 -+
4751 -+const enrichCodexSnapshotViaDiscovery = (input: {
4752 -+ readonly settings: CodexSettings;
4753 -+ readonly snapshot: ServerProvider;
4754 -+ readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>;
4755 -+ readonly getDiscovery: (input: {
4756 -+ readonly binaryPath: string;
4757 -+ readonly homePath?: string;
4758 -+ readonly cwd: string;
4759 -+ }) => Effect.Effect<CodexDiscoverySnapshot | undefined>;
4760 -+}) =>
4761 -+ (input.settings.enabled && input.snapshot.installed
4762 -+ ? input
4763 -+ .getDiscovery({
4764 -+ binaryPath: input.settings.binaryPath,
4765 -+ homePath: input.settings.homePath,
4766 -+ cwd: process.cwd(),
4767 -+ })
4768 -+ .pipe(
4769 -+ Effect.flatMap((discovery) =>
4770 -+ discovery
4771 -+ ? input.publishSnapshot(applyCodexDiscoverySnapshot(input.snapshot, discovery))
4772 -+ : Effect.void,
4773 -+ ),
4774 -+ )
4775 -+ : Effect.void
4776 -+ ).pipe(Effect.catchCause((cause) => Effect.logError(cause)));
4777 -+
4778 -+export const CodexProviderLive = Layer.effect(
4779 -+ CodexProvider,
4780 -+ Effect.gen(function* () {
4781 -+ const serverSettings = yield* ServerSettingsService;
4782 -+ const fileSystem = yield* FileSystem.FileSystem;
4783 -+ const path = yield* Path.Path;
4784 -+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
4785 -+ const accountProbeCache = yield* Cache.make({
4786 -+ capacity: 4,
4787 -+ timeToLive: Duration.minutes(5),
4788 -+ lookup: (key: CodexDiscoveryCacheKey) => {
4789 -+ const { binaryPath, homePath, cwd } = key;
4790 -+ return probeCodexCapabilities({
4791 -+ binaryPath,
4792 -+ cwd,
4793 -+ ...(homePath ? { homePath } : {}),
4794 -+ });
4795 - },
4796 - });
4797 -+ });
4798 -+
4799 -+ const getDiscovery = (input: {
4800 -+ readonly binaryPath: string;
4801 -+ readonly homePath?: string;
4802 -+ readonly cwd: string;
4803 -+ }) => Cache.get(accountProbeCache, new CodexDiscoveryCacheKey(input));
4804 -+
4805 -+ const checkProvider = checkCodexProviderStatus().pipe(
4806 -+ Effect.provideService(ServerSettingsService, serverSettings),
4807 -+ Effect.provideService(FileSystem.FileSystem, fileSystem),
4808 -+ Effect.provideService(Path.Path, path),
4809 -+ Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
4810 -+ );
4811 -+
4812 -+ return yield* makeManagedServerProvider<CodexSettings>({
4813 -+ getSettings: serverSettings.getSettings.pipe(
4814 -+ Effect.map((settings) => settings.providers.codex),
4815 -+ Effect.orDie,
4816 -+ ),
4817 -+ streamSettings: serverSettings.streamChanges.pipe(
4818 -+ Stream.map((settings) => settings.providers.codex),
4819 -+ ),
4820 -+ haveSettingsChanged: (previous, next) => !Equal.equals(previous, next),
4821 -+ buildInitialSnapshot: buildInitialCodexProviderSnapshot,
4822 -+ checkProvider,
4823 -+ enrichSnapshot: ({ settings, snapshot, publishSnapshot }) =>
4824 -+ enrichCodexSnapshotViaDiscovery({
4825 -+ settings,
4826 -+ snapshot,
4827 -+ publishSnapshot,
4828 -+ getDiscovery,
4829 -+ }),
4830 -+ });
4831 - }
4832 -
4833 - const probeResult = yield* probe({
4834 -
4835 - ## apps/server/src/provider/Layers/CursorAdapter.test.ts ##
4836 -@@ apps/server/src/provider/Layers/CursorAdapter.test.ts: const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.
4837 - const mockAgentCommand = "node";
4838 - const mockAgentArgs = [mockAgentPath] as const;
4839 -
4840 --async function makeMockAgentWrapper(
4841 -- extraEnv?: Record<string, string>,
4842 -- options?: { initialDelaySeconds?: number },
4843 --) {
4844 -- const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-mock-"));
4845 -- const wrapperPath = NodePath.join(dir, "fake-agent.sh");
4846 -+async function makeMockAgentWrapper(extraEnv?: Record<string, string>) {
4847 -+ const dir = await mkdtemp(path.join(os.tmpdir(), "cursor-acp-mock-"));
4848 -+ const wrapperPath = path.join(dir, "fake-agent.sh");
4849 - const envExports = Object.entries(extraEnv ?? {})
4850 - .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
4851 - .join("\n");
4852 - const script = `#!/bin/sh
4853 - ${envExports}
4854 --${options?.initialDelaySeconds ? `sleep ${JSON.stringify(String(options.initialDelaySeconds))}` : ""}
4855 --exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"
4856 -+exec ${JSON.stringify(bunExe)} ${JSON.stringify(mockAgentPath)} "$@"
4857 - `;
4858 - await NodeFSP.writeFile(wrapperPath, script, "utf8");
4859 - await NodeFSP.chmod(wrapperPath, 0o755);
4860 -@@ apps/server/src/provider/Layers/CursorAdapter.test.ts: async function readJsonLines(filePath: string) {
4861 - .map((line) => JSON.parse(line) as Record<string, unknown>);
4862 +-async function makeMockAgentWrapper(
4863 +- extraEnv?: Record<string, string>,
4864 +- options?: { initialDelaySeconds?: number },
4865 +-) {
4866 +- const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-mock-"));
4867 +- const wrapperPath = NodePath.join(dir, "fake-agent.sh");
4868 ++async function makeMockAgentWrapper(extraEnv?: Record<string, string>) {
4869 ++ const dir = await mkdtemp(path.join(os.tmpdir(), "cursor-acp-mock-"));
4870 ++ const wrapperPath = path.join(dir, "fake-agent.sh");
4871 + const envExports = Object.entries(extraEnv ?? {})
4872 + .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
4873 + .join("\n");
4874 + const script = `#!/bin/sh
4875 + ${envExports}
4876 +-${options?.initialDelaySeconds ? `sleep ${JSON.stringify(String(options.initialDelaySeconds))}` : ""}
4877 +-exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"
4878 ++exec ${JSON.stringify(bunExe)} ${JSON.stringify(mockAgentPath)} "$@"
4879 + `;
4880 + await NodeFSP.writeFile(wrapperPath, script, "utf8");
4881 + await NodeFSP.chmod(wrapperPath, 0o755);
4882 +@@ apps/server/src/provider/Layers/CursorAdapter.test.ts: async function readJsonLines(filePath: string) {
4883 + .map((line) => JSON.parse(line) as Record<string, unknown>);
4884 }
4885
4886 -async function waitForFileContent(filePath: string, attempts = 40) {
4887 @@ apps/server/src/provider/Layers/CursorAdapter.test.ts: cursorAdapterTestLayer("C
4888 }),
4889 );
4890
4891 +- it.effect("sends selected project skills in Cursor's native slash form", () =>
4892 +- Effect.gen(function* () {
4893 +- const adapter = yield* CursorAdapter;
4894 +- const settings = yield* ServerSettingsService;
4895 +- const threadId = ThreadId.make("cursor-skill-dispatch");
4896 +- const workspace = yield* Effect.promise(() =>
4897 +- NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-skill-dispatch-")),
4898 +- );
4899 +- const requestLogPath = NodePath.join(workspace, "requests.ndjson");
4900 +- const argvLogPath = NodePath.join(workspace, "argv.txt");
4901 +- const skillDirectory = NodePath.join(workspace, ".cursor", "skills", "review");
4902 +- yield* Effect.promise(() => NodeFSP.mkdir(skillDirectory, { recursive: true }));
4903 +- yield* Effect.promise(() =>
4904 +- NodeFSP.writeFile(NodePath.join(skillDirectory, "SKILL.md"), "# Review\n", "utf8"),
4905 +- );
4906 +- yield* Effect.promise(() => NodeFSP.writeFile(requestLogPath, "", "utf8"));
4907 +- const wrapperPath = yield* Effect.promise(() =>
4908 +- makeProbeWrapper(requestLogPath, argvLogPath),
4909 +- );
4910 +- yield* settings.updateSettings({ providers: { cursor: { binaryPath: wrapperPath } } });
4911 +-
4912 +- yield* adapter.startSession({
4913 +- threadId,
4914 +- provider: ProviderDriverKind.make("cursor"),
4915 +- cwd: workspace,
4916 +- runtimeMode: "full-access",
4917 +- modelSelection: { instanceId: ProviderInstanceId.make("cursor"), model: "default" },
4918 +- });
4919 +- yield* adapter.sendTurn({
4920 +- threadId,
4921 +- input: "please $review this",
4922 +- attachments: [],
4923 +- });
4924 +- const snapshot = yield* adapter.readThread(threadId);
4925 +- assert.deepStrictEqual(
4926 +- snapshot.turns.map((turn) => turn.items),
4927 +- [
4928 +- [
4929 +- {
4930 +- prompt: [{ type: "text", text: "please /review this" }],
4931 +- result: { stopReason: "end_turn" },
4932 +- },
4933 +- ],
4934 +- ],
4935 +- );
4936 +- yield* adapter.stopSession(threadId);
4937 +-
4938 +- const requests = yield* Effect.promise(() => readJsonLines(requestLogPath));
4939 +- const promptRequests = requests.filter((entry) => entry.method === "session/prompt");
4940 +- assert.deepStrictEqual(
4941 +- promptRequests.map(
4942 +- (request) => (request.params as Record<string, unknown> | undefined)?.prompt,
4943 +- ),
4944 +- [
4945 +- [
4946 +- { type: "text", text: "please /review this" },
4947 +- { type: "text", text: buildRuntimeInstructions({ harness: "Cursor" }) },
4948 +- ],
4949 +- ],
4950 +- );
4951 +- }),
4952 +- );
4953 +-
4954 - it.effect("steers a running turn instead of opening a new one on mid-turn sendTurn", () =>
4955 - Effect.gen(function* () {
4956 - const adapter = yield* CursorAdapter;
4957 @@ apps/server/src/provider/Layers/CursorAdapter.test.ts: cursorAdapterTestLayer("C
4958 it.effect(
4959 "streams ACP tool calls and approvals on the active turn in approval-required mode",
4960 () =>
4961 +@@ apps/server/src/provider/Layers/CursorAdapter.test.ts: cursorAdapterTestLayer("CursorAdapterLive", (it) => {
4962 + );
4963 + assert.isDefined(permissionResponse);
4964 +
4965 +- const argvRuns = yield* Effect.promise(() => readArgvLog(argvLogPath));
4966 +- assert.deepStrictEqual(argvRuns, [["--force", "acp"]]);
4967 +-
4968 + yield* adapter.stopSession(threadId);
4969 + }),
4970 + );
4971 @@ apps/server/src/provider/Layers/CursorAdapter.test.ts: cursorAdapterTestLayer("CursorAdapterLive", (it) => {
4972 }),
4973 );
4974 @@ apps/server/src/provider/Layers/CursorAdapter.test.ts: cursorAdapterTestLayer("C
4975 it.effect("switches model in-session via session/set_config_option", () =>
4976 Effect.gen(function* () {
4977 const adapter = yield* CursorAdapter;
4978 +@@ apps/server/src/provider/Layers/CursorAdapter.test.ts: cursorAdapterTestLayer("CursorAdapterLive", (it) => {
4979 +
4980 + const argvRuns = yield* Effect.promise(() => readArgvLog(argvLogPath));
4981 + assert.lengthOf(argvRuns, 1, "session should not restart — only one spawn");
4982 +- assert.deepStrictEqual(argvRuns[0], ["--force", "acp"]);
4983 ++ assert.deepStrictEqual(argvRuns[0], ["acp"]);
4984 +
4985 + const requests = yield* Effect.promise(() => readJsonLines(requestLogPath));
4986 + const setConfigRequests = requests.filter(
4987
4988 ## apps/server/src/provider/Layers/CursorAdapter.ts ##
4989 @@
4990 @@ apps/server/src/provider/Layers/CursorAdapter.ts: import {
4991 import type * as EffectAcpSchema from "effect-acp/schema";
4992
4993 import { resolveAttachmentPath } from "../../attachmentStore.ts";
4994 +@@ apps/server/src/provider/Layers/CursorAdapter.ts: import {
4995 + import { type CursorAdapterShape } from "../Services/CursorAdapter.ts";
4996 + import { resolveCursorAcpBaseModelId } from "./CursorProvider.ts";
4997 + import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts";
4998 +-import {
4999 +- discoverCursorSkills,

101729 more lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/provider-adapter-regression-probes.test.ts+207−0
@@ -0,0 +1,207 @@
1import type { SDKMessage } from "../../../apps/server/node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts";
2import * as NodeServices from "../../../apps/server/node_modules/@effect/platform-node/dist/NodeServices.js";
3import { assert, it } from "../../../apps/server/node_modules/@effect/vitest/dist/index.js";
4import {
5 ClaudeSettings,
6 MessageId,
7 type ModelSelection,
8 NodeId,
9 type OrchestrationV2AppThread,
10 type OrchestrationV2ProviderThread,
11 ProjectId,
12 ProviderInstanceId,
13 ProviderSessionId,
14 RunAttemptId,
15 RunId,
16 ThreadId,
17} from "../../../apps/server/node_modules/@t3tools/contracts/src/index.ts";
18import * as DateTime from "../../../apps/server/node_modules/effect/dist/DateTime.js";
19import * as Effect from "../../../apps/server/node_modules/effect/dist/Effect.js";
20import * as Fiber from "../../../apps/server/node_modules/effect/dist/Fiber.js";
21import * as FileSystem from "../../../apps/server/node_modules/effect/dist/FileSystem.js";
22import * as Layer from "../../../apps/server/node_modules/effect/dist/Layer.js";
23import * as Option from "../../../apps/server/node_modules/effect/dist/Option.js";
24import * as Path from "../../../apps/server/node_modules/effect/dist/Path.js";
25import * as Queue from "../../../apps/server/node_modules/effect/dist/Queue.js";
26import * as Schema from "../../../apps/server/node_modules/effect/dist/Schema.js";
27import * as Stream from "../../../apps/server/node_modules/effect/dist/Stream.js";
28
29import {
30 CLAUDE_DEFAULT_INSTANCE_ID,
31 CLAUDE_PROVIDER,
32 makeClaudeAdapterV2,
33} from "../../../apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts";
34import {
35 ProviderAdapterV2RuntimePolicy,
36 type ProviderAdapterV2Event,
37 type ProviderAdapterV2TurnInput,
38} from "../../../apps/server/src/orchestration-v2/ProviderAdapter.ts";
39import {
40 layer as idAllocatorLayer,
41 IdAllocatorV2,
42} from "../../../apps/server/src/orchestration-v2/IdAllocator.ts";
43
44const settings = Schema.decodeSync(ClaudeSettings)({});
45const modelSelection = {
46 instanceId: ProviderInstanceId.make(CLAUDE_PROVIDER),
47 model: "claude-sonnet-4-6",
48 options: [],
49} satisfies ModelSelection;
50const runtimePolicy = ProviderAdapterV2RuntimePolicy.make({
51 runtimeMode: "full-access",
52 interactionMode: "default",
53 cwd: "/workspace",
54});
55
56function appThread(input: {
57 readonly threadId: ThreadId;
58 readonly providerThread: OrchestrationV2ProviderThread;
59 readonly now: DateTime.Utc;
60}): OrchestrationV2AppThread {
61 return {
62 id: input.threadId,
63 projectId: ProjectId.make(`project-${input.threadId}`),
64 title: "Claude terminal reason audit probe",
65 providerInstanceId: CLAUDE_DEFAULT_INSTANCE_ID,
66 modelSelection,
67 runtimeMode: "full-access",
68 interactionMode: "default",
69 branch: null,
70 worktreePath: null,
71 activeProviderThreadId: input.providerThread.id,
72 lineage: {
73 parentThreadId: null,
74 relationshipToParent: null,
75 rootThreadId: input.threadId,
76 },
77 forkedFrom: null,
78 createdBy: "user",
79 creationSource: "web",
80 createdAt: input.now,
81 updatedAt: input.now,
82 archivedAt: null,
83 settledOverride: null,
84 settledAt: null,
85 lastVisitedAt: null,
86 deletedAt: null,
87 };
88}
89
90function turnInput(input: {
91 readonly threadId: ThreadId;
92 readonly providerThread: OrchestrationV2ProviderThread;
93 readonly now: DateTime.Utc;
94}): ProviderAdapterV2TurnInput {
95 const attemptId = RunAttemptId.make("attempt-claude-terminal-reason-audit");
96 return {
97 appThread: appThread(input),
98 threadId: input.threadId,
99 runId: RunId.make("run-claude-terminal-reason-audit"),
100 runOrdinal: 1,
101 providerTurnOrdinal: 1,
102 attemptId,
103 rootNodeId: NodeId.make("node-claude-terminal-reason-audit"),
104 providerThread: input.providerThread,
105 message: {
106 createdBy: "user",
107 creationSource: "web",
108 messageId: MessageId.make("message-claude-terminal-reason-audit"),
109 text: "hello",
110 attachments: [],
111 },
112 modelSelection,
113 runtimePolicy,
114 };
115}
116
117const apiErrorResult = {
118 type: "result",
119 subtype: "success",
120 duration_ms: 10,
121 duration_api_ms: 10,
122 is_error: false,
123 num_turns: 0,
124 result: "",
125 errors: [],
126 stop_reason: null,
127 terminal_reason: "api_error",
128 total_cost_usd: 0,
129 usage: {
130 input_tokens: 1,
131 output_tokens: 0,
132 cache_creation_input_tokens: 0,
133 cache_read_input_tokens: 0,
134 },
135 modelUsage: {},
136 permission_denials: [],
137 uuid: "result-claude-terminal-reason-audit",
138 session_id: "native-claude-terminal-reason-audit",
139} as unknown as SDKMessage;
140
141it.effect("reproduces V2 completing a Claude api_error terminal reason", () =>
142 Effect.gen(function* () {
143 const fileSystem = yield* FileSystem.FileSystem;
144 const idAllocator = yield* IdAllocatorV2;
145 const messages = yield* Queue.unbounded<SDKMessage>();
146 const attachmentsDir = yield* fileSystem.makeTempDirectoryScoped({
147 prefix: "t3-claude-terminal-reason-audit-",
148 });
149 const adapter = makeClaudeAdapterV2({
150 instanceId: CLAUDE_DEFAULT_INSTANCE_ID,
151 settings,
152 environment: {},
153 attachmentsDir,
154 fileSystem,
155 path: yield* Path.Path,
156 idAllocator,
157 queryRunner: {
158 allocateSessionId: Effect.succeed("native-claude-terminal-reason-audit"),
159 open: () =>
160 Effect.succeed({
161 messages: Stream.fromQueue(messages),
162 offer: () => Effect.void,
163 setModel: () => Effect.void,
164 interrupt: Effect.void,
165 close: Effect.void,
166 }),
167 forkSession: () => Effect.die("unused forkSession"),
168 assertComplete: Effect.void,
169 },
170 });
171 const threadId = ThreadId.make("thread-claude-terminal-reason-audit");
172 const runtime = yield* adapter.openSession({
173 threadId,
174 providerSessionId: ProviderSessionId.make("session-claude-terminal-reason-audit"),
175 modelSelection,
176 runtimePolicy,
177 });
178 const providerThread = yield* runtime.ensureThread({
179 threadId,
180 modelSelection,
181 runtimePolicy,
182 });
183 const terminalFiber = yield* runtime.events.pipe(
184 Stream.filter(
185 (event): event is Extract<ProviderAdapterV2Event, { readonly type: "turn.terminal" }> =>
186 event.type === "turn.terminal",
187 ),
188 Stream.runHead,
189 Effect.forkScoped,
190 );
191
192 yield* runtime.startTurn(
193 turnInput({
194 threadId,
195 providerThread,
196 now: yield* DateTime.now,
197 }),
198 );
199 yield* Queue.offer(messages, apiErrorResult);
200
201 const terminalOption = yield* Fiber.join(terminalFiber);
202 assert.isTrue(Option.isSome(terminalOption));
203 const terminal = Option.getOrThrow(terminalOption);
204 assert.equal(terminal.status, "completed");
205 assert.equal(terminal.failure, null);
206 }).pipe(Effect.scoped, Effect.provide(Layer.merge(idAllocatorLayer, NodeServices.layer))),
207);
audits/orchestrator-v2/2026-09-04/providers.md+109−0
@@ -0,0 +1,109 @@
1# Orchestrator V2 provider parity and regression audit
2
3## Result
4
5The frozen committed branch still has four provider findings from the 2026-09-02 audit and has four additional concrete provider-parity defects exposed by changes that landed on main since that audit. I found no current-main-only provider change among the final eight unmerged commits and no committed registration, discovery, or auth regression across the seven built-in drivers.
6
7The dropped range-diff patch for Codex availability was absorbed by the refactor. It is not a regression: `CodexProvider.ts:489-525` still produces the unchecked pending snapshot with `installed: false`, and `CodexDriver.ts:192-204` still uses that builder as the managed driver's `initialSnapshot`.
8
9Audit basis:
10
11- Frozen branch: `8af5734365f7c45bc08b57066dbae42f9f7d4235`
12- Frozen main: `d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70`
13- Merge base: `c8f77e0d441264efb0acfac312e852c81ae3da83`
14- Prior audit branch/main: `d2f1f511f4cc833bc930d6c355cd0f9b61e835a0` / `57a66608b918d673eeec7e6c94ea5906b756fcd0`
15
16All committed evidence below came from `git show` at those frozen revisions. Dirty-worktree observations came from the `.snapshot` files in `worktree-overlay`.
17
18## Prior finding status
19
20| ID | Severity | Status | Current evidence |
21| --- | -------: | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22| F06 | P1 | Persists in committed HEAD | OpenCode V2 treats descendant enumeration, descendant abort, and the whole 15-second descendant traversal as best effort (`OpenCodeAdapterV2.ts:3121-3150`). Only the root abort can reach `ProviderAdapterInterruptError`. An explicit Stop can therefore acknowledge success while a child session continues. Current main preserves the first relevant descendant error instead of discarding it (`OpenCodeAdapter.ts:756-823`). |
23| F07 | P1 | Persists in committed HEAD | An external OpenCode server is accepted at `OpenCodeAdapterV2.ts:893-905`, but session teardown only aborts the local SSE controller at `2542-2548`. The now-bounded and logged manager scope close (`ProviderSessionManager.ts:651-705`) prevents release itself from hanging; it does not abort the root or child OpenCode sessions. Current main calls bounded root and descendant aborts during teardown (`OpenCodeAdapter.ts:825-837`). |
24| F08 | P1 | Persists in committed HEAD | Cursor metadata generation still launches the SDK in unrestricted `agent` mode at the real project cwd with its sandbox disabled (`CursorTextGeneration.ts:75-124`). The timeout bounds duration, not filesystem effects. Current main uses Cursor ACP and requests `ask` mode before prompting (`CursorTextGeneration.ts:85-105`). |
25| F11 | P2 | Persists in committed HEAD | Claude `Read` remains a generic `dynamic_tool`: the classifier only accepts the tool name and maps `read` without inspecting the path (`ClaudeAdapterV2.ts:1401-1449, 3614-3626`). Web and mobile then reduce dynamic tool data to input/output (`apps/web/src/session-logic.ts:631-637`; `apps/mobile/src/lib/threadActivity.ts:534-539`), so the shared image-preview path never receives `viewedImagePath`. Coordinate the client presentation portion with the root review. |
26
27F12 and F16 are owned by the root audit and are not duplicated here.
28
29## New concrete findings
30
31### PROV-OPENCODE-01: P1, a clean SSE EOF silently disables all later provider events
32
33`OpenCodeAdapterV2.ts:2542-2575` creates one `event.subscribe` stream. Its terminal handler returns without changing session state when `Exit.isSuccess(exit)` at line 2562. The public provider runtime continues exposing a separate never-ending queue stream at `2710-2716`, so the session manager does not observe that the native subscription fiber ended.
34
35Trigger: the SDK async iterable ends cleanly without the local abort signal being set. This includes a transport that reports disconnect as EOF instead of throwing.
36
37Consequence: the provider session remains apparently ready, but subsequent status, text, tool, approval, and completion events are no longer ingested. An active turn or request can remain stuck, and later activity on the same session is invisible.
38
39Current main addressed this in [#9653](https://github.com/pingdotgg/t3code/pull/9653): `OpenCodeAdapter.ts:2713-2757` records SSE errors and emits an unexpected-exit failure even when the iterable ends successfully, enabling the stopped/reconnect path. This is a missing port, not an intentional V2 difference.
40
41I left this finding source-backed. The existing mocked adapter lifecycle does not expose a receipt when the private subscription fiber completes. A passing absence assertion after closing the iterable would depend on scheduler timing, so it would not be a meaningful regression probe.
42
43### PROV-OPENCODE-02: P1, approval and question replies can hang the durable effect worker indefinitely
44
45`OpenCodeAdapterV2.ts:3162-3212` calls `question.reply` and `permission.reply` without passing an SDK abort signal and without a timeout. The orchestrator has already persisted the request as resolved before enqueueing this external effect (`Orchestrator.ts:5068-5075, 5222-5237`), and `RuntimeRequestService.ts:91-125` then waits directly on the adapter call.
46
47Trigger: an OpenCode server accepts the reply connection but never resolves it.
48
49Consequence: the user cannot submit another answer because the durable request is already resolved, while the provider may never receive the response. The call also pins one effect worker and leaves its outbox row running. `EffectWorker.ts:216-238` and `RuntimeRequestService.ts:112-138` only translate errors; neither adds cancellation or a deadline. The worker races execution only against the outbox's explicit cancellation signal (`EffectWorker.ts:485-502`). `EffectOutbox.ts:278-303` claims pending rows only and blocks later non-title effects in the same thread while this row remains running. Lease expiry does not start an automatic duplicate attempt. One hung reply therefore blocks that thread's effect lane until explicit cancellation or process restart; four distinct hung replies can occupy the default four-worker pool.
50
51Current main addressed both reply types in [#9653](https://github.com/pingdotgg/t3code/pull/9653): it passes the SDK signal, applies a 10-second timeout, and only then resolves the local pending request and emits its terminal event (`OpenCodeAdapter.ts:3611-3715`). This is a missing port.
52
53### PROV-CLAUDE-01: P1, new Claude dead-turn terminal reasons are reported as successful completion
54
55The branch already depends on Claude Agent SDK `0.3.260`, but V2's result classifier predates that SDK's structured dead-turn results. `ClaudeAdapterV2.ts:1991-2025` returns `completed` for every `subtype: "success"`, `is_error: false` result, regardless of `terminal_reason`; `providerFailureFromResult` likewise returns no failure at `2045-2065`. The result handler commits those values at `4769-4788`.
56
57Trigger: Claude exhausts retries or otherwise ends with a structured failure such as `terminal_reason: "api_error"`, `"blocking_limit"`, `"rapid_refill_breaker"`, `"prompt_too_long"`, `"image_error"`, `"model_error"`, `"malformed_tool_use_exhausted"`, `"budget_exhausted"`, `"structured_output_retry_exhausted"`, `"tool_deferred_unavailable"`, or `"turn_setup_failed"`, while the SDK reports a success subtype and no ordinary error.
58
59Consequence: a turn that produced no successful answer is projected as completed and has no provider failure for the UI to explain.
60
61Current main added the structured classification and user-facing messages in [#9135](https://github.com/pingdotgg/t3code/pull/9135) (`ClaudeAdapter.ts:438-478, 1510-1548`), including a regression test for the observed `success` / `is_error: false` / `terminal_reason: "api_error"` shape and exhaustive tests for the dead-turn set. This is a missing port.
62
63`provider-adapter-regression-probes.test.ts` confirms the behavior through the actual V2 adapter. It starts a mocked Claude query session, sends the observed `api_error` result shape through the adapter's message stream, and receives `turn.terminal` with `status: "completed"` and `failure: null`. The probe passed as a reproduction in 25 ms.
64
65### PROV-OPENCODE-03: P2, OpenCode per-turn token telemetry is always unavailable
66
67`OpenCodeAdapterV2.ts:1029-1051` emits terminal provider-turn updates without `turnTokenUsage`. Its part switch at `2310-2340` handles text, reasoning, and tools, but ignores `step-finish`, the OpenCode event carrying token counts. `ProviderEventIngestor.ts:88-114` therefore emits `usageStatus: "unavailable"` for every OpenCode turn.
68
69Consequence: V2 OpenCode terminal provider-turn telemetry and its analytics properties cannot report per-turn input, cache, output, or reasoning token counts. This does not prove increased spend and does not establish that a separate transcript-backed usage dashboard is missing data.
70
71Current main added ownership-aware `step-finish` accumulation and complete/partial/unavailable terminal usage in [#9132](https://github.com/pingdotgg/t3code/pull/9132) (`OpenCodeAdapter.ts:403-439, 2409-2429`). Codex and Claude V2 already populate the same provider-turn field. This is a missing port.
72
73## Feature parity matrix
74
75| Provider | Registry, discovery, auth | Native session/history | Approvals and questions | Stop and release | Per-turn usage | Metadata generation boundary | Assessment |
76| ------------ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------- |
77| Codex | Registered; unchecked state remains unavailable until probed; auth/settings hydration retained | Strong resume, fork/from-turn, rollback, subagent fork | Command/read/change/patch approvals and structured questions | Interrupt and scoped cleanup covered by focused tests | Complete/partial telemetry implemented | Existing constrained helper behavior retained | No committed regression found; dirty capability change described below |
78| Claude | Registered with managed health/auth | Resume, fork/from-turn, rollback; no subagent fork advertised | Command/read/change approvals and structured questions | Interrupt/query teardown covered | Complete/partial telemetry implemented | Existing behavior unchanged from main | F11 and PROV-CLAUDE-01 |
79| Cursor | Registered; API-key auth path retained | Resume by native session id; fork and rollback explicitly unsupported | No approvals or structured questions are advertised; runtime access modes are applied at launch | Interrupt-restart behavior advertised and covered | Unavailable by protocol/design | Unrestricted SDK agent at project cwd | F08; unsupported capabilities are explicit, not hidden parity claims |
80| OpenCode | Registered; local/external settings and password path retained | Resume, fork/from-turn, rollback, child-session projection | All approval kinds and structured questions | Root abort is bounded; descendant Stop failures and external release are defective | Missing despite native `step-finish` data | Text helper denies tools; no new boundary issue found | F06, F07, PROV-OPENCODE-01/02/03 |
81| Grok | Registered ACP specialization; auth/settings retained | ACP load snapshot supported; native fork explicitly unavailable | ACP permission/questions supported, including Grok question extension | ACP interrupt and hard teardown paths covered | Unavailable by protocol/design | Existing Grok helper behavior matches main | No concrete regression found |
82| Antigravity | Registered; Google-profile auth coordination retained | ACP capabilities are negotiated; resume is preferred, base fork/rollback remain unavailable unless runtime advertises support | ACP permissions/questions supported | ACP interrupt and scoped process teardown covered | Unavailable by protocol/design | Isolated temporary cwd, no injected tools, rejects tool work | No concrete regression found |
83| ACP Registry | Registered as a built-in; instance-specific command/auth discovery retained | Load/fork/model/MCP capabilities are negotiated from the agent; no rollback | ACP permissions/questions supported; request IDs are weak/live-only | Explicit interrupt and hard teardown propagate relevant failures | Unavailable unless a future protocol field is added | Text generation is intentionally unsupported | No concrete regression found |
84
85The built-in list at `provider/builtInDrivers.ts:52-60` contains Codex, Claude, Cursor, Grok, OpenCode, Antigravity, and ACP Registry. The seemingly incomplete older adapter-driver helper is not used by production registration and is not a finding.
86
87## Main-since-prior and final-eight review
88
89The main changes that materially affect this domain since the prior audit are the OpenCode transport/request fixes in [#9653](https://github.com/pingdotgg/t3code/pull/9653), OpenCode token accumulation in [#9132](https://github.com/pingdotgg/t3code/pull/9132), Claude terminal classification in [#9135](https://github.com/pingdotgg/t3code/pull/9135), Claude attachment ordering in [#9122](https://github.com/pingdotgg/t3code/pull/9122), provider image-embedding instructions in [#9597](https://github.com/pingdotgg/t3code/pull/9597), OpenCode access rules in [#9282](https://github.com/pingdotgg/t3code/pull/9282), OpenCode workspace skills in [#9585](https://github.com/pingdotgg/t3code/pull/9585), and Codex async questions in [#9512](https://github.com/pingdotgg/t3code/pull/9512). Except for the four new findings above, the inspected V2 equivalents of those provider fixes are present.
90
91Mobile's cwd-specific provider catalog refresh from [#9180](https://github.com/pingdotgg/t3code/pull/9180) is present at `use-composer-command-menu.ts:188-244`; the prior broad V02 “outside web” validation gap should not be carried forward. I did not establish a narrower execution-only stale-catalog failure. Project auto-pull is also retained through `VcsStatusBroadcaster` policy and startup `autoPullProjects`; no missing provider launch hook was found.
92
93The final eight commits between branch HEAD and frozen main are [#9740](https://github.com/pingdotgg/t3code/pull/9740), [#9744](https://github.com/pingdotgg/t3code/pull/9744), [#9627](https://github.com/pingdotgg/t3code/pull/9627), [#9748](https://github.com/pingdotgg/t3code/pull/9748), [#9747](https://github.com/pingdotgg/t3code/pull/9747), [#9749](https://github.com/pingdotgg/t3code/pull/9749), [#9743](https://github.com/pingdotgg/t3code/pull/9743), and [#9739](https://github.com/pingdotgg/t3code/pull/9739). None changes a provider registry, adapter, provider text-generation helper, or provider contract in this bounded scope. The branch's lag behind those commits is not itself a provider regression.
94
95## Dirty overlay
96
97The only provider-material frozen overlay change is local-only: `CodexAdapterV2` adds `optOutNotificationMethods: ["turn/diff/updated"]`, with the matching fixture/test update. It asks Codex app-server not to send a notification that T3 already presents through its own diff flow. I found no new regression in that local change. The focused Codex test passed against the live overlay.
98
99The frozen `Orchestrator.ts.snapshot` has no content difference from committed HEAD for this domain. Other recorded session-turn-control/fixture changes are interface or settlement work outside a concrete provider regression. No product source or existing test was modified by this audit.
100
101## Tests and limitations
102
103Focused live-worktree run: 9 files, 215 tests passed in 2.17 seconds. The exact target and result are in `providers-tests.log`. The selection covered all seven V2 adapter suites plus provider auth and instance-registry live tests.
104
105Audit-only behavior probe: 1 file, 1 test passed in 632 ms total, with 25 ms spent in the test. The exact command and result are in `provider-adapter-regression-probes.log`. It imported and exercised the real `makeClaudeAdapterV2` implementation with an in-process query runner. The probe stays in this audit directory and does not alter product tests.
106
107These are mock/in-process suites. The new probe covers the observed Claude `api_error` result shape but not every new terminal reason. No test here exercises graceful OpenCode SSE EOF, a never-resolving OpenCode reply promise, OpenCode `step-finish` token accumulation, real Cursor metadata filesystem behavior, or Claude Read image presentation. Their passing result therefore does not contradict the remaining source-path findings.
108
109No provider or server was launched, no browser/simulator was used, no live T3 userdata was touched, no network was used, and no repo-wide check ran. The copied audit tests now use `.snapshot` suffixes; the earlier Vitest substring import failure caused by an unsuffixed frozen copy was an audit-artifact issue, not a product regression.
audits/orchestrator-v2/2026-09-04/recent-branch-work.txt+24−0
@@ -0,0 +1,24 @@
18af5734365f7c45bc08b57066dbae42f9f7d4235 fix: reconcile main updates with orchestration v2
2d98f1d5e38fb7dac334193d1cd7e103997b486e8 fix(server): reduce v2 recovery and runtime resource usage
396aff3564b3c2a9a543fed654388d2741fb6de9f fix: reconcile main's round-20 features after the rebase
44f924bfd310d5d673f038ed9ce3be3fe50fba959 fix(web): realign the composer and timeline with main
54199e9db5169f6a88a04457a1bd6224deaf2853e fix(web): right-align the stash shoulder tab again
6dc4cd901af59e732c06ff60bfea9d8c730f2146e fix: reconcile main's round-19 features after the rebase
7702ef5e5851264ea231c9cb4ecfd0b5f9c016042 feat(server): evaluate automatic thread settlement in the v2 orchestrator
8ddf58d90d47e2b6aaefc61bc15e6c3028b61c3e0 fix: reconcile main's round-18 features after the rebase
938f3e4d0e02cd58f79fa7ebe37250ee93c973f60 feat(web): port working and thinking timeline rows to orchestration v2
1027ef79dc301dc4400e3fafe834d514fa3b8de942 fix: reconcile main's round-17 features after the rebase
1142dee149f7861f9d84650c04e047f5fd48b3466b fix(mobile): keep scroll bounds current after animations
128b2f951e68a7f5effa16201263e4b47cbf517ad0 fix(chat): remove added tool summary status counts
136bc5b0f798228f387e9e42cabe51ae3d99a1cfa1 feat(mobile): port chat summaries and transitions to orchestration v2
14cafacd53b80eb399df5ad0cac37d9f6777d46e80 feat(web): summarize T3 orchestration actions
155145881d4c23094f7ca14deea1ee0cc8c2603219 fix(web): keep composer shortcut tooltip stable on Mod
16f8ed1f080ddec3c13eed613927178b0614cbf696 fix(web): match composer actions to draft and modifier state
175d5f370ad678f933e66b215eaef856656a464b9f fix(web): keep queued messages in place while editing
181a56fa6768fdd7ef063e291bcfc8900f07926995 fix(web): keep queued message editing inside the queue panel
19b952c574d5350c880f1f2f805d39839baeae3845 refactor(web): use shared banner rows for queued messages
201a600882893faf73f596d57ddba72a5b239482b2 fix(web): keep stash separate from the composer activity column
21f21af84a704c3435c461a199468f7ce56232741f fix(web): share the outline for joined composer tabs
2261e3568dac81141fa3d5e7c90a78eb4cb485207c fix(web): align queue headers and prevent stash overlap
237efa4e84b1a52583fcc3249293e92e94d0fadab4 fix(web): port composer activity and grouping to orchestration v2
2493620dfab0f877d204b97c5b5c3d9ea5352f080e fix(orchestration): select visible history before limiting SQL
audits/orchestrator-v2/2026-09-04/recent-review-comments.json+1−0
@@ -0,0 +1 @@
1[]
audits/orchestrator-v2/2026-09-04/references.json+11−0
@@ -0,0 +1,11 @@
1{
2 "capturedAt": "2026-09-04T19:00:02.808291+00:00",
3 "branch": "t3code/codex-turn-mapping",
4 "head": "8af5734365f7c45bc08b57066dbae42f9f7d4235",
5 "main": "d7cf8aaa8d4fbcbdd523b4f4bc86fda5c47b4a70",
6 "mergeBase": "c8f77e0d441264efb0acfac312e852c81ae3da83",
7 "previousAuditHead": "d2f1f511f4cc833bc930d6c355cd0f9b61e835a0",
8 "previousAuditMain": "57a66608b918d673eeec7e6c94ea5906b756fcd0",
9 "dirtyFiles": 56,
10 "comparisonCounts": "8\t333"
11}
audits/orchestrator-v2/2026-09-04/worktree-drift.diff+561−0
@@ -0,0 +1,561 @@
1--- initial/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts
2+++ current/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts
3@@ -34,6 +34,7 @@
4 const old = DateTime.subtract(now, { days: 10 });
5 const providerInstanceId = ProviderInstanceId.make("codex");
6 const modelSelection = { instanceId: providerInstanceId, model: "test-model" };
7+const encodeNumbers = Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.Number)));
8
9 const createThread = Effect.fn(function* (
10 name: string,
11@@ -314,12 +315,12 @@
12 yield* sql`INSERT INTO orchestration_v2_projection_turn_items
13 SELECT 'history:' || value, i.thread_id, i.run_id, i.node_id, i.provider_thread_id, i.provider_turn_id,
14 i.parent_item_id, i.ordinal + value, i.type, i.status, i.updated_at, i.payload_json
15- FROM orchestration_v2_projection_turn_items i, json_each(${Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.Number)))(Array.from({ length: 2000 }, (_, i) => i + 1))})
16+ FROM orchestration_v2_projection_turn_items i, json_each(${encodeNumbers(Array.from({ length: 2000 }, (_, i) => i + 1))})
17 WHERE i.thread_id = ${threadId}`;
18- const queries: Array<string> = [];
19+ const queries: Array<readonly [string, ReadonlyArray<unknown>]> = [];
20 const record: Statement.Transformer = (statement) =>
21 Effect.sync(() => {
22- queries.push(statement.compile()[0]);
23+ queries.push(statement.compile());
24 return statement;
25 });
26 const candidates = yield* store
27@@ -329,19 +330,18 @@
28 candidates.map((thread) => thread.id),
29 [threadId],
30 );
31- assert.isTrue(queries.every((query) => !query.includes("COUNT(")));
32- const pendingQuery = queries.find((query) =>
33+ assert.isTrue(queries.every(([query]) => !query.includes("COUNT(")));
34+ const pendingQuery = queries.find(([query]) =>
35 query.includes("FROM orchestration_v2_projection_turn_items"),
36 );
37 assert.isDefined(pendingQuery);
38 const metadata = yield* store.getThread(threadId);
39 assert.equal(metadata.id, threadId);
40 assert.equal((yield* store.getThreadProjection(threadId).pipe(Effect.exit))._tag, "Failure");
41- const plan = yield* sql<{ readonly detail: string }>`EXPLAIN QUERY PLAN
42- SELECT i.payload_json FROM orchestration_v2_projection_turn_items i
43- WHERE i.thread_id = ${threadId}
44- AND i.type IN ('command_execution', 'dynamic_tool', 'subagent')
45- AND i.status NOT IN ('completed', 'interrupted', 'failed', 'cancelled')`;
46+ const plan = yield* sql.unsafe<{ readonly detail: string }>(
47+ `EXPLAIN QUERY PLAN ${pendingQuery![0]}`,
48+ pendingQuery![1],
49+ );
50 assert.isTrue(plan.some((row) => row.detail.includes("turn_items_shell_pending_idx")));
51 const countsPlan = yield* sql<{ readonly detail: string }>`EXPLAIN QUERY PLAN
52 SELECT thread_id, run_id, COUNT(*) FROM orchestration_v2_projection_turn_items
53--- initial/apps/server/src/relay/AgentAwarenessRelay.test.ts
54+++ current/apps/server/src/relay/AgentAwarenessRelay.test.ts
55@@ -19,7 +19,7 @@
56 import * as TestClock from "effect/testing/TestClock";
57 import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
58
59-import { ServerSecretStore } from "../auth/ServerSecretStore.ts";
60+import { SecretStoreReadError, ServerSecretStore } from "../auth/ServerSecretStore.ts";
61 import {
62 PUBLISH_AGENT_ACTIVITY_SECRET,
63 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
64@@ -89,7 +89,12 @@
65 const decodePublishPayload = Schema.decodeUnknownSync(Schema.fromJsonString(PublishPayload));
66 const unused = () => Effect.die("Unexpected test dependency call");
67
68-const makeTestRelay = Effect.fnUntraced(function* () {
69+const makeTestRelay = Effect.fnUntraced(function* (
70+ options: {
71+ readonly respond?: (attempt: number) => Response;
72+ readonly failSecretRead?: (name: string) => boolean;
73+ } = {},
74+) {
75 const values = new Map<string, Uint8Array>([
76 [PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("true")],
77 [RELAY_URL_SECRET, new TextEncoder().encode("https://relay.example.test")],
78@@ -99,9 +104,14 @@
79 const secretReads: string[] = [];
80 const secrets = ServerSecretStore.of({
81 get: (name) =>
82- Effect.sync(() => {
83+ Effect.suspend(() => {
84 secretReads.push(name);
85- return Option.fromUndefinedOr(values.get(name));
86+ if (options.failSecretRead?.(name)) {
87+ return Effect.fail(
88+ new SecretStoreReadError({ resource: name, cause: "temporary read failure" }),
89+ );
90+ }
91+ return Effect.succeed(Option.fromUndefinedOr(values.get(name)));
92 }),
93 create: (name, value) => Effect.sync(() => void values.set(name, value)),
94 set: (name, value) => Effect.sync(() => void values.set(name, value)),
95@@ -152,7 +162,9 @@
96 authorization: new Headers(init?.headers).get("authorization"),
97 state: payload.state,
98 });
99- return Promise.resolve(Response.json({ ok: true, deliveries: [] }));
100+ return Promise.resolve(
101+ options.respond?.(publications.length) ?? Response.json({ ok: true, deliveries: [] }),
102+ );
103 },
104 { preconnect: () => {} },
105 );
106@@ -318,6 +330,207 @@
107 }),
108 );
109
110+ it.effect("retries a failed final state without another thread event", () =>
111+ Effect.gen(function* () {
112+ const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
113+ respond: (attempt) =>
114+ attempt === 2
115+ ? new Response("relay unavailable", { status: 503 })
116+ : Response.json({ ok: true, deliveries: [] }),
117+ });
118+ yield* relay.publishThread(THREAD_ID);
119+ yield* Ref.set(currentShell, shell({ status: "completed" }));
120+ yield* relay.publishThread(THREAD_ID);
121+ assert.equal(publications.length, 2);
122+ yield* TestClock.adjust("1 second");
123+ yield* relay.drain;
124+ assert.equal(publications.length, 3);
125+ assert.equal(publications[2]?.state?.phase, "completed");
126+ yield* TestClock.adjust("1 minute");
127+ yield* relay.drain;
128+ assert.equal(shellReads.length, 3);
129+ }),
130+ );
131+
132+ it.effect("retries the latest shell and credentials with first-completion confirmation", () =>
133+ Effect.gen(function* () {
134+ const { relay, secrets, currentShell, publications } = yield* makeTestRelay({
135+ respond: (attempt) =>
136+ attempt === 1
137+ ? new Response("relay unavailable", { status: 503 })
138+ : Response.json({ ok: true, deliveries: [] }),
139+ });
140+ yield* relay.publishThread(THREAD_ID);
141+ yield* Ref.set(currentShell, shell({ status: "completed", title: "Final title" }));
142+ yield* secrets.set(
143+ RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
144+ new TextEncoder().encode("credential-2"),
145+ );
146+ yield* TestClock.adjust("1 second");
147+ yield* relay.drain;
148+ assert.equal(publications.length, 1);
149+ yield* TestClock.adjust("5 seconds");
150+ yield* relay.drain;
151+ assert.equal(publications.length, 2);
152+ assert.equal(publications[1]?.authorization, "Bearer credential-2");
153+ assert.equal(publications[1]?.state?.phase, "completed");
154+ assert.equal(publications[1]?.state?.threadTitle, "Final title");
155+ }),
156+ );
157+
158+ it.effect("cancels a stale retry when a newer update publishes successfully", () =>
159+ Effect.gen(function* () {
160+ const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
161+ respond: (attempt) =>
162+ attempt === 1
163+ ? new Response("relay unavailable", { status: 503 })
164+ : Response.json({ ok: true, deliveries: [] }),
165+ });
166+ yield* relay.publishThread(THREAD_ID);
167+ yield* Ref.set(currentShell, shell({ title: "New title" }));
168+ yield* relay.publishThread(THREAD_ID);
169+ assert.equal(publications.length, 2);
170+ assert.equal(publications[1]?.state?.threadTitle, "New title");
171+ yield* TestClock.adjust("1 minute");
172+ yield* relay.drain;
173+ assert.equal(shellReads.length, 2);
174+ }),
175+ );
176+
177+ it.effect.each(["disable", "unlink"] as const)(
178+ "stops retries before shell reads after %s",
179+ (change) =>
180+ Effect.gen(function* () {
181+ const { relay, secrets, shellReads, publications } = yield* makeTestRelay({
182+ respond: () => new Response("relay unavailable", { status: 503 }),
183+ });
184+ yield* relay.publishThread(THREAD_ID);
185+ if (change === "disable") {
186+ yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
187+ } else {
188+ yield* secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET);
189+ }
190+ yield* TestClock.adjust("1 second");
191+ yield* relay.drain;
192+ yield* TestClock.adjust("1 minute");
193+ yield* relay.drain;
194+ assert.equal(publications.length, 1);
195+ assert.equal(shellReads.length, 1);
196+ }),
197+ );
198+
199+ it.effect("recovers from a transient configuration read failure", () =>
200+ Effect.gen(function* () {
201+ let failRead = false;
202+ const { relay, shellReads, publications } = yield* makeTestRelay({
203+ failSecretRead: (name) => failRead && name === PUBLISH_AGENT_ACTIVITY_SECRET,
204+ });
205+ failRead = true;
206+ yield* relay.publishThread(THREAD_ID);
207+ assert.equal(shellReads.length, 0);
208+ failRead = false;
209+ yield* TestClock.adjust("1 second");
210+ yield* relay.drain;
211+ assert.equal(publications.length, 1);
212+ assert.equal(publications[0]?.state?.phase, "running");
213+ }),
214+ );
215+
216+ it.effect("bounds retry attempts and resets the budget for a newer update", () =>
217+ Effect.gen(function* () {
218+ const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
219+ respond: () => new Response("relay unavailable", { status: 503 }),
220+ });
221+ yield* relay.publishThread(THREAD_ID);
222+ let attempts = 1;
223+ for (const delay of [1_000, 2_000, 4_000, 8_000, 16_000]) {
224+ yield* TestClock.adjust(delay - 1);
225+ yield* relay.drain;
226+ assert.equal(publications.length, attempts);
227+ yield* TestClock.adjust(1);
228+ yield* relay.drain;
229+ attempts += 1;
230+ assert.equal(publications.length, attempts);
231+ }
232+ yield* TestClock.adjust("1 minute");
233+ yield* relay.drain;
234+ assert.equal(publications.length, 6);
235+ assert.equal(shellReads.length, 6);
236+ yield* Ref.set(currentShell, shell({ title: "New update" }));
237+ yield* relay.publishThread(THREAD_ID);
238+ yield* TestClock.adjust("1 second");
239+ yield* relay.drain;
240+ assert.equal(publications.length, 8);
241+ assert.equal(publications[7]?.state?.threadTitle, "New update");
242+ }),
243+ );
244+
245+ it.effect("retries a confirmed completion without restarting its confirmation window", () =>
246+ Effect.gen(function* () {
247+ const { relay, currentShell, publications } = yield* makeTestRelay({
248+ respond: (attempt) =>
249+ attempt <= 2
250+ ? new Response("relay unavailable", { status: 503 })
251+ : Response.json({ ok: true, deliveries: [] }),
252+ });
253+ yield* Ref.set(currentShell, shell({ status: "completed" }));
254+ yield* relay.publishThread(THREAD_ID);
255+ yield* TestClock.adjust("5 seconds");
256+ yield* relay.drain;
257+ assert.equal(publications.length, 1);
258+ yield* TestClock.adjust("1 second");
259+ yield* relay.drain;
260+ assert.equal(publications.length, 2);
261+ yield* TestClock.adjust("2 seconds");
262+ yield* relay.drain;
263+ assert.equal(publications.length, 3);
264+ assert.isTrue(publications.every((publication) => publication.state?.phase === "completed"));
265+ yield* TestClock.adjust("1 minute");
266+ yield* relay.drain;
267+ assert.equal(publications.length, 3);
268+ }),
269+ );
270+
271+ it.effect("does not reset an exhausted retry budget for old confirmation timers", () =>
272+ Effect.gen(function* () {
273+ let unavailable = false;
274+ const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
275+ respond: () =>
276+ unavailable
277+ ? new Response("relay unavailable", { status: 503 })
278+ : Response.json({ ok: true, deliveries: [] }),
279+ });
280+ yield* relay.publishThread(THREAD_ID);
281+ // Leave six obsolete confirmations at 5,000 through 5,005 milliseconds.
282+ for (let i = 0; i < 6; i++) {
283+ yield* Ref.set(currentShell, null);
284+ yield* relay.publishThread(THREAD_ID);
285+ yield* Ref.set(currentShell, shell());
286+ yield* relay.publishThread(THREAD_ID);
287+ yield* TestClock.adjust(1);
288+ }
289+ unavailable = true;
290+ yield* Ref.set(currentShell, shell({ status: "failed" }));
291+ yield* relay.publishThread(THREAD_ID);
292+ yield* TestClock.adjust(1_000);
293+ yield* relay.drain;
294+ yield* TestClock.adjust(2_000);
295+ yield* relay.drain;
296+ yield* TestClock.adjust(1_994);
297+ yield* relay.drain;
298+ for (let i = 0; i < 5; i++) {
299+ yield* TestClock.adjust(1);
300+ yield* relay.drain;
301+ }
302+ const attempts = publications.length;
303+ const reads = shellReads.length;
304+ yield* TestClock.adjust("1 minute");
305+ yield* relay.drain;
306+ assert.equal(publications.length, attempts);
307+ assert.equal(shellReads.length, reads);
308+ }),
309+ );
310+
311 it.effect("confirms tombstones and cancels stale confirmations when activity recovers", () =>
312 Effect.gen(function* () {
313 const { relay, currentShell, publications } = yield* makeTestRelay();
314--- initial/apps/server/src/relay/AgentAwarenessRelay.ts
315+++ current/apps/server/src/relay/AgentAwarenessRelay.ts
316@@ -23,6 +23,7 @@
317 import * as Crypto from "effect/Crypto";
318 import * as DateTime from "effect/DateTime";
319 import * as Effect from "effect/Effect";
320+import * as Fiber from "effect/Fiber";
321 import * as Layer from "effect/Layer";
322 import * as Option from "effect/Option";
323 import * as Ref from "effect/Ref";
324@@ -152,6 +153,7 @@
325
326 const RELAY_AGENT_ACTIVITY_DETAIL_MAX_LENGTH = 160;
327 const REDACTED_RELAY_AGENT_FAILURE_DETAIL = "The agent run failed.";
328+const RELAY_AGENT_ACTIVITY_RETRY_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 16_000] as const;
329
330 export function sanitizeRelayAgentActivityState(
331 state: RelayAgentActivityState | null,
332@@ -330,6 +332,7 @@
333 const threads = yield* ThreadManagement.ThreadManagementService;
334 const projects = yield* ProjectService.ProjectService;
335 const crypto = yield* Crypto.Crypto;
336+ const scope = yield* Effect.scope;
337 const cloudLinkKeyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
338 const activeSnapshotPublishedRef = yield* Ref.make(false);
339 const publishedStateByThreadRef = yield* Ref.make(new Map<ThreadId, string>());
340@@ -377,6 +380,19 @@
341 // clears the deadline. Assigned after the worker exists.
342 const publishConfirmDeadlines = new Map<ThreadId, number>();
343 let schedulePublishConfirm: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;
344+ const publishRetries = new Map<
345+ ThreadId,
346+ { readonly attempts: number; timer: Fiber.Fiber<void> | undefined }
347+ >();
348+ const cancelPublishRetry = Effect.fnUntraced(function* (threadId: ThreadId) {
349+ const retry = publishRetries.get(threadId);
350+ publishRetries.delete(threadId);
351+ if (retry?.timer !== undefined) yield* Fiber.interrupt(retry.timer);
352+ });
353+ const cancelPublishRetries = Effect.suspend(() =>
354+ Effect.forEach([...publishRetries.keys()], cancelPublishRetry, { discard: true }),
355+ );
356+ let schedulePublishRetry: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;
357 const resetPublishedConnection = Effect.gen(function* () {
358 publishedRelayConfig = undefined;
359 cachedRelayClient = undefined;
360@@ -385,18 +401,18 @@
361 });
362
363 const publishThreadUnsafe = Effect.fn("publishThreadUnsafe")(function* (threadId: ThreadId) {
364- const publishAgentActivity = yield* readPublishAgentActivityEnabled.pipe(
365- Effect.orElseSucceed(() => false),
366- );
367+ const publishAgentActivity = yield* readPublishAgentActivityEnabled;
368 if (!publishAgentActivity) {
369+ yield* cancelPublishRetries;
370 yield* resetPublishedConnection;
371 yield* Effect.logDebug("agent activity publish skipped; publication disabled", {
372 threadId,
373 });
374 return;
375 }
376- const relayConfig = yield* readRelayConfig.pipe(Effect.orElseSucceed(() => null));
377+ const relayConfig = yield* readRelayConfig;
378 if (!relayConfig) {
379+ yield* cancelPublishRetries;
380 yield* resetPublishedConnection;
381 yield* Effect.logDebug("agent activity publish skipped; relay link credentials unavailable", {
382 threadId,
383@@ -525,7 +541,6 @@
384 if (nowMs < deadline) {
385 return;
386 }
387- publishConfirmDeadlines.delete(threadId);
388 yield* Effect.logInfo("agent activity deferred publish confirmed", {
389 environmentId,
390 threadId,
391@@ -555,6 +570,7 @@
392 state: snapshot.state,
393 reason: snapshot.reason,
394 });
395+ publishConfirmDeadlines.delete(threadId);
396 yield* Ref.update(publishedStateByThreadRef, (publishedStates) => {
397 const nextPublishedStates = new Map(publishedStates);
398 nextPublishedStates.set(threadId, publishIdentity);
399@@ -562,21 +578,63 @@
400 });
401 });
402
403- const processThreadPublish = (threadId: ThreadId) =>
404- publishThreadUnsafe(threadId).pipe(
405+ const processThreadPublish = Effect.fnUntraced(function* (threadId: ThreadId) {
406+ const retry = publishRetries.get(threadId);
407+ if (retry?.timer !== undefined) {
408+ const timer = retry.timer;
409+ retry.timer = undefined;
410+ yield* Fiber.interrupt(timer);
411+ }
412+ yield* publishThreadUnsafe(threadId).pipe(
413+ Effect.tap(() => cancelPublishRetry(threadId)),
414 Effect.catchCause((cause) => {
415+ if (Cause.hasInterruptsOnly(cause)) return Effect.void;
416 return Effect.logWarning("agent activity publish failed", {
417 threadId,
418 cause: Cause.pretty(cause),
419- });
420+ }).pipe(Effect.andThen(schedulePublishRetry(threadId)));
421 }),
422 Effect.withSpan("AgentAwarenessRelay.publishThread"),
423 withRelayClientTracing,
424 );
425+ });
426
427 const worker = yield* makeAgentAwarenessPublishWorker(processThreadPublish);
428+ const enqueueThreadPublish = (threadId: ThreadId) =>
429+ cancelPublishRetry(threadId).pipe(Effect.andThen(worker.enqueue(threadId)));
430 const publishThread: AgentAwarenessRelay["Service"]["publishThread"] = (threadId) =>
431- worker.enqueue(threadId).pipe(Effect.andThen(worker.drain));
432+ enqueueThreadPublish(threadId).pipe(Effect.andThen(worker.drain));
433+
434+ schedulePublishRetry = Effect.fnUntraced(function* (threadId: ThreadId) {
435+ const attempts = publishRetries.get(threadId)?.attempts ?? 0;
436+ const delayMs = RELAY_AGENT_ACTIVITY_RETRY_DELAYS_MS[attempts];
437+ if (delayMs === undefined) {
438+ // Keep the exhausted budget until fresh activity or a successful publish
439+ // clears it; an old confirmation timer must not start another retry series.
440+ yield* Effect.logWarning("agent activity publish retry budget exhausted", { threadId });
441+ return;
442+ }
443+ const retry = {
444+ attempts: attempts + 1,
445+ timer: undefined as Fiber.Fiber<void> | undefined,
446+ };
447+ publishRetries.set(threadId, retry);
448+ const timer = yield* Effect.sleep(delayMs).pipe(
449+ Effect.andThen(
450+ Effect.suspend(() => {
451+ if (publishRetries.get(threadId) !== retry) return Effect.void;
452+ retry.timer = undefined;
453+ return worker.enqueue(threadId);
454+ }),
455+ ),
456+ Effect.forkIn(scope),
457+ );
458+ if (publishRetries.get(threadId) !== retry) {
459+ yield* Fiber.interrupt(timer);
460+ } else {
461+ retry.timer = timer;
462+ }
463+ });
464
465 const publishActiveThreadsUnsafe = Effect.gen(function* () {
466 const publishAgentActivity = yield* readPublishAgentActivityEnabled.pipe(
467@@ -608,7 +666,7 @@
468 yield* Effect.logInfo("publishing active agent activity snapshot", {
469 count: activeThreadIds.length,
470 });
471- yield* Effect.forEach(activeThreadIds, worker.enqueue, { discard: true });
472+ yield* Effect.forEach(activeThreadIds, enqueueThreadPublish, { discard: true });
473 yield* worker.drain;
474 return true;
475 });
476@@ -631,7 +689,6 @@
477 }
478 });
479
480- const scope = yield* Effect.scope;
481 schedulePublishConfirm = (threadId) =>
482 Effect.forkIn(
483 Effect.sleep("5 seconds").pipe(
484@@ -685,7 +742,7 @@
485 return Effect.logDebug("agent activity publishing queued thread publish", {
486 eventType: event.type,
487 threadId,
488- }).pipe(Effect.andThen(worker.enqueue(threadId)));
489+ }).pipe(Effect.andThen(enqueueThreadPublish(threadId)));
490 }),
491 );
492 },
493--- initial/docs/internals/mobile-development.md
494+++ current/docs/internals/mobile-development.md
495@@ -48,3 +48,14 @@
496 `vp run dev:client:reset` from `apps/mobile`. pnpm gives patched packages new
497 filesystem paths, and cached transforms can otherwise retain references to the
498 previous package. Ordinary development starts should retain the transform cache.
499+
500+The expo-notifications patch protects `NotificationCenterManager`'s delegate and pending-response
501+arrays with a lock. React runtimes can register and remove delegates concurrently during reloads
502+or native scene startup. Delivery takes a snapshot under the lock and invokes delegates after
503+releasing it, allowing callbacks to register other delegates. Pending-response replay removes
504+only the responses it saw so a response arriving during a callback remains available.
505+
506+Changes to this native patch require a fresh dependency install, CocoaPods refresh, and an iOS
507+rebuild. `vp test run apps/mobile/scripts/notification-center-manager.test.ts` compiles the
508+installed dependency with a small native harness on macOS and runs concurrent registration and
509+delivery checks under ThreadSanitizer without launching a simulator.
510--- initial/docs/internals/performance-regressions.md
511+++ current/docs/internals/performance-regressions.md
512@@ -50,3 +50,37 @@
513
514 The focused regression coverage lives in `OrchestrationEventStore.sequence.test.ts`,
515 `ProjectionRecovery.test.ts`, and the provider runtime recovery tests under `apps/server/src`.
516+
517+## Background work and navigation
518+
519+The settlement worker reads active, unpinned threads without explicit settlement overrides from
520+`ProjectionStore.getSettlementCandidates`. It reads the latest run and user-message timestamps,
521+rejects active runs and pending requests, and checks background work using the same derivation as
522+the shell. It does not load archived histories, fork ancestry, transcript counts, or provider
523+sessions. The orchestrator still validates the current thread before applying settlement.
524+
525+Read receipts use `ProjectionStore.getThread` to load only canonical thread metadata. A visit
526+advances `lastVisitedAt` monotonically without changing `updatedAt` or decoding history. Shell
527+queries use a covering thread/run index for item counts and a partial index for nonterminal
528+background items, including idle items. Latest user-message reads use an index ordered by update
529+time.
530+
531+Command palette results keep live VCS and linked pull-request queries leased to the visible
532+viewport, including a small overscan region. Cached badges remain available while leases are
533+released. Local Git status reads skip divergence calculations; callers that need ahead/behind
534+counts keep the full status path. Cached fetch failures share the existing backoff and log only
535+the actual failed attempt. Unchanged desktop environment bootstrap reads retain their array
536+identity so polling does not invalidate subscribers.
537+
538+Relay awareness queues one pending publication per thread and retains a follow-up when the thread
539+changes during a send. Transcript deltas and tool output do not enqueue awareness work. Run,
540+request, and relevant thread metadata events do. Configuration is checked before shell reads, and
541+connection changes invalidate published identities so relinking can publish unchanged state.
542+Failed publications retry the latest state up to five times with exponential delays. A new
543+relevant event resets the budget; successful publication, disabling, or unlinking cancels retries.
544+After the budget is exhausted, publication waits for another relevant event instead of generating
545+continuous traffic during an outage.
546+
547+Focused coverage: `ProjectionSettlement.test.ts`, `ThreadSettlementService.test.ts`,
548+`runtimeLayer.test.ts`, `AgentAwarenessRelay.test.ts`, `GitVcsDriverCore.test.ts`,
549+`ThreadStatusIndicators.subscriptions.test.tsx`, and `desktopLocal.test.ts`.
550--- initial/docs/internals/providers.md
551+++ current/docs/internals/providers.md
552@@ -69,6 +69,9 @@
553
554 Codex thread resume requests exclude historical turns when only the thread identity and update time
555 are needed. The adapter decodes those metadata fields without loading a duplicate transcript.
556+Its initialization capabilities opt out of `turn/diff/updated`: T3 derives diffs from checkpoints
557+and does not consume the provider's cumulative diff notifications. The logger also filters those
558+notifications before traversal when an older provider still sends them.
559
560 ## Registry and routing
561
audits/orchestrator-v2/2026-09-04/worktree-drift/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts.snapshot+363−0
@@ -0,0 +1,363 @@
1import { assert, it } from "@effect/vitest";
2import {
3 EventId,
4 MessageId,
5 NodeId,
6 type OrchestrationV2AppThread,
7 type OrchestrationV2Run,
8 ProjectId,
9 ProviderDriverKind,
10 ProviderInstanceId,
11 ProviderThreadId,
12 RunId,
13 RuntimeRequestId,
14 ThreadId,
15 TurnItemId,
16} from "@t3tools/contracts";
17import * as DateTime from "effect/DateTime";
18import * as Effect from "effect/Effect";
19import * as Layer from "effect/Layer";
20import * as Schema from "effect/Schema";
21import * as SqlClient from "effect/unstable/sql/SqlClient";
22import * as Statement from "effect/unstable/sql/Statement";
23
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
25import {
26 ProjectionStoreV2,
27 layer as projectionStoreLayer,
28 layerMemory,
29} from "./ProjectionStore.ts";
30import { isAutoSettlementCandidate, resolveAutoSettlementAt } from "./ThreadSettlementService.ts";
31
32const SqlLayer = projectionStoreLayer.pipe(Layer.provideMerge(SqlitePersistenceMemory));
33const now = DateTime.makeUnsafe("2026-09-04T12:00:00Z");
34const old = DateTime.subtract(now, { days: 10 });
35const providerInstanceId = ProviderInstanceId.make("codex");
36const modelSelection = { instanceId: providerInstanceId, model: "test-model" };
37const encodeNumbers = Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.Number)));
38
39const createThread = Effect.fn(function* (
40 name: string,
41 overrides: Partial<OrchestrationV2AppThread> = {},
42) {
43 const store = yield* ProjectionStoreV2;
44 const threadId = ThreadId.make(`thread:settlement:${name}`);
45 const thread: OrchestrationV2AppThread = {
46 createdBy: "user",
47 creationSource: "web",
48 id: threadId,
49 projectId: ProjectId.make("project:settlement"),
50 title: name,
51 providerInstanceId,
52 modelSelection,
53 runtimeMode: "full-access",
54 interactionMode: "default",
55 branch: "feature",
56 worktreePath: null,
57 activeProviderThreadId: null,
58 lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId },
59 forkedFrom: null,
60 createdAt: old,
61 updatedAt: old,
62 archivedAt: null,
63 settledOverride: null,
64 settledAt: null,
65 lastVisitedAt: null,
66 deletedAt: null,
67 ...overrides,
68 };
69 yield* store.apply({
70 id: EventId.make(`event:${threadId}`),
71 type: "thread.created",
72 threadId,
73 occurredAt: old,
74 payload: thread,
75 });
76 return threadId;
77});
78
79const createRun = Effect.fn(function* (
80 threadId: ThreadId,
81 status: OrchestrationV2Run["status"] = "completed",
82 ordinal = 1,
83) {
84 const store = yield* ProjectionStoreV2;
85 const runId = RunId.make(`run:${threadId}:${ordinal}`);
86 yield* store.apply({
87 id: EventId.make(`event:${runId}`),
88 type: "run.created",
89 threadId,
90 occurredAt: old,
91 payload: {
92 id: runId,
93 threadId,
94 ordinal,
95 providerInstanceId,
96 modelSelection,
97 providerThreadId: null,
98 userMessageId: MessageId.make(`message:${runId}`),
99 rootNodeId: null,
100 activeAttemptId: null,
101 status,
102 requestedAt: old,
103 startedAt: status === "queued" ? null : old,
104 completedAt: ["completed", "rolled_back", "failed"].includes(status) ? old : null,
105 checkpointId: null,
106 contextHandoffId: null,
107 },
108 });
109 return runId;
110});
111
112const createItem = Effect.fn(function* (
113 threadId: ThreadId,
114 runId: RunId,
115 status: "idle" | "running" | "completed",
116 persistent = false,
117) {
118 const store = yield* ProjectionStoreV2;
119 yield* store.apply({
120 id: EventId.make(`event:item:${runId}`),
121 type: "turn-item.updated",
122 threadId,
123 occurredAt: old,
124 payload: {
125 id: TurnItemId.make(`item:${runId}`),
126 threadId,
127 runId,
128 nodeId: null,
129 providerThreadId: null,
130 providerTurnId: null,
131 nativeItemRef: null,
132 parentItemId: null,
133 ordinal: 1,
134 type: "dynamic_tool",
135 status,
136 title: null,
137 startedAt: old,
138 completedAt: status === "completed" ? old : null,
139 updatedAt: old,
140 toolName: "background",
141 input: persistent ? { persistent: true } : null,
142 output: null,
143 },
144 });
145});
146
147for (const [name, testLayer] of [
148 ["sql", SqlLayer],
149 ["memory", layerMemory],
150] as const) {
151 it.effect(
152 `${name}: discovers settlement work with the same activity and background semantics as the shell`,
153 () =>
154 Effect.gen(function* () {
155 const store = yield* ProjectionStoreV2;
156 const idle = yield* createThread("idle");
157 const completed = yield* createThread("completed");
158 yield* createRun(completed);
159 for (const [name, overrides] of [
160 ["archived", { archivedAt: old }],
161 ["deleted", { deletedAt: old }],
162 ["settled", { settledOverride: "settled" }],
163 ["unsettled", { settledOverride: "active" }],
164 ["pinned", { pinnedAt: old }],
165 ] satisfies ReadonlyArray<readonly [string, Partial<OrchestrationV2AppThread>]>) {
166 yield* createRun(yield* createThread(name, overrides));
167 }
168 for (const status of ["preparing", "starting", "running", "waiting"] as const) {
169 yield* createRun(yield* createThread(status), status);
170 }
171 const queued = yield* createThread("queued");
172 yield* createRun(queued, "queued");
173 const blocked = yield* createThread("blocked");
174 yield* store.apply({
175 id: EventId.make("event:settlement:request"),
176 type: "runtime-request.updated",
177 threadId: blocked,
178 occurredAt: old,
179 payload: {
180 id: RuntimeRequestId.make("request:settlement"),
181 nodeId: NodeId.make("node:settlement"),
182 providerTurnId: null,
183 nativeRequestRef: null,
184 kind: "user_input",
185 status: "pending",
186 responseCapability: { type: "not_resumable", reason: "Process stopped" },
187 createdAt: old,
188 resolvedAt: null,
189 },
190 });
191 const recent = yield* createThread("recent-message");
192 yield* createRun(recent);
193 yield* store.apply({
194 id: EventId.make("event:settlement:message"),
195 type: "message.updated",
196 threadId: recent,
197 occurredAt: now,
198 payload: {
199 createdBy: "user",
200 creationSource: "web",
201 id: MessageId.make("message:settlement:recent"),
202 threadId: recent,
203 runId: null,
204 nodeId: null,
205 role: "user",
206 text: "Next turn",
207 attachments: [],
208 streaming: false,
209 createdAt: now,
210 updatedAt: now,
211 },
212 });
213 yield* createRun(
214 yield* createThread("snoozed", {
215 snoozedUntil: DateTime.add(now, { days: 1 }),
216 snoozedAt: now,
217 }),
218 );
219 const woke = yield* createThread("woke", {
220 snoozedUntil: DateTime.add(now, { days: 1 }),
221 snoozedAt: DateTime.subtract(old, { days: 1 }),
222 });
223 yield* createRun(woke);
224 const background = yield* createThread("idle-background");
225 yield* createItem(background, yield* createRun(background), "idle");
226 const persistent = yield* createThread("persistent-monitor");
227 yield* createItem(persistent, yield* createRun(persistent), "running", true);
228 const rolledBack = yield* createThread("rolled-back-background");
229 yield* createItem(rolledBack, yield* createRun(rolledBack, "rolled_back"), "running");
230 yield* createRun(rolledBack, "completed", 2);
231 const roster = yield* createThread("provider-roster");
232 yield* createRun(roster);
233 yield* store.apply({
234 id: EventId.make("event:settlement:roster"),
235 type: "provider-thread.updated",
236 threadId: roster,
237 occurredAt: old,
238 payload: {
239 id: ProviderThreadId.make("provider-thread:settlement"),
240 appThreadId: roster,
241 ownerNodeId: null,
242 driver: ProviderDriverKind.make("codex"),
243 providerInstanceId,
244 providerSessionId: null,
245 nativeThreadRef: null,
246 nativeConversationHeadRef: null,
247 status: "idle",
248 firstRunOrdinal: null,
249 lastRunOrdinal: null,
250 handoffIds: [],
251 forkedFrom: null,
252 createdAt: old,
253 updatedAt: old,
254 pendingBackgroundTasks: [{ taskId: "running-task" }],
255 },
256 });
257 const candidates = yield* store.getSettlementCandidates();
258 const shell = yield* store.getShellSnapshot({ location: "active" });
259 const eligible = candidates.filter((thread) =>
260 isAutoSettlementCandidate(thread, DateTime.toEpochMillis(now)),
261 );
262 assert.deepEqual(
263 new Set(eligible.map((thread) => thread.id)),
264 new Set([idle, completed, queued, woke, persistent, rolledBack]),
265 );
266 assert.deepEqual(
267 new Set(eligible.map((thread) => thread.id)),
268 new Set(
269 shell.threads
270 .filter((thread) => isAutoSettlementCandidate(thread, DateTime.toEpochMillis(now)))
271 .map((thread) => thread.id),
272 ),
273 );
274 for (const candidate of candidates) {
275 const expected = shell.threads.find((thread) => thread.id === candidate.id)!;
276 assert.deepEqual(candidate.pendingBackgroundTasks, expected.pendingBackgroundTasks);
277 const settings = {
278 pullRequest: null,
279 nowMs: DateTime.toEpochMillis(now),
280 autoSettleAfterDays: 7,
281 autoSettleOnMerge: false,
282 };
283 assert.deepEqual(
284 resolveAutoSettlementAt({ ...settings, thread: candidate }),
285 resolveAutoSettlementAt({ ...settings, thread: expected }),
286 );
287 }
288 assert.equal(
289 DateTime.formatIso((yield* store.getThread(completed)).createdAt),
290 DateTime.formatIso(old),
291 );
292 assert.equal(
293 (yield* store.getThread(ThreadId.make("missing")).pipe(Effect.flip))._tag,
294 "ProjectionStoreThreadNotFoundError",
295 );
296 }).pipe(Effect.provide(testLayer)),
297 );
298}
299
300it.effect(
301 "reads settlement candidates and thread metadata without loading historical or archived payloads",
302 () =>
303 Effect.gen(function* () {
304 const store = yield* ProjectionStoreV2;
305 const sql = yield* SqlClient.SqlClient;
306 const source = yield* createThread("historical-source", { archivedAt: old });
307 const sourceRun = yield* createRun(source);
308 const threadId = yield* createThread("fork", {
309 forkedFrom: { type: "run", threadId: source, runId: sourceRun },
310 });
311 const runId = yield* createRun(threadId);
312 yield* createItem(threadId, runId, "completed");
313 yield* sql`UPDATE orchestration_v2_projection_turn_items SET payload_json = '{broken' WHERE thread_id = ${threadId}`;
314 yield* sql`UPDATE orchestration_v2_projection_runs SET payload_json = '{broken' WHERE thread_id = ${source}`;
315 yield* sql`INSERT INTO orchestration_v2_projection_turn_items
316 SELECT 'history:' || value, i.thread_id, i.run_id, i.node_id, i.provider_thread_id, i.provider_turn_id,
317 i.parent_item_id, i.ordinal + value, i.type, i.status, i.updated_at, i.payload_json
318 FROM orchestration_v2_projection_turn_items i, json_each(${encodeNumbers(Array.from({ length: 2000 }, (_, i) => i + 1))})
319 WHERE i.thread_id = ${threadId}`;
320 const queries: Array<readonly [string, ReadonlyArray<unknown>]> = [];
321 const record: Statement.Transformer = (statement) =>
322 Effect.sync(() => {
323 queries.push(statement.compile());
324 return statement;
325 });
326 const candidates = yield* store
327 .getSettlementCandidates()
328 .pipe(Effect.provideService(Statement.CurrentTransformer, record));
329 assert.deepEqual(
330 candidates.map((thread) => thread.id),
331 [threadId],
332 );
333 assert.isTrue(queries.every(([query]) => !query.includes("COUNT(")));
334 const pendingQuery = queries.find(([query]) =>
335 query.includes("FROM orchestration_v2_projection_turn_items"),
336 );
337 assert.isDefined(pendingQuery);
338 const metadata = yield* store.getThread(threadId);
339 assert.equal(metadata.id, threadId);
340 assert.equal((yield* store.getThreadProjection(threadId).pipe(Effect.exit))._tag, "Failure");
341 const plan = yield* sql.unsafe<{ readonly detail: string }>(
342 `EXPLAIN QUERY PLAN ${pendingQuery![0]}`,
343 pendingQuery![1],
344 );
345 assert.isTrue(plan.some((row) => row.detail.includes("turn_items_shell_pending_idx")));
346 const countsPlan = yield* sql<{ readonly detail: string }>`EXPLAIN QUERY PLAN
347 SELECT thread_id, run_id, COUNT(*) FROM orchestration_v2_projection_turn_items
348 WHERE thread_id = ${threadId} AND run_id IS NOT NULL GROUP BY thread_id, run_id`;
349 assert.isTrue(
350 countsPlan.some((row) =>
351 row.detail.includes(
352 "COVERING INDEX orchestration_v2_projection_turn_items_thread_run_idx",
353 ),
354 ),
355 );
356 assert.isFalse(countsPlan.some((row) => row.detail.includes("TEMP B-TREE")));
357 const messagePlan = yield* sql<{ readonly detail: string }>`EXPLAIN QUERY PLAN
358 SELECT updated_at FROM orchestration_v2_projection_messages
359 WHERE thread_id = ${threadId} AND role = 'user' ORDER BY updated_at DESC, message_id DESC LIMIT 1`;
360 assert.isTrue(messagePlan.some((row) => row.detail.includes("messages_latest_user_idx")));
361 assert.isFalse(messagePlan.some((row) => row.detail.includes("TEMP B-TREE")));
362 }).pipe(Effect.provide(SqlLayer)),
363);
audits/orchestrator-v2/2026-09-04/worktree-drift/apps/server/src/relay/AgentAwarenessRelay.test.ts.snapshot+573−0
@@ -0,0 +1,573 @@
1import { assert, describe, it } from "@effect/vitest";
2import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
3import {
4 EnvironmentId,
5 type OrchestrationV2ThreadShell,
6 ProjectId,
7 ProviderInstanceId,
8 ThreadId,
9} from "@t3tools/contracts";
10import { RelayAgentActivityState } from "@t3tools/contracts/relay";
11import * as DateTime from "effect/DateTime";
12import * as Deferred from "effect/Deferred";
13import * as Effect from "effect/Effect";
14import * as Fiber from "effect/Fiber";
15import * as Option from "effect/Option";
16import * as Ref from "effect/Ref";
17import * as Schema from "effect/Schema";
18import * as Stream from "effect/Stream";
19import * as TestClock from "effect/testing/TestClock";
20import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
21
22import { SecretStoreReadError, ServerSecretStore } from "../auth/ServerSecretStore.ts";
23import {
24 PUBLISH_AGENT_ACTIVITY_SECRET,
25 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
26 RELAY_ISSUER_SECRET,
27 RELAY_URL_SECRET,
28} from "../cloud/config.ts";
29import { ServerEnvironment } from "../environment/ServerEnvironment.ts";
30import { ThreadManagementService } from "../orchestration-v2/ThreadManagementService.ts";
31import { ProjectService } from "../project/ProjectService.ts";
32import {
33 make,
34 makeAgentAwarenessPublishWorker,
35 shouldPublishAgentAwarenessEvent,
36} from "./AgentAwarenessRelay.ts";
37
38const THREAD_ID = ThreadId.make("relay-thread");
39const SECOND_THREAD_ID = ThreadId.make("relay-thread-2");
40const PROJECT_ID = ProjectId.make("relay-project");
41const NOW = "2026-09-04T12:00:00.000Z";
42
43function shell(overrides: Partial<OrchestrationV2ThreadShell> = {}): OrchestrationV2ThreadShell {
44 return {
45 id: THREAD_ID,
46 projectId: PROJECT_ID,
47 title: "Thread",
48 providerInstanceId: ProviderInstanceId.make("codex"),
49 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "test-model" },
50 runtimeMode: "full-access",
51 interactionMode: "default",
52 worktreePath: null,
53 activeProviderThreadId: null,
54 lineage: { rootThreadId: THREAD_ID, parentThreadId: null, relationshipToParent: null },
55 forkedFrom: null,
56 createdBy: "user",
57 creationSource: "web",
58 activeRunId: null,
59 latestVisibleMessage: null,
60 hasActionableProposedPlan: false,
61 itemCount: 0,
62 visibleItemCount: 0,
63 lastVisitedAt: null,
64 deletedAt: null,
65 branch: null,
66 linkedPullRequest: null,
67 status: "running",
68 activityRunStatus: null,
69 pendingRuntimeRequest: null,
70 pendingBackgroundTasks: [],
71 latestRunId: null,
72 latestRunRequestedAt: null,
73 latestRunStartedAt: null,
74 latestRunCompletedAt: null,
75 latestUserMessageAt: null,
76 createdAt: DateTime.makeUnsafe(NOW),
77 updatedAt: DateTime.makeUnsafe(NOW),
78 archivedAt: null,
79 settledOverride: null,
80 settledAt: null,
81 snoozedUntil: null,
82 snoozedAt: null,
83 pinnedAt: null,
84 ...overrides,
85 };
86}
87
88const PublishPayload = Schema.Struct({ state: Schema.NullOr(RelayAgentActivityState) });
89const decodePublishPayload = Schema.decodeUnknownSync(Schema.fromJsonString(PublishPayload));
90const unused = () => Effect.die("Unexpected test dependency call");
91
92const makeTestRelay = Effect.fnUntraced(function* (
93 options: {
94 readonly respond?: (attempt: number) => Response;
95 readonly failSecretRead?: (name: string) => boolean;
96 } = {},
97) {
98 const values = new Map<string, Uint8Array>([
99 [PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("true")],
100 [RELAY_URL_SECRET, new TextEncoder().encode("https://relay.example.test")],
101 [RELAY_ISSUER_SECRET, new TextEncoder().encode("https://relay.example.test")],
102 [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, new TextEncoder().encode("credential-1")],
103 ]);
104 const secretReads: string[] = [];
105 const secrets = ServerSecretStore.of({
106 get: (name) =>
107 Effect.suspend(() => {
108 secretReads.push(name);
109 if (options.failSecretRead?.(name)) {
110 return Effect.fail(
111 new SecretStoreReadError({ resource: name, cause: "temporary read failure" }),
112 );
113 }
114 return Effect.succeed(Option.fromUndefinedOr(values.get(name)));
115 }),
116 create: (name, value) => Effect.sync(() => void values.set(name, value)),
117 set: (name, value) => Effect.sync(() => void values.set(name, value)),
118 remove: (name) => Effect.sync(() => void values.delete(name)),
119 getOrCreateRandom: unused,
120 });
121 const currentShell = yield* Ref.make<OrchestrationV2ThreadShell | null>(shell());
122 const shellReads: ThreadId[] = [];
123 const threads = ThreadManagementService.of({
124 getThreadShell: (threadId) =>
125 Effect.sync(() => shellReads.push(threadId)).pipe(Effect.andThen(Ref.get(currentShell))),
126 getShellSnapshot: unused,
127 ensureLegacyTranscript: unused,
128 dispatch: unused,
129 getThreadProjection: unused,
130 getCheckpointContext: unused,
131 getThreadSnapshot: unused,
132 getThreadSnapshotWindow: unused,
133 getProjectThread: unused,
134 listProjectThreads: unused,
135 sendToThread: unused,
136 waitForThread: unused,
137 interruptThread: unused,
138 getThreadEventSequence: unused,
139 streamStoredEvents: Stream.empty,
140 streamStoredEventsFrom: () => Stream.empty,
141 streamDomainEvents: Stream.empty,
142 });
143 const publications: Array<{
144 readonly url: string;
145 readonly authorization: string | null;
146 readonly state: RelayAgentActivityState | null;
147 }> = [];
148 const fetch: typeof globalThis.fetch = Object.assign(
149 (
150 input: Parameters<typeof globalThis.fetch>[0],
151 init?: Parameters<typeof globalThis.fetch>[1],
152 ) => {
153 const body = init?.body;
154 if (typeof body !== "string" && !(body instanceof Uint8Array)) {
155 return Promise.reject(new Error("Expected a serialized activity publish payload"));
156 }
157 const payload = decodePublishPayload(
158 typeof body === "string" ? body : new TextDecoder().decode(body),
159 );
160 publications.push({
161 url: String(input),
162 authorization: new Headers(init?.headers).get("authorization"),
163 state: payload.state,
164 });
165 return Promise.resolve(
166 options.respond?.(publications.length) ?? Response.json({ ok: true, deliveries: [] }),
167 );
168 },
169 { preconnect: () => {} },
170 );
171 const relay = yield* make.pipe(
172 Effect.provideService(ServerSecretStore, secrets),
173 Effect.provideService(ThreadManagementService, threads),
174 Effect.provideService(ServerEnvironment, {
175 getEnvironmentId: Effect.succeed(EnvironmentId.make("relay-environment")),
176 getDescriptor: unused(),
177 }),
178 Effect.provideService(ProjectService, {
179 create: unused,
180 bootstrap: unused,
181 update: unused,
182 delete: unused,
183 getByWorkspaceRoot: unused,
184 snapshot: unused(),
185 getById: () =>
186 Effect.succeed(
187 Option.some({
188 id: PROJECT_ID,
189 title: "Project",
190 workspaceRoot: "/workspace",
191 defaultModelSelection: null,
192 scripts: [],
193 createdAt: NOW,
194 updatedAt: NOW,
195 deletedAt: null,
196 }),
197 ),
198 }),
199 Effect.provideService(FetchHttpClient.Fetch, fetch),
200 Effect.provide(NodeCrypto.layer),
201 );
202 return { relay, secrets, secretReads, currentShell, shellReads, publications };
203});
204
205describe("AgentAwarenessRelay", () => {
206 it("ignores transcript and tool updates but retains activity and metadata changes", () => {
207 for (const type of [
208 "message.updated",
209 "turn-item.updated",
210 "provider-turn.updated",
211 "thread.visited",
212 "thread.pinned",
213 ] as const) {
214 assert.isFalse(shouldPublishAgentAwarenessEvent({ type }));
215 }
216 for (const type of [
217 "run.created",
218 "run.updated",
219 "runtime-request.updated",
220 "thread.metadata-updated",
221 "thread.model-selection-updated",
222 "thread.provider-switched",
223 "thread.archived",
224 "thread.unarchived",
225 "thread.deleted",
226 ] as const) {
227 assert.isTrue(shouldPublishAgentAwarenessEvent({ type }));
228 }
229 });
230
231 it.effect("coalesces queued updates and reruns a thread dirtied during publishing", () =>
232 Effect.gen(function* () {
233 const started = yield* Deferred.make<void>();
234 const release = yield* Deferred.make<void>();
235 const rerunStarted = yield* Deferred.make<void>();
236 const releaseRerun = yield* Deferred.make<void>();
237 const processed: Array<{ threadId: ThreadId; revision: number }> = [];
238 let revision = 1;
239 const worker = yield* makeAgentAwarenessPublishWorker((threadId) =>
240 Effect.gen(function* () {
241 const currentRevision = revision;
242 const index = processed.length;
243 processed.push({ threadId, revision: currentRevision });
244 if (index === 0) {
245 yield* Deferred.succeed(started, undefined);
246 yield* Deferred.await(release);
247 } else if (threadId === THREAD_ID) {
248 yield* Deferred.succeed(rerunStarted, undefined);
249 yield* Deferred.await(releaseRerun);
250 }
251 }),
252 );
253 yield* worker.enqueue(THREAD_ID);
254 yield* Deferred.await(started);
255 revision = 2;
256 for (let i = 0; i < 100; i++) {
257 yield* worker.enqueue(SECOND_THREAD_ID);
258 yield* worker.enqueue(THREAD_ID);
259 }
260 const drained = yield* Deferred.make<void>();
261 const draining = yield* worker.drain.pipe(
262 Effect.andThen(Deferred.succeed(drained, undefined)),
263 Effect.forkChild,
264 );
265 yield* Deferred.succeed(release, undefined);
266 yield* Deferred.await(rerunStarted);
267 assert.isFalse(yield* Deferred.isDone(drained));
268 yield* Deferred.succeed(releaseRerun, undefined);
269 yield* Fiber.join(draining);
270 assert.deepEqual(processed, [
271 { threadId: THREAD_ID, revision: 1 },
272 { threadId: SECOND_THREAD_ID, revision: 2 },
273 { threadId: THREAD_ID, revision: 2 },
274 ]);
275 }),
276 );
277
278 it.effect("deduplicates state and republishes title changes", () =>
279 Effect.gen(function* () {
280 const { relay, currentShell, publications } = yield* makeTestRelay();
281 yield* relay.publishThread(THREAD_ID);
282 yield* Ref.set(
283 currentShell,
284 shell({ updatedAt: DateTime.makeUnsafe("2026-09-04T13:00:00Z") }),
285 );
286 yield* relay.publishThread(THREAD_ID);
287 assert.equal(publications.length, 1);
288 yield* Ref.set(currentShell, shell({ title: "Renamed thread" }));
289 yield* relay.publishThread(THREAD_ID);
290 assert.equal(publications.length, 2);
291 assert.equal(publications[1]?.state?.threadTitle, "Renamed thread");
292 }),
293 );
294
295 it.effect("stops before shell reads when disabled and republishes after re-enabling", () =>
296 Effect.gen(function* () {
297 const { relay, secrets, secretReads, shellReads, publications } = yield* makeTestRelay();
298 yield* relay.publishThread(THREAD_ID);
299 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
300 const previousReads = secretReads.length;
301 yield* relay.publishThread(THREAD_ID);
302 assert.deepEqual(secretReads.slice(previousReads), [PUBLISH_AGENT_ACTIVITY_SECRET]);
303 assert.equal(shellReads.length, 1);
304 assert.equal(publications.length, 1);
305 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("true"));
306 yield* relay.publishThread(THREAD_ID);
307 assert.equal(publications.length, 2);
308 }),
309 );
310
311 it.effect("republishes unchanged state with fresh credentials after relinking", () =>
312 Effect.gen(function* () {
313 const { relay, secrets, publications } = yield* makeTestRelay();
314 yield* relay.publishThread(THREAD_ID);
315 yield* secrets.set(
316 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
317 new TextEncoder().encode("credential-2"),
318 );
319 yield* relay.publishThread(THREAD_ID);
320 assert.equal(publications.length, 2);
321 assert.equal(publications[0]?.authorization, "Bearer credential-1");
322 assert.equal(publications[1]?.authorization, "Bearer credential-2");
323 yield* secrets.set(
324 RELAY_URL_SECRET,
325 new TextEncoder().encode("https://new-relay.example.test"),
326 );
327 yield* relay.publishThread(THREAD_ID);
328 assert.equal(publications.length, 3);
329 assert.isTrue(publications[2]?.url.startsWith("https://new-relay.example.test/") ?? false);
330 }),
331 );
332
333 it.effect("retries a failed final state without another thread event", () =>
334 Effect.gen(function* () {
335 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
336 respond: (attempt) =>
337 attempt === 2
338 ? new Response("relay unavailable", { status: 503 })
339 : Response.json({ ok: true, deliveries: [] }),
340 });
341 yield* relay.publishThread(THREAD_ID);
342 yield* Ref.set(currentShell, shell({ status: "completed" }));
343 yield* relay.publishThread(THREAD_ID);
344 assert.equal(publications.length, 2);
345 yield* TestClock.adjust("1 second");
346 yield* relay.drain;
347 assert.equal(publications.length, 3);
348 assert.equal(publications[2]?.state?.phase, "completed");
349 yield* TestClock.adjust("1 minute");
350 yield* relay.drain;
351 assert.equal(shellReads.length, 3);
352 }),
353 );
354
355 it.effect("retries the latest shell and credentials with first-completion confirmation", () =>
356 Effect.gen(function* () {
357 const { relay, secrets, currentShell, publications } = yield* makeTestRelay({
358 respond: (attempt) =>
359 attempt === 1
360 ? new Response("relay unavailable", { status: 503 })
361 : Response.json({ ok: true, deliveries: [] }),
362 });
363 yield* relay.publishThread(THREAD_ID);
364 yield* Ref.set(currentShell, shell({ status: "completed", title: "Final title" }));
365 yield* secrets.set(
366 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
367 new TextEncoder().encode("credential-2"),
368 );
369 yield* TestClock.adjust("1 second");
370 yield* relay.drain;
371 assert.equal(publications.length, 1);
372 yield* TestClock.adjust("5 seconds");
373 yield* relay.drain;
374 assert.equal(publications.length, 2);
375 assert.equal(publications[1]?.authorization, "Bearer credential-2");
376 assert.equal(publications[1]?.state?.phase, "completed");
377 assert.equal(publications[1]?.state?.threadTitle, "Final title");
378 }),
379 );
380
381 it.effect("cancels a stale retry when a newer update publishes successfully", () =>
382 Effect.gen(function* () {
383 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
384 respond: (attempt) =>
385 attempt === 1
386 ? new Response("relay unavailable", { status: 503 })
387 : Response.json({ ok: true, deliveries: [] }),
388 });
389 yield* relay.publishThread(THREAD_ID);
390 yield* Ref.set(currentShell, shell({ title: "New title" }));
391 yield* relay.publishThread(THREAD_ID);
392 assert.equal(publications.length, 2);
393 assert.equal(publications[1]?.state?.threadTitle, "New title");
394 yield* TestClock.adjust("1 minute");
395 yield* relay.drain;
396 assert.equal(shellReads.length, 2);
397 }),
398 );
399
400 it.effect.each(["disable", "unlink"] as const)(
401 "stops retries before shell reads after %s",
402 (change) =>
403 Effect.gen(function* () {
404 const { relay, secrets, shellReads, publications } = yield* makeTestRelay({
405 respond: () => new Response("relay unavailable", { status: 503 }),
406 });
407 yield* relay.publishThread(THREAD_ID);
408 if (change === "disable") {
409 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
410 } else {
411 yield* secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET);
412 }
413 yield* TestClock.adjust("1 second");
414 yield* relay.drain;
415 yield* TestClock.adjust("1 minute");
416 yield* relay.drain;
417 assert.equal(publications.length, 1);
418 assert.equal(shellReads.length, 1);
419 }),
420 );
421
422 it.effect("recovers from a transient configuration read failure", () =>
423 Effect.gen(function* () {
424 let failRead = false;
425 const { relay, shellReads, publications } = yield* makeTestRelay({
426 failSecretRead: (name) => failRead && name === PUBLISH_AGENT_ACTIVITY_SECRET,
427 });
428 failRead = true;
429 yield* relay.publishThread(THREAD_ID);
430 assert.equal(shellReads.length, 0);
431 failRead = false;
432 yield* TestClock.adjust("1 second");
433 yield* relay.drain;
434 assert.equal(publications.length, 1);
435 assert.equal(publications[0]?.state?.phase, "running");
436 }),
437 );
438
439 it.effect("bounds retry attempts and resets the budget for a newer update", () =>
440 Effect.gen(function* () {
441 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
442 respond: () => new Response("relay unavailable", { status: 503 }),
443 });
444 yield* relay.publishThread(THREAD_ID);
445 let attempts = 1;
446 for (const delay of [1_000, 2_000, 4_000, 8_000, 16_000]) {
447 yield* TestClock.adjust(delay - 1);
448 yield* relay.drain;
449 assert.equal(publications.length, attempts);
450 yield* TestClock.adjust(1);
451 yield* relay.drain;
452 attempts += 1;
453 assert.equal(publications.length, attempts);
454 }
455 yield* TestClock.adjust("1 minute");
456 yield* relay.drain;
457 assert.equal(publications.length, 6);
458 assert.equal(shellReads.length, 6);
459 yield* Ref.set(currentShell, shell({ title: "New update" }));
460 yield* relay.publishThread(THREAD_ID);
461 yield* TestClock.adjust("1 second");
462 yield* relay.drain;
463 assert.equal(publications.length, 8);
464 assert.equal(publications[7]?.state?.threadTitle, "New update");
465 }),
466 );
467
468 it.effect("retries a confirmed completion without restarting its confirmation window", () =>
469 Effect.gen(function* () {
470 const { relay, currentShell, publications } = yield* makeTestRelay({
471 respond: (attempt) =>
472 attempt <= 2
473 ? new Response("relay unavailable", { status: 503 })
474 : Response.json({ ok: true, deliveries: [] }),
475 });
476 yield* Ref.set(currentShell, shell({ status: "completed" }));
477 yield* relay.publishThread(THREAD_ID);
478 yield* TestClock.adjust("5 seconds");
479 yield* relay.drain;
480 assert.equal(publications.length, 1);
481 yield* TestClock.adjust("1 second");
482 yield* relay.drain;
483 assert.equal(publications.length, 2);
484 yield* TestClock.adjust("2 seconds");
485 yield* relay.drain;
486 assert.equal(publications.length, 3);
487 assert.isTrue(publications.every((publication) => publication.state?.phase === "completed"));
488 yield* TestClock.adjust("1 minute");
489 yield* relay.drain;
490 assert.equal(publications.length, 3);
491 }),
492 );
493
494 it.effect("does not reset an exhausted retry budget for old confirmation timers", () =>
495 Effect.gen(function* () {
496 let unavailable = false;
497 const { relay, currentShell, shellReads, publications } = yield* makeTestRelay({
498 respond: () =>
499 unavailable
500 ? new Response("relay unavailable", { status: 503 })
501 : Response.json({ ok: true, deliveries: [] }),
502 });
503 yield* relay.publishThread(THREAD_ID);
504 // Leave six obsolete confirmations at 5,000 through 5,005 milliseconds.
505 for (let i = 0; i < 6; i++) {
506 yield* Ref.set(currentShell, null);
507 yield* relay.publishThread(THREAD_ID);
508 yield* Ref.set(currentShell, shell());
509 yield* relay.publishThread(THREAD_ID);
510 yield* TestClock.adjust(1);
511 }
512 unavailable = true;
513 yield* Ref.set(currentShell, shell({ status: "failed" }));
514 yield* relay.publishThread(THREAD_ID);
515 yield* TestClock.adjust(1_000);
516 yield* relay.drain;
517 yield* TestClock.adjust(2_000);
518 yield* relay.drain;
519 yield* TestClock.adjust(1_994);
520 yield* relay.drain;
521 for (let i = 0; i < 5; i++) {
522 yield* TestClock.adjust(1);
523 yield* relay.drain;
524 }
525 const attempts = publications.length;
526 const reads = shellReads.length;
527 yield* TestClock.adjust("1 minute");
528 yield* relay.drain;
529 assert.equal(publications.length, attempts);
530 assert.equal(shellReads.length, reads);
531 }),
532 );
533
534 it.effect("confirms tombstones and cancels stale confirmations when activity recovers", () =>
535 Effect.gen(function* () {
536 const { relay, currentShell, publications } = yield* makeTestRelay();
537 yield* relay.publishThread(THREAD_ID);
538 yield* Ref.set(currentShell, null);
539 yield* relay.publishThread(THREAD_ID);
540 assert.equal(publications.length, 1);
541 yield* Ref.set(currentShell, shell());
542 yield* relay.publishThread(THREAD_ID);
543 yield* TestClock.adjust("5 seconds");
544 yield* relay.drain;
545 assert.equal(publications.length, 1);
546 yield* Ref.set(currentShell, null);
547 yield* relay.publishThread(THREAD_ID);
548 assert.equal(publications.length, 1);
549 yield* TestClock.adjust("5 seconds");
550 yield* relay.drain;
551 assert.equal(publications.length, 2);
552 assert.equal(publications[1]?.state, null);
553 }),
554 );
555
556 it.effect("confirms a first completed state and respects disabling during confirmation", () =>
557 Effect.gen(function* () {
558 const { relay, secrets, currentShell, publications } = yield* makeTestRelay();
559 yield* Ref.set(currentShell, shell({ status: "completed" }));
560 yield* relay.publishThread(THREAD_ID);
561 assert.equal(publications.length, 0);
562 yield* TestClock.adjust("5 seconds");
563 yield* relay.drain;
564 assert.equal(publications[0]?.state?.phase, "completed");
565 yield* Ref.set(currentShell, null);
566 yield* relay.publishThread(THREAD_ID);
567 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, new TextEncoder().encode("false"));
568 yield* TestClock.adjust("5 seconds");
569 yield* relay.drain;
570 assert.equal(publications.length, 1);
571 }),
572 );
573});
audits/orchestrator-v2/2026-09-04/worktree-drift/apps/server/src/relay/AgentAwarenessRelay.ts.snapshot+758−0
@@ -0,0 +1,758 @@
1import type {
2 EnvironmentId,
3 OrchestrationV2DomainEvent,
4 OrchestrationV2ThreadShell,
5 Project,
6 ThreadId,
7} from "@t3tools/contracts";
8import {
9 RelayApi,
10 type RelayAgentActivityPublishProofPayload,
11 type RelayAgentActivityState,
12} from "@t3tools/contracts/relay";
13import { projectThreadAwarenessV2 } from "@t3tools/shared/agentAwareness";
14import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker";
15import { withRelayClientTracing } from "@t3tools/shared/relayTracing";
16import {
17 normalizeRelayIssuer,
18 RELAY_ACTIVITY_PUBLISH_TYP,
19 signRelayJwt,
20} from "@t3tools/shared/relayJwt";
21import * as Cause from "effect/Cause";
22import * as Context from "effect/Context";
23import * as Crypto from "effect/Crypto";
24import * as DateTime from "effect/DateTime";
25import * as Effect from "effect/Effect";
26import * as Fiber from "effect/Fiber";
27import * as Layer from "effect/Layer";
28import * as Option from "effect/Option";
29import * as Ref from "effect/Ref";
30import type * as Scope from "effect/Scope";
31import * as Stream from "effect/Stream";
32import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
33import * as HttpClient from "effect/unstable/http/HttpClient";
34import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
35import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
36
37import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
38import {
39 isAgentActivityPublishingEnabledValue,
40 PUBLISH_AGENT_ACTIVITY_SECRET,
41 RELAY_ENVIRONMENT_CREDENTIAL_SECRET,
42 RELAY_ISSUER_SECRET,
43 RELAY_URL_SECRET,
44} from "../cloud/config.ts";
45import { getOrCreateEnvironmentKeyPairFromSecretStore } from "../cloud/environmentKeys.ts";
46import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
47import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts";
48import * as ProjectService from "../project/ProjectService.ts";
49import { forkParked } from "../serverActivation.ts";
50
51export class AgentAwarenessRelay extends Context.Service<
52 AgentAwarenessRelay,
53 {
54 readonly publishThread: (threadId: ThreadId) => Effect.Effect<void>;
55 readonly drain: Effect.Effect<void>;
56 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
57 }
58>()("t3/relay/AgentAwarenessRelay") {}
59
60export function eventThreadId(event: OrchestrationV2DomainEvent): ThreadId {
61 return event.threadId;
62}
63
64export function shouldPublishAgentAwarenessEvent(
65 event: Pick<OrchestrationV2DomainEvent, "type">,
66): boolean {
67 // projectThreadAwarenessV2 reads thread metadata, run status, and pending requests.
68 // Message bodies and tool progress cannot change the published activity.
69 switch (event.type) {
70 case "thread.created":
71 case "thread.archived":
72 case "thread.unarchived":
73 case "thread.deleted":
74 case "thread.metadata-updated":
75 case "thread.model-selection-updated":
76 case "thread.provider-switched":
77 case "run.created":
78 case "run.updated":
79 case "runtime-request.updated":
80 return true;
81 case "thread.settled":
82 case "thread.unsettled":
83 case "thread.snoozed":
84 case "thread.unsnoozed":
85 case "thread.pinned":
86 case "thread.unpinned":
87 case "thread.pin-reordered":
88 case "thread.visited":
89 case "thread.marked-unread":
90 case "thread.runtime-mode-updated":
91 case "thread.interaction-mode-updated":
92 case "run-attempt.created":
93 case "run-attempt.updated":
94 case "node.updated":
95 case "subagent.updated":
96 case "provider-session.attached":
97 case "provider-session.updated":
98 case "provider-session.detached":
99 case "provider-thread.updated":
100 case "provider-turn.updated":
101 case "message.updated":
102 case "turn-item.updated":
103 case "plan.updated":
104 case "checkpoint-scope.created":
105 case "checkpoint.captured":
106 case "checkpoint.rollback-requested":
107 case "context-handoff.updated":
108 case "context-transfer.created":
109 case "context-transfer.updated":
110 return false;
111 }
112}
113
114export const makeAgentAwarenessPublishWorker = Effect.fnUntraced(function* <R>(
115 publish: (threadId: ThreadId) => Effect.Effect<void, never, R>,
116) {
117 const queued = new Set<ThreadId>();
118 const worker = yield* makeDrainableWorker((threadId: ThreadId) =>
119 Effect.sync(() => queued.delete(threadId)).pipe(Effect.andThen(publish(threadId))),
120 );
121 const enqueue = (threadId: ThreadId) =>
122 Effect.suspend(() => {
123 if (queued.has(threadId)) return Effect.void;
124 // Removing the ID when processing starts allows one new queue entry
125 // for updates received while the current snapshot is being published.
126 queued.add(threadId);
127 return worker.enqueue(threadId);
128 }).pipe(Effect.uninterruptible);
129 return { enqueue, drain: worker.drain };
130});
131
132export function agentAwarenessPublishIdentity(state: RelayAgentActivityState | null): string {
133 if (state === null) {
134 return "null";
135 }
136 const { updatedAt: _updatedAt, ...meaningfulState } = state;
137 return JSON.stringify(meaningfulState);
138}
139
140export function isAgentActivityPublishingEnabled(value: string | null): boolean {
141 return isAgentActivityPublishingEnabledValue(value);
142}
143
144export function resolveAgentActivityPublishingStartupState(input: {
145 readonly relayConfigured: boolean;
146 readonly publishEnabled: boolean;
147}): "waiting-for-link" | "disabled" | "enabled" {
148 if (!input.relayConfigured) {
149 return "waiting-for-link";
150 }
151 return input.publishEnabled ? "enabled" : "disabled";
152}
153
154const RELAY_AGENT_ACTIVITY_DETAIL_MAX_LENGTH = 160;
155const REDACTED_RELAY_AGENT_FAILURE_DETAIL = "The agent run failed.";
156const RELAY_AGENT_ACTIVITY_RETRY_DELAYS_MS = [1_000, 2_000, 4_000, 8_000, 16_000] as const;
157
158export function sanitizeRelayAgentActivityState(
159 state: RelayAgentActivityState | null,
160): RelayAgentActivityState | null {
161 if (state === null) {
162 return null;
163 }
164 const { detail: _detail, ...rest } = state;
165 const detail = (state.phase === "failed" ? REDACTED_RELAY_AGENT_FAILURE_DETAIL : state.detail)
166 ?.trim()
167 .slice(0, RELAY_AGENT_ACTIVITY_DETAIL_MAX_LENGTH)
168 .trim();
169 return detail ? { ...rest, detail } : rest;
170}
171
172function relayEnvironmentClient(token: string) {
173 return HttpClient.mapRequest(HttpClientRequest.setHeader("authorization", `Bearer ${token}`));
174}
175
176function deliveryStats(
177 deliveries: ReadonlyArray<{
178 readonly ok: boolean;
179 readonly queued?: boolean | undefined;
180 readonly kind: string;
181 readonly apnsStatus?: number | null;
182 readonly apnsReason?: string | null;
183 }>,
184) {
185 let queued = 0;
186 let successful = 0;
187 let failed = 0;
188 const failedReasons: string[] = [];
189 const kinds = new Set<string>();
190
191 for (const delivery of deliveries) {
192 kinds.add(delivery.kind);
193 if (delivery.queued) {
194 queued += 1;
195 continue;
196 }
197 if (delivery.ok) {
198 successful += 1;
199 continue;
200 }
201 failed += 1;
202 failedReasons.push(`${delivery.apnsStatus ?? "transport"}:${delivery.apnsReason ?? "unknown"}`);
203 }
204
205 return {
206 total: deliveries.length,
207 queued,
208 successful,
209 failed,
210 kinds: [...kinds],
211 failedReasons,
212 };
213}
214
215export function signRelayAgentActivityPublishProof(input: {
216 readonly privateKey: string;
217 readonly payload: RelayAgentActivityPublishProofPayload;
218}) {
219 return signRelayJwt({
220 privateKey: input.privateKey,
221 typ: RELAY_ACTIVITY_PUBLISH_TYP,
222 payload: input.payload,
223 });
224}
225
226const makePublishProof = Effect.fn("makePublishProof")(function* (input: {
227 readonly privateKey: string;
228 readonly relayIssuer: string;
229 readonly environmentId: string;
230 readonly threadId: ThreadId;
231 readonly state: RelayAgentActivityState | null;
232 readonly jti: string;
233}) {
234 const now = yield* DateTime.now;
235 const expiresAt = DateTime.add(now, { minutes: 5 });
236 const payload = {
237 iss: `t3-env:${input.environmentId}`,
238 aud: normalizeRelayIssuer(input.relayIssuer),
239 sub: input.environmentId,
240 jti: input.jti,
241 iat: Math.floor(now.epochMilliseconds / 1_000),
242 exp: Math.floor(expiresAt.epochMilliseconds / 1_000),
243 environmentId: input.environmentId as RelayAgentActivityPublishProofPayload["environmentId"],
244 threadId: input.threadId,
245 state: input.state,
246 } satisfies RelayAgentActivityPublishProofPayload;
247 return yield* signRelayAgentActivityPublishProof({ privateKey: input.privateKey, payload });
248});
249
250// Compact, log-safe view of the fields the awareness phase ladder reads.
251export function describeThreadShellForAwareness(
252 thread: Option.Option<OrchestrationV2ThreadShell>,
253): Record<string, unknown> {
254 if (Option.isNone(thread)) {
255 return { found: false };
256 }
257 const shell = thread.value;
258 return {
259 found: true,
260 status: shell.status,
261 activityRunStatus: shell.activityRunStatus ?? null,
262 activeRunId: shell.activeRunId ?? null,
263 latestRunId: shell.latestRunId ?? null,
264 pendingRuntimeRequestKind: shell.pendingRuntimeRequest?.kind ?? null,
265 hasActionableProposedPlan: shell.hasActionableProposedPlan,
266 };
267}
268
269export function resolveAgentAwarenessRelayPublishSnapshot(input: {
270 readonly environmentId: EnvironmentId;
271 readonly threadId: ThreadId;
272 readonly thread: Option.Option<OrchestrationV2ThreadShell>;
273 readonly project: Option.Option<Project>;
274}): {
275 readonly projectId: string | null;
276 readonly state: RelayAgentActivityState | null;
277 readonly reason: "snapshot" | "thread-not-found" | "project-not-found";
278} {
279 if (Option.isNone(input.thread)) {
280 return {
281 projectId: null,
282 state: null,
283 reason: "thread-not-found",
284 };
285 }
286 if (Option.isNone(input.project)) {
287 return {
288 projectId: input.thread.value.projectId,
289 state: null,
290 reason: "project-not-found",
291 };
292 }
293 return {
294 projectId: input.thread.value.projectId,
295 state: sanitizeRelayAgentActivityState(
296 projectThreadAwarenessV2({
297 environmentId: input.environmentId,
298 project: input.project.value,
299 thread: input.thread.value,
300 }),
301 ),
302 reason: "snapshot",
303 };
304}
305
306export function resolveAgentAwarenessRelayActiveThreadIds(input: {
307 readonly environmentId: EnvironmentId;
308 readonly projects: ReadonlyArray<Pick<Project, "id" | "title">>;
309 readonly threads: ReadonlyArray<OrchestrationV2ThreadShell>;
310}): ReadonlyArray<ThreadId> {
311 const projectById = new Map(input.projects.map((project) => [project.id, project]));
312 return input.threads
313 .filter((thread) => {
314 const project = projectById.get(thread.projectId);
315 if (!project) {
316 return false;
317 }
318 return (
319 projectThreadAwarenessV2({
320 environmentId: input.environmentId,
321 project,
322 thread,
323 }) !== null
324 );
325 })
326 .map((thread) => thread.id);
327}
328
329export const make = Effect.gen(function* () {
330 const secrets = yield* ServerSecretStore.ServerSecretStore;
331 const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
332 const threads = yield* ThreadManagement.ThreadManagementService;
333 const projects = yield* ProjectService.ProjectService;
334 const crypto = yield* Crypto.Crypto;
335 const scope = yield* Effect.scope;
336 const cloudLinkKeyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secrets);
337 const activeSnapshotPublishedRef = yield* Ref.make(false);
338 const publishedStateByThreadRef = yield* Ref.make(new Map<ThreadId, string>());
339
340 const readSecretString = (name: string) =>
341 secrets
342 .get(name)
343 .pipe(
344 Effect.map((bytes) =>
345 Option.isSome(bytes) ? new TextDecoder().decode(bytes.value) : null,
346 ),
347 );
348
349 const readRelayConfig = Effect.gen(function* () {
350 const [url, issuer, environmentCredential] = yield* Effect.all([
351 readSecretString(RELAY_URL_SECRET),
352 readSecretString(RELAY_ISSUER_SECRET),
353 readSecretString(RELAY_ENVIRONMENT_CREDENTIAL_SECRET),
354 ]);
355 return url && environmentCredential
356 ? { url, issuer: issuer ?? url, environmentCredential }
357 : null;
358 });
359
360 const readPublishAgentActivityEnabled = readSecretString(PUBLISH_AGENT_ACTIVITY_SECRET).pipe(
361 Effect.map(isAgentActivityPublishingEnabled),
362 );
363
364 const makeRelayClient = (relayConfig: {
365 readonly url: string;
366 readonly environmentCredential: string;
367 }) =>
368 HttpApiClient.make(RelayApi, {
369 baseUrl: relayConfig.url,
370 transformClient: relayEnvironmentClient(relayConfig.environmentCredential),
371 }).pipe(Effect.provide(FetchHttpClient.layer));
372
373 let cachedRelayClient: Effect.Success<ReturnType<typeof makeRelayClient>> | undefined;
374 let publishedRelayConfig: NonNullable<Effect.Success<typeof readRelayConfig>> | undefined;
375
376 // Deadlines for publishes that need confirmation (tombstones and
377 // first-state completions). The confirming publish is re-enqueued through
378 // the same drainable worker as every other publish, so a confirmed
379 // tombstone can never race an in-flight live update; a recovered state
380 // clears the deadline. Assigned after the worker exists.
381 const publishConfirmDeadlines = new Map<ThreadId, number>();
382 let schedulePublishConfirm: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;
383 const publishRetries = new Map<
384 ThreadId,
385 { readonly attempts: number; timer: Fiber.Fiber<void> | undefined }
386 >();
387 const cancelPublishRetry = Effect.fnUntraced(function* (threadId: ThreadId) {
388 const retry = publishRetries.get(threadId);
389 publishRetries.delete(threadId);
390 if (retry?.timer !== undefined) yield* Fiber.interrupt(retry.timer);
391 });
392 const cancelPublishRetries = Effect.suspend(() =>
393 Effect.forEach([...publishRetries.keys()], cancelPublishRetry, { discard: true }),
394 );
395 let schedulePublishRetry: (threadId: ThreadId) => Effect.Effect<void> = () => Effect.void;
396 const resetPublishedConnection = Effect.gen(function* () {
397 publishedRelayConfig = undefined;
398 cachedRelayClient = undefined;
399 publishConfirmDeadlines.clear();
400 yield* Ref.set(publishedStateByThreadRef, new Map());
401 });
402
403 const publishThreadUnsafe = Effect.fn("publishThreadUnsafe")(function* (threadId: ThreadId) {
404 const publishAgentActivity = yield* readPublishAgentActivityEnabled;
405 if (!publishAgentActivity) {
406 yield* cancelPublishRetries;
407 yield* resetPublishedConnection;
408 yield* Effect.logDebug("agent activity publish skipped; publication disabled", {
409 threadId,
410 });
411 return;
412 }
413 const relayConfig = yield* readRelayConfig;
414 if (!relayConfig) {
415 yield* cancelPublishRetries;
416 yield* resetPublishedConnection;
417 yield* Effect.logDebug("agent activity publish skipped; relay link credentials unavailable", {
418 threadId,
419 });
420 return;
421 }
422 if (
423 publishedRelayConfig?.url !== relayConfig.url ||
424 publishedRelayConfig.issuer !== relayConfig.issuer ||
425 publishedRelayConfig.environmentCredential !== relayConfig.environmentCredential
426 ) {
427 yield* resetPublishedConnection;
428 publishedRelayConfig = relayConfig;
429 }
430 const environmentId = yield* serverEnvironment.getEnvironmentId;
431
432 const publishState = (input: {
433 readonly projectId: string | null;
434 readonly state: RelayAgentActivityState | null;
435 readonly reason: string;
436 }) =>
437 Effect.gen(function* () {
438 if (cachedRelayClient === undefined) {
439 cachedRelayClient = yield* makeRelayClient(relayConfig);
440 }
441 const relayClient = cachedRelayClient;
442 const proof = yield* makePublishProof({
443 privateKey: cloudLinkKeyPair.privateKey,
444 relayIssuer: relayConfig.issuer,
445 environmentId,
446 threadId,
447 state: input.state,
448 jti: yield* crypto.randomUUIDv4,
449 });
450
451 yield* Effect.logInfo("publishing agent activity for thread", {
452 environmentId,
453 threadId,
454 projectId: input.projectId,
455 statePhase: input.state?.phase ?? null,
456 hasState: input.state !== null,
457 reason: input.reason,
458 });
459
460 const response = yield* relayClient.server.publishAgentActivity({
461 params: {
462 environmentId,
463 threadId,
464 },
465 payload: {
466 state: input.state,
467 proof,
468 },
469 });
470
471 yield* Effect.logInfo("agent activity publish completed", {
472 environmentId,
473 threadId,
474 ok: response.ok,
475 deliveries: deliveryStats(response.deliveries),
476 });
477 });
478
479 // Per-thread shell read: this publish runs for every activity-relevant
480 // domain event, so materializing the full shell here would make the cost
481 // of one thread's activity proportional to how many threads exist.
482 const threadShell = yield* threads.getThreadShell(threadId);
483 const thread =
484 threadShell === null || threadShell.archivedAt !== null
485 ? Option.none<OrchestrationV2ThreadShell>()
486 : Option.some(threadShell);
487 const project = Option.isSome(thread)
488 ? yield* projects.getById(thread.value.projectId)
489 : Option.none<Project>();
490 const snapshot = resolveAgentAwarenessRelayPublishSnapshot({
491 environmentId,
492 threadId,
493 thread,
494 project,
495 });
496 const publishIdentity = agentAwarenessPublishIdentity(snapshot.state);
497 const publishedStateByThread = yield* Ref.get(publishedStateByThreadRef);
498 if (publishedStateByThread.get(threadId) === publishIdentity) {
499 // The projection is back at (or never left) the last published state, so
500 // any pending deferred confirmation is moot. Leaving the deadline in
501 // place would let a much later transient null find it already expired
502 // and publish a tombstone immediately, skipping the deferral window.
503 publishConfirmDeadlines.delete(threadId);
504 yield* Effect.logDebug("agent activity publish skipped; projected state unchanged", {
505 environmentId,
506 threadId,
507 reason: snapshot.reason,
508 });
509 return;
510 }
511
512 // Two projections need confirmation before publishing, because both can
513 // appear transiently while the projector is mid-write and publishing them
514 // immediately is destructive or noisy:
515 // - null (tombstone) while the previous published state was live: deletes
516 // the thread from every armed card mid-conversation.
517 // - completed as the thread's FIRST published state: sessions boot at
518 // "ready" before their first turn, which projects as completed for an
519 // instant and sends a spurious Done notification at thread birth.
520 // Defer, schedule a re-publish through the ordinary worker queue, and
521 // only publish if the projection still holds when it drains.
522 const requiresConfirmation =
523 (snapshot.state === null &&
524 publishedStateByThread.get(threadId) !== agentAwarenessPublishIdentity(null)) ||
525 (snapshot.state?.phase === "completed" && !publishedStateByThread.has(threadId));
526 if (requiresConfirmation) {
527 const nowMs = (yield* DateTime.now).epochMilliseconds;
528 const deadline = publishConfirmDeadlines.get(threadId);
529 if (deadline === undefined) {
530 publishConfirmDeadlines.set(threadId, nowMs + 5_000);
531 yield* Effect.logInfo("agent activity publish deferred pending confirmation", {
532 environmentId,
533 threadId,
534 reason: snapshot.reason,
535 statePhase: snapshot.state?.phase ?? null,
536 shell: describeThreadShellForAwareness(thread),
537 });
538 yield* schedulePublishConfirm(threadId);
539 return;
540 }
541 if (nowMs < deadline) {
542 return;
543 }
544 yield* Effect.logInfo("agent activity deferred publish confirmed", {
545 environmentId,
546 threadId,
547 reason: snapshot.reason,
548 statePhase: snapshot.state?.phase ?? null,
549 shell: describeThreadShellForAwareness(thread),
550 });
551 } else {
552 publishConfirmDeadlines.delete(threadId);
553 }
554
555 if (snapshot.reason === "thread-not-found") {
556 yield* Effect.logDebug("publishing agent activity tombstone; thread not found", {
557 environmentId,
558 threadId,
559 });
560 } else if (snapshot.reason === "project-not-found") {
561 yield* Effect.logDebug("publishing agent activity tombstone; project not found", {
562 environmentId,
563 threadId,
564 projectId: snapshot.projectId,
565 });
566 }
567
568 yield* publishState({
569 projectId: snapshot.projectId,
570 state: snapshot.state,
571 reason: snapshot.reason,
572 });
573 publishConfirmDeadlines.delete(threadId);
574 yield* Ref.update(publishedStateByThreadRef, (publishedStates) => {
575 const nextPublishedStates = new Map(publishedStates);
576 nextPublishedStates.set(threadId, publishIdentity);
577 return nextPublishedStates;
578 });
579 });
580
581 const processThreadPublish = Effect.fnUntraced(function* (threadId: ThreadId) {
582 const retry = publishRetries.get(threadId);
583 if (retry?.timer !== undefined) {
584 const timer = retry.timer;
585 retry.timer = undefined;
586 yield* Fiber.interrupt(timer);
587 }
588 yield* publishThreadUnsafe(threadId).pipe(
589 Effect.tap(() => cancelPublishRetry(threadId)),
590 Effect.catchCause((cause) => {
591 if (Cause.hasInterruptsOnly(cause)) return Effect.void;
592 return Effect.logWarning("agent activity publish failed", {
593 threadId,
594 cause: Cause.pretty(cause),
595 }).pipe(Effect.andThen(schedulePublishRetry(threadId)));
596 }),
597 Effect.withSpan("AgentAwarenessRelay.publishThread"),
598 withRelayClientTracing,
599 );
600 });
601
602 const worker = yield* makeAgentAwarenessPublishWorker(processThreadPublish);
603 const enqueueThreadPublish = (threadId: ThreadId) =>
604 cancelPublishRetry(threadId).pipe(Effect.andThen(worker.enqueue(threadId)));
605 const publishThread: AgentAwarenessRelay["Service"]["publishThread"] = (threadId) =>
606 enqueueThreadPublish(threadId).pipe(Effect.andThen(worker.drain));
607
608 schedulePublishRetry = Effect.fnUntraced(function* (threadId: ThreadId) {
609 const attempts = publishRetries.get(threadId)?.attempts ?? 0;
610 const delayMs = RELAY_AGENT_ACTIVITY_RETRY_DELAYS_MS[attempts];
611 if (delayMs === undefined) {
612 // Keep the exhausted budget until fresh activity or a successful publish
613 // clears it; an old confirmation timer must not start another retry series.
614 yield* Effect.logWarning("agent activity publish retry budget exhausted", { threadId });
615 return;
616 }
617 const retry = {
618 attempts: attempts + 1,
619 timer: undefined as Fiber.Fiber<void> | undefined,
620 };
621 publishRetries.set(threadId, retry);
622 const timer = yield* Effect.sleep(delayMs).pipe(
623 Effect.andThen(
624 Effect.suspend(() => {
625 if (publishRetries.get(threadId) !== retry) return Effect.void;
626 retry.timer = undefined;
627 return worker.enqueue(threadId);
628 }),
629 ),
630 Effect.forkIn(scope),
631 );
632 if (publishRetries.get(threadId) !== retry) {
633 yield* Fiber.interrupt(timer);
634 } else {
635 retry.timer = timer;
636 }
637 });
638
639 const publishActiveThreadsUnsafe = Effect.gen(function* () {
640 const publishAgentActivity = yield* readPublishAgentActivityEnabled.pipe(
641 Effect.orElseSucceed(() => false),
642 );
643 if (!publishAgentActivity) {
644 yield* Effect.logDebug("agent activity snapshot skipped; publication disabled");
645 return false;
646 }
647 const relayConfig = yield* readRelayConfig.pipe(Effect.orElseSucceed(() => null));
648 if (!relayConfig) {
649 yield* Effect.logDebug("agent activity snapshot skipped; relay link credentials unavailable");
650 return false;
651 }
652 const environmentId = yield* serverEnvironment.getEnvironmentId;
653 const [projectSnapshot, shellSnapshot] = yield* Effect.all([
654 projects.snapshot,
655 threads.getShellSnapshot(),
656 ]);
657 const activeThreadIds = resolveAgentAwarenessRelayActiveThreadIds({
658 environmentId,
659 projects: projectSnapshot.projects,
660 threads: shellSnapshot.threads,
661 });
662 if (activeThreadIds.length === 0) {
663 yield* Effect.logDebug("agent activity snapshot has no publishable threads");
664 return true;
665 }
666 yield* Effect.logInfo("publishing active agent activity snapshot", {
667 count: activeThreadIds.length,
668 });
669 yield* Effect.forEach(activeThreadIds, enqueueThreadPublish, { discard: true });
670 yield* worker.drain;
671 return true;
672 });
673
674 const publishActiveThreadsOnceWhenConfigured = (logEnabledWhenReady: boolean) =>
675 Effect.gen(function* () {
676 while (!(yield* Ref.get(activeSnapshotPublishedRef))) {
677 const published = yield* publishActiveThreadsUnsafe.pipe(Effect.orElseSucceed(() => false));
678 if (published) {
679 yield* Ref.set(activeSnapshotPublishedRef, true);
680 if (logEnabledWhenReady) {
681 const relayConfig = yield* readRelayConfig.pipe(Effect.orElseSucceed(() => null));
682 yield* Effect.logInfo("agent activity publishing enabled after link reconciliation", {
683 relayUrl: relayConfig?.url,
684 });
685 }
686 return;
687 }
688 yield* Effect.sleep("5 seconds");
689 }
690 });
691
692 schedulePublishConfirm = (threadId) =>
693 Effect.forkIn(
694 Effect.sleep("5 seconds").pipe(
695 Effect.andThen(worker.enqueue(threadId)),
696 Effect.catchCause((cause) =>
697 Effect.logWarning("deferred agent activity confirmation failed", {
698 threadId,
699 cause: Cause.pretty(cause),
700 }),
701 ),
702 ),
703 scope,
704 ).pipe(Effect.asVoid);
705
706 const start: AgentAwarenessRelay["Service"]["start"] = Effect.fn("AgentAwarenessRelay.start")(
707 function* () {
708 const [relayConfig, publishEnabled] = yield* Effect.all([
709 readRelayConfig.pipe(Effect.orElseSucceed(() => null)),
710 readPublishAgentActivityEnabled.pipe(Effect.orElseSucceed(() => false)),
711 ]);
712 const startupState = resolveAgentActivityPublishingStartupState({
713 relayConfigured: relayConfig !== null,
714 publishEnabled,
715 });
716 switch (startupState) {
717 case "waiting-for-link":
718 yield* Effect.logInfo(
719 "agent activity publishing standby; waiting for T3 Connect link reconciliation",
720 );
721 break;
722 case "disabled":
723 yield* Effect.logInfo("agent activity publishing disabled by T3 Connect configuration");
724 break;
725 case "enabled":
726 yield* Effect.logInfo("agent activity publishing enabled", {
727 relayUrl: relayConfig?.url,
728 });
729 break;
730 }
731 yield* forkParked(
732 Effect.sleep("1 second").pipe(
733 Effect.andThen(publishActiveThreadsOnceWhenConfigured(startupState !== "enabled")),
734 ),
735 );
736 yield* forkParked(
737 Stream.runForEach(threads.streamDomainEvents, (event) => {
738 const threadId = eventThreadId(event);
739 if (!shouldPublishAgentAwarenessEvent(event)) {
740 return Effect.void;
741 }
742 return Effect.logDebug("agent activity publishing queued thread publish", {
743 eventType: event.type,
744 threadId,
745 }).pipe(Effect.andThen(enqueueThreadPublish(threadId)));
746 }),
747 );
748 },
749 );
750
751 return AgentAwarenessRelay.of({
752 publishThread,
753 drain: worker.drain,
754 start,
755 });
756});
757
758export const layer = Layer.effect(AgentAwarenessRelay, make);
audits/orchestrator-v2/2026-09-04/worktree-drift/docs/internals/mobile-development.md.snapshot+61−0
@@ -0,0 +1,61 @@
1# Mobile development lifecycle
2
3Ordinary component changes use Metro Fast Refresh. The connection runtime uses
4a stable development-only atom runtime whose writable layer atom receives the
5newly evaluated Effect layer. Its module accepts the update only after installing
6that layer. Existing subscribers observe the new context, and Effect releases the
7previous runtime scope without restarting React Navigation. Production uses an
8ordinary `Atom.runtime` without this hot-update boundary.
9
10Replacing the atom registry itself disposes its old nodes; replacing the managed
11runtime starts its asynchronous disposal. These modules use normal Metro update
12propagation. The app does not call `DevSettings.reload` during their replacement.
13React can still reset state when its normal Fast Refresh rules require it.
14
15Do not reset the shared registry to clean up a connection-runtime edit: registry
16reset removes listeners from unedited mounted consumers. Do not self-accept a
17runtime module while leaving importers attached to its old implementation. A
18hot-update boundary must install fresh behavior through the existing reactive
19dependencies. These boundaries do not make every module-level atom family in the
20app hot-swappable; new runtime singletons still need explicit ownership. Editing
21the registry or managed runtime can still rebuild a much larger dependency graph
22than an ordinary component or connection-runtime edit.
23
24Environment supervisor scopes are children of the connection registry scope.
25The registry's per-environment map supports targeted shutdown, but it cannot be
26the sole owner: a supervisor created after that map's finalizer has run must
27still inherit the closed parent scope. Otherwise interrupted startup or runtime
28replacement can leave a session and WebSocket alive outside the current registry.
29
30The compact Home list owns its minute-based presentation clock in a focus effect.
31Blur clears the interval without a render-driving focus subscription; focus
32refreshes the clock immediately. Other prop or state changes can still render
33the hidden list. Exact snooze-expiry timers remain active, and the visible iPad
34sidebar keeps its own minute updates.
35
36Connection and runtime projections are shared per environment. Thread selection
37consumers should read those atoms instead of repeatedly parsing the same socket
38URL or constructing new connection objects during each render.
39
40The Uniwind dependency patch still recompiles CSS on Metro updates so newly used
41classes are discovered. It fingerprints the generated native stylesheet and
42theme list, then skips development-only global invalidation when that output is
43unchanged. A changed stylesheet or theme list still resets the style caches and
44notifies subscribers. The digest is committed only after initialization succeeds.
45Web and production retain their existing initialization behavior.
46
47After installing or changing the Uniwind patch, restart Metro once with
48`vp run dev:client:reset` from `apps/mobile`. pnpm gives patched packages new
49filesystem paths, and cached transforms can otherwise retain references to the
50previous package. Ordinary development starts should retain the transform cache.
51
52The expo-notifications patch protects `NotificationCenterManager`'s delegate and pending-response
53arrays with a lock. React runtimes can register and remove delegates concurrently during reloads
54or native scene startup. Delivery takes a snapshot under the lock and invokes delegates after
55releasing it, allowing callbacks to register other delegates. Pending-response replay removes
56only the responses it saw so a response arriving during a callback remains available.
57
58Changes to this native patch require a fresh dependency install, CocoaPods refresh, and an iOS
59rebuild. `vp test run apps/mobile/scripts/notification-center-manager.test.ts` compiles the
60installed dependency with a small native harness on macOS and runs concurrent registration and
61delivery checks under ThreadSanitizer without launching a simulator.
audits/orchestrator-v2/2026-09-04/worktree-drift/docs/internals/performance-regressions.md.snapshot+86−0
@@ -0,0 +1,86 @@
1# Performance regression checks
2
3The v2 transport has a focused regression command:
4
5```bash
6vp run test:perf:v2-wire
7```
8
9It exercises the real v2 projection and client reducers. This matters because the older
10built-app benchmark fixtures seed v1 orchestration events; running those fixtures against a v2
11client can produce reassuring timings while missing the active transport path.
12
13The v2 checks pin these invariants:
14
15- Client cold opens use a recent timeline window capped at 75 rows and about 1 MiB of encoded
16 timeline data.
17- The bounded snapshot does not retain the full duplicate conversation-message table.
18- Older activity is fetched in bounded HTTP pages and merged without disturbing the live scroll
19 window.
20- Resume catch-up replays at most 128 thread events and 1 MiB of projected event JSON before
21 replacing stale state with a current snapshot.
22- Oversized dynamic-tool results and detail strings are reduced only at the wire boundary; the
23 persisted event remains complete.
24- The initial shell contains active navigation rows only. Archived rows use the dedicated archive
25 query, and transcript message bodies stay in thread detail regardless of message size.
26- Shell resume sends deltas plus compact repository-enrichment metadata, not another full project
27 and thread snapshot.
28- Known idle sends and interrupts do not fetch a full thread projection before dispatch.
29
30When changing projection schemas, paging, shell synchronization, or thread state, run this command
31alongside the focused package typechecks and a real-client pass on every affected surface. Payload
32budgets belong in these tests rather than logs or one-off recordings so regressions fail locally.
33
34## Event store and startup
35
36Sequence cursors must seek an index rather than scan retained history. The application sequence
37index contains project events and v2 thread events; the per-thread sequence index contains only v2
38thread events. Legacy-only and unknown threads must return zero without scanning other threads.
39Catch-up queries keep sequence ranges and optional thread or command predicates indexable.
40
41Startup selects recovery candidates from current projection state before reading full thread
42projections. Candidate selection includes queued runs, pending runtime requests, live provider
43sessions, background work, and unfinished delegated deliveries. Archived threads still participate
44where recovery requires them. Completed thread history is not loaded merely to discover that no
45work remains.
46
47Projection verification decodes canonical rows in bounded pages, including shared provider records
48and fork ancestry. Rebuild replays event pages through a fixed sequence within its transaction;
49it must not collect the entire event log before projecting it.
50
51The focused regression coverage lives in `OrchestrationEventStore.sequence.test.ts`,
52`ProjectionRecovery.test.ts`, and the provider runtime recovery tests under `apps/server/src`.
53
54## Background work and navigation
55
56The settlement worker reads active, unpinned threads without explicit settlement overrides from
57`ProjectionStore.getSettlementCandidates`. It reads the latest run and user-message timestamps,
58rejects active runs and pending requests, and checks background work using the same derivation as
59the shell. It does not load archived histories, fork ancestry, transcript counts, or provider
60sessions. The orchestrator still validates the current thread before applying settlement.
61
62Read receipts use `ProjectionStore.getThread` to load only canonical thread metadata. A visit
63advances `lastVisitedAt` monotonically without changing `updatedAt` or decoding history. Shell
64queries use a covering thread/run index for item counts and a partial index for nonterminal
65background items, including idle items. Latest user-message reads use an index ordered by update
66time.
67
68Command palette results keep live VCS and linked pull-request queries leased to the visible
69viewport, including a small overscan region. Cached badges remain available while leases are
70released. Local Git status reads skip divergence calculations; callers that need ahead/behind
71counts keep the full status path. Cached fetch failures share the existing backoff and log only
72the actual failed attempt. Unchanged desktop environment bootstrap reads retain their array
73identity so polling does not invalidate subscribers.
74
75Relay awareness queues one pending publication per thread and retains a follow-up when the thread
76changes during a send. Transcript deltas and tool output do not enqueue awareness work. Run,
77request, and relevant thread metadata events do. Configuration is checked before shell reads, and
78connection changes invalidate published identities so relinking can publish unchanged state.
79Failed publications retry the latest state up to five times with exponential delays. A new
80relevant event resets the budget; successful publication, disabling, or unlinking cancels retries.
81After the budget is exhausted, publication waits for another relevant event instead of generating
82continuous traffic during an outage.
83
84Focused coverage: `ProjectionSettlement.test.ts`, `ThreadSettlementService.test.ts`,
85`runtimeLayer.test.ts`, `AgentAwarenessRelay.test.ts`, `GitVcsDriverCore.test.ts`,
86`ThreadStatusIndicators.subscriptions.test.tsx`, and `desktopLocal.test.ts`.
audits/orchestrator-v2/2026-09-04/worktree-drift/docs/internals/providers.md.snapshot+411−0
@@ -0,0 +1,411 @@
1# Provider architecture
2
3> For maintainers. Using T3 Code? See [docs/user](../user/).
4
5A provider is the agent runtime that does the actual work. T3 Code supports several, and the
6orchestration layer does not know which one is behind a thread.
7
8## Built-in drivers
9
10[`builtInDrivers.ts`][drivers] exports `BUILT_IN_DRIVERS` with six entries:
11
12| Driver kind | Driver source |
13| ------------- | --------------------------------------------- |
14| `codex` | [`Drivers/CodexDriver.ts`][codex] |
15| `claudeAgent` | [`Drivers/ClaudeDriver.ts`][claude] |
16| `cursor` | [`Drivers/CursorDriver.ts`][cursor] |
17| `grok` | [`Drivers/GrokDriver.ts`][grok] |
18| `opencode` | [`Drivers/OpenCodeDriver.ts`][opencode] |
19| `antigravity` | [`Drivers/AntigravityDriver.ts`][antigravity] |
20
21Each driver declares its `driverKind`, a `configSchema`, and a `create` function that builds an
22adapter in a child scope. V2 adapter implementations live in
23`apps/server/src/orchestration-v2/Adapters/` and conform to
24[`ProviderAdapter.ts`](../../apps/server/src/orchestration-v2/ProviderAdapter.ts). Read the driver plus its adapter to see how a specific agent's
25transport, config, and event shapes are mapped.
26
27## Runtime context
28
29Every adapter uses `apps/server/src/provider/RuntimeInstructions.ts` to identify T3 Code and
30the harness, and describe Markdown image/video embeds. Codex includes it in developer
31instructions; Claude appends it to its system preset; OpenCode sends it in each prompt's
32`system` field. Grok and Antigravity append a separate text block to ACP prompts,
33which have no system-message field. Cursor supplies context through its SDK prompt. This does not change the stored user message.
34
35Per-turn context includes the current model when known. Codex includes reasoning effort;
36Grok includes it when explicitly selected for the turn. Claude's session-level context omits model and effort because they can
37change during a session. OpenCode variants are not assumed to be reasoning-effort levels.
38
39## Codex async questions
40
41Codex 0.153 exposes `request_user_input_async` through `item/started` and `item/completed`
42notifications. The item has `type: "agentMessage"`, `delivery: "async"`, and a `questions` array.
43Each question has a `title` and an optional `options` array of strings. The tool returns `{"accepted":true}`
44without waiting. This is separate from the `item/tool/requestUserInput` server request.
45See the [Codex tool handler](https://github.com/openai/codex/blob/d979df154cf60e13eafb5453e75b6d84f21c67bf/codex-rs/core/src/tools/handlers/request_user_input_async.rs).
46
47The Codex adapter persists completed question items as `user_input_request` turn items and
48runtime requests with `responseCapability: { type: "message" }`. Their execution nodes do not
49block the run. Web, desktop, and mobile use their normal question panels. The request stays
50pending after the turn finishes, the provider exits, or the server restarts.
51
52`runtime-request.respond` reads the persisted request and question item, validates required
53answers, and commits the resolution and a user message in one transaction. Repeating the same
54command returns its receipt without posting the answer twice. The normal message path starts or
55resumes a run, queues behind active work, or steers an active turn when its adapter supports it.
56Blocking questions retain the provider's live response path.
57
58## Provider diagnostics
59
60Native event logs retain protocol lifecycle events, responses, and failures. Token deltas and
61duplicate raw frames are filtered before adapters copy or redact their payloads. The filter accepts
62both legacy native events and the decoded protocol envelopes used by v2. Decode failures remain
63visible through their diagnostic frames.
64
65Log payloads have a 64 KiB encoded budget. Large or deeply nested payloads become structural
66summaries that retain routing identifiers, methods, status, and error fields. Traversal is bounded
67before redaction and serialization, so a large response cannot create several full copies just to
68write a diagnostic record. These limits apply to logging; provider event handling is unchanged.
69
70Codex thread resume requests exclude historical turns when only the thread identity and update time
71are needed. The adapter decodes those metadata fields without loading a duplicate transcript.
72Its initialization capabilities opt out of `turn/diff/updated`: T3 derives diffs from checkpoints
73and does not consume the provider's cumulative diff notifications. The logger also filters those
74notifications before traversal when an older provider still sends them.
75
76## Registry and routing
77
78Two registries separate configuration from live processes:
79
80- [`ProviderInstanceRegistry`][instances] keys configured instances by `ProviderInstanceId`. Creating
81 one looks up the driver by `driverKind`, decodes `entry.config` with that driver's schema, opens a
82 child scope, and calls `driver.create`.
83- [`ProviderAdapterRegistry`][registry] resolves an instance ID to its live adapter via
84 `getByInstance`.
85
86[`ProviderService`][service] sits on top. It combines the adapter registry with the provider session
87directory to route session and turn operations for a thread, so callers name a thread, not an agent.
88
89`ProviderService.sendTurn` expands [assistant citations](./assistant-citations.md) into quoted
90reference data before dispatching to any adapter. Bound user comments remain distinct from the quoted
91assistant text. Persisted messages keep their serialized links.
92
93Adding a driver means writing the driver plus adapter and adding it to `BUILT_IN_DRIVERS`. No
94orchestration, contract, or client change is required for the common case.
95
96### Grok health check
97
98`checkGrokProviderStatus` never opens an ACP session. It runs `grok --version`, then `grok models`
99for login state and model slugs, then a single ACP `initialize` and reads models from
100`_meta.modelState`. `authenticate` and `session/new` are skipped on purpose: `authenticate` can open
101a browser login and `session/new` boots every configured MCP server, both of which made background
102probes hang or surprise the user. A failed `initialize` degrades to `warning` with the CLI's model
103list instead of persisting `error` over a working install. The built-in `grok-build` slug is the
104CLI's product name, not an ACP model id. `applyGrokAcpModelSelection` treats it as "keep the
105session's current model" and never sends it in `session/set_model`.
106
107## Antigravity ownership and protocol
108
109[`AntigravityDriver`][antigravity] uses Google's official ACP executable. The instance config
110selects the ACP auth method: `oauth-personal` (default), `oauth-business`, `gemini-api-key`, or
111`agent-platform`. The two OAuth methods share the loopback sign-in flow below. The API key
112methods pass the configured key to the agent as `GEMINI_API_KEY` or `GOOGLE_API_KEY` and never
113open a browser. A GCP project and location are written to the profile's `settings.json` on each
114launch. The driver never reuses CLI credentials or ambient `GOOGLE_*` variables and never falls
115back to another method. Antigravity is disabled by default and supports multiple provider
116instances. The open driver and instance identifiers require no database migration.
117
118### Runtime installation
119
120[`AntigravityInstallation`][antigravity-installation] belongs to the environment, outside
121WebSocket and provider-instance scopes. Instances share an explicit download operation and the
122completed runtime. Client disconnects and instance rebuilds do not cancel installation.
123
124The fixed [release table][antigravity-release] contains official Google URLs, SHA-256 hashes,
125archive sizes, and the exact executable pair for each published host. Downloads stream to disk.
126Lazy `yauzl` entry streams extract only that pair, with member names, types, duplicates, and
127sizes checked. Validation runs ACP `initialize` in a temporary profile without authentication
128or a session. Progress updates are bounded, not sent for every network chunk.
129
130Complete releases live in immutable version directories under the T3 home
131`tools/antigravity-acp/<platform>-<arch>/versions`. An atomic `active.json` change selects the
132release for new processes. Each process holds a version lease until it exits. Updates do not
133replace running executables. Removal refuses active leases or explicit binary paths that still
134reference the managed files. Failure or cancellation removes owned partial files, not the
135previous release or account data.
136
137Resolution order is explicit `binaryPath`, active managed release, then the instance's `PATH`.
138An invalid explicit path fails without fallback. Manual installations are never changed by the
139installer. Every launch pins `ANTIGRAVITY_HARNESS_PATH` to the selected executable's sibling.
140
141### Google profiles and sign-in
142
143Each instance owns a stable profile at
144`<stateDir>/providers/antigravity/<sha256(instanceId)>`.
145[`antigravityAuthSupport.ts`][antigravity-auth-support] sets `GEMINI_HOME` to this directory and
146`AGY_ACP_FORCE_FILE_STORAGE=1` after merging instance environment variables. File storage avoids
147the official macOS keychain entry being shared across instances. Profile directories use mode
148`0700` on POSIX. This is file storage, not an encrypted keychain. Windows uses the host profile's
149filesystem permissions.
150
151The launch environment removes API-key and cloud-billing variables, disables inherited
152environment extension, sets `PYTHONUNBUFFERED=1`, and controls `BROWSER`. A tested Node or
153Electron-as-Node helper prevents the official agent from opening a browser on the environment.
154The same launch factory serves setup, health checks, chat, and text generation.
155
156The official agent prints a non-JSON OAuth line on stderr in version 1.1.1. Earlier versions
157print it on stdout. T3 accepts the exact native prefix on either stream and its browser-helper
158marker on stderr. Fragmented lines are joined and bounded. Other malformed protocol output
159remains fatal. Authorization URLs are validated before use. Other stderr is discarded because
160it can contain OAuth data. Normal work rejects an interactive login request with a
161sign-in-required error instead of waiting for consent. A rejected stderr callback fails pending
162ACP requests and closes the owned process.
163
164[`AntigravityAuth`][antigravity-auth] owns each sign-in process and deadline in the instance
165scope. Only the initiating T3 auth session receives its URL and flow ID or can complete or
166cancel it. Other clients receive busy state without those values. Subscriptions follow
167controller replacement when settings rebuild an instance.
168
169For remote completion, the client sends the full return URL through the typed setup RPC.
170The server validates the pending loopback origin, port, root path, and single matching state
171before forwarding once to the owned listener. It does not probe the listener or follow
172redirects. Google's process owns PKCE, token exchange, refresh, and storage. Callback HTTP
173success is not authentication success. The controller waits for authenticated session setup
174and catalog discovery. Cancellation closes the process instead of sending a synthetic denial.
175
176Auth RPCs `provider.auth.start`, `complete`, `cancel`, `logout`, and `subscribe` require
177`orchestration:operate`. Install `start`, `cancel`, and `remove` use that scope too.
178`provider.install.subscribe` and public provider snapshots require `orchestration:read`.
179[`providerSetup.ts`][provider-setup] defines the operation IDs, states, and safe errors.
180
181Sign-out closes process admission for the instance, stops provider bindings through
182[`ProviderAuthService`][provider-auth-service], then stops owned startup and helper processes.
183A fresh official process calls `initialize` and native `logout` without authenticating.
184Only then does the provider clear auth, models, commands, skills, and workspace metadata.
185Thread history and native session files remain. Settings sign-out and a text-only `/logout`
186use this same path. The command is handled before model prompting or title generation.
187Disabling an instance closes its processes but keeps credentials. Account replacement is
188explicit sign-out followed by sign-in.
189
190### Sessions, models, and client capabilities
191
192[`AntigravityAdapter`][antigravity-adapter] owns one ACP process per active thread. It uses
193native `session/resume` without transcript replay and reapplies the persisted model and
194permission mode after new or resumed setup. An unavailable explicit model fails instead of
195accepting the native default. Steering cancels the previous prompt, waits for its result and
196event drain, then sends the replacement. Native background commands use T3's existing
197background-task state.
198
199The permission mapping is `approval-required` and `auto` to `default`, `auto-accept-edits` to
200`auto_edit`, and `full-access` to `yolo`. Native requests still need replies in `yolo`.
201`interaction_` requests are user questions, not approvals. T3 keeps opaque option IDs in
202`UserInputQuestion.options[].value` and sets `allowCustomAnswer=false`. Both clients preserve
203these values. Ordinary approval replies use only offered option IDs, including `allow_always`
204only when present. Existing providers keep their prior behavior when the optional fields are
205absent.
206
207`showInteractionModeToggle=false` keeps native `/plan` separate from T3 Plan mode.
208`supportsConversationRollback=false` hides unsupported client actions and makes checkpoint
209revert fail before filesystem changes. Checkpoint capture and diffs remain supported.
210
211Automatic status refreshes, reconnects, and workspace checks do not open catalog sessions.
212Health probes use `initialize` only. Disabled instances do not run background probes.
213An explicit `serverRefreshProviders` request with `refreshModels: true` calls the driver's
214optional `refreshModels` operation. Antigravity opens a short-lived catalog session under the
215instance's process admission guard, uses saved credentials, publishes models and commands,
216then closes the process. An interactive login request fails with sign-in required. Web's
217**Refresh provider status** and mobile's **Refresh models** actions request this operation.
218Account access starts unknown and becomes authenticated after successful session setup,
219including an explicit model refresh.
220The [provider snapshot][antigravity-provider] takes models and commands from setup and native
221updates. It preserves returned Gemini model IDs, labels, order, and thinking-level choices.
222ACP `config_option_update` notifications and `session/set_config_option` responses replace
223the instance's model catalog. Child session notifications do not change the root catalog.
224The registry treats a successful empty catalog as authoritative and clears cached metadata
225after sign-out. It must not retain a previous account's models. Cached models do not prove
226current access. The auth response does not supply an email, plan tier, or reliable quota.
227
228Some upstream failures arrive as assistant text followed by `end_turn`. Preserve that message
229without treating model-written text as a structured error or successful task completion.
230
231### Text generation
232
233[`AntigravityTextGeneration`][antigravity-text] implements titles, branch names, commit text,
234and PR text through the same instance and Google sign-in. Each helper uses a temporary empty
235workspace, no injected MCP servers, native `default` mode, and explicit denial of tools and
236questions. Output is bounded, parsed against the existing schemas, and sanitized. Cancellation,
237timeout, and sign-out close the process. Cleanup removes only that helper's verified temporary
238native session files.
239
240The official agent has no verified hard no-tools setting. Global hooks and MCP configuration
241can run before a prompt. Helpers check the profile's `config/hooks.json` and
242`config/mcp_config.json` before launch and reject nonempty, malformed, or oversized
243configuration. `supportsTextGeneration=false` keeps such an instance out of system-model
244pickers. Empty managed profiles are supported. Do not describe prompt-time denial as a native
245sandbox.
246
247## OpenCode server ownership and catalog
248
249Native OpenCode event logs skip text deltas and running tool snapshots. Pending, completed,
250and failed tool states remain, including final output and errors. This filter does not change
251events sent to clients or orchestration storage.
252
253Each OpenCode provider instance owns one lazy local server for catalog discovery and
254text-generation helpers through [`OpenCodeServerOwner.ts`][opencode-server-owner]. Concurrent
255borrowers share startup. The server closes 30 seconds after the last borrower releases it, or
256when the provider instance closes. A failed or exited process can be started again on the next
257use. An externally configured OpenCode server remains externally owned.
258
259The local server and its SDK clients use one resolved password. An explicit provider password
260overrides `OPENCODE_SERVER_PASSWORD` in the spawned environment. Without an explicit password,
261the client uses the password from the environment that the process inherits. External servers use
262only their explicit provider password and never inherit the host's local password.
263
264Every server connection must pass the authenticated `/global/health` check before inventory or
265session operations start. The response must contain a valid version at or above 1.14.19. Local
266owners cache this result for the lifetime of the spawned process. External actions check once when
267they create their server connection, not for each model or SDK request.
268
269Chat adapters keep their own server per thread. They register a thread-specific `t3-code` MCP
270connection, while OpenCode stores MCP connections by directory. Sharing these chat servers
271without changing MCP routing would let two threads in one directory replace each other's
272connection.
273
274Chat adapters send the runtime mode as a session ruleset, but upstream OpenCode evaluates
275doom-loop and subagent asks against the agent ruleset only. In full access the adapter answers
276those asks itself so the user never sees an approval they already granted. It replies `once`
277rather than `always` because OpenCode stores `always` grants per directory, and on a shared
278external server that would widen what a supervised thread in the same directory may do.
279
280OpenCode loads its catalog through the HTTP API when an enabled provider instance starts. The
281provider registry keeps the snapshot in memory and persists it in the existing per-instance cache.
282Each `subscribeServerConfig` connection refreshes all providers, so a client reconnect reloads the
283OpenCode catalog from the current helper. The `serverRefreshProviders` request also refreshes it.
284Periodic OpenCode probes remain disabled. OpenCode reads credentials for each inventory request,
285but its native configuration files can remain cached for the lifetime of the helper process. The
286helper closes 30 seconds after its last inventory or text-generation borrower releases it. A
287refresh after that idle period starts a new helper and reads file changes. Repeated refreshes and
288active text-generation work can extend process reuse. Changes to the provider configuration or
289environment replace the instance and start a new discovery. Changes to unrelated settings only
290update snapshot enrichment. Other providers retain their existing refresh policy.
291
292T3 Code does not own an external OpenCode process. Native configuration changes there can require
293an external reload or restart before T3 Code's next refresh sees them.
294
295The shared server's idle shutdown does not clear the catalog. Failed discovery keeps the last
296known models, slash commands, and skills through the registry's existing merge rules. A successful
297empty inventory is authoritative. Existing threads keep their explicit model identifier and
298options when catalog metadata is missing; the catalog is not permission to choose a different
299model for a thread.
300
301## Model manifest
302
303The model picker's legacy section is driven by `apps/server/src/provider/model-manifest.json`, which
304lists the current (non-legacy) model slugs per driver kind. The `ModelManifest` service
305(`apps/server/src/provider/ModelManifest.ts`) refreshes that data from the same file on `main` via
306raw.githubusercontent.com, so moving a model in or out of the legacy section is a commit, not a
307release. Preference order is remote fetch, then the on-disk copy of the last successful fetch (in
308the state directory), then the bundled copy. Fetches are TTL-gated, run concurrently with provider
309probes, respect the `enableProviderUpdateChecks` setting, and never fail a provider check. The
310Codex and Claude drivers apply the classification to every snapshot with `applyModelManifest`;
311driver kinds absent from the manifest have no legacy concept.
312
313## Attachment access
314
315The server stores uploaded attachments in its attachment directory, outside the project workspace.
316`ProviderService` adds the absolute path of each attachment to the turn text, then passes every
317attachment to the provider adapter. Each adapter decides what its provider ingests natively:
318
319- Codex, Claude, Cursor, and Grok send images as native image inputs and skip generic files. For
320 these providers, generic files reach the agent only as file paths in the turn text.
321- Antigravity sends BMP, JPEG, PNG, and WebP images and common audio formats as native blocks,
322 text files as embedded resources, and PDFs as resource links. Other files are rejected with an
323 error instead of being dropped. The session advertises the ACP client file system capability,
324 so workspace reads and writes come back through `fs/read_text_file` and `fs/write_text_file`
325 and are confined to the workspace and the attachments directory.
326- OpenCode sends PNG/JPEG/GIF/WebP images, text files, and PDFs up to 20 MB as native file parts
327 with their real mime type. Everything else (ZIP and other binaries, image formats model APIs
328 reject, oversized files) falls back to the file path in the turn text, like the other providers.
329- Antigravity sends BMP/JPEG/PNG/WebP images as native image blocks, UTF-8 text as embedded
330 resources, and PDFs as local resource links. Text is limited to 1 MiB per file, images to
331 10 MiB each, and all attachments to 50 MiB per turn. Unsupported formats or oversized inputs
332 fail explicitly. Native path permissions still apply to PDFs.
333
334Claude receives the attachment directory as an allowed additional directory. Codex keeps its
335configured sandbox policy, so access depends on that policy and the selected runtime mode. OpenCode
336allows all paths in full-access mode and requests approval for directories outside the workspace in
337restricted modes. Cursor and Grok use their own provider permission rules.
338
339The server does not copy attachments into a project or bypass provider approval rules. If an agent
340cannot read an attachment, the user must approve the access or select a runtime mode that permits it.
341
342Updated attachment schemas tolerate unknown attachment members, but old image-only clients still
343cannot decode messages that contain file attachments. Client file-picking rollouts must account for
344this limit.
345
346Do not run an old image-only server against state that contains file attachments. Replay decodes
347each persisted event before projection. A file-bearing event can make `ProjectionPipeline` bootstrap
348and `OrchestrationEngine` startup fail for the entire environment, not only the affected thread.
349
350## How provider work is requested
351
352Clients never call a provider directly. They dispatch orchestration commands over the RPC method
353`orchestration.dispatchCommand`, defined with the rest of the orchestration surface in
354[`orchestration.ts`][contracts]. The client-dispatchable provider-facing commands are
355`thread.turn.start`, `thread.turn.interrupt`, `thread.approval.respond`,
356`thread.user-input.respond`, `thread.checkpoint.revert`, and `thread.session.stop`, plus the mode
357setters `thread.runtime-mode.set` and `thread.interaction-mode.set`.
358
359The engine persists an event for the command, and a server-side reactor performs the provider call.
360Provider output comes back as internal commands such as `thread.message.assistant.delta` and
361`thread.session.set`, which clients observe through `orchestration.subscribeThread`. See
362[overview.md](./overview.md) for the command/event loop.
363
364## Server-side workers
365
366Provider work flows through three queue-backed workers. All three are built with
367`makeDrainableWorker` from [`DrainableWorker.ts`][worker] and expose `drain` for deterministic test
368synchronization.
369
3701. [`ProviderRuntimeIngestion`][ingest] consumes provider runtime streams and emits orchestration
371 commands.
3722. [`ProviderCommandReactor`][cmd] reacts to orchestration intent events and dispatches provider
373 calls.
3743. [`CheckpointReactor`][checkpoint] captures workspace checkpoints on turn start and completion, and
375 performs reverts.
376
377### Buffered assistant delivery
378
379A thread in `buffered` assistant delivery mode accumulates assistant text instead of streaming each
380delta. The buffer is not held until turn completion. In [`ProviderRuntimeIngestion`][ingest],
381`MAX_BUFFERED_ASSISTANT_CHARS` is 24,000: the append that would exceed it invalidates the buffer and
382spills the whole accumulated text as one delta. The buffer also flushes at interaction boundaries,
383when a request opens (approval) or user input is requested, via
384`flushBufferedAssistantMessagesForTurn`.
385
386[drivers]: ../../apps/server/src/provider/builtInDrivers.ts
387[codex]: ../../apps/server/src/provider/Drivers/CodexDriver.ts
388[claude]: ../../apps/server/src/provider/Drivers/ClaudeDriver.ts
389[cursor]: ../../apps/server/src/provider/Drivers/CursorDriver.ts
390[grok]: ../../apps/server/src/provider/Drivers/GrokDriver.ts
391[opencode]: ../../apps/server/src/provider/Drivers/OpenCodeDriver.ts
392[antigravity]: ../../apps/server/src/provider/Drivers/AntigravityDriver.ts
393[antigravity-adapter]: ../../apps/server/src/provider/Layers/AntigravityAdapter.ts
394[antigravity-provider]: ../../apps/server/src/provider/Layers/AntigravityProvider.ts
395[antigravity-installation]: ../../apps/server/src/provider/AntigravityInstallation.ts
396[antigravity-release]: ../../apps/server/src/provider/antigravityRelease.ts
397[antigravity-auth]: ../../apps/server/src/provider/AntigravityAuth.ts
398[antigravity-auth-support]: ../../apps/server/src/provider/antigravityAuthSupport.ts
399[antigravity-text]: ../../apps/server/src/textGeneration/AntigravityTextGeneration.ts
400[provider-auth-service]: ../../apps/server/src/provider/Layers/ProviderAuthService.ts
401[provider-setup]: ../../packages/contracts/src/providerSetup.ts
402[opencode-server-owner]: ../../apps/server/src/provider/OpenCodeServerOwner.ts
403[adapter]: ../../apps/server/src/provider/Services/ProviderAdapter.ts
404[instances]: ../../apps/server/src/provider/Services/ProviderInstanceRegistry.ts
405[registry]: ../../apps/server/src/provider/Services/ProviderAdapterRegistry.ts
406[service]: ../../apps/server/src/provider/Layers/ProviderService.ts
407[contracts]: ../../packages/contracts/src/orchestration.ts
408[worker]: ../../packages/shared/src/DrainableWorker.ts
409[ingest]: ../../apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
410[cmd]: ../../apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
411[checkpoint]: ../../apps/server/src/orchestration/Layers/CheckpointReactor.ts
audits/orchestrator-v2/2026-09-04/worktree-final-manifest.json+310−0
@@ -0,0 +1,310 @@
1{
2 "capturedAt": "2026-09-04T19:12:05.098110+00:00",
3 "head": "8af5734365f7c45bc08b57066dbae42f9f7d4235",
4 "dirtyFiles": 59,
5 "driftFiles": [
6 "apps/server/src/orchestration-v2/ProjectionSettlement.test.ts",
7 "apps/server/src/relay/AgentAwarenessRelay.test.ts",
8 "apps/server/src/relay/AgentAwarenessRelay.ts",
9 "docs/internals/mobile-development.md",
10 "docs/internals/performance-regressions.md",
11 "docs/internals/providers.md"
12 ],
13 "files": {
14 "apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift": {
15 "sha256": "9cad9e025396de88f74ee8656149f52033d2389addc482aaf6ad76307075608c",
16 "bytes": 6702,
17 "overlayPath": "worktree-overlay/apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift.snapshot"
18 },
19 "apps/mobile/scripts/notification-center-manager.test.ts": {
20 "sha256": "57d6ffe2e8e6ea9750ee7e47d1cebc81524e56968109d9cd800ea8afe33c1c13",
21 "bytes": 2421,
22 "overlayPath": "worktree-overlay/apps/mobile/scripts/notification-center-manager.test.ts.snapshot"
23 },
24 "apps/server/src/git/GitManager.ts": {
25 "sha256": "693fc385a3c2556caafccc771f5e065252d91c67b88750ae60a8fc7f73486e6d",
26 "bytes": 97986,
27 "overlayPath": "worktree-overlay/apps/server/src/git/GitManager.ts.snapshot"
28 },
29 "apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts": {
30 "sha256": "b9dc88f850b8a70095555c541d28d6a65ae04d63e52c782aac5ec0237ad1da71",
31 "bytes": 141892,
32 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts.snapshot"
33 },
34 "apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts": {
35 "sha256": "ff88decb39c96729746004ed5cf5c92b72feec96914d9991460191ca82ad8125",
36 "bytes": 219173,
37 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts.snapshot"
38 },
39 "apps/server/src/orchestration-v2/Orchestrator.ts": {
40 "sha256": "85672e62e3464aec751ed68119b832ce559c7f3b5e2d59d3b44f957986ea5472",
41 "bytes": 277609,
42 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/Orchestrator.ts.snapshot"
43 },
44 "apps/server/src/orchestration-v2/ProjectionSettlement.test.ts": {
45 "sha256": "60564e08840f212e3e3c210f08df2f15daa3a91c9e69567b3c3de8aeb3645937",
46 "bytes": 13715,
47 "overlayPath": "worktree-drift/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts.snapshot"
48 },
49 "apps/server/src/orchestration-v2/ProjectionStore.ts": {
50 "sha256": "c071b0a6132e7e6512bf083537180b806e695731302db98b1e688ee5245c933e",
51 "bytes": 157823,
52 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ProjectionStore.ts.snapshot"
53 },
54 "apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts": {
55 "sha256": "d65aab80d32a565d6c0f10bc972e7538685cb62e49824a0d5ea95410161677a4",
56 "bytes": 10285,
57 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts.snapshot"
58 },
59 "apps/server/src/orchestration-v2/ThreadSettlementService.test.ts": {
60 "sha256": "aba58edd845d2393af27db9b8565df60a4a51ae67fb2bc6f23d339fe2ea522f9",
61 "bytes": 26347,
62 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts.snapshot"
63 },
64 "apps/server/src/orchestration-v2/ThreadSettlementService.ts": {
65 "sha256": "409429553c929e0205932e7b5ba144b7ff47ec1df4ac53baabd32c988dc70094",
66 "bytes": 15478,
67 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.ts.snapshot"
68 },
69 "apps/server/src/orchestration-v2/runtimeLayer.test.ts": {
70 "sha256": "868a2e87f7c6eb809b324c76a03efea36eb378b6d01273bbdd65301305460f2f",
71 "bytes": 66554,
72 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/runtimeLayer.test.ts.snapshot"
73 },
74 "apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson": {
75 "sha256": "a316aa20ee8a26a0efca75c2af3cd4040452c520e9b0db75826a7ec949a5d2ee",
76 "bytes": 16898,
77 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson.snapshot"
78 },
79 "apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson": {
80 "sha256": "5b9419d1cb8b071c1cc9f36b0015cf0e1593600d8036e195d2ffad369c6d3cd3",
81 "bytes": 16385,
82 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson.snapshot"
83 },
84 "apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson": {
85 "sha256": "b42c180ee16954924242db7f8adb40aab325001d1fe786a7335a14b022c90440",
86 "bytes": 28613,
87 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson.snapshot"
88 },
89 "apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson": {
90 "sha256": "6ab96dd5ea94f7215c541c64bd3faf7eef6fb64d032333d77fdfb9ee620374ba",
91 "bytes": 232594,
92 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson.snapshot"
93 },
94 "apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson": {
95 "sha256": "6811a12c275fa69d8d2a3bebaba15052173021f18f9de9dc3daa0208162d3e24",
96 "bytes": 24937,
97 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson.snapshot"
98 },
99 "apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson": {
100 "sha256": "ee3d33f947e038ab9bb8f5791ce9724cfb646b160ed23f24526ceaa02d52425a",
101 "bytes": 16387,
102 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson.snapshot"
103 },
104 "apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson": {
105 "sha256": "abcbc5a69dec6d42bae11637372c32923ca7a549b8017e168a178ca5b320a3e7",
106 "bytes": 9533,
107 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson.snapshot"
108 },
109 "apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson": {
110 "sha256": "f44c288981cccbf70f769a69e59404980fa85711143903148c1f6c26ef6fd4df",
111 "bytes": 183171,
112 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson.snapshot"
113 },
114 "apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson": {
115 "sha256": "eca073e6fc250a10bd1fac0dd13337fef6b19803be256e2cd5f24438557953a3",
116 "bytes": 11423,
117 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson.snapshot"
118 },
119 "apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson": {
120 "sha256": "c5243474180b9c89c794d55d605cb819f204e1c27d7e6fd66fb49d113ba0d767",
121 "bytes": 7152,
122 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson.snapshot"
123 },
124 "apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson": {
125 "sha256": "3adf9b93f92b4bfbf554a73c1c04130562beab39a6fbf161c876a85db8c526d4",
126 "bytes": 9518,
127 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson.snapshot"
128 },
129 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson": {
130 "sha256": "b3804e921d1c4ed6d4707f783a5b25a0516c6846f7319f85af65d9a1af489ac4",
131 "bytes": 6175,
132 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson.snapshot"
133 },
134 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson": {
135 "sha256": "1218fecab334200622c6d1d7a7fed16ef0a7559ca6d1cb4bf8d2565adf82d901",
136 "bytes": 31674,
137 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson.snapshot"
138 },
139 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson": {
140 "sha256": "99dcffc4d3b2fcbc9796e00d885a1a8a7affeae2aeb922063646cd2a53d21c11",
141 "bytes": 286110,
142 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson.snapshot"
143 },
144 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson": {
145 "sha256": "5fd7ee66840cdc4f072b652d24949765278ddd7278219ebd5912f626c646fa44",
146 "bytes": 43112,
147 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson.snapshot"
148 },
149 "apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson": {
150 "sha256": "3f11be00e9e67ddcabfad00d00d86af1502d5db74256567ab9466c86c99ab547",
151 "bytes": 37612,
152 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson.snapshot"
153 },
154 "apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson": {
155 "sha256": "3c60dad0995ef22514873c1bded309d2dd7974ea6abdc56db55b85031a506aa1",
156 "bytes": 60794,
157 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson.snapshot"
158 },
159 "apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson": {
160 "sha256": "452d53eccc740e1b3cb29733cebf5ccceef0ee0765168eaf3d69c0d57408eccc",
161 "bytes": 32459,
162 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson.snapshot"
163 },
164 "apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson": {
165 "sha256": "3349e46191ffb143477c827f175f945f88f13824fe14384d98543b58e7ace721",
166 "bytes": 37191,
167 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson.snapshot"
168 },
169 "apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson": {
170 "sha256": "97742cacd751f0c83004d1eca8ecda3e33847a2708c934d64fd2a05ec477bff4",
171 "bytes": 21736,
172 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson.snapshot"
173 },
174 "apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson": {
175 "sha256": "dd4b0e9a1cc61275d47b0f67ab84b3f5756d6b2aab8f66b8fc21a9614c9dd320",
176 "bytes": 46460,
177 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson.snapshot"
178 },
179 "apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson": {
180 "sha256": "3dfbafd96734a63a47c6f2604d7da0b07336d7e0e6b59025d06835160de0af04",
181 "bytes": 19068,
182 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson.snapshot"
183 },
184 "apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson": {
185 "sha256": "a5761cde4262a93e731c4a935f527e4d1400481e64c1d2d4ff76305a7b68e4d2",
186 "bytes": 5813,
187 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson.snapshot"
188 },
189 "apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson": {
190 "sha256": "c92362c1432111ddb8dc8f4c8b2667783e19e395e210a553edcd5e6aefd73ab8",
191 "bytes": 11566,
192 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson.snapshot"
193 },
194 "apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson": {
195 "sha256": "95f20a210f99ca744003d3250964f3a57e8fd3395d478e28beec3c65f6b2d800",
196 "bytes": 7113,
197 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson.snapshot"
198 },
199 "apps/server/src/persistence/Migrations.ts": {
200 "sha256": "4d103bc15c74cf5ccf27e84aa9941d9771f28531c2c0f29a53c9930e6930f37a",
201 "bytes": 10221,
202 "overlayPath": "worktree-overlay/apps/server/src/persistence/Migrations.ts.snapshot"
203 },
204 "apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts": {
205 "sha256": "b72c175833de68f01785cc7769a186cc3c736356eb9da2996562fdd763c1c428",
206 "bytes": 1008,
207 "overlayPath": "worktree-overlay/apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts.snapshot"
208 },
209 "apps/server/src/provider/Layers/EventNdjsonLogger.test.ts": {
210 "sha256": "b0c5c75cdb2fa245127595a8afacf94e15ce07a631db27cb0503f5917e4524e1",
211 "bytes": 29869,
212 "overlayPath": "worktree-overlay/apps/server/src/provider/Layers/EventNdjsonLogger.test.ts.snapshot"
213 },
214 "apps/server/src/provider/Layers/EventNdjsonLogger.ts": {
215 "sha256": "c77e7b7d0d528be5e742dc54944c2241497e9a61144a79cf7311bbc15c53c6f4",
216 "bytes": 26552,
217 "overlayPath": "worktree-overlay/apps/server/src/provider/Layers/EventNdjsonLogger.ts.snapshot"
218 },
219 "apps/server/src/relay/AgentAwarenessRelay.test.ts": {
220 "sha256": "d2c3a409560c273588ccdcc1f82a1fed9c27bc87d881ab1431b40148a3374c0c",
221 "bytes": 22116,
222 "overlayPath": "worktree-drift/apps/server/src/relay/AgentAwarenessRelay.test.ts.snapshot"
223 },
224 "apps/server/src/relay/AgentAwarenessRelay.ts": {
225 "sha256": "19250510d3b875e124c33f2386035f38007592b53338853035ad122d89a4546e",
226 "bytes": 27406,
227 "overlayPath": "worktree-drift/apps/server/src/relay/AgentAwarenessRelay.ts.snapshot"
228 },
229 "apps/server/src/server.ts": {
230 "sha256": "8e65974a03911d42a322e04aa864d58cc2b1f3387bb955c0a6487ac3bb985666",
231 "bytes": 33871,
232 "overlayPath": "worktree-overlay/apps/server/src/server.ts.snapshot"
233 },
234 "apps/server/src/vcs/GitVcsDriver.ts": {
235 "sha256": "6dc2f15294725c8eff128cf29da0ef6d6020a42f9dc10777c3b0fcc5919cc93d",
236 "bytes": 29866,
237 "overlayPath": "worktree-overlay/apps/server/src/vcs/GitVcsDriver.ts.snapshot"
238 },
239 "apps/server/src/vcs/GitVcsDriverCore.test.ts": {
240 "sha256": "ee5f8d95dc326b9e651906d22f7f4b254c3ac9ee917e83d5d290a55ba6240417",
241 "bytes": 86150,
242 "overlayPath": "worktree-overlay/apps/server/src/vcs/GitVcsDriverCore.test.ts.snapshot"
243 },
244 "apps/server/src/vcs/GitVcsDriverCore.ts": {
245 "sha256": "158e93d8acb1f86026efe31e3b7425bde4217284e4b8e9a0871699e032a90151",
246 "bytes": 118521,
247 "overlayPath": "worktree-overlay/apps/server/src/vcs/GitVcsDriverCore.ts.snapshot"
248 },
249 "apps/web/src/components/CommandPalette.tsx": {
250 "sha256": "43289676c4605eecca56d3ceac725abd31d7632236585f195e5e76b2a2106a74",
251 "bytes": 91906,
252 "overlayPath": "worktree-overlay/apps/web/src/components/CommandPalette.tsx.snapshot"
253 },
254 "apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx": {
255 "sha256": "7e9afd073410e02d57e2bf08180de818be9328b19bf8366fa2d21e379246f8b1",
256 "bytes": 6352,
257 "overlayPath": "worktree-overlay/apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx.snapshot"
258 },
259 "apps/web/src/components/ThreadStatusIndicators.test.ts": {
260 "sha256": "770c0ce42152993676577ee9d6519f28827a24ae23aa866b02311c890cc78017",
261 "bytes": 20742,
262 "overlayPath": "worktree-overlay/apps/web/src/components/ThreadStatusIndicators.test.ts.snapshot"
263 },
264 "apps/web/src/components/ThreadStatusIndicators.tsx": {
265 "sha256": "d7cbb9d3d54e11be727ba0eadb0d28c7719869422ee867df4af637d8709b8959",
266 "bytes": 23069,
267 "overlayPath": "worktree-overlay/apps/web/src/components/ThreadStatusIndicators.tsx.snapshot"
268 },
269 "apps/web/src/connection/desktopLocal.test.ts": {
270 "sha256": "feaf1bb33845837970adfca6a1b4f5ec4914a24d5701ae243aab2eabd029c596",
271 "bytes": 5566,
272 "overlayPath": "worktree-overlay/apps/web/src/connection/desktopLocal.test.ts.snapshot"
273 },
274 "apps/web/src/connection/desktopLocal.ts": {
275 "sha256": "47cd649eca399df82bb9d9bae9929ee6c008c0b94a6a3bedd859e419bbd3502f",
276 "bytes": 4316,
277 "overlayPath": "worktree-overlay/apps/web/src/connection/desktopLocal.ts.snapshot"
278 },
279 "docs/internals/mobile-development.md": {
280 "sha256": "151ba896ca235cabcbba735c6a9152d0d3846457bc4ca70dde49eff09b0dad11",
281 "bytes": 3997,
282 "overlayPath": "worktree-drift/docs/internals/mobile-development.md.snapshot"
283 },
284 "docs/internals/performance-regressions.md": {
285 "sha256": "c9cb5d90c8f2833249cc75bc614688586161a4de5fc626093ceca370fcf5d46f",
286 "bytes": 5312,
287 "overlayPath": "worktree-drift/docs/internals/performance-regressions.md.snapshot"
288 },
289 "docs/internals/providers.md": {
290 "sha256": "306025ee90d75a8b45e359786e91c58cee5e6f9571a72672cc7771c6227e9d3c",
291 "bytes": 27306,
292 "overlayPath": "worktree-drift/docs/internals/providers.md.snapshot"
293 },
294 "patches/expo-notifications@57.0.15.patch": {
295 "sha256": "f89252c2c08dc7b3acb8415f319ebbe15fff8c9f88a64ebe1830cd5ac5c9ac40",
296 "bytes": 4327,
297 "overlayPath": "worktree-overlay/patches/expo-notifications@57.0.15.patch.snapshot"
298 },
299 "pnpm-lock.yaml": {
300 "sha256": "c11bff52e74aaf56861eaa1dd01c3d8ff5224d71150dc70dbb225e8862192265",
301 "bytes": 821015,
302 "overlayPath": "worktree-overlay/pnpm-lock.yaml.snapshot"
303 },
304 "pnpm-workspace.yaml": {
305 "sha256": "b68fd66ae334d694ae9d4d500cb300b1fa0ce0a7513991c7821c4069b5b0b572",
306 "bytes": 6724,
307 "overlayPath": "worktree-overlay/pnpm-workspace.yaml.snapshot"
308 }
309 }
310}
audits/orchestrator-v2/2026-09-04/worktree-manifest.json+282−0
@@ -0,0 +1,282 @@
1{
2 "apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift": {
3 "sha256": "9cad9e025396de88f74ee8656149f52033d2389addc482aaf6ad76307075608c",
4 "bytes": 6702,
5 "overlayPath": "worktree-overlay/apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift.snapshot"
6 },
7 "apps/mobile/scripts/notification-center-manager.test.ts": {
8 "sha256": "57d6ffe2e8e6ea9750ee7e47d1cebc81524e56968109d9cd800ea8afe33c1c13",
9 "bytes": 2421,
10 "overlayPath": "worktree-overlay/apps/mobile/scripts/notification-center-manager.test.ts.snapshot"
11 },
12 "apps/server/src/git/GitManager.ts": {
13 "sha256": "693fc385a3c2556caafccc771f5e065252d91c67b88750ae60a8fc7f73486e6d",
14 "bytes": 97986,
15 "overlayPath": "worktree-overlay/apps/server/src/git/GitManager.ts.snapshot"
16 },
17 "apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts": {
18 "sha256": "b9dc88f850b8a70095555c541d28d6a65ae04d63e52c782aac5ec0237ad1da71",
19 "bytes": 141892,
20 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts.snapshot"
21 },
22 "apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts": {
23 "sha256": "ff88decb39c96729746004ed5cf5c92b72feec96914d9991460191ca82ad8125",
24 "bytes": 219173,
25 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts.snapshot"
26 },
27 "apps/server/src/orchestration-v2/Orchestrator.ts": {
28 "sha256": "85672e62e3464aec751ed68119b832ce559c7f3b5e2d59d3b44f957986ea5472",
29 "bytes": 277609,
30 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/Orchestrator.ts.snapshot"
31 },
32 "apps/server/src/orchestration-v2/ProjectionSettlement.test.ts": {
33 "sha256": "88775fb4845311c7be4f9eed971e4d6cff6e2e8601aea4564f5346a6f5ac54dc",
34 "bytes": 13834,
35 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts.snapshot"
36 },
37 "apps/server/src/orchestration-v2/ProjectionStore.ts": {
38 "sha256": "c071b0a6132e7e6512bf083537180b806e695731302db98b1e688ee5245c933e",
39 "bytes": 157823,
40 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ProjectionStore.ts.snapshot"
41 },
42 "apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts": {
43 "sha256": "d65aab80d32a565d6c0f10bc972e7538685cb62e49824a0d5ea95410161677a4",
44 "bytes": 10285,
45 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts.snapshot"
46 },
47 "apps/server/src/orchestration-v2/ThreadSettlementService.test.ts": {
48 "sha256": "aba58edd845d2393af27db9b8565df60a4a51ae67fb2bc6f23d339fe2ea522f9",
49 "bytes": 26347,
50 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts.snapshot"
51 },
52 "apps/server/src/orchestration-v2/ThreadSettlementService.ts": {
53 "sha256": "409429553c929e0205932e7b5ba144b7ff47ec1df4ac53baabd32c988dc70094",
54 "bytes": 15478,
55 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.ts.snapshot"
56 },
57 "apps/server/src/orchestration-v2/runtimeLayer.test.ts": {
58 "sha256": "868a2e87f7c6eb809b324c76a03efea36eb378b6d01273bbdd65301305460f2f",
59 "bytes": 66554,
60 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/runtimeLayer.test.ts.snapshot"
61 },
62 "apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson": {
63 "sha256": "a316aa20ee8a26a0efca75c2af3cd4040452c520e9b0db75826a7ec949a5d2ee",
64 "bytes": 16898,
65 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson.snapshot"
66 },
67 "apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson": {
68 "sha256": "5b9419d1cb8b071c1cc9f36b0015cf0e1593600d8036e195d2ffad369c6d3cd3",
69 "bytes": 16385,
70 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson.snapshot"
71 },
72 "apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson": {
73 "sha256": "b42c180ee16954924242db7f8adb40aab325001d1fe786a7335a14b022c90440",
74 "bytes": 28613,
75 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson.snapshot"
76 },
77 "apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson": {
78 "sha256": "6ab96dd5ea94f7215c541c64bd3faf7eef6fb64d032333d77fdfb9ee620374ba",
79 "bytes": 232594,
80 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson.snapshot"
81 },
82 "apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson": {
83 "sha256": "6811a12c275fa69d8d2a3bebaba15052173021f18f9de9dc3daa0208162d3e24",
84 "bytes": 24937,
85 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson.snapshot"
86 },
87 "apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson": {
88 "sha256": "ee3d33f947e038ab9bb8f5791ce9724cfb646b160ed23f24526ceaa02d52425a",
89 "bytes": 16387,
90 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson.snapshot"
91 },
92 "apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson": {
93 "sha256": "abcbc5a69dec6d42bae11637372c32923ca7a549b8017e168a178ca5b320a3e7",
94 "bytes": 9533,
95 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson.snapshot"
96 },
97 "apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson": {
98 "sha256": "f44c288981cccbf70f769a69e59404980fa85711143903148c1f6c26ef6fd4df",
99 "bytes": 183171,
100 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson.snapshot"
101 },
102 "apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson": {
103 "sha256": "eca073e6fc250a10bd1fac0dd13337fef6b19803be256e2cd5f24438557953a3",
104 "bytes": 11423,
105 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson.snapshot"
106 },
107 "apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson": {
108 "sha256": "c5243474180b9c89c794d55d605cb819f204e1c27d7e6fd66fb49d113ba0d767",
109 "bytes": 7152,
110 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson.snapshot"
111 },
112 "apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson": {
113 "sha256": "3adf9b93f92b4bfbf554a73c1c04130562beab39a6fbf161c876a85db8c526d4",
114 "bytes": 9518,
115 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson.snapshot"
116 },
117 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson": {
118 "sha256": "b3804e921d1c4ed6d4707f783a5b25a0516c6846f7319f85af65d9a1af489ac4",
119 "bytes": 6175,
120 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson.snapshot"
121 },
122 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson": {
123 "sha256": "1218fecab334200622c6d1d7a7fed16ef0a7559ca6d1cb4bf8d2565adf82d901",
124 "bytes": 31674,
125 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson.snapshot"
126 },
127 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson": {
128 "sha256": "99dcffc4d3b2fcbc9796e00d885a1a8a7affeae2aeb922063646cd2a53d21c11",
129 "bytes": 286110,
130 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_prior_turn/codex_transcript.ndjson.snapshot"
131 },
132 "apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson": {
133 "sha256": "5fd7ee66840cdc4f072b652d24949765278ddd7278219ebd5912f626c646fa44",
134 "bytes": 43112,
135 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_siblings/codex_transcript.ndjson.snapshot"
136 },
137 "apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson": {
138 "sha256": "3f11be00e9e67ddcabfad00d00d86af1502d5db74256567ab9466c86c99ab547",
139 "bytes": 37612,
140 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_continue/codex_transcript.ndjson.snapshot"
141 },
142 "apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson": {
143 "sha256": "3c60dad0995ef22514873c1bded309d2dd7974ea6abdc56db55b85031a506aa1",
144 "bytes": 60794,
145 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_merge_back_siblings/codex_transcript.ndjson.snapshot"
146 },
147 "apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson": {
148 "sha256": "452d53eccc740e1b3cb29733cebf5ccceef0ee0765168eaf3d69c0d57408eccc",
149 "bytes": 32459,
150 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/codex_transcript.ndjson.snapshot"
151 },
152 "apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson": {
153 "sha256": "3349e46191ffb143477c827f175f945f88f13824fe14384d98543b58e7ace721",
154 "bytes": 37191,
155 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/todo_list/codex_transcript.ndjson.snapshot"
156 },
157 "apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson": {
158 "sha256": "97742cacd751f0c83004d1eca8ecda3e33847a2708c934d64fd2a05ec477bff4",
159 "bytes": 21736,
160 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/codex_transcript.ndjson.snapshot"
161 },
162 "apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson": {
163 "sha256": "dd4b0e9a1cc61275d47b0f67ab84b3f5756d6b2aab8f66b8fc21a9614c9dd320",
164 "bytes": 46460,
165 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_restricted_granular/codex_transcript.ndjson.snapshot"
166 },
167 "apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson": {
168 "sha256": "3dfbafd96734a63a47c6f2604d7da0b07336d7e0e6b59025d06835160de0af04",
169 "bytes": 19068,
170 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_workspace_never/codex_transcript.ndjson.snapshot"
171 },
172 "apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson": {
173 "sha256": "a5761cde4262a93e731c4a935f527e4d1400481e64c1d2d4ff76305a7b68e4d2",
174 "bytes": 5813,
175 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/codex_transcript.ndjson.snapshot"
176 },
177 "apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson": {
178 "sha256": "c92362c1432111ddb8dc8f4c8b2667783e19e395e210a553edcd5e6aefd73ab8",
179 "bytes": 11566,
180 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/codex_transcript.ndjson.snapshot"
181 },
182 "apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson": {
183 "sha256": "95f20a210f99ca744003d3250964f3a57e8fd3395d478e28beec3c65f6b2d800",
184 "bytes": 7113,
185 "overlayPath": "worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/web_search/codex_transcript.ndjson.snapshot"
186 },
187 "apps/server/src/persistence/Migrations.ts": {
188 "sha256": "4d103bc15c74cf5ccf27e84aa9941d9771f28531c2c0f29a53c9930e6930f37a",
189 "bytes": 10221,
190 "overlayPath": "worktree-overlay/apps/server/src/persistence/Migrations.ts.snapshot"
191 },
192 "apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts": {
193 "sha256": "b72c175833de68f01785cc7769a186cc3c736356eb9da2996562fdd763c1c428",
194 "bytes": 1008,
195 "overlayPath": "worktree-overlay/apps/server/src/persistence/Migrations/059_OrchestrationV2ShellIndexes.ts.snapshot"
196 },
197 "apps/server/src/provider/Layers/EventNdjsonLogger.test.ts": {
198 "sha256": "b0c5c75cdb2fa245127595a8afacf94e15ce07a631db27cb0503f5917e4524e1",
199 "bytes": 29869,
200 "overlayPath": "worktree-overlay/apps/server/src/provider/Layers/EventNdjsonLogger.test.ts.snapshot"
201 },
202 "apps/server/src/provider/Layers/EventNdjsonLogger.ts": {
203 "sha256": "c77e7b7d0d528be5e742dc54944c2241497e9a61144a79cf7311bbc15c53c6f4",
204 "bytes": 26552,
205 "overlayPath": "worktree-overlay/apps/server/src/provider/Layers/EventNdjsonLogger.ts.snapshot"
206 },
207 "apps/server/src/relay/AgentAwarenessRelay.test.ts": {
208 "sha256": "3c88c74461ca24729e068b22f8e1905601f83ebf2816126f2ece8d34a0252ab0",
209 "bytes": 13695,
210 "overlayPath": "worktree-overlay/apps/server/src/relay/AgentAwarenessRelay.test.ts.snapshot"
211 },
212 "apps/server/src/relay/AgentAwarenessRelay.ts": {
213 "sha256": "8144b55804e90389fcedb5901a7594a5c43bff8b4e857dadac1d7c70fa177303",
214 "bytes": 25003,
215 "overlayPath": "worktree-overlay/apps/server/src/relay/AgentAwarenessRelay.ts.snapshot"
216 },
217 "apps/server/src/server.ts": {
218 "sha256": "8e65974a03911d42a322e04aa864d58cc2b1f3387bb955c0a6487ac3bb985666",
219 "bytes": 33871,
220 "overlayPath": "worktree-overlay/apps/server/src/server.ts.snapshot"
221 },
222 "apps/server/src/vcs/GitVcsDriver.ts": {
223 "sha256": "6dc2f15294725c8eff128cf29da0ef6d6020a42f9dc10777c3b0fcc5919cc93d",
224 "bytes": 29866,
225 "overlayPath": "worktree-overlay/apps/server/src/vcs/GitVcsDriver.ts.snapshot"
226 },
227 "apps/server/src/vcs/GitVcsDriverCore.test.ts": {
228 "sha256": "ee5f8d95dc326b9e651906d22f7f4b254c3ac9ee917e83d5d290a55ba6240417",
229 "bytes": 86150,
230 "overlayPath": "worktree-overlay/apps/server/src/vcs/GitVcsDriverCore.test.ts.snapshot"
231 },
232 "apps/server/src/vcs/GitVcsDriverCore.ts": {
233 "sha256": "158e93d8acb1f86026efe31e3b7425bde4217284e4b8e9a0871699e032a90151",
234 "bytes": 118521,
235 "overlayPath": "worktree-overlay/apps/server/src/vcs/GitVcsDriverCore.ts.snapshot"
236 },
237 "apps/web/src/components/CommandPalette.tsx": {
238 "sha256": "43289676c4605eecca56d3ceac725abd31d7632236585f195e5e76b2a2106a74",
239 "bytes": 91906,
240 "overlayPath": "worktree-overlay/apps/web/src/components/CommandPalette.tsx.snapshot"
241 },
242 "apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx": {
243 "sha256": "7e9afd073410e02d57e2bf08180de818be9328b19bf8366fa2d21e379246f8b1",
244 "bytes": 6352,
245 "overlayPath": "worktree-overlay/apps/web/src/components/ThreadStatusIndicators.subscriptions.test.tsx.snapshot"
246 },
247 "apps/web/src/components/ThreadStatusIndicators.test.ts": {
248 "sha256": "770c0ce42152993676577ee9d6519f28827a24ae23aa866b02311c890cc78017",
249 "bytes": 20742,
250 "overlayPath": "worktree-overlay/apps/web/src/components/ThreadStatusIndicators.test.ts.snapshot"
251 },
252 "apps/web/src/components/ThreadStatusIndicators.tsx": {
253 "sha256": "d7cbb9d3d54e11be727ba0eadb0d28c7719869422ee867df4af637d8709b8959",
254 "bytes": 23069,
255 "overlayPath": "worktree-overlay/apps/web/src/components/ThreadStatusIndicators.tsx.snapshot"
256 },
257 "apps/web/src/connection/desktopLocal.test.ts": {
258 "sha256": "feaf1bb33845837970adfca6a1b4f5ec4914a24d5701ae243aab2eabd029c596",
259 "bytes": 5566,
260 "overlayPath": "worktree-overlay/apps/web/src/connection/desktopLocal.test.ts.snapshot"
261 },
262 "apps/web/src/connection/desktopLocal.ts": {
263 "sha256": "47cd649eca399df82bb9d9bae9929ee6c008c0b94a6a3bedd859e419bbd3502f",
264 "bytes": 4316,
265 "overlayPath": "worktree-overlay/apps/web/src/connection/desktopLocal.ts.snapshot"
266 },
267 "patches/expo-notifications@57.0.15.patch": {
268 "sha256": "f89252c2c08dc7b3acb8415f319ebbe15fff8c9f88a64ebe1830cd5ac5c9ac40",
269 "bytes": 4327,
270 "overlayPath": "worktree-overlay/patches/expo-notifications@57.0.15.patch.snapshot"
271 },
272 "pnpm-lock.yaml": {
273 "sha256": "c11bff52e74aaf56861eaa1dd01c3d8ff5224d71150dc70dbb225e8862192265",
274 "bytes": 821015,
275 "overlayPath": "worktree-overlay/pnpm-lock.yaml.snapshot"
276 },
277 "pnpm-workspace.yaml": {
278 "sha256": "b68fd66ae334d694ae9d4d500cb300b1fa0ce0a7513991c7821c4069b5b0b572",
279 "bytes": 6724,
280 "overlayPath": "worktree-overlay/pnpm-workspace.yaml.snapshot"
281 }
282}
audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/mobile/scripts/fixtures/NotificationCenterManagerRegression.swift.snapshot+189−0
@@ -0,0 +1,189 @@
1import Foundation
2
3// The registry runs on macOS without starting a simulator. Only the OS-facing
4// types are replaced; the tests compile the dependency's actual Swift source.
5public enum UIBackgroundFetchResult {
6 case noData
7}
8
9public struct UNNotificationPresentationOptions: OptionSet {
10 public let rawValue: Int
11 public init(rawValue: Int) { self.rawValue = rawValue }
12}
13
14public final class UNNotification: NSObject {}
15
16public final class UNNotificationResponse: NSObject {
17 let identifier: String
18 init(_ identifier: String) { self.identifier = identifier }
19}
20
21public protocol UNUserNotificationCenterDelegate: AnyObject {}
22
23public final class UNUserNotificationCenter: NSObject {
24 private static let instance = UNUserNotificationCenter()
25 public weak var delegate: UNUserNotificationCenterDelegate?
26 public static func current() -> UNUserNotificationCenter { instance }
27}
28
29private final class TestDelegate: NotificationDelegate {
30 private let lock = NSLock()
31 private var recordedEvents: [String] = []
32 var onEvent: ((String) -> Void)?
33 var onResponse: ((UNNotificationResponse) -> Bool)?
34
35 var events: [String] { lock.withLock { recordedEvents } }
36
37 private func record(_ event: String) {
38 lock.withLock { recordedEvents.append(event) }
39 onEvent?(event)
40 }
41
42 func didRegister(_ deviceToken: String) { record("registered") }
43 func didFailRegistration(_ error: Error) { record("failed") }
44 func openSettings(_ notification: UNNotification?) { record("settings") }
45
46 func willPresent(_ notification: UNNotification, completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) -> Bool {
47 record("present")
48 return false
49 }
50
51 func didReceive(_ userInfo: [AnyHashable: Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool {
52 record("background")
53 return false
54 }
55
56 func didReceive(_ response: UNNotificationResponse, completionHandler: @escaping () -> Void) -> Bool {
57 record(response.identifier)
58 return onResponse?(response) ?? false
59 }
60}
61
62private func require(_ condition: @autoclosure () -> Bool, _ message: String) throws {
63 if !condition() {
64 throw NSError(domain: "NotificationCenterManagerRegression", code: 1, userInfo: [NSLocalizedDescriptionKey: message])
65 }
66}
67
68private func deliver(_ identifier: String) {
69 NotificationCenterManager.shared.userNotificationCenter(
70 UNUserNotificationCenter.current(),
71 didReceive: UNNotificationResponse(identifier),
72 withCompletionHandler: {}
73 )
74}
75
76private func reentrantCallbacks() throws {
77 let manager = NotificationCenterManager.shared
78 let center = UNUserNotificationCenter.current()
79 let callbacks: [(String, () -> Void)] = [
80 ("registered", { manager.didRegister("token") }),
81 ("failed", { manager.didFailRegistration(NSError(domain: "test", code: 1)) }),
82 ("settings", { manager.userNotificationCenter(center, openSettingsFor: nil) }),
83 ("present", { manager.userNotificationCenter(center, willPresent: UNNotification(), withCompletionHandler: { _ in }) }),
84 ("background", { manager.didReceive([:], completionHandler: { _ in }) })
85 ]
86
87 for (event, callback) in callbacks {
88 let original = TestDelegate()
89 let replacement = TestDelegate()
90 original.onEvent = { [weak original] _ in
91 guard let original else { return }
92 manager.removeDelegate(original)
93 manager.addDelegate(replacement)
94 }
95 manager.addDelegate(original)
96 callback()
97 try require(original.events == [event], "The original delegate must receive the callback once")
98 try require(replacement.events.isEmpty, "New delegates must not join an in-flight callback snapshot")
99 callback()
100 try require(replacement.events == [event], "Reentrant registration must take effect for the next callback")
101 manager.removeDelegate(replacement)
102 }
103}
104
105private func registrationDuringDelivery() throws {
106 let manager = NotificationCenterManager.shared
107 let original = TestDelegate()
108 let receiver = TestDelegate()
109 receiver.onResponse = { _ in true }
110 original.onResponse = { _ in
111 manager.addDelegate(receiver)
112 return false
113 }
114 manager.addDelegate(original)
115 deliver("handoff")
116 try require(receiver.events == ["handoff"], "A delegate registering during delivery must not miss the pending response")
117 manager.removeDelegate(original)
118 manager.removeDelegate(receiver)
119}
120
121private func responseDuringReplay() throws {
122 let manager = NotificationCenterManager.shared
123 deliver("first")
124 let original = TestDelegate()
125 original.onResponse = { response in
126 if response.identifier == "first" {
127 deliver("second")
128 return true
129 }
130 return false
131 }
132 manager.addDelegate(original)
133 let receiver = TestDelegate()
134 receiver.onResponse = { _ in true }
135 manager.addDelegate(receiver)
136 try require(receiver.events == ["second"], "Finishing a replay must retain responses received during callbacks")
137 manager.removeDelegate(original)
138 manager.removeDelegate(receiver)
139}
140
141private func concurrentRegistrations() throws {
142 let manager = NotificationCenterManager.shared
143 let delegates = (0..<512).map { _ in
144 let delegate = TestDelegate()
145 delegate.onResponse = { _ in true }
146 return delegate
147 }
148 DispatchQueue.concurrentPerform(iterations: delegates.count) { index in
149 manager.addDelegate(delegates[index])
150 if index.isMultiple(of: 16) {
151 manager.didRegister("during-add")
152 deliver("during-add")
153 }
154 }
155 let before = delegates.map { $0.events.count }
156 manager.didRegister("after-add")
157 for (index, delegate) in delegates.enumerated() {
158 try require(delegate.events.count == before[index] + 1, "Concurrent registration must retain every delegate exactly once")
159 }
160 DispatchQueue.concurrentPerform(iterations: delegates.count) { index in
161 manager.removeDelegate(delegates[index])
162 if index.isMultiple(of: 16) {
163 manager.didRegister("during-remove")
164 deliver("during-remove")
165 }
166 }
167 let removed = delegates.map { $0.events.count }
168 manager.didRegister("after-remove")
169 try require(delegates.map { $0.events.count } == removed, "Removed delegates must not receive new callbacks")
170}
171
172@main
173private enum RegressionTests {
174 static func main() {
175 do {
176 switch CommandLine.arguments.last {
177 case "reentrant": try reentrantCallbacks()
178 case "handoff": try registrationDuringDelivery()
179 case "pending": try responseDuringReplay()
180 case "concurrent": try concurrentRegistrations()
181 default: throw NSError(domain: "NotificationCenterManagerRegression", code: 2)
182 }
183 print("passed")
184 } catch {
185 FileHandle.standardError.write(Data("\(error.localizedDescription)\n".utf8))
186 exit(1)
187 }
188 }
189}
audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/mobile/scripts/notification-center-manager.test.ts.snapshot+65−0
@@ -0,0 +1,65 @@
1// @effect-diagnostics nodeBuiltinImport:off - Compiles and runs the native dependency regression directly.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeFS from "node:fs";
4import * as NodeOS from "node:os";
5import * as NodePath from "node:path";
6import * as NodeURL from "node:url";
7import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test";
8
9// oxlint-disable-next-line t3code/no-global-process-runtime -- This native integration test uses the host Swift compiler.
10describe.skipIf(NodeOS.platform() !== "darwin")(
11 "NotificationCenterManager native concurrency",
12 () => {
13 let directory: string;
14 let executable: string;
15
16 beforeAll(() => {
17 directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-notifications-test-"));
18 executable = NodePath.join(directory, "notification-regression");
19 const source = NodeFS.readFileSync(
20 new URL(
21 "../node_modules/expo-notifications/ios/ExpoNotifications/Notifications/NotificationCenterManager.swift",
22 import.meta.url,
23 ),
24 "utf8",
25 );
26 const manager = NodePath.join(directory, "NotificationCenterManager.swift");
27 NodeFS.writeFileSync(
28 manager,
29 source.replace(/^import (ExpoModulesCore|UserNotifications)\n/gm, ""),
30 );
31 NodeChildProcess.execFileSync(
32 "swiftc",
33 [
34 "-swift-version",
35 "5",
36 "-sanitize=thread",
37 manager,
38 NodeURL.fileURLToPath(
39 new URL("./fixtures/NotificationCenterManagerRegression.swift", import.meta.url),
40 ),
41 "-o",
42 executable,
43 ],
44 { timeout: 30_000, encoding: "utf8" },
45 );
46 });
47
48 afterAll(() => {
49 if (directory) NodeFS.rmSync(directory, { recursive: true, force: true });
50 });
51
52 it.each([
53 ["reentrant", "allows callbacks to replace delegates without deadlocking"],
54 ["handoff", "delivers responses to delegates registering during delivery"],
55 ["pending", "retains new responses received while replaying pending responses"],
56 ["concurrent", "registers, removes, and broadcasts concurrently without data races"],
57 ])("%s: %s", (name) => {
58 const output = NodeChildProcess.execFileSync(executable, [name], {
59 encoding: "utf8",
60 timeout: 15_000,
61 });
62 expect(output.trim()).toBe("passed");
63 });
64 },
65);
audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/git/GitManager.ts.snapshot+2719−0
@@ -0,0 +1,2719 @@
1import * as Arr from "effect/Array";
2import * as Cache from "effect/Cache";
3import * as Context from "effect/Context";
4import * as Crypto from "effect/Crypto";
5import * as DateTime from "effect/DateTime";
6import * as Duration from "effect/Duration";
7import * as Effect from "effect/Effect";
8import * as Exit from "effect/Exit";
9import * as FileSystem from "effect/FileSystem";
10import * as Layer from "effect/Layer";
11import * as Option from "effect/Option";
12import * as Order from "effect/Order";
13import * as Path from "effect/Path";
14import * as Ref from "effect/Ref";
15import * as Schema from "effect/Schema";
16import {
17 GitActionProgressEvent,
18 GitActionProgressPhase,
19 GitCommandError,
20 GitPreparePullRequestThreadInput,
21 GitPreparePullRequestThreadResult,
22 GitPullRequestRefInput,
23 GitResolvePullRequestResult,
24 GitRunStackedActionInput,
25 GitRunStackedActionResult,
26 GitStackedAction,
27 VcsStatusInput,
28 type VcsStatusLocalResult,
29 type VcsStatusRemoteResult,
30 VcsStatusResult,
31 ModelSelection,
32 SourceControlProviderError,
33 type SourceControlWritingStyleSettings,
34} from "@t3tools/contracts";
35import {
36 detectSourceControlProviderFromGitRemoteUrl,
37 mergeGitStatusParts,
38 normalizeGitRemoteUrl,
39 resolveAutoFeatureBranchName,
40 sanitizeBranchFragment,
41 sanitizeFeatureBranchName,
42} from "@t3tools/shared/git";
43import {
44 getChangeRequestTerminologyForKind,
45 isSshRemoteUrl,
46 type ChangeRequestTerminology,
47} from "@t3tools/shared/sourceControl";
48
49import { GitManagerError, GitPullRequestMaterializationError } from "@t3tools/contracts";
50import * as TextGeneration from "../textGeneration/TextGeneration.ts";
51import {
52 conventionalCommitsTextGenerationPolicy,
53 customTextGenerationPolicy,
54 repositoryConventionsTextGenerationPolicy,
55} from "../textGeneration/TextGenerationPresets.ts";
56import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts";
57import * as ProviderRegistry from "../provider/Services/ProviderRegistry.ts";
58import { extractBranchNameFromRemoteRef } from "./remoteRefs.ts";
59import * as ServerSettings from "../serverSettings.ts";
60import type { GitManagerServiceError } from "@t3tools/contracts";
61import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
62import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
63import { detectPrTemplate } from "../sourceControl/PrTemplateDetection.ts";
64import type { ChangeRequest } from "@t3tools/contracts";
65
66export interface GitActionProgressReporter {
67 readonly publish: (event: GitActionProgressEvent) => Effect.Effect<void, never>;
68}
69
70export interface GitRunStackedActionOptions {
71 readonly actionId?: string;
72 readonly progressReporter?: GitActionProgressReporter;
73}
74
75export interface GitRemoteStatusOptions extends GitVcsDriver.GitRemoteStatusOptions {
76 /** Retry a cached missing PR without clearing known PRs or failed lookup backoff. */
77 readonly refreshMissingPullRequest?: boolean;
78}
79
80interface SourceControlTextGenerationSettings {
81 readonly modelSelection: ModelSelection;
82 readonly style: SourceControlWritingStyleSettings;
83}
84
85export class GitManager extends Context.Service<
86 GitManager,
87 {
88 readonly status: (
89 input: VcsStatusInput,
90 ) => Effect.Effect<VcsStatusResult, GitManagerServiceError>;
91 readonly localStatus: (
92 input: VcsStatusInput,
93 ) => Effect.Effect<VcsStatusLocalResult, GitManagerServiceError>;
94 readonly remoteStatus: (
95 input: VcsStatusInput,
96 options?: GitRemoteStatusOptions,
97 ) => Effect.Effect<VcsStatusRemoteResult | null, GitManagerServiceError>;
98 /** Resolve the PR for a saved branch without changing the current checkout. */
99 readonly branchPullRequest: (input: {
100 readonly cwd: string;
101 readonly branch: string;
102 }) => Effect.Effect<
103 { readonly state: "open" | "closed" | "merged"; readonly updatedAt: string | null } | null,
104 GitManagerServiceError
105 >;
106 readonly invalidateLocalStatus: (cwd: string) => Effect.Effect<void, never>;
107 readonly invalidateRemoteStatus: (cwd: string) => Effect.Effect<void, never>;
108 readonly invalidateStatus: (cwd: string) => Effect.Effect<void, never>;
109 readonly resolvePullRequest: (
110 input: GitPullRequestRefInput,
111 ) => Effect.Effect<GitResolvePullRequestResult, GitManagerServiceError>;
112 readonly preparePullRequestThread: (
113 input: GitPreparePullRequestThreadInput,
114 ) => Effect.Effect<GitPreparePullRequestThreadResult, GitManagerServiceError>;
115 readonly runStackedAction: (
116 input: GitRunStackedActionInput,
117 options?: GitRunStackedActionOptions,
118 ) => Effect.Effect<GitRunStackedActionResult, GitManagerServiceError>;
119 }
120>()("t3/git/GitManager") {}
121
122const COMMIT_TIMEOUT_MS = 10 * 60_000;
123const MAX_PROGRESS_TEXT_LENGTH = 500;
124const SHORT_SHA_LENGTH = 7;
125const TOAST_DESCRIPTION_MAX = 72;
126const STATUS_RESULT_CACHE_TTL = Duration.seconds(1);
127const STATUS_RESULT_CACHE_CAPACITY = 2_048;
128// Matches the automatic settlement sweep cadence so every background sweep
129// reads fresh branch state: an external merge settles within about a minute
130// instead of waiting out a longer cache. Unpublished branches never reach the
131// host (a local probe answers first), and failed lookups still back off
132// exponentially via prLookupFailureTtl, so throttling pressure still drops
133// under 429s instead of amplifying it.
134const PR_LOOKUP_CACHE_TTL = Duration.seconds(60);
135const PR_LOOKUP_FAILURE_BASE_TTL = Duration.seconds(20);
136const PR_LOOKUP_FAILURE_MAX_TTL = Duration.minutes(15);
137const PR_LOOKUP_CACHE_CAPACITY = 2_048;
138const isSourceControlProviderError = Schema.is(SourceControlProviderError);
139
140/**
141 * How long a failed PR lookup is cached, given the number of consecutive
142 * failures for that branch.
143 *
144 * A hosting provider rejects a throttled request immediately, so caching every
145 * failure for a flat 20s made a rate-limited poller re-ask *faster* than a
146 * healthy one does (which waits PR_LOOKUP_CACHE_TTL), turning a transient 429
147 * into sustained pressure. Backing off per branch keeps the retry rate below
148 * the healthy rate once a branch has failed more than a couple of times.
149 */
150export function prLookupFailureTtl(consecutiveFailures: number): Duration.Duration {
151 const exponent = Math.max(0, consecutiveFailures - 1);
152 const backoffMs = Duration.toMillis(PR_LOOKUP_FAILURE_BASE_TTL) * Math.pow(2, exponent);
153 return Duration.min(Duration.millis(backoffMs), PR_LOOKUP_FAILURE_MAX_TTL);
154}
155type StripProgressContext<T> = T extends any ? Omit<T, "actionId" | "cwd" | "action"> : never;

2564 more lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts.snapshot+4033−0

This diff is large: its 4034 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts.snapshot+5465−0

This diff is large: its 5466 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/Orchestrator.ts.snapshot+7524−0

This diff is large: its 7525 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ProjectionSettlement.test.ts.snapshot+363−0

This diff is large: its 364 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ProjectionStore.ts.snapshot+3940−0

This diff is large: its 3941 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts.snapshot+292−0

This diff is large: its 293 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.test.ts.snapshot+716−0

This diff is large: its 717 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/ThreadSettlementService.ts.snapshot+371−0

This diff is large: its 372 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/runtimeLayer.test.ts.snapshot+1751−0

This diff is large: its 1752 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson.snapshot+51−0

This diff is large: its 52 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/codex_transcript.ndjson.snapshot+52−0

This diff is large: its 53 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/plan_questions/codex_transcript.ndjson.snapshot+67−0

This diff is large: its 68 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/proposed_plan/codex_transcript.ndjson.snapshot+619−0

This diff is large: its 620 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/codex_transcript.ndjson.snapshot+78−0

This diff is large: its 79 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/codex_transcript.ndjson.snapshot+52−0

This diff is large: its 53 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/simple/codex_transcript.ndjson.snapshot+31−0

This diff is large: its 32 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent/codex_transcript.ndjson.snapshot+577−0

This diff is large: its 578 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_continue/codex_transcript.ndjson.snapshot+33−0

This diff is large: its 34 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2/codex_transcript.ndjson.snapshot+25−0

This diff is large: its 26 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/subagent_v2_nested/codex_transcript.ndjson.snapshot+33−0

This diff is large: its 34 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native/codex_transcript.ndjson.snapshot+21−0

This diff is large: its 22 lines aren’t shown here.

audits/orchestrator-v2/2026-09-04/worktree-overlay/apps/server/src/orchestration-v2/testkit/fixtures/thread_fork_native_continue/codex_transcript.ndjson.snapshot+96−0

This diff is large: its 97 lines aren’t shown here.

This diff is larger than a page shows: the files after these aren’t listed. Clone the repository to see all of it.