@tutti-os/agent-gui 0.0.45 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/{AgentMentionSearchController-D5ks45fN.d.ts → AgentMentionSearchController-CExFPobz.d.ts} +1 -1
  2. package/dist/AgentMessageMarkdown-DeYPURtF.d.ts +7 -0
  3. package/dist/agent-conversation/index.d.ts +4 -3
  4. package/dist/agent-conversation/index.js +8 -8
  5. package/dist/agent-message-center/index.d.ts +1 -1
  6. package/dist/agent-message-center/index.js +9 -9
  7. package/dist/{agentConversationVM-1QgRwvwZ.d.ts → agentConversationVM-BTMtRteS.d.ts} +14 -0
  8. package/dist/{AgentMessageMarkdown-Cts0dAIm.d.ts → agentGuiNodeTypes-B5Y6yDKy.d.ts} +43 -9
  9. package/dist/app/renderer/agentactivity.css +148 -25
  10. package/dist/{chunk-ZG3WZSMV.js → chunk-2FOBSURO.js} +29 -10
  11. package/dist/chunk-2FOBSURO.js.map +1 -0
  12. package/dist/{chunk-AMNXH4DJ.js → chunk-2RYZ4EBF.js} +2 -2
  13. package/dist/{chunk-EIC7XS3C.js → chunk-5QTC2L52.js} +2 -2
  14. package/dist/{chunk-UGV6JTWM.js → chunk-7NCWSH7U.js} +9 -9
  15. package/dist/{chunk-UGV6JTWM.js.map → chunk-7NCWSH7U.js.map} +1 -1
  16. package/dist/{chunk-62QOR3TC.js → chunk-HCS3HUUX.js} +5 -5
  17. package/dist/{chunk-OL54R6OL.js → chunk-J3SJZMI5.js} +2 -2
  18. package/dist/{chunk-VFQR7AZI.js → chunk-KACBTC7Y.js} +25 -3
  19. package/dist/chunk-KACBTC7Y.js.map +1 -0
  20. package/dist/{chunk-FJG2EH4V.js → chunk-KUCWRSXU.js} +2 -2
  21. package/dist/{chunk-D6IYEVDT.js → chunk-MCH35MAX.js} +2 -2
  22. package/dist/{chunk-NZ2BGOJU.js → chunk-Q4X6Q4E6.js} +2 -2
  23. package/dist/{chunk-EU7RWKHE.js → chunk-R6GOICRO.js} +5 -5
  24. package/dist/chunk-SD56WDSC.js +359 -0
  25. package/dist/chunk-SD56WDSC.js.map +1 -0
  26. package/dist/{chunk-TXXWUG2J.js → chunk-VE6JY2TH.js} +2 -1
  27. package/dist/{chunk-TXXWUG2J.js.map → chunk-VE6JY2TH.js.map} +1 -1
  28. package/dist/{chunk-QT45WUPQ.js → chunk-XJXSR5XI.js} +244 -21
  29. package/dist/chunk-XJXSR5XI.js.map +1 -0
  30. package/dist/{chunk-CZ7PS3DU.js → chunk-ZAF4IVUT.js} +859 -104
  31. package/dist/chunk-ZAF4IVUT.js.map +1 -0
  32. package/dist/context-mention-palette/index.d.ts +3 -3
  33. package/dist/context-mention-palette/index.js +9 -9
  34. package/dist/i18n/index.d.ts +24 -2
  35. package/dist/i18n/index.js +2 -2
  36. package/dist/index.d.ts +22 -6
  37. package/dist/index.js +1011 -825
  38. package/dist/index.js.map +1 -1
  39. package/dist/plan-decision-ops.d.ts +1 -1
  40. package/dist/queued-prompt-runtime.d.ts +97 -0
  41. package/dist/queued-prompt-runtime.js +9 -0
  42. package/dist/queued-prompt-runtime.js.map +1 -0
  43. package/dist/workbench/contribution.js +3 -3
  44. package/dist/workbench/index.js +9 -9
  45. package/dist/workbench/launch.js +2 -2
  46. package/dist/workbench/sessionTitle.js +3 -3
  47. package/dist/workspace-agent-generated-files.js +3 -3
  48. package/package.json +19 -12
  49. package/dist/chunk-CZ7PS3DU.js.map +0 -1
  50. package/dist/chunk-QT45WUPQ.js.map +0 -1
  51. package/dist/chunk-VFQR7AZI.js.map +0 -1
  52. package/dist/chunk-ZG3WZSMV.js.map +0 -1
  53. /package/dist/{chunk-AMNXH4DJ.js.map → chunk-2RYZ4EBF.js.map} +0 -0
  54. /package/dist/{chunk-EIC7XS3C.js.map → chunk-5QTC2L52.js.map} +0 -0
  55. /package/dist/{chunk-62QOR3TC.js.map → chunk-HCS3HUUX.js.map} +0 -0
  56. /package/dist/{chunk-OL54R6OL.js.map → chunk-J3SJZMI5.js.map} +0 -0
  57. /package/dist/{chunk-FJG2EH4V.js.map → chunk-KUCWRSXU.js.map} +0 -0
  58. /package/dist/{chunk-D6IYEVDT.js.map → chunk-MCH35MAX.js.map} +0 -0
  59. /package/dist/{chunk-NZ2BGOJU.js.map → chunk-Q4X6Q4E6.js.map} +0 -0
  60. /package/dist/{chunk-EU7RWKHE.js.map → chunk-R6GOICRO.js.map} +0 -0
@@ -10,14 +10,15 @@ import {
10
10
  resolveWorkspaceFileLinkAction,
11
11
  resolveWorkspaceFilePathCandidate,
12
12
  resolveWorkspaceLinkAction
13
- } from "./chunk-QT45WUPQ.js";
13
+ } from "./chunk-XJXSR5XI.js";
14
14
  import {
15
+ AGENT_RICH_TEXT_CARET_ANCHOR,
15
16
  attrsToMentionItem,
16
17
  createAgentFileMentionExtension,
17
18
  formatAgentMentionMarkdown,
18
19
  mentionItemToAttrs,
19
20
  parseAgentMentionMarkdown
20
- } from "./chunk-ZG3WZSMV.js";
21
+ } from "./chunk-2FOBSURO.js";
21
22
  import {
22
23
  fileChangeCountFromChanges,
23
24
  fileChangeEntriesFromChanges,
@@ -27,13 +28,14 @@ import {
27
28
  import {
28
29
  getOptionalAgentActivityRuntime,
29
30
  useOptionalAgentHostApi
30
- } from "./chunk-TXXWUG2J.js";
31
+ } from "./chunk-VE6JY2TH.js";
31
32
  import {
32
33
  workspaceAgentProviderLabel
33
34
  } from "./chunk-TYGL25EL.js";
34
35
  import {
36
+ getActiveUiLanguage,
35
37
  translate
36
- } from "./chunk-EIC7XS3C.js";
38
+ } from "./chunk-5QTC2L52.js";
37
39
 
38
40
  // shared/imageGenerationTool.ts
39
41
  var KNOWN_IMAGE_GENERATION_TOOL_NAMES = /* @__PURE__ */ new Set([
@@ -3349,7 +3351,10 @@ function tryExtractContentField(text) {
3349
3351
 
3350
3352
  // shared/agentConversation/projection/agentTurnSummaryProjection.ts
3351
3353
  function projectAgentTurnSummaryRows(detail) {
3352
- const options = { workspaceRoot: detail.workspaceRoot };
3354
+ const options = {
3355
+ defaultCwd: detail.cwd,
3356
+ workspaceRoot: detail.workspaceRoot
3357
+ };
3353
3358
  const rows = detail.turns.flatMap(
3354
3359
  (turn) => projectAgentTurnSummaryRowForTurn(turn, options)
3355
3360
  );
@@ -3401,16 +3406,14 @@ function normalizedActivityFilePath(value, options) {
3401
3406
  }) ? path : null;
3402
3407
  }
3403
3408
  function projectAgentTurnSummaryRowForTurn(turn, options = {}) {
3409
+ const summaryCalls = turnToolCallsForSummary(turn);
3404
3410
  const files = applyShortestUniqueFileLabels(
3405
- dedupeFiles(
3406
- turnToolCallsForSummary(turn).flatMap(
3407
- (call) => filesFromCall(call, options)
3408
- )
3409
- )
3411
+ dedupeFiles(summaryCalls.flatMap((call) => filesFromCall(call, options)))
3410
3412
  );
3411
3413
  if (files.length === 0) {
3412
3414
  return [];
3413
3415
  }
3416
+ const patchBatches = patchBatchesFromCalls(summaryCalls, options);
3414
3417
  const fileCount = files.length;
3415
3418
  const createdCount = files.filter(
3416
3419
  (file) => file.changeType === "created"
@@ -3423,6 +3426,7 @@ function projectAgentTurnSummaryRowForTurn(turn, options = {}) {
3423
3426
  id: `turn-summary:${turn.id}`,
3424
3427
  turnId: turn.id,
3425
3428
  files,
3429
+ ...patchBatches.length > 0 ? { patchBatches } : {},
3426
3430
  fileCount,
3427
3431
  modifiedCount,
3428
3432
  createdCount,
@@ -3452,11 +3456,10 @@ function turnToolCallsForSummary(turn) {
3452
3456
  }
3453
3457
  function filesFromCall(call, options) {
3454
3458
  const toolState = objectValue5(call.payload?.tool_state);
3455
- const metadata = objectValue5(call.payload?.metadata);
3456
3459
  const input = objectValue5(call.payload?.input) ?? objectValue5(toolState?.input) ?? summaryPathInput(call.summary, options);
3457
3460
  const output = objectValue5(call.payload?.output) ?? objectValue5(toolState?.output);
3458
3461
  const error = objectValue5(call.payload?.error) ?? objectValue5(toolState?.error);
3459
- const nestedTaskSteps = arrayValue5(metadata?.steps) ?? arrayValue5(output?.steps) ?? arrayValue5(call.payload?.steps) ?? [];
3462
+ const nestedTaskSteps = nestedTaskStepsFromPayload(call.payload, output);
3460
3463
  const directChanges = extractFileChanges({
3461
3464
  id: call.id,
3462
3465
  toolName: call.toolName,
@@ -3476,11 +3479,7 @@ function filesFromCall(call, options) {
3476
3479
  return extractFileChanges({
3477
3480
  id: stringValue6(step.toolUseId) ?? stringValue6(step.id) ?? `${call.id}:step:${index + 1}`,
3478
3481
  toolName: stringValue6(step.toolName) ?? stringValue6(step.tool_name) ?? stringValue6(step.name) ?? null,
3479
- statusKind: firstNonEmptyString(
3480
- stringValue6(step.statusKind),
3481
- stringValue6(step.status),
3482
- stringValue6(step.toolStatus)
3483
- ) ?? call.statusKind ?? null,
3482
+ statusKind: nestedTaskStepStatusKind(step, call.statusKind ?? null),
3484
3483
  occurredAtUnixMs: numberValue2(step.occurredAtUnixMs) ?? numberValue2(step.occurred_at_unix_ms) ?? call.occurredAtUnixMs ?? null,
3485
3484
  payload: objectValue5(step.payload),
3486
3485
  input: objectValue5(step.toolInput) ?? objectValue5(step.tool_input),
@@ -3491,6 +3490,125 @@ function filesFromCall(call, options) {
3491
3490
  });
3492
3491
  return [...directChanges, ...nestedChanges];
3493
3492
  }
3493
+ function patchBatchesFromCalls(calls, options) {
3494
+ return calls.flatMap((call) => patchBatchesFromCall(call, options));
3495
+ }
3496
+ function patchBatchesFromCall(call, options) {
3497
+ const directBatch = isFailedToolStatus(call.statusKind ?? null) ? [] : patchBatchFromPayload({
3498
+ id: call.id,
3499
+ payload: call.payload ?? null,
3500
+ toolInput: null,
3501
+ toolOutput: null,
3502
+ toolError: null,
3503
+ options
3504
+ });
3505
+ const output = objectValue5(call.payload?.output);
3506
+ const nestedTaskSteps = nestedTaskStepsFromPayload(call.payload, output);
3507
+ const nestedBatches = nestedTaskSteps.flatMap((value, index) => {
3508
+ const step = objectValue5(value);
3509
+ if (!step) {
3510
+ return [];
3511
+ }
3512
+ if (isFailedToolStatus(
3513
+ nestedTaskStepStatusKind(step, call.statusKind ?? null)
3514
+ )) {
3515
+ return [];
3516
+ }
3517
+ return patchBatchFromPayload({
3518
+ id: stringValue6(step.toolUseId) ?? stringValue6(step.id) ?? `${call.id}:step:${index + 1}`,
3519
+ payload: objectValue5(step.payload),
3520
+ toolInput: objectValue5(step.toolInput) ?? objectValue5(step.tool_input),
3521
+ toolOutput: objectValue5(step.toolResult) ?? objectValue5(step.tool_result),
3522
+ toolError: objectValue5(step.toolError) ?? objectValue5(step.tool_error),
3523
+ options
3524
+ });
3525
+ });
3526
+ return [...directBatch, ...nestedBatches];
3527
+ }
3528
+ function patchBatchFromPayload(input) {
3529
+ const toolState = objectValue5(input.payload?.tool_state);
3530
+ const metadata = objectValue5(input.payload?.metadata);
3531
+ const payloadInput = input.toolInput ?? objectValue5(input.payload?.input) ?? objectValue5(toolState?.input);
3532
+ const payloadOutput = input.toolOutput ?? objectValue5(input.payload?.output) ?? objectValue5(toolState?.output);
3533
+ const rawInput = objectValue5(payloadInput?.rawInput);
3534
+ const changes = firstFileChangeValue(
3535
+ payloadOutput?.changes,
3536
+ input.payload?.changes,
3537
+ payloadInput?.changes,
3538
+ rawInput?.changes
3539
+ );
3540
+ const patchChanges = patchChangesFromChangeMap(changes);
3541
+ if (patchChanges.length === 0) {
3542
+ return [];
3543
+ }
3544
+ return [
3545
+ {
3546
+ cwd: firstNonEmptyString(
3547
+ stringValue6(input.payload?.cwd),
3548
+ stringValue6(payloadInput?.cwd),
3549
+ stringValue6(rawInput?.cwd),
3550
+ stringValue6(payloadOutput?.cwd),
3551
+ stringValue6(metadata?.cwd),
3552
+ input.options.defaultCwd ?? null
3553
+ ) ?? null,
3554
+ toolCallId: input.id,
3555
+ changes: patchChanges
3556
+ }
3557
+ ];
3558
+ }
3559
+ function patchChangesFromChangeMap(changes) {
3560
+ return fileChangeEntriesFromChanges(changes).flatMap((entry) => {
3561
+ const change = entry.change;
3562
+ const normalizedPath = entry.path.trim();
3563
+ if (!normalizedPath) {
3564
+ return [];
3565
+ }
3566
+ const normalizedType = normalizeChangeType(fileChangeTypeValue(change));
3567
+ const unifiedDiff = firstNonEmptyString(
3568
+ stringValue6(change.unified_diff),
3569
+ stringValue6(change.unifiedDiff),
3570
+ stringValue6(change.diff),
3571
+ stringValue6(change.patch)
3572
+ ) ?? null;
3573
+ let oldString = firstPresentString6(
3574
+ literalStringValue(change.old_string),
3575
+ literalStringValue(change.oldString)
3576
+ );
3577
+ const explicitContent = literalStringValue(change.content);
3578
+ let newString = firstPresentString6(
3579
+ literalStringValue(change.new_string),
3580
+ literalStringValue(change.newString),
3581
+ explicitContent
3582
+ );
3583
+ if (normalizedType === "created" && oldString === null && newString !== null) {
3584
+ oldString = "";
3585
+ }
3586
+ if (normalizedType === "deleted" && oldString === null && newString !== null) {
3587
+ oldString = newString;
3588
+ newString = "";
3589
+ }
3590
+ if (normalizedType === "deleted" && newString === null && oldString !== null) {
3591
+ newString = "";
3592
+ }
3593
+ const content = firstPresentString6(
3594
+ normalizedType === "deleted" ? null : explicitContent,
3595
+ normalizedType === "created" ? newString : null
3596
+ );
3597
+ if (!unifiedDiff && oldString === null && newString === null && content === null) {
3598
+ return [];
3599
+ }
3600
+ return [
3601
+ {
3602
+ path: normalizedPath,
3603
+ changeType: normalizedType ?? inferAgentPatchChangeType(unifiedDiff),
3604
+ unifiedDiff,
3605
+ oldString,
3606
+ newString,
3607
+ content
3608
+ }
3609
+ ];
3610
+ });
3611
+ }
3494
3612
  function summaryPathInput(summary, options) {
3495
3613
  const path = normalizedFilePath(summary, options);
3496
3614
  return path ? { path, summaryPathFallback: true } : null;
@@ -3531,21 +3649,21 @@ function extractFileChanges(input) {
3531
3649
  stringValue6(payload?.patch),
3532
3650
  stringValue6(metadata?.patch)
3533
3651
  ),
3534
- firstNonEmptyString(
3535
- stringValue6(input.output?.oldString),
3536
- stringValue6(input.output?.old_string),
3537
- stringValue6(input.input?.oldString),
3538
- stringValue6(input.input?.old_string)
3652
+ firstPresentString6(
3653
+ literalStringValue(input.output?.oldString),
3654
+ literalStringValue(input.output?.old_string),
3655
+ literalStringValue(input.input?.oldString),
3656
+ literalStringValue(input.input?.old_string)
3539
3657
  ),
3540
- firstNonEmptyString(
3541
- stringValue6(input.output?.newString),
3542
- stringValue6(input.output?.new_string),
3543
- stringValue6(input.input?.newString),
3544
- stringValue6(input.input?.new_string)
3658
+ firstPresentString6(
3659
+ literalStringValue(input.output?.newString),
3660
+ literalStringValue(input.output?.new_string),
3661
+ literalStringValue(input.input?.newString),
3662
+ literalStringValue(input.input?.new_string)
3545
3663
  ),
3546
- firstNonEmptyString(
3547
- stringValue6(input.output?.content),
3548
- stringValue6(input.input?.content)
3664
+ firstPresentString6(
3665
+ literalStringValue(input.output?.content),
3666
+ literalStringValue(input.input?.content)
3549
3667
  ),
3550
3668
  input.options
3551
3669
  );
@@ -3604,25 +3722,25 @@ function extractFileChanges(input) {
3604
3722
  stringValue6(metadata?.patch),
3605
3723
  stringValue6(input.output?.content)
3606
3724
  ) ?? null;
3607
- const oldString = firstNonEmptyString(
3608
- stringValue6(input.output?.oldString),
3609
- stringValue6(input.output?.old_string),
3610
- stringValue6(input.input?.oldString),
3611
- stringValue6(input.input?.old_string)
3612
- ) ?? null;
3613
- const newString = firstNonEmptyString(
3614
- stringValue6(input.output?.newString),
3615
- stringValue6(input.output?.new_string),
3616
- stringValue6(input.input?.newString),
3617
- stringValue6(input.input?.new_string)
3618
- ) ?? null;
3619
- const content = firstNonEmptyString(
3620
- stringValue6(input.input?.content),
3621
- stringValue6(rawInput?.content),
3622
- stringValue6(input.input?.new_source),
3623
- stringValue6(input.output?.content),
3624
- stringValue6(input.output?.new_source)
3625
- ) ?? null;
3725
+ const oldString = firstPresentString6(
3726
+ literalStringValue(input.output?.oldString),
3727
+ literalStringValue(input.output?.old_string),
3728
+ literalStringValue(input.input?.oldString),
3729
+ literalStringValue(input.input?.old_string)
3730
+ );
3731
+ const newString = firstPresentString6(
3732
+ literalStringValue(input.output?.newString),
3733
+ literalStringValue(input.output?.new_string),
3734
+ literalStringValue(input.input?.newString),
3735
+ literalStringValue(input.input?.new_string)
3736
+ );
3737
+ const content = firstPresentString6(
3738
+ literalStringValue(input.input?.content),
3739
+ literalStringValue(rawInput?.content),
3740
+ literalStringValue(input.input?.new_source),
3741
+ literalStringValue(input.output?.content),
3742
+ literalStringValue(input.output?.new_source)
3743
+ );
3626
3744
  const explicitChangeType = normalizeChangeType(
3627
3745
  firstNonEmptyString(
3628
3746
  stringValue6(payload?.fileChangeKind),
@@ -3721,16 +3839,16 @@ function collectChangeMapFiles(messageId, toolName, occurredAtUnixMs, changes, o
3721
3839
  stringValue6(change.diff),
3722
3840
  stringValue6(change.patch)
3723
3841
  );
3724
- let oldString = firstNonEmptyString(
3725
- stringValue6(change.old_string),
3726
- stringValue6(change.oldString)
3727
- ) ?? null;
3728
- const explicitContent = stringValue6(change.content);
3729
- let newString = firstNonEmptyString(
3730
- stringValue6(change.new_string),
3731
- stringValue6(change.newString),
3842
+ let oldString = firstPresentString6(
3843
+ literalStringValue(change.old_string),
3844
+ literalStringValue(change.oldString)
3845
+ );
3846
+ const explicitContent = literalStringValue(change.content);
3847
+ let newString = firstPresentString6(
3848
+ literalStringValue(change.new_string),
3849
+ literalStringValue(change.newString),
3732
3850
  explicitContent
3733
- ) ?? null;
3851
+ );
3734
3852
  if (normalizedType === "created" && oldString === null && newString !== null) {
3735
3853
  oldString = "";
3736
3854
  }
@@ -3741,10 +3859,10 @@ function collectChangeMapFiles(messageId, toolName, occurredAtUnixMs, changes, o
3741
3859
  if (normalizedType === "deleted" && newString === null && oldString !== null) {
3742
3860
  newString = "";
3743
3861
  }
3744
- const content = firstNonEmptyString(
3862
+ const content = firstPresentString6(
3745
3863
  normalizedType === "deleted" ? null : explicitContent,
3746
3864
  normalizedType === "created" ? newString : null
3747
- ) ?? null;
3865
+ );
3748
3866
  if (!unifiedDiff && oldString === null && newString === null && content === null) {
3749
3867
  return [];
3750
3868
  }
@@ -3796,20 +3914,20 @@ function collectContentDiffFiles(messageId, toolName, occurredAtUnixMs, contentI
3796
3914
  stringValue6(relatedChange?.unified_diff),
3797
3915
  stringValue6(relatedChange?.unifiedDiff)
3798
3916
  );
3799
- let oldString = firstNonEmptyString(
3800
- stringValue6(item.oldText),
3801
- stringValue6(item.oldString),
3802
- stringValue6(relatedChange?.old_string),
3803
- stringValue6(relatedChange?.oldString)
3804
- ) ?? null;
3805
- const relatedContent = stringValue6(relatedChange?.content);
3806
- let newString = firstNonEmptyString(
3807
- stringValue6(item.newText),
3808
- stringValue6(item.newString),
3809
- stringValue6(relatedChange?.new_string),
3810
- stringValue6(relatedChange?.newString),
3917
+ let oldString = firstPresentString6(
3918
+ literalStringValue(item.oldText),
3919
+ literalStringValue(item.oldString),
3920
+ literalStringValue(relatedChange?.old_string),
3921
+ literalStringValue(relatedChange?.oldString)
3922
+ );
3923
+ const relatedContent = literalStringValue(relatedChange?.content);
3924
+ let newString = firstPresentString6(
3925
+ literalStringValue(item.newText),
3926
+ literalStringValue(item.newString),
3927
+ literalStringValue(relatedChange?.new_string),
3928
+ literalStringValue(relatedChange?.newString),
3811
3929
  relatedContent
3812
- ) ?? null;
3930
+ );
3813
3931
  if (normalizedType === "created" && oldString === null && newString !== null) {
3814
3932
  oldString = "";
3815
3933
  }
@@ -3820,7 +3938,10 @@ function collectContentDiffFiles(messageId, toolName, occurredAtUnixMs, contentI
3820
3938
  if (normalizedType === "deleted" && newString === null && oldString !== null) {
3821
3939
  newString = "";
3822
3940
  }
3823
- const explicitContent = firstNonEmptyString(stringValue6(item.content), relatedContent) ?? null;
3941
+ const explicitContent = firstPresentString6(
3942
+ literalStringValue(item.content),
3943
+ relatedContent
3944
+ );
3824
3945
  const inferredType = normalizedType ?? inferChangeTypeFromToolContent(
3825
3946
  toolName,
3826
3947
  normalizedType === "deleted" ? null : explicitContent,
@@ -3830,10 +3951,10 @@ function collectContentDiffFiles(messageId, toolName, occurredAtUnixMs, contentI
3830
3951
  if (inferredType === "created" && oldString === null && newString !== null) {
3831
3952
  oldString = "";
3832
3953
  }
3833
- const content = firstNonEmptyString(
3954
+ const content = firstPresentString6(
3834
3955
  inferredType === "deleted" ? null : explicitContent,
3835
3956
  inferredType === "created" ? newString : null
3836
- ) ?? null;
3957
+ );
3837
3958
  if (!unifiedDiff && oldString === null && newString === null && content === null) {
3838
3959
  return [];
3839
3960
  }
@@ -4036,7 +4157,7 @@ function normalizeChangeType(value) {
4036
4157
  function inferChangeTypeFromToolContent(toolName, content, oldString, newString) {
4037
4158
  const normalizedToolName = normalizeToolName4(toolName);
4038
4159
  if (normalizedToolName === "write" || normalizedToolName === "writefile") {
4039
- return content || newString ? "created" : null;
4160
+ return content !== null || newString !== null ? "created" : null;
4040
4161
  }
4041
4162
  if (oldString !== null || newString !== null) {
4042
4163
  return "modified";
@@ -4071,6 +4192,17 @@ function firstFileChangeValue(...values) {
4071
4192
  function arrayValue5(value) {
4072
4193
  return Array.isArray(value) ? value : null;
4073
4194
  }
4195
+ function nestedTaskStepsFromPayload(payload, output) {
4196
+ const metadata = objectValue5(payload?.metadata);
4197
+ return arrayValue5(metadata?.steps) ?? arrayValue5(output?.steps) ?? arrayValue5(payload?.steps) ?? [];
4198
+ }
4199
+ function nestedTaskStepStatusKind(step, fallback) {
4200
+ return firstNonEmptyString(
4201
+ stringValue6(step.statusKind),
4202
+ stringValue6(step.status),
4203
+ stringValue6(step.toolStatus)
4204
+ ) ?? fallback ?? null;
4205
+ }
4074
4206
  function firstLocationPath(value) {
4075
4207
  if (!value) {
4076
4208
  return null;
@@ -4099,6 +4231,9 @@ function firstPathValue(value) {
4099
4231
  function stringValue6(value) {
4100
4232
  return typeof value === "string" && value.trim() ? value.trim() : null;
4101
4233
  }
4234
+ function literalStringValue(value) {
4235
+ return typeof value === "string" ? value : null;
4236
+ }
4102
4237
  function numberValue2(value) {
4103
4238
  return typeof value === "number" && Number.isFinite(value) ? value : null;
4104
4239
  }
@@ -4110,6 +4245,14 @@ function firstNonEmptyString(...values) {
4110
4245
  }
4111
4246
  return null;
4112
4247
  }
4248
+ function firstPresentString6(...values) {
4249
+ for (const value of values) {
4250
+ if (typeof value === "string") {
4251
+ return value;
4252
+ }
4253
+ }
4254
+ return null;
4255
+ }
4113
4256
 
4114
4257
  // shared/agentConversation/projection/agentConversationProjection.ts
4115
4258
  var RENDER_IRRELEVANT_TRANSCRIPT_ROW_FIELDS = /* @__PURE__ */ new Set(["occurredAtUnixMs"]);
@@ -4886,6 +5029,186 @@ var Button = React.forwardRef(
4886
5029
  );
4887
5030
  Button.displayName = "Button";
4888
5031
 
5032
+ // shared/errors/appError.ts
5033
+ function createMessageMap() {
5034
+ return {
5035
+ "common.invalid_input": "The request was invalid.",
5036
+ "common.approved_path_required": "The selected path is outside approved workspaces.",
5037
+ "common.unavailable": "This feature is unavailable.",
5038
+ "common.unexpected": "Something went wrong. Please try again.",
5039
+ "session.not_found": "Session not found.",
5040
+ "control_surface.unauthorized": "Unauthorized request.",
5041
+ "workspace.select_directory_failed": "Unable to open the directory picker.",
5042
+ "workspace.select_files_failed": "Unable to open the file picker.",
5043
+ "workspace.ensure_directory_failed": "Unable to create the directory.",
5044
+ "workspace.import_files_failed": "Unable to import the selected files.",
5045
+ "workspace.read_file_failed": "Unable to load the selected file.",
5046
+ "workspace.write_file_failed": "Unable to save the selected file.",
5047
+ "workspace.export_file_failed": "Unable to export the selected file.",
5048
+ "workspace.copy_path_failed": "Unable to copy the path.",
5049
+ "workspace.host_unsupported": "This Mac cannot run the workspace environment required by tsh. Please use macOS 13 or later on Apple Silicon (or an Intel Mac that supports Apple Virtualization), and ensure virtualization is not blocked by MDM.",
5050
+ "workspace.runtime_artifact_unavailable": "Unable to prepare the workspace environment required by tsh. Check your network connection and try again.",
5051
+ "runtime.guest_agent_lane_unavailable": "The workspace environment connection is unavailable. Refresh and try again.",
5052
+ "workspace.room_full": "Room is full.",
5053
+ "workspace.room_delete_forbidden": "Only the space owner can delete this space.",
5054
+ "workspace.room_delete_not_found": "This space no longer exists.",
5055
+ "filesystem.create_directory_failed": "Unable to create the directory.",
5056
+ "filesystem.read_file_bytes_failed": "Unable to read the file.",
5057
+ "filesystem.read_file_text_failed": "Unable to read the file.",
5058
+ "filesystem.write_file_text_failed": "Unable to save the file.",
5059
+ "filesystem.copy_entry_failed": "Unable to copy the file or folder.",
5060
+ "filesystem.move_entry_failed": "Unable to move the file or folder.",
5061
+ "filesystem.rename_entry_failed": "Unable to rename the file or folder.",
5062
+ "filesystem.delete_entry_failed": "Unable to delete the file or folder.",
5063
+ "filesystem.read_directory_failed": "Unable to load the directory.",
5064
+ "filesystem.stat_failed": "Unable to read file details.",
5065
+ "terminal.spawn_failed": "Unable to start the terminal.",
5066
+ "terminal.write_failed": "Unable to write to the terminal.",
5067
+ "terminal.resize_failed": "Unable to resize the terminal.",
5068
+ "terminal.close_failed": "Unable to close the terminal.",
5069
+ "terminal.attach_failed": "Unable to attach the terminal session.",
5070
+ "terminal.detach_failed": "Unable to detach the terminal session.",
5071
+ "terminal.snapshot_failed": "Unable to read terminal output.",
5072
+ "agent.list_models_failed": "Unable to load models for this provider.",
5073
+ "agent.launch_failed": "Unable to start the agent.",
5074
+ "agent.read_last_message_failed": "Unable to read the last agent message.",
5075
+ "agent.provider_session_not_found": "The previous agent session can no longer be restored.",
5076
+ "agent.resume_session_not_local": "The previous agent session is not available on this machine.",
5077
+ "agent.resume_session_resolve_failed": "Unable to resolve the previous agent session.",
5078
+ "agent.settings_require_new_session": "This model can only be used in a new session to preserve context.",
5079
+ "task.suggest_title_failed": "Unable to generate task details.",
5080
+ "persistence.unavailable": "Storage is unavailable; changes will not be saved.",
5081
+ "persistence.quota_exceeded": "Storage quota was exceeded.",
5082
+ "persistence.payload_too_large": "Workspace state is too large to save.",
5083
+ "persistence.io_failed": "Unable to save data to storage.",
5084
+ "persistence.invalid_state": "The workspace state could not be saved.",
5085
+ "persistence.invalid_node_id": "The terminal history could not be saved.",
5086
+ "update.get_state_failed": "Unable to read the update status.",
5087
+ "update.configure_failed": "Unable to apply update settings.",
5088
+ "update.check_failed": "Unable to check for updates.",
5089
+ "update.download_failed": "Unable to download the update.",
5090
+ "update.install_failed": "Unable to install the update.",
5091
+ PACKAGE_DOWNLOAD_INTERRUPTED: "The agent package download was interrupted. Check your network connection and retry.",
5092
+ PACKAGE_DOWNLOAD_HTTP_STATUS: "The agent package server rejected the download. Retry later or contact support.",
5093
+ PACKAGE_DOWNLOAD_INVALID: "The downloaded agent package failed integrity checks. Retry the download.",
5094
+ PACKAGE_DOWNLOAD_DISK_ERROR: "Unable to write the agent package cache. Check disk permissions and free space."
5095
+ };
5096
+ }
5097
+ var APP_ERROR_MESSAGES = createMessageMap();
5098
+ function createAppErrorDescriptor(code, options = {}) {
5099
+ return {
5100
+ code,
5101
+ ...options.params ? { params: options.params } : {},
5102
+ ...options.debugMessage ? { debugMessage: options.debugMessage } : {}
5103
+ };
5104
+ }
5105
+ function isAppErrorDescriptor(value) {
5106
+ if (!value || typeof value !== "object") {
5107
+ return false;
5108
+ }
5109
+ const record = value;
5110
+ return typeof record.code === "string" && record.code in APP_ERROR_MESSAGES;
5111
+ }
5112
+ function getAppErrorCode(error) {
5113
+ if (error instanceof TshAppError) {
5114
+ return error.code;
5115
+ }
5116
+ if (isAppErrorDescriptor(error)) {
5117
+ return error.code;
5118
+ }
5119
+ if (error && typeof error === "object") {
5120
+ const code = error.code;
5121
+ if (typeof code === "string" && code in APP_ERROR_MESSAGES) {
5122
+ return code;
5123
+ }
5124
+ }
5125
+ return null;
5126
+ }
5127
+ var TshAppError = class extends Error {
5128
+ code;
5129
+ params;
5130
+ debugMessage;
5131
+ constructor(descriptor) {
5132
+ super(formatAppErrorMessage(descriptor));
5133
+ this.name = "TshAppError";
5134
+ this.code = descriptor.code;
5135
+ this.params = descriptor.params;
5136
+ this.debugMessage = descriptor.debugMessage;
5137
+ }
5138
+ toDescriptor() {
5139
+ return createAppErrorDescriptor(this.code, {
5140
+ params: this.params,
5141
+ debugMessage: this.debugMessage
5142
+ });
5143
+ }
5144
+ };
5145
+ function formatAppErrorMessage(error) {
5146
+ const descriptor = error instanceof TshAppError ? error.toDescriptor() : error;
5147
+ return APP_ERROR_MESSAGES[descriptor.code] ?? APP_ERROR_MESSAGES["common.unexpected"];
5148
+ }
5149
+
5150
+ // app/renderer/shell/utils/format.ts
5151
+ var shortDateTimeFormatterByLanguage = /* @__PURE__ */ new Map();
5152
+ var weekdayTimeFormatterByLanguage = /* @__PURE__ */ new Map();
5153
+ function getShortDateTimeFormatter(language) {
5154
+ const cached = shortDateTimeFormatterByLanguage.get(language);
5155
+ if (cached) {
5156
+ return cached;
5157
+ }
5158
+ const formatter = new Intl.DateTimeFormat(language, {
5159
+ month: language === "en" ? "short" : "numeric",
5160
+ day: "numeric",
5161
+ hour: "2-digit",
5162
+ minute: "2-digit",
5163
+ hourCycle: "h23"
5164
+ });
5165
+ shortDateTimeFormatterByLanguage.set(language, formatter);
5166
+ return formatter;
5167
+ }
5168
+ function getWeekdayTimeFormatter(language) {
5169
+ const cached = weekdayTimeFormatterByLanguage.get(language);
5170
+ if (cached) {
5171
+ return cached;
5172
+ }
5173
+ const formatter = new Intl.DateTimeFormat(language, {
5174
+ weekday: "long",
5175
+ hour: "2-digit",
5176
+ minute: "2-digit",
5177
+ hourCycle: "h23"
5178
+ });
5179
+ weekdayTimeFormatterByLanguage.set(language, formatter);
5180
+ return formatter;
5181
+ }
5182
+ function toLocalWeekdayTime(value, language = getActiveUiLanguage()) {
5183
+ const formatter = getWeekdayTimeFormatter(language);
5184
+ const parts = formatter.formatToParts(value);
5185
+ const weekday = parts.find((part) => part.type === "weekday")?.value;
5186
+ const hour = parts.find((part) => part.type === "hour")?.value;
5187
+ const minute = parts.find((part) => part.type === "minute")?.value;
5188
+ if (weekday && hour && minute) {
5189
+ return `${weekday} ${hour}:${minute}`;
5190
+ }
5191
+ return formatter.format(value);
5192
+ }
5193
+ function toLocalShortDateTime(value, language = getActiveUiLanguage()) {
5194
+ return getShortDateTimeFormatter(language).format(value);
5195
+ }
5196
+ function formatAgentMessageTimestamp(unix, language = getActiveUiLanguage(), now = /* @__PURE__ */ new Date()) {
5197
+ if (unix == null || !Number.isFinite(unix)) {
5198
+ return null;
5199
+ }
5200
+ const value = unix > 1e12 ? unix : unix * 1e3;
5201
+ const messageDate = new Date(value);
5202
+ const startOfWeek = new Date(now);
5203
+ const daysSinceMonday = (startOfWeek.getDay() + 6) % 7;
5204
+ startOfWeek.setHours(0, 0, 0, 0);
5205
+ startOfWeek.setDate(startOfWeek.getDate() - daysSinceMonday);
5206
+ if (messageDate.getTime() >= startOfWeek.getTime()) {
5207
+ return toLocalWeekdayTime(value, language);
5208
+ }
5209
+ return toLocalShortDateTime(value, language);
5210
+ }
5211
+
4889
5212
  // shared/agentConversation/components/AgentPlanCard.tsx
4890
5213
  import { useCallback, useRef, useState } from "react";
4891
5214
  import { Check, ChevronDown, ChevronUp, Copy, ListChecks } from "lucide-react";
@@ -5284,6 +5607,9 @@ function createEmptyDocument() {
5284
5607
  content: [{ type: "paragraph" }]
5285
5608
  };
5286
5609
  }
5610
+ function isVisualLineStart(content) {
5611
+ return content.length === 0 || content.at(-1)?.type === "hardBreak";
5612
+ }
5287
5613
  function createParagraphFromText(text, options = {}) {
5288
5614
  const content = [];
5289
5615
  let index = 0;
@@ -5306,6 +5632,9 @@ function createParagraphFromText(text, options = {}) {
5306
5632
  const parsedMention = parseAgentMentionMarkdown(text, index);
5307
5633
  if (parsedMention) {
5308
5634
  flushTextBuffer();
5635
+ if (isVisualLineStart(content)) {
5636
+ content.push({ type: "text", text: AGENT_RICH_TEXT_CARET_ANCHOR });
5637
+ }
5309
5638
  content.push({
5310
5639
  type: "agentFileMention",
5311
5640
  // 转成规范 node attrs(如 workspace-reference 的 source/groupId/fileCount),
@@ -5383,7 +5712,7 @@ function isBlockPromptNode(node) {
5383
5712
  }
5384
5713
  function nodeToPromptText(node) {
5385
5714
  if (node.type === "text") {
5386
- return node.text ?? "";
5715
+ return (node.text ?? "").replaceAll(AGENT_RICH_TEXT_CARET_ANCHOR, "");
5387
5716
  }
5388
5717
  if (node.type === "agentFileMention") {
5389
5718
  return formatAgentMentionMarkdown(attrsToMentionItem(node.attrs ?? {}));
@@ -5603,7 +5932,9 @@ function isMentionOnlyRichTextDoc(doc) {
5603
5932
  if (paragraph?.type !== "paragraph") {
5604
5933
  return false;
5605
5934
  }
5606
- const inlineContent = paragraph.content ?? [];
5935
+ const inlineContent = (paragraph.content ?? []).filter(
5936
+ (node) => !(node.type === "text" && (node.text ?? "").replaceAll(AGENT_RICH_TEXT_CARET_ANCHOR, "").length === 0)
5937
+ );
5607
5938
  return inlineContent.length === 1 && inlineContent[0]?.type === "agentFileMention";
5608
5939
  }
5609
5940
  function plainTextToAgentRichTextDocWithWorkspaceAppIcons(value, availableSkills, workspaceAppIcons) {
@@ -6799,6 +7130,7 @@ function AgentMessageBlock({
6799
7130
  AgentCopyableMessageGroup,
6800
7131
  {
6801
7132
  copyText: message.copyText ?? null,
7133
+ occurredAtUnixMs: message.occurredAtUnixMs,
6802
7134
  speaker: row.speaker,
6803
7135
  onCopyMessageText: handleCopyMessageText,
6804
7136
  children: [
@@ -6815,6 +7147,7 @@ function AgentMessageBlock({
6815
7147
  AgentCopyableMessageGroup,
6816
7148
  {
6817
7149
  copyText,
7150
+ occurredAtUnixMs: message.occurredAtUnixMs,
6818
7151
  speaker: row.speaker,
6819
7152
  onCopyMessageText: handleCopyMessageText,
6820
7153
  children: content
@@ -6831,19 +7164,24 @@ function AgentMessageBlock({
6831
7164
  function AgentCopyableMessageGroup({
6832
7165
  children,
6833
7166
  copyText,
7167
+ occurredAtUnixMs,
6834
7168
  onCopyMessageText,
6835
7169
  speaker
6836
7170
  }) {
6837
7171
  "use memo";
7172
+ const timestamp = formatAgentMessageTimestamp(occurredAtUnixMs);
6838
7173
  return /* @__PURE__ */ jsxs7("div", { className: AgentGUIConversation_styles_default.messageGroup, "data-agent-message-speaker": speaker, children: [
6839
7174
  children,
6840
- copyText ? /* @__PURE__ */ jsx12(
6841
- AgentMessageCopyButton,
6842
- {
6843
- copyText,
6844
- onCopyMessageText
6845
- }
6846
- ) : null
7175
+ timestamp || copyText ? /* @__PURE__ */ jsxs7("div", { className: AgentGUIConversation_styles_default.messageFooter, children: [
7176
+ timestamp ? /* @__PURE__ */ jsx12("span", { className: AgentGUIConversation_styles_default.messageTimestamp, children: timestamp }) : null,
7177
+ copyText ? /* @__PURE__ */ jsx12(
7178
+ AgentMessageCopyButton,
7179
+ {
7180
+ copyText,
7181
+ onCopyMessageText
7182
+ }
7183
+ ) : null
7184
+ ] }) : null
6847
7185
  ] });
6848
7186
  }
6849
7187
  function AgentMessageCopyButton({
@@ -11913,8 +12251,21 @@ function renderToolCard(call, onLinkClick, previewMode = false, showRawTimelineJ
11913
12251
  }
11914
12252
 
11915
12253
  // shared/agentConversation/components/AgentTurnSummaryRow.tsx
11916
- import { useMemo as useMemo5, useState as useState16 } from "react";
11917
- import { ChevronDown as ChevronDown7, ChevronRight as ChevronRight7 } from "lucide-react";
12254
+ import { useEffect as useEffect10, useMemo as useMemo5, useState as useState16 } from "react";
12255
+ import {
12256
+ ChevronDown as ChevronDown7,
12257
+ ChevronRight as ChevronRight7,
12258
+ LoaderCircle as LoaderCircle2,
12259
+ Redo2,
12260
+ Undo2
12261
+ } from "lucide-react";
12262
+ import {
12263
+ Tooltip,
12264
+ TooltipContent,
12265
+ TooltipProvider,
12266
+ TooltipTrigger,
12267
+ toast
12268
+ } from "@tutti-os/ui-system";
11918
12269
 
11919
12270
  // app/renderer/components/icons/DirectLinedIcon.tsx
11920
12271
  import { jsx as jsx53 } from "react/jsx-runtime";
@@ -11941,6 +12292,195 @@ function DirectLinedIcon(props) {
11941
12292
  );
11942
12293
  }
11943
12294
 
12295
+ // shared/agentConversation/rules/agentTurnSummaryPatchDiff.ts
12296
+ function buildAgentTurnSummaryPatchDiff(batch) {
12297
+ return batch.changes.map((change) => patchChangeToUnifiedDiff(change, batch.cwd)).filter((diff) => diff.trim().length > 0).join("\n");
12298
+ }
12299
+ function patchChangeToUnifiedDiff(change, cwd) {
12300
+ const path = patchPathRelativeToCwd(change.path, cwd);
12301
+ const rawDiff = normalizeAgentPatchText(change.unifiedDiff ?? "").trim();
12302
+ if (rawDiff && looksLikeUnifiedDiff(rawDiff)) {
12303
+ return ensureTrailingNewline(
12304
+ wrapUnifiedDiff(path, change.changeType, rawDiff)
12305
+ );
12306
+ }
12307
+ if (change.changeType === "created") {
12308
+ return fileContentPatch(
12309
+ path,
12310
+ "created",
12311
+ change.content ?? change.newString ?? rawDiff
12312
+ );
12313
+ }
12314
+ if (change.changeType === "deleted") {
12315
+ return fileContentPatch(
12316
+ path,
12317
+ "deleted",
12318
+ change.oldString ?? change.content ?? rawDiff
12319
+ );
12320
+ }
12321
+ if (change.oldString != null || change.newString != null) {
12322
+ return modifiedFilePatch(
12323
+ path,
12324
+ change.oldString ?? "",
12325
+ change.newString ?? ""
12326
+ );
12327
+ }
12328
+ return "";
12329
+ }
12330
+ function wrapUnifiedDiff(path, changeType, diff) {
12331
+ if (diff.startsWith("diff --git ")) {
12332
+ return diff;
12333
+ }
12334
+ const headers = [`diff --git a/${path} b/${path}`];
12335
+ if (changeType === "created") {
12336
+ headers.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
12337
+ } else if (changeType === "deleted") {
12338
+ headers.push("deleted file mode 100644", `--- a/${path}`, "+++ /dev/null");
12339
+ } else if (!diff.startsWith("--- ") && !diff.includes("\n--- ")) {
12340
+ headers.push(`--- a/${path}`, `+++ b/${path}`);
12341
+ }
12342
+ return [...headers, diff].join("\n");
12343
+ }
12344
+ function fileContentPatch(path, changeType, content) {
12345
+ const lines = splitPatchContentLines(content);
12346
+ const count = Math.max(lines.length, 1);
12347
+ const prefix = changeType === "created" ? "+" : "-";
12348
+ const body = lines.length > 0 ? lines.map((line) => `${prefix}${line}`) : [`${prefix}`];
12349
+ const header = changeType === "created" ? [
12350
+ `diff --git a/${path} b/${path}`,
12351
+ "new file mode 100644",
12352
+ "--- /dev/null",
12353
+ `+++ b/${path}`,
12354
+ `@@ -0,0 +1,${count} @@`
12355
+ ] : [
12356
+ `diff --git a/${path} b/${path}`,
12357
+ "deleted file mode 100644",
12358
+ `--- a/${path}`,
12359
+ "+++ /dev/null",
12360
+ `@@ -1,${count} +0,0 @@`
12361
+ ];
12362
+ return ensureTrailingNewline([...header, ...body].join("\n"));
12363
+ }
12364
+ function modifiedFilePatch(path, oldContent, newContent) {
12365
+ const oldLines = splitPatchContentLines(oldContent);
12366
+ const newLines = splitPatchContentLines(newContent);
12367
+ const oldCount = Math.max(oldLines.length, 1);
12368
+ const newCount = Math.max(newLines.length, 1);
12369
+ return ensureTrailingNewline(
12370
+ [
12371
+ `diff --git a/${path} b/${path}`,
12372
+ `--- a/${path}`,
12373
+ `+++ b/${path}`,
12374
+ `@@ -1,${oldCount} +1,${newCount} @@`,
12375
+ ...oldLines.map((line) => `-${line}`),
12376
+ ...newLines.map((line) => `+${line}`)
12377
+ ].join("\n")
12378
+ );
12379
+ }
12380
+ function patchPathRelativeToCwd(path, cwd) {
12381
+ const normalizedPath = normalizePathForPatch(path);
12382
+ const normalizedCwd = normalizePathForPatch(cwd ?? "");
12383
+ if (normalizedPath.startsWith("/") && normalizedCwd && normalizedPath.startsWith(`${normalizedCwd}/`)) {
12384
+ return normalizedPath.slice(normalizedCwd.length + 1);
12385
+ }
12386
+ return normalizedPath.replace(/^\/+/, "");
12387
+ }
12388
+ function normalizePathForPatch(path) {
12389
+ return path.trim().replaceAll("\\", "/").replace(/\/+$/, "");
12390
+ }
12391
+ function looksLikeUnifiedDiff(value) {
12392
+ return value.startsWith("diff --git ") || value.startsWith("@@ ") || value.startsWith("--- ") || value.includes("\n@@ ");
12393
+ }
12394
+ function splitPatchContentLines(content) {
12395
+ if (!content) {
12396
+ return [];
12397
+ }
12398
+ return content.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n");
12399
+ }
12400
+ function ensureTrailingNewline(value) {
12401
+ return value.endsWith("\n") ? value : `${value}
12402
+ `;
12403
+ }
12404
+
12405
+ // shared/agentConversation/rules/agentTurnSummaryPatchRuntime.ts
12406
+ function fileCanBuildPatch(file) {
12407
+ return file.unifiedDiff != null || file.content != null || file.oldString != null || file.newString != null;
12408
+ }
12409
+ function patchBatchDirectoryCwd(cwd, changes) {
12410
+ const normalizedCwd = normalizePatchHostPath(cwd ?? "");
12411
+ if (!normalizedCwd) {
12412
+ return null;
12413
+ }
12414
+ const cwdIsChangedFilePath = changes.some(
12415
+ (change) => normalizePatchHostPath(change.path) === normalizedCwd
12416
+ );
12417
+ return cwdIsChangedFilePath ? dirnameForPatchHostPath(normalizedCwd) : cwd;
12418
+ }
12419
+ function resolvePatchExecutionCwd(cwd, workspaceRoot) {
12420
+ const normalizedCwd = normalizePatchHostPath(cwd ?? "");
12421
+ const normalizedRoot = normalizePatchHostPath(workspaceRoot ?? "");
12422
+ if (!normalizedCwd) {
12423
+ return normalizedRoot || null;
12424
+ }
12425
+ if (!normalizedRoot) {
12426
+ return normalizedCwd;
12427
+ }
12428
+ if (isSyntheticWorkspacePath(normalizedRoot)) {
12429
+ return normalizedCwd;
12430
+ }
12431
+ if (normalizedCwd === "/workspace") {
12432
+ return normalizedRoot;
12433
+ }
12434
+ if (normalizedCwd.startsWith("/workspace/")) {
12435
+ return `${normalizedRoot}/${normalizedCwd.slice("/workspace/".length)}`;
12436
+ }
12437
+ return normalizedCwd;
12438
+ }
12439
+ function resolvePatchDiffCwd({
12440
+ sourceCwd,
12441
+ executionCwd,
12442
+ changes
12443
+ }) {
12444
+ const normalizedSourceCwd = normalizePatchHostPath(sourceCwd ?? "");
12445
+ const normalizedExecutionCwd = normalizePatchHostPath(executionCwd ?? "");
12446
+ if (!isSyntheticWorkspacePath(normalizedSourceCwd) || !normalizedExecutionCwd) {
12447
+ return sourceCwd;
12448
+ }
12449
+ const hasSyntheticChangePath = changes.some(
12450
+ (change) => isInsideOrEqualPatchPath(
12451
+ normalizePatchHostPath(change.path),
12452
+ normalizedSourceCwd
12453
+ )
12454
+ );
12455
+ const hasHostChangePath = changes.some(
12456
+ (change) => isInsideOrEqualPatchPath(
12457
+ normalizePatchHostPath(change.path),
12458
+ normalizedExecutionCwd
12459
+ )
12460
+ );
12461
+ return hasHostChangePath && !hasSyntheticChangePath ? normalizedExecutionCwd : sourceCwd;
12462
+ }
12463
+ function normalizePatchHostPath(path) {
12464
+ return path.trim().replaceAll("\\", "/").replace(/\/+$/, "");
12465
+ }
12466
+ function dirnameForPatchHostPath(path) {
12467
+ const normalized = normalizePatchHostPath(path);
12468
+ const index = normalized.lastIndexOf("/");
12469
+ if (index < 0) {
12470
+ return "";
12471
+ }
12472
+ if (index === 0) {
12473
+ return "/";
12474
+ }
12475
+ return normalized.slice(0, index);
12476
+ }
12477
+ function isSyntheticWorkspacePath(path) {
12478
+ return path === "/workspace" || path.startsWith("/workspace/");
12479
+ }
12480
+ function isInsideOrEqualPatchPath(path, root) {
12481
+ return path === root || path.startsWith(`${root}/`);
12482
+ }
12483
+
11944
12484
  // shared/agentConversation/components/AgentTurnSummaryRow.tsx
11945
12485
  import { jsx as jsx54, jsxs as jsxs41 } from "react/jsx-runtime";
11946
12486
  function AgentTurnSummaryRow({
@@ -11953,29 +12493,201 @@ function AgentTurnSummaryRow({
11953
12493
  {}
11954
12494
  );
11955
12495
  const [showAllFiles, setShowAllFiles] = useState16(false);
12496
+ const [patchAction, setPatchAction] = useState16("undo");
12497
+ const [patchPending, setPatchPending] = useState16(false);
12498
+ const [patchSupportState, setPatchSupportState] = useState16(null);
12499
+ const agentHostApi = useOptionalAgentHostApi();
11956
12500
  const aggregateStats = useMemo5(
11957
12501
  () => summarizeRowDiff(row.files),
11958
12502
  [row.files]
11959
12503
  );
12504
+ const patchBatches = useMemo5(() => {
12505
+ const fileFallbackBatches = row.files.flatMap(
12506
+ (file) => fileCanBuildPatch(file) ? [
12507
+ {
12508
+ changes: [
12509
+ {
12510
+ path: file.path,
12511
+ changeType: file.changeType,
12512
+ unifiedDiff: file.unifiedDiff,
12513
+ oldString: file.oldString ?? null,
12514
+ newString: file.newString ?? null,
12515
+ content: file.content ?? null
12516
+ }
12517
+ ],
12518
+ cwd: fallbackPatchFileCwd(file.path, workspaceRoot),
12519
+ toolCallId: `${row.id}:${file.messageId}:unified-diff`
12520
+ }
12521
+ ] : []
12522
+ );
12523
+ const sourceBatches = row.patchBatches && row.patchBatches.length > 0 ? row.patchBatches : fileFallbackBatches;
12524
+ const batches = buildExecutablePatchBatches(sourceBatches, workspaceRoot);
12525
+ if (batches.length > 0 || sourceBatches === fileFallbackBatches) {
12526
+ return batches;
12527
+ }
12528
+ return buildExecutablePatchBatches(fileFallbackBatches, workspaceRoot);
12529
+ }, [row.files, row.id, row.patchBatches, workspaceRoot]);
12530
+ const canRenderPatchButton = Boolean(
12531
+ agentHostApi?.workspace.applyGitPatch && row.files.length > 0
12532
+ );
12533
+ const canApplyPatch = Boolean(
12534
+ agentHostApi?.workspace.applyGitPatch && patchBatches.length > 0
12535
+ );
12536
+ const patchSupportCwds = useMemo5(
12537
+ () => Array.from(new Set(patchBatches.map((batch) => batch.cwd))).sort(),
12538
+ [patchBatches]
12539
+ );
12540
+ const patchSupportKey = patchSupportCwds.join("\n");
12541
+ const resolveGitPatchSupport = agentHostApi?.workspace.resolveGitPatchSupport;
12542
+ useEffect10(() => {
12543
+ if (!resolveGitPatchSupport || patchSupportCwds.length === 0) {
12544
+ setPatchSupportState(null);
12545
+ return;
12546
+ }
12547
+ let disposed = false;
12548
+ setPatchSupportState({ key: patchSupportKey, status: "checking" });
12549
+ void (async () => {
12550
+ try {
12551
+ for (const cwd of patchSupportCwds) {
12552
+ const result = await resolveGitPatchSupport({ cwd });
12553
+ if (!result.supported) {
12554
+ if (!disposed) {
12555
+ setPatchSupportState({
12556
+ key: patchSupportKey,
12557
+ status: "unsupported"
12558
+ });
12559
+ }
12560
+ return;
12561
+ }
12562
+ }
12563
+ if (!disposed) {
12564
+ setPatchSupportState({ key: patchSupportKey, status: "supported" });
12565
+ }
12566
+ } catch {
12567
+ if (!disposed) {
12568
+ setPatchSupportState({ key: patchSupportKey, status: "supported" });
12569
+ }
12570
+ }
12571
+ })();
12572
+ return () => {
12573
+ disposed = true;
12574
+ };
12575
+ }, [patchSupportCwds, patchSupportKey, resolveGitPatchSupport]);
11960
12576
  const visibleFiles = row.files.slice(0, 3);
11961
12577
  const hiddenFiles = row.files.slice(3);
11962
12578
  const hiddenFileCount = hiddenFiles.length;
12579
+ const patchActionLabel = patchAction === "undo" ? translate("agentHost.agentGui.turnSummaryUndo") : translate("agentHost.agentGui.turnSummaryReapply");
12580
+ const isPatchSupportChecking = Boolean(
12581
+ resolveGitPatchSupport && patchSupportCwds.length > 0 && (patchSupportState?.key !== patchSupportKey || patchSupportState.status === "checking")
12582
+ );
12583
+ const isPatchUnsupported = Boolean(
12584
+ patchSupportState?.key === patchSupportKey && patchSupportState.status === "unsupported"
12585
+ );
12586
+ const patchDisabledReason = isPatchUnsupported ? translate("agentHost.agentGui.turnSummaryGitRequired") : isPatchSupportChecking ? translate("agentHost.agentGui.turnSummaryCheckingGit") : !canApplyPatch ? translate("agentHost.agentGui.turnSummaryPatchUnavailable") : null;
12587
+ const isPatchActionDisabled = Boolean(
12588
+ patchPending || isPatchSupportChecking || isPatchUnsupported || !canApplyPatch
12589
+ );
12590
+ const handlePatchAction = async (event) => {
12591
+ event.stopPropagation();
12592
+ if (!agentHostApi?.workspace.applyGitPatch || isPatchActionDisabled) {
12593
+ return;
12594
+ }
12595
+ const orderedBatches = patchAction === "undo" ? [...patchBatches].reverse() : patchBatches;
12596
+ if (orderedBatches.length === 0) {
12597
+ return;
12598
+ }
12599
+ setPatchPending(true);
12600
+ let changed = false;
12601
+ const failureMessage = patchAction === "undo" ? translate("agentHost.agentGui.turnSummaryUndoFailed") : translate("agentHost.agentGui.turnSummaryReapplyFailed");
12602
+ try {
12603
+ for (const batch of orderedBatches) {
12604
+ const result = await agentHostApi.workspace.applyGitPatch({
12605
+ cwd: batch.cwd,
12606
+ diff: batch.diff,
12607
+ revert: patchAction === "undo"
12608
+ });
12609
+ if (result.status === "success" || result.appliedPaths.length > 0 || result.conflictedPaths.length > 0) {
12610
+ changed = true;
12611
+ }
12612
+ if (result.status !== "success") {
12613
+ showPatchFailureToast(agentHostApi, failureMessage);
12614
+ return;
12615
+ }
12616
+ }
12617
+ setPatchAction((current) => current === "undo" ? "reapply" : "undo");
12618
+ } catch {
12619
+ showPatchFailureToast(agentHostApi, failureMessage);
12620
+ } finally {
12621
+ setPatchPending(false);
12622
+ if (changed) {
12623
+ window.dispatchEvent(new CustomEvent("tutti-agent-git-patch-applied"));
12624
+ }
12625
+ }
12626
+ };
11963
12627
  return /* @__PURE__ */ jsx54("section", { className: "workspace-agents-status-panel__detail-turn-summary", children: /* @__PURE__ */ jsxs41("div", { className: "agent-turn-summary-card w-full overflow-hidden rounded-[8px] text-[var(--text-primary)]", children: [
11964
- /* @__PURE__ */ jsx54("div", { className: "flex items-start gap-3 px-4 py-3", children: /* @__PURE__ */ jsx54("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxs41("div", { className: "flex min-w-0 items-center gap-3", children: [
11965
- /* @__PURE__ */ jsx54("div", { className: "min-w-0 text-[15px] font-semibold leading-5 tracking-[0] text-[var(--text-primary)]", children: translate("agentHost.agentGui.turnSummaryFilesChanged", {
11966
- count: row.fileCount
11967
- }) }),
11968
- /* @__PURE__ */ jsxs41("div", { className: "inline-flex shrink-0 items-center gap-2.5 text-[11px] font-semibold", children: [
11969
- aggregateStats.added > 0 ? /* @__PURE__ */ jsxs41("span", { className: "workspace-agents-status-panel__detail-tool-diff-added", children: [
11970
- "+",
11971
- aggregateStats.added
11972
- ] }) : null,
11973
- aggregateStats.removed > 0 ? /* @__PURE__ */ jsxs41("span", { className: "workspace-agents-status-panel__detail-tool-diff-removed", children: [
11974
- "-",
11975
- aggregateStats.removed
11976
- ] }) : null
11977
- ] })
11978
- ] }) }) }),
12628
+ /* @__PURE__ */ jsxs41("div", { className: "flex items-start gap-3 px-4 py-3", children: [
12629
+ /* @__PURE__ */ jsx54("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxs41("div", { className: "flex min-w-0 items-center gap-3", children: [
12630
+ /* @__PURE__ */ jsx54("div", { className: "min-w-0 text-[15px] font-semibold leading-5 tracking-[0] text-[var(--text-primary)]", children: translate("agentHost.agentGui.turnSummaryFilesChanged", {
12631
+ count: row.fileCount
12632
+ }) }),
12633
+ /* @__PURE__ */ jsxs41("div", { className: "inline-flex shrink-0 items-center gap-2.5 text-[11px] font-semibold", children: [
12634
+ aggregateStats.added > 0 ? /* @__PURE__ */ jsxs41("span", { className: "workspace-agents-status-panel__detail-tool-diff-added", children: [
12635
+ "+",
12636
+ aggregateStats.added
12637
+ ] }) : null,
12638
+ aggregateStats.removed > 0 ? /* @__PURE__ */ jsxs41("span", { className: "workspace-agents-status-panel__detail-tool-diff-removed", children: [
12639
+ "-",
12640
+ aggregateStats.removed
12641
+ ] }) : null
12642
+ ] })
12643
+ ] }) }),
12644
+ canRenderPatchButton ? /* @__PURE__ */ jsx54(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs41(Tooltip, { children: [
12645
+ /* @__PURE__ */ jsx54(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx54(
12646
+ "span",
12647
+ {
12648
+ className: `inline-flex shrink-0 ${isPatchActionDisabled ? "cursor-not-allowed" : ""}`,
12649
+ children: /* @__PURE__ */ jsxs41(
12650
+ CanvasNodeGhostIconButton,
12651
+ {
12652
+ "aria-label": patchActionLabel,
12653
+ className: "w-auto min-w-0 gap-1.5 px-2 text-[12px] font-medium leading-4 disabled:pointer-events-none",
12654
+ disabled: isPatchActionDisabled,
12655
+ onClick: handlePatchAction,
12656
+ children: [
12657
+ patchPending || isPatchSupportChecking ? /* @__PURE__ */ jsx54(
12658
+ LoaderCircle2,
12659
+ {
12660
+ width: 14,
12661
+ height: 14,
12662
+ "aria-hidden": "true",
12663
+ className: "animate-spin text-[var(--text-secondary)]"
12664
+ }
12665
+ ) : patchAction === "undo" ? /* @__PURE__ */ jsx54(
12666
+ Undo2,
12667
+ {
12668
+ width: 14,
12669
+ height: 14,
12670
+ "aria-hidden": "true",
12671
+ className: "text-[var(--text-secondary)]"
12672
+ }
12673
+ ) : /* @__PURE__ */ jsx54(
12674
+ Redo2,
12675
+ {
12676
+ width: 14,
12677
+ height: 14,
12678
+ "aria-hidden": "true",
12679
+ className: "text-[var(--text-secondary)]"
12680
+ }
12681
+ ),
12682
+ /* @__PURE__ */ jsx54("span", { children: patchActionLabel })
12683
+ ]
12684
+ }
12685
+ )
12686
+ }
12687
+ ) }),
12688
+ patchDisabledReason ? /* @__PURE__ */ jsx54(TooltipContent, { className: "max-w-[260px] whitespace-normal text-left", children: patchDisabledReason }) : null
12689
+ ] }) }) : null
12690
+ ] }),
11979
12691
  /* @__PURE__ */ jsxs41("div", { className: "agent-turn-summary-card__list", children: [
11980
12692
  visibleFiles.map((file) => {
11981
12693
  const key = `${file.path}:${file.messageId}`;
@@ -12206,6 +12918,47 @@ function summarizeRowDiff(files) {
12206
12918
  { added: 0, removed: 0 }
12207
12919
  );
12208
12920
  }
12921
+ function showPatchFailureToast(agentHostApi, message) {
12922
+ if (agentHostApi?.toast?.error) {
12923
+ agentHostApi.toast.error(message);
12924
+ return;
12925
+ }
12926
+ toast.error(message);
12927
+ }
12928
+ function buildExecutablePatchBatches(sourceBatches, workspaceRoot) {
12929
+ return sourceBatches.flatMap((batch) => {
12930
+ const sourceCwd = patchBatchDirectoryCwd(
12931
+ batch.cwd ?? workspaceRoot ?? null,
12932
+ batch.changes
12933
+ );
12934
+ const executionCwd = resolvePatchExecutionCwd(sourceCwd, workspaceRoot);
12935
+ const diffCwd = resolvePatchDiffCwd({
12936
+ executionCwd,
12937
+ sourceCwd,
12938
+ changes: batch.changes
12939
+ });
12940
+ const diff = buildAgentTurnSummaryPatchDiff({
12941
+ ...batch,
12942
+ cwd: diffCwd
12943
+ });
12944
+ return executionCwd && diff.trim() ? [{ cwd: executionCwd, diff }] : [];
12945
+ });
12946
+ }
12947
+ function fallbackPatchFileCwd(path, workspaceRoot) {
12948
+ const root = workspaceRoot?.trim() ?? "";
12949
+ if (root && root !== "/") {
12950
+ return root;
12951
+ }
12952
+ const normalizedPath = path.trim().replaceAll("\\", "/").replace(/\/+$/, "");
12953
+ if (!normalizedPath.startsWith("/")) {
12954
+ return root || null;
12955
+ }
12956
+ const index = normalizedPath.lastIndexOf("/");
12957
+ if (index <= 0) {
12958
+ return "/";
12959
+ }
12960
+ return normalizedPath.slice(0, index);
12961
+ }
12209
12962
  function summarizeFileDiff(file) {
12210
12963
  if (file.unifiedDiff?.trim()) {
12211
12964
  return parseAgentUnifiedDiffStats(file.unifiedDiff);
@@ -12736,6 +13489,8 @@ function useProjectedAgentConversation({
12736
13489
  }
12737
13490
 
12738
13491
  export {
13492
+ getAppErrorCode,
13493
+ toLocalShortDateTime,
12739
13494
  Button,
12740
13495
  buildWorkspaceAgentSessionDetailViewModel,
12741
13496
  projectAgentConversationVM,
@@ -12755,4 +13510,4 @@ export {
12755
13510
  AgentConversationFlow,
12756
13511
  useProjectedAgentConversation
12757
13512
  };
12758
- //# sourceMappingURL=chunk-CZ7PS3DU.js.map
13513
+ //# sourceMappingURL=chunk-ZAF4IVUT.js.map