@lvce-editor/chat-view 6.8.0 → 6.10.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
  };
@@ -3349,194 +3343,6 @@ const getTools = async () => {
3349
3343
  return invoke$3('ChatTool.getTools');
3350
3344
  };
3351
3345
 
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 executeWriteFileTool = async (args, _options) => {
3503
- const filePath = typeof args.path === 'string' ? args.path : '';
3504
- const content = typeof args.content === 'string' ? args.content : '';
3505
- if (!filePath || isPathTraversalAttempt(filePath)) {
3506
- return JSON.stringify({
3507
- error: 'Access denied: path must be relative and stay within the open workspace folder.'
3508
- });
3509
- }
3510
- const normalizedPath = normalizeRelativePath(filePath);
3511
- try {
3512
- await writeFile(normalizedPath, content);
3513
- return JSON.stringify({
3514
- ok: true,
3515
- path: normalizedPath
3516
- });
3517
- } catch (error) {
3518
- return JSON.stringify({
3519
- ...getToolErrorPayload(error),
3520
- path: normalizedPath
3521
- });
3522
- }
3523
- };
3524
-
3525
- const parseToolArguments = rawArguments => {
3526
- if (typeof rawArguments !== 'string') {
3527
- return {};
3528
- }
3529
- try {
3530
- const parsed = JSON.parse(rawArguments);
3531
- if (!parsed || typeof parsed !== 'object') {
3532
- return {};
3533
- }
3534
- return parsed;
3535
- } catch {
3536
- return {};
3537
- }
3538
- };
3539
-
3540
3346
  const stringifyToolOutput = output => {
3541
3347
  if (typeof output === 'string') {
3542
3348
  return output;
@@ -3544,35 +3350,14 @@ const stringifyToolOutput = output => {
3544
3350
  return JSON.stringify(output) ?? 'null';
3545
3351
  };
3546
3352
  const executeChatTool = async (name, rawArguments, options) => {
3547
- if (options.useChatToolWorker) {
3548
- const workerOutput = await execute(name, rawArguments, {
3549
- assetDir: options.assetDir,
3550
- platform: options.platform
3551
- });
3552
- return stringifyToolOutput(workerOutput);
3553
- }
3554
- const args = parseToolArguments(rawArguments);
3555
- if (name === 'read_file') {
3556
- return executeReadFileTool(args);
3353
+ if (!options.useChatToolWorker) {
3354
+ throw new Error('Chat tools must be executed in a web worker environment. Please set useChatToolWorker to true in the options.');
3557
3355
  }
3558
- if (name === 'write_file') {
3559
- return executeWriteFileTool(args);
3560
- }
3561
- if (name === 'list_files') {
3562
- return executeListFilesTool(args);
3563
- }
3564
- if (name === 'getWorkspaceUri') {
3565
- return executeGetWorkspaceUriTool();
3566
- }
3567
- if (name === 'render_html') {
3568
- return executeRenderHtmlTool(args);
3569
- }
3570
- if (name === 'ask_question') {
3571
- return executeAskQuestionTool(args);
3572
- }
3573
- return JSON.stringify({
3574
- error: `Unknown tool: ${name}`
3356
+ const workerOutput = await execute(name, rawArguments, {
3357
+ assetDir: options.assetDir,
3358
+ platform: options.platform
3575
3359
  });
3360
+ return stringifyToolOutput(workerOutput);
3576
3361
  };
3577
3362
 
3578
3363
  const getReadFileTool = () => {
@@ -7235,6 +7020,31 @@ Assistant: ${assistantText}`;
7235
7020
  return title && !isDefaultSessionTitle(title) ? title : '';
7236
7021
  };
7237
7022
 
7023
+ const isPathTraversalAttempt = path => {
7024
+ if (!path) {
7025
+ return false;
7026
+ }
7027
+ if (path.startsWith('/') || path.startsWith('\\')) {
7028
+ return true;
7029
+ }
7030
+ if (path.startsWith('file://')) {
7031
+ return true;
7032
+ }
7033
+ if (/^[a-zA-Z]:[\\/]/.test(path)) {
7034
+ return true;
7035
+ }
7036
+ const segments = path.split(/[\\/]/);
7037
+ return segments.includes('..');
7038
+ };
7039
+
7040
+ const normalizeRelativePath = path => {
7041
+ const segments = path.split(/[\\/]/).filter(segment => segment && segment !== '.');
7042
+ if (segments.length === 0) {
7043
+ return '.';
7044
+ }
7045
+ return segments.join('/');
7046
+ };
7047
+
7238
7048
  const mentionRegex = /(^|\s)@([^\s]+)/g;
7239
7049
  const maxMentionCount = 5;
7240
7050
  const parseMentionedPaths = value => {
@@ -9288,12 +9098,14 @@ const ChatHeader = 'ChatHeader';
9288
9098
  const Button = 'Button';
9289
9099
  const ButtonPrimary = 'ButtonPrimary';
9290
9100
  const ButtonSecondary = 'ButtonSecondary';
9101
+ const Deletion = 'Deletion';
9291
9102
  const Empty = '';
9292
9103
  const FileIcon = 'FileIcon';
9293
9104
  const IconButton = 'IconButton';
9294
9105
  const IconButtonDisabled = 'IconButtonDisabled';
9295
9106
  const ImageElement = 'ImageElement';
9296
9107
  const InputBox = 'InputBox';
9108
+ const Insertion = 'Insertion';
9297
9109
  const Label = 'Label';
9298
9110
  const LabelDetail = 'LabelDetail';
9299
9111
  const ChatList = 'ChatList';
@@ -10014,6 +9826,76 @@ const getFileNameFromUri = uri => {
10014
9826
  return fileName || uri;
10015
9827
  };
10016
9828
 
9829
+ const isCompleteJson = value => {
9830
+ const trimmed = value.trim();
9831
+ if (!trimmed) {
9832
+ return false;
9833
+ }
9834
+ let depth = 0;
9835
+ let inString = false;
9836
+ let escaped = false;
9837
+ for (const char of trimmed) {
9838
+ if (inString) {
9839
+ if (escaped) {
9840
+ escaped = false;
9841
+ continue;
9842
+ }
9843
+ if (char === '\\') {
9844
+ escaped = true;
9845
+ continue;
9846
+ }
9847
+ if (char === '"') {
9848
+ inString = false;
9849
+ }
9850
+ continue;
9851
+ }
9852
+ if (char === '"') {
9853
+ inString = true;
9854
+ continue;
9855
+ }
9856
+ if (char === '{' || char === '[') {
9857
+ depth += 1;
9858
+ continue;
9859
+ }
9860
+ if (char === '}' || char === ']') {
9861
+ depth -= 1;
9862
+ if (depth < 0) {
9863
+ return false;
9864
+ }
9865
+ }
9866
+ }
9867
+ return depth === 0 && !inString && !escaped;
9868
+ };
9869
+ const getReadFileTarget = rawArguments => {
9870
+ // Tool arguments stream in chunks, so skip parsing until a full JSON payload is available.
9871
+ if (!isCompleteJson(rawArguments)) {
9872
+ return undefined;
9873
+ }
9874
+ let parsed;
9875
+ try {
9876
+ parsed = JSON.parse(rawArguments);
9877
+ } catch {
9878
+ return undefined;
9879
+ }
9880
+ if (!parsed || typeof parsed !== 'object') {
9881
+ return undefined;
9882
+ }
9883
+ const uri = Reflect.get(parsed, 'uri');
9884
+ const path = Reflect.get(parsed, 'path');
9885
+ const uriValue = typeof uri === 'string' ? uri : '';
9886
+ const pathValue = typeof path === 'string' ? path : '';
9887
+ const title = uriValue || pathValue;
9888
+ if (!title) {
9889
+ return undefined;
9890
+ }
9891
+ // `read_file` tool calls now use absolute `uri`; keep `path` as a legacy fallback for old transcripts.
9892
+ const clickableUri = uriValue || pathValue;
9893
+ return {
9894
+ clickableUri,
9895
+ title
9896
+ };
9897
+ };
9898
+
10017
9899
  const getToolCallArgumentPreview = rawArguments => {
10018
9900
  if (!rawArguments.trim()) {
10019
9901
  return '""';
@@ -10106,76 +9988,6 @@ const getToolCallAskQuestionVirtualDom = toolCall => {
10106
9988
  }, text(answer.trim() ? answer : '(empty answer)')])];
10107
9989
  };
10108
9990
 
10109
- const isCompleteJson = value => {
10110
- const trimmed = value.trim();
10111
- if (!trimmed) {
10112
- return false;
10113
- }
10114
- let depth = 0;
10115
- let inString = false;
10116
- let escaped = false;
10117
- for (const char of trimmed) {
10118
- if (inString) {
10119
- if (escaped) {
10120
- escaped = false;
10121
- continue;
10122
- }
10123
- if (char === '\\') {
10124
- escaped = true;
10125
- continue;
10126
- }
10127
- if (char === '"') {
10128
- inString = false;
10129
- }
10130
- continue;
10131
- }
10132
- if (char === '"') {
10133
- inString = true;
10134
- continue;
10135
- }
10136
- if (char === '{' || char === '[') {
10137
- depth += 1;
10138
- continue;
10139
- }
10140
- if (char === '}' || char === ']') {
10141
- depth -= 1;
10142
- if (depth < 0) {
10143
- return false;
10144
- }
10145
- }
10146
- }
10147
- return depth === 0 && !inString && !escaped;
10148
- };
10149
- const getReadFileTarget = rawArguments => {
10150
- // Tool arguments stream in chunks, so skip parsing until a full JSON payload is available.
10151
- if (!isCompleteJson(rawArguments)) {
10152
- return undefined;
10153
- }
10154
- let parsed;
10155
- try {
10156
- parsed = JSON.parse(rawArguments);
10157
- } catch {
10158
- return undefined;
10159
- }
10160
- if (!parsed || typeof parsed !== 'object') {
10161
- return undefined;
10162
- }
10163
- const uri = Reflect.get(parsed, 'uri');
10164
- const path = Reflect.get(parsed, 'path');
10165
- const uriValue = typeof uri === 'string' ? uri : '';
10166
- const pathValue = typeof path === 'string' ? path : '';
10167
- const title = uriValue || pathValue;
10168
- if (!title) {
10169
- return undefined;
10170
- }
10171
- // `read_file` tool calls now use absolute `uri`; keep `path` as a legacy fallback for old transcripts.
10172
- const clickableUri = uriValue || pathValue;
10173
- return {
10174
- clickableUri,
10175
- title
10176
- };
10177
- };
10178
-
10179
9991
  const getToolCallReadFileVirtualDom = toolCall => {
10180
9992
  const target = getReadFileTarget(toolCall.arguments);
10181
9993
  if (!target) {
@@ -10514,8 +10326,19 @@ const getToolCallDisplayName = name => {
10514
10326
  }
10515
10327
  return name;
10516
10328
  };
10329
+ const hasIncompleteJsonArguments = rawArguments => {
10330
+ try {
10331
+ JSON.parse(rawArguments);
10332
+ return false;
10333
+ } catch {
10334
+ return true;
10335
+ }
10336
+ };
10517
10337
  const getToolCallLabel = toolCall => {
10518
10338
  const displayName = getToolCallDisplayName(toolCall.name);
10339
+ if (toolCall.name === 'write_file' && !toolCall.status && hasIncompleteJsonArguments(toolCall.arguments)) {
10340
+ return `${displayName} (in progress)`;
10341
+ }
10519
10342
  const argumentPreview = getToolCallArgumentPreview(toolCall.arguments);
10520
10343
  const statusLabel = getToolCallStatusLabel(toolCall);
10521
10344
  if (argumentPreview === '{}') {
@@ -10546,6 +10369,101 @@ const getToolCallGetWorkspaceUriVirtualDom = toolCall => {
10546
10369
  type: Span
10547
10370
  }, text(fileName), ...(statusLabel ? [text(statusLabel)] : [])];
10548
10371
  };
10372
+ const parseWriteFileLineCounts = rawResult => {
10373
+ if (!rawResult) {
10374
+ return {
10375
+ linesAdded: 0,
10376
+ linesDeleted: 0
10377
+ };
10378
+ }
10379
+ let parsed;
10380
+ try {
10381
+ parsed = JSON.parse(rawResult);
10382
+ } catch {
10383
+ return {
10384
+ linesAdded: 0,
10385
+ linesDeleted: 0
10386
+ };
10387
+ }
10388
+ if (!parsed || typeof parsed !== 'object') {
10389
+ return {
10390
+ linesAdded: 0,
10391
+ linesDeleted: 0
10392
+ };
10393
+ }
10394
+ const linesAdded = Reflect.get(parsed, 'linesAdded');
10395
+ const linesDeleted = Reflect.get(parsed, 'linesDeleted');
10396
+ return {
10397
+ linesAdded: typeof linesAdded === 'number' ? Math.max(0, linesAdded) : 0,
10398
+ linesDeleted: typeof linesDeleted === 'number' ? Math.max(0, linesDeleted) : 0
10399
+ };
10400
+ };
10401
+ const getToolCallWriteFileVirtualDom = toolCall => {
10402
+ const target = getReadFileTarget(toolCall.arguments);
10403
+ if (!target) {
10404
+ return [];
10405
+ }
10406
+ const fileName = getFileNameFromUri(target.title);
10407
+ const statusLabel = getToolCallStatusLabel(toolCall);
10408
+ const {
10409
+ linesAdded,
10410
+ linesDeleted
10411
+ } = parseWriteFileLineCounts(toolCall.result);
10412
+ const fileNameClickableProps = target.clickableUri ? {
10413
+ 'data-uri': target.clickableUri,
10414
+ onClick: HandleClickReadFile
10415
+ } : {};
10416
+ return [{
10417
+ childCount: statusLabel ? 6 : 5,
10418
+ className: ChatOrderedListItem,
10419
+ title: target.title,
10420
+ type: Li
10421
+ }, {
10422
+ childCount: 0,
10423
+ className: FileIcon,
10424
+ type: Div
10425
+ }, text('write_file '), {
10426
+ childCount: 1,
10427
+ className: ChatToolCallReadFileLink,
10428
+ ...fileNameClickableProps,
10429
+ type: Span
10430
+ }, text(fileName), {
10431
+ childCount: 1,
10432
+ className: Insertion,
10433
+ type: Span
10434
+ }, text(` +${linesAdded}`), {
10435
+ childCount: 1,
10436
+ className: Deletion,
10437
+ type: Span
10438
+ }, text(` -${linesDeleted}`), ...(statusLabel ? [text(statusLabel)] : [])];
10439
+ };
10440
+ const getToolCallEditFileVirtualDom = toolCall => {
10441
+ const target = getReadFileTarget(toolCall.arguments);
10442
+ if (!target) {
10443
+ return [];
10444
+ }
10445
+ const fileName = getFileNameFromUri(target.title);
10446
+ const fileNameClickableProps = target.clickableUri ? {
10447
+ 'data-uri': target.clickableUri,
10448
+ onClick: HandleClickReadFile
10449
+ } : {};
10450
+ return [{
10451
+ childCount: 3,
10452
+ className: ChatOrderedListItem,
10453
+ title: target.title,
10454
+ type: Li
10455
+ }, {
10456
+ childCount: 0,
10457
+ className: FileIcon,
10458
+ type: Div
10459
+ }, text('edit_file '), {
10460
+ childCount: 1,
10461
+ className: ChatToolCallReadFileLink,
10462
+ title: target.clickableUri,
10463
+ ...fileNameClickableProps,
10464
+ type: Span
10465
+ }, text(fileName)];
10466
+ };
10549
10467
  const getToolCallDom = toolCall => {
10550
10468
  if (toolCall.name === 'getWorkspaceUri') {
10551
10469
  const virtualDom = getToolCallGetWorkspaceUriVirtualDom(toolCall);
@@ -10559,6 +10477,18 @@ const getToolCallDom = toolCall => {
10559
10477
  return virtualDom;
10560
10478
  }
10561
10479
  }
10480
+ if (toolCall.name === 'write_file') {
10481
+ const virtualDom = getToolCallWriteFileVirtualDom(toolCall);
10482
+ if (virtualDom.length > 0) {
10483
+ return virtualDom;
10484
+ }
10485
+ }
10486
+ if (toolCall.name === 'edit_file') {
10487
+ const virtualDom = getToolCallEditFileVirtualDom(toolCall);
10488
+ if (virtualDom.length > 0) {
10489
+ return virtualDom;
10490
+ }
10491
+ }
10562
10492
  if (toolCall.name === 'render_html') {
10563
10493
  const virtualDom = getToolCallRenderHtmlVirtualDom(toolCall);
10564
10494
  if (virtualDom.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/chat-view",
3
- "version": "6.8.0",
3
+ "version": "6.10.0",
4
4
  "description": "Chat View Worker",
5
5
  "repository": {
6
6
  "type": "git",