@junctionpanel/server 0.1.31 → 0.1.32

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.
Files changed (59) hide show
  1. package/dist/server/client/daemon-client.d.ts +1 -0
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +27 -0
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-manager.d.ts +2 -0
  6. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  7. package/dist/server/server/agent/agent-manager.js +63 -4
  8. package/dist/server/server/agent/agent-manager.js.map +1 -1
  9. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-projections.js +9 -2
  11. package/dist/server/server/agent/agent-projections.js.map +1 -1
  12. package/dist/server/server/agent/agent-sdk-types.d.ts +13 -2
  13. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  14. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  15. package/dist/server/server/agent/agent-storage.d.ts +30 -30
  16. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  17. package/dist/server/server/agent/agent-storage.js +33 -1
  18. package/dist/server/server/agent/agent-storage.js.map +1 -1
  19. package/dist/server/server/agent/codex-config.d.ts +12 -0
  20. package/dist/server/server/agent/codex-config.d.ts.map +1 -0
  21. package/dist/server/server/agent/codex-config.js +42 -0
  22. package/dist/server/server/agent/codex-config.js.map +1 -0
  23. package/dist/server/server/agent/mcp-server.js +8 -8
  24. package/dist/server/server/agent/mcp-server.js.map +1 -1
  25. package/dist/server/server/agent/provider-launch-config.d.ts +2 -2
  26. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  27. package/dist/server/server/agent/provider-launch-config.js +32 -5
  28. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  29. package/dist/server/server/agent/provider-manifest.js +10 -10
  30. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  31. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +17 -25
  32. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -1
  33. package/dist/server/server/agent/providers/claude/model-catalog.js +228 -40
  34. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -1
  35. package/dist/server/server/agent/providers/claude-agent.d.ts +2 -1
  36. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  37. package/dist/server/server/agent/providers/claude-agent.js +201 -36
  38. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  39. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +30 -1
  40. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  41. package/dist/server/server/agent/providers/codex-app-server-agent.js +309 -49
  42. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  43. package/dist/server/server/agent/providers/gemini-agent.d.ts +17 -5
  44. package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
  45. package/dist/server/server/agent/providers/gemini-agent.js +1040 -482
  46. package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
  47. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  48. package/dist/server/server/agent/providers/opencode-agent.js +1 -1
  49. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  50. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  51. package/dist/server/server/session.d.ts +1 -0
  52. package/dist/server/server/session.d.ts.map +1 -1
  53. package/dist/server/server/session.js +35 -0
  54. package/dist/server/server/session.js.map +1 -1
  55. package/dist/server/shared/messages.d.ts +1550 -1298
  56. package/dist/server/shared/messages.d.ts.map +1 -1
  57. package/dist/server/shared/messages.js +19 -0
  58. package/dist/server/shared/messages.js.map +1 -1
  59. package/package.json +3 -2
@@ -7,7 +7,7 @@ import path from "node:path";
7
7
  import { query, } from "@anthropic-ai/claude-agent-sdk";
8
8
  import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./claude/tool-call-mapper.js";
9
9
  import { isTaskNotificationUserContent, mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./claude/task-notification-tool-call.js";
10
- import { buildClaudeModelFamilyAliases, buildClaudeSelectableModelIds, listClaudeCatalogModels, } from "./claude/model-catalog.js";
10
+ import { buildClaudeModelDefinitions, buildClaudeModelFamilyAliases, buildClaudeSelectableModelIds, getClaudeFallbackModels, normalizeClaudeThinkingOptionId, resolveClaudeThinkingPreset, } from "./claude/model-catalog.js";
11
11
  import { buildToolCallDisplayModel } from "../../../shared/tool-call-display.js";
12
12
  import { applyProviderEnv, isProviderCommandAvailable, } from "../provider-launch-config.js";
13
13
  import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
@@ -265,6 +265,52 @@ function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings) {
265
265
  },
266
266
  };
267
267
  }
268
+ function buildClaudeControlOptions(params) {
269
+ const baseOptions = {
270
+ ...(params.cwd ? { cwd: params.cwd } : {}),
271
+ ...(params.claudePath
272
+ ? { pathToClaudeCodeExecutable: params.claudePath }
273
+ : {}),
274
+ settingSources: CLAUDE_SETTING_SOURCES,
275
+ includePartialMessages: false,
276
+ stderr: (data) => {
277
+ params.logger.warn({ stderr: data.trim() }, "Claude Agent SDK stderr");
278
+ },
279
+ env: {
280
+ ...process.env,
281
+ MCP_TIMEOUT: "600000",
282
+ MCP_TOOL_TIMEOUT: "600000",
283
+ },
284
+ };
285
+ return applyRuntimeSettingsToClaudeOptions(baseOptions, params.runtimeSettings);
286
+ }
287
+ async function fetchClaudeSupportedModels(params) {
288
+ const abortController = new AbortController();
289
+ const controlQuery = query({
290
+ prompt: "",
291
+ options: {
292
+ ...buildClaudeControlOptions(params),
293
+ abortController,
294
+ },
295
+ });
296
+ try {
297
+ return await controlQuery.supportedModels();
298
+ }
299
+ finally {
300
+ abortController.abort();
301
+ try {
302
+ await Promise.race([
303
+ controlQuery.interrupt(),
304
+ new Promise((resolve) => {
305
+ setTimeout(resolve, 1000);
306
+ }),
307
+ ]);
308
+ }
309
+ catch {
310
+ // Ignore teardown errors for short-lived control queries.
311
+ }
312
+ }
313
+ }
268
314
  function summarizeClaudeOptionsForLog(options) {
269
315
  const systemPromptRaw = options.systemPrompt;
270
316
  const systemPromptSummary = (() => {
@@ -300,6 +346,12 @@ function summarizeClaudeOptionsForLog(options) {
300
346
  : [],
301
347
  enableFileCheckpointing: options.enableFileCheckpointing === true,
302
348
  hasResume: typeof options.resume === "string" && options.resume.length > 0,
349
+ thinkingMode: typeof options.thinking?.type === "string" ? options.thinking.type : null,
350
+ thinkingBudgetTokens: options.thinking?.type === "enabled" &&
351
+ typeof options.thinking.budgetTokens === "number"
352
+ ? options.thinking.budgetTokens
353
+ : null,
354
+ effort: typeof options.effort === "string" ? options.effort : null,
303
355
  maxThinkingTokens: typeof options.maxThinkingTokens === "number"
304
356
  ? options.maxThinkingTokens
305
357
  : null,
@@ -536,6 +588,20 @@ function isClaudeContentChunk(value) {
536
588
  function isClaudeExtra(value) {
537
589
  return isMetadata(value);
538
590
  }
591
+ function readPreferredClaudeModeId(extra) {
592
+ const candidate = extra?.claude?.junctionPreferredModeId;
593
+ if (!isPermissionMode(candidate) || candidate === "plan") {
594
+ return null;
595
+ }
596
+ return candidate;
597
+ }
598
+ function getClaudeSdkExtraOptions(extra) {
599
+ if (!extra?.claude) {
600
+ return undefined;
601
+ }
602
+ const { junctionPreferredModeId: _junctionPreferredModeId, ...claudeOptions } = extra.claude;
603
+ return claudeOptions;
604
+ }
539
605
  function isPermissionUpdate(value) {
540
606
  if (!isMetadata(value)) {
541
607
  return false;
@@ -1007,11 +1073,13 @@ export class ClaudeAgentClient {
1007
1073
  }
1008
1074
  async createSession(config) {
1009
1075
  const claudeConfig = this.assertConfig(config);
1076
+ const knownModels = await this.discoverKnownModels(claudeConfig.cwd);
1010
1077
  return new ClaudeAgentSession(claudeConfig, {
1011
1078
  defaults: this.defaults,
1012
1079
  claudePath: this.claudePath,
1013
1080
  runtimeSettings: this.runtimeSettings,
1014
1081
  logger: this.logger,
1082
+ knownModels,
1015
1083
  });
1016
1084
  }
1017
1085
  async resumeSession(handle, overrides) {
@@ -1022,16 +1090,33 @@ export class ClaudeAgentClient {
1022
1090
  }
1023
1091
  const mergedConfig = { ...merged, provider: "claude", cwd: merged.cwd };
1024
1092
  const claudeConfig = this.assertConfig(mergedConfig);
1093
+ const knownModels = await this.discoverKnownModels(claudeConfig.cwd);
1025
1094
  return new ClaudeAgentSession(claudeConfig, {
1026
1095
  defaults: this.defaults,
1027
1096
  claudePath: this.claudePath,
1028
1097
  runtimeSettings: this.runtimeSettings,
1029
1098
  handle,
1030
1099
  logger: this.logger,
1100
+ knownModels,
1031
1101
  });
1032
1102
  }
1033
- async listModels(_options) {
1034
- return listClaudeCatalogModels();
1103
+ async listModels(options) {
1104
+ return this.discoverKnownModels(options?.cwd);
1105
+ }
1106
+ async discoverKnownModels(cwd) {
1107
+ try {
1108
+ const models = await fetchClaudeSupportedModels({
1109
+ cwd,
1110
+ claudePath: this.claudePath,
1111
+ runtimeSettings: this.runtimeSettings,
1112
+ logger: this.logger,
1113
+ });
1114
+ return buildClaudeModelDefinitions(models);
1115
+ }
1116
+ catch (error) {
1117
+ this.logger.warn({ err: error }, "Failed to discover Claude models dynamically; using fallback catalog");
1118
+ return getClaudeFallbackModels();
1119
+ }
1035
1120
  }
1036
1121
  async listPersistedAgents(options) {
1037
1122
  const configDir = resolveClaudeConfigDir();
@@ -1056,7 +1141,7 @@ export class ClaudeAgentClient {
1056
1141
  async isAvailable() {
1057
1142
  const commandConfig = this.runtimeSettings?.command;
1058
1143
  if (commandConfig?.mode === "replace") {
1059
- return isProviderCommandAvailable(commandConfig, resolveClaudeBinary);
1144
+ return isProviderCommandAvailable(commandConfig, resolveClaudeBinary, applyProviderEnv(process.env, this.runtimeSettings));
1060
1145
  }
1061
1146
  return this.claudePath !== null;
1062
1147
  }
@@ -1073,6 +1158,7 @@ class ClaudeAgentSession {
1073
1158
  this.capabilities = CLAUDE_CAPABILITIES;
1074
1159
  this.query = null;
1075
1160
  this.input = null;
1161
+ this.lastNonPlanMode = "default";
1076
1162
  this.availableModes = DEFAULT_MODES;
1077
1163
  this.toolUseCache = new Map();
1078
1164
  this.toolUseIndexToId = new Map();
@@ -1093,8 +1179,8 @@ class ClaudeAgentSession {
1093
1179
  this.activeTurnPromise = null;
1094
1180
  this.cachedRuntimeInfo = null;
1095
1181
  this.lastOptionsModel = null;
1096
- this.selectableModelIds = buildClaudeSelectableModelIds();
1097
- this.selectableModelFamilyAliases = buildClaudeModelFamilyAliases();
1182
+ this.pendingExplicitModeSync = false;
1183
+ this.runtimeReportedModelId = null;
1098
1184
  this.activeSidechains = new Map();
1099
1185
  this.compacting = false;
1100
1186
  this.queryPumpPromise = null;
@@ -1106,6 +1192,26 @@ class ClaudeAgentSession {
1106
1192
  this.handlePermissionRequest = async (toolName, input, options) => {
1107
1193
  const requestId = `permission-${randomUUID()}`;
1108
1194
  const kind = resolvePermissionKind(toolName, input);
1195
+ if (kind === "plan" && this.lastNonPlanMode === "bypassPermissions") {
1196
+ this.currentMode = "bypassPermissions";
1197
+ this.cachedRuntimeInfo = null;
1198
+ this.pushToolCall(mapClaudeCompletedToolCall({
1199
+ name: "plan_approval",
1200
+ callId: options.toolUseID ?? requestId,
1201
+ input,
1202
+ output: { approved: true, automatic: true },
1203
+ }));
1204
+ this.pushEvent({
1205
+ type: "permission_resolved",
1206
+ provider: "claude",
1207
+ requestId,
1208
+ resolution: { behavior: "allow" },
1209
+ });
1210
+ return {
1211
+ behavior: "allow",
1212
+ updatedInput: input,
1213
+ };
1214
+ }
1109
1215
  const metadata = {};
1110
1216
  if (options.toolUseID) {
1111
1217
  metadata.toolUseId = options.toolUseID;
@@ -1190,6 +1296,21 @@ class ClaudeAgentSession {
1190
1296
  throw new Error(`Invalid mode '${config.modeId}' for Claude provider. Valid modes: ${validModesList}`);
1191
1297
  }
1192
1298
  this.currentMode = isPermissionMode(config.modeId) ? config.modeId : "default";
1299
+ const preferredNonPlanMode = readPreferredClaudeModeId(config.extra);
1300
+ if (this.currentMode !== "plan") {
1301
+ this.lastNonPlanMode = this.currentMode;
1302
+ }
1303
+ else if (preferredNonPlanMode) {
1304
+ this.lastNonPlanMode = preferredNonPlanMode;
1305
+ }
1306
+ this.knownModels =
1307
+ options.knownModels && options.knownModels.length > 0
1308
+ ? options.knownModels
1309
+ : getClaudeFallbackModels();
1310
+ this.selectableModelIds = buildClaudeSelectableModelIds(this.knownModels);
1311
+ this.selectableModelFamilyAliases = buildClaudeModelFamilyAliases(this.knownModels);
1312
+ this.config.thinkingOptionId =
1313
+ normalizeClaudeThinkingOptionId(this.config.thinkingOptionId) ?? undefined;
1193
1314
  }
1194
1315
  get id() {
1195
1316
  return this.claudeSessionId;
@@ -1198,11 +1319,24 @@ class ClaudeAgentSession {
1198
1319
  if (this.cachedRuntimeInfo) {
1199
1320
  return { ...this.cachedRuntimeInfo };
1200
1321
  }
1322
+ const thinkingOptionId = normalizeClaudeThinkingOptionId(this.config.thinkingOptionId) ?? "off";
1201
1323
  const info = {
1202
1324
  provider: "claude",
1203
1325
  sessionId: this.claudeSessionId,
1204
1326
  model: this.lastOptionsModel,
1327
+ thinkingOptionId,
1205
1328
  modeId: this.currentMode ?? null,
1329
+ extra: {
1330
+ preferredModeId: this.lastNonPlanMode,
1331
+ ...(this.runtimeReportedModelId
1332
+ ? { reportedModelId: this.runtimeReportedModelId }
1333
+ : {}),
1334
+ ...(this.lastOptionsModel &&
1335
+ this.runtimeReportedModelId &&
1336
+ this.lastOptionsModel !== this.runtimeReportedModelId
1337
+ ? { resolvedModelId: this.lastOptionsModel }
1338
+ : {}),
1339
+ },
1206
1340
  };
1207
1341
  this.cachedRuntimeInfo = info;
1208
1342
  return { ...info };
@@ -1238,7 +1372,19 @@ class ClaudeAgentSession {
1238
1372
  provider: "claude",
1239
1373
  sessionId: this.claudeSessionId,
1240
1374
  model: this.lastOptionsModel,
1375
+ thinkingOptionId: normalizeClaudeThinkingOptionId(this.config.thinkingOptionId) ?? "off",
1241
1376
  modeId: this.currentMode ?? null,
1377
+ extra: {
1378
+ preferredModeId: this.lastNonPlanMode,
1379
+ ...(this.runtimeReportedModelId
1380
+ ? { reportedModelId: this.runtimeReportedModelId }
1381
+ : {}),
1382
+ ...(this.lastOptionsModel &&
1383
+ this.runtimeReportedModelId &&
1384
+ this.lastOptionsModel !== this.runtimeReportedModelId
1385
+ ? { resolvedModelId: this.lastOptionsModel }
1386
+ : {}),
1387
+ },
1242
1388
  };
1243
1389
  if (!this.claudeSessionId) {
1244
1390
  throw new Error("Session ID not set after run completed");
@@ -1406,6 +1552,10 @@ class ClaudeAgentSession {
1406
1552
  const query = await this.ensureQuery();
1407
1553
  await query.setPermissionMode(normalized);
1408
1554
  this.currentMode = normalized;
1555
+ if (normalized !== "plan") {
1556
+ this.lastNonPlanMode = normalized;
1557
+ }
1558
+ this.cachedRuntimeInfo = null;
1409
1559
  }
1410
1560
  async setModel(modelId) {
1411
1561
  const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
@@ -1418,21 +1568,16 @@ class ClaudeAgentSession {
1418
1568
  this.persistence = null;
1419
1569
  }
1420
1570
  async setThinkingOption(thinkingOptionId) {
1421
- const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
1422
- ? thinkingOptionId
1423
- : null;
1424
- if (!normalizedThinkingOptionId || normalizedThinkingOptionId === "default") {
1425
- this.config.thinkingOptionId = undefined;
1426
- }
1427
- else if (normalizedThinkingOptionId === "on") {
1428
- this.config.thinkingOptionId = "on";
1429
- }
1430
- else if (normalizedThinkingOptionId === "off") {
1431
- this.config.thinkingOptionId = "off";
1432
- }
1433
- else {
1434
- throw new Error(`Unknown thinking option: ${normalizedThinkingOptionId}`);
1435
- }
1571
+ const normalizedThinkingOptionId = normalizeClaudeThinkingOptionId(thinkingOptionId);
1572
+ if (thinkingOptionId !== null &&
1573
+ typeof thinkingOptionId === "string" &&
1574
+ thinkingOptionId.trim().length > 0 &&
1575
+ thinkingOptionId !== "default" &&
1576
+ !normalizedThinkingOptionId) {
1577
+ throw new Error(`Unknown thinking option: ${thinkingOptionId}`);
1578
+ }
1579
+ this.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
1580
+ this.cachedRuntimeInfo = null;
1436
1581
  this.queryRestartNeeded = true;
1437
1582
  }
1438
1583
  getPendingPermissions() {
@@ -1447,7 +1592,8 @@ class ClaudeAgentSession {
1447
1592
  pending.cleanup?.();
1448
1593
  if (response.behavior === "allow") {
1449
1594
  if (pending.request.kind === "plan") {
1450
- await this.setMode("acceptEdits");
1595
+ this.currentMode = this.lastNonPlanMode;
1596
+ this.cachedRuntimeInfo = null;
1451
1597
  this.pushToolCall(mapClaudeCompletedToolCall({
1452
1598
  name: "plan_approval",
1453
1599
  callId: pending.request.id,
@@ -1463,6 +1609,10 @@ class ClaudeAgentSession {
1463
1609
  pending.resolve(result);
1464
1610
  }
1465
1611
  else {
1612
+ if (pending.request.kind === "plan") {
1613
+ this.currentMode = "plan";
1614
+ this.cachedRuntimeInfo = null;
1615
+ }
1466
1616
  if (pending.request.kind === "tool") {
1467
1617
  this.pushToolCall(mapClaudeFailedToolCall({
1468
1618
  name: pending.request.name,
@@ -1773,6 +1923,10 @@ class ClaudeAgentSession {
1773
1923
  this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
1774
1924
  this.input = input;
1775
1925
  this.query = query({ prompt: input, options });
1926
+ if (this.currentMode !== "default") {
1927
+ this.pendingExplicitModeSync = true;
1928
+ await this.query.setPermissionMode(this.currentMode);
1929
+ }
1776
1930
  // Do not kick off background control-plane queries here. Methods like
1777
1931
  // supportedCommands()/setPermissionMode() may execute immediately after
1778
1932
  // ensureQuery() (for listCommands()/setMode()), and sharing the same query
@@ -1796,17 +1950,8 @@ class ClaudeAgentSession {
1796
1950
  }
1797
1951
  }
1798
1952
  buildOptions() {
1799
- const configuredThinkingOptionId = this.config.thinkingOptionId;
1800
- const thinkingOptionId = configuredThinkingOptionId && configuredThinkingOptionId !== "default"
1801
- ? configuredThinkingOptionId
1802
- : "off";
1803
- let maxThinkingTokens;
1804
- if (thinkingOptionId === "on") {
1805
- maxThinkingTokens = 10000;
1806
- }
1807
- else if (thinkingOptionId === "off") {
1808
- maxThinkingTokens = 0;
1809
- }
1953
+ const thinkingPreset = resolveClaudeThinkingPreset(this.config.thinkingOptionId) ??
1954
+ resolveClaudeThinkingPreset("off");
1810
1955
  const appendedSystemPrompt = [
1811
1956
  getOrchestratorModeInstructions(),
1812
1957
  this.config.systemPrompt?.trim(),
@@ -1817,6 +1962,10 @@ class ClaudeAgentSession {
1817
1962
  cwd: this.config.cwd,
1818
1963
  includePartialMessages: true,
1819
1964
  permissionMode: this.currentMode,
1965
+ ...(this.currentMode === "bypassPermissions" ||
1966
+ this.lastNonPlanMode === "bypassPermissions"
1967
+ ? { allowDangerouslySkipPermissions: true }
1968
+ : {}),
1820
1969
  agents: this.defaults?.agents,
1821
1970
  canUseTool: this.handlePermissionRequest,
1822
1971
  ...(this.claudePath ? { pathToClaudeCodeExecutable: this.claudePath } : {}),
@@ -1842,8 +1991,9 @@ class ClaudeAgentSession {
1842
1991
  // If we have a session ID from a previous query (e.g., after interrupt),
1843
1992
  // resume that session to continue the conversation history.
1844
1993
  ...(this.claudeSessionId ? { resume: this.claudeSessionId } : {}),
1845
- ...(maxThinkingTokens !== undefined ? { maxThinkingTokens } : {}),
1846
- ...this.config.extra?.claude,
1994
+ ...(thinkingPreset ? { thinking: thinkingPreset.thinking } : {}),
1995
+ ...(thinkingPreset?.effort ? { effort: thinkingPreset.effort } : {}),
1996
+ ...getClaudeSdkExtraOptions(this.config.extra),
1847
1997
  };
1848
1998
  if (this.config.mcpServers) {
1849
1999
  base.mcpServers = this.normalizeMcpServers(this.config.mcpServers);
@@ -2830,10 +2980,25 @@ class ClaudeAgentSession {
2830
2980
  `This indicates a session identity corruption bug.`);
2831
2981
  }
2832
2982
  this.availableModes = DEFAULT_MODES;
2833
- this.currentMode = message.permissionMode;
2983
+ if (this.pendingExplicitModeSync &&
2984
+ this.currentMode !== "default" &&
2985
+ message.permissionMode === "default") {
2986
+ this.logger.debug({
2987
+ reportedPermissionMode: message.permissionMode,
2988
+ preservedPermissionMode: this.currentMode,
2989
+ }, "Ignoring stale Claude init permission mode after explicit mode sync");
2990
+ }
2991
+ else {
2992
+ this.currentMode = message.permissionMode;
2993
+ if (message.permissionMode !== "plan") {
2994
+ this.lastNonPlanMode = message.permissionMode;
2995
+ }
2996
+ }
2997
+ this.pendingExplicitModeSync = false;
2834
2998
  this.persistence = null;
2835
2999
  // Capture actual model from SDK init message (not just the configured model)
2836
3000
  if (message.model) {
3001
+ this.runtimeReportedModelId = message.model;
2837
3002
  const normalizedModel = normalizeClaudeRuntimeModelId({
2838
3003
  runtimeModelId: message.model,
2839
3004
  supportedModelIds: this.selectableModelIds,