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 @@
1+import { assert, it } from "@effect/vitest";
2+import {
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";
15+import * as DateTime from "effect/DateTime";
16+import * as Effect from "effect/Effect";
17+
18+import { layer, ThreadForkServiceV2 } from "./ThreadForkService.ts";
19+
20+const sourceThreadId = ThreadId.make("thread:fork-snoozed-source");
21+const targetThreadId = ThreadId.make("thread:fork-awake-target");
22+const sourceRunId = RunId.make("run:fork-snoozed-source");
23+const providerInstanceId = ProviderInstanceId.make("codex");
24+const modelSelection = {
25+ instanceId: providerInstanceId,
26+ model: "gpt-5.4",
27+} satisfies ModelSelection;
28+const sourceCreatedAt = DateTime.makeUnsafe("2026-07-24T09:00:00.000Z");
29+const snoozedAt = DateTime.makeUnsafe("2026-07-24T09:05:00.000Z");
30+const snoozedUntil = DateTime.makeUnsafe("2026-07-25T09:00:00.000Z");
31+const forkCreatedAt = DateTime.makeUnsafe("2026-07-24T09:10:00.000Z");
32+
33+function 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+
64+function 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+
85+it.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