@lvce-editor/chat-view 6.9.0 → 6.12.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.
@@ -1269,15 +1269,9 @@ const sendMessagePortToChatNetworkWorker = async port => {
1269
1269
  const sendMessagePortToChatToolWorker = async port => {
1270
1270
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToChatToolWorker', port, 'HandleMessagePort.handleMessagePort');
1271
1271
  };
1272
- const writeFile = async (uri, text) => {
1273
- await invoke$1('FileSystem.writeFile', uri, text);
1274
- };
1275
1272
  const activateByEvent$1 = (event, assetDir, platform) => {
1276
1273
  return invoke$1('ExtensionHostManagement.activateByEvent', event, assetDir, platform);
1277
1274
  };
1278
- const getWorkspacePath = () => {
1279
- return invoke$1('Workspace.getPath');
1280
- };
1281
1275
  const getPreference = async key => {
1282
1276
  return await invoke$1('Preferences.get', key);
1283
1277
  };
@@ -1670,6 +1664,10 @@ const createDefaultState = () => {
1670
1664
  chatMessageFontFamily: 'system-ui',
1671
1665
  chatMessageFontSize,
1672
1666
  chatMessageLineHeight,
1667
+ chatSendAreaPaddingBottom: 10,
1668
+ chatSendAreaPaddingLeft: 8,
1669
+ chatSendAreaPaddingRight: 8,
1670
+ chatSendAreaPaddingTop: 10,
1673
1671
  composerDropActive: false,
1674
1672
  composerDropEnabled: true,
1675
1673
  composerFontFamily: 'system-ui',
@@ -1716,6 +1714,7 @@ const createDefaultState = () => {
1716
1714
  }],
1717
1715
  questionToolEnabled: false,
1718
1716
  renamingSessionId: '',
1717
+ runMode: 'local',
1719
1718
  selectedModelId: defaultModelId,
1720
1719
  selectedProjectId: defaultProjectId,
1721
1720
  selectedSessionId: defaultSessionId,
@@ -1725,7 +1724,12 @@ const createDefaultState = () => {
1725
1724
  projectId: defaultProjectId,
1726
1725
  title: defaultSessionTitle()
1727
1726
  }],
1727
+ showRunMode: false,
1728
1728
  streamingEnabled: true,
1729
+ textAreaPaddingBottom: 0,
1730
+ textAreaPaddingLeft: 12,
1731
+ textAreaPaddingRight: 12,
1732
+ textAreaPaddingTop: 0,
1729
1733
  tokensMax: 0,
1730
1734
  tokensUsed: 0,
1731
1735
  uid: 0,
@@ -2519,7 +2523,7 @@ const getRenderHtmlCss = (sessions, selectedSessionId) => {
2519
2523
  const isEqual$1 = (oldState, newState) => {
2520
2524
  const oldRenderHtmlCss = getRenderHtmlCss(oldState.sessions, oldState.selectedSessionId);
2521
2525
  const newRenderHtmlCss = getRenderHtmlCss(newState.sessions, newState.selectedSessionId);
2522
- return oldState.initial === newState.initial && oldState.chatMessageFontFamily === newState.chatMessageFontFamily && oldState.chatMessageFontSize === newState.chatMessageFontSize && oldState.chatMessageLineHeight === newState.chatMessageLineHeight && oldState.composerHeight === newState.composerHeight && oldState.composerLineHeight === newState.composerLineHeight && oldState.composerFontFamily === newState.composerFontFamily && oldState.composerFontSize === newState.composerFontSize && oldState.listItemHeight === newState.listItemHeight && oldRenderHtmlCss === newRenderHtmlCss;
2526
+ return oldState.initial === newState.initial && oldState.chatMessageFontFamily === newState.chatMessageFontFamily && oldState.chatMessageFontSize === newState.chatMessageFontSize && oldState.chatMessageLineHeight === newState.chatMessageLineHeight && oldState.chatSendAreaPaddingTop === newState.chatSendAreaPaddingTop && oldState.chatSendAreaPaddingLeft === newState.chatSendAreaPaddingLeft && oldState.chatSendAreaPaddingRight === newState.chatSendAreaPaddingRight && oldState.chatSendAreaPaddingBottom === newState.chatSendAreaPaddingBottom && oldState.composerHeight === newState.composerHeight && oldState.composerLineHeight === newState.composerLineHeight && oldState.composerFontFamily === newState.composerFontFamily && oldState.composerFontSize === newState.composerFontSize && oldState.listItemHeight === newState.listItemHeight && oldState.textAreaPaddingTop === newState.textAreaPaddingTop && oldState.textAreaPaddingLeft === newState.textAreaPaddingLeft && oldState.textAreaPaddingRight === newState.textAreaPaddingRight && oldState.textAreaPaddingBottom === newState.textAreaPaddingBottom && oldRenderHtmlCss === newRenderHtmlCss;
2523
2527
  };
2524
2528
 
2525
2529
  const diffFocus = (oldState, newState) => {
@@ -2541,7 +2545,7 @@ const isEqualProjectExpandedIds = (a, b) => {
2541
2545
  return true;
2542
2546
  };
2543
2547
  const isEqual = (oldState, newState) => {
2544
- return oldState.authEnabled === newState.authEnabled && oldState.authErrorMessage === newState.authErrorMessage && oldState.authStatus === newState.authStatus && oldState.composerDropActive === newState.composerDropActive && oldState.composerDropEnabled === newState.composerDropEnabled && oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && isEqualProjectExpandedIds(oldState.projectExpandedIds, newState.projectExpandedIds) && oldState.projectListScrollTop === newState.projectListScrollTop && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedProjectId === newState.selectedProjectId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.useChatMathWorker === newState.useChatMathWorker && oldState.viewMode === newState.viewMode && oldState.voiceDictationEnabled === newState.voiceDictationEnabled;
2548
+ return oldState.authEnabled === newState.authEnabled && oldState.authErrorMessage === newState.authErrorMessage && oldState.authStatus === newState.authStatus && oldState.composerDropActive === newState.composerDropActive && oldState.composerDropEnabled === newState.composerDropEnabled && oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && isEqualProjectExpandedIds(oldState.projectExpandedIds, newState.projectExpandedIds) && oldState.projectListScrollTop === newState.projectListScrollTop && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedProjectId === newState.selectedProjectId && oldState.selectedSessionId === newState.selectedSessionId && oldState.showRunMode === newState.showRunMode && oldState.runMode === newState.runMode && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.useChatMathWorker === newState.useChatMathWorker && oldState.viewMode === newState.viewMode && oldState.voiceDictationEnabled === newState.voiceDictationEnabled;
2545
2549
  };
2546
2550
 
2547
2551
  const diffScrollTop = (oldState, newState) => {
@@ -3132,12 +3136,14 @@ const selectProject = async (state, projectId) => {
3132
3136
  };
3133
3137
  };
3134
3138
 
3139
+ const fileSchemeRegex = /^file:\/\//;
3140
+ const trailingSlashesRegex$4 = /\/+$/;
3135
3141
  const getProjectName = (uri, fallbackIndex) => {
3136
3142
  if (!uri) {
3137
3143
  return `Project ${fallbackIndex}`;
3138
3144
  }
3139
- const withoutScheme = uri.replace(/^file:\/\//, '');
3140
- const normalized = withoutScheme.replace(/\/+$/, '');
3145
+ const withoutScheme = uri.replace(fileSchemeRegex, '');
3146
+ const normalized = withoutScheme.replace(trailingSlashesRegex$4, '');
3141
3147
  const lastSegment = normalized.split('/').pop();
3142
3148
  if (!lastSegment) {
3143
3149
  return `Project ${fallbackIndex}`;
@@ -3200,6 +3206,7 @@ const update = async settings => {
3200
3206
  await invoke$1('Preferences.update', settings);
3201
3207
  };
3202
3208
 
3209
+ const trailingSlashesRegex$3 = /\/+$/;
3203
3210
  const isLoginResponse = value => {
3204
3211
  if (!value || typeof value !== 'object') {
3205
3212
  return false;
@@ -3207,7 +3214,7 @@ const isLoginResponse = value => {
3207
3214
  return true;
3208
3215
  };
3209
3216
  const trimTrailingSlashes = value => {
3210
- return value.replace(/\/+$/, '');
3217
+ return value.replace(trailingSlashesRegex$3, '');
3211
3218
  };
3212
3219
  const handleClickLogin = async state => {
3213
3220
  if (!state.backendUrl) {
@@ -3349,240 +3356,6 @@ const getTools = async () => {
3349
3356
  return invoke$3('ChatTool.getTools');
3350
3357
  };
3351
3358
 
3352
- const executeAskQuestionTool = args => {
3353
- const normalized = args && typeof args === 'object' ? args : {};
3354
- const question = Reflect.get(normalized, 'question');
3355
- const answers = Reflect.get(normalized, 'answers');
3356
- return JSON.stringify({
3357
- answers: Array.isArray(answers) ? answers.filter(answer => typeof answer === 'string') : [],
3358
- ok: true,
3359
- question: typeof question === 'string' ? question : ''
3360
- });
3361
- };
3362
-
3363
- const getToolErrorPayload = error => {
3364
- const rawStack = error && typeof error === 'object' ? Reflect.get(error, 'stack') : undefined;
3365
- return {
3366
- error: String(error),
3367
- ...(typeof rawStack === 'string' && rawStack.trim() ? {
3368
- errorStack: rawStack,
3369
- stack: rawStack
3370
- } : {})
3371
- };
3372
- };
3373
-
3374
- const executeGetWorkspaceUriTool = async (_args, _options) => {
3375
- try {
3376
- const workspaceUri = await getWorkspacePath();
3377
- return JSON.stringify({
3378
- workspaceUri
3379
- });
3380
- } catch (error) {
3381
- return JSON.stringify(getToolErrorPayload(error));
3382
- }
3383
- };
3384
-
3385
- const isAbsoluteUri$1 = value => {
3386
- return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
3387
- };
3388
- const executeListFilesTool = async (args, _options) => {
3389
- const uri = typeof args.uri === 'string' ? args.uri : '';
3390
- if (!uri || !isAbsoluteUri$1(uri)) {
3391
- return JSON.stringify({
3392
- error: 'Invalid argument: uri must be an absolute URI.'
3393
- });
3394
- }
3395
- try {
3396
- const entries = await invoke$1('FileSystem.readDirWithFileTypes', uri);
3397
- return JSON.stringify({
3398
- entries,
3399
- uri
3400
- });
3401
- } catch (error) {
3402
- return JSON.stringify({
3403
- ...getToolErrorPayload(error),
3404
- uri
3405
- });
3406
- }
3407
- };
3408
-
3409
- const isPathTraversalAttempt = path => {
3410
- if (!path) {
3411
- return false;
3412
- }
3413
- if (path.startsWith('/') || path.startsWith('\\')) {
3414
- return true;
3415
- }
3416
- if (path.startsWith('file://')) {
3417
- return true;
3418
- }
3419
- if (/^[a-zA-Z]:[\\/]/.test(path)) {
3420
- return true;
3421
- }
3422
- const segments = path.split(/[\\/]/);
3423
- return segments.includes('..');
3424
- };
3425
-
3426
- const normalizeRelativePath = path => {
3427
- const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
3428
- if (segments.length === 0) {
3429
- return '.';
3430
- }
3431
- return segments.join('/');
3432
- };
3433
-
3434
- const isAbsoluteUri = value => {
3435
- return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
3436
- };
3437
- const executeReadFileTool = async (args, _options) => {
3438
- const uri = typeof args.uri === 'string' ? args.uri : '';
3439
- if (uri) {
3440
- if (!isAbsoluteUri(uri)) {
3441
- return JSON.stringify({
3442
- error: 'Invalid argument: uri must be an absolute URI.'
3443
- });
3444
- }
3445
- try {
3446
- const content = await readFile(uri);
3447
- return JSON.stringify({
3448
- content,
3449
- uri
3450
- });
3451
- } catch (error) {
3452
- return JSON.stringify({
3453
- ...getToolErrorPayload(error),
3454
- uri
3455
- });
3456
- }
3457
- }
3458
- const filePath = typeof args.path === 'string' ? args.path : '';
3459
- if (!filePath || isPathTraversalAttempt(filePath)) {
3460
- return JSON.stringify({
3461
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3462
- });
3463
- }
3464
- const normalizedPath = normalizeRelativePath(filePath);
3465
- try {
3466
- const content = await readFile(normalizedPath);
3467
- return JSON.stringify({
3468
- content,
3469
- path: normalizedPath
3470
- });
3471
- } catch (error) {
3472
- return JSON.stringify({
3473
- ...getToolErrorPayload(error),
3474
- path: normalizedPath
3475
- });
3476
- }
3477
- };
3478
-
3479
- const maxPayloadLength = 40_000;
3480
- const executeRenderHtmlTool = async (args, _options) => {
3481
- const html = typeof args.html === 'string' ? args.html : '';
3482
- const css = typeof args.css === 'string' ? args.css : '';
3483
- const title = typeof args.title === 'string' ? args.title : '';
3484
- if (!html) {
3485
- return JSON.stringify({
3486
- error: 'Missing required argument: html'
3487
- });
3488
- }
3489
- if (html.length > maxPayloadLength || css.length > maxPayloadLength) {
3490
- return JSON.stringify({
3491
- error: 'Payload too large: keep html/css under 40,000 characters each.'
3492
- });
3493
- }
3494
- return JSON.stringify({
3495
- css,
3496
- html,
3497
- ok: true,
3498
- title
3499
- });
3500
- };
3501
-
3502
- const toLines = value => {
3503
- if (!value) {
3504
- return [];
3505
- }
3506
- const split = value.split('\n').map(line => line.endsWith('\r') ? line.slice(0, -1) : line);
3507
- if (split.length > 0 && split.at(-1) === '') {
3508
- split.pop();
3509
- }
3510
- return split;
3511
- };
3512
- const getLineCounts = (before, after) => {
3513
- const beforeLines = toLines(before);
3514
- const afterLines = toLines(after);
3515
- let start = 0;
3516
- while (start < beforeLines.length && start < afterLines.length && beforeLines[start] === afterLines[start]) {
3517
- start++;
3518
- }
3519
- let beforeEnd = beforeLines.length - 1;
3520
- let afterEnd = afterLines.length - 1;
3521
- while (beforeEnd >= start && afterEnd >= start && beforeLines[beforeEnd] === afterLines[afterEnd]) {
3522
- beforeEnd--;
3523
- afterEnd--;
3524
- }
3525
- return {
3526
- linesAdded: Math.max(0, afterEnd - start + 1),
3527
- linesDeleted: Math.max(0, beforeEnd - start + 1)
3528
- };
3529
- };
3530
- const isFileNotFoundError = error => {
3531
- const message = String(error).toLowerCase();
3532
- return message.includes('enoent') || message.includes('not found');
3533
- };
3534
- const executeWriteFileTool = async (args, _options) => {
3535
- const filePath = typeof args.path === 'string' ? args.path : '';
3536
- const content = typeof args.content === 'string' ? args.content : '';
3537
- if (!filePath || isPathTraversalAttempt(filePath)) {
3538
- return JSON.stringify({
3539
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3540
- });
3541
- }
3542
- const normalizedPath = normalizeRelativePath(filePath);
3543
- try {
3544
- let previousContent = '';
3545
- try {
3546
- previousContent = await readFile(normalizedPath);
3547
- } catch (error) {
3548
- if (!isFileNotFoundError(error)) {
3549
- throw error;
3550
- }
3551
- }
3552
- await writeFile(normalizedPath, content);
3553
- const {
3554
- linesAdded,
3555
- linesDeleted
3556
- } = getLineCounts(previousContent, content);
3557
- return JSON.stringify({
3558
- linesAdded,
3559
- linesDeleted,
3560
- ok: true,
3561
- path: normalizedPath
3562
- });
3563
- } catch (error) {
3564
- return JSON.stringify({
3565
- ...getToolErrorPayload(error),
3566
- path: normalizedPath
3567
- });
3568
- }
3569
- };
3570
-
3571
- const parseToolArguments = rawArguments => {
3572
- if (typeof rawArguments !== 'string') {
3573
- return {};
3574
- }
3575
- try {
3576
- const parsed = JSON.parse(rawArguments);
3577
- if (!parsed || typeof parsed !== 'object') {
3578
- return {};
3579
- }
3580
- return parsed;
3581
- } catch {
3582
- return {};
3583
- }
3584
- };
3585
-
3586
3359
  const stringifyToolOutput = output => {
3587
3360
  if (typeof output === 'string') {
3588
3361
  return output;
@@ -3590,35 +3363,14 @@ const stringifyToolOutput = output => {
3590
3363
  return JSON.stringify(output) ?? 'null';
3591
3364
  };
3592
3365
  const executeChatTool = async (name, rawArguments, options) => {
3593
- if (options.useChatToolWorker) {
3594
- const workerOutput = await execute(name, rawArguments, {
3595
- assetDir: options.assetDir,
3596
- platform: options.platform
3597
- });
3598
- return stringifyToolOutput(workerOutput);
3366
+ if (!options.useChatToolWorker) {
3367
+ throw new Error('Chat tools must be executed in a web worker environment. Please set useChatToolWorker to true in the options.');
3599
3368
  }
3600
- const args = parseToolArguments(rawArguments);
3601
- if (name === 'read_file') {
3602
- return executeReadFileTool(args);
3603
- }
3604
- if (name === 'write_file') {
3605
- return executeWriteFileTool(args);
3606
- }
3607
- if (name === 'list_files') {
3608
- return executeListFilesTool(args);
3609
- }
3610
- if (name === 'getWorkspaceUri') {
3611
- return executeGetWorkspaceUriTool();
3612
- }
3613
- if (name === 'render_html') {
3614
- return executeRenderHtmlTool(args);
3615
- }
3616
- if (name === 'ask_question') {
3617
- return executeAskQuestionTool(args);
3618
- }
3619
- return JSON.stringify({
3620
- error: `Unknown tool: ${name}`
3369
+ const workerOutput = await execute(name, rawArguments, {
3370
+ assetDir: options.assetDir,
3371
+ platform: options.platform
3621
3372
  });
3373
+ return stringifyToolOutput(workerOutput);
3622
3374
  };
3623
3375
 
3624
3376
  const getReadFileTool = () => {
@@ -4262,6 +4014,8 @@ const getTextContent = content => {
4262
4014
 
4263
4015
  /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
4264
4016
 
4017
+ const errorPrefixRegex = /^Error:\s*/;
4018
+ const notFoundErrorRegex = /not[\s_-]?found|enoent/i;
4265
4019
  const getOpenAiTools = tools => {
4266
4020
  return tools.map(tool => {
4267
4021
  if (!tool || typeof tool !== 'object') {
@@ -4327,7 +4081,7 @@ const getStreamChunkText = content => {
4327
4081
  }).join('');
4328
4082
  };
4329
4083
  const getShortToolErrorMessage = error => {
4330
- const trimmed = error.trim().replace(/^Error:\s*/, '');
4084
+ const trimmed = error.trim().replace(errorPrefixRegex, '');
4331
4085
  const firstLine = trimmed.split('\n')[0];
4332
4086
  if (firstLine.length <= 80) {
4333
4087
  return firstLine;
@@ -4359,7 +4113,7 @@ const getToolCallExecutionStatus = content => {
4359
4113
  const rawStack = Reflect.get(parsed, 'errorStack') ?? Reflect.get(parsed, 'stack');
4360
4114
  const errorStack = typeof rawStack === 'string' && rawStack.trim() ? rawStack : undefined;
4361
4115
  const errorMessage = getShortToolErrorMessage(rawError);
4362
- if (/not[\s_-]?found|enoent/i.test(errorMessage)) {
4116
+ if (notFoundErrorRegex.test(errorMessage)) {
4363
4117
  return {
4364
4118
  ...(errorStack ? {
4365
4119
  errorStack
@@ -5347,13 +5101,15 @@ const getOpenApiModelId = selectedModelId => {
5347
5101
  return selectedModelId;
5348
5102
  };
5349
5103
 
5104
+ const trailingSlashesRegex$2 = /\/+$/;
5350
5105
  const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
5351
- const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
5106
+ const trimmedBaseUrl = openRouterApiBaseUrl.replace(trailingSlashesRegex$2, '');
5352
5107
  return `${trimmedBaseUrl}/chat/completions`;
5353
5108
  };
5354
5109
 
5110
+ const trailingSlashesRegex$1 = /\/+$/;
5355
5111
  const getOpenRouterKeyEndpoint = openRouterApiBaseUrl => {
5356
- const trimmedBaseUrl = openRouterApiBaseUrl.replace(/\/+$/, '');
5112
+ const trimmedBaseUrl = openRouterApiBaseUrl.replace(trailingSlashesRegex$1, '');
5357
5113
  return `${trimmedBaseUrl}/auth/key`;
5358
5114
  };
5359
5115
 
@@ -5739,8 +5495,9 @@ const getAll = () => {
5739
5495
 
5740
5496
  /* eslint-disable prefer-destructuring */
5741
5497
 
5498
+ const trailingSlashesRegex = /\/+$/;
5742
5499
  const getBackendCompletionsEndpoint = backendUrl => {
5743
- const trimmedBackendUrl = backendUrl.replace(/\/+$/, '');
5500
+ const trimmedBackendUrl = backendUrl.replace(trailingSlashesRegex, '');
5744
5501
  return `${trimmedBackendUrl}/v1/chat/completions`;
5745
5502
  };
5746
5503
  const getEffectiveBackendModelId = selectedModelId => {
@@ -6515,21 +6272,25 @@ const parseInlineNodes = value => {
6515
6272
  };
6516
6273
 
6517
6274
  const markdownMathBlockDelimiter = '$$';
6275
+ const escapedNewlineRegex = /\\r\\n|\\n/g;
6276
+ const lineBreakRegex = /\r?\n/;
6277
+ const tableDelimiterRegex = /\|\s*[-:]{3,}/;
6278
+ const inlineTableCellRegex = /\|\s+\|/g;
6518
6279
  const normalizeEscapedNewlines = value => {
6519
6280
  if (value.includes('\\n')) {
6520
- return value.replaceAll(/\\r\\n|\\n/g, '\n');
6281
+ return value.replaceAll(escapedNewlineRegex, '\n');
6521
6282
  }
6522
6283
  return value;
6523
6284
  };
6524
6285
  const normalizeInlineTables = value => {
6525
- return value.split(/\r?\n/).map(line => {
6286
+ return value.split(lineBreakRegex).map(line => {
6526
6287
  if (!line.includes('|')) {
6527
6288
  return line;
6528
6289
  }
6529
- if (!/\|\s*[-:]{3,}/.test(line)) {
6290
+ if (!tableDelimiterRegex.test(line)) {
6530
6291
  return line;
6531
6292
  }
6532
- return line.replaceAll(/\|\s+\|/g, '|\n|');
6293
+ return line.replaceAll(inlineTableCellRegex, '|\n|');
6533
6294
  }).join('\n');
6534
6295
  };
6535
6296
  const parseHeadingLine = line => {
@@ -6608,6 +6369,33 @@ const parseBlockQuoteLine = line => {
6608
6369
  }
6609
6370
  return content;
6610
6371
  };
6372
+ const isThematicBreakLine = line => {
6373
+ const trimmedStart = line.trimStart();
6374
+ const leadingSpaces = line.length - trimmedStart.length;
6375
+ if (leadingSpaces > 3) {
6376
+ return false;
6377
+ }
6378
+ const trimmed = trimmedStart.trimEnd();
6379
+ if (!trimmed) {
6380
+ return false;
6381
+ }
6382
+ const marker = trimmed[0];
6383
+ if (marker !== '-' && marker !== '_' && marker !== '*') {
6384
+ return false;
6385
+ }
6386
+ let markerCount = 0;
6387
+ for (let i = 0; i < trimmed.length; i++) {
6388
+ const char = trimmed[i];
6389
+ if (char === marker) {
6390
+ markerCount++;
6391
+ continue;
6392
+ }
6393
+ if (char !== ' ') {
6394
+ return false;
6395
+ }
6396
+ }
6397
+ return markerCount >= 3;
6398
+ };
6611
6399
  const isTableRow = line => {
6612
6400
  const trimmed = line.trim();
6613
6401
  if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) {
@@ -6621,7 +6409,7 @@ const getTableCells = line => {
6621
6409
  };
6622
6410
  const scanBlockTokens = rawMessage => {
6623
6411
  const normalizedMessage = normalizeInlineTables(normalizeEscapedNewlines(rawMessage));
6624
- const lines = normalizedMessage.split(/\r?\n/);
6412
+ const lines = normalizedMessage.split(lineBreakRegex);
6625
6413
  const tokens = [];
6626
6414
  for (let i = 0; i < lines.length; i++) {
6627
6415
  const line = lines[i];
@@ -6687,6 +6475,12 @@ const scanBlockTokens = rawMessage => {
6687
6475
  });
6688
6476
  continue;
6689
6477
  }
6478
+ if (isThematicBreakLine(line)) {
6479
+ tokens.push({
6480
+ type: 'thematic-break'
6481
+ });
6482
+ continue;
6483
+ }
6690
6484
  const ordered = parseOrderedListItemLine(line);
6691
6485
  if (ordered) {
6692
6486
  tokens.push({
@@ -6832,6 +6626,14 @@ const parseBlockTokens = tokens => {
6832
6626
  });
6833
6627
  continue;
6834
6628
  }
6629
+ if (token.type === 'thematic-break') {
6630
+ flushList();
6631
+ flushParagraph();
6632
+ nodes.push({
6633
+ type: 'thematic-break'
6634
+ });
6635
+ continue;
6636
+ }
6835
6637
  if (token.type === 'blockquote-line') {
6836
6638
  flushList();
6837
6639
  flushParagraph();
@@ -7205,12 +7007,16 @@ const executeSlashCommand = async (state, command) => {
7205
7007
  });
7206
7008
  };
7207
7009
 
7010
+ const defaultSessionTitleRegex = /^Chat \d+$/;
7208
7011
  const isDefaultSessionTitle = title => {
7209
- return /^Chat \d+$/.test(title);
7012
+ return defaultSessionTitleRegex.test(title);
7210
7013
  };
7211
7014
 
7015
+ const titlePrefixRegex = /^title:\s*/i;
7016
+ const wrappedQuotesAndWhitespaceRegex = /^['"`\s]+|['"`\s]+$/g;
7017
+ const consecutiveWhitespaceRegex = /\s+/g;
7212
7018
  const sanitizeGeneratedTitle = value => {
7213
- return value.replace(/^title:\s*/i, '').replaceAll(/^['"`\s]+|['"`\s]+$/g, '').replaceAll(/\s+/g, ' ').trim().slice(0, 80);
7019
+ return value.replace(titlePrefixRegex, '').replaceAll(wrappedQuotesAndWhitespaceRegex, '').replaceAll(consecutiveWhitespaceRegex, ' ').trim().slice(0, 80);
7214
7020
  };
7215
7021
 
7216
7022
  const getAiSessionTitle = async (state, userText, assistantText) => {
@@ -7281,14 +7087,43 @@ Assistant: ${assistantText}`;
7281
7087
  return title && !isDefaultSessionTitle(title) ? title : '';
7282
7088
  };
7283
7089
 
7090
+ const windowsAbsolutePathRegex = /^[a-zA-Z]:[\\/]/;
7091
+ const pathSeparatorRegex$1 = /[\\/]/;
7092
+ const isPathTraversalAttempt = path => {
7093
+ if (!path) {
7094
+ return false;
7095
+ }
7096
+ if (path.startsWith('/') || path.startsWith('\\')) {
7097
+ return true;
7098
+ }
7099
+ if (path.startsWith('file://')) {
7100
+ return true;
7101
+ }
7102
+ if (windowsAbsolutePathRegex.test(path)) {
7103
+ return true;
7104
+ }
7105
+ const segments = path.split(pathSeparatorRegex$1);
7106
+ return segments.includes('..');
7107
+ };
7108
+
7109
+ const pathSeparatorRegex = /[\\/]/;
7110
+ const normalizeRelativePath = path => {
7111
+ const segments = path.split(pathSeparatorRegex).filter(segment => segment && segment !== '.');
7112
+ if (segments.length === 0) {
7113
+ return '.';
7114
+ }
7115
+ return segments.join('/');
7116
+ };
7117
+
7284
7118
  const mentionRegex = /(^|\s)@([^\s]+)/g;
7119
+ const trailingPunctuationRegex = /[),.;:!?]+$/g;
7285
7120
  const maxMentionCount = 5;
7286
7121
  const parseMentionedPaths = value => {
7287
7122
  const matches = value.matchAll(mentionRegex);
7288
7123
  const paths = [];
7289
7124
  for (const match of matches) {
7290
7125
  const rawPath = match[2] || '';
7291
- const cleanedPath = rawPath.replaceAll(/[),.;:!?]+$/g, '');
7126
+ const cleanedPath = rawPath.replaceAll(trailingPunctuationRegex, '');
7292
7127
  if (!cleanedPath || isPathTraversalAttempt(cleanedPath)) {
7293
7128
  continue;
7294
7129
  }
@@ -7816,6 +7651,7 @@ const Dictate = 'dictate';
7816
7651
  const Send = 'send';
7817
7652
  const Back = 'back';
7818
7653
  const Model = 'model';
7654
+ const RunMode = 'runMode';
7819
7655
  const ToggleChatFocus = 'toggle-chat-focus';
7820
7656
  const CreateProject = 'create-project';
7821
7657
  const CreateSession = 'create-session';
@@ -8333,6 +8169,19 @@ const handleProjectListContextMenu = async state => {
8333
8169
  return state;
8334
8170
  };
8335
8171
 
8172
+ const isRunMode = value => {
8173
+ return value === 'local' || value === 'background' || value === 'cloud';
8174
+ };
8175
+ const handleRunModeChange = async (state, value) => {
8176
+ if (!isRunMode(value)) {
8177
+ return state;
8178
+ }
8179
+ return {
8180
+ ...state,
8181
+ runMode: value
8182
+ };
8183
+ };
8184
+
8336
8185
  const handleChatListScroll = async (state, chatListScrollTop) => {
8337
8186
  if (state.chatListScrollTop === chatListScrollTop) {
8338
8187
  return state;
@@ -8976,15 +8825,31 @@ const registerMockResponse = (state, mockResponse) => {
8976
8825
  return state;
8977
8826
  };
8978
8827
 
8979
- const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, renderHtmlCss) => {
8828
+ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, textAreaPaddingTop, textAreaPaddingLeft, textAreaPaddingRight, textAreaPaddingBottom, chatSendAreaPaddingTop, chatSendAreaPaddingLeft, chatSendAreaPaddingRight, chatSendAreaPaddingBottom, renderHtmlCss) => {
8980
8829
  const baseCss = `:root {
8981
8830
  --ChatInputBoxHeight: ${composerHeight}px;
8831
+ --ChatTextAreaHeight: ${composerHeight}px;
8832
+ --ChatSendAreaHeight: ${composerHeight + 62}px;
8833
+ --ChatTextAreaPaddingTop: ${textAreaPaddingTop}px;
8834
+ --ChatTextAreaPaddingLeft: ${textAreaPaddingLeft}px;
8835
+ --ChatTextAreaPaddingRight: ${textAreaPaddingRight}px;
8836
+ --ChatTextAreaPaddingBottom: ${textAreaPaddingBottom}px;
8837
+ --ChatSendAreaPaddingTop: ${chatSendAreaPaddingTop}px;
8838
+ --ChatSendAreaPaddingLeft: ${chatSendAreaPaddingLeft}px;
8839
+ --ChatSendAreaPaddingRight: ${chatSendAreaPaddingRight}px;
8840
+ --ChatSendAreaPaddingBottom: ${chatSendAreaPaddingBottom}px;
8982
8841
  --ChatListItemHeight: ${listItemHeight}px;
8983
8842
  --ChatMessageFontSize: ${chatMessageFontSize}px;
8984
8843
  --ChatMessageLineHeight: ${chatMessageLineHeight}px;
8985
8844
  --ChatMessageFontFamily: ${chatMessageFontFamily};
8986
8845
  }
8987
8846
 
8847
+
8848
+ .ChatSendArea{
8849
+ height: var(--ChatSendAreaHeight);
8850
+ padding: var(--ChatSendAreaPaddingTop) var(--ChatSendAreaPaddingRight) var(--ChatSendAreaPaddingBottom) var(--ChatSendAreaPaddingLeft);
8851
+ }
8852
+
8988
8853
  .Viewlet.Chat.ChatFocus {
8989
8854
  background: linear-gradient(180deg, var(--ColorViewBackground, #1d2229) 0%, #1f252d 100%);
8990
8855
  display: grid;
@@ -8996,6 +8861,11 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
8996
8861
  grid-column: 1 / 3;
8997
8862
  }
8998
8863
 
8864
+ .ChatHeader .Label {
8865
+ text-decoration: underline;
8866
+ text-underline-offset: 5px;
8867
+ }
8868
+
8999
8869
  .Chat.ChatFocus .ProjectSidebar {
9000
8870
  background: color-mix(in srgb, var(--ColorSideBarBackground, #232b35) 88%, #1f2b38 12%);
9001
8871
  border-right: 1px solid var(--ColorBorder, #3a3d41);
@@ -9180,7 +9050,9 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
9180
9050
  .ChatList,
9181
9051
  .ChatListEmpty,
9182
9052
  .ChatMessages {
9053
+ margin: 0;
9183
9054
  min-height: 0;
9055
+ padding: 0;
9184
9056
  }
9185
9057
 
9186
9058
  .Chat.ChatFocus .ChatList,
@@ -9196,6 +9068,32 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
9196
9068
  .Chat.ChatFocus .ChatSendArea {
9197
9069
  grid-column: 2;
9198
9070
  grid-row: 3;
9071
+ height: var(--ChatSendAreaHeight);
9072
+ min-height: var(--ChatSendAreaHeight);
9073
+ }
9074
+
9075
+ .Chat .MultilineInputBox {
9076
+ height: var(--ChatTextAreaHeight);
9077
+ min-height: var(--ChatTextAreaHeight);
9078
+ padding: var(--ChatTextAreaPaddingTop) var(--ChatTextAreaPaddingRight) var(--ChatTextAreaPaddingBottom) var(--ChatTextAreaPaddingLeft);
9079
+ }
9080
+
9081
+ .Select {
9082
+ -moz-appearance: none;
9083
+ -webkit-appearance: none;
9084
+ appearance: none;
9085
+ background-image:
9086
+ linear-gradient(45deg, transparent 50%, color-mix(in srgb, var(--ColorForeground, #d5dbe3) 84%, transparent) 50%),
9087
+ linear-gradient(135deg, color-mix(in srgb, var(--ColorForeground, #d5dbe3) 84%, transparent) 50%, transparent 50%);
9088
+ background-position:
9089
+ calc(100% - 10px) 50%,
9090
+ calc(100% - 6px) 50%;
9091
+ background-repeat: no-repeat;
9092
+ background-size:
9093
+ 4px 4px,
9094
+ 4px 4px;
9095
+ max-width: 60px;
9096
+ padding-right: 16px;
9199
9097
  }
9200
9098
 
9201
9099
  .MarkdownMathInline {
@@ -9216,6 +9114,16 @@ const getCss = (composerHeight, listItemHeight, chatMessageFontSize, chatMessage
9216
9114
  overflow-y: hidden;
9217
9115
  }
9218
9116
 
9117
+ .ChatMessageContent hr,
9118
+ .ChatToolCallRenderHtmlBody hr {
9119
+ border: 0;
9120
+ border-top: 1px solid color-mix(in srgb, var(--ColorBorder, #3a3d41) 78%, transparent);
9121
+ display: block;
9122
+ height: 0;
9123
+ margin: 12px 0;
9124
+ width: 100%;
9125
+ }
9126
+
9219
9127
  .StrikeThrough {
9220
9128
  text-decoration: line-through;
9221
9129
  }
@@ -9284,14 +9192,22 @@ const renderCss = (oldState, newState) => {
9284
9192
  chatMessageFontFamily,
9285
9193
  chatMessageFontSize,
9286
9194
  chatMessageLineHeight,
9195
+ chatSendAreaPaddingBottom,
9196
+ chatSendAreaPaddingLeft,
9197
+ chatSendAreaPaddingRight,
9198
+ chatSendAreaPaddingTop,
9287
9199
  composerHeight,
9288
9200
  listItemHeight,
9289
9201
  selectedSessionId,
9290
9202
  sessions,
9203
+ textAreaPaddingBottom,
9204
+ textAreaPaddingLeft,
9205
+ textAreaPaddingRight,
9206
+ textAreaPaddingTop,
9291
9207
  uid
9292
9208
  } = newState;
9293
9209
  const renderHtmlCss = getRenderHtmlCss(sessions, selectedSessionId);
9294
- const css = getCss(composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, renderHtmlCss);
9210
+ const css = getCss(composerHeight, listItemHeight, chatMessageFontSize, chatMessageLineHeight, chatMessageFontFamily, textAreaPaddingTop, textAreaPaddingLeft, textAreaPaddingRight, textAreaPaddingBottom, chatSendAreaPaddingTop, chatSendAreaPaddingLeft, chatSendAreaPaddingRight, chatSendAreaPaddingBottom, renderHtmlCss);
9295
9211
  return [SetCss, uid, css];
9296
9212
  };
9297
9213
 
@@ -9429,6 +9345,7 @@ const HandleProjectListScroll = 32;
9429
9345
  const HandleProjectListContextMenu = 33;
9430
9346
  const HandleClickDictationButton = 34;
9431
9347
  const HandleMissingApiKeySubmit = 35;
9348
+ const HandleRunModeChange = 36;
9432
9349
 
9433
9350
  const getModelLabel = model => {
9434
9351
  if (model.provider === 'openRouter') {
@@ -9462,6 +9379,28 @@ const getChatSelectVirtualDom = (models, selectedModelId) => {
9462
9379
  }, ...modelOptions];
9463
9380
  };
9464
9381
 
9382
+ const getRunModeOptionDom = (runMode, selectedRunMode) => {
9383
+ return [{
9384
+ childCount: 1,
9385
+ selected: runMode === selectedRunMode,
9386
+ type: Option$1,
9387
+ value: runMode
9388
+ }, text(runMode)];
9389
+ };
9390
+
9391
+ const runModes = ['local', 'background', 'cloud'];
9392
+ const getRunModeSelectVirtualDom = selectedRunMode => {
9393
+ const runModeOptions = runModes.flatMap(runMode => getRunModeOptionDom(runMode, selectedRunMode));
9394
+ return [{
9395
+ childCount: runModes.length,
9396
+ className: Select,
9397
+ name: RunMode,
9398
+ onInput: HandleRunModeChange,
9399
+ type: Select$1,
9400
+ value: selectedRunMode
9401
+ }, ...runModeOptions];
9402
+ };
9403
+
9465
9404
  const getSendButtonClassName = isSendDisabled => {
9466
9405
  return isSendDisabled ? `${IconButton} ${SendButtonDisabled}` : `${IconButton}`;
9467
9406
  };
@@ -9529,9 +9468,9 @@ const getUsageOverviewDom = (tokensUsed, tokensMax) => {
9529
9468
  }, text(usageLabel)];
9530
9469
  };
9531
9470
 
9532
- const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, voiceDictationEnabled = false) => {
9471
+ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, showRunMode, runMode, voiceDictationEnabled = false) => {
9533
9472
  const isSendDisabled = composerValue.trim() === '';
9534
- const controlsCount = usageOverviewEnabled ? 3 : 2;
9473
+ const controlsCount = 2 + (usageOverviewEnabled ? 1 : 0) + (showRunMode ? 1 : 0);
9535
9474
  return [{
9536
9475
  childCount: 1,
9537
9476
  className: ChatSendArea,
@@ -9554,7 +9493,7 @@ const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOvervie
9554
9493
  childCount: voiceDictationEnabled ? controlsCount + 1 : controlsCount,
9555
9494
  className: ChatSendAreaBottom,
9556
9495
  type: Div
9557
- }, ...getChatSelectVirtualDom(models, selectedModelId), ...(usageOverviewEnabled ? getUsageOverviewDom(tokensUsed, tokensMax) : []), ...getSendButtonDom(isSendDisabled, voiceDictationEnabled)];
9496
+ }, ...getChatSelectVirtualDom(models, selectedModelId), ...(showRunMode ? getRunModeSelectVirtualDom(runMode) : []), ...(usageOverviewEnabled ? getUsageOverviewDom(tokensUsed, tokensMax) : []), ...getSendButtonDom(isSendDisabled, voiceDictationEnabled)];
9558
9497
  };
9559
9498
 
9560
9499
  const getImageAltText = alt => {
@@ -9919,6 +9858,12 @@ const getMessageNodeDom = (node, useChatMathWorker = false) => {
9919
9858
  if (node.type === 'math-block-dom') {
9920
9859
  return node.dom;
9921
9860
  }
9861
+ if (node.type === 'thematic-break') {
9862
+ return [{
9863
+ childCount: 0,
9864
+ type: Hr
9865
+ }];
9866
+ }
9922
9867
  if (node.type === 'heading') {
9923
9868
  return getHeadingDom(node, useChatMathWorker);
9924
9869
  }
@@ -10053,57 +9998,123 @@ const getOpenRouterTooManyRequestsDom = () => {
10053
9998
  })];
10054
9999
  };
10055
10000
 
10056
- const RE_QUERY_OR_HASH = /[?#].*$/;
10057
- const RE_TRAILING_SLASH = /\/$/;
10058
- const getFileNameFromUri = uri => {
10059
- const stripped = uri.replace(RE_QUERY_OR_HASH, '').replace(RE_TRAILING_SLASH, '');
10060
- const slashIndex = Math.max(stripped.lastIndexOf('/'), stripped.lastIndexOf('\\\\'));
10061
- const fileName = slashIndex === -1 ? stripped : stripped.slice(slashIndex + 1);
10062
- return fileName || uri;
10063
- };
10064
-
10065
- const isCompleteJson = value => {
10066
- const trimmed = value.trim();
10067
- if (!trimmed) {
10068
- return false;
10001
+ const getToolCallStatusLabel = toolCall => {
10002
+ if (toolCall.status === 'not-found') {
10003
+ return ' (not-found)';
10069
10004
  }
10070
- let depth = 0;
10071
- let inString = false;
10072
- let escaped = false;
10073
- for (const char of trimmed) {
10074
- if (inString) {
10075
- if (escaped) {
10076
- escaped = false;
10077
- continue;
10078
- }
10079
- if (char === '\\') {
10080
- escaped = true;
10081
- continue;
10082
- }
10083
- if (char === '"') {
10084
- inString = false;
10085
- }
10086
- continue;
10087
- }
10088
- if (char === '"') {
10089
- inString = true;
10090
- continue;
10091
- }
10092
- if (char === '{' || char === '[') {
10093
- depth += 1;
10094
- continue;
10095
- }
10096
- if (char === '}' || char === ']') {
10097
- depth -= 1;
10098
- if (depth < 0) {
10099
- return false;
10100
- }
10005
+ if (toolCall.status === 'error') {
10006
+ if (toolCall.errorMessage) {
10007
+ return ` (error: ${toolCall.errorMessage})`;
10101
10008
  }
10009
+ return ' (error)';
10102
10010
  }
10103
- return depth === 0 && !inString && !escaped;
10011
+ return '';
10104
10012
  };
10105
- const getReadFileTarget = rawArguments => {
10106
- // Tool arguments stream in chunks, so skip parsing until a full JSON payload is available.
10013
+
10014
+ const parseAskQuestionArguments = rawArguments => {
10015
+ let parsed;
10016
+ try {
10017
+ parsed = JSON.parse(rawArguments);
10018
+ } catch {
10019
+ return {
10020
+ answers: [],
10021
+ question: ''
10022
+ };
10023
+ }
10024
+ if (!parsed || typeof parsed !== 'object') {
10025
+ return {
10026
+ answers: [],
10027
+ question: ''
10028
+ };
10029
+ }
10030
+ const question = Reflect.get(parsed, 'question');
10031
+ const rawAnswers = Reflect.get(parsed, 'answers');
10032
+ const rawChoices = Reflect.get(parsed, 'choices');
10033
+ const rawOptions = Reflect.get(parsed, 'options');
10034
+ const arrayValue = Array.isArray(rawAnswers) ? rawAnswers : Array.isArray(rawChoices) ? rawChoices : Array.isArray(rawOptions) ? rawOptions : [];
10035
+ const answers = arrayValue.filter(value => typeof value === 'string');
10036
+ return {
10037
+ answers,
10038
+ question: typeof question === 'string' ? question : ''
10039
+ };
10040
+ };
10041
+
10042
+ const getToolCallAskQuestionVirtualDom = toolCall => {
10043
+ const parsed = parseAskQuestionArguments(toolCall.arguments);
10044
+ const statusLabel = getToolCallStatusLabel(toolCall);
10045
+ const questionLabel = parsed.question.trim() ? parsed.question : '(empty question)';
10046
+ const answers = parsed.answers.length > 0 ? parsed.answers : ['(no answers)'];
10047
+ const childCount = 2;
10048
+ return [{
10049
+ childCount,
10050
+ className: ChatOrderedListItem,
10051
+ type: Li
10052
+ }, {
10053
+ childCount: 1,
10054
+ className: ChatToolCallQuestionText,
10055
+ type: Div
10056
+ }, text(`ask_question: ${questionLabel}${statusLabel}`), {
10057
+ childCount: answers.length,
10058
+ className: ChatToolCallQuestionOptions,
10059
+ type: Div
10060
+ }, ...answers.flatMap(answer => [{
10061
+ childCount: 1,
10062
+ className: ChatToolCallQuestionOption,
10063
+ type: Span
10064
+ }, text(answer.trim() ? answer : '(empty answer)')])];
10065
+ };
10066
+
10067
+ const RE_QUERY_OR_HASH = /[?#].*$/;
10068
+ const RE_TRAILING_SLASH = /\/$/;
10069
+ const getFileNameFromUri = uri => {
10070
+ const stripped = uri.replace(RE_QUERY_OR_HASH, '').replace(RE_TRAILING_SLASH, '');
10071
+ const slashIndex = Math.max(stripped.lastIndexOf('/'), stripped.lastIndexOf('\\\\'));
10072
+ const fileName = slashIndex === -1 ? stripped : stripped.slice(slashIndex + 1);
10073
+ return fileName || uri;
10074
+ };
10075
+
10076
+ const isCompleteJson = value => {
10077
+ const trimmed = value.trim();
10078
+ if (!trimmed) {
10079
+ return false;
10080
+ }
10081
+ let depth = 0;
10082
+ let inString = false;
10083
+ let escaped = false;
10084
+ for (const char of trimmed) {
10085
+ if (inString) {
10086
+ if (escaped) {
10087
+ escaped = false;
10088
+ continue;
10089
+ }
10090
+ if (char === '\\') {
10091
+ escaped = true;
10092
+ continue;
10093
+ }
10094
+ if (char === '"') {
10095
+ inString = false;
10096
+ }
10097
+ continue;
10098
+ }
10099
+ if (char === '"') {
10100
+ inString = true;
10101
+ continue;
10102
+ }
10103
+ if (char === '{' || char === '[') {
10104
+ depth += 1;
10105
+ continue;
10106
+ }
10107
+ if (char === '}' || char === ']') {
10108
+ depth -= 1;
10109
+ if (depth < 0) {
10110
+ return false;
10111
+ }
10112
+ }
10113
+ }
10114
+ return depth === 0 && !inString && !escaped;
10115
+ };
10116
+ const getReadFileTarget = rawArguments => {
10117
+ // Tool arguments stream in chunks, so skip parsing until a full JSON payload is available.
10107
10118
  if (!isCompleteJson(rawArguments)) {
10108
10119
  return undefined;
10109
10120
  }
@@ -10132,6 +10143,58 @@ const getReadFileTarget = rawArguments => {
10132
10143
  };
10133
10144
  };
10134
10145
 
10146
+ const getToolCallEditFileVirtualDom = toolCall => {
10147
+ const target = getReadFileTarget(toolCall.arguments);
10148
+ if (!target) {
10149
+ return [];
10150
+ }
10151
+ const fileName = getFileNameFromUri(target.title);
10152
+ const fileNameClickableProps = target.clickableUri ? {
10153
+ 'data-uri': target.clickableUri,
10154
+ onClick: HandleClickReadFile
10155
+ } : {};
10156
+ return [{
10157
+ childCount: 3,
10158
+ className: ChatOrderedListItem,
10159
+ title: target.title,
10160
+ type: Li
10161
+ }, {
10162
+ childCount: 0,
10163
+ className: FileIcon,
10164
+ type: Div
10165
+ }, text('edit_file '), {
10166
+ childCount: 1,
10167
+ className: ChatToolCallReadFileLink,
10168
+ title: target.clickableUri,
10169
+ ...fileNameClickableProps,
10170
+ type: Span
10171
+ }, text(fileName)];
10172
+ };
10173
+
10174
+ const getToolCallGetWorkspaceUriVirtualDom = toolCall => {
10175
+ if (!toolCall.result) {
10176
+ return [];
10177
+ }
10178
+ const statusLabel = getToolCallStatusLabel(toolCall);
10179
+ const fileName = getFileNameFromUri(toolCall.result);
10180
+ return [{
10181
+ childCount: statusLabel ? 4 : 3,
10182
+ className: ChatOrderedListItem,
10183
+ title: toolCall.result,
10184
+ type: Li
10185
+ }, {
10186
+ childCount: 0,
10187
+ className: FileIcon,
10188
+ type: Div
10189
+ }, text('get_workspace_uri '), {
10190
+ childCount: 1,
10191
+ className: ChatToolCallReadFileLink,
10192
+ 'data-uri': toolCall.result,
10193
+ onClick: HandleClickReadFile,
10194
+ type: Span
10195
+ }, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
10196
+ };
10197
+
10135
10198
  const getToolCallArgumentPreview = rawArguments => {
10136
10199
  if (!rawArguments.trim()) {
10137
10200
  return '""';
@@ -10159,69 +10222,33 @@ const getToolCallArgumentPreview = rawArguments => {
10159
10222
  return rawArguments;
10160
10223
  };
10161
10224
 
10162
- const getToolCallStatusLabel = toolCall => {
10163
- if (toolCall.status === 'not-found') {
10164
- return ' (not-found)';
10165
- }
10166
- if (toolCall.status === 'error') {
10167
- if (toolCall.errorMessage) {
10168
- return ` (error: ${toolCall.errorMessage})`;
10169
- }
10170
- return ' (error)';
10225
+ const getToolCallDisplayName = name => {
10226
+ if (name === 'getWorkspaceUri') {
10227
+ return 'get_workspace_uri';
10171
10228
  }
10172
- return '';
10229
+ return name;
10173
10230
  };
10174
10231
 
10175
- const parseAskQuestionArguments = rawArguments => {
10176
- let parsed;
10232
+ const hasIncompleteJsonArguments = rawArguments => {
10177
10233
  try {
10178
- parsed = JSON.parse(rawArguments);
10234
+ JSON.parse(rawArguments);
10235
+ return false;
10179
10236
  } catch {
10180
- return {
10181
- answers: [],
10182
- question: ''
10183
- };
10184
- }
10185
- if (!parsed || typeof parsed !== 'object') {
10186
- return {
10187
- answers: [],
10188
- question: ''
10189
- };
10237
+ return true;
10190
10238
  }
10191
- const question = Reflect.get(parsed, 'question');
10192
- const rawAnswers = Reflect.get(parsed, 'answers');
10193
- const rawChoices = Reflect.get(parsed, 'choices');
10194
- const rawOptions = Reflect.get(parsed, 'options');
10195
- const arrayValue = Array.isArray(rawAnswers) ? rawAnswers : Array.isArray(rawChoices) ? rawChoices : Array.isArray(rawOptions) ? rawOptions : [];
10196
- const answers = arrayValue.filter(value => typeof value === 'string');
10197
- return {
10198
- answers,
10199
- question: typeof question === 'string' ? question : ''
10200
- };
10201
10239
  };
10202
- const getToolCallAskQuestionVirtualDom = toolCall => {
10203
- const parsed = parseAskQuestionArguments(toolCall.arguments);
10240
+
10241
+ const getToolCallLabel = toolCall => {
10242
+ const displayName = getToolCallDisplayName(toolCall.name);
10243
+ if (toolCall.name === 'write_file' && !toolCall.status && hasIncompleteJsonArguments(toolCall.arguments)) {
10244
+ return `${displayName} (in progress)`;
10245
+ }
10246
+ const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
10204
10247
  const statusLabel = getToolCallStatusLabel(toolCall);
10205
- const questionLabel = parsed.question.trim() ? parsed.question : '(empty question)';
10206
- const answers = parsed.answers.length > 0 ? parsed.answers : ['(no answers)'];
10207
- const childCount = 2;
10208
- return [{
10209
- childCount,
10210
- className: ChatOrderedListItem,
10211
- type: Li
10212
- }, {
10213
- childCount: 1,
10214
- className: ChatToolCallQuestionText,
10215
- type: Div
10216
- }, text(`ask_question: ${questionLabel}${statusLabel}`), {
10217
- childCount: answers.length,
10218
- className: ChatToolCallQuestionOptions,
10219
- type: Div
10220
- }, ...answers.flatMap(answer => [{
10221
- childCount: 1,
10222
- className: ChatToolCallQuestionOption,
10223
- type: Span
10224
- }, text(answer.trim() ? answer : '(empty answer)')])];
10248
+ if (argumentPreview === '{}') {
10249
+ return `${displayName}${statusLabel}`;
10250
+ }
10251
+ return `${displayName} ${argumentPreview}${statusLabel}`;
10225
10252
  };
10226
10253
 
10227
10254
  const getToolCallReadFileVirtualDom = toolCall => {
@@ -10256,16 +10283,24 @@ const getToolCallReadFileVirtualDom = toolCall => {
10256
10283
  const maxHtmlLength = 40_000;
10257
10284
  const tokenRegex = /<!--[\s\S]*?-->|<\/?[a-zA-Z][\w:-]*(?:\s[^<>]*?)?>|[^<]+/g;
10258
10285
  const attributeRegex = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
10286
+ const scriptTagRegex = /<script\b[\s\S]*?<\/script>/gi;
10287
+ const styleTagRegex = /<style\b[\s\S]*?<\/style>/gi;
10288
+ const headTagRegex = /<head\b[\s\S]*?<\/head>/gi;
10289
+ const metaTagRegex = /<meta\b[^>]*>/gi;
10290
+ const linkTagRegex = /<link\b[^>]*>/gi;
10291
+ const tagPrefixRegex = /^<\/?\s*[a-zA-Z][\w:-]*/;
10292
+ const tagSuffixRegex = /\/?\s*>$/;
10293
+ const openTagNameRegex = /^<\s*([a-zA-Z][\w:-]*)/;
10259
10294
  const inlineTags = new Set(['a', 'abbr', 'b', 'code', 'em', 'i', 'label', 'small', 'span', 'strong', 'sub', 'sup', 'u']);
10260
10295
  const voidElements = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
10261
10296
  const sanitizeHtml = value => {
10262
- return value.slice(0, maxHtmlLength).replaceAll(/<script\b[\s\S]*?<\/script>/gi, '').replaceAll(/<style\b[\s\S]*?<\/style>/gi, '').replaceAll(/<head\b[\s\S]*?<\/head>/gi, '').replaceAll(/<meta\b[^>]*>/gi, '').replaceAll(/<link\b[^>]*>/gi, '');
10297
+ return value.slice(0, maxHtmlLength).replaceAll(scriptTagRegex, '').replaceAll(styleTagRegex, '').replaceAll(headTagRegex, '').replaceAll(metaTagRegex, '').replaceAll(linkTagRegex, '');
10263
10298
  };
10264
10299
  const decodeEntities = value => {
10265
10300
  return value.replaceAll('&nbsp;', ' ').replaceAll('&quot;', '"').replaceAll('&#39;', "'").replaceAll('&lt;', '<').replaceAll('&gt;', '>').replaceAll('&amp;', '&');
10266
10301
  };
10267
10302
  const parseAttributes = token => {
10268
- const withoutTag = token.replace(/^<\/?\s*[a-zA-Z][\w:-]*/, '').replace(/\/?\s*>$/, '').trim();
10303
+ const withoutTag = token.replace(tagPrefixRegex, '').replace(tagSuffixRegex, '').trim();
10269
10304
  if (!withoutTag) {
10270
10305
  return Object.create(null);
10271
10306
  }
@@ -10312,7 +10347,7 @@ const parseHtml = value => {
10312
10347
  continue;
10313
10348
  }
10314
10349
  if (token.startsWith('<')) {
10315
- const openTagNameMatch = /^<\s*([a-zA-Z][\w:-]*)/.exec(token);
10350
+ const openTagNameMatch = openTagNameRegex.exec(token);
10316
10351
  if (!openTagNameMatch) {
10317
10352
  continue;
10318
10353
  }
@@ -10556,55 +10591,6 @@ const getToolCallRenderHtmlVirtualDom = toolCall => {
10556
10591
  }, ...parsedHtml.virtualDom];
10557
10592
  };
10558
10593
 
10559
- const getToolCallDisplayName = name => {
10560
- if (name === 'getWorkspaceUri') {
10561
- return 'get_workspace_uri';
10562
- }
10563
- return name;
10564
- };
10565
- const hasIncompleteJsonArguments = rawArguments => {
10566
- try {
10567
- JSON.parse(rawArguments);
10568
- return false;
10569
- } catch {
10570
- return true;
10571
- }
10572
- };
10573
- const getToolCallLabel = toolCall => {
10574
- const displayName = getToolCallDisplayName(toolCall.name);
10575
- if (toolCall.name === 'write_file' && !toolCall.status && hasIncompleteJsonArguments(toolCall.arguments)) {
10576
- return `${displayName} (in progress)`;
10577
- }
10578
- const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
10579
- const statusLabel = getToolCallStatusLabel(toolCall);
10580
- if (argumentPreview === '{}') {
10581
- return `${displayName}${statusLabel}`;
10582
- }
10583
- return `${displayName} ${argumentPreview}${statusLabel}`;
10584
- };
10585
- const getToolCallGetWorkspaceUriVirtualDom = toolCall => {
10586
- if (!toolCall.result) {
10587
- return [];
10588
- }
10589
- const statusLabel = getToolCallStatusLabel(toolCall);
10590
- const fileName = getFileNameFromUri(toolCall.result);
10591
- return [{
10592
- childCount: statusLabel ? 4 : 3,
10593
- className: ChatOrderedListItem,
10594
- title: toolCall.result,
10595
- type: Li
10596
- }, {
10597
- childCount: 0,
10598
- className: FileIcon,
10599
- type: Div
10600
- }, text('get_workspace_uri '), {
10601
- childCount: 1,
10602
- className: ChatToolCallReadFileLink,
10603
- 'data-uri': toolCall.result,
10604
- onClick: HandleClickReadFile,
10605
- type: Span
10606
- }, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
10607
- };
10608
10594
  const parseWriteFileLineCounts = rawResult => {
10609
10595
  if (!rawResult) {
10610
10596
  return {
@@ -10634,6 +10620,7 @@ const parseWriteFileLineCounts = rawResult => {
10634
10620
  linesDeleted: typeof linesDeleted === 'number' ? Math.max(0, linesDeleted) : 0
10635
10621
  };
10636
10622
  };
10623
+
10637
10624
  const getToolCallWriteFileVirtualDom = toolCall => {
10638
10625
  const target = getReadFileTarget(toolCall.arguments);
10639
10626
  if (!target) {
@@ -10673,6 +10660,7 @@ const getToolCallWriteFileVirtualDom = toolCall => {
10673
10660
  type: Span
10674
10661
  }, text(` -${linesDeleted}`), ...(statusLabel ? [text(statusLabel)] : [])];
10675
10662
  };
10663
+
10676
10664
  const getToolCallDom = toolCall => {
10677
10665
  if (toolCall.name === 'getWorkspaceUri') {
10678
10666
  const virtualDom = getToolCallGetWorkspaceUriVirtualDom(toolCall);
@@ -10692,6 +10680,12 @@ const getToolCallDom = toolCall => {
10692
10680
  return virtualDom;
10693
10681
  }
10694
10682
  }
10683
+ if (toolCall.name === 'edit_file') {
10684
+ const virtualDom = getToolCallEditFileVirtualDom(toolCall);
10685
+ if (virtualDom.length > 0) {
10686
+ return virtualDom;
10687
+ }
10688
+ }
10695
10689
  if (toolCall.name === 'render_html') {
10696
10690
  const virtualDom = getToolCallRenderHtmlVirtualDom(toolCall);
10697
10691
  if (virtualDom.length > 0) {
@@ -10801,9 +10795,9 @@ const getDisplayMessages = (messages, parsedMessages) => {
10801
10795
  }
10802
10796
  return displayMessages;
10803
10797
  };
10804
- const getMessagesDom = (messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', messagesScrollTop = 0, useChatMathWorker = false) => {
10798
+ const getMessagesDom = (messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput = '', openRouterApiKeyState = 'idle', messagesScrollTop = 0, useChatMathWorker = false, hideWelcomeMessage = false) => {
10805
10799
  if (messages.length === 0) {
10806
- return getEmptyMessagesDom();
10800
+ return hideWelcomeMessage ? [] : getEmptyMessagesDom();
10807
10801
  }
10808
10802
  const displayMessages = getDisplayMessages(messages, parsedMessages);
10809
10803
  return [{
@@ -10901,7 +10895,38 @@ const getProjectListDom = (projects, sessions, projectExpandedIds, selectedProje
10901
10895
  }, text('+ Add Project')];
10902
10896
  };
10903
10897
 
10904
- const getChatModeChatFocusVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, messagesScrollTop = 0, composerDropActive = false, composerDropEnabled = true, projects = [], projectExpandedIds = [], selectedProjectId = '', projectListScrollTop = 0, voiceDictationEnabled = false, useChatMathWorker = false, parsedMessages = [], authEnabled = false, authStatus = 'signed-out', authErrorMessage = '') => {
10898
+ const getChatModeChatFocusVirtualDom = ({
10899
+ authEnabled = false,
10900
+ authErrorMessage = '',
10901
+ authStatus = 'signed-out',
10902
+ composerDropActive = false,
10903
+ composerDropEnabled = true,
10904
+ composerFontFamily = 'system-ui',
10905
+ composerFontSize = 13,
10906
+ composerHeight = 28,
10907
+ composerLineHeight = 20,
10908
+ composerValue,
10909
+ messagesScrollTop = 0,
10910
+ models,
10911
+ openApiApiKeyInput,
10912
+ openRouterApiKeyInput,
10913
+ openRouterApiKeyState = 'idle',
10914
+ parsedMessages = [],
10915
+ projectExpandedIds = [],
10916
+ projectListScrollTop = 0,
10917
+ projects = [],
10918
+ runMode,
10919
+ selectedModelId,
10920
+ selectedProjectId = '',
10921
+ selectedSessionId,
10922
+ sessions,
10923
+ showRunMode,
10924
+ tokensMax,
10925
+ tokensUsed,
10926
+ usageOverviewEnabled,
10927
+ useChatMathWorker = false,
10928
+ voiceDictationEnabled = false
10929
+ }) => {
10905
10930
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
10906
10931
  const messages = selectedSession ? selectedSession.messages : [];
10907
10932
  const isDropOverlayVisible = composerDropEnabled && composerDropActive;
@@ -10911,7 +10936,7 @@ const getChatModeChatFocusVirtualDom = (sessions, selectedSessionId, composerVal
10911
10936
  onDragEnter: HandleDragEnterChatView,
10912
10937
  onDragOver: HandleDragOverChatView,
10913
10938
  type: Div
10914
- }, ...getProjectListDom(projects, sessions, projectExpandedIds, selectedProjectId, selectedSessionId, projectListScrollTop), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop, useChatMathWorker), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, voiceDictationEnabled), ...(isDropOverlayVisible ? [{
10939
+ }, ...getProjectListDom(projects, sessions, projectExpandedIds, selectedProjectId, selectedSessionId, projectListScrollTop), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop, useChatMathWorker, true), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, showRunMode, runMode, voiceDictationEnabled), ...(isDropOverlayVisible ? [{
10915
10940
  childCount: 1,
10916
10941
  className: mergeClassNames(ChatViewDropOverlay, ChatViewDropOverlayActive),
10917
10942
  name: ComposerDropTarget,
@@ -11027,7 +11052,34 @@ const getChatHeaderDomDetailMode = (selectedSessionTitle, authEnabled = false, a
11027
11052
  }, text(authErrorMessage)] : [])];
11028
11053
  };
11029
11054
 
11030
- const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState = 'idle', composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, messagesScrollTop = 0, composerDropActive = false, composerDropEnabled = true, voiceDictationEnabled = false, useChatMathWorker = false, parsedMessages = [], authEnabled = false, authStatus = 'signed-out', authErrorMessage = '') => {
11055
+ const getChatModeDetailVirtualDom = ({
11056
+ authEnabled = false,
11057
+ authErrorMessage = '',
11058
+ authStatus = 'signed-out',
11059
+ composerDropActive = false,
11060
+ composerDropEnabled = true,
11061
+ composerFontFamily = 'system-ui',
11062
+ composerFontSize = 13,
11063
+ composerHeight = 28,
11064
+ composerLineHeight = 20,
11065
+ composerValue,
11066
+ messagesScrollTop = 0,
11067
+ models,
11068
+ openApiApiKeyInput,
11069
+ openRouterApiKeyInput,
11070
+ openRouterApiKeyState = 'idle',
11071
+ parsedMessages = [],
11072
+ runMode,
11073
+ selectedModelId,
11074
+ selectedSessionId,
11075
+ sessions,
11076
+ showRunMode,
11077
+ tokensMax,
11078
+ tokensUsed,
11079
+ usageOverviewEnabled,
11080
+ useChatMathWorker = false,
11081
+ voiceDictationEnabled = false
11082
+ }) => {
11031
11083
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
11032
11084
  const selectedSessionTitle = selectedSession?.title || chatTitle();
11033
11085
  const messages = selectedSession ? selectedSession.messages : [];
@@ -11038,7 +11090,7 @@ const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue,
11038
11090
  onDragEnter: HandleDragEnterChatView,
11039
11091
  onDragOver: HandleDragOverChatView,
11040
11092
  type: Div
11041
- }, ...getChatHeaderDomDetailMode(selectedSessionTitle, authEnabled, authStatus, authErrorMessage), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop, useChatMathWorker), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, voiceDictationEnabled), ...(isDropOverlayVisible ? [{
11093
+ }, ...getChatHeaderDomDetailMode(selectedSessionTitle, authEnabled, authStatus, authErrorMessage), ...getMessagesDom(messages, parsedMessages, openRouterApiKeyInput, openApiApiKeyInput, openRouterApiKeyState, messagesScrollTop, useChatMathWorker), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, showRunMode, runMode, voiceDictationEnabled), ...(isDropOverlayVisible ? [{
11042
11094
  childCount: 1,
11043
11095
  className: mergeClassNames(ChatViewDropOverlay, ChatViewDropOverlayActive),
11044
11096
  name: ComposerDropTarget,
@@ -11086,7 +11138,7 @@ const getSessionDom = session => {
11086
11138
  return [{
11087
11139
  childCount: 2,
11088
11140
  className: sessionClassName,
11089
- type: Div
11141
+ type: Li
11090
11142
  }, {
11091
11143
  childCount: 1,
11092
11144
  className: ChatListItemLabel,
@@ -11120,11 +11172,33 @@ const getChatListDom = (sessions, selectedSessionId, chatListScrollTop = 0) => {
11120
11172
  onClick: HandleClickList,
11121
11173
  onScroll: HandleChatListScroll,
11122
11174
  scrollTop: chatListScrollTop,
11123
- type: Div
11175
+ type: Ul
11124
11176
  }, ...sessions.flatMap(getSessionDom)];
11125
11177
  };
11126
11178
 
11127
- const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight = 28, composerFontSize = 13, composerFontFamily = 'system-ui', composerLineHeight = 20, chatListScrollTop = 0, composerDropActive = false, composerDropEnabled = true, voiceDictationEnabled = false, authEnabled = false, authStatus = 'signed-out', authErrorMessage = '') => {
11179
+ const getChatModeListVirtualDom = ({
11180
+ authEnabled = false,
11181
+ authErrorMessage = '',
11182
+ authStatus = 'signed-out',
11183
+ chatListScrollTop = 0,
11184
+ composerDropActive = false,
11185
+ composerDropEnabled = true,
11186
+ composerFontFamily = 'system-ui',
11187
+ composerFontSize = 13,
11188
+ composerHeight = 28,
11189
+ composerLineHeight = 20,
11190
+ composerValue,
11191
+ models,
11192
+ runMode,
11193
+ selectedModelId,
11194
+ selectedSessionId,
11195
+ sessions,
11196
+ showRunMode,
11197
+ tokensMax,
11198
+ tokensUsed,
11199
+ usageOverviewEnabled,
11200
+ voiceDictationEnabled = false
11201
+ }) => {
11128
11202
  const isDropOverlayVisible = composerDropEnabled && composerDropActive;
11129
11203
  return [{
11130
11204
  childCount: isDropOverlayVisible ? 4 : 3,
@@ -11132,7 +11206,7 @@ const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, m
11132
11206
  onDragEnter: HandleDragEnterChatView,
11133
11207
  onDragOver: HandleDragOverChatView,
11134
11208
  type: Div
11135
- }, ...getChatHeaderListModeDom(authEnabled, authStatus, authErrorMessage), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, voiceDictationEnabled), ...(isDropOverlayVisible ? [{
11209
+ }, ...getChatHeaderListModeDom(authEnabled, authStatus, authErrorMessage), ...getChatListDom(sessions, selectedSessionId, chatListScrollTop), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, showRunMode, runMode, voiceDictationEnabled), ...(isDropOverlayVisible ? [{
11136
11210
  childCount: 1,
11137
11211
  className: mergeClassNames(ChatViewDropOverlay, ChatViewDropOverlayActive),
11138
11212
  name: ComposerDropTarget,
@@ -11169,15 +11243,129 @@ const getFallbackParsedMessages = sessions => {
11169
11243
  }
11170
11244
  return parsedMessages;
11171
11245
  };
11172
- const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop, composerDropActive = false, composerDropEnabled = true, projects = [], projectExpandedIds = [], selectedProjectId = '', projectListScrollTop = 0, voiceDictationEnabled = false, useChatMathWorker = false, parsedMessages, authEnabled = false, authStatus = 'signed-out', authErrorMessage = '') => {
11173
- const effectiveParsedMessages = parsedMessages || getFallbackParsedMessages(sessions);
11246
+ const getChatVirtualDom = options => {
11247
+ const {
11248
+ authEnabled = false,
11249
+ authErrorMessage = '',
11250
+ authStatus = 'signed-out',
11251
+ chatListScrollTop,
11252
+ composerDropActive = false,
11253
+ composerDropEnabled = true,
11254
+ composerFontFamily,
11255
+ composerFontSize,
11256
+ composerHeight,
11257
+ composerLineHeight,
11258
+ composerValue,
11259
+ messagesScrollTop,
11260
+ models,
11261
+ openApiApiKeyInput,
11262
+ openRouterApiKeyInput,
11263
+ openRouterApiKeyState,
11264
+ parsedMessages: parsedMessagesInput,
11265
+ projectExpandedIds = [],
11266
+ projectListScrollTop = 0,
11267
+ projects = [],
11268
+ runMode,
11269
+ selectedModelId,
11270
+ selectedProjectId = '',
11271
+ selectedSessionId,
11272
+ sessions,
11273
+ showRunMode,
11274
+ tokensMax,
11275
+ tokensUsed,
11276
+ usageOverviewEnabled,
11277
+ useChatMathWorker = false,
11278
+ viewMode,
11279
+ voiceDictationEnabled = false
11280
+ } = options;
11281
+ const parsedMessages = parsedMessagesInput ?? getFallbackParsedMessages(sessions);
11174
11282
  switch (viewMode) {
11175
11283
  case 'chat-focus':
11176
- return getChatModeChatFocusVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop, composerDropActive, composerDropEnabled, projects, projectExpandedIds, selectedProjectId, projectListScrollTop, voiceDictationEnabled, useChatMathWorker, effectiveParsedMessages, authEnabled, authStatus, authErrorMessage);
11284
+ return getChatModeChatFocusVirtualDom({
11285
+ authEnabled,
11286
+ authErrorMessage,
11287
+ authStatus,
11288
+ composerDropActive,
11289
+ composerDropEnabled,
11290
+ composerFontFamily,
11291
+ composerFontSize,
11292
+ composerHeight,
11293
+ composerLineHeight,
11294
+ composerValue,
11295
+ messagesScrollTop,
11296
+ models,
11297
+ openApiApiKeyInput,
11298
+ openRouterApiKeyInput,
11299
+ openRouterApiKeyState,
11300
+ parsedMessages,
11301
+ projectExpandedIds,
11302
+ projectListScrollTop,
11303
+ projects,
11304
+ runMode,
11305
+ selectedModelId,
11306
+ selectedProjectId,
11307
+ selectedSessionId,
11308
+ sessions,
11309
+ showRunMode,
11310
+ tokensMax,
11311
+ tokensUsed,
11312
+ usageOverviewEnabled,
11313
+ useChatMathWorker,
11314
+ voiceDictationEnabled
11315
+ });
11177
11316
  case 'detail':
11178
- return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, openApiApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, messagesScrollTop, composerDropActive, composerDropEnabled, voiceDictationEnabled, useChatMathWorker, effectiveParsedMessages, authEnabled, authStatus, authErrorMessage);
11317
+ return getChatModeDetailVirtualDom({
11318
+ authEnabled,
11319
+ authErrorMessage,
11320
+ authStatus,
11321
+ composerDropActive,
11322
+ composerDropEnabled,
11323
+ composerFontFamily,
11324
+ composerFontSize,
11325
+ composerHeight,
11326
+ composerLineHeight,
11327
+ composerValue,
11328
+ messagesScrollTop,
11329
+ models,
11330
+ openApiApiKeyInput,
11331
+ openRouterApiKeyInput,
11332
+ openRouterApiKeyState,
11333
+ parsedMessages,
11334
+ runMode,
11335
+ selectedModelId,
11336
+ selectedSessionId,
11337
+ sessions,
11338
+ showRunMode,
11339
+ tokensMax,
11340
+ tokensUsed,
11341
+ usageOverviewEnabled,
11342
+ useChatMathWorker,
11343
+ voiceDictationEnabled
11344
+ });
11179
11345
  case 'list':
11180
- return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, composerDropActive, composerDropEnabled, voiceDictationEnabled, authEnabled, authStatus, authErrorMessage);
11346
+ return getChatModeListVirtualDom({
11347
+ authEnabled,
11348
+ authErrorMessage,
11349
+ authStatus,
11350
+ chatListScrollTop,
11351
+ composerDropActive,
11352
+ composerDropEnabled,
11353
+ composerFontFamily,
11354
+ composerFontSize,
11355
+ composerHeight,
11356
+ composerLineHeight,
11357
+ composerValue,
11358
+ models,
11359
+ runMode,
11360
+ selectedModelId,
11361
+ selectedSessionId,
11362
+ sessions,
11363
+ showRunMode,
11364
+ tokensMax,
11365
+ tokensUsed,
11366
+ usageOverviewEnabled,
11367
+ voiceDictationEnabled
11368
+ });
11181
11369
  default:
11182
11370
  return getChatModeUnsupportedVirtualDom();
11183
11371
  }
@@ -11206,10 +11394,12 @@ const renderItems = (oldState, newState) => {
11206
11394
  projectExpandedIds,
11207
11395
  projectListScrollTop,
11208
11396
  projects,
11397
+ runMode,
11209
11398
  selectedModelId,
11210
11399
  selectedProjectId,
11211
11400
  selectedSessionId,
11212
11401
  sessions,
11402
+ showRunMode,
11213
11403
  tokensMax,
11214
11404
  tokensUsed,
11215
11405
  uid,
@@ -11221,7 +11411,40 @@ const renderItems = (oldState, newState) => {
11221
11411
  if (initial) {
11222
11412
  return [SetDom2, uid, []];
11223
11413
  }
11224
- const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax, openApiApiKeyInput, openRouterApiKeyState, composerHeight, composerFontSize, composerFontFamily, composerLineHeight, chatListScrollTop, messagesScrollTop, composerDropActive, composerDropEnabled, projects, projectExpandedIds, selectedProjectId, projectListScrollTop, voiceDictationEnabled, useChatMathWorker, parsedMessages, authEnabled, authStatus, authErrorMessage);
11414
+ const dom = getChatVirtualDom({
11415
+ authEnabled,
11416
+ authErrorMessage,
11417
+ authStatus,
11418
+ chatListScrollTop,
11419
+ composerDropActive,
11420
+ composerDropEnabled,
11421
+ composerFontFamily,
11422
+ composerFontSize,
11423
+ composerHeight,
11424
+ composerLineHeight,
11425
+ composerValue,
11426
+ messagesScrollTop,
11427
+ models,
11428
+ openApiApiKeyInput,
11429
+ openRouterApiKeyInput,
11430
+ openRouterApiKeyState,
11431
+ parsedMessages,
11432
+ projectExpandedIds,
11433
+ projectListScrollTop,
11434
+ projects,
11435
+ runMode,
11436
+ selectedModelId,
11437
+ selectedProjectId,
11438
+ selectedSessionId,
11439
+ sessions,
11440
+ showRunMode,
11441
+ tokensMax,
11442
+ tokensUsed,
11443
+ usageOverviewEnabled,
11444
+ useChatMathWorker,
11445
+ viewMode,
11446
+ voiceDictationEnabled
11447
+ });
11225
11448
  return [SetDom2, uid, dom];
11226
11449
  };
11227
11450
 
@@ -11425,6 +11648,9 @@ const renderEventListeners = () => {
11425
11648
  }, {
11426
11649
  name: HandleModelChange,
11427
11650
  params: ['handleModelChange', TargetValue]
11651
+ }, {
11652
+ name: HandleRunModeChange,
11653
+ params: ['handleRunModeChange', TargetValue]
11428
11654
  }, {
11429
11655
  name: HandleChatListScroll,
11430
11656
  params: ['handleChatListScroll', 'event.target.scrollTop']
@@ -11570,6 +11796,13 @@ const setQuestionToolEnabled = (state, questionToolEnabled) => {
11570
11796
  };
11571
11797
  };
11572
11798
 
11799
+ const setShowRunMode = (state, showRunMode) => {
11800
+ return {
11801
+ ...state,
11802
+ showRunMode
11803
+ };
11804
+ };
11805
+
11573
11806
  const setStreamingEnabled = (state, streamingEnabled) => {
11574
11807
  return {
11575
11808
  ...state,
@@ -11666,6 +11899,7 @@ const commandMap = {
11666
11899
  'Chat.handleModelChange': wrapCommand(handleModelChange),
11667
11900
  'Chat.handleProjectListContextMenu': wrapCommand(handleProjectListContextMenu),
11668
11901
  'Chat.handleProjectListScroll': wrapCommand(handleProjectListScroll),
11902
+ 'Chat.handleRunModeChange': wrapCommand(handleRunModeChange),
11669
11903
  'Chat.handleSubmit': wrapCommand(handleSubmit),
11670
11904
  'Chat.initialize': initialize,
11671
11905
  'Chat.loadContent': wrapCommand(loadContent),
@@ -11692,6 +11926,7 @@ const commandMap = {
11692
11926
  'Chat.setEmitStreamingFunctionCallEvents': wrapCommand(setEmitStreamingFunctionCallEvents),
11693
11927
  'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
11694
11928
  'Chat.setQuestionToolEnabled': wrapCommand(setQuestionToolEnabled),
11929
+ 'Chat.setShowRunMode': wrapCommand(setShowRunMode),
11695
11930
  'Chat.setStreamingEnabled': wrapCommand(setStreamingEnabled),
11696
11931
  'Chat.setUseChatCoordinatorWorker': wrapCommand(setUseChatCoordinatorWorker),
11697
11932
  'Chat.setUseChatMathWorker': wrapCommand(setUseChatMathWorker),