History

fix(server): keep derived threads awake

Reset snooze metadata when creating forks and provider subagent children, with focused regressions for both inheritance paths.

Julius Marminge committed 3 months ago

Commit f6bf68c1 parent 1bb55d24 files · +254 −0Browse files

Showing the changes to apps/server/src/orchestration-v2/ThreadForkService.test.ts (1 of 4 files). Show all

Unified · Split

apps/server/src/orchestration-v2/ThreadForkService.test.ts+147−0
@@ -0,0 +1,147 @@
1import { assert, it } from "@effect/vitest";
2import {
3 ContextTransferId,
4 MessageId,
5 type ModelSelection,
6 type OrchestrationV2AppThread,
7 type OrchestrationV2Run,
8 type OrchestrationV2ThreadProjection,
9 ProjectId,
10 ProviderInstanceId,
11 ProviderThreadId,
12 RunId,
13 ThreadId,
14} from "@t3tools/contracts";
15import * as DateTime from "effect/DateTime";
16import * as Effect from "effect/Effect";
17
18import { layer, ThreadForkServiceV2 } from "./ThreadForkService.ts";
19
20const sourceThreadId = ThreadId.make("thread:fork-snoozed-source");
21const targetThreadId = ThreadId.make("thread:fork-awake-target");
22const sourceRunId = RunId.make("run:fork-snoozed-source");
23const providerInstanceId = ProviderInstanceId.make("codex");
24const modelSelection = {
25 instanceId: providerInstanceId,
26 model: "gpt-5.4",
27} satisfies ModelSelection;
28const sourceCreatedAt = DateTime.makeUnsafe("2026-07-24T09:00:00.000Z");
29const snoozedAt = DateTime.makeUnsafe("2026-07-24T09:05:00.000Z");
30const snoozedUntil = DateTime.makeUnsafe("2026-07-25T09:00:00.000Z");
31const forkCreatedAt = DateTime.makeUnsafe("2026-07-24T09:10:00.000Z");
32
33function makeSourceThread(): OrchestrationV2AppThread {
34 return {
35 createdBy: "user",
36 creationSource: "web",
37 id: sourceThreadId,
38 projectId: ProjectId.make("project:fork-snooze"),
39 title: "Snoozed source",
40 providerInstanceId,
41 modelSelection,
42 runtimeMode: "full-access",
43 interactionMode: "plan",
44 branch: "feature/source",
45 worktreePath: "/tmp/source-worktree",
46 activeProviderThreadId: ProviderThreadId.make("provider-thread:fork-snoozed-source"),
47 lineage: {
48 parentThreadId: null,
49 relationshipToParent: null,
50 rootThreadId: sourceThreadId,
51 },
52 forkedFrom: null,
53 createdAt: sourceCreatedAt,
54 updatedAt: snoozedAt,
55 archivedAt: null,
56 settledOverride: null,
57 settledAt: null,
58 snoozedUntil,
59 snoozedAt,
60 deletedAt: null,
61 };
62}
63
64function makeCompletedSourceRun(): OrchestrationV2Run {
65 return {
66 id: sourceRunId,
67 threadId: sourceThreadId,
68 ordinal: 1,
69 providerInstanceId,
70 modelSelection,
71 providerThreadId: ProviderThreadId.make("provider-thread:fork-snoozed-source"),
72 userMessageId: MessageId.make("message:fork-snoozed-source"),
73 rootNodeId: null,
74 activeAttemptId: null,
75 status: "completed",
76 queuePosition: null,
77 requestedAt: sourceCreatedAt,
78 startedAt: sourceCreatedAt,
79 completedAt: snoozedAt,
80 checkpointId: null,
81 contextHandoffId: null,
82 };
83}
84
85it.effect("keeps a fork awake when its source thread is snoozed", () =>
86 Effect.gen(function* () {
87 const sourceThread = makeSourceThread();
88 const sourceRun = makeCompletedSourceRun();
89 const sourceProjection: OrchestrationV2ThreadProjection = {
90 thread: sourceThread,
91 runs: [sourceRun],
92 attempts: [],
93 nodes: [],
94 subagents: [],
95 providerSessions: [],
96 providerThreads: [],
97 providerTurns: [],
98 runtimeRequests: [],
99 messages: [],
100 plans: [],
101 turnItems: [],
102 checkpointScopes: [],
103 checkpoints: [],
104 contextHandoffs: [],
105 contextTransfers: [],
106 visibleTurnItems: [],
107 updatedAt: snoozedAt,
108 };
109 const service = yield* ThreadForkServiceV2;
110 const result = yield* service.plan({
111 sourceProjection,
112 sourceRun,
113 sourceProviderThread: undefined,
114 canonicalSourcePoint: {
115 threadId: sourceThreadId,
116 runId: sourceRunId,
117 },
118 transferId: ContextTransferId.make("context-transfer:fork-snoozed-source"),
119 targetThreadId,
120 title: "Awake fork",
121 createdBy: "user",
122 creationSource: "mobile",
123 createdAt: forkCreatedAt,
124 });
125
126 assert.isNull(result.targetThread.snoozedUntil);
127 assert.isNull(result.targetThread.snoozedAt);
128 assert.equal(result.targetThread.projectId, sourceThread.projectId);
129 assert.equal(result.targetThread.providerInstanceId, sourceThread.providerInstanceId);
130 assert.deepEqual(result.targetThread.modelSelection, sourceThread.modelSelection);
131 assert.equal(result.targetThread.runtimeMode, sourceThread.runtimeMode);
132 assert.equal(result.targetThread.interactionMode, sourceThread.interactionMode);
133 assert.equal(result.targetThread.branch, sourceThread.branch);
134 assert.equal(result.targetThread.worktreePath, sourceThread.worktreePath);
135 assert.isNull(result.targetThread.activeProviderThreadId);
136 assert.deepEqual(result.targetThread.lineage, {
137 parentThreadId: sourceThreadId,
138 relationshipToParent: "fork",
139 rootThreadId: sourceThreadId,
140 });
141 assert.deepEqual(result.targetThread.forkedFrom, {
142 type: "run",
143 threadId: sourceThreadId,
144 runId: sourceRunId,
145 });
146 }).pipe(Effect.provide(layer)),
147);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t