Unified · Split
| @@ -27,9 +27,7 @@ import Migration0012 from "./Migrations/012_ProjectionThreadsInteractionMode.ts" · 26 unmodified lines | |||
| 27 | import Migration0013 from "./Migrations/013_ProjectionThreadProposedPlans.ts"; | 27 | import Migration0013 from "./Migrations/013_ProjectionThreadProposedPlans.ts"; |
| 28 | import Migration0014 from "./Migrations/014_ProjectionThreadProposedPlanImplementation.ts"; | 28 | import Migration0014 from "./Migrations/014_ProjectionThreadProposedPlanImplementation.ts"; |
| 29 | import Migration0015 from "./Migrations/015_ProjectionTurnsSourceProposedPlan.ts"; | 29 | import Migration0015 from "./Migrations/015_ProjectionTurnsSourceProposedPlan.ts"; |
| 30 | import Migration0016 from "./Migrations/016_ProjectionProviders.ts"; | 30 | import Migration0016 from "./Migrations/016_CanonicalizeModelSelections.ts"; |
| 31 | import Migration0017 from "./Migrations/017_ProjectionModelSelectionOptions.ts"; | ||
| 32 | import Migration0018 from "./Migrations/018_CanonicalizeLegacyModelSelections.ts"; | ||
| 33 | import { Effect } from "effect"; | 31 | import { Effect } from "effect"; |
| 34 | 32 | ||
| 35 | /** | 33 | /** |
| @@ -58,9 +56,7 @@ const loader = Migrator.fromRecord({ · 22 unmodified lines | |||
| 58 | "13_ProjectionThreadProposedPlans": Migration0013, | 56 | "13_ProjectionThreadProposedPlans": Migration0013, |
| 59 | "14_ProjectionThreadProposedPlanImplementation": Migration0014, | 57 | "14_ProjectionThreadProposedPlanImplementation": Migration0014, |
| 60 | "15_ProjectionTurnsSourceProposedPlan": Migration0015, | 58 | "15_ProjectionTurnsSourceProposedPlan": Migration0015, |
| 61 | "16_ProjectionProviders": Migration0016, | 59 | "16_CanonicalizeModelSelections": Migration0016, |
| 62 | "17_ProjectionModelSelectionOptions": Migration0017, | ||
| 63 | "18_CanonicalizeLegacyModelSelections": Migration0018, | ||
| 64 | }); | 60 | }); |
| 65 | 61 | ||
| 66 | /** | 62 | /** |
| @@ -0,0 +1,182 @@ | |||
| 1 | import { assert, it } from "@effect/vitest"; | ||
| 2 | import { Effect, Layer } from "effect"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 4 | |||
| 5 | import Migration0016 from "./016_CanonicalizeModelSelections.ts"; | ||
| 6 | import * as NodeSqliteClient from "../NodeSqliteClient.ts"; | ||
| 7 | |||
| 8 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); | ||
| 9 | |||
| 10 | layer("016_CanonicalizeModelSelections", (it) => { | ||
| 11 | it.effect( | ||
| 12 | "migrates legacy projection rows and event payloads to the canonical model-selection shape", | ||
| 13 | () => | ||
| 14 | Effect.gen(function* () { | ||
| 15 | const sql = yield* SqlClient.SqlClient; | ||
| 16 | |||
| 17 | yield* sql` | ||
| 18 | CREATE TABLE projection_projects ( | ||
| 19 | project_id TEXT PRIMARY KEY, | ||
| 20 | default_model TEXT | ||
| 21 | ) | ||
| 22 | `; | ||
| 23 | yield* sql` | ||
| 24 | CREATE TABLE projection_threads ( | ||
| 25 | thread_id TEXT PRIMARY KEY, | ||
| 26 | model TEXT NOT NULL | ||
| 27 | ) | ||
| 28 | `; | ||
| 29 | yield* sql` | ||
| 30 | CREATE TABLE projection_thread_sessions ( | ||
| 31 | thread_id TEXT PRIMARY KEY, | ||
| 32 | provider_name TEXT | ||
| 33 | ) | ||
| 34 | `; | ||
| 35 | yield* sql` | ||
| 36 | CREATE TABLE orchestration_events ( | ||
| 37 | event_type TEXT NOT NULL, | ||
| 38 | payload_json TEXT NOT NULL | ||
| 39 | ) | ||
| 40 | `; | ||
| 41 | |||
| 42 | yield* sql` | ||
| 43 | INSERT INTO projection_projects (project_id, default_model) | ||
| 44 | VALUES | ||
| 45 | ('project-codex', 'gpt-5.4'), | ||
| 46 | ('project-claude', 'claude-sonnet-4-6'), | ||
| 47 | ('project-null', NULL) | ||
| 48 | `; | ||
| 49 | yield* sql` | ||
| 50 | UPDATE projection_projects | ||
| 51 | SET default_model = 'claude-opus-4-6' | ||
| 52 | WHERE project_id = 'project-claude' | ||
| 53 | `; | ||
| 54 | yield* sql` | ||
| 55 | INSERT INTO projection_threads (thread_id, model) | ||
| 56 | VALUES | ||
| 57 | ('thread-session', 'gpt-5.4'), | ||
| 58 | ('thread-claude', 'claude-opus-4-6'), | ||
| 59 | ('thread-codex', 'gpt-5.4'), | ||
| 60 | ('thread-legacy-options', 'claude-opus-4-6') | ||
| 61 | `; | ||
| 62 | yield* sql` | ||
| 63 | INSERT INTO projection_thread_sessions (thread_id, provider_name) | ||
| 64 | VALUES ('thread-session', 'claudeAgent') | ||
| 65 | `; | ||
| 66 | yield* sql` | ||
| 67 | INSERT INTO orchestration_events ( | ||
| 68 | event_type, | ||
| 69 | payload_json | ||
| 70 | ) | ||
| 71 | VALUES | ||
| 72 | ( | ||
| 73 | 'project.created', | ||
| 74 | '{"projectId":"project-1","title":"Project","workspaceRoot":"/tmp/project","defaultModel":"claude-opus-4-6","defaultModelOptions":{"codex":{"reasoningEffort":"high"},"claudeAgent":{"effort":"max"}},"scripts":[],"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}' | ||
| 75 | ), | ||
| 76 | ( | ||
| 77 | 'thread.created', | ||
| 78 | '{"threadId":"thread-1","projectId":"project-1","title":"Thread","model":"claude-opus-4-6","modelOptions":{"codex":{"reasoningEffort":"high"},"claudeAgent":{"effort":"max","thinking":false}},"runtimeMode":"full-access","interactionMode":"default","branch":null,"worktreePath":null,"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}' | ||
| 79 | ), | ||
| 80 | ( | ||
| 81 | 'thread.turn-start-requested', | ||
| 82 | '{"threadId":"thread-1","turnId":"turn-1","input":"hi","model":"gpt-5.4","modelOptions":{"codex":{"fastMode":true},"claudeAgent":{"effort":"max"}},"deliveryMode":"buffered"}' | ||
| 83 | ) | ||
| 84 | `; | ||
| 85 | |||
| 86 | yield* Migration0016; | ||
| 87 | |||
| 88 | const projectRows = yield* sql<{ | ||
| 89 | readonly projectId: string; | ||
| 90 | readonly defaultProvider: string | null; | ||
| 91 | }>` | ||
| 92 | SELECT | ||
| 93 | project_id AS "projectId", | ||
| 94 | default_provider AS "defaultProvider" | ||
| 95 | FROM projection_projects | ||
| 96 | ORDER BY project_id | ||
| 97 | `; | ||
| 98 | assert.deepStrictEqual(projectRows, [ | ||
| 99 | { projectId: "project-claude", defaultProvider: "claudeAgent" }, | ||
| 100 | { projectId: "project-codex", defaultProvider: "codex" }, | ||
| 101 | { projectId: "project-null", defaultProvider: null }, | ||
| 102 | ]); | ||
| 103 | |||
| 104 | const threadRows = yield* sql<{ | ||
| 105 | readonly threadId: string; | ||
| 106 | readonly provider: string | null; | ||
| 107 | readonly modelOptions: string | null; | ||
| 108 | }>` | ||
| 109 | SELECT | ||
| 110 | thread_id AS "threadId", | ||
| 111 | provider, | ||
| 112 | model_options_json AS "modelOptions" | ||
| 113 | FROM projection_threads | ||
| 114 | ORDER BY thread_id | ||
| 115 | `; | ||
| 116 | assert.deepStrictEqual(threadRows, [ | ||
| 117 | { threadId: "thread-claude", provider: "claudeAgent", modelOptions: null }, | ||
| 118 | { threadId: "thread-codex", provider: "codex", modelOptions: null }, | ||
| 119 | { threadId: "thread-legacy-options", provider: "claudeAgent", modelOptions: null }, | ||
| 120 | { threadId: "thread-session", provider: "claudeAgent", modelOptions: null }, | ||
| 121 | ]); | ||
| 122 | |||
| 123 | const eventRows = yield* sql<{ | ||
| 124 | readonly payloadJson: string; | ||
| 125 | }>` | ||
| 126 | SELECT payload_json AS "payloadJson" | ||
| 127 | FROM orchestration_events | ||
| 128 | ORDER BY rowid ASC | ||
| 129 | `; | ||
| 130 | |||
| 131 | assert.deepStrictEqual(JSON.parse(eventRows[0]!.payloadJson), { | ||
| 132 | projectId: "project-1", | ||
| 133 | title: "Project", | ||
| 134 | workspaceRoot: "/tmp/project", | ||
| 135 | defaultModelSelection: { | ||
| 136 | provider: "claudeAgent", | ||
| 137 | model: "claude-opus-4-6", | ||
| 138 | options: { | ||
| 139 | effort: "max", | ||
| 140 | }, | ||
| 141 | }, | ||
| 142 | scripts: [], | ||
| 143 | createdAt: "2026-01-01T00:00:00.000Z", | ||
| 144 | updatedAt: "2026-01-01T00:00:00.000Z", | ||
| 145 | }); | ||
| 146 | |||
| 147 | assert.deepStrictEqual(JSON.parse(eventRows[1]!.payloadJson), { | ||
| 148 | threadId: "thread-1", | ||
| 149 | projectId: "project-1", | ||
| 150 | title: "Thread", | ||
| 151 | modelSelection: { | ||
| 152 | provider: "claudeAgent", | ||
| 153 | model: "claude-opus-4-6", | ||
| 154 | options: { | ||
| 155 | effort: "max", | ||
| 156 | thinking: false, | ||
| 157 | }, | ||
| 158 | }, | ||
| 159 | runtimeMode: "full-access", | ||
| 160 | interactionMode: "default", | ||
| 161 | branch: null, | ||
| 162 | worktreePath: null, | ||
| 163 | createdAt: "2026-01-01T00:00:00.000Z", | ||
| 164 | updatedAt: "2026-01-01T00:00:00.000Z", | ||
| 165 | }); | ||
| 166 | |||
| 167 | assert.deepStrictEqual(JSON.parse(eventRows[2]!.payloadJson), { | ||
| 168 | threadId: "thread-1", | ||
| 169 | turnId: "turn-1", | ||
| 170 | input: "hi", | ||
| 171 | modelSelection: { | ||
| 172 | provider: "codex", | ||
| 173 | model: "gpt-5.4", | ||
| 174 | options: { | ||
| 175 | fastMode: true, | ||
| 176 | }, | ||
| 177 | }, | ||
| 178 | deliveryMode: "buffered", | ||
| 179 | }); | ||
| 180 | }), | ||
| 181 | ); | ||
| 182 | }); | ||
| @@ -4,24 +4,64 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; · 3 unmodified lines | |||
| 4 | export default Effect.gen(function* () { | 4 | export default Effect.gen(function* () { |
| 5 | const sql = yield* SqlClient.SqlClient; | 5 | const sql = yield* SqlClient.SqlClient; |
| 6 | 6 | ||
| 7 | yield* sql` | ||
| 8 | ALTER TABLE projection_projects | ||
| 9 | ADD COLUMN default_provider TEXT | ||
| 10 | `; | ||
| 11 | |||
| 7 | yield* sql` | 12 | yield* sql` |
| 8 | UPDATE projection_projects | 13 | UPDATE projection_projects |
| 9 | SET | 14 | SET default_provider = CASE |
| 10 | default_provider = CASE | 15 | WHEN default_model IS NULL THEN NULL |
| 11 | WHEN default_model IS NULL THEN NULL | 16 | WHEN lower(default_model) LIKE '%claude%' THEN 'claudeAgent' |
| 12 | WHEN lower(default_model) LIKE '%claude%' THEN 'claudeAgent' | 17 | ELSE 'codex' |
| 18 | END | ||
| 19 | WHERE default_provider IS NULL | ||
| 20 | `; | ||
| 21 | |||
| 22 | yield* sql` | ||
| 23 | ALTER TABLE projection_threads | ||
| 24 | ADD COLUMN provider TEXT | ||
| 25 | `; | ||
| 26 | |||
| 27 | yield* sql` | ||
| 28 | UPDATE projection_threads | ||
| 29 | SET provider = COALESCE( | ||
| 30 | ( | ||
| 31 | SELECT provider_name | ||
| 32 | FROM projection_thread_sessions | ||
| 33 | WHERE projection_thread_sessions.thread_id = projection_threads.thread_id | ||
| 34 | ), | ||
| 35 | CASE | ||
| 36 | WHEN lower(model) LIKE '%claude%' THEN 'claudeAgent' | ||
| 13 | ELSE 'codex' | 37 | ELSE 'codex' |
| 14 | END, | 38 | END, |
| 39 | 'codex' | ||
| 40 | ) | ||
| 41 | WHERE provider IS NULL | ||
| 42 | `; | ||
| 43 | |||
| 44 | yield* sql` | ||
| 45 | ALTER TABLE projection_projects | ||
| 46 | ADD COLUMN default_model_options_json TEXT | ||
| 47 | `; | ||
| 48 | |||
| 49 | yield* sql` | ||
| 50 | ALTER TABLE projection_threads | ||
| 51 | ADD COLUMN model_options_json TEXT | ||
| 52 | `; | ||
| 53 | |||
| 54 | yield* sql` | ||
| 55 | UPDATE projection_projects | ||
| 56 | SET | ||
| 15 | default_model_options_json = CASE | 57 | default_model_options_json = CASE |
| 16 | WHEN default_model_options_json IS NULL THEN NULL | 58 | WHEN default_model_options_json IS NULL THEN NULL |
| 17 | WHEN json_valid(default_model_options_json) = 0 THEN default_model_options_json | 59 | WHEN json_valid(default_model_options_json) = 0 THEN default_model_options_json |
| 18 | WHEN json_type(default_model_options_json, '$.codex') IS NOT NULL | 60 | WHEN json_type(default_model_options_json, '$.codex') IS NOT NULL |
| 19 | OR json_type(default_model_options_json, '$.claudeAgent') IS NOT NULL | 61 | OR json_type(default_model_options_json, '$.claudeAgent') IS NOT NULL |
| 20 | THEN CASE | 62 | THEN CASE |
| 21 | WHEN lower(default_model) LIKE '%claude%' THEN json_extract( | 63 | WHEN default_provider = 'claudeAgent' |
| 22 | default_model_options_json, | 64 | THEN json_extract(default_model_options_json, '$.claudeAgent') |
| 23 | '$.claudeAgent' | ||
| 24 | ) | ||
| 25 | ELSE json_extract(default_model_options_json, '$.codex') | 65 | ELSE json_extract(default_model_options_json, '$.codex') |
| 26 | END | 66 | END |
| 27 | ELSE default_model_options_json | 67 | ELSE default_model_options_json |
| @@ -32,17 +72,14 @@ export default Effect.gen(function* () { · 4 unmodified lines | |||
| 32 | yield* sql` | 72 | yield* sql` |
| 33 | UPDATE projection_threads | 73 | UPDATE projection_threads |
| 34 | SET | 74 | SET |
| 35 | provider = CASE | ||
| 36 | WHEN lower(model) LIKE '%claude%' THEN 'claudeAgent' | ||
| 37 | ELSE 'codex' | ||
| 38 | END, | ||
| 39 | model_options_json = CASE | 75 | model_options_json = CASE |
| 40 | WHEN model_options_json IS NULL THEN NULL | 76 | WHEN model_options_json IS NULL THEN NULL |
| 41 | WHEN json_valid(model_options_json) = 0 THEN model_options_json | 77 | WHEN json_valid(model_options_json) = 0 THEN model_options_json |
| 42 | WHEN json_type(model_options_json, '$.codex') IS NOT NULL | 78 | WHEN json_type(model_options_json, '$.codex') IS NOT NULL |
| 43 | OR json_type(model_options_json, '$.claudeAgent') IS NOT NULL | 79 | OR json_type(model_options_json, '$.claudeAgent') IS NOT NULL |
| 44 | THEN CASE | 80 | THEN CASE |
| 45 | WHEN lower(model) LIKE '%claude%' THEN json_extract(model_options_json, '$.claudeAgent') | 81 | WHEN provider = 'claudeAgent' |
| 82 | THEN json_extract(model_options_json, '$.claudeAgent') | ||
| 46 | ELSE json_extract(model_options_json, '$.codex') | 83 | ELSE json_extract(model_options_json, '$.codex') |
| 47 | END | 84 | END |
| 48 | ELSE model_options_json | 85 | ELSE model_options_json |
| @@ -1,38 +0,0 @@ | |||
| 1 | import * as Effect from "effect/Effect"; | ||
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 3 | |||
| 4 | export default Effect.gen(function* () { | ||
| 5 | const sql = yield* SqlClient.SqlClient; | ||
| 6 | |||
| 7 | yield* sql` | ||
| 8 | ALTER TABLE projection_projects | ||
| 9 | ADD COLUMN default_provider TEXT | ||
| 10 | `; | ||
| 11 | |||
| 12 | yield* sql` | ||
| 13 | UPDATE projection_projects | ||
| 14 | SET default_provider = CASE | ||
| 15 | WHEN default_model IS NULL THEN NULL | ||
| 16 | ELSE 'codex' | ||
| 17 | END | ||
| 18 | WHERE default_provider IS NULL | ||
| 19 | `; | ||
| 20 | |||
| 21 | yield* sql` | ||
| 22 | ALTER TABLE projection_threads | ||
| 23 | ADD COLUMN provider TEXT | ||
| 24 | `; | ||
| 25 | |||
| 26 | yield* sql` | ||
| 27 | UPDATE projection_threads | ||
| 28 | SET provider = COALESCE( | ||
| 29 | ( | ||
| 30 | SELECT provider_name | ||
| 31 | FROM projection_thread_sessions | ||
| 32 | WHERE projection_thread_sessions.thread_id = projection_threads.thread_id | ||
| 33 | ), | ||
| 34 | 'codex' | ||
| 35 | ) | ||
| 36 | WHERE provider IS NULL | ||
| 37 | `; | ||
| 38 | }); | ||
| @@ -1,28 +0,0 @@ | |||
| 1 | import * as Effect from "effect/Effect"; | ||
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 3 | |||
| 4 | export default Effect.gen(function* () { | ||
| 5 | const sql = yield* SqlClient.SqlClient; | ||
| 6 | |||
| 7 | yield* sql` | ||
| 8 | ALTER TABLE projection_projects | ||
| 9 | ADD COLUMN default_model_options_json TEXT | ||
| 10 | `; | ||
| 11 | |||
| 12 | yield* sql` | ||
| 13 | UPDATE projection_projects | ||
| 14 | SET default_model_options_json = NULL | ||
| 15 | WHERE default_model_options_json IS NULL | ||
| 16 | `; | ||
| 17 | |||
| 18 | yield* sql` | ||
| 19 | ALTER TABLE projection_threads | ||
| 20 | ADD COLUMN model_options_json TEXT | ||
| 21 | `; | ||
| 22 | |||
| 23 | yield* sql` | ||
| 24 | UPDATE projection_threads | ||
| 25 | SET model_options_json = NULL | ||
| 26 | WHERE model_options_json IS NULL | ||
| 27 | `; | ||
| 28 | }); | ||
| @@ -1,180 +0,0 @@ | |||
| 1 | import { assert, it } from "@effect/vitest"; | ||
| 2 | import { Effect, Layer } from "effect"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 4 | |||
| 5 | import Migration0018 from "./018_CanonicalizeLegacyModelSelections.ts"; | ||
| 6 | import * as NodeSqliteClient from "../NodeSqliteClient.ts"; | ||
| 7 | |||
| 8 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); | ||
| 9 | |||
| 10 | layer("018_CanonicalizeLegacyModelSelections", (it) => { | ||
| 11 | it.effect("canonicalizes legacy projection rows and orchestration event payloads", () => | ||
| 12 | Effect.gen(function* () { | ||
| 13 | const sql = yield* SqlClient.SqlClient; | ||
| 14 | |||
| 15 | yield* sql` | ||
| 16 | CREATE TABLE projection_projects ( | ||
| 17 | project_id TEXT PRIMARY KEY, | ||
| 18 | default_provider TEXT, | ||
| 19 | default_model TEXT, | ||
| 20 | default_model_options_json TEXT | ||
| 21 | ) | ||
| 22 | `; | ||
| 23 | yield* sql` | ||
| 24 | CREATE TABLE projection_threads ( | ||
| 25 | thread_id TEXT PRIMARY KEY, | ||
| 26 | provider TEXT, | ||
| 27 | model TEXT NOT NULL, | ||
| 28 | model_options_json TEXT | ||
| 29 | ) | ||
| 30 | `; | ||
| 31 | yield* sql` | ||
| 32 | CREATE TABLE orchestration_events ( | ||
| 33 | event_type TEXT NOT NULL, | ||
| 34 | payload_json TEXT NOT NULL | ||
| 35 | ) | ||
| 36 | `; | ||
| 37 | |||
| 38 | yield* sql` | ||
| 39 | INSERT INTO projection_projects ( | ||
| 40 | project_id, | ||
| 41 | default_provider, | ||
| 42 | default_model, | ||
| 43 | default_model_options_json | ||
| 44 | ) | ||
| 45 | VALUES ( | ||
| 46 | 'project-1', | ||
| 47 | 'codex', | ||
| 48 | 'claude-opus-4-6', | ||
| 49 | '{"codex":{"reasoningEffort":"high"},"claudeAgent":{"effort":"max","thinking":false}}' | ||
| 50 | ) | ||
| 51 | `; | ||
| 52 | yield* sql` | ||
| 53 | INSERT INTO projection_threads ( | ||
| 54 | thread_id, | ||
| 55 | provider, | ||
| 56 | model, | ||
| 57 | model_options_json | ||
| 58 | ) | ||
| 59 | VALUES ( | ||
| 60 | 'thread-1', | ||
| 61 | 'codex', | ||
| 62 | 'claude-opus-4-6', | ||
| 63 | '{"codex":{"reasoningEffort":"high"},"claudeAgent":{"effort":"max"}}' | ||
| 64 | ) | ||
| 65 | `; | ||
| 66 | yield* sql` | ||
| 67 | INSERT INTO orchestration_events ( | ||
| 68 | event_type, | ||
| 69 | payload_json | ||
| 70 | ) | ||
| 71 | VALUES | ||
| 72 | ( | ||
| 73 | 'project.created', | ||
| 74 | '{"projectId":"project-1","title":"Project","workspaceRoot":"/tmp/project","defaultModel":"claude-opus-4-6","defaultModelOptions":{"codex":{"reasoningEffort":"high"},"claudeAgent":{"effort":"max"}},"scripts":[],"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}' | ||
| 75 | ), | ||
| 76 | ( | ||
| 77 | 'thread.created', | ||
| 78 | '{"threadId":"thread-1","projectId":"project-1","title":"Thread","model":"claude-opus-4-6","modelOptions":{"codex":{"reasoningEffort":"high"},"claudeAgent":{"effort":"max","thinking":false}},"runtimeMode":"full-access","interactionMode":"default","branch":null,"worktreePath":null,"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}' | ||
| 79 | ), | ||
| 80 | ( | ||
| 81 | 'thread.turn-start-requested', | ||
| 82 | '{"threadId":"thread-1","turnId":"turn-1","input":"hi","model":"gpt-5.4","modelOptions":{"codex":{"fastMode":true},"claudeAgent":{"effort":"max"}},"deliveryMode":"buffered"}' | ||
| 83 | ) | ||
| 84 | `; | ||
| 85 | |||
| 86 | yield* Migration0018; | ||
| 87 | |||
| 88 | const projectRows = yield* sql<{ | ||
| 89 | readonly defaultProvider: string | null; | ||
| 90 | readonly defaultModelOptions: string | null; | ||
| 91 | }>` | ||
| 92 | SELECT | ||
| 93 | default_provider AS "defaultProvider", | ||
| 94 | default_model_options_json AS "defaultModelOptions" | ||
| 95 | FROM projection_projects | ||
| 96 | WHERE project_id = 'project-1' | ||
| 97 | `; | ||
| 98 | assert.deepStrictEqual(projectRows[0], { | ||
| 99 | defaultProvider: "claudeAgent", | ||
| 100 | defaultModelOptions: '{"effort":"max","thinking":false}', | ||
| 101 | }); | ||
| 102 | |||
| 103 | const threadRows = yield* sql<{ | ||
| 104 | readonly provider: string | null; | ||
| 105 | readonly modelOptions: string | null; | ||
| 106 | }>` | ||
| 107 | SELECT | ||
| 108 | provider, | ||
| 109 | model_options_json AS "modelOptions" | ||
| 110 | FROM projection_threads | ||
| 111 | WHERE thread_id = 'thread-1' | ||
| 112 | `; | ||
| 113 | assert.deepStrictEqual(threadRows[0], { | ||
| 114 | provider: "claudeAgent", | ||
| 115 | modelOptions: '{"effort":"max"}', | ||
| 116 | }); | ||
| 117 | |||
| 118 | const eventRows = yield* sql<{ | ||
| 119 | readonly eventType: string; | ||
| 120 | readonly payloadJson: string; | ||
| 121 | }>` | ||
| 122 | SELECT | ||
| 123 | event_type AS "eventType", | ||
| 124 | payload_json AS "payloadJson" | ||
| 125 | FROM orchestration_events | ||
| 126 | ORDER BY rowid ASC | ||
| 127 | `; | ||
| 128 | |||
| 129 | assert.deepStrictEqual(JSON.parse(eventRows[0]!.payloadJson), { | ||
| 130 | projectId: "project-1", | ||
| 131 | title: "Project", | ||
| 132 | workspaceRoot: "/tmp/project", | ||
| 133 | defaultModelSelection: { | ||
| 134 | provider: "claudeAgent", | ||
| 135 | model: "claude-opus-4-6", | ||
| 136 | options: { | ||
| 137 | effort: "max", | ||
| 138 | }, | ||
| 139 | }, | ||
| 140 | scripts: [], | ||
| 141 | createdAt: "2026-01-01T00:00:00.000Z", | ||
| 142 | updatedAt: "2026-01-01T00:00:00.000Z", | ||
| 143 | }); | ||
| 144 | |||
| 145 | assert.deepStrictEqual(JSON.parse(eventRows[1]!.payloadJson), { | ||
| 146 | threadId: "thread-1", | ||
| 147 | projectId: "project-1", | ||
| 148 | title: "Thread", | ||
| 149 | modelSelection: { | ||
| 150 | provider: "claudeAgent", | ||
| 151 | model: "claude-opus-4-6", | ||
| 152 | options: { | ||
| 153 | effort: "max", | ||
| 154 | thinking: false, | ||
| 155 | }, | ||
| 156 | }, | ||
| 157 | runtimeMode: "full-access", | ||
| 158 | interactionMode: "default", | ||
| 159 | branch: null, | ||
| 160 | worktreePath: null, | ||
| 161 | createdAt: "2026-01-01T00:00:00.000Z", | ||
| 162 | updatedAt: "2026-01-01T00:00:00.000Z", | ||
| 163 | }); | ||
| 164 | |||
| 165 | assert.deepStrictEqual(JSON.parse(eventRows[2]!.payloadJson), { | ||
| 166 | threadId: "thread-1", | ||
| 167 | turnId: "turn-1", | ||
| 168 | input: "hi", | ||
| 169 | modelSelection: { | ||
| 170 | provider: "codex", | ||
| 171 | model: "gpt-5.4", | ||
| 172 | options: { | ||
| 173 | fastMode: true, | ||
| 174 | }, | ||
| 175 | }, | ||
| 176 | deliveryMode: "buffered", | ||
| 177 | }); | ||
| 178 | }), | ||
| 179 | ); | ||
| 180 | }); | ||