@markusylisiurunen/tau 0.3.67 → 0.3.69

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 (107) hide show
  1. package/dist/core/personas.js +47 -3
  2. package/dist/core/personas.js.map +1 -1
  3. package/dist/core/static/tau_docs/remote-sessions.md +1 -1
  4. package/dist/core/static/tau_docs/telegram.md +3 -3
  5. package/dist/core/static/tau_docs/tui.md +2 -2
  6. package/dist/core/telegram/adapter.js +26 -10
  7. package/dist/core/telegram/adapter.js.map +1 -1
  8. package/dist/core/utils/gemini_speech.js +1 -1
  9. package/dist/core/utils/gemini_transcription.js +1 -1
  10. package/dist/core/utils/openai_transcription.js +72 -22
  11. package/dist/core/utils/openai_transcription.js.map +1 -1
  12. package/dist/core/utils/speech_to_text.js +17 -24
  13. package/dist/core/utils/speech_to_text.js.map +1 -1
  14. package/dist/core/version.js +2 -2
  15. package/dist/diff_tool/app/dist/assets/{angular-html-BeSCPCet.js → angular-html-GYtPR4ZN.js} +1 -1
  16. package/dist/diff_tool/app/dist/assets/{angular-ts-M6ucwcXp.js → angular-ts-CP8tBFCL.js} +1 -1
  17. package/dist/diff_tool/app/dist/assets/{apl-BTy2T_s_.js → apl-DlYjD5SW.js} +1 -1
  18. package/dist/diff_tool/app/dist/assets/{astro-cFE44UBz.js → astro-D8wjxID2.js} +1 -1
  19. package/dist/diff_tool/app/dist/assets/{blade-BSzl0tbR.js → blade-DwY1HngW.js} +1 -1
  20. package/dist/diff_tool/app/dist/assets/{c-wBFdKaz6.js → c-CAILAMk6.js} +1 -1
  21. package/dist/diff_tool/app/dist/assets/{chapel-b-MisDxs.js → chapel-VXP46g0K.js} +1 -1
  22. package/dist/diff_tool/app/dist/assets/{cobol-dLRFXfHq.js → cobol-BwAQKs_L.js} +1 -1
  23. package/dist/diff_tool/app/dist/assets/{coffee-DY5l5u2h.js → coffee-DeNZ8VRy.js} +1 -1
  24. package/dist/diff_tool/app/dist/assets/{cpp-DjXgSTII.js → cpp-CF9ADHZh.js} +1 -1
  25. package/dist/diff_tool/app/dist/assets/{crystal-CP6xOayN.js → crystal-Dlr9u7o6.js} +1 -1
  26. package/dist/diff_tool/app/dist/assets/{css-xaNrTKYV.js → css-DqAHzxWB.js} +1 -1
  27. package/dist/diff_tool/app/dist/assets/{edge-BOmlY556.js → edge-ds7vvsur.js} +1 -1
  28. package/dist/diff_tool/app/dist/assets/{elixir-DcKGE0v2.js → elixir-Es7Jrt6x.js} +1 -1
  29. package/dist/diff_tool/app/dist/assets/{elm-Cr3UN5CN.js → elm-D11ntXWx.js} +1 -1
  30. package/dist/diff_tool/app/dist/assets/{erb-B0s1W7oL.js → erb-BqfIFYyn.js} +1 -1
  31. package/dist/diff_tool/app/dist/assets/{git-rebase-BicxzMm4.js → git-rebase-BNJHcUvp.js} +1 -1
  32. package/dist/diff_tool/app/dist/assets/{glimmer-js-ODVU1BSP.js → glimmer-js-DNpIS5IN.js} +1 -1
  33. package/dist/diff_tool/app/dist/assets/{glimmer-ts-BjlaPEbT.js → glimmer-ts-Bv4OLkK5.js} +1 -1
  34. package/dist/diff_tool/app/dist/assets/{glsl-DARqrBAL.js → glsl-BrwGmSBp.js} +1 -1
  35. package/dist/diff_tool/app/dist/assets/{graphql-9AgTO0pa.js → graphql-BNCK5rQk.js} +1 -1
  36. package/dist/diff_tool/app/dist/assets/{hack-a62s-UZ0.js → hack-DuFQc5W0.js} +1 -1
  37. package/dist/diff_tool/app/dist/assets/{haml-D2r7a_kb.js → haml-DfSIBmXD.js} +1 -1
  38. package/dist/diff_tool/app/dist/assets/{handlebars-CIj0uB_t.js → handlebars-97zL-LWI.js} +1 -1
  39. package/dist/diff_tool/app/dist/assets/{html-D8D8vyLW.js → html-5cQu52Yo.js} +1 -1
  40. package/dist/diff_tool/app/dist/assets/{html-derivative-CLdYoNWv.js → html-derivative-BtDKJ__D.js} +1 -1
  41. package/dist/diff_tool/app/dist/assets/{http-BHeQHXaH.js → http-DDVZeUrK.js} +1 -1
  42. package/dist/diff_tool/app/dist/assets/{hurl-CHiE6F8G.js → hurl-jYWcmpTH.js} +1 -1
  43. package/dist/diff_tool/app/dist/assets/index-B1D38aHD.js +1559 -0
  44. package/dist/diff_tool/app/dist/assets/{java-vZct0aFW.js → java-DztsnWnX.js} +1 -1
  45. package/dist/diff_tool/app/dist/assets/{javascript-CYtnIw0K.js → javascript-B9237BHu.js} +1 -1
  46. package/dist/diff_tool/app/dist/assets/{jinja-CLfmA9uq.js → jinja-8JSK8Y6a.js} +1 -1
  47. package/dist/diff_tool/app/dist/assets/{jison-XTtTo0I_.js → jison-B5IknAGj.js} +1 -1
  48. package/dist/diff_tool/app/dist/assets/{json-GZxy-acy.js → json-DRnIZCq8.js} +1 -1
  49. package/dist/diff_tool/app/dist/assets/{jsx-C7hyhPZ0.js → jsx-CFiWn8fZ.js} +1 -1
  50. package/dist/diff_tool/app/dist/assets/{julia-BmMkiSmK.js → julia-BtFBdMTG.js} +1 -1
  51. package/dist/diff_tool/app/dist/assets/{just-RxJWAQxh.js → just-C14ODGGY.js} +1 -1
  52. package/dist/diff_tool/app/dist/assets/{latex-poGrpwNW.js → latex-DeaaLUFy.js} +1 -1
  53. package/dist/diff_tool/app/dist/assets/{liquid-Bi2qx1OK.js → liquid-HEBlYFrk.js} +1 -1
  54. package/dist/diff_tool/app/dist/assets/{lua-CKOivslC.js → lua-CR87cBpR.js} +1 -1
  55. package/dist/diff_tool/app/dist/assets/{marko-B71GrBHJ.js → marko-Eh01TuqB.js} +1 -1
  56. package/dist/diff_tool/app/dist/assets/{mdc-DFiDlus5.js → mdc-B6RhKbU2.js} +1 -1
  57. package/dist/diff_tool/app/dist/assets/{nginx-ByKHg69_.js → nginx-VWZdkDqr.js} +1 -1
  58. package/dist/diff_tool/app/dist/assets/{nim-BwiP7WMs.js → nim-CHz86e8d.js} +1 -1
  59. package/dist/diff_tool/app/dist/assets/{org-Cy_LFEd1.js → org-VhIfCSLb.js} +1 -1
  60. package/dist/diff_tool/app/dist/assets/{perl-L6AmMgUk.js → perl-aD0uyR3K.js} +1 -1
  61. package/dist/diff_tool/app/dist/assets/{php-r2zLMjgX.js → php-DgAIzA1E.js} +1 -1
  62. package/dist/diff_tool/app/dist/assets/{pug-BCKOpP4a.js → pug-DrOOTekJ.js} +1 -1
  63. package/dist/diff_tool/app/dist/assets/{qml-BGFEfuZ-.js → qml-CBowC-In.js} +1 -1
  64. package/dist/diff_tool/app/dist/assets/{r-25U_CdGU.js → r-BDVQ17do.js} +1 -1
  65. package/dist/diff_tool/app/dist/assets/{razor-SIQ_YLlC.js → razor-3Cui7XJA.js} +1 -1
  66. package/dist/diff_tool/app/dist/assets/{regexp-DCxK7MQQ.js → regexp-CGlnek7r.js} +1 -1
  67. package/dist/diff_tool/app/dist/assets/{rst-9_Rowq3o.js → rst-BJ8-NYKF.js} +1 -1
  68. package/dist/diff_tool/app/dist/assets/{ruby-O5wpMn8A.js → ruby-CYg2GUU3.js} +1 -1
  69. package/dist/diff_tool/app/dist/assets/{sas-C-k458kN.js → sas-DfcBvB-I.js} +1 -1
  70. package/dist/diff_tool/app/dist/assets/{scss-BwP9aXVf.js → scss-S7r2xNkU.js} +1 -1
  71. package/dist/diff_tool/app/dist/assets/{shellscript-BFMLRpod.js → shellscript-U4EOW3nV.js} +1 -1
  72. package/dist/diff_tool/app/dist/assets/{shellsession-DlRPuye4.js → shellsession-DYrNCgPk.js} +1 -1
  73. package/dist/diff_tool/app/dist/assets/{soy-C5Ch5nY5.js → soy-B25GTE01.js} +1 -1
  74. package/dist/diff_tool/app/dist/assets/{sql-BHt0XWX7.js → sql-CrqkYif6.js} +1 -1
  75. package/dist/diff_tool/app/dist/assets/{stata-CgvX5dGK.js → stata-CMX2LAnU.js} +1 -1
  76. package/dist/diff_tool/app/dist/assets/{surrealql-Cj3grwXO.js → surrealql-DDFKL5G0.js} +1 -1
  77. package/dist/diff_tool/app/dist/assets/{svelte-DveFys-B.js → svelte-CLZCBtO7.js} +1 -1
  78. package/dist/diff_tool/app/dist/assets/{templ-DmoziF6W.js → templ-BKLVAgyX.js} +1 -1
  79. package/dist/diff_tool/app/dist/assets/{tex-CQEH3eWy.js → tex-DbqBQGS7.js} +1 -1
  80. package/dist/diff_tool/app/dist/assets/{ts-tags-CyATrp4W.js → ts-tags-B9kA6WQP.js} +1 -1
  81. package/dist/diff_tool/app/dist/assets/{tsx-SmI3BYI2.js → tsx-CxQG0405.js} +1 -1
  82. package/dist/diff_tool/app/dist/assets/{twig-CZdHY8II.js → twig-0z1Xause.js} +1 -1
  83. package/dist/diff_tool/app/dist/assets/{typescript-BEXbDl0M.js → typescript-R3tpeET4.js} +1 -1
  84. package/dist/diff_tool/app/dist/assets/{typst-DVVDEOeR.js → typst-DiHSix3p.js} +1 -1
  85. package/dist/diff_tool/app/dist/assets/{vue-cLMg-8kU.js → vue-C-Pb_COm.js} +1 -1
  86. package/dist/diff_tool/app/dist/assets/{vue-html-DsBku6bW.js → vue-html-CA18u2Ne.js} +1 -1
  87. package/dist/diff_tool/app/dist/assets/{vue-vine-CJ-uTo5p.js → vue-vine-CMnXmPqz.js} +1 -1
  88. package/dist/diff_tool/app/dist/assets/{xml-DFarLFG4.js → xml-5qEjbdVg.js} +1 -1
  89. package/dist/diff_tool/app/dist/assets/{xsl-C-KcDKTd.js → xsl-CeJj5xD1.js} +1 -1
  90. package/dist/diff_tool/app/dist/assets/{yaml-C-7s0ZGy.js → yaml-C2k4Cqy8.js} +1 -1
  91. package/dist/diff_tool/app/dist/index.html +1 -1
  92. package/dist/host/hosted_ephemeral_agent_session.js +18 -4
  93. package/dist/host/hosted_ephemeral_agent_session.js.map +1 -1
  94. package/dist/host/local_session_host.js +54 -7
  95. package/dist/host/local_session_host.js.map +1 -1
  96. package/dist/host/session_protocol_handler.js +29 -2
  97. package/dist/host/session_protocol_handler.js.map +1 -1
  98. package/dist/protocol/session_protocol.js +22 -5
  99. package/dist/protocol/session_protocol.js.map +1 -1
  100. package/dist/tui/listen_capture.js +0 -1
  101. package/dist/tui/listen_capture.js.map +1 -1
  102. package/dist/tui/session_chat_controller.js +255 -39
  103. package/dist/tui/session_chat_controller.js.map +1 -1
  104. package/dist/tui/ui/footer.js +1 -1
  105. package/dist/tui/ui/footer.js.map +1 -1
  106. package/package.json +9 -9
  107. package/dist/diff_tool/app/dist/assets/index-ryW0IvBs.js +0 -1565
@@ -10,7 +10,7 @@ import { TOOL_NAME_BASH } from "../core/tools/tool_names.js";
10
10
  import { REASONING_LEVELS } from "../core/types.js";
11
11
  import { formatAdaptiveNumber, formatCwd, formatTokenWindow } from "../core/utils/format.js";
12
12
  import { extractAssistantText } from "../core/utils/messages.js";
13
- import { getSpeechToTextStreamingSampleRate, } from "../core/utils/speech_to_text.js";
13
+ import { getSpeechToTextStreamingSampleRate, SPEECH_TO_TEXT_CLIENT_MAX_DURATION_MS, } from "../core/utils/speech_to_text.js";
14
14
  import { collectSpeechToTextContext } from "../core/utils/speech_to_text_context.js";
15
15
  import { hasAutoCompactionContinuationMetadata } from "../core/utils/user_metadata.js";
16
16
  import { APP_VERSION } from "../core/version.js";
@@ -25,7 +25,7 @@ import { InterruptLifecycle } from "./chat_controller/interrupt_lifecycle.js";
25
25
  import { formatDurationMs, formatFooterTokenCount, formatSessionCost, } from "./chat_controller/status_format.js";
26
26
  import { copyTextToClipboard } from "./clipboard.js";
27
27
  import { DOUBLE_PRESS_WINDOW_MS } from "./constants.js";
28
- import { cleanupListenTempFile, createListenTempFilePath, createListenTranscription, deleteListenTempFile, getSpeechToTextApiKey, getSpeechToTextApiKeyErrorMessage, getSpeechToTextProvider, LISTEN_CAPTURE_START_TIMEOUT_MS, LISTEN_RECORDING_MAX_DURATION_MS, LISTEN_RECORDING_MIN_BYTES, readListenAudio, startListenAudioCapture, } from "./listen_capture.js";
28
+ import { cleanupListenTempFile, createListenTempFilePath, createListenTranscription, deleteListenTempFile, getSpeechToTextApiKey, getSpeechToTextApiKeyErrorMessage, getSpeechToTextProvider, LISTEN_CAPTURE_START_TIMEOUT_MS, LISTEN_RECORDING_MIN_BYTES, readListenAudio, startListenAudioCapture, } from "./listen_capture.js";
29
29
  import { createSdkDiffSnapshotDeps, createSdkToolExecutionBackend, } from "./session_tool_execution_backend.js";
30
30
  import { runSpeechPlaybackTask } from "./speech_playback.js";
31
31
  const LISTEN_CAPTURE_START_CANCELLED = Symbol("listen capture start cancelled");
@@ -76,7 +76,7 @@ export class SessionChatController {
76
76
  turnTimer;
77
77
  lastEmptySubmitAt;
78
78
  listenRecording;
79
- retainedListenAudioPath;
79
+ retainedListenAudio;
80
80
  listenTransition;
81
81
  listenStartupAbortController;
82
82
  activeListenTranscription;
@@ -641,7 +641,7 @@ export class SessionChatController {
641
641
  ]);
642
642
  return;
643
643
  }
644
- const retainedAudioPath = this.retainedListenAudioPath;
644
+ const retainedAudio = this.retainedListenAudio;
645
645
  let audioPath;
646
646
  let transcription;
647
647
  let abortController;
@@ -666,11 +666,11 @@ export class SessionChatController {
666
666
  if (this.listenStartupAbortController === abortController) {
667
667
  this.listenStartupAbortController = undefined;
668
668
  }
669
- if (retainedAudioPath) {
669
+ if (retainedAudio) {
670
670
  try {
671
- await deleteListenTempFile(retainedAudioPath);
672
- if (this.retainedListenAudioPath === retainedAudioPath) {
673
- this.retainedListenAudioPath = undefined;
671
+ await deleteListenTempFile(retainedAudio.audioPath);
672
+ if (this.retainedListenAudio === retainedAudio) {
673
+ this.retainedListenAudio = undefined;
674
674
  }
675
675
  }
676
676
  catch (error) {
@@ -680,13 +680,14 @@ export class SessionChatController {
680
680
  await cleanupListenTempFile(audioPath);
681
681
  this.view.addTranscriptNotice("failed to replace retained recording", "error", [
682
682
  error.message,
683
- `recording retained at ${retainedAudioPath}`,
683
+ `recording retained at ${retainedAudio.audioPath}`,
684
684
  ]);
685
685
  return;
686
686
  }
687
687
  }
688
688
  const recording = {
689
689
  audioPath,
690
+ startedAt: Date.now(),
690
691
  stopRequested: false,
691
692
  abortController,
692
693
  completion,
@@ -696,7 +697,7 @@ export class SessionChatController {
696
697
  if (this.listenRecording !== recording || this.listenTransition)
697
698
  return;
698
699
  void this.runListenTransition(() => this.stopListenCapture());
699
- }, LISTEN_RECORDING_MAX_DURATION_MS);
700
+ }, SPEECH_TO_TEXT_CLIENT_MAX_DURATION_MS);
700
701
  this.listenRecording = recording;
701
702
  this.view.setEditorInputEnabled(false);
702
703
  this.refreshStatus();
@@ -761,39 +762,42 @@ export class SessionChatController {
761
762
  await cleanupListenTempFile(recording.audioPath);
762
763
  return;
763
764
  }
764
- await this.transcribeListenAudioFile(recording.audioPath, recording.transcription);
765
+ const durationMs = Math.min(Math.max(Date.now() - recording.startedAt, 0), SPEECH_TO_TEXT_CLIENT_MAX_DURATION_MS);
766
+ await this.transcribeListenAudioFile(recording.audioPath, durationMs, recording.transcription);
765
767
  }
766
768
  async retryRetainedListenAudio() {
767
769
  if (this.listenRecording || this.listenTransition || this.listenActivityLabel) {
768
770
  this.view.showFooterNotice("speech recording state change already in progress", "default");
769
771
  return;
770
772
  }
771
- const audioPath = this.retainedListenAudioPath;
772
- if (!audioPath) {
773
+ const retainedAudio = this.retainedListenAudio;
774
+ if (!retainedAudio) {
773
775
  this.view.showFooterNotice("no failed speech recording to retry", "default");
774
776
  return;
775
777
  }
776
- await this.runListenTransition(() => this.transcribeListenAudioFile(audioPath));
778
+ await this.runListenTransition(() => this.transcribeListenAudioFile(retainedAudio.audioPath, retainedAudio.durationMs));
777
779
  }
778
780
  async discardRetainedListenAudio() {
779
781
  if (this.listenRecording || this.listenTransition || this.listenActivityLabel) {
780
782
  this.view.showFooterNotice("speech recording state change already in progress", "default");
781
783
  return;
782
784
  }
783
- const audioPath = this.retainedListenAudioPath;
784
- if (!audioPath) {
785
+ const retainedAudio = this.retainedListenAudio;
786
+ if (!retainedAudio) {
785
787
  this.view.showFooterNotice("no failed speech recording to discard", "default");
786
788
  return;
787
789
  }
788
790
  try {
789
- await deleteListenTempFile(audioPath);
790
- this.retainedListenAudioPath = undefined;
791
+ await deleteListenTempFile(retainedAudio.audioPath);
792
+ if (this.retainedListenAudio === retainedAudio) {
793
+ this.retainedListenAudio = undefined;
794
+ }
791
795
  this.view.showFooterNotice("discarded retained speech recording", "default");
792
796
  }
793
797
  catch (error) {
794
798
  this.view.addTranscriptNotice("failed to discard speech recording", "error", [
795
799
  error.message,
796
- `recording retained at ${audioPath}`,
800
+ `recording retained at ${retainedAudio.audioPath}`,
797
801
  ]);
798
802
  }
799
803
  }
@@ -806,7 +810,17 @@ export class SessionChatController {
806
810
  speechToTextDeps: this.speechToTextDeps,
807
811
  });
808
812
  }
809
- async transcribeListenAudioFile(audioPath, transcription) {
813
+ async transcribeListenAudioFile(audioPath, durationMs, transcription) {
814
+ if (durationMs > SPEECH_TO_TEXT_CLIENT_MAX_DURATION_MS) {
815
+ transcription?.abort();
816
+ this.retainedListenAudio = { audioPath, durationMs };
817
+ this.view.addTranscriptNotice("failed to transcribe speech", "error", [
818
+ "audio is longer than 20 minutes",
819
+ `recording retained at ${audioPath}`,
820
+ "run /listen discard to delete it",
821
+ ]);
822
+ return;
823
+ }
810
824
  let audio;
811
825
  try {
812
826
  audio = await readListenAudio(audioPath);
@@ -816,8 +830,8 @@ export class SessionChatController {
816
830
  this.view.addTranscriptNotice("failed to read speech recording", "error", [
817
831
  error.message,
818
832
  ]);
819
- if (this.retainedListenAudioPath === audioPath) {
820
- this.retainedListenAudioPath = undefined;
833
+ if (this.retainedListenAudio?.audioPath === audioPath) {
834
+ this.retainedListenAudio = undefined;
821
835
  }
822
836
  await cleanupListenTempFile(audioPath);
823
837
  return;
@@ -825,8 +839,8 @@ export class SessionChatController {
825
839
  if (audio.byteLength < LISTEN_RECORDING_MIN_BYTES) {
826
840
  transcription?.abort();
827
841
  this.view.showFooterNotice("recording too short, try again", "default");
828
- if (this.retainedListenAudioPath === audioPath) {
829
- this.retainedListenAudioPath = undefined;
842
+ if (this.retainedListenAudio?.audioPath === audioPath) {
843
+ this.retainedListenAudio = undefined;
830
844
  }
831
845
  await cleanupListenTempFile(audioPath);
832
846
  return;
@@ -842,7 +856,7 @@ export class SessionChatController {
842
856
  mimeType: "audio/wav",
843
857
  });
844
858
  this.view.insertEditorTextAtCursor(text);
845
- this.retainedListenAudioPath = undefined;
859
+ this.retainedListenAudio = undefined;
846
860
  try {
847
861
  await deleteListenTempFile(audioPath);
848
862
  }
@@ -854,7 +868,7 @@ export class SessionChatController {
854
868
  }
855
869
  }
856
870
  catch (error) {
857
- this.retainedListenAudioPath = audioPath;
871
+ this.retainedListenAudio = { audioPath, durationMs };
858
872
  this.view.addTranscriptNotice("failed to transcribe speech", "error", [
859
873
  error.message,
860
874
  `recording retained at ${audioPath}`,
@@ -1130,10 +1144,13 @@ export class SessionChatController {
1130
1144
  return false;
1131
1145
  }
1132
1146
  try {
1147
+ if (this.tryApplyFastSnapshotStateDelta(delta)) {
1148
+ return true;
1149
+ }
1133
1150
  if (this.tryApplyFastContentAppendDelta(delta)) {
1134
1151
  return true;
1135
1152
  }
1136
- if (this.tryApplyFastToolUiDelta(delta)) {
1153
+ if (this.tryApplyFastTimelineDelta(delta)) {
1137
1154
  return true;
1138
1155
  }
1139
1156
  if (this.tryApplyFastAgentDelta(delta)) {
@@ -1166,6 +1183,33 @@ export class SessionChatController {
1166
1183
  }
1167
1184
  return true;
1168
1185
  }
1186
+ tryApplyFastSnapshotStateDelta(delta) {
1187
+ if (delta.delta.type !== "snapshot.patch" ||
1188
+ delta.delta.changes.length === 0 ||
1189
+ delta.delta.changes.some((change) => {
1190
+ switch (change.type) {
1191
+ case "agent-state.set":
1192
+ case "lifecycle.set":
1193
+ case "goal.set":
1194
+ case "cost.set":
1195
+ case "settings.set":
1196
+ case "turn.set":
1197
+ case "timeline.advance":
1198
+ case "operation.set":
1199
+ case "operation.remove":
1200
+ return false;
1201
+ default:
1202
+ return true;
1203
+ }
1204
+ })) {
1205
+ return false;
1206
+ }
1207
+ this.snapshot = applySessionProtocolDelta(this.snapshot, delta);
1208
+ this.observedSessionRevision = Math.max(this.observedSessionRevision, this.snapshot.revision);
1209
+ this.updateStreamingStateFromSnapshot(this.snapshot);
1210
+ this.refreshStatus();
1211
+ return true;
1212
+ }
1169
1213
  tryApplyFastContentAppendDelta(delta) {
1170
1214
  if (delta.delta.type !== "snapshot.patch" || delta.delta.changes.length !== 1) {
1171
1215
  return false;
@@ -1202,6 +1246,128 @@ export class SessionChatController {
1202
1246
  }
1203
1247
  return true;
1204
1248
  }
1249
+ tryApplyFastTimelineDelta(delta) {
1250
+ if (delta.delta.type !== "snapshot.patch" || delta.delta.changes.length === 0) {
1251
+ return false;
1252
+ }
1253
+ const previousSnapshot = this.snapshot;
1254
+ const affectedMessageIds = new Set();
1255
+ const affectedToolIds = new Set();
1256
+ const appendedNoticeIds = new Set();
1257
+ let hasTimelineChange = false;
1258
+ for (const change of delta.delta.changes) {
1259
+ switch (change.type) {
1260
+ case "agent-state.set":
1261
+ case "lifecycle.set":
1262
+ case "goal.set":
1263
+ case "cost.set":
1264
+ case "settings.set":
1265
+ case "turn.set":
1266
+ case "timeline.advance":
1267
+ case "operation.set":
1268
+ case "operation.remove":
1269
+ break;
1270
+ case "message.append":
1271
+ case "message.replace":
1272
+ hasTimelineChange = true;
1273
+ affectedMessageIds.add(change.message.id);
1274
+ break;
1275
+ case "message.content.append":
1276
+ hasTimelineChange = true;
1277
+ affectedMessageIds.add(change.messageId);
1278
+ break;
1279
+ case "timeline.append":
1280
+ hasTimelineChange = true;
1281
+ if (change.item.type === "message") {
1282
+ affectedMessageIds.add(change.item.messageId);
1283
+ }
1284
+ else if (change.item.type === "tool") {
1285
+ affectedToolIds.add(change.item.toolId);
1286
+ }
1287
+ else if (change.item.type === "notice") {
1288
+ appendedNoticeIds.add(change.item.id);
1289
+ }
1290
+ break;
1291
+ case "tool.set":
1292
+ hasTimelineChange = true;
1293
+ affectedToolIds.add(change.tool.id);
1294
+ break;
1295
+ case "facet.set":
1296
+ if (change.facet.kind !== "tau.tool-ui-events" || change.facet.subject.type !== "tool") {
1297
+ return false;
1298
+ }
1299
+ hasTimelineChange = true;
1300
+ affectedToolIds.add(change.facet.subject.id);
1301
+ break;
1302
+ default:
1303
+ return false;
1304
+ }
1305
+ }
1306
+ if (!hasTimelineChange) {
1307
+ return false;
1308
+ }
1309
+ const nextSnapshot = applySessionProtocolDelta(previousSnapshot, delta);
1310
+ this.snapshot = nextSnapshot;
1311
+ this.observedSessionRevision = Math.max(this.observedSessionRevision, nextSnapshot.revision);
1312
+ const remainingMessageIds = new Set(affectedMessageIds);
1313
+ const affectedMessages = new Map();
1314
+ for (const messageId of affectedMessageIds) {
1315
+ const message = nextSnapshot.messages.find((entry) => entry.id === messageId);
1316
+ if (message) {
1317
+ affectedMessages.set(messageId, message);
1318
+ }
1319
+ }
1320
+ const timelineItems = [...nextSnapshot.timeline.items, ...this.ephemeralTimelineItems.values()]
1321
+ .filter((item) => {
1322
+ if (item.type === "message") {
1323
+ return affectedMessageIds.has(item.messageId);
1324
+ }
1325
+ if (item.type === "tool") {
1326
+ return affectedToolIds.has(item.toolId);
1327
+ }
1328
+ return item.type === "notice" && appendedNoticeIds.has(item.id);
1329
+ })
1330
+ .sort((left, right) => left.sequence - right.sequence);
1331
+ for (const item of timelineItems) {
1332
+ if (item.type === "message" && remainingMessageIds.delete(item.messageId)) {
1333
+ const message = affectedMessages.get(item.messageId);
1334
+ if (message) {
1335
+ this.syncProtocolMessage(message);
1336
+ }
1337
+ }
1338
+ else if (item.type === "tool" && affectedToolIds.has(item.toolId)) {
1339
+ const tool = nextSnapshot.tools[item.toolId];
1340
+ if (tool) {
1341
+ this.upsertRenderedMessage(item.id, {
1342
+ type: "tool",
1343
+ tool: buildToolUiModel(nextSnapshot, tool),
1344
+ });
1345
+ }
1346
+ }
1347
+ else if (item.type === "notice" && appendedNoticeIds.has(item.id)) {
1348
+ this.upsertRenderedMessage(item.id, {
1349
+ type: "transcript_notice",
1350
+ title: item.notice.presentation.title,
1351
+ ...(item.notice.presentation.content
1352
+ ? { content: item.notice.presentation.content }
1353
+ : {}),
1354
+ tone: item.notice.severity === "error" ? "error" : "default",
1355
+ });
1356
+ }
1357
+ }
1358
+ for (const messageId of remainingMessageIds) {
1359
+ const message = affectedMessages.get(messageId);
1360
+ if (message) {
1361
+ this.syncProtocolMessage(message);
1362
+ }
1363
+ else {
1364
+ this.removeProtocolMessage(messageId);
1365
+ }
1366
+ }
1367
+ this.updateStreamingStateFromSnapshot(nextSnapshot);
1368
+ this.refreshStatus();
1369
+ return true;
1370
+ }
1205
1371
  tryApplyFastAgentDelta(delta) {
1206
1372
  if (delta.delta.type !== "snapshot.patch") {
1207
1373
  return false;
@@ -1221,20 +1387,70 @@ export class SessionChatController {
1221
1387
  this.refreshStatus();
1222
1388
  return true;
1223
1389
  }
1224
- tryApplyFastToolUiDelta(delta) {
1225
- if (delta.delta.type !== "snapshot.patch" ||
1226
- delta.delta.changes.length === 0 ||
1227
- delta.delta.changes.some((change) => change.type !== "tool.set" && change.type !== "facet.set")) {
1228
- return false;
1390
+ syncProtocolMessage(message) {
1391
+ if (this.hiddenHistoryEntryIds.has(message.id) || !this.isMessageProjected(message.id)) {
1392
+ this.removeProtocolMessage(message.id);
1393
+ return;
1229
1394
  }
1230
- const facetChanges = delta.delta.changes.filter((change) => change.type === "facet.set");
1231
- if (facetChanges.some(({ facet }) => facet.kind !== "tau.tool-ui-events" || facet.subject.type !== "tool")) {
1232
- return false;
1395
+ const model = this.buildProtocolMessageModel(message);
1396
+ if (model) {
1397
+ this.upsertRenderedMessage(message.id, model);
1233
1398
  }
1234
- const nextSnapshot = applySessionProtocolDelta(this.snapshot, delta);
1235
- this.syncRenderedHistory(nextSnapshot);
1236
- this.refreshStatus();
1237
- return true;
1399
+ else {
1400
+ this.removeRenderedProtocolItems([message.id]);
1401
+ }
1402
+ const outcomeNotice = getMessageOutcomeNotice(message);
1403
+ const outcomeNoticeId = `presentation:failure:${message.id}`;
1404
+ const interruptionNoticeId = `presentation:interruption:${message.id}`;
1405
+ if (outcomeNotice) {
1406
+ this.upsertRenderedMessage(outcomeNotice.id, {
1407
+ type: "transcript_notice",
1408
+ title: outcomeNotice.title,
1409
+ ...(outcomeNotice.content ? { content: outcomeNotice.content } : {}),
1410
+ tone: outcomeNotice.severity === "error" ? "error" : "default",
1411
+ });
1412
+ }
1413
+ if (outcomeNotice?.id !== outcomeNoticeId) {
1414
+ this.removeRenderedProtocolItems([outcomeNoticeId]);
1415
+ }
1416
+ if (outcomeNotice?.id !== interruptionNoticeId) {
1417
+ this.removeRenderedProtocolItems([interruptionNoticeId]);
1418
+ }
1419
+ }
1420
+ removeProtocolMessage(messageId) {
1421
+ this.removeRenderedProtocolItems([
1422
+ messageId,
1423
+ `presentation:failure:${messageId}`,
1424
+ `presentation:interruption:${messageId}`,
1425
+ ]);
1426
+ }
1427
+ upsertRenderedMessage(protocolId, model) {
1428
+ const viewMessageId = this.getViewMessageId(protocolId);
1429
+ if (this.renderedMessageIds.includes(viewMessageId)) {
1430
+ if (model.type === "assistant" || model.type === "assistant_partial") {
1431
+ this.view.updateAssistantMessage(viewMessageId, model);
1432
+ }
1433
+ else {
1434
+ this.view.updateMessage(viewMessageId, model);
1435
+ }
1436
+ return;
1437
+ }
1438
+ this.renderedMessageIds.push(this.view.addMessage(model, viewMessageId));
1439
+ }
1440
+ removeRenderedProtocolItems(protocolIds) {
1441
+ this.removeRenderedMessages(protocolIds.flatMap((id) => {
1442
+ const viewMessageId = this.viewMessageIds.get(id);
1443
+ return viewMessageId ? [viewMessageId] : [];
1444
+ }));
1445
+ }
1446
+ removeRenderedMessages(ids) {
1447
+ const idsToRemove = ids.filter((id) => this.renderedMessageIds.includes(id));
1448
+ if (idsToRemove.length === 0) {
1449
+ return;
1450
+ }
1451
+ const idSet = new Set(idsToRemove);
1452
+ this.renderedMessageIds.splice(0, this.renderedMessageIds.length, ...this.renderedMessageIds.filter((id) => !idSet.has(id)));
1453
+ this.view.removeMessages(idsToRemove);
1238
1454
  }
1239
1455
  getViewMessageId(protocolId) {
1240
1456
  const existingId = this.viewMessageIds.get(protocolId);