kanna-code 0.41.6 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
6
  <meta name="theme-color" content="#ffffff" />
7
7
  <title>Kanna Transcript</title>
8
- <script type="module" crossorigin src="./assets/index--p-u0wfg.js"></script>
9
- <link rel="stylesheet" crossorigin href="./assets/index-Bw6GI0aH.css">
8
+ <script type="module" crossorigin src="./assets/index-DoK2sc-h.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-DH7NuXKA.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kanna-code",
3
3
  "type": "module",
4
- "version": "0.41.6",
4
+ "version": "0.42.0",
5
5
  "description": "A beautiful web UI for Claude Code",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -9,14 +9,7 @@ import {
9
9
  } from "./agent"
10
10
  import type { HarnessTurn } from "./harness-types"
11
11
  import type { ChatAttachment, TranscriptEntry } from "../shared/types"
12
-
13
- function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(entry: T): TranscriptEntry {
14
- return {
15
- _id: crypto.randomUUID(),
16
- createdAt: Date.now(),
17
- ...entry,
18
- } as TranscriptEntry
19
- }
12
+ import { timestamped } from "./transcript"
20
13
 
21
14
  async function waitFor(condition: () => boolean, timeoutMs = 2000) {
22
15
  const start = Date.now()
@@ -3,6 +3,7 @@ import { homedir } from "node:os"
3
3
  import type {
4
4
  AgentProvider,
5
5
  ChatAttachment,
6
+ CodexReasoningEffort,
6
7
  ContextWindowUsageSnapshot,
7
8
  ModelOptions,
8
9
  NormalizedToolCall,
@@ -17,19 +18,24 @@ import { EventStore } from "./event-store"
17
18
  import type { AnalyticsReporter } from "./analytics"
18
19
  import { NoopAnalyticsReporter } from "./analytics"
19
20
  import { CodexAppServerManager } from "./codex-app-server"
21
+ import { CursorCliManager } from "./cursor-cli"
20
22
  import { type GenerateChatTitleResult, generateTitleForChatDetailed } from "./generate-title"
21
23
  import type { HarnessEvent, HarnessToolRequest, HarnessTurn } from "./harness-types"
22
24
  import {
23
25
  applyClaudeSdkModels,
24
26
  type ClaudeSdkModelInfo,
25
27
  codexServiceTierFromModelOptions,
28
+ cursorModelIdForOptions,
26
29
  getServerProviderCatalog,
27
30
  normalizeClaudeModelOptions,
28
31
  normalizeCodexModelOptions,
32
+ normalizeCursorModelOptions,
29
33
  normalizeServerModel,
30
34
  } from "./provider-catalog"
31
35
  import { resolveClaudeApiModelId } from "../shared/types"
32
36
  import { fallbackTitleFromMessage } from "./generate-title"
37
+ import { asNumber, asRecord } from "../shared/json"
38
+ import { timestamped } from "./transcript"
33
39
 
34
40
  const CLAUDE_TOOLSET = [
35
41
  "Skill",
@@ -106,6 +112,7 @@ interface AgentCoordinatorArgs {
106
112
  onStateChange: (chatId?: string, options?: { immediate?: boolean }) => void
107
113
  analytics?: AnalyticsReporter
108
114
  codexManager?: CodexAppServerManager
115
+ cursorManager?: CursorCliManager
109
116
  generateTitle?: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
110
117
  startClaudeSession?: (args: {
111
118
  localPath: string
@@ -147,17 +154,6 @@ interface SendMessageOptions {
147
154
  planMode?: boolean
148
155
  }
149
156
 
150
- function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
151
- entry: T,
152
- createdAt = Date.now()
153
- ): TranscriptEntry {
154
- return {
155
- _id: crypto.randomUUID(),
156
- createdAt,
157
- ...entry,
158
- } as TranscriptEntry
159
- }
160
-
161
157
  function stringFromUnknown(value: unknown) {
162
158
  if (typeof value === "string") return value
163
159
  try {
@@ -173,14 +169,6 @@ function buildSteeredMessageContent(content: string) {
173
169
  : STEERED_MESSAGE_PREFIX
174
170
  }
175
171
 
176
- function asRecord(value: unknown): Record<string, unknown> | null {
177
- return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null
178
- }
179
-
180
- function asNumber(value: unknown): number | undefined {
181
- return typeof value === "number" && Number.isFinite(value) ? value : undefined
182
- }
183
-
184
172
  function escapeXmlAttribute(value: string) {
185
173
  return value
186
174
  .replaceAll("&", "&amp;")
@@ -680,6 +668,7 @@ export class AgentCoordinator {
680
668
  private readonly onStateChange: (chatId?: string, options?: { immediate?: boolean }) => void
681
669
  private readonly analytics: AnalyticsReporter
682
670
  private readonly codexManager: CodexAppServerManager
671
+ private readonly cursorManager: CursorCliManager
683
672
  private readonly generateTitle: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
684
673
  private readonly startClaudeSessionFn: NonNullable<AgentCoordinatorArgs["startClaudeSession"]>
685
674
  private reportBackgroundError: ((message: string) => void) | null = null
@@ -692,6 +681,7 @@ export class AgentCoordinator {
692
681
  this.onStateChange = args.onStateChange
693
682
  this.analytics = args.analytics ?? NoopAnalyticsReporter
694
683
  this.codexManager = args.codexManager ?? new CodexAppServerManager()
684
+ this.cursorManager = args.cursorManager ?? new CursorCliManager()
695
685
  this.generateTitle = args.generateTitle ?? generateTitleForChatDetailed
696
686
  this.startClaudeSessionFn = args.startClaudeSession ?? startClaudeSession
697
687
  }
@@ -784,9 +774,20 @@ export class AgentCoordinator {
784
774
  }
785
775
  }
786
776
 
787
- const modelOptions = normalizeCodexModelOptions(options.modelOptions, options.effort)
777
+ if (provider === "cursor") {
778
+ const modelOptions = normalizeCursorModelOptions(options.modelOptions)
779
+ return {
780
+ model: cursorModelIdForOptions(normalizeServerModel(provider, options.model), modelOptions),
781
+ effort: undefined,
782
+ serviceTier: undefined,
783
+ planMode: false,
784
+ }
785
+ }
786
+
787
+ const model = normalizeServerModel(provider, options.model)
788
+ const modelOptions = normalizeCodexModelOptions(model, options.modelOptions, options.effort)
788
789
  return {
789
- model: normalizeServerModel(provider, options.model),
790
+ model,
790
791
  effort: modelOptions.reasoningEffort,
791
792
  serviceTier: codexServiceTierFromModelOptions(modelOptions),
792
793
  planMode: catalog.supportsPlanMode ? Boolean(options.planMode) : false,
@@ -957,6 +958,24 @@ export class AgentCoordinator {
957
958
  provider: args.provider,
958
959
  model: args.model,
959
960
  })
961
+ } else if (args.provider === "cursor") {
962
+ logSendToStartingProfile(args.profile, "start_turn.provider_boot.begin", {
963
+ chatId: args.chatId,
964
+ provider: args.provider,
965
+ model: args.model,
966
+ })
967
+ // Cursor cannot fork (see canForkChat), so a turn always resumes its own session.
968
+ turn = await this.cursorManager.startTurn({
969
+ cwd: project.localPath,
970
+ content: buildPromptText(args.content, args.attachments),
971
+ model: args.model,
972
+ sessionToken: chat.sessionToken,
973
+ })
974
+ logSendToStartingProfile(args.profile, "start_turn.provider_boot.ready", {
975
+ chatId: args.chatId,
976
+ provider: args.provider,
977
+ model: args.model,
978
+ })
960
979
  } else {
961
980
  logSendToStartingProfile(args.profile, "start_turn.provider_boot.begin", {
962
981
  chatId: args.chatId,
@@ -983,7 +1002,7 @@ export class AgentCoordinator {
983
1002
  chatId: args.chatId,
984
1003
  content: buildPromptText(args.content, args.attachments),
985
1004
  model: args.model,
986
- effort: args.effort as any,
1005
+ effort: args.effort as CodexReasoningEffort | undefined,
987
1006
  serviceTier: args.serviceTier,
988
1007
  planMode: args.planMode,
989
1008
  onToolRequest,
@@ -44,9 +44,16 @@ function expectedSettingsSnapshot(filePath: string, overrides: Partial<AppSettin
44
44
  planMode: false,
45
45
  },
46
46
  codex: {
47
- model: "gpt-5.5",
47
+ model: "gpt-5.6-sol",
48
+ modelOptions: {
49
+ reasoningEffort: "medium",
50
+ fastMode: false,
51
+ },
52
+ planMode: false,
53
+ },
54
+ cursor: {
55
+ model: "composer-2.5",
48
56
  modelOptions: {
49
- reasoningEffort: "high",
50
57
  fastMode: false,
51
58
  },
52
59
  planMode: false,
@@ -159,4 +166,43 @@ describe("AppSettingsManager", () => {
159
166
 
160
167
  manager.dispose()
161
168
  })
169
+
170
+ test("normalizes GPT-5.6 reasoning levels when settings are written", async () => {
171
+ const filePath = await createTempFilePath()
172
+ const manager = new AppSettingsManager(filePath)
173
+ await manager.initialize()
174
+
175
+ const sol = await manager.writePatch({
176
+ providerDefaults: {
177
+ codex: { model: "gpt-5.6-sol", modelOptions: { reasoningEffort: "ultra" } },
178
+ },
179
+ })
180
+ expect(sol.providerDefaults.codex.modelOptions.reasoningEffort).toBe("ultra")
181
+
182
+ const terra = await manager.writePatch({
183
+ providerDefaults: {
184
+ codex: { model: "gpt-5.6-terra", modelOptions: { reasoningEffort: "max" } },
185
+ },
186
+ })
187
+ expect(terra.providerDefaults.codex.modelOptions.reasoningEffort).toBe("max")
188
+
189
+ const luna = await manager.writePatch({
190
+ providerDefaults: {
191
+ codex: { model: "gpt-5.6-luna", modelOptions: { reasoningEffort: "ultra" } },
192
+ },
193
+ })
194
+ expect(luna.providerDefaults.codex.modelOptions.reasoningEffort).toBe("max")
195
+
196
+ const legacy = await manager.writePatch({
197
+ providerDefaults: {
198
+ codex: { model: "gpt-5.5", modelOptions: { reasoningEffort: "xhigh" } },
199
+ },
200
+ })
201
+ expect(legacy.providerDefaults.codex).toMatchObject({
202
+ model: "gpt-5.5",
203
+ modelOptions: { reasoningEffort: "xhigh", fastMode: false },
204
+ })
205
+
206
+ manager.dispose()
207
+ })
162
208
  })
@@ -7,11 +7,14 @@ import { getSettingsFilePath, LOG_PREFIX } from "../shared/branding"
7
7
  import {
8
8
  DEFAULT_CLAUDE_MODEL_OPTIONS,
9
9
  DEFAULT_CODEX_MODEL_OPTIONS,
10
+ DEFAULT_CURSOR_MODEL_OPTIONS,
10
11
  isClaudeReasoningEffort,
11
12
  isCodexReasoningEffort,
12
13
  normalizeClaudeContextWindow,
13
14
  normalizeClaudeModelId,
14
15
  normalizeCodexModelId,
16
+ normalizeCodexReasoningEffort,
17
+ normalizeCursorModelId,
15
18
  supportsClaudeMaxReasoningEffort,
16
19
  type AppSettingsPatch,
17
20
  type AppSettingsSnapshot,
@@ -21,6 +24,7 @@ import {
21
24
  type ChatSoundPreference,
22
25
  type ClaudeModelOptions,
23
26
  type CodexModelOptions,
27
+ type CursorModelOptions,
24
28
  type DefaultProviderPreference,
25
29
  type EditorPreset,
26
30
  type ProviderPreference,
@@ -45,6 +49,7 @@ interface AppSettingsFile {
45
49
  providerDefaults?: {
46
50
  claude?: Partial<ProviderPreference<Partial<ClaudeModelOptions>>> & { effort?: unknown }
47
51
  codex?: Partial<ProviderPreference<Partial<CodexModelOptions>>> & { effort?: unknown }
52
+ cursor?: Partial<ProviderPreference<Partial<CursorModelOptions>>>
48
53
  }
49
54
  }
50
55
 
@@ -104,10 +109,15 @@ function createDefaultProviderDefaults(): ChatProviderPreferences {
104
109
  planMode: false,
105
110
  },
106
111
  codex: {
107
- model: "gpt-5.5",
112
+ model: "gpt-5.6-sol",
108
113
  modelOptions: { ...DEFAULT_CODEX_MODEL_OPTIONS },
109
114
  planMode: false,
110
115
  },
116
+ cursor: {
117
+ model: "composer-2.5",
118
+ modelOptions: { ...DEFAULT_CURSOR_MODEL_OPTIONS },
119
+ planMode: false,
120
+ },
111
121
  }
112
122
  }
113
123
 
@@ -143,7 +153,7 @@ function normalizeChatSoundId(value: unknown): ChatSoundId {
143
153
  }
144
154
 
145
155
  function normalizeDefaultProvider(value: unknown): DefaultProviderPreference {
146
- return value === "claude" || value === "codex" || value === "last_used" ? value : "last_used"
156
+ return value === "claude" || value === "codex" || value === "cursor" || value === "last_used" ? value : "last_used"
147
157
  }
148
158
 
149
159
  function normalizeEditorPreset(value: unknown): EditorPreset {
@@ -187,15 +197,15 @@ function normalizeCodexPreference(value?: {
187
197
  modelOptions?: Partial<Record<keyof CodexModelOptions, unknown>>
188
198
  planMode?: unknown
189
199
  }): ProviderPreference<CodexModelOptions> {
200
+ const model = normalizeCodexModelId(typeof value?.model === "string" ? value.model : undefined)
190
201
  const reasoningEffort = value?.modelOptions?.reasoningEffort
191
202
  return {
192
- model: normalizeCodexModelId(typeof value?.model === "string" ? value.model : undefined),
203
+ model,
193
204
  modelOptions: {
194
- reasoningEffort: isCodexReasoningEffort(reasoningEffort)
195
- ? reasoningEffort
196
- : isCodexReasoningEffort(value?.effort)
197
- ? value.effort
198
- : DEFAULT_CODEX_MODEL_OPTIONS.reasoningEffort,
205
+ reasoningEffort: normalizeCodexReasoningEffort(
206
+ model,
207
+ isCodexReasoningEffort(reasoningEffort) ? reasoningEffort : value?.effort,
208
+ ),
199
209
  fastMode: typeof value?.modelOptions?.fastMode === "boolean"
200
210
  ? value.modelOptions.fastMode
201
211
  : DEFAULT_CODEX_MODEL_OPTIONS.fastMode,
@@ -204,11 +214,28 @@ function normalizeCodexPreference(value?: {
204
214
  }
205
215
  }
206
216
 
217
+ function normalizeCursorPreference(value?: {
218
+ model?: unknown
219
+ modelOptions?: Partial<Record<keyof CursorModelOptions, unknown>>
220
+ planMode?: unknown
221
+ }): ProviderPreference<CursorModelOptions> {
222
+ return {
223
+ model: normalizeCursorModelId(typeof value?.model === "string" ? value.model : undefined),
224
+ modelOptions: {
225
+ fastMode: typeof value?.modelOptions?.fastMode === "boolean"
226
+ ? value.modelOptions.fastMode
227
+ : DEFAULT_CURSOR_MODEL_OPTIONS.fastMode,
228
+ },
229
+ planMode: false,
230
+ }
231
+ }
232
+
207
233
  function normalizeProviderDefaults(value: AppSettingsFile["providerDefaults"] | undefined): ChatProviderPreferences {
208
234
  const defaults = createDefaultProviderDefaults()
209
235
  return {
210
236
  claude: normalizeClaudePreference(value?.claude ?? defaults.claude),
211
237
  codex: normalizeCodexPreference(value?.codex ?? defaults.codex),
238
+ cursor: normalizeCursorPreference(value?.cursor ?? defaults.cursor),
212
239
  }
213
240
  }
214
241
 
@@ -348,6 +375,14 @@ function applyPatch(state: AppSettingsState, patch: AppSettingsPatch): AppSettin
348
375
  ...patch.providerDefaults?.codex?.modelOptions,
349
376
  },
350
377
  },
378
+ cursor: {
379
+ ...state.providerDefaults.cursor,
380
+ ...patch.providerDefaults?.cursor,
381
+ modelOptions: {
382
+ ...state.providerDefaults.cursor.modelOptions,
383
+ ...patch.providerDefaults?.cursor?.modelOptions,
384
+ },
385
+ },
351
386
  },
352
387
  }, state.filePathDisplay).payload
353
388
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A minimal push/pull async queue used to turn callback/stream-driven agent
3
+ * output into an `AsyncIterable`. Shared by the provider harness adapters
4
+ * (Codex app-server, Cursor CLI).
5
+ */
6
+ export class AsyncQueue<T> implements AsyncIterable<T> {
7
+ private values: T[] = []
8
+ private resolvers: Array<(value: IteratorResult<T>) => void> = []
9
+ private done = false
10
+
11
+ push(value: T) {
12
+ if (this.done) return
13
+ const resolver = this.resolvers.shift()
14
+ if (resolver) {
15
+ resolver({ value, done: false })
16
+ return
17
+ }
18
+ this.values.push(value)
19
+ }
20
+
21
+ finish() {
22
+ if (this.done) return
23
+ this.done = true
24
+ while (this.resolvers.length > 0) {
25
+ const resolver = this.resolvers.shift()
26
+ resolver?.({ value: undefined as T, done: true })
27
+ }
28
+ }
29
+
30
+ [Symbol.asyncIterator](): AsyncIterator<T> {
31
+ return {
32
+ next: () => {
33
+ if (this.values.length > 0) {
34
+ return Promise.resolve({ value: this.values.shift() as T, done: false })
35
+ }
36
+ if (this.done) {
37
+ return Promise.resolve({ value: undefined as T, done: true })
38
+ }
39
+ return new Promise<IteratorResult<T>>((resolve) => {
40
+ this.resolvers.push(resolve)
41
+ })
42
+ },
43
+ }
44
+ }
45
+ }
@@ -210,7 +210,76 @@ describe("CodexAppServerManager", () => {
210
210
  expect(threadStart?.params.serviceTier).toBe("fast")
211
211
  expect(turnStart?.params.effort).toBe("xhigh")
212
212
  expect(turnStart?.params.serviceTier).toBe("fast")
213
- expect(turnStart?.params.collaborationMode?.settings?.reasoning_effort).toBeNull()
213
+ expect(turnStart?.params.collaborationMode?.settings?.reasoning_effort).toBe("xhigh")
214
+ })
215
+
216
+ test("forwards every supported GPT-5.6 model and reasoning combination", async () => {
217
+ const combinations = [
218
+ ...(["low", "medium", "high", "xhigh", "max", "ultra"] as const)
219
+ .map((effort) => ({ model: "gpt-5.6-sol", effort })),
220
+ ...(["low", "medium", "high", "xhigh", "max", "ultra"] as const)
221
+ .map((effort) => ({ model: "gpt-5.6-terra", effort })),
222
+ ...(["low", "medium", "high", "xhigh", "max"] as const)
223
+ .map((effort) => ({ model: "gpt-5.6-luna", effort })),
224
+ ]
225
+
226
+ expect(combinations).toHaveLength(17)
227
+
228
+ for (const [index, combination] of combinations.entries()) {
229
+ const threadId = `thread-matrix-${index}`
230
+ const process = new FakeCodexProcess((message, child) => {
231
+ if (message.method === "initialize") {
232
+ child.writeServerMessage({ id: message.id, result: { userAgent: "codex-test" } })
233
+ } else if (message.method === "thread/start") {
234
+ child.writeServerMessage({
235
+ id: message.id,
236
+ result: { thread: { id: threadId }, model: combination.model, reasoningEffort: combination.effort },
237
+ })
238
+ } else if (message.method === "turn/start") {
239
+ child.writeServerMessage({
240
+ id: message.id,
241
+ result: { turn: { id: `turn-matrix-${index}`, status: "completed", error: null } },
242
+ })
243
+ child.writeServerMessage({
244
+ method: "turn/completed",
245
+ params: {
246
+ threadId,
247
+ turn: { id: `turn-matrix-${index}`, status: "completed", error: null },
248
+ },
249
+ })
250
+ }
251
+ })
252
+ const manager = new CodexAppServerManager({ spawnProcess: () => process as never })
253
+ const chatId = `chat-matrix-${index}`
254
+
255
+ await manager.startSession({
256
+ chatId,
257
+ cwd: "/tmp/project",
258
+ model: combination.model,
259
+ sessionToken: null,
260
+ })
261
+ const turn = await manager.startTurn({
262
+ chatId,
263
+ model: combination.model,
264
+ effort: combination.effort,
265
+ content: "matrix test",
266
+ planMode: false,
267
+ onToolRequest: async () => ({}),
268
+ })
269
+ await collectStream(turn.stream)
270
+
271
+ const threadStart = process.messages.find((message: any) => message.method === "thread/start") as any
272
+ const turnStart = process.messages.find((message: any) => message.method === "turn/start") as any
273
+ expect(threadStart.params.model).toBe(combination.model)
274
+ expect(turnStart.params.model).toBe(combination.model)
275
+ expect(turnStart.params.effort).toBe(combination.effort)
276
+ expect(turnStart.params.collaborationMode.settings).toMatchObject({
277
+ model: combination.model,
278
+ reasoning_effort: combination.effort,
279
+ })
280
+
281
+ manager.stopSession(chatId)
282
+ }
214
283
  })
215
284
 
216
285
  test("maps thread token usage updates into context window transcript entries", async () => {
@@ -11,6 +11,9 @@ import type {
11
11
  TranscriptEntry,
12
12
  } from "../shared/types"
13
13
  import type { HarnessEvent, HarnessToolRequest, HarnessTurn } from "./harness-types"
14
+ import { AsyncQueue } from "./async-queue"
15
+ import { asNumber, asRecord } from "../shared/json"
16
+ import { timestamped } from "./transcript"
14
17
  import {
15
18
  type CollabAgentToolCallItem,
16
19
  type ContextCompactedNotification,
@@ -142,17 +145,6 @@ export interface GenerateStructuredArgs {
142
145
  serviceTier?: ServiceTier
143
146
  }
144
147
 
145
- function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
146
- entry: T,
147
- createdAt = Date.now()
148
- ): TranscriptEntry {
149
- return {
150
- _id: randomUUID(),
151
- createdAt,
152
- ...entry,
153
- } as TranscriptEntry
154
- }
155
-
156
148
  function codexSystemInitEntry(model: string): TranscriptEntry {
157
149
  return timestamped({
158
150
  kind: "system_init",
@@ -237,15 +229,6 @@ function contentFromMcpResult(item: McpToolCallItem): unknown {
237
229
  return item.result?.structuredContent ?? item.result?.content ?? null
238
230
  }
239
231
 
240
- function asRecord(value: unknown): Record<string, unknown> | null {
241
- if (!value || typeof value !== "object" || Array.isArray(value)) return null
242
- return value as Record<string, unknown>
243
- }
244
-
245
- function asNumber(value: unknown): number | undefined {
246
- return typeof value === "number" && Number.isFinite(value) ? value : undefined
247
- }
248
-
249
232
  function normalizeCodexTokenUsage(
250
233
  notification: ThreadTokenUsageUpdatedNotification,
251
234
  ): ContextWindowUsageSnapshot | null {
@@ -692,47 +675,6 @@ function itemToToolResults(item: ThreadItem): TranscriptEntry[] {
692
675
  }
693
676
  }
694
677
 
695
- class AsyncQueue<T> implements AsyncIterable<T> {
696
- private values: T[] = []
697
- private resolvers: Array<(value: IteratorResult<T>) => void> = []
698
- private done = false
699
-
700
- push(value: T) {
701
- if (this.done) return
702
- const resolver = this.resolvers.shift()
703
- if (resolver) {
704
- resolver({ value, done: false })
705
- return
706
- }
707
- this.values.push(value)
708
- }
709
-
710
- finish() {
711
- if (this.done) return
712
- this.done = true
713
- while (this.resolvers.length > 0) {
714
- const resolver = this.resolvers.shift()
715
- resolver?.({ value: undefined as T, done: true })
716
- }
717
- }
718
-
719
- [Symbol.asyncIterator](): AsyncIterator<T> {
720
- return {
721
- next: () => {
722
- if (this.values.length > 0) {
723
- return Promise.resolve({ value: this.values.shift() as T, done: false })
724
- }
725
- if (this.done) {
726
- return Promise.resolve({ value: undefined as T, done: true })
727
- }
728
- return new Promise<IteratorResult<T>>((resolve) => {
729
- this.resolvers.push(resolve)
730
- })
731
- },
732
- }
733
- }
734
- }
735
-
736
678
  export class CodexAppServerManager {
737
679
  private readonly sessions = new Map<string, SessionContext>()
738
680
  private readonly spawnProcess: SpawnCodexAppServer
@@ -880,7 +822,7 @@ export class CodexAppServerManager {
880
822
  mode: args.planMode ? "plan" : "default",
881
823
  settings: {
882
824
  model: args.model,
883
- reasoning_effort: null,
825
+ reasoning_effort: args.effort ?? null,
884
826
  developer_instructions: null,
885
827
  },
886
828
  },